IDEA-161259 follow-up: support scenarios when iterator.next() call is inlined into condition

This commit is contained in:
Tagir Valeev
2016-10-05 15:24:55 +07:00
parent 9a8480fba1
commit afd1d1b470
5 changed files with 148 additions and 20 deletions
@@ -23,18 +23,26 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.SuggestedNameInfo;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.controlFlow.DefUseUtil;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.ControlFlowUtils;
import one.util.streamex.MoreCollectors;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Optional;
/**
* @author Tagir Valeev
@@ -52,22 +60,62 @@ public class Java8CollectionRemoveIfInspection extends BaseJavaBatchLocalInspect
PsiStatement body = statement.getBody();
if(!(body instanceof PsiBlockStatement)) return;
PsiStatement[] statements = ((PsiBlockStatement)body).getCodeBlock().getStatements();
if(statements.length != 2 || !(statements[1] instanceof PsiIfStatement)) return;
PsiVariable element = declaration.getNextElementVariable(statements[0]);
if(element == null) return;
PsiIfStatement ifStatement = (PsiIfStatement)statements[1];
PsiExpression condition = ifStatement.getCondition();
if(condition == null || ifStatement.getElseBranch() != null) return;
PsiStatement thenStatement = ControlFlowUtils.stripBraces(ifStatement.getThenBranch());
if(!(thenStatement instanceof PsiExpressionStatement)) return;
if(!declaration.isIteratorMethodCall(((PsiExpressionStatement)thenStatement).getExpression(), "remove")) return;
if(!LambdaGenerationUtil.canBeUncheckedLambda(condition)) return;
if (statements.length == 2 && statements[1] instanceof PsiIfStatement) {
PsiVariable element = declaration.getNextElementVariable(statements[0]);
if (element == null) return;
PsiIfStatement ifStatement = (PsiIfStatement)statements[1];
if(checkAndExtractCondition(declaration, ifStatement) == null) return;
registerProblem(statement, endToken);
}
else if (statements.length == 1 && statements[0] instanceof PsiIfStatement){
PsiIfStatement ifStatement = (PsiIfStatement)statements[0];
PsiExpression condition = checkAndExtractCondition(declaration, ifStatement);
if (condition == null) return;
declaration.iteratorRefs(condition).collect(MoreCollectors.onlyOne()).ifPresent(ref -> {
if(declaration.isIteratorMethodCall(ref.getParent().getParent(), "next") && isAlwaysExecuted(condition, ref)) {
registerProblem(statement, endToken);
}
});
}
}
private boolean isAlwaysExecuted(PsiExpression condition, PsiElement ref) {
while(ref != condition) {
PsiElement parent = ref.getParent();
if(parent instanceof PsiPolyadicExpression) {
PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression)parent;
IElementType type = polyadicExpression.getOperationTokenType();
if ((type.equals(JavaTokenType.ANDAND) || type.equals(JavaTokenType.OROR)) && polyadicExpression.getOperands()[0] != ref) {
return false;
}
}
if(parent instanceof PsiConditionalExpression && ((PsiConditionalExpression)parent).getCondition() != ref) {
return false;
}
ref = parent;
}
return true;
}
private void registerProblem(PsiLoopStatement statement, PsiJavaToken endToken) {
//noinspection DialogTitleCapitalization
holder.registerProblem(statement, new TextRange(0, endToken.getTextOffset() - statement.getTextOffset() + 1),
QuickFixBundle.message("java.8.collection.removeif.inspection.description"),
new ReplaceWithRemoveIfQuickFix());
}
@Nullable
private PsiExpression checkAndExtractCondition(IteratorDeclaration declaration,
PsiIfStatement ifStatement) {
PsiExpression condition = ifStatement.getCondition();
if (condition == null || ifStatement.getElseBranch() != null) return null;
PsiStatement thenStatement = ControlFlowUtils.stripBraces(ifStatement.getThenBranch());
if (!(thenStatement instanceof PsiExpressionStatement)) return null;
if (!declaration.isIteratorMethodCall(((PsiExpressionStatement)thenStatement).getExpression(), "remove")) return null;
if (!LambdaGenerationUtil.canBeUncheckedLambda(condition)) return null;
return condition;
}
@Override
public void visitForStatement(PsiForStatement statement) {
super.visitForStatement(statement);
@@ -120,18 +168,41 @@ public class Java8CollectionRemoveIfInspection extends BaseJavaBatchLocalInspect
PsiStatement body = loop.getBody();
if(!(body instanceof PsiBlockStatement)) return;
PsiStatement[] statements = ((PsiBlockStatement)body).getCodeBlock().getStatements();
if(statements.length != 2 || !(statements[1] instanceof PsiIfStatement)) return;
PsiVariable variable = declaration.getNextElementVariable(statements[0]);
if(variable == null) return;
PsiExpression condition = ((PsiIfStatement)statements[1]).getCondition();
if(condition == null) return;
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
String replacement = null;
if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return;
String replacement = (declaration.myCollection == null ? "" : declaration.myCollection.getText() + ".") +
"removeIf(" + LambdaUtil.createLambda(variable, condition) + ");";
if (statements.length == 2 && statements[1] instanceof PsiIfStatement) {
PsiVariable variable = declaration.getNextElementVariable(statements[0]);
if (variable == null) return;
PsiExpression condition = ((PsiIfStatement)statements[1]).getCondition();
if (condition == null) return;
replacement = (declaration.myCollection == null ? "" : declaration.myCollection.getText() + ".") +
"removeIf(" + LambdaUtil.createLambda(variable, condition) + ");";
}
else if (statements.length == 1 && statements[0] instanceof PsiIfStatement){
PsiExpression condition = ((PsiIfStatement)statements[0]).getCondition();
if (condition == null) return;
Optional<PsiElement> iteratorRef = declaration.iteratorRefs(condition).collect(MoreCollectors.onlyOne());
if(iteratorRef.isPresent()) {
PsiElement call = iteratorRef.get().getParent().getParent();
if(!declaration.isIteratorMethodCall(call, "next")) return;
PsiType type = ((PsiExpression)call).getType();
JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project);
SuggestedNameInfo info = javaCodeStyleManager.suggestVariableName(VariableKind.PARAMETER, null, null, type);
if(info.names.length == 0) {
info = javaCodeStyleManager.suggestVariableName(VariableKind.PARAMETER, "value", null, type);
}
String paramName = javaCodeStyleManager.suggestUniqueVariableName(info, condition, true).names[0];
call.replace(factory.createIdentifier(paramName));
replacement = (declaration.myCollection == null ? "" : declaration.myCollection.getText() + ".") +
"removeIf(" + paramName + "->"+condition.getText() + ");";
}
}
if(replacement == null) return;
Collection<PsiComment> comments = ContainerUtil.map(PsiTreeUtil.findChildrenOfType(loop, PsiComment.class),
comment -> (PsiComment)comment.copy());
PsiElement result = loop.replace(JavaPsiFacade.getElementFactory(project).createStatementFromText(replacement, loop));
if(previous != null) previous.delete();
PsiElement result = loop.replace(factory.createStatementFromText(replacement, loop));
if (previous != null) previous.delete();
LambdaCanBeMethodReferenceInspection.replaceAllLambdasWithMethodReferences(result);
CodeStyleManager.getInstance(project).reformat(result);
comments.forEach(comment -> result.getParent().addBefore(comment, result));
@@ -151,7 +222,16 @@ public class Java8CollectionRemoveIfInspection extends BaseJavaBatchLocalInspect
return isIteratorMethodCall(condition, "hasNext");
}
boolean isIteratorMethodCall(PsiExpression candidate, String method) {
public StreamEx<PsiElement> iteratorRefs(PsiExpression parent) {
PsiElement element = PsiUtil.getVariableCodeBlock(myIterator, null);
PsiCodeBlock block =
element instanceof PsiCodeBlock ? (PsiCodeBlock)element : PsiTreeUtil.getParentOfType(element, PsiCodeBlock.class);
if(block == null) return StreamEx.empty();
return StreamEx.of(DefUseUtil.getRefs(block, myIterator, myIterator.getInitializer()))
.filter(e -> PsiTreeUtil.isAncestor(parent, e, false));
}
boolean isIteratorMethodCall(PsiElement candidate, String method) {
if(!(candidate instanceof PsiMethodCallExpression)) return false;
PsiMethodCallExpression call = (PsiMethodCallExpression)candidate;
if(call.getArgumentList().getExpressions().length != 0) return false;
@@ -0,0 +1,9 @@
// "Replace the loop with Collection.removeIf" "true"
import java.util.Iterator;
import java.util.List;
public class Main {
public void testIterator(List<List<String>> data, boolean b) {
data.removeIf(strings -> strings.isEmpty() && b);
}
}
@@ -0,0 +1,13 @@
// "Replace the loop with Collection.removeIf" "true"
import java.util.Iterator;
import java.util.List;
public class Main {
public void testIterator(List<List<String>> data, boolean b) {
for(Ite<caret>rator<List<String>> iter = data.iterator(); iter.hasNext();) {
if(iter.next().isEmpty() && b) {
iter.remove();
}
}
}
}
@@ -0,0 +1,13 @@
// "Replace the loop with Collection.removeIf" "false"
import java.util.Iterator;
import java.util.List;
public class Main {
public void testIterator(List<List<String>> data, boolean b) {
for(Ite<caret>rator<List<String>> iter = data.iterator(); iter.hasNext();) {
if(b && iter.next().isEmpty()) {
iter.remove();
}
}
}
}
@@ -0,0 +1,13 @@
// "Replace the loop with Collection.removeIf" "false"
import java.util.Iterator;
import java.util.List;
public class Main {
public void testIterator(List<List<String>> data, boolean b) {
for(Ite<caret>rator<List<String>> iter = data.iterator(); iter.hasNext();) {
if(iter.next().isEmpty() && iter.next().isEmpty()) {
iter.remove();
}
}
}
}