df1fe781baa1d497b16ff0220848776174a0fd1e
[idea/community.git] / java / java-analysis-impl / src / com / intellij / codeInspection / RedundantSuppressInspectionBase.java
1 /*
2  * Copyright 2000-2016 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 package com.intellij.codeInspection;
17
18 import com.intellij.analysis.AnalysisScope;
19 import com.intellij.codeInsight.daemon.GroupNames;
20 import com.intellij.codeInsight.daemon.impl.RemoveSuppressWarningAction;
21 import com.intellij.codeInspection.deadCode.UnusedDeclarationInspectionBase;
22 import com.intellij.codeInspection.ex.GlobalInspectionContextBase;
23 import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper;
24 import com.intellij.codeInspection.ex.InspectionToolWrapper;
25 import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
26 import com.intellij.codeInspection.reference.*;
27 import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.util.WriteExternalException;
30 import com.intellij.openapi.util.text.StringUtil;
31 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
32 import com.intellij.psi.*;
33 import com.intellij.psi.util.PsiTreeUtil;
34 import com.intellij.util.containers.BidirectionalMap;
35 import com.intellij.util.containers.ContainerUtil;
36 import gnu.trove.THashMap;
37 import org.jdom.Element;
38 import org.jetbrains.annotations.NonNls;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
41
42 import javax.swing.*;
43 import java.util.*;
44
45 /**
46  * @author cdr
47  */
48 public class RedundantSuppressInspectionBase extends GlobalInspectionTool {
49   private BidirectionalMap<String, QuickFix> myQuickFixes;
50   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.RedundantSuppressInspection");
51
52   public boolean IGNORE_ALL;
53
54   @Override
55   @NotNull
56   public String getGroupDisplayName() {
57     return GroupNames.DECLARATION_REDUNDANCY;
58   }
59
60   @Override
61   @NotNull
62   public String getDisplayName() {
63     return InspectionsBundle.message("inspection.redundant.suppression.name");
64   }
65
66   @Override
67   @NotNull
68   @NonNls
69   public String getShortName() {
70     return "RedundantSuppression";
71   }
72
73   @Override
74   public JComponent createOptionsPanel() {
75     return new SingleCheckboxOptionsPanel("Ignore @SuppressWarning(\"ALL\")", this, "IGNORE_ALL");
76   }
77
78   @Override
79   public void writeSettings(@NotNull Element node) throws WriteExternalException {
80     if (IGNORE_ALL) {
81       super.writeSettings(node);
82     }
83   }
84
85   @Override
86   public void runInspection(@NotNull final AnalysisScope scope,
87                             @NotNull final InspectionManager manager,
88                             @NotNull final GlobalInspectionContext globalContext,
89                             @NotNull final ProblemDescriptionsProcessor problemDescriptionsProcessor) {
90     globalContext.getRefManager().iterate(new RefJavaVisitor() {
91       @Override public void visitClass(@NotNull RefClass refClass) {
92         if (!globalContext.shouldCheck(refClass, RedundantSuppressInspectionBase.this)) return;
93         CommonProblemDescriptor[] descriptors = checkElement(refClass, manager);
94         if (descriptors != null) {
95           for (CommonProblemDescriptor descriptor : descriptors) {
96             if (descriptor instanceof ProblemDescriptor) {
97               final PsiElement psiElement = ((ProblemDescriptor)descriptor).getPsiElement();
98               final PsiMember member = PsiTreeUtil.getParentOfType(psiElement, PsiMember.class);
99               final RefElement refElement = globalContext.getRefManager().getReference(member);
100               if (refElement != null) {
101                 problemDescriptionsProcessor.addProblemElement(refElement, descriptor);
102                 continue;
103               }
104             }
105             problemDescriptionsProcessor.addProblemElement(refClass, descriptor);
106           }
107         }
108       }
109     });
110   }
111
112   @Nullable
113   private CommonProblemDescriptor[] checkElement(@NotNull RefClass refEntity, @NotNull InspectionManager manager) {
114     final PsiClass psiClass = refEntity.getElement();
115     if (psiClass == null) return null;
116     return checkElement(psiClass, manager);
117   }
118
119   public CommonProblemDescriptor[] checkElement(@NotNull final PsiElement psiElement, @NotNull final InspectionManager manager) {
120     final Map<PsiElement, Collection<String>> suppressedScopes = new THashMap<PsiElement, Collection<String>>();
121     psiElement.accept(new JavaRecursiveElementWalkingVisitor() {
122       @Override public void visitModifierList(PsiModifierList list) {
123         super.visitModifierList(list);
124         final PsiElement parent = list.getParent();
125         if (parent instanceof PsiModifierListOwner && !(parent instanceof PsiClass)) {
126           checkElement(parent);
127         }
128       }
129
130       @Override public void visitComment(PsiComment comment) {
131         checkElement(comment);
132       }
133
134       @Override public void visitClass(PsiClass aClass) {
135         if (aClass == psiElement) {
136           super.visitClass(aClass);
137           checkElement(aClass);
138         }
139       }
140
141
142       private void checkElement(final PsiElement owner) {
143         String idsString = JavaSuppressionUtil.getSuppressedInspectionIdsIn(owner);
144         if (idsString != null && !idsString.isEmpty()) {
145           List<String> ids = StringUtil.split(idsString, ",");
146           if (IGNORE_ALL && (ids.contains(SuppressionUtil.ALL) || ids.contains(SuppressionUtil.ALL.toLowerCase()))) return;
147           Collection<String> suppressed = suppressedScopes.get(owner);
148           if (suppressed == null) {
149             suppressed = ids;
150           }
151           else {
152             for (String id : ids) {
153               if (!suppressed.contains(id)) {
154                 suppressed.add(id);
155               }
156             }
157           }
158           suppressedScopes.put(owner, suppressed);
159         }
160       }
161     });
162
163     if (suppressedScopes.values().isEmpty()) return null;
164     // have to visit all file from scratch since inspections can be written in any pervasive way including checkFile() overriding
165     Map<InspectionToolWrapper, String> suppressedTools = new THashMap<InspectionToolWrapper, String>();
166     InspectionToolWrapper[] toolWrappers = getInspectionTools(psiElement, manager);
167     for (Collection<String> ids : suppressedScopes.values()) {
168       for (Iterator<String> iterator = ids.iterator(); iterator.hasNext(); ) {
169         final String shortName = iterator.next().trim();
170         for (InspectionToolWrapper toolWrapper : toolWrappers) {
171           if (toolWrapper instanceof LocalInspectionToolWrapper &&
172               (((LocalInspectionToolWrapper)toolWrapper).getTool().getID().equals(shortName) ||
173                shortName.equals(((LocalInspectionToolWrapper)toolWrapper).getTool().getAlternativeID()))) {
174             if (((LocalInspectionToolWrapper)toolWrapper).isUnfair()) {
175               iterator.remove();
176               break;
177             }
178             else {
179               suppressedTools.put(toolWrapper, shortName);
180             }
181           }
182           else if (toolWrapper.getShortName().equals(shortName)) {
183             //ignore global unused as it won't be checked anyway
184             if (toolWrapper instanceof LocalInspectionToolWrapper ||
185                 toolWrapper instanceof GlobalInspectionToolWrapper && !isGlobalInspectionRunCustomly(toolWrapper.getTool())) {
186               suppressedTools.put(toolWrapper, shortName);
187             }
188             else {
189               iterator.remove();
190               break;
191             }
192           }
193         }
194       }
195     }
196
197     PsiFile file = psiElement.getContainingFile();
198     final AnalysisScope scope = new AnalysisScope(file);
199
200     final GlobalInspectionContextBase globalContext = createContext(file);
201     globalContext.setCurrentScope(scope);
202     final RefManagerImpl refManager = (RefManagerImpl)globalContext.getRefManager();
203     refManager.inspectionReadActionStarted();
204     final List<ProblemDescriptor> result;
205     try {
206       result = new ArrayList<ProblemDescriptor>();
207       for (InspectionToolWrapper toolWrapper : suppressedTools.keySet()) {
208         String toolId = suppressedTools.get(toolWrapper);
209         toolWrapper.initialize(globalContext);
210         final Collection<CommonProblemDescriptor> descriptors;
211         if (toolWrapper instanceof LocalInspectionToolWrapper) {
212           LocalInspectionToolWrapper local = (LocalInspectionToolWrapper)toolWrapper;
213           if (local.isUnfair()) continue; //cant't work with passes other than LocalInspectionPass
214           List<ProblemDescriptor> results = local.getTool().processFile(file, manager);
215           descriptors = new ArrayList<CommonProblemDescriptor>(results);
216         }
217         else if (toolWrapper instanceof GlobalInspectionToolWrapper) {
218           final GlobalInspectionToolWrapper global = (GlobalInspectionToolWrapper)toolWrapper;
219           GlobalInspectionTool globalTool = global.getTool();
220           if (isGlobalInspectionRunCustomly(globalTool)) continue;
221           if (globalTool.isGraphNeeded()) {
222             refManager.findAllDeclarations();
223           }
224           descriptors = new ArrayList<CommonProblemDescriptor>();
225           globalContext.getRefManager().iterate(new RefVisitor() {
226             @Override public void visitElement(@NotNull RefEntity refEntity) {
227               CommonProblemDescriptor[]
228                 descriptors1 = global.getTool().checkElement(refEntity, scope, manager, globalContext, new ProblemDescriptionsProcessor() {
229                 @Nullable
230                 @Override
231                 public CommonProblemDescriptor[] getDescriptions(@NotNull RefEntity refEntity) {
232                   return CommonProblemDescriptor.EMPTY_ARRAY;
233                 }
234
235                 @Override
236                 public void ignoreElement(@NotNull RefEntity refEntity) {
237
238                 }
239
240                 @Override
241                 public void addProblemElement(@Nullable RefEntity refEntity, @NotNull CommonProblemDescriptor... commonProblemDescriptors) {
242                   int i =0;
243                 }
244
245                 @Override
246                 public RefEntity getElement(@NotNull CommonProblemDescriptor descriptor) {
247                   return null;
248                 }
249               });
250               if (descriptors1 != null) {
251                 ContainerUtil.addAll(descriptors, descriptors1);
252               }
253             }
254           });
255         }
256         else {
257           continue;
258         }
259         for (PsiElement suppressedScope : suppressedScopes.keySet()) {
260           Collection<String> suppressedIds = suppressedScopes.get(suppressedScope);
261           if (!suppressedIds.contains(toolId)) continue;
262           for (CommonProblemDescriptor descriptor : descriptors) {
263             if (!(descriptor instanceof ProblemDescriptor)) continue;
264             PsiElement element = ((ProblemDescriptor)descriptor).getPsiElement();
265             if (element == null) continue;
266             PsiElement annotation = JavaSuppressionUtil.getElementToolSuppressedIn(element, toolId);
267             if (annotation != null && PsiTreeUtil.isAncestor(suppressedScope, annotation, false) || annotation == null && !PsiTreeUtil.isAncestor(suppressedScope, element, false)) {
268               suppressedIds.remove(toolId);
269               break;
270             }
271           }
272         }
273       }
274       for (PsiElement suppressedScope : suppressedScopes.keySet()) {
275         Collection<String> suppressedIds = suppressedScopes.get(suppressedScope);
276         for (String toolId : suppressedIds) {
277           PsiMember psiMember;
278           String problemLine = null;
279           if (suppressedScope instanceof PsiMember) {
280             psiMember = (PsiMember)suppressedScope;
281           }
282           else {
283             psiMember = PsiTreeUtil.getParentOfType(suppressedScope, PsiDocCommentOwner.class);
284             final PsiStatement statement = PsiTreeUtil.getNextSiblingOfType(suppressedScope, PsiStatement.class);
285             problemLine = statement != null ? statement.getText() : null;
286           }
287           if (psiMember != null && psiMember.isValid()) {
288             String description = InspectionsBundle.message("inspection.redundant.suppression.description");
289             if (myQuickFixes == null) myQuickFixes = new BidirectionalMap<String, QuickFix>();
290             final String key = toolId + (problemLine != null ? ";" + problemLine : "");
291             QuickFix fix = myQuickFixes.get(key);
292             if (fix == null) {
293               fix = new RemoveSuppressWarningAction(toolId, problemLine);
294               myQuickFixes.put(key, fix);
295             }
296             PsiElement identifier = null;
297             if (!(suppressedScope instanceof PsiMember)) {
298               identifier = suppressedScope;
299             }
300             else if (psiMember instanceof PsiMethod) {
301               identifier = ((PsiMethod)psiMember).getNameIdentifier();
302             }
303             else if (psiMember instanceof PsiField) {
304               identifier = ((PsiField)psiMember).getNameIdentifier();
305             }
306             else if (psiMember instanceof PsiClass) {
307               identifier = ((PsiClass)psiMember).getNameIdentifier();
308             }
309             if (identifier == null) {
310               identifier = psiMember;
311             }
312             result.add(
313               manager.createProblemDescriptor(identifier, description, (LocalQuickFix)fix, ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
314                                               false));
315           }
316         }
317       }
318     }
319     finally {
320       refManager.inspectionReadActionFinished();
321       globalContext.close(true);
322     }
323     return result.toArray(new ProblemDescriptor[result.size()]);
324   }
325
326   private static boolean isGlobalInspectionRunCustomly(InspectionProfileEntry tool) {
327     return tool instanceof UnusedDeclarationInspectionBase;
328   }
329
330   protected GlobalInspectionContextBase createContext(PsiFile file) {
331     return new GlobalInspectionContextBase(file.getProject());
332   }
333
334   protected InspectionToolWrapper[] getInspectionTools(PsiElement psiElement, @NotNull InspectionManager manager) {
335     return InspectionProjectProfileManager.getInstance(manager.getProject()).getCurrentProfile().getModifiableModel()
336       .getInspectionTools(psiElement);
337   }
338
339   @Override
340   @Nullable
341   public QuickFix getQuickFix(final String hint) {
342     return myQuickFixes != null ? myQuickFixes.get(hint) : new RemoveSuppressWarningAction(hint);
343   }
344
345
346   @Override
347   @Nullable
348   public String getHint(@NotNull final QuickFix fix) {
349     if (myQuickFixes != null) {
350       final List<String> list = myQuickFixes.getKeysByValue(fix);
351       if (list != null) {
352         LOG.assertTrue(list.size() == 1);
353         return list.get(0);
354       }
355     }
356     return null;
357   }
358
359   @Override
360   public boolean isEnabledByDefault() {
361     return false;
362   }
363 }