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