From 0831efe6c297a0bf68d31d9863c05e7c9da625a3 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Mon, 27 Jan 2025 13:50:54 +0100 Subject: [PATCH] [java-highlighting] checkUnderscore, checkUnnamedVariableDeclaration migrated Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: c6dbe7c307f68e1270c5c6b6b5e7972e9c3cf417 --- .../JavaCompilationErrorBundle.properties | 9 +++ .../highlighting/ExpressionChecker.java | 52 ++++++++++++++ .../highlighting/JavaErrorVisitor.java | 9 ++- .../highlighting/errors/JavaErrorKinds.java | 29 ++++++++ .../daemon/impl/analysis/HighlightUtil.java | 67 ------------------- .../impl/analysis/HighlightVisitorImpl.java | 10 --- .../impl/analysis/JavaErrorFixProvider.java | 6 ++ 7 files changed, 104 insertions(+), 78 deletions(-) diff --git a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties index a9cac126718b..edd4a3aa9d1a 100644 --- a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties +++ b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties @@ -313,3 +313,12 @@ import.single.static.field.already.defined=Field ''{0}'' is already defined in a import.single.static.field.ambiguous=Field ''{0}'' is ambiguous in a single static import import.static.on.demand.resolves.to.class=Class {0} not found +underscore.identifier=Since Java 9, '_' is a keyword, and may not be used as an identifier +underscore.identifier.unnamed=Using '_' as a reference is not allowed +underscore.identifier.lambda=Use of '_' as a lambda parameter name is not allowed + +unnamed.field.not.allowed=Unnamed field is not allowed +unnamed.method.parameter.not.allowed=Unnamed method parameter is not allowed +unnamed.variable.not.allowed.in.this.context=Unnamed variable declaration is not allowed in this context +unnamed.variable.brackets=Brackets are not allowed after an unnamed variable declaration +unnamed.variable.without.initializer=Unnamed variable declaration must have an initializer diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ExpressionChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ExpressionChecker.java index c7d327215ae6..5fd877fada3e 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ExpressionChecker.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ExpressionChecker.java @@ -3,12 +3,14 @@ package com.intellij.java.codeserver.highlighting; import com.intellij.codeInsight.ExceptionUtil; import com.intellij.core.JavaPsiBundle; +import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind; import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds; import com.intellij.java.codeserver.highlighting.errors.JavaIncompatibleTypeErrorContext; import com.intellij.java.codeserver.highlighting.errors.JavaMismatchedCallContext; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.util.Pair; import com.intellij.pom.java.JavaFeature; +import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.impl.IncompleteModelUtil; import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession; @@ -724,6 +726,56 @@ final class ExpressionChecker { } } + void checkUnderscore(@NotNull PsiIdentifier identifier) { + if ("_".equals(identifier.getText())) { + PsiElement parent = identifier.getParent(); + LanguageLevel languageLevel = myVisitor.languageLevel(); + if (languageLevel.isAtLeast(LanguageLevel.JDK_1_9) && !(parent instanceof PsiUnnamedPattern) && + !(parent instanceof PsiVariable var && var.isUnnamed())) { + JavaErrorKind.Simple text = myVisitor.isApplicable(JavaFeature.UNNAMED_PATTERNS_AND_VARIABLES) ? + JavaErrorKinds.UNDERSCORE_IDENTIFIER_UNNAMED : + JavaErrorKinds.UNDERSCORE_IDENTIFIER; + myVisitor.report(text.create(identifier)); + } + else if (myVisitor.isApplicable(JavaFeature.LAMBDA_EXPRESSIONS)) { + if (parent instanceof PsiParameter parameter && parameter.getDeclarationScope() instanceof PsiLambdaExpression && + !parameter.isUnnamed()) { + myVisitor.report(JavaErrorKinds.UNDERSCORE_IDENTIFIER_LAMBDA.create(identifier)); + } + } + } + } + + private static boolean isArrayDeclaration(@NotNull PsiVariable variable) { + // Java-style 'var' arrays are prohibited by the parser; for C-style ones, looking for a bracket is enough + return ContainerUtil.or(variable.getChildren(), e -> PsiUtil.isJavaToken(e, JavaTokenType.LBRACKET)); + } + + void checkUnnamedVariableDeclaration(@NotNull PsiVariable variable) { + if (isArrayDeclaration(variable)) { + myVisitor.report(JavaErrorKinds.UNNAMED_VARIABLE_BRACKETS.create(variable)); + return; + } + if (variable instanceof PsiPatternVariable) return; + if (variable instanceof PsiResourceVariable) return; + if (variable instanceof PsiLocalVariable local) { + if (local.getInitializer() == null) { + myVisitor.report(JavaErrorKinds.UNNAMED_VARIABLE_WITHOUT_INITIALIZER.create(local)); + } + } + else if (variable instanceof PsiParameter parameter) { + if (parameter.getDeclarationScope() instanceof PsiMethod) { + myVisitor.report(JavaErrorKinds.UNNAMED_METHOD_PARAMETER_NOT_ALLOWED.create(parameter)); + } + } + else if (variable instanceof PsiField field) { + myVisitor.report(JavaErrorKinds.UNNAMED_FIELD_NOT_ALLOWED.create(field)); + } + else { + myVisitor.report(JavaErrorKinds.UNNAMED_VARIABLE_NOT_ALLOWED_IN_THIS_CONTEXT.create(variable)); + } + } + private static @NotNull PsiJavaCodeReferenceElement getOuterReferenceParent(@NotNull PsiJavaCodeReferenceElement ref) { PsiJavaCodeReferenceElement element = ref; while (true) { diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java index c6aac7bc038d..fa2090ae1c59 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java @@ -386,7 +386,13 @@ final class JavaErrorVisitor extends JavaElementVisitor { public void visitIdentifier(@NotNull PsiIdentifier identifier) { PsiElement parent = identifier.getParent(); if (parent instanceof PsiVariable variable) { - if (variable instanceof PsiField field) { + if (variable.isUnnamed()) { + checkFeature(variable, JavaFeature.UNNAMED_PATTERNS_AND_VARIABLES); + if (!hasErrorResults()) { + myExpressionChecker.checkUnnamedVariableDeclaration(variable); + } + } + else if (variable instanceof PsiField field) { myClassChecker.checkImplicitClassMember(field); } } @@ -400,6 +406,7 @@ final class JavaErrorVisitor extends JavaElementVisitor { else if (parent instanceof PsiMethod method) { myClassChecker.checkImplicitClassMember(method); } + myExpressionChecker.checkUnderscore(identifier); } @Override diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java index 211201f7be9d..bfd58c74db50 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java @@ -11,6 +11,7 @@ import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.io.FileUtil; import com.intellij.pom.java.JavaFeature; import com.intellij.psi.*; +import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.*; import com.intellij.util.VisibilityUtil; import org.jetbrains.annotations.Nls; @@ -21,6 +22,7 @@ import org.jetbrains.annotations.PropertyKey; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.intellij.java.codeserver.highlighting.JavaCompilationErrorBundle.message; import static com.intellij.java.codeserver.highlighting.errors.JavaErrorFormatUtil.*; @@ -885,6 +887,33 @@ public final class JavaErrorKinds { error(PsiJavaCodeReferenceElement.class, "import.static.on.demand.resolves.to.class") .withAnchor(ref -> requireNonNullElse(ref.getReferenceNameElement(), ref)) .withRawDescription(ref -> message("import.static.on.demand.resolves.to.class", ref.getCanonicalText())); + + public static final Simple UNDERSCORE_IDENTIFIER = error("underscore.identifier"); + public static final Simple UNDERSCORE_IDENTIFIER_UNNAMED = error("underscore.identifier.unnamed"); + public static final Simple UNDERSCORE_IDENTIFIER_LAMBDA = error("underscore.identifier.lambda"); + + public static final Simple UNNAMED_VARIABLE_BRACKETS = + error(PsiVariable.class, "unnamed.variable.brackets") + .withRange(var -> { + TokenSet brackets = TokenSet.create(JavaTokenType.LBRACKET, JavaTokenType.RBRACKET); + return Stream.of(var.getChildren()) + .filter(t -> PsiUtil.isJavaToken(t, brackets)) + .map(PsiElement::getTextRangeInParent) + .reduce(TextRange::union) + .orElseThrow(); // Must have at least one + }); + public static final Simple UNNAMED_VARIABLE_WITHOUT_INITIALIZER = + error(PsiLocalVariable.class, "unnamed.variable.without.initializer") + .withRange(var -> TextRange.create(0, requireNonNull(var.getNameIdentifier()).getTextRangeInParent().getEndOffset())); + public static final Simple UNNAMED_FIELD_NOT_ALLOWED = + error(PsiField.class, "unnamed.field.not.allowed") + .withRange(var -> TextRange.create(0, requireNonNull(var.getNameIdentifier()).getTextRangeInParent().getEndOffset())); + public static final Simple UNNAMED_METHOD_PARAMETER_NOT_ALLOWED = + error(PsiParameter.class, "unnamed.method.parameter.not.allowed") + .withRange(var -> TextRange.create(0, requireNonNull(var.getNameIdentifier()).getTextRangeInParent().getEndOffset())); + public static final Simple UNNAMED_VARIABLE_NOT_ALLOWED_IN_THIS_CONTEXT = + error(PsiVariable.class, "unnamed.variable.not.allowed.in.this.context") + .withRange(var -> TextRange.create(0, requireNonNull(var.getNameIdentifier()).getTextRangeInParent().getEndOffset())); private static @NotNull Simple error( @NotNull @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) String key) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java index ded9bc413d3a..2ac74cc845e0 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java @@ -20,7 +20,6 @@ import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixUpdater; import com.intellij.codeInspection.dataFlow.fix.RedundantInstanceofFix; import com.intellij.core.JavaPsiBundle; import com.intellij.ide.IdeBundle; -import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.modcommand.ModCommandAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.LanguageLevelUtil; @@ -47,7 +46,6 @@ import com.intellij.psi.scope.processor.VariablesNotProcessor; import com.intellij.psi.scope.util.PsiScopesUtil; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.tree.IElementType; -import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.*; import com.intellij.refactoring.util.RefactoringChangeUtil; import com.intellij.ui.ColorUtil; @@ -63,7 +61,6 @@ import com.siyeh.ig.psiutils.ControlFlowUtils; import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.InstanceOfUtils; import com.siyeh.ig.psiutils.VariableAccessUtils; -import one.util.streamex.StreamEx; import org.jetbrains.annotations.*; import java.awt.*; @@ -639,70 +636,6 @@ public final class HighlightUtil { return null; } - static HighlightInfo.Builder checkUnderscore(@NotNull PsiIdentifier identifier, @NotNull LanguageLevel languageLevel) { - if ("_".equals(identifier.getText())) { - PsiElement parent = identifier.getParent(); - if (languageLevel.isAtLeast(LanguageLevel.JDK_1_9) && !(parent instanceof PsiUnnamedPattern) && - !(parent instanceof PsiVariable var && var.isUnnamed())) { - String text = JavaFeature.UNNAMED_PATTERNS_AND_VARIABLES.isSufficient(languageLevel) ? - JavaErrorBundle.message("underscore.identifier.error.unnamed") : - JavaErrorBundle.message("underscore.identifier.error"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(text); - } - else if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { - if (parent instanceof PsiParameter parameter && parameter.getDeclarationScope() instanceof PsiLambdaExpression && - !parameter.isUnnamed()) { - String text = JavaErrorBundle.message("underscore.lambda.identifier"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(text); - } - } - } - - return null; - } - - static HighlightInfo.Builder checkUnnamedVariableDeclaration(@NotNull PsiVariable variable) { - if (isArrayDeclaration(variable)) { - IntentionAction fix = new NormalizeBracketsFix(variable).asIntention(); - TokenSet brackets = TokenSet.create(JavaTokenType.LBRACKET, JavaTokenType.RBRACKET); - TextRange range = StreamEx.of(variable.getChildren()) - .filter(t -> PsiUtil.isJavaToken(t, brackets)) - .map(PsiElement::getTextRangeInParent) - .reduce(TextRange::union) - .orElseThrow() - .shiftRight(variable.getTextRange().getStartOffset());// Must have at least one - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip( - JavaAnalysisBundle.message("error.unnamed.variable.brackets")).registerFix(fix, null, null, null, null); - } - if (variable instanceof PsiPatternVariable) return null; - if (variable instanceof PsiResourceVariable) return null; - String message; - IntentionAction fix = null; - if (variable instanceof PsiLocalVariable local) { - if (local.getInitializer() != null) return null; - message = JavaAnalysisBundle.message("error.unnamed.variable.without.initializer"); - fix = getFixFactory().createAddVariableInitializerFix(local); - } - else if (variable instanceof PsiParameter parameter) { - PsiElement scope = parameter.getDeclarationScope(); - if (!(scope instanceof PsiMethod)) return null; - message = JavaAnalysisBundle.message("error.unnamed.method.parameter.not.allowed"); - } - else if (variable instanceof PsiField) { - message = JavaAnalysisBundle.message("error.unnamed.field.not.allowed"); - } - else { - message = JavaAnalysisBundle.message("error.unnamed.variable.not.allowed.in.this.context"); - } - TextRange range = TextRange.create(variable.getTextRange().getStartOffset(), - Objects.requireNonNull(variable.getNameIdentifier()).getTextRange().getEndOffset()); - HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message); - if (fix != null) { - builder.registerFix(fix, null, null, null, null); - } - return builder; - } - public static @NotNull @NlsSafe String formatClass(@NotNull PsiClass aClass) { return formatClass(aClass, true); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java index 796a6fed6ad3..76224b54cbce 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java @@ -565,14 +565,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh PsiElement parent = identifier.getParent(); if (parent instanceof PsiVariable variable) { add(HighlightUtil.checkVariableAlreadyDefined(variable)); - if (variable.isUnnamed()) { - HighlightInfo.Builder notAvailable = checkFeature(variable, JavaFeature.UNNAMED_PATTERNS_AND_VARIABLES); - if (notAvailable != null) { - add(notAvailable); - } else { - add(HighlightUtil.checkUnnamedVariableDeclaration(variable)); - } - } } else if (parent instanceof PsiClass aClass) { if (!hasErrorResults() && JavaFeature.EXTENSION_METHODS.isSufficient(myLanguageLevel)) { @@ -600,8 +592,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh } } - add(HighlightUtil.checkUnderscore(identifier, myLanguageLevel)); - super.visitIdentifier(identifier); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java index 29dabb0c74b6..d22556c5213a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java @@ -109,6 +109,7 @@ final class JavaErrorFixProvider { createConstructorFixes(); createMethodFixes(); createExpressionFixes(); + createVariableFixes(); createExceptionFixes(); createGenericFixes(); createRecordFixes(); @@ -244,6 +245,11 @@ final class JavaErrorFixProvider { fix(MODIFIER_INCOMPATIBLE, removeModifier); fix(MODIFIER_NOT_ALLOWED_NON_SEALED, removeModifier); } + + private void createVariableFixes() { + fix(UNNAMED_VARIABLE_BRACKETS, error -> new NormalizeBracketsFix(error.psi())); + fix(UNNAMED_VARIABLE_WITHOUT_INITIALIZER, error -> myFactory.createAddVariableInitializerFix(error.psi())); + } private void createExpressionFixes() { fix(NEW_EXPRESSION_QUALIFIED_MALFORMED, error -> myFactory.createRemoveNewQualifierFix(error.psi(), null));