[java-highlighting] checkUnderscore, checkUnnamedVariableDeclaration migrated

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

GitOrigin-RevId: c6dbe7c307f68e1270c5c6b6b5e7972e9c3cf417
This commit is contained in:
Tagir Valeev
2025-01-27 18:52:56 +00:00
committed by intellij-monorepo-bot
parent 60119c1cbf
commit 0831efe6c2
7 changed files with 104 additions and 78 deletions
@@ -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
@@ -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<PsiIdentifier> 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) {
@@ -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
@@ -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<PsiIdentifier> UNDERSCORE_IDENTIFIER = error("underscore.identifier");
public static final Simple<PsiIdentifier> UNDERSCORE_IDENTIFIER_UNNAMED = error("underscore.identifier.unnamed");
public static final Simple<PsiIdentifier> UNDERSCORE_IDENTIFIER_LAMBDA = error("underscore.identifier.lambda");
public static final Simple<PsiVariable> 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<PsiLocalVariable> 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<PsiField> 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<PsiParameter> 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<PsiVariable> 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 <Psi extends PsiElement> Simple<Psi> error(
@NotNull @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) String key) {
@@ -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);
}
@@ -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);
}
@@ -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));