mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-highlighting] Finish moving annotation-related errors
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: 21317f34d9e16b4169c56ad957c90fb07d943355
This commit is contained in:
committed by
intellij-monorepo-bot
parent
ee123058f6
commit
d016a592c8
@@ -24,6 +24,19 @@ annotation.member.invalid.type=Invalid type ''{0}'' for annotation member
|
||||
annotation.member.clash=@interface member clashes with ''{0}'' in {1}
|
||||
annotation.may.not.have.extends.list='extends' not allowed on @interface
|
||||
annotation.cyclic.element.type=Cyclic annotation element type
|
||||
annotation.container.wrong.place=Container annotation ''{0}'' must not be present at the same time as the element it contains
|
||||
annotation.container.not.applicable=Container annotation ''@{0}'' is not applicable to {1}
|
||||
annotation.duplicate=Duplicate annotation
|
||||
annotation.duplicate.explained=Duplicate annotation. {0}
|
||||
annotation.duplicate.non.repeatable=Duplicate annotation. The declaration of ''{0}'' does not have a valid java.lang.annotation.Repeatable annotation
|
||||
annotation.malformed.repeatable.explained={0}
|
||||
|
||||
annotation.container.missed.annotation=Container annotation ''{0}'' does not have required @{1} annotation
|
||||
annotation.container.no.value=Invalid container annotation ''{0}'': no ''value'' method declared
|
||||
annotation.container.bad.type=Invalid container annotation ''{0}'': ''value'' method should have type ''{1}''
|
||||
annotation.container.low.retention=Container annotation ''{0}'' has shorter retention (''{1}'') than the contained annotation
|
||||
annotation.container.wide.target=Target of container annotation ''{0}'' is not a subset of target of this annotation
|
||||
annotation.container.abstract=Container annotation ''{0}'' does not have a default value for ''{1}''
|
||||
|
||||
lambda.not.a.functional.interface={0} is not a functional interface
|
||||
lambda.no.target.method.found=No target method found
|
||||
|
||||
+223
-21
@@ -2,20 +2,29 @@
|
||||
package com.intellij.java.codeserver.highlighting;
|
||||
|
||||
import com.intellij.codeInsight.AnnotationTargetUtil;
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds.AnnotationValueErrorContext;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.patterns.ElementPattern;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.IncompleteModelUtil;
|
||||
import com.intellij.psi.impl.PsiImplUtil;
|
||||
import com.intellij.psi.impl.source.PsiClassReferenceType;
|
||||
import com.intellij.psi.impl.source.PsiImmediateClassType;
|
||||
import com.intellij.psi.search.searches.SuperMethodsSearch;
|
||||
import com.intellij.psi.util.*;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.patterns.PsiJavaPatterns.psiElement;
|
||||
@@ -96,7 +105,7 @@ final class AnnotationChecker {
|
||||
}
|
||||
|
||||
void checkValidAnnotationType(@Nullable PsiType type, @NotNull PsiTypeElement typeElement) {
|
||||
if (type == null || !PsiTypesUtil.isValidAnnotationMethodType(type)) {
|
||||
if (type == null || !isValidAnnotationMethodType(type)) {
|
||||
myVisitor.report(JavaErrorKinds.ANNOTATION_METHOD_INVALID_TYPE.create(typeElement, type));
|
||||
}
|
||||
}
|
||||
@@ -160,6 +169,191 @@ final class AnnotationChecker {
|
||||
if (!myVisitor.hasErrorResults()) checkTargetAnnotationDuplicates(annotation);
|
||||
if (!myVisitor.hasErrorResults()) checkFunctionalInterface(annotation);
|
||||
if (!myVisitor.hasErrorResults()) checkOverrideAnnotation(annotation);
|
||||
if (!myVisitor.hasErrorResults()) checkDuplicateAnnotations(annotation);
|
||||
if (!myVisitor.hasErrorResults()) checkRepeatableAnnotation(annotation);
|
||||
}
|
||||
|
||||
private void checkRepeatableAnnotation(@NotNull PsiAnnotation annotation) {
|
||||
String qualifiedName = annotation.getQualifiedName();
|
||||
if (!CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE.equals(qualifiedName)) return;
|
||||
|
||||
String description = doCheckRepeatableAnnotation(annotation);
|
||||
if (description != null) {
|
||||
PsiAnnotationMemberValue containerRef = PsiImplUtil.findAttributeValue(annotation, null);
|
||||
if (containerRef != null) {
|
||||
myVisitor.report(JavaErrorKinds.ANNOTATION_MALFORMED_REPEATABLE_EXPLAINED.create(containerRef, description));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkDuplicateAnnotations(@NotNull PsiAnnotation annotation) {
|
||||
PsiAnnotationOwner owner = annotation.getOwner();
|
||||
if (owner == null) return;
|
||||
|
||||
PsiClass annotationType = annotation.resolveAnnotationType();
|
||||
if (annotationType == null) return;
|
||||
|
||||
PsiClass contained = contained(annotationType);
|
||||
String containedElementFQN = contained == null ? null : contained.getQualifiedName();
|
||||
|
||||
if (containedElementFQN != null && isAnnotationRepeatedTwice(owner, containedElementFQN)) {
|
||||
myVisitor.report(JavaErrorKinds.ANNOTATION_CONTAINER_WRONG_PLACE.create(annotation));
|
||||
return;
|
||||
}
|
||||
if (isAnnotationRepeatedTwice(owner, annotationType.getQualifiedName())) {
|
||||
if (!myVisitor.isApplicable(JavaFeature.REPEATING_ANNOTATIONS)) {
|
||||
myVisitor.report(JavaErrorKinds.ANNOTATION_DUPLICATE.create(annotation));
|
||||
return;
|
||||
}
|
||||
|
||||
PsiAnnotation metaAnno =
|
||||
PsiImplUtil.findAnnotation(annotationType.getModifierList(), CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE);
|
||||
if (metaAnno == null) {
|
||||
myVisitor.report(JavaErrorKinds.ANNOTATION_DUPLICATE_NON_REPEATABLE.create(annotation));
|
||||
return;
|
||||
}
|
||||
|
||||
String explanation = doCheckRepeatableAnnotation(metaAnno);
|
||||
if (explanation != null) {
|
||||
myVisitor.report(JavaErrorKinds.ANNOTATION_DUPLICATE_EXPLAINED.create(annotation, explanation));
|
||||
return;
|
||||
}
|
||||
|
||||
PsiClass container = getRepeatableContainer(metaAnno);
|
||||
if (container != null) {
|
||||
PsiAnnotation.TargetType[] targets = AnnotationTargetUtil.getTargetsForLocation(owner);
|
||||
PsiAnnotation.TargetType applicable = AnnotationTargetUtil.findAnnotationTarget(container, targets);
|
||||
if (applicable == null) {
|
||||
myVisitor.report(JavaErrorKinds.ANNOTATION_CONTAINER_NOT_APPLICABLE.create(annotation, container));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkArrayInitializer(@NotNull PsiArrayInitializerMemberValue initializer) {
|
||||
PsiMethod method = null;
|
||||
|
||||
PsiElement parent = initializer.getParent();
|
||||
if (parent instanceof PsiNameValuePair) {
|
||||
PsiReference reference = parent.getReference();
|
||||
if (reference != null) {
|
||||
method = (PsiMethod)reference.resolve();
|
||||
}
|
||||
}
|
||||
else if (PsiUtil.isAnnotationMethod(parent)) {
|
||||
method = (PsiMethod)parent;
|
||||
}
|
||||
|
||||
if (method instanceof PsiAnnotationMethod annotationMethod) {
|
||||
PsiType type = method.getReturnType();
|
||||
if (type instanceof PsiArrayType arrayType) {
|
||||
type = arrayType.getComponentType();
|
||||
PsiAnnotationMemberValue[] initializers = initializer.getInitializers();
|
||||
for (PsiAnnotationMemberValue initializer1 : initializers) {
|
||||
checkMemberValueType(initializer1, type, annotationMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static @NlsContexts.DetailedDescription String doCheckRepeatableAnnotation(@NotNull PsiAnnotation annotation) {
|
||||
PsiAnnotationOwner owner = annotation.getOwner();
|
||||
if (!(owner instanceof PsiModifierList list)) return null;
|
||||
PsiElement target = list.getParent();
|
||||
if (!(target instanceof PsiClass psiClass) || !psiClass.isAnnotationType()) return null;
|
||||
PsiClass container = getRepeatableContainer(annotation);
|
||||
if (container == null) return null;
|
||||
|
||||
PsiMethod[] methods = !container.isAnnotationType() ? PsiMethod.EMPTY_ARRAY
|
||||
: container.findMethodsByName("value", false);
|
||||
if (methods.length == 0) {
|
||||
return JavaCompilationErrorBundle.message("annotation.container.no.value", container.getQualifiedName());
|
||||
}
|
||||
|
||||
if (methods.length == 1) {
|
||||
PsiType expected = new PsiImmediateClassType(psiClass, PsiSubstitutor.EMPTY).createArrayType();
|
||||
if (!expected.equals(methods[0].getReturnType())) {
|
||||
return JavaCompilationErrorBundle.message("annotation.container.bad.type", container.getQualifiedName(),
|
||||
PsiTypesUtil.removeExternalAnnotations(expected).getInternalCanonicalText());
|
||||
}
|
||||
}
|
||||
|
||||
RetentionPolicy targetPolicy = JavaPsiAnnotationUtil.getRetentionPolicy(psiClass);
|
||||
if (targetPolicy != null) {
|
||||
RetentionPolicy containerPolicy = JavaPsiAnnotationUtil.getRetentionPolicy(container);
|
||||
if (containerPolicy != null && targetPolicy.compareTo(containerPolicy) > 0) {
|
||||
return JavaCompilationErrorBundle.message("annotation.container.low.retention", container.getQualifiedName(), containerPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
Set<PsiAnnotation.TargetType> repeatableTargets = AnnotationTargetUtil.getAnnotationTargets(psiClass);
|
||||
if (repeatableTargets != null) {
|
||||
Set<PsiAnnotation.TargetType> containerTargets = AnnotationTargetUtil.getAnnotationTargets(container);
|
||||
if (containerTargets != null) {
|
||||
for (PsiAnnotation.TargetType containerTarget : containerTargets) {
|
||||
if (repeatableTargets.contains(containerTarget)) {
|
||||
continue;
|
||||
}
|
||||
if (containerTarget == PsiAnnotation.TargetType.ANNOTATION_TYPE &&
|
||||
(repeatableTargets.contains(PsiAnnotation.TargetType.TYPE) ||
|
||||
repeatableTargets.contains(PsiAnnotation.TargetType.TYPE_USE))) {
|
||||
continue;
|
||||
}
|
||||
if ((containerTarget == PsiAnnotation.TargetType.TYPE || containerTarget == PsiAnnotation.TargetType.TYPE_PARAMETER) &&
|
||||
repeatableTargets.contains(PsiAnnotation.TargetType.TYPE_USE)) {
|
||||
continue;
|
||||
}
|
||||
return JavaCompilationErrorBundle.message("annotation.container.wide.target", container.getQualifiedName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (PsiMethod method : container.getMethods()) {
|
||||
if (method instanceof PsiAnnotationMethod annotationMethod &&
|
||||
!"value".equals(method.getName()) &&
|
||||
annotationMethod.getDefaultValue() == null) {
|
||||
return JavaCompilationErrorBundle.message("annotation.container.abstract", container.getQualifiedName(), method.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable String missedAnnotationError = getMissedAnnotationError(psiClass, container, Inherited.class.getName());
|
||||
if (missedAnnotationError != null) {
|
||||
return missedAnnotationError;
|
||||
}
|
||||
return getMissedAnnotationError(psiClass, container, Documented.class.getName());
|
||||
}
|
||||
|
||||
private static @Nls String getMissedAnnotationError(PsiClass target, PsiClass container, String annotationFqn) {
|
||||
if (AnnotationUtil.isAnnotated(target, annotationFqn, 0) && !AnnotationUtil.isAnnotated(container, annotationFqn, 0)) {
|
||||
return JavaCompilationErrorBundle.message("annotation.container.missed.annotation", container.getQualifiedName(),
|
||||
StringUtil.getShortName(annotationFqn));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable PsiClass getRepeatableContainer(@NotNull PsiAnnotation annotation) {
|
||||
PsiAnnotationMemberValue containerRef = PsiImplUtil.findAttributeValue(annotation, null);
|
||||
if (!(containerRef instanceof PsiClassObjectAccessExpression expression)) return null;
|
||||
PsiType containerType = expression.getOperand().getType();
|
||||
if (!(containerType instanceof PsiClassType classType)) return null;
|
||||
return classType.resolve();
|
||||
}
|
||||
|
||||
// returns contained element
|
||||
private static PsiClass contained(@NotNull PsiClass annotationType) {
|
||||
if (!annotationType.isAnnotationType()) return null;
|
||||
PsiMethod[] values = annotationType.findMethodsByName("value", false);
|
||||
if (values.length != 1) return null;
|
||||
PsiMethod value = values[0];
|
||||
PsiType returnType = value.getReturnType();
|
||||
if (!(returnType instanceof PsiArrayType arrayType)) return null;
|
||||
PsiType type = arrayType.getComponentType();
|
||||
if (!(type instanceof PsiClassType classType)) return null;
|
||||
PsiClass contained = classType.resolve();
|
||||
if (contained == null || !contained.isAnnotationType()) return null;
|
||||
if (PsiImplUtil.findAnnotation(contained.getModifierList(), CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE) == null) return null;
|
||||
|
||||
return contained;
|
||||
}
|
||||
|
||||
private void checkFunctionalInterface(@NotNull PsiAnnotation annotation) {
|
||||
@@ -492,29 +686,37 @@ final class AnnotationChecker {
|
||||
}
|
||||
}
|
||||
|
||||
void checkArrayInitializer(@NotNull PsiArrayInitializerMemberValue list) {
|
||||
PsiMethod method = null;
|
||||
private static boolean isAnnotationRepeatedTwice(@NotNull PsiAnnotationOwner owner, @Nullable String qualifiedName) {
|
||||
int count = 0;
|
||||
for (PsiAnnotation annotation : owner.getAnnotations()) {
|
||||
PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
|
||||
if (nameRef == null) continue;
|
||||
PsiElement resolved = nameRef.resolve();
|
||||
if (!(resolved instanceof PsiClass psiClass) || !Objects.equals(qualifiedName, psiClass.getQualifiedName())) continue;
|
||||
if (++count == 2) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
PsiElement parent = list.getParent();
|
||||
if (parent instanceof PsiNameValuePair) {
|
||||
PsiReference reference = parent.getReference();
|
||||
if (reference != null) {
|
||||
method = (PsiMethod)reference.resolve();
|
||||
private static boolean isValidAnnotationMethodType(@NotNull PsiType type) {
|
||||
if (type instanceof PsiArrayType arrayType) {
|
||||
if (arrayType.getArrayDimensions() != 1) return false;
|
||||
type = arrayType.getComponentType();
|
||||
}
|
||||
if (type instanceof PsiPrimitiveType) {
|
||||
return !PsiTypes.voidType().equals(type) && !PsiTypes.nullType().equals(type);
|
||||
}
|
||||
if (type instanceof PsiClassType classType) {
|
||||
if (classType.getParameters().length > 0) {
|
||||
return PsiTypesUtil.classNameEquals(classType, CommonClassNames.JAVA_LANG_CLASS);
|
||||
}
|
||||
if (classType.equalsToText(CommonClassNames.JAVA_LANG_CLASS) || classType.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (PsiUtil.isAnnotationMethod(parent)) {
|
||||
method = (PsiMethod)parent;
|
||||
}
|
||||
|
||||
if (method instanceof PsiAnnotationMethod annotationMethod) {
|
||||
PsiType type = method.getReturnType();
|
||||
if (type instanceof PsiArrayType arrayType) {
|
||||
type = arrayType.getComponentType();
|
||||
PsiAnnotationMemberValue[] initializers = list.getInitializers();
|
||||
for (PsiAnnotationMemberValue initializer : initializers) {
|
||||
checkMemberValueType(initializer, type, annotationMethod);
|
||||
}
|
||||
}
|
||||
PsiClass aClass = classType.resolve();
|
||||
return aClass != null && (aClass.isAnnotationType() || aClass.isEnum());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+29
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.codeserver.highlighting.errors;
|
||||
|
||||
import com.intellij.codeInsight.AnnotationTargetUtil;
|
||||
import com.intellij.core.JavaPsiBundle;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind.Parameterized;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind.Simple;
|
||||
@@ -122,6 +123,34 @@ public final class JavaErrorKinds {
|
||||
.<@NotNull List<String>>withContext()
|
||||
.withRawDescription((annotation, attributeNames) -> message(
|
||||
"annotation.missing.attribute", attributeNames.stream().map(attr -> "'" + attr + "'").collect(Collectors.joining(", "))));
|
||||
public static final Simple<PsiAnnotation> ANNOTATION_CONTAINER_WRONG_PLACE =
|
||||
error(PsiAnnotation.class, "annotation.container.wrong.place")
|
||||
.withRawDescription(annotation ->
|
||||
message("annotation.container.wrong.place",
|
||||
requireNonNull(annotation.resolveAnnotationType()).getQualifiedName()));
|
||||
public static final Parameterized<PsiAnnotation, PsiClass> ANNOTATION_CONTAINER_NOT_APPLICABLE =
|
||||
parameterized(PsiAnnotation.class, PsiClass.class, "annotation.container.not.applicable")
|
||||
.withRawDescription((annotation, containerClass) -> {
|
||||
PsiAnnotation.TargetType[] targets = AnnotationTargetUtil.getTargetsForLocation(annotation.getOwner());
|
||||
String target = JavaPsiBundle.message("annotation.target." + targets[0]);
|
||||
return message("annotation.container.not.applicable", containerClass.getName(), target);
|
||||
});
|
||||
public static final Simple<PsiAnnotation> ANNOTATION_DUPLICATE =
|
||||
error(PsiAnnotation.class, "annotation.duplicate").withAnchor(annotation -> requireNonNull(annotation.getNameReferenceElement()));
|
||||
public static final Simple<PsiAnnotation> ANNOTATION_DUPLICATE_NON_REPEATABLE =
|
||||
error(PsiAnnotation.class, "annotation.duplicate.non.repeatable")
|
||||
.withAnchor(annotation -> requireNonNull(annotation.getNameReferenceElement()))
|
||||
.withRawDescription(annotation -> message(
|
||||
"annotation.duplicate.non.repeatable", requireNonNull(annotation.resolveAnnotationType()).getQualifiedName()));
|
||||
public static final Parameterized<PsiAnnotation, String> ANNOTATION_DUPLICATE_EXPLAINED =
|
||||
error(PsiAnnotation.class, "annotation.duplicate.explained")
|
||||
.withAnchor(annotation -> requireNonNull(annotation.getNameReferenceElement()))
|
||||
.<String>withContext()
|
||||
.withRawDescription((annotation, message) -> message("annotation.duplicate.explained", message));
|
||||
public static final Parameterized<PsiAnnotationMemberValue, String> ANNOTATION_MALFORMED_REPEATABLE_EXPLAINED =
|
||||
parameterized(PsiAnnotationMemberValue.class, String.class, "annotation.malformed.repeatable.explained")
|
||||
.withRawDescription((containerRef, message) -> message("annotation.malformed.repeatable.explained", message));
|
||||
|
||||
public static final Simple<PsiAnnotation> SAFE_VARARGS_ON_RECORD_COMPONENT =
|
||||
error("safe.varargs.on.record.component");
|
||||
public static final Parameterized<PsiAnnotation, PsiMethod> SAFE_VARARGS_ON_FIXED_ARITY = parameterized("safe.varargs.on.fixed.arity");
|
||||
|
||||
+11
-242
@@ -1,254 +1,23 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.daemon.impl.analysis;
|
||||
|
||||
import com.intellij.codeInsight.AnnotationTargetUtil;
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.daemon.JavaErrorBundle;
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.QuickFixFactory;
|
||||
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
|
||||
import com.intellij.core.JavaPsiBundle;
|
||||
import com.intellij.java.analysis.JavaAnalysisBundle;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.PsiImplUtil;
|
||||
import com.intellij.psi.impl.source.PsiImmediateClassType;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.util.JavaPsiAnnotationUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @deprecated the functionality was moved to other places
|
||||
*/
|
||||
@Deprecated
|
||||
public final class AnnotationsHighlightUtil {
|
||||
private static final Logger LOG = Logger.getInstance(AnnotationsHighlightUtil.class);
|
||||
|
||||
static HighlightInfo.Builder checkDuplicateAnnotations(@NotNull PsiAnnotation annotationToCheck, @NotNull LanguageLevel languageLevel) {
|
||||
PsiAnnotationOwner owner = annotationToCheck.getOwner();
|
||||
if (owner == null) return null;
|
||||
|
||||
PsiJavaCodeReferenceElement element = annotationToCheck.getNameReferenceElement();
|
||||
if (element == null) return null;
|
||||
PsiElement resolved = element.resolve();
|
||||
if (!(resolved instanceof PsiClass annotationType)) return null;
|
||||
|
||||
PsiClass contained = contained(annotationType);
|
||||
String containedElementFQN = contained == null ? null : contained.getQualifiedName();
|
||||
|
||||
if (containedElementFQN != null) {
|
||||
String containerName = annotationType.getQualifiedName();
|
||||
if (isAnnotationRepeatedTwice(owner, containedElementFQN)) {
|
||||
String description = JavaErrorBundle.message("annotation.container.wrong.place", containerName);
|
||||
return createAnnotationError(annotationToCheck, description);
|
||||
}
|
||||
}
|
||||
if (isAnnotationRepeatedTwice(owner, annotationType.getQualifiedName())) {
|
||||
if (!languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) {
|
||||
String description = JavaErrorBundle.message("annotation.duplicate.annotation");
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description);
|
||||
}
|
||||
|
||||
PsiAnnotation metaAnno = PsiImplUtil.findAnnotation(annotationType.getModifierList(), CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE);
|
||||
if (metaAnno == null) {
|
||||
String explanation = JavaErrorBundle.message("annotation.non.repeatable", annotationType.getQualifiedName());
|
||||
String description = JavaErrorBundle.message("annotation.duplicate.explained", explanation);
|
||||
HighlightInfo.Builder info =
|
||||
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description);
|
||||
IntentionAction action = QuickFixFactory.getInstance().createCollapseAnnotationsFix(annotationToCheck);
|
||||
if (action != null) {
|
||||
info.registerFix(action, null, null, null, null);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
String explanation = doCheckRepeatableAnnotation(metaAnno);
|
||||
if (explanation != null) {
|
||||
String description = JavaErrorBundle.message("annotation.duplicate.explained", explanation);
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description);
|
||||
}
|
||||
|
||||
PsiClass container = getRepeatableContainer(metaAnno);
|
||||
if (container != null) {
|
||||
PsiAnnotation.TargetType[] targets = AnnotationTargetUtil.getTargetsForLocation(owner);
|
||||
PsiAnnotation.TargetType applicable = AnnotationTargetUtil.findAnnotationTarget(container, targets);
|
||||
if (applicable == null) {
|
||||
String target = JavaPsiBundle.message("annotation.target." + targets[0]);
|
||||
String message = JavaErrorBundle.message("annotation.container.not.applicable", container.getName(), target);
|
||||
return createAnnotationError(annotationToCheck, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// returns contained element
|
||||
private static PsiClass contained(@NotNull PsiClass annotationType) {
|
||||
if (!annotationType.isAnnotationType()) return null;
|
||||
PsiMethod[] values = annotationType.findMethodsByName("value", false);
|
||||
if (values.length != 1) return null;
|
||||
PsiMethod value = values[0];
|
||||
PsiType returnType = value.getReturnType();
|
||||
if (!(returnType instanceof PsiArrayType arrayType)) return null;
|
||||
PsiType type = arrayType.getComponentType();
|
||||
if (!(type instanceof PsiClassType classType)) return null;
|
||||
PsiClass contained = classType.resolve();
|
||||
if (contained == null || !contained.isAnnotationType()) return null;
|
||||
if (PsiImplUtil.findAnnotation(contained.getModifierList(), CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE) == null) return null;
|
||||
|
||||
return contained;
|
||||
}
|
||||
|
||||
private static boolean isAnnotationRepeatedTwice(@NotNull PsiAnnotationOwner owner, @Nullable String qualifiedName) {
|
||||
int count = 0;
|
||||
for (PsiAnnotation annotation : owner.getAnnotations()) {
|
||||
PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
|
||||
if (nameRef == null) continue;
|
||||
PsiElement resolved = nameRef.resolve();
|
||||
if (!(resolved instanceof PsiClass psiClass) || !Objects.equals(qualifiedName, psiClass.getQualifiedName())) continue;
|
||||
if (++count == 2) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static @NotNull HighlightInfo.Builder createAnnotationError(@NotNull PsiAnnotation annotation, @NotNull @NlsContexts.DetailedDescription String message) {
|
||||
LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance()
|
||||
.createDeleteFix(annotation, JavaAnalysisBundle.message("intention.text.remove.annotation"));
|
||||
return createAnnotationError(annotation, message, fix);
|
||||
}
|
||||
|
||||
private static @NotNull HighlightInfo.Builder createAnnotationError(@NotNull PsiAnnotation annotation,
|
||||
@NotNull @NlsContexts.DetailedDescription String message,
|
||||
@NotNull IntentionAction fix) {
|
||||
HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
|
||||
.range(annotation)
|
||||
.descriptionAndTooltip(message);
|
||||
info.registerFix(fix, null, null, null, null);
|
||||
return info;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkRepeatableAnnotation(@NotNull PsiAnnotation annotation) {
|
||||
String qualifiedName = annotation.getQualifiedName();
|
||||
if (!CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE.equals(qualifiedName)) return null;
|
||||
|
||||
String description = doCheckRepeatableAnnotation(annotation);
|
||||
if (description != null) {
|
||||
PsiAnnotationMemberValue containerRef = PsiImplUtil.findAttributeValue(annotation, null);
|
||||
if (containerRef != null) {
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(containerRef).descriptionAndTooltip(description);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static @NlsContexts.DetailedDescription String doCheckRepeatableAnnotation(@NotNull PsiAnnotation annotation) {
|
||||
PsiAnnotationOwner owner = annotation.getOwner();
|
||||
if (!(owner instanceof PsiModifierList list)) return null;
|
||||
PsiElement target = list.getParent();
|
||||
if (!(target instanceof PsiClass psiClass) || !psiClass.isAnnotationType()) return null;
|
||||
PsiClass container = getRepeatableContainer(annotation);
|
||||
if (container == null) return null;
|
||||
|
||||
PsiMethod[] methods = !container.isAnnotationType() ? PsiMethod.EMPTY_ARRAY
|
||||
: container.findMethodsByName("value", false);
|
||||
if (methods.length == 0) {
|
||||
return JavaErrorBundle.message("annotation.container.no.value", container.getQualifiedName());
|
||||
}
|
||||
|
||||
if (methods.length == 1) {
|
||||
PsiType expected = new PsiImmediateClassType(psiClass, PsiSubstitutor.EMPTY).createArrayType();
|
||||
if (!expected.equals(methods[0].getReturnType())) {
|
||||
return JavaErrorBundle.message("annotation.container.bad.type", container.getQualifiedName(), JavaHighlightUtil.formatType(expected));
|
||||
}
|
||||
}
|
||||
|
||||
RetentionPolicy targetPolicy = getRetentionPolicy(psiClass);
|
||||
if (targetPolicy != null) {
|
||||
RetentionPolicy containerPolicy = getRetentionPolicy(container);
|
||||
if (containerPolicy != null && targetPolicy.compareTo(containerPolicy) > 0) {
|
||||
return JavaErrorBundle.message("annotation.container.low.retention", container.getQualifiedName(), containerPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
Set<PsiAnnotation.TargetType> repeatableTargets = AnnotationTargetUtil.getAnnotationTargets(psiClass);
|
||||
if (repeatableTargets != null) {
|
||||
Set<PsiAnnotation.TargetType> containerTargets = AnnotationTargetUtil.getAnnotationTargets(container);
|
||||
if (containerTargets != null) {
|
||||
for (PsiAnnotation.TargetType containerTarget : containerTargets) {
|
||||
if (repeatableTargets.contains(containerTarget)) {
|
||||
continue;
|
||||
}
|
||||
if (containerTarget == PsiAnnotation.TargetType.ANNOTATION_TYPE &&
|
||||
(repeatableTargets.contains(PsiAnnotation.TargetType.TYPE) || repeatableTargets.contains(PsiAnnotation.TargetType.TYPE_USE))) {
|
||||
continue;
|
||||
}
|
||||
if ((containerTarget == PsiAnnotation.TargetType.TYPE || containerTarget == PsiAnnotation.TargetType.TYPE_PARAMETER) &&
|
||||
repeatableTargets.contains(PsiAnnotation.TargetType.TYPE_USE)) {
|
||||
continue;
|
||||
}
|
||||
return JavaErrorBundle.message("annotation.container.wide.target", container.getQualifiedName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (PsiMethod method : container.getMethods()) {
|
||||
if (method instanceof PsiAnnotationMethod annotationMethod && !"value".equals(method.getName()) && annotationMethod.getDefaultValue() == null) {
|
||||
return JavaErrorBundle.message("annotation.container.abstract", container.getQualifiedName(), method.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable String missedAnnotationError = getMissedAnnotationError(psiClass, container, Inherited.class.getName());
|
||||
if (missedAnnotationError != null) {
|
||||
return missedAnnotationError;
|
||||
}
|
||||
return getMissedAnnotationError(psiClass, container, Documented.class.getName());
|
||||
}
|
||||
|
||||
private static @Nls String getMissedAnnotationError(PsiClass target, PsiClass container, String annotationFqn) {
|
||||
if (AnnotationUtil.isAnnotated(target, annotationFqn, 0) && !AnnotationUtil.isAnnotated(container, annotationFqn, 0)) {
|
||||
return JavaErrorBundle.message("annotation.container.missed.annotation", container.getQualifiedName(), StringUtil.getShortName(annotationFqn));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable PsiClass getRepeatableContainer(@NotNull PsiAnnotation annotation) {
|
||||
PsiAnnotationMemberValue containerRef = PsiImplUtil.findAttributeValue(annotation, null);
|
||||
if (!(containerRef instanceof PsiClassObjectAccessExpression expression)) return null;
|
||||
PsiType containerType = expression.getOperand().getType();
|
||||
if (!(containerType instanceof PsiClassType classType)) return null;
|
||||
return classType.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link JavaPsiAnnotationUtil#getRetentionPolicy(PsiClass)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static @Nullable RetentionPolicy getRetentionPolicy(@NotNull PsiClass annotation) {
|
||||
PsiModifierList modifierList = annotation.getModifierList();
|
||||
if (modifierList != null) {
|
||||
PsiAnnotation retentionAnno = modifierList.findAnnotation(CommonClassNames.JAVA_LANG_ANNOTATION_RETENTION);
|
||||
if (retentionAnno == null) return RetentionPolicy.CLASS;
|
||||
|
||||
PsiAnnotationMemberValue policyRef = PsiImplUtil.findAttributeValue(retentionAnno, null);
|
||||
if (policyRef instanceof PsiReference psiReference) {
|
||||
PsiElement field = psiReference.resolve();
|
||||
if (field instanceof PsiEnumConstant constant) {
|
||||
String name = constant.getName();
|
||||
try {
|
||||
return Enum.valueOf(RetentionPolicy.class, name);
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOG.warn("Unknown policy: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return JavaPsiAnnotationUtil.getRetentionPolicy(annotation);
|
||||
}
|
||||
}
|
||||
|
||||
-7
@@ -235,13 +235,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
return reference instanceof PsiJavaReference psiJavaReference ? psiJavaReference.advancedResolve(false) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAnnotation(@NotNull PsiAnnotation annotation) {
|
||||
super.visitAnnotation(annotation);
|
||||
if (!hasErrorResults()) add(AnnotationsHighlightUtil.checkDuplicateAnnotations(annotation, myLanguageLevel));
|
||||
if (!hasErrorResults()) add(AnnotationsHighlightUtil.checkRepeatableAnnotation(annotation));
|
||||
}
|
||||
|
||||
private boolean add(@Nullable HighlightInfo.Builder builder) {
|
||||
if (builder != null) {
|
||||
HighlightInfo info = builder.create();
|
||||
|
||||
+3
-1
@@ -93,7 +93,8 @@ final class JavaErrorFixProvider {
|
||||
LAMBDA_NOT_FUNCTIONAL_INTERFACE, ANNOTATION_NOT_APPLICABLE,
|
||||
LAMBDA_FUNCTIONAL_INTERFACE_SEALED, OVERRIDE_ON_STATIC_METHOD,
|
||||
OVERRIDE_ON_NON_OVERRIDING_METHOD, SAFE_VARARGS_ON_FIXED_ARITY,
|
||||
SAFE_VARARGS_ON_NON_FINAL_METHOD, SAFE_VARARGS_ON_RECORD_COMPONENT)) {
|
||||
SAFE_VARARGS_ON_NON_FINAL_METHOD, SAFE_VARARGS_ON_RECORD_COMPONENT,
|
||||
ANNOTATION_CONTAINER_WRONG_PLACE, ANNOTATION_CONTAINER_NOT_APPLICABLE)) {
|
||||
single(kind, annotationRemover);
|
||||
}
|
||||
single(ANNOTATION_NOT_ALLOWED_VAR, error -> {
|
||||
@@ -160,6 +161,7 @@ final class JavaErrorFixProvider {
|
||||
new MoveAnnotationToPackageInfoFileFix(error.psi())));
|
||||
single(ANNOTATION_NOT_ALLOWED_ON_PACKAGE, error ->
|
||||
factory.createDeleteFix(error.psi(), JavaAnalysisBundle.message("intention.text.remove.annotation")));
|
||||
single(ANNOTATION_DUPLICATE_NON_REPEATABLE, error -> factory.createCollapseAnnotationsFix(error.psi()));
|
||||
}
|
||||
|
||||
private static <Psi extends PsiElement, Context> void single(@NotNull JavaErrorKind<Psi, Context> kind,
|
||||
|
||||
+2
-6
@@ -6,7 +6,6 @@ import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.ExternalAnnotationsManager;
|
||||
import com.intellij.codeInsight.ExternalAnnotationsManager.AnnotationPlace;
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.AnnotationsHighlightUtil;
|
||||
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.LocalQuickFixOnPsiElement;
|
||||
@@ -20,10 +19,7 @@ import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.impl.light.LightElement;
|
||||
import com.intellij.psi.util.JavaElementKind;
|
||||
import com.intellij.psi.util.PsiFormatUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ArrayUtilRt;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
@@ -238,7 +234,7 @@ public class AddAnnotationPsiFix extends LocalQuickFixOnPsiElement implements Lo
|
||||
if (BaseIntentionAction.canModify(modifierListOwner)) {
|
||||
PsiClass aClass = JavaPsiFacade.getInstance(project).findClass(annotation, modifierListOwner.getResolveScope());
|
||||
if (aClass != null) {
|
||||
if (AnnotationsHighlightUtil.getRetentionPolicy(aClass) == RetentionPolicy.RUNTIME) {
|
||||
if (JavaPsiAnnotationUtil.getRetentionPolicy(aClass) == RetentionPolicy.RUNTIME) {
|
||||
return AnnotationPlace.IN_CODE;
|
||||
}
|
||||
if (!CommonClassNames.DEFAULT_PACKAGE.equals(StringUtil.getPackageName(annotation))) {
|
||||
|
||||
@@ -78,6 +78,7 @@ feature.extension.methods=Extension methods
|
||||
feature.method.references=Method references
|
||||
feature.lambda.expressions=Lambda expressions
|
||||
feature.type.annotations=Type annotations
|
||||
feature.repeating.annotations=Repeating annotations
|
||||
feature.type.receivers=Receiver parameters
|
||||
feature.intersections.in.casts=Intersection types in casts
|
||||
feature.static.interface.calls=Static interface method calls
|
||||
|
||||
@@ -43,6 +43,7 @@ public enum JavaFeature {
|
||||
METHOD_REFERENCES(LanguageLevel.JDK_1_8, "feature.method.references"),
|
||||
LAMBDA_EXPRESSIONS(LanguageLevel.JDK_1_8, "feature.lambda.expressions"),
|
||||
TYPE_ANNOTATIONS(LanguageLevel.JDK_1_8, "feature.type.annotations"),
|
||||
REPEATING_ANNOTATIONS(LanguageLevel.JDK_1_8, "feature.repeating.annotations"),
|
||||
RECEIVERS(LanguageLevel.JDK_1_8, "feature.type.receivers"),
|
||||
INTERSECTION_CASTS(LanguageLevel.JDK_1_8, "feature.intersections.in.casts"),
|
||||
STATIC_INTERFACE_CALLS(LanguageLevel.JDK_1_8, "feature.static.interface.calls"),
|
||||
|
||||
@@ -2,17 +2,9 @@
|
||||
annotation.not.allowed.here=Annotations are not allowed here
|
||||
# suppress inspection "UnusedProperty"
|
||||
annotation.missing.method=Cannot find @interface method ''{0}()''
|
||||
annotation.duplicate.annotation=Duplicate annotation
|
||||
annotation.not.applicable=''@{0}'' not applicable to {1}
|
||||
annotation.annotation.type.expected=Annotation type expected
|
||||
annotation.container.no.value=Invalid container annotation ''{0}'': no ''value'' method declared
|
||||
annotation.container.bad.type=Invalid container annotation ''{0}'': ''value'' method should have type ''{1}''
|
||||
annotation.container.low.retention=Container annotation ''{0}'' has shorter retention (''{1}'') than the contained annotation
|
||||
annotation.container.wide.target=Target of container annotation ''{0}'' is not a subset of target of this annotation
|
||||
annotation.container.abstract=Container annotation ''{0}'' does not have a default value for ''{1}''
|
||||
annotation.container.missed.annotation=Container annotation ''{0}'' does not have required @{1} annotation
|
||||
annotation.duplicate.explained=Duplicate annotation. {0}
|
||||
annotation.non.repeatable=The declaration of ''{0}'' does not have a valid java.lang.annotation.Repeatable annotation
|
||||
annotation.container.wrong.place=Container annotation ''{0}'' must not be present at the same time as the element it contains
|
||||
annotation.container.not.applicable=Container annotation ''@{0}'' is not applicable to {1}
|
||||
annotation.not.allowed.in.permit.list=Annotations not allowed in 'permits' list
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.util;
|
||||
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.PsiImplUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
public final class JavaPsiAnnotationUtil {
|
||||
/**
|
||||
* @param annotation annotation class
|
||||
* @return annotation retention policy; null if cannot be determined
|
||||
*/
|
||||
public static @Nullable RetentionPolicy getRetentionPolicy(@NotNull PsiClass annotation) {
|
||||
PsiModifierList modifierList = annotation.getModifierList();
|
||||
if (modifierList != null) {
|
||||
PsiAnnotation retentionAnno = modifierList.findAnnotation(CommonClassNames.JAVA_LANG_ANNOTATION_RETENTION);
|
||||
if (retentionAnno == null) return RetentionPolicy.CLASS;
|
||||
|
||||
PsiAnnotationMemberValue policyRef = PsiImplUtil.findAttributeValue(retentionAnno, null);
|
||||
if (policyRef instanceof PsiReference) {
|
||||
PsiElement field = ((PsiReference)policyRef).resolve();
|
||||
if (field instanceof PsiEnumConstant) {
|
||||
String name = ((PsiEnumConstant)field).getName();
|
||||
try {
|
||||
return Enum.valueOf(RetentionPolicy.class, name);
|
||||
}
|
||||
catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user