diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml index 48b09fce6a02..561811adffff 100644 --- a/java/java-impl/src/META-INF/JavaPlugin.xml +++ b/java/java-impl/src/META-INF/JavaPlugin.xml @@ -935,6 +935,11 @@ groupKey="group.names.probable.bugs" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="WARNING" implementationClass="com.intellij.codeInspection.OverflowingLoopIndexInspection" /> + diff --git a/java/java-impl/src/com/intellij/codeInspection/EnhancedSwitchMigrationInspection.java b/java/java-impl/src/com/intellij/codeInspection/EnhancedSwitchMigrationInspection.java new file mode 100644 index 000000000000..415740c5cdd6 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/EnhancedSwitchMigrationInspection.java @@ -0,0 +1,560 @@ +// 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.openapi.project.Project; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.siyeh.ig.psiutils.CommentTracker; +import com.siyeh.ig.psiutils.ControlFlowUtils; +import com.siyeh.ig.psiutils.ExpressionUtils; +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.Arrays; +import java.util.List; +import java.util.Set; + +import static com.intellij.util.ObjectUtils.tryCast; + +public class EnhancedSwitchMigrationInspection extends AbstractBaseJavaLocalInspectionTool { + private final static SwitchInspection[] ourExpressionInspections = new SwitchInspection[]{ + new ReturningSwitch(), + new VariableAssigningSwitch(), + new StatementSwitch() + }; + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + if (!PsiUtil.getLanguageLevel(holder.getProject()).isAtLeast(LanguageLevel.JDK_12_PREVIEW)) return PsiElementVisitor.EMPTY_VISITOR; + return new JavaElementVisitor() { + @Override + public void visitSwitchStatement(PsiSwitchStatement statement) { + SwitchReplacer replacer = getSwitchReplacer(statement); + if (replacer == null) return; + PsiElement switchKeyword = statement.getFirstChild(); + holder.registerProblem(switchKeyword, InspectionsBundle.message("inspection.switch.expression.migration.inspection.name"), + new ReplaceWithSwitchExpressionFix(replacer.getType())); + } + }; + } + + private static void addBranch(List branches, + PsiStatement[] statements, + int unmatchedCaseIndex, + int endIndexExcl, + boolean isFallthrough, PsiBreakStatement current) { + PsiSwitchLabelStatement labelStatement = (PsiSwitchLabelStatement)statements[unmatchedCaseIndex]; + PsiStatement[] branchStatements = Arrays.copyOfRange(statements, unmatchedCaseIndex + 1, endIndexExcl); + SwitchBranch branch = new SwitchBranch(isFallthrough, branchStatements, labelStatement, current); + branches.add(branch); + } + + @Nullable + private static List extractBranches(@NotNull PsiCodeBlock body) { + List branches = new ArrayList<>(); + PsiStatement[] statements = body.getStatements(); + int unmatchedCaseIndex = -1; + for (int i = 0, length = statements.length; i < length; i++) { + PsiStatement current = statements[i]; + if (current instanceof PsiSwitchLabelStatement) { + if (unmatchedCaseIndex != -1) { + boolean isFallthrough = i != 0 && ControlFlowUtils.statementMayCompleteNormally(statements[i - 1]); + addBranch(branches, statements, unmatchedCaseIndex, i, isFallthrough, null); + } + unmatchedCaseIndex = i; + } + else if (current instanceof PsiBreakStatement) { + if (unmatchedCaseIndex == -1) return null; + addBranch(branches, statements, unmatchedCaseIndex, i, false, (PsiBreakStatement)current); + unmatchedCaseIndex = -1; + } + else if (current instanceof PsiSwitchLabeledRuleStatement) { + return null; + } + } + // tail + if (unmatchedCaseIndex != -1) { + addBranch(branches, statements, unmatchedCaseIndex, statements.length, false, null); + } + return branches; + } + + + @Nullable + private static PsiLocalVariable getVariable(PsiDeclarationStatement declaration) { + if (declaration == null) return null; + PsiElement[] declaredElements = declaration.getDeclaredElements(); + if (declaredElements.length != 1) return null; + PsiLocalVariable variable = tryCast(declaredElements[0], PsiLocalVariable.class); + if (variable == null) return null; + return variable; + } + + @Nullable + private static SwitchReplacer getSwitchReplacer(PsiSwitchStatement switchStatement) { + PsiExpression expression = switchStatement.getExpression(); + if (expression == null) return null; + PsiCodeBlock body = switchStatement.getBody(); + if (body == null) return null; + List branches = extractBranches(body); + if (branches == null) return null; + boolean isExhaustive = isExhaustiveSwitch(branches, expression); + for (SwitchInspection inspection : ourExpressionInspections) { + SwitchReplacer replacer = inspection.suggestReplacer(switchStatement, branches, isExhaustive); + if (replacer != null) return replacer; + } + return null; + } + + @NotNull + private static PsiSwitchBlock generateEnhancedSwitch(@NotNull PsiSwitchStatement switchStatement, + PsiExpression expressionBeingSwitched, + List newBranches, + CommentTracker mainCommentTracker, + boolean isExpr) { + PsiElementFactory factory = JavaPsiFacade.getInstance(switchStatement.getProject()).getElementFactory(); + mainCommentTracker.markUnchanged(expressionBeingSwitched); + PsiCodeBlock body = switchStatement.getBody(); + assert body != null; + for (PsiStatement statement : body.getStatements()) { + mainCommentTracker.markUnchanged(statement); + } + StringBuilder sb = new StringBuilder() + .append("switch(") + .append(expressionBeingSwitched.getText()) + .append("){\n"); + int size = newBranches.size(); + List branchTrackers = new ArrayList<>(newBranches.size()); + for (int i = 0; i < size; i++) { + SwitchExpressionBranch newBranch = newBranches.get(i); + CommentTracker ct = new CommentTracker(); + branchTrackers.add(ct); + sb.append(newBranch.generate(ct)); + if (i != size - 1) { + sb.append("\n"); + } + } + sb.append("\n}"); + PsiSwitchBlock switchBlock; + if (isExpr) { + switchBlock = (PsiSwitchBlock)factory.createExpressionFromText(sb.toString(), switchStatement); + } else { + switchBlock = (PsiSwitchBlock)factory.createStatementFromText(sb.toString(), switchStatement); + } + PsiCodeBlock resultBody = switchBlock.getBody(); + PsiSwitchLabeledRuleStatement[] labeledStatements = PsiTreeUtil.getChildrenOfType(resultBody, PsiSwitchLabeledRuleStatement.class); + if (labeledStatements == null || labeledStatements.length != size) return switchBlock; + // restore comments near every branch + for (int i = 0; i < labeledStatements.length; i++) { + PsiSwitchLabeledRuleStatement labeledStatement = labeledStatements[i]; + branchTrackers.get(i).insertCommentsBefore(labeledStatement); + } + return switchBlock; + } + + private static boolean isExhaustiveSwitch(List branches, PsiExpression expressionBeingSwitched) { + for (SwitchBranch branch : branches) { + if (branch.isDefault()) return true; + } + final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(expressionBeingSwitched.getType()); + if (aClass == null || !aClass.isEnum()) return false; + Set names = StreamEx.of(aClass.getAllFields()) + .select(PsiEnumConstant.class) + .map(PsiEnumConstant::getName) + .toSet(); + for (SwitchBranch branch : branches) { + PsiExpressionList caseValues = branch.myLabelStatement.getCaseValues(); + if (caseValues != null) { + for (PsiExpression caseLabel : caseValues.getExpressions()) { + PsiReferenceExpression reference = tryCast(caseLabel, PsiReferenceExpression.class); + if (reference != null) { + PsiEnumConstant enumConstant = tryCast(reference.resolve(), PsiEnumConstant.class); + if (enumConstant != null) { + names.remove(enumConstant.getName()); + } + } + } + } + } + return names.isEmpty(); + } + + private enum ReplacementType { + Expression("inspection.replace.with.switch.expression.fix.name"), + Statement("inspection.replace.with.enhanced.switch.statement.fix.name"); + + private final String key; + + ReplacementType(String key) { + this.key = key; + } + + String getFixName() { + return InspectionsBundle.message(key); + } + } + + private interface SwitchReplacer { + void replace(@NotNull PsiSwitchStatement switchStatement); + + ReplacementType getType(); + } + + private interface SwitchInspection { + @Nullable + SwitchReplacer suggestReplacer(@NotNull PsiSwitchStatement switchStmt, @NotNull List branches, boolean isExhaustive); + } + + //Right part of switch rule (case labels -> result) + private interface SwitchRuleResult { + String generate(CommentTracker ct); + } + + static class ReplaceWithSwitchExpressionFix implements LocalQuickFix { + private final ReplacementType myReplacementType; + + ReplaceWithSwitchExpressionFix(ReplacementType replacementType) {myReplacementType = replacementType;} + + @Nls(capitalization = Nls.Capitalization.Sentence) + @NotNull + @Override + public String getFamilyName() { + return myReplacementType.getFixName(); + } + + @Nls(capitalization = Nls.Capitalization.Sentence) + @NotNull + @Override + public String getName() { + return getFamilyName(); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiSwitchStatement switchStatement = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiSwitchStatement.class); + if (switchStatement == null) return; + SwitchReplacer replacer = getSwitchReplacer(switchStatement); + if (replacer == null) return; + replacer.replace(switchStatement); + } + } + + private static class ReturningSwitchReplacer implements SwitchReplacer { + @NotNull final PsiSwitchStatement mySwitchStatement; + @NotNull final PsiExpression myExpressionBeingSwitched; + final List myNewBranches; + final @Nullable PsiReturnStatement myReturnToDelete; + + private ReturningSwitchReplacer(@NotNull PsiSwitchStatement switchStatement, + @NotNull PsiExpression expressionBeingSwitched, + List newBranches, @Nullable PsiReturnStatement returnToDelete) { + mySwitchStatement = switchStatement; + myExpressionBeingSwitched = expressionBeingSwitched; + myNewBranches = newBranches; + myReturnToDelete = returnToDelete; + } + + @Override + public void replace(@NotNull PsiSwitchStatement switchStatement) { + CommentTracker commentTracker = new CommentTracker(); + PsiSwitchBlock replacement = generateEnhancedSwitch(switchStatement, myExpressionBeingSwitched, myNewBranches, commentTracker, true); + commentTracker.replaceAndRestoreComments(switchStatement, replacement); + if (myReturnToDelete != null) { + CommentTracker ct = new CommentTracker(); + PsiExpression returnValue = myReturnToDelete.getReturnValue(); + if (returnValue != null) { + ct.markUnchanged(returnValue); + } + ct.deleteAndRestoreComments(myReturnToDelete); + } + } + + @Override + public ReplacementType getType() { + return ReplacementType.Expression; + } + } + + /** + * switch (n) { + * case 1: + * return "a"; + * case 2: + * return "b"; + * default: + * return "?"; + * } + */ + private static class ReturningSwitch implements SwitchInspection { + @Nullable + @Override + public SwitchReplacer suggestReplacer(@NotNull PsiSwitchStatement switchStmt, + @NotNull List branches, + boolean isExhaustive) { + PsiExpression expression = switchStmt.getExpression(); + if (expression == null) return null; + PsiReturnStatement returnAfterSwitch = + tryCast(PsiTreeUtil.getNextSiblingOfType(switchStmt, PsiStatement.class), PsiReturnStatement.class); + if (returnAfterSwitch == null && !isExhaustive) return null; + List newBranches = new ArrayList<>(); + for (SwitchBranch branch : branches) { + PsiStatement[] statements = branch.getStatements(); + if (statements.length != 1) return null; + PsiReturnStatement returnStmt = tryCast(statements[0], PsiReturnStatement.class); + if (returnStmt == null) return null; + PsiExpression returnExpr = returnStmt.getReturnValue(); + if (returnExpr == null) return null; + newBranches.add(new SwitchExpressionBranch(branch.isDefault(), branch.getCaseExpressions(), new SwitchRuleExpressionResult(returnExpr), branch)); + } + if (!isExhaustive) { + PsiExpression returnExpr = returnAfterSwitch.getReturnValue(); + if (returnExpr == null) return null; + newBranches.add(new SwitchExpressionBranch(true, PsiExpression.EMPTY_ARRAY, new SwitchRuleExpressionResult(returnExpr), null)); + } + return new ReturningSwitchReplacer(switchStmt, expression, newBranches, returnAfterSwitch); + } + } + + private static class SwitchExistingVariableReplacer implements SwitchReplacer { + @NotNull final PsiVariable myVariableToAssign; + @NotNull final PsiSwitchStatement mySwitchStatement; + @NotNull final PsiExpression myExpressionBeingSwitched; + final List myNewBranches; + + private SwitchExistingVariableReplacer( + @NotNull PsiVariable variableToAssign, + @NotNull PsiSwitchStatement switchStatement, + @NotNull PsiExpression expressionBeingSwitched, + List newBranches + ) { + myVariableToAssign = variableToAssign; + mySwitchStatement = switchStatement; + myExpressionBeingSwitched = expressionBeingSwitched; + myNewBranches = newBranches; + } + + @Override + public void replace(@NotNull PsiSwitchStatement switchStatement) { + CommentTracker commentTracker = new CommentTracker(); + PsiSwitchBlock replacement = generateEnhancedSwitch(switchStatement, myExpressionBeingSwitched, myNewBranches, commentTracker, true); + myVariableToAssign.setInitializer((PsiSwitchExpression)replacement); + commentTracker.delete(switchStatement); + commentTracker.insertCommentsBefore(myVariableToAssign); + } + + @Override + public ReplacementType getType() { + return ReplacementType.Expression; + } + } + + /** + * int result; + * switch(s) { + * case "a": result = 1; break; + * case "b": result = 2; break; + * default: result = 0; + * } + */ + private static class VariableAssigningSwitch implements SwitchInspection { + @Nullable + @Override + public SwitchReplacer suggestReplacer(@NotNull PsiSwitchStatement switchStmt, + @NotNull List branches, + boolean isExhaustive) { + PsiExpression expressionBeingSwitched = switchStmt.getExpression(); + if (expressionBeingSwitched == null) return null; + PsiDeclarationStatement declaration = + tryCast(PsiTreeUtil.getPrevSiblingOfType(switchStmt, PsiStatement.class), PsiDeclarationStatement.class); + PsiLocalVariable variable = getVariable(declaration); + if (variable == null) return null; + List newBranches = new ArrayList<>(); + PsiExpression initializer = variable.getInitializer(); + if (!isExhaustive && initializer == null) return null; + for (SwitchBranch branch : branches) { + if (branch.myIsFallthrough) { + return null; + } + PsiStatement[] statements = branch.getStatements(); + if (statements.length != 1) return null; + PsiExpressionStatement expressionStatement = tryCast(statements[0], PsiExpressionStatement.class); + if (expressionStatement == null) return null; + PsiAssignmentExpression assign = tryCast(expressionStatement.getExpression(), PsiAssignmentExpression.class); + if (assign == null) return null; + if (!ExpressionUtils.isReferenceTo(assign.getLExpression(), variable)) return null; + PsiExpression rExpression = assign.getRExpression(); + if (rExpression == null) return null; + newBranches.add(new SwitchExpressionBranch(branch.isDefault(), branch.getCaseExpressions(), new SwitchRuleExpressionResult(rExpression), branch)); + } + if (!isExhaustive) { + newBranches.add(new SwitchExpressionBranch(true, PsiExpression.EMPTY_ARRAY, new SwitchRuleExpressionResult(initializer), null)); + } + return new SwitchExistingVariableReplacer(variable, switchStmt, expressionBeingSwitched, newBranches); + } + } + + /** + * Replaces with enhanced switch statement + */ + private static class SwitchStatementReplacer implements SwitchReplacer { + @NotNull final PsiSwitchStatement mySwitchStatement; + @NotNull final PsiExpression myExpressionBeingSwitched; + @NotNull final List myExpressionBranches; + + private SwitchStatementReplacer(@NotNull PsiSwitchStatement switchStatement, + @NotNull PsiExpression expressionBeingSwitched, + @NotNull List ruleResults) { + mySwitchStatement = switchStatement; + myExpressionBeingSwitched = expressionBeingSwitched; + myExpressionBranches = ruleResults; + } + + @Override + public void replace(@NotNull PsiSwitchStatement switchStatement) { + CommentTracker commentTracker = new CommentTracker(); + PsiSwitchBlock switchBlock = generateEnhancedSwitch(switchStatement, myExpressionBeingSwitched, myExpressionBranches, commentTracker, false); + commentTracker.replace(switchStatement, switchBlock); + } + + @Override + public ReplacementType getType() { + return ReplacementType.Statement; + } + } + + /** + * Suggest replacement with enhanced switch statement + */ + private static class StatementSwitch implements SwitchInspection { + @Nullable + @Override + public SwitchReplacer suggestReplacer(@NotNull PsiSwitchStatement switchStmt, + @NotNull List branches, + boolean isExhaustive) { + PsiExpression expression = switchStmt.getExpression(); + if (expression == null) return null; + for (SwitchBranch branch : branches) { + if (branch.myIsFallthrough) return null; + } + List switchRules = new ArrayList<>(); + for (SwitchBranch branch : branches) { + PsiStatement[] statements = branch.getStatements(); + switchRules.add(new SwitchExpressionBranch(branch.isDefault(), branch.getCaseExpressions(), new SwitchStatementBranch(statements), branch)); + } + return new SwitchStatementReplacer(switchStmt, expression, switchRules); + } + } + + private static class SwitchStatementBranch implements SwitchRuleResult { + final PsiStatement[] myResultStatements; + + private SwitchStatementBranch(PsiStatement[] resultStatements) { + myResultStatements = resultStatements; + } + + @Override + public String generate(CommentTracker ct) { + return StreamEx.of(myResultStatements).map(stmt -> ct.text(stmt)).joining("\n", "{\n", "}"); + } + } + + private static class SwitchRuleExpressionResult implements SwitchRuleResult { + private final PsiExpression myExpression; + + private SwitchRuleExpressionResult(PsiExpression expression) {myExpression = expression;} + + @Override + public String generate(CommentTracker ct) { + return ct.text(myExpression); + } + } + + private static class SwitchExpressionBranch { + final boolean myIsDefault; + final PsiExpression[] myLabelExpressions; + private final SwitchRuleResult myRuleResult; + final @Nullable SwitchBranch mySourceBranch; + + private SwitchExpressionBranch(boolean isDefault, + PsiExpression[] labelExpressions, + SwitchRuleResult ruleResult, + @Nullable SwitchBranch sourceBranch) { + myIsDefault = isDefault; + myLabelExpressions = labelExpressions; + myRuleResult = ruleResult; + mySourceBranch = sourceBranch; + } + + String generate(CommentTracker ct) { + StringBuilder sb = new StringBuilder(); + if (myIsDefault) { + sb.append("default"); + } + else { + sb.append("case "); + int length = myLabelExpressions.length; + for (int i = 0; i < length; i++) { + PsiExpression labelExpression = myLabelExpressions[i]; + sb.append(ct.text(labelExpression)); + if (i != length - 1) { + sb.append(","); + } + } + } + sb.append("->"); + sb.append(myRuleResult.generate(ct)); + sb.append(";"); + if (mySourceBranch != null) { + for (PsiStatement relatedStatement : mySourceBranch.getRelatedStatements()) { + ct.grabComments(relatedStatement); + } + } + return sb.toString(); + } + } + + private static class SwitchBranch { + final boolean myIsFallthrough; + final @NotNull PsiStatement[] myStatements; + final @NotNull PsiSwitchLabelStatement myLabelStatement; + final @Nullable PsiBreakStatement myBreakStatement; + + private SwitchBranch(boolean isFallthrough, + @NotNull PsiStatement[] statements, + @NotNull PsiSwitchLabelStatement switchLabelStatement, + @Nullable PsiBreakStatement breakStatement) { + myIsFallthrough = isFallthrough; + myStatements = statements; + myLabelStatement = switchLabelStatement; + myBreakStatement = breakStatement; + } + + boolean isDefault() { + return myLabelStatement.isDefaultCase(); + } + + PsiExpression[] getCaseExpressions() { + PsiExpressionList caseValues = myLabelStatement.getCaseValues(); + if (caseValues == null) return PsiExpression.EMPTY_ARRAY; + return caseValues.getExpressions(); + } + + /** + * @return only meaningful statements, without break and case statements + */ + PsiStatement[] getStatements() { + return myStatements; + } + + StreamEx getRelatedStatements() { + StreamEx withoutBreak = StreamEx.of(myStatements).prepend(myLabelStatement); + return withoutBreak.prepend(myBreakStatement); + } + } +} diff --git a/java/java-impl/src/inspectionDescriptions/EnhancedSwitchMigration.html b/java/java-impl/src/inspectionDescriptions/EnhancedSwitchMigration.html new file mode 100644 index 000000000000..7660000d1e78 --- /dev/null +++ b/java/java-impl/src/inspectionDescriptions/EnhancedSwitchMigration.html @@ -0,0 +1,5 @@ + + +Reports 'switch' statement, which can be replaced with enhanced 'switch' statement or expression + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchStatements.java b/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchStatements.java new file mode 100644 index 000000000000..69d4f2fd2054 --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchStatements.java @@ -0,0 +1,16 @@ +// "Replace with enhanced 'switch' statement" "true" +import java.util.*; + +class SwitchExpressionMigration { + private static String m(int n) { + switch (n) { + /*1*/ + case 1 -> { + System.out.println("a"/*2*/); /*3*/ + }; + case 2 -> { + System.out.println("b"); + }; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchStatements2.java b/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchStatements2.java new file mode 100644 index 000000000000..7beb161d5311 --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchStatements2.java @@ -0,0 +1,15 @@ +// "Replace with enhanced 'switch' statement" "true" +import java.util.*; + +class SwitchExpressionMigration { + private static String m(int n) { + switch (n) { + case 1 -> { + return "a"; + }; + case 2 -> { + return "b"; + }; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithReturn.java b/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithReturn.java new file mode 100644 index 000000000000..bf7e7c202214 --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithReturn.java @@ -0,0 +1,12 @@ +// "Replace with 'switch' expression" "true" +import java.util.*; + +class SwitchExpressionMigration { + private static String m(int n) { + switch (n) { + case 1 -> "a"; + case 2 -> "b"; + default -> "?"; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithReturnComments.java b/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithReturnComments.java new file mode 100644 index 000000000000..86ed3bef99e6 --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithReturnComments.java @@ -0,0 +1,20 @@ +// "Replace with 'switch' expression" "true" +import java.util.*; + +class SwitchExpressionMigration { + private static String m(int n) { + /*1*/ + /*2*/ + /*4*/ + /*5*/ + /*6*/ + switch (n +/*3*/ n) { + /*7*/ + /*8*/ + /*9*/ + case 1 -> "a"; + case 2 -> "b"; + default -> "?"/*10*/ + "!"; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithReturnDefault.java b/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithReturnDefault.java new file mode 100644 index 000000000000..ac9d663024a0 --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithReturnDefault.java @@ -0,0 +1,12 @@ +// "Replace with 'switch' expression" "true" +import java.util.*; + +class SwitchExpressionMigration { + private static String m(int n) { + switch (n) { + case 1 -> "a"; + case 2 -> "b"; + default -> "c"; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithVarComments.java b/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithVarComments.java new file mode 100644 index 000000000000..83ac3ba4cabe --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithVarComments.java @@ -0,0 +1,19 @@ +// "Replace with 'switch' expression" "true" +import java.util.*; + +class SwitchExpressionMigration { + private static void m(String s) { + /*3*//*4*//*6*//*7*/int result/*1*//*2*/ = switch (s /*5*/ + s) { + /*12*/ + /*8*/ + /*9*/ + /*10*/ + /*11*/ + case "a" -> 1; + case "b" -> 2 + /*13*/ 3; + /*14*/ + /*16*/ + default -> 0 +/*15*/ 1; + }; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithVariableAndDefault.java b/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithVariableAndDefault.java new file mode 100644 index 000000000000..139a196aac95 --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithVariableAndDefault.java @@ -0,0 +1,12 @@ +// "Replace with 'switch' expression" "true" +import java.util.*; + +class SwitchExpressionMigration { + private static void m() { + int result = switch (s) { + case "a" -> 1; + case "b" -> 2; + default -> 0; + }; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithVariableInitializedNonExhaustive.java b/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithVariableInitializedNonExhaustive.java new file mode 100644 index 000000000000..139a196aac95 --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/afterSwitchWithVariableInitializedNonExhaustive.java @@ -0,0 +1,12 @@ +// "Replace with 'switch' expression" "true" +import java.util.*; + +class SwitchExpressionMigration { + private static void m() { + int result = switch (s) { + case "a" -> 1; + case "b" -> 2; + default -> 0; + }; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchStatements.java b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchStatements.java new file mode 100644 index 000000000000..b5336893cfe1 --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchStatements.java @@ -0,0 +1,15 @@ +// "Replace with enhanced 'switch' statement" "true" +import java.util.*; + +class SwitchExpressionMigration { + private static String m(int n) { + switch (n) { + case 1:/*1*/ + System.out.println("a"/*2*/); /*3*/ + break; + case 2: + System.out.println("b"); + break; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchStatements2.java b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchStatements2.java new file mode 100644 index 000000000000..c5486312a415 --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchStatements2.java @@ -0,0 +1,13 @@ +// "Replace with enhanced 'switch' statement" "true" +import java.util.*; + +class SwitchExpressionMigration { + private static String m(int n) { + switch (n) { + case 1: + return "a"; + case 2: + return "b"; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchStatementsNoBreak.java b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchStatementsNoBreak.java new file mode 100644 index 000000000000..3271dd71b3ed --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchStatementsNoBreak.java @@ -0,0 +1,13 @@ +// "Replace with enhanced 'switch' statement" "false" +import java.util.*; + +class SwitchExpressionMigration { + private static String m(int n) { + switch (n) { + case 1: + System.out.println("a"); + case 2: + System.out.println("b"); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithReturn.java b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithReturn.java new file mode 100644 index 000000000000..d32a2e6db06c --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithReturn.java @@ -0,0 +1,14 @@ +// "Replace with 'switch' expression" "true" +import java.util.*; + +class SwitchExpressionMigration { + private static String m(int n) { + switch (n) { + case 1: + return "a"; + case 2: + return "b"; + } + return "?"; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithReturnComments.java b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithReturnComments.java new file mode 100644 index 000000000000..9d820d69f4d9 --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithReturnComments.java @@ -0,0 +1,14 @@ +// "Replace with 'switch' expression" "true" +import java.util.*; + +class SwitchExpressionMigration { + private static String m(int n) { + switch/*1*/ (/*2*/n +/*3*/ n) /*4*/{/*5*/ + /*6*/case /*7*/1:/*8*/ + return "a"/*9*/; + case 2: + return "b"; + } + return "?"/*10*/ + "!"; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithReturnDefault.java b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithReturnDefault.java new file mode 100644 index 000000000000..1380bd85ddd8 --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithReturnDefault.java @@ -0,0 +1,15 @@ +// "Replace with 'switch' expression" "true" +import java.util.*; + +class SwitchExpressionMigration { + private static String m(int n) { + switch (n) { + case 1: + return "a"; + case 2: + return "b"; + default: + return "c" + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithVarComments.java b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithVarComments.java new file mode 100644 index 000000000000..6c282d091a5f --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithVarComments.java @@ -0,0 +1,13 @@ +// "Replace with 'switch' expression" "true" +import java.util.*; + +class SwitchExpressionMigration { + private static void m(String s) { + int result/*1*//*2*/; + switch/*3*/(/*4*/s /*5*/ + s) /*6*/ {/*7*/ + case /*8*/"a"/*9*/: /*10*/result = 1;/*11*/ break/*12*/; + case "b": result = 2 + /*13*/ 3; break; + default/*14*/: result = 0 +/*15*/ 1;/*16*/ + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithVariableAndDefault.java b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithVariableAndDefault.java new file mode 100644 index 000000000000..b011bb0c36a7 --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithVariableAndDefault.java @@ -0,0 +1,13 @@ +// "Replace with 'switch' expression" "true" +import java.util.*; + +class SwitchExpressionMigration { + private static void m() { + int result; + switch(s) { + case "a": result = 1; break; + case "b": result = 2; break; + default: result = 0; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithVariableInitializedNonExhaustive.java b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithVariableInitializedNonExhaustive.java new file mode 100644 index 000000000000..c07dc395315a --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithVariableInitializedNonExhaustive.java @@ -0,0 +1,12 @@ +// "Replace with 'switch' expression" "true" +import java.util.*; + +class SwitchExpressionMigration { + private static void m() { + int result = 0; + switch(s) { + case "a": result = 1; break; + case "b": result = 2; break; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithVariableNonExhaustive.java b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithVariableNonExhaustive.java new file mode 100644 index 000000000000..f47e229023dd --- /dev/null +++ b/java/java-tests/testData/inspection/switchExpressionMigration/beforeSwitchWithVariableNonExhaustive.java @@ -0,0 +1,12 @@ +// "Replace with 'switch' expression" "false" +import java.util.*; + +class SwitchExpressionMigration { + private static void m() { + int result; + switch(s) { + case "a": result = 1; break; + case "b": result = 2; break; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/EnhancedSwitchMigrationInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/EnhancedSwitchMigrationInspectionTest.java new file mode 100644 index 000000000000..b12f9ab624b9 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/EnhancedSwitchMigrationInspectionTest.java @@ -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.LocalInspectionTool; +import com.intellij.codeInspection.EnhancedSwitchMigrationInspection; +import com.intellij.pom.java.LanguageLevel; +import org.jetbrains.annotations.NotNull; + +public class EnhancedSwitchMigrationInspectionTest extends LightQuickFixParameterizedTestCase { + @NotNull + @Override + protected LocalInspectionTool[] configureLocalInspectionTools() { + return new LocalInspectionTool[]{ + new EnhancedSwitchMigrationInspection() + }; + } + + @Override + protected String getBasePath() { + return "/inspection/switchExpressionMigration/"; + } + + @Override + protected LanguageLevel getLanguageLevel() { + return LanguageLevel.JDK_12_PREVIEW; + } +} diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index e362b57fe71a..162122725133 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -1023,6 +1023,10 @@ inspection.join.declaration.and.assignment.fix.family.name=Join declaration and inspection.overflowing.loop.index.inspection.name=Loop executes zero or billions times inspection.overflowing.loop.index.inspection.description=Loop executes zero or billions times +inspection.switch.expression.migration.inspection.name=Switch statement can be replaced with enhanced 'switch' +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.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