From b27935c747899ee7d7f5dbccf0b6909d1ac4b374 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Mon, 27 Dec 2021 16:42:45 +0700 Subject: [PATCH] [java-inspections] IDEA-285742 New inspection: replacement method has no effect GitOrigin-RevId: 708af15324387475e434e2e0c87fe2d26ceac1ef --- .../ReplaceOnLiteralHasNoEffect.html | 13 +++ .../afterReplaceOnLiteral.java | 4 + .../beforeReplaceOnLiteral.java | 4 + .../beforeReplaceOnLiteralVoidContext.java | 6 + .../replaceOnLiteral/ReplaceOnLiteral.java | 25 ++++ ...OnLiteralHasNoEffectInspectionFixTest.java | 23 ++++ ...aceOnLiteralHasNoEffectInspectionTest.java | 27 +++++ .../InspectionGadgetsBundle.properties | 1 + .../src/META-INF/InspectionGadgets.xml | 3 + ...ReplaceOnLiteralHasNoEffectInspection.java | 109 ++++++++++++++++++ 10 files changed, 215 insertions(+) create mode 100644 java/java-impl/src/inspectionDescriptions/ReplaceOnLiteralHasNoEffect.html create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceOnLiteral/afterReplaceOnLiteral.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceOnLiteral/beforeReplaceOnLiteral.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceOnLiteral/beforeReplaceOnLiteralVoidContext.java create mode 100644 java/java-tests/testData/inspection/replaceOnLiteral/ReplaceOnLiteral.java create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/ReplaceOnLiteralHasNoEffectInspectionFixTest.java create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/ReplaceOnLiteralHasNoEffectInspectionTest.java create mode 100644 plugins/InspectionGadgets/src/com/siyeh/ig/redundancy/ReplaceOnLiteralHasNoEffectInspection.java diff --git a/java/java-impl/src/inspectionDescriptions/ReplaceOnLiteralHasNoEffect.html b/java/java-impl/src/inspectionDescriptions/ReplaceOnLiteralHasNoEffect.html new file mode 100644 index 000000000000..cf10c0e64b86 --- /dev/null +++ b/java/java-impl/src/inspectionDescriptions/ReplaceOnLiteralHasNoEffect.html @@ -0,0 +1,13 @@ + + +Reports string methods like replace, replaceAll, replaceFirst +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. +

Example:

+
+  "hello".replace("$value$", value); // replacement does nothing
+
+ +

New in 2022.1

+ + \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceOnLiteral/afterReplaceOnLiteral.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceOnLiteral/afterReplaceOnLiteral.java new file mode 100644 index 000000000000..529b154657e6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceOnLiteral/afterReplaceOnLiteral.java @@ -0,0 +1,4 @@ +// "Remove redundant call" "true" +class X { + String s = "a$b"+ "c"; +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceOnLiteral/beforeReplaceOnLiteral.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceOnLiteral/beforeReplaceOnLiteral.java new file mode 100644 index 000000000000..e9752dae01f5 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceOnLiteral/beforeReplaceOnLiteral.java @@ -0,0 +1,4 @@ +// "Remove redundant call" "true" +class X { + String s = "a$b"+"c".replace("$", "/"); +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceOnLiteral/beforeReplaceOnLiteralVoidContext.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceOnLiteral/beforeReplaceOnLiteralVoidContext.java new file mode 100644 index 000000000000..d9e9db422c5d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceOnLiteral/beforeReplaceOnLiteralVoidContext.java @@ -0,0 +1,6 @@ +// "Fix all 'Replacement operation has no effect' problems in file" "false" +class X { + void test() { + "c".replace("$", "/"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/replaceOnLiteral/ReplaceOnLiteral.java b/java/java-tests/testData/inspection/replaceOnLiteral/ReplaceOnLiteral.java new file mode 100644 index 000000000000..908bdd7ce921 --- /dev/null +++ b/java/java-tests/testData/inspection/replaceOnLiteral/ReplaceOnLiteral.java @@ -0,0 +1,25 @@ +public class ReplaceOnLiteral { + private static final String PATTERN = "$a$"; + + void test(String s) { + "a".replace("b", "c"); + "a".replace('b', 'c'); + "abc".replace('a', 'b'); + "abc".replaceFirst("^b", "c"); + "abc".replaceFirst("^a", "c"); + "abc".replaceAll("^b", "c"); + "abc".replaceAll("^a", "c"); + "abc".replaceAll(PATTERN, s); + "abc".replace(PATTERN, s); + + "a".replace("b" + s, ""); + "a".replace("a" + s, ""); + "a".replace('a' + s, ""); + "a".replace('b' + s, ""); + "a".replace(123 + s, ""); + "a".replace((s + ('x') + s), "c"); + "a".replace(PATTERN+s, ""); + + "a".replaceFirst("x" + s, ""); + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/ReplaceOnLiteralHasNoEffectInspectionFixTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/ReplaceOnLiteralHasNoEffectInspectionFixTest.java new file mode 100644 index 000000000000..102c41701af7 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/ReplaceOnLiteralHasNoEffectInspectionFixTest.java @@ -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"; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/ReplaceOnLiteralHasNoEffectInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/ReplaceOnLiteralHasNoEffectInspectionTest.java new file mode 100644 index 000000000000..a3e27bcb1fef --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/ReplaceOnLiteralHasNoEffectInspectionTest.java @@ -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/"; + } +} \ No newline at end of file diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/resources/messages/InspectionGadgetsBundle.properties b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/resources/messages/InspectionGadgetsBundle.properties index 48c8f6c4fd50..ac3d20f9881c 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/resources/messages/InspectionGadgetsBundle.properties +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/resources/messages/InspectionGadgetsBundle.properties @@ -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 {1} #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 diff --git a/plugins/InspectionGadgets/src/META-INF/InspectionGadgets.xml b/plugins/InspectionGadgets/src/META-INF/InspectionGadgets.xml index b5a511559b1b..a425cafaaee6 100644 --- a/plugins/InspectionGadgets/src/META-INF/InspectionGadgets.xml +++ b/plugins/InspectionGadgets/src/META-INF/InspectionGadgets.xml @@ -2689,6 +2689,9 @@ + 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); + } + } +}