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