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