IDEA-193288 Inspection to detect Xyz.class.isInstance(foo) and Xyz.class.cast(foo)

This commit is contained in:
Tagir Valeev
2018-06-05 12:17:41 +07:00
parent ae37e14386
commit ec483ef976
10 changed files with 201 additions and 1 deletions

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>Reports redundant calls of <b>java.lang.Class</b> methods. E.g. <b>Xyz.class.isInstance(object)</b> could be replaced with <b>object instanceof Xyz</b>.</p>
<!-- tooltip end -->
<small>New in 2018.2</small>
</body>
</html>

View File

@@ -0,0 +1,8 @@
// "Replace with '(Integer)'" "true"
class X {
void test(Object obj) {
if(Integer.class.isInstance(obj)) {
System.out.println((Integer) obj);
}
}
}

View File

@@ -0,0 +1,8 @@
// "Replace with 'instanceof Integer'" "true"
class X {
void test(Object obj) {
if(obj instanceof Integer) {
System.out.println("Integer");
}
}
}

View File

@@ -0,0 +1,8 @@
// "Replace with '(Integer)'" "true"
class X {
void test(Object obj) {
if(Integer.class.isInstance(obj)) {
System.out.println(Integer.class.c<caret>ast(obj));
}
}
}

View File

@@ -0,0 +1,8 @@
// "Replace with 'instanceof Integer'" "true"
class X {
void test(Object obj) {
if(Integer.class.isIns<caret>tance(obj)) {
System.out.println("Integer");
}
}
}

View File

@@ -0,0 +1,8 @@
// "Replace with 'instanceof Integer'" "false"
class X {
void test(String obj) {
if(Integer.class.isIns<caret>tance(obj)) {
System.out.println("Integer");
}
}
}

View File

@@ -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";
}
}

View File

@@ -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
implicit.default.charset.usage.fix.family.name=Specify UTF-8 charset
inspection.redundant.class.call.display.name=Redundant 'isInstance' or 'cast' call

View File

@@ -2020,6 +2020,9 @@
<localInspection groupPath="Java" language="JAVA" shortName="RedundantStringOperation" bundle="com.siyeh.InspectionGadgetsBundle"
key="inspection.redundant.string.operation.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.verbose.or.redundant.code.constructs"
enabledByDefault="true" level="WARNING" cleanupTool="true" implementationClass="com.siyeh.ig.redundancy.RedundantStringOperationInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="RedundantClassCall" bundle="com.siyeh.InspectionGadgetsBundle"
key="inspection.redundant.class.call.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.verbose.or.redundant.code.constructs"
enabledByDefault="true" level="WARNING" cleanupTool="true" implementationClass="com.siyeh.ig.redundancy.RedundantClassCallInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="RedundantCollectionOperation" bundle="com.siyeh.InspectionGadgetsBundle"
key="inspection.redundant.collection.operation.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.verbose.or.redundant.code.constructs"
enabledByDefault="true" level="WARNING" cleanupTool="true" implementationClass="com.siyeh.ig.redundancy.RedundantCollectionOperationInspection"/>

View File

@@ -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;
}
}
}