[java-highlighting] checkTargetAnnotationDuplicates, checkFunctionalInterface -> AnnotationChecker

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

GitOrigin-RevId: 963733ce2b57abd81e349da79209fde86e1ec28e
This commit is contained in:
Tagir Valeev
2025-01-08 16:42:47 +00:00
committed by intellij-monorepo-bot
parent 9af408b6ff
commit 6a2b751ffd
8 changed files with 106 additions and 79 deletions
@@ -7,5 +7,11 @@ annotation.not.allowed.var='var' type may not be annotated
annotation.not.allowed.class=Class literal type may not be annotated
annotation.not.allowed.ref=Annotation is not applicable to this kind of reference
annotation.not.allowed.static=Static member qualifying type may not be annotated
annotation.annotation.type.expected=Annotation type expected
annotation.type.expected=Annotation type expected
annotation.missing.attribute={0} missing though required
annotation.repeated.target=Repeated annotation target
lambda.not.a.functional.interface={0} is not a functional interface
lambda.no.target.method.found=No target method found
lambda.multiple.sam.candidates=Multiple non-overriding abstract methods found in interface {0}
lambda.sealed.functional.interface=Functional interface can't be declared as 'sealed'
@@ -3,6 +3,7 @@ package com.intellij.java.codeserver.highlighting;
import com.intellij.codeInsight.AnnotationTargetUtil;
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
import com.intellij.openapi.util.Comparing;
import com.intellij.patterns.ElementPattern;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
@@ -27,9 +28,9 @@ final class AnnotationChecker {
psiElement().withParents(PsiModifierList.class, PsiParameterList.class)
);
private final JavaErrorVisitor myVisitor;
private final @NotNull JavaErrorVisitor myVisitor;
AnnotationChecker(JavaErrorVisitor visitor) { myVisitor = visitor; }
AnnotationChecker(@NotNull JavaErrorVisitor visitor) { myVisitor = visitor; }
private void checkReferenceTarget(@NotNull PsiAnnotation annotation, @Nullable PsiJavaCodeReferenceElement ref) {
if (ref == null) return;
@@ -71,6 +72,28 @@ final class AnnotationChecker {
if (!myVisitor.hasErrorResults()) checkAnnotationApplicability(annotation);
if (!myVisitor.hasErrorResults()) checkAnnotationType(annotation);
if (!myVisitor.hasErrorResults()) checkMissingAttributes(annotation);
if (!myVisitor.hasErrorResults()) checkTargetAnnotationDuplicates(annotation);
if (!myVisitor.hasErrorResults()) checkFunctionalInterface(annotation);
}
private void checkFunctionalInterface(@NotNull PsiAnnotation annotation) {
if (myVisitor.isApplicable(JavaFeature.LAMBDA_EXPRESSIONS) &&
Comparing.strEqual(annotation.getQualifiedName(), CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE)) {
PsiAnnotationOwner owner = annotation.getOwner();
if (owner instanceof PsiModifierList list) {
PsiElement parent = list.getParent();
if (parent instanceof PsiClass psiClass) {
switch (LambdaUtil.checkInterfaceFunctional(psiClass)) {
case NOT_INTERFACE -> myVisitor.report(JavaErrorKinds.LAMBDA_NOT_FUNCTIONAL_INTERFACE.create(annotation, psiClass));
case NO_ABSTRACT_METHOD -> myVisitor.report(JavaErrorKinds.LAMBDA_NO_TARGET_METHOD.create(annotation, psiClass));
case MULTIPLE_ABSTRACT_METHODS -> myVisitor.report(JavaErrorKinds.LAMBDA_MULTIPLE_TARGET_METHODS.create(annotation, psiClass));
}
if (psiClass.hasModifierProperty(PsiModifier.SEALED)) {
myVisitor.report(JavaErrorKinds.LAMBDA_FUNCTIONAL_INTERFACE_SEALED.create(annotation, psiClass));
}
}
}
}
}
private void checkMissingAttributes(@NotNull PsiAnnotation annotation) {
@@ -185,4 +208,32 @@ final class AnnotationChecker {
}
}
}
private void checkTargetAnnotationDuplicates(@NotNull PsiAnnotation annotation) {
PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
if (nameRef == null) return;
PsiElement resolved = nameRef.resolve();
if (!(resolved instanceof PsiClass psiClass) || !CommonClassNames.JAVA_LANG_ANNOTATION_TARGET.equals(psiClass.getQualifiedName())) {
return;
}
PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes();
if (attributes.length < 1) return;
PsiAnnotationMemberValue value = attributes[0].getValue();
if (!(value instanceof PsiArrayInitializerMemberValue initializerMemberValue)) return;
PsiAnnotationMemberValue[] arrayInitializers = initializerMemberValue.getInitializers();
Set<PsiElement> targets = new HashSet<>();
for (PsiAnnotationMemberValue initializer : arrayInitializers) {
if (initializer instanceof PsiReferenceExpression referenceExpression) {
PsiElement target = referenceExpression.resolve();
if (target != null) {
if (targets.contains(target)) {
myVisitor.report(JavaErrorKinds.ANNOTATION_REPEATED_TARGET.create(referenceExpression));
}
targets.add(target);
}
}
}
}
}
@@ -35,6 +35,11 @@ final class JavaErrorVisitor extends JavaElementVisitor {
myErrorConsumer.accept(error);
myHasError = true;
}
@Contract(pure = true)
boolean isApplicable(@NotNull JavaFeature feature) {
return feature.isSufficient(myLanguageLevel);
}
@Contract(pure = true)
boolean hasErrorResults() {
@@ -6,9 +6,7 @@ import com.intellij.java.codeserver.highlighting.JavaCompilationErrorBundle;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.util.text.HtmlChunk;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.NotNull;
@@ -45,7 +43,30 @@ public final class JavaErrorKinds {
public static final JavaSimpleErrorKind<PsiAnnotation> ANNOTATION_NOT_ALLOWED_STATIC =
new JavaSimpleErrorKind<>("annotation.not.allowed.static");
public static final JavaSimpleErrorKind<PsiJavaCodeReferenceElement> ANNOTATION_TYPE_EXPECTED =
new JavaSimpleErrorKind<>("annotation.annotation.type.expected");
new JavaSimpleErrorKind<>("annotation.type.expected");
public static final JavaSimpleErrorKind<PsiReferenceExpression> ANNOTATION_REPEATED_TARGET =
new JavaSimpleErrorKind<>("annotation.repeated.target");
// Can be anchored on @FunctionalInterface annotation or at call site
public static final JavaErrorKind<PsiElement, PsiClass> LAMBDA_NOT_FUNCTIONAL_INTERFACE =
new Parameterized<>("lambda.not.a.functional.interface") {
@Override
public @NotNull HtmlChunk description(@NotNull PsiElement element, PsiClass aClass) {
return HtmlChunk.raw(JavaCompilationErrorBundle.message("lambda.not.a.functional.interface", aClass.getName()));
}
};
// Can be anchored on @FunctionalInterface annotation or at call site
public static final JavaErrorKind<PsiElement, PsiClass> LAMBDA_NO_TARGET_METHOD =
new Parameterized<>("lambda.no.target.method.found") {};
// Can be anchored on @FunctionalInterface annotation or at call site
public static final JavaErrorKind<PsiElement, PsiClass> LAMBDA_MULTIPLE_TARGET_METHODS =
new Parameterized<>("lambda.multiple.sam.candidates") {
@Override
public @NotNull HtmlChunk description(@NotNull PsiElement element, PsiClass aClass) {
return HtmlChunk.raw(JavaCompilationErrorBundle.message("lambda.multiple.sam.candidates", aClass.getName()));
}
};
public static final JavaErrorKind<PsiAnnotation, PsiClass> LAMBDA_FUNCTIONAL_INTERFACE_SEALED =
new Parameterized<>("lambda.sealed.functional.interface") {};
public static final JavaErrorKind<PsiAnnotation, @NotNull List<PsiAnnotation.@NotNull TargetType>> ANNOTATION_NOT_APPLICABLE =
new Parameterized<>("annotation.not.applicable") {
@Override
@@ -2,6 +2,7 @@
package com.intellij.java.codeserver.highlighting.errors;
import com.intellij.java.codeserver.highlighting.JavaCompilationErrorBundle;
import com.intellij.openapi.util.text.HtmlChunk;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.PropertyKey;
@@ -22,4 +23,9 @@ non-sealed abstract class Parameterized<Psi extends PsiElement, Context> impleme
public String toString() {
return "JavaErrorKind[" + myKey + "]";
}
@Override
public @NotNull HtmlChunk description(@NotNull Psi element, Context aClass) {
return HtmlChunk.raw(JavaCompilationErrorBundle.message("lambda.sealed.functional.interface"));
}
}
@@ -17,7 +17,6 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.IncompleteModelUtil;
@@ -435,36 +434,6 @@ public final class AnnotationsHighlightUtil {
return null;
}
static HighlightInfo.Builder checkTargetAnnotationDuplicates(@NotNull PsiAnnotation annotation) {
PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
if (nameRef == null) return null;
PsiElement resolved = nameRef.resolve();
if (!(resolved instanceof PsiClass psiClass) || !CommonClassNames.JAVA_LANG_ANNOTATION_TARGET.equals(psiClass.getQualifiedName())) {
return null;
}
PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes();
if (attributes.length < 1) return null;
PsiAnnotationMemberValue value = attributes[0].getValue();
if (!(value instanceof PsiArrayInitializerMemberValue initializerMemberValue)) return null;
PsiAnnotationMemberValue[] arrayInitializers = initializerMemberValue.getInitializers();
Set<PsiElement> targets = new HashSet<>();
for (PsiAnnotationMemberValue initializer : arrayInitializers) {
if (initializer instanceof PsiReferenceExpression referenceExpression) {
PsiElement target = referenceExpression.resolve();
if (target != null) {
if (targets.contains(target)) {
String description = JavaErrorBundle.message("repeated.annotation.target");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(initializer).descriptionAndTooltip(description);
}
targets.add(target);
}
}
}
return null;
}
static HighlightInfo.Builder checkInvalidAnnotationOnRecordComponent(@NotNull PsiAnnotation annotation) {
if (!Comparing.strEqual(annotation.getQualifiedName(), CommonClassNames.JAVA_LANG_SAFE_VARARGS)) return null;
PsiAnnotationOwner owner = annotation.getOwner();
@@ -475,37 +444,6 @@ public final class AnnotationsHighlightUtil {
.descriptionAndTooltip(JavaErrorBundle.message("safevararg.annotation.cannot.be.applied.for.record.component"));
}
static HighlightInfo.Builder checkFunctionalInterface(@NotNull PsiAnnotation annotation, @NotNull LanguageLevel languageLevel) {
if (JavaFeature.LAMBDA_EXPRESSIONS.isSufficient(languageLevel) &&
Comparing.strEqual(annotation.getQualifiedName(), CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE)) {
PsiAnnotationOwner owner = annotation.getOwner();
if (owner instanceof PsiModifierList list) {
PsiElement parent = list.getParent();
if (parent instanceof PsiClass psiClass) {
String errorMessage = LambdaHighlightingUtil.checkInterfaceFunctional(psiClass, JavaErrorBundle.message("not.a.functional.interface", psiClass.getName()));
if (errorMessage != null) {
HighlightInfo.Builder info =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(annotation).descriptionAndTooltip(errorMessage);
IntentionAction action = QuickFixFactory.getInstance().createDeleteFix(annotation);
info.registerFix(action, null, null, null, null);
return info;
}
if (psiClass.hasModifierProperty(PsiModifier.SEALED)) {
HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(annotation)
.descriptionAndTooltip(
JavaErrorBundle.message("functional.interface.must.not.be.sealed.error.description", PsiModifier.SEALED));
IntentionAction action = QuickFixFactory.getInstance().createDeleteFix(annotation);
info.registerFix(action, null, null, null, null);
return info;
}
}
}
}
return null;
}
static HighlightInfo.Builder checkRepeatableAnnotation(@NotNull PsiAnnotation annotation) {
String qualifiedName = annotation.getQualifiedName();
if (!CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE.equals(qualifiedName)) return null;
@@ -244,9 +244,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
@Override
public void visitAnnotation(@NotNull PsiAnnotation annotation) {
super.visitAnnotation(annotation);
if (!hasErrorResults()) add(AnnotationsHighlightUtil.checkTargetAnnotationDuplicates(annotation));
if (!hasErrorResults()) add(AnnotationsHighlightUtil.checkDuplicateAnnotations(annotation, myLanguageLevel));
if (!hasErrorResults()) add(AnnotationsHighlightUtil.checkFunctionalInterface(annotation, myLanguageLevel));
if (!hasErrorResults()) add(AnnotationsHighlightUtil.checkInvalidAnnotationOnRecordComponent(annotation));
if (!hasErrorResults()) add(AnnotationsHighlightUtil.checkRepeatableAnnotation(annotation));
if (CommonClassNames.JAVA_LANG_OVERRIDE.equals(annotation.getQualifiedName())) {
@@ -40,14 +40,16 @@ final class JavaErrorFixProvider {
private static final Map<JavaErrorKind<?, ?>, List<JavaFixesProvider<?, ?>>> FIXES = new HashMap<>();
static {
JavaFixProvider<PsiAnnotation, Object> annotationRemover = error ->
QuickFixFactory.getInstance().createDeleteFix(error.psi(), JavaAnalysisBundle.message("intention.text.remove.annotation"));
single(ANNOTATION_NOT_ALLOWED_CLASS, annotationRemover);
single(ANNOTATION_NOT_ALLOWED_HERE, annotationRemover);
single(ANNOTATION_NOT_ALLOWED_REF, annotationRemover);
single(ANNOTATION_NOT_ALLOWED_VAR, annotationRemover);
single(ANNOTATION_NOT_ALLOWED_VOID, annotationRemover);
single(ANNOTATION_NOT_APPLICABLE, annotationRemover);
JavaFixProvider<PsiElement, Object> annotationRemover = error ->
error.psi() instanceof PsiAnnotation annotation ? QuickFixFactory.getInstance()
.createDeleteFix(annotation, JavaAnalysisBundle.message("intention.text.remove.annotation")) : null;
for (JavaErrorKind<?, ?> kind : List.of(ANNOTATION_NOT_ALLOWED_CLASS, ANNOTATION_NOT_ALLOWED_HERE,
ANNOTATION_NOT_ALLOWED_REF, ANNOTATION_NOT_ALLOWED_VAR,
ANNOTATION_NOT_ALLOWED_VOID, LAMBDA_MULTIPLE_TARGET_METHODS, LAMBDA_NO_TARGET_METHOD,
LAMBDA_NOT_FUNCTIONAL_INTERFACE, ANNOTATION_NOT_APPLICABLE,
LAMBDA_FUNCTIONAL_INTERFACE_SEALED)) {
single(kind, annotationRemover);
}
single(ANNOTATION_NOT_ALLOWED_VAR, error -> {
PsiAnnotationOwner owner = error.psi().getOwner();
PsiTypeElement type = owner instanceof PsiTypeElement te ? te :