ControlFlowUtils#statementBreaksLoop; StreamApiMigrationInspection#getInitializerUsageStatus simplified

This commit is contained in:
Tagir Valeev
2016-10-04 09:49:28 +07:00
parent aa583de71b
commit c6cf323132
9 changed files with 182 additions and 8 deletions
@@ -21,6 +21,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.siyeh.ig.psiutils.BoolUtils;
import com.siyeh.ig.psiutils.ControlFlowUtils;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.NotNull;
@@ -69,7 +70,9 @@ class ReplaceWithMatchFix extends MigrateToStreamFix {
}
}
PsiStatement[] statements = tb.getStatements();
if (!(statements.length == 1 || (statements.length == 2 && statements[1] instanceof PsiBreakStatement))) return null;
if (!(statements.length == 1 || (statements.length == 2 && ControlFlowUtils.statementBreaksLoop(statements[1], foreachStatement)))) {
return null;
}
restoreComments(foreachStatement, body);
String streamText = generateStream(iteratedValue, tb.getLastOperation()).toString();
streamText = addTerminalOperation(streamText, "anyMatch", foreachStatement, tb);
@@ -39,6 +39,7 @@ import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.IntArrayList;
import com.siyeh.ig.psiutils.BoolUtils;
import com.siyeh.ig.psiutils.ControlFlowUtils;
import com.siyeh.ig.psiutils.EquivalenceChecker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import one.util.streamex.EntryStream;
@@ -415,10 +416,6 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
static InitializerUsageStatus getInitializerUsageStatus(PsiVariable var, PsiStatement nextStatement) {
if(!(var instanceof PsiLocalVariable) || var.getInitializer() == null) return UNKNOWN;
if(isDeclarationJustBefore(var, nextStatement)) return DECLARED_JUST_BEFORE;
PsiElement declaration = var.getParent();
// Check if variable is not referenced in the same declaration like "int a = 0, b = a;"
if(!PsiTreeUtil.processElements(declaration, e -> !(e instanceof PsiReferenceExpression) ||
((PsiReferenceExpression)e).resolve() != var)) return UNKNOWN;
// Check that variable is declared in the same method or the same lambda expression
if(PsiTreeUtil.getParentOfType(var, PsiLambdaExpression.class, PsiMethod.class) !=
PsiTreeUtil.getParentOfType(nextStatement, PsiLambdaExpression.class, PsiMethod.class)) return UNKNOWN;
@@ -432,7 +429,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
catch (AnalysisCanceledException ignored) {
return UNKNOWN;
}
int start = controlFlow.getEndOffset(declaration);
int start = controlFlow.getEndOffset(var.getInitializer())+1;
int stop = controlFlow.getStartOffset(nextStatement);
if(ControlFlowUtil.isVariableReferencedBetween(controlFlow, start, stop, var)) return UNKNOWN;
if (!ControlFlowUtil.isValueUsedWithoutVisitingStop(controlFlow, start, stop, var)) return AT_WANTED_PLACE_ONLY;
@@ -559,8 +556,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
PsiStatement[] statements = tb.getStatements();
if (statements.length == 2) {
PsiStatement breakStatement = statements[1];
if (!(breakStatement instanceof PsiBreakStatement) ||
((PsiBreakStatement)breakStatement).findExitedStatement() != statement) {
if (!ControlFlowUtils.statementBreaksLoop(breakStatement, statement)) {
return;
}
if (ReferencesSearch.search(tb.getVariable(), new LocalSearchScope(statements)).findFirst() == null
@@ -0,0 +1,17 @@
// "Replace with anyMatch()" "true"
import java.util.Collection;
import java.util.List;
public class Main {
public boolean testAnyMatch(List<List<String>> data) {
if (!data.isEmpty()) {
if (data.stream().flatMap(Collection::stream).anyMatch(str -> !str.isEmpty())) {
System.out.println("Found!");
}
return true;
}
return false;
}
}
@@ -0,0 +1,16 @@
// "Replace with findFirst()" "true"
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class Main {
public List<Integer> testFindFirstIfPresent(List<List<String>> data) {
List<Integer> result = new ArrayList<>();
if (!data.isEmpty()) {
data.stream().flatMap(Collection::stream).filter(str -> !str.isEmpty()).findFirst().ifPresent(str -> result.add(str.length()));
}
return result;
}
}
@@ -0,0 +1,21 @@
// "Replace with anyMatch()" "true"
import java.util.List;
public class Main {
public boolean testAnyMatch(List<List<String>> data) {
if (!data.isEmpty()) {
for (List<String> list : dat<caret>a) {
for (String str : list) {
if (!str.isEmpty()) {
System.out.println("Found!");
return true;
}
}
}
return true;
}
return false;
}
}
@@ -0,0 +1,21 @@
// "Replace with anyMatch()" "false"
import java.util.List;
public class Main {
public boolean testAnyMatch(List<List<String>> data) {
if (!data.isEmpty()) {
for (List<String> list : dat<caret>a) {
for (String str : list) {
if (!str.isEmpty()) {
System.out.println("Found!");
return false;
}
}
}
return true;
}
return false;
}
}
@@ -0,0 +1,22 @@
// "Replace with anyMatch()" "false"
import java.util.List;
public class Main {
public boolean testAnyMatch(List<List<String>> data) {
if (!data.isEmpty()) {
for (List<String> list : dat<caret>a) {
for (String str : list) {
if (!str.isEmpty()) {
System.out.println("Found!");
return true;
}
}
}
System.out.println("Oops");
return true;
}
return false;
}
}
@@ -0,0 +1,22 @@
// "Replace with findFirst()" "true"
import java.util.ArrayList;
import java.util.List;
public class Main {
public List<Integer> testFindFirstIfPresent(List<List<String>> data) {
List<Integer> result = new ArrayList<>();
if (!data.isEmpty()) {
for (List<String> list : da<caret>ta) {
for (String str : list) {
if (!str.isEmpty()) {
result.add(str.length());
return result;
}
}
}
}
return result;
}
}
@@ -17,6 +17,7 @@ package com.siyeh.ig.psiutils;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -544,6 +545,61 @@ public class ControlFlowUtils {
return breakFinder.breakFound();
}
/**
* Checks whether the given statement effectively breaks given loop. Returns true
* if the statement is {@link PsiBreakStatement} having given loop as a target. Also may return
* true in other cases if the statement is semantically equivalent to break like this:
*
* <pre>{@code
* int myMethod(int[] data) {
* for(int val : data) {
* if(val == 5) {
* System.out.println(val);
* return 0; // this statement is semantically equivalent to break.
* }
* }
* return 0;
* }}</pre>
*
* @param statement statement which may break the loop
* @param loop a loop to break
* @return true if the statement actually breaks the loop
*/
@Contract("null, _ -> false")
public static boolean statementBreaksLoop(PsiStatement statement, PsiLoopStatement loop) {
if(statement instanceof PsiBreakStatement) {
return ((PsiBreakStatement)statement).findExitedStatement() == loop;
}
if(statement instanceof PsiReturnStatement) {
PsiExpression returnValue = ((PsiReturnStatement)statement).getReturnValue();
PsiElement cur = loop;
for(PsiElement parent = cur.getParent();;parent = cur.getParent()) {
if(parent instanceof PsiLabeledStatement) {
cur = parent;
} else if(parent instanceof PsiCodeBlock) {
PsiCodeBlock block = (PsiCodeBlock)parent;
PsiStatement[] statements = block.getStatements();
if(block.getParent() instanceof PsiBlockStatement && statements.length > 0 && statements[statements.length-1] == cur) {
cur = block.getParent();
} else break;
} else if(parent instanceof PsiIfStatement) {
if(cur == ((PsiIfStatement)parent).getThenBranch() || cur == ((PsiIfStatement)parent).getElseBranch()) {
cur = parent;
} else break;
} else break;
}
PsiElement nextElement = PsiTreeUtil.skipSiblingsForward(cur, PsiComment.class, PsiWhiteSpace.class);
if(nextElement instanceof PsiReturnStatement) {
return EquivalenceChecker.getCanonicalPsiEquivalence()
.expressionsAreEquivalent(returnValue, ((PsiReturnStatement)nextElement).getReturnValue());
}
if(nextElement == null && returnValue == null && cur.getParent() instanceof PsiMethod) {
return true;
}
}
return false;
}
private static class NakedBreakFinder extends JavaRecursiveElementWalkingVisitor {
private boolean m_found;