mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-05-06 05:10:22 +07:00
IDEA-167466 String concatenation in loop inspection: provide additional quick-fix to introduce a new StringBuilder;
String concatenation in loop is enabled by default now
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
// "Introduce new StringBuilder to update variable 's'" "true"
|
||||
|
||||
public class Main {
|
||||
void test(boolean b) {
|
||||
String s = "";
|
||||
if (b) {
|
||||
StringBuilder sBuilder = new StringBuilder();
|
||||
for(int i = 0; i<10; i++) {
|
||||
if(sBuilder.indexOf("a") > 0) {
|
||||
System.out.println(sBuilder);
|
||||
break;
|
||||
}
|
||||
sBuilder.append(i);
|
||||
}
|
||||
s = sBuilder.toString();
|
||||
}
|
||||
s = s.trim();
|
||||
System.out.println(s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// "Introduce new StringBuilder to update variable 's'" "true"
|
||||
|
||||
public class Main {
|
||||
void test(String s) {
|
||||
StringBuilder sBuilder = new StringBuilder(s);
|
||||
for(int i = 0; i<10; i++) {
|
||||
sBuilder.append(i);
|
||||
}
|
||||
s = sBuilder.toString();
|
||||
System.out.println(s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// "Introduce new StringBuilder to update variable 's'" "true"
|
||||
|
||||
public class Main {
|
||||
void test(boolean b) {
|
||||
String s = "";
|
||||
if (b)
|
||||
for(int i=0; i<10; i++) {
|
||||
if(s.indexOf("a") > 0) {
|
||||
System.out.println(s);
|
||||
break;
|
||||
}
|
||||
s+<caret>=i;
|
||||
}
|
||||
s = s.trim();
|
||||
System.out.println(s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// "Introduce new StringBuilder to update variable 's'" "true"
|
||||
|
||||
public class Main {
|
||||
void test(String s) {
|
||||
for(int i=0; i<10; i++) {
|
||||
s+<caret>=i;
|
||||
}
|
||||
System.out.println(s);
|
||||
}
|
||||
}
|
||||
@@ -2032,7 +2032,7 @@
|
||||
implementationClass="com.siyeh.ig.performance.StringBufferToStringInConcatenationInspection"/>
|
||||
<localInspection groupPath="Java" language="JAVA" suppressId="StringConcatenationInLoop" alternativeId="StringContatenationInLoop" shortName="StringConcatenationInLoops"
|
||||
bundle="com.siyeh.InspectionGadgetsBundle" key="string.concatenation.in.loops.display.name"
|
||||
groupBundle="messages.InspectionsBundle" groupKey="group.names.performance.issues" enabledByDefault="false"
|
||||
groupBundle="messages.InspectionsBundle" groupKey="group.names.performance.issues" enabledByDefault="true"
|
||||
level="WARNING" implementationClass="com.siyeh.ig.performance.StringConcatenationInLoopsInspection"/>
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="StringConcatenationInsideStringBufferAppend" bundle="com.siyeh.InspectionGadgetsBundle"
|
||||
key="string.concatenation.inside.string.buffer.append.display.name" groupBundle="messages.InspectionsBundle"
|
||||
|
||||
@@ -2201,6 +2201,11 @@ junit5.converter.display.name=JUnit 4 test can be JUnit 5
|
||||
junit5.converter.fix.name=Migrate to JUnit 5
|
||||
call.to.suspicious.string.method.display.name=Call to suspicious String method
|
||||
call.to.suspicious.string.method.problem.descriptor=<code>String.#ref()</code> called in internationalized context #loc
|
||||
|
||||
string.concatenation.replace.fix=Replace with StringBuilder
|
||||
string.concatenation.replace.fix.name=Convert variable ''{0}'' from String to {1}
|
||||
string.concatenation.introduce.fix=Introduce StringBuilder
|
||||
string.concatenation.introduce.fix.name=Introduce new {1} to update variable ''{0}''
|
||||
|
||||
ignored.class.names=Ignore classes (including subclasses)
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.intellij.psi.search.searches.ReferencesSearch;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.Query;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
@@ -73,6 +74,35 @@ public class StringConcatenationInLoopsInspection extends BaseInspection {
|
||||
return new StringConcatenationInLoopsVisitor();
|
||||
}
|
||||
|
||||
static PsiLoopStatement getOutermostCommonLoop(PsiExpression expression, PsiVariable variable) {
|
||||
PsiElement stopAt = null;
|
||||
PsiCodeBlock block = StringConcatenationInLoopsVisitor.getSurroundingBlock(expression);
|
||||
if (block != null) {
|
||||
PsiElement ref;
|
||||
if (expression instanceof PsiAssignmentExpression) {
|
||||
ref = expression;
|
||||
}
|
||||
else {
|
||||
PsiReference reference = ReferencesSearch.search(variable, new LocalSearchScope(expression)).findFirst();
|
||||
ref = reference != null ? reference.getElement() : null;
|
||||
}
|
||||
if (ref != null) {
|
||||
PsiElement[] elements = StreamEx.of(DefUseUtil.getDefs(block, variable, expression)).prepend(expression).toArray(PsiElement[]::new);
|
||||
stopAt = PsiTreeUtil.findCommonParent(elements);
|
||||
}
|
||||
}
|
||||
PsiElement parent = expression.getParent();
|
||||
PsiLoopStatement commonLoop = null;
|
||||
while (parent != null && parent != stopAt && !(parent instanceof PsiMethod)
|
||||
&& !(parent instanceof PsiClass) && !(parent instanceof PsiLambdaExpression)) {
|
||||
if (parent instanceof PsiLoopStatement) {
|
||||
commonLoop = (PsiLoopStatement)parent;
|
||||
}
|
||||
parent = parent.getParent();
|
||||
}
|
||||
return commonLoop;
|
||||
}
|
||||
|
||||
private static class StringConcatenationInLoopsVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
@@ -92,7 +122,7 @@ public class StringConcatenationInLoopsInspection extends BaseInspection {
|
||||
if (!isAppendedRepeatedly(expression)) return;
|
||||
final PsiJavaToken sign = expression.getTokenBeforeOperand(operands[1]);
|
||||
assert sign != null;
|
||||
registerError(sign, getAppendedVariable(expression));
|
||||
registerError(sign, expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,7 +140,7 @@ public class StringConcatenationInLoopsInspection extends BaseInspection {
|
||||
PsiExpression lhs = PsiUtil.skipParenthesizedExprDown(expression.getLExpression());
|
||||
|
||||
if (!(lhs instanceof PsiReferenceExpression)) return;
|
||||
registerError(sign, getAppendedVariable(expression));
|
||||
registerError(sign, expression);
|
||||
}
|
||||
|
||||
private static boolean checkExpression(PsiExpression expression) {
|
||||
@@ -160,34 +190,6 @@ public class StringConcatenationInLoopsInspection extends BaseInspection {
|
||||
return !notUsedCompletely;
|
||||
}
|
||||
|
||||
private static PsiLoopStatement getOutermostCommonLoop(PsiExpression expression, PsiVariable variable) {
|
||||
PsiElement stopAt = null;
|
||||
PsiCodeBlock block = getSurroundingBlock(expression);
|
||||
if(block != null) {
|
||||
PsiElement ref;
|
||||
if(expression instanceof PsiAssignmentExpression) {
|
||||
ref = expression;
|
||||
} else {
|
||||
PsiReference reference = ReferencesSearch.search(variable, new LocalSearchScope(expression)).findFirst();
|
||||
ref = reference != null ? reference.getElement() : null;
|
||||
}
|
||||
if(ref != null) {
|
||||
PsiElement[] elements = StreamEx.of(DefUseUtil.getDefs(block, variable, expression)).prepend(expression).toArray(PsiElement[]::new);
|
||||
stopAt = PsiTreeUtil.findCommonParent(elements);
|
||||
}
|
||||
}
|
||||
PsiElement parent = expression.getParent();
|
||||
PsiLoopStatement commonLoop = null;
|
||||
while(parent != null && parent != stopAt && !(parent instanceof PsiMethod)
|
||||
&& !(parent instanceof PsiClass) && !(parent instanceof PsiLambdaExpression)) {
|
||||
if(parent instanceof PsiLoopStatement) {
|
||||
commonLoop = (PsiLoopStatement)parent;
|
||||
}
|
||||
parent = parent.getParent();
|
||||
}
|
||||
return commonLoop;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiCodeBlock getSurroundingBlock(PsiElement expression) {
|
||||
PsiElement parent = PsiTreeUtil.getParentOfType(expression, PsiMethod.class, PsiClassInitializer.class, PsiLambdaExpression.class);
|
||||
@@ -284,52 +286,41 @@ public class StringConcatenationInLoopsInspection extends BaseInspection {
|
||||
return element instanceof PsiVariable ? (PsiVariable)element : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@NotNull
|
||||
@Override
|
||||
protected InspectionGadgetsFix buildFix(Object... infos) {
|
||||
return (infos.length > 0 && infos[0] instanceof PsiLocalVariable) ? new ReplaceWithStringBuilderFix((PsiVariable)infos[0]) : null;
|
||||
protected InspectionGadgetsFix[] buildFixes(Object... infos) {
|
||||
PsiExpression expression = ObjectUtils.tryCast(ArrayUtil.getFirstElement(infos), PsiExpression.class);
|
||||
PsiVariable var = getAppendedVariable(expression);
|
||||
if (var == null) return InspectionGadgetsFix.EMPTY_ARRAY;
|
||||
List<InspectionGadgetsFix> fixes = new ArrayList<>();
|
||||
if (var instanceof PsiLocalVariable) {
|
||||
fixes.add(new ReplaceWithStringBuilderFix(var));
|
||||
PsiLoopStatement loop = getOutermostCommonLoop(expression, var);
|
||||
if (ReferencesSearch.search(var).findAll().stream()
|
||||
.map(PsiReference::getElement).filter(e -> !PsiTreeUtil.isAncestor(loop, e, true))
|
||||
.limit(2).count() > 1) {
|
||||
fixes.add(new IntroduceStringBuilderFix(var));
|
||||
}
|
||||
}
|
||||
else if (var instanceof PsiParameter) {
|
||||
fixes.add(new IntroduceStringBuilderFix(var));
|
||||
}
|
||||
return fixes.toArray(InspectionGadgetsFix.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
static class ReplaceWithStringBuilderFix extends InspectionGadgetsFix {
|
||||
private static final Pattern PRINT_OR_PRINTLN = Pattern.compile("print|println");
|
||||
static abstract class AbstractStringBuilderFix extends InspectionGadgetsFix {
|
||||
static final Pattern PRINT_OR_PRINTLN = Pattern.compile("print|println");
|
||||
|
||||
private String myName;
|
||||
private String myTargetType;
|
||||
String myName;
|
||||
String myTargetType;
|
||||
|
||||
public ReplaceWithStringBuilderFix(PsiVariable variable) {
|
||||
public AbstractStringBuilderFix(PsiVariable variable) {
|
||||
myName = variable.getName();
|
||||
myTargetType = PsiUtil.isLanguageLevel5OrHigher(variable) ? "StringBuilder" : "StringBuffer";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFix(Project project, ProblemDescriptor descriptor) {
|
||||
PsiExpression expression = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiExpression.class);
|
||||
if (expression == null) return;
|
||||
PsiVariable variable = getAppendedVariable(expression);
|
||||
if(!(variable instanceof PsiLocalVariable)) return;
|
||||
variable.normalizeDeclaration();
|
||||
PsiTypeElement typeElement = variable.getTypeElement();
|
||||
if(typeElement == null) return;
|
||||
List<PsiElement> results = new ArrayList<>();
|
||||
CommentTracker ct = new CommentTracker();
|
||||
replaceAll(variable, null, results, ct);
|
||||
results.add(ct.replace(typeElement, "java.lang." + myTargetType));
|
||||
PsiExpression initializer = variable.getInitializer();
|
||||
if(initializer != null) {
|
||||
results.add(ct.replace(initializer, generateNewStringBuilder(initializer, ct)));
|
||||
}
|
||||
PsiStatement commentPlace = PsiTreeUtil.getParentOfType(variable, PsiStatement.class);
|
||||
ct.insertCommentsBefore(commentPlace == null ? variable : commentPlace);
|
||||
for(PsiElement result : results) {
|
||||
if(result.isValid()) {
|
||||
result = JavaCodeStyleManager.getInstance(project).shortenClassReferences(result);
|
||||
CodeStyleManager.getInstance(project).reformat(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String generateNewStringBuilder(PsiExpression initializer, CommentTracker ct) {
|
||||
String generateNewStringBuilder(PsiExpression initializer, CommentTracker ct) {
|
||||
if(ExpressionUtils.isNullLiteral(initializer)) {
|
||||
return ct.text(initializer);
|
||||
}
|
||||
@@ -337,36 +328,44 @@ public class StringConcatenationInLoopsInspection extends BaseInspection {
|
||||
return "new java.lang." + myTargetType + "(" + text + ")";
|
||||
}
|
||||
|
||||
private void replaceAll(PsiVariable variable,
|
||||
PsiElement scope,
|
||||
List<PsiElement> results,
|
||||
CommentTracker ct) {
|
||||
void replaceAll(PsiVariable variable,
|
||||
PsiVariable builderVariable,
|
||||
PsiElement scope,
|
||||
List<PsiElement> results,
|
||||
CommentTracker ct) {
|
||||
Query<PsiReference> query =
|
||||
scope == null ? ReferencesSearch.search(variable) : ReferencesSearch.search(variable, new LocalSearchScope(scope));
|
||||
Collection<PsiReference> refs = query.findAll();
|
||||
for(PsiReference ref : refs) {
|
||||
PsiElement target = ref.getElement();
|
||||
if(target instanceof PsiReferenceExpression && target.isValid()) {
|
||||
replace(variable, results, (PsiReferenceExpression)target, ct);
|
||||
replace(variable, builderVariable, results, (PsiReferenceExpression)target, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void replace(PsiVariable variable, List<PsiElement> results, PsiReferenceExpression ref, CommentTracker ct) {
|
||||
private void replace(PsiVariable variable,
|
||||
PsiVariable builderVariable,
|
||||
List<PsiElement> results,
|
||||
PsiReferenceExpression ref,
|
||||
CommentTracker ct) {
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(ref.getParent());
|
||||
if(parent instanceof PsiAssignmentExpression) {
|
||||
PsiAssignmentExpression assignment = (PsiAssignmentExpression)parent;
|
||||
if(PsiUtil.skipParenthesizedExprDown(assignment.getLExpression()) == ref) {
|
||||
replaceInAssignment(variable, results, assignment, ct);
|
||||
replaceInAssignment(variable, builderVariable, results, assignment, ct);
|
||||
return;
|
||||
} else {
|
||||
// ref is r-value
|
||||
if(assignment.getOperationTokenType().equals(JavaTokenType.PLUSEQ)) return;
|
||||
}
|
||||
}
|
||||
if (variable != builderVariable) {
|
||||
ref.handleElementRename(builderVariable.getName());
|
||||
}
|
||||
PsiMethodCallExpression methodCallExpression = ExpressionUtils.getCallForQualifier(ref);
|
||||
if(methodCallExpression != null) {
|
||||
replaceInCallQualifier(variable, results, methodCallExpression, ct);
|
||||
replaceInCallQualifier(builderVariable, results, methodCallExpression, ct);
|
||||
return;
|
||||
}
|
||||
if(parent instanceof PsiExpressionList && parent.getParent() instanceof PsiMethodCallExpression) {
|
||||
@@ -392,22 +391,21 @@ public class StringConcatenationInLoopsInspection extends BaseInspection {
|
||||
}
|
||||
if (operands.length > 1 && operands[0] == ref && TypeUtils.isJavaLangString(operands[1].getType())) return;
|
||||
}
|
||||
results.add(ct.replace(ref, variable.getName()+".toString()"));
|
||||
results.add(ct.replace(ref, builderVariable.getName() + ".toString()"));
|
||||
}
|
||||
|
||||
private static boolean canAcceptBuilderInsteadOfString(PsiMethodCallExpression call) {
|
||||
return MethodCallUtils.isCallToMethod(call, CommonClassNames.JAVA_LANG_STRING_BUILDER, null, "append",
|
||||
(PsiType[])null) ||
|
||||
MethodCallUtils.isCallToMethod(call, CommonClassNames.JAVA_LANG_STRING_BUFFER, null, "append",
|
||||
(PsiType[])null) ||
|
||||
(PsiType[])null) ||
|
||||
MethodCallUtils.isCallToMethod(call, "java.io.PrintStream", null, PRINT_OR_PRINTLN,
|
||||
(PsiType[])null) ||
|
||||
(PsiType[])null) ||
|
||||
MethodCallUtils.isCallToMethod(call, "java.io.PrintWriter", null, PRINT_OR_PRINTLN,
|
||||
(PsiType[])null);
|
||||
(PsiType[])null);
|
||||
}
|
||||
|
||||
private static void replaceInCallQualifier(PsiVariable variable,
|
||||
List<PsiElement> results,
|
||||
private static void replaceInCallQualifier(PsiVariable variable, List<PsiElement> results,
|
||||
PsiMethodCallExpression call,
|
||||
CommentTracker ct) {
|
||||
PsiMethod method = call.resolveMethod();
|
||||
@@ -460,7 +458,7 @@ public class StringConcatenationInLoopsInspection extends BaseInspection {
|
||||
}
|
||||
|
||||
private void replaceInAssignment(PsiVariable variable,
|
||||
List<PsiElement> results,
|
||||
PsiVariable builderVariable, List<PsiElement> results,
|
||||
PsiAssignmentExpression assignment,
|
||||
CommentTracker ct) {
|
||||
PsiExpression rValue = PsiUtil.skipParenthesizedExprDown(assignment.getRExpression());
|
||||
@@ -475,8 +473,9 @@ public class StringConcatenationInLoopsInspection extends BaseInspection {
|
||||
StreamEx.iterate(operands[1], Objects::nonNull, PsiElement::getNextSibling).forEach(ct::markUnchanged);
|
||||
String text = rValue.getText().substring(operands[1].getStartOffsetInParent());
|
||||
PsiExpression added = JavaPsiFacade.getElementFactory(variable.getProject()).createExpressionFromText(text, assignment);
|
||||
replaceAll(variable, added, results, ct);
|
||||
StringBuilder replacement = ChangeToAppendUtil.buildAppendExpression(added, false, new StringBuilder(variable.getName()));
|
||||
replaceAll(variable, builderVariable, added, results, ct);
|
||||
StringBuilder replacement =
|
||||
ChangeToAppendUtil.buildAppendExpression(added, false, new StringBuilder(builderVariable.getName()));
|
||||
if (replacement != null) {
|
||||
results.add(ct.replace(assignment, replacement.toString()));
|
||||
}
|
||||
@@ -486,15 +485,15 @@ public class StringConcatenationInLoopsInspection extends BaseInspection {
|
||||
PsiExpression lastOp = operands[operands.length - 1];
|
||||
if (ExpressionUtils.isReferenceTo(lastOp, variable)) {
|
||||
ct.delete(concat.getTokenBeforeOperand(lastOp), lastOp);
|
||||
replaceAll(variable, rValue, results, ct);
|
||||
results.add(ct.replace(assignment, variable.getName() + ".insert(0," + ct.text(rValue) + ")"));
|
||||
replaceAll(variable, builderVariable, rValue, results, ct);
|
||||
results.add(ct.replace(assignment, builderVariable.getName() + ".insert(0," + ct.text(rValue) + ")"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(rValue != null) {
|
||||
replaceAll(variable, rValue, results, ct);
|
||||
replaceAll(variable, builderVariable, rValue, results, ct);
|
||||
rValue = assignment.getRExpression();
|
||||
}
|
||||
if(assignment.getOperationTokenType().equals(JavaTokenType.PLUSEQ)) {
|
||||
@@ -502,17 +501,140 @@ public class StringConcatenationInLoopsInspection extends BaseInspection {
|
||||
String replacement = "";
|
||||
if (rValue != null) {
|
||||
StringBuilder sb =
|
||||
ChangeToAppendUtil.buildAppendExpression(ct.markUnchanged(rValue), false, new StringBuilder(variable.getName()));
|
||||
ChangeToAppendUtil.buildAppendExpression(ct.markUnchanged(rValue), false, new StringBuilder(builderVariable.getName()));
|
||||
if (sb != null) {
|
||||
replacement = sb.toString();
|
||||
}
|
||||
}
|
||||
results.add(ct.replace(assignment, replacement));
|
||||
} else if(assignment.getOperationTokenType().equals(JavaTokenType.EQ)) {
|
||||
results.add(ct.replace(assignment, variable.getName() + "=" + generateNewStringBuilder(rValue, ct)));
|
||||
results.add(ct.replace(assignment, builderVariable.getName() + "=" + generateNewStringBuilder(rValue, ct)));
|
||||
}
|
||||
}
|
||||
|
||||
protected static void cleanUp(Project project, List<PsiElement> results) {
|
||||
for (PsiElement result : results) {
|
||||
if (result.isValid()) {
|
||||
result = JavaCodeStyleManager.getInstance(project).shortenClassReferences(result);
|
||||
CodeStyleManager.getInstance(project).reformat(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class IntroduceStringBuilderFix extends AbstractStringBuilderFix {
|
||||
public IntroduceStringBuilderFix(PsiVariable variable) {
|
||||
super(variable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFix(Project project, ProblemDescriptor descriptor) {
|
||||
PsiExpression expression = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiExpression.class);
|
||||
if (expression == null) return;
|
||||
PsiVariable variable = getAppendedVariable(expression);
|
||||
if (variable == null) return;
|
||||
PsiLoopStatement loop = getOutermostCommonLoop(expression, variable);
|
||||
if (loop == null) return;
|
||||
ControlFlowUtils.InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(variable, loop);
|
||||
String newName = JavaCodeStyleManager.getInstance(project).suggestUniqueVariableName(variable.getName() + "Builder", loop, true);
|
||||
String newStringBuilder =
|
||||
"java.lang." + myTargetType + " " + newName + "=new java.lang." + myTargetType + "(" + variable.getName() + ");";
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
Object marker = new Object();
|
||||
PsiTreeUtil.mark(loop, marker);
|
||||
PsiDeclarationStatement declaration =
|
||||
(PsiDeclarationStatement)BlockUtils.addBefore(loop, factory.createStatementFromText(newStringBuilder, loop));
|
||||
if (!loop.isValid()) {
|
||||
loop = (PsiLoopStatement)PsiTreeUtil.releaseMark(declaration.getParent(), marker);
|
||||
if (loop == null) return;
|
||||
}
|
||||
PsiVariable builderVariable = (PsiVariable)declaration.getDeclaredElements()[0];
|
||||
PsiExpression builderInitializer = Objects.requireNonNull(builderVariable.getInitializer());
|
||||
List<PsiElement> results = new ArrayList<>();
|
||||
CommentTracker ct = new CommentTracker();
|
||||
replaceAll(variable, builderVariable, loop, results, ct);
|
||||
String toString = variable.getName() + " = " + newName + ".toString();";
|
||||
|
||||
PsiExpression initializer = variable.getInitializer();
|
||||
switch (status) {
|
||||
case DECLARED_JUST_BEFORE:
|
||||
// Put original variable declaration after the loop and use its original initializer in StringBuilder constructor
|
||||
PsiTypeElement typeElement = variable.getTypeElement();
|
||||
if (typeElement != null && initializer != null) {
|
||||
ct.replace(builderInitializer, generateNewStringBuilder(initializer, ct));
|
||||
ct.replace(initializer, newName + ".toString()");
|
||||
toString = variable.getText();
|
||||
ct.delete(variable);
|
||||
}
|
||||
break;
|
||||
case AT_WANTED_PLACE_ONLY:
|
||||
// Move original initializer to the StringBuilder constructor
|
||||
if (initializer != null) {
|
||||
ct.replace(builderInitializer, generateNewStringBuilder(initializer, ct));
|
||||
initializer.delete();
|
||||
}
|
||||
break;
|
||||
case AT_WANTED_PLACE:
|
||||
// Copy original initializer to the StringBuilder constructor if possible
|
||||
if (ExpressionUtils.isSimpleExpression(initializer)) {
|
||||
ct.replace(builderInitializer, generateNewStringBuilder(initializer, ct));
|
||||
}
|
||||
break;
|
||||
case UNKNOWN:
|
||||
PsiElement prevStatement = PsiTreeUtil.skipSiblingsBackward(declaration, PsiWhiteSpace.class, PsiComment.class);
|
||||
PsiExpression prevAssignment = ExpressionUtils.getAssignmentTo(prevStatement, variable);
|
||||
if (prevAssignment != null) {
|
||||
ct.replace(builderInitializer, generateNewStringBuilder(prevAssignment, ct));
|
||||
ct.delete(prevStatement);
|
||||
}
|
||||
break;
|
||||
}
|
||||
BlockUtils.addAfter(loop, factory.createStatementFromText(toString, loop));
|
||||
ct.insertCommentsBefore(loop);
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return InspectionGadgetsBundle.message("string.concatenation.introduce.fix.name", myName, myTargetType);
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return InspectionGadgetsBundle.message("string.concatenation.introduce.fix");
|
||||
}
|
||||
}
|
||||
|
||||
static class ReplaceWithStringBuilderFix extends AbstractStringBuilderFix {
|
||||
public ReplaceWithStringBuilderFix(PsiVariable variable) {
|
||||
super(variable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFix(Project project, ProblemDescriptor descriptor) {
|
||||
PsiExpression expression = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiExpression.class);
|
||||
if (expression == null) return;
|
||||
PsiVariable variable = getAppendedVariable(expression);
|
||||
if (!(variable instanceof PsiLocalVariable)) return;
|
||||
variable.normalizeDeclaration();
|
||||
PsiTypeElement typeElement = variable.getTypeElement();
|
||||
if (typeElement == null) return;
|
||||
List<PsiElement> results = new ArrayList<>();
|
||||
CommentTracker ct = new CommentTracker();
|
||||
replaceAll(variable, variable, null, results, ct);
|
||||
results.add(ct.replace(typeElement, "java.lang." + myTargetType));
|
||||
PsiExpression initializer = variable.getInitializer();
|
||||
if (initializer != null) {
|
||||
results.add(ct.replace(initializer, generateNewStringBuilder(initializer, ct)));
|
||||
}
|
||||
PsiStatement commentPlace = PsiTreeUtil.getParentOfType(variable, PsiStatement.class);
|
||||
ct.insertCommentsBefore(commentPlace == null ? variable : commentPlace);
|
||||
cleanUp(project, results);
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.siyeh.ig.psiutils;
|
||||
|
||||
import com.intellij.psi.*;
|
||||
|
||||
/**
|
||||
* @author Tagir Valeev
|
||||
*/
|
||||
public class BlockUtils {
|
||||
/**
|
||||
* Add new statement before given anchor statement creating code block, if necessary
|
||||
*
|
||||
* @param anchor existing statement
|
||||
* @param newStatement a new statement which should be added before an existing one
|
||||
* @return added physical statement
|
||||
*/
|
||||
public static PsiStatement addBefore(PsiStatement anchor, PsiStatement newStatement) {
|
||||
PsiElement oldStatement = anchor;
|
||||
PsiElement parent = oldStatement.getParent();
|
||||
while (parent instanceof PsiLabeledStatement) {
|
||||
oldStatement = parent;
|
||||
parent = oldStatement.getParent();
|
||||
}
|
||||
final PsiElement result;
|
||||
if (parent instanceof PsiCodeBlock) {
|
||||
result = parent.addBefore(newStatement, oldStatement);
|
||||
}
|
||||
else {
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(anchor.getProject());
|
||||
final PsiBlockStatement newBlockStatement = (PsiBlockStatement)factory.createStatementFromText("{}", oldStatement);
|
||||
final PsiElement codeBlock = newBlockStatement.getCodeBlock();
|
||||
codeBlock.add(newStatement);
|
||||
codeBlock.add(oldStatement);
|
||||
result = ((PsiBlockStatement)oldStatement.replace(newBlockStatement)).getCodeBlock().getStatements()[0];
|
||||
}
|
||||
return (PsiStatement)result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new statement after given anchor statement creating code block, if necessary
|
||||
*
|
||||
* @param anchor existing statement
|
||||
* @param newStatement a new statement which should be added after an existing one
|
||||
* @return added physical statement
|
||||
*/
|
||||
public static PsiStatement addAfter(PsiStatement anchor, PsiStatement newStatement) {
|
||||
PsiElement oldStatement = anchor;
|
||||
PsiElement parent = oldStatement.getParent();
|
||||
while (parent instanceof PsiLabeledStatement) {
|
||||
oldStatement = parent;
|
||||
parent = oldStatement.getParent();
|
||||
}
|
||||
final PsiElement result;
|
||||
if (parent instanceof PsiCodeBlock) {
|
||||
result = parent.addAfter(newStatement, oldStatement);
|
||||
}
|
||||
else {
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(anchor.getProject());
|
||||
final PsiBlockStatement newBlockStatement = (PsiBlockStatement)factory.createStatementFromText("{}", oldStatement);
|
||||
final PsiElement codeBlock = newBlockStatement.getCodeBlock();
|
||||
codeBlock.add(oldStatement);
|
||||
codeBlock.add(newStatement);
|
||||
result = ((PsiBlockStatement)oldStatement.replace(newBlockStatement)).getCodeBlock().getStatements()[1];
|
||||
}
|
||||
return (PsiStatement)result;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports String concatenation in loops. For performance reasons, it
|
||||
is preferable to replace such concatenation with explicit calls to
|
||||
<b>StringBuilder.append()</b> or
|
||||
<b>StringBuffer.append()</b>
|
||||
Reports String concatenation in loops. As every String concatenation copies the whole String, usually it is preferable to replace
|
||||
it with explicit calls to <b>StringBuilder.append()</b> or <b>StringBuffer.append()</b>.
|
||||
<!-- tooltip end -->
|
||||
<p>
|
||||
Use the checkbox below to indicate that this inspection should only warn when the same variable
|
||||
is appended to inside the loop.
|
||||
<p>
|
||||
|
||||
Sometimes quick-fix actions are available which allow you to convert <code>String</code> variable to <code>StringBuilder</code> or
|
||||
introduce a new <code>StringBuilder</code>. Be careful if the original code handles <code>null</code> value specially: the replacement
|
||||
might not be semantically correct after that. Also it's not guaranteed that the automatic replacement will always be more performant.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.siyeh.ipp.forloop;
|
||||
|
||||
import com.intellij.psi.*;
|
||||
import com.siyeh.ig.psiutils.BlockUtils;
|
||||
import com.siyeh.ipp.base.Intention;
|
||||
import com.siyeh.ipp.base.PsiElementPredicate;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -88,21 +89,7 @@ public class ReplaceForLoopWithWhileLoopIntention extends Intention {
|
||||
}
|
||||
initialization = (PsiStatement)initialization.copy();
|
||||
PsiElement newElement = forStatement.replace(whileStatement);
|
||||
PsiElement parent = newElement.getParent();
|
||||
while (parent instanceof PsiLabeledStatement) {
|
||||
newElement = parent;
|
||||
parent = newElement.getParent();
|
||||
}
|
||||
if (parent instanceof PsiCodeBlock) {
|
||||
parent.addBefore(initialization, newElement);
|
||||
}
|
||||
else {
|
||||
final PsiStatement newBlockStatement = factory.createStatementFromText("{}", newElement);
|
||||
final PsiElement codeBlock = newBlockStatement.getFirstChild();
|
||||
codeBlock.add(initialization);
|
||||
codeBlock.add(newElement);
|
||||
newElement.replace(newBlockStatement);
|
||||
}
|
||||
BlockUtils.addBefore((PsiStatement)newElement, initialization);
|
||||
}
|
||||
|
||||
private static class UpdateInserter extends JavaRecursiveElementWalkingVisitor {
|
||||
|
||||
Reference in New Issue
Block a user