[java-refactoring] Refactor InlineMethod refactoring to use CodeBlockSurrounder

Added SurroundResult#collapse method to collapse expanded code block back.
Now, InlineMethodProcessor is simpler and preserves semantics at the call site more often.
Fixes IDEA-297364 Inline Method changes semantic for a method throwing exception

GitOrigin-RevId: 2ceb23e372e2876202dac687048ac86fb23cfc3b
This commit is contained in:
Tagir Valeev
2022-07-07 13:31:50 +02:00
committed by intellij-monorepo-bot
parent c2c6689387
commit 06d63c351a
17 changed files with 454 additions and 288 deletions

View File

@@ -87,6 +87,13 @@ public class ConditionalBreakInInfiniteLoopInspection extends AbstractBaseJavaLo
};
}
public static void tryTransform(@NotNull PsiWhileStatement whileStatement) {
Context context = Context.from(whileStatement, true);
if (context != null) {
context.simplify(whileStatement);
}
}
private static class Context {
final @NotNull PsiLoopStatement myLoopStatement;
final @NotNull PsiStatement myLoopBody;
@@ -194,6 +201,31 @@ public class ConditionalBreakInInfiniteLoopInspection extends AbstractBaseJavaLo
.collect(Collectors.toSet());
return !Collections.disjoint(variablesCreatedInBranch, otherVariablesUsedInLoop);
}
private void simplify(@NotNull PsiConditionalLoopStatement loop) {
CommentTracker ct = new CommentTracker();
String loopText;
if (ControlFlowUtils.isEndlessLoop(loop)) {
String conditionForWhile = this.myConditionInThen ? BoolUtils.getNegatedExpressionText(this.myCondition, ct) : ct.text(
this.myCondition);
LoopTransformationFix.pullDownStatements(this.myConditionStatement, this.myConditionInThen ? this.myConditionStatement.getElseBranch() : this.myConditionStatement.getThenBranch());
ct.delete(this.myConditionStatement);
loopText = this.myConditionInTheBeginning
? "while(" + conditionForWhile + ")" + ct.text(this.myLoopBody)
: "do" + ct.text(this.myLoopBody) + "while(" + conditionForWhile + ");";
} else {
String conditionForWhile = this.myConditionInThen ? BoolUtils.getNegatedExpressionText(this.myCondition, ParenthesesUtils.AND_PRECEDENCE, ct) : ct.text(
this.myCondition, ParenthesesUtils.AND_PRECEDENCE);
ct.delete(this.myConditionStatement);
PsiExpression loopCondition = loop.getCondition();
assert loopCondition != null;
loopText = this.myConditionInTheBeginning
? "while(" + ct.text(loopCondition, ParenthesesUtils.AND_PRECEDENCE) + " && " + conditionForWhile + ")" + ct.text(
this.myLoopBody)
: "do" + ct.text(this.myLoopBody) + "while(" + conditionForWhile + " && " + ct.text(loopCondition, ParenthesesUtils.AND_PRECEDENCE) + ");";
}
ct.replaceAndRestoreComments(this.myLoopStatement, loopText);
}
}
private static class LoopTransformationFix implements LocalQuickFix {
@@ -216,25 +248,7 @@ public class ConditionalBreakInInfiniteLoopInspection extends AbstractBaseJavaLo
if (loop == null) return;
Context context = Context.from(loop, noConversionToDoWhile);
if (context == null) return;
CommentTracker ct = new CommentTracker();
String loopText;
if (ControlFlowUtils.isEndlessLoop(loop)) {
String conditionForWhile = context.myConditionInThen ? BoolUtils.getNegatedExpressionText(context.myCondition, ct) : ct.text(context.myCondition);
pullDownStatements(context.myConditionStatement, context.myConditionInThen ? context.myConditionStatement.getElseBranch() : context.myConditionStatement.getThenBranch());
ct.delete(context.myConditionStatement);
loopText = context.myConditionInTheBeginning
? "while(" + conditionForWhile + ")" + ct.text(context.myLoopBody)
: "do" + ct.text(context.myLoopBody) + "while(" + conditionForWhile + ");";
} else {
String conditionForWhile = context.myConditionInThen ? BoolUtils.getNegatedExpressionText(context.myCondition, ParenthesesUtils.AND_PRECEDENCE, ct) : ct.text(context.myCondition, ParenthesesUtils.AND_PRECEDENCE);
ct.delete(context.myConditionStatement);
PsiExpression loopCondition = loop.getCondition();
assert loopCondition != null;
loopText = context.myConditionInTheBeginning
? "while(" + ct.text(loopCondition, ParenthesesUtils.AND_PRECEDENCE) + " && " + conditionForWhile + ")" + ct.text(context.myLoopBody)
: "do" + ct.text(context.myLoopBody) + "while(" + conditionForWhile + " && " + ct.text(loopCondition, ParenthesesUtils.AND_PRECEDENCE) + ");";
}
ct.replaceAndRestoreComments(context.myLoopStatement, loopText);
context.simplify(loop);
}
private static void pullDownStatements(@NotNull PsiIfStatement conditionStatement, @Nullable PsiStatement branch) {

View File

@@ -26,7 +26,6 @@ import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.controlFlow.*;
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
import com.intellij.psi.impl.source.javadoc.PsiDocMethodOrFieldRef;
import com.intellij.psi.impl.source.resolve.reference.impl.JavaLangClassMemberReference;
import com.intellij.psi.search.GlobalSearchScope;
@@ -41,7 +40,6 @@ import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.introduceParameter.Util;
import com.intellij.refactoring.listeners.RefactoringEventData;
import com.intellij.refactoring.rename.NonCodeUsageInfoFactory;
import com.intellij.refactoring.rename.RenameJavaMemberProcessor;
import com.intellij.refactoring.util.*;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
@@ -54,7 +52,7 @@ import com.intellij.util.containers.MultiMap;
import com.siyeh.ig.psiutils.CodeBlockSurrounder;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.SideEffectChecker;
import org.jetbrains.annotations.NonNls;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -76,14 +74,11 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
private final boolean myDeleteTheDeclaration;
private final Function<PsiReference, InlineTransformer> myTransformerChooser;
private final PsiManager myManager;
private final PsiElementFactory myFactory;
private final CodeStyleManager myCodeStyleManager;
private final JavaCodeStyleManager myJavaCodeStyle;
private PsiCodeBlock[] myAddedBraces;
private final String myDescriptiveName;
private Map<PsiField, PsiClassInitializer> myAddedClassInitializers;
private List<CodeBlockSurrounder.SurroundResult> mySurroundResults;
private PsiMethod myMethodCopy;
@SuppressWarnings("LeakableMapKey") //short living refactoring
private Map<Language, InlineHandler.Inliner> myInliners;
@@ -124,9 +119,8 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
mySearchForTextOccurrences = searchForTextOccurrences;
myDeleteTheDeclaration = isDeleteTheDeclaration;
myManager = PsiManager.getInstance(myProject);
myFactory = JavaPsiFacade.getElementFactory(myManager.getProject());
myCodeStyleManager = CodeStyleManager.getInstance(myProject);
PsiManager manager = PsiManager.getInstance(myProject);
myFactory = JavaPsiFacade.getElementFactory(manager.getProject());
myJavaCodeStyle = JavaCodeStyleManager.getInstance(myProject);
myDescriptiveName = DescriptiveNameUtil.getDescriptiveName(myMethod);
}
@@ -187,9 +181,7 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
}
private boolean shouldDeleteOverrideAttribute(PsiMethod method) {
return method.getHierarchicalMethodSignature()
.getSuperSignatures().stream()
.allMatch(signature -> {
return ContainerUtil.and(method.getHierarchicalMethodSignature().getSuperSignatures(), signature -> {
PsiMethod superMethod = signature.getMethod();
if (superMethod == myMethod) {
return true;
@@ -461,7 +453,7 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
}
}
else {
myReference = addBracesWhenNeeded(new PsiReferenceExpression[]{(PsiReferenceExpression)myReference})[0];
myReference = surroundWithCodeBlock(new PsiReferenceExpression[]{(PsiReferenceExpression)myReference})[0];
if (myReference instanceof PsiMethodReferenceExpression) {
inlineMethodReference((PsiMethodReferenceExpression)myReference);
}
@@ -523,7 +515,7 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
}
}
PsiReferenceExpression[] refs = refExprList.toArray(new PsiReferenceExpression[0]);
refs = addBracesWhenNeeded(refs);
refs = surroundWithCodeBlock(refs);
for (PsiReferenceExpression ref : refs) {
if (ref instanceof PsiMethodReferenceExpression) {
inlineMethodReference((PsiMethodReferenceExpression)ref);
@@ -545,7 +537,11 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
tracker.deleteAndRestoreComments(myMethod);
}
}
removeAddedBracesWhenPossible();
if (mySurroundResults != null) {
for (CodeBlockSurrounder.SurroundResult result : mySurroundResults) {
result.collapse();
}
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
@@ -662,31 +658,7 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
PsiElement anchor = CommonJavaRefactoringUtil.getParentStatement(methodCall, true);
if (anchor == null) {
PsiEnumConstant enumConstant = PsiTreeUtil.getParentOfType(methodCall, PsiEnumConstant.class);
if (enumConstant != null) {
PsiExpression returnExpr = getSimpleReturnedExpression(myMethod);
if (returnExpr != null) {
ChangeContextUtil.encodeContextInfo(returnExpr, true);
PsiElement copy = returnExpr.copy();
ChangeContextUtil.clearContextInfo(returnExpr);
if (copy instanceof PsiReferenceExpression && ((PsiReferenceExpression)copy).getQualifierExpression() == null) {
copy = inlineParameterReference((PsiReferenceExpression)copy, blockData);
} else {
copy.accept(new JavaRecursiveElementVisitor() {
@Override
public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) {
super.visitReferenceExpression(expression);
inlineParameterReference(expression, blockData);
}
});
}
PsiElement replace = methodCall.replace(copy);
if (blockData.thisVar != null) {
ChangeContextUtil.decodeContextInfo(replace, myMethod.getContainingClass(), blockData.thisVar.getInitializer());
}
}
}
return;
throw new IllegalStateException("Cannot inline: parent statement should be available after CodeBlockSurround");
}
PsiElement anchorParent = anchor.getParent();
PsiLocalVariable thisVar = null;
@@ -930,145 +902,44 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
}
}
private static final Key<String> MARK_KEY = Key.create("");
private static final Key<PsiReferenceExpression> MARK_KEY = Key.create("MarkForSurround");
private PsiReferenceExpression[] addBracesWhenNeeded(PsiReferenceExpression[] refs) throws IncorrectOperationException {
ArrayList<PsiReferenceExpression> refsVector = new ArrayList<>();
ArrayList<PsiCodeBlock> addedBracesVector = new ArrayList<>();
myAddedClassInitializers = new HashMap<>();
private PsiReferenceExpression[] surroundWithCodeBlock(PsiReferenceExpression[] refs) throws IncorrectOperationException {
mySurroundResults = new ArrayList<>();
for (PsiReferenceExpression ref : refs) {
if (ref instanceof PsiMethodReferenceExpression) continue;
ref.putCopyableUserData(MARK_KEY, "");
ref.putCopyableUserData(MARK_KEY, ref);
}
var visitor = new PsiRecursiveElementWalkingVisitor() {
final Map<PsiReferenceExpression, PsiReferenceExpression> mapping = new HashMap<>();
RefLoop:
for (PsiReferenceExpression ref : refs) {
if (!ref.isValid()) continue;
if (ref instanceof PsiMethodReferenceExpression) {
refsVector.add(ref);
continue;
@Override
public void visitElement(@NotNull PsiElement element) {
if (element instanceof PsiReferenceExpression) {
PsiReferenceExpression orig = element.getCopyableUserData(MARK_KEY);
if (orig != null) {
mapping.put(orig, (PsiReferenceExpression)element);
}
}
super.visitElement(element);
}
PsiElement parentStatement = CommonJavaRefactoringUtil.getParentStatement(ref, true);
if (parentStatement != null) {
PsiElement parent = ref.getParent();
while (!parent.equals(parentStatement)) {
if (parent instanceof PsiExpressionStatement || parent instanceof PsiReturnStatement) {
String text = "{\n}";
PsiBlockStatement blockStatement = (PsiBlockStatement)myFactory.createStatementFromText(text, null);
blockStatement = (PsiBlockStatement)myCodeStyleManager.reformat(blockStatement);
blockStatement.getCodeBlock().add(parent);
blockStatement = (PsiBlockStatement)parent.replace(blockStatement);
PsiElement newStatement = blockStatement.getCodeBlock().getStatements()[0];
addMarkedElements(refsVector, newStatement);
addedBracesVector.add(blockStatement.getCodeBlock());
continue RefLoop;
}
parent = parent.getParent();
}
final PsiElement lambdaExpr = parentStatement.getParent();
if (lambdaExpr instanceof PsiLambdaExpression) {
final PsiLambdaExpression newLambdaExpr = (PsiLambdaExpression)myFactory.createExpressionFromText(
((PsiLambdaExpression)lambdaExpr).getParameterList().getText() + " -> " + "{\n}", lambdaExpr);
final PsiStatement statementFromText;
if (PsiType.VOID.equals(LambdaUtil.getFunctionalInterfaceReturnType((PsiLambdaExpression)lambdaExpr))) {
statementFromText = myFactory.createStatementFromText("a;", lambdaExpr);
((PsiExpressionStatement)statementFromText).getExpression().replace(parentStatement);
} else {
statementFromText = myFactory.createStatementFromText("return a;", lambdaExpr);
((PsiReturnStatement)statementFromText).getReturnValue().replace(parentStatement);
}
newLambdaExpr.getBody().add(statementFromText);
final PsiCodeBlock body = (PsiCodeBlock)((PsiLambdaExpression)lambdaExpr.replace(newLambdaExpr)).getBody();
PsiElement newStatement = body.getStatements()[0];
addMarkedElements(refsVector, newStatement);
addedBracesVector.add(body);
continue;
}
else if (lambdaExpr instanceof PsiSwitchLabeledRuleStatement && parentStatement instanceof PsiExpressionStatement) {
CodeBlockSurrounder surrounder = CodeBlockSurrounder.forExpression(ref);
if (surrounder != null) {
CodeBlockSurrounder.SurroundResult surround = surrounder.surround();
PsiExpression expression = surround.getExpression();
addMarkedElements(refsVector, expression);
ContainerUtil.addIfNotNull(addedBracesVector, PsiTreeUtil.getParentOfType(surround.getAnchor(), PsiCodeBlock.class));
continue;
}
}
}
else {
final PsiField field = PsiTreeUtil.getParentOfType(ref, PsiField.class);
if (field != null) {
if (field instanceof PsiEnumConstant) {
inlineEnumConstantParameter(refsVector, ref);
continue;
}
if (myAddedClassInitializers.containsKey(field)) {
continue;
}
field.normalizeDeclaration();
final PsiExpression initializer = field.getInitializer();
LOG.assertTrue(initializer != null);
PsiClassInitializer classInitializer = myFactory.createClassInitializer();
final PsiClass containingClass = field.getContainingClass();
classInitializer = (PsiClassInitializer)containingClass.addAfter(classInitializer, field);
containingClass.addAfter(CodeEditUtil.createLineFeed(field.getManager()), field);
final PsiCodeBlock body = classInitializer.getBody();
PsiExpressionStatement statement = (PsiExpressionStatement)myFactory.createStatementFromText(field.getName() + " = 0;", body);
statement = (PsiExpressionStatement)body.add(statement);
final PsiAssignmentExpression assignment = (PsiAssignmentExpression)statement.getExpression();
assignment.getLExpression().replace(RenameJavaMemberProcessor.createMemberReference(field, assignment));
assignment.getRExpression().replace(initializer);
addMarkedElements(refsVector, statement);
if (field.hasModifierProperty(PsiModifier.STATIC)) {
PsiUtil.setModifierProperty(classInitializer, PsiModifier.STATIC, true);
}
myAddedClassInitializers.put(field, classInitializer);
continue;
}
}
refsVector.add(ref);
}
};
for (PsiReferenceExpression ref : refs) {
ref.putCopyableUserData(MARK_KEY, null);
}
if (!ref.isValid() || ref instanceof PsiMethodReferenceExpression) continue;
myAddedBraces = addedBracesVector.toArray(PsiCodeBlock.EMPTY_ARRAY);
return refsVector.toArray(new PsiReferenceExpression[0]);
}
private void inlineEnumConstantParameter(final List<? super PsiReferenceExpression> refsVector,
final PsiReferenceExpression ref) throws IncorrectOperationException {
PsiExpression expr = getSimpleReturnedExpression(myMethod);
if (expr != null) {
refsVector.add(ref);
}
else {
PsiCall call = PsiTreeUtil.getParentOfType(ref, PsiCall.class);
@NonNls String text = "new Object() { " + myMethod.getReturnTypeElement().getText() + " evaluate() { return " + call.getText() + ";}}.evaluate";
PsiExpression callExpr = JavaPsiFacade.getInstance(myProject).getParserFacade().createExpressionFromText(text, call);
PsiReferenceExpression classExpr = (PsiReferenceExpression)ref.replace(callExpr);
PsiNewExpression newObject = (PsiNewExpression)Objects.requireNonNull(classExpr.getQualifierExpression());
PsiMethod evaluateMethod = Objects.requireNonNull(newObject.getAnonymousClass()).getMethods()[0];
PsiExpression retVal = ((PsiReturnStatement)Objects.requireNonNull(evaluateMethod.getBody())
.getStatements()[0]).getReturnValue();
if (retVal instanceof PsiMethodCallExpression) {
refsVector.add(((PsiMethodCallExpression) retVal).getMethodExpression());
}
if (classExpr.getParent() instanceof PsiMethodCallExpression) {
PsiExpressionList args = ((PsiMethodCallExpression)classExpr.getParent()).getArgumentList();
PsiExpression[] argExpressions = args.getExpressions();
if (argExpressions.length > 0) {
args.deleteChildRange(argExpressions [0], argExpressions [argExpressions.length-1]);
}
CodeBlockSurrounder surrounder = CodeBlockSurrounder.forExpression(ref);
if (surrounder != null) {
CodeBlockSurrounder.SurroundResult surround = surrounder.surround();
surround.getAnchor().accept(visitor);
mySurroundResults.add(surround);
}
}
return StreamEx.of(refs).map(ref -> visitor.mapping.getOrDefault(ref, ref))
.peek(ref -> ref.putCopyableUserData(MARK_KEY, null))
.toArray(new PsiReferenceExpression[0]);
}
@Nullable
@@ -1082,91 +953,6 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
return ((PsiReturnStatement) statement).getReturnValue();
}
private static void addMarkedElements(final List<? super PsiReferenceExpression> array, PsiElement scope) {
scope.accept(new PsiRecursiveElementWalkingVisitor() {
@Override public void visitElement(@NotNull PsiElement element) {
if (element.getCopyableUserData(MARK_KEY) != null) {
array.add((PsiReferenceExpression)element);
element.putCopyableUserData(MARK_KEY, null);
}
super.visitElement(element);
}
});
}
private void removeAddedBracesWhenPossible() throws IncorrectOperationException {
if (myAddedBraces == null) return;
for (PsiCodeBlock codeBlock : myAddedBraces) {
PsiStatement[] statements = codeBlock.getStatements();
if (statements.length == 1) {
final PsiElement codeBlockParent = codeBlock.getParent();
PsiStatement statement = statements[0];
if (codeBlockParent instanceof PsiLambdaExpression) {
if (statement instanceof PsiReturnStatement) {
final PsiExpression returnValue = ((PsiReturnStatement)statement).getReturnValue();
if (returnValue != null) {
codeBlock.replace(returnValue);
}
} else if (statement instanceof PsiExpressionStatement){
codeBlock.replace(((PsiExpressionStatement)statement).getExpression());
}
}
else if (codeBlockParent instanceof PsiBlockStatement) {
if (statement instanceof PsiYieldStatement) {
PsiExpression expression = ((PsiYieldStatement)statement).getExpression();
if (expression != null) {
PsiExpressionStatement statementFromText = (PsiExpressionStatement)myFactory.createStatementFromText("a;", expression);
statementFromText.getExpression().replace(expression);
codeBlockParent.replace(statementFromText);
}
} else if (!(ifStatementWithAppendableElseBranch(statement) &&
codeBlockParent.getParent() instanceof PsiIfStatement &&
((PsiIfStatement)codeBlockParent.getParent()).getElseBranch() != null)) {
codeBlockParent.replace(statement);
}
}
else {
codeBlock.replace(statement);
}
}
}
myAddedClassInitializers.forEach((psiField, classInitializer) -> {
PsiExpression newInitializer = getSimpleFieldInitializer(psiField, classInitializer);
PsiExpression fieldInitializer = Objects.requireNonNull(psiField.getInitializer());
if (newInitializer != null) {
fieldInitializer.replace(newInitializer);
classInitializer.delete();
}
else {
fieldInitializer.delete();
}
});
}
private static boolean ifStatementWithAppendableElseBranch(PsiStatement statement) {
if (statement instanceof PsiIfStatement) {
PsiStatement elseBranch = ((PsiIfStatement)statement).getElseBranch();
return elseBranch == null || elseBranch instanceof PsiIfStatement;
}
return false;
}
@Nullable
private PsiExpression getSimpleFieldInitializer(PsiField field, PsiClassInitializer initializer) {
final PsiStatement[] statements = initializer.getBody().getStatements();
if (statements.length != 1) return null;
if (!(statements[0] instanceof PsiExpressionStatement)) return null;
final PsiExpression expression = ((PsiExpressionStatement)statements[0]).getExpression();
if (!(expression instanceof PsiAssignmentExpression)) return null;
final PsiExpression lExpression = ((PsiAssignmentExpression)expression).getLExpression();
if (!(lExpression instanceof PsiReferenceExpression)) return null;
final PsiElement resolved = ((PsiReferenceExpression)lExpression).resolve();
if (!myManager.areElementsEquivalent(field, resolved)) return null;
return ((PsiAssignmentExpression)expression).getRExpression();
}
public static @NlsContexts.DialogMessage String checkUnableToInsertCodeBlock(PsiCodeBlock methodBody, final PsiElement element) {
if (checkUnableToInsertCodeBlock(methodBody, element,
expr -> JavaPsiConstructorUtil.isConstructorCall(expr) && expr.getMethodExpression() != element)) {

View File

@@ -0,0 +1,23 @@
import java.util.NoSuchElementException;
public class MyQueue {
private long[] items = new long[16];
private int head;
private int tail;
public boolean isEmpty() { return head == tail; }
public long getFirst() {
if (isEmpty())
throw new NoSuchElementException("Queue is empty");
return items[head];
}
// true if the first element of the queue is greater than specified number
public boolean firstIsGreaterThan(int x) {
return !isEmpty() && g<caret>etFirst() > x;
}
// other methods
}

View File

@@ -0,0 +1,20 @@
import java.util.NoSuchElementException;
public class MyQueue {
private long[] items = new long[16];
private int head;
private int tail;
public boolean isEmpty() { return head == tail; }
// true if the first element of the queue is greater than specified number
public boolean firstIsGreaterThan(int x) {
if (isEmpty()) return false;
if (isEmpty())
throw new NoSuchElementException("Queue is empty");
return items[head] > x;
}
// other methods
}

View File

@@ -0,0 +1,13 @@
import java.util.function.Predicate;
class X {
Predicate<String> predicate() {
return s -> !s.isEmpty() && <caret>test(s) > 0;
}
int test(String s) {
int length = s.length();
int ch = s.charAt(0);
return length + ch;
}
}

View File

@@ -0,0 +1,13 @@
import java.util.function.Predicate;
class X {
Predicate<String> predicate() {
return s -> {
if (s.isEmpty()) return false;
int length = s.length();
int ch = s.charAt(0);
return length + ch > 0;
};
}
}

View File

@@ -0,0 +1,11 @@
import java.util.function.Predicate;
class X {
Predicate<String> predicate() {
return s -> !s.isEmpty() && <caret>test(s) > 0;
}
int test(String s) {
return s.length() + s.charAt(0);
}
}

View File

@@ -0,0 +1,8 @@
import java.util.function.Predicate;
class X {
Predicate<String> predicate() {
return s -> !s.isEmpty() && s.length() + s.charAt(0) > 0;
}
}

View File

@@ -1,7 +1,7 @@
class Foo {
public static final Bar[] bars= new Bar[] {new Bar("a", 0, "A", "B", "C"), new Bar("b", 1, "A", "B")};
public static final Bar[] bars = new Bar[] {new Bar("a", 0, "A", "B", "C"), new Bar("b", 1, "A", "B")};
}

View File

@@ -3,11 +3,11 @@ class MyWorld {
public void process(Object[] o) {
int i = 0;
while (o[i] instanceof String && ((String) o[i]).startsWith("hello")) {
i++;
}
i++;
}
i = 0;
while (!(o[i] instanceof String && ((String) o[i]).startsWith("hello"))) {
i++;
}
i++;
}
}
}

View File

@@ -0,0 +1,11 @@
import java.util.function.Predicate;
class X {
int x = Math.random() > 0.5 ? te<caret>st(1, 2, 3) + 1 : 0;
int test(int a, int b, int c) {
int one = a * a + b * b;
int two = c * c;
return one / two;
}
}

View File

@@ -0,0 +1,14 @@
class X {
int x;
{
if (Math.random() > 0.5) {
int one = 1 * 1 + 2 * 2;
int two = 3 * 3;
x = one / two + 1;
} else {
x = 0;
}
}
}

View File

@@ -0,0 +1,9 @@
import java.util.function.Predicate;
class X {
int x = Math.random() > 0.5 ? te<caret>st(1, 2, 3) + 1 : 0;
int test(int a, int b, int c) {
return (a * a + b * b) / (c * c);
}
}

View File

@@ -0,0 +1,4 @@
class X {
int x = Math.random() > 0.5 ? (1 * 1 + 2 * 2) / (3 * 3) + 1 : 0;
}

View File

@@ -4,8 +4,7 @@ class Temp {
}
public Object foo(Set bar) {
if (bar.size() < 2) {
// Inline this
if (bar.size() < 2) {// Inline this
bar.size(); // or online this
return null;
}

View File

@@ -16,6 +16,8 @@ import com.intellij.testFramework.IdeaTestUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class InlineMethodTest extends LightRefactoringTestCase {
@NotNull
@Override
@@ -554,6 +556,13 @@ public class InlineMethodTest extends LightRefactoringTestCase {
public void testInSwitchExpression() { doTest(); }
public void testInSwitchExpressionYield() { doTest(); }
public void testAndChain() { doTest(); }
public void testAndChainLambda() { doTest(); }
public void testAndChainLambdaSingleLine() { doTest(); }
public void testTernaryBranch() { doTest(); }
public void testTernaryBranchCollapsible() { doTest(); }
@Override
protected Sdk getProjectJDK() {
return getTestName(false).contains("Src") ? IdeaTestUtil.getMockJdk17() : super.getProjectJDK();

View File

@@ -3,6 +3,8 @@ package com.siyeh.ig.psiutils;
import com.intellij.codeInsight.BlockUtils;
import com.intellij.codeInsight.intention.impl.SplitConditionUtil;
import com.intellij.codeInspection.ConditionalBreakInInfiniteLoopInspection;
import com.intellij.codeInspection.RedundantLambdaCodeBlockInspection;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
@@ -14,6 +16,8 @@ import com.intellij.util.ArrayUtil;
import com.intellij.util.JavaPsiConstructorUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.style.ConditionalExpressionGenerator;
import com.siyeh.ig.style.IfConditionalModel;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -24,9 +28,11 @@ import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import static com.intellij.util.ObjectUtils.tryCast;
/**
* A facility to ensure that there's a code block that surrounds given expression, so if parts of expression should be extracted
* to separate statements, these is a place where new statements could be generated.
* to separate statements, there is a place where new statements could be generated.
* <p>
* The lifecycle is the following:
* <ol>
@@ -70,10 +76,14 @@ public abstract class CodeBlockSurrounder {
public static class SurroundResult {
private final @NotNull PsiExpression myExpression;
private final @NotNull PsiStatement myAnchor;
private final @NotNull PsiElement myContext;
private final @Nullable CodeBlockSurrounder mySurrounder;
SurroundResult(@NotNull PsiExpression expression, @NotNull PsiStatement anchor) {
SurroundResult(@NotNull PsiExpression expression, @NotNull PsiStatement anchor, @NotNull PsiElement context, @Nullable CodeBlockSurrounder surrounder) {
myExpression = expression;
myAnchor = anchor;
myContext = context;
mySurrounder = surrounder;
}
/**
@@ -89,6 +99,17 @@ public abstract class CodeBlockSurrounder {
public @NotNull PsiStatement getAnchor() {
return myAnchor;
}
/**
* Tries to collapse the created code-block back if it still consists of a single statement.
* This method could be called after the whole refactoring is completed if it's possible that additional statements
* were not actually necessary.
*/
public void collapse() {
if (mySurrounder != null && myContext.isValid()) {
mySurrounder.collapse(myContext);
}
}
}
final @NotNull PsiExpression myExpression;
@@ -143,14 +164,38 @@ public abstract class CodeBlockSurrounder {
boolean physical = myExpression.isPhysical();
PsiStatement replacement = replace(project, factory);
assert replacement.isPhysical() == physical;
PsiElement anchor = anchor(replacement);
PsiExpression newExpression = Objects.requireNonNull((PsiExpression)PsiTreeUtil.releaseMark(replacement, marker));
return new SurroundResult(newExpression, replacement);
return new SurroundResult(newExpression, replacement, anchor, this);
}
/**
* Performs the replacement
*
* @param project project to use
* @param factory factory to use
* @return PSI statement, a place before which it's safe to add more statements
*/
@NotNull PsiStatement replace(@NotNull Project project, @NotNull PsiElementFactory factory) {
throw new UnsupportedOperationException();
}
/**
* @param context PSI statement returned from replace
* @return anchor a stable PSI anchor for subsequent use in {@link #collapse(PsiElement)}. Could be a statement itself,
* but there's a risk that intermediate refactoring makes it invalid, so it's better to return something bigger
* (e.g., a PsiBlockStatement created).
*/
@NotNull PsiElement anchor(@NotNull PsiStatement context) {
return context;
}
/**
* Tries to collapse back the block if it becomes unnecessary
* @param anchor anchor previously returned by {@link #anchor(PsiStatement)}
*/
void collapse(@NotNull PsiElement anchor) {}
/**
* @param expression expression to test
* @return true if expression can be surrounded with a code block or already has a surrounding code block
@@ -209,6 +254,9 @@ public abstract class CodeBlockSurrounder {
cur = parent;
parent = cur.getParent();
}
if (parent instanceof PsiEnumConstant) {
return new AnonymousCallSurrounder(expression);
}
if (parent instanceof PsiStatement) {
PsiElement grandParent = parent.getParent();
if (grandParent instanceof PsiForStatement && ((PsiForStatement)grandParent).getUpdate() == parent) return null;
@@ -223,7 +271,7 @@ public abstract class CodeBlockSurrounder {
return forStatement((PsiStatement)parent, expression);
}
if (parent instanceof PsiLocalVariable) {
PsiDeclarationStatement decl = ObjectUtils.tryCast(parent.getParent(), PsiDeclarationStatement.class);
PsiDeclarationStatement decl = tryCast(parent.getParent(), PsiDeclarationStatement.class);
if (decl != null && ArrayUtil.getFirstElement(decl.getDeclaredElements()) == parent) {
PsiTypeElement typeElement = ((PsiLocalVariable)parent).getTypeElement();
if (!typeElement.isInferredType() ||
@@ -241,7 +289,7 @@ public abstract class CodeBlockSurrounder {
}
}
if (parent instanceof PsiResourceVariable) {
PsiResourceList list = ObjectUtils.tryCast(parent.getParent(), PsiResourceList.class);
PsiResourceList list = tryCast(parent.getParent(), PsiResourceList.class);
if (list != null && list.getParent() instanceof PsiTryStatement) {
Iterator<PsiResourceListElement> iterator = list.iterator();
PsiTryStatement tryStatement = (PsiTryStatement)list.getParent();
@@ -253,7 +301,7 @@ public abstract class CodeBlockSurrounder {
}
return null;
}
if (parent instanceof PsiField && !(parent instanceof PsiEnumConstant)) {
if (parent instanceof PsiField) {
return new ExtractFieldInitializerSurrounder(expression, (PsiField)parent);
}
@@ -281,7 +329,7 @@ public abstract class CodeBlockSurrounder {
private static CodeBlockSurrounder forStatement(PsiStatement statement, PsiExpression expression) {
PsiElement statementParent = statement.getParent();
PsiForStatement forStatement = ObjectUtils.tryCast(statementParent, PsiForStatement.class);
PsiForStatement forStatement = tryCast(statementParent, PsiForStatement.class);
if (statementParent instanceof PsiLabeledStatement || (forStatement != null && forStatement.getBody() != statement)) {
statement = (PsiStatement)statementParent;
statementParent = statement.getParent();
@@ -306,10 +354,65 @@ public abstract class CodeBlockSurrounder {
@Override
public @NotNull CodeBlockSurrounder.SurroundResult surround() {
return new SurroundResult(myExpression, myAnchor);
return new SurroundResult(myExpression, myAnchor, myAnchor, null);
}
}
private static class AnonymousCallSurrounder extends CodeBlockSurrounder {
AnonymousCallSurrounder(@NotNull PsiExpression expression) {
super(expression);
}
@Override
@NotNull
PsiStatement replace(@NotNull Project project, @NotNull PsiElementFactory factory) {
PsiExpression expression = myExpression;
if (expression.getParent() instanceof PsiMethodCallExpression &&
((PsiMethodCallExpression)expression.getParent()).getMethodExpression() == expression) {
expression = (PsiExpression)expression.getParent();
}
PsiType type = expression.getType();
String typeText = type == null ? CommonClassNames.JAVA_LANG_OBJECT : type.getCanonicalText();
String text = "new java.lang.Object() { " + typeText + " evaluate() { return x;}}.evaluate()";
PsiMethodCallExpression anonymousCall =
(PsiMethodCallExpression)JavaPsiFacade.getElementFactory(expression.getProject()).createExpressionFromText(text, expression);
PsiReturnStatement returnStatement = Objects.requireNonNull(getReturnStatement(anonymousCall));
Objects.requireNonNull(returnStatement.getReturnValue()).replace(expression);
return Objects.requireNonNull(getReturnStatement((PsiMethodCallExpression)expression.replace(anonymousCall)));
}
private static @Nullable PsiReturnStatement getReturnStatement(@NotNull PsiMethodCallExpression anonymousCall) {
PsiNewExpression newExpression = tryCast(anonymousCall.getMethodExpression().getQualifierExpression(), PsiNewExpression.class);
if (newExpression == null) return null;
PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass();
if (anonymousClass == null) return null;
PsiMethod[] methods = anonymousClass.getMethods();
if (methods.length != 1) return null;
PsiCodeBlock body = methods[0].getBody();
if (body == null) return null;
PsiStatement[] statements = body.getStatements();
if (statements.length != 1) return null;
return tryCast(statements[0], PsiReturnStatement.class);
}
@Override
@NotNull PsiMethodCallExpression anchor(@NotNull PsiStatement context) {
PsiMethod method = (PsiMethod)context.getParent().getParent();
PsiNewExpression newExpression = (PsiNewExpression)method.getParent().getParent();
return (PsiMethodCallExpression) newExpression.getParent().getParent();
}
@Override
void collapse(@NotNull PsiElement anchor) {
PsiMethodCallExpression anonymousCall = tryCast(anchor, PsiMethodCallExpression.class);
if (anonymousCall == null) return;
PsiReturnStatement returnStatement = getReturnStatement(anonymousCall);
if (returnStatement == null) return;
PsiExpression targetExpression = returnStatement.getReturnValue();
if (targetExpression == null) return;
new CommentTracker().replaceAndRestoreComments(anonymousCall, targetExpression);
}
}
private static class LambdaCodeBlockSurrounder extends CodeBlockSurrounder {
private final @NotNull PsiLambdaExpression myLambda;
@@ -338,6 +441,22 @@ public abstract class CodeBlockSurrounder {
newBody = (PsiCodeBlock)myLambda.getBody().replace(newBody);
return newBody.getStatements()[0]; // either expression statement or return statement
}
@Override
@NotNull PsiLambdaExpression anchor(@NotNull PsiStatement context) {
return (PsiLambdaExpression)context.getParent().getParent();
}
@Override
void collapse(@NotNull PsiElement anchor) {
PsiLambdaExpression lambda = tryCast(anchor, PsiLambdaExpression.class);
if (lambda == null) return;
PsiElement body = lambda.getBody();
PsiExpression expression = RedundantLambdaCodeBlockInspection.isCodeBlockRedundant(body);
if (expression != null) {
body.replace(expression);
}
}
}
private static class YieldSurrounder extends CodeBlockSurrounder {
@@ -361,6 +480,23 @@ public abstract class CodeBlockSurrounder {
block = (PsiBlockStatement)myStatement.replace(block);
return block.getCodeBlock().getStatements()[0];
}
@Override
@NotNull PsiBlockStatement anchor(@NotNull PsiStatement context) {
return (PsiBlockStatement)context.getParent().getParent();
}
@Override
void collapse(@NotNull PsiElement anchor) {
PsiBlockStatement blockStatement = tryCast(anchor, PsiBlockStatement.class);
if (blockStatement == null) return;
PsiStatement statement = ControlFlowUtils.getOnlyStatementInBlock(blockStatement.getCodeBlock());
if (!(statement instanceof PsiYieldStatement)) return;
PsiExpression expression = ((PsiYieldStatement)statement).getExpression();
if (expression == null) return;
CommentTracker ct = new CommentTracker();
ct.replaceAndRestoreComments(blockStatement, ct.text(expression) + ";");
}
}
private static class SimpleSurrounder extends CodeBlockSurrounder {
@@ -378,6 +514,26 @@ public abstract class CodeBlockSurrounder {
block = (PsiBlockStatement)myStatement.replace(block);
return block.getCodeBlock().getStatements()[0];
}
@Override
@NotNull PsiBlockStatement anchor(@NotNull PsiStatement context) {
return (PsiBlockStatement)context.getParent().getParent();
}
@Override
void collapse(@NotNull PsiElement anchor) {
PsiBlockStatement blockStatement = tryCast(anchor, PsiBlockStatement.class);
if (blockStatement == null) return;
PsiStatement statement = ControlFlowUtils.getOnlyStatementInBlock(blockStatement.getCodeBlock());
if (statement == null) return;
if (statement instanceof PsiIfStatement &&
blockStatement.getParent() instanceof PsiIfStatement &&
((PsiIfStatement)blockStatement.getParent()).getThenBranch() == blockStatement) {
// if(...) {if(...)} else {...} -- do not unwrap nested `if`
return;
}
new CommentTracker().replaceAndRestoreComments(blockStatement, statement);
}
}
private static class ExtractFieldInitializerSurrounder extends CodeBlockSurrounder {
@@ -397,7 +553,7 @@ public abstract class CodeBlockSurrounder {
@NotNull PsiStatement replace(@NotNull Project project, @NotNull PsiElementFactory factory) {
myField.normalizeDeclaration();
PsiClassInitializer initializer =
ObjectUtils.tryCast(PsiTreeUtil.skipWhitespacesAndCommentsForward(myField), PsiClassInitializer.class);
tryCast(PsiTreeUtil.skipWhitespacesAndCommentsForward(myField), PsiClassInitializer.class);
boolean isStatic = myField.hasModifierProperty(PsiModifier.STATIC);
if (initializer == null || initializer.hasModifierProperty(PsiModifier.STATIC) != isStatic) {
initializer = factory.createClassInitializer();
@@ -426,6 +582,27 @@ public abstract class CodeBlockSurrounder {
Objects.requireNonNull(myField.getInitializer()).delete();
return assignment;
}
@Override
void collapse(@NotNull PsiElement anchor) {
PsiExpressionStatement statement = tryCast(anchor, PsiExpressionStatement.class);
if (statement == null) return;
PsiAssignmentExpression assignment = tryCast(statement.getExpression(), PsiAssignmentExpression.class);
if (assignment == null) return;
if (myField.hasInitializer()) return;
PsiExpression lExpression = assignment.getLExpression();
if (!(lExpression instanceof PsiReferenceExpression) || !((PsiReferenceExpression)lExpression).isReferenceTo(myField) ||
((PsiReferenceExpression)lExpression).getQualifierExpression() != null) {
return;
}
PsiExpression rExpression = assignment.getRExpression();
if (rExpression == null) return;
PsiCodeBlock block = tryCast(statement.getParent(), PsiCodeBlock.class);
if (block == null || !(block.getParent() instanceof PsiClassInitializer)) return;
if (block.getStatementCount() != 1) return;
myField.setInitializer(rExpression);
block.getParent().delete();
}
}
private static class SplitTrySurrounder extends CodeBlockSurrounder {
@@ -533,6 +710,19 @@ public abstract class CodeBlockSurrounder {
Objects.requireNonNull(whileStatement.getCondition()).replace(lOperands);
return ifStatement;
}
@Override
@NotNull PsiWhileStatement anchor(@NotNull PsiStatement context) {
return (PsiWhileStatement)context.getParent().getParent().getParent();
}
@Override
void collapse(@NotNull PsiElement anchor) {
PsiWhileStatement whileStatement = tryCast(anchor, PsiWhileStatement.class);
if (whileStatement != null) {
ConditionalBreakInInfiniteLoopInspection.tryTransform(whileStatement);
}
}
}
private static class TernaryToIfSurrounder extends CodeBlockSurrounder {
@@ -602,6 +792,21 @@ public abstract class CodeBlockSurrounder {
(PsiStatement)((PsiBlockStatement)Objects.requireNonNull(ifStatement.getElseBranch())).getCodeBlock().add(elseStatement);
return then ? thenStatement : elseStatement;
}
@Override
@NotNull PsiIfStatement anchor(@NotNull PsiStatement context) {
// PsiCodeBlock -> PsiBlockStatement -> PsiIfStatement
return (PsiIfStatement)context.getParent().getParent().getParent();
}
@Override
void collapse(@NotNull PsiElement anchor) {
PsiIfStatement ifStatement = tryCast(anchor, PsiIfStatement.class);
if (ifStatement == null) return;
PsiStatement result = collapseIf(ifStatement, "?:");
if (result == null) return;
myUpstream.collapse(myUpstream.anchor(result));
}
}
private static class AndOrToIfSurrounder extends CodeBlockSurrounder {
@@ -689,5 +894,32 @@ public abstract class CodeBlockSurrounder {
}
return rOperands;
}
@Override
void collapse(@NotNull PsiElement anchor) {
PsiIfStatement ifStatement = tryCast(PsiTreeUtil.skipWhitespacesAndCommentsBackward(anchor), PsiIfStatement.class);
if (ifStatement == null) return;
PsiStatement result = collapseIf(ifStatement, "&&", "||");
if (result == null) return;
myUpstream.collapse(myUpstream.anchor(result));
}
}
@Nullable
private static PsiStatement collapseIf(PsiIfStatement ifStatement, String... operators) {
IfConditionalModel model = IfConditionalModel.from(ifStatement, false);
if (model == null) return null;
ConditionalExpressionGenerator generator = ConditionalExpressionGenerator.from(model);
if (generator == null) return null;
String operator = generator.getTokenType();
if (!ArrayUtil.contains(operator, operators)) return null;
CommentTracker commentTracker = new CommentTracker();
String conditional = generator.generate(commentTracker);
commentTracker.replace(model.getThenExpression(), conditional);
PsiStatement branch = model.getElseBranch();
if (!PsiTreeUtil.isAncestor(ifStatement, branch, true)) {
commentTracker.delete(branch);
}
return (PsiStatement)commentTracker.replaceAndRestoreComments(ifStatement, model.getThenBranch());
}
}