001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.annotation;
021
022import com.puppycrawl.tools.checkstyle.StatelessCheck;
023import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
027import com.puppycrawl.tools.checkstyle.utils.NullUtil;
028import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
029
030/**
031 * <div>
032 * Checks location of annotation on language elements.
033 * By default, Check enforce to locate annotations before target element,
034 * annotation should be located on separate line from target element.
035 * This check also verifies that the annotations are on the same indenting level
036 * as the annotated element if they are not on the same line.
037 * </div>
038 *
039 * <p>
040 * Attention: Elements that cannot have JavaDoc comments like local variables are not in the
041 * scope of this check even though a token type like {@code VARIABLE_DEF} would match them.
042 * </p>
043 *
044 * <p>
045 * Attention: Annotations among modifiers are ignored (looks like false-negative)
046 * as there might be a problem with annotations for return types:
047 * </p>
048 * {@snippet lang="text" :
049 * public @Nullable Long getStartTimeOrNull() { ... }
050 * }
051 *
052 * <p>
053 * Such annotations are better to keep close to type.
054 * Due to limitations, Checkstyle can not examine the target of an annotation.
055 * </p>
056 *
057 * <p>
058 * Example:
059 * </p>
060 * {@snippet lang="text" :
061 * @Override
062 * @Nullable
063 * public String getNameIfPresent() { ... }
064 * }
065 *
066 * <p>
067 * Notes:
068 * This check does <strong>not</strong> enforce annotations to be placed
069 * immediately after the documentation block. If that behavior is desired, consider also using
070 * <a href="https://checkstyle.org/checks/javadoc/invalidjavadocposition.html#InvalidJavadocPosition">
071 * InvalidJavadocPosition</a>.
072 * </p>
073 *
074 * <p>
075 * The property {@code allowSamelineMultipleAnnotations} has the
076 * dominant effect and allows both single and multiple annotations on
077 * the same line, regardless of whether they are parameterized or parameterless.
078 * </p>
079 *
080 * @since 6.0
081 */
082@StatelessCheck
083public class AnnotationLocationCheck extends AbstractCheck {
084
085    /**
086     * A key is pointing to the warning message text in "messages.properties" file.
087     */
088    public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone";
089
090    /**
091     * A key is pointing to the warning message text in "messages.properties" file.
092     */
093    public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location";
094
095    /**
096     * Allow single parameterless annotation to be located on the same line as target element.
097     */
098    private boolean allowSamelineSingleParameterlessAnnotation = true;
099
100    /**
101     * Allow one and only parameterized annotation to be located on the same line as target element.
102     */
103    private boolean allowSamelineParameterizedAnnotation;
104
105    /**
106     * Allow annotation(s) to be located on the same line as target element.
107     */
108    private boolean allowSamelineMultipleAnnotations;
109
110    /**
111     * Creates a new {@code AnnotationLocationCheck} instance.
112     */
113    public AnnotationLocationCheck() {
114        // no code by default
115    }
116
117    /**
118     * Setter to allow single parameterless annotation to be located on the same line as
119     * target element.
120     *
121     * @param allow User's value of allowSamelineSingleParameterlessAnnotation.
122     * @since 6.1
123     */
124    public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) {
125        allowSamelineSingleParameterlessAnnotation = allow;
126    }
127
128    /**
129     * Setter to allow one and only parameterized annotation to be located on the same line as
130     * target element.
131     *
132     * @param allow User's value of allowSamelineParameterizedAnnotation.
133     * @since 6.4
134     */
135    public final void setAllowSamelineParameterizedAnnotation(boolean allow) {
136        allowSamelineParameterizedAnnotation = allow;
137    }
138
139    /**
140     * Setter to allow annotation(s) to be located on the same line as target element.
141     *
142     * @param allow User's value of allowSamelineMultipleAnnotations.
143     * @since 6.0
144     */
145    public final void setAllowSamelineMultipleAnnotations(boolean allow) {
146        allowSamelineMultipleAnnotations = allow;
147    }
148
149    @Override
150    public int[] getDefaultTokens() {
151        return new int[] {
152            TokenTypes.CLASS_DEF,
153            TokenTypes.INTERFACE_DEF,
154            TokenTypes.PACKAGE_DEF,
155            TokenTypes.ENUM_CONSTANT_DEF,
156            TokenTypes.ENUM_DEF,
157            TokenTypes.METHOD_DEF,
158            TokenTypes.CTOR_DEF,
159            TokenTypes.VARIABLE_DEF,
160            TokenTypes.RECORD_DEF,
161            TokenTypes.COMPACT_CTOR_DEF,
162            TokenTypes.MODULE_DEF,
163        };
164    }
165
166    @Override
167    public int[] getAcceptableTokens() {
168        return new int[] {
169            TokenTypes.CLASS_DEF,
170            TokenTypes.INTERFACE_DEF,
171            TokenTypes.PACKAGE_DEF,
172            TokenTypes.ENUM_CONSTANT_DEF,
173            TokenTypes.ENUM_DEF,
174            TokenTypes.METHOD_DEF,
175            TokenTypes.CTOR_DEF,
176            TokenTypes.VARIABLE_DEF,
177            TokenTypes.ANNOTATION_DEF,
178            TokenTypes.ANNOTATION_FIELD_DEF,
179            TokenTypes.RECORD_DEF,
180            TokenTypes.COMPACT_CTOR_DEF,
181            TokenTypes.MODULE_DEF,
182        };
183    }
184
185    @Override
186    public int[] getRequiredTokens() {
187        return CommonUtil.EMPTY_INT_ARRAY;
188    }
189
190    @Override
191    public void visitToken(DetailAST ast) {
192        // ignore variable def tokens that are not field definitions
193        if (ast.getType() != TokenTypes.VARIABLE_DEF
194                || ast.getParent().getType() == TokenTypes.OBJBLOCK
195                || ast.getParent().getType() == TokenTypes.COMPACT_COMPILATION_UNIT) {
196            final DetailAST node = getAnnotationParent(ast);
197            checkAnnotations(node, getExpectedAnnotationIndentation(node));
198        }
199    }
200
201    /**
202     * Returns the node holding the annotations of the given ast.
203     *
204     * @param annotatedNodeAst node being visited.
205     * @return the MODIFIERS node, or, if absent, the ANNOTATIONS node.
206     *
207     * @notNull because  absence of MODIFIERS on such a token means it is a package declaration,
208     *                 which by grammar always carries an ANNOTATIONS node.
209     */
210    private static DetailAST getAnnotationParent(DetailAST annotatedNodeAst) {
211        DetailAST node = annotatedNodeAst.findFirstToken(TokenTypes.MODIFIERS);
212        if (node == null) {
213            node = NullUtil.notNull(annotatedNodeAst.findFirstToken(TokenTypes.ANNOTATIONS));
214        }
215        return node;
216    }
217
218    /**
219     * Returns an expected annotation indentation.
220     * The expected indentation should be the same as the indentation of the target node.
221     *
222     * @param node modifiers or annotations node.
223     * @return the annotation indentation.
224     */
225    private static int getExpectedAnnotationIndentation(DetailAST node) {
226        return node.getColumnNo();
227    }
228
229    /**
230     * Checks annotations positions in code:
231     * 1) Checks whether the annotations locations are correct.
232     * 2) Checks whether the annotations have the valid indentation level.
233     *
234     * @param annotationParent node.
235     * @param correctIndentation correct indentation of the annotation.
236     */
237    private void checkAnnotations(DetailAST annotationParent, int correctIndentation) {
238        DetailAST annotation = annotationParent.getFirstChild();
239
240        while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) {
241            final boolean hasParameters = isParameterized(annotation);
242
243            if (!isCorrectLocation(annotation, hasParameters)) {
244                log(annotation,
245                        MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation));
246            }
247            else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) {
248                log(annotation, MSG_KEY_ANNOTATION_LOCATION,
249                    getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation);
250            }
251            annotation = annotation.getNextSibling();
252        }
253    }
254
255    /**
256     * Checks whether an annotation has parameters.
257     *
258     * @param annotation annotation node.
259     * @return true if the annotation has parameters.
260     */
261    private static boolean isParameterized(DetailAST annotation) {
262        return TokenUtil.findFirstTokenByPredicate(annotation, ast -> {
263            return ast.getType() == TokenTypes.EXPR
264                || ast.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR;
265        }).isPresent();
266    }
267
268    /**
269     * Returns the name of the given annotation.
270     *
271     * @param annotation annotation node.
272     * @return annotation name.
273     *
274     * @notNull because method operates only on annotation ast nodes;
275     *          absence of IDENT as a direct child of the annotation means
276     *          it has a fully qualified name expressed via DOT and IDENT.
277     */
278    private static String getAnnotationName(DetailAST annotation) {
279        DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT);
280        if (identNode == null) {
281            final DetailAST dotNode = NullUtil.notNull(annotation.findFirstToken(TokenTypes.DOT));
282            identNode = NullUtil.notNull(dotNode.findFirstToken(TokenTypes.IDENT));
283        }
284        return identNode.getText();
285    }
286
287    /**
288     * Checks whether an annotation has a correct location.
289     * Annotation location is considered correct
290     * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true.
291     * The method also:
292     * 1) checks parameterized annotation location considering
293     * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation};
294     * 2) checks parameterless annotation location considering
295     * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation};
296     * 3) checks annotation location;
297     *
298     * @param annotation annotation node.
299     * @param hasParams whether an annotation has parameters.
300     * @return true if the annotation has a correct location.
301     */
302    private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) {
303        final boolean allowingCondition;
304
305        if (hasParams) {
306            allowingCondition = allowSamelineParameterizedAnnotation;
307        }
308        else {
309            allowingCondition = allowSamelineSingleParameterlessAnnotation;
310        }
311        return allowSamelineMultipleAnnotations
312            || allowingCondition && !hasNodeBefore(annotation)
313            || !hasNodeBeside(annotation);
314    }
315
316    /**
317     * Checks whether an annotation node has any node before on the same line.
318     *
319     * @param annotation annotation node.
320     * @return true if an annotation node has any node before on the same line.
321     */
322    private static boolean hasNodeBefore(DetailAST annotation) {
323        final int annotationLineNo = annotation.getLineNo();
324        final DetailAST previousNode = annotation.getPreviousSibling();
325
326        return previousNode != null && annotationLineNo == previousNode.getLineNo();
327    }
328
329    /**
330     * Checks whether an annotation node has any node before or after on the same line.
331     *
332     * @param annotation annotation node.
333     * @return true if an annotation node has any node before or after on the same line.
334     */
335    private static boolean hasNodeBeside(DetailAST annotation) {
336        return hasNodeBefore(annotation) || hasNodeAfter(annotation);
337    }
338
339    /**
340     * Checks whether an annotation node has any node after on the same line.
341     *
342     * @param annotation annotation node.
343     * @return true if an annotation node has any node after on the same line.
344     */
345    private static boolean hasNodeAfter(DetailAST annotation) {
346        final int annotationLineNo = annotation.getLineNo();
347        DetailAST nextNode = annotation.getNextSibling();
348
349        if (nextNode == null) {
350            nextNode = annotation.getParent().getNextSibling();
351        }
352
353        return annotationLineNo == nextNode.getLineNo();
354    }
355
356}