don't error-highlight groovy builder members as unresolved
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / codeInspection / local / GroovyPostHighlightingPass.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package org.jetbrains.plugins.groovy.codeInspection.local;
18
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.lang.annotation.Annotation;
28 import com.intellij.lang.annotation.AnnotationHolder;
29 import com.intellij.lang.annotation.AnnotationSession;
30 import com.intellij.lang.annotation.HighlightSeverity;
31 import com.intellij.openapi.application.ApplicationManager;
32 import com.intellij.openapi.command.CommandProcessor;
33 import com.intellij.openapi.editor.Editor;
34 import com.intellij.openapi.progress.ProgressIndicator;
35 import com.intellij.openapi.project.Project;
36 import com.intellij.openapi.roots.ProjectFileIndex;
37 import com.intellij.openapi.roots.ProjectRootManager;
38 import com.intellij.openapi.util.TextRange;
39 import com.intellij.openapi.vfs.VirtualFile;
40 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
41 import com.intellij.psi.PsiElement;
42 import com.intellij.psi.PsiFile;
43 import com.intellij.psi.PsiRecursiveElementWalkingVisitor;
44 import com.intellij.util.Processor;
45 import org.jetbrains.annotations.NotNull;
46 import org.jetbrains.plugins.groovy.codeInspection.GroovyInspectionBundle;
47 import org.jetbrains.plugins.groovy.codeInspection.GroovyUnusedDeclarationInspection;
48 import org.jetbrains.plugins.groovy.lang.editor.GroovyImportOptimizer;
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.typedef.GrTypeDefinition;
55 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
56
57 import java.util.ArrayList;
58 import java.util.HashSet;
59 import java.util.List;
60 import java.util.Set;
61
62 /**
63  * @author ilyas
64  */
65 public class GroovyPostHighlightingPass extends TextEditorHighlightingPass {
66   private final GroovyFile myFile;
67   private final Editor myEditor;
68   private volatile Set<GrImportStatement> myUnusedImports;
69   private volatile Runnable myOptimizeRunnable;
70   private volatile List<HighlightInfo> myUnusedDeclarations;
71
72   public GroovyPostHighlightingPass(GroovyFile file, Editor editor) {
73     super(file.getProject(), editor.getDocument(), true);
74     myFile = file;
75     myEditor = editor;
76   }
77
78   public void doCollectInformation(final ProgressIndicator progress) {
79     InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile();
80     final boolean deadCodeEnabled = profile.isToolEnabled(HighlightDisplayKey.find(GroovyUnusedDeclarationInspection.SHORT_NAME), myFile);
81     ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
82     VirtualFile virtualFile = myFile.getViewProvider().getVirtualFile();
83     if (!fileIndex.isInContent(virtualFile)) {
84       return;
85     }
86
87
88     final List<HighlightInfo> unusedDeclarations = new ArrayList<HighlightInfo>();
89     final Set<GrImportStatement> unusedImports = new HashSet<GrImportStatement>(GroovyImportOptimizer.getValidImportStatements(myFile));
90     myFile.accept(new PsiRecursiveElementWalkingVisitor() {
91       @Override
92       public void visitElement(PsiElement element) {
93         if (element instanceof GrReferenceElement) {
94           for (GroovyResolveResult result : ((GrReferenceElement)element).multiResolve(true)) {
95             GroovyPsiElement context = result.getCurrentFileResolveContext();
96             if (context instanceof GrImportStatement) {
97               GrImportStatement importStatement = (GrImportStatement)context;
98               unusedImports.remove(importStatement);
99             }
100           }
101         }
102
103         if (deadCodeEnabled && element instanceof GrNamedElement) {
104           PsiElement nameId = ((GrNamedElement)element).getNameIdentifierGroovy();
105           String name = ((GrNamedElement)element).getName();
106           if (element instanceof GrTypeDefinition && PostHighlightingPass.isGloballyUnused((GrTypeDefinition)element, progress, null, name)) {
107             unusedDeclarations.add(
108               PostHighlightingPass.createUnusedSymbolInfo(nameId, "Class " + name + " is unused", HighlightInfoType.UNUSED_SYMBOL));
109           }
110         }
111         
112         super.visitElement(element);
113       }
114     });
115     myUnusedImports = unusedImports;
116     myUnusedDeclarations = unusedDeclarations;
117     if (!unusedImports.isEmpty() && CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) {
118       final VirtualFile vfile = myFile.getVirtualFile();
119       if (vfile != null && ProjectRootManager.getInstance(myFile.getProject()).getFileIndex().isInSource(vfile)) {
120         final GrImportStatement[] imports = myFile.getImportStatements();
121         if (imports.length > 0) {
122           final int offset = myEditor.getCaretModel().getOffset();
123           if (imports[0].getTextRange().getStartOffset() <= offset && offset <= imports[imports.length - 1].getTextRange().getEndOffset()) {
124             return;
125           }
126         }
127
128         myOptimizeRunnable = new GroovyImportOptimizer().processFile(myFile);
129       }
130     }
131
132   }
133
134   private static IntentionAction createUnusedImportIntention() {
135     return new IntentionAction() {
136
137       @NotNull
138       public String getText() {
139         return GroovyInspectionBundle.message("optimize.all.imports");
140       }
141
142       @NotNull
143       public String getFamilyName() {
144         return GroovyInspectionBundle.message("optimize.imports");
145       }
146
147       public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
148         return true;
149       }
150
151       public void invoke(@NotNull final Project project, Editor editor, PsiFile file) {
152         optimizeImports(project, file);
153       }
154
155       public boolean startInWriteAction() {
156         return true;
157       }
158     };
159   }
160
161   public static void optimizeImports(final Project project, PsiFile file) {
162     GroovyImportOptimizer optimizer = new GroovyImportOptimizer();
163     final Runnable runnable = optimizer.processFile(file);
164     ApplicationManager.getApplication().runWriteAction(new Runnable() {
165       public void run() {
166         CommandProcessor.getInstance().executeCommand(project, runnable, "optimize imports", this);
167       }
168     });
169   }
170
171   public void doApplyInformationToEditor() {
172     AnnotationHolder annotationHolder = new AnnotationHolderImpl(new AnnotationSession(myFile));
173     List<HighlightInfo> infos = new ArrayList<HighlightInfo>(myUnusedDeclarations);
174     for (GrImportStatement unusedImport : myUnusedImports) {
175       Annotation annotation = annotationHolder.createWarningAnnotation(unusedImport, GroovyInspectionBundle.message("unused.import"));
176       annotation.setHighlightType(ProblemHighlightType.LIKE_UNUSED_SYMBOL);
177       annotation.registerFix(createUnusedImportIntention());
178       infos.add(HighlightInfo.fromAnnotation(annotation));
179     }
180
181     UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, 0, myFile.getTextLength(), infos, getColorsScheme(), getId());
182
183     final Runnable optimize = myOptimizeRunnable;
184     if (optimize != null && timeToOptimizeImports()) {
185       PostHighlightingPass.invokeOnTheFlyImportOptimizer(new Runnable() {
186         @Override
187         public void run() {
188           optimize.run();
189         }
190       },myFile,myEditor);
191     }
192   }
193
194   private boolean timeToOptimizeImports() {
195     if (!CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) return false;
196
197     DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject);
198     if (!codeAnalyzer.isHighlightingAvailable(myFile)) return false;
199
200     if (!codeAnalyzer.isErrorAnalyzingFinished(myFile)) return false;
201     boolean errors = containsErrorsPreventingOptimize();
202
203     return !errors && codeAnalyzer.canChangeFileSilently(myFile);
204   }
205
206   private boolean containsErrorsPreventingOptimize() {
207     // ignore unresolved imports errors
208     final TextRange ignoreRange;
209     final GrImportStatement[] imports = myFile.getImportStatements();
210     if (imports.length != 0) {
211       final int start = imports[0].getTextRange().getStartOffset();
212       final int end = imports[imports.length - 1].getTextRange().getEndOffset();
213       ignoreRange = new TextRange(start, end);
214     } else {
215       ignoreRange = TextRange.EMPTY_RANGE;
216     }
217
218     return !DaemonCodeAnalyzerImpl.processHighlights(myDocument, myProject, HighlightSeverity.ERROR, 0, myDocument.getTextLength(), new Processor<HighlightInfo>() {
219       public boolean process(HighlightInfo error) {
220         int infoStart = error.getActualStartOffset();
221         int infoEnd = error.getActualEndOffset();
222
223         return ignoreRange.containsRange(infoStart,infoEnd) && error.type.equals(HighlightInfoType.WRONG_REF);
224       }
225     });
226   }
227
228
229 }