2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package org.jetbrains.plugins.groovy.codeInspection.local;
19 import com.intellij.codeHighlighting.TextEditorHighlightingPass;
20 import com.intellij.codeInsight.CodeInsightSettings;
21 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
22 import com.intellij.codeInsight.daemon.HighlightDisplayKey;
23 import com.intellij.codeInsight.daemon.impl.*;
24 import com.intellij.codeInsight.intention.IntentionAction;
25 import com.intellij.codeInspection.InspectionProfile;
26 import com.intellij.codeInspection.ProblemHighlightType;
27 import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection;
28 import com.intellij.lang.annotation.Annotation;
29 import com.intellij.lang.annotation.AnnotationHolder;
30 import com.intellij.lang.annotation.AnnotationSession;
31 import com.intellij.lang.annotation.HighlightSeverity;
32 import com.intellij.openapi.application.ApplicationManager;
33 import com.intellij.openapi.command.CommandProcessor;
34 import com.intellij.openapi.editor.Editor;
35 import com.intellij.openapi.progress.ProgressIndicator;
36 import com.intellij.openapi.project.Project;
37 import com.intellij.openapi.roots.ProjectFileIndex;
38 import com.intellij.openapi.roots.ProjectRootManager;
39 import com.intellij.openapi.util.TextRange;
40 import com.intellij.openapi.vfs.VirtualFile;
41 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
42 import com.intellij.psi.*;
43 import com.intellij.util.Processor;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.plugins.groovy.codeInspection.GroovyInspectionBundle;
46 import org.jetbrains.plugins.groovy.codeInspection.GroovyUnusedDeclarationInspection;
47 import org.jetbrains.plugins.groovy.lang.editor.GroovyImportOptimizer;
48 import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
49 import org.jetbrains.plugins.groovy.lang.psi.GrNamedElement;
50 import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement;
51 import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
52 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
53 import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
54 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
55 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
56 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
57 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
59 import java.util.ArrayList;
60 import java.util.HashSet;
61 import java.util.List;
67 public class GroovyPostHighlightingPass extends TextEditorHighlightingPass {
68 private final GroovyFile myFile;
69 private final Editor myEditor;
70 private volatile Set<GrImportStatement> myUnusedImports;
71 private volatile Runnable myOptimizeRunnable;
72 private volatile List<HighlightInfo> myUnusedDeclarations;
74 public GroovyPostHighlightingPass(GroovyFile file, Editor editor) {
75 super(file.getProject(), editor.getDocument(), true);
80 public void doCollectInformation(final ProgressIndicator progress) {
81 final InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile();
82 final boolean deadCodeEnabled = profile.isToolEnabled(HighlightDisplayKey.find(GroovyUnusedDeclarationInspection.SHORT_NAME), myFile);
83 ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
84 VirtualFile virtualFile = myFile.getViewProvider().getVirtualFile();
85 if (!fileIndex.isInContent(virtualFile)) {
88 final UnusedDeclarationInspection deadCodeInspection = (UnusedDeclarationInspection)profile.getInspectionTool(UnusedDeclarationInspection.SHORT_NAME, myFile);
89 final GlobalUsageHelper usageHelper = new GlobalUsageHelper() {
90 public boolean shouldIgnoreUsagesInCurrentFile() {
94 public boolean isLocallyUsed(@NotNull PsiNamedElement member) {
99 public boolean shouldCheckUsages(@NotNull PsiMember member) {
100 return deadCodeInspection == null || !deadCodeInspection.isEntryPoint(member);
104 final List<HighlightInfo> unusedDeclarations = new ArrayList<HighlightInfo>();
105 final Set<GrImportStatement> unusedImports = new HashSet<GrImportStatement>(GroovyImportOptimizer.getValidImportStatements(myFile));
106 myFile.accept(new PsiRecursiveElementWalkingVisitor() {
108 public void visitElement(PsiElement element) {
109 if (element instanceof GrReferenceElement) {
110 for (GroovyResolveResult result : ((GrReferenceElement)element).multiResolve(true)) {
111 GroovyPsiElement context = result.getCurrentFileResolveContext();
112 if (context instanceof GrImportStatement) {
113 GrImportStatement importStatement = (GrImportStatement)context;
114 unusedImports.remove(importStatement);
119 if (deadCodeEnabled && element instanceof GrNamedElement && !PostHighlightingPass.isImplicitUsage((GrNamedElement)element, progress)) {
120 PsiElement nameId = ((GrNamedElement)element).getNameIdentifierGroovy();
121 if (nameId.getNode().getElementType() == GroovyTokenTypes.mIDENT) {
122 String name = ((GrNamedElement)element).getName();
123 if (element instanceof GrTypeDefinition && !PostHighlightingPass.isClassUsed((GrTypeDefinition)element, progress, usageHelper)) {
124 unusedDeclarations.add(
125 PostHighlightingPass.createUnusedSymbolInfo(nameId, "Class " + name + " is unused", HighlightInfoType.UNUSED_SYMBOL));
127 else if (element instanceof GrMethod) {
128 GrMethod method = (GrMethod)element;
129 if (!PostHighlightingPass.isMethodReferenced(method, progress, usageHelper)) {
130 unusedDeclarations.add(
131 PostHighlightingPass.createUnusedSymbolInfo(nameId, (method.isConstructor() ? "Constructor" : "Method") +" " + name + " is unused", HighlightInfoType.UNUSED_SYMBOL));
134 else if (element instanceof GrField && PostHighlightingPass.isFieldUnused((GrField)element, progress, usageHelper)) {
135 unusedDeclarations.add(
136 PostHighlightingPass.createUnusedSymbolInfo(nameId, "Property " + name + " is unused", HighlightInfoType.UNUSED_SYMBOL));
141 super.visitElement(element);
144 myUnusedImports = unusedImports;
145 myUnusedDeclarations = unusedDeclarations;
146 if (!unusedImports.isEmpty() && CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) {
147 final VirtualFile vfile = myFile.getVirtualFile();
148 if (vfile != null && ProjectRootManager.getInstance(myFile.getProject()).getFileIndex().isInSource(vfile)) {
149 final GrImportStatement[] imports = myFile.getImportStatements();
150 if (imports.length > 0) {
151 final int offset = myEditor.getCaretModel().getOffset();
152 if (imports[0].getTextRange().getStartOffset() <= offset && offset <= imports[imports.length - 1].getTextRange().getEndOffset()) {
157 myOptimizeRunnable = new GroovyImportOptimizer().processFile(myFile);
163 private static IntentionAction createUnusedImportIntention() {
164 return new IntentionAction() {
167 public String getText() {
168 return GroovyInspectionBundle.message("optimize.all.imports");
172 public String getFamilyName() {
173 return GroovyInspectionBundle.message("optimize.imports");
176 public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
180 public void invoke(@NotNull final Project project, Editor editor, PsiFile file) {
181 optimizeImports(project, file);
184 public boolean startInWriteAction() {
190 public static void optimizeImports(final Project project, PsiFile file) {
191 GroovyImportOptimizer optimizer = new GroovyImportOptimizer();
192 final Runnable runnable = optimizer.processFile(file);
193 ApplicationManager.getApplication().runWriteAction(new Runnable() {
195 CommandProcessor.getInstance().executeCommand(project, runnable, "optimize imports", this);
200 public void doApplyInformationToEditor() {
201 if (myUnusedDeclarations == null || myUnusedImports == null) {
205 AnnotationHolder annotationHolder = new AnnotationHolderImpl(new AnnotationSession(myFile));
206 List<HighlightInfo> infos = new ArrayList<HighlightInfo>(myUnusedDeclarations);
207 for (GrImportStatement unusedImport : myUnusedImports) {
208 Annotation annotation = annotationHolder.createWarningAnnotation(unusedImport, GroovyInspectionBundle.message("unused.import"));
209 annotation.setHighlightType(ProblemHighlightType.LIKE_UNUSED_SYMBOL);
210 annotation.registerFix(createUnusedImportIntention());
211 infos.add(HighlightInfo.fromAnnotation(annotation));
214 UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, 0, myFile.getTextLength(), infos, getColorsScheme(), getId());
216 final Runnable optimize = myOptimizeRunnable;
217 if (optimize != null && timeToOptimizeImports()) {
218 PostHighlightingPass.invokeOnTheFlyImportOptimizer(new Runnable() {
223 }, myFile, myEditor);
227 private boolean timeToOptimizeImports() {
228 if (!CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) return false;
230 DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject);
231 if (!codeAnalyzer.isHighlightingAvailable(myFile)) return false;
233 if (!codeAnalyzer.isErrorAnalyzingFinished(myFile)) return false;
234 boolean errors = containsErrorsPreventingOptimize();
236 return !errors && codeAnalyzer.canChangeFileSilently(myFile);
239 private boolean containsErrorsPreventingOptimize() {
240 // ignore unresolved imports errors
241 final TextRange ignoreRange;
242 final GrImportStatement[] imports = myFile.getImportStatements();
243 if (imports.length != 0) {
244 final int start = imports[0].getTextRange().getStartOffset();
245 final int end = imports[imports.length - 1].getTextRange().getEndOffset();
246 ignoreRange = new TextRange(start, end);
248 ignoreRange = TextRange.EMPTY_RANGE;
251 return !DaemonCodeAnalyzerImpl.processHighlights(myDocument, myProject, HighlightSeverity.ERROR, 0, myDocument.getTextLength(), new Processor<HighlightInfo>() {
252 public boolean process(HighlightInfo error) {
253 int infoStart = error.getActualStartOffset();
254 int infoEnd = error.getActualEndOffset();
256 return ignoreRange.containsRange(infoStart,infoEnd) && error.type.equals(HighlightInfoType.WRONG_REF);