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:
Tagir Valeev
2017-02-02 14:26:32 +03:00
parent b73c0c1000
commit 0183b826f9
10 changed files with 364 additions and 112 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"

View File

@@ -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)

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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 {