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