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