[codeInsight] IDEA-240288 Inspection for StringBuilder.toString().substring()

This patch enhances RedundantStringOperationInspection with detecting either unnecessary naked calls StringBuilder.toString or redundant toString in StringBuilder.toString.substring.

Signed-off-by: Nikita Eshkeev <nikita.eshkeev@jetbrains.com>

GitOrigin-RevId: 3cd7e964c0acc73fb1ac38061ef31264035f82dc
This commit is contained in:
Nikita Eshkeev
2020-05-08 20:04:18 +03:00
committed by intellij-monorepo-bot
parent 43636d4a6e
commit 597a92c0b2
3 changed files with 173 additions and 4 deletions

View File

@@ -0,0 +1,86 @@
// "Fix all 'Redundant String operation' problems in file" "true"
class StringBuilderToString {
StringBuilderToString() {
String s1 = new StringBuilder().toString();
/* 1 */
String s2 = new StringBuilder()/* 2 */.substring(1);
/* 1 */
String s3 = new StringBuilder()/* 2 */.substring(1, 4);
/* 1 */
String s4 = new StringBuilder()/* 2 */.substring(1, 4);
/* 1 */
int s5 = new StringBuilder()/* 2 */.substring(1, 4).length();
/* 1 */
System.out.println(new StringBuilder()/* 2 */);
/* 1 */
System.out.println(new StringBuilder()/* 2 */.substring(1));
/* 1 */
System.out.println(new StringBuilder()/* 2 */.substring(1, 3));
/* 1 */
System.out.println(new StringBuilder()/* 2 */.substring(1, 3).length());
System.out.println(new StringBuilder().substring(1, 3));
System.out.println(new StringBuilder().substring(1, 3).length());
StringBuilder sb = new StringBuilder();
/* 1 */
String s6 = sb/* 2 */ + sb. /* 3 */toString()/* 4 */;
/* 1 */
String s7 = sb/* 2 */ + sb./* 3 */toString()/* 4 */;
String s8 = "Hello, World" + sb.toString();
String s8 = ("Hello, " + "World") + sb.toString();
String s9 = sb./* 1 */toString()/* 2 */ + 42;
/* 1 */
String s10 = sb/* 2 */ + "Hello, World";
/* 3 */
/* 1 */
String s11 = sb/* 2 */ + sb/* 4 */ + sb./* 5 */toString()/* 6 */;
/* 1 */
System.out.println(sb/* 2 */ + sb. /* 3 */toString()/* 4 */);
/* 1 */
System.out.println(sb/* 2 */ + sb./* 3 */toString()/* 4 */);
/* 3 */
/* 1 */
System.out.println(sb/* 2 */ + sb/* 4 */ + sb./* 5 */toString()/* 6 */);
System.out.println("Hello, World" + sb.toString());
System.out.println(("Hello, " + "World") + sb.toString());
System.out.println(sb./* 1 */toString()/* 2 */ + 42);
/* 1 */
System.out.println(sb/* 2 */ + "Hello, World");
}
void builder(StringBuilder sb) {
String s1 = sb.toString();
/* 1 */
String s2 = sb/* 2 */.substring(1);
/* 1 */
String s3 = sb/* 2 */.substring(1, 4);
/* 1 */
String s4 = sb/* 2 */.substring(1, 4);
/* 1 */
int s5 = sb/* 2 */.substring(1, 4).length();
/* 1 */
String s6 = sb.append(sb)/* 2 */.substring(1);
/* 1 */
String s7 = (sb/* 2 */ + sb./* 3 */toString()/* 4 */).substring(1);
/* 1 */
System.out.println(sb/* 2 */);
/* 1 */
System.out.println(sb/* 2 */.substring(1));
/* 1 */
System.out.println(sb/* 2 */.substring(1, 3));
/* 1 */
System.out.println(sb/* 2 */.substring(1, 3).length());
System.out.println(sb.substring(1, 3));
System.out.println(sb.substring(1, 3).length());
f(sb);
f(sb.toString());
}
void f(StringBuilder s) {}
void f(String s) {}
}

View File

