IDEA-199811 RefactoringUtil#ensureCodeBlock: support while conditions; support && chains in if-then and while

This commit is contained in:
Tagir Valeev
2018-10-02 16:14:25 +07:00
parent dea7100fa8
commit 49e8a5a7ff
6 changed files with 254 additions and 7 deletions

View File

@@ -15,11 +15,13 @@
*/
package com.intellij.refactoring.util;
import com.intellij.codeInsight.BlockUtils;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.ExpectedTypesProvider;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil;
import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.codeInsight.intention.impl.SplitConditionUtil;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
@@ -48,6 +50,7 @@ import com.intellij.psi.util.*;
import com.intellij.refactoring.PackageWrapper;
import com.intellij.refactoring.introduceField.ElementToWorkOn;
import com.intellij.refactoring.introduceVariable.IntroduceVariableBase;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.intellij.util.text.UniqueNameGenerator;
@@ -1028,8 +1031,19 @@ public class RefactoringUtil {
parent = PsiTreeUtil.getParentOfType(expression, PsiField.class, true, PsiClass.class);
if (parent == null) return null;
}
PsiPolyadicExpression andChain = findSurroundingConjunction(expression);
PsiExpression operand = null;
if (andChain != null) {
operand = StreamEx.of(andChain.getOperands()).findFirst(op -> PsiTreeUtil.isAncestor(op, expression, false))
.orElseThrow(AssertionError::new);
}
PsiElement grandParent = parent.getParent();
if (parent instanceof PsiStatement && grandParent instanceof PsiCodeBlock) {
if (andChain == null &&
parent instanceof PsiStatement &&
!(parent instanceof PsiWhileStatement) &&
grandParent instanceof PsiCodeBlock) {
if (!(parent instanceof PsiForStatement) ||
!PsiTreeUtil.isAncestor(((PsiForStatement)parent).getInitialization(), expression, true) ||
!hasNameCollision(((PsiForStatement)parent).getInitialization(), grandParent)) {
@@ -1083,6 +1097,12 @@ public class RefactoringUtil {
rExpression.replace(fieldInitializer);
Objects.requireNonNull(field.getInitializer()).delete();
newParent = assignment;
}
else if (parent instanceof PsiIfStatement && andChain != null) {
newParent = splitIf((PsiIfStatement)parent, andChain, operand);
}
else if (parent instanceof PsiWhileStatement) {
newParent = extractWhileCondition((PsiWhileStatement)parent, andChain, operand);
} else {
PsiBlockStatement blockStatement = (PsiBlockStatement)parent.replace(factory.createStatementFromText("{}", parent));
newParent = blockStatement.getCodeBlock().add(copy);
@@ -1091,6 +1111,86 @@ public class RefactoringUtil {
return (T)PsiTreeUtil.releaseMark(newParent, marker);
}
private static PsiElement extractWhileCondition(PsiWhileStatement whileStatement, PsiPolyadicExpression andChain, PsiExpression operand) {
PsiExpression oldCondition = Objects.requireNonNull(whileStatement.getCondition());
PsiStatement body = whileStatement.getBody();
PsiBlockStatement blockBody;
Project project = whileStatement.getProject();
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
if (body == null) {
PsiWhileStatement newWhileStatement = (PsiWhileStatement)factory.createStatementFromText("while(true) {}", whileStatement);
blockBody = (PsiBlockStatement)Objects.requireNonNull(newWhileStatement.getBody());
whileStatement = (PsiWhileStatement)whileStatement.replace(newWhileStatement);
}
else if (body instanceof PsiBlockStatement) {
blockBody = (PsiBlockStatement)body;
}
else {
PsiBlockStatement newBody = BlockUtils.createBlockStatement(project);
newBody.add(body);
blockBody = (PsiBlockStatement)body.replace(newBody);
}
PsiExpression lOperands;
PsiExpression rOperands;
if (andChain != null) {
lOperands = SplitConditionUtil.getLOperands(andChain, andChain.getTokenBeforeOperand(operand));
rOperands = getRightOperands(andChain, operand);
}
else {
lOperands = factory.createExpressionFromText("true", whileStatement);
rOperands = oldCondition;
}
PsiCodeBlock codeBlock = blockBody.getCodeBlock();
PsiIfStatement ifStatement = (PsiIfStatement)factory.createStatementFromText("if(!true) break;", whileStatement);
ifStatement = (PsiIfStatement)codeBlock.addAfter(ifStatement, codeBlock.getLBrace());
PsiPrefixExpression negation = (PsiPrefixExpression)Objects.requireNonNull(ifStatement.getCondition());
PsiElement newParent = Objects.requireNonNull(negation.getOperand()).replace(rOperands);
Objects.requireNonNull(whileStatement.getCondition()).replace(lOperands);
return newParent;
}
private static PsiElement splitIf(PsiIfStatement outerIf, PsiPolyadicExpression andChain, PsiExpression operand) {
PsiExpression lOperands = SplitConditionUtil.getLOperands(andChain, andChain.getTokenBeforeOperand(operand));
PsiExpression rOperands = getRightOperands(andChain, operand);
Project project = outerIf.getProject();
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
PsiBlockStatement newThenBranch = (PsiBlockStatement)factory.createStatementFromText("{if(true);}", outerIf);
PsiStatement thenBranch = Objects.requireNonNull(outerIf.getThenBranch());
Objects.requireNonNull(((PsiIfStatement)newThenBranch.getCodeBlock().getStatements()[0]).getThenBranch()).replace(thenBranch);
newThenBranch = (PsiBlockStatement)thenBranch.replace(newThenBranch);
PsiIfStatement innerIf =
(PsiIfStatement)CodeStyleManager.getInstance(project).reformat(newThenBranch.getCodeBlock().getStatements()[0]);
PsiElement newParent = Objects.requireNonNull(innerIf.getCondition()).replace(rOperands);
andChain.replace(lOperands);
return newParent;
}
private static PsiExpression getRightOperands(PsiPolyadicExpression andChain, PsiExpression operand) {
PsiExpression rOperands;
if (operand == ArrayUtil.getLastElement(andChain.getOperands())) {
rOperands = operand;
}
else {
rOperands = SplitConditionUtil.getROperands(andChain, andChain.getTokenBeforeOperand(operand));
// To preserve mark
((PsiPolyadicExpression)rOperands).getOperands()[0].replace(operand);
}
return rOperands;
}
@Nullable
private static PsiPolyadicExpression findSurroundingConjunction(@NotNull PsiExpression expression) {
PsiExpression current = expression;
PsiPolyadicExpression andChain;
do {
current = andChain = PsiTreeUtil.getParentOfType(current, PsiPolyadicExpression.class, true,
PsiStatement.class, PsiLambdaExpression.class);
}
while (andChain != null && (andChain.getOperationTokenType() != JavaTokenType.ANDAND ||
PsiTreeUtil.isAncestor(andChain.getOperands()[0], expression, false)));
return andChain;
}
private static boolean hasNameCollision(PsiElement declaration, PsiElement context) {
if (declaration instanceof PsiDeclarationStatement) {
PsiResolveHelper helper = JavaPsiFacade.getInstance(context.getProject()).getResolveHelper();

View File

@@ -1,18 +1,45 @@
// "Replace Stream API chain with loop" "true"
// "Fix all 'Stream API call chain can be replaced with loop' problems in file" "true"
import java.util.Arrays;
import java.util.List;
public class Main {
private static void test(List<String> list) {
for (String x : list) {
if (x != null) {
if (x.startsWith("x")) {
for (String s : list) {
if (s != null) {
if (s.startsWith("x")) {
System.out.println("Ok!");
break;
}
}
}
if(list.size() > 2) {
for (String x : list) {
if (x != null) {
if (x.startsWith("x")) {
System.out.println("Ok!");
break;
}
}
}
}
if(list.size() > 2) {
boolean b = false;
for (String x : list) {
if (x != null) {
if (x.startsWith("x")) {
b = list.size() < 10;
break;
}
}
}
if (b) {
System.out.println("Ok!");
}
}
if(list.size() > 2 || list.stream().filter(x -> x != null).anyMatch(x -> x.startsWith("x"))) { // not supported
System.out.println("Ok!");
}
}
public static void main(String[] args) {

View File

@@ -0,0 +1,71 @@
// "Fix all 'Stream API call chain can be replaced with loop' problems in file" "true"
import java.util.Arrays;
import java.util.List;
public class Main {
private static void test(List<String> list) {
while(true) {
boolean b = true;
for (String x : list) {
if (x != null) {
if (x.startsWith("x")) {
b = false;
break;
}
}
}
if (b) break;
list = process(list);
}
}
private static void test2(List<String> list) {
while(list.size() > 2) {
boolean b = true;
for (String x : list) {
if (x != null) {
if (x.startsWith("x")) {
b = false;
break;
}
}
}
if (b) break;
list = process(list);
}
}
private static void test3(List<String> list) {
while(true) {
boolean b = false;
for (String x : list) {
if (x != null) {
if (x.startsWith("x")) {
b = list.size() > 2;
break;
}
}
}
if (!(b)) break;
list = process(list);
}
}
private static void test4(List<String> list) {
while(list.size() > 2) {
long count = 0L;
for (String x : list) {
if (x != null) {
if (x.startsWith("x")) {
count++;
}
}
}
if (!(count > 10 && list.size() < 10)) break;
list = process(list);
}
}
native List<String> process(List<String> list);
}

View File

@@ -1,4 +1,4 @@
// "Replace Stream API chain with loop" "true"
// "Fix all 'Stream API call chain can be replaced with loop' problems in file" "true"
import java.util.Arrays;
import java.util.List;
@@ -8,6 +8,15 @@ public class Main {
if(list.stream().filter(x -> x != null).an<caret>yMatch(x -> x.startsWith("x"))) {
System.out.println("Ok!");
}
if(list.size() > 2 && list.stream().filter(x -> x != null).anyMatch(x -> x.startsWith("x"))) {
System.out.println("Ok!");
}
if(list.size() > 2 && list.stream().filter(x -> x != null).anyMatch(x -> x.startsWith("x")) && list.size() < 10) {
System.out.println("Ok!");
}
if(list.size() > 2 || list.stream().filter(x -> x != null).anyMatch(x -> x.startsWith("x"))) { // not supported
System.out.println("Ok!");
}
}
public static void main(String[] args) {

View File

@@ -0,0 +1,32 @@
// "Fix all 'Stream API call chain can be replaced with loop' problems in file" "true"
import java.util.Arrays;
import java.util.List;
public class Main {
private static void test(List<String> list) {
while(list.stream().filter(x -> x != null).an<caret>yMatch(x -> x.startsWith("x"))) {
list = process(list);
}
}
private static void test2(List<String> list) {
while(list.size() > 2 && list.stream().filter(x -> x != null).anyMatch(x -> x.startsWith("x"))) {
list = process(list);
}
}
private static void test3(List<String> list) {
while(list.stream().filter(x -> x != null).anyMatch(x -> x.startsWith("x")) && list.size() > 2) {
list = process(list);
}
}
private static void test4(List<String> list) {
while(list.size() > 2 && list.stream().filter(x -> x != null).filter(x -> x.startsWith("x")).count() > 10 && list.size() < 10) {
list = process(list);
}
}
native List<String> process(List<String> list);
}

View File

@@ -922,7 +922,14 @@ public class ControlFlowUtils {
if (checkExecuted && parent instanceof PsiPolyadicExpression) {
PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression)parent;
IElementType type = polyadicExpression.getOperationTokenType();
if ((type.equals(JavaTokenType.ANDAND) || type.equals(JavaTokenType.OROR)) && polyadicExpression.getOperands()[0] != cur) {
if (type.equals(JavaTokenType.ANDAND) && polyadicExpression.getOperands()[0] != cur) {
PsiElement polyParent = PsiUtil.skipParenthesizedExprUp(polyadicExpression.getParent());
// not the first in the &&/|| chain: we cannot properly generate code which would short-circuit as well
// except some special cases
return (polyParent instanceof PsiIfStatement && ((PsiIfStatement)polyParent).getElseBranch() == null) ||
(polyParent instanceof PsiWhileStatement);
}
else if (type.equals(JavaTokenType.OROR) && polyadicExpression.getOperands()[0] != cur) {
// not the first in the &&/|| chain: we cannot properly generate code which would short-circuit as well
return false;
}
@@ -945,6 +952,7 @@ public class ControlFlowUtils {
return false;
}
}
if (parent instanceof PsiWhileStatement && ((PsiWhileStatement)parent).getCondition() == cur) return true;
if(parent instanceof PsiReturnStatement || parent instanceof PsiExpressionStatement) return true;
if(parent instanceof PsiLocalVariable) {
PsiElement grandParent = parent.getParent();