diff --git a/plugins/groovy/resources/inspectionDescriptions/DelegatesTo.html b/plugins/groovy/resources/inspectionDescriptions/DelegatesTo.html new file mode 100644 index 000000000000..9ac5b461f412 --- /dev/null +++ b/plugins/groovy/resources/inspectionDescriptions/DelegatesTo.html @@ -0,0 +1,7 @@ + + + + This inspection checks @groovy.lang.DelegatesTo annotation arguments. + + + diff --git a/plugins/groovy/src/META-INF/plugin.xml b/plugins/groovy/src/META-INF/plugin.xml index b9aa8e97bee9..657dde77d7cc 100644 --- a/plugins/groovy/src/META-INF/plugin.xml +++ b/plugins/groovy/src/META-INF/plugin.xml @@ -994,6 +994,10 @@ groupName="Annotations verifying" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.plugins.groovy.annotator.inspections.GroovySingletonAnnotationInspection"/> + + diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/GroovyInspectionBundle.properties b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/GroovyInspectionBundle.properties index 4fb8fcf16e0f..eec071a7f02a 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/GroovyInspectionBundle.properties +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/GroovyInspectionBundle.properties @@ -102,4 +102,6 @@ gr.package=Package mismatch java.style.property.access=Java-style accessor invocation type.customizer.is.not.marked.as.a.resource.file=Type customizer script is not marked as compiler resources add.to.resources=Add to resources -add.type.customizer.to.resources=Add type customizer script to resources \ No newline at end of file +add.type.customizer.to.resources=Add type customizer script to resources +target.0.does.not.exist=Target ''{0}'' does not exist +target.annotation.is.unused=@Target is unused \ No newline at end of file diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/confusing/DelegatesToInspection.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/confusing/DelegatesToInspection.java new file mode 100644 index 000000000000..bc23a1d6c319 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/confusing/DelegatesToInspection.java @@ -0,0 +1,108 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.groovy.codeInspection.confusing; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemHighlightType; +import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiAnnotationMemberValue; +import com.intellij.psi.PsiElement; +import org.codehaus.groovy.runtime.DefaultGroovyMethods; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.groovy.codeInspection.BaseInspection; +import org.jetbrains.plugins.groovy.codeInspection.BaseInspectionVisitor; +import org.jetbrains.plugins.groovy.codeInspection.GroovyInspectionBundle; +import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameterList; +import org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtil; +import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; + +/** + * @author Max Medvedev + */ +public class DelegatesToInspection extends BaseInspection { + @NotNull + @Override + protected BaseInspectionVisitor buildVisitor() { + return new BaseInspectionVisitor() { + @Override + public void visitAnnotation(GrAnnotation annotation) { + checkTarget(annotation); + checkDelegatesTo(annotation); + } + + private void checkTarget(GrAnnotation annotation) { + if (!GroovyCommonClassNames.GROOVY_LANG_DELEGATES_TO_TARGET.equals(annotation.getQualifiedName())) return; + + + final PsiElement owner = annotation.getParent().getParent(); + if (!(owner instanceof GrParameter)) return; + + + final boolean isTargetDeclared = annotation.findDeclaredAttributeValue("value") != null; + String targetName = GrAnnotationUtil.inferStringAttribute(annotation, "value"); + + final GrParameterList parameterList = DefaultGroovyMethods.asType(owner.getParent(), GrParameterList.class); + for (GrParameter parameter : parameterList.getParameters()) { + final PsiAnnotation delegatesTo = parameter.getModifierList().findAnnotation(GroovyCommonClassNames.GROOVY_LANG_DELEGATES_TO); + if (delegatesTo != null) { + if (isTargetDeclared) { + final String curTarget = GrAnnotationUtil.inferStringAttribute(delegatesTo, "target"); + if (curTarget != null && curTarget.equals(targetName)) { + return; //target is used + } + } + else { + if (delegatesTo.findDeclaredAttributeValue("target") == null) { + return; // target is used + } + } + } + } + + registerError(annotation.getClassReference(), GroovyInspectionBundle.message("target.annotation.is.unused"), + LocalQuickFix.EMPTY_ARRAY, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); + } + + private void checkDelegatesTo(GrAnnotation annotation) { + if (!GroovyCommonClassNames.GROOVY_LANG_DELEGATES_TO.equals(annotation.getQualifiedName())) return; + + final PsiElement owner = annotation.getParent().getParent(); + if (!(owner instanceof GrParameter)) return; + + final PsiAnnotationMemberValue targetPair = annotation.findDeclaredAttributeValue("target"); + if (targetPair == null) return; + + String targetName = GrAnnotationUtil.inferStringAttribute(annotation, "target"); + + final GrParameterList parameterList = DefaultGroovyMethods.asType(owner.getParent(), GrParameterList.class); + for (GrParameter parameter : parameterList.getParameters()) { + final PsiAnnotation target = parameter.getModifierList().findAnnotation(GroovyCommonClassNames.GROOVY_LANG_DELEGATES_TO_TARGET); + if (target != null) { + final String curTarget = GrAnnotationUtil.inferStringAttribute(target, "value"); + if (curTarget != null && curTarget.equals(targetName)) { + return; //target is used + } + } + } + + registerError(targetPair, GroovyInspectionBundle.message("target.0.does.not.exist", targetName != null ? targetName : "?"), + LocalQuickFix.EMPTY_ARRAY, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); + } + }; + } +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrAnnotationUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrAnnotationUtil.java new file mode 100644 index 000000000000..61a82e9d6f43 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrAnnotationUtil.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.groovy.lang.psi.impl; + +import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiAnnotationMemberValue; +import com.intellij.psi.PsiLiteral; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Max Medvedev + */ +public class GrAnnotationUtil { + @Nullable + public static String inferStringAttribute(@NotNull PsiAnnotation annotation, @NotNull String attributeName) { + final PsiAnnotationMemberValue targetValue = annotation.findAttributeValue(attributeName); + if (targetValue instanceof PsiLiteral) { + final Object value = ((PsiLiteral)targetValue).getValue(); + if (value instanceof String) return (String)value; + } + return null; + } +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/blocks/GrDelegatesToUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/blocks/GrDelegatesToUtil.java index 69d21def4e37..58467842b010 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/blocks/GrDelegatesToUtil.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/blocks/GrDelegatesToUtil.java @@ -29,6 +29,7 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpres import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral; +import org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtil; import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil; import org.jetbrains.plugins.groovy.lang.psi.util.GdkMethodUtil; import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; @@ -106,7 +107,7 @@ public class GrDelegatesToUtil { else if (value == null || value instanceof PsiLiteralExpression && ((PsiLiteralExpression)value).getType() == PsiType.NULL || value instanceof GrLiteral && ((GrLiteral)value).getType() == PsiType.NULL) { - String target = inferStringAttribute(delegatesTo, "target"); + String target = GrAnnotationUtil.inferStringAttribute(delegatesTo, "target"); if (target == null) return null; final int parameter = findTargetParameter(delegatesTo, target); @@ -132,7 +133,7 @@ public class GrDelegatesToUtil { final PsiAnnotation targetAnnotation = modifierList.findAnnotation(GroovyCommonClassNames.GROOVY_LANG_DELEGATES_TO_TARGET); if (targetAnnotation == null) continue; - final String value = inferStringAttribute(targetAnnotation, "value"); + final String value = GrAnnotationUtil.inferStringAttribute(targetAnnotation, "value"); if (value == null) continue; if (value.equals(target)) return i; @@ -141,17 +142,6 @@ public class GrDelegatesToUtil { return -1; } - @Nullable - private static String inferStringAttribute(@NotNull PsiAnnotation annotation, @NotNull String attributeName) { - final PsiAnnotationMemberValue targetValue = annotation.findAttributeValue(attributeName); - if (targetValue instanceof PsiLiteral) { - final Object value = ((PsiLiteral)targetValue).getValue(); - if (value instanceof String) return (String)value; - } - - return null; - } - @Nullable private static GrExpression inferCallQualifier(@NotNull GrMethodCall call) { final GrExpression expression = call.getInvokedExpression(); diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrInspectionTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrInspectionTest.groovy index c31974758727..775741ae3584 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrInspectionTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrInspectionTest.groovy @@ -225,4 +225,34 @@ class I{ } ''', GrMethodMayBeStaticInspection) } + + void testDelegatesTo() { + testHighlighting(''' + +def with1(@DelegatesTo.Target() Object target, @DelegatesTo() Closure arg) { //unused + arg.delegate = target + arg() +} + +def with2(@DelegatesTo.Target('abc') Object target, @DelegatesTo() Closure arg) { //unused + arg.delegate = target + arg() +} + +def with3(@DelegatesTo.Target('abc') Object target, @DelegatesTo(target='abc') Closure arg) { //unused + arg.delegate = target + arg() +} + +def with4(@DelegatesTo.Target('abcd') Object target, @DelegatesTo(target='abc') Closure arg) { //unused + arg.delegate = target + arg() +} + +def with5(@DelegatesTo.Target() Object target, @DelegatesTo(target='abc') Closure arg) { //unused + arg.delegate = target + arg() +} +''', DelegatesToInspection) + } }