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"
087     * file.
088     */
089    public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone";
090
091    /**
092     * A key is pointing to the warning message text in "messages.properties"
093     * file.
094     */
095    public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location";
096
097    /**
098     * Allow single parameterless annotation to be located on the same line as
099     * 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}