From ec483ef976316859793dae2deb7e7ff2efc9faf6 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 5 Jun 2018 12:17:41 +0700 Subject: [PATCH] IDEA-193288 Inspection to detect Xyz.class.isInstance(foo) and Xyz.class.cast(foo) --- .../RedundantClassCall.html | 7 + .../redundantClassCall/afterCast.java | 8 ++ .../redundantClassCall/afterInstanceOf.java | 8 ++ .../redundantClassCall/beforeCast.java | 8 ++ .../redundantClassCall/beforeInstanceOf.java | 8 ++ .../beforeInstanceOfWrongType.java | 8 ++ .../RedundantClassCallInspectionTest.java | 25 ++++ .../siyeh/InspectionGadgetsBundle.properties | 5 +- .../src/META-INF/InspectionGadgets.xml | 3 + .../RedundantClassCallInspection.java | 122 ++++++++++++++++++ 10 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 java/java-impl/src/inspectionDescriptions/RedundantClassCall.html create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/afterCast.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/afterInstanceOf.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/beforeCast.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/beforeInstanceOf.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/beforeInstanceOfWrongType.java create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/RedundantClassCallInspectionTest.java create mode 100644 plugins/InspectionGadgets/src/com/siyeh/ig/redundancy/RedundantClassCallInspection.java diff --git a/java/java-impl/src/inspectionDescriptions/RedundantClassCall.html b/java/java-impl/src/inspectionDescriptions/RedundantClassCall.html new file mode 100644 index 000000000000..39dca030ec6d --- /dev/null +++ b/java/java-impl/src/inspectionDescriptions/RedundantClassCall.html @@ -0,0 +1,7 @@ + + +

Reports redundant calls of java.lang.Class methods. E.g. Xyz.class.isInstance(object) could be replaced with object instanceof Xyz.

