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 }