IDEA-96664 (highlighting of Java 8 repeating annotations)

This commit is contained in:
Roman Shevchenko
2013-04-03 16:19:26 +02:00
parent 79695d184b
commit 79f04f5709
8 changed files with 165 additions and 8 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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"

View File

@@ -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];
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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";