[java-highlighting] IDEA-315469 Quickfixes for switch labels with 'default'

- fix to reverse 'case default, null'
- fix to replace 'case default' with 'default'

GitOrigin-RevId: 3762a40c859bb7a13a638614b3200b0005d8e85a
This commit is contained in:
Mikhail Pyltsin
2023-10-31 17:31:21 +01:00
committed by intellij-monorepo-bot
parent 17affe2b0d
commit 3d7a18e986
21 changed files with 355 additions and 17 deletions

View File

@@ -630,6 +630,13 @@ public abstract class QuickFixFactory {
@NotNull
public abstract IntentionAction createDeleteFix(@NotNull PsiElement @NotNull [] elements, @NotNull @Nls String text);
@NotNull
public abstract IntentionAction createReplaceCaseDefaultWithDefaultFix(@NotNull PsiCaseLabelElementList list);
@NotNull
public abstract IntentionAction createReverseCaseDefaultNullFixFix(@NotNull PsiCaseLabelElementList list);
@ApiStatus.Experimental
@NotNull
public abstract IntentionAction createAddMainMethodFix(@NotNull PsiUnnamedClass unnamedClass);

View File

@@ -2415,6 +2415,8 @@ mark.modules.as.loaded.together.fix.family.name=Mark modules as loaded together
mark.modules.as.loaded.together.fix.text=Mark ''{0}'' and ''{1}'' modules as loaded together
inspection.suspicious.package.private.access.description={0} is {1}, but declared in a different module ''{2}''
ignore.class.fix.family.name=Ignore for these types
replace.case.default.with.default=Replace 'case default' with 'default'
replace.case.default.null.with.null.default=Replace 'case default, null' with 'case null, default'
create.default.branch.fix.family.name=Insert 'default' branch
create.null.branch.fix.family.name=Insert 'null' branch
create.missing.enum.switch.branches.fix.family.name=Create missing enum switch branches

View File

@@ -245,6 +245,8 @@ public class SwitchBlockHighlightingModel {
// if default is not the only case in the label, insufficient language level will be reported
// see HighlightVisitorImpl#visitDefaultCaseLabelElement
HighlightInfo.Builder info = createError(defaultElement, JavaErrorBundle.message("default.label.must.not.contains.case.keyword"));
IntentionAction fix = getFixFactory().createReplaceCaseDefaultWithDefaultFix(labelElementList);
info.registerFix(fix, null, null, null, null);
errorSink.accept(info);
reported = true;
}
@@ -992,11 +994,11 @@ public class SwitchBlockHighlightingModel {
for (PsiSwitchLabelStatementBase switchLabelElement : switchLabel) {
PsiCaseLabelElementList labelElementList = switchLabelElement.getCaseLabelElementList();
if (labelElementList == null || labelElementList.getElementCount() == 0) continue;
CaseLabelCombinationProblem problem = checkCaseLabelCombination(labelElementList);
PsiCaseLabelElement[] elements = labelElementList.getElements();
final PsiCaseLabelElement first = elements[0];
CaseLabelCombinationProblem problem = checkCaseLabelCombination(elements);
if (problem != null) {
HighlightInfo.Builder info = addIllegalFallThroughError(problem.element(), problem.message(), alreadyFallThroughElements);
HighlightInfo.Builder info = addIllegalFallThroughError(problem.element(), problem.message(), problem.customAction(), alreadyFallThroughElements);
errorSink.accept(info);
reported = true;
}
@@ -1010,7 +1012,7 @@ public class SwitchBlockHighlightingModel {
}
if (PsiTreeUtil.skipWhitespacesAndCommentsForward(switchLabelElement) instanceof PsiSwitchLabelStatement ||
PsiTreeUtil.skipWhitespacesAndCommentsBackward(switchLabelElement) instanceof PsiSwitchLabelStatement) {
HighlightInfo.Builder info = addIllegalFallThroughError(first, "multiple.switch.labels", alreadyFallThroughElements);
HighlightInfo.Builder info = addIllegalFallThroughError(first, "multiple.switch.labels", null, alreadyFallThroughElements);
errorSink.accept(info);
reported = true;
}
@@ -1022,14 +1024,17 @@ public class SwitchBlockHighlightingModel {
}
private record CaseLabelCombinationProblem(@NotNull PsiCaseLabelElement element,
@NotNull @PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String message) {
@NotNull @PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String message,
@Nullable IntentionAction customAction) {
}
private static @Nullable CaseLabelCombinationProblem checkCaseLabelCombination(PsiCaseLabelElement[] elements) {
private static @Nullable CaseLabelCombinationProblem checkCaseLabelCombination(PsiCaseLabelElementList labelElementList) {
PsiCaseLabelElement[] elements = labelElementList.getElements();
PsiCaseLabelElement firstElement = elements[0];
if (elements.length == 1) {
if (firstElement instanceof PsiDefaultCaseLabelElement) {
return new CaseLabelCombinationProblem(firstElement, "default.label.must.not.contains.case.keyword");
IntentionAction fix = SwitchBlockHighlightingModel.getFixFactory().createReplaceCaseDefaultWithDefaultFix(labelElementList);
return new CaseLabelCombinationProblem(firstElement, "default.label.must.not.contains.case.keyword", fix);
}
return null;
}
@@ -1037,7 +1042,8 @@ public class SwitchBlockHighlightingModel {
if (firstElement instanceof PsiDefaultCaseLabelElement &&
elements[1] instanceof PsiExpression expr &&
ExpressionUtils.isNullLiteral(expr)) {
return new CaseLabelCombinationProblem(firstElement, "invalid.default.and.null.order");
IntentionAction fix = SwitchBlockHighlightingModel.getFixFactory().createReverseCaseDefaultNullFixFix(labelElementList);
return new CaseLabelCombinationProblem(firstElement, "invalid.default.and.null.order", fix);
}
if (firstElement instanceof PsiExpression expr &&
ExpressionUtils.isNullLiteral(expr) &&
@@ -1065,10 +1071,10 @@ public class SwitchBlockHighlightingModel {
}
if (defaultIndex != -1) {
return new CaseLabelCombinationProblem(elements[defaultIndex], "default.label.not.allowed.here");
return new CaseLabelCombinationProblem(elements[defaultIndex], "default.label.not.allowed.here", null);
}
if (nullIndex != -1) {
return new CaseLabelCombinationProblem(elements[nullIndex], "null.label.not.allowed.here");
return new CaseLabelCombinationProblem(elements[nullIndex], "null.label.not.allowed.here", null);
}
if (firstElement instanceof PsiExpression && patternIndex != -1) {
return getPatternConstantCombinationProblem(elements[patternIndex]);
@@ -1081,10 +1087,10 @@ public class SwitchBlockHighlightingModel {
if (HighlightingFeature.UNNAMED_PATTERNS_AND_VARIABLES.isAvailable(firstElement)) {
PsiCaseLabelElement patternVarElement = ContainerUtil.find(elements, JavaPsiPatternUtil::containsNamedPatternVariable);
if (patternVarElement != null) {
return new CaseLabelCombinationProblem(patternVarElement, "invalid.case.label.combination.several.patterns.unnamed");
return new CaseLabelCombinationProblem(patternVarElement, "invalid.case.label.combination.several.patterns.unnamed", null);
}
} else {
return new CaseLabelCombinationProblem(elements[1], "invalid.case.label.combination.several.patterns");
return new CaseLabelCombinationProblem(elements[1], "invalid.case.label.combination.several.patterns", null);
}
}
return null;
@@ -1093,20 +1099,23 @@ public class SwitchBlockHighlightingModel {
@NotNull
private static CaseLabelCombinationProblem getPatternConstantCombinationProblem(PsiCaseLabelElement anchor) {
if (HighlightingFeature.UNNAMED_PATTERNS_AND_VARIABLES.isAvailable(anchor)) {
return new CaseLabelCombinationProblem(anchor, "invalid.case.label.combination.constants.and.patterns.unnamed");
return new CaseLabelCombinationProblem(anchor, "invalid.case.label.combination.constants.and.patterns.unnamed", null);
} else {
return new CaseLabelCombinationProblem(anchor, "invalid.case.label.combination.constants.and.patterns");
return new CaseLabelCombinationProblem(anchor, "invalid.case.label.combination.constants.and.patterns", null);
}
}
@NotNull
private static HighlightInfo.Builder addIllegalFallThroughError(@NotNull PsiElement element,
@NotNull @PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String key,
@NotNull Set<? super PsiElement> alreadyFallThroughElements) {
private static HighlightInfo.@NotNull Builder addIllegalFallThroughError(@NotNull PsiElement element,
@NotNull @PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String key,
@Nullable IntentionAction customAction,
@NotNull Set<? super PsiElement> alreadyFallThroughElements) {
alreadyFallThroughElements.add(element);
HighlightInfo.Builder info = createError(element, JavaErrorBundle.message(key));
IntentionAction action = getFixFactory().createSplitSwitchBranchWithSeveralCaseValuesAction();
info.registerFix(action, null, null, null, null);
if (customAction != null) {
info.registerFix(customAction, null, null, null, null);
}
return info;
}

View File

@@ -1217,6 +1217,16 @@ public final class QuickFixFactoryImpl extends QuickFixFactory {
return new DeleteElementFix.DeleteMultiFix(elements, text).asIntention();
}
@Override
public @NotNull IntentionAction createReplaceCaseDefaultWithDefaultFix(@NotNull PsiCaseLabelElementList list){
return new ReplaceCaseDefaultWithDefaultFix(list).asIntention();
}
@Override
public @NotNull IntentionAction createReverseCaseDefaultNullFixFix(@NotNull PsiCaseLabelElementList list){
return new ReverseCaseDefaultNullFix(list).asIntention();
}
@Override
public @NotNull IntentionAction createAddMainMethodFix(@NotNull PsiUnnamedClass unnamedClass) {
return new AddMainMethodFix(unnamedClass).asIntention();

View File

@@ -0,0 +1,49 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.fixes;
import com.intellij.modcommand.ActionContext;
import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.modcommand.PsiUpdateModCommandAction;
import com.intellij.psi.*;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.psiutils.CommentTracker;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
public final class ReplaceCaseDefaultWithDefaultFix extends PsiUpdateModCommandAction<PsiCaseLabelElementList> {
public ReplaceCaseDefaultWithDefaultFix(@NotNull PsiCaseLabelElementList list) {
super(list);
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getFamilyName() {
return InspectionGadgetsBundle.message("replace.case.default.with.default");
}
@Override
protected void invoke(@NotNull ActionContext context, @NotNull PsiCaseLabelElementList list, @NotNull ModPsiUpdater updater) {
PsiCaseLabelElement[] elements = list.getElements();
if (elements.length != 1 || !(elements[0] instanceof PsiDefaultCaseLabelElement)) {
return;
}
PsiElement parent = list.getParent();
if (!(parent instanceof PsiSwitchLabelStatementBase caseLabels)) {
return;
}
String dummyText;
CommentTracker tracker = new CommentTracker();
if (parent instanceof PsiSwitchLabeledRuleStatement ruleStatement) {
dummyText = "default -> " + (ruleStatement.getBody() != null ? ruleStatement.getBody().getText() : "");
tracker.markUnchanged(ruleStatement.getBody());
}
else {
dummyText = "default:" ;
}
PsiStatement dummy = JavaPsiFacade.getElementFactory(context.project()).createStatementFromText(dummyText, list);
PsiElement replaced = tracker.replace(caseLabels, dummy);
tracker.insertCommentsBefore(replaced);
}
}

View File

@@ -0,0 +1,52 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.fixes;
import com.intellij.modcommand.ActionContext;
import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.modcommand.PsiUpdateModCommandAction;
import com.intellij.psi.*;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
public final class ReverseCaseDefaultNullFix extends PsiUpdateModCommandAction<PsiCaseLabelElementList> {
public ReverseCaseDefaultNullFix(@NotNull PsiCaseLabelElementList list) {
super(list);
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getFamilyName() {
return InspectionGadgetsBundle.message("replace.case.default.null.with.null.default");
}
@Override
protected void invoke(@NotNull ActionContext context, @NotNull PsiCaseLabelElementList list, @NotNull ModPsiUpdater updater) {
PsiCaseLabelElement[] elements = list.getElements();
if (elements.length != 2 ||
!(elements[0] instanceof PsiDefaultCaseLabelElement &&
elements[1] instanceof PsiLiteralExpression literalExpression && ExpressionUtils.isNullLiteral(literalExpression)) ) {
return;
}
PsiElement parent = list.getParent();
if (!(parent instanceof PsiSwitchLabelStatementBase caseLabels)) {
return;
}
String dummyText;
CommentTracker tracker = new CommentTracker();
if (parent instanceof PsiSwitchLabeledRuleStatement ruleStatement) {
dummyText = "case null, default -> " + (ruleStatement.getBody() != null ? ruleStatement.getBody().getText() : "");
tracker.markUnchanged(ruleStatement.getBody());
}
else {
dummyText = "case null, default:" ;
}
PsiStatement dummy = JavaPsiFacade.getElementFactory(context.project()).createStatementFromText(dummyText, list);
PsiElement replaced = tracker.replace(caseLabels, dummy);
tracker.insertCommentsBefore(replaced);
}
}

View File

@@ -0,0 +1,12 @@
// "Replace 'case default' with 'default'" "true-preview"
class Test {
void f() {
Object o = null;
switch (o) {
case String s:
System.out.println("2");
/*some text*/
default:
System.out.println("3") /*some text2*/;
}
}

View File

@@ -0,0 +1,11 @@
// "Replace 'case default' with 'default'" "true-preview"
class Test {
void f() {
Object o = null;
switch (o) {
/*some text*/
default -> {
System.out.println("1") /*some text2*/;
}
} }
}

View File

@@ -0,0 +1,11 @@
// "Replace 'case default' with 'default'" "true-preview"
class Test {
void f() {
Object o = null;
switch (o) {
case String s:
System.out.println("2");
case /*some text*/ def<caret>ault:
System.out.println("3") /*some text2*/;
}
}

View File

@@ -0,0 +1,10 @@
// "Replace 'case default' with 'default'" "true-preview"
class Test {
void f() {
Object o = null;
switch (o) {
case /*some text*/ defau<caret>lt -> {
System.out.println("1") /*some text2*/;
}
} }
}

View File

@@ -0,0 +1,12 @@
// "Replace 'case default' with 'default'" "true-preview"
class Test {
void f() {
int o = 1;
switch (o) {
case 1:
System.out.println("2");
/*some text*/
default:
System.out.println("3") /*some text2*/;
}
}

View File

@@ -0,0 +1,12 @@
// "Replace 'case default' with 'default'" "true-preview"
class Test {
void f() {
Object o = null;
int o1 = 1;
switch (o1) {
case 1 -> System.out.println("2");
/*some text*/
default -> System.out.println("3") /*some text2*/;
}
}
}

View File

@@ -0,0 +1,11 @@
// "Replace 'case default' with 'default'" "true-preview"
class Test {
void f() {
int o = 1;
switch (o) {
case 1:
System.out.println("2");
case /*some text*/ de<caret>fault:
System.out.println("3") /*some text2*/;
}
}

View File

@@ -0,0 +1,11 @@
// "Replace 'case default' with 'default'" "true-preview"
class Test {
void f() {
Object o = null;
int o1 = 1;
switch (o1) {
case 1 -> System.out.println("2");
case /*some text*/ d<caret>efault -> System.out.println("3") /*some text2*/;
}
}
}

View File

@@ -0,0 +1,12 @@
// "Replace 'case default, null' with 'case null, default'" "true-preview"
class Test {
void f() {
Object o = null;
switch (o) {
case String s:
System.out.println("2");
/*some text*/
case null, default:
System.out.println("3") /*some text2*/;
}
}

View File

@@ -0,0 +1,11 @@
// "Replace 'case default, null' with 'case null, default'" "true-preview"
class Test {
void f() {
Object o = null;
switch (o) {
/*some text*/
case null, default -> {
System.out.println("1") /*some text2*/;
}
} }
}

View File

@@ -0,0 +1,11 @@
// "Replace 'case default, null' with 'case null, default'" "true-preview"
class Test {
void f() {
Object o = null;
switch (o) {
case String s:
System.out.println("2");
case /*some text*/ def<caret>ault, null:
System.out.println("3") /*some text2*/;
}
}

View File

@@ -0,0 +1,10 @@
// "Replace 'case default, null' with 'case null, default'" "true-preview"
class Test {
void f() {
Object o = null;
switch (o) {
case /*some text*/ defau<caret>lt, null -> {
System.out.println("1") /*some text2*/;
}
} }
}

View File

@@ -0,0 +1,25 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
public class ReplaceCaseDefaultWithDefault20FixTest extends LightQuickFixParameterizedTestCase {
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/replaceCaseDefaultWithDefault20";
}
@Override
protected LanguageLevel getLanguageLevel() {
return LanguageLevel.JDK_20;
}
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return LightJavaCodeInsightFixtureTestCase.JAVA_20;
}
}

View File

@@ -0,0 +1,25 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
public class ReplaceCaseDefaultWithDefault21FixTest extends LightQuickFixParameterizedTestCase {
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/replaceCaseDefaultWithDefault";
}
@Override
protected LanguageLevel getLanguageLevel() {
return LanguageLevel.JDK_21;
}
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return LightJavaCodeInsightFixtureTestCase.JAVA_21;
}
}

View File

@@ -0,0 +1,25 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
public class ReverseCaseDefaultNullFixTest extends LightQuickFixParameterizedTestCase {
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/reverseCaseDefaultNull";
}
@Override
protected LanguageLevel getLanguageLevel() {
return LanguageLevel.JDK_21;
}
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return LightJavaCodeInsightFixtureTestCase.JAVA_21;
}
}