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