StreamToLoopInspection: unwrap && and || chains; copy return statement in found/not found places; do not eagerly evaluate non-trivial ternary branch

This commit is contained in:
Tagir Valeev
2016-12-27 12:35:08 +07:00
parent eb1eb4de75
commit fb12495825
11 changed files with 154 additions and 19 deletions

View File

@@ -99,6 +99,10 @@ interface ConditionalExpression {
return new Boolean(myCondition, !myInvert);
}
public boolean isInverted() {
return myInvert;
}
public Plain toPlain(String type, String trueBranch, String falseBranch) {
return myInvert ? new Plain(type, myCondition, falseBranch, trueBranch) :
new Plain(type, myCondition, trueBranch, falseBranch);

View File

@@ -44,7 +44,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Predicate;
import static com.intellij.codeInspection.streamToLoop.Operation.FlatMapOperation;
@@ -521,25 +520,33 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
public void setFinisher(ConditionalExpression conditionalExpression) {
if(conditionalExpression instanceof ConditionalExpression.Optional) {
conditionalExpression = tryUnwrapOptional((ConditionalExpression.Optional)conditionalExpression, expr -> true);
conditionalExpression = tryUnwrapOptional((ConditionalExpression.Optional)conditionalExpression, true);
}
setFinisher(conditionalExpression.asExpression());
}
public String assignAndBreak(ConditionalExpression conditionalExpression) {
Predicate<PsiElement> predicate = expr -> PsiUtil.skipParenthesizedExprUp(expr.getParent()) instanceof PsiReturnStatement;
PsiStatement statement = PsiTreeUtil.getParentOfType(myPlaceholder, PsiStatement.class);
boolean inReturn = statement instanceof PsiReturnStatement;
if(conditionalExpression instanceof ConditionalExpression.Optional) {
conditionalExpression = tryUnwrapOptional((ConditionalExpression.Optional)conditionalExpression, predicate);
conditionalExpression = tryUnwrapOptional((ConditionalExpression.Optional)conditionalExpression, inReturn);
}
if(conditionalExpression instanceof ConditionalExpression.Boolean) {
conditionalExpression = tryUnwrapBoolean((ConditionalExpression.Boolean)conditionalExpression);
if (conditionalExpression instanceof ConditionalExpression.Boolean) {
conditionalExpression = tryUnwrapBoolean((ConditionalExpression.Boolean)conditionalExpression, inReturn);
}
if(predicate.test(myPlaceholder)) {
if (inReturn) {
setFinisher(conditionalExpression.getFalseBranch());
return "return " + conditionalExpression.getTrueBranch() + ";";
Object mark = new Object();
PsiTreeUtil.mark(myPlaceholder, mark);
PsiElement returnCopy = statement.copy();
PsiElement placeHolderCopy = PsiTreeUtil.releaseMark(returnCopy, mark);
LOG.assertTrue(placeHolderCopy != null);
PsiElement replacement = placeHolderCopy.replace(createExpression(conditionalExpression.getTrueBranch()));
return (placeHolderCopy == returnCopy ? replacement : returnCopy).getText();
}
PsiElement parent = PsiUtil.skipParenthesizedExprUp(myPlaceholder.getParent());
if(parent instanceof PsiIfStatement && conditionalExpression.getTrueBranch().equals(String.valueOf(true))) {
if(parent instanceof PsiIfStatement && conditionalExpression instanceof ConditionalExpression.Boolean &&
!((ConditionalExpression.Boolean)conditionalExpression).isInverted()) {
PsiIfStatement ifStatement = (PsiIfStatement)parent;
if(ifStatement.getElseBranch() == null) {
PsiStatement thenStatement = ControlFlowUtils.stripBraces(ifStatement.getThenBranch());
@@ -573,32 +580,51 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
return found + " = " + conditionalExpression.getTrueBranch() + ";\n" + getBreakStatement();
}
private ConditionalExpression tryUnwrapBoolean(ConditionalExpression.Boolean condition) {
private ConditionalExpression tryUnwrapBoolean(ConditionalExpression.Boolean condition, boolean unwrapLazilyEvaluated) {
if (myPlaceholder instanceof PsiExpression) {
PsiExpression negation = BoolUtils.findNegation((PsiExpression)myPlaceholder);
if (negation != null) {
myPlaceholder = negation;
condition = condition.negate();
}
PsiElement parent = PsiUtil.skipParenthesizedExprUp(myPlaceholder.getParent());
if (parent instanceof PsiConditionalExpression) {
ConditionalExpression candidate = null;
if (parent instanceof PsiPolyadicExpression) {
PsiPolyadicExpression expression = (PsiPolyadicExpression)parent;
PsiExpression[] operands = expression.getOperands();
if (operands.length > 1 && PsiTreeUtil.isAncestor(operands[0], myPlaceholder, false)) {
IElementType type = expression.getOperationTokenType();
if (type.equals(JavaTokenType.ANDAND)) {
candidate = condition
.toPlain("boolean", StreamEx.of(operands, 1, operands.length).map(PsiExpression::getText).joining(" && "), "false");
} else if (type.equals(JavaTokenType.OROR)) {
candidate = condition
.toPlain("boolean", "true", StreamEx.of(operands, 1, operands.length).map(PsiExpression::getText).joining(" || "));
}
}
} else if (parent instanceof PsiConditionalExpression) {
PsiConditionalExpression ternary = (PsiConditionalExpression)parent;
if (PsiTreeUtil.isAncestor(ternary.getCondition(), myPlaceholder, false)) {
myPlaceholder = ternary;
PsiType type = ternary.getType();
PsiExpression thenExpression = ternary.getThenExpression();
PsiExpression elseExpression = ternary.getElseExpression();
if (type != null && thenExpression != null && elseExpression != null) {
return condition.toPlain(type.getCanonicalText(), thenExpression.getText(), elseExpression.getText());
candidate = condition.toPlain(type.getCanonicalText(), thenExpression.getText(), elseExpression.getText());
}
}
}
if (candidate != null &&
(unwrapLazilyEvaluated || ExpressionUtils.isSimpleExpression(createExpression(candidate.getFalseBranch())))) {
myPlaceholder = parent;
return candidate;
}
}
return condition;
}
@NotNull
private ConditionalExpression tryUnwrapOptional(ConditionalExpression.Optional condition, Predicate<PsiElement> predicate) {
private ConditionalExpression tryUnwrapOptional(ConditionalExpression.Optional condition, boolean unwrapLazilyEvaluated) {
if (myPlaceholder instanceof PsiExpression) {
PsiMethodCallExpression call = ExpressionUtils.getCallForQualifier((PsiExpression)myPlaceholder);
if (call != null && !(call.getParent() instanceof PsiExpressionStatement)) {
@@ -613,7 +639,7 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
if ("orElse".equals(name)) {
absentExpression = args[0].getText();
}
else if ("orElseGet".equals(name) && predicate.test(call)) {
else if (unwrapLazilyEvaluated && "orElseGet".equals(name)) {
FunctionHelper helper = FunctionHelper.create(args[0], 0);
if (helper != null) {
helper.transform(this);

View File

@@ -0,0 +1,15 @@
// "Replace Stream API chain with loop" "true"
import java.util.Arrays;
import java.util.List;
public class Main {
public boolean testCond(List<String> list) {
for (String s : list) {
if (s.isEmpty()) {
return list.stream().anyMatch(Objects::isNull);
}
}
return false;
}
}

View File

@@ -0,0 +1,17 @@
// "Replace Stream API chain with loop" "true"
import java.util.Arrays;
import java.util.List;
public class Main {
public boolean testCond(List<String> list) {
boolean x = false;
for (String s : list) {
if (s.isEmpty()) {
x = list.stream().anyMatch(Objects::isNull);
break;
}
}
return x;
}
}

View File

@@ -0,0 +1,18 @@
// "Replace Stream API chain with loop" "true"
import java.util.Arrays;
import java.util.List;
public class Main {
public boolean testCond(List<String> list) {
boolean b = true;
for (String s : list) {
if (s.isEmpty()) {
b = false;
break;
}
}
boolean x = b && list.stream().anyMatch(Objects::isNull) && list.size() > 2;
return x;
}
}

View File

@@ -0,0 +1,15 @@
// "Replace Stream API chain with loop" "true"
import java.util.Arrays;
import java.util.List;
public class Main {
public boolean testCond(List<String> list) {
for (String s : list) {
if (s.isEmpty()) {
return false;
}
}
return list.stream().anyMatch(Objects::isNull);
}
}

View File

@@ -6,14 +6,12 @@ import java.util.Optional;
public class Main {
private static test(List<String> packages) {
Optional<String> found = Optional.empty();
for (String s : packages) {
if (s.startsWith("xyz")) {
found = Optional.of(s);
break;
return Optional.of(s).filter(pkg -> pkg.endsWith("abc")).isPresent();
}
}
return found.filter(pkg -> pkg.endsWith("abc")).isPresent();
return Optional.<String>empty().filter(pkg -> pkg.endsWith("abc")).isPresent();
}
public static void main(String[] args) {

View File

@@ -0,0 +1,10 @@
// "Replace Stream API chain with loop" "true"
import java.util.Arrays;
import java.util.List;
public class Main {
public boolean testCond(List<String> list) {
return list.stream().<caret>anyMatch(String::isEmpty) && list.stream().anyMatch(Objects::isNull);
}
}

View File

@@ -0,0 +1,11 @@
// "Replace Stream API chain with loop" "true"
import java.util.Arrays;
import java.util.List;
public class Main {
public boolean testCond(List<String> list) {
boolean x = list.stream().<caret>anyMatch(String::isEmpty) && list.stream().anyMatch(Objects::isNull);
return x;
}
}

View File

@@ -0,0 +1,11 @@
// "Replace Stream API chain with loop" "true"
import java.util.Arrays;
import java.util.List;
public class Main {
public boolean testCond(List<String> list) {
boolean x = !list.stream().<caret>anyMatch(String::isEmpty) && list.stream().anyMatch(Objects::isNull) && list.size() > 2;
return x;
}
}

View File

@@ -0,0 +1,10 @@
// "Replace Stream API chain with loop" "true"
import java.util.Arrays;
import java.util.List;
public class Main {
public boolean testCond(List<String> list) {
return !list.stream().<caret>anyMatch(String::isEmpty) && list.stream().anyMatch(Objects::isNull);
}
}