From 0b397a6bdd44b6a01d998bb211109228d00191d5 Mon Sep 17 00:00:00 2001 From: Andrei Selkin Date: Sat, 18 Jun 2016 10:53:31 +0300 Subject: [PATCH] Issue #3117: Add ability to differentiate annotation placement in foreach, for loops, parameter definition --- .../annotation/AnnotationLocationCheck.java | 73 ++++++++++++++++++- .../AnnotationLocationCheckTest.java | 14 ++++ .../annotation/InputAnnotationLocation4.java | 50 +++++++++++++ src/xdocs/config_annotation.xml | 28 +++++++ 4 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/com/puppycrawl/tools/checkstyle/checks/annotation/InputAnnotationLocation4.java diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/checks/annotation/AnnotationLocationCheck.java b/src/main/java/com/puppycrawl/tools/checkstyle/checks/annotation/AnnotationLocationCheck.java index d6b21c6130d6..27588625bc0c 100644 --- a/src/main/java/com/puppycrawl/tools/checkstyle/checks/annotation/AnnotationLocationCheck.java +++ b/src/main/java/com/puppycrawl/tools/checkstyle/checks/annotation/AnnotationLocationCheck.java @@ -19,6 +19,8 @@ package com.puppycrawl.tools.checkstyle.checks.annotation; +import java.util.Arrays; + import com.puppycrawl.tools.checkstyle.api.AbstractCheck; import com.puppycrawl.tools.checkstyle.api.DetailAST; import com.puppycrawl.tools.checkstyle.api.TokenTypes; @@ -121,6 +123,40 @@ * /> * </module> * + *
+ *

+ * The following example demonstrates how the check validates annotation of method parameters, + * catch parameters, foreach, for-loop variable definitions. + *

+ * + *

Configuration: + *

+ * <module name="AnnotationLocation">
+ *    <property name="allowSamelineMultipleAnnotations" value="false"/>
+ *    <property name="allowSamelineSingleParameterlessAnnotation"
+ *    value="false"/>
+ *    <property name="allowSamelineParameterizedAnnotation" value="false"
+ *    />
+ *    <property name="tokens" value="VARIABLE_DEF, PARAMETER_DEF"/>
+ * </module>
+ * 
+ * + *

