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);
+ }
+ }
+}