[jvm] KTIJ-22044 Improve Kotlin suspending function error message

GitOrigin-RevId: bde403e6f3fe15712426b4394651160bd5494a09
This commit is contained in:
Bart van Helvert
2022-06-22 12:27:25 +02:00
committed by intellij-monorepo-bot
parent 13ad5f166a
commit f56c9230cb
5 changed files with 53 additions and 4 deletions

View File

@@ -39,6 +39,9 @@ The following problems are reported by this inspection:
</li>
</ul>
Note that in Kotlin, suspending functions do have arguments and a non-void return type. Therefore, they also will not be executed by the
JUnit test runner. This inspection will also report about this problem.
<p><b>Malformed <code>@Before</code> method example (Java):</b></p>
<pre><code>@Before private int foo(int arg) { ... } </code></pre>
<p>After the quick-fix is applied:</p>

View File

@@ -130,9 +130,11 @@ jvm.inspections.junit.malformed.annotated.method.single.param.double.descriptor=
jvm.inspections.junit.malformed.annotated.method.double.param.double.descriptor=Method <code>#ref</code> annotated with ''@{0}'' should be {1}, {2} and not declare parameters ''{3}'' and ''{4}''
jvm.inspections.junit.malformed.annotated.method.single.typed.param.double.descriptor=Method <code>#ref</code> annotated with ''@{0}'' should be {1}, of type ''{2}'' and not declare parameters {3} and ''{4}''
jvm.inspections.junit.malformed.annotated.method.double.typed.param.double.descriptor=Method <code>#ref</code> annotated with ''@{0}'' should be {1}, {2}, of type ''{3}'' and not declare parameters {4} and ''{5}''
jvm.inspections.junit.malformed.annotated.suspend.function.descriptor=Method <code>#ref</code> annotated with ''@{0}'' should not be a suspending function
jvm.inspections.junit.malformed.suspend.function.descriptor=Method <code>#ref</code> should not be a suspending function
jvm.inspections.junit.malformed.test.combination.descriptor=Suspicious combination of {0} and ''@{1}''
jvm.inspections.junit.malformed.repetition.number.descriptor=The number of repetitions must be greater than zero
jvm.inspections.junit.malformed.nested.class.descriptor=Only non-static nested classes can serve as '@Nested' test classes.
jvm.inspections.junit.malformed.nested.class.descriptor=Only non-static nested classes can serve as '@Nested' test classes
jvm.inspections.junit.malformed.extension.registration.descriptor=''{0}'' should implement ''{1}''
jvm.inspections.junit.malformed.extension.class.level.descriptor={0} should be registered at the class level
jvm.inspections.junit.malformed.param.method.source.unresolved.descriptor=Cannot resolve target method source: ''{0}''

View File

