IG: care about side effects in pointless compound assignment

This commit is contained in:
Bas Leijdekkers
2018-01-12 18:00:01 +01:00
parent 1b7de60c98
commit 0dc80d038d
6 changed files with 84 additions and 21 deletions
@@ -23,7 +23,7 @@ import java.util.function.Predicate;
* @author Tagir Valeev
*/
public class CommentTracker {
private Set<PsiElement> ignoredParents = new HashSet<>();
private final Set<PsiElement> ignoredParents = new HashSet<>();
private List<PsiComment> comments = new ArrayList<>();
/**
@@ -174,6 +174,26 @@ public class CommentTracker {
return result;
}
public static @NotNull PsiElement replaceWithSubexpressionAndRestoreComments(@NotNull PsiExpression expression,
@NotNull PsiExpression replacement) {
if (!PsiTreeUtil.isAncestor(expression, replacement, true)) throw new IllegalArgumentException("replacement is not a subexpression");
final CommentTracker tracker = new CommentTracker();
tracker.markUnchanged(replacement);
tracker.grabComments(expression);
final PsiElement parent = expression.getParent();
final int limit = replacement.getTextOffset();
PsiElement anchor = expression;
for (PsiComment comment : tracker.comments) {
if (comment.getTextOffset() < limit) {
parent.addBefore(comment, anchor);
}
else {
anchor = parent.addAfter(comment, anchor);
}
}
return expression.replace(replacement);
}
/**
* Creates a replacement element from the text and replaces given element,
* collecting all the comments inside it and restores comments putting them
@@ -198,26 +218,24 @@ public class CommentTracker {
private static @NotNull PsiElement createElement(@NotNull PsiElement element, @NotNull String text) {
PsiElementFactory factory = JavaPsiFacade.getElementFactory(element.getProject());
PsiElement replacement;
if (element instanceof PsiExpression) {
replacement = factory.createExpressionFromText(text, element);
return factory.createExpressionFromText(text, element);
}
else if (element instanceof PsiStatement) {
replacement = factory.createStatementFromText(text, element);
return factory.createStatementFromText(text, element);
}
else if (element instanceof PsiTypeElement) {
replacement = factory.createTypeElementFromText(text, element);
return factory.createTypeElementFromText(text, element);
}
else if (element instanceof PsiIdentifier) {
replacement = factory.createIdentifier(text);
return factory.createIdentifier(text);
}
else if (element instanceof PsiComment) {
replacement = factory.createCommentFromText(text, element);
return factory.createCommentFromText(text, element);
}
else {
throw new IllegalArgumentException("Unsupported element type: " + element);
}
return replacement;
}
/**
@@ -21,6 +21,7 @@ import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.SmartList;
import gnu.trove.THashSet;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
@@ -79,7 +80,7 @@ public class SideEffectChecker {
}
public static List<PsiExpression> extractSideEffectExpressions(@NotNull PsiExpression element) {
List<PsiElement> list = new ArrayList<>();
List<PsiElement> list = new SmartList<>();
element.accept(new SideEffectsVisitor(list, element));
return StreamEx.of(list).select(PsiExpression.class).toList();
}
@@ -36,6 +36,7 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
import java.util.function.Supplier;
public class PointlessBooleanExpressionInspection extends BaseInspection {
private enum BooleanExpressionKind {
@@ -78,9 +79,9 @@ public class PointlessBooleanExpressionInspection extends BaseInspection {
@NotNull
public String buildErrorString(Object... infos) {
final String replacement = (String)infos[1];
if (replacement.isEmpty()) {
final PsiAssignmentExpression expression = (PsiAssignmentExpression)infos[0];
return InspectionGadgetsBundle.message("boolean.expression.does.not.modify.problem.descriptor", expression);
final PsiExpression expression = (PsiExpression)infos[0];
if (replacement.isEmpty() && expression instanceof PsiAssignmentExpression) {
return InspectionGadgetsBundle.message("boolean.expression.does.not.modify.problem.descriptor", expression.getText());
}
return InspectionGadgetsBundle.message("boolean.expression.can.be.simplified.problem.descriptor", replacement);
}
@@ -305,7 +306,7 @@ public class PointlessBooleanExpressionInspection extends BaseInspection {
@Override
public InspectionGadgetsFix buildFix(Object... infos) {
final String replacement = (String)infos[1];
if (replacement.isEmpty()) {
if (replacement.isEmpty() && infos[0] instanceof PsiAssignmentExpression) {
return new RemovePointlessBooleanExpressionFix();
}
boolean hasSideEffect = (boolean)infos[2];
@@ -323,7 +324,19 @@ public class PointlessBooleanExpressionInspection extends BaseInspection {
@Override
protected void doFix(Project project, ProblemDescriptor descriptor) {
new CommentTracker().deleteAndRestoreComments(descriptor.getPsiElement().getParent());
final PsiElement element = descriptor.getPsiElement();
if (!(element instanceof PsiAssignmentExpression)) {
return;
}
final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)element;
final List<PsiExpression> sideEffects = SideEffectChecker.extractSideEffectExpressions(assignmentExpression.getLExpression());
assert sideEffects.size() < 2;
if (!sideEffects.isEmpty()) {
CommentTracker.replaceWithSubexpressionAndRestoreComments(assignmentExpression, sideEffects.get(0));
}
else {
new CommentTracker().deleteAndRestoreComments(element.getParent());
}
}
}
@@ -430,11 +443,10 @@ public class PointlessBooleanExpressionInspection extends BaseInspection {
return;
}
String replacement = buildSimplifiedExpression(expression, new StringBuilder(), new CommentTracker()).toString();
if (parent instanceof PsiLambdaExpression &&
!LambdaUtil.isSafeLambdaBodyReplacement((PsiLambdaExpression)parent,
() -> JavaPsiFacade.getElementFactory(expression.getProject()).createExpressionFromText(replacement, expression))) {
final String replacement = buildSimplifiedExpression(expression, new StringBuilder(), new CommentTracker()).toString();
final Supplier<PsiElement> newBodySupplier =
() -> JavaPsiFacade.getElementFactory(expression.getProject()).createExpressionFromText(replacement, expression);
if (parent instanceof PsiLambdaExpression && !LambdaUtil.isSafeLambdaBodyReplacement((PsiLambdaExpression)parent, newBodySupplier)) {
return;
}
@@ -0,0 +1,16 @@
class CompoundAssignmentSideEffect {
void m() {
createSomeObject(/*1*/)// 2
/*3*//*4*/
;
}
X createSomeObject() {
return new X();
}
class X {
boolean b = false;
}
}
@@ -0,0 +1,15 @@
class CompoundAssignmentSideEffect {
void m() {
createSomeObject(/*1*/).b // 2
&= /*3*//*4*/ true<caret>;
}
X createSomeObject() {
return new X();
}
class X {
boolean b = false;
}
}
@@ -20,9 +20,10 @@ public class PointlessBooleanExpressionFixTest extends IGQuickFixesTestCase {
public void testNegation() { doTest(); }
public void testPolyadic() { doTest(); }
public void testBoxed() { doTest(); }
public void testSideEffects() { doTest(InspectionGadgetsBundle.message("constant.conditional.expression.simplify.quickfix.sideEffect"));}
public void testSideEffectsField() { doTest(InspectionGadgetsBundle.message("constant.conditional.expression.simplify.quickfix.sideEffect"));}
public void testSideEffects() { doTest(InspectionGadgetsBundle.message("constant.conditional.expression.simplify.quickfix.sideEffect")); }
public void testSideEffectsField() { doTest(InspectionGadgetsBundle.message("constant.conditional.expression.simplify.quickfix.sideEffect")); }
public void testCompoundAssignment1() { doTest(InspectionGadgetsBundle.message("boolean.expression.remove.compound.assignment.quickfix")); }
public void testCompoundAssignment2() { doTest(); }
public void testCompoundAssignment3() { doTest(); }
public void testCompoundAssignmentSideEffect() { doTest(InspectionGadgetsBundle.message("boolean.expression.remove.compound.assignment.quickfix")); }
}