From 96b87e85a59f6ede515d8efaf091586ce363dc9a Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 28 Feb 2025 11:45:38 +0100 Subject: [PATCH] [java-highlighting] fix-related code moved to HighlightFixUtil Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: fcb88cf8e0bb3aa976d675f28863a4ec292c5df5 --- .../core/JavaPatternExhaustivenessUtil.java | 10 +- .../codeserver/core/JavaPsiSwitchUtil.java | 40 ++-- .../highlighting/SwitchChecker.java | 12 +- .../impl/analysis/HighlightFixUtil.java | 187 +++++++++++++--- .../SwitchBlockHighlightingModel.java | 203 ++---------------- 5 files changed, 210 insertions(+), 242 deletions(-) diff --git a/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPatternExhaustivenessUtil.java b/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPatternExhaustivenessUtil.java index f62ec813b336..2a0de4f71e19 100644 --- a/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPatternExhaustivenessUtil.java +++ b/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPatternExhaustivenessUtil.java @@ -735,7 +735,15 @@ public final class JavaPatternExhaustivenessUtil { /** * @param block switch block to analyze - * @param elements list of labels to analyze (can be a subset of all labels of block) + * @return true if this block is not exhaustive while it should be + */ + public static boolean hasExhaustivenessError(@NotNull PsiSwitchBlock block) { + return hasExhaustivenessError(block, JavaPsiSwitchUtil.getCaseLabelElements(block)); + } + + /** + * @param block switch block to analyze + * @param elements list of labels to analyze (can be a subset of all labels of the block) * @return true if this block is not exhaustive while it should be */ public static boolean hasExhaustivenessError(@NotNull PsiSwitchBlock block, @NotNull List elements) { diff --git a/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiSwitchUtil.java b/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiSwitchUtil.java index 8414dd6db57d..2e013966d11e 100644 --- a/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiSwitchUtil.java +++ b/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiSwitchUtil.java @@ -93,20 +93,17 @@ public final class JavaPsiSwitchUtil { return false; } - static @Nullable PsiEnumConstant getEnumConstant(@Nullable PsiElement element) { - if (element instanceof PsiReferenceExpression) { - return ObjectUtils.tryCast(((PsiReferenceExpression)element).resolve(), PsiEnumConstant.class); + static @Nullable PsiEnumConstant getEnumConstant(@Nullable PsiCaseLabelElement element) { + if (element instanceof PsiReferenceExpression ref && ref.resolve() instanceof PsiEnumConstant enumConstant) { + return enumConstant; } return null; } private static @Nullable Object getBranchConstant(@NotNull PsiCaseLabelElement labelElement, @NotNull PsiType selectorType) { if (labelElement instanceof PsiExpression expr) { - if (expr instanceof PsiReferenceExpression) { - PsiEnumConstant enumConstant = getEnumConstant(expr); - if (enumConstant != null) { - return enumConstant; - } + if (expr instanceof PsiReferenceExpression ref && ref.resolve() instanceof PsiEnumConstant constant) { + return constant; } Object operand = JavaPsiFacade.getInstance(labelElement.getProject()).getConstantEvaluationHelper() .computeConstantExpression(labelElement, false); @@ -216,11 +213,7 @@ public final class JavaPsiSwitchUtil { private static boolean isConstantLabelElement(@NotNull PsiCaseLabelElement labelElement) { Object value = JavaPsiFacade.getInstance(labelElement.getProject()).getConstantEvaluationHelper() .computeConstantExpression(labelElement, false); - return value != null || isEnumConstant(labelElement); - } - - private static boolean isEnumConstant(@NotNull PsiCaseLabelElement element) { - return getEnumConstant(element) != null; + return value != null || getEnumConstant(labelElement) != null; } /** @@ -383,7 +376,8 @@ public final class JavaPsiSwitchUtil { /** * @param block the switch block - * @return a list of switch branches consisting of either {@link PsiSwitchLabelStatementBase} or {@link PsiCaseLabelElement} + * @return a list of switch branches consisting of either {@link PsiSwitchLabelStatementBase} for default case, + * or {@link PsiCaseLabelElement} */ public static @NotNull List getSwitchBranches(@NotNull PsiSwitchBlock block) { final PsiCodeBlock body = block.getBody(); @@ -402,6 +396,24 @@ public final class JavaPsiSwitchUtil { return result; } + /** + * @param block the switch block + * @return a list of all case labels within this switch block (default branches are naturally not returned). + */ + public static @NotNull List getCaseLabelElements(@NotNull PsiSwitchBlock block) { + PsiCodeBlock body = block.getBody(); + if (body == null) return List.of(); + List elementsToCheckCompleteness = new ArrayList<>(); + for (PsiStatement st : body.getStatements()) { + if (!(st instanceof PsiSwitchLabelStatementBase labelStatement) || labelStatement.isDefaultCase()) continue; + PsiCaseLabelElementList labelElementList = labelStatement.getCaseLabelElementList(); + if (labelElementList != null) { + Collections.addAll(elementsToCheckCompleteness, labelElementList.getElements()); + } + } + return elementsToCheckCompleteness; + } + /** * Kinds of switch selector * @see #getSwitchSelectorKind(PsiType) diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/SwitchChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/SwitchChecker.java index 8f2e4f4011fc..008a1414e22c 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/SwitchChecker.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/SwitchChecker.java @@ -328,18 +328,8 @@ final class SwitchChecker { } } - static @Nullable PsiEnumConstant getEnumConstant(@Nullable PsiElement element) { - if (element instanceof PsiReferenceExpression referenceExpression && - referenceExpression.resolve() instanceof PsiEnumConstant enumConstant) { - return enumConstant; - } - return null; - } - static @Nullable String evaluateEnumConstantName(@NotNull PsiReferenceExpression expr) { - PsiEnumConstant enumConstant = getEnumConstant(expr); - if (enumConstant != null) return enumConstant.getName(); - return null; + return expr.resolve() instanceof PsiEnumConstant enumConstant ? enumConstant.getName() : null; } private static @Nullable Object evaluateConstant(@NotNull PsiCaseLabelElement constant) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightFixUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightFixUtil.java index 0b2e7d9931f6..e1c713f5b372 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightFixUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightFixUtil.java @@ -8,7 +8,10 @@ import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.intention.QuickFixFactory; import com.intellij.codeInsight.intention.impl.PriorityIntentionActionWrapper; import com.intellij.codeInsight.quickfix.ChangeVariableTypeQuickFixProvider; +import com.intellij.java.codeserver.core.JavaPatternExhaustivenessUtil; import com.intellij.java.codeserver.core.JavaPsiModifierUtil; +import com.intellij.java.codeserver.core.JavaPsiSealedUtil; +import com.intellij.java.codeserver.core.JavaPsiSwitchUtil; import com.intellij.lang.jvm.JvmModifier; import com.intellij.lang.jvm.actions.JvmElementActionFactories; import com.intellij.lang.jvm.actions.MemberRequestsKt; @@ -27,17 +30,15 @@ import com.intellij.psi.infos.MethodCandidateInfo; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.PsiShortNamesCache; import com.intellij.psi.util.*; +import com.intellij.psi.util.InheritanceUtil; import com.intellij.refactoring.util.RefactoringChangeUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.JavaPsiConstructorUtil; -import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import com.siyeh.ig.callMatcher.CallMatcher; -import com.siyeh.ig.psiutils.ExpressionUtils; -import com.siyeh.ig.psiutils.InstanceOfUtils; -import com.siyeh.ig.psiutils.SwitchUtils; -import com.siyeh.ig.psiutils.VariableAccessUtils; +import com.siyeh.ig.psiutils.*; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -45,6 +46,8 @@ import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.function.Consumer; +import static com.intellij.java.codeserver.core.JavaPatternExhaustivenessUtil.checkRecordExhaustiveness; +import static com.intellij.java.codeserver.core.JavaPatternExhaustivenessUtil.findMissedClasses; import static com.intellij.util.ObjectUtils.tryCast; import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNullElse; @@ -353,8 +356,7 @@ public final class HighlightFixUtil { public static void registerFixesForExpressionStatement(@NotNull PsiElement statement, @NotNull Consumer info) { if (!(statement instanceof PsiExpressionStatement)) return; - PsiCodeBlock block = ObjectUtils.tryCast(statement.getParent(), PsiCodeBlock.class); - if (block == null) return; + if (!(statement.getParent() instanceof PsiCodeBlock block)) return; PsiExpression expression = ((PsiExpressionStatement)statement).getExpression(); if (expression instanceof PsiAssignmentExpression) return; PsiType type = expression.getType(); @@ -384,25 +386,22 @@ public final class HighlightFixUtil { PsiElement block = PsiUtil.getVariableCodeBlock(variable, null); if (block == null) return; PsiTreeUtil.processElements(block, PsiReferenceExpression.class, ref -> { - if (ref.isReferenceTo(variable)) { - PsiAssignmentExpression assignment = ObjectUtils.tryCast(ref.getParent(), PsiAssignmentExpression.class); - if (assignment != null) { - if (assignment.getLExpression() == ref && - assignment.getOperationTokenType() == JavaTokenType.EQ) { - PsiExpression rExpression = assignment.getRExpression(); - if (rExpression != null) { - PsiType type = rExpression.getType(); - if (type instanceof PsiPrimitiveType && !PsiTypes.voidType().equals(type) && variable.getInitializer() != null) { - type = ((PsiPrimitiveType)type).getBoxedType(variable); - } - if (type != null) { - type = GenericsUtil.getVariableTypeByExpressionType(type); - if (PsiTypesUtil.isDenotableType(type, variable) && !PsiTypes.voidType().equals(type)) { - info.accept(QuickFixFactory.getInstance().createSetVariableTypeFix(variable, type)); - } - return false; - } + if (ref.isReferenceTo(variable) && + ref.getParent() instanceof PsiAssignmentExpression assignment && + assignment.getLExpression() == ref && + assignment.getOperationTokenType() == JavaTokenType.EQ) { + PsiExpression rExpression = assignment.getRExpression(); + if (rExpression != null) { + PsiType type = rExpression.getType(); + if (type instanceof PsiPrimitiveType primitiveType && !PsiTypes.voidType().equals(type) && variable.getInitializer() != null) { + type = primitiveType.getBoxedType(variable); + } + if (type != null) { + type = GenericsUtil.getVariableTypeByExpressionType(type); + if (PsiTypesUtil.isDenotableType(type, variable) && !PsiTypes.voidType().equals(type)) { + info.accept(QuickFixFactory.getInstance().createSetVariableTypeFix(variable, type)); } + return false; } } } @@ -1009,6 +1008,144 @@ public final class HighlightFixUtil { return true; } + /** + * Registers actions to fix switch block completeness (exhaustiveness) + * + * @param block switch block which is not exhaustive + * @param info sink + */ + static void addCompletenessFixes(@NotNull PsiSwitchBlock block, @NotNull Consumer info) { + List elements = JavaPsiSwitchUtil.getCaseLabelElements(block); + PsiExpression selector = block.getExpression(); + if (selector == null) return; + PsiType selectorType = selector.getType(); + if (selectorType == null) return; + PsiClass selectorClass = PsiUtil.resolveClassInClassTypeOnly(TypeConversionUtil.erasure(selectorType)); + JavaPsiSwitchUtil.SelectorKind kind = JavaPsiSwitchUtil.getSwitchSelectorKind(selectorType); + if (selectorClass != null && kind == JavaPsiSwitchUtil.SelectorKind.ENUM) { + Set enumElements = StreamEx.of(elements) + .select(PsiReferenceExpression.class) + .map(PsiReferenceExpression::resolve) + .select(PsiEnumConstant.class) + .toSet(); + addEnumCompletenessFixes(block, selectorClass, enumElements, info); + return; + } + List sealedTypes = getAbstractSealedTypes(JavaPsiPatternUtil.deconstructSelectorType(selectorType)); + if (!sealedTypes.isEmpty()) { + addSealedClassCompletenessFixes(block, selectorType, elements, info); + return; + } + //records are final; checking intersections are not needed + if (selectorClass != null && selectorClass.isRecord()) { + if (checkRecordCaseSetNotEmpty(elements)) { + addRecordExhaustivenessFixes(block, elements, selectorType, selectorClass, info); + } + } + else { + if (kind == JavaPsiSwitchUtil.SelectorKind.BOOLEAN) { + QuickFixFactory factory = QuickFixFactory.getInstance(); + IntentionAction fix = factory.createAddMissingBooleanPrimitiveBranchesFix(block); + if (fix != null) { + info.accept(fix); + IntentionAction fixWithNull = factory.createAddMissingBooleanPrimitiveBranchesFixWithNull(block); + if (fixWithNull != null) { + info.accept(fixWithNull); + } + } + } + } + } + + private static void addEnumCompletenessFixes(@NotNull PsiSwitchBlock block, + @NotNull PsiClass selectorClass, + @NotNull Set enumElements, + @NotNull Consumer info) { + LinkedHashSet missingConstants = + StreamEx.of(selectorClass.getFields()).select(PsiEnumConstant.class).remove(enumElements::contains) + .map(PsiField::getName) + .toCollection(LinkedHashSet::new); + if (!missingConstants.isEmpty()) { + IntentionAction enumBranchesFix = QuickFixFactory.getInstance().createAddMissingEnumBranchesFix(block, missingConstants); + info.accept(PriorityIntentionActionWrapper.highPriority(enumBranchesFix)); + } + } + + private static @NotNull List getAbstractSealedTypes(@NotNull List selectorTypes) { + return selectorTypes.stream() + .filter(type -> { + PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(TypeConversionUtil.erasure(type)); + return psiClass != null && (JavaPsiSealedUtil.isAbstractSealed(psiClass)); + }) + .toList(); + } + + private static void addRecordExhaustivenessFixes(@NotNull PsiSwitchBlock block, + @NotNull List elements, + @NotNull PsiType selectorClassType, + @NotNull PsiClass selectorClass, + @NotNull Consumer info) { + JavaPatternExhaustivenessUtil.RecordExhaustivenessResult + exhaustivenessResult = checkRecordExhaustiveness(elements, selectorClassType, block); + + if (!exhaustivenessResult.isExhaustive() && exhaustivenessResult.canBeAdded()) { + IntentionAction fix = + QuickFixFactory.getInstance().createAddMissingRecordClassBranchesFix( + block, selectorClass, exhaustivenessResult.getMissedBranchesByType(), elements); + if (fix != null) { + info.accept(fix); + } + } + } + + private static boolean checkRecordCaseSetNotEmpty(@NotNull List elements) { + return ContainerUtil.exists(elements, element -> element instanceof PsiPattern pattern && !JavaPsiPatternUtil.isGuarded(pattern)); + } + + private static @NotNull Set getMissedClassesInSealedHierarchy(@NotNull PsiType selectorType, + @NotNull List elements, + @NotNull PsiSwitchBlock block) { + Set classes = findMissedClasses(block, selectorType, elements); + List missedSealedClasses = StreamEx.of(classes).sortedBy(t -> t.getQualifiedName()).toList(); + Set missedClasses = new LinkedHashSet<>(); + //if T is intersection types, it is allowed to choose any of them to cover + PsiExpression selector = requireNonNull(block.getExpression()); + for (PsiClass missedClass : missedSealedClasses) { + PsiClassType missedClassType = TypeUtils.getType(missedClass); + if (JavaPsiPatternUtil.covers(selector, missedClassType, selectorType)) { + missedClasses.clear(); + missedClasses.add(missedClass); + break; + } + else { + missedClasses.add(missedClass); + } + } + return missedClasses; + } + + private static void addSealedClassCompletenessFixes(@NotNull PsiSwitchBlock block, @NotNull PsiType selectorType, + @NotNull List elements, + @NotNull Consumer info) { + Set missedClasses = getMissedClassesInSealedHierarchy(selectorType, elements, block); + List allNames = collectLabelElementNames(elements, missedClasses); + Set missingCases = ContainerUtil.map2LinkedSet(missedClasses, PsiClass::getQualifiedName); + QuickFixFactory factory = QuickFixFactory.getInstance(); + info.accept(factory.createAddMissingSealedClassBranchesFix(block, missingCases, allNames)); + IntentionAction fixWithNull = factory.createAddMissingSealedClassBranchesFixWithNull(block, missingCases, allNames); + if (fixWithNull != null) { + info.accept(fixWithNull); + } + } + + private static @NotNull List collectLabelElementNames(@NotNull List elements, + @NotNull Set missingClasses) { + return StreamEx.of(elements).map(PsiElement::getText) + .append(StreamEx.of(missingClasses).map(PsiClass::getQualifiedName)) + .distinct() + .toList(); + } + private static final class ReturnModel { final PsiReturnStatement myStatement; final PsiType myType; diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java index 4f9e2546df15..c0063be15123 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java @@ -7,26 +7,17 @@ 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.codeInsight.intention.impl.PriorityIntentionActionWrapper; -import com.intellij.java.codeserver.core.JavaPsiSealedUtil; import com.intellij.java.codeserver.core.JavaPsiSwitchUtil; -import com.intellij.openapi.util.NlsContexts; -import com.intellij.psi.*; -import com.intellij.psi.util.JavaPsiPatternUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.psi.util.TypeConversionUtil; -import com.intellij.util.containers.ContainerUtil; -import com.siyeh.ig.psiutils.TypeUtils; -import one.util.streamex.StreamEx; +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.Nullable; import org.jetbrains.annotations.PropertyKey; -import java.util.*; import java.util.function.Consumer; -import static com.intellij.java.codeserver.core.JavaPatternExhaustivenessUtil.*; -import static java.util.Objects.requireNonNull; +import static com.intellij.java.codeserver.core.JavaPatternExhaustivenessUtil.hasExhaustivenessError; import static java.util.Objects.requireNonNullElse; public final class SwitchBlockHighlightingModel { @@ -34,8 +25,7 @@ public final class SwitchBlockHighlightingModel { if (!ExpressionUtil.isEnhancedSwitch(block)) return false; if (JavaPsiSwitchUtil.getUnconditionalPatternLabel(block) != null) return false; if (JavaPsiSwitchUtil.findDefaultElement(block) != null) return false; - List elementsToCheckCompleteness = getCaseLabelElements(block); - return hasExhaustivenessError(block, elementsToCheckCompleteness); + return hasExhaustivenessError(block); } static void checkExhaustiveness(@NotNull PsiSwitchBlock block, @NotNull Consumer errorSink) { @@ -45,8 +35,7 @@ public final class SwitchBlockHighlightingModel { if (!ExpressionUtil.isEnhancedSwitch(block)) return; if (JavaPsiSwitchUtil.getUnconditionalPatternLabel(block) != null) return; if (JavaPsiSwitchUtil.findDefaultElement(block) != null) return; - List elementsToCheckCompleteness = getCaseLabelElements(block); - if (!hasExhaustivenessError(block, elementsToCheckCompleteness)) return; + if (!hasExhaustivenessError(block)) return; boolean hasAnyCaseLabels = JavaPsiSwitchUtil.hasAnyCaseLabels(block); @PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String messageKey; @@ -57,180 +46,12 @@ public final class SwitchBlockHighlightingModel { else { messageKey = isSwitchExpr ? "switch.expr.empty" : "switch.statement.empty"; } - HighlightInfo.Builder info = createError(requireNonNullElse(block.getExpression(), block.getFirstChild()), - JavaErrorBundle.message(messageKey)); - IntentionAction action = getFixFactory().createAddSwitchDefaultFix(block, null); + 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); - addCompletenessFixes(block, elementsToCheckCompleteness, info); + HighlightFixUtil.addCompletenessFixes(block, fix -> info.registerFix(fix.asIntention(), null, null, null, null)); errorSink.accept(info); } - - private static @NotNull List getCaseLabelElements(@NotNull PsiSwitchBlock block) { - PsiCodeBlock body = block.getBody(); - if (body == null) return List.of(); - List elementsToCheckCompleteness = new ArrayList<>(); - for (PsiStatement st : body.getStatements()) { - if (!(st instanceof PsiSwitchLabelStatementBase labelStatement) || labelStatement.isDefaultCase()) continue; - PsiCaseLabelElementList labelElementList = labelStatement.getCaseLabelElementList(); - if (labelElementList != null) { - Collections.addAll(elementsToCheckCompleteness, labelElementList.getElements()); - } - } - return elementsToCheckCompleteness; - } - - static @Nullable PsiEnumConstant getEnumConstant(@Nullable PsiElement element) { - if (element instanceof PsiReferenceExpression referenceExpression && - referenceExpression.resolve() instanceof PsiEnumConstant enumConstant) { - return enumConstant; - } - return null; - } - - private static void addEnumCompletenessFixes(@NotNull PsiSwitchBlock block, - @NotNull PsiClass selectorClass, - @NotNull List enumElements, - HighlightInfo.Builder info) { - LinkedHashSet missingConstants = findMissingEnumConstant(selectorClass, enumElements); - if (!missingConstants.isEmpty()) { - IntentionAction enumBranchesFix = - getFixFactory().createAddMissingEnumBranchesFix(block, ContainerUtil.map2LinkedSet(missingConstants, PsiField::getName)); - IntentionAction fix = PriorityIntentionActionWrapper.highPriority(enumBranchesFix); - info.registerFix(fix, null, null, null, null); - } - } - - static @NotNull LinkedHashSet findMissingEnumConstant(@NotNull PsiClass selectorClass, - @NotNull List enumElements) { - LinkedHashSet missingConstants = - StreamEx.of(selectorClass.getFields()).select(PsiEnumConstant.class).toCollection(LinkedHashSet::new); - enumElements.forEach(missingConstants::remove); - return missingConstants; - } - - static @NotNull HighlightInfo.Builder createError(@NotNull PsiElement range, @NlsContexts.DetailedDescription @NotNull String message) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message); - } - - private static void addCompletenessFixes(@NotNull PsiSwitchBlock block, @NotNull List elements, - HighlightInfo.Builder info) { - PsiExpression selector = block.getExpression(); - if (selector == null) return; - PsiType selectorType = selector.getType(); - if (selectorType == null) return; - PsiClass selectorClass = PsiUtil.resolveClassInClassTypeOnly(TypeConversionUtil.erasure(selectorType)); - JavaPsiSwitchUtil.SelectorKind kind = JavaPsiSwitchUtil.getSwitchSelectorKind(selectorType); - if (selectorClass != null && kind == JavaPsiSwitchUtil.SelectorKind.ENUM) { - List enumElements = getEnumConstants(elements); - addEnumCompletenessFixes(block, selectorClass, enumElements, info); - return; - } - List sealedTypes = getAbstractSealedTypes(JavaPsiPatternUtil.deconstructSelectorType(selectorType)); - if (!sealedTypes.isEmpty()) { - addSealedClassCompletenessFixes(block, selectorType, elements, info); - return; - } - //records are final; checking intersections are not needed - if (selectorClass != null && selectorClass.isRecord()) { - if (checkRecordCaseSetNotEmpty(elements)) { - addRecordExhaustivenessFixes(block, elements, selectorType, selectorClass, info); - } - } - else { - if (kind == JavaPsiSwitchUtil.SelectorKind.BOOLEAN) { - IntentionAction fix = getFixFactory().createAddMissingBooleanPrimitiveBranchesFix(block); - if (fix != null) { - info.registerFix(fix, null, null, null, null); - IntentionAction fixWithNull = getFixFactory().createAddMissingBooleanPrimitiveBranchesFixWithNull(block); - if (fixWithNull != null) { - info.registerFix(fixWithNull, null, null, null, null); - } - } - } - } - } - - private static @NotNull List getAbstractSealedTypes(@NotNull List selectorTypes) { - return selectorTypes.stream() - .filter(type -> { - PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(TypeConversionUtil.erasure(type)); - return psiClass != null && (JavaPsiSealedUtil.isAbstractSealed(psiClass)); - }) - .toList(); - } - - private static void addRecordExhaustivenessFixes(@NotNull PsiSwitchBlock block, - @NotNull List elements, - @NotNull PsiType selectorClassType, - @NotNull PsiClass selectorClass, - HighlightInfo.Builder info) { - RecordExhaustivenessResult exhaustivenessResult = checkRecordExhaustiveness(elements, selectorClassType, block); - - if (!exhaustivenessResult.isExhaustive() && exhaustivenessResult.canBeAdded()) { - IntentionAction fix = - getFixFactory().createAddMissingRecordClassBranchesFix( - block, selectorClass, exhaustivenessResult.getMissedBranchesByType(), elements); - if (fix != null) { - info.registerFix(fix, null, null, null, null); - } - } - } - - private static boolean checkRecordCaseSetNotEmpty(@NotNull List elements) { - return ContainerUtil.exists(elements, element -> element instanceof PsiPattern pattern && !JavaPsiPatternUtil.isGuarded(pattern)); - } - - private static @NotNull List getEnumConstants(@NotNull List elements) { - return StreamEx.of(elements).map(element -> getEnumConstant(element)).nonNull().toList(); - } - - private static @NotNull Set getMissedClassesInSealedHierarchy(@NotNull PsiType selectorType, - @NotNull List elements, - @NotNull PsiSwitchBlock block) { - Set classes = findMissedClasses(block, selectorType, elements); - List missedSealedClasses = StreamEx.of(classes).sortedBy(t -> t.getQualifiedName()).toList(); - Set missedClasses = new LinkedHashSet<>(); - //if T is intersection types, it is allowed to choose any of them to cover - PsiExpression selector = requireNonNull(block.getExpression()); - for (PsiClass missedClass : missedSealedClasses) { - PsiClassType missedClassType = TypeUtils.getType(missedClass); - if (JavaPsiPatternUtil.covers(selector, missedClassType, selectorType)) { - missedClasses.clear(); - missedClasses.add(missedClass); - break; - } - else { - missedClasses.add(missedClass); - } - } - return missedClasses; - } - - private static void addSealedClassCompletenessFixes(@NotNull PsiSwitchBlock block, @NotNull PsiType selectorType, - @NotNull List elements, - HighlightInfo.Builder info) { - Set missedClasses = getMissedClassesInSealedHierarchy(selectorType, elements, block); - List allNames = collectLabelElementNames(elements, missedClasses); - Set missingCases = ContainerUtil.map2LinkedSet(missedClasses, PsiClass::getQualifiedName); - IntentionAction fix = getFixFactory().createAddMissingSealedClassBranchesFix(block, missingCases, allNames); - info.registerFix(fix, null, null, null, null); - IntentionAction fixWithNull = getFixFactory().createAddMissingSealedClassBranchesFixWithNull(block, missingCases, allNames); - if (fixWithNull != null) { - info.registerFix(fixWithNull, null, null, null, null); - } - } - - - private static @NotNull List collectLabelElementNames(@NotNull List elements, - @NotNull Set missingClasses) { - List result = new ArrayList<>(ContainerUtil.map(elements, PsiElement::getText)); - for (PsiClass aClass : missingClasses) { - result.add(aClass.getQualifiedName()); - } - return StreamEx.of(result).distinct().toList(); - } - - private static QuickFixFactory getFixFactory() { - return QuickFixFactory.getInstance(); - } }