[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:
Tagir Valeev
2025-01-09 12:30:34 +00:00
committed by intellij-monorepo-bot
parent ee123058f6
commit d016a592c8
11 changed files with 321 additions and 285 deletions
@@ -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
@@ -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;
}
}
@@ -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");
@@ -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);
}
}
@@ -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();
@@ -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,
@@ -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;
}
}