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