diff --git a/java/java-analysis-impl/java-analysis-impl.iml b/java/java-analysis-impl/java-analysis-impl.iml
index ebcda34a7da5..ac27777d1517 100644
--- a/java/java-analysis-impl/java-analysis-impl.iml
+++ b/java/java-analysis-impl/java-analysis-impl.iml
@@ -21,5 +21,6 @@
+
\ No newline at end of file
diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/TestUtils.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/TestUtils.java
index ce9cf552c73a..fe0585333b9a 100644
--- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/TestUtils.java
+++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/TestUtils.java
@@ -16,6 +16,7 @@
package com.siyeh.ig.psiutils;
import com.intellij.codeInsight.AnnotationUtil;
+import com.intellij.codeInsight.MetaAnnotationUtil;
import com.intellij.codeInsight.TestFrameworks;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.vfs.VirtualFile;
@@ -25,12 +26,14 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.testIntegration.TestFramework;
import com.intellij.util.ObjectUtils;
+import com.intellij.util.containers.hash.HashSet;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.junit.JUnitCommonClassNames;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.util.Collections;
import java.util.Set;
import static com.intellij.codeInsight.AnnotationUtil.CHECK_HIERARCHY;
@@ -148,13 +151,23 @@ public class TestUtils {
* @return true if class is annotated with {@code @TestInstance(TestInstance.Lifecycle.PER_CLASS)}
*/
public static boolean testInstancePerClass(@NotNull PsiClass containingClass) {
- PsiAnnotation annotation = AnnotationUtil.findAnnotation(containingClass, JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_TEST_INSTANCE);
+ return testInstancePerClass(containingClass, new HashSet<>());
+ }
+
+ private static boolean testInstancePerClass(@NotNull PsiClass containingClass, HashSet classes) {
+ PsiAnnotation annotation = MetaAnnotationUtil.findMetaAnnotations(containingClass, Collections.singletonList(JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_TEST_INSTANCE))
+ .findFirst().orElse(null);
if (annotation != null) {
PsiAnnotationMemberValue value = annotation.findDeclaredAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME);
if (value != null && value.getText().contains("PER_CLASS")) {
return true;
}
}
+ else {
+ for (PsiClass superClass : containingClass.getSupers()) {
+ if (classes.add(superClass) && testInstancePerClass(superClass, classes)) return true;
+ }
+ }
return false;
}
diff --git a/plugins/junit/src/com/intellij/execution/junit/codeInsight/JUnit5MalformedParameterizedInspection.kt b/plugins/junit/src/com/intellij/execution/junit/codeInsight/JUnit5MalformedParameterizedInspection.kt
index 4115ccf8b0d5..dc0e105af152 100644
--- a/plugins/junit/src/com/intellij/execution/junit/codeInsight/JUnit5MalformedParameterizedInspection.kt
+++ b/plugins/junit/src/com/intellij/execution/junit/codeInsight/JUnit5MalformedParameterizedInspection.kt
@@ -28,6 +28,7 @@ import com.intellij.psi.util.TypeConversionUtil
import com.intellij.util.containers.ContainerUtil
import com.siyeh.InspectionGadgetsBundle
import com.siyeh.ig.junit.JUnitCommonClassNames
+import com.siyeh.ig.psiutils.TestUtils
import org.jetbrains.annotations.Nls
import java.util.*
@@ -154,14 +155,16 @@ class JUnit5MalformedParameterizedInspection : AbstractBaseJavaLocalInspectionTo
processArrayInAnnotationParameter(annotationMemberValue, { attributeValue ->
for (reference in attributeValue.references) {
if (reference is MethodSourceReference) {
+ val containingClass = method.containingClass
val resolve = reference.resolve()
if (resolve !is PsiMethod) {
- val containingClass = method.containingClass
var createFix : CreateMethodQuickFix? = null
- if (containingClass != null && holder.isOnTheFly)
+ if (containingClass != null && holder.isOnTheFly) {
+ val staticModifier = if (!TestUtils.testInstancePerClass(containingClass)) " static" else "";
createFix = CreateMethodQuickFix.createFix(containingClass,
- "static Object[][] " + reference.value + "()",
+ "private$staticModifier Object[][] " + reference.value + "()",
"return new Object[][] {};")
+ }
holder.registerProblem(attributeValue,
"Cannot resolve target method source: \'" + reference.value + "\'",
createFix)
@@ -170,7 +173,8 @@ class JUnit5MalformedParameterizedInspection : AbstractBaseJavaLocalInspectionTo
val sourceProvider : PsiMethod = resolve
val providerName = sourceProvider.name
- if (!sourceProvider.hasModifierProperty(PsiModifier.STATIC)) {
+ if (!sourceProvider.hasModifierProperty(PsiModifier.STATIC) &&
+ containingClass != null && !TestUtils.testInstancePerClass(containingClass)) {
holder.registerProblem(attributeValue, "Method source \'$providerName\' must be static",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
QuickFixFactory.getInstance().createModifierListFix(sourceProvider, PsiModifier.STATIC, true, false))
@@ -248,12 +252,12 @@ class JUnit5MalformedParameterizedInspection : AbstractBaseJavaLocalInspectionTo
if (InheritanceUtil.isInheritor(returnType, CommonClassNames.JAVA_UTIL_STREAM_LONG_STREAM)) return PsiType.LONG
if (InheritanceUtil.isInheritor(returnType, CommonClassNames.JAVA_UTIL_STREAM_DOUBLE_STREAM)) return PsiType.DOUBLE
- val streamItemType = PsiUtil.substituteTypeParameter(returnType, CommonClassNames.JAVA_UTIL_STREAM_STREAM, 0, false)
+ val streamItemType = PsiUtil.substituteTypeParameter(returnType, CommonClassNames.JAVA_UTIL_STREAM_STREAM, 0, true)
if (streamItemType != null) {
return streamItemType
}
- return PsiUtil.substituteTypeParameter(returnType, CommonClassNames.JAVA_UTIL_ITERATOR, 0, false)
+ return PsiUtil.substituteTypeParameter(returnType, CommonClassNames.JAVA_UTIL_ITERATOR, 0, true)
}
}
}
diff --git a/plugins/junit/test/com/intellij/execution/junit/codeInsight/JUnit5MalformedParameterizedTest.java b/plugins/junit/test/com/intellij/execution/junit/codeInsight/JUnit5MalformedParameterizedTest.java
index a7dfcebd3cf6..94c2677b68ad 100644
--- a/plugins/junit/test/com/intellij/execution/junit/codeInsight/JUnit5MalformedParameterizedTest.java
+++ b/plugins/junit/test/com/intellij/execution/junit/codeInsight/JUnit5MalformedParameterizedTest.java
@@ -66,12 +66,18 @@ public class JUnit5MalformedParameterizedTest extends LightInspectionTestCase {
addEnvironmentClass("package org.junit.jupiter.params.provider;\n" +
"public @interface ArgumentsSource {}");
+
+ addEnvironmentClass("package org.junit.jupiter.api;\n" +
+ "public @interface TestInstance {\n" +
+ "enum Lifecycle {PER_CLASS, PER_METHOD;}\n" +
+ "Lifecycle value();}");
}
public void testMalformedSources() { doTest(); }
public void testMethodSource() { doTest(); }
public void testMalformedSourcesImplicitConversion() { doTest(); }
public void testMalformedSourcesImplicitParameters() { doTest(); }
+ public void testMalformedSourcesTestInstancePerClass() { doTest(); }
@Override
protected String getBasePath() {
diff --git a/plugins/junit/testData/codeInsight/malformedParameterized/MalformedSourcesTestInstancePerClass.java b/plugins/junit/testData/codeInsight/malformedParameterized/MalformedSourcesTestInstancePerClass.java
new file mode 100644
index 000000000000..56c5d08b6465
--- /dev/null
+++ b/plugins/junit/testData/codeInsight/malformedParameterized/MalformedSourcesTestInstancePerClass.java
@@ -0,0 +1,22 @@
+
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.api.TestInstance;
+
+@TestInstance( TestInstance.Lifecycle.PER_CLASS )
+@interface Meta{}
+
+@Meta
+abstract class AbstractTest {}
+class TestWithMethodSource extends AbstractTest {
+ @ParameterizedTest
+ @MethodSource("getParameters")
+ public void shouldExecuteWithParameterizedMethodSource(String arguments) { }
+
+ public Stream getParameters(){ //non static but that's expected due to PER_CLASS test instance
+ return Arrays.asList( "Another execution", "Last execution").stream();
+ }
+}
\ No newline at end of file