mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[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:
committed by
intellij-monorepo-bot
parent
9af408b6ff
commit
6a2b751ffd
+7
-1
@@ -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'
|
||||
|
||||
+53
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -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() {
|
||||
|
||||
+25
-4
@@ -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
|
||||
|
||||
+6
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
-62
@@ -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;
|
||||
|
||||
-2
@@ -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())) {
|
||||
|
||||
+10
-8
@@ -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 :
|
||||
|
||||
Reference in New Issue
Block a user