first stab at an inspection to detect identical 'catch' blocks in a 'try' statement (quickfix coming soon)

This commit is contained in:
Dmitry Jemerov
2011-02-17 20:23:11 +01:00
parent 3c2db3502a
commit 1dbdb1377c
10 changed files with 213 additions and 13 deletions
@@ -34,6 +34,7 @@ import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.IntArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@@ -49,12 +50,12 @@ public class DuplicatesFinder {
private final List<? extends PsiVariable> myOutputParameters;
private final List<PsiElement> myPatternAsList;
private boolean myMultipleExitPoints = false;
private final ReturnValue myReturnValue;
@Nullable private final ReturnValue myReturnValue;
public DuplicatesFinder(PsiElement[] pattern,
InputVariables parameters,
ReturnValue returnValue,
List<? extends PsiVariable> outputParameters
@Nullable ReturnValue returnValue,
@NotNull List<? extends PsiVariable> outputParameters
) {
myReturnValue = returnValue;
LOG.assertTrue(pattern.length > 0);
@@ -109,6 +110,14 @@ public class DuplicatesFinder {
return result;
}
@Nullable
public Match isDuplicate(PsiElement element, boolean ignoreParameterTypes) {
annotatePattern();
Match match = isDuplicateFragment(element, ignoreParameterTypes);
deannotatePattern();
return match;
}
private void annotatePattern() {
for (final PsiElement patternComponent : myPattern) {
patternComponent.accept(new JavaRecursiveElementWalkingVisitor() {
@@ -146,7 +155,7 @@ public class DuplicatesFinder {
private void findPatternOccurrences(List<Match> array, PsiElement scope) {
PsiElement[] children = scope.getChildren();
for (PsiElement child : children) {
final Match match = isDuplicateFragment(child);
final Match match = isDuplicateFragment(child, false);
if (match != null) {
array.add(match);
continue;
@@ -157,7 +166,7 @@ public class DuplicatesFinder {
@Nullable
private Match isDuplicateFragment(PsiElement candidate) {
private Match isDuplicateFragment(PsiElement candidate, boolean ignoreParameterTypes) {
if (PsiTreeUtil.isAncestor(myPattern[0], candidate, false)) return null;
PsiElement sibling = candidate;
ArrayList<PsiElement> candidates = new ArrayList<PsiElement>();
@@ -188,7 +197,7 @@ public class DuplicatesFinder {
}
}
final Match match = new Match(candidates.get(0), candidates.get(candidates.size() - 1));
final Match match = new Match(candidates.get(0), candidates.get(candidates.size() - 1), ignoreParameterTypes);
for (int i = 0; i < myPattern.length; i++) {
if (!matchPattern(myPattern[i], candidates.get(i), candidates, match)) return null;
}
@@ -48,16 +48,18 @@ public final class Match {
private final PsiElement myMatchStart;
private final PsiElement myMatchEnd;
private final Map<PsiVariable, List<PsiElement>> myParameterValues = new HashMap<PsiVariable, List<PsiElement>>();
private final Map<PsiVariable, ArrayList<PsiElement>> myParameterOccurences = new HashMap<PsiVariable, ArrayList<PsiElement>>();
private final Map<PsiVariable, ArrayList<PsiElement>> myParameterOccurrences = new HashMap<PsiVariable, ArrayList<PsiElement>>();
private final Map<PsiElement, PsiElement> myDeclarationCorrespondence = new HashMap<PsiElement, PsiElement>();
private ReturnValue myReturnValue = null;
private Ref<PsiExpression> myInstanceExpression = null;
private final Map<PsiVariable, PsiType> myChangedParams = new HashMap<PsiVariable, PsiType>();
private final boolean myIgnoreParameterTypes;
Match(PsiElement start, PsiElement end) {
Match(PsiElement start, PsiElement end, boolean ignoreParameterTypes) {
LOG.assertTrue(start.getParent() == end.getParent());
myMatchStart = start;
myMatchEnd = end;
myIgnoreParameterTypes = ignoreParameterTypes;
}
@@ -135,14 +137,14 @@ public final class Match {
myChangedParams.put(psiVariable, new PsiEllipsisType(parameterType));
}
} else {
if (!parameterType.isAssignableFrom(type)) return false; //todo
if (!myIgnoreParameterTypes && !parameterType.isAssignableFrom(type)) return false; //todo
}
}
final List<PsiElement> values = new ArrayList<PsiElement>();
values.add(value);
myParameterValues.put(psiVariable, values);
final ArrayList<PsiElement> elements = new ArrayList<PsiElement>();
myParameterOccurences.put(psiVariable, elements);
myParameterOccurrences.put(psiVariable, elements);
return true;
}
else {
@@ -157,7 +159,7 @@ public final class Match {
currentValue.add(value);
}
}
myParameterOccurences.get(psiVariable).add(value);
myParameterOccurrences.get(psiVariable).add(value);
return true;
}
}
@@ -1843,4 +1843,6 @@ package.dot.html.delete.command=package.html deletion
package.dot.html.may.be.package.info.convert.quickfix=Convert to package-info.java
package.dot.html.convert.command=package.html to package-info.java conversion
choose.super.class.to.ignore=Choose class
ignore.anonymous.inner.classes=Ignore anonymous inner classes
ignore.anonymous.inner.classes=Ignore anonymous inner classes
try.with.identical.catches.display.name=Identical 'catch' branches in 'try' statement
try.with.identical.catches.problem.descriptor=Identical 'catch' branches in 'try' statement #loc
@@ -24,7 +24,9 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.ui.DocumentAdapter;
import com.siyeh.ig.ui.FormattedTextFieldMacFix;
@@ -283,4 +285,9 @@ public abstract class BaseInspection extends BaseJavaLocalInspectionTool {
inspectionGadgetsPlugin = null;
}
}
@Nullable
public TextRange getProblemTextRange(PsiElement element) {
return null;
}
}
@@ -16,6 +16,7 @@
package com.siyeh.ig;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -167,7 +168,13 @@ public abstract class BaseInspectionVisitor extends JavaElementVisitor{
fix.setOnTheFly(onTheFly);
}
final String description = inspection.buildErrorString(infos);
holder.registerProblem(location, description, fixes);
TextRange range = inspection.getProblemTextRange(location);
if (range != null) {
holder.registerProblem(location, range, description, fixes);
}
else {
holder.registerProblem(location, description, fixes);
}
}
@NotNull
@@ -833,6 +833,7 @@ public class InspectionGadgetsPlugin implements ApplicationComponent,
m_inspectionClasses.add(ThrowFromFinallyBlockInspection.class);
m_inspectionClasses.add(TooBroadCatchInspection.class);
m_inspectionClasses.add(TooBroadThrowsInspection.class);
m_inspectionClasses.add(TryWithIdenticalCatchesInspection.class);
m_inspectionClasses.add(UncheckedExceptionClassInspection.class);
m_inspectionClasses.add(UnusedCatchParameterInspection.class);
}
@@ -0,0 +1,95 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.siyeh.ig.errorhandling;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.extractMethod.InputVariables;
import com.intellij.refactoring.util.duplicates.DuplicatesFinder;
import com.intellij.refactoring.util.duplicates.Match;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
/**
* @author yole
*/
public class TryWithIdenticalCatchesInspection extends BaseInspection {
@NotNull
@Override
protected String buildErrorString(Object... infos) {
return InspectionGadgetsBundle.message("try.with.identical.catches.problem.descriptor");
}
@Override
public BaseInspectionVisitor buildVisitor() {
return new TryWithIdenticalCatchesVisitor();
}
@Nls
@NotNull
@Override
public String getDisplayName() {
return InspectionGadgetsBundle.message("try.with.identical.catches.display.name");
}
@Override
public TextRange getProblemTextRange(PsiElement element) {
if (element instanceof PsiCatchSection) {
PsiJavaToken rParenth = ((PsiCatchSection)element).getRParenth();
if (rParenth != null) {
return new TextRange(0, rParenth.getTextOffset() + 1 - element.getTextOffset());
}
}
return null;
}
private static class TryWithIdenticalCatchesVisitor extends BaseInspectionVisitor {
@Override
public void visitTryStatement(PsiTryStatement statement) {
super.visitTryStatement(statement);
if (!PsiUtil.isLanguageLevel7OrHigher(statement)) {
return;
}
PsiCatchSection[] catchSections = statement.getCatchSections();
boolean[] duplicates = new boolean[catchSections.length];
for (int i = 0; i < catchSections.length; i++) {
if (duplicates[i]) continue;
InputVariables inputVariables = new InputVariables(Collections.singletonList(catchSections[i].getParameter()),
statement.getProject(),
new LocalSearchScope(catchSections[i].getCatchBlock()),
false);
DuplicatesFinder finder = new DuplicatesFinder(new PsiElement[] { catchSections [i].getCatchBlock() },
inputVariables, null, Collections.<PsiVariable>emptyList());
for (int j = 0; j < catchSections.length; j++) {
if (i == j || duplicates[j]) continue;
Match match = finder.isDuplicate(catchSections[j].getCatchBlock(), true);
if (match != null) {
registerError(catchSections[j]);
duplicates[i] = true;
duplicates[j] = true;
}
}
}
}
}
}
@@ -0,0 +1,7 @@
<html>
<body>
This inspection reports identical 'catch' sections in 'try' blocks under JDK 7. A quickfix is provided to collapse the sections into
a multi-catch section.
<small>Powered by InspectionGadgets</small>
</body>
</html>
@@ -0,0 +1,35 @@
package com.siyeh.igtest.errorhandling.try_identical_catches;
class TryIdenticalCatches {
public void notIdentical() {
try {
}
catch(NumberFormatException e) {
log(e);
}
catch(RuntimeException e) {
throw e;
}
}
public void identical(boolean value) {
try {
if (value) {
throw new ClassNotFoundException();
}
else {
throw new NumberFormatException();
}
}
catch(ClassNotFoundException cnfe) {
log(cnfe);
}
<warning descr="Identical 'catch' branches in 'try' statement">catch(NumberFormatException nfe)</warning> {
log(nfe);
}
}
private void log(Exception e) {
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.siyeh.ig.errorhandling;
import com.intellij.openapi.application.PluginPathManager;
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
/**
* @author yole
*/
public class TryWithIdenticalCatchesTest extends LightCodeInsightFixtureTestCase {
public void test() {
myFixture.enableInspections(TryWithIdenticalCatchesInspection.class);
myFixture.configureByFile("com/siyeh/igtest/errorhandling/try_identical_catches/TryIdenticalCatches.java");
myFixture.checkHighlighting(true, false, false);
}
@Override
protected String getBasePath() {
return PluginPathManager.getPluginHomePathRelative("InspectionGadgets") + "/test";
}
}