diff --git a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties index 241ba0239431..3974b341572e 100644 --- a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties +++ b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties @@ -317,10 +317,6 @@ inspection.suspicious.collections.method.calls.problem.descriptor1=Suspicious ca inspection.suspicious.collections.method.calls.problem.descriptor=''{0}'' may not contain {2} of type ''{1}'' inspection.suspicious.getter.setter.field.option=Only warn when field matching getter/setter name is present inspection.suspicious.integer.div.assignment.option=Report suspicious but possibly exact divisions -inspection.test.only.problems.display.name=Test-only class or method call in production code -inspection.test.only.problems.test.only.class.reference=Test-only class is referenced in production code -inspection.test.only.problems.test.only.field.reference=Test-only field is referenced in production code -inspection.test.only.problems.test.only.method.call=Test-only method is called in production code inspection.unary.plus.unary.binary.option=Only report in confusing binary or unary expression context inspection.unnecessary.super.qualifier.option=Ignore clarification 'super' qualifier inspection.use.compare.method.fix.family.name=Replace with single comparison method @@ -390,7 +386,6 @@ unused.library.problem.descriptor=Unused library ''{0}'' unused.library.roots.problem.descriptor=Unused roots {0} from library ''{1}'' var.can.be.replaced.with.explicit.type='var' can be replaced with explicit type vararg.method.call.with.50.poly.arguments=Vararg method call with 50+ poly arguments may cause compilation and analysis slowdown -visible.for.testing.makes.little.sense.on.test.only.code=@VisibleForTesting makes little sense on @TestOnly code inspection.inconsistent.language.level.display.name=Inconsistent language level settings inspection.weaker.access.display.name=Declaration access can be weaker inspection.can.be.final.display.name=Declaration can have final modifier diff --git a/java/java-analysis-impl/src/META-INF/JavaAnalysisPlugin.xml b/java/java-analysis-impl/src/META-INF/JavaAnalysisPlugin.xml index d1213f006277..f21fe5421e2d 100644 --- a/java/java-analysis-impl/src/META-INF/JavaAnalysisPlugin.xml +++ b/java/java-analysis-impl/src/META-INF/JavaAnalysisPlugin.xml @@ -166,9 +166,6 @@ key="unnecessary.module.dependency.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.declaration.redundancy" enabledByDefault="true" level="WARNING" implementationClass="com.intellij.codeInspection.unnecessaryModuleDependency.UnnecessaryModuleDependencyInspection"/> - diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/testOnly/TestOnlyInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/testOnly/TestOnlyInspection.java deleted file mode 100644 index 076e61751503..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/testOnly/TestOnlyInspection.java +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.intellij.codeInspection.testOnly; - -import com.intellij.codeInsight.AnnotationUtil; -import com.intellij.codeInsight.TestFrameworks; -import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool; -import com.intellij.codeInspection.ProblemHighlightType; -import com.intellij.codeInspection.ProblemsHolder; -import com.intellij.codeInspection.RemoveAnnotationQuickFix; -import com.intellij.java.analysis.JavaAnalysisBundle; -import com.intellij.lang.java.JavaLanguage; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; -import com.intellij.psi.impl.light.LightModifierList; -import com.intellij.psi.impl.source.resolve.JavaResolveUtil; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.ObjectUtils; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Arrays; -import java.util.List; - -import static com.intellij.codeInsight.AnnotationUtil.CHECK_EXTERNAL; - -public class TestOnlyInspection extends AbstractBaseJavaLocalInspectionTool { - - @Override - @NotNull - public String getShortName() { - return "TestOnlyProblems"; - } - - @Override - @NotNull - public String getGroupDisplayName() { - return getGeneralGroupName(); - } - - @Override - @NotNull - public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder h, boolean isOnTheFly) { - return new JavaElementVisitor() { - @Override - public void visitMethodCallExpression(PsiMethodCallExpression expression) { - validate(expression.getMethodExpression(), expression.resolveMethod(), h); - } - - @Override - public void visitNewExpression(PsiNewExpression expression) { - PsiJavaCodeReferenceElement reference = expression.getClassOrAnonymousClassReference(); - if (reference != null && validate(reference, expression.resolveMethod(), h)) { - validate(reference, ObjectUtils.tryCast(reference.resolve(), PsiMember.class), h); - } - } - - @Override - public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) { - PsiElement resolve = expression.resolve(); - if (resolve instanceof PsiMethod) { - validate(expression, (PsiMethod)resolve, h); - } - } - - @Override - public void visitReferenceExpression(PsiReferenceExpression reference) { - PsiElement resolve = reference.resolve(); - if (resolve instanceof PsiField) { - validate(reference, (PsiField)resolve, h); - } - } - - @Override - public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { - if (reference.getParent() instanceof PsiNewExpression - || reference.getParent() instanceof PsiAnonymousClass - || PsiTreeUtil.getParentOfType(reference, PsiImportStatementBase.class) != null) { - return; - } - PsiElement resolve = reference.resolve(); - if (resolve instanceof PsiClass) validate(reference, (PsiClass)resolve, h); - } - - @Override - public void visitElement(@NotNull PsiElement element) { - if (element instanceof PsiMember) { - PsiAnnotation vft = findVisibleForTestingAnnotation((PsiMember)element); - if (vft != null && isDirectlyTestOnly((PsiMember)element)) { - PsiElement toHighlight = null; - if (element instanceof PsiNameIdentifierOwner) { - toHighlight = ((PsiNameIdentifierOwner)element).getNameIdentifier(); - } - if (toHighlight == null) { - toHighlight = element; - } - h.registerProblem(toHighlight, JavaAnalysisBundle.message("visible.for.testing.makes.little.sense.on.test.only.code"), new RemoveAnnotationQuickFix(vft, (PsiModifierListOwner)element)); - } - } - super.visitElement(element); - } - }; - } - - private static boolean validate(@NotNull PsiElement place, @Nullable PsiMember member, ProblemsHolder h) { - if (member == null) { - return true; - } - - PsiAnnotation vft = findVisibleForTestingAnnotation(member); - if (vft == null && !isAnnotatedAsTestOnly(member)) { - return true; - } - if (isInsideTestOnlyMethod(place) || isInsideTestOnlyField(place) || isInsideTestOnlyClass(place) || isInsideTestClass(place) - || isUnderTestSources(place)) { - return true; - } - - if (vft != null) { - String modifier = getAccessModifierWithoutTesting(vft); - if (modifier == null) { - modifier = getNextLowerAccessLevel(member); - } - - LightModifierList modList = new LightModifierList(member.getManager(), JavaLanguage.INSTANCE, modifier); - if (JavaResolveUtil.isAccessible(member, member.getContainingClass(), modList, place, null, null)) { - return true; - } - } - - reportProblem(place, member, h); - return false; - } - - private static final List ourModifiersDescending = - Arrays.asList(PsiModifier.PUBLIC, PsiModifier.PROTECTED, PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE); - - private static String getNextLowerAccessLevel(@NotNull PsiMember member) { - int methodModifier = ContainerUtil.indexOf(ourModifiersDescending, member::hasModifierProperty); - int minModifier = ourModifiersDescending.size() - 1; - if (member instanceof PsiMethod) { - for (PsiMethod superMethod : ((PsiMethod)member).findSuperMethods()) { - minModifier = Math.min(minModifier, ContainerUtil.indexOf(ourModifiersDescending, superMethod::hasModifierProperty)); - } - } - return ourModifiersDescending.get(Math.min(minModifier, methodModifier + 1)); - } - - @Nullable - private static String getAccessModifierWithoutTesting(PsiAnnotation anno) { - PsiAnnotationMemberValue ref = anno.findAttributeValue("visibility"); - if (ref instanceof PsiReferenceExpression) { - PsiElement target = ((PsiReferenceExpression)ref).resolve(); - if (target instanceof PsiEnumConstant) { - String name = ((PsiEnumConstant)target).getName(); - return "PRIVATE".equals(name) ? PsiModifier.PRIVATE : "PROTECTED".equals(name) ? PsiModifier.PROTECTED : PsiModifier.PACKAGE_LOCAL; - } - } - return null; - } - - @Nullable - private static PsiAnnotation findVisibleForTestingAnnotation(@NotNull PsiMember member) { - return AnnotationUtil.findAnnotation(member, - "com.google.common.annotations.VisibleForTesting", - "com.android.annotations.VisibleForTesting", - "org.jetbrains.annotations.VisibleForTesting"); - } - - private static boolean isInsideTestOnlyMethod(PsiElement e) { - return isAnnotatedAsTestOnly(getTopLevelParentOfType(e, PsiMethod.class)); - } - - private static boolean isInsideTestOnlyField(PsiElement e) { - return isAnnotatedAsTestOnly(getTopLevelParentOfType(e, PsiField.class)); - } - - private static boolean isInsideTestOnlyClass(@NotNull PsiElement e) { - return isAnnotatedAsTestOnly(getTopLevelParentOfType(e, PsiClass.class)); - } - - private static boolean isAnnotatedAsTestOnly(@Nullable PsiMember m) { - if (m == null) return false; - return isDirectlyTestOnly(m) || isAnnotatedAsTestOnly(m.getContainingClass()); - } - - private static boolean isDirectlyTestOnly(@NotNull PsiMember m) { - return AnnotationUtil.isAnnotated(m, AnnotationUtil.TEST_ONLY, CHECK_EXTERNAL); - } - - private static boolean isInsideTestClass(PsiElement e) { - PsiClass c = getTopLevelParentOfType(e, PsiClass.class); - return c != null && TestFrameworks.getInstance().isTestClass(c); - } - - private static T getTopLevelParentOfType(PsiElement e, Class c) { - T parent = PsiTreeUtil.getParentOfType(e, c); - if (parent == null) return null; - - do { - T next = PsiTreeUtil.getParentOfType(parent, c); - if (next == null) return parent; - parent = next; - } - while (true); - } - - private static boolean isUnderTestSources(PsiElement e) { - ProjectRootManager rm = ProjectRootManager.getInstance(e.getProject()); - VirtualFile f = e.getContainingFile().getVirtualFile(); - return f != null && rm.getFileIndex().isInTestSourceContent(f); - } - - private static void reportProblem(PsiElement e, PsiMember target, ProblemsHolder h) { - String message = JavaAnalysisBundle.message(target instanceof PsiClass - ? "inspection.test.only.problems.test.only.class.reference" - : target instanceof PsiField ? "inspection.test.only.problems.test.only.field.reference" - : "inspection.test.only.problems.test.only.method.call"); - h.registerProblem(e, message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); - } -} diff --git a/java/java-impl/src/inspectionDescriptions/TestOnlyProblems.html b/java/java-impl/src/inspectionDescriptions/TestOnlyProblems.html deleted file mode 100644 index cfecc36695f9..000000000000 --- a/java/java-impl/src/inspectionDescriptions/TestOnlyProblems.html +++ /dev/null @@ -1,14 +0,0 @@ - - - -Reports usages of @TestOnly - or @VisibleForTesting -annotated methods and classes in production code. -
The problems are not reported if such method or class is referenced from -
    -
  • code under 'Test Sources' folder -
  • a test-class (JUnit/TestNG) -
  • another @TestOnly-annotated method -
