[java-inspections] IDEA-285742 New inspection: replacement method has no effect

GitOrigin-RevId: 708af15324387475e434e2e0c87fe2d26ceac1ef
This commit is contained in:
Tagir Valeev
2021-12-28 06:23:36 +00:00
committed by intellij-monorepo-bot
parent 8fab3337b0
commit b27935c747
10 changed files with 215 additions and 0 deletions
@@ -0,0 +1,13 @@
<html>
<body>
Reports string methods like <code>replace</code>, <code>replaceAll</code>, <code>replaceFirst</code>
applied to the string literal where they have no effect because the search string is not found in the
literal. This is redundant code and may indicate a programming error.
<p>Example:</p>
<pre>
"hello".replace("$value$", value); // replacement does nothing
</pre>
<!-- tooltip end -->
<p><small>New in 2022.1</small></p>
</body>
</html>
@@ -0,0 +1,4 @@
// "Remove redundant call" "true"
class X {
String s = "a$b"+ "c";
}
@@ -0,0 +1,4 @@
// "Remove redundant call" "true"
class X {
String s = "a$b"+"c".<caret>replace("$", "/");
}
@@ -0,0 +1,6 @@
// "Fix all 'Replacement operation has no effect' problems in file" "false"
class X {
void test() {
"c".<caret>replace("$", "/");
}
}
@@ -0,0 +1,25 @@
public class ReplaceOnLiteral {
private static final String PATTERN = "$a$";
void test(String s) {
"a".<warning descr="Replacement operation has no effect">replace("b", "c")</warning>;
"a".<warning descr="Replacement operation has no effect">replace('b', 'c')</warning>;
"abc".replace('a', 'b');
"abc".<warning descr="Replacement operation has no effect">replaceFirst("^b", "c")</warning>;
"abc".replaceFirst("^a", "c");
"abc".<warning descr="Replacement operation has no effect">replaceAll("^b", "c")</warning>;
"abc".replaceAll("^a", "c");
"abc".<warning descr="Replacement operation has no effect">replaceAll(PATTERN, s)</warning>;
"abc".<warning descr="Replacement operation has no effect">replace(PATTERN, s)</warning>;
"a".<warning descr="Replacement operation has no effect">replace("b" + s, "")</warning>;
"a".replace("a" + s, "");
"a".replace('a' + s, "");
"a".<warning descr="Replacement operation has no effect">replace('b' + s, "")</warning>;
"a".replace(123 + s, "");
"a".<warning descr="Replacement operation has no effect">replace((s + ('x') + s), "c")</warning>;
"a".<warning descr="Replacement operation has no effect">replace(PATTERN+s, "")</warning>;
"a".replaceFirst("x" + s, "");
}
}
@@ -0,0 +1,23 @@
// Copyright 2000-2017 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.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import com.siyeh.ig.redundancy.RedundantStringOperationInspection;
import com.siyeh.ig.redundancy.ReplaceOnLiteralHasNoEffectInspection;
import org.jetbrains.annotations.NotNull;
public class ReplaceOnLiteralHasNoEffectInspectionFixTest extends LightQuickFixParameterizedTestCase {
@Override
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
return new LocalInspectionTool[]{new ReplaceOnLiteralHasNoEffectInspection()};
}
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/replaceOnLiteral";
}
}
@@ -0,0 +1,27 @@
// Copyright 2000-2021 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.JavaTestUtil;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import com.siyeh.ig.LightJavaInspectionTestCase;
import com.siyeh.ig.redundancy.RedundantStringOperationInspection;
import com.siyeh.ig.redundancy.ReplaceOnLiteralHasNoEffectInspection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ReplaceOnLiteralHasNoEffectInspectionTest extends LightJavaInspectionTestCase {
@Nullable
@Override
protected InspectionProfileEntry getInspection() {
return new ReplaceOnLiteralHasNoEffectInspection();
}
public void testReplaceOnLiteral() {doTest();}
@Override
protected String getBasePath() {
return JavaTestUtil.getRelativeJavaTestDataPath() + "/inspection/replaceOnLiteral/";
}
}
@@ -2192,6 +2192,7 @@ assignment.of.field.with.mutable.type.problem.descriptor=Assignment to {0} field
return.of.field.with.mutable.type.problem.descriptor=Return of {0} field <code>{1}</code> #loc
ignore.private.methods.option=Ignore assignments in and returns from private methods
inspection.replace.on.literal.display.name=Replacement operation has no effect
inspection.redundant.string.operation.display.name=Redundant 'String' operation
inspection.redundant.string.remove.fix.name=Remove redundant ''{0}()'' call
inspection.redundant.string.fix.family.name=Remove redundant call
@@ -2689,6 +2689,9 @@
<localInspection groupPath="Java" language="JAVA" shortName="StringOperationCanBeSimplified" bundle="messages.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="ReplaceOnLiteralHasNoEffect" bundle="messages.InspectionGadgetsBundle"
key="inspection.replace.on.literal.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.verbose.or.redundant.code.constructs"
enabledByDefault="true" level="WARNING" implementationClass="com.siyeh.ig.redundancy.ReplaceOnLiteralHasNoEffectInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="StringBufferReplaceableByString" bundle="messages.InspectionGadgetsBundle"
key="string.buffer.replaceable.by.string.display.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.verbose.or.redundant.code.constructs" enabledByDefault="true" level="WARNING"
@@ -0,0 +1,109 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.redundancy;
import com.intellij.codeInspection.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import static com.intellij.util.ObjectUtils.tryCast;
public class ReplaceOnLiteralHasNoEffectInspection extends AbstractBaseJavaLocalInspectionTool {
private static final CallMatcher STRING_REPLACE = CallMatcher.exactInstanceCall(CommonClassNames.JAVA_LANG_STRING,
"replace", "replaceFirst", "replaceAll");
private static final int MAX_QUALIFIER_LENGTH = 1000;
private static final int MAX_PATTERN_LENGTH = 200;
@Override
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder,
boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitMethodCallExpression(PsiMethodCallExpression call) {
if (!STRING_REPLACE.test(call)) return;
PsiLiteralExpression qualifier = tryCast(
PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression()), PsiLiteralExpression.class);
if (qualifier == null || qualifier.getTextLength() > MAX_QUALIFIER_LENGTH) return;
String str = tryCast(qualifier.getValue(), String.class);
PsiExpression pattern = PsiUtil.skipParenthesizedExprDown(call.getArgumentList().getExpressions()[0]);
String name = call.getMethodExpression().getReferenceName();
if (!isRedundant(str, pattern, "replace".equals(name))) return;
PsiElement refName = Objects.requireNonNull(call.getMethodExpression().getReferenceNameElement());
holder.registerProblem(call, InspectionGadgetsBundle.message("inspection.replace.on.literal.display.name"),
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
TextRange.create(refName.getTextRangeInParent().getStartOffset(), call.getTextLength()),
ExpressionUtils.isVoidContext(call) ? null : new ReplaceWithQualifierFix());
}
private boolean isRedundant(String str, PsiExpression pattern, boolean literalMatch) {
Object constValue = ExpressionUtils.computeConstantExpression(pattern);
if (constValue != null) {
return isRedundantLiteralMatch(str, constValue, literalMatch);
}
if (literalMatch && pattern instanceof PsiPolyadicExpression &&
((PsiPolyadicExpression)pattern).getOperationTokenType().equals(JavaTokenType.PLUS)) {
PsiExpression[] operands = ((PsiPolyadicExpression)pattern).getOperands();
boolean stringType = false;
for (int i = 0; i < operands.length; i++) {
PsiExpression operand = operands[i];
stringType = stringType || TypeUtils.isJavaLangString(operand.getType());
if (!stringType && (i > 0 || !TypeUtils.isJavaLangString(operands[1].getType()))) continue;
constValue = ExpressionUtils.computeConstantExpression(operand);
if (constValue == null) continue;
if (isRedundantLiteralMatch(str, constValue, true)) return true;
}
}
return false;
}
private boolean isRedundantLiteralMatch(String str, Object value, boolean literalMatch) {
String patternValue;
if (value instanceof String) {
patternValue = (String)value;
} else if (value instanceof Character) {
patternValue = value.toString();
} else {
return false;
}
if (literalMatch) return !str.contains(patternValue);
if (patternValue.length() > MAX_PATTERN_LENGTH) return false;
Pattern regex;
try {
regex = Pattern.compile(patternValue);
}
catch (PatternSyntaxException e) {
return false;
}
return !regex.matcher(str).find();
}
};
}
private static class ReplaceWithQualifierFix implements LocalQuickFix {
@Override
public @NotNull String getFamilyName() {
return InspectionGadgetsBundle.message("inspection.redundant.string.fix.family.name");
}
@Override
public void applyFix(@NotNull Project project,
@NotNull ProblemDescriptor descriptor) {
PsiMethodCallExpression call = tryCast(descriptor.getStartElement(), PsiMethodCallExpression.class);
if (call == null) return;
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
if (qualifier == null) return;
new CommentTracker().replace(call, qualifier);
}
}
}