Skip to content

Commit

Permalink
Issue checkstyle#3117: Add ability to differentiate annotation placem…
Browse files Browse the repository at this point in the history
…ent in foreach, for loops, parameter definition
  • Loading branch information
MEZk committed Jun 18, 2016
1 parent b02bdda commit 0b397a6
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 1 deletion.
Expand Up @@ -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;
Expand Down Expand Up @@ -121,6 +123,40 @@
* />
* </module>
* </pre>
* <br>
* <p>
* The following example demonstrates how the check validates annotation of method parameters,
* catch parameters, foreach, for-loop variable definitions.
* </p>
*
* <p>Configuration:
* <pre>
* &lt;module name=&quot;AnnotationLocation&quot;&gt;
* &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;false&quot;/&gt;
* &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
* value=&quot;false&quot;/&gt;
* &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;false&quot;
* /&gt;
* &lt;property name=&quot;tokens&quot; value=&quot;VARIABLE_DEF, PARAMETER_DEF&quot;/&gt;
* &lt;/module&gt;
* </pre>
*
* <p>Code example
* {@code
* ...
* public void test(&#64;MyAnnotation String s) { // OK
* ...
* for (&#64;MyAnnotation char c : s.toCharArray()) { ... } // OK
* ...
* try { ... }
* catch (&#64;MyAnnotation Exception ex) { ... } // OK
* ...
* for (&#64;MyAnnotation int i = 0; i &lt; 10; i++) { ... } // OK
* ...
* MathOperation c = (&#64;MyAnnotation int a, &#64;MyAnnotation int b) -&gt; a + b; // OK
* ...
* }
* }
*
* @author maxvetrenko
*/
Expand All @@ -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.
Expand Down Expand Up @@ -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));
}

/**
Expand Down Expand Up @@ -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;
}
}
Expand Up @@ -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);
}
}
@@ -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 {}
}
28 changes: 28 additions & 0 deletions src/xdocs/config_annotation.xml
Expand Up @@ -153,6 +153,34 @@ public String getNameIfPresent() { ... }
&lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;true&quot;/&gt;
&lt;/module&gt;
</source>
<p>
The following example demonstrates how the check validates annotations of method parameters,
catch parameters, foreach, for-loop variable definitions.
</p>
<p>Configuration:</p>
<source>
&lt;module name=&quot;AnnotationLocation&quot;&gt;
&lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;false&quot;/&gt;
&lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot; value=&quot;false&quot;/&gt;
&lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;false&quot;/&gt;
&lt;property name=&quot;tokens&quot; value=&quot;VARIABLE_DEF, PARAMETER_DEF&quot;/&gt;
&lt;/module&gt;
</source>
<p>Code example:</p>
<source>
public void test(&#64;MyAnnotation String s) { // OK
...
for (&#64;MyAnnotation char c : s.toCharArray()) { ... } // OK
...
try { ... }
catch (&#64;MyAnnotation Exception ex) { ... } // OK
...
for (&#64;MyAnnotation int i = 0; i &lt; 10; i++) { ... } // OK
...
MathOperation c = (&#64;MyAnnotation int a, &#64;MyAnnotation int b) -&gt; a + b; // OK
...
}
</source>
</subsection>

<subsection name="Example of Usage">
Expand Down

0 comments on commit 0b397a6

Please sign in to comment.