mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-highlighting] Initial draft of highlighting server; some annotation-related errors are reported by it
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: e815adec0be9017c8237bdd8fe7c67632f2438da
This commit is contained in:
committed by
intellij-monorepo-bot
parent
275753dbec
commit
e711100561
Generated
+1
@@ -426,6 +426,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/java/java-analysis-api/intellij.java.analysis.iml" filepath="$PROJECT_DIR$/java/java-analysis-api/intellij.java.analysis.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/java/java-analysis-impl/intellij.java.analysis.impl.iml" filepath="$PROJECT_DIR$/java/java-analysis-impl/intellij.java.analysis.impl.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/ByteCodeViewer/intellij.java.byteCodeViewer.iml" filepath="$PROJECT_DIR$/plugins/ByteCodeViewer/intellij.java.byteCodeViewer.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/java/codeserver/highlighting/intellij.java.codeserver.highlighting.iml" filepath="$PROJECT_DIR$/java/codeserver/highlighting/intellij.java.codeserver.highlighting.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/java/compiler/openapi/intellij.java.compiler.iml" filepath="$PROJECT_DIR$/java/compiler/openapi/intellij.java.compiler.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/java/compiler/javac2/intellij.java.compiler.antTasks.iml" filepath="$PROJECT_DIR$/java/compiler/javac2/intellij.java.compiler.antTasks.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/java/compiler/charts/intellij.java.compiler.charts.iml" filepath="$PROJECT_DIR$/java/compiler/charts/intellij.java.compiler.charts.iml" />
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="jetbrains-annotations" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.java.frontback.psi" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util" />
|
||||
<orderEntry type="module" module-name="intellij.java.psi" />
|
||||
<orderEntry type="module" module-name="intellij.java.psi.impl" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -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
|
||||
+188
@@ -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<PsiElement> 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<String> 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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -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);
|
||||
}
|
||||
}
|
||||
+37
@@ -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);
|
||||
}
|
||||
}
|
||||
+62
@@ -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<JavaCompilationError<?, ?>> 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<JavaCompilationError<?, ?>> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -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 <Context> type of the context
|
||||
*/
|
||||
public record JavaCompilationError<Psi extends PsiElement, Context>(@NotNull JavaErrorKind<Psi, Context> 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);
|
||||
}
|
||||
}
|
||||
+53
@@ -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 <Psi> type of context PSI element required for this error
|
||||
* @param <Context> additional context required for a particular kind, if any
|
||||
*/
|
||||
public sealed interface JavaErrorKind<Psi extends PsiElement, Context> 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<Psi, Context> create(@NotNull Psi psi, Context context) {
|
||||
return new JavaCompilationError<>(this, psi, context);
|
||||
}
|
||||
}
|
||||
+80
@@ -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<PsiElement, @NotNull JavaFeature> 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<PsiAnnotation> ANNOTATION_NOT_ALLOWED_HERE =
|
||||
new JavaSimpleErrorKind<>("annotation.not.allowed.here");
|
||||
public static final JavaSimpleErrorKind<PsiAnnotation> ANNOTATION_NOT_ALLOWED_VAR =
|
||||
new JavaSimpleErrorKind<>("annotation.not.allowed.var");
|
||||
public static final JavaSimpleErrorKind<PsiAnnotation> ANNOTATION_NOT_ALLOWED_VOID =
|
||||
new JavaSimpleErrorKind<>("annotation.not.allowed.void");
|
||||
public static final JavaSimpleErrorKind<PsiAnnotation> ANNOTATION_NOT_ALLOWED_CLASS =
|
||||
new JavaSimpleErrorKind<>("annotation.not.allowed.class");
|
||||
public static final JavaSimpleErrorKind<PsiAnnotation> ANNOTATION_NOT_ALLOWED_REF =
|
||||
new JavaSimpleErrorKind<>("annotation.not.allowed.ref");
|
||||
public static final JavaSimpleErrorKind<PsiAnnotation> ANNOTATION_NOT_ALLOWED_STATIC =
|
||||
new JavaSimpleErrorKind<>("annotation.not.allowed.static");
|
||||
public static final JavaSimpleErrorKind<PsiJavaCodeReferenceElement> ANNOTATION_TYPE_EXPECTED =
|
||||
new JavaSimpleErrorKind<>("annotation.annotation.type.expected");
|
||||
public static final JavaErrorKind<PsiAnnotation, @NotNull List<PsiAnnotation.@NotNull TargetType>> ANNOTATION_NOT_APPLICABLE =
|
||||
new Parameterized<>("annotation.not.applicable") {
|
||||
@Override
|
||||
public void validate(@NotNull PsiAnnotation annotation, @NotNull List<PsiAnnotation.@NotNull TargetType> types)
|
||||
throws IllegalArgumentException {
|
||||
if (types.isEmpty()) {
|
||||
throw new IllegalArgumentException("types must not be empty");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull HtmlChunk description(@NotNull PsiAnnotation annotation, @NotNull List<PsiAnnotation.@NotNull TargetType> 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<PsiAnnotation, @NotNull List<String>> ANNOTATION_MISSING_ATTRIBUTE =
|
||||
new Parameterized<>("annotation.missing.attribute") {
|
||||
@Override
|
||||
public @NotNull PsiElement anchor(@NotNull PsiAnnotation annotation, @NotNull List<String> strings) {
|
||||
return Objects.requireNonNull(annotation.getNameReferenceElement());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull HtmlChunk description(@NotNull PsiAnnotation annotation, @NotNull List<String> attributeNames) {
|
||||
return HtmlChunk.raw(JavaCompilationErrorBundle.message(
|
||||
"annotation.missing.attribute", attributeNames.stream().map(attr -> "'" + attr + "'").collect(Collectors.joining(", "))));
|
||||
}
|
||||
};
|
||||
}
|
||||
+38
@@ -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<Psi extends PsiElement> implements JavaErrorKind<Psi, Void> {
|
||||
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<Psi, Void> create(@NotNull Psi psi) {
|
||||
return JavaErrorKind.super.create(psi, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JavaErrorKind[" + myKey + "]";
|
||||
}
|
||||
}
|
||||
+25
@@ -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<Psi extends PsiElement, Context> implements JavaErrorKind<Psi, Context> {
|
||||
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 + "]";
|
||||
}
|
||||
}
|
||||
+5
@@ -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;
|
||||
+8
@@ -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;
|
||||
@@ -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<String> missedElements);
|
||||
|
||||
@@ -29,5 +29,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.backend.workspace" />
|
||||
<orderEntry type="module" module-name="intellij.java.frontback.impl" exported="" />
|
||||
<orderEntry type="module" module-name="intellij.platform.editor.ex" />
|
||||
<orderEntry type="module" module-name="intellij.java.codeserver.highlighting" />
|
||||
</component>
|
||||
</module>
|
||||
+3
-212
@@ -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<String> 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<String> 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<PsiElement> 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<PsiClass> checked = new HashSet<>();
|
||||
|
||||
+13
-4
@@ -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));
|
||||
|
||||
+90
@@ -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<Psi extends PsiElement, Context> {
|
||||
@Nullable CommonIntentionAction provide(@NotNull JavaCompilationError<? extends Psi, ? extends Context> error);
|
||||
|
||||
default JavaFixesProvider<Psi, Context> asMulti() {
|
||||
return error -> ContainerUtil.createMaybeSingletonList(provide(error));
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface JavaFixesProvider<Psi extends PsiElement, Context> {
|
||||
@NotNull List<@NotNull CommonIntentionAction> provide(@NotNull JavaCompilationError<? extends Psi, ? extends Context> error);
|
||||
}
|
||||
|
||||
private static final Map<JavaErrorKind<?, ?>, List<JavaFixesProvider<?, ?>>> FIXES = new HashMap<>();
|
||||
|
||||
static {
|
||||
JavaFixProvider<PsiAnnotation, Object> annotationRemover = error ->
|
||||
QuickFixFactory.getInstance().createDeleteFix(error.psi(), JavaAnalysisBundle.message("intention.text.remove.annotation"));
|
||||
single(ANNOTATION_NOT_ALLOWED_CLASS, annotationRemover);
|
||||
single(ANNOTATION_NOT_ALLOWED_HERE, annotationRemover);
|
||||
single(ANNOTATION_NOT_ALLOWED_REF, annotationRemover);
|
||||
single(ANNOTATION_NOT_ALLOWED_VAR, annotationRemover);
|
||||
single(ANNOTATION_NOT_ALLOWED_VOID, annotationRemover);
|
||||
single(ANNOTATION_NOT_APPLICABLE, annotationRemover);
|
||||
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 <Psi extends PsiElement, Context> void single(@NotNull JavaErrorKind<Psi, Context> kind,
|
||||
@NotNull JavaFixProvider<? super Psi, ? super Context> fixProvider) {
|
||||
multi(kind, fixProvider.asMulti());
|
||||
}
|
||||
|
||||
private static <Psi extends PsiElement, Context> void multi(@NotNull JavaErrorKind<Psi, Context> kind,
|
||||
@NotNull JavaFixesProvider<? super Psi, ? super Context> fixProvider) {
|
||||
FIXES.computeIfAbsent(kind, k -> new ArrayList<>()).add(fixProvider);
|
||||
}
|
||||
|
||||
static @NotNull List<CommonIntentionAction> getFixes(@NotNull JavaCompilationError<?, ?> error) {
|
||||
var providers = FIXES.get(error.kind());
|
||||
if (providers == null) return List.of();
|
||||
List<CommonIntentionAction> fixes = new ArrayList<>();
|
||||
for (var provider : providers) {
|
||||
@SuppressWarnings("unchecked") var fn = (JavaFixesProvider<PsiElement, Object>)provider;
|
||||
fixes.addAll(fn.provide(error));
|
||||
}
|
||||
return fixes;
|
||||
}
|
||||
}
|
||||
+13
-10
@@ -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<PsiAnnotation> {
|
||||
private static final Logger LOG = Logger.getInstance(AddMissingRequiredAnnotationParametersFix.class);
|
||||
|
||||
private final PsiMethod[] myAnnotationMethods;
|
||||
private final Collection<String> myMissedElements;
|
||||
|
||||
public AddMissingRequiredAnnotationParametersFix(final PsiAnnotation annotation,
|
||||
final PsiMethod[] annotationMethods,
|
||||
final Collection<String> missedElements) {
|
||||
public AddMissingRequiredAnnotationParametersFix(final PsiAnnotation annotation, final Collection<String> 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<String> 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<String> annotationsOrderMap = getAnnotationsOrderMap(methods);
|
||||
final SortedSet<Pair<String, PsiAnnotationMemberValue>> 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<String> getAnnotationsOrderMap() {
|
||||
private Object2IntMap<String> getAnnotationsOrderMap(PsiMethod[] methods) {
|
||||
final Object2IntMap<String> 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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -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<String> missedElements) {
|
||||
return new AddMissingRequiredAnnotationParametersFix(annotation, annotationMethods, missedElements).asIntention();
|
||||
return new AddMissingRequiredAnnotationParametersFix(annotation, missedElements).asIntention();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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}
|
||||
java.test.sum.time=Sum time: {0}
|
||||
error.no.annotation.class.found=No annotation class found
|
||||
@@ -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
|
||||
|
||||
@@ -59,6 +59,7 @@ object JavaPluginLayout {
|
||||
}
|
||||
|
||||
spec.withModules(listOf(
|
||||
"intellij.java.codeserver.highlighting",
|
||||
"intellij.java.compiler.impl",
|
||||
"intellij.java.debugger.impl",
|
||||
"intellij.java.terminal",
|
||||
|
||||
Reference in New Issue
Block a user