[java-inspections] RedundantStringOperation: suggest using 'String#contentEquals' to compare strings with StringBuilder

IDEA-273162

GitOrigin-RevId: 89c0c2d697cec22b8d5c661f2063e35ccab9e9aa
This commit is contained in:
Andrey Cherkasov
2022-12-12 21:47:48 +04:00
committed by intellij-monorepo-bot
parent f88fdf391c
commit f0efafd51c
6 changed files with 87 additions and 16 deletions

View File

@@ -0,0 +1,6 @@
// "Use 'contentEquals()' and remove redundant 'toString()' call" "true-preview"
class Main {
boolean foo(String s, StringBuffer sb) {
return ((s)).contentEquals(((((sb)))));
}
}

View File

@@ -0,0 +1,6 @@
// "Use 'contentEquals()' and remove redundant 'toString()' call" "true-preview"
class Main {
boolean foo(String s, StringBuilder sb) {
return s.contentEquals(sb);
}
}

View File

@@ -0,0 +1,6 @@
// "Use 'contentEquals()' and remove redundant 'toString()' call" "true-preview"
class Main {
boolean foo(String s, StringBuffer sb) {
return ((s)).equals(((((sb)).toString<caret>())));
}
}

View File

@@ -0,0 +1,6 @@
// "Use 'contentEquals()' and remove redundant 'toString()' call" "true-preview"
class Main {
boolean foo(String s, StringBuilder sb) {
return s.equals(sb.toString<caret>());
}
}

View File

@@ -2383,7 +2383,8 @@ unnecessary.fully.qualified.name.fix.family.name=Replace fully qualified name
return.of.collection.field.fix.family.name=Make return collection 'unmodifiable'
remove.redundant.substring.fix.family.name=Remove redundant 'substring()' call
remove.redundant.string.fix.text=Use ''{0}()'' and remove redundant ''{1}()'' call
use.equalsignorecase.for.case.insensitive.comparison=Use equalsIgnoreCase() for case-insensitive comparison
use.equalsignorecase.for.case.insensitive.comparison=Use 'equalsIgnoreCase()' for case-insensitive comparison
use.contentequals=Use 'contentEquals()' for comparison with 'AbstractStringBuilder'
use.isblank.to.check.if.string.is.whitespace.or.empty=Use 'isBlank()' to check if a string is empty or only contains whitespace
make.class.final.fix.family.name=Make class final
side.effects.method.ref.to.lambda.fix.family.name={0} (side effects)

View File

