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