diff --git a/java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java b/java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java index 239d496fe5d5..990fea7bd281 100644 --- a/java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java +++ b/java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java @@ -16,11 +16,13 @@ package com.intellij.execution.junit; import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.codeInsight.MetaAnnotationUtil; import com.intellij.codeInsight.TestFrameworks; import com.intellij.execution.*; import com.intellij.execution.junit2.info.MethodLocation; import com.intellij.execution.testframework.SourceScope; import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; @@ -35,6 +37,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; @SuppressWarnings({"UtilityClassWithoutPrivateConstructor"}) public class JUnitUtil { @@ -209,13 +213,16 @@ public class JUnitUtil { if (!PsiClassUtil.isRunnableClass(psiClass, false, checkAbstract)) return false; - for (final PsiMethod method : psiClass.getAllMethods()) { - ProgressManager.checkCanceled(); - if (AnnotationUtil.isAnnotated(method, TEST5_ANNOTATIONS)) return true; - } + Module module = ModuleUtilCore.findModuleForPsiElement(psiClass); + if (module != null) { + for (final PsiMethod method : psiClass.getAllMethods()) { + ProgressManager.checkCanceled(); + if (isMetaAnnotated(method, TEST5_ANNOTATIONS, module)) return true; + } - for (PsiClass aClass : psiClass.getInnerClasses()) { - if (AnnotationUtil.isAnnotated(aClass, JUNIT5_NESTED, false)) return true; + for (PsiClass aClass : psiClass.getInnerClasses()) { + if (isMetaAnnotated(aClass, JUNIT5_NESTED, module)) return true; + } } return false; @@ -230,7 +237,7 @@ public class JUnitUtil { } public static boolean isTestAnnotated(final PsiMethod method) { - if (AnnotationUtil.isAnnotated(method, TEST_ANNOTATIONS, false) || JUnitRecognizer.willBeAnnotatedAfterCompilation(method)) { + if (AnnotationUtil.isAnnotated(method, TEST_ANNOTATION, false) || JUnitRecognizer.willBeAnnotatedAfterCompilation(method)) { final PsiAnnotation annotation = AnnotationUtil.findAnnotationInHierarchy(method.getContainingClass(), Collections.singleton(RUN_WITH)); if (annotation != null) { final PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes(); @@ -246,6 +253,24 @@ public class JUnitUtil { } return true; } + + Module module = ModuleUtilCore.findModuleForPsiElement(method); + return module != null && isMetaAnnotated(method, TEST5_ANNOTATIONS, module); + } + + private static boolean isMetaAnnotated(PsiModifierListOwner owner, final Collection metaAnnotations, final Module module) { + for (String annotation : metaAnnotations) { + if (isMetaAnnotated(owner, annotation, module)) return true; + } + return false; + } + + private static boolean isMetaAnnotated(PsiModifierListOwner owner, String annotation, Module module) { + Collection annotations = MetaAnnotationUtil.getAnnotationTypesWithChildren(module, annotation, true); + Stream qualifiedNames = annotations.stream().map(psiClass -> psiClass.getQualifiedName()); + if (AnnotationUtil.isAnnotated(owner, qualifiedNames.collect(Collectors.toSet()), false)) { + return true; + } return false; } diff --git a/java/openapi/src/com/intellij/codeInsight/MetaAnnotationUtil.java b/java/openapi/src/com/intellij/codeInsight/MetaAnnotationUtil.java index 0f97f665c446..0dd9e1e2bb25 100644 --- a/java/openapi/src/com/intellij/codeInsight/MetaAnnotationUtil.java +++ b/java/openapi/src/com/intellij/codeInsight/MetaAnnotationUtil.java @@ -53,23 +53,7 @@ public class MetaAnnotationUtil { } }; - - @NotNull - private static Collection getAnnotationTypesWithChildren(final String annotationName, - Project project, - GlobalSearchScope scope) { - final PsiClass psiClass = JavaPsiFacade.getInstance(project).findClass(annotationName, scope); - - if (psiClass == null || !psiClass.isAnnotationType()) return Collections.emptyList(); - - final Set classes = new THashSet<>(HASHING_STRATEGY); - - collectClassWithChildren(psiClass, classes, scope); - - return classes; - } - - public static Collection getAnnotationTypesWithChildren(final Module module, + public static Collection getAnnotationTypesWithChildren(@NotNull final Module module, final String annotationName, final boolean includeTests) { Map, Collection> map = @@ -96,37 +80,6 @@ public class MetaAnnotationUtil { return map.get(Pair.create(annotationName, includeTests)); } - private static GlobalSearchScope getAllAnnotationFilesScope(Module module) { - return CachedValuesManager.getManager(module.getProject()).getCachedValue(module, () -> { - GlobalSearchScope scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, true); - PsiClass javaLangAnnotation = - JavaPsiFacade.getInstance(scope.getProject()).findClass(CommonClassNames.JAVA_LANG_ANNOTATION_ANNOTATION, scope); - if (javaLangAnnotation == null) { - return CachedValueProvider.Result.createSingleDependency(GlobalSearchScope.EMPTY_SCOPE, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT); - } - - Set allAnnotationFiles = new HashSet<>(); - DirectClassInheritorsSearch.search(javaLangAnnotation, scope, false).forEach(annotationClass -> { - ContainerUtil.addIfNotNull(allAnnotationFiles, PsiUtilCore.getVirtualFile(annotationClass)); - return true; - }); - - return CachedValueProvider.Result - .createSingleDependency(GlobalSearchScope.filesWithLibrariesScope(module.getProject(), allAnnotationFiles), - PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT); - }); - } - - private static void collectClassWithChildren(final PsiClass psiClass, final Set classes, final GlobalSearchScope scope) { - classes.add(psiClass); - - for (PsiClass aClass : getChildren(psiClass, scope)) { - if (!classes.contains(aClass)) { - collectClassWithChildren(aClass, classes, scope); - } - } - } - public static Set getChildren(final PsiClass psiClass, final GlobalSearchScope scope) { if (AnnotationTargetUtil.findAnnotationTarget(psiClass, PsiAnnotation.TargetType.ANNOTATION_TYPE, PsiAnnotation.TargetType.TYPE) == null) { @@ -165,4 +118,50 @@ public class MetaAnnotationUtil { return new CachedValueProvider.Result<>(classes, PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT); }, false); } + + @NotNull + private static Collection getAnnotationTypesWithChildren(final String annotationName, + Project project, + GlobalSearchScope scope) { + final PsiClass psiClass = JavaPsiFacade.getInstance(project).findClass(annotationName, scope); + + if (psiClass == null || !psiClass.isAnnotationType()) return Collections.emptyList(); + + final Set classes = new THashSet<>(HASHING_STRATEGY); + + collectClassWithChildren(psiClass, classes, scope); + + return classes; + } + + private static GlobalSearchScope getAllAnnotationFilesScope(Module module) { + return CachedValuesManager.getManager(module.getProject()).getCachedValue(module, () -> { + GlobalSearchScope scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, true); + PsiClass javaLangAnnotation = + JavaPsiFacade.getInstance(scope.getProject()).findClass(CommonClassNames.JAVA_LANG_ANNOTATION_ANNOTATION, scope); + if (javaLangAnnotation == null) { + return CachedValueProvider.Result.createSingleDependency(GlobalSearchScope.EMPTY_SCOPE, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT); + } + + Set allAnnotationFiles = new HashSet<>(); + DirectClassInheritorsSearch.search(javaLangAnnotation, scope, false).forEach(annotationClass -> { + ContainerUtil.addIfNotNull(allAnnotationFiles, PsiUtilCore.getVirtualFile(annotationClass)); + return true; + }); + + return CachedValueProvider.Result + .createSingleDependency(GlobalSearchScope.filesWithLibrariesScope(module.getProject(), allAnnotationFiles), + PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT); + }); + } + + private static void collectClassWithChildren(final PsiClass psiClass, final Set classes, final GlobalSearchScope scope) { + classes.add(psiClass); + + for (PsiClass aClass : getChildren(psiClass, scope)) { + if (!classes.contains(aClass)) { + collectClassWithChildren(aClass, classes, scope); + } + } + } } diff --git a/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5AcceptanceTest.java b/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5AcceptanceTest.java index 9c208dac192a..23eadaa1a793 100644 --- a/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5AcceptanceTest.java +++ b/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5AcceptanceTest.java @@ -116,4 +116,23 @@ class JUnit5AcceptanceTest extends JUnit5CodeInsightTest { }); } + + @Test + void metaAnnotations() { + doTest(() -> { + myFixture.addClass("package a;\n" + + "import java.lang.annotation.Retention;\n" + + "import java.lang.annotation.RetentionPolicy;\n" + + "@Retention(RetentionPolicy.RUNTIME)\n" + + "@org.junit.jupiter.api.Test\n" + + "@interface MyTest {}"); + PsiClass aClass = myFixture.addClass("class ATest {\n" + + " @a.MyTest\n" + + " void foo() {}\n" + + "}\n"); + assertTrue(JUnitUtil.isTestClass(aClass, false, false)); + assertTrue(JUnitUtil.isTestMethod(MethodLocation.elementInClass(aClass.getMethods()[0], aClass))); + }); + + } }