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