View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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   * <div>
31   * Checks 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. This check also verifies that the annotations
35   * are on the same indenting level as the annotated element if they are not on the same line.
36   * </div>
37   *
38   * <p>
39   * Attention: Elements that cannot have JavaDoc comments like local variables are not in the
40   * scope of this check even though a token type like {@code VARIABLE_DEF} would match them.
41   * </p>
42   *
43   * <p>
44   * Attention: Annotations among modifiers are ignored (looks like false-negative)
45   * as there might be a problem with annotations for return types:
46   * </p>
47   * <pre>
48   * public @Nullable Long getStartTimeOrNull() { ... }
49   * </pre>
50   *
51   * <p>
52   * Such annotations are better to keep close to type.
53   * Due to limitations, Checkstyle can not examine the target of an annotation.
54   * </p>
55   *
56   * <p>
57   * Example:
58   * </p>
59   * <pre>
60   * &#64;Override
61   * &#64;Nullable
62   * public String getNameIfPresent() { ... }
63   * </pre>
64   * <ul>
65   * <li>
66   * Property {@code allowSamelineMultipleAnnotations} - Allow annotation(s) to be located on
67   * the same line as target element.
68   * Type is {@code boolean}.
69   * Default value is {@code false}.
70   * </li>
71   * <li>
72   * Property {@code allowSamelineParameterizedAnnotation} - Allow one and only parameterized
73   * annotation to be located on the same line as target element.
74   * Type is {@code boolean}.
75   * Default value is {@code false}.
76   * </li>
77   * <li>
78   * Property {@code allowSamelineSingleParameterlessAnnotation} - Allow single parameterless
79   * annotation to be located on the same line as target element.
80   * Type is {@code boolean}.
81   * Default value is {@code true}.
82   * </li>
83   * <li>
84   * Property {@code tokens} - tokens to check
85   * Type is {@code java.lang.String[]}.
86   * Validation type is {@code tokenSet}.
87   * Default value is:
88   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF">
89   * CLASS_DEF</a>,
90   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF">
91   * INTERFACE_DEF</a>,
92   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PACKAGE_DEF">
93   * PACKAGE_DEF</a>,
94   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF">
95   * ENUM_CONSTANT_DEF</a>,
96   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF">
97   * ENUM_DEF</a>,
98   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
99   * METHOD_DEF</a>,
100  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
101  * CTOR_DEF</a>,
102  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF">
103  * VARIABLE_DEF</a>,
104  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF">
105  * RECORD_DEF</a>,
106  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF">
107  * COMPACT_CTOR_DEF</a>.
108  * </li>
109  * </ul>
110  *
111  * <p>
112  * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
113  * </p>
114  *
115  * <p>
116  * Violation Message Keys:
117  * </p>
118  * <ul>
119  * <li>
120  * {@code annotation.location}
121  * </li>
122  * <li>
123  * {@code annotation.location.alone}
124  * </li>
125  * </ul>
126  *
127  * @since 6.0
128  */
129 @StatelessCheck
130 public class AnnotationLocationCheck extends AbstractCheck {
131 
132     /**
133      * A key is pointing to the warning message text in "messages.properties"
134      * file.
135      */
136     public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone";
137 
138     /**
139      * A key is pointing to the warning message text in "messages.properties"
140      * file.
141      */
142     public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location";
143 
144     /**
145      * Allow single parameterless annotation to be located on the same line as
146      * target element.
147      */
148     private boolean allowSamelineSingleParameterlessAnnotation = true;
149 
150     /**
151      * Allow one and only parameterized annotation to be located on the same line as
152      * target element.
153      */
154     private boolean allowSamelineParameterizedAnnotation;
155 
156     /**
157      * Allow annotation(s) to be located on the same line as
158      * target element.
159      */
160     private boolean allowSamelineMultipleAnnotations;
161 
162     /**
163      * Setter to allow single parameterless annotation to be located on the same line as
164      * target element.
165      *
166      * @param allow User's value of allowSamelineSingleParameterlessAnnotation.
167      * @since 6.1
168      */
169     public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) {
170         allowSamelineSingleParameterlessAnnotation = allow;
171     }
172 
173     /**
174      * Setter to allow one and only parameterized annotation to be located on the same line as
175      * target element.
176      *
177      * @param allow User's value of allowSamelineParameterizedAnnotation.
178      * @since 6.4
179      */
180     public final void setAllowSamelineParameterizedAnnotation(boolean allow) {
181         allowSamelineParameterizedAnnotation = allow;
182     }
183 
184     /**
185      * Setter to allow annotation(s) to be located on the same line as
186      * target element.
187      *
188      * @param allow User's value of allowSamelineMultipleAnnotations.
189      * @since 6.0
190      */
191     public final void setAllowSamelineMultipleAnnotations(boolean allow) {
192         allowSamelineMultipleAnnotations = allow;
193     }
194 
195     @Override
196     public int[] getDefaultTokens() {
197         return new int[] {
198             TokenTypes.CLASS_DEF,
199             TokenTypes.INTERFACE_DEF,
200             TokenTypes.PACKAGE_DEF,
201             TokenTypes.ENUM_CONSTANT_DEF,
202             TokenTypes.ENUM_DEF,
203             TokenTypes.METHOD_DEF,
204             TokenTypes.CTOR_DEF,
205             TokenTypes.VARIABLE_DEF,
206             TokenTypes.RECORD_DEF,
207             TokenTypes.COMPACT_CTOR_DEF,
208         };
209     }
210 
211     @Override
212     public int[] getAcceptableTokens() {
213         return new int[] {
214             TokenTypes.CLASS_DEF,
215             TokenTypes.INTERFACE_DEF,
216             TokenTypes.PACKAGE_DEF,
217             TokenTypes.ENUM_CONSTANT_DEF,
218             TokenTypes.ENUM_DEF,
219             TokenTypes.METHOD_DEF,
220             TokenTypes.CTOR_DEF,
221             TokenTypes.VARIABLE_DEF,
222             TokenTypes.ANNOTATION_DEF,
223             TokenTypes.ANNOTATION_FIELD_DEF,
224             TokenTypes.RECORD_DEF,
225             TokenTypes.COMPACT_CTOR_DEF,
226         };
227     }
228 
229     @Override
230     public int[] getRequiredTokens() {
231         return CommonUtil.EMPTY_INT_ARRAY;
232     }
233 
234     @Override
235     public void visitToken(DetailAST ast) {
236         // ignore variable def tokens that are not field definitions
237         if (ast.getType() != TokenTypes.VARIABLE_DEF
238                 || ast.getParent().getType() == TokenTypes.OBJBLOCK) {
239             DetailAST node = ast.findFirstToken(TokenTypes.MODIFIERS);
240             if (node == null) {
241                 node = ast.findFirstToken(TokenTypes.ANNOTATIONS);
242             }
243             checkAnnotations(node, getExpectedAnnotationIndentation(node));
244         }
245     }
246 
247     /**
248      * Returns an expected annotation indentation.
249      * The expected indentation should be the same as the indentation of the target node.
250      *
251      * @param node modifiers or annotations node.
252      * @return the annotation indentation.
253      */
254     private static int getExpectedAnnotationIndentation(DetailAST node) {
255         return node.getColumnNo();
256     }
257 
258     /**
259      * Checks annotations positions in code:
260      * 1) Checks whether the annotations locations are correct.
261      * 2) Checks whether the annotations have the valid indentation level.
262      *
263      * @param modifierNode modifiers node.
264      * @param correctIndentation correct indentation of the annotation.
265      */
266     private void checkAnnotations(DetailAST modifierNode, int correctIndentation) {
267         DetailAST annotation = modifierNode.getFirstChild();
268 
269         while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) {
270             final boolean hasParameters = isParameterized(annotation);
271 
272             if (!isCorrectLocation(annotation, hasParameters)) {
273                 log(annotation,
274                         MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation));
275             }
276             else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) {
277                 log(annotation, MSG_KEY_ANNOTATION_LOCATION,
278                     getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation);
279             }
280             annotation = annotation.getNextSibling();
281         }
282     }
283 
284     /**
285      * Checks whether an annotation has parameters.
286      *
287      * @param annotation annotation node.
288      * @return true if the annotation has parameters.
289      */
290     private static boolean isParameterized(DetailAST annotation) {
291         return TokenUtil.findFirstTokenByPredicate(annotation, ast -> {
292             return ast.getType() == TokenTypes.EXPR
293                 || ast.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR;
294         }).isPresent();
295     }
296 
297     /**
298      * Returns the name of the given annotation.
299      *
300      * @param annotation annotation node.
301      * @return annotation name.
302      */
303     private static String getAnnotationName(DetailAST annotation) {
304         DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT);
305         if (identNode == null) {
306             identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT);
307         }
308         return identNode.getText();
309     }
310 
311     /**
312      * Checks whether an annotation has a correct location.
313      * Annotation location is considered correct
314      * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true.
315      * The method also:
316      * 1) checks parameterized annotation location considering
317      * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation};
318      * 2) checks parameterless annotation location considering
319      * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation};
320      * 3) checks annotation location;
321      *
322      * @param annotation annotation node.
323      * @param hasParams whether an annotation has parameters.
324      * @return true if the annotation has a correct location.
325      */
326     private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) {
327         final boolean allowingCondition;
328 
329         if (hasParams) {
330             allowingCondition = allowSamelineParameterizedAnnotation;
331         }
332         else {
333             allowingCondition = allowSamelineSingleParameterlessAnnotation;
334         }
335         return allowSamelineMultipleAnnotations
336             || allowingCondition && !hasNodeBefore(annotation)
337             || !hasNodeBeside(annotation);
338     }
339 
340     /**
341      * Checks whether an annotation node has any node before on the same line.
342      *
343      * @param annotation annotation node.
344      * @return true if an annotation node has any node before on the same line.
345      */
346     private static boolean hasNodeBefore(DetailAST annotation) {
347         final int annotationLineNo = annotation.getLineNo();
348         final DetailAST previousNode = annotation.getPreviousSibling();
349 
350         return previousNode != null && annotationLineNo == previousNode.getLineNo();
351     }
352 
353     /**
354      * Checks whether an annotation node has any node before or after on the same line.
355      *
356      * @param annotation annotation node.
357      * @return true if an annotation node has any node before or after on the same line.
358      */
359     private static boolean hasNodeBeside(DetailAST annotation) {
360         return hasNodeBefore(annotation) || hasNodeAfter(annotation);
361     }
362 
363     /**
364      * Checks whether an annotation node has any node after on the same line.
365      *
366      * @param annotation annotation node.
367      * @return true if an annotation node has any node after on the same line.
368      */
369     private static boolean hasNodeAfter(DetailAST annotation) {
370         final int annotationLineNo = annotation.getLineNo();
371         DetailAST nextNode = annotation.getNextSibling();
372 
373         if (nextNode == null) {
374             nextNode = annotation.getParent().getNextSibling();
375         }
376 
377         return annotationLineNo == nextNode.getLineNo();
378     }
379 
380 }