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            TokenTypes.TYPECAST,
173            TokenTypes.TYPE_ARGUMENT,
174            TokenTypes.DOT,
175            TokenTypes.LITERAL_NEW,
176            TokenTypes.LITERAL_THROWS,
177            TokenTypes.IMPLEMENTS_CLAUSE,
178        };
179    }
180
181    @Override
182    public int[] getRequiredTokens() {
183        return CommonUtil.EMPTY_INT_ARRAY;
184    }
185
186    @Override
187    public void visitToken(DetailAST ast) {
188        // ignore variable def tokens that are not field definitions
189        if (ast.getType() != TokenTypes.VARIABLE_DEF
190                || ast.getParent().getType() == TokenTypes.OBJBLOCK) {
191            DetailAST node = ast.findFirstToken(TokenTypes.MODIFIERS);
192            if (node == null) {
193                node = ast.findFirstToken(TokenTypes.ANNOTATIONS);
194            }
195            checkAnnotations(node, getExpectedAnnotationIndentation(node));
196        }
197    }
198
199    /**
200     * Returns an expected annotation indentation.
201     * The expected indentation should be the same as the indentation of the target node.
202     *
203     * @param node modifiers or annotations node.
204     * @return the annotation indentation.
205     */
206    private static int getExpectedAnnotationIndentation(DetailAST node) {
207        return node.getColumnNo();
208    }
209
210    /**
211     * Checks annotations positions in code:
212     * 1) Checks whether the annotations locations are correct.
213     * 2) Checks whether the annotations have the valid indentation level.
214     *
215     * @param modifierNode modifiers node.
216     * @param correctIndentation correct indentation of the annotation.
217     */
218    private void checkAnnotations(DetailAST modifierNode, int correctIndentation) {
219        DetailAST annotation = modifierNode.getFirstChild();
220
221        while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) {
222            final boolean hasParameters = isParameterized(annotation);
223
224            if (!isCorrectLocation(annotation, hasParameters)) {
225                log(annotation,
226                        MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation));
227            }
228            else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) {
229                log(annotation, MSG_KEY_ANNOTATION_LOCATION,
230                    getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation);
231            }
232            annotation = annotation.getNextSibling();
233        }
234    }
235
236    /**
237     * Checks whether an annotation has parameters.
238     *
239     * @param annotation annotation node.
240     * @return true if the annotation has parameters.
241     */
242    private static boolean isParameterized(DetailAST annotation) {
243        return TokenUtil.findFirstTokenByPredicate(annotation, ast -> {
244            return ast.getType() == TokenTypes.EXPR
245                || ast.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR;
246        }).isPresent();
247    }
248
249    /**
250     * Returns the name of the given annotation.
251     *
252     * @param annotation annotation node.
253     * @return annotation name.
254     */
255    private static String getAnnotationName(DetailAST annotation) {
256        DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT);
257        if (identNode == null) {
258            identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT);
259        }
260        return identNode.getText();
261    }
262
263    /**
264     * Checks whether an annotation has a correct location.
265     * Annotation location is considered correct
266     * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true.
267     * The method also:
268     * 1) checks parameterized annotation location considering
269     * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation};
270     * 2) checks parameterless annotation location considering
271     * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation};
272     * 3) checks annotation location;
273     *
274     * @param annotation annotation node.
275     * @param hasParams whether an annotation has parameters.
276     * @return true if the annotation has a correct location.
277     */
278    private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) {
279        final boolean allowingCondition;
280
281        if (hasParams) {
282            allowingCondition = allowSamelineParameterizedAnnotation;
283        }
284        else {
285            allowingCondition = allowSamelineSingleParameterlessAnnotation;
286        }
287        return allowSamelineMultipleAnnotations
288            || allowingCondition && !hasNodeBefore(annotation)
289            || !hasNodeBeside(annotation);
290    }
291
292    /**
293     * Checks whether an annotation node has any node before on the same line.
294     *
295     * @param annotation annotation node.
296     * @return true if an annotation node has any node before on the same line.
297     */
298    private static boolean hasNodeBefore(DetailAST annotation) {
299        final int annotationLineNo = annotation.getLineNo();
300        final DetailAST previousNode = annotation.getPreviousSibling();
301
302        return previousNode != null && annotationLineNo == previousNode.getLineNo();
303    }
304
305    /**
306     * Checks whether an annotation node has any node before or after on the same line.
307     *
308     * @param annotation annotation node.
309     * @return true if an annotation node has any node before or after on the same line.
310     */
311    private static boolean hasNodeBeside(DetailAST annotation) {
312        return hasNodeBefore(annotation) || hasNodeAfter(annotation);
313    }
314
315    /**
316     * Checks whether an annotation node has any node after on the same line.
317     *
318     * @param annotation annotation node.
319     * @return true if an annotation node has any node after on the same line.
320     */
321    private static boolean hasNodeAfter(DetailAST annotation) {
322        final int annotationLineNo = annotation.getLineNo();
323        DetailAST nextNode = annotation.getNextSibling();
324
325        if (nextNode == null) {
326            nextNode = annotation.getParent().getNextSibling();
327        }
328
329        return annotationLineNo == nextNode.getLineNo();
330    }
331
332}