From 6569ef7a7507f9d167104fc9e1de27b57e080e4d Mon Sep 17 00:00:00 2001 From: Max Medvedev Date: Mon, 28 Jan 2013 13:12:33 +0400 Subject: [PATCH] IDEA-99589 Groovy 2.1 : highlighting for attributes of annotation aliases --- .../annotator/AnnotationCollectorChecker.java | 24 +++ .../annotator/CustomAnnotationChecker.java | 146 +++++++++++++++++- .../annotator/GrAliasAnnotationChecker.java | 64 ++++++-- .../groovy/annotator/GroovyAnnotator.java | 130 ++-------------- .../modifiers/GrAnnotationCollector.java | 20 ++- .../GrAnnotationHighlightingTest.groovy | 62 ++++++++ 6 files changed, 312 insertions(+), 134 deletions(-) diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/AnnotationCollectorChecker.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/AnnotationCollectorChecker.java index 385db3a3a750..c46968c2f941 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/AnnotationCollectorChecker.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/AnnotationCollectorChecker.java @@ -17,13 +17,17 @@ package org.jetbrains.plugins.groovy.annotator; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.openapi.util.Condition; +import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList; import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation; +import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationNameValuePair; import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; +import java.util.Map; + /** * @author Medvdedev Max */ @@ -31,6 +35,26 @@ import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; public class AnnotationCollectorChecker extends CustomAnnotationChecker { @Override public boolean checkApplicability(@NotNull AnnotationHolder holder, @NotNull GrAnnotation annotation) { + return isInAliasDeclaration(annotation); + } + + @Override + public boolean checkArgumentList(@NotNull AnnotationHolder holder, @NotNull GrAnnotation annotation) { + if (!isInAliasDeclaration(annotation)) return false; + + Map errors = ContainerUtil.newHashMap(); + final PsiClass clazz = (PsiClass)annotation.getClassReference().resolve(); + if (clazz == null) return true; + final GrAnnotationNameValuePair[] attributes = annotation.getParameterList().getAttributes(); + CustomAnnotationChecker.checkAnnotationArguments(errors, clazz, annotation.getClassReference(), attributes, false); + for (Map.Entry entry : errors.entrySet()) { + holder.createErrorAnnotation(entry.getKey(), entry.getValue()); + } + + return true; + } + + private static boolean isInAliasDeclaration(GrAnnotation annotation) { final PsiElement parent = annotation.getParent(); if (parent instanceof GrModifierList) { final GrAnnotation collector = ContainerUtil.find(((GrModifierList)parent).getRawAnnotations(), new Condition() { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/CustomAnnotationChecker.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/CustomAnnotationChecker.java index a1b22283f52f..6718f8c4e504 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/CustomAnnotationChecker.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/CustomAnnotationChecker.java @@ -18,13 +18,28 @@ package org.jetbrains.plugins.groovy.annotator; import com.intellij.codeInsight.daemon.JavaErrorMessages; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiModifierList; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.containers.HashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.groovy.GroovyBundle; +import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation; +import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationArrayInitializer; +import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationMemberValue; +import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationNameValuePair; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement; import org.jetbrains.plugins.groovy.lang.psi.impl.auxiliary.annotation.GrAnnotationImpl; +import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * @author Max Medvedev @@ -51,4 +66,131 @@ public abstract class CustomAnnotationChecker { return null; } + + public static void checkAnnotationArguments(@NotNull Map holder, + @NotNull PsiClass annotation, + @NotNull GrCodeReferenceElement refToHighlight, + @NotNull GrAnnotationNameValuePair[] attributes, boolean checkMissedAttributes) { + Set usedAttrs = new HashSet(); + + if (attributes.length > 0) { + final PsiElement identifier = attributes[0].getNameIdentifierGroovy(); + if (attributes.length == 1 && identifier == null) { + checkAnnotationValue(annotation, attributes[0], "value", usedAttrs, attributes[0].getValue(), holder); + } + else { + for (GrAnnotationNameValuePair attribute : attributes) { + final String name = attribute.getName(); + if (name != null) { + final PsiElement toHighlight = attribute.getNameIdentifierGroovy(); + assert toHighlight != null; + checkAnnotationValue(annotation, toHighlight, name, usedAttrs, attribute.getValue(), holder); + } + } + } + } + + List missedAttrs = new ArrayList(); + final PsiMethod[] methods = annotation.getMethods(); + for (PsiMethod method : methods) { + final String name = method.getName(); + if (usedAttrs.contains(name) || + method instanceof PsiAnnotationMethod && ((PsiAnnotationMethod)method).getDefaultValue() != null) { + continue; + } + missedAttrs.add(name); + } + + if (checkMissedAttributes && !missedAttrs.isEmpty()) { + holder.put(refToHighlight, GroovyBundle.message("missed.attributes", StringUtil.join(missedAttrs, ", "))); + } + } + + private static void checkAnnotationValue(@NotNull PsiClass annotation, + @NotNull PsiElement identifierToHighlight, + @NotNull String name, + @NotNull Set usedAttrs, + @Nullable GrAnnotationMemberValue value, + @NotNull Map holder) { + if (usedAttrs.contains(name)) { + holder.put(identifierToHighlight, GroovyBundle.message("duplicate.attribute")); + } + + usedAttrs.add(name); + + final PsiMethod[] methods = annotation.findMethodsByName(name, false); + if (methods.length == 0) { + holder.put(identifierToHighlight, + GroovyBundle.message("at.interface.0.does.not.contain.attribute", annotation.getQualifiedName(), name)); + } + else { + final PsiMethod method = methods[0]; + final PsiType ltype = method.getReturnType(); + if (ltype != null && value != null) { + checkAnnotationValueByType(holder, value, ltype, true); + } + } + } + + public static void checkAnnotationValueByType(@NotNull Map holder, + @NotNull GrAnnotationMemberValue value, + @Nullable PsiType ltype, + boolean skipArrays) { + final GlobalSearchScope resolveScope = value.getResolveScope(); + final PsiManager manager = value.getManager(); + + if (value instanceof GrExpression) { + final PsiType rtype; + if (value instanceof GrClosableBlock) { + rtype = PsiType.getJavaLangClass(manager, resolveScope); + } + else { + rtype = ((GrExpression)value).getType(); + } + + if (rtype != null && !checkAnnoTypeAssignable(ltype, rtype, value, skipArrays)) { + holder.put(value, GroovyBundle.message("cannot.assign", rtype.getPresentableText(), ltype.getPresentableText())); + } + } + + else if (value instanceof GrAnnotation) { + final PsiElement resolved = ((GrAnnotation)value).getClassReference().resolve(); + if (resolved instanceof PsiClass) { + final PsiClassType rtype = JavaPsiFacade.getElementFactory(value.getProject()).createType((PsiClass)resolved, PsiSubstitutor.EMPTY); + if (!checkAnnoTypeAssignable(ltype, rtype, value, skipArrays)) { + holder.put(value, GroovyBundle.message("cannot.assign", rtype.getPresentableText(), ltype.getPresentableText())); + } + } + } + + else if (value instanceof GrAnnotationArrayInitializer) { + + if (ltype instanceof PsiArrayType) { + final PsiType componentType = ((PsiArrayType)ltype).getComponentType(); + final GrAnnotationMemberValue[] initializers = ((GrAnnotationArrayInitializer)value).getInitializers(); + for (GrAnnotationMemberValue initializer : initializers) { + checkAnnotationValueByType(holder, initializer, componentType, false); + } + } + else { + final PsiType rtype = TypesUtil.getTupleByAnnotationArrayInitializer((GrAnnotationArrayInitializer)value); + if (!checkAnnoTypeAssignable(ltype, rtype, value, skipArrays)) { + holder.put(value, GroovyBundle.message("cannot.assign", rtype.getPresentableText(), ltype.getPresentableText())); + } + } + } + } + + private static boolean checkAnnoTypeAssignable(@Nullable PsiType type, + @Nullable PsiType rtype, + @NotNull GroovyPsiElement context, + boolean skipArrays) { + rtype = TypesUtil.unboxPrimitiveTypeWrapper(rtype); + if (TypesUtil.isAssignableByMethodCallConversion(type, rtype, context)) return true; + + if (!(type instanceof PsiArrayType && skipArrays)) return false; + + final PsiType componentType = ((PsiArrayType)type).getComponentType(); + return checkAnnoTypeAssignable(componentType, rtype, context, skipArrays); + } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GrAliasAnnotationChecker.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GrAliasAnnotationChecker.java index 8de1e97c110f..a379b76cda3b 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GrAliasAnnotationChecker.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GrAliasAnnotationChecker.java @@ -16,16 +16,25 @@ package org.jetbrains.plugins.groovy.annotator; import com.intellij.lang.annotation.AnnotationHolder; +import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.groovy.GroovyBundle; import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList; import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation; +import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationNameValuePair; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition; import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement; import org.jetbrains.plugins.groovy.lang.psi.impl.auxiliary.modifiers.GrAnnotationCollector; import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; +import java.util.ArrayList; +import java.util.Map; +import java.util.Set; + /** * @author Max Medvedev */ @@ -46,15 +55,14 @@ public class GrAliasAnnotationChecker extends CustomAnnotationChecker { @Override public boolean checkApplicability(@NotNull AnnotationHolder holder, @NotNull GrAnnotation annotation) { - - final GrCodeReferenceElement ref = annotation.getClassReference(); - - final GrTypeDefinition resolved = resolveAlias(annotation); - if (resolved == null) { + final GrTypeDefinition alias = resolveAlias(annotation); + if (alias == null) { return false; } - final GrModifierList list = resolved.getModifierList(); + final GrCodeReferenceElement ref = annotation.getClassReference(); + + final GrModifierList list = alias.getModifierList(); assert list != null; for (GrAnnotation anno : list.getRawAnnotations()) { if (GroovyCommonClassNames.GROOVY_TRANSFORM_ANNOTATION_COLLECTOR.equals(anno.getQualifiedName())) continue; @@ -69,11 +77,49 @@ public class GrAliasAnnotationChecker extends CustomAnnotationChecker { @Override public boolean checkArgumentList(@NotNull AnnotationHolder holder, @NotNull GrAnnotation annotation) { - if (resolveAlias(annotation) != null) { - return true; + final GrTypeDefinition alias = resolveAlias(annotation); + if (alias == null) { + return false; } + final ArrayList annotations = ContainerUtil.newArrayList(); + final GrAnnotation annotationCollector = GrAnnotationCollector.findAnnotationCollector(alias); + assert annotationCollector != null; + final Set usedAttributes = GrAnnotationCollector.collectAnnotations(annotations, annotation, annotationCollector); - return false; + final GrCodeReferenceElement ref = annotation.getClassReference(); + + Map map = ContainerUtil.newHashMap(); + for (GrAnnotation aliased : annotations) { + final PsiClass clazz = (PsiClass)aliased.getClassReference().resolve(); + assert clazz != null; + checkAnnotationArguments(map, clazz, ref, aliased.getParameterList().getAttributes(), true); + } + + for (Map.Entry entry : map.entrySet()) { + final PsiElement key = entry.getKey(); + final String value = entry.getValue(); + if (PsiTreeUtil.isAncestor(annotation, key, true)) { + holder.createErrorAnnotation(key, value); + } + else { + holder.createErrorAnnotation(ref, value); + } + } + + final GrAnnotationNameValuePair[] attributes = annotation.getParameterList().getAttributes(); + final String aliasQName = annotation.getQualifiedName(); + + if (attributes.length == 1 && attributes[0].getNameIdentifierGroovy() == null && !usedAttributes.contains("value")) { + holder.createErrorAnnotation(attributes[0], GroovyBundle.message("at.interface.0.does.not.contain.attribute", aliasQName, "value")); + } + + for (GrAnnotationNameValuePair pair : attributes) { + final PsiElement nameIdentifier = pair.getNameIdentifierGroovy(); + if (nameIdentifier != null && !usedAttributes.contains(pair.getName())) { + holder.createErrorAnnotation(nameIdentifier, GroovyBundle.message("at.interface.0.does.not.contain.attribute", aliasQName, pair.getName())); + } + } + return true; } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java index 0a07f07a3920..0db8596509de 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java @@ -34,7 +34,6 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.impl.light.LightElement; import com.intellij.psi.infos.CandidateInfo; -import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.searches.SuperMethodsSearch; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.*; @@ -60,7 +59,9 @@ import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult; import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap; import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifier; import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList; -import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.*; +import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation; +import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationArgumentList; +import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationMemberValue; import org.jetbrains.plugins.groovy.lang.psi.api.statements.*; import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel; import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList; @@ -86,7 +87,6 @@ import org.jetbrains.plugins.groovy.lang.psi.api.types.*; import org.jetbrains.plugins.groovy.lang.psi.dataFlow.types.TypeInferenceHelper; import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil; import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil; -import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightParameter; import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrScriptField; import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass; @@ -1340,59 +1340,11 @@ public class GroovyAnnotator extends GroovyElementVisitor implements Annotator { if (checker.checkArgumentList(myHolder, annotation)) return; } - final GrAnnotationNameValuePair[] attributes = annotationArgumentList.getAttributes(); - - Set usedAttrs = new HashSet(); - if (attributes.length == 1 && attributes[0].getNameIdentifierGroovy() == null) { - checkAnnotationValue(anno, attributes[0], "value", usedAttrs, attributes[0].getValue()); - } - else { - for (GrAnnotationNameValuePair attribute : attributes) { - final PsiElement identifier = attribute.getNameIdentifierGroovy(); - final String name = identifier.getText(); - checkAnnotationValue(anno, identifier, name, usedAttrs, attribute.getValue()); - } - } - - List missedAttrs = new ArrayList(); - final PsiMethod[] methods = anno.getMethods(); - for (PsiMethod method : methods) { - final String name = method.getName(); - if (usedAttrs.contains(name) || - method instanceof PsiAnnotationMethod && ((PsiAnnotationMethod)method).getDefaultValue() != null) { - continue; - } - missedAttrs.add(name); - } - - if (!missedAttrs.isEmpty()) { - myHolder.createErrorAnnotation(annotation.getClassReference(), - GroovyBundle.message("missed.attributes", StringUtil.join(missedAttrs, ", "))); - } - } - - private void checkAnnotationValue(@NotNull PsiClass anno, - @NotNull PsiElement identifierToHighlight, - @NotNull String name, - @NotNull Set usedAttrs, - @Nullable GrAnnotationMemberValue value) { - if (usedAttrs.contains(name)) { - myHolder.createErrorAnnotation(identifierToHighlight, GroovyBundle.message("duplicate.attribute")); - } - - usedAttrs.add(name); - - final PsiMethod[] methods = anno.findMethodsByName(name, false); - if (methods.length == 0) { - myHolder.createErrorAnnotation(identifierToHighlight, - GroovyBundle.message("at.interface.0.does.not.contain.attribute", anno.getQualifiedName(), name)); - } - else { - final PsiMethod method = methods[0]; - final PsiType ltype = method.getReturnType(); - if (ltype != null && value != null) { - checkAnnotationValueByType(value, ltype, true); - } + Map errors = ContainerUtil.newHashMap(); + CustomAnnotationChecker.checkAnnotationArguments(errors, anno, annotation.getClassReference(), annotationArgumentList.getAttributes(), + true); + for (Map.Entry entry : errors.entrySet()) { + myHolder.createErrorAnnotation(entry.getKey(), entry.getValue()); } } @@ -1405,69 +1357,11 @@ public class GroovyAnnotator extends GroovyElementVisitor implements Annotator { assert parent instanceof GrAnnotationMethod; final PsiType type = ((GrAnnotationMethod)parent).getReturnType(); - checkAnnotationValueByType(value, type, false); - } - - private void checkAnnotationValueByType(@NotNull GrAnnotationMemberValue value, @Nullable PsiType ltype, boolean skipArrays) { - final GlobalSearchScope resolveScope = value.getResolveScope(); - final PsiManager manager = value.getManager(); - - if (value instanceof GrExpression) { - final PsiType rtype; - if (value instanceof GrClosableBlock) { - rtype = PsiType.getJavaLangClass(manager, resolveScope); - } - else { - rtype = ((GrExpression)value).getType(); - } - - if (rtype != null && !checkAnnoTypeAssignable(ltype, rtype, value, skipArrays)) { - myHolder - .createErrorAnnotation(value, GroovyBundle.message("cannot.assign", rtype.getPresentableText(), ltype.getPresentableText())); - } + Map errors = ContainerUtil.newHashMap(); + CustomAnnotationChecker.checkAnnotationValueByType(errors, value, type, false); + for (Map.Entry entry : errors.entrySet()) { + myHolder.createErrorAnnotation(entry.getKey(), entry.getValue()); } - - else if (value instanceof GrAnnotation) { - final PsiElement resolved = ((GrAnnotation)value).getClassReference().resolve(); - if (resolved instanceof PsiClass) { - final PsiClassType rtype = JavaPsiFacade.getElementFactory(value.getProject()).createType((PsiClass)resolved, PsiSubstitutor.EMPTY); - if (!checkAnnoTypeAssignable(ltype, rtype, value, skipArrays)) { - myHolder - .createErrorAnnotation(value, GroovyBundle.message("cannot.assign", rtype.getPresentableText(), ltype.getPresentableText())); - } - } - } - - else if (value instanceof GrAnnotationArrayInitializer) { - - if (ltype instanceof PsiArrayType) { - final PsiType componentType = ((PsiArrayType)ltype).getComponentType(); - final GrAnnotationMemberValue[] initializers = ((GrAnnotationArrayInitializer)value).getInitializers(); - for (GrAnnotationMemberValue initializer : initializers) { - checkAnnotationValueByType(initializer, componentType, false); - } - } - else { - final PsiType rtype = TypesUtil.getTupleByAnnotationArrayInitializer((GrAnnotationArrayInitializer)value); - if (!checkAnnoTypeAssignable(ltype, rtype, value, skipArrays)) { - myHolder - .createErrorAnnotation(value, GroovyBundle.message("cannot.assign", rtype.getPresentableText(), ltype.getPresentableText())); - } - } - } - } - - private static boolean checkAnnoTypeAssignable(@Nullable PsiType type, - @Nullable PsiType rtype, - @NotNull GroovyPsiElement context, - boolean skipArrays) { - rtype = TypesUtil.unboxPrimitiveTypeWrapper(rtype); - if (TypesUtil.isAssignableByMethodCallConversion(type, rtype, context)) return true; - - if (!(type instanceof PsiArrayType && skipArrays)) return false; - - final PsiType componentType = ((PsiArrayType)type).getComponentType(); - return checkAnnoTypeAssignable(componentType, rtype, context, skipArrays); } @Override diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/auxiliary/modifiers/GrAnnotationCollector.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/auxiliary/modifiers/GrAnnotationCollector.java index 84aaf710846e..0175323e7e9d 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/auxiliary/modifiers/GrAnnotationCollector.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/auxiliary/modifiers/GrAnnotationCollector.java @@ -68,9 +68,16 @@ public class GrAnnotationCollector { return false; } - public static void collectAnnotations(@NotNull List list, - @NotNull GrAnnotation alias, - @NotNull GrAnnotation annotationCollector) { + /** + * + * @param list resulting collection of aliased annotations + * @param alias alias annotation + * @param annotationCollector @AnnotationCollector annotation used in alias declaration + * @return set of used arguments of alias annotation + */ + public static Set collectAnnotations(@NotNull List list, + @NotNull GrAnnotation alias, + @NotNull GrAnnotation annotationCollector) { final GrModifierList modifierList = (GrModifierList)annotationCollector.getParent(); @@ -83,6 +90,7 @@ public class GrAnnotationCollector { final PsiManager manager = alias.getManager(); final GrAnnotationNameValuePair[] attributes = alias.getParameterList().getAttributes(); + Set allUsedAttrs = ContainerUtil.newHashSet(); for (Map.Entry> entry : annotations.entrySet()) { final String qname = entry.getKey(); final PsiClass resolved = JavaPsiFacade.getInstance(alias.getProject()).findClass(qname, alias.getResolveScope()); @@ -95,9 +103,9 @@ public class GrAnnotationCollector { final String name = attr.getName() != null ? attr.getName() : "value"; if (resolved.findMethodsByName(name, false).length > 0) { annotation.addAttribute(attr); + allUsedAttrs.add(name); + usedAttrs.add(name); } - - usedAttrs.add(name); } @@ -111,6 +119,8 @@ public class GrAnnotationCollector { list.add(annotation); } + + return allUsedAttrs; } private static void collectAliasedAnnotationsFromAnnotationCollectorAnnotations(GrModifierList modifierList, diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/aliasAnnotations/GrAnnotationHighlightingTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/aliasAnnotations/GrAnnotationHighlightingTest.groovy index 8cbd236938c2..61290303eb7a 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/aliasAnnotations/GrAnnotationHighlightingTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/aliasAnnotations/GrAnnotationHighlightingTest.groovy @@ -54,6 +54,25 @@ class Foo { ''') } + void testAliasWithMissedProperty() { + testHighlighting('''\ +import groovy.transform.* + +@interface X { + String[] excludes() +} + +@ToString(excludes = ['a', 'b']) +@AnnotationCollector([X, Immutable]) +@interface Alias {} + +@Alias +class Foo { + Integer a, b +} +''') + } + void testInapplicableAlias() { testHighlighting('''\ import groovy.transform.* @@ -75,6 +94,49 @@ import groovy.transform.* @AnnotationCollector([ToString, Immutable]) @Field @interface Alias {} +''') + } + + void testInapplicableAttributeInAliasDeclaration() { + testHighlighting('''\ +import groovy.transform.* + +@ToString(excludes = ['a', 'b'], foo = 4) +@AnnotationCollector([EqualsAndHashCode, Immutable]) +@interface Alias {} + +@Alias +class Foo{} +''') + } + + void testUnknownAttributeInAliasUsage() { + testHighlighting('''\ +import groovy.transform.* + +@ToString(excludes = ['a', 'b']) +@AnnotationCollector([EqualsAndHashCode, Immutable]) +@interface Alias {} + +@Alias(foo = 5) +class Foo { + Integer a, b +} +''') + } + + void testInapplicableAttributeInAliasUsage() { + testHighlighting('''\ +import groovy.transform.* + +@ToString(excludes = ['a', 'b']) +@AnnotationCollector([EqualsAndHashCode, Immutable]) +@interface Alias {} + +@Alias(excludes = 5) +class Foo { + Integer a, b +} ''') } }