- - - diff --git a/java/java-tests/testData/inspection/testOnly/class/expected.xml b/java/java-tests/testData/inspection/testOnly/class/expected.xml deleted file mode 100644 index c9aadb095e3a..000000000000 --- a/java/java-tests/testData/inspection/testOnly/class/expected.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - B.java - 10 - <default> - Test-only class is referenced in production code - - - B.java - 10 - <default> - Test-only class is referenced in production code - - - B.java - 11 - <default> - Test-only field is referenced in production code - - - - B.java - 13 - <default> - Test-only class is referenced in production code - - - B.java - 13 - <default> - Test-only method is called in production code - - - - B.java - 14 - <default> - Test-only method is called in production code - - - B.java - 15 - <default> - Test-only method is called in production code - - - B.java - 16 - <default> - Test-only method is called in production code - - - - B.java - 18 - <default> - Test-only field is referenced in production code - - - B.java - 19 - <default> - Test-only field is referenced in production code - - - B.java - 20 - <default> - Test-only field is referenced in production code - - - - B.java - 22 - <default> - Test-only class is referenced in production code - - - B.java - 22 - <default> - Test-only method is called in production code - - - diff --git a/java/java-tests/testData/inspection/testOnly/class/src/B.java b/java/java-tests/testData/inspection/testOnly/class/src/B.java deleted file mode 100644 index 2d7492895e79..000000000000 --- a/java/java-tests/testData/inspection/testOnly/class/src/B.java +++ /dev/null @@ -1,24 +0,0 @@ -import packageA.A; - -import java.util.ArrayList; - -import static packageA.A.staticImportedField; -import static packageA.A.staticImportedMethod; - -public class B { - public void foo() { - Iterable iterable = new ArrayList(); - System.out.println(iterable.iterator().next().field); - - A a = new A(); - a.method(); - A.staticMethod(); - staticImportedMethod(); - - System.out.println(a.field); - System.out.println(A.staticField); - System.out.println(staticImportedField); - - A b = new A() { }; - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/class/src/packageA/A.java b/java/java-tests/testData/inspection/testOnly/class/src/packageA/A.java deleted file mode 100644 index 78282591851d..000000000000 --- a/java/java-tests/testData/inspection/testOnly/class/src/packageA/A.java +++ /dev/null @@ -1,14 +0,0 @@ -package packageA; - -@org.jetbrains.annotations.TestOnly -public class A { - public String field; - public static String staticField; - public static String staticImportedField; - - public A() {} - - public void method() {} - public static void staticMethod() {} - public static void staticImportedMethod() {} -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/constructor/expected.xml b/java/java-tests/testData/inspection/testOnly/constructor/expected.xml deleted file mode 100644 index 6845ecd4c261..000000000000 --- a/java/java-tests/testData/inspection/testOnly/constructor/expected.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - B.java - 5 - - A() - A() - <default> - - A - A - - - Test-only method is called in production code - - diff --git a/java/java-tests/testData/inspection/testOnly/constructor/src/A.java b/java/java-tests/testData/inspection/testOnly/constructor/src/A.java deleted file mode 100644 index e96e056615e2..000000000000 --- a/java/java-tests/testData/inspection/testOnly/constructor/src/A.java +++ /dev/null @@ -1,6 +0,0 @@ -public class A { - @org.jetbrains.annotations.TestOnly - public A() {} - - public A(String ignore) {} -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/constructor/src/B.java b/java/java-tests/testData/inspection/testOnly/constructor/src/B.java deleted file mode 100644 index 277a4cda341f..000000000000 --- a/java/java-tests/testData/inspection/testOnly/constructor/src/B.java +++ /dev/null @@ -1,8 +0,0 @@ -import org.jetbrains.annotations.TestOnly; - -public class B { - public void foo() { - new A(); - new A("ignore"); - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/insideField/expected.xml b/java/java-tests/testData/inspection/testOnly/insideField/expected.xml deleted file mode 100644 index 2e24645d1723..000000000000 --- a/java/java-tests/testData/inspection/testOnly/insideField/expected.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - A.java - 14 - testInsideField - <default> - - Test-only field is referenced in production code - - - diff --git a/java/java-tests/testData/inspection/testOnly/insideField/src/A.java b/java/java-tests/testData/inspection/testOnly/insideField/src/A.java deleted file mode 100644 index 4c3af301bb4e..000000000000 --- a/java/java-tests/testData/inspection/testOnly/insideField/src/A.java +++ /dev/null @@ -1,17 +0,0 @@ -public class A { - @org.jetbrains.annotations.TestOnly - public static final String MY_FIELD = "VALUE"; - - @org.jetbrains.annotations.TestOnly - public static final Runnable MY_TEST_ONLY_RUNNABLE = new Runnable() { - public void run() { - System.out.println(MY_FIELD); - } - }; - - public static final Runnable MY_PRODUCTION_RUNNABLE = new Runnable() { - public void run() { - System.out.println(MY_FIELD); - } - }; -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/insideField/src/C.java b/java/java-tests/testData/inspection/testOnly/insideField/src/C.java deleted file mode 100644 index dbcea8c5bc7c..000000000000 --- a/java/java-tests/testData/inspection/testOnly/insideField/src/C.java +++ /dev/null @@ -1,10 +0,0 @@ -public class C { - @org.junit.Test - public void foo() { - new Runnable() { - public void run() { - System.out.println(A.MY_FIELD); - } - }; - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/insideField/test/D.java b/java/java-tests/testData/inspection/testOnly/insideField/test/D.java deleted file mode 100644 index 3cf73676c457..000000000000 --- a/java/java-tests/testData/inspection/testOnly/insideField/test/D.java +++ /dev/null @@ -1,15 +0,0 @@ -public class D { - public static final Runnable MY_RUNNABLE = new Runnable() { - public void run() { - System.out.println(A.MY_FIELD); - } - }; - - public void test() { - new Runnable() { - public void run() { - System.out.println(A.MY_FIELD); - } - }; - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/insideInner/expected.xml b/java/java-tests/testData/inspection/testOnly/insideInner/expected.xml deleted file mode 100644 index d4d91ff59e22..000000000000 --- a/java/java-tests/testData/inspection/testOnly/insideInner/expected.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - A.java - 17 - - void production() - production() - <default> - - A - A - - - Test-only method is called in production code - - - diff --git a/java/java-tests/testData/inspection/testOnly/insideInner/src/A.java b/java/java-tests/testData/inspection/testOnly/insideInner/src/A.java deleted file mode 100644 index 5c3cd06b5896..000000000000 --- a/java/java-tests/testData/inspection/testOnly/insideInner/src/A.java +++ /dev/null @@ -1,21 +0,0 @@ -public class A { - @org.jetbrains.annotations.TestOnly - public void foo() {} - - @org.jetbrains.annotations.TestOnly - public void test() { - new Runnable() { - public void run() { - foo(); - } - }; - } - - public void production() { - new Runnable() { - public void run() { - foo(); - } - }; - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/insideInner/src/C.java b/java/java-tests/testData/inspection/testOnly/insideInner/src/C.java deleted file mode 100644 index f7a819544f4d..000000000000 --- a/java/java-tests/testData/inspection/testOnly/insideInner/src/C.java +++ /dev/null @@ -1,10 +0,0 @@ -public class C { - @org.junit.Test - public void foo() { - new Runnable() { - public void run() { - new A().foo(); - } - }; - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/insideInner/test/D.java b/java/java-tests/testData/inspection/testOnly/insideInner/test/D.java deleted file mode 100644 index 85262a057b5f..000000000000 --- a/java/java-tests/testData/inspection/testOnly/insideInner/test/D.java +++ /dev/null @@ -1,11 +0,0 @@ -import org.jetbrains.annotations.TestOnly; - -public class D { - public void test() { - new Runnable() { - public void run() { - new A().foo(); - } - }; - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/insideTestOnlyClass/expected.xml b/java/java-tests/testData/inspection/testOnly/insideTestOnlyClass/expected.xml deleted file mode 100644 index 580a889dbfd6..000000000000 --- a/java/java-tests/testData/inspection/testOnly/insideTestOnlyClass/expected.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/java/java-tests/testData/inspection/testOnly/insideTestOnlyClass/src/InnerGenericType.java b/java/java-tests/testData/inspection/testOnly/insideTestOnlyClass/src/InnerGenericType.java deleted file mode 100644 index 9b1259a8c873..000000000000 --- a/java/java-tests/testData/inspection/testOnly/insideTestOnlyClass/src/InnerGenericType.java +++ /dev/null @@ -1,8 +0,0 @@ -import org.jetbrains.annotations.TestOnly; -import java.util.List; - -@TestOnly -public class Bar { - static abstract class Foo implements List { - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/insideTestOnlyClass/src/SeveralGenericTypes.java b/java/java-tests/testData/inspection/testOnly/insideTestOnlyClass/src/SeveralGenericTypes.java deleted file mode 100644 index 3d369fd0f4f2..000000000000 --- a/java/java-tests/testData/inspection/testOnly/insideTestOnlyClass/src/SeveralGenericTypes.java +++ /dev/null @@ -1,19 +0,0 @@ -import org.jetbrains.annotations.TestOnly; - -@TestOnly -public class SeveralGenericTypes implements BiFunction { - private final boolean isLeftBorder; - private final int offset; - private final String text; - - public SeveralGenericTypes(boolean isLeftBorder, int offset, String text) { - this.isLeftBorder = isLeftBorder; - this.offset = offset; - this.text = text; - } - - @Override - public SeveralGenericTypes apply(SeveralGenericTypes first, Integer second) { - return first; - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/insideTestOnlyClass/src/SingleGenericType.java b/java/java-tests/testData/inspection/testOnly/insideTestOnlyClass/src/SingleGenericType.java deleted file mode 100644 index e646586d3e73..000000000000 --- a/java/java-tests/testData/inspection/testOnly/insideTestOnlyClass/src/SingleGenericType.java +++ /dev/null @@ -1,19 +0,0 @@ -import org.jetbrains.annotations.TestOnly; - -@TestOnly -public class SingleGenericType implements Comparable { - private final boolean isLeftBorder; - private final int offset; - private final String text; - - public SingleGenericType(boolean isLeftBorder, int offset, String text) { - this.isLeftBorder = isLeftBorder; - this.offset = offset; - this.text = text; - } - - @Override - public int compareTo(@NotNull SingleGenericType o) { - return offset < o.offset ? 1 : -1; - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/insideTestOnlyClass/src/StaticInitializerVariable.java b/java/java-tests/testData/inspection/testOnly/insideTestOnlyClass/src/StaticInitializerVariable.java deleted file mode 100644 index 94544e4690c5..000000000000 --- a/java/java-tests/testData/inspection/testOnly/insideTestOnlyClass/src/StaticInitializerVariable.java +++ /dev/null @@ -1,8 +0,0 @@ -import org.jetbrains.annotations.TestOnly; - -@TestOnly -public class Bar { - static { - java.lang.Class clazz = Bar.class; - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/simple/expected.xml b/java/java-tests/testData/inspection/testOnly/simple/expected.xml deleted file mode 100644 index e58ea407e9c0..000000000000 --- a/java/java-tests/testData/inspection/testOnly/simple/expected.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - A.java - 6 - - void production() - production() - <default> - - A - A - - - Test-only method is called in production code - - - - B.java - 3 - - void production() - production() - <default> - - B - B - - - Test-only method is called in production code - - - diff --git a/java/java-tests/testData/inspection/testOnly/simple/src/A.java b/java/java-tests/testData/inspection/testOnly/simple/src/A.java deleted file mode 100644 index 3a35d4059a21..000000000000 --- a/java/java-tests/testData/inspection/testOnly/simple/src/A.java +++ /dev/null @@ -1,8 +0,0 @@ -public class A { - @org.jetbrains.annotations.TestOnly - public void test() {} - - public void production() { - test(); - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/simple/src/B.java b/java/java-tests/testData/inspection/testOnly/simple/src/B.java deleted file mode 100644 index 18096db185dc..000000000000 --- a/java/java-tests/testData/inspection/testOnly/simple/src/B.java +++ /dev/null @@ -1,10 +0,0 @@ -public class B { - public void production() { - new A().test(); - } - - @org.jetbrains.annotations.TestOnly - public void test() { - new A().test(); - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/simple/src/C.java b/java/java-tests/testData/inspection/testOnly/simple/src/C.java deleted file mode 100644 index f61a2529def3..000000000000 --- a/java/java-tests/testData/inspection/testOnly/simple/src/C.java +++ /dev/null @@ -1,6 +0,0 @@ -public class C { - @org.junit.Test - public void foo() { - new A().test(); - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/simple/test/D.java b/java/java-tests/testData/inspection/testOnly/simple/test/D.java deleted file mode 100644 index bb6fc9142c98..000000000000 --- a/java/java-tests/testData/inspection/testOnly/simple/test/D.java +++ /dev/null @@ -1,7 +0,0 @@ -import org.jetbrains.annotations.TestOnly; - -public class D { - public void foo() { - new A().test(); - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/unresolved/expected.xml b/java/java-tests/testData/inspection/testOnly/unresolved/expected.xml deleted file mode 100644 index 4704d91e891d..000000000000 --- a/java/java-tests/testData/inspection/testOnly/unresolved/expected.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/unresolved/src/A.java b/java/java-tests/testData/inspection/testOnly/unresolved/src/A.java deleted file mode 100644 index 99d34078bb2b..000000000000 --- a/java/java-tests/testData/inspection/testOnly/unresolved/src/A.java +++ /dev/null @@ -1,7 +0,0 @@ -import org.jetbrains.annotations.TestOnly; - -public class A { - public void foo() { - unresolved(); - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/visibleForTesting/expected.xml b/java/java-tests/testData/inspection/testOnly/visibleForTesting/expected.xml deleted file mode 100644 index 3518b3ad0773..000000000000 --- a/java/java-tests/testData/inspection/testOnly/visibleForTesting/expected.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - B.java - 4 - Test-only method is called in production code - - - B.java - 6 - Test-only method is called in production code - - - B.java - 7 - Test-only class is referenced in production code - - - B.java - 8 - Test-only class is referenced in production code - - - AGrandChild.java - 6 - Test-only method is called in production code - - - A.java - 39 - @VisibleForTesting makes little sense on @TestOnly code - bothAnnotationsAreAnError - - diff --git a/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/com/android/annotations/VisibleForTesting.java b/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/com/android/annotations/VisibleForTesting.java deleted file mode 100644 index abad39a0e1b9..000000000000 --- a/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/com/android/annotations/VisibleForTesting.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.android.annotations; - -public @interface VisibleForTesting { - enum Visibility { - /** The element should be considered protected. */ - PROTECTED, - /** The element should be considered package-private. */ - PACKAGE, - /** The element should be considered private. */ - PRIVATE - } - - Visibility visibility() default Visibility.PRIVATE; -} diff --git a/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/com/google/common/annotations/VisibleForTesting.java b/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/com/google/common/annotations/VisibleForTesting.java deleted file mode 100644 index 06ab6bc2ca6a..000000000000 --- a/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/com/google/common/annotations/VisibleForTesting.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.google.common.annotations; - -public @interface VisibleForTesting { - -} diff --git a/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/pkg1/A.java b/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/pkg1/A.java deleted file mode 100644 index e2a529579817..000000000000 --- a/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/pkg1/A.java +++ /dev/null @@ -1,40 +0,0 @@ -package pkg1; -public class A { - void publicMethod() { - invisibleMethod(1); - visibleMethod(1); - } - - @com.android.annotations.VisibleForTesting - void invisibleMethod(int a) { - - } - - @com.android.annotations.VisibleForTesting(visibility=com.android.annotations.VisibleForTesting.Visibility.PROTECTED) - void visibleMethod(int a) { - relaxedToPackageLevel(a); - } - - @com.google.common.annotations.VisibleForTesting - void relaxedToPackageLevel(int a) { - - } - - protected void aProtectedMethod() {} - - @com.google.common.annotations.VisibleForTesting - static class FooException extends RuntimeException { - FooException(String message) { - super(message); - } - } - - public static void usingExceptionPrivately(String[] args) { - A.FooException exception = - new A.FooException(""); - } - - @org.jetbrains.annotations.TestOnly - @com.google.common.annotations.VisibleForTesting - void bothAnnotationsAreAnError() {} -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/pkg1/AChild.java b/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/pkg1/AChild.java deleted file mode 100644 index 1e93035437cf..000000000000 --- a/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/pkg1/AChild.java +++ /dev/null @@ -1,11 +0,0 @@ -package pkg1; -public class AChild extends A { - @com.google.common.annotations.VisibleForTesting - protected void aProtectedMethod() { - super.aProtectedMethod(); - } - - @com.google.common.annotations.VisibleForTesting - protected void childProtectedMethod() { - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/pkg1/B.java b/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/pkg1/B.java deleted file mode 100644 index c10696caf6dc..000000000000 --- a/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/pkg1/B.java +++ /dev/null @@ -1,11 +0,0 @@ -package pkg1; -public class B { - void publicMethod() { - new A().invisibleMethod(2); - new A().visibleMethod(2); - new A().relaxedToPackageLevel(2); - A.FooException exception = - new A.FooException(""); - new AChild().aProtectedMethod(); - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/pkg1/InstantiatingPackageLocal.java b/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/pkg1/InstantiatingPackageLocal.java deleted file mode 100644 index 2ddfd55d256d..000000000000 --- a/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/pkg1/InstantiatingPackageLocal.java +++ /dev/null @@ -1,11 +0,0 @@ -@com.google.common.annotations.VisibleForTesting -public class RelaxedForTesting { - RelaxedForTesting() { - } -} - -class Client { - { - new RelaxedForTesting(); - } -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/pkg2/AGrandChild.java b/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/pkg2/AGrandChild.java deleted file mode 100644 index 1dcff3aff008..000000000000 --- a/java/java-tests/testData/inspection/testOnly/visibleForTesting/src/pkg2/AGrandChild.java +++ /dev/null @@ -1,9 +0,0 @@ -package pkg2; - -public class AGrandChild extends pkg1.AChild { - void foo() { - aProtectedMethod(); - childProtectedMethod(); - } - -} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/testOnly/visibleForTesting/test/pkg1/Test.java b/java/java-tests/testData/inspection/testOnly/visibleForTesting/test/pkg1/Test.java deleted file mode 100644 index 511d0d41b240..000000000000 --- a/java/java-tests/testData/inspection/testOnly/visibleForTesting/test/pkg1/Test.java +++ /dev/null @@ -1,9 +0,0 @@ -package pkg1; -public class Test { - void publicMethod() { - new A().invisibleMethod(3); - new A().visibleMethod(3); - new A().relaxedToPackageLevel(3); - new AChild().aProtectedMethod(); - } -} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/TestOnlyInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/TestOnlyInspectionTest.java deleted file mode 100644 index 19ac896bfaf3..000000000000 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/TestOnlyInspectionTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.java.codeInspection; - -import com.intellij.analysis.AnalysisScope; -import com.intellij.codeInspection.ex.LocalInspectionToolWrapper; -import com.intellij.codeInspection.testOnly.TestOnlyInspection; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.roots.ContentEntry; -import com.intellij.openapi.roots.ModifiableRootModel; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.project.IntelliJProjectConfiguration; -import com.intellij.testFramework.JavaInspectionTestCase; -import com.intellij.testFramework.LightProjectDescriptor; -import com.intellij.testFramework.PsiTestUtil; -import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor; -import com.intellij.util.ArrayUtil; -import org.jetbrains.annotations.NotNull; - -public class TestOnlyInspectionTest extends JavaInspectionTestCase { - - private final static DefaultLightProjectDescriptor ourProjectDescriptor = new DefaultLightProjectDescriptor() { - @Override - public void configureModule(@NotNull Module module, @NotNull ModifiableRootModel model, @NotNull ContentEntry contentEntry) { - super.configureModule(module, model, contentEntry); - contentEntry.addSourceFolder(contentEntry.getUrl() + "/test", true); - IntelliJProjectConfiguration.LibraryRoots junit4Library = IntelliJProjectConfiguration.getProjectLibrary("JUnit4"); - PsiTestUtil.addLibrary(model, "JUnit4", "", ArrayUtil.toStringArray(junit4Library.getClassesPaths())); - } - }; - - @NotNull - @Override - protected LightProjectDescriptor getProjectDescriptor() { - return ourProjectDescriptor; - } - - @NotNull - @Override - protected AnalysisScope createAnalysisScope(VirtualFile sourceDir) { - return new AnalysisScope(getModule()); - } - - public void testSimple() { - doTest(); - } - - public void testInsideInner() { - doTest(); - } - - public void testConstructor() { - doTest(); - } - - public void testVisibleForTesting() { doTest(); } - - public void testUnresolved() { - doTest(); // shouldn't throw - } - - public void testClass() { - doTest(); - } - - public void testInsideField() { - doTest(); - } - - public void testInsideTestOnlyClass() { - doTest(); - } - - private void doTest() { - TestOnlyInspection i = new TestOnlyInspection(); - doTest("testOnly/" + getTestName(true), new LocalInspectionToolWrapper(i)); - } -} \ No newline at end of file diff --git a/jvm/jvm-analysis-impl/resources/META-INF/JvmAnalysisPlugin.xml b/jvm/jvm-analysis-impl/resources/META-INF/JvmAnalysisPlugin.xml index 66b717de947d..322ae00bd866 100644 --- a/jvm/jvm-analysis-impl/resources/META-INF/JvmAnalysisPlugin.xml +++ b/jvm/jvm-analysis-impl/resources/META-INF/JvmAnalysisPlugin.xml @@ -44,6 +44,10 @@ groupBundle="messages.JvmAnalysisBundle" bundle="messages.JvmAnalysisBundle" groupKey="jvm.inspections.group.name" key="jvm.inspections.junit.rule.display.name" implementationClass="com.intellij.codeInspection.JUnitRuleInspection"/> + diff --git a/jvm/jvm-analysis-impl/resources/inspectionDescriptions/TestOnlyProblems.html b/jvm/jvm-analysis-impl/resources/inspectionDescriptions/TestOnlyProblems.html new file mode 100644 index 000000000000..039ca37e7f62 --- /dev/null +++ b/jvm/jvm-analysis-impl/resources/inspectionDescriptions/TestOnlyProblems.html @@ -0,0 +1,12 @@ + + + +Reports usages of @TestOnly and @VisibleForTesting annotated methods and classes in production code. +
The problems are not reported if such method or class is referenced from: +
    +
  • code under 'Test Sources' folder +
  • a test-class (JUnit/TestNG) +
  • another @TestOnly-annotated method +
+ + diff --git a/jvm/jvm-analysis-impl/resources/messages/JvmAnalysisBundle.properties b/jvm/jvm-analysis-impl/resources/messages/JvmAnalysisBundle.properties index dc11350ce685..e5ee2575cd9b 100644 --- a/jvm/jvm-analysis-impl/resources/messages/JvmAnalysisBundle.properties +++ b/jvm/jvm-analysis-impl/resources/messages/JvmAnalysisBundle.properties @@ -54,6 +54,12 @@ jvm.inspections.junit.rule.display.name=Malformed @Rule/@ClassRule field jvm.inspections.junit.rule.problem.descriptor=Fields annotated with ''@{0}'' should be {1} jvm.inspections.junit.rule.type.problem.descriptor=Field type should be subtype of 'org.junit.rules.TestRule' +jvm.inspections.testonly.display.name=Test-only usage in production code +jvm.inspections.testonly.class.reference=Test-only class is referenced in production code +jvm.inspections.testonly.field.reference=Test-only field is referenced in production code +jvm.inspections.testonly.method.call=Test-only method is called in production code +jvm.inspections.testonly.visiblefortesting=@VisibleForTesting makes little sense on @TestOnly code + jvm.inspections.string.touppercase.tolowercase.without.locale.description=String.{0}() called without specifying a Locale using internationalized strings #loc can.t.build.uast.tree.for.file=Can't build UAST tree for file title.uast=UAST diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/TestOnlyInspection.kt b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/TestOnlyInspection.kt new file mode 100644 index 000000000000..9a8855598ae0 --- /dev/null +++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/TestOnlyInspection.kt @@ -0,0 +1,170 @@ +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInspection + +import com.intellij.analysis.JvmAnalysisBundle +import com.intellij.codeInsight.AnnotationUtil +import com.intellij.codeInsight.TestFrameworks +import com.intellij.lang.java.JavaLanguage +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.psi.* +import com.intellij.psi.impl.light.LightModifierList +import com.intellij.psi.impl.source.resolve.JavaResolveUtil +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.uast.UastVisitorAdapter +import com.intellij.util.containers.ContainerUtil +import org.jetbrains.uast.* +import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor +import kotlin.math.min + +class TestOnlyInspection : AbstractBaseUastLocalInspectionTool() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor = + UastVisitorAdapter(TestOnlyVisitor(holder), true) + + class TestOnlyVisitor(private val holder: ProblemsHolder) : AbstractUastNonRecursiveVisitor() { + override fun visitCallExpression(node: UCallExpression): Boolean { + val parent = node.uastParent + if (parent is UQualifiedReferenceExpression) { + val parentRecResolved = parent.receiver.tryResolve() + if (parentRecResolved is PsiPackage || parentRecResolved is PsiVariable || parentRecResolved is PsiClass) return true + } + val method = node.resolve() ?: return true + val sourcePsi = node.sourcePsi ?: return true + return validate(sourcePsi, method, holder) + } + + override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression): Boolean { + if (node.uastParent is UQualifiedReferenceExpression) return true + val resolve = node.resolveToUElement() + val resolvedJava = resolve?.javaPsi ?: return true + if (resolvedJava !is PsiMember) return true + val sourcePsi = node.sourcePsi ?: return true + return when (resolve) { + is UField -> validate(sourcePsi, resolvedJava, holder) + is UMethod -> if (node.javaPsi !is PsiReferenceExpression) validate(sourcePsi, resolvedJava, holder) else true + else -> true + } + } + + override fun visitCallableReferenceExpression(node: UCallableReferenceExpression): Boolean { + val resolve = node.resolveToUElement() + val resolvedJava = resolve?.javaPsi ?: return true + if (resolvedJava !is PsiMember) return true + val sourcePsi = node.sourcePsi ?: return true + return validate(sourcePsi, resolvedJava, holder) + } + + override fun visitElement(node: UElement): Boolean { + val javaPsi = node.javaPsi ?: return true + if (javaPsi is PsiMember && node is UDeclaration) { + val vft = findVisibleForTestingAnnotation(javaPsi) + if (vft != null && isDirectlyTestOnly(javaPsi)) { + val toHighlight = node.uastAnchor.sourcePsiElement ?: return true + holder.registerProblem( + toHighlight, + JvmAnalysisBundle.message("jvm.inspections.testonly.visiblefortesting"), + RemoveAnnotationQuickFix(vft, javaPsi as PsiModifierListOwner) + ) + return true + } + } + return true + } + + private fun validate(place: PsiElement, member: PsiMember, holder: ProblemsHolder): Boolean { + val vft = findVisibleForTestingAnnotation(member) + if (vft == null && !isAnnotatedAsTestOnly(member)) return true + if (isInsideTestOnlyMethod(place) || isInsideTestOnlyField(place) || isInsideTestOnlyClass(place) || isInsideTestClass(place) || + isUnderTestSources(place) + ) return false + if (vft != null) { + var modifier = getAccessModifierWithoutTesting(vft) + if (modifier == null) modifier = getNextLowerAccessLevel(member) + val modList = LightModifierList(member.manager, JavaLanguage.INSTANCE, modifier) + if (JavaResolveUtil.isAccessible(member, member.containingClass, modList, place, null, null)) return true + } + reportProblem(place, member, holder) + return false + } + + private fun getNextLowerAccessLevel(member: PsiMember): String { + val methodModifier = ContainerUtil.indexOf(modifierPriority) { name: String -> member.hasModifierProperty(name) } + var minModifier = modifierPriority.size - 1 + if (member is PsiMethod) { + for (superMethod in member.findSuperMethods()) { + minModifier = min(minModifier, ContainerUtil.indexOf(modifierPriority) { name: String -> superMethod.hasModifierProperty(name) }) + } + } + return modifierPriority[min(minModifier, methodModifier + 1)] + } + + private fun getAccessModifierWithoutTesting(anno: PsiAnnotation): String? { + val ref = anno.findAttributeValue("visibility") + if (ref is PsiReferenceExpression) { + val target = ref.resolve() + if (target is PsiEnumConstant) { + return when (target.name) { + "PRIVATE" -> PsiModifier.PRIVATE + "PROTECTED" -> PsiModifier.PROTECTED + else -> PsiModifier.PACKAGE_LOCAL + } + } + } + return null + } + + private fun findVisibleForTestingAnnotation(member: PsiMember) = AnnotationUtil.findAnnotation(member, visibleForTestingAnnotations) + + private fun isInsideTestOnlyMethod(elem: PsiElement) = isAnnotatedAsTestOnly(getTopLevelParentOfType(elem, PsiMethod::class.java)) + + private fun isInsideTestOnlyField(elem: PsiElement) = isAnnotatedAsTestOnly(getTopLevelParentOfType(elem, PsiField::class.java)) + + private fun isInsideTestOnlyClass(elem: PsiElement) = isAnnotatedAsTestOnly(getTopLevelParentOfType(elem, PsiClass::class.java)) + + private fun isAnnotatedAsTestOnly(member: PsiMember?): Boolean = + if (member == null) false else isDirectlyTestOnly(member) || isAnnotatedAsTestOnly(member.containingClass) + + private fun isDirectlyTestOnly(member: PsiMember) = + AnnotationUtil.isAnnotated(member, AnnotationUtil.TEST_ONLY, AnnotationUtil.CHECK_EXTERNAL) + + private fun isInsideTestClass(elem: PsiElement): Boolean { + val parent = getTopLevelParentOfType(elem, PsiClass::class.java) + return parent != null && TestFrameworks.getInstance().isTestClass(parent) + } + + private fun getTopLevelParentOfType(e: PsiElement, c: Class): T? { + var parent = PsiTreeUtil.getParentOfType(e, c) ?: return null + do { + val next = PsiTreeUtil.getParentOfType(parent, c) ?: return parent + parent = next + } + while (true) + } + + private fun isUnderTestSources(elem: PsiElement): Boolean { + val rootManger = ProjectRootManager.getInstance(elem.project) + val file = elem.containingFile.virtualFile + return file != null && rootManger.fileIndex.isInTestSourceContent(file) + } + + private fun reportProblem(elem: PsiElement, target: PsiMember, holder: ProblemsHolder) { + val message = JvmAnalysisBundle.message( + when { + target is PsiMethod && target.isConstructor -> "jvm.inspections.testonly.class.reference" + target is PsiField -> "jvm.inspections.testonly.field.reference" + else -> "jvm.inspections.testonly.method.call" + } + ) + holder.registerProblem(elem, message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING) + } + } + + companion object { + private val visibleForTestingAnnotations = listOf( + "com.google.common.annotations.VisibleForTesting", + "com.android.annotations.VisibleForTesting", + "org.jetbrains.annotations.VisibleForTesting" + ) + + private val modifierPriority = listOf(PsiModifier.PUBLIC, PsiModifier.PROTECTED, PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE) + } +} \ No newline at end of file diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/testonly/TestOnlyTest.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/testonly/TestOnlyTest.java new file mode 100644 index 000000000000..55d8b66f459a --- /dev/null +++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/testonly/TestOnlyTest.java @@ -0,0 +1,34 @@ +package test; + +import org.jetbrains.annotations.TestOnly; +import org.jetbrains.annotations.VisibleForTesting; +import java.util.function.Function; + +public class TestOnlyTest { + @TestOnly + TestOnlyTest() { } + + @TestOnly + static String someString(String someStr) { return someStr + "Foo"; } + + @TestOnly + @VisibleForTesting + static String doubleAnn() { return "Foo"; } + + static class Bar { + @TestOnly + int aField = 0; + + @TestOnly + void aMethod() { } + } + + public static void main(String[] args) { + TestOnlyTest foo = new TestOnlyTest(); + Bar bar = new Bar(); + int aField = bar.aField; + bar.aMethod(); + Function methodRef = TestOnlyTest::someString; + } +} + diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/testonly/VisibleForTestingTest.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/testonly/VisibleForTestingTest.java new file mode 100644 index 000000000000..2709c1d4a443 --- /dev/null +++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/testonly/VisibleForTestingTest.java @@ -0,0 +1,16 @@ +package test; + +import org.jetbrains.annotations.VisibleForTesting; +import test.VisibleForTestingTestApi; + +public class VisibleForTestingTest { + @VisibleForTesting + static int fooBar = 0; + + public static void main(String[] args) { + System.out.println(fooBar); + System.out.println(VisibleForTestingTestApi.foo); + VisibleForTestingTestApi.bar(); + } +} + diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/testonly/VisibleForTestingTestApi.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/testonly/VisibleForTestingTestApi.java new file mode 100644 index 000000000000..50ad9ef9a847 --- /dev/null +++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/testonly/VisibleForTestingTestApi.java @@ -0,0 +1,12 @@ +package test; + +import org.jetbrains.annotations.VisibleForTesting; + +public class VisibleForTestingTestApi { + @VisibleForTesting + static int foo = x; + + @VisibleForTesting + static void bar() { } +} + diff --git a/jvm/jvm-analysis-java-tests/testSrc/com/intellij/codeInspection/tests/java/JavaTestOnlyInspectionTest.kt b/jvm/jvm-analysis-java-tests/testSrc/com/intellij/codeInspection/tests/java/JavaTestOnlyInspectionTest.kt new file mode 100644 index 000000000000..971ffb0ab5ed --- /dev/null +++ b/jvm/jvm-analysis-java-tests/testSrc/com/intellij/codeInspection/tests/java/JavaTestOnlyInspectionTest.kt @@ -0,0 +1,16 @@ +package com.intellij.codeInspection.tests.java + +import com.intellij.codeInspection.tests.TestOnlyInspectionTestBase +import com.intellij.jvm.analysis.JvmAnalysisTestsUtil + +class JavaTestOnlyInspectionTest : TestOnlyInspectionTestBase() { + override fun getTestDataPath() = JvmAnalysisTestsUtil.TEST_DATA_PROJECT_RELATIVE_BASE_PATH + "/codeInspection/testonly" + + fun `test @TestOnly in production code`() { + myFixture.testHighlighting("TestOnlyTest.java") + } + + fun `test @VisibleForTesting in production code`() { + myFixture.testHighlighting("VisibleForTestingTest.java", "VisibleForTestingTestApi.java") + } +} \ No newline at end of file diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/testonly/TestOnlyTest.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/testonly/TestOnlyTest.kt new file mode 100644 index 000000000000..fd8fc02ed884 --- /dev/null +++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/testonly/TestOnlyTest.kt @@ -0,0 +1,28 @@ +package test + +import org.jetbrains.annotations.TestOnly +import org.jetbrains.annotations.VisibleForTesting + +class TestOnlyTest @TestOnly constructor() { + val nonTestField = 0 + + var aField = 0 + @TestOnly get() = field + + @TestOnly + fun aMethod(x: Int): Int = x + + @TestOnly + @VisibleForTesting + fun aStringMethod < / warning >(): String = "Foo" +} + +fun main() { + val foo1 = TestOnlyTest() + val foo2 = test.TestOnlyTest() + val foo3 = TestOnlyTest().nonTestField + val bar = foo1.aField + foo1.aMethod(bar) + TestOnlyTest::aMethod.invoke(foo2, foo3) + test.TestOnlyTest::aMethod.invoke(foo2, foo3) +} \ No newline at end of file diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/testonly/VisibleForTestingTest.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/testonly/VisibleForTestingTest.kt new file mode 100644 index 000000000000..74ae3cb63678 --- /dev/null +++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/testonly/VisibleForTestingTest.kt @@ -0,0 +1,15 @@ +package test + +import org.jetbrains.annotations.VisibleForTesting +import testapi.VisibleForTestingTestApi + +object VisibleForTestingTest { + val foobar = 0 + @VisibleForTesting get() = field + + fun main() { + foobar + VisibleForTestingTestApi.foo + VisibleForTestingTestApi.bar() + } +} \ No newline at end of file diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/testonly/VisibleForTestingTestApi.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/testonly/VisibleForTestingTestApi.kt new file mode 100644 index 000000000000..395b3498930b --- /dev/null +++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/testonly/VisibleForTestingTestApi.kt @@ -0,0 +1,12 @@ +package testapi + +import org.jetbrains.annotations.VisibleForTesting + +object VisibleForTestingTestApi { + var foo = 0 + @VisibleForTesting get() = field + + @VisibleForTesting + fun bar() { + } +} \ No newline at end of file diff --git a/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/codeInspection/KotlinTestOnlyInspectionTest.kt b/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/codeInspection/KotlinTestOnlyInspectionTest.kt new file mode 100644 index 000000000000..a16315a3f876 --- /dev/null +++ b/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/codeInspection/KotlinTestOnlyInspectionTest.kt @@ -0,0 +1,16 @@ +package com.intellij.codeInspection + +import com.intellij.codeInspection.tests.TestOnlyInspectionTestBase +import com.intellij.jvm.analysis.JvmAnalysisKtTestsUtil + +class KotlinTestOnlyInspectionTest : TestOnlyInspectionTestBase() { + override fun getBasePath() = JvmAnalysisKtTestsUtil.TEST_DATA_PROJECT_RELATIVE_BASE_PATH + "/codeInspection/testonly" + + fun `test @TestOnly in production code`() { + myFixture.testHighlighting("TestOnlyTest.kt") + } + + fun `test @VisibleForTesting in production code`() { + myFixture.testHighlighting("VisibleForTestingTest.kt", "VisibleForTestingTestApi.kt") + } +} \ No newline at end of file diff --git a/jvm/jvm-analysis-tests-api/src/com/intellij/codeInspection/tests/TestOnlyInspectionTestBase.kt b/jvm/jvm-analysis-tests-api/src/com/intellij/codeInspection/tests/TestOnlyInspectionTestBase.kt new file mode 100644 index 000000000000..eb71035541d6 --- /dev/null +++ b/jvm/jvm-analysis-tests-api/src/com/intellij/codeInspection/tests/TestOnlyInspectionTestBase.kt @@ -0,0 +1,53 @@ +package com.intellij.codeInspection.tests + +import com.intellij.codeInspection.TestOnlyInspection +import com.intellij.pom.java.LanguageLevel +import com.intellij.testFramework.IdeaTestUtil +import com.intellij.testFramework.builders.JavaModuleFixtureBuilder +import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase + +abstract class TestOnlyInspectionTestBase : JavaCodeInsightFixtureTestCase() { + override fun setUp() { + super.setUp() + myFixture.enableInspections(inspection) + myFixture.addClass(""" + package org.jetbrains.annotations; + + import java.lang.annotation.*; + + @Documented + @Retention(RetentionPolicy.CLASS) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.TYPE}) + public @interface VisibleForTesting { } + """.trimIndent()) + + myFixture.addClass(""" + package org.jetbrains.annotations; + + import java.lang.annotation.*; + + @Documented + @Retention(RetentionPolicy.CLASS) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.TYPE}) + public @interface TestOnly { } + """.trimIndent()) + } + + override fun tuneFixture(moduleBuilder: JavaModuleFixtureBuilder<*>) { + moduleBuilder.addJdk(IdeaTestUtil.getMockJdk18Path().path) + moduleBuilder.setLanguageLevel(LanguageLevel.JDK_1_8) + } + + override fun tearDown() { + try { + myFixture.disableInspections(inspection) + } + finally { + super.tearDown() + } + } + + companion object { + private val inspection = TestOnlyInspection() + } +} \ No newline at end of file