diff --git a/.idea/modules.xml b/.idea/modules.xml
index b261c380a21a..d608389ec304 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -426,6 +426,7 @@
+
diff --git a/java/codeserver/highlighting/intellij.java.codeserver.highlighting.iml b/java/codeserver/highlighting/intellij.java.codeserver.highlighting.iml
new file mode 100644
index 000000000000..b78c2920f47c
--- /dev/null
+++ b/java/codeserver/highlighting/intellij.java.codeserver.highlighting.iml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties
new file mode 100644
index 000000000000..91ee28142333
--- /dev/null
+++ b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties
@@ -0,0 +1,11 @@
+insufficient.language.level={0} are not supported at language level ''{1}''
+
+annotation.not.allowed.here=Annotations are not allowed here
+annotation.not.applicable=''@{0}'' not applicable to {1}
+annotation.not.allowed.void='void' type may not be annotated
+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.missing.attribute={0} missing though required
diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/AnnotationChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/AnnotationChecker.java
new file mode 100644
index 000000000000..0f44b185b801
--- /dev/null
+++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/AnnotationChecker.java
@@ -0,0 +1,188 @@
+// 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;
+
+import com.intellij.codeInsight.AnnotationTargetUtil;
+import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
+import com.intellij.patterns.ElementPattern;
+import com.intellij.pom.java.JavaFeature;
+import com.intellij.psi.*;
+import com.intellij.psi.impl.source.PsiClassReferenceType;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.psi.util.PsiUtil;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+import static com.intellij.patterns.PsiJavaPatterns.psiElement;
+
+final class AnnotationChecker {
+ private static final ElementPattern ANY_ANNOTATION_ALLOWED = psiElement().andOr(
+ psiElement().withParent(PsiNameValuePair.class),
+ psiElement().withParents(PsiArrayInitializerMemberValue.class, PsiNameValuePair.class),
+ psiElement().withParents(PsiArrayInitializerMemberValue.class, PsiAnnotationMethod.class),
+ psiElement().withParent(PsiAnnotationMethod.class).afterLeaf(PsiKeyword.DEFAULT),
+ // Unterminated parameter list like "void test(@NotNull String)": error on annotation looks annoying here
+ psiElement().withParents(PsiModifierList.class, PsiParameterList.class)
+ );
+
+ private final JavaErrorVisitor myVisitor;
+
+ AnnotationChecker(JavaErrorVisitor visitor) { myVisitor = visitor; }
+
+ private void checkReferenceTarget(@NotNull PsiAnnotation annotation, @Nullable PsiJavaCodeReferenceElement ref) {
+ if (ref == null) return;
+ PsiElement refTarget = ref.resolve();
+ if (refTarget == null) return;
+
+ if (!(refTarget instanceof PsiClass)) {
+ myVisitor.report(JavaErrorKinds.ANNOTATION_NOT_ALLOWED_REF.create(annotation));
+ return;
+ }
+
+ PsiElement parent = ref.getParent();
+ while (parent instanceof PsiJavaCodeReferenceElement referenceElement) {
+ PsiElement qualified = referenceElement.resolve();
+ if (qualified instanceof PsiMember member && member.hasModifierProperty(PsiModifier.STATIC)) {
+ myVisitor.report(JavaErrorKinds.ANNOTATION_NOT_ALLOWED_STATIC.create(annotation));
+ return;
+ }
+ if (qualified instanceof PsiClass) {
+ parent = parent.getParent();
+ }
+ else {
+ break;
+ }
+ }
+ }
+
+ @Contract("null->null; !null->!null")
+ private static PsiJavaCodeReferenceElement getOutermostReferenceElement(@Nullable PsiJavaCodeReferenceElement ref) {
+ if (ref == null) return null;
+
+ while (ref.getQualifier() instanceof PsiJavaCodeReferenceElement referenceElement) {
+ ref = referenceElement;
+ }
+ return ref;
+ }
+
+ void checkAnnotation(@NotNull PsiAnnotation annotation) {
+ if (!myVisitor.hasErrorResults()) checkAnnotationApplicability(annotation);
+ if (!myVisitor.hasErrorResults()) checkAnnotationType(annotation);
+ if (!myVisitor.hasErrorResults()) checkMissingAttributes(annotation);
+ }
+
+ private void checkMissingAttributes(@NotNull PsiAnnotation annotation) {
+ PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
+ if (nameRef == null) return;
+ PsiElement aClass = nameRef.resolve();
+ if (aClass instanceof PsiClass psiClass && psiClass.isAnnotationType()) {
+ Set names = new HashSet<>();
+ PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes();
+ for (PsiNameValuePair attribute : attributes) {
+ String name = attribute.getName();
+ names.add(Objects.requireNonNullElse(name, PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME));
+ }
+
+ PsiMethod[] annotationMethods = psiClass.getMethods();
+ List missed = new ArrayList<>();
+ for (PsiMethod method : annotationMethods) {
+ if (PsiUtil.isAnnotationMethod(method)) {
+ PsiAnnotationMethod annotationMethod = (PsiAnnotationMethod)method;
+ if (annotationMethod.getDefaultValue() == null) {
+ if (!names.contains(annotationMethod.getName())) {
+ missed.add(annotationMethod.getName());
+ }
+ }
+ }
+ }
+
+ if (!missed.isEmpty()) {
+ myVisitor.report(JavaErrorKinds.ANNOTATION_MISSING_ATTRIBUTE.create(annotation, missed));
+ }
+ }
+ }
+
+ private void checkAnnotationType(@NotNull PsiAnnotation annotation) {
+ PsiJavaCodeReferenceElement nameReferenceElement = annotation.getNameReferenceElement();
+ if (nameReferenceElement != null) {
+ PsiElement resolved = nameReferenceElement.resolve();
+ if (resolved != null && (!(resolved instanceof PsiClass psiClass) || !psiClass.isAnnotationType())) {
+ myVisitor.report(JavaErrorKinds.ANNOTATION_TYPE_EXPECTED.create(nameReferenceElement));
+ }
+ }
+ }
+
+ private void checkAnnotationApplicability(@NotNull PsiAnnotation annotation) {
+ if (ANY_ANNOTATION_ALLOWED.accepts(annotation)) return;
+ PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
+ if (nameRef == null) return;
+
+ PsiAnnotationOwner owner = annotation.getOwner();
+ PsiAnnotation.TargetType[] targets = AnnotationTargetUtil.getTargetsForLocation(owner);
+ if (owner == null || targets.length == 0) {
+ myVisitor.report(JavaErrorKinds.ANNOTATION_NOT_ALLOWED_HERE.create(annotation));
+ return;
+ }
+ if (!(owner instanceof PsiModifierList)) {
+ myVisitor.checkFeature(annotation, JavaFeature.TYPE_ANNOTATIONS);
+ if (myVisitor.hasErrorResults()) return;
+ }
+
+ PsiAnnotation.TargetType applicable = AnnotationTargetUtil.findAnnotationTarget(annotation, targets);
+ if (applicable == PsiAnnotation.TargetType.UNKNOWN) return;
+
+ if (applicable == null) {
+ if (targets.length == 1 && targets[0] == PsiAnnotation.TargetType.TYPE_USE) {
+ PsiElement parent = annotation.getParent();
+ if (parent instanceof PsiTypeElement) {
+ PsiElement modifierList =
+ PsiTreeUtil.skipSiblingsBackward(parent, PsiWhiteSpace.class, PsiComment.class, PsiTypeParameterList.class);
+ if (modifierList instanceof PsiModifierList psiModifierList) {
+ targets = AnnotationTargetUtil.getTargetsForLocation(psiModifierList);
+ if (AnnotationTargetUtil.findAnnotationTarget(annotation, targets) == null) {
+ myVisitor.report(JavaErrorKinds.ANNOTATION_NOT_APPLICABLE.create(annotation, Arrays.asList(targets)));
+ }
+ return;
+ }
+ }
+ }
+ myVisitor.report(JavaErrorKinds.ANNOTATION_NOT_APPLICABLE.create(annotation, Arrays.asList(targets)));
+ return;
+ }
+
+ if (applicable == PsiAnnotation.TargetType.TYPE_USE) {
+ if (PsiTreeUtil.skipParentsOfType(annotation, PsiTypeElement.class) instanceof PsiClassObjectAccessExpression) {
+ myVisitor.report(JavaErrorKinds.ANNOTATION_NOT_ALLOWED_CLASS.create(annotation));
+ return;
+ }
+ if (owner instanceof PsiClassReferenceType referenceType) {
+ PsiJavaCodeReferenceElement ref = referenceType.getReference();
+ checkReferenceTarget(annotation, ref);
+ }
+ else if (owner instanceof PsiModifierList || owner instanceof PsiTypeElement) {
+ PsiElement nextElement = owner instanceof PsiTypeElement typeElementOwner
+ ? typeElementOwner
+ : PsiTreeUtil.skipSiblingsForward((PsiModifierList)owner, PsiComment.class, PsiWhiteSpace.class,
+ PsiTypeParameterList.class);
+ if (nextElement instanceof PsiTypeElement typeElement) {
+ PsiType type = typeElement.getType();
+ //see JLS 9.7.4 Where Annotations May Appear
+ if (PsiTypes.voidType().equals(type)) {
+ myVisitor.report(JavaErrorKinds.ANNOTATION_NOT_ALLOWED_VOID.create(annotation));
+ return;
+ }
+ if (typeElement.isInferredType()) {
+ myVisitor.report(JavaErrorKinds.ANNOTATION_NOT_ALLOWED_VAR.create(annotation));
+ return;
+ }
+ if (!(type instanceof PsiPrimitiveType || type instanceof PsiArrayType)) {
+ PsiJavaCodeReferenceElement ref = getOutermostReferenceElement(typeElement.getInnermostComponentReferenceElement());
+ checkReferenceTarget(annotation, ref);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaCompilationErrorBundle.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaCompilationErrorBundle.java
new file mode 100644
index 000000000000..a9a03f84ea0f
--- /dev/null
+++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaCompilationErrorBundle.java
@@ -0,0 +1,26 @@
+// 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;
+
+import com.intellij.DynamicBundle;
+import org.jetbrains.annotations.Nls;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.PropertyKey;
+
+import java.util.function.Supplier;
+
+public final class JavaCompilationErrorBundle {
+ public static final @NonNls String BUNDLE = "messages.JavaCompilationErrorBundle";
+ private static final DynamicBundle INSTANCE = new DynamicBundle(JavaCompilationErrorBundle.class, BUNDLE);
+
+ private JavaCompilationErrorBundle() {
+ }
+
+ public static @NotNull @Nls String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) {
+ return INSTANCE.getMessage(key, params);
+ }
+
+ public static @NotNull Supplier<@Nls String> messagePointer(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) {
+ return INSTANCE.getLazyMessage(key, params);
+ }
+}
diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorCollector.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorCollector.java
new file mode 100644
index 000000000000..6e1017f27d0f
--- /dev/null
+++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorCollector.java
@@ -0,0 +1,37 @@
+// 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;
+
+import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.function.Consumer;
+
+/**
+ * A starting point to highlight errors in Java code
+ */
+public final class JavaErrorCollector {
+ private final JavaErrorVisitor myVisitor;
+
+ /**
+ * Create a collector to collect Java errors. After creation every single PSI element in the desired range should be passed into
+ * {@link #processElement(PsiElement)} to actually gather errors
+ *
+ * @param file Java file to process
+ * @param consumer a consumer to get errors
+ */
+ public JavaErrorCollector(@NotNull PsiFile file, @NotNull Consumer<@NotNull JavaCompilationError, ?>> consumer) {
+ myVisitor = new JavaErrorVisitor(file, consumer);
+ }
+
+ /**
+ * Finds the errors related to a given element. One must call this method for every element recursively in the desired range
+ * to get all the errors.
+ *
+ * @param element element to find the errors at.
+ */
+ public void processElement(@NotNull PsiElement element) {
+ element.accept(myVisitor);
+ }
+}
diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java
new file mode 100644
index 000000000000..b4f031fcc4ea
--- /dev/null
+++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java
@@ -0,0 +1,62 @@
+// 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;
+
+import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError;
+import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
+import com.intellij.openapi.project.Project;
+import com.intellij.pom.java.JavaFeature;
+import com.intellij.pom.java.LanguageLevel;
+import com.intellij.psi.JavaElementVisitor;
+import com.intellij.psi.PsiAnnotation;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.util.PsiUtil;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.function.Consumer;
+
+final class JavaErrorVisitor extends JavaElementVisitor {
+ private final @NotNull Consumer> myErrorConsumer;
+ private final @NotNull Project myProject;
+ private final @NotNull PsiFile myFile;
+ private final @NotNull LanguageLevel myLanguageLevel;
+ private final @NotNull AnnotationChecker myAnnotationChecker = new AnnotationChecker(this);
+ private boolean myHasError; // true if myHolder.add() was called with HighlightInfo of >=ERROR severity. On each .visit(PsiElement) call this flag is reset. Useful to determine whether the error was already reported while visiting this PsiElement.
+
+ JavaErrorVisitor(@NotNull PsiFile file, @NotNull Consumer> consumer) {
+ myFile = file;
+ myProject = file.getProject();
+ myLanguageLevel = PsiUtil.getLanguageLevel(file);
+ myErrorConsumer = consumer;
+ }
+
+ void report(@NotNull JavaCompilationError, ?> error) {
+ myErrorConsumer.accept(error);
+ myHasError = true;
+ }
+
+ @Contract(pure = true)
+ boolean hasErrorResults() {
+ return myHasError;
+ }
+
+ @Override
+ public void visitElement(@NotNull PsiElement element) {
+ super.visitElement(element);
+ myHasError = false;
+ }
+
+ @Override
+ public void visitAnnotation(@NotNull PsiAnnotation annotation) {
+ super.visitAnnotation(annotation);
+ if (!hasErrorResults()) checkFeature(annotation, JavaFeature.ANNOTATIONS);
+ myAnnotationChecker.checkAnnotation(annotation);
+ }
+
+ void checkFeature(@NotNull PsiElement element, @NotNull JavaFeature feature) {
+ if (!feature.isSufficient(myLanguageLevel)) {
+ report(JavaErrorKinds.UNSUPPORTED_FEATURE.create(element, feature));
+ }
+ }
+}
diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaCompilationError.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaCompilationError.java
new file mode 100644
index 000000000000..7dc446932384
--- /dev/null
+++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaCompilationError.java
@@ -0,0 +1,36 @@
+// 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.openapi.util.text.HtmlChunk;
+import com.intellij.psi.PsiElement;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A concrete instance of a Java compilation error
+ *
+ * @param kind error kind
+ * @param psi PSI element where the error occurred
+ * @param context additional context necessary to properly render the error and the corresponding quick-fixes
+ * @param type of the context
+ */
+public record JavaCompilationError(@NotNull JavaErrorKind kind,
+ @NotNull Psi psi,
+ Context context) {
+ public JavaCompilationError {
+ kind.validate(psi, context);
+ }
+
+ /**
+ * @return a desired anchor to put the error message at
+ */
+ public @NotNull PsiElement anchor() {
+ return kind.anchor(psi, context);
+ }
+
+ /**
+ * @return a user-readable localized error description
+ */
+ public @NotNull HtmlChunk description() {
+ return kind.description(psi, context);
+ }
+}
diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKind.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKind.java
new file mode 100644
index 000000000000..b069d8c0a03a
--- /dev/null
+++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKind.java
@@ -0,0 +1,53 @@
+// 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.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;
+
+/**
+ * A kind of Java compilation error
+ *
+ * @param type of context PSI element required for this error
+ * @param additional context required for a particular kind, if any
+ */
+public sealed interface JavaErrorKind permits Parameterized, JavaSimpleErrorKind {
+ /**
+ * @return a key, which uniquely identifies the error kind
+ */
+ @NotNull @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) String key();
+
+ /**
+ * @param psi PSI element associated with an error
+ * @param context a context in which the error should be rendered
+ * @return rendered localized error description
+ */
+ @NotNull HtmlChunk description(@NotNull Psi psi, Context context);
+
+ /**
+ * @param psi PSI element associated with an error
+ * @param context a context in which the error should be rendered
+ * @return error message anchor (must be within the psi)
+ */
+ default @NotNull PsiElement anchor(@NotNull Psi psi, Context context) {
+ return psi;
+ }
+
+ /**
+ * @param psi PSI element associated with an error
+ * @param context a context in which the error should be rendered
+ * @throws IllegalArgumentException if the context or PSI element are not applicable to this error kind
+ */
+ default void validate(@NotNull Psi psi, Context context) throws IllegalArgumentException {
+ }
+
+ /**
+ * @param context context to bind an instance to
+ * @return create an instance of this error
+ */
+ default @NotNull JavaCompilationError create(@NotNull Psi psi, Context context) {
+ return new JavaCompilationError<>(this, psi, context);
+ }
+}
diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java
new file mode 100644
index 000000000000..51d18cbfcdea
--- /dev/null
+++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java
@@ -0,0 +1,80 @@
+// 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.core.JavaPsiBundle;
+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.util.PsiUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * All possible Java error kinds
+ */
+public final class JavaErrorKinds {
+ private JavaErrorKinds() {}
+
+ public static final JavaErrorKind UNSUPPORTED_FEATURE =
+ new Parameterized<>("insufficient.language.level") {
+ @Override
+ public @NotNull HtmlChunk description(@NotNull PsiElement element, @NotNull JavaFeature feature) {
+ String name = feature.getFeatureName();
+ String version = JavaSdkVersion.fromLanguageLevel(PsiUtil.getLanguageLevel(element)).getDescription();
+ return HtmlChunk.raw(JavaCompilationErrorBundle.message("insufficient.language.level", name, version));
+ }
+ };
+
+ public static final JavaSimpleErrorKind ANNOTATION_NOT_ALLOWED_HERE =
+ new JavaSimpleErrorKind<>("annotation.not.allowed.here");
+ public static final JavaSimpleErrorKind ANNOTATION_NOT_ALLOWED_VAR =
+ new JavaSimpleErrorKind<>("annotation.not.allowed.var");
+ public static final JavaSimpleErrorKind ANNOTATION_NOT_ALLOWED_VOID =
+ new JavaSimpleErrorKind<>("annotation.not.allowed.void");
+ public static final JavaSimpleErrorKind ANNOTATION_NOT_ALLOWED_CLASS =
+ new JavaSimpleErrorKind<>("annotation.not.allowed.class");
+ public static final JavaSimpleErrorKind ANNOTATION_NOT_ALLOWED_REF =
+ new JavaSimpleErrorKind<>("annotation.not.allowed.ref");
+ public static final JavaSimpleErrorKind ANNOTATION_NOT_ALLOWED_STATIC =
+ new JavaSimpleErrorKind<>("annotation.not.allowed.static");
+ public static final JavaSimpleErrorKind ANNOTATION_TYPE_EXPECTED =
+ new JavaSimpleErrorKind<>("annotation.annotation.type.expected");
+ public static final JavaErrorKind> ANNOTATION_NOT_APPLICABLE =
+ new Parameterized<>("annotation.not.applicable") {
+ @Override
+ public void validate(@NotNull PsiAnnotation annotation, @NotNull List types)
+ throws IllegalArgumentException {
+ if (types.isEmpty()) {
+ throw new IllegalArgumentException("types must not be empty");
+ }
+ }
+
+ @Override
+ public @NotNull HtmlChunk description(@NotNull PsiAnnotation annotation, @NotNull List types) {
+ String target = JavaPsiBundle.message("annotation.target." + types.get(0));
+ PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
+ return HtmlChunk.raw(JavaCompilationErrorBundle.message(
+ "annotation.not.applicable", nameRef != null ? nameRef.getText() : annotation.getText(), target));
+ }
+ };
+ public static final JavaErrorKind> ANNOTATION_MISSING_ATTRIBUTE =
+ new Parameterized<>("annotation.missing.attribute") {
+ @Override
+ public @NotNull PsiElement anchor(@NotNull PsiAnnotation annotation, @NotNull List strings) {
+ return Objects.requireNonNull(annotation.getNameReferenceElement());
+ }
+
+ @Override
+ public @NotNull HtmlChunk description(@NotNull PsiAnnotation annotation, @NotNull List attributeNames) {
+ return HtmlChunk.raw(JavaCompilationErrorBundle.message(
+ "annotation.missing.attribute", attributeNames.stream().map(attr -> "'" + attr + "'").collect(Collectors.joining(", "))));
+ }
+ };
+}
diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaSimpleErrorKind.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaSimpleErrorKind.java
new file mode 100644
index 000000000000..2fb889dea7b9
--- /dev/null
+++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaSimpleErrorKind.java
@@ -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.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;
+
+/**
+ * Java compilation error without additional context
+ */
+public final class JavaSimpleErrorKind implements JavaErrorKind {
+ private final @NotNull @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) String myKey;
+
+ JavaSimpleErrorKind(@NotNull @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) String key) {
+ myKey = key;
+ }
+
+ @Override
+ public @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) @NotNull String key() {
+ return myKey;
+ }
+
+ @Override
+ public @NotNull HtmlChunk description(@NotNull Psi psi, Void unused) {
+ return HtmlChunk.raw(JavaCompilationErrorBundle.message(myKey));
+ }
+
+ public @NotNull JavaCompilationError create(@NotNull Psi psi) {
+ return JavaErrorKind.super.create(psi, null);
+ }
+
+ @Override
+ public String toString() {
+ return "JavaErrorKind[" + myKey + "]";
+ }
+}
diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/Parameterized.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/Parameterized.java
new file mode 100644
index 000000000000..861467a06359
--- /dev/null
+++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/Parameterized.java
@@ -0,0 +1,25 @@
+// 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.java.codeserver.highlighting.JavaCompilationErrorBundle;
+import com.intellij.psi.PsiElement;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.PropertyKey;
+
+non-sealed abstract class Parameterized implements JavaErrorKind {
+ private final @NotNull @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) String myKey;
+
+ Parameterized(@NotNull @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) String key) {
+ myKey = key;
+ }
+
+ @Override
+ public @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) @NotNull String key() {
+ return myKey;
+ }
+
+ @Override
+ public String toString() {
+ return "JavaErrorKind[" + myKey + "]";
+ }
+}
diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/package-info.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/package-info.java
new file mode 100644
index 000000000000..10947215b9af
--- /dev/null
+++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+@Experimental
+package com.intellij.java.codeserver.highlighting.errors;
+
+import org.jetbrains.annotations.ApiStatus.Experimental;
diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/package-info.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/package-info.java
new file mode 100644
index 000000000000..25c8e4f5d1c1
--- /dev/null
+++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/package-info.java
@@ -0,0 +1,8 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+/**
+ * Error highlighting of Java code
+ */
+@Experimental
+package com.intellij.java.codeserver.highlighting;
+
+import static org.jetbrains.annotations.ApiStatus.Experimental;
diff --git a/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java b/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java
index 7ff3b39fac28..b971bcca5284 100644
--- a/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java
+++ b/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java
@@ -312,6 +312,9 @@ public abstract class QuickFixFactory {
public abstract @NotNull List<@NotNull LocalQuickFix> registerOrderEntryFixes(@NotNull PsiReference reference,
@NotNull List super IntentionAction> registrar);
+ /**
+ * @param annotationMethods unused, could be empty array
+ */
public abstract @NotNull IntentionAction createAddMissingRequiredAnnotationParametersFix(@NotNull PsiAnnotation annotation,
PsiMethod @NotNull [] annotationMethods,
@NotNull Collection missedElements);
diff --git a/java/java-analysis-impl/intellij.java.analysis.impl.iml b/java/java-analysis-impl/intellij.java.analysis.impl.iml
index 44c41eae087c..7979e49e72d8 100644
--- a/java/java-analysis-impl/intellij.java.analysis.impl.iml
+++ b/java/java-analysis-impl/intellij.java.analysis.impl.iml
@@ -29,5 +29,6 @@
+
\ No newline at end of file
diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java
index 2246acc93932..fafbc22d566d 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java
@@ -6,33 +6,27 @@ 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.daemon.impl.quickfix.MoveAnnotationOnStaticMemberQualifyingTypeFix;
import com.intellij.codeInsight.daemon.impl.quickfix.MoveAnnotationToPackageInfoFileFix;
-import com.intellij.codeInsight.daemon.impl.quickfix.ReplaceVarWithExplicitTypeFix;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
import com.intellij.core.JavaPsiBundle;
import com.intellij.java.analysis.JavaAnalysisBundle;
-import com.intellij.modcommand.ModCommandAction;
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.patterns.ElementPattern;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
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.util.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.TypeUtils;
-import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -40,9 +34,9 @@ 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;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
public final class AnnotationsHighlightUtil {
private static final Logger LOG = Logger.getInstance(AnnotationsHighlightUtil.class);
@@ -313,51 +307,6 @@ public final class AnnotationsHighlightUtil {
return false;
}
- static HighlightInfo.Builder checkMissingAttributes(@NotNull PsiAnnotation annotation) {
- PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
- if (nameRef == null) return null;
- PsiElement aClass = nameRef.resolve();
- if (aClass instanceof PsiClass psiClass && psiClass.isAnnotationType()) {
- Set names = new HashSet<>();
- PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes();
- for (PsiNameValuePair attribute : attributes) {
- String name = attribute.getName();
- names.add(Objects.requireNonNullElse(name, PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME));
- }
-
- PsiMethod[] annotationMethods = psiClass.getMethods();
- List missed = new ArrayList<>();
- for (PsiMethod method : annotationMethods) {
- if (PsiUtil.isAnnotationMethod(method)) {
- PsiAnnotationMethod annotationMethod = (PsiAnnotationMethod)method;
- if (annotationMethod.getDefaultValue() == null) {
- if (!names.contains(annotationMethod.getName())) {
- missed.add(annotationMethod.getName());
- }
- }
- }
- }
-
- if (!missed.isEmpty()) {
- StringBuffer buff = new StringBuffer("'" + missed.get(0) + "'");
- for (int i = 1; i < missed.size(); i++) {
- buff.append(", ");
- buff.append("'").append(missed.get(i)).append("'");
- }
-
- String description = JavaErrorBundle.message("annotation.missing.attribute", buff);
- HighlightInfo.Builder info =
- HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameRef).descriptionAndTooltip(description);
- IntentionAction fix = QuickFixFactory.getInstance().createAddMissingRequiredAnnotationParametersFix(
- annotation, annotationMethods, missed);
- info.registerFix(fix, null, null, null, null);
- return info;
- }
- }
-
- return null;
- }
-
static HighlightInfo.Builder checkConstantExpression(@NotNull PsiExpression expression) {
PsiElement parent = expression.getParent();
if (PsiUtil.isAnnotationMethod(parent) || parent instanceof PsiNameValuePair || parent instanceof PsiArrayInitializerMemberValue) {
@@ -383,115 +332,6 @@ public final class AnnotationsHighlightUtil {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description);
}
- private static final ElementPattern ANY_ANNOTATION_ALLOWED = psiElement().andOr(
- psiElement().withParent(PsiNameValuePair.class),
- psiElement().withParents(PsiArrayInitializerMemberValue.class, PsiNameValuePair.class),
- psiElement().withParents(PsiArrayInitializerMemberValue.class, PsiAnnotationMethod.class),
- psiElement().withParent(PsiAnnotationMethod.class).afterLeaf(PsiKeyword.DEFAULT),
- // Unterminated parameter list like "void test(@NotNull String)": error on annotation looks annoying here
- psiElement().withParents(PsiModifierList.class, PsiParameterList.class)
- );
-
- public static HighlightInfo.Builder checkApplicability(@NotNull PsiAnnotation annotation, @NotNull LanguageLevel level, @NotNull PsiFile file) {
- if (ANY_ANNOTATION_ALLOWED.accepts(annotation)) {
- return null;
- }
-
- PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
- if (nameRef == null) return null;
-
- PsiAnnotationOwner owner = annotation.getOwner();
- PsiAnnotation.TargetType[] targets = AnnotationTargetUtil.getTargetsForLocation(owner);
- if (owner == null || targets.length == 0) {
- String message = JavaErrorBundle.message("annotation.not.allowed.here");
- return createAnnotationError(annotation, message);
- }
-
- if (!(owner instanceof PsiModifierList)) {
- HighlightInfo.Builder info = HighlightUtil.checkFeature(annotation, JavaFeature.TYPE_ANNOTATIONS, level, file);
- if (info != null) return info;
- }
-
- PsiAnnotation.TargetType applicable = AnnotationTargetUtil.findAnnotationTarget(annotation, targets);
- if (applicable == PsiAnnotation.TargetType.UNKNOWN) return null;
-
- if (applicable == null) {
- if (targets.length == 1 && targets[0] == PsiAnnotation.TargetType.TYPE_USE) {
- PsiElement parent = annotation.getParent();
- if (parent instanceof PsiTypeElement) {
- PsiElement modifierList = PsiTreeUtil.skipSiblingsBackward(parent, PsiWhiteSpace.class, PsiComment.class, PsiTypeParameterList.class);
- if (modifierList instanceof PsiModifierList psiModifierList) {
- targets = AnnotationTargetUtil.getTargetsForLocation(psiModifierList);
- if (AnnotationTargetUtil.findAnnotationTarget(annotation, targets) == null) {
- return createNotApplicableTargetInfo(annotation, nameRef, targets);
- }
- return null;
- }
- }
- }
- return createNotApplicableTargetInfo(annotation, nameRef, targets);
- }
-
-
- if (applicable == PsiAnnotation.TargetType.TYPE_USE) {
- if (owner instanceof PsiClassReferenceType referenceType) {
- PsiJavaCodeReferenceElement ref = referenceType.getReference();
- HighlightInfo.Builder info = checkReferenceTarget(annotation, ref);
- if (info != null) return info;
- }
- else if (owner instanceof PsiModifierList || owner instanceof PsiTypeElement) {
- PsiElement nextElement = owner instanceof PsiTypeElement typeElementOwner
- ? typeElementOwner
- : PsiTreeUtil.skipSiblingsForward((PsiModifierList)owner, PsiComment.class, PsiWhiteSpace.class, PsiTypeParameterList.class);
- if (nextElement instanceof PsiTypeElement typeElement) {
- PsiType type = typeElement.getType();
- //see JLS 9.7.4 Where Annotations May Appear
- if (PsiTypes.voidType().equals(type)) {
- String message = JavaErrorBundle.message("annotation.not.allowed.void");
- return createAnnotationError(annotation, message);
- }
- if (typeElement.isInferredType()) {
- HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
- .range(annotation)
- .descriptionAndTooltip(JavaErrorBundle.message("annotation.not.allowed.var"));
- IntentionAction action1 =
- QuickFixFactory.getInstance().createDeleteFix(annotation, JavaAnalysisBundle.message("intention.text.remove.annotation"));
- info.registerFix(action1, null, null, null, null);
- ModCommandAction action = new ReplaceVarWithExplicitTypeFix(typeElement);
- info.registerFix(action, null, null, null, null);
- return info;
- }
- if (!(type instanceof PsiPrimitiveType || type instanceof PsiArrayType)) {
- PsiJavaCodeReferenceElement ref = getOutermostReferenceElement(typeElement.getInnermostComponentReferenceElement());
- HighlightInfo.Builder info = checkReferenceTarget(annotation, ref);
- if (info != null) return info;
- }
- }
- }
- if (PsiTreeUtil.skipParentsOfType(annotation, PsiTypeElement.class) instanceof PsiClassObjectAccessExpression) {
- String message = JavaErrorBundle.message("annotation.not.allowed.class");
- return createAnnotationError(annotation, message);
- }
- }
-
- return null;
- }
-
- private static @NotNull HighlightInfo.Builder createNotApplicableTargetInfo(@NotNull PsiAnnotation annotation,
- PsiJavaCodeReferenceElement nameRef,
- PsiAnnotation.TargetType[] targets) {
- String target = JavaPsiBundle.message("annotation.target." + targets[0]);
- String message = JavaErrorBundle.message("annotation.not.applicable", nameRef.getText(), target);
- HighlightInfo.Builder info = createAnnotationError(annotation, message);
- if (BaseIntentionAction.canModify(Objects.requireNonNull(annotation.resolveAnnotationType()))) {
- for (PsiAnnotation.TargetType targetType : targets) {
- IntentionAction action = QuickFixFactory.getInstance().createAddAnnotationTargetFix(annotation, targetType);
- info.registerFix(action, null, null, null, null);
- }
- }
- return info;
- }
-
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"));
@@ -508,55 +348,6 @@ public final class AnnotationsHighlightUtil {
return info;
}
- private static @Nullable HighlightInfo.Builder checkReferenceTarget(@NotNull PsiAnnotation annotation, @Nullable PsiJavaCodeReferenceElement ref) {
- if (ref == null) return null;
- PsiElement refTarget = ref.resolve();
- if (refTarget == null) return null;
-
- if (!(refTarget instanceof PsiClass)) {
- return createAnnotationError(annotation, JavaErrorBundle.message("annotation.not.allowed.ref"));
- }
-
- PsiElement parent = ref.getParent();
- while (parent instanceof PsiJavaCodeReferenceElement referenceElement) {
- PsiElement qualified = referenceElement.resolve();
- if (qualified instanceof PsiMember member && member.hasModifierProperty(PsiModifier.STATIC)) {
- return createAnnotationError(annotation,
- JavaErrorBundle.message("annotation.not.allowed.static"),
- new MoveAnnotationOnStaticMemberQualifyingTypeFix(annotation).asIntention());
- }
- if (qualified instanceof PsiClass) {
- parent = parent.getParent();
- }
- else {
- break;
- }
- }
- return null;
- }
-
- @Contract("null->null; !null->!null")
- private static PsiJavaCodeReferenceElement getOutermostReferenceElement(@Nullable PsiJavaCodeReferenceElement ref) {
- if (ref == null) return null;
-
- while (ref.getQualifier() instanceof PsiJavaCodeReferenceElement referenceElement) {
- ref = referenceElement;
- }
- return ref;
- }
-
- static HighlightInfo.Builder checkAnnotationType(@NotNull PsiAnnotation annotation) {
- PsiJavaCodeReferenceElement nameReferenceElement = annotation.getNameReferenceElement();
- if (nameReferenceElement != null) {
- PsiElement resolved = nameReferenceElement.resolve();
- if (resolved != null && (!(resolved instanceof PsiClass psiClass) || !psiClass.isAnnotationType())) {
- String description = JavaErrorBundle.message("annotation.annotation.type.expected");
- return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameReferenceElement).descriptionAndTooltip(description);
- }
- }
- return null;
- }
-
static HighlightInfo.Builder checkCyclicMemberType(@NotNull PsiTypeElement typeElement, @NotNull PsiClass aClass) {
PsiType type = typeElement.getType();
Set checked = new HashSet<>();
diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java
index 669fd9c45f82..ea389a2ad01f 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java
@@ -9,9 +9,11 @@ import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.codeInsight.daemon.impl.HighlightVisitor;
import com.intellij.codeInsight.daemon.impl.quickfix.AdjustFunctionContextFix;
import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction;
+import com.intellij.codeInsight.intention.CommonIntentionAction;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.codeInspection.ex.GlobalInspectionContextBase;
+import com.intellij.java.codeserver.highlighting.JavaErrorCollector;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.lang.jvm.JvmModifier;
@@ -64,6 +66,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
private @NotNull PsiFile myFile;
private PsiJavaModule myJavaModule;
+ private JavaErrorCollector myCollector;
private PreviewFeatureUtil.PreviewFeatureVisitor myPreviewFeatureVisitor;
@@ -164,6 +167,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
public void visit(@NotNull PsiElement element) {
myHasError = false;
element.accept(this);
+ myCollector.processElement(element);
}
@Override
@@ -215,6 +219,15 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
.notNull(JavaVersionService.getInstance().getJavaSdkVersion(file), JavaSdkVersion.fromLanguageLevel(myLanguageLevel));
myJavaModule = JavaFeature.MODULES.isSufficient(myLanguageLevel) ? JavaModuleGraphUtil.findDescriptorByElement(file) : null;
myPreviewFeatureVisitor = myLanguageLevel.isPreview() ? null : new PreviewFeatureUtil.PreviewFeatureVisitor(myLanguageLevel, myErrorSink);
+ myCollector = new JavaErrorCollector(myFile, error -> {
+ HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
+ .range(error.anchor())
+ .descriptionAndTooltip(error.description().toString());
+ for (CommonIntentionAction fix : JavaErrorFixProvider.getFixes(error)) {
+ info.registerFix(fix.asIntention(), null, null, null, null);
+ }
+ add(info);
+ });
}
@Override
@@ -231,10 +244,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
@Override
public void visitAnnotation(@NotNull PsiAnnotation annotation) {
super.visitAnnotation(annotation);
- if (!hasErrorResults()) add(checkFeature(annotation, JavaFeature.ANNOTATIONS));
- if (!hasErrorResults()) add(AnnotationsHighlightUtil.checkApplicability(annotation, myLanguageLevel, myFile));
- if (!hasErrorResults()) add(AnnotationsHighlightUtil.checkAnnotationType(annotation));
- if (!hasErrorResults()) add(AnnotationsHighlightUtil.checkMissingAttributes(annotation));
if (!hasErrorResults()) add(AnnotationsHighlightUtil.checkTargetAnnotationDuplicates(annotation));
if (!hasErrorResults()) add(AnnotationsHighlightUtil.checkDuplicateAnnotations(annotation, myLanguageLevel));
if (!hasErrorResults()) add(AnnotationsHighlightUtil.checkFunctionalInterface(annotation, myLanguageLevel));
diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java
new file mode 100644
index 000000000000..10a6e07c76b2
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java
@@ -0,0 +1,90 @@
+// 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.codeInsight.daemon.impl.analysis;
+
+import com.intellij.codeInsight.daemon.impl.quickfix.MoveAnnotationOnStaticMemberQualifyingTypeFix;
+import com.intellij.codeInsight.daemon.impl.quickfix.ReplaceVarWithExplicitTypeFix;
+import com.intellij.codeInsight.intention.CommonIntentionAction;
+import com.intellij.codeInsight.intention.QuickFixFactory;
+import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
+import com.intellij.java.analysis.JavaAnalysisBundle;
+import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError;
+import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind;
+import com.intellij.psi.*;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+import static com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds.*;
+
+/**
+ * Fixes attached to error messages provided by {@link com.intellij.java.codeserver.highlighting.JavaErrorCollector}
+ */
+final class JavaErrorFixProvider {
+ @FunctionalInterface
+ private interface JavaFixProvider {
+ @Nullable CommonIntentionAction provide(@NotNull JavaCompilationError extends Psi, ? extends Context> error);
+
+ default JavaFixesProvider asMulti() {
+ return error -> ContainerUtil.createMaybeSingletonList(provide(error));
+ }
+ }
+
+ @FunctionalInterface
+ private interface JavaFixesProvider {
+ @NotNull List<@NotNull CommonIntentionAction> provide(@NotNull JavaCompilationError extends Psi, ? extends Context> error);
+ }
+
+ private static final Map, List>> FIXES = new HashMap<>();
+
+ static {
+ JavaFixProvider annotationRemover = error ->
+ QuickFixFactory.getInstance().createDeleteFix(error.psi(), JavaAnalysisBundle.message("intention.text.remove.annotation"));
+ single(ANNOTATION_NOT_ALLOWED_CLASS, annotationRemover);
+ single(ANNOTATION_NOT_ALLOWED_HERE, annotationRemover);
+ single(ANNOTATION_NOT_ALLOWED_REF, annotationRemover);
+ single(ANNOTATION_NOT_ALLOWED_VAR, annotationRemover);
+ single(ANNOTATION_NOT_ALLOWED_VOID, annotationRemover);
+ single(ANNOTATION_NOT_APPLICABLE, annotationRemover);
+ single(ANNOTATION_NOT_ALLOWED_VAR, error -> {
+ PsiAnnotationOwner owner = error.psi().getOwner();
+ PsiTypeElement type = owner instanceof PsiTypeElement te ? te :
+ PsiTreeUtil.skipSiblingsForward((PsiModifierList)owner, PsiComment.class, PsiWhiteSpace.class,
+ PsiTypeParameterList.class) instanceof PsiTypeElement te ? te :
+ null;
+ return type != null && type.isInferredType() ? new ReplaceVarWithExplicitTypeFix(type) : null;
+ });
+ multi(ANNOTATION_NOT_APPLICABLE, error -> {
+ if (!BaseIntentionAction.canModify(Objects.requireNonNull(error.psi().resolveAnnotationType()))) return List.of();
+ return ContainerUtil.map(error.context(),
+ targetType -> QuickFixFactory.getInstance().createAddAnnotationTargetFix(error.psi(), targetType));
+ });
+ single(ANNOTATION_NOT_ALLOWED_STATIC, error -> new MoveAnnotationOnStaticMemberQualifyingTypeFix(error.psi()));
+ multi(UNSUPPORTED_FEATURE, error -> HighlightUtil.getIncreaseLanguageLevelFixes(error.psi(), error.context()));
+ single(ANNOTATION_MISSING_ATTRIBUTE, error -> QuickFixFactory.getInstance().createAddMissingRequiredAnnotationParametersFix(
+ error.psi(), PsiMethod.EMPTY_ARRAY, error.context()));
+ }
+
+ private static void single(@NotNull JavaErrorKind kind,
+ @NotNull JavaFixProvider super Psi, ? super Context> fixProvider) {
+ multi(kind, fixProvider.asMulti());
+ }
+
+ private static void multi(@NotNull JavaErrorKind kind,
+ @NotNull JavaFixesProvider super Psi, ? super Context> fixProvider) {
+ FIXES.computeIfAbsent(kind, k -> new ArrayList<>()).add(fixProvider);
+ }
+
+ static @NotNull List getFixes(@NotNull JavaCompilationError, ?> error) {
+ var providers = FIXES.get(error.kind());
+ if (providers == null) return List.of();
+ List fixes = new ArrayList<>();
+ for (var provider : providers) {
+ @SuppressWarnings("unchecked") var fn = (JavaFixesProvider)provider;
+ fixes.addAll(fn.provide(error));
+ }
+ return fixes;
+ }
+}
diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddMissingRequiredAnnotationParametersFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddMissingRequiredAnnotationParametersFix.java
index 39d810b07132..ef935da07a45 100644
--- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddMissingRequiredAnnotationParametersFix.java
+++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddMissingRequiredAnnotationParametersFix.java
@@ -3,6 +3,7 @@ package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.template.impl.TextExpression;
+import com.intellij.java.JavaBundle;
import com.intellij.modcommand.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
@@ -25,17 +26,13 @@ import java.util.TreeSet;
public final class AddMissingRequiredAnnotationParametersFix extends PsiUpdateModCommandAction {
private static final Logger LOG = Logger.getInstance(AddMissingRequiredAnnotationParametersFix.class);
- private final PsiMethod[] myAnnotationMethods;
private final Collection myMissedElements;
- public AddMissingRequiredAnnotationParametersFix(final PsiAnnotation annotation,
- final PsiMethod[] annotationMethods,
- final Collection missedElements) {
+ public AddMissingRequiredAnnotationParametersFix(final PsiAnnotation annotation, final Collection missedElements) {
super(annotation);
if (missedElements.isEmpty()) {
throw new IllegalArgumentException("missedElements can't be empty");
}
- myAnnotationMethods = annotationMethods;
myMissedElements = missedElements;
}
@@ -55,7 +52,13 @@ public final class AddMissingRequiredAnnotationParametersFix extends PsiUpdateMo
protected void invoke(@NotNull ActionContext context, @NotNull PsiAnnotation annotation, @NotNull ModPsiUpdater updater) {
final PsiNameValuePair[] addedParameters = annotation.getParameterList().getAttributes();
- final Object2IntMap annotationsOrderMap = getAnnotationsOrderMap();
+ PsiClass aClass = annotation.resolveAnnotationType();
+ if (aClass == null) {
+ updater.cancel(JavaBundle.message("error.no.annotation.class.found"));
+ return;
+ }
+ PsiMethod[] methods = aClass.getMethods();
+ final Object2IntMap annotationsOrderMap = getAnnotationsOrderMap(methods);
final SortedSet> newParameters =
new TreeSet<>(Comparator.comparingInt(o -> annotationsOrderMap.getInt(o.getFirst())));
@@ -80,7 +83,7 @@ public final class AddMissingRequiredAnnotationParametersFix extends PsiUpdateMo
}
PsiElementFactory factory = JavaPsiFacade.getElementFactory(context.project());
- for (PsiMethod method : myAnnotationMethods) {
+ for (PsiMethod method : methods) {
if (myMissedElements.contains(method.getName())) {
PsiType type = method.getReturnType();
String defaultValue;
@@ -106,10 +109,10 @@ public final class AddMissingRequiredAnnotationParametersFix extends PsiUpdateMo
}
}
- private Object2IntMap getAnnotationsOrderMap() {
+ private Object2IntMap getAnnotationsOrderMap(PsiMethod[] methods) {
final Object2IntMap map = new Object2IntOpenHashMap<>();
- for (int i = 0; i < myAnnotationMethods.length; i++) {
- map.put(myAnnotationMethods[i].getName(), i);
+ for (int i = 0; i < methods.length; i++) {
+ map.put(methods[i].getName(), i);
}
return map;
}
diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java
index 5600d8316d63..5fff262d3cd1 100644
--- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java
+++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java
@@ -747,7 +747,7 @@ public final class QuickFixFactoryImpl extends QuickFixFactory {
public @NotNull IntentionAction createAddMissingRequiredAnnotationParametersFix(final @NotNull PsiAnnotation annotation,
final PsiMethod @NotNull [] annotationMethods,
final @NotNull Collection missedElements) {
- return new AddMissingRequiredAnnotationParametersFix(annotation, annotationMethods, missedElements).asIntention();
+ return new AddMissingRequiredAnnotationParametersFix(annotation, missedElements).asIntention();
}
@Override
diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties
index 41c347032f49..116a22b0db63 100644
--- a/java/openapi/resources/messages/JavaBundle.properties
+++ b/java/openapi/resources/messages/JavaBundle.properties
@@ -1964,4 +1964,5 @@ advanced.setting.java.show.irrelevant.templates.in.source.roots=Show irrelevant
java.test.use.wall.time=Use Wall Time
java.test.use.live.time=Use Live Time
java.test.overall.time=Overall time: {0}
-java.test.sum.time=Sum time: {0}
\ No newline at end of file
+java.test.sum.time=Sum time: {0}
+error.no.annotation.class.found=No annotation class found
\ No newline at end of file
diff --git a/java/plugin/plugin-content.yaml b/java/plugin/plugin-content.yaml
index 50e22cb0cac4..4e09534b1ad9 100644
--- a/java/plugin/plugin-content.yaml
+++ b/java/plugin/plugin-content.yaml
@@ -37,6 +37,7 @@
- name: intellij.platform.uast
- name: intellij.platform.uast.ide
- name: intellij.java.uast.ide
+ - name: intellij.java.codeserver.highlighting
- name: intellij.java.compiler.impl
- name: intellij.java.debugger.impl
- name: intellij.java.debugger.memory.agent
diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/JavaPluginLayout.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/JavaPluginLayout.kt
index d347945bf5d7..d312bfcbb24f 100644
--- a/platform/build-scripts/src/org/jetbrains/intellij/build/JavaPluginLayout.kt
+++ b/platform/build-scripts/src/org/jetbrains/intellij/build/JavaPluginLayout.kt
@@ -59,6 +59,7 @@ object JavaPluginLayout {
}
spec.withModules(listOf(
+ "intellij.java.codeserver.highlighting",
"intellij.java.compiler.impl",
"intellij.java.debugger.impl",
"intellij.java.terminal",