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