+ +New in 2018.2 + + \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/afterCast.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/afterCast.java new file mode 100644 index 000000000000..3655ec0fa0c4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/afterCast.java @@ -0,0 +1,8 @@ +// "Replace with '(Integer)'" "true" +class X { + void test(Object obj) { + if(Integer.class.isInstance(obj)) { + System.out.println((Integer) obj); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/afterInstanceOf.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/afterInstanceOf.java new file mode 100644 index 000000000000..a5b81339642d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/afterInstanceOf.java @@ -0,0 +1,8 @@ +// "Replace with 'instanceof Integer'" "true" +class X { + void test(Object obj) { + if(obj instanceof Integer) { + System.out.println("Integer"); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/beforeCast.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/beforeCast.java new file mode 100644 index 000000000000..e1434c9990c8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/beforeCast.java @@ -0,0 +1,8 @@ +// "Replace with '(Integer)'" "true" +class X { + void test(Object obj) { + if(Integer.class.isInstance(obj)) { + System.out.println(Integer.class.cast(obj)); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/beforeInstanceOf.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/beforeInstanceOf.java new file mode 100644 index 000000000000..69644adbd4ab --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/beforeInstanceOf.java @@ -0,0 +1,8 @@ +// "Replace with 'instanceof Integer'" "true" +class X { + void test(Object obj) { + if(Integer.class.isInstance(obj)) { + System.out.println("Integer"); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/beforeInstanceOfWrongType.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/beforeInstanceOfWrongType.java new file mode 100644 index 000000000000..48ecb5c452f4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall/beforeInstanceOfWrongType.java @@ -0,0 +1,8 @@ +// "Replace with 'instanceof Integer'" "false" +class X { + void test(String obj) { + if(Integer.class.isInstance(obj)) { + System.out.println("Integer"); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/RedundantClassCallInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/RedundantClassCallInspectionTest.java new file mode 100644 index 000000000000..f504ccdd4565 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/RedundantClassCallInspectionTest.java @@ -0,0 +1,25 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.java.codeInsight.daemon.quickFix; + +import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; +import com.intellij.codeInspection.LocalInspectionTool; +import com.siyeh.ig.redundancy.RedundantClassCallInspection; +import org.jetbrains.annotations.NotNull; + + +public class RedundantClassCallInspectionTest extends LightQuickFixParameterizedTestCase { + @NotNull + @Override + protected LocalInspectionTool[] configureLocalInspectionTools() { + return new LocalInspectionTool[]{ + new RedundantClassCallInspection() + }; + } + + public void test() { doAllTests(); } + + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/redundantClassCall"; + } +} \ No newline at end of file diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/InspectionGadgetsBundle.properties b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/InspectionGadgetsBundle.properties index e5012fa36b8c..7081b68a58c0 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/InspectionGadgetsBundle.properties +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/InspectionGadgetsBundle.properties @@ -2287,4 +2287,7 @@ inspection.serializable.can.have.default.serial.uid='Serializable' can have defa inspection.serializable.can.have.default.serial.uid.message='serialVersionUID' differs from default generated inspection.serializable.can.have.default.serial.uid.fix.name=Change 'serialVersionUID' to generated by signature junit5.nested.test.display.name=JUnit 5 malformed @Nested class -implicit.default.charset.usage.fix.family.name=Specify UTF-8 charset \ No newline at end of file +implicit.default.charset.usage.fix.family.name=Specify UTF-8 charset + +inspection.redundant.class.call.display.name=Redundant 'isInstance' or 'cast' call + diff --git a/plugins/InspectionGadgets/src/META-INF/InspectionGadgets.xml b/plugins/InspectionGadgets/src/META-INF/InspectionGadgets.xml index d35b3bfba2bf..0accc3033d10 100644 --- a/plugins/InspectionGadgets/src/META-INF/InspectionGadgets.xml +++ b/plugins/InspectionGadgets/src/META-INF/InspectionGadgets.xml @@ -2020,6 +2020,9 @@ + diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/redundancy/RedundantClassCallInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/redundancy/RedundantClassCallInspection.java new file mode 100644 index 000000000000..895ad5d6fa95 --- /dev/null +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/redundancy/RedundantClassCallInspection.java @@ -0,0 +1,122 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.siyeh.ig.redundancy; + +import com.intellij.codeInspection.*; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.ArrayUtil; +import com.siyeh.InspectionGadgetsBundle; +import com.siyeh.ig.callMatcher.CallMatcher; +import com.siyeh.ig.psiutils.CommentTracker; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +import static com.intellij.util.ObjectUtils.tryCast; + +public class RedundantClassCallInspection extends AbstractBaseJavaLocalInspectionTool implements CleanupLocalInspectionTool { + private static final CallMatcher IS_INSTANCE = + CallMatcher.exactInstanceCall(CommonClassNames.JAVA_LANG_CLASS, "isInstance").parameterCount(1); + private static final CallMatcher CAST = + CallMatcher.exactInstanceCall(CommonClassNames.JAVA_LANG_CLASS, "cast").parameterCount(1); + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new JavaElementVisitor() { + @Override + public void visitMethodCallExpression(PsiMethodCallExpression call) { + PsiElement nameElement = call.getMethodExpression().getReferenceNameElement(); + if (nameElement == null) return; + boolean isInstance = IS_INSTANCE.test(call); + boolean cast = CAST.test(call); + if (isInstance || cast) { + PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression()); + if (qualifier instanceof PsiClassObjectAccessExpression) { + PsiTypeElement typeElement = ((PsiClassObjectAccessExpression)qualifier).getOperand(); + PsiType classType = typeElement.getType(); + PsiExpression argument = call.getArgumentList().getExpressions()[0]; + PsiType argumentType = argument.getType(); + if (argumentType == null || !argumentType.isConvertibleFrom(classType)) { + // will be a compilation error after replacement; skip this + return; + } + LocalQuickFix fix = isInstance ? new ReplaceWithInstanceOfFix(typeElement) : new ReplaceWithCastFix(typeElement); + holder.registerProblem(nameElement, InspectionGadgetsBundle.message("redundant.call.problem.descriptor"), fix); + } + } + } + }; + } + + private static abstract class ReplaceRedundantClassCallFix implements LocalQuickFix { + final String myReplacement; + + ReplaceRedundantClassCallFix(String replacement) { + myReplacement = replacement; + } + + @Nls(capitalization = Nls.Capitalization.Sentence) + @NotNull + @Override + public String getName() { + return InspectionGadgetsBundle.message("replace.with", myReplacement); + } + + @Override + public final void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiMethodCallExpression.class); + if (call == null) return; + PsiExpression arg = ArrayUtil.getFirstElement(call.getArgumentList().getExpressions()); + if (arg == null) return; + PsiClassObjectAccessExpression qualifier = + tryCast(PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression()), + PsiClassObjectAccessExpression.class); + if (qualifier == null) return; + CommentTracker ct = new CommentTracker(); + ct.replaceAndRestoreComments(call, createReplacement(ct.text(arg), ct.text(qualifier.getOperand()))); + } + + @NotNull + abstract String createReplacement(String argText, String classText); + } + + private static class ReplaceWithInstanceOfFix extends ReplaceRedundantClassCallFix { + public ReplaceWithInstanceOfFix(@NotNull PsiTypeElement typeElement) { + super("instanceof "+typeElement.getType().getPresentableText()); + } + + @Nls(capitalization = Nls.Capitalization.Sentence) + @NotNull + @Override + public String getFamilyName() { + return "Replace with 'instanceof'"; + } + + @NotNull + @Override + String createReplacement(String argText, String classText) { + return argText + " instanceof " + classText; + } + } + + private static class ReplaceWithCastFix extends ReplaceRedundantClassCallFix { + public ReplaceWithCastFix(@NotNull PsiTypeElement typeElement) { + super("("+typeElement.getType().getPresentableText()+")"); + } + + @Nls(capitalization = Nls.Capitalization.Sentence) + @NotNull + @Override + public String getFamilyName() { + return "Replace with cast"; + } + + @NotNull + @Override + String createReplacement(String argText, String classText) { + return "("+classText+")"+argText; + } + } +}