mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 21:11:28 +07:00
[java-highlighting] Migrate fallthrough-related errors
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: 0a2b7e91a93e8207101368c0fcfb3cf58a793203
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1da6f5d864
commit
6d7ef89b5a
@@ -0,0 +1,22 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.codeserver.core;
|
||||
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiLiteralExpression;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Utilities related to Java expressions
|
||||
*/
|
||||
public final class JavaPsiExpressionUtil {
|
||||
/**
|
||||
* @param expression expression to check
|
||||
* @return true if the expression is a null literal (possibly parenthesized or cast)
|
||||
*/
|
||||
@Contract("null -> false")
|
||||
public static boolean isNullLiteral(@Nullable PsiExpression expression) {
|
||||
return PsiUtil.deparenthesizeExpression(expression) instanceof PsiLiteralExpression literal && literal.getValue() == null;
|
||||
}
|
||||
}
|
||||
@@ -330,6 +330,15 @@ switch.default.label.contains.case=The label for the default case must only use
|
||||
switch.label.duplicate.unconditional.pattern=Duplicate unconditional pattern
|
||||
switch.label.duplicate.default=Duplicate default label
|
||||
switch.label.duplicate=Duplicate label ''{0}''
|
||||
switch.fallthrough.to.pattern=Illegal fall-through to a pattern
|
||||
switch.multiple.labels.with.pattern.variables=Multiple switch labels are permitted for a switch labeled statement group only if none of them declare any pattern variables
|
||||
switch.default.null.order=Invalid case label order: 'null' must be first and 'default' must be second
|
||||
switch.default.label.not.allowed=Default label not allowed here: 'default' can only be used as a single case label or paired only with 'null'
|
||||
switch.null.label.not.allowed=Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'
|
||||
switch.label.combination.constants.and.patterns=Invalid case label combination: a case label must consist of either a list of case constants or a single case pattern
|
||||
switch.label.combination.constants.and.patterns.unnamed=Invalid case label combination: a case label must consist of either a list of case constants or a list of case patterns
|
||||
switch.label.multiple.patterns=Invalid case label combination: a case label must not consist of more than one case pattern
|
||||
switch.label.multiple.patterns.unnamed=Invalid case label combination: multiple patterns are allowed only if none of them declare any pattern variables
|
||||
|
||||
guard.misplaced=Guard is allowed after patterns only
|
||||
guard.evaluated.to.false=Case label has a guard that is a constant expression with value 'false'
|
||||
|
||||
@@ -647,6 +647,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
if (!hasErrorResults()) mySwitchChecker.checkSwitchSelectorType(block);
|
||||
if (!hasErrorResults()) mySwitchChecker.checkLabelSelectorCompatibility(block);
|
||||
if (!hasErrorResults()) mySwitchChecker.checkDuplicates(block);
|
||||
if (!hasErrorResults()) mySwitchChecker.checkFallthroughLegality(block);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.intellij.java.codeserver.highlighting;
|
||||
|
||||
import com.intellij.core.JavaPsiBundle;
|
||||
import com.intellij.java.codeserver.core.JavaPsiExpressionUtil;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaIncompatibleTypeErrorContext;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
@@ -11,12 +12,12 @@ import com.intellij.psi.controlFlow.ControlFlowUtil;
|
||||
import com.intellij.psi.impl.IncompleteModelUtil;
|
||||
import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
final class SwitchChecker {
|
||||
private final @NotNull JavaErrorVisitor myVisitor;
|
||||
@@ -449,4 +450,154 @@ final class SwitchChecker {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkFallthroughLegality(@NotNull PsiSwitchBlock block) {
|
||||
if (!myVisitor.isApplicable(JavaFeature.PATTERNS_IN_SWITCH)) return;
|
||||
PsiCodeBlock body = block.getBody();
|
||||
if (body == null) return;
|
||||
List<List<PsiSwitchLabelStatementBase>> elementsToCheckFallThroughLegality = new SmartList<>();
|
||||
int switchBlockGroupCounter = 0;
|
||||
for (PsiStatement st : body.getStatements()) {
|
||||
if (!(st instanceof PsiSwitchLabelStatementBase labelStatement)) continue;
|
||||
List<PsiSwitchLabelStatementBase> switchLabels;
|
||||
if (switchBlockGroupCounter < elementsToCheckFallThroughLegality.size()) {
|
||||
switchLabels = elementsToCheckFallThroughLegality.get(switchBlockGroupCounter);
|
||||
}
|
||||
else {
|
||||
switchLabels = new SmartList<>();
|
||||
elementsToCheckFallThroughLegality.add(switchLabels);
|
||||
}
|
||||
switchLabels.add(labelStatement);
|
||||
if (!(PsiTreeUtil.skipWhitespacesAndCommentsForward(labelStatement) instanceof PsiSwitchLabelStatement)) {
|
||||
switchBlockGroupCounter++;
|
||||
}
|
||||
}
|
||||
Set<PsiElement> alreadyFallThroughElements = new HashSet<>();
|
||||
checkFallThroughFromPatternWithSeveralLabels(elementsToCheckFallThroughLegality, alreadyFallThroughElements);
|
||||
checkFallThroughToPatternPrecedingCompleteNormally(elementsToCheckFallThroughLegality, alreadyFallThroughElements);
|
||||
}
|
||||
|
||||
private void checkFallThroughFromPatternWithSeveralLabels(@NotNull List<? extends List<PsiSwitchLabelStatementBase>> switchBlockGroup,
|
||||
@NotNull Set<? super PsiElement> alreadyFallThroughElements) {
|
||||
if (switchBlockGroup.isEmpty()) return;
|
||||
for (List<PsiSwitchLabelStatementBase> switchLabel : switchBlockGroup) {
|
||||
for (PsiSwitchLabelStatementBase switchLabelElement : switchLabel) {
|
||||
PsiCaseLabelElementList labelElementList = switchLabelElement.getCaseLabelElementList();
|
||||
if (labelElementList == null || labelElementList.getElementCount() == 0) continue;
|
||||
if (!checkCaseLabelCombination(labelElementList)) {
|
||||
PsiCaseLabelElement[] elements = labelElementList.getElements();
|
||||
final PsiCaseLabelElement first = elements[0];
|
||||
if (JavaPsiPatternUtil.containsNamedPatternVariable(first)) {
|
||||
PsiElement nextNotLabel = PsiTreeUtil.skipSiblingsForward(switchLabelElement, PsiWhiteSpace.class, PsiComment.class,
|
||||
PsiSwitchLabelStatement.class);
|
||||
//there is no statement, it is allowed to go through (14.11.1 JEP 440-441)
|
||||
if (!(nextNotLabel instanceof PsiStatement)) {
|
||||
continue;
|
||||
}
|
||||
if (PsiTreeUtil.skipWhitespacesAndCommentsForward(switchLabelElement) instanceof PsiSwitchLabelStatement ||
|
||||
PsiTreeUtil.skipWhitespacesAndCommentsBackward(switchLabelElement) instanceof PsiSwitchLabelStatement) {
|
||||
alreadyFallThroughElements.add(first);
|
||||
myVisitor.report(JavaErrorKinds.SWITCH_MULTIPLE_LABELS_WITH_PATTERN_VARIABLES.create(first));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkCaseLabelCombination(PsiCaseLabelElementList labelElementList) {
|
||||
PsiCaseLabelElement[] elements = labelElementList.getElements();
|
||||
PsiCaseLabelElement firstElement = elements[0];
|
||||
if (elements.length == 1) {
|
||||
if (firstElement instanceof PsiDefaultCaseLabelElement defaultLabel) {
|
||||
myVisitor.report(JavaErrorKinds.SWITCH_DEFAULT_LABEL_CONTAINS_CASE.create(defaultLabel, labelElementList));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (elements.length == 2) {
|
||||
if (firstElement instanceof PsiDefaultCaseLabelElement defaultLabel &&
|
||||
elements[1] instanceof PsiExpression expr &&
|
||||
JavaPsiExpressionUtil.isNullLiteral(expr)) {
|
||||
myVisitor.report(JavaErrorKinds.SWITCH_DEFAULT_NULL_ORDER.create(defaultLabel, labelElementList));
|
||||
return true;
|
||||
}
|
||||
if (firstElement instanceof PsiExpression expr &&
|
||||
JavaPsiExpressionUtil.isNullLiteral(expr) &&
|
||||
elements[1] instanceof PsiDefaultCaseLabelElement) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasUnnamed = myVisitor.isApplicable(JavaFeature.UNNAMED_PATTERNS_AND_VARIABLES);
|
||||
boolean reported = false;
|
||||
for (PsiCaseLabelElement element : elements) {
|
||||
if (element instanceof PsiDefaultCaseLabelElement defaultLabel) {
|
||||
myVisitor.report(JavaErrorKinds.SWITCH_DEFAULT_LABEL_NOT_ALLOWED.create(defaultLabel));
|
||||
reported = true;
|
||||
}
|
||||
else if (element instanceof PsiExpression expr && JavaPsiExpressionUtil.isNullLiteral(expr)) {
|
||||
myVisitor.report(JavaErrorKinds.SWITCH_NULL_LABEL_NOT_ALLOWED.create(expr));
|
||||
reported = true;
|
||||
}
|
||||
else if (element instanceof PsiPattern pattern && firstElement instanceof PsiExpression) {
|
||||
var kind = hasUnnamed
|
||||
? JavaErrorKinds.SWITCH_LABEL_COMBINATION_CONSTANTS_AND_PATTERNS_UNNAMED
|
||||
: JavaErrorKinds.SWITCH_LABEL_COMBINATION_CONSTANTS_AND_PATTERNS;
|
||||
myVisitor.report(kind.create(pattern));
|
||||
reported = true;
|
||||
}
|
||||
}
|
||||
if (reported) return true;
|
||||
|
||||
if (firstElement instanceof PsiPattern) {
|
||||
PsiCaseLabelElement nonPattern = ContainerUtil.find(elements, e -> !(e instanceof PsiPattern));
|
||||
if (nonPattern != null) {
|
||||
var kind = hasUnnamed
|
||||
? JavaErrorKinds.SWITCH_LABEL_COMBINATION_CONSTANTS_AND_PATTERNS_UNNAMED
|
||||
: JavaErrorKinds.SWITCH_LABEL_COMBINATION_CONSTANTS_AND_PATTERNS;
|
||||
myVisitor.report(kind.create(nonPattern));
|
||||
return true;
|
||||
}
|
||||
if (!hasUnnamed) {
|
||||
myVisitor.report(JavaErrorKinds.SWITCH_LABEL_MULTIPLE_PATTERNS.create(elements[1]));
|
||||
return true;
|
||||
}
|
||||
PsiCaseLabelElement patternVarElement = ContainerUtil.find(elements, JavaPsiPatternUtil::containsNamedPatternVariable);
|
||||
if (patternVarElement != null) {
|
||||
myVisitor.report(JavaErrorKinds.SWITCH_LABEL_MULTIPLE_PATTERNS_UNNAMED.create(patternVarElement));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void checkFallThroughToPatternPrecedingCompleteNormally(@NotNull List<? extends List<? extends PsiSwitchLabelStatementBase>> switchBlockGroup,
|
||||
@NotNull Set<PsiElement> alreadyFallThroughElements) {
|
||||
for (int i = 1; i < switchBlockGroup.size(); i++) {
|
||||
List<? extends PsiSwitchLabelStatementBase> switchLabels = switchBlockGroup.get(i);
|
||||
PsiSwitchLabelStatementBase firstSwitchLabelInGroup = switchLabels.get(0);
|
||||
for (PsiSwitchLabelStatementBase switchLabel : switchLabels) {
|
||||
if (!(switchLabel instanceof PsiSwitchLabelStatement)) {
|
||||
return;
|
||||
}
|
||||
PsiCaseLabelElementList labelElementList = switchLabel.getCaseLabelElementList();
|
||||
if (labelElementList == null) continue;
|
||||
List<PsiCaseLabelElement> patternElements = ContainerUtil.filter(labelElementList.getElements(),
|
||||
labelElement -> JavaPsiPatternUtil.containsNamedPatternVariable(
|
||||
labelElement));
|
||||
if (patternElements.isEmpty()) continue;
|
||||
PsiStatement prevStatement = PsiTreeUtil.getPrevSiblingOfType(firstSwitchLabelInGroup, PsiStatement.class);
|
||||
if (prevStatement == null) continue;
|
||||
ControlFlow flow = ControlFlowChecker.getControlFlow(prevStatement);
|
||||
if (flow != null && ControlFlowUtil.canCompleteNormally(flow, 0, flow.getSize())) {
|
||||
List<PsiCaseLabelElement> elements =
|
||||
ContainerUtil.filter(patternElements, patternElement -> !alreadyFallThroughElements.contains(patternElement));
|
||||
for (PsiCaseLabelElement patternElement : elements) {
|
||||
myVisitor.report(JavaErrorKinds.SWITCH_FALLTHROUGH_TO_PATTERN.create(patternElement));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -994,6 +994,23 @@ public final class JavaErrorKinds {
|
||||
return message("switch.label.duplicate", value);
|
||||
}
|
||||
});
|
||||
public static final Simple<PsiCaseLabelElement> SWITCH_FALLTHROUGH_TO_PATTERN = error("switch.fallthrough.to.pattern");
|
||||
public static final Simple<PsiCaseLabelElement> SWITCH_MULTIPLE_LABELS_WITH_PATTERN_VARIABLES =
|
||||
error("switch.multiple.labels.with.pattern.variables");
|
||||
public static final Parameterized<PsiDefaultCaseLabelElement, PsiCaseLabelElementList> SWITCH_DEFAULT_NULL_ORDER =
|
||||
parameterized("switch.default.null.order");
|
||||
public static final Simple<PsiDefaultCaseLabelElement> SWITCH_DEFAULT_LABEL_NOT_ALLOWED =
|
||||
error("switch.default.label.not.allowed");
|
||||
public static final Simple<PsiExpression> SWITCH_NULL_LABEL_NOT_ALLOWED =
|
||||
error("switch.null.label.not.allowed");
|
||||
public static final Simple<PsiCaseLabelElement> SWITCH_LABEL_COMBINATION_CONSTANTS_AND_PATTERNS =
|
||||
error("switch.label.combination.constants.and.patterns");
|
||||
public static final Simple<PsiCaseLabelElement> SWITCH_LABEL_COMBINATION_CONSTANTS_AND_PATTERNS_UNNAMED =
|
||||
error("switch.label.combination.constants.and.patterns.unnamed");
|
||||
public static final Simple<PsiCaseLabelElement> SWITCH_LABEL_MULTIPLE_PATTERNS =
|
||||
error("switch.label.multiple.patterns");
|
||||
public static final Simple<PsiCaseLabelElement> SWITCH_LABEL_MULTIPLE_PATTERNS_UNNAMED =
|
||||
error("switch.label.multiple.patterns.unnamed");
|
||||
|
||||
public static final Simple<PsiReferenceExpression> EXPRESSION_EXPECTED = error("expression.expected");
|
||||
public static final Parameterized<PsiReferenceExpression, PsiSuperExpression> EXPRESSION_SUPER_UNQUALIFIED_DEFAULT_METHOD =
|
||||
|
||||
@@ -734,6 +734,13 @@ final class JavaErrorFixProvider {
|
||||
fix(SWITCH_LABEL_QUALIFIED_ENUM, error -> myFactory.createDeleteFix(
|
||||
requireNonNull(error.psi().getQualifier()), JavaErrorBundle.message("qualified.enum.constant.in.switch.remove.fix")));
|
||||
fix(SWITCH_DEFAULT_LABEL_CONTAINS_CASE, error -> myFactory.createReplaceCaseDefaultWithDefaultFix(error.context()));
|
||||
JavaFixProvider<PsiCaseLabelElement, Void> splitCase = error -> myFactory.createSplitSwitchBranchWithSeveralCaseValuesAction();
|
||||
fix(SWITCH_MULTIPLE_LABELS_WITH_PATTERN_VARIABLES, splitCase);
|
||||
fix(SWITCH_LABEL_COMBINATION_CONSTANTS_AND_PATTERNS, splitCase);
|
||||
fix(SWITCH_LABEL_COMBINATION_CONSTANTS_AND_PATTERNS_UNNAMED, splitCase);
|
||||
fix(SWITCH_LABEL_MULTIPLE_PATTERNS, splitCase);
|
||||
fix(SWITCH_LABEL_MULTIPLE_PATTERNS_UNNAMED, splitCase);
|
||||
fix(SWITCH_DEFAULT_NULL_ORDER, error -> myFactory.createReverseCaseDefaultNullFixFix(error.context()));
|
||||
}
|
||||
|
||||
private void createAccessFixes() {
|
||||
|
||||
@@ -6,8 +6,6 @@ import com.intellij.codeInsight.daemon.impl.HighlightInfo;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.QuickFixFactory;
|
||||
import com.intellij.java.codeserver.core.JavaPsiSealedUtil;
|
||||
import com.intellij.modcommand.ModCommandAction;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.*;
|
||||
@@ -17,14 +15,12 @@ import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.intellij.util.containers.SmartHashSet;
|
||||
import com.siyeh.ig.fixes.MakeDefaultLastCaseFix;
|
||||
import com.siyeh.ig.psiutils.ControlFlowUtils;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.SwitchUtils;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -57,16 +53,10 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight
|
||||
PsiCodeBlock body = myBlock.getBody();
|
||||
if (body == null) return;
|
||||
|
||||
List<List<PsiSwitchLabelStatementBase>> elementsToCheckFallThroughLegality = new SmartList<>();
|
||||
List<PsiElement> elementsToCheckDominance = new ArrayList<>();
|
||||
List<PsiCaseLabelElement> elementsToCheckCompleteness = new ArrayList<>();
|
||||
int switchBlockGroupCounter = 0;
|
||||
for (PsiStatement st : body.getStatements()) {
|
||||
if (!(st instanceof PsiSwitchLabelStatementBase labelStatement)) continue;
|
||||
fillElementsToCheckFallThroughLegality(elementsToCheckFallThroughLegality, labelStatement, switchBlockGroupCounter);
|
||||
if (!(PsiTreeUtil.skipWhitespacesAndCommentsForward(labelStatement) instanceof PsiSwitchLabelStatement)) {
|
||||
switchBlockGroupCounter++;
|
||||
}
|
||||
if (labelStatement.isDefaultCase()) {
|
||||
elementsToCheckDominance.add(requireNonNull(labelStatement.getFirstChild()));
|
||||
continue;
|
||||
@@ -78,14 +68,6 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight
|
||||
elementsToCheckCompleteness.add(labelElement);
|
||||
}
|
||||
}
|
||||
Set<PsiElement> alreadyFallThroughElements = new HashSet<>();
|
||||
boolean reported =
|
||||
checkFallThroughFromPatternWithSeveralLabels(elementsToCheckFallThroughLegality, alreadyFallThroughElements, errorSink);
|
||||
reported |= checkFallThroughToPatternPrecedingCompleteNormally(elementsToCheckFallThroughLegality, alreadyFallThroughElements, errorSink);
|
||||
if (reported) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkDominance(elementsToCheckDominance, errorSink)) {
|
||||
return;
|
||||
}
|
||||
@@ -95,20 +77,6 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight
|
||||
}
|
||||
}
|
||||
|
||||
private static void fillElementsToCheckFallThroughLegality(@NotNull List<List<PsiSwitchLabelStatementBase>> elements,
|
||||
@NotNull PsiSwitchLabelStatementBase labelStatement,
|
||||
int switchBlockGroupCounter) {
|
||||
List<PsiSwitchLabelStatementBase> switchLabels;
|
||||
if (switchBlockGroupCounter < elements.size()) {
|
||||
switchLabels = elements.get(switchBlockGroupCounter);
|
||||
}
|
||||
else {
|
||||
switchLabels = new SmartList<>();
|
||||
elements.add(switchLabels);
|
||||
}
|
||||
switchLabels.add(labelStatement);
|
||||
}
|
||||
|
||||
@NotNull Map<PsiCaseLabelElement, PsiElement> findDominatedLabels(@NotNull List<? extends PsiElement> switchLabels) {
|
||||
Map<PsiCaseLabelElement, PsiElement> result = new HashMap<>();
|
||||
for (int i = 0; i < switchLabels.size() - 1; i++) {
|
||||
@@ -175,174 +143,6 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight
|
||||
elements[1] instanceof PsiDefaultCaseLabelElement;
|
||||
}
|
||||
|
||||
private static boolean checkFallThroughFromPatternWithSeveralLabels(@NotNull List<? extends List<PsiSwitchLabelStatementBase>> switchBlockGroup,
|
||||
@NotNull Set<? super PsiElement> alreadyFallThroughElements,
|
||||
@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
|
||||
if (switchBlockGroup.isEmpty()) return false;
|
||||
boolean reported = false;
|
||||
for (List<PsiSwitchLabelStatementBase> switchLabel : switchBlockGroup) {
|
||||
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];
|
||||
if (problem != null) {
|
||||
HighlightInfo.Builder info =
|
||||
addIllegalFallThroughError(problem.element(), problem.message(), problem.customAction(), alreadyFallThroughElements);
|
||||
errorSink.accept(info);
|
||||
reported = true;
|
||||
}
|
||||
else {
|
||||
if (JavaPsiPatternUtil.containsNamedPatternVariable(first)) {
|
||||
PsiElement nextNotLabel = PsiTreeUtil.skipSiblingsForward(switchLabelElement, PsiWhiteSpace.class, PsiComment.class,
|
||||
PsiSwitchLabelStatement.class);
|
||||
//there is no statement, it is allowed to go through (14.11.1 JEP 440-441)
|
||||
if (!(nextNotLabel instanceof PsiStatement)) {
|
||||
continue;
|
||||
}
|
||||
if (PsiTreeUtil.skipWhitespacesAndCommentsForward(switchLabelElement) instanceof PsiSwitchLabelStatement ||
|
||||
PsiTreeUtil.skipWhitespacesAndCommentsBackward(switchLabelElement) instanceof PsiSwitchLabelStatement) {
|
||||
HighlightInfo.Builder info = addIllegalFallThroughError(first, "multiple.switch.labels", null, alreadyFallThroughElements);
|
||||
errorSink.accept(info);
|
||||
reported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return reported;
|
||||
}
|
||||
|
||||
private record CaseLabelCombinationProblem(@NotNull PsiCaseLabelElement element,
|
||||
@NotNull @PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String message,
|
||||
@Nullable ModCommandAction customAction) {
|
||||
}
|
||||
|
||||
private static @Nullable CaseLabelCombinationProblem checkCaseLabelCombination(PsiCaseLabelElementList labelElementList) {
|
||||
PsiCaseLabelElement[] elements = labelElementList.getElements();
|
||||
PsiCaseLabelElement firstElement = elements[0];
|
||||
if (elements.length == 1) {
|
||||
if (firstElement instanceof PsiDefaultCaseLabelElement) {
|
||||
ModCommandAction fix = getFixFactory().createReplaceCaseDefaultWithDefaultFix(labelElementList);
|
||||
return new CaseLabelCombinationProblem(firstElement, "default.label.must.not.contains.case.keyword", fix);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (elements.length == 2) {
|
||||
if (firstElement instanceof PsiDefaultCaseLabelElement &&
|
||||
elements[1] instanceof PsiExpression expr &&
|
||||
ExpressionUtils.isNullLiteral(expr)) {
|
||||
ModCommandAction fix = getFixFactory().createReverseCaseDefaultNullFixFix(labelElementList);
|
||||
return new CaseLabelCombinationProblem(firstElement, "invalid.default.and.null.order", fix);
|
||||
}
|
||||
if (firstElement instanceof PsiExpression expr &&
|
||||
ExpressionUtils.isNullLiteral(expr) &&
|
||||
elements[1] instanceof PsiDefaultCaseLabelElement) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
int defaultIndex = -1;
|
||||
int nullIndex = -1;
|
||||
int patternIndex = -1;
|
||||
|
||||
for (int i = 0; i < elements.length; i++) {
|
||||
if (elements[i] instanceof PsiDefaultCaseLabelElement) {
|
||||
defaultIndex = i;
|
||||
break;
|
||||
}
|
||||
else if (elements[i] instanceof PsiExpression expr && ExpressionUtils.isNullLiteral(expr)) {
|
||||
nullIndex = i;
|
||||
break;
|
||||
}
|
||||
else if (elements[i] instanceof PsiPattern) {
|
||||
patternIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultIndex != -1) {
|
||||
return new CaseLabelCombinationProblem(elements[defaultIndex], "default.label.not.allowed.here", null);
|
||||
}
|
||||
if (nullIndex != -1) {
|
||||
return new CaseLabelCombinationProblem(elements[nullIndex], "null.label.not.allowed.here", null);
|
||||
}
|
||||
if (firstElement instanceof PsiExpression && patternIndex != -1) {
|
||||
return getPatternConstantCombinationProblem(elements[patternIndex]);
|
||||
}
|
||||
else if (firstElement instanceof PsiPattern) {
|
||||
PsiCaseLabelElement nonPattern = ContainerUtil.find(elements, e -> !(e instanceof PsiPattern));
|
||||
if (nonPattern != null) {
|
||||
return getPatternConstantCombinationProblem(nonPattern);
|
||||
}
|
||||
if (PsiUtil.isAvailable(JavaFeature.UNNAMED_PATTERNS_AND_VARIABLES, firstElement)) {
|
||||
PsiCaseLabelElement patternVarElement = ContainerUtil.find(elements, JavaPsiPatternUtil::containsNamedPatternVariable);
|
||||
if (patternVarElement != null) {
|
||||
return new CaseLabelCombinationProblem(patternVarElement, "invalid.case.label.combination.several.patterns.unnamed", null);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return new CaseLabelCombinationProblem(elements[1], "invalid.case.label.combination.several.patterns", null);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @NotNull CaseLabelCombinationProblem getPatternConstantCombinationProblem(PsiCaseLabelElement anchor) {
|
||||
if (PsiUtil.isAvailable(JavaFeature.UNNAMED_PATTERNS_AND_VARIABLES, anchor)) {
|
||||
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", null);
|
||||
}
|
||||
}
|
||||
|
||||
private static HighlightInfo.@NotNull Builder addIllegalFallThroughError(@NotNull PsiElement element,
|
||||
@NotNull @PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String key,
|
||||
@Nullable ModCommandAction 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;
|
||||
}
|
||||
|
||||
private static boolean checkFallThroughToPatternPrecedingCompleteNormally(@NotNull List<? extends List<? extends PsiSwitchLabelStatementBase>> switchBlockGroup,
|
||||
@NotNull Set<PsiElement> alreadyFallThroughElements,
|
||||
@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
|
||||
boolean reported = false;
|
||||
for (int i = 1; i < switchBlockGroup.size(); i++) {
|
||||
List<? extends PsiSwitchLabelStatementBase> switchLabels = switchBlockGroup.get(i);
|
||||
PsiSwitchLabelStatementBase firstSwitchLabelInGroup = switchLabels.get(0);
|
||||
for (PsiSwitchLabelStatementBase switchLabel : switchLabels) {
|
||||
if (!(switchLabel instanceof PsiSwitchLabelStatement)) {
|
||||
return reported;
|
||||
}
|
||||
PsiCaseLabelElementList labelElementList = switchLabel.getCaseLabelElementList();
|
||||
if (labelElementList == null) continue;
|
||||
List<PsiCaseLabelElement> patternElements = ContainerUtil.filter(labelElementList.getElements(),
|
||||
labelElement -> JavaPsiPatternUtil.containsNamedPatternVariable(
|
||||
labelElement));
|
||||
if (patternElements.isEmpty()) continue;
|
||||
PsiStatement prevStatement = PsiTreeUtil.getPrevSiblingOfType(firstSwitchLabelInGroup, PsiStatement.class);
|
||||
if (prevStatement == null) continue;
|
||||
if (ControlFlowUtils.statementMayCompleteNormally(prevStatement)) {
|
||||
List<PsiCaseLabelElement> elements =
|
||||
ContainerUtil.filter(patternElements, patternElement -> !alreadyFallThroughElements.contains(patternElement));
|
||||
for (PsiCaseLabelElement patternElement : elements) {
|
||||
errorSink.accept(createError(patternElement, JavaErrorBundle.message("switch.illegal.fall.through.to")));
|
||||
reported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return reported;
|
||||
}
|
||||
|
||||
/**
|
||||
* 14.11.1 Switch Blocks
|
||||
* To ensure the absence of unreachable statements, domination rules provide a possible order
|
||||
|
||||
@@ -118,7 +118,7 @@ public class Main {
|
||||
void test11(Integer integer) {
|
||||
switch (integer) {
|
||||
case 1, 2:
|
||||
case <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>, Integer i when i == 42:
|
||||
case <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>, <error descr="Invalid case label combination: a case label must consist of either a list of case constants or a list of case patterns">Integer i</error> when i == 42:
|
||||
System.out.println("blah blah blah");
|
||||
break;
|
||||
default: System.out.println("null");
|
||||
@@ -144,7 +144,7 @@ public class Main {
|
||||
|
||||
void test14(Object obj) {
|
||||
switch (obj) {
|
||||
case null, String s when s.isEmpty()<error descr="':' or '->' expected"><error descr="Unexpected token">,</error></error> Integer i<error descr="';' expected"> </error><error descr="Cannot resolve symbol 'when'" textAttributesKey="WRONG_REFERENCES_ATTRIBUTES">when</error> <error descr="Variable 'i' is already defined in the scope">i</error><error descr="';' expected"> </error><error descr="Unexpected token">==</error> <error descr="Not a statement">42</error> <error descr="Unexpected token">-></error> {}
|
||||
case null, String s when s.isEmpty()<error descr="':' or '->' expected"><error descr="Unexpected token">,</error></error> Integer i<error descr="';' expected"> </error><error descr="Cannot resolve symbol 'when'">when</error> <error descr="Variable 'i' is already defined in the scope">i</error><error descr="';' expected"> </error><error descr="Unexpected token">==</error> <error descr="Not a statement">42</error> <error descr="Unexpected token">-></error> {}
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
@@ -172,20 +172,20 @@ public class Main {
|
||||
|
||||
void test18(String s) {
|
||||
switch (s) {
|
||||
case "hello", "world", null, String str when <error descr="Cannot resolve symbol 'str'" textAttributesKey="WRONG_REFERENCES_ATTRIBUTES">str</error>.isEmpty() -> {}
|
||||
case "hello", "world", null, String str when <error descr="Cannot resolve symbol 'str'">str</error>.isEmpty() -> {}
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
|
||||
void test19(String s) {
|
||||
switch (s) {
|
||||
case "hello", "world", <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>, String str -> {}
|
||||
case "hello", "world", <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>, <error descr="Invalid case label combination: a case label must consist of either a list of case constants or a list of case patterns">String str</error> -> {}
|
||||
}
|
||||
}
|
||||
|
||||
void test20(Object obj) {
|
||||
switch (obj) {
|
||||
case <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>, S(), R() -> {}
|
||||
case <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>, <error descr="Invalid case label combination: a case label must consist of either a list of case constants or a list of case patterns">S()</error>, <error descr="Invalid case label combination: a case label must consist of either a list of case constants or a list of case patterns">R()</error> -> {}
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
@@ -213,13 +213,13 @@ public class Main {
|
||||
|
||||
void test24(String s) {
|
||||
switch (s) {
|
||||
case "hello", "world", <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>, String str -> {}
|
||||
case "hello", "world", <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>, <error descr="Invalid case label combination: a case label must consist of either a list of case constants or a list of case patterns">String str</error> -> {}
|
||||
}
|
||||
}
|
||||
|
||||
void test25(String s) {
|
||||
switch (s) {
|
||||
case "hello", "world", String str, <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error> -> {}
|
||||
case "hello", "world", <error descr="Invalid case label combination: a case label must consist of either a list of case constants or a list of case patterns">String str</error>, <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error> -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class Foo {
|
||||
case Triangle t when t.calculateArea() > 100:
|
||||
System.out.println("Large triangle");
|
||||
break;
|
||||
case <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>, Triangle t:
|
||||
case <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>, <error descr="Invalid case label combination: a case label must consist of either a list of case constants or a single case pattern">Triangle t</error>:
|
||||
System.out.println("Small triangle or null");
|
||||
break;
|
||||
case Color c:
|
||||
@@ -59,7 +59,7 @@ class Foo {
|
||||
|
||||
int foo3(Integer i) {
|
||||
return switch (i) {
|
||||
case 1, 2, 3, 4, 5, <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>, default -> 42;
|
||||
case 1, 2, 3, 4, 5, <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>, <error descr="Default label not allowed here: 'default' can only be used as a single case label or paired only with 'null'">default</error> -> 42;
|
||||
case 42 -> 666;
|
||||
};
|
||||
}
|
||||
@@ -121,7 +121,7 @@ class Bar extends Foo {
|
||||
case Triangle t when t.calculateArea() > 100:
|
||||
System.out.println("Large triangle");
|
||||
break;
|
||||
case <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>, Triangle t:
|
||||
case <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>, <error descr="Invalid case label combination: a case label must consist of either a list of case constants or a single case pattern">Triangle t</error>:
|
||||
System.out.println("Small triangle or null");
|
||||
break;
|
||||
case Color c:
|
||||
@@ -141,7 +141,7 @@ class Bar extends Foo {
|
||||
@Override
|
||||
int <warning descr="Method 'foo3()' is identical to its super method">foo3</warning>(Integer i) {
|
||||
return switch (i) {
|
||||
case <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>, 4, default, 1, 5, 3, 2 -> 42;
|
||||
case <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>, 4, <error descr="Default label not allowed here: 'default' can only be used as a single case label or paired only with 'null'">default</error>, 1, 5, 3, 2 -> 42;
|
||||
case 42 -> 666;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user