From 6a2b751ffd4c2691cec79a6f57eccfe21465c128 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 7 Jan 2025 11:33:24 +0100 Subject: [PATCH] [java-highlighting] checkTargetAnnotationDuplicates, checkFunctionalInterface -> AnnotationChecker Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: 963733ce2b57abd81e349da79209fde86e1ec28e --- .../JavaCompilationErrorBundle.properties | 8 ++- .../highlighting/AnnotationChecker.java | 55 +++++++++++++++- .../highlighting/JavaErrorVisitor.java | 5 ++ .../highlighting/errors/JavaErrorKinds.java | 29 +++++++-- .../highlighting/errors/Parameterized.java | 6 ++ .../analysis/AnnotationsHighlightUtil.java | 62 ------------------- .../impl/analysis/HighlightVisitorImpl.java | 2 - .../impl/analysis/JavaErrorFixProvider.java | 18 +++--- 8 files changed, 106 insertions(+), 79 deletions(-) diff --git a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties index 91ee28142333..68820ee6ab67 100644 --- a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties +++ b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties @@ -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' diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/AnnotationChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/AnnotationChecker.java index 0f44b185b801..d0d90227c48b 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/AnnotationChecker.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/AnnotationChecker.java @@ -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 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); + } + } + } + } } 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 b4f031fcc4ea..9a011d01a4a0 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 @@ -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() { 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 51d18cbfcdea..7a1c08259c83 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 @@ -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 ANNOTATION_NOT_ALLOWED_STATIC = new JavaSimpleErrorKind<>("annotation.not.allowed.static"); public static final JavaSimpleErrorKind ANNOTATION_TYPE_EXPECTED = - new JavaSimpleErrorKind<>("annotation.annotation.type.expected"); + new JavaSimpleErrorKind<>("annotation.type.expected"); + public static final JavaSimpleErrorKind ANNOTATION_REPEATED_TARGET = + new JavaSimpleErrorKind<>("annotation.repeated.target"); + // Can be anchored on @FunctionalInterface annotation or at call site + public static final JavaErrorKind 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 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 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 LAMBDA_FUNCTIONAL_INTERFACE_SEALED = + new Parameterized<>("lambda.sealed.functional.interface") {}; public static final JavaErrorKind> ANNOTATION_NOT_APPLICABLE = new Parameterized<>("annotation.not.applicable") { @Override diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/Parameterized.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/Parameterized.java index 861467a06359..3ef4278471a9 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/Parameterized.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/Parameterized.java @@ -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 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")); + } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java index fafbc22d566d..e03b894088e9 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java @@ -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 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; 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 ea389a2ad01f..e2e3f1076bfb 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 @@ -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())) { 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 10a6e07c76b2..f2769b1ac328 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 @@ -40,14 +40,16 @@ final class JavaErrorFixProvider { private static final Map, List>> FIXES = new HashMap<>(); static { - JavaFixProvider 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 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 :