highlight globally unused groovy classes (IDEA-75803)
authorpeter <peter@jetbrains.com>
Mon, 30 Jan 2012 18:24:59 +0000 (19:24 +0100)
committerpeter <peter@jetbrains.com>
Tue, 31 Jan 2012 17:30:24 +0000 (18:30 +0100)
java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java
plugins/groovy/src/META-INF/plugin.xml
plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/GroovyUnusedDeclarationInspection.java [new file with mode: 0644]
plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/local/GroovyPostHighlightingPass.java [moved from plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/local/GroovyUnusedImportPass.java with 80% similarity]
plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/local/GroovyUnusedImportsPassFactory.java

index 5a429287899606d3858c578ef68662e77f6d09e8..48b7f0ee5b2525a089db7c2c4a39b036f9638ab8 100644 (file)
@@ -378,7 +378,7 @@ public class PostHighlightingPass extends TextEditorHighlightingPass {
     return UnusedSymbolLocalInspection.isInjected(element);
   }
 
-  private static HighlightInfo createUnusedSymbolInfo(PsiElement element, String message, final HighlightInfoType highlightInfoType) {
+  public static HighlightInfo createUnusedSymbolInfo(PsiElement element, String message, final HighlightInfoType highlightInfoType) {
     HighlightInfo info = HighlightInfo.createHighlightInfo(highlightInfoType, element, message);
     UnusedDeclarationFixProvider[] fixProviders = Extensions.getExtensions(UnusedDeclarationFixProvider.EP_NAME);
     for (UnusedDeclarationFixProvider provider : fixProviders) {
@@ -436,6 +436,9 @@ public class PostHighlightingPass extends TextEditorHighlightingPass {
       return null;
     }
     else if (!myRefCountHolder.isReferenced(field) && weAreSureThereAreNoUsages(field, progress)) {
+      if (field instanceof PsiEnumConstant && isEnumValuesMethodUsed(field, progress)) {
+        return null;
+      }
       return formatUnusedSymbolHighlightInfo("field.is.not.used", field, "fields", myDeadCodeKey, myDeadCodeInfoType);
     }
     return null;
@@ -580,49 +583,31 @@ public class PostHighlightingPass extends TextEditorHighlightingPass {
     if (!myDeadCodeEnabled) return false;
     if (myDeadCodeInspection.isEntryPoint(member)) return false;
 
-    String name = member.getName();
+    return isGloballyUnused(member, progress, myFile, member.getName());
+  }
+
+  public static boolean isGloballyUnused(PsiMember member, ProgressIndicator progress, @Nullable PsiFile fileToIgnoreOccurrencesIn, String name) {
     if (name == null) return false;
     SearchScope useScope = member.getUseScope();
     if (!(useScope instanceof GlobalSearchScope)) return false;
     GlobalSearchScope scope = (GlobalSearchScope)useScope;
     // some classes may have references from within XML outside dependent modules, e.g. our actions
-    if (member instanceof PsiClass) scope = GlobalSearchScope.projectScope(myProject).uniteWith(scope);
+    Project project = member.getProject();
+    if (member instanceof PsiClass) scope = GlobalSearchScope.projectScope(project).uniteWith(scope);
 
-    PsiSearchHelper.SearchCostResult cheapEnough = PsiSearchHelper.SERVICE.getInstance(myFile.getProject())
-        .isCheapEnoughToSearch(name, scope, myFile, progress);
+    PsiSearchHelper.SearchCostResult cheapEnough = PsiSearchHelper.SERVICE.getInstance(project).isCheapEnoughToSearch(name, scope, fileToIgnoreOccurrencesIn, progress);
     if (cheapEnough == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) return false;
 
     //search usages if it cheap
     //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before
     if (cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES) {
-      if (member instanceof PsiEnumConstant) {
-        return !isEnumValuesMethodUsed(member, progress);
-      }
       if (!canBeReferencedViaWeirdNames(member)) return true;
     }
-    FindUsagesManager findUsagesManager = ((FindManagerImpl)FindManager.getInstance(myProject)).getFindUsagesManager();
-    FindUsagesOptions findUsagesOptions;
-    if (member instanceof PsiClass) {
-      findUsagesOptions = new JavaClassFindUsagesOptions(myProject);
-    }
-    else if (member instanceof PsiMethod) {
-      findUsagesOptions = new JavaMethodFindUsagesOptions(myProject);
-    }
-    else if (member instanceof PsiField) {
-      findUsagesOptions = new JavaVariableFindUsagesOptions(myProject);
-    }
-    else {
-      LOG.error("unknown member: " + member);
-      return false;
-    }
+    FindUsagesManager findUsagesManager = ((FindManagerImpl)FindManager.getInstance(project)).getFindUsagesManager();
+    FindUsagesHandler handler = new JavaFindUsagesHandler(member, new JavaFindUsagesHandlerFactory(project));
+    FindUsagesOptions findUsagesOptions = handler.getFindUsagesOptions();
     findUsagesOptions.searchScope = scope;
-
-    boolean used = findUsagesManager.isUsed(member, findUsagesOptions);
-
-    if (!used && member instanceof PsiEnumConstant) {
-      return !isEnumValuesMethodUsed(member, progress);
-    }
-    return !used;
+    return !findUsagesManager.isUsed(member, findUsagesOptions);
   }
 
   private boolean isEnumValuesMethodUsed(PsiMember member, ProgressIndicator progress) {
index 9a1384fa312ad103ac6912e0938accf5266158fb..63f176ae28104d8cff5bf53cd30a221c45d9d19b 100644 (file)
     <copyPastePreProcessor implementation="org.jetbrains.plugins.groovy.lang.editor.GroovyLiteralCopyPasteProcessor"/>
     <copyPastePostProcessor implementation="org.jetbrains.plugins.groovy.lang.editor.GroovyReferenceCopyPasteProcessor"/>
 
+    <specialTool shortName="GroovyUnusedDeclaration" displayName="Unused declaration"
+                 groupName="Declaration Redundancy" enabledByDefault="true" level="WARNING"
+                 implementationClass="org.jetbrains.plugins.groovy.codeInspection.GroovyUnusedDeclarationInspection"/>
+
     <localInspection language="Groovy" groupPath="Groovy" shortName="SecondUnsafeCall" bundle="org.jetbrains.plugins.groovy.codeInspection.GroovyInspectionBundle"
                      key="second.unsafe.call" groupName="Probable bugs" enabledByDefault="true" level="WARNING"
                      implementationClass="org.jetbrains.plugins.groovy.codeInspection.secondUnsafeCall.SecondUnsafeCallInspection"/>
diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/GroovyUnusedDeclarationInspection.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/GroovyUnusedDeclarationInspection.java
new file mode 100644 (file)
index 0000000..3b953f6
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2012 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 org.jetbrains.plugins.groovy.codeInspection;
+
+import com.intellij.analysis.AnalysisScope;
+import com.intellij.codeInspection.GlobalInspectionContext;
+import com.intellij.codeInspection.InspectionManager;
+import com.intellij.codeInspection.ex.DescriptorProviderInspection;
+import com.intellij.codeInspection.ex.JobDescriptor;
+import com.intellij.codeInspection.ex.UnfairLocalInspectionTool;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author peter
+ */
+public class GroovyUnusedDeclarationInspection extends DescriptorProviderInspection implements UnfairLocalInspectionTool {
+  public static final String SHORT_NAME = "GroovyUnusedDeclaration";
+
+  @Override
+  public void runInspection(@NotNull AnalysisScope scope, @NotNull InspectionManager manager) {
+  }
+
+  @NotNull
+  @Override
+  public JobDescriptor[] getJobDescriptors(GlobalInspectionContext globalInspectionContext) {
+    return JobDescriptor.EMPTY_ARRAY;
+  }
+
+}
similarity index 80%
rename from plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/local/GroovyUnusedImportPass.java
rename to plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/local/GroovyPostHighlightingPass.java
index 818f4badb3d375c0d6a99a7b4d4ef81f93f87824..d3f3b5f4033008e9235200c0e453e928a1739fef 100644 (file)
@@ -19,8 +19,10 @@ package org.jetbrains.plugins.groovy.codeInspection.local;
 import com.intellij.codeHighlighting.TextEditorHighlightingPass;
 import com.intellij.codeInsight.CodeInsightSettings;
 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
+import com.intellij.codeInsight.daemon.HighlightDisplayKey;
 import com.intellij.codeInsight.daemon.impl.*;
 import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.codeInspection.InspectionProfile;
 import com.intellij.codeInspection.ProblemHighlightType;
 import com.intellij.lang.annotation.Annotation;
 import com.intellij.lang.annotation.AnnotationHolder;
@@ -28,24 +30,28 @@ import com.intellij.lang.annotation.AnnotationSession;
 import com.intellij.lang.annotation.HighlightSeverity;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.command.CommandProcessor;
-import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectFileIndex;
 import com.intellij.openapi.roots.ProjectRootManager;
 import com.intellij.openapi.util.TextRange;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
 import com.intellij.psi.PsiRecursiveElementWalkingVisitor;
 import com.intellij.util.Processor;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.plugins.groovy.codeInspection.GroovyInspectionBundle;
+import org.jetbrains.plugins.groovy.codeInspection.GroovyUnusedDeclarationInspection;
 import org.jetbrains.plugins.groovy.lang.editor.GroovyImportOptimizer;
+import org.jetbrains.plugins.groovy.lang.psi.GrNamedElement;
 import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement;
 import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
 import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
 
 import java.util.ArrayList;
@@ -56,20 +62,30 @@ import java.util.Set;
 /**
  * @author ilyas
  */
-public class GroovyUnusedImportPass extends TextEditorHighlightingPass {
+public class GroovyPostHighlightingPass extends TextEditorHighlightingPass {
   private final GroovyFile myFile;
   private final Editor myEditor;
-  public static final Logger LOG = Logger.getInstance("org.jetbrains.plugins.groovy.codeInspection.local.GroovyUnusedImportsPass");
   private volatile Set<GrImportStatement> myUnusedImports;
   private volatile Runnable myOptimizeRunnable;
+  private volatile List<HighlightInfo> myUnusedDeclarations;
 
-  public GroovyUnusedImportPass(GroovyFile file, Editor editor) {
+  public GroovyPostHighlightingPass(GroovyFile file, Editor editor) {
     super(file.getProject(), editor.getDocument(), true);
     myFile = file;
     myEditor = editor;
   }
 
-  public void doCollectInformation(ProgressIndicator progress) {
+  public void doCollectInformation(final ProgressIndicator progress) {
+    InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile();
+    final boolean deadCodeEnabled = profile.isToolEnabled(HighlightDisplayKey.find(GroovyUnusedDeclarationInspection.SHORT_NAME), myFile);
+    ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
+    VirtualFile virtualFile = myFile.getViewProvider().getVirtualFile();
+    if (!fileIndex.isInContent(virtualFile)) {
+      return;
+    }
+
+
+    final List<HighlightInfo> unusedDeclarations = new ArrayList<HighlightInfo>();
     final Set<GrImportStatement> unusedImports = new HashSet<GrImportStatement>(GroovyImportOptimizer.getValidImportStatements(myFile));
     myFile.accept(new PsiRecursiveElementWalkingVisitor() {
       @Override
@@ -83,10 +99,21 @@ public class GroovyUnusedImportPass extends TextEditorHighlightingPass {
             }
           }
         }
+
+        if (deadCodeEnabled && element instanceof GrNamedElement) {
+          PsiElement nameId = ((GrNamedElement)element).getNameIdentifierGroovy();
+          String name = ((GrNamedElement)element).getName();
+          if (element instanceof GrTypeDefinition && PostHighlightingPass.isGloballyUnused((GrTypeDefinition)element, progress, null, name)) {
+            unusedDeclarations.add(
+              PostHighlightingPass.createUnusedSymbolInfo(nameId, "Class " + name + " is unused", HighlightInfoType.UNUSED_SYMBOL));
+          }
+        }
+        
         super.visitElement(element);
       }
     });
     myUnusedImports = unusedImports;
+    myUnusedDeclarations = unusedDeclarations;
     if (!unusedImports.isEmpty() && CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) {
       final VirtualFile vfile = myFile.getVirtualFile();
       if (vfile != null && ProjectRootManager.getInstance(myFile.getProject()).getFileIndex().isInSource(vfile)) {
@@ -143,7 +170,7 @@ public class GroovyUnusedImportPass extends TextEditorHighlightingPass {
 
   public void doApplyInformationToEditor() {
     AnnotationHolder annotationHolder = new AnnotationHolderImpl(new AnnotationSession(myFile));
-    List<HighlightInfo> infos = new ArrayList<HighlightInfo>(myUnusedImports.size());
+    List<HighlightInfo> infos = new ArrayList<HighlightInfo>(myUnusedDeclarations);
     for (GrImportStatement unusedImport : myUnusedImports) {
       Annotation annotation = annotationHolder.createWarningAnnotation(unusedImport, GroovyInspectionBundle.message("unused.import"));
       annotation.setHighlightType(ProblemHighlightType.LIKE_UNUSED_SYMBOL);
index 2ea820e498b7f370c4cc2daac5ca0b087728ca4e..593564cf1bd03744a2e2ca5bbdb6fe7356169d12 100644 (file)
@@ -42,7 +42,7 @@ public class GroovyUnusedImportsPassFactory extends AbstractProjectComponent imp
   @Nullable
   public TextEditorHighlightingPass createHighlightingPass(@NotNull PsiFile file, @NotNull Editor editor) {
     if (!(file instanceof GroovyFile)) return null;
-    return new GroovyUnusedImportPass((GroovyFile)file, editor);
+    return new GroovyPostHighlightingPass((GroovyFile)file, editor);
   }
 
   @NonNls