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 index 17d77286d140..9ce9470d3b1b 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/testOnly/TestOnlyInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/testOnly/TestOnlyInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -17,7 +17,10 @@ package com.intellij.codeInspection.testOnly; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.TestFrameworks; -import com.intellij.codeInspection.*; +import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool; +import com.intellij.codeInspection.InspectionsBundle; +import com.intellij.codeInspection.ProblemHighlightType; +import com.intellij.codeInspection.ProblemsHolder; import com.intellij.lang.java.JavaLanguage; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.vfs.VirtualFile; @@ -52,35 +55,51 @@ public class TestOnlyInspection extends BaseJavaBatchLocalInspectionTool { public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder h, boolean isOnTheFly) { return new JavaElementVisitor() { @Override public void visitCallExpression(PsiCallExpression e) { - validate(e, h); + validate(e, e.resolveMethod(), 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 + || PsiTreeUtil.getParentOfType(reference, PsiImportStatementBase.class) != null) { + return; + } + PsiElement resolve = reference.resolve(); + if (resolve instanceof PsiClass) validate(reference, ((PsiClass)resolve), h); } }; } - private static void validate(PsiCallExpression e, ProblemsHolder h) { - PsiMethod method = e.resolveMethod(); + private static void validate(@NotNull PsiElement reference, @Nullable PsiMember member, ProblemsHolder h) { + if (member == null || !isAnnotatedAsTestOnly(member)) return; + if (isInsideTestOnlyMethod(reference)) return; + if (isInsideTestClass(reference)) return; + if (isUnderTestSources(reference)) return; - if (method == null || !isAnnotatedAsTestOnly(method)) return; - if (isInsideTestOnlyMethod(e)) return; - if (isInsideTestClass(e)) return; - if (isUnderTestSources(e)) return; - - PsiAnnotation anno = findVisibleForTestingAnnotation(method); + PsiAnnotation anno = findVisibleForTestingAnnotation(member); if (anno != null) { String modifier = getAccessModifierWithoutTesting(anno); if (modifier == null) { - modifier = method.hasModifierProperty(PsiModifier.PUBLIC) ? PsiModifier.PROTECTED : - method.hasModifierProperty(PsiModifier.PROTECTED) ? PsiModifier.PACKAGE_LOCAL : + modifier = member.hasModifierProperty(PsiModifier.PUBLIC) ? PsiModifier.PROTECTED : + member.hasModifierProperty(PsiModifier.PROTECTED) ? PsiModifier.PACKAGE_LOCAL : PsiModifier.PRIVATE; } - LightModifierList modList = new LightModifierList(method.getManager(), JavaLanguage.INSTANCE, modifier); - if (JavaResolveUtil.isAccessible(method, method.getContainingClass(), modList, e, null, null)) { + LightModifierList modList = new LightModifierList(member.getManager(), JavaLanguage.INSTANCE, modifier); + if (JavaResolveUtil.isAccessible(member, member.getContainingClass(), modList, reference, null, null)) { return; } } - reportProblem(e, h); + reportProblem(reference, member, h); } @Nullable @@ -97,21 +116,24 @@ public class TestOnlyInspection extends BaseJavaBatchLocalInspectionTool { } @Nullable - private static PsiAnnotation findVisibleForTestingAnnotation(@NotNull PsiMethod method) { - PsiAnnotation anno = AnnotationUtil.findAnnotation(method, "com.google.common.annotations.VisibleForTesting"); - return anno != null ? anno : AnnotationUtil.findAnnotation(method, "com.android.annotations.VisibleForTesting"); + private static PsiAnnotation findVisibleForTestingAnnotation(@NotNull PsiMember member) { + PsiAnnotation anno = AnnotationUtil.findAnnotation(member, "com.google.common.annotations.VisibleForTesting"); + return anno != null ? anno : AnnotationUtil.findAnnotation(member, "com.android.annotations.VisibleForTesting"); } - private static boolean isInsideTestOnlyMethod(PsiCallExpression e) { + private static boolean isInsideTestOnlyMethod(PsiElement e) { PsiMethod m = getTopLevelParentOfType(e, PsiMethod.class); return isAnnotatedAsTestOnly(m); } - private static boolean isAnnotatedAsTestOnly(@Nullable PsiMethod m) { - return m != null && (AnnotationUtil.isAnnotated(m, AnnotationUtil.TEST_ONLY, false, false) || findVisibleForTestingAnnotation(m) != null); + private static boolean isAnnotatedAsTestOnly(@Nullable PsiMember m) { + if (m == null) return false; + return AnnotationUtil.isAnnotated(m, AnnotationUtil.TEST_ONLY, false, false) + || findVisibleForTestingAnnotation(m) != null + || isAnnotatedAsTestOnly(m.getContainingClass()); } - private static boolean isInsideTestClass(PsiCallExpression e) { + private static boolean isInsideTestClass(PsiElement e) { PsiClass c = getTopLevelParentOfType(e, PsiClass.class); return c != null && TestFrameworks.getInstance().isTestClass(c); } @@ -128,14 +150,16 @@ public class TestOnlyInspection extends BaseJavaBatchLocalInspectionTool { while (true); } - private static boolean isUnderTestSources(PsiCallExpression e) { + 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(PsiCallExpression e, ProblemsHolder h) { - String message = InspectionsBundle.message("inspection.test.only.problems.test.only.method.call"); + private static void reportProblem(PsiElement e, PsiMember target, ProblemsHolder h) { + String message = target instanceof PsiClass + ? InspectionsBundle.message("inspection.test.only.problems.test.only.class.reference") + : InspectionsBundle.message("inspection.test.only.problems.test.only.method.call"); h.registerProblem(e, message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); } } diff --git a/java/java-tests/testData/inspection/testOnly/class/expected.xml b/java/java-tests/testData/inspection/testOnly/class/expected.xml new file mode 100644 index 000000000000..2302c74cf00f --- /dev/null +++ b/java/java-tests/testData/inspection/testOnly/class/expected.xml @@ -0,0 +1,73 @@ + + + + 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 method is called 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 method is called in production code + + + B.java + 19 + <default> + Test-only method is called in production code + + + B.java + 20 + <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 new file mode 100644 index 000000000000..515121d003cd --- /dev/null +++ b/java/java-tests/testData/inspection/testOnly/class/src/B.java @@ -0,0 +1,22 @@ +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); + } +} \ 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 new file mode 100644 index 000000000000..78282591851d --- /dev/null +++ b/java/java-tests/testData/inspection/testOnly/class/src/packageA/A.java @@ -0,0 +1,14 @@ +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/testSrc/com/intellij/codeInspection/TestOnlyInspectionTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/TestOnlyInspectionTest.java index e368d0e73e14..07d17637f218 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/TestOnlyInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/TestOnlyInspectionTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2000-2015 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.codeInspection; import com.intellij.analysis.AnalysisScope; @@ -41,6 +56,10 @@ public class TestOnlyInspectionTest extends InspectionTestCase { public void testUnresolved() throws Exception { doTest(); // shouldn't throw } + + public void testClass() throws Exception { + doTest(); + } private void doTest() throws Exception { TestOnlyInspection i = new TestOnlyInspection(); diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index 40e563729a0e..9242f80b2e93 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -154,8 +154,9 @@ inspection.nullable.problems.NotNull.parameter.overrides.not.annotated=Parameter inspection.nullable.problems.parameter.overrides.NotNull=Not annotated parameter overrides @{0} parameter inspection.nullable.problems.primitive.type.annotation=Primitive type members cannot be annotated -inspection.test.only.problems.display.name=Test-only method call in production code +inspection.test.only.problems.display.name=Test-only class or method call in production code inspection.test.only.problems.test.only.method.call=Test-only method is called in production code +inspection.test.only.problems.test.only.class.reference=Test-only class is referenced in production code inspection.visibility.display.name=Declaration access can be weaker inspection.visibility.option=Suggest package local visibility level for class members diff --git a/resources-en/src/inspectionDescriptions/TestOnlyProblems.html b/resources-en/src/inspectionDescriptions/TestOnlyProblems.html index 4410aef61075..2703c37964c0 100644 --- a/resources-en/src/inspectionDescriptions/TestOnlyProblems.html +++ b/resources-en/src/inspectionDescriptions/TestOnlyProblems.html @@ -2,8 +2,8 @@ This inspection reports usages of @TestOnly - or VisibleForTesting -annotated methods in production code. -
The problems are not reported if such method is called from +annotated methods and classes in production code. +
The problems are not reported if such method or class is referenced from