Java: Improvements in intention to split switch branch with multiple values into separate branches (IDEA-203895)

This commit is contained in:
Pavel Dolgov
2019-02-01 13:11:28 +03:00
parent 7e401bfee5
commit 4c260c88bc
30 changed files with 355 additions and 84 deletions
@@ -10,12 +10,14 @@ import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.CommentTracker;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import static com.intellij.psi.util.PsiTreeUtil.getNextSiblingOfType;
import static com.intellij.psi.util.PsiTreeUtil.getPrevSiblingOfType;
import static com.intellij.util.ObjectUtils.notNull;
@@ -26,30 +28,39 @@ import static com.siyeh.ig.psiutils.ControlFlowUtils.statementMayCompleteNormall
*/
public class SplitSwitchBranchWithSeveralCaseValuesAction extends PsiElementBaseIntentionAction {
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getFamilyName() {
return CodeInsightBundle.message("intention.split.switch.branch.with.several.case.values.family");
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
PsiSwitchLabelStatementBase statement = findLabelStatement(editor, element);
// traditional syntax "case 1: case 2: some code"
if (statement instanceof PsiSwitchLabelStatement) {
if (getPrevSiblingOfType(statement, PsiStatement.class) instanceof PsiSwitchLabelStatement ||
getNextSiblingOfType(statement, PsiStatement.class) instanceof PsiSwitchLabelStatement) {
PsiStatement lastSiblingLabel = findLastSiblingLabel(statement);
PsiStatement lastStatement = findLastStatementInBranch(lastSiblingLabel);
if (lastStatement != null && (!statementMayCompleteNormally(lastStatement) ||
getNextSiblingOfType(lastStatement, PsiSwitchLabelStatement.class) == null)) {
setText(CodeInsightBundle.message("intention.split.switch.branch.with.several.case.values.label.text"));
PsiSwitchLabelStatementBase labelStatement = findLabelStatement(editor, element);
if (labelStatement instanceof PsiSwitchLabelStatement) {
if (isMultiValueCase(labelStatement)) {
// mixed syntax "case 1, 2: some code"
if (isAvailableForLabel(labelStatement)) {
setText(CodeInsightBundle.message("intention.split.switch.branch.with.several.case.values.split.text"));
return true;
}
}
else if (hasSiblingLabel(labelStatement)) {
// traditional syntax "case 1: case 2: some code"
PsiSwitchLabelStatement lastSiblingLabel = findLastSiblingLabel(labelStatement, false);
if (isAvailableForLabel(lastSiblingLabel)) {
setText(CodeInsightBundle.message("intention.split.switch.branch.with.several.case.values.copy.text"));
return true;
}
}
}
// enhanced syntax "case 1, 2 -> some code"
else if (statement instanceof PsiSwitchLabeledRuleStatement) {
PsiExpressionList caseValues = statement.getCaseValues();
if (caseValues != null && caseValues.getExpressionCount() > 1) {
PsiStatement body = ((PsiSwitchLabeledRuleStatement)statement).getBody();
else if (labelStatement instanceof PsiSwitchLabeledRuleStatement) {
// enhanced syntax "case 1, 2 -> some code"
if (isMultiValueCase(labelStatement)) {
PsiStatement body = ((PsiSwitchLabeledRuleStatement)labelStatement).getBody();
if (body != null && element.getTextOffset() < body.getTextOffset()) {
setText(CodeInsightBundle.message("intention.split.switch.branch.with.several.case.values.rule.text"));
setText(CodeInsightBundle.message("intention.split.switch.branch.with.several.case.values.split.text"));
return true;
}
}
@@ -57,6 +68,22 @@ public class SplitSwitchBranchWithSeveralCaseValuesAction extends PsiElementBase
return false;
}
private static boolean isAvailableForLabel(@Nullable PsiSwitchLabelStatementBase label) {
PsiStatement lastStatement = findLastStatementInBranch(label);
return lastStatement != null && (!statementMayCompleteNormally(lastStatement) ||
getNextSiblingOfType(lastStatement, PsiSwitchLabelStatement.class) == null);
}
private static boolean isMultiValueCase(@NotNull PsiSwitchLabelStatementBase label) {
PsiExpressionList caseValues = label.getCaseValues();
return caseValues != null && caseValues.getExpressionCount() > 1;
}
private static boolean hasSiblingLabel(@Nullable PsiSwitchLabelStatementBase label) {
return getPrevSiblingOfType(label, PsiStatement.class) instanceof PsiSwitchLabelStatement ||
getNextSiblingOfType(label, PsiStatement.class) instanceof PsiSwitchLabelStatement;
}
/**
* Handle the case where the caret is at the right side of the element we're interested in
*/
@@ -78,14 +105,29 @@ public class SplitSwitchBranchWithSeveralCaseValuesAction extends PsiElementBase
PsiElement result = null;
if (statement instanceof PsiSwitchLabelStatement) {
PsiSwitchLabelStatement labelStatement = (PsiSwitchLabelStatement)statement;
PsiSwitchLabelStatement lastSibling = getLastSiblingLabel(labelStatement);
if (lastSibling != null) {
result = moveAfter(labelStatement, lastSibling);
PsiExpressionList caseValues = labelStatement.getCaseValues();
if (caseValues != null && caseValues.getExpressionCount() > 1) {
PsiExpression caseValue = findCaseValue(element, caseValues, editor);
Branch branch = Branch.fromLabel(labelStatement);
if (branch != null) {
if (isInList(caseValue, caseValues)) {
result = moveLabelValueAfter(labelStatement, caseValue);
}
else {
result = splitLabelValues(labelStatement);
}
}
}
else {
PsiStatement previousSibling = getPrevSiblingOfType(statement, PsiStatement.class);
if (previousSibling instanceof PsiSwitchLabelStatement) {
result = copyTo(labelStatement, (PsiSwitchLabelStatement)previousSibling);
PsiSwitchLabelStatement lastSiblingLabel = findLastSiblingLabel(labelStatement, true);
if (lastSiblingLabel != null) {
result = moveLabelAfter(labelStatement, lastSiblingLabel);
}
else {
PsiStatement previousSibling = getPrevSiblingOfType(statement, PsiStatement.class);
if (previousSibling instanceof PsiSwitchLabelStatement) {
result = copyLabelTo(labelStatement, (PsiSwitchLabelStatement)previousSibling);
}
}
}
}
@@ -93,17 +135,12 @@ public class SplitSwitchBranchWithSeveralCaseValuesAction extends PsiElementBase
PsiSwitchLabeledRuleStatement labeledRule = (PsiSwitchLabeledRuleStatement)statement;
PsiExpressionList caseValues = labeledRule.getCaseValues();
if (caseValues != null) {
PsiExpression caseValue = PsiTreeUtil.getNonStrictParentOfType(element, PsiExpression.class);
if (!isInList(caseValue, caseValues)) {
PsiElement previousElement = getPreviousElement(editor, element);
caseValue = PsiTreeUtil.getNonStrictParentOfType(previousElement, PsiExpression.class);
}
PsiExpression caseValue = findCaseValue(element, caseValues, editor);
if (isInList(caseValue, caseValues)) {
result = moveAfter(caseValue, labeledRule);
result = moveRuleAfter(caseValue, labeledRule);
}
else {
result = splitAll(caseValues, labeledRule);
result = splitRule(caseValues, labeledRule);
}
}
}
@@ -112,6 +149,18 @@ public class SplitSwitchBranchWithSeveralCaseValuesAction extends PsiElementBase
}
}
private static PsiExpression findCaseValue(@NotNull PsiElement element, @NotNull PsiExpressionList caseValues, @NotNull Editor editor) {
PsiExpression caseValue = PsiTreeUtil.getNonStrictParentOfType(element, PsiExpression.class);
if (!isInList(caseValue, caseValues)) {
PsiElement previousElement = getPreviousElement(editor, element);
caseValue = PsiTreeUtil.getNonStrictParentOfType(previousElement, PsiExpression.class);
}
while (caseValue != null && caseValue.getParent() instanceof PsiExpression) {
caseValue = (PsiExpression)caseValue.getParent();
}
return caseValue;
}
@Contract("null,_ -> false")
private static boolean isInList(@Nullable PsiExpression caseValue, @NotNull PsiExpressionList caseValues) {
return caseValue != null && PsiTreeUtil.isAncestor(caseValues, caseValue, true);
@@ -130,25 +179,7 @@ public class SplitSwitchBranchWithSeveralCaseValuesAction extends PsiElementBase
return ObjectUtils.tryCast(statement, PsiSwitchLabelStatementBase.class);
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getFamilyName() {
return CodeInsightBundle.message("intention.split.switch.branch.with.several.case.values.family");
}
@Nullable
private static PsiSwitchLabelStatement getLastSiblingLabel(@NotNull PsiSwitchLabelStatement original) {
PsiSwitchLabelStatement result = null;
for (PsiStatement next = getNextSiblingOfType(original, PsiStatement.class);
next instanceof PsiSwitchLabelStatement;
next = getNextSiblingOfType(next, PsiStatement.class)) {
result = (PsiSwitchLabelStatement)next;
}
return result;
}
private static PsiElement moveAfter(@NotNull PsiSwitchLabelStatement labelToMove, @NotNull PsiSwitchLabelStatement anchor) {
private static PsiElement moveLabelAfter(@NotNull PsiSwitchLabelStatement labelToMove, @NotNull PsiSwitchLabelStatement anchor) {
Branch branch = Branch.fromLabel(anchor);
if (branch == null) {
return null;
@@ -156,43 +187,74 @@ public class SplitSwitchBranchWithSeveralCaseValuesAction extends PsiElementBase
return branch.moveAfter(labelToMove);
}
private static PsiElement copyTo(@NotNull PsiSwitchLabelStatement labelToCopyFrom, @NotNull PsiSwitchLabelStatement anchor) {
private static PsiElement copyLabelTo(@NotNull PsiSwitchLabelStatement labelToCopyFrom, @NotNull PsiSwitchLabelStatement anchor) {
Branch branch = Branch.fromLabel(labelToCopyFrom);
if (branch == null) return null;
return branch.copyTo(anchor);
}
private static PsiSwitchLabeledRuleStatement moveAfter(@NotNull PsiExpression caseValue,
@NotNull PsiSwitchLabeledRuleStatement labeledRule) {
CommentTracker tracker = new CommentTracker();
@Nullable
private static PsiElement moveLabelValueAfter(@NotNull PsiSwitchLabelStatement labelStatement, @NotNull PsiExpression caseValue) {
Branch branch = Branch.fromLabel(labelStatement);
if (branch == null) {
return null;
}
PsiSwitchLabelStatement newLabel = branch.addLabelAfter(caseValue);
caseValue.delete();
return branch.copyTo(newLabel);
}
@Nullable
private static PsiElement splitLabelValues(@NotNull PsiSwitchLabelStatement labelStatement) {
Branch branch = Branch.fromLabel(labelStatement);
if (branch == null) {
return null;
}
List<PsiSwitchLabelStatement> newLabels = new ArrayList<>();
PsiExpression[] expressions = notNull(labelStatement.getCaseValues()).getExpressions();
for (int i = expressions.length - 1; i >= 1; i--) {
PsiExpression caseValue = expressions[i];
PsiSwitchLabelStatement newLabel = branch.addLabelAfter(caseValue);
newLabels.add(newLabel);
caseValue.delete();
}
for (PsiSwitchLabelStatement newLabel : newLabels) {
branch.copyTo(newLabel);
}
return !newLabels.isEmpty() ? newLabels.get(0) : null;
}
private static PsiSwitchLabeledRuleStatement moveRuleAfter(@NotNull PsiExpression caseValue,
@NotNull PsiSwitchLabeledRuleStatement labeledRule) {
Project project = labeledRule.getProject();
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
PsiSwitchBlock tempSwitch =
(PsiSwitchBlock)factory.createStatementFromText("switch(1){case " + tracker.text(caseValue) + "->{}}", caseValue);
PsiCodeBlock tempBody = notNull(tempSwitch.getBody());
PsiSwitchLabeledRuleStatement newRule = (PsiSwitchLabeledRuleStatement)factory.createStatementFromText("case 1->{}", null);
PsiSwitchLabeledRuleStatement newRule = (PsiSwitchLabeledRuleStatement)tempBody.getStatements()[0];
notNull(newRule.getCaseValues()).getExpressions()[0].replace(caseValue);
newRule = (PsiSwitchLabeledRuleStatement)labeledRule.getParent().addAfter(newRule, labeledRule);
newRule = (PsiSwitchLabeledRuleStatement)CodeStyleManager.getInstance(project).reformat(newRule);
notNull(newRule.getBody()).replace(notNull(labeledRule.getBody()));
tracker.deleteAndRestoreComments(caseValue);
caseValue.delete();
return newRule;
}
private static PsiSwitchLabeledRuleStatement splitAll(@NotNull PsiExpressionList caseValues,
@NotNull PsiSwitchLabeledRuleStatement labeledRule) {
private static PsiSwitchLabeledRuleStatement splitRule(@NotNull PsiExpressionList caseValues,
@NotNull PsiSwitchLabeledRuleStatement labeledRule) {
PsiExpression[] expressions = caseValues.getExpressions();
PsiSwitchLabeledRuleStatement anchor = labeledRule;
for (int i = 1; i < expressions.length; i++) {
anchor = moveAfter(expressions[i], anchor);
anchor = moveRuleAfter(expressions[i], anchor);
}
return anchor;
}
@Contract("null -> null")
@Nullable
static PsiStatement findLastStatementInBranch(@Nullable PsiElement label) {
PsiStatement lastStatement = null;
@@ -205,12 +267,13 @@ public class SplitSwitchBranchWithSeveralCaseValuesAction extends PsiElementBase
}
@Nullable
static PsiStatement findLastSiblingLabel(@Nullable PsiStatement statement) {
PsiStatement lastSiblingLabel = null;
for (PsiStatement next = statement; next instanceof PsiSwitchLabelStatement; next = getNextSiblingOfType(next, PsiStatement.class)) {
lastSiblingLabel = next;
private static PsiSwitchLabelStatement findLastSiblingLabel(@Nullable PsiStatement statement, boolean strict) {
PsiSwitchLabelStatement result = null;
PsiStatement start = strict ? getNextSiblingOfType(statement, PsiStatement.class) : statement;
for (PsiStatement next = start; next instanceof PsiSwitchLabelStatement; next = getNextSiblingOfType(next, PsiStatement.class)) {
result = (PsiSwitchLabelStatement)next;
}
return lastSiblingLabel;
return result;
}
private static class Branch {
@@ -289,13 +352,25 @@ public class SplitSwitchBranchWithSeveralCaseValuesAction extends PsiElementBase
return labelCopy;
}
PsiElement copyTo(@NotNull PsiSwitchLabelStatement anchor) {
PsiSwitchLabelStatement copyTo(@NotNull PsiSwitchLabelStatement anchor) {
myCodeBlock.addRangeAfter(myFirstElement, myLastStatement, anchor);
addBreakIfNeeded(anchor);
return anchor;
}
@NotNull
PsiSwitchLabelStatement addLabelAfter(@NotNull PsiExpression caseValue) {
Project project = myCodeBlock.getProject();
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
PsiSwitchLabelStatement newLabel = (PsiSwitchLabelStatement)factory.createStatementFromText("case 1:", null);
notNull(newLabel.getCaseValues()).getExpressions()[0].replace(caseValue);
newLabel = (PsiSwitchLabelStatement)myCodeBlock.addAfter(newLabel, myLastStatement);
newLabel = (PsiSwitchLabelStatement)CodeStyleManager.getInstance(project).reformat(newLabel);
return newLabel;
}
private void addBreakIfNeeded(@Nullable PsiElement label) {
PsiStatement lastStatementInBranch = findLastStatementInBranch(label);
@@ -1,4 +1,4 @@
// "Split values of 'switch' rule" "true"
// "Split values of 'switch' branch" "true"
class C {
void foo(int n) {
String s = "";
@@ -1,4 +1,4 @@
// "Split values of 'switch' rule" "true"
// "Split values of 'switch' branch" "true"
class C {
void foo(int n) {
String s = "";
@@ -1,4 +1,4 @@
// "Split values of 'switch' rule" "true"
// "Split values of 'switch' branch" "true"
class C {
void foo(int n) {
String s = "";
@@ -0,0 +1,18 @@
// "Split values of 'switch' branch" "true"
class C {
void test(int i) {
switch (i) {
case 1:
System.out.println("hello");
break;
case 2:
System.out.println("hello");
break;
case 3:
System.out.println("hello");
break;
case 4:
System.out.println("bye");
}
}
}
@@ -0,0 +1,13 @@
// "Split values of 'switch' branch" "true"
class C {
void test(int i) {
switch (i) {
case 2:
System.out.println("hello");
break;
<caret>case 1:
System.out.println("hello");
break;
}
}
}
@@ -0,0 +1,17 @@
// "Split values of 'switch' branch" "true"
class C {
void test(int i) {
switch (i) {
// 1
case /*2*/ /*3*/2: // 4
// 5
System.out.println("hello");
break;
case 1:
// 5
System.out.println("hello");
break;
/*6*/
}
}
}
@@ -0,0 +1,13 @@
// "Split values of 'switch' branch" "true"
class C {
void test(int i) {
switch (i) {
case 1:
System.out.println("hello");
break;
<caret>case 2:
System.out.println("hello");
break;
}
}
}
@@ -0,0 +1,13 @@
// "Split values of 'switch' branch" "true"
class C {
void test(int i) {
switch (i) {
case 1:
System.out.println("hello");
break;
case 1 + 1:
System.out.println("hello");
break;
}
}
}
@@ -0,0 +1,23 @@
// "Split values of 'switch' branch" "true"
class C {
void test(int i) {
switch (i) {
// 1
case /*2*/1 /*3*/ /*4*/: //5
// 6
System.out.println("hello");
break;
case 2:
// 6
System.out.println("hello");
break;
case 3:
// 6
System.out.println("hello");
break;
// 7
case 4:
System.out.println("bye");
}
}
}
@@ -1,4 +1,4 @@
// "Split values of 'switch' rule" "true"
// "Split values of 'switch' branch" "true"
class C {
void foo(int n) {
String s = "";
@@ -1,4 +1,4 @@
// "Split values of 'switch' rule" "true"
// "Split values of 'switch' branch" "true"
class C {
void foo(int n) {
String s = switch (n) {
@@ -1,4 +1,4 @@
// "Split values of 'switch' rule" "true"
// "Split values of 'switch' branch" "true"
class C {
void foo(int n) {
String s = "";
@@ -0,0 +1,10 @@
// "Split values of 'switch' branch" "true"
class C {
void foo(int n) {
String s = "";
switch (n) {
case 3 -> s = "x";
case 1 + 1 -> s = "x";
}
}
}
@@ -1,4 +1,4 @@
// "Split values of 'switch' rule" "false"
// "Split values of 'switch' branch" "false"
class C {
void foo(int n) {
String s = "";
@@ -1,4 +1,4 @@
// "Split values of 'switch' rule" "true"
// "Split values of 'switch' branch" "true"
class C {
void foo(int n) {
String s = "";
@@ -1,4 +1,4 @@
// "Split values of 'switch' rule" "true"
// "Split values of 'switch' branch" "true"
class C {
void foo(int n) {
String s = "";
@@ -1,4 +1,4 @@
// "Split values of 'switch' rule" "true"
// "Split values of 'switch' branch" "true"
class C {
void foo(int n) {
String s = "";
@@ -0,0 +1,12 @@
// "Split values of 'switch' branch" "true"
class C {
void test(int i) {
switch (i) {
<caret>case 1, 2, 3:
System.out.println("hello");
break;
case 4:
System.out.println("bye");
}
}
}
@@ -0,0 +1,10 @@
// "Split values of 'switch' branch" "true"
class C {
void test(int i) {
switch (i) {
case 1<caret>, 2:
System.out.println("hello");
break;
}
}
}
@@ -0,0 +1,13 @@
// "Split values of 'switch' branch" "true"
class C {
void test(int i) {
switch (i) {
// 1
case /*2*/1<caret>, /*3*/2: // 4
// 5
System.out.println("hello");
break;
/*6*/
}
}
}
@@ -0,0 +1,10 @@
// "Split values of 'switch' branch" "true"
class C {
void test(int i) {
switch (i) {
case 1, <caret>2:
System.out.println("hello");
break;
}
}
}
@@ -0,0 +1,10 @@
// "Split values of 'switch' branch" "true"
class C {
void test(int i) {
switch (i) {
case 1, 1+1<caret>:
System.out.println("hello");
break;
}
}
}
@@ -0,0 +1,15 @@
// "Split values of 'switch' branch" "true"
class C {
void test(int i) {
switch (i) {
// 1
<caret>case /*2*/1, /*3*/2, /*4*/3: //5
// 6
System.out.println("hello");
break;
// 7
case 4:
System.out.println("bye");
}
}
}
@@ -1,4 +1,4 @@
// "Split values of 'switch' rule" "true"
// "Split values of 'switch' branch" "true"
class C {
void foo(int n) {
String s = "";
@@ -1,4 +1,4 @@
// "Split values of 'switch' rule" "true"
// "Split values of 'switch' branch" "true"
class C {
void foo(int n) {
String s = switch (n) {
@@ -1,4 +1,4 @@
// "Split values of 'switch' rule" "true"
// "Split values of 'switch' branch" "true"
class C {
void foo(int n) {
String s = "";
@@ -0,0 +1,9 @@
// "Split values of 'switch' branch" "true"
class C {
void foo(int n) {
String s = "";
switch (n) {
case <caret>1 + 1, 3 -> s = "x";
}
}
}
@@ -2,6 +2,10 @@
package com.intellij.java.codeInsight.intention;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.testFramework.LightProjectDescriptor;
import org.jetbrains.annotations.NotNull;
import static com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase.JAVA_12;
public class SplitSwitchBranchWithSeveralCaseValuesTest extends LightQuickFixParameterizedTestCase {
@@ -10,6 +14,12 @@ public class SplitSwitchBranchWithSeveralCaseValuesTest extends LightQuickFixPar
return "/codeInsight/daemonCodeAnalyzer/quickFix/splitSwitchBranchWithSeveralCaseValues";
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JAVA_12;
}
@Override
protected boolean shouldBeAvailableAfterExecution() {
return false;
@@ -242,8 +242,8 @@ intention.break.string.on.line.breaks.text=Break string on '\\n'
intention.unwrap.else.branch=Unwrap 'else' branch
intention.unwrap.else.branch.changes.semantics=Unwrap 'else' branch (changes semantics)
intention.split.switch.branch.with.several.case.values.family=Split switch branch with several case values into individual 'switch' branches
intention.split.switch.branch.with.several.case.values.label.text=Copy 'switch' branch
intention.split.switch.branch.with.several.case.values.rule.text=Split values of 'switch' rule
intention.split.switch.branch.with.several.case.values.copy.text=Copy 'switch' branch
intention.split.switch.branch.with.several.case.values.split.text=Split values of 'switch' branch
intention.create.test=Create Test
intention.create.test.dialog.testing.library=Testing &library: