mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-highlighting] Exhaustiveness checks migrated (the last error from HighlightVisitorImpl!)
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: c45b258fc18e267122946be7d3aebfd9dc77387f
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1e74ac0725
commit
89dda01eeb
+5
-3
@@ -325,11 +325,11 @@ switch.different.case.kinds=Different 'case' kinds used in 'switch'
|
||||
switch.selector.type.invalid=Selector type of ''{0}'' is not supported
|
||||
switch.selector.type.invalid.level=Selector type of ''{0}'' is not supported at language level ''{1}''
|
||||
switch.null.type.incompatible=''null'' cannot be converted to ''{0}''
|
||||
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.qualified.enum=An enum switch case label must be the unqualified name of an enumeration constant
|
||||
switch.label.constant.expected=Constant expression required
|
||||
switch.label.pattern.expected=Pattern expected for switch selector type ''{0}''
|
||||
switch.label.unexpected=Constant expression, pattern or null is required
|
||||
switch.default.label.contains.case=The label for the default case must only use the 'default' keyword, without 'case'
|
||||
switch.label.duplicate.unconditional.pattern=Duplicate unconditional pattern
|
||||
switch.label.duplicate.default=Duplicate default label
|
||||
switch.label.duplicate=Duplicate label ''{0}''
|
||||
@@ -337,15 +337,17 @@ 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
|
||||
switch.dominance.violation=Label is dominated by a preceding case label ''{0}''
|
||||
switch.unconditional.pattern.and.default='switch' has both an unconditional pattern and a default label
|
||||
switch.default.and.boolean='switch' has all boolean values and a default label
|
||||
switch.default.label.contains.case=The label for the default case must only use the 'default' keyword, without 'case'
|
||||
switch.unconditional.pattern.and.default='switch' has both an unconditional pattern and a default label
|
||||
switch.unconditional.pattern.and.boolean='switch' has all boolean values and an unconditional pattern
|
||||
switch.empty=''switch'' {0} does not have any case clauses
|
||||
switch.incomplete=''switch'' {0} does not cover all possible input values
|
||||
|
||||
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'
|
||||
|
||||
+16
@@ -21,6 +21,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.java.codeserver.core.JavaPatternExhaustivenessUtil.hasExhaustivenessError;
|
||||
|
||||
final class SwitchChecker {
|
||||
private final @NotNull JavaErrorVisitor myVisitor;
|
||||
|
||||
@@ -34,6 +36,7 @@ final class SwitchChecker {
|
||||
if (!myVisitor.hasErrorResults()) checkFallthroughLegality(block);
|
||||
if (!myVisitor.hasErrorResults()) checkDominance(block);
|
||||
if (!myVisitor.hasErrorResults()) checkNoDefaultBranchAllowed(block);
|
||||
if (!myVisitor.hasErrorResults()) checkExhaustiveness(block);
|
||||
}
|
||||
|
||||
void checkSwitchExpressionReturnTypeCompatible(@NotNull PsiSwitchExpression switchExpression) {
|
||||
@@ -632,4 +635,17 @@ final class SwitchChecker {
|
||||
}
|
||||
}
|
||||
|
||||
void checkExhaustiveness(@NotNull PsiSwitchBlock block) {
|
||||
PsiCodeBlock body = block.getBody();
|
||||
if (body == null) return;
|
||||
|
||||
if (!ExpressionUtil.isEnhancedSwitch(block)) return;
|
||||
if (JavaPsiSwitchUtil.getUnconditionalPatternLabel(block) != null) return;
|
||||
if (JavaPsiSwitchUtil.findDefaultElement(block) != null) return;
|
||||
if (!hasExhaustivenessError(block)) return;
|
||||
|
||||
boolean hasAnyCaseLabels = JavaPsiSwitchUtil.hasAnyCaseLabels(block);
|
||||
var kind = hasAnyCaseLabels ? JavaErrorKinds.SWITCH_INCOMPLETE : JavaErrorKinds.SWITCH_EMPTY;
|
||||
myVisitor.report(kind.create(block));
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -1021,6 +1021,12 @@ public final class JavaErrorKinds {
|
||||
error("switch.default.and.boolean");
|
||||
public static final Simple<PsiCaseLabelElement> SWITCH_UNCONDITIONAL_PATTERN_AND_BOOLEAN =
|
||||
error("switch.unconditional.pattern.and.boolean");
|
||||
public static final Simple<PsiSwitchBlock> SWITCH_EMPTY = error(PsiSwitchBlock.class, "switch.empty")
|
||||
.withAnchor(block -> requireNonNullElse(block.getExpression(), block.getFirstChild()))
|
||||
.withDescription(block -> message("switch.empty", JavaElementKind.fromElement(block).subject()));
|
||||
public static final Simple<PsiSwitchBlock> SWITCH_INCOMPLETE = error(PsiSwitchBlock.class, "switch.incomplete")
|
||||
.withAnchor(block -> requireNonNullElse(block.getExpression(), block.getFirstChild()))
|
||||
.withDescription(psi -> message("switch.incomplete", JavaElementKind.fromElement(psi).subject()));
|
||||
|
||||
public static final Simple<PsiReferenceExpression> EXPRESSION_EXPECTED = error("expression.expected");
|
||||
public static final Parameterized<PsiReferenceExpression, PsiSuperExpression> EXPRESSION_SUPER_UNQUALIFIED_DEFAULT_METHOD =
|
||||
|
||||
+2
-40
@@ -10,7 +10,6 @@ import com.intellij.codeInspection.ex.GlobalInspectionContextBase;
|
||||
import com.intellij.java.codeserver.highlighting.JavaErrorCollector;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaErrorHighlightType;
|
||||
import com.intellij.lang.annotation.HighlightSeverity;
|
||||
import com.intellij.lang.injection.InjectedLanguageManager;
|
||||
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
@@ -19,17 +18,13 @@ import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.ui.ColorUtil;
|
||||
import com.intellij.ui.NewUI;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.NamedColorUtil;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Map;
|
||||
@@ -43,13 +38,10 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
private final Map<String, String> myTooltipStyles = initTooltipStyles();
|
||||
|
||||
private @NotNull HighlightInfoHolder myHolder;
|
||||
private @NotNull LanguageLevel myLanguageLevel;
|
||||
|
||||
private @NotNull PsiFile myFile;
|
||||
private JavaErrorCollector myCollector;
|
||||
|
||||
private boolean myHasError; // true if myHolder.add() was called with HighlightInfo of >=ERROR severity. On each .visit(PsiElement) call this flag is reset. Useful to determine whether the error was already reported while visiting this PsiElement.
|
||||
|
||||
protected HighlightVisitorImpl() {
|
||||
}
|
||||
|
||||
@@ -68,11 +60,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
"color: " + ColorUtil.toHtmlColor(NamedColorUtil.getErrorForeground()));
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
private boolean hasErrorResults() {
|
||||
return myHasError;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #HighlightVisitorImpl()}
|
||||
*/
|
||||
@@ -99,7 +86,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
|
||||
@Override
|
||||
public void visit(@NotNull PsiElement element) {
|
||||
myHasError = false;
|
||||
element.accept(this);
|
||||
}
|
||||
|
||||
@@ -137,9 +123,8 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
private void prepare(@NotNull HighlightInfoHolder holder, @NotNull PsiFile file) {
|
||||
myHolder = holder;
|
||||
myFile = file;
|
||||
myLanguageLevel = PsiUtil.getLanguageLevel(file);
|
||||
JavaErrorFixProvider errorFixProvider = JavaErrorFixProvider.getInstance();
|
||||
myCollector = new JavaErrorCollector(myFile, error -> reportError(error, errorFixProvider));
|
||||
myCollector = new JavaErrorCollector(file, error -> reportError(error, errorFixProvider));
|
||||
}
|
||||
|
||||
private void reportError(JavaCompilationError<?, ?> error, JavaErrorFixProvider errorFixProvider) {
|
||||
@@ -178,7 +163,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
.or(() -> error.psiForKind(TYPE_UNKNOWN_CLASS).map(PsiTypeElement::getInnermostComponentReferenceElement))
|
||||
.or(() -> error.psiForKind(CALL_AMBIGUOUS_NO_MATCH, CALL_UNRESOLVED).map(PsiMethodCallExpression::getMethodExpression))
|
||||
.ifPresent(ref -> UnresolvedReferenceQuickFixProvider.registerUnresolvedReferenceLazyQuickFixes(ref, info));
|
||||
add(info);
|
||||
myHolder.add(info.create());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -186,32 +171,9 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
myCollector.processElement(element);
|
||||
}
|
||||
|
||||
private boolean add(@Nullable HighlightInfo.Builder builder) {
|
||||
if (builder != null) {
|
||||
HighlightInfo info = builder.create();
|
||||
if (info != null && info.getSeverity().compareTo(HighlightSeverity.ERROR) >= 0) {
|
||||
myHasError = true;
|
||||
}
|
||||
return myHolder.add(info);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) {
|
||||
// Necessary to call visitElement, as super-implementation is empty
|
||||
visitElement(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSwitchStatement(@NotNull PsiSwitchStatement statement) {
|
||||
super.visitSwitchStatement(statement);
|
||||
if (!hasErrorResults()) SwitchBlockHighlightingModel.checkExhaustiveness(statement, builder -> add(builder));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSwitchExpression(@NotNull PsiSwitchExpression expression) {
|
||||
super.visitSwitchExpression(expression);
|
||||
if (!hasErrorResults()) SwitchBlockHighlightingModel.checkExhaustiveness(expression, builder -> add(builder));
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -765,6 +765,12 @@ final class JavaErrorFixProvider {
|
||||
: myFactory.createDeleteDefaultFix(null, error.psi()));
|
||||
fix(SWITCH_UNCONDITIONAL_PATTERN_AND_BOOLEAN, error -> myFactory.createDeleteSwitchLabelFix(error.psi()));
|
||||
fix(SWITCH_DEFAULT_AND_BOOLEAN, error -> myFactory.createDeleteDefaultFix(null, error.psi()));
|
||||
JavaFixesPusher<PsiSwitchBlock, Void> switchFixes = (error, sink) -> {
|
||||
sink.accept(myFactory.createAddSwitchDefaultFix(error.psi(), null));
|
||||
HighlightFixUtil.addCompletenessFixes(error.psi(), sink);
|
||||
};
|
||||
fixes(SWITCH_EMPTY, switchFixes);
|
||||
fixes(SWITCH_INCOMPLETE, switchFixes);
|
||||
}
|
||||
|
||||
private void createAccessFixes() {
|
||||
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
// Copyright 2000-2024 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.analysis;
|
||||
|
||||
import com.intellij.codeInsight.ExpressionUtil;
|
||||
import com.intellij.codeInsight.daemon.JavaErrorBundle;
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.QuickFixFactory;
|
||||
import com.intellij.java.codeserver.core.JavaPsiSwitchUtil;
|
||||
import com.intellij.psi.PsiCodeBlock;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiSwitchBlock;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.intellij.java.codeserver.core.JavaPatternExhaustivenessUtil.hasExhaustivenessError;
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
|
||||
public final class SwitchBlockHighlightingModel {
|
||||
|
||||
static void checkExhaustiveness(@NotNull PsiSwitchBlock block, @NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
|
||||
PsiCodeBlock body = block.getBody();
|
||||
if (body == null) return;
|
||||
|
||||
if (!ExpressionUtil.isEnhancedSwitch(block)) return;
|
||||
if (JavaPsiSwitchUtil.getUnconditionalPatternLabel(block) != null) return;
|
||||
if (JavaPsiSwitchUtil.findDefaultElement(block) != null) return;
|
||||
if (!hasExhaustivenessError(block)) return;
|
||||
|
||||
boolean hasAnyCaseLabels = JavaPsiSwitchUtil.hasAnyCaseLabels(block);
|
||||
@PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String messageKey;
|
||||
boolean isSwitchExpr = block instanceof PsiExpression;
|
||||
if (hasAnyCaseLabels) {
|
||||
messageKey = isSwitchExpr ? "switch.expr.incomplete" : "switch.statement.incomplete";
|
||||
}
|
||||
else {
|
||||
messageKey = isSwitchExpr ? "switch.expr.empty" : "switch.statement.empty";
|
||||
}
|
||||
PsiElement anchor = requireNonNullElse(block.getExpression(), block.getFirstChild());
|
||||
HighlightInfo.Builder info =
|
||||
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(anchor).descriptionAndTooltip(JavaErrorBundle.message(messageKey));
|
||||
IntentionAction action = QuickFixFactory.getInstance().createAddSwitchDefaultFix(block, null);
|
||||
info.registerFix(action, null, null, null, null);
|
||||
HighlightFixUtil.addCompletenessFixes(block, fix -> info.registerFix(fix.asIntention(), null, null, null, null));
|
||||
errorSink.accept(info);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -241,8 +241,8 @@ class Main {
|
||||
System.out.println("ok");
|
||||
};
|
||||
|
||||
str = switch (d) {
|
||||
case MONDAY, TUESDAY -> <error descr="Bad type in switch expression: void cannot be converted to java.lang.String">System.out.println("ok")</error>;
|
||||
str = switch (<error descr="'switch' expression does not cover all possible input values">d</error>) {
|
||||
case MONDAY, TUESDAY -> System.out.println("ok");
|
||||
};
|
||||
str = switch (<error descr="'switch' expression does not cover all possible input values">d</error>) {
|
||||
case MONDAY, TUESDAY -> "ok";
|
||||
|
||||
+2
-2
@@ -246,8 +246,8 @@ class Main {
|
||||
System.out.println("ok");
|
||||
}
|
||||
|
||||
str = switch (d) {
|
||||
case MONDAY, TUESDAY -> <error descr="Bad type in switch expression: void cannot be converted to java.lang.String">System.out.println("ok")</error>;
|
||||
str = switch (<error descr="'switch' expression does not cover all possible input values">d</error>) {
|
||||
case MONDAY, TUESDAY -> System.out.println("ok");
|
||||
};
|
||||
str = switch (d) {
|
||||
case MONDAY, TUESDAY, WEDNESDAY -> "ok";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Test {
|
||||
int testIncomplete(Object obj) {
|
||||
return <error descr="Switch expression should produce a result in all execution paths">switch</error>(obj) {
|
||||
return switch(<error descr="'switch' expression does not cover all possible input values">obj</error>) {
|
||||
case String s when<EOLError descr="Expression expected"></EOLError><EOLError descr="':' or '->' expected"></EOLError>
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Test {
|
||||
int testIncomplete(Object obj) {
|
||||
return <error descr="Switch expression should produce a result in all execution paths">switch</error>(obj) {
|
||||
return switch(<error descr="'switch' expression does not cover all possible input values">obj</error>) {
|
||||
case String s when<EOLError descr="Expression expected"></EOLError><EOLError descr="':' or '->' expected"></EOLError>
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user