From b8d0914f35128be8bc7868399ce682c6db2d6d09 Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Tue, 5 Jul 2011 13:00:22 +0200 Subject: [PATCH] IDEA-24588 (Inspection: symbol in production code is only referenced from test code) --- .../siyeh/InspectionGadgetsBundle.properties | 4 + .../com/siyeh/ig/InspectionGadgetsPlugin.java | 2 + ...ElementOnlyUsedFromTestCodeInspection.java | 191 ++++++++++++++++++ .../ElementOnlyUsedFromTestCode.html | 8 + 4 files changed, 205 insertions(+) create mode 100644 plugins/InspectionGadgets/src/com/siyeh/ig/redundancy/ElementOnlyUsedFromTestCodeInspection.java create mode 100644 plugins/InspectionGadgets/src/inspectionDescriptions/ElementOnlyUsedFromTestCode.html diff --git a/plugins/InspectionGadgets/src/com/siyeh/InspectionGadgetsBundle.properties b/plugins/InspectionGadgets/src/com/siyeh/InspectionGadgetsBundle.properties index 81d3d7f48c05..3a8dcecb4fe9 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/InspectionGadgetsBundle.properties +++ b/plugins/InspectionGadgets/src/com/siyeh/InspectionGadgetsBundle.properties @@ -1904,3 +1904,7 @@ unnecessary.explicit.numeric.cast.quickfix=Remove cast null.thrown.display.name='null' thrown null.thrown.problem.descriptor=#ref thrown null.thrown.quickfix=Replace with 'new NullPointerException()' +element.only.used.from.test.code.display.name=Symbol only used from test code +class.only.used.from.test.code.problem.descriptor=class #ref only used from test code #loc +method.only.used.from.test.code.problem.descriptor=method #ref only called from test code #loc +field.only.used.from.test.code.problem.descriptor=field #ref only accessed from test code #loc diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/InspectionGadgetsPlugin.java b/plugins/InspectionGadgets/src/com/siyeh/ig/InspectionGadgetsPlugin.java index bd2f826922e4..b193d10f99b9 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/InspectionGadgetsPlugin.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/InspectionGadgetsPlugin.java @@ -74,6 +74,7 @@ import com.siyeh.ig.numeric.*; import com.siyeh.ig.packaging.*; import com.siyeh.ig.performance.*; import com.siyeh.ig.portability.*; +import com.siyeh.ig.redundancy.ElementOnlyUsedFromTestCodeInspection; import com.siyeh.ig.redundancy.UnusedLabelInspection; import com.siyeh.ig.resources.*; import com.siyeh.ig.security.*; @@ -985,6 +986,7 @@ public class InspectionGadgetsPlugin implements ApplicationComponent, } private void registerRedundancyInspections() { + m_inspectionClasses.add(ElementOnlyUsedFromTestCodeInspection.class); m_inspectionClasses.add(UnusedLabelInspection.class); } diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/redundancy/ElementOnlyUsedFromTestCodeInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/redundancy/ElementOnlyUsedFromTestCodeInspection.java new file mode 100644 index 000000000000..1a4ff58e143a --- /dev/null +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/redundancy/ElementOnlyUsedFromTestCodeInspection.java @@ -0,0 +1,191 @@ +/* + * Copyright 2011 Bas Leijdekkers + * + * 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.siyeh.ig.redundancy; + +import com.intellij.analysis.AnalysisScope; +import com.intellij.codeInsight.TestUtil; +import com.intellij.codeInspection.*; +import com.intellij.codeInspection.reference.*; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.siyeh.InspectionGadgetsBundle; +import com.siyeh.ig.BaseGlobalInspection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class ElementOnlyUsedFromTestCodeInspection + extends BaseGlobalInspection { + + private static final Key ONLY_USED_FROM_TEST_CODE = + Key.create("ONLY_USED_FROM_TEST_CODE"); + + @NotNull + @Override + public String getDisplayName() { + return InspectionGadgetsBundle.message( + "element.only.used.from.test.code.display.name"); + } + + @Override + @Nullable + public RefGraphAnnotator getAnnotator(RefManager refManager) { + return new ElementOnlyUsedFromTestCodeAnnotator(); + } + + @Nullable + @Override + public CommonProblemDescriptor[] checkElement( + RefEntity refEntity, AnalysisScope scope, InspectionManager manager, + GlobalInspectionContext globalContext, + ProblemDescriptionsProcessor processor) { + if (!isOnlyUsedFromTestCode(refEntity)) { + return null; + } + if (!(refEntity instanceof RefJavaElement)) { + return null; + } + final RefJavaElement javaElement = (RefJavaElement) refEntity; + if (!javaElement.isReferenced()) { + return null; + } + final PsiElement element = javaElement.getElement(); + if (element instanceof PsiClass) { + final PsiClass aClass = (PsiClass) element; + final PsiIdentifier identifier = aClass.getNameIdentifier(); + if (identifier == null) { + return null; + } + return new CommonProblemDescriptor[]{ + manager.createProblemDescriptor(identifier, + InspectionGadgetsBundle.message( + "class.only.used.from.test.code.problem.descriptor"), + true, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false) + }; + } else if (element instanceof PsiMethod) { + final PsiMethod method = (PsiMethod) element; + final PsiIdentifier identifier = method.getNameIdentifier(); + if (identifier == null) { + return null; + } + return new CommonProblemDescriptor[]{ + manager.createProblemDescriptor(identifier, + InspectionGadgetsBundle.message( + "method.only.used.from.test.code.problem.descriptor"), + true, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false) + }; + } else if (element instanceof PsiField) { + final PsiField field = (PsiField) element; + final PsiIdentifier identifier = field.getNameIdentifier(); + return new CommonProblemDescriptor[]{ + manager.createProblemDescriptor(identifier, + InspectionGadgetsBundle.message( + "field.only.used.from.test.code.problem.descriptor"), + true, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false) + }; + } + return null; + } + + private static boolean isInsideTestClass(@NotNull PsiElement e) { + final PsiClass aClass = getTopLevelParentClass(e); + return aClass != null && TestUtil.isTestClass(aClass); + } + + private static boolean isUnderTestSources(PsiElement e) { + final ProjectRootManager rootManager = + ProjectRootManager.getInstance(e.getProject()); + final VirtualFile file = e.getContainingFile().getVirtualFile(); + return file != null && + rootManager.getFileIndex().isInTestSourceContent(file); + } + + @Nullable + public static PsiClass getTopLevelParentClass(PsiElement e) { + PsiClass result = null; + PsiElement parent = e.getParent(); + while (parent != null && !(parent instanceof PsiFile)) { + if (parent instanceof PsiClass) { + result = (PsiClass)parent; + } + parent = parent.getParent(); + } + return result; + } + + private static boolean isOnlyUsedFromTestCode(RefEntity refElement) { + final Boolean usedFromTestCode = + refElement.getUserData(ONLY_USED_FROM_TEST_CODE); + return usedFromTestCode != null && usedFromTestCode.booleanValue(); + } + + private static class ElementOnlyUsedFromTestCodeAnnotator + extends RefGraphAnnotator { + + @Override + public void onMarkReferenced(RefElement refWhat, RefElement refFrom, + boolean referencedFromClassInitializer) { + if (!(refWhat instanceof RefMethod) && + !(refWhat instanceof RefField) && + !(refWhat instanceof RefClass)) { + return; + } + if (referencedFromClassInitializer || + refFrom instanceof RefImplicitConstructor) { + return; + } + final PsiElement whatElement = refWhat.getElement(); + if (isInsideTestClass(whatElement) || + isUnderTestSources(whatElement)) { + // test code itself is allowed to only be used from test code + return; + } + if (refFrom instanceof RefMethod && refWhat instanceof RefClass) { + final RefMethod method = (RefMethod) refFrom; + if (method.isConstructor() && + method.getOwnerClass() == refWhat) { + // don't count references to class from its own constructor + return; + } + } + final Boolean onlyUsedFromTestCode = + refWhat.getUserData(ONLY_USED_FROM_TEST_CODE); + if (onlyUsedFromTestCode == null) { + refWhat.putUserData(ONLY_USED_FROM_TEST_CODE, Boolean.TRUE); + } else if (!onlyUsedFromTestCode.booleanValue()) { + return; + } + final PsiElement fromElement = refFrom.getElement(); + if (isInsideTestClass(fromElement) || + isUnderTestSources(fromElement)) { + return; + } + + if (refWhat instanceof RefMethod) { + final RefMethod what = (RefMethod) refWhat; + if (what.isConstructor()) { + final RefClass ownerClass = what.getOwnerClass(); + ownerClass.putUserData(ONLY_USED_FROM_TEST_CODE, + Boolean.FALSE); + // do count references to class from its own constructor + // when that constructor is used outside of test code. + } + } + refWhat.putUserData(ONLY_USED_FROM_TEST_CODE, Boolean.FALSE); + } + } +} diff --git a/plugins/InspectionGadgets/src/inspectionDescriptions/ElementOnlyUsedFromTestCode.html b/plugins/InspectionGadgets/src/inspectionDescriptions/ElementOnlyUsedFromTestCode.html new file mode 100644 index 000000000000..4bb83235c869 --- /dev/null +++ b/plugins/InspectionGadgets/src/inspectionDescriptions/ElementOnlyUsedFromTestCode.html @@ -0,0 +1,8 @@ + + +This global inspection reports classes, methods or fields which are only used from test code. +Since this inspection requires global code analysis, it is only available in batch inspection mode. +

+New in 11, Powered by InspectionGadgets + + \ No newline at end of file