mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 13:31:28 +07:00
IDEA-96664 (highlighting of Java 8 repeating annotations)
This commit is contained in:
@@ -30,6 +30,7 @@ import com.intellij.patterns.ElementPattern;
|
||||
import com.intellij.psi.*;
|
||||
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.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
@@ -38,6 +39,7 @@ import com.intellij.util.containers.HashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -172,10 +174,38 @@ public class AnnotationsHighlightUtil {
|
||||
PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
|
||||
if (nameRef == null) continue;
|
||||
PsiElement aClass = nameRef.resolve();
|
||||
if (resolved.equals(aClass)) {
|
||||
if (!resolved.equals(aClass)) continue;
|
||||
|
||||
if (!PsiUtil.isLanguageLevel8OrHigher(annotationToCheck)) {
|
||||
String description = JavaErrorMessages.message("annotation.duplicate.annotation");
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create();
|
||||
}
|
||||
|
||||
PsiClass annotationType = (PsiClass)resolved;
|
||||
PsiAnnotation metaAnno = PsiImplUtil.findAnnotation(annotationType.getModifierList(), CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE);
|
||||
if (metaAnno == null) {
|
||||
String explanation = JavaErrorMessages.message("annotation.non.repeatable", annotationType.getQualifiedName());
|
||||
String description = JavaErrorMessages.message("annotation.duplicate.explained", explanation);
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create();
|
||||
}
|
||||
|
||||
String explanation = doCheckRepeatableAnnotation(metaAnno);
|
||||
if (explanation != null) {
|
||||
String description = JavaErrorMessages.message("annotation.duplicate.explained", explanation);
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create();
|
||||
}
|
||||
|
||||
PsiClass collector = getRepeatableCollector(metaAnno);
|
||||
if (collector != null) {
|
||||
String collectorName = collector.getQualifiedName();
|
||||
if (collectorName != null) {
|
||||
PsiAnnotation collectorAnno = owner.findAnnotation(collectorName);
|
||||
if (collectorAnno != null) {
|
||||
String description = JavaErrorMessages.message("annotation.collector.wrong.place", collectorName);
|
||||
return annotationError(collectorAnno, description);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -494,6 +524,90 @@ public class AnnotationsHighlightUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static HighlightInfo checkRepeatableAnnotation(PsiAnnotation annotation) {
|
||||
String qualifiedName = annotation.getQualifiedName();
|
||||
if (!CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE.equals(qualifiedName)) return null;
|
||||
|
||||
String description = doCheckRepeatableAnnotation(annotation);
|
||||
if (description != null) {
|
||||
PsiAnnotationMemberValue collectorRef = PsiImplUtil.findAttributeValue(annotation, null);
|
||||
if (collectorRef != null) {
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(collectorRef).descriptionAndTooltip(description).create();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String doCheckRepeatableAnnotation(PsiAnnotation annotation) {
|
||||
PsiAnnotationOwner owner = annotation.getOwner();
|
||||
if (!(owner instanceof PsiModifierList)) return null;
|
||||
PsiElement target = ((PsiModifierList)owner).getParent();
|
||||
if (!(target instanceof PsiClass) || !((PsiClass)target).isAnnotationType()) return null;
|
||||
PsiClass collector = getRepeatableCollector(annotation);
|
||||
if (collector == null) return null;
|
||||
|
||||
PsiMethod[] methods = collector.findMethodsByName("value", false);
|
||||
if (methods.length == 0) {
|
||||
return JavaErrorMessages.message("annotation.container.no.value", collector.getQualifiedName());
|
||||
}
|
||||
|
||||
if (methods.length == 1) {
|
||||
PsiType expected = new PsiImmediateClassType((PsiClass)target, PsiSubstitutor.EMPTY).createArrayType();
|
||||
if (!expected.equals(methods[0].getReturnType())) {
|
||||
return JavaErrorMessages.message("annotation.container.bad.type", collector.getQualifiedName(), HighlightUtil.formatType(expected));
|
||||
}
|
||||
}
|
||||
|
||||
RetentionPolicy targetPolicy = getRetentionPolicy((PsiClass)target);
|
||||
if (targetPolicy != null) {
|
||||
RetentionPolicy collectorPolicy = getRetentionPolicy(collector);
|
||||
if (collectorPolicy != null && targetPolicy.compareTo(collectorPolicy) > 0) {
|
||||
return JavaErrorMessages.message("annotation.container.low.retention", collector.getQualifiedName(), collectorPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiClass getRepeatableCollector(PsiAnnotation annotation) {
|
||||
PsiAnnotationMemberValue collectorRef = PsiImplUtil.findAttributeValue(annotation, null);
|
||||
if (!(collectorRef instanceof PsiClassObjectAccessExpression)) return null;
|
||||
PsiType collectorType = ((PsiClassObjectAccessExpression)collectorRef).getOperand().getType();
|
||||
if (!(collectorType instanceof PsiClassType)) return null;
|
||||
PsiClass collector = ((PsiClassType)collectorType).resolve();
|
||||
if (collector == null || !collector.isAnnotationType()) return null;
|
||||
return collector;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static RetentionPolicy getRetentionPolicy(PsiClass annotation) {
|
||||
PsiModifierList modifierList = annotation.getModifierList();
|
||||
if (modifierList != null) {
|
||||
PsiAnnotation retentionAnno = modifierList.findAnnotation(CommonClassNames.JAVA_LANG_ANNOTATION_RETENTION);
|
||||
if (retentionAnno == null) return RetentionPolicy.CLASS;
|
||||
|
||||
PsiAnnotationMemberValue policyRef = PsiImplUtil.findAttributeValue(retentionAnno, null);
|
||||
if (policyRef instanceof PsiReference) {
|
||||
PsiElement field = ((PsiReference)policyRef).resolve();
|
||||
if (field instanceof PsiEnumConstant) {
|
||||
String name = ((PsiEnumConstant)field).getName();
|
||||
try {
|
||||
return RetentionPolicy.valueOf(name);
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOG.warn("Unknown policy: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class AnnotationReturnTypeVisitor extends PsiTypeVisitor<Boolean> {
|
||||
public static final AnnotationReturnTypeVisitor INSTANCE = new AnnotationReturnTypeVisitor();
|
||||
@Override
|
||||
|
||||
@@ -183,6 +183,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
if (!myHolder.hasErrorResults()) myHolder.add(AnnotationsHighlightUtil.checkDuplicateAnnotations(annotation));
|
||||
if (!myHolder.hasErrorResults()) myHolder.add(AnnotationsHighlightUtil.checkForeignInnerClassesUsed(annotation));
|
||||
if (!myHolder.hasErrorResults()) myHolder.add(AnnotationsHighlightUtil.checkFunctionalInterface(annotation));
|
||||
if (!myHolder.hasErrorResults()) myHolder.add(AnnotationsHighlightUtil.checkRepeatableAnnotation(annotation));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -319,11 +319,11 @@ public class PsiImplUtil {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiAnnotation findAnnotation(@NotNull PsiAnnotationOwner annotationOwner, @NotNull String qualifiedName) {
|
||||
public static PsiAnnotation findAnnotation(@Nullable PsiAnnotationOwner annotationOwner, @NotNull String qualifiedName) {
|
||||
if (annotationOwner == null) return null;
|
||||
|
||||
PsiAnnotation[] annotations = annotationOwner.getAnnotations();
|
||||
if (annotations.length == 0) {
|
||||
return null;
|
||||
}
|
||||
if (annotations.length == 0) return null;
|
||||
|
||||
String shortName = StringUtil.getShortName(qualifiedName);
|
||||
for (PsiAnnotation annotation : annotations) {
|
||||
|
||||
@@ -20,6 +20,12 @@ annotation.not.allowed.ref=Annotation not applicable to this kind of reference
|
||||
annotation.not.allowed.static=Static member qualifying type may not be annotated
|
||||
annotation.not.allowed.void='void' type may not be annotated
|
||||
annotation.not.allowed.class=Class literal type may not be annotated
|
||||
annotation.container.no.value=Invalid containing annotation ''{0}'': no ''value'' method declared
|
||||
annotation.container.bad.type=Invalid containing annotation ''{0}'': ''value'' method should have type ''{1}''
|
||||
annotation.container.low.retention=Containing annotation ''{0}'' has shorter retention (''{1}'') than the contained annotation
|
||||
annotation.duplicate.explained=Duplicate annotation. {0}
|
||||
annotation.non.repeatable=The declaration of ''{0}'' does not have a valid java.lang.annotation.Repeatable annotation
|
||||
annotation.collector.wrong.place=Containing annotation ''{0}'' must not be present at the same time as the element it contains
|
||||
|
||||
# These aren't unused.
|
||||
# suppress inspection "UnusedProperty"
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import java.lang.annotation.*;
|
||||
import java.util.*;
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
|
||||
@interface AA1 { }
|
||||
@Repeatable(<error descr="Invalid containing annotation 'AA1': no 'value' method declared">AA1.class</error>) @interface A1 { }
|
||||
|
||||
@interface AA2 { String[] value(); }
|
||||
@Repeatable(<error descr="Invalid containing annotation 'AA2': 'value' method should have type 'A2[]'">AA2.class</error>) @interface A2 { }
|
||||
|
||||
@interface AA3 { A3[] value(); }
|
||||
@Repeatable(<error descr="Containing annotation 'AA3' has shorter retention ('CLASS') than the contained annotation">AA3.class</error>)
|
||||
@Retention(RetentionPolicy.RUNTIME) @interface A3 { }
|
||||
|
||||
@interface A4 { }
|
||||
@<error descr="Duplicate annotation. The declaration of 'A4' does not have a valid java.lang.annotation.Repeatable annotation">A4</error>
|
||||
@<error descr="Duplicate annotation. The declaration of 'A4' does not have a valid java.lang.annotation.Repeatable annotation">A4</error>
|
||||
class C4 { }
|
||||
@A4 class C4bis { }
|
||||
|
||||
@<error descr="Duplicate annotation. Invalid containing annotation 'AA1': no 'value' method declared">A1</error>
|
||||
@<error descr="Duplicate annotation. Invalid containing annotation 'AA1': no 'value' method declared">A1</error>
|
||||
class C5 { }
|
||||
@A1 class C5bis { }
|
||||
|
||||
@interface AA6 { A6[] value() default { }; }
|
||||
@Repeatable(AA6.class) @interface A6 { }
|
||||
@A6 @A6 <error descr="Containing annotation 'AA6' must not be present at the same time as the element it contains">@AA6</error> class C6 { }
|
||||
@A6 @A6 class C6bis1 { }
|
||||
@A6 @AA6 class C6bis2 { }
|
||||
|
||||
@Target({TYPE_USE}) @interface TA { }
|
||||
class DupTypeAnno {
|
||||
List<@<error descr="Duplicate annotation. The declaration of 'TA' does not have a valid java.lang.annotation.Repeatable annotation">TA</error> @<error descr="Duplicate annotation. The declaration of 'TA' does not have a valid java.lang.annotation.Repeatable annotation">TA</error> String> l = null;
|
||||
Boolean[] b = new Boolean @<error descr="Duplicate annotation. The declaration of 'TA' does not have a valid java.lang.annotation.Repeatable annotation">TA</error> @<error descr="Duplicate annotation. The declaration of 'TA' does not have a valid java.lang.annotation.Repeatable annotation">TA</error> [42];
|
||||
}
|
||||
@@ -105,7 +105,6 @@ class Outer {
|
||||
@TA String @TA [] @TA [] docs1 = new @TA String @TA [2] @TA [2];
|
||||
@TA int @TA [] ints = new @TA int @TA [2];
|
||||
new Boolean @TA [2] <error descr="Annotations are not allowed here">@TA</error>;
|
||||
new Boolean @<error descr="Duplicate annotation">TA</error> @<error descr="Duplicate annotation">TA</error> [42];
|
||||
}
|
||||
|
||||
int @TA [] mixedArrays() @TA [] <error descr="Annotations are not allowed here">@TA</error> { return new int[0][0]; }
|
||||
@@ -140,6 +139,4 @@ class Outer {
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
List<@<error descr="Duplicate annotation">TA</error> @<error descr="Duplicate annotation">TA</error> String> c = null;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ public class AnnotationsHighlightingTest extends LightDaemonAnalyzerTestCase {
|
||||
public void testPackageAnnotationNotInPackageInfo() { doTest(BASE_PATH + "/" + getTestName(true) + "/notPackageInfo.java", false, false); }
|
||||
|
||||
public void testTypeAnnotations() { doTest8(false); }
|
||||
public void testRepeatable() { doTest8(false); }
|
||||
|
||||
private void doTest(boolean checkWarnings) {
|
||||
setLanguageLevel(LanguageLevel.JDK_1_7);
|
||||
|
||||
@@ -47,6 +47,8 @@ public interface CommonClassNames {
|
||||
@NonNls String JAVA_LANG_ANNOTATION_TARGET = "java.lang.annotation.Target";
|
||||
@NonNls String JAVA_LANG_ANNOTATION_INHERITED = "java.lang.annotation.Inherited";
|
||||
@NonNls String JAVA_LANG_ANNOTATION_ANNOTATION = "java.lang.annotation.Annotation";
|
||||
@NonNls String JAVA_LANG_ANNOTATION_RETENTION = "java.lang.annotation.Retention";
|
||||
@NonNls String JAVA_LANG_ANNOTATION_REPEATABLE = "java.lang.annotation.Repeatable";
|
||||
|
||||
@NonNls String JAVA_LANG_REFLECT_ARRAY = "java.lang.reflect.Array";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user