Code example + * {@code + * ... + * public void test(@MyAnnotation String s) { // OK + * ... + * for (@MyAnnotation char c : s.toCharArray()) { ... } // OK + * ... + * try { ... } + * catch (@MyAnnotation Exception ex) { ... } // OK + * ... + * for (@MyAnnotation int i = 0; i < 10; i++) { ... } // OK + * ... + * MathOperation c = (@MyAnnotation int a, @MyAnnotation int b) -> a + b; // OK + * ... + * } + * } * * @author maxvetrenko */ @@ -137,6 +173,11 @@ public class AnnotationLocationCheck extends AbstractCheck { */ public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location"; + /** Array of single line annotation parents. */ + private static final int[] SINGLELINE_ANNOTATION_PARENTS = {TokenTypes.FOR_EACH_CLAUSE, + TokenTypes.PARAMETER_DEF, + TokenTypes.FOR_INIT, }; + /** * If true, it allows single prameterless annotation to be located on the same line as * target element. @@ -308,7 +349,8 @@ private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) { } return allowSamelineMultipleAnnotations || allowingCondition && !hasNodeBefore(annotation) - || !allowingCondition && !hasNodeBeside(annotation); + || !allowingCondition && (!hasNodeBeside(annotation) + || isAllowedPosition(annotation, SINGLELINE_ANNOTATION_PARENTS)); } /** @@ -347,4 +389,33 @@ private static boolean hasNodeAfter(DetailAST annotation) { return annotationLineNo == nextNode.getLineNo(); } + + /** + * Checks whether position of annotation is allowed. + * @param annotation annotation token. + * @param allowedPositions an array of allowed annotation positions. + * @return true if position of annotation is allowed. + */ + public static boolean isAllowedPosition(DetailAST annotation, int... allowedPositions) { + return Arrays.stream(allowedPositions) + .anyMatch(position -> isInSpecificCodeBlock(annotation, position)); + } + + /** + * Checks whether the scope of a node is restricted to a specific code block. + * @param node node. + * @param blockType block type. + * @return true if the scope of a node is restricted to a specific code block. + */ + private static boolean isInSpecificCodeBlock(DetailAST node, int blockType) { + boolean returnValue = false; + for (DetailAST token = node.getParent(); token != null; token = token.getParent()) { + final int type = token.getType(); + if (type == blockType) { + returnValue = true; + break; + } + } + return returnValue; + } } diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/checks/annotation/AnnotationLocationCheckTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/checks/annotation/AnnotationLocationCheckTest.java index 9e5aa535bb45..cd9a04a6208b 100644 --- a/src/test/java/com/puppycrawl/tools/checkstyle/checks/annotation/AnnotationLocationCheckTest.java +++ b/src/test/java/com/puppycrawl/tools/checkstyle/checks/annotation/AnnotationLocationCheckTest.java @@ -158,4 +158,18 @@ public void testAllTokens() throws Exception { final String[] expected = CommonUtils.EMPTY_STRING_ARRAY; verify(checkConfig, getPath("InputAnnotationLocation3.java"), expected); } + + @Test + public void testAnnotationPlacementInForEachLoop() throws Exception { + final DefaultConfiguration checkConfig = createCheckConfig(AnnotationLocationCheck.class); + checkConfig.addAttribute("tokens", "CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF," + + " CTOR_DEF, VARIABLE_DEF, PARAMETER_DEF, ANNOTATION_DEF, TYPECAST, LITERAL_THROWS," + + " IMPLEMENTS_CLAUSE, TYPE_ARGUMENT, LITERAL_NEW, DOT, ANNOTATION_FIELD_DEF," + + " TYPE_ARGUMENT"); + checkConfig.addAttribute("allowSamelineMultipleAnnotations", "false"); + checkConfig.addAttribute("allowSamelineSingleParameterlessAnnotation", "false"); + checkConfig.addAttribute("allowSamelineParameterizedAnnotation", "false"); + final String[] expected = CommonUtils.EMPTY_STRING_ARRAY; + verify(checkConfig, getPath("InputAnnotationLocation4.java"), expected); + } } diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/annotation/InputAnnotationLocation4.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/annotation/InputAnnotationLocation4.java new file mode 100644 index 000000000000..473a0aa6237a --- /dev/null +++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/annotation/InputAnnotationLocation4.java @@ -0,0 +1,50 @@ +package com.puppycrawl.tools.checkstyle.checks.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +public class InputAnnotationLocation4 { + @Deprecated // <--class, separate line + public class Annotation + { + @Deprecated // <--method, separate line + public void test(@MyAnnotation String s) { // <--parameter, same line + @MyAnnotation // <--variable, separate line + Integer i; + for (@MyAnnotation char c : s.toCharArray()) { // <--variable in for each, same line + } + } + } + + public class Test { + public void foo1() { + try { + // some code + } + catch (@MyAnnotation Exception ex) { + + } + } + + public void foo2() { + for (@MyAnnotation int i = 0; i < 10; i++) { + + } + } + + public void foo3() { + MathOperation c = (@MyAnnotation int a, @MyAnnotation int b) -> a + b; + } + + public void foo4(@MyAnnotation int a, @MyAnnotation int b) {} + + public void foo5(@SuppressWarnings("unchecked") int a) {} + } + + interface MathOperation { + int operation(int a, int b); + } + + @Target(ElementType.TYPE_USE) + public @interface MyAnnotation {} +} diff --git a/src/xdocs/config_annotation.xml b/src/xdocs/config_annotation.xml index 8e37be0ec084..36e0a54bc543 100644 --- a/src/xdocs/config_annotation.xml +++ b/src/xdocs/config_annotation.xml @@ -153,6 +153,34 @@ public String getNameIfPresent() { ... } <property name="allowSamelineParameterizedAnnotation" value="true"/> </module> +

+ The following example demonstrates how the check validates annotations of method parameters, + catch parameters, foreach, for-loop variable definitions. +

+

Configuration:

+ +<module name="AnnotationLocation"> + <property name="allowSamelineMultipleAnnotations" value="false"/> + <property name="allowSamelineSingleParameterlessAnnotation" value="false"/> + <property name="allowSamelineParameterizedAnnotation" value="false"/> + <property name="tokens" value="VARIABLE_DEF, PARAMETER_DEF"/> + </module> + +

Code example:

+ +public void test(@MyAnnotation String s) { // OK + ... + for (@MyAnnotation char c : s.toCharArray()) { ... } // OK + ... + try { ... } + catch (@MyAnnotation Exception ex) { ... } // OK + ... + for (@MyAnnotation int i = 0; i < 10; i++) { ... } // OK + ... + MathOperation c = (@MyAnnotation int a, @MyAnnotation int b) -> a + b; // OK + ... +} +