View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.annotation;
21  
22  import com.puppycrawl.tools.checkstyle.StatelessCheck;
23  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24  import com.puppycrawl.tools.checkstyle.api.DetailAST;
25  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
27  
28  /**
29   * Check location of annotation on language elements.
30   * By default, Check enforce to locate annotations immediately after
31   * documentation block and before target element, annotation should be located
32   * on separate line from target element.
33   * <p>
34   * Attention: Annotations among modifiers are ignored (looks like false-negative)
35   * as there might be a problem with annotations for return types.
36   * </p>
37   * <pre>public @Nullable Long getStartTimeOrNull() { ... }</pre>.
38   * <p>
39   * Such annotations are better to keep close to type.
40   * Due to limitations, Checkstyle can not examine the target of an annotation.
41   * </p>
42   *
43   * <p>
44   * Example:
45   * </p>
46   *
47   * <pre>
48   * &#64;Override
49   * &#64;Nullable
50   * public String getNameIfPresent() { ... }
51   * </pre>
52   *
53   * <p>
54   * The check has the following options:
55   * </p>
56   * <ul>
57   * <li>allowSamelineMultipleAnnotations - to allow annotation to be located on
58   * the same line as the target element. Default value is false.
59   * </li>
60   *
61   * <li>
62   * allowSamelineSingleParameterlessAnnotation - to allow single parameterless
63   * annotation to be located on the same line as the target element. Default value is false.
64   * </li>
65   *
66   * <li>
67   * allowSamelineParameterizedAnnotation - to allow parameterized annotation
68   * to be located on the same line as the target element. Default value is false.
69   * </li>
70   * </ul>
71   * <br>
72   * <p>
73   * Example to allow single parameterless annotation on the same line:
74   * </p>
75   * <pre>
76   * &#64;Override public int hashCode() { ... }
77   * </pre>
78   *
79   * <p>Use the following configuration:
80   * <pre>
81   * &lt;module name=&quot;AnnotationLocation&quot;&gt;
82   *    &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;false&quot;/&gt;
83   *    &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
84   *    value=&quot;true&quot;/&gt;
85   *    &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;false&quot;
86   *    /&gt;
87   * &lt;/module&gt;
88   * </pre>
89   * <br>
90   * <p>
91   * Example to allow multiple parameterized annotations on the same line:
92   * </p>
93   * <pre>
94   * &#64;SuppressWarnings("deprecation") &#64;Mock DataLoader loader;
95   * </pre>
96   *
97   * <p>Use the following configuration:
98   * <pre>
99   * &lt;module name=&quot;AnnotationLocation&quot;&gt;
100  *    &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;true&quot;/&gt;
101  *    &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
102  *    value=&quot;true&quot;/&gt;
103  *    &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;true&quot;
104  *    /&gt;
105  * &lt;/module&gt;
106  * </pre>
107  * <br>
108  * <p>
109  * Example to allow multiple parameterless annotations on the same line:
110  * </p>
111  * <pre>
112  * &#64;Partial &#64;Mock DataLoader loader;
113  * </pre>
114  *
115  * <p>Use the following configuration:
116  * <pre>
117  * &lt;module name=&quot;AnnotationLocation&quot;&gt;
118  *    &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;true&quot;/&gt;
119  *    &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
120  *    value=&quot;true&quot;/&gt;
121  *    &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;false&quot;
122  *    /&gt;
123  * &lt;/module&gt;
124  * </pre>
125  * <br>
126  * <p>
127  * The following example demonstrates how the check validates annotation of method parameters,
128  * catch parameters, foreach, for-loop variable definitions.
129  * </p>
130  *
131  * <p>Configuration:
132  * <pre>
133  * &lt;module name=&quot;AnnotationLocation&quot;&gt;
134  *    &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;false&quot;/&gt;
135  *    &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
136  *    value=&quot;false&quot;/&gt;
137  *    &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;false&quot;
138  *    /&gt;
139  *    &lt;property name=&quot;tokens&quot; value=&quot;VARIABLE_DEF, PARAMETER_DEF&quot;/&gt;
140  * &lt;/module&gt;
141  * </pre>
142  *
143  * <p>Code example
144  * {@code
145  * ...
146  * public void test(&#64;MyAnnotation String s) { // OK
147  *   ...
148  *   for (&#64;MyAnnotation char c : s.toCharArray()) { ... }  // OK
149  *   ...
150  *   try { ... }
151  *   catch (&#64;MyAnnotation Exception ex) { ... } // OK
152  *   ...
153  *   for (&#64;MyAnnotation int i = 0; i &lt; 10; i++) { ... } // OK
154  *   ...
155  *   MathOperation c = (&#64;MyAnnotation int a, &#64;MyAnnotation int b) -&gt; a + b; // OK
156  *   ...
157  * }
158  * }
159  *
160  * @author maxvetrenko
161  */
162 @StatelessCheck
163 public class AnnotationLocationCheck extends AbstractCheck {
164     /**
165      * A key is pointing to the warning message text in "messages.properties"
166      * file.
167      */
168     public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone";
169 
170     /**
171      * A key is pointing to the warning message text in "messages.properties"
172      * file.
173      */
174     public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location";
175 
176     /** Array of single line annotation parents. */
177     private static final int[] SINGLELINE_ANNOTATION_PARENTS = {TokenTypes.FOR_EACH_CLAUSE,
178                                                                 TokenTypes.PARAMETER_DEF,
179                                                                 TokenTypes.FOR_INIT, };
180 
181     /**
182      * If true, it allows single prameterless annotation to be located on the same line as
183      * target element.
184      */
185     private boolean allowSamelineSingleParameterlessAnnotation = true;
186 
187     /**
188      * If true, it allows parameterized annotation to be located on the same line as
189      * target element.
190      */
191     private boolean allowSamelineParameterizedAnnotation;
192 
193     /**
194      * If true, it allows annotation to be located on the same line as
195      * target element.
196      */
197     private boolean allowSamelineMultipleAnnotations;
198 
199     /**
200      * Sets if allow same line single parameterless annotation.
201      * @param allow User's value of allowSamelineSingleParameterlessAnnotation.
202      */
203     public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) {
204         allowSamelineSingleParameterlessAnnotation = allow;
205     }
206 
207     /**
208      * Sets if allow parameterized annotation to be located on the same line as
209      * target element.
210      * @param allow User's value of allowSamelineParameterizedAnnotation.
211      */
212     public final void setAllowSamelineParameterizedAnnotation(boolean allow) {
213         allowSamelineParameterizedAnnotation = allow;
214     }
215 
216     /**
217      * Sets if allow annotation to be located on the same line as
218      * target element.
219      * @param allow User's value of allowSamelineMultipleAnnotations.
220      */
221     public final void setAllowSamelineMultipleAnnotations(boolean allow) {
222         allowSamelineMultipleAnnotations = allow;
223     }
224 
225     @Override
226     public int[] getDefaultTokens() {
227         return new int[] {
228             TokenTypes.CLASS_DEF,
229             TokenTypes.INTERFACE_DEF,
230             TokenTypes.ENUM_DEF,
231             TokenTypes.METHOD_DEF,
232             TokenTypes.CTOR_DEF,
233             TokenTypes.VARIABLE_DEF,
234         };
235     }
236 
237     @Override
238     public int[] getAcceptableTokens() {
239         return new int[] {
240             TokenTypes.CLASS_DEF,
241             TokenTypes.INTERFACE_DEF,
242             TokenTypes.ENUM_DEF,
243             TokenTypes.METHOD_DEF,
244             TokenTypes.CTOR_DEF,
245             TokenTypes.VARIABLE_DEF,
246             TokenTypes.PARAMETER_DEF,
247             TokenTypes.ANNOTATION_DEF,
248             TokenTypes.TYPECAST,
249             TokenTypes.LITERAL_THROWS,
250             TokenTypes.IMPLEMENTS_CLAUSE,
251             TokenTypes.TYPE_ARGUMENT,
252             TokenTypes.LITERAL_NEW,
253             TokenTypes.DOT,
254             TokenTypes.ANNOTATION_FIELD_DEF,
255         };
256     }
257 
258     @Override
259     public int[] getRequiredTokens() {
260         return CommonUtils.EMPTY_INT_ARRAY;
261     }
262 
263     @Override
264     public void visitToken(DetailAST ast) {
265         final DetailAST modifiersNode = ast.findFirstToken(TokenTypes.MODIFIERS);
266 
267         if (hasAnnotations(modifiersNode)) {
268             checkAnnotations(modifiersNode, getExpectedAnnotationIndentation(modifiersNode));
269         }
270     }
271 
272     /**
273      * Checks whether a given modifier node has an annotation.
274      * @param modifierNode modifier node.
275      * @return true if the given modifier node has the annotation.
276      */
277     private static boolean hasAnnotations(DetailAST modifierNode) {
278         return modifierNode != null
279             && modifierNode.findFirstToken(TokenTypes.ANNOTATION) != null;
280     }
281 
282     /**
283      * Returns an expected annotation indentation.
284      * The expected indentation should be the same as the indentation of the node
285      * which is the parent of the target modifier node.
286      * @param modifierNode modifier node.
287      * @return the annotation indentation.
288      */
289     private static int getExpectedAnnotationIndentation(DetailAST modifierNode) {
290         return modifierNode.getParent().getColumnNo();
291     }
292 
293     /**
294      * Checks annotations positions in code:
295      * 1) Checks whether the annotations locations are correct.
296      * 2) Checks whether the annotations have the valid indentation level.
297      * @param modifierNode modifiers node.
298      * @param correctIndentation correct indentation of the annotation.
299      */
300     private void checkAnnotations(DetailAST modifierNode, int correctIndentation) {
301         DetailAST annotation = modifierNode.getFirstChild();
302 
303         while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) {
304             final boolean hasParameters = isParameterized(annotation);
305 
306             if (!isCorrectLocation(annotation, hasParameters)) {
307                 log(annotation.getLineNo(),
308                         MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation));
309             }
310             else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) {
311                 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_LOCATION,
312                     getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation);
313             }
314             annotation = annotation.getNextSibling();
315         }
316     }
317 
318     /**
319      * Checks whether an annotation has parameters.
320      * @param annotation annotation node.
321      * @return true if the annotation has parameters.
322      */
323     private static boolean isParameterized(DetailAST annotation) {
324         return annotation.findFirstToken(TokenTypes.EXPR) != null;
325     }
326 
327     /**
328      * Returns the name of the given annotation.
329      * @param annotation annotation node.
330      * @return annotation name.
331      */
332     private static String getAnnotationName(DetailAST annotation) {
333         DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT);
334         if (identNode == null) {
335             identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT);
336         }
337         return identNode.getText();
338     }
339 
340     /**
341      * Checks whether an annotation has a correct location.
342      * Annotation location is considered correct
343      * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true.
344      * The method also:
345      * 1) checks parameterized annotation location considering
346      * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation};
347      * 2) checks parameterless annotation location considering
348      * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation};
349      * 3) checks annotation location considering the elements
350      * of {@link AnnotationLocationCheck#SINGLELINE_ANNOTATION_PARENTS};
351      * @param annotation annotation node.
352      * @param hasParams whether an annotation has parameters.
353      * @return true if the annotation has a correct location.
354      */
355     private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) {
356         final boolean allowingCondition;
357 
358         if (hasParams) {
359             allowingCondition = allowSamelineParameterizedAnnotation;
360         }
361         else {
362             allowingCondition = allowSamelineSingleParameterlessAnnotation;
363         }
364         return allowSamelineMultipleAnnotations
365             || allowingCondition && !hasNodeBefore(annotation)
366             || !allowingCondition && (!hasNodeBeside(annotation)
367             || isAllowedPosition(annotation, SINGLELINE_ANNOTATION_PARENTS));
368     }
369 
370     /**
371      * Checks whether an annotation node has any node before on the same line.
372      * @param annotation annotation node.
373      * @return true if an annotation node has any node before on the same line.
374      */
375     private static boolean hasNodeBefore(DetailAST annotation) {
376         final int annotationLineNo = annotation.getLineNo();
377         final DetailAST previousNode = annotation.getPreviousSibling();
378 
379         return previousNode != null && annotationLineNo == previousNode.getLineNo();
380     }
381 
382     /**
383      * Checks whether an annotation node has any node before or after on the same line.
384      * @param annotation annotation node.
385      * @return true if an annotation node has any node before or after on the same line.
386      */
387     private static boolean hasNodeBeside(DetailAST annotation) {
388         return hasNodeBefore(annotation) || hasNodeAfter(annotation);
389     }
390 
391     /**
392      * Checks whether an annotation node has any node after on the same line.
393      * @param annotation annotation node.
394      * @return true if an annotation node has any node after on the same line.
395      */
396     private static boolean hasNodeAfter(DetailAST annotation) {
397         final int annotationLineNo = annotation.getLineNo();
398         DetailAST nextNode = annotation.getNextSibling();
399 
400         if (nextNode == null) {
401             nextNode = annotation.getParent().getNextSibling();
402         }
403 
404         return annotationLineNo == nextNode.getLineNo();
405     }
406 
407     /**
408      * Checks whether position of annotation is allowed.
409      * @param annotation annotation token.
410      * @param allowedPositions an array of allowed annotation positions.
411      * @return true if position of annotation is allowed.
412      */
413     private static boolean isAllowedPosition(DetailAST annotation, int... allowedPositions) {
414         boolean allowed = false;
415         for (int position : allowedPositions) {
416             if (isInSpecificCodeBlock(annotation, position)) {
417                 allowed = true;
418                 break;
419             }
420         }
421         return allowed;
422     }
423 
424     /**
425      * Checks whether the scope of a node is restricted to a specific code block.
426      * @param node node.
427      * @param blockType block type.
428      * @return true if the scope of a node is restricted to a specific code block.
429      */
430     private static boolean isInSpecificCodeBlock(DetailAST node, int blockType) {
431         boolean returnValue = false;
432         for (DetailAST token = node.getParent(); token != null; token = token.getParent()) {
433             final int type = token.getType();
434             if (type == blockType) {
435                 returnValue = true;
436                 break;
437             }
438         }
439         return returnValue;
440     }
441 }