@@ -6,7 +6,6 @@ import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.quickfix.DeleteElementFix;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.options.OptPane;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.codeInspection.util.IntentionFamilyName;
import com.intellij.codeInspection.util.IntentionName;
import com.intellij.java.analysis.JavaAnalysisBundle;
@@ -26,17 +25,18 @@ import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.*;
import org.jetbrains.annotations.*;
import javax.swing.*;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Function;
import static com.intellij.codeInspection.options.OptPane.*;
import static com.intellij.codeInspection.options.OptPane.checkbox;
import static com.intellij.codeInspection.options.OptPane.pane;
import static com.intellij.psi.CommonClassNames.*;
import static com.intellij.util.ObjectUtils.tryCast;
import static com.siyeh.HardcodedMethodConstants.EQUALS_IGNORE_CASE;
import static com.siyeh.HardcodedMethodConstants.TO_STRING;
import static com.siyeh.InspectionGadgetsBundle.BUNDLE;
import static com.siyeh.ig.callMatcher.CallMatcher.*;
@@ -46,6 +46,8 @@ public class RedundantStringOperationInspection extends AbstractBaseJavaLocalIns
REPLACE_WITH_ARGUMENTS
}
public static final String CONTENT_EQUALS = "contentEquals";
private static final CallMatcher BYTE_ARRAY_OUTPUT_STREAM_INTO_BYTE_ARRAY =
exactInstanceCall(JAVA_IO_BYTE_ARRAY_OUTPUT_STREAM, "toByteArray").parameterCount(0);
private static final CallMatcher STRING_TO_STRING = exactInstanceCall(JAVA_LANG_STRING, "toString").parameterCount(0);
@@ -99,7 +101,7 @@ public class RedundantStringOperationInspection extends AbstractBaseJavaLocalIns
.register(STRING_TO_STRING, call -> getProblem(call, "inspection.redundant.string.call.message"))
.register(STRING_SUBSTRING, this::getSubstringProblem)
.register(STRING_BUILDER_APPEND, this::getAppendProblem)
.register(STRING_BUILDER_TO_STRING, this::getRedundantStringBuilderToStringProblem)
.register(STRING_BUILDER_TO_STRING, this::getStringBuilderToStringProblem)
.register(STRING_INTERN, this::getInternProblem)
.register(PRINTSTREAM_PRINTLN, call ->
getRedundantArgumentProblem(getSingleEmptyStringArgument(call), "inspection.redundant.empty.string.argument.message"))
@@ -417,6 +419,13 @@ public class RedundantStringOperationInspection extends AbstractBaseJavaLocalIns
return getSingleEmptyStringArgument(call) != null ? getProblem(call, "inspection.redundant.string.call.message") : null;
}
@Nullable
private ProblemDescriptor getStringBuilderToStringProblem(@NotNull final PsiMethodCallExpression call) {
final ProblemDescriptor descriptor = getRedundantStringBuilderToStringProblem(call);
if (descriptor != null) return descriptor;
return getUseContentEqualsProblem(call);
}
@Nullable
private ProblemDescriptor getRedundantStringBuilderToStringProblem(@NotNull final PsiMethodCallExpression call) {
final PsiMethodCallExpression substringCall = PsiTreeUtil.getParentOfType(call, PsiMethodCallExpression.class);
@@ -428,6 +437,21 @@ public class RedundantStringOperationInspection extends AbstractBaseJavaLocalIns
return getProblem(call, "inspection.redundant.string.call.message");
}
@Nullable
private ProblemDescriptor getUseContentEqualsProblem(@NotNull final PsiMethodCallExpression call) {
PsiElement parent = PsiUtil.skipParenthesizedExprUp(call.getParent());
if (parent instanceof PsiExpressionList list &&
list.getExpressionCount() == 1 &&
parent.getParent() instanceof PsiMethodCallExpression parentCall &&
STRING_EQUALS.test(parentCall)) {
PsiElement nameElement = Objects.requireNonNull(call.getMethodExpression().getReferenceNameElement());
final String message = InspectionGadgetsBundle.message("inspection.x.call.can.be.replaced.with.y", CONTENT_EQUALS);
return myManager.createProblemDescriptor(nameElement, message, new UseContentEqualsFix(),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, myIsOnTheFly);
}
return null;
}
@Nullable
private ProblemDescriptor getInternProblem(PsiMethodCallExpression call) {
return PsiUtil.isConstantExpression(call.getMethodExpression().getQualifierExpression())
@@ -665,6 +689,38 @@ public class RedundantStringOperationInspection extends AbstractBaseJavaLocalIns
}
}
private static void useMethodInsteadOfRedundantCall(String methodToUse, PsiMethodCallExpression redundantCall) {
PsiMethodCallExpression equalsCall = PsiTreeUtil.getParentOfType(redundantCall, PsiMethodCallExpression.class);
if (equalsCall == null) return;
PsiExpression qualifierBeforeChangeCase = ExpressionUtils.getEffectiveQualifier(redundantCall.getMethodExpression());
if (qualifierBeforeChangeCase == null) return;
CommentTracker ct = new CommentTracker();
ct.replaceAndRestoreComments(redundantCall, qualifierBeforeChangeCase);
ExpressionUtils.bindCallTo(equalsCall, methodToUse);
}
private static class UseContentEqualsFix implements LocalQuickFix {
@Override
public @NotNull String getFamilyName() {
return InspectionGadgetsBundle.message("use.contentequals");
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getName() {
return InspectionGadgetsBundle.message("remove.redundant.string.fix.text", CONTENT_EQUALS, TO_STRING);
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiMethodCallExpression changeCaseCall = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiMethodCallExpression.class);
if (changeCaseCall == null) return;
useMethodInsteadOfRedundantCall(CONTENT_EQUALS, changeCaseCall);
}
}
private static class RemoveRedundantChangeCaseFix implements LocalQuickFix {
private final @NotNull String caseRedundant;
private final @NotNull PlaceCaseEqualType myPlaceCaseEqualType;
@@ -699,23 +755,13 @@ public class RedundantStringOperationInspection extends AbstractBaseJavaLocalIns
if (changeCaseCall == null) return;
if (myPlaceCaseEqualType == PlaceCaseEqualType.RIGHT) {
fixRightChangeCase(changeCaseCall);
useMethodInsteadOfRedundantCall(EQUALS_IGNORE_CASE, changeCaseCall);
return;
}
fixLeftAndBothChangeCase(changeCaseCall);
}
private static void fixRightChangeCase(PsiMethodCallExpression changeCaseCall) {
PsiMethodCallExpression equalsCall = PsiTreeUtil.getParentOfType(changeCaseCall, PsiMethodCallExpression.class);
if (equalsCall == null) return;
PsiExpression qualifierBeforeChangeCase = ExpressionUtils.getEffectiveQualifier(changeCaseCall.getMethodExpression());
if (qualifierBeforeChangeCase == null) return;
CommentTracker ct = new CommentTracker();
ct.replaceAndRestoreComments(changeCaseCall, qualifierBeforeChangeCase);
ExpressionUtils.bindCallTo(equalsCall, EQUALS_IGNORE_CASE);
}
private void fixLeftAndBothChangeCase(PsiMethodCallExpression changeCaseCall) {
PsiExpression qualifierBeforeChangeCase = changeCaseCall.getMethodExpression().getQualifierExpression();
if (qualifierBeforeChangeCase == null) return;