create EnhancedSwitchBackwardMigrationInspection: convert new switches back to old style ones IDEA-202384

This commit is contained in:
Roman.Ivanov
2018-12-06 17:41:53 +07:00
parent 7b3014b9c4
commit 2fbec3fa2a
19 changed files with 630 additions and 5 deletions
+5 -5
View File
@@ -940,16 +940,16 @@
groupKey="group.names.language.level.specific.issues.and.migration.aids12" groupBundle="messages.InspectionsBundle"
enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.EnhancedSwitchMigrationInspection" />
<localInspection groupPath="Java,Java language level migration aids" language="JAVA" shortName="EnhancedSwitchBackwardMigration"
key="inspection.switch.expression.backward.migration.inspection.name" bundle="messages.InspectionsBundle"
groupKey="group.names.language.level.specific.issues.and.migration.aids12" groupBundle="messages.InspectionsBundle"
enabledByDefault="true" level="INFORMATION"
implementationClass="com.intellij.codeInspection.EnhancedSwitchBackwardMigrationInspection" />
<localInspection groupPath="Java" language="JAVA" shortName="SwitchLabeledRuleCanBeCodeBlock"
key="inspection.switch.labeled.rule.can.be.code.block.display.name" bundle="messages.InspectionsBundle"
groupKey="group.names.code.style.issues" groupBundle="messages.InspectionsBundle"
enabledByDefault="true" level="INFORMATION"
implementationClass="com.intellij.codeInspection.enhancedSwitch.SwitchLabeledRuleCanBeCodeBlockInspection" />
<localInspection groupPath="Java" language="JAVA" shortName="RedundantLabeledSwitchRuleCodeBlock"
key="inspection.labeled.switch.rule.redundant.code.block.display.name" bundle="messages.InspectionsBundle"
groupKey="group.names.code.style.issues" groupBundle="messages.InspectionsBundle"
enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.enhancedSwitch.RedundantLabeledSwitchRuleCodeBlockInspection" />
<globalInspection groupPath="Java" language="JAVA" shortName="EmptyMethod" displayName="Empty method" groupKey="group.names.declaration.redundancy" enabledByDefault="true" groupBundle="messages.InspectionsBundle"
level="WARNING" implementationClass="com.intellij.codeInspection.emptyMethod.EmptyMethodInspection"/>
@@ -0,0 +1,294 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection;
import com.intellij.codeInsight.BlockUtils;
import com.intellij.openapi.project.Project;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ControlFlowUtils;
import com.siyeh.ig.psiutils.SwitchUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
import static com.intellij.util.ObjectUtils.tryCast;
public class EnhancedSwitchBackwardMigrationInspection extends AbstractBaseJavaLocalInspectionTool {
private static final SwitchMigrationCase[] ourCases = new SwitchMigrationCase[]{
EnhancedSwitchBackwardMigrationInspection::inspectReturningSwitch,
EnhancedSwitchBackwardMigrationInspection::inspectVariableSavingSwitch,
EnhancedSwitchBackwardMigrationInspection::inspectSwitchStatement
};
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
if (!PsiUtil.getLanguageLevel(holder.getFile()).isAtLeast(LanguageLevel.JDK_12_PREVIEW)) return PsiElementVisitor.EMPTY_VISITOR;
return new JavaElementVisitor() {
@Override
public void visitSwitchExpression(PsiSwitchExpression expression) {
if (findReplacer(expression) == null) return;
String message = InspectionsBundle.message("inspection.switch.expression.backward.expression.migration.inspection.name");
holder.registerProblem(expression.getFirstChild(), message, new ReplaceWithOldStyleSwitchFix());
}
@Override
public void visitSwitchStatement(PsiSwitchStatement statement) {
if (findReplacer(statement) == null) return;
if (!SwitchUtils.isRuleFormatSwitch(statement)) return;
String message = InspectionsBundle.message("inspection.switch.expression.backward.statement.migration.inspection.name");
holder.registerProblem(statement.getFirstChild(), message, new ReplaceWithOldStyleSwitchFix());
}
};
}
private static Replacer findReplacer(@NotNull PsiSwitchBlock block) {
for (SwitchMigrationCase migrationCase : ourCases) {
Replacer replacer = migrationCase.suggestReplacer(block);
if (replacer != null) return replacer;
}
return null;
}
private static Replacer inspectReturningSwitch(@NotNull PsiSwitchBlock switchBlock) {
if (!(switchBlock instanceof PsiSwitchExpression)) return null;
PsiReturnStatement returnStatement = tryCast(switchBlock.getParent(), PsiReturnStatement.class);
if (returnStatement == null) return null;
return new ReturningReplacer(returnStatement);
}
private static Replacer inspectVariableSavingSwitch(@NotNull PsiSwitchBlock switchBlock) {
if (!(switchBlock instanceof PsiSwitchExpression)) return null;
PsiLocalVariable variable = tryCast(switchBlock.getParent(), PsiLocalVariable.class);
if (variable == null) return null;
return new VariableSavingReplacer(variable);
}
private static Replacer inspectSwitchStatement(@NotNull PsiSwitchBlock switchBlock) {
if (!(switchBlock instanceof PsiSwitchStatement)) return null;
return new SwitchStatementReplacer();
}
private interface SwitchMigrationCase {
@Nullable
Replacer suggestReplacer(@NotNull PsiSwitchBlock switchBlock);
}
private interface Replacer {
void replace(PsiSwitchBlock block);
}
private static class ReplaceWithOldStyleSwitchFix implements LocalQuickFix {
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getFamilyName() {
return InspectionsBundle.message("inspection.replace.with.old.style.switch.statement.fix.name");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiSwitchBlock switchBlock = tryCast(descriptor.getStartElement().getParent(), PsiSwitchBlock.class);
if (switchBlock == null) return;
Replacer replacer = findReplacer(switchBlock);
if (replacer == null) return;
replacer.replace(switchBlock);
}
}
private static class ReturningReplacer implements Replacer {
private final PsiReturnStatement myReturnStatement;
private ReturningReplacer(PsiReturnStatement returnStatement) {myReturnStatement = returnStatement;}
@Override
public void replace(PsiSwitchBlock block) {
CommentTracker ct = new CommentTracker();
PsiSwitchStatement switchStatement = new ReturnSwitchGenerator(block).generate(ct);
if (switchStatement == null) return;
ct.markUnchanged(block);
ct.replaceAndRestoreComments(myReturnStatement, switchStatement);
}
}
private static class VariableSavingReplacer implements Replacer {
private final PsiLocalVariable myVariable;
private VariableSavingReplacer(PsiLocalVariable variable) {
myVariable = variable;
}
@Override
public void replace(PsiSwitchBlock block) {
PsiElementFactory factory = JavaPsiFacade.getInstance(block.getProject()).getElementFactory();
PsiTypesUtil.replaceWithExplicitType(myVariable.getTypeElement());
CommentTracker ct = new CommentTracker();
PsiSwitchStatement switchStatement = new VarSavingSwitchGenerator(block, myVariable).generate(ct);
ct.markUnchanged(block);
PsiDeclarationStatement variableDeclaration =
(PsiDeclarationStatement)factory.createStatementFromText(myVariable.getTypeElement().getText() + " " + myVariable.getName() + ";", myVariable);
ct.markUnchanged(switchStatement);
PsiStatement declaration = (PsiStatement)ct.replaceAndRestoreComments(myVariable.getParent(), variableDeclaration);
BlockUtils.addAfter(declaration, switchStatement);
}
}
private static class SwitchStatementReplacer implements Replacer {
@Override
public void replace(PsiSwitchBlock block) {
CommentTracker ct = new CommentTracker();
PsiSwitchStatement switchStatement = new SwitchStatementGenerator(block).generate(ct);
if (switchStatement == null) return;
PsiElement newStatement = block.replace(switchStatement);
ct.insertCommentsBefore(newStatement);
}
}
private static abstract class SwitchGenerator {
private final PsiSwitchBlock mySwitchBlock;
final PsiElementFactory myFactory;
SwitchGenerator(PsiSwitchBlock switchBlock) {mySwitchBlock = switchBlock;
myFactory = JavaPsiFacade.getElementFactory(mySwitchBlock.getProject());
}
PsiSwitchStatement generate(CommentTracker mainCommentTracker) {
PsiSwitchBlock switchCopy = (PsiSwitchBlock)mySwitchBlock.copy();
PsiExpression expression = switchCopy.getExpression();
if (expression == null) return null;
PsiCodeBlock body = switchCopy.getBody();
if (body == null) return null;
List<PsiSwitchLabeledRuleStatement> rules = StreamEx.of(body.getStatements())
.select(PsiSwitchLabeledRuleStatement.class)
.toList();
List<CommentTracker> branchTrackers = new ArrayList<>();
StringJoiner joiner = new StringJoiner("\n");
for (PsiSwitchLabeledRuleStatement rule : rules) {
CommentTracker ct = new CommentTracker();
branchTrackers.add(ct);
String generate = generateBranch(rule, ct, switchCopy);
joiner.add(generate);
mainCommentTracker.markUnchanged(rule);
}
String bodyText = joiner.toString();
String switchText = "switch(" + mainCommentTracker.text(expression) + "){" + bodyText + "}";
mainCommentTracker.grabComments(switchCopy);
PsiSwitchStatement newBlock = (PsiSwitchStatement)myFactory.createStatementFromText(switchText, mySwitchBlock);
PsiCodeBlock newBody = newBlock.getBody();
assert newBody != null;
List<PsiSwitchLabelStatement> branches = StreamEx.of(newBody.getStatements())
.select(PsiSwitchLabelStatement.class)
.toList();
if (branches.size() != branchTrackers.size()) return newBlock;
for (int i = 0; i < branches.size(); i++) {
PsiSwitchLabelStatement branch = branches.get(i);
branchTrackers.get(i).insertCommentsBefore(branch);
}
return newBlock;
}
// rule changes inside, must be copied
private String generateBranch(PsiSwitchLabeledRuleStatement rule,
CommentTracker ct,
PsiSwitchBlock switchBlock) {
StreamEx.ofTree((PsiElement)rule, el -> StreamEx.of(el.getChildren()))
.select(PsiBreakStatement.class)
.filter(breakStatement -> breakStatement.getValueExpression() != null && breakStatement.findExitedElement() == switchBlock)
.forEach(breakStatement -> handleBreakInside(breakStatement, ct));
PsiExpressionList caseValues = rule.getCaseValues();
String caseValuesText = caseValues == null ? "" : ct.text(caseValues);
PsiStatement body = rule.getBody();
String finalBody;
if (!(body instanceof PsiBlockStatement) && body != null) {
finalBody = generateExpressionBranch(body, ct);
} else {
finalBody = StreamEx.of(ControlFlowUtils.unwrapBlock(body))
.map(el -> ct.text(el))
.joining("\n");
}
ct.grabComments(rule);
String prefix = rule.isDefaultCase() ? "default" : "case " + caseValuesText;
return prefix + ":" + finalBody;
}
abstract void handleBreakInside(@NotNull PsiBreakStatement breakStatement, CommentTracker ct);
abstract String generateExpressionBranch(@NotNull PsiStatement statement, CommentTracker ct);
}
private static class ReturnSwitchGenerator extends SwitchGenerator {
ReturnSwitchGenerator(PsiSwitchBlock switchBlock) {
super(switchBlock);
}
@Override
void handleBreakInside(@NotNull PsiBreakStatement breakStatement, CommentTracker ct) {
PsiExpression valueExpression = breakStatement.getValueExpression();
assert valueExpression != null;
PsiStatement replacement = myFactory.createStatementFromText("return " + ct.text(valueExpression) + ";", breakStatement);
ct.markUnchanged(valueExpression);
ct.grabComments(breakStatement);
breakStatement.replace(replacement);
}
@Override
String generateExpressionBranch(@NotNull PsiStatement statement, CommentTracker ct) {
return "return " + ct.text(statement);
}
}
private static class VarSavingSwitchGenerator extends SwitchGenerator {
private final @NotNull PsiLocalVariable myVariable;
VarSavingSwitchGenerator(PsiSwitchBlock switchBlock, @NotNull PsiLocalVariable variable) {
super(switchBlock);
myVariable = variable;
}
@Override
void handleBreakInside(@NotNull PsiBreakStatement breakStatement, CommentTracker ct) {
PsiExpression valueExpression = breakStatement.getValueExpression();
assert valueExpression != null;
String assignText = myVariable.getName() + " = " + valueExpression.getText() + ";\n";
PsiStatement assignment = myFactory.createStatementFromText(assignText, valueExpression);
ct.markUnchanged(valueExpression);
ct.grabComments(breakStatement);
PsiStatement newAssignment = (PsiStatement)breakStatement.replace(assignment);
BlockUtils.addAfter(newAssignment, myFactory.createStatementFromText("break;", null));
}
@Override
String generateExpressionBranch(@NotNull PsiStatement statement, CommentTracker ct) {
return myVariable.getName() + " = " + ct.text(statement) + "\nbreak;";
}
}
private static class SwitchStatementGenerator extends SwitchGenerator {
SwitchStatementGenerator(PsiSwitchBlock switchBlock) {
super(switchBlock);
}
@Override
void handleBreakInside(@NotNull PsiBreakStatement breakStatement, CommentTracker ct) {
// impossible, only if code is already broken, it can happen
}
@Override
String generateExpressionBranch(@NotNull PsiStatement statement, CommentTracker ct) {
return ct.text(statement) + "\nbreak;";
}
}
}
@@ -0,0 +1,10 @@
<html>
<body>
<p>
Reports 'switch' statements, which can be replaced with enhanced 'switch' statement or expression.
</p>
<p>Available if the language level is at least Java 12 Preview.</p>
<!-- tooltip end -->
<p><small>New in 2019.1</small></p>
</body>
</html>
@@ -0,0 +1,21 @@
// "Replace with old style 'switch' statement" "true"
import java.util.*;
class SwitchExpressionMigration {
private static void m(int x) {
switch (x) {
case 1:
if (true)
return 0;
else
return 1;
case 2:
return 2;
case 3, 4:
System.out.println("asda");
return 3;
default:
return 12;
}
}
}
@@ -0,0 +1,25 @@
// "Replace with old style 'switch' statement" "true"
import java.util.*;
class SwitchExpressionMigration {
private static void m(int x) {
/*1*//*3*//*4*//*6*//*7*//*8*//*18*//*2*/
switch (x +/*5*/ x) {/*14*//*9*//*11*/
case 1 +/*10*/ 1:
if (true /*12*/)
/*13*/ return 0;
else
return 1;
/*15*/
/*16*/
case 2:
return 2 +/*17*/ 2;
case 3, 4:
System.out.println("asda");
return 3;
/*19*/
default:
return 12 /*20*/ + 12;
}
}
}
@@ -0,0 +1,23 @@
// "Replace with old style 'switch' statement" "true"
class SwitchExpressionMigration {
private static void m(int x) {
switch (x) {
case 1:
if (true) {
return 0;
} else {
return 1;
}
case 2:
return switch (1) {
case 1 -> {
break 12;
}
default -> 55;
};
default:
return 12;
}
}
}
@@ -0,0 +1,17 @@
// "Replace with old style 'switch' statement" "true"
import java.util.*;
class SwitchExpressionMigration {
private static String m(int n) {
/*1*/
/*3*/
switch (n +/*cond*/ n) {/*case*/
case 1:
System.out.println("a"/*2*/);
break;
case 2:
System.out.println("b");
break;
}
}
}
@@ -0,0 +1,28 @@
// "Replace with old style 'switch' statement" "true"
import java.util.*;
class SwitchExpressionMigration {
private static void m(int x) {
int x;
switch (x) {
case 1:
if (true) {
x = 0;
break;
} else {
x = 1;
break;
}
case 2:
x = 2;
break;
case 3, 4:
System.out.println("asda");
x = 3;
break;
default:
x = 12;
break;
}
}
}
@@ -0,0 +1,32 @@
// "Replace with old style 'switch' statement" "true"
import java.util.*;
class SwitchExpressionMigration {
private static void m(int x) {
/*4*/
/*1*/
/*2*/
/*3*/
int x;
switch (x +/*cond*/ x) {/*5*/
case 1:
if (true) {
x = 0;
break;
} else {
x = 1;
break;
}
case 2:
x = 2;
break;
case 3, 4:
System.out.println("asda");
x = 3;
break;
default:
x = 12/*6*/;
break;
}
}
}
@@ -0,0 +1,16 @@
// "Replace with old style 'switch' statement" "true"
import java.util.*;
class SwitchExpressionMigration {
private static void m(int x) {
int y;
switch (x) {
case 1:
y = 1;
break;
default:
y = 0;
break;
}
}
}
@@ -0,0 +1,20 @@
// "Replace with old style 'switch' statement" "true"
import java.util.*;
class SwitchExpressionMigration {
private static void m(int x) {
return switch<caret> (x) {
case 1 -> {if (true)
break 0;
else
break 1;
}
case 2 -> 2;
case 3, 4 -> {
System.out.println("asda");
break 3;
}
default -> 12;
};
}
}
@@ -0,0 +1,20 @@
// "Replace with old style 'switch' statement" "true"
import java.util.*;
class SwitchExpressionMigration {
private static void m(int x) {
/*1*/return/*2*/ switch<caret> /*3*/(/*4*/x +/*5*/ x/*6*/) /*7*/ {
/*8*/case /*9*/ 1 +/*10*/ 1 -> /*11*/{if (true /*12*/)
/*13*/break /*14*/ 0;
else
break 1;
}
case/*15*/ 2 -> /*16*/2 +/*17*/ 2;
case 3, 4 -> {
System.out.println("asda");
break 3;
}
/*18*/default/*19*/ -> 12 /*20*/ + 12;
};
}
}
@@ -0,0 +1,23 @@
// "Replace with old style 'switch' statement" "true"
class SwitchExpressionMigration {
private static void m(int x) {
return switch<caret> (x){
case 1 -> {
if (true) {
break 0;
}
else {
break 1;
}
}
case 2 -> switch (1) {
case 1 -> {
break 12;
}
default -> 55;
};
default -> 12;
};
}
}
@@ -0,0 +1,12 @@
// "Replace with old style 'switch' statement" "true"
import java.util.*;
class SwitchExpressionMigration {
private static String m(int n) {
switch<caret> (n +/*cond*/ n) {
/*1*/
case /*case*/1 -> System.out.println("a"/*2*/); /*3*/
case 2 -> System.out.println("b");
}
}
}
@@ -0,0 +1,20 @@
// "Replace with old style 'switch' statement" "true"
import java.util.*;
class SwitchExpressionMigration {
private static void m(int x) {
int x = switch<caret> (x) {
case 1 -> {if (true)
break 0;
else
break 1;
}
case 2 -> 2;
case 3, 4 -> {
System.out.println("asda");
break 3;
}
default -> 12;
};
}
}
@@ -0,0 +1,20 @@
// "Replace with old style 'switch' statement" "true"
import java.util.*;
class SwitchExpressionMigration {
private static void m(int x) {
int/*1*/ x /*2*/= /*3*/switch<caret>/*4*/ (x +/*cond*/ x) {
case 1 -> {if (true)
break /*5*/0;
else
break 1;
}
case 2 -> 2;
case 3, 4 -> {
System.out.println("asda");
break 3;
}
default -> 12/*6*/;
};
}
}
@@ -0,0 +1,11 @@
// "Replace with old style 'switch' statement" "true"
import java.util.*;
class SwitchExpressionMigration {
private static void m(int x) {
var y = switch<caret> (x) {
case 1 -> 1;
default -> 0;
};
}
}
@@ -0,0 +1,28 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.java.codeInspection;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.EnhancedSwitchBackwardMigrationInspection;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.pom.java.LanguageLevel;
import org.jetbrains.annotations.NotNull;
public class EnhancedSwitchBackwardMigrationInspectionTest extends LightQuickFixParameterizedTestCase {
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{
new EnhancedSwitchBackwardMigrationInspection()
};
}
@Override
protected String getBasePath() {
return "/inspection/switchExpressionBackwardMigration/";
}
@Override
protected LanguageLevel getLanguageLevel() {
return LanguageLevel.JDK_12_PREVIEW;
}
}
@@ -1034,6 +1034,11 @@ inspection.switch.expression.migration.inspection.if.name=If statement can be re
inspection.replace.with.switch.expression.fix.name=Replace with 'switch' expression
inspection.replace.with.enhanced.switch.statement.fix.name=Replace with enhanced 'switch' statement
inspection.switch.expression.backward.migration.inspection.name='switch' expression can be replaced with old style 'switch' statement
inspection.switch.expression.backward.expression.migration.inspection.name='switch' expression can be replaced with old style 'switch' statement
inspection.switch.expression.backward.statement.migration.inspection.name='switch' statement can be replaced with old style 'switch' statement
inspection.replace.with.old.style.switch.statement.fix.name=Replace with old style 'switch' statement
inspection.duplicate.branches.in.switch.display.name=Duplicate branches in 'switch' statement
inspection.duplicate.branches.in.switch.message=Duplicate branch in 'switch' statement
inspection.duplicate.branches.in.switch.fix.family.name=Merge duplicate branches of 'switch' statement