@@ -0,0 +1,58 @@
// "Fix all 'Redundant String operation' problems in file" "true"
class StringBuilderToString {
StringBuilderToString() {
String s1 = new StringBuilder().toString();
String s2 = new StringBuilder()./* 1 */<caret>toString()/* 2 */.substring(1);
String s3 = new StringBuilder()./* 1 */toString()/* 2 */.substring(1, 4);
String s4 = new StringBuilder()./* 1 */toString()/* 2 */.substring(1, 4);
int s5 = new StringBuilder()./* 1 */toString()/* 2 */.substring(1, 4).length();
System.out.println(new StringBuilder()./* 1 */toString()/* 2 */);
System.out.println(new StringBuilder()./* 1 */toString()/* 2 */.substring(1));
System.out.println(new StringBuilder()./* 1 */toString()/* 2 */.substring(1, 3));
System.out.println(new StringBuilder()./* 1 */toString()/* 2 */.substring(1, 3).length());
System.out.println(new StringBuilder().substring(1, 3));
System.out.println(new StringBuilder().substring(1, 3).length());
StringBuilder sb = new StringBuilder();
String s6 = sb./* 1 */toString()/* 2 */ + sb. /* 3 */toString()/* 4 */;
String s7 = sb./* 1 */toString()/* 2 */ + sb./* 3 */toString()/* 4 */;
String s8 = "Hello, World" + sb.toString();
String s8 = ("Hello, " + "World") + sb.toString();
String s9 = sb./* 1 */toString()/* 2 */ + 42;
String s10 = sb./* 1 */toString()/* 2 */ + "Hello, World";
String s11 = sb./* 1 */toString()/* 2 */ + sb./* 3 */toString()/* 4 */ + sb./* 5 */toString()/* 6 */;
System.out.println(sb./* 1 */toString()/* 2 */ + sb. /* 3 */toString()/* 4 */);
System.out.println(sb./* 1 */toString()/* 2 */ + sb./* 3 */toString()/* 4 */);
System.out.println(sb./* 1 */toString()/* 2 */ + sb./* 3 */toString()/* 4 */ + sb./* 5 */toString()/* 6 */);
System.out.println("Hello, World" + sb.toString());
System.out.println(("Hello, " + "World") + sb.toString());
System.out.println(sb./* 1 */toString()/* 2 */ + 42);
System.out.println(sb./* 1 */toString()/* 2 */ + "Hello, World");
}
void builder(StringBuilder sb) {
String s1 = sb.toString();
String s2 = sb./* 1 */toString()/* 2 */.substring(1);
String s3 = sb./* 1 */toString()/* 2 */.substring(1, 4);
String s4 = sb./* 1 */toString()/* 2 */.substring(1, 4);
int s5 = sb./* 1 */toString()/* 2 */.substring(1, 4).length();
String s6 = sb.append(sb)./* 1 */toString()/* 2 */.substring(1);
String s7 = (sb./* 1 */toString()/* 2 */ + sb./* 3 */toString()/* 4 */).substring(1);
System.out.println(sb./* 1 */toString()/* 2 */);
System.out.println(sb./* 1 */toString()/* 2 */.substring(1));
System.out.println(sb./* 1 */toString()/* 2 */.substring(1, 3));
System.out.println(sb./* 1 */toString()/* 2 */.substring(1, 3).length());
System.out.println(sb.substring(1, 3));
System.out.println(sb.substring(1, 3).length());
f(sb);
f(sb.toString());
}
void f(StringBuilder s) {}
void f(String s) {}
}

View File

@@ -31,8 +31,7 @@ import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import static com.intellij.psi.CommonClassNames.JAVA_LANG_OBJECT;
import static com.intellij.psi.CommonClassNames.JAVA_LANG_STRING;
import static com.intellij.psi.CommonClassNames.*;
import static com.intellij.util.ObjectUtils.tryCast;
import static com.siyeh.InspectionGadgetsBundle.BUNDLE;
import static com.siyeh.ig.callMatcher.CallMatcher.*;
@@ -50,7 +49,8 @@ public class RedundantStringOperationInspection extends AbstractBaseJavaLocalIns
private static final CallMatcher STRING_SUBSTRING_TWO_ARG = exactInstanceCall(JAVA_LANG_STRING, "substring").parameterTypes("int", "int");
private static final CallMatcher STRING_SUBSTRING = anyOf(STRING_SUBSTRING_ONE_ARG, STRING_SUBSTRING_TWO_ARG);
private static final CallMatcher STRING_BUILDER_APPEND =
instanceCall(CommonClassNames.JAVA_LANG_ABSTRACT_STRING_BUILDER, "append").parameterTypes(JAVA_LANG_STRING);
instanceCall(JAVA_LANG_ABSTRACT_STRING_BUILDER, "append").parameterTypes(JAVA_LANG_STRING);
private static final CallMatcher STRING_BUILDER_TO_STRING = instanceCall(JAVA_LANG_STRING_BUILDER, "toString").parameterCount(0);
private static final CallMatcher PRINTSTREAM_PRINTLN = instanceCall("java.io.PrintStream", "println")
.parameterTypes(JAVA_LANG_STRING);
private static final CallMatcher METHOD_WITH_REDUNDANT_ZERO_AS_SECOND_PARAMETER =
@@ -87,6 +87,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_INTERN, this::getInternProblem)
.register(PRINTSTREAM_PRINTLN, call -> getRedundantArgumentProblem(getSingleEmptyStringArgument(call)))
.register(METHOD_WITH_REDUNDANT_ZERO_AS_SECOND_PARAMETER, this::getRedundantZeroAsSecondParameterProblem)
@@ -116,7 +117,7 @@ public class RedundantStringOperationInspection extends AbstractBaseJavaLocalIns
public void visitNewExpression(PsiNewExpression expression) {
PsiJavaCodeReferenceElement classRef = expression.getClassReference();
ProblemDescriptor descriptor = null;
if (ConstructionUtils.isReferenceTo(classRef, CommonClassNames.JAVA_LANG_STRING_BUILDER, CommonClassNames.JAVA_LANG_STRING_BUFFER)) {
if (ConstructionUtils.isReferenceTo(classRef, JAVA_LANG_STRING_BUILDER, JAVA_LANG_STRING_BUFFER)) {
descriptor = getRedundantArgumentProblem(getSingleEmptyStringArgument(expression));
}
else if (ConstructionUtils.isReferenceTo(classRef, JAVA_LANG_STRING) && !myInspection.ignoreStringConstructor) {
@@ -276,6 +277,30 @@ public class RedundantStringOperationInspection extends AbstractBaseJavaLocalIns
return getSingleEmptyStringArgument(call) != null ? getProblem(call, "inspection.redundant.string.call.message") : null;
}
@Nullable
private ProblemDescriptor getRedundantStringBuilderToStringProblem(@NotNull final PsiMethodCallExpression call) {
if (!ExpressionUtils.isConversionToStringNecessary(call, false)) {
// report naked `new StringBuilder().toString()`
final PsiElement parent = PsiUtil.skipParenthesizedExprUp(call.getParent());
if (parent instanceof PsiPolyadicExpression && ((PsiPolyadicExpression)parent).getOperationTokenType() == JavaTokenType.PLUS) {
// if there is a polyadic expression where all the operands are `new StringBuilder().toString()`
// then it is wrong to report all the `toString` calls as redundant: at least one of them should call
// `toString()`, let's choose the last one to remain with `toString()`
final PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression)parent;
final PsiExpression lastChild = PsiUtil.skipParenthesizedExprDown(polyadicExpression.getOperands()[polyadicExpression.getOperands().length - 1]);
if (lastChild == call) {
return null;
}
}
return getProblem(call, "inspection.redundant.string.call.message");
}
final PsiMethodCallExpression substringCall = PsiTreeUtil.getParentOfType(call, PsiMethodCallExpression.class);
if (!STRING_SUBSTRING.test(substringCall)) return null;
return getProblem(call, "inspection.redundant.string.call.message");
}
@Nullable
private ProblemDescriptor getInternProblem(PsiMethodCallExpression call) {
return PsiUtil.isConstantExpression(call.getMethodExpression().getQualifierExpression())