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.TokenUtil;
028
029/**
030 * <div>
031 * Checks location of annotation on language elements.
032 * By default, Check enforce to locate annotations before target element,
033 * annotation should be located on separate line from target element.
034 * This check also verifies that the annotations are on the same indenting level
035 * as the annotated element if they are not on the same line.
036 * </div>
037 *
038 * <p>
039 * Attention: Elements that cannot have JavaDoc comments like local variables are not in the
040 * scope of this check even though a token type like {@code VARIABLE_DEF} would match them.
041 * </p>
042 *
043 * <p>
044 * Attention: Annotations among modifiers are ignored (looks like false-negative)
045 * as there might be a problem with annotations for return types:
046 * </p>
047 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
048 * public @Nullable Long getStartTimeOrNull() { ... }
049 * </code></pre></div>
050 *
051 * <p>
052 * Such annotations are better to keep close to type.
053 * Due to limitations, Checkstyle can not examine the target of an annotation.
054 * </p>
055 *
056 * <p>
057 * Example:
058 * </p>
059 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
060 * &#64;Override
061 * &#64;Nullable
062 * public String getNameIfPresent() { ... }
063 * </code></pre></div>
064 *
065 * <p>
066 * Notes:
067 * This check does <strong>not</strong> enforce annotations to be placed
068 * immediately after the documentation block. If that behavior is desired, consider also using
069 * <a href="https://checkstyle.org/checks/javadoc/invalidjavadocposition.html#InvalidJavadocPosition">
070 * InvalidJavadocPosition</a>.
071 * </p>
072 *
073 * <p>
074 * The property {@code allowSamelineMultipleAnnotations} has the
075 * dominant effect and allows both single and multiple annotations on
076 * the same line, regardless of whether they are parameterized or parameterless.
077 * </p>
078 *
079 * @since 6.0
080 */
081@StatelessCheck
082public class AnnotationLocationCheck extends AbstractCheck {
083
084    /**
085     * A key is pointing to the warning message text in "messages.properties"
086     * 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"
092     * file.
093     */
094    public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location";
095
096    /**
097     * Allow single parameterless annotation to be located on the same line as
098     * target element.
099     */
100    private boolean allowSamelineSingleParameterlessAnnotation = true;
101
102    /**
103     * Allow one and only parameterized annotation to be located on the same line as
104     * target element.
105     */
106    private boolean allowSamelineParameterizedAnnotation;
107
108    /**
109     * Allow annotation(s) to be located on the same line as
110     * target element.
111     */
112    private boolean allowSamelineMultipleAnnotations;
113
114    /**
115     * Creates a new {@code AnnotationLocationCheck} instance.
116     */
117    public AnnotationLocationCheck() {
118        // no code by default
119    }
120
121    /**
122     * Setter to allow single parameterless annotation to be located on the same line as
123     * target element.
124     *
125     * @param allow User's value of allowSamelineSingleParameterlessAnnotation.
126     * @since 6.1
127     */
128    public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) {
129        allowSamelineSingleParameterlessAnnotation = allow;
130    }
131
132    /**
133     * Setter to allow one and only parameterized annotation to be located on the same line as
134     * target element.
135     *
136     * @param allow User's value of allowSamelineParameterizedAnnotation.
137     * @since 6.4
138     */
139    public final void setAllowSamelineParameterizedAnnotation(boolean allow) {
140        allowSamelineParameterizedAnnotation = allow;
141    }
142
143    /**
144     * Setter to allow annotation(s) to be located on the same line as
145     * target element.
146     *
147     * @param allow User's value of allowSamelineMultipleAnnotations.
148     * @since 6.0
149     */
150    public final void setAllowSamelineMultipleAnnotations(boolean allow) {
151        allowSamelineMultipleAnnotations = allow;
152    }
153
154    @Override
155    public int[] getDefaultTokens() {
156        return new int[] {
157            TokenTypes.CLASS_DEF,
158            TokenTypes.INTERFACE_DEF,
159            TokenTypes.PACKAGE_DEF,
160            TokenTypes.ENUM_CONSTANT_DEF,
161            TokenTypes.ENUM_DEF,
162            TokenTypes.METHOD_DEF,
163            TokenTypes.CTOR_DEF,
164            TokenTypes.VARIABLE_DEF,
165            TokenTypes.RECORD_DEF,
166            TokenTypes.COMPACT_CTOR_DEF,
167        };
168    }
169
170    @Override
171    public int[] getAcceptableTokens() {
172        return new int[] {
173            TokenTypes.CLASS_DEF,
174            TokenTypes.INTERFACE_DEF,
175            TokenTypes.PACKAGE_DEF,
176            TokenTypes.ENUM_CONSTANT_DEF,
177            TokenTypes.ENUM_DEF,
178            TokenTypes.METHOD_DEF,
179            TokenTypes.CTOR_DEF,
180            TokenTypes.VARIABLE_DEF,
181            TokenTypes.ANNOTATION_DEF,
182            TokenTypes.ANNOTATION_FIELD_DEF,
183            TokenTypes.RECORD_DEF,
184            TokenTypes.COMPACT_CTOR_DEF,
185        };
186    }
187
188    @Override
189    public int[] getRequiredTokens() {
190        return CommonUtil.EMPTY_INT_ARRAY;
191    }
192
193    @Override
194    public void visitToken(DetailAST ast) {
195        // ignore variable def tokens that are not field definitions
196        if (ast.getType() != TokenTypes.VARIABLE_DEF
197                || ast.getParent().getType() == TokenTypes.OBJBLOCK
198                || ast.getParent().getType() == TokenTypes.COMPACT_COMPILATION_UNIT) {
199            DetailAST node = ast.findFirstToken(TokenTypes.MODIFIERS);
200            if (node == null) {
201                node = ast.findFirstToken(TokenTypes.ANNOTATIONS);
202            }
203            checkAnnotations(node, getExpectedAnnotationIndentation(node));
204        }
205    }
206
207    /**
208     * Returns an expected annotation indentation.
209     * The expected indentation should be the same as the indentation of the target node.
210     *
211     * @param node modifiers or annotations node.
212     * @return the annotation indentation.
213     */
214    private static int getExpectedAnnotationIndentation(DetailAST node) {
215        return node.getColumnNo();
216    }
217
218    /**
219     * Checks annotations positions in code:
220     * 1) Checks whether the annotations locations are correct.
221     * 2) Checks whether the annotations have the valid indentation level.
222     *
223     * @param modifierNode modifiers node.
224     * @param correctIndentation correct indentation of the annotation.
225     */
226    private void checkAnnotations(DetailAST modifierNode, int correctIndentation) {
227        DetailAST annotation = modifierNode.getFirstChild();
228
229        while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) {
230            final boolean hasParameters = isParameterized(annotation);
231
232            if (!isCorrectLocation(annotation, hasParameters)) {
233                log(annotation,
234                        MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation));
235            }
236            else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) {
237                log(annotation, MSG_KEY_ANNOTATION_LOCATION,
238                    getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation);
239            }
240            annotation = annotation.getNextSibling();
241        }
242    }
243
244    /**
245     * Checks whether an annotation has parameters.
246     *
247     * @param annotation annotation node.
248     * @return true if the annotation has parameters.
249     */
250    private static boolean isParameterized(DetailAST annotation) {
251        return TokenUtil.findFirstTokenByPredicate(annotation, ast -> {
252            return ast.getType() == TokenTypes.EXPR
253                || ast.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR;
254        }).isPresent();
255    }
256
257    /**
258     * Returns the name of the given annotation.
259     *
260     * @param annotation annotation node.
261     * @return annotation name.
262     */
263    private static String getAnnotationName(DetailAST annotation) {
264        DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT);
265        if (identNode == null) {
266            identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT);
267        }
268        return identNode.getText();
269    }
270
271    /**
272     * Checks whether an annotation has a correct location.
273     * Annotation location is considered correct
274     * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true.
275     * The method also:
276     * 1) checks parameterized annotation location considering
277     * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation};
278     * 2) checks parameterless annotation location considering
279     * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation};
280     * 3) checks annotation location;
281     *
282     * @param annotation annotation node.
283     * @param hasParams whether an annotation has parameters.
284     * @return true if the annotation has a correct location.
285     */
286    private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) {
287        final boolean allowingCondition;
288
289        if (hasParams) {
290            allowingCondition = allowSamelineParameterizedAnnotation;
291        }
292        else {
293            allowingCondition = allowSamelineSingleParameterlessAnnotation;
294        }
295        return allowSamelineMultipleAnnotations
296            || allowingCondition && !hasNodeBefore(annotation)
297            || !hasNodeBeside(annotation);
298    }
299
300    /**
301     * Checks whether an annotation node has any node before on the same line.
302     *
303     * @param annotation annotation node.
304     * @return true if an annotation node has any node before on the same line.
305     */
306    private static boolean hasNodeBefore(DetailAST annotation) {
307        final int annotationLineNo = annotation.getLineNo();
308        final DetailAST previousNode = annotation.getPreviousSibling();
309
310        return previousNode != null && annotationLineNo == previousNode.getLineNo();
311    }
312
313    /**
314     * Checks whether an annotation node has any node before or after on the same line.
315     *
316     * @param annotation annotation node.
317     * @return true if an annotation node has any node before or after on the same line.
318     */
319    private static boolean hasNodeBeside(DetailAST annotation) {
320        return hasNodeBefore(annotation) || hasNodeAfter(annotation);
321    }
322
323    /**
324     * Checks whether an annotation node has any node after on the same line.
325     *
326     * @param annotation annotation node.
327     * @return true if an annotation node has any node after on the same line.
328     */
329    private static boolean hasNodeAfter(DetailAST annotation) {
330        final int annotationLineNo = annotation.getLineNo();
331        DetailAST nextNode = annotation.getNextSibling();
332
333        if (nextNode == null) {
334            nextNode = annotation.getParent().getNextSibling();
335        }
336
337        return annotationLineNo == nextNode.getLineNo();
338    }
339
340}