IDEA-112398 (Yellow code is green: Inspection "Overly broad 'catch' clause" and Runti...
[idea/community.git] / plugins / InspectionGadgets / src / com / siyeh / ig / errorhandling / TooBroadCatchInspection.java
1 /*
2  * Copyright 2003-2013 Dave Griffith, Bas Leijdekkers
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.siyeh.ig.errorhandling;
17
18 import com.intellij.codeInspection.ProblemDescriptor;
19 import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
20 import com.intellij.openapi.editor.Document;
21 import com.intellij.openapi.editor.Editor;
22 import com.intellij.openapi.editor.ScrollType;
23 import com.intellij.openapi.fileEditor.FileEditorManager;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.util.TextRange;
26 import com.intellij.psi.*;
27 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
28 import com.intellij.util.IncorrectOperationException;
29 import com.siyeh.InspectionGadgetsBundle;
30 import com.siyeh.ig.BaseInspection;
31 import com.siyeh.ig.BaseInspectionVisitor;
32 import com.siyeh.ig.InspectionGadgetsFix;
33 import com.siyeh.ig.psiutils.ClassUtils;
34 import com.siyeh.ig.psiutils.ExceptionUtils;
35 import com.siyeh.ig.psiutils.TestUtils;
36 import org.jetbrains.annotations.NotNull;
37
38 import javax.swing.*;
39 import java.util.*;
40
41 public class TooBroadCatchInspection extends BaseInspection {
42
43   @SuppressWarnings({"PublicField"})
44   public boolean onlyWarnOnRootExceptions = false;
45
46   @SuppressWarnings("PublicField")
47   public boolean ignoreInTestCode = false;
48
49   @SuppressWarnings("PublicField")
50   public boolean ignoreThrown = false;
51
52   @Override
53   @NotNull
54   public String getID() {
55     return "OverlyBroadCatchBlock";
56   }
57
58   @Override
59   @NotNull
60   public String getDisplayName() {
61     return InspectionGadgetsBundle.message("too.broad.catch.display.name");
62   }
63
64   @Override
65   @NotNull
66   protected String buildErrorString(Object... infos) {
67     final List<PsiClass> typesMasked = (List<PsiClass>)infos[0];
68     String typesMaskedString = typesMasked.get(0).getName();
69     if (typesMasked.size() == 1) {
70       return InspectionGadgetsBundle.message("too.broad.catch.problem.descriptor", typesMaskedString);
71     }
72     else {
73       //Collections.sort(typesMasked);
74       final int lastTypeIndex = typesMasked.size() - 1;
75       for (int i = 1; i < lastTypeIndex; i++) {
76         typesMaskedString += ", ";
77         typesMaskedString += typesMasked.get(i).getName();
78       }
79       final String lastTypeString = typesMasked.get(lastTypeIndex).getName();
80       return InspectionGadgetsBundle.message("too.broad.catch.problem.descriptor1", typesMaskedString, lastTypeString);
81     }
82   }
83
84   @NotNull
85   @Override
86   protected InspectionGadgetsFix[] buildFixes(Object... infos) {
87     final List<PsiClass> maskedTypes = (List<PsiClass>)infos[0];
88     final List<InspectionGadgetsFix> fixes = new ArrayList();
89     for (PsiClass thrown : maskedTypes) {
90       if (CommonClassNames.JAVA_LANG_RUNTIME_EXCEPTION.equals(maskedTypes.get(0).getQualifiedName())) {
91         fixes.add(new ReplaceWithRuntimeExceptionFix());
92       }
93       else {
94         fixes.add(new AddCatchSectionFix(thrown));
95       }
96     }
97     return fixes.toArray(new InspectionGadgetsFix[fixes.size()]);
98   }
99
100   @Override
101   public JComponent createOptionsPanel() {
102     final MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this);
103     panel.addCheckbox(InspectionGadgetsBundle.message("too.broad.catch.option"), "onlyWarnOnRootExceptions");
104     panel.addCheckbox(InspectionGadgetsBundle.message("ignore.in.test.code"), "ignoreInTestCode");
105     panel.addCheckbox(InspectionGadgetsBundle.message("overly.broad.throws.clause.ignore.thrown.option"), "ignoreThrown");
106     return panel;
107   }
108
109   private static class ReplaceWithRuntimeExceptionFix extends InspectionGadgetsFix {
110     @NotNull
111     @Override
112     public String getName() {
113       return "Replace with 'catch' clause for 'RuntimeException'";
114     }
115
116     @NotNull
117     @Override
118     public String getFamilyName() {
119       return getName();
120     }
121
122     @Override
123     protected void doFix(Project project, ProblemDescriptor descriptor) {
124       final PsiElement element = descriptor.getPsiElement();
125       if (!(element instanceof PsiTypeElement)) {
126         return;
127       }
128       final PsiTypeElement typeElement = (PsiTypeElement)element;
129       final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
130       final PsiClassType type = factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_RUNTIME_EXCEPTION);
131       final PsiTypeElement newTypeElement = factory.createTypeElement(type);
132       typeElement.replace(newTypeElement);
133     }
134   }
135
136   private static class AddCatchSectionFix extends InspectionGadgetsFix {
137
138     private final SmartPsiElementPointer<PsiClass> myThrown;
139     private final String myText;
140
141     AddCatchSectionFix(PsiClass thrown) {
142       myThrown = SmartPointerManager.getInstance(thrown.getProject()).createSmartPsiElementPointer(thrown);
143       myText = thrown.getName();
144     }
145
146     @Override
147     @NotNull
148     public String getName() {
149       return InspectionGadgetsBundle.message("too.broad.catch.quickfix", myText);
150     }
151
152     @NotNull
153     @Override
154     public String getFamilyName() {
155       return "Add 'catch' clause";
156     }
157
158     @Override
159     protected void doFix(Project project, ProblemDescriptor descriptor) throws IncorrectOperationException {
160       final PsiElement typeElement = descriptor.getPsiElement();
161       if (typeElement == null) {
162         return;
163       }
164       final PsiElement catchParameter = typeElement.getParent();
165       if (!(catchParameter instanceof PsiParameter)) {
166         return;
167       }
168       final PsiElement catchBlock = ((PsiParameter)catchParameter).getDeclarationScope();
169       if (!(catchBlock instanceof PsiCatchSection)) {
170         return;
171       }
172       final PsiCatchSection myBeforeCatchSection = (PsiCatchSection)catchBlock;
173       final PsiTryStatement myTryStatement = myBeforeCatchSection.getTryStatement();
174       final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project);
175       final String name = codeStyleManager.suggestUniqueVariableName("e", myTryStatement.getTryBlock(), false);
176       final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
177       final PsiClass aClass = myThrown.getElement();
178       if (aClass == null) {
179         return;
180       }
181       final PsiCatchSection section = factory.createCatchSection(factory.createType(aClass), name, myTryStatement);
182       final PsiCatchSection element = (PsiCatchSection)myTryStatement.addBefore(section, myBeforeCatchSection);
183       codeStyleManager.shortenClassReferences(element);
184
185       if (isOnTheFly()) {
186         final TextRange range = getRangeToSelect(element.getCatchBlock());
187         final PsiFile file = element.getContainingFile();
188         final Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
189         if (editor == null) {
190           return;
191         }
192         final Document document = PsiDocumentManager.getInstance(project).getDocument(file);
193         if (editor.getDocument() != document) {
194           return;
195         }
196         editor.getCaretModel().moveToOffset(range.getStartOffset());
197         editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
198         editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
199       }
200     }
201   }
202
203   private static TextRange getRangeToSelect(PsiCodeBlock block) {
204     PsiElement first = block.getFirstBodyElement();
205     if (first instanceof PsiWhiteSpace) {
206       first = first.getNextSibling();
207     }
208     if (first == null) {
209       final int offset = block.getTextRange().getStartOffset() + 1;
210       return new TextRange(offset, offset);
211     }
212     PsiElement last = block.getLastBodyElement();
213     if (last instanceof PsiWhiteSpace) {
214       last = last.getPrevSibling();
215     }
216     final TextRange textRange;
217     if (last == null) {
218       textRange = first.getTextRange();
219     }
220     else {
221       textRange = last.getTextRange();
222     }
223     return new TextRange(first.getTextRange().getStartOffset(), textRange.getEndOffset());
224   }
225
226   @Override
227   public BaseInspectionVisitor buildVisitor() {
228     return new TooBroadCatchVisitor();
229   }
230
231   private class TooBroadCatchVisitor extends BaseInspectionVisitor {
232
233     @Override
234     public void visitTryStatement(@NotNull PsiTryStatement statement) {
235       super.visitTryStatement(statement);
236       final PsiCodeBlock tryBlock = statement.getTryBlock();
237       if (tryBlock == null) {
238         return;
239       }
240       if (ignoreInTestCode && TestUtils.isInTestCode(statement)) {
241         return;
242       }
243       final Set<PsiClassType> thrownTypes = ExceptionUtils.calculateExceptionsThrown(tryBlock);
244       final Set<PsiType> caughtTypes = new HashSet<PsiType>(thrownTypes.size());
245       final PsiCatchSection[] catchSections = statement.getCatchSections();
246       for (final PsiCatchSection catchSection : catchSections) {
247         final PsiParameter parameter = catchSection.getParameter();
248         if (parameter == null) {
249           continue;
250         }
251         final PsiType caughtType = parameter.getType();
252         if (caughtType instanceof PsiDisjunctionType) {
253           final PsiDisjunctionType disjunctionType = (PsiDisjunctionType)caughtType;
254           final List<PsiType> types = disjunctionType.getDisjunctions();
255           for (PsiType type : types) {
256             check(thrownTypes, caughtTypes, parameter, type);
257           }
258         }
259         else {
260           if (thrownTypes.isEmpty()) {
261             if (CommonClassNames.JAVA_LANG_EXCEPTION.equals(caughtType.getCanonicalText())) {
262               final PsiTypeElement typeElement = parameter.getTypeElement();
263               if (typeElement == null) {
264                 continue;
265               }
266               final PsiClass runtimeExceptionClass = ClassUtils.findClass(CommonClassNames.JAVA_LANG_RUNTIME_EXCEPTION, parameter);
267               registerError(typeElement, Collections.singletonList(runtimeExceptionClass));
268             }
269           }
270           else {
271             check(thrownTypes, caughtTypes, parameter, caughtType);
272           }
273         }
274       }
275     }
276
277     private void check(Set<PsiClassType> thrownTypes, Set<PsiType> caughtTypes, PsiParameter parameter, PsiType caughtType) {
278       final List<PsiClass> maskedExceptions = findMaskedExceptions(thrownTypes, caughtTypes, caughtType);
279       if (maskedExceptions.isEmpty()) {
280         return;
281       }
282       final PsiTypeElement typeElement = parameter.getTypeElement();
283       if (typeElement == null) {
284         return;
285       }
286       registerError(typeElement, maskedExceptions);
287     }
288
289     private List<PsiClass> findMaskedExceptions(Set<PsiClassType> thrownTypes, Set<PsiType> caughtTypes, PsiType caughtType) {
290       if (thrownTypes.contains(caughtType)) {
291         if (ignoreThrown) {
292           return Collections.emptyList();
293         }
294         caughtTypes.add(caughtType);
295         thrownTypes.remove(caughtType);
296       }
297       if (onlyWarnOnRootExceptions) {
298         if (!ExceptionUtils.isGenericExceptionClass(caughtType)) {
299           return Collections.emptyList();
300         }
301       }
302       final List<PsiClass> maskedTypes = new ArrayList();
303       for (PsiClassType typeThrown : thrownTypes) {
304         if (!caughtTypes.contains(typeThrown) && caughtType.isAssignableFrom(typeThrown)) {
305           caughtTypes.add(typeThrown);
306           final PsiClass aClass = typeThrown.resolve();
307           if (aClass != null) {
308             maskedTypes.add(aClass);
309           }
310         }
311       }
312       return maskedTypes;
313     }
314   }
315 }