[java-highlighting] SwitchBlockHighlightingModel.checkSwitchBlockStatements migrated

Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only)

GitOrigin-RevId: 463b1549d45bf98586e6ae9dafb4bc4b330bbe52
This commit is contained in:
Tagir Valeev
2025-02-18 09:36:18 +00:00
committed by intellij-monorepo-bot
parent 2da31596ac
commit e6691d9ea8
12 changed files with 85 additions and 82 deletions
@@ -314,6 +314,8 @@ switch.expression.no.result=Switch expression does not have any result expressio
switch.expression.should.produce.result=Switch expression should produce a result in all execution paths
switch.expression.incompatible.type=Bad type in switch expression: {0} cannot be converted to {1}
switch.expression.cannot.be.void=Target type for switch expression cannot be void
switch.label.expected=Statement must be prepended with a case label
switch.different.case.kinds=Different 'case' kinds used in 'switch'
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'
@@ -1729,4 +1729,63 @@ final class ExpressionChecker {
}
}
}
void checkSwitchBlockStatements(@NotNull PsiSwitchBlock block) {
PsiCodeBlock body = block.getBody();
if (body == null) return;
PsiElement first = PsiTreeUtil.skipWhitespacesAndCommentsForward(body.getLBrace());
if (first != null && !(first instanceof PsiSwitchLabelStatementBase) && !PsiUtil.isJavaToken(first, JavaTokenType.RBRACE)) {
myVisitor.report(JavaErrorKinds.SWITCH_LABEL_EXPECTED.create(first));
}
PsiElement element = first;
PsiStatement alien = null;
boolean classicLabels = false;
boolean enhancedLabels = false;
boolean levelChecked = false;
while (element != null && !PsiUtil.isJavaToken(element, JavaTokenType.RBRACE)) {
if (element instanceof PsiSwitchLabeledRuleStatement) {
if (!levelChecked) {
myVisitor.checkFeature(element, JavaFeature.ENHANCED_SWITCH);
if (myVisitor.hasErrorResults()) return;
levelChecked = true;
}
if (classicLabels) {
alien = (PsiStatement)element;
break;
}
enhancedLabels = true;
}
else if (element instanceof PsiStatement statement) {
if (enhancedLabels) {
//let's not highlight twice
if (statement instanceof PsiSwitchLabelStatement labelStatement &&
labelStatement.getChildren().length != 0 &&
labelStatement.getChildren()[labelStatement.getChildren().length - 1] instanceof PsiErrorElement errorElement &&
errorElement.getErrorDescription().startsWith(JavaPsiBundle.message("expected.colon.or.arrow"))) {
break;
}
alien = statement;
break;
}
classicLabels = true;
}
if (!levelChecked && element instanceof PsiSwitchLabelStatementBase label) {
@Nullable PsiCaseLabelElementList values = label.getCaseLabelElementList();
if (values != null && values.getElementCount() > 1) {
myVisitor.checkFeature(values, JavaFeature.ENHANCED_SWITCH);
if (myVisitor.hasErrorResults()) return;
levelChecked = true;
}
}
element = PsiTreeUtil.skipWhitespacesAndCommentsForward(element);
}
if (alien == null) return;
if (enhancedLabels && !(alien instanceof PsiSwitchLabelStatementBase)) {
myVisitor.report(JavaErrorKinds.SWITCH_LABEL_EXPECTED.create(alien));
return;
}
myVisitor.report(JavaErrorKinds.SWITCH_DIFFERENT_CASE_KINDS.create(alien));
}
}
@@ -628,6 +628,17 @@ final class JavaErrorVisitor extends JavaElementVisitor {
if (!hasErrorResults()) checkFeature(expression, JavaFeature.SWITCH_EXPRESSION);
if (!hasErrorResults()) myExpressionChecker.checkSwitchExpressionReturnTypeCompatible(expression);
if (!hasErrorResults()) myExpressionChecker.checkSwitchExpressionHasResult(expression);
checkSwitchBlock(expression);
}
@Override
public void visitSwitchStatement(@NotNull PsiSwitchStatement statement) {
super.visitSwitchStatement(statement);
checkSwitchBlock(statement);
}
private void checkSwitchBlock(@NotNull PsiSwitchBlock block) {
if (!hasErrorResults()) myExpressionChecker.checkSwitchBlockStatements(block);
}
@Override
@@ -923,6 +923,8 @@ public final class JavaErrorKinds {
public static final Parameterized<PsiExpression, JavaIncompatibleTypeErrorContext> SWITCH_EXPRESSION_INCOMPATIBLE_TYPE =
parameterized(PsiExpression.class, JavaIncompatibleTypeErrorContext.class, "switch.expression.incompatible.type")
.withRawDescription((expr, context) -> message("switch.expression.incompatible.type", formatType(context.rType()), formatType(context.lType())));
public static final Simple<PsiElement> SWITCH_LABEL_EXPECTED = error(PsiElement.class, "switch.label.expected");
public static final Simple<PsiElement> SWITCH_DIFFERENT_CASE_KINDS = error("switch.different.case.kinds");
public static final Simple<PsiReferenceExpression> EXPRESSION_EXPECTED = error("expression.expected");
public static final Parameterized<PsiReferenceExpression, PsiSuperExpression> EXPRESSION_SUPER_UNQUALIFIED_DEFAULT_METHOD =
@@ -327,7 +327,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
private void checkSwitchBlock(@NotNull PsiSwitchBlock switchBlock) {
SwitchBlockHighlightingModel model = SwitchBlockHighlightingModel.createInstance(myLanguageLevel, switchBlock, myFile);
if (model == null) return;
if (!hasErrorResults()) model.checkSwitchBlockStatements(myErrorSink);
if (!hasErrorResults()) model.checkSwitchSelectorType(myErrorSink);
if (!hasErrorResults()) model.checkSwitchLabelValues(myErrorSink);
}
@@ -223,6 +223,10 @@ final class JavaErrorFixProvider {
error -> myFactory.createDeleteFix(error.psi(), QuickFixBundle.message("delete.unreachable.statement.fix.text")));
fix(STATEMENT_UNREACHABLE_LOOP_BODY, error -> myFactory.createSimplifyBooleanFix(error.psi(), false));
fix(FOREACH_NOT_APPLICABLE, error -> myFactory.createNotIterableForEachLoopFix(error.psi()));
fix(SWITCH_LABEL_EXPECTED, error -> {
PsiSwitchLabeledRuleStatement previousRule = PsiTreeUtil.getPrevSiblingOfType(error.psi(), PsiSwitchLabeledRuleStatement.class);
return previousRule == null ? null : myFactory.createWrapSwitchRuleStatementsIntoBlockFix(previousRule);
});
}
private void createMethodFixes() {
@@ -7,7 +7,6 @@ import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.codeInsight.intention.impl.PriorityIntentionActionWrapper;
import com.intellij.core.JavaPsiBundle;
import com.intellij.modcommand.ModCommandAction;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.NlsContexts;
@@ -80,77 +79,6 @@ public class SwitchBlockHighlightingModel {
return found.get();
}
void checkSwitchBlockStatements(@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
PsiCodeBlock body = myBlock.getBody();
if (body == null) return;
PsiElement first = PsiTreeUtil.skipWhitespacesAndCommentsForward(body.getLBrace());
if (first != null && !(first instanceof PsiSwitchLabelStatementBase) && !PsiUtil.isJavaToken(first, JavaTokenType.RBRACE)) {
errorSink.accept(createError(first, JavaErrorBundle.message("statement.must.be.prepended.with.case.label")));
}
PsiElement element = first;
PsiStatement alien = null;
boolean classicLabels = false;
boolean enhancedLabels = false;
boolean levelChecked = false;
while (element != null && !PsiUtil.isJavaToken(element, JavaTokenType.RBRACE)) {
if (element instanceof PsiSwitchLabeledRuleStatement) {
if (!levelChecked) {
HighlightInfo.Builder info = HighlightUtil.checkFeature(element, JavaFeature.ENHANCED_SWITCH, myLevel, myFile);
if (info != null) {
errorSink.accept(info);
return;
}
levelChecked = true;
}
if (classicLabels) {
alien = (PsiStatement)element;
break;
}
enhancedLabels = true;
}
else if (element instanceof PsiStatement statement) {
if (enhancedLabels) {
//let's not highlight twice
if (statement instanceof PsiSwitchLabelStatement labelStatement &&
labelStatement.getChildren().length != 0 &&
labelStatement.getChildren()[labelStatement.getChildren().length - 1] instanceof PsiErrorElement errorElement &&
errorElement.getErrorDescription().startsWith(JavaPsiBundle.message("expected.colon.or.arrow"))) {
break;
}
alien = statement;
break;
}
classicLabels = true;
}
if (!levelChecked && element instanceof PsiSwitchLabelStatementBase label) {
@Nullable PsiCaseLabelElementList values = label.getCaseLabelElementList();
if (values != null && values.getElementCount() > 1) {
HighlightInfo.Builder info = HighlightUtil.checkFeature(values, JavaFeature.ENHANCED_SWITCH, myLevel, myFile);
if (info != null) {
errorSink.accept(info);
return;
}
levelChecked = true;
}
}
element = PsiTreeUtil.skipWhitespacesAndCommentsForward(element);
}
if (alien == null) return;
if (enhancedLabels && !(alien instanceof PsiSwitchLabelStatementBase)) {
PsiSwitchLabeledRuleStatement previousRule = PsiTreeUtil.getPrevSiblingOfType(alien, PsiSwitchLabeledRuleStatement.class);
HighlightInfo.Builder info = createError(alien, JavaErrorBundle.message("statement.must.be.prepended.with.case.label"));
if (previousRule != null) {
IntentionAction action = getFixFactory().createWrapSwitchRuleStatementsIntoBlockFix(previousRule);
info.registerFix(action, null, null, null, null);
}
errorSink.accept(info);
return;
}
errorSink.accept(createError(alien, JavaErrorBundle.message("different.case.kinds.in.switch")));
}
void checkSwitchSelectorType(@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
SelectorKind kind = getSwitchSelectorKind();
if (kind == SelectorKind.INT) return;
@@ -97,8 +97,6 @@ identifier.is.not.allowed.here=Identifier is not allowed here
illegal.forward.reference=Cannot read value of field ''{0}'' before the field''s definition
unknown.class=Unknown class: ''{0}''
illegal.type.void=Illegal type: 'void'
statement.must.be.prepended.with.case.label=Statement must be prepended with case label
different.case.kinds.in.switch=Different 'case' kinds used in 'switch'
numeric.overflow.in.expression=Numeric overflow in expression
static.member.accessed.via.instance.reference=Static member ''{0}.{1}'' accessed via instance reference
deprecated.symbol=''{0}'' is deprecated
@@ -11,15 +11,15 @@ class SwitchStatement {
switch (0) {
////////////////
/** */
<error descr="Statement must be prepended with case label">System.out.println();</error>
<error descr="Statement must be prepended with a case label">System.out.println();</error>
}
switch (0) {
<error descr="Statement must be prepended with case label">break;</error>
<error descr="Statement must be prepended with a case label">break;</error>
}
switch (0) {
<error descr="Statement must be prepended with case label">return;</error>
<error descr="Statement must be prepended with a case label">return;</error>
}
switch (0) {
@@ -15,7 +15,7 @@ class EnhancedSwitchStatements {
switch (new Random().nextInt()) {
case 0 -> throw new IllegalStateException("no args");
<error descr="Statement must be prepended with case label">break;</error>
<error descr="Statement must be prepended with a case label">break;</error>
}
switch (new Random().nextInt()) {
case 0 -> throw new IllegalStateException("no args");
@@ -10,9 +10,9 @@ class SwitchExpressions {
System.out.println(switch (<error descr="'switch' expression does not have any case clauses">new Random().nextInt()</error>) { });
System.out.println(switch (new Random().nextInt()) {
System.out.println(switch (<error descr="'switch' expression does not cover all possible input values">new Random().nextInt()</error>) {
case 0 -> throw new IllegalStateException("no args");
<error descr="Different 'case' kinds used in 'switch'">case 1:</error> yield "lone";
case 1: yield "lone";
});
System.out.println(
@@ -2,7 +2,7 @@ class C {
public static void main(String[] args) {
switch (args.length) {
<error descr="Statement must be prepended with case label">return;</error>
<error descr="Statement must be prepended with a case label">return;</error>
case 1:
System.out.println("");
System.out.println("");