call inspectionFinished only once: IDEA-136834 LocalInspectionTool: inspectionFinishe...
[idea/community.git] / platform / analysis-api / src / com / intellij / codeInspection / ProblemsHolder.java
1 /*
2  * Copyright 2000-2015 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 com.intellij.codeInspection;
18
19 import com.intellij.BundleBase;
20 import com.intellij.codeInsight.CodeInsightBundle;
21 import com.intellij.codeInsight.daemon.EmptyResolveMessageProvider;
22 import com.intellij.lang.injection.InjectedLanguageManager;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.util.TextRange;
26 import com.intellij.openapi.util.io.FileUtil;
27 import com.intellij.openapi.vfs.VirtualFile;
28 import com.intellij.psi.ExternallyDefinedPsiElement;
29 import com.intellij.psi.PsiElement;
30 import com.intellij.psi.PsiFile;
31 import com.intellij.psi.PsiReference;
32 import com.intellij.psi.util.PsiTreeUtil;
33 import com.intellij.xml.util.XmlStringUtil;
34 import org.jetbrains.annotations.Nls;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
37
38 import java.util.ArrayList;
39 import java.util.List;
40
41 /**
42  * @author max
43  */
44 public class ProblemsHolder {
45   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ProblemsHolder");
46   private final InspectionManager myManager;
47   private final PsiFile myFile;
48   private final boolean myOnTheFly;
49   private final List<ProblemDescriptor> myProblems = new ArrayList<ProblemDescriptor>();
50
51   public ProblemsHolder(@NotNull InspectionManager manager, @NotNull PsiFile file, boolean onTheFly) {
52     myManager = manager;
53     myFile = file;
54     myOnTheFly = onTheFly;
55   }
56
57   public void registerProblem(@NotNull PsiElement psiElement, @NotNull @Nls(capitalization = Nls.Capitalization.Sentence) String descriptionTemplate, LocalQuickFix... fixes) {
58     registerProblem(psiElement, descriptionTemplate, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, fixes);
59   }
60
61   public void registerProblem(@NotNull PsiElement psiElement,
62                               @NotNull @Nls(capitalization = Nls.Capitalization.Sentence) String descriptionTemplate,
63                               ProblemHighlightType highlightType,
64                               LocalQuickFix... fixes) {
65     registerProblem(myManager.createProblemDescriptor(psiElement, descriptionTemplate, myOnTheFly, fixes, highlightType));
66   }
67
68   public void registerProblem(@NotNull ProblemDescriptor problemDescriptor) {
69     PsiElement element = problemDescriptor.getPsiElement();
70     if (element != null && !isInPsiFile(element)) {
71       ExternallyDefinedPsiElement external = PsiTreeUtil.getParentOfType(element, ExternallyDefinedPsiElement.class, false);
72       if (external != null) {
73         PsiElement newTarget = external.getProblemTarget();
74         if (newTarget != null) {
75           redirectProblem(problemDescriptor, newTarget);
76           return;
77         }
78       }
79
80       PsiFile containingFile = element.getContainingFile();
81       PsiElement context = InjectedLanguageManager.getInstance(containingFile.getProject()).getInjectionHost(containingFile);
82       PsiElement myContext = InjectedLanguageManager.getInstance(myFile.getProject()).getInjectionHost(myFile);
83       LOG.error("Reported element " + element + " is not from the file '" + myFile + "' the inspection was invoked for. Message: '" + problemDescriptor.getDescriptionTemplate()+"'.\n" +
84                 "Element' containing file: "+ containingFile +"; context: "+(context == null ? null : context.getContainingFile())+"\n"
85                 +"Inspection invoked for file: "+ myFile +"; context: "+(myContext == null ? null : myContext.getContainingFile())+"\n"
86                 );
87     }
88
89     myProblems.add(problemDescriptor);
90   }
91
92   private boolean isInPsiFile(@NotNull PsiElement element) {
93     PsiFile file = element.getContainingFile();
94     return myFile.getViewProvider() == file.getViewProvider();
95   }
96
97   private void redirectProblem(@NotNull final ProblemDescriptor problem, @NotNull final PsiElement target) {
98     final PsiElement original = problem.getPsiElement();
99     final VirtualFile vFile = original.getContainingFile().getVirtualFile();
100     assert vFile != null;
101     final String path = FileUtil.toSystemIndependentName(vFile.getPath());
102
103     String description = XmlStringUtil.stripHtml(problem.getDescriptionTemplate());
104
105     final String template =
106       InspectionsBundle.message("inspection.redirect.template",
107                                 description, path, original.getTextRange().getStartOffset(), vFile.getName());
108
109
110     final InspectionManager manager = InspectionManager.getInstance(original.getProject());
111     final ProblemDescriptor newProblem =
112       manager.createProblemDescriptor(target, template, (LocalQuickFix)null, problem.getHighlightType(), isOnTheFly());
113     registerProblem(newProblem);
114   }
115
116   public void registerProblem(@NotNull PsiReference reference, String descriptionTemplate, ProblemHighlightType highlightType) {
117     LocalQuickFix[] fixes = null;
118     if (reference instanceof LocalQuickFixProvider) {
119       fixes = ((LocalQuickFixProvider)reference).getQuickFixes();
120     }
121     registerProblemForReference(reference, highlightType, descriptionTemplate, fixes);
122   }
123
124   public void registerProblemForReference(@NotNull PsiReference reference,
125                               ProblemHighlightType highlightType,
126                               String descriptionTemplate,
127                               LocalQuickFix... fixes) {
128     ProblemDescriptor descriptor = myManager.createProblemDescriptor(reference.getElement(), reference.getRangeInElement(), descriptionTemplate, highlightType,
129                                         myOnTheFly, fixes);
130     registerProblem(descriptor);
131   }
132
133   public void registerProblem(@NotNull PsiReference reference) {
134     registerProblem(reference, unresolvedReferenceMessage(reference), ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
135   }
136
137   public static String unresolvedReferenceMessage(PsiReference reference) {
138     String message;
139     if (reference instanceof EmptyResolveMessageProvider) {
140       String pattern = ((EmptyResolveMessageProvider)reference).getUnresolvedMessagePattern();
141       try {
142         message = BundleBase.format(pattern, reference.getCanonicalText()); // avoid double formatting
143       }
144       catch (IllegalArgumentException ex) {
145         // unresolvedMessage provided by third-party reference contains wrong format string (e.g. {}), tolerate it
146         message = pattern;
147         LOG.info(pattern);
148       }
149     }
150     else {
151       message = CodeInsightBundle.message("error.cannot.resolve.default.message", reference.getCanonicalText());
152     }
153     return message;
154   }
155
156   /**
157    * Creates highlighter for the specified place in the file.
158    * @param psiElement The highlighter will be created at the text range od this element. This psiElement must be in the current file.
159    * @param message Message for this highlighter. Will also serve as a tooltip.
160    * @param highlightType The level of highlighter.
161    * @param rangeInElement The (sub)range (must be inside (0..psiElement.getTextRange().getLength()) to create highlighter in.
162    *                       If you want to highlight only part of the supplied psiElement. Pass null otherwise.
163    * @param fixes (Optional) fixes to appear for this highlighter.
164    */
165   public void registerProblem(@NotNull final PsiElement psiElement,
166                               @NotNull final String message,
167                               final ProblemHighlightType highlightType,
168                               @Nullable TextRange rangeInElement,
169                               final LocalQuickFix... fixes) {
170
171     final ProblemDescriptor descriptor = myManager.createProblemDescriptor(psiElement, rangeInElement, message, highlightType, myOnTheFly,
172                                                                            fixes);
173     registerProblem(descriptor);
174   }
175
176   public void registerProblem(@NotNull final PsiElement psiElement,
177                               final TextRange rangeInElement,
178                               @NotNull final String message,
179                               final LocalQuickFix... fixes) {
180     final ProblemDescriptor descriptor = myManager.createProblemDescriptor(psiElement, rangeInElement, message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, myOnTheFly, fixes);
181     registerProblem(descriptor);
182   }
183
184   @NotNull
185   public List<ProblemDescriptor> getResults() {
186     return myProblems;
187   }
188
189   @NotNull
190   public ProblemDescriptor[] getResultsArray() {
191     final List<ProblemDescriptor> problems = getResults();
192     return problems.toArray(new ProblemDescriptor[problems.size()]);
193   }
194
195   @NotNull
196   public final InspectionManager getManager() {
197     return myManager;
198   }
199
200   public boolean hasResults() {
201     return !myProblems.isEmpty();
202   }
203
204   public int getResultCount() {
205     return myProblems.size();
206   }
207
208   public boolean isOnTheFly() {
209     return myOnTheFly;
210   }
211
212   @NotNull
213   public PsiFile getFile() {
214     return myFile;
215   }
216
217   @NotNull
218   public final Project getProject() {
219     return myManager.getProject();
220   }
221 }