@@ -11,6 +11,7 @@ import com.intellij.codeInspection.*
import com.intellij.codeInspection.test.junit.references.MethodSourceReference
import com.intellij.codeInspection.util.InspectionMessage
import com.intellij.codeInspection.util.SpecialAnnotationsUtil
import com.intellij.lang.Language
import com.intellij.lang.jvm.JvmMethod
import com.intellij.lang.jvm.JvmModifier
import com.intellij.lang.jvm.JvmModifiersOwner
@@ -240,6 +241,14 @@ private class JUnitMalformedSignatureVisitor(
param.javaPsi?.castSafelyTo<PsiParameter>()?.let { AnnotationUtil.isAnnotated(it, ignorableAnnotations, 0) } == true
}
private fun checkSuspendFunction(method: UMethod): Boolean {
return if (method.lang == Language.findLanguageByID("kotlin") && method.javaPsi.modifierList.text.contains("suspend")) {
val message = JvmAnalysisBundle.message("jvm.inspections.junit.malformed.suspend.function.descriptor")
holder.registerUProblem(method, message)
true
} else false
}
private fun checkJUnit3Test(method: UMethod) {
val sourcePsi = method.sourcePsi ?: return
val alternatives = UastFacade.convertToAlternatives(sourcePsi, arrayOf(UMethod::class.java))
@@ -248,8 +257,9 @@ private class JUnitMalformedSignatureVisitor(
if (!TestUtils.isJUnit3TestMethod(javaMethod.javaPsi)) return
val containingClass = method.javaPsi.containingClass ?: return
if (AnnotationUtil.isAnnotated(containingClass, TestUtils.RUN_WITH, AnnotationUtil.CHECK_HIERARCHY)) return
val message = JvmAnalysisBundle.message("jvm.inspections.junit.malformed.method.no.arg.void.descriptor", "public", "non-static")
if (checkSuspendFunction(method)) return
if (PsiType.VOID != method.returnType || method.visibility != UastVisibility.PUBLIC || javaMethod.isStatic || !method.isNoArg()) {
val message = JvmAnalysisBundle.message("jvm.inspections.junit.malformed.method.no.arg.void.descriptor", "public", "non-static")
return holder.registerUProblem(method, message, MethodSignatureQuickfix(method.name, false, newVisibility = JvmModifier.PUBLIC))
}
}
@@ -258,6 +268,7 @@ private class JUnitMalformedSignatureVisitor(
if ("setUp" != method.name && "tearDown" != method.name) return
if (!InheritanceUtil.isInheritor(method.javaPsi.containingClass, JUNIT_FRAMEWORK_TEST_CASE)) return
val sourcePsi = method.sourcePsi ?: return
if (checkSuspendFunction(method)) return
val alternatives = UastFacade.convertToAlternatives(sourcePsi, arrayOf(UMethod::class.java))
val javaMethod = alternatives.firstOrNull { it.isStatic } ?: alternatives.firstOrNull() ?: return
if (PsiType.VOID != method.returnType || method.visibility == UastVisibility.PRIVATE || javaMethod.isStatic || !method.isNoArg()) {
@@ -273,6 +284,7 @@ private class JUnitMalformedSignatureVisitor(
if ("suite" != method.name) return
if (!InheritanceUtil.isInheritor(method.javaPsi.containingClass, JUNIT_FRAMEWORK_TEST_CASE)) return
val sourcePsi = method.sourcePsi ?: return
if (checkSuspendFunction(method)) return
val alternatives = UastFacade.convertToAlternatives(sourcePsi, arrayOf(UMethod::class.java))
val javaMethod = alternatives.firstOrNull { it.isStatic } ?: alternatives.firstOrNull() ?: return
if (method.visibility == UastVisibility.PRIVATE || !javaMethod.isStatic || !method.isNoArg()) {
@@ -796,6 +808,13 @@ private class JUnitMalformedSignatureVisitor(
val problems = modifierProblems(
visibility, element.visibility, elementIsStatic, javaPsi.containingClass?.let { cls -> TestUtils.testInstancePerClass(cls) } == true
)
if (element.lang == Language.findLanguageByID("kotlin") && element.javaPsi.modifierList.text.contains("suspend")) {
val message = JvmAnalysisBundle.message(
"jvm.inspections.junit.malformed.annotated.suspend.function.descriptor",
annotation.substringAfterLast('.')
)
return holder.registerUProblem(element, message)
}
if (params != null && params.size != element.uastParameters.size) {
if (shouldBeVoidType == true && element.returnType != PsiType.VOID) {
return holder.methodParameterTypeProblem(element, visibility, annotation, problems, PsiType.VOID.name, params)

View File

@@ -37,7 +37,7 @@ class JavaJUnitMalformedDeclarationInspectionTest : JUnitMalformedDeclarationIns
myFixture.testHighlighting(ULanguage.JAVA, """
class A {
@org.junit.jupiter.api.Nested
static class <warning descr="Only non-static nested classes can serve as '@Nested' test classes.">B</warning> { }
static class <warning descr="Only non-static nested classes can serve as '@Nested' test classes">B</warning> { }
}
""".trimIndent())
}

View File

@@ -52,7 +52,7 @@ class KotlinJUnitMalformedDeclarationInspectionTest : JUnitMalformedDeclarationI
myFixture.testHighlighting(ULanguage.KOTLIN, """
class A {
@org.junit.jupiter.api.Nested
class <warning descr="Only non-static nested classes can serve as '@Nested' test classes.">B</warning> { }
class <warning descr="Only non-static nested classes can serve as '@Nested' test classes">B</warning> { }
}
""".trimIndent())
}
@@ -1086,4 +1086,29 @@ class KotlinJUnitMalformedDeclarationInspectionTest : JUnitMalformedDeclarationI
}
""".trimIndent(), "Fix 'suite' method signature")
}
/* Suspending test function */
fun `test malformed suspending test JUnit 3 function`() {
myFixture.testHighlighting(ULanguage.KOTLIN, """
class JUnit3Test : junit.framework.TestCase() {
suspend fun <warning descr="Method 'testFoo' should not be a suspending function">testFoo</warning>() { }
}
""".trimIndent())
}
fun `test malformed suspending test JUnit 4 function`() {
myFixture.testHighlighting(ULanguage.KOTLIN, """
class JUnit4Test {
@org.junit.Test
suspend fun <warning descr="Method 'testFoo' annotated with '@Test' should not be a suspending function">testFoo</warning>() { }
}
""".trimIndent())
}
fun `test malformed suspending test JUnit 5 function`() {
myFixture.testHighlighting(ULanguage.KOTLIN, """
class JUnit5Test {
@org.junit.jupiter.api.Test
suspend fun <warning descr="Method 'testFoo' annotated with '@Test' should not be a suspending function">testFoo</warning>() { }
}
""".trimIndent())
}
}