001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2021 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 allowSamelineSingleParameterlessAnnotation} - Allow single parameterless
069 * annotation to be located on the same line as target element.
070 * Type is {@code boolean}.
071 * Default value is {@code true}.
072 * </li>
073 * <li>
074 * Property {@code allowSamelineParameterizedAnnotation} - Allow one and only parameterized
075 * annotation to be located on the same line as target element.
076 * Type is {@code boolean}.
077 * Default value is {@code false}.
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 * To configure the default check to allow one single parameterless annotation on the same line:
108 * </p>
109 * <pre>
110 * &lt;module name=&quot;AnnotationLocation&quot;/&gt;
111 * </pre>
112 * <p>
113 * Example for above configuration:
114 * </p>
115 * <pre>
116 * &#64;NotNull private boolean field1; //ok
117 * &#64;Override public int hashCode() { return 1; } //ok
118 * &#64;NotNull //ok
119 * private boolean field2;
120 * &#64;Override //ok
121 * public boolean equals(Object obj) { return true; }
122 * &#64;Mock DataLoader loader; //ok
123 * &#64;SuppressWarnings("deprecation") DataLoader loader; //violation
124 * &#64;SuppressWarnings("deprecation") public int foo() { return 1; } //violation
125 * &#64;NotNull &#64;Mock DataLoader loader; //violation
126 * </pre>
127 * <p>
128 * Use the following configuration to allow multiple annotations on the same line:
129 * </p>
130 * <pre>
131 * &lt;module name=&quot;AnnotationLocation&quot;&gt;
132 *   &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;true&quot;/&gt;
133 *   &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
134 *     value=&quot;false&quot;/&gt;
135 *   &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;false&quot;/&gt;
136 * &lt;/module&gt;
137 * </pre>
138 * <p>
139 * Example to allow any location multiple annotations:
140 * </p>
141 * <pre>
142 * &#64;NotNull private boolean field1; //ok
143 * &#64;Override public int hashCode() { return 1; } //ok
144 * &#64;NotNull //ok
145 * private boolean field2;
146 * &#64;Override //ok
147 * public boolean equals(Object obj) { return true; }
148 * &#64;Mock DataLoader loader; //ok
149 * &#64;SuppressWarnings("deprecation") DataLoader loader; //ok
150 * &#64;SuppressWarnings("deprecation") public int foo() { return 1; } //ok
151 * &#64;NotNull &#64;Mock DataLoader loader; //ok
152 * </pre>
153 * <p>
154 * Use the following configuration to allow only one and only parameterized annotation
155 * on the same line:
156 * </p>
157 * <pre>
158 * &lt;module name=&quot;AnnotationLocation&quot;&gt;
159 *   &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;false&quot;/&gt;
160 *   &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
161 *     value=&quot;false&quot;/&gt;
162 *   &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;true&quot;/&gt;
163 * &lt;/module&gt;
164 * </pre>
165 * <p>
166 * Example to allow only one and only parameterized annotation on the same line:
167 * </p>
168 * <pre>
169 * &#64;NotNull private boolean field1; //violation
170 * &#64;Override public int hashCode() { return 1; } //violation
171 * &#64;NotNull //ok
172 * private boolean field2;
173 * &#64;Override //ok
174 * public boolean equals(Object obj) { return true; }
175 * &#64;Mock DataLoader loader; //violation
176 * &#64;SuppressWarnings("deprecation") DataLoader loader; //ok
177 * &#64;SuppressWarnings("deprecation") public int foo() { return 1; } //ok
178 * &#64;NotNull &#64;Mock DataLoader loader; //violation
179 * </pre>
180 * <p>
181 * Use the following configuration to only validate annotations on methods to allow one
182 * single parameterless annotation on the same line:
183 * </p>
184 * <pre>
185 * &lt;module name=&quot;AnnotationLocation&quot;&gt;
186 *   &lt;property name=&quot;tokens&quot; value=&quot;METHOD_DEF&quot;/&gt;
187 *   &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;false&quot;/&gt;
188 *   &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
189 *     value=&quot;true&quot;/&gt;
190 *   &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;false&quot;/&gt;
191 *  &lt;/module&gt;
192 * </pre>
193 * <p>
194 * Example for above configuration to check only methods:
195 * </p>
196 * <pre>
197 * &#64;NotNull private boolean field1; //ok
198 * &#64;Override public int hashCode() { return 1; } //ok
199 * &#64;NotNull //ok
200 * private boolean field2;
201 * &#64;Override //ok
202 * public boolean equals(Object obj) { return true; }
203 * &#64;Mock DataLoader loader; //ok
204 * &#64;SuppressWarnings("deprecation") DataLoader loader; //ok
205 * &#64;SuppressWarnings("deprecation") public int foo() { return 1; } //violation
206 * &#64;NotNull &#64;Mock DataLoader loader; //ok
207 * </pre>
208 * <p>
209 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
210 * </p>
211 * <p>
212 * Violation Message Keys:
213 * </p>
214 * <ul>
215 * <li>
216 * {@code annotation.location}
217 * </li>
218 * <li>
219 * {@code annotation.location.alone}
220 * </li>
221 * </ul>
222 *
223 * @since 6.0
224 */
225@StatelessCheck
226public class AnnotationLocationCheck extends AbstractCheck {
227
228    /**
229     * A key is pointing to the warning message text in "messages.properties"
230     * file.
231     */
232    public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone";
233
234    /**
235     * A key is pointing to the warning message text in "messages.properties"
236     * file.
237     */
238    public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location";
239
240    /**
241     * Allow single parameterless annotation to be located on the same line as
242     * target element.
243     */
244    private boolean allowSamelineSingleParameterlessAnnotation = true;
245
246    /**
247     * Allow one and only parameterized annotation to be located on the same line as
248     * target element.
249     */
250    private boolean allowSamelineParameterizedAnnotation;
251
252    /**
253     * Allow annotation(s) to be located on the same line as
254     * target element.
255     */
256    private boolean allowSamelineMultipleAnnotations;
257
258    /**
259     * Setter to allow single parameterless annotation to be located on the same line as
260     * target element.
261     *
262     * @param allow User's value of allowSamelineSingleParameterlessAnnotation.
263     */
264    public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) {
265        allowSamelineSingleParameterlessAnnotation = allow;
266    }
267
268    /**
269     * Setter to allow one and only parameterized annotation to be located on the same line as
270     * target element.
271     *
272     * @param allow User's value of allowSamelineParameterizedAnnotation.
273     */
274    public final void setAllowSamelineParameterizedAnnotation(boolean allow) {
275        allowSamelineParameterizedAnnotation = allow;
276    }
277
278    /**
279     * Setter to allow annotation(s) to be located on the same line as
280     * target element.
281     *
282     * @param allow User's value of allowSamelineMultipleAnnotations.
283     */
284    public final void setAllowSamelineMultipleAnnotations(boolean allow) {
285        allowSamelineMultipleAnnotations = allow;
286    }
287
288    @Override
289    public int[] getDefaultTokens() {
290        return new int[] {
291            TokenTypes.CLASS_DEF,
292            TokenTypes.INTERFACE_DEF,
293            TokenTypes.PACKAGE_DEF,
294            TokenTypes.ENUM_CONSTANT_DEF,
295            TokenTypes.ENUM_DEF,
296            TokenTypes.METHOD_DEF,
297            TokenTypes.CTOR_DEF,
298            TokenTypes.VARIABLE_DEF,
299            TokenTypes.RECORD_DEF,
300            TokenTypes.COMPACT_CTOR_DEF,
301        };
302    }
303
304    @Override
305    public int[] getAcceptableTokens() {
306        return new int[] {
307            TokenTypes.CLASS_DEF,
308            TokenTypes.INTERFACE_DEF,
309            TokenTypes.PACKAGE_DEF,
310            TokenTypes.ENUM_CONSTANT_DEF,
311            TokenTypes.ENUM_DEF,
312            TokenTypes.METHOD_DEF,
313            TokenTypes.CTOR_DEF,
314            TokenTypes.VARIABLE_DEF,
315            TokenTypes.ANNOTATION_DEF,
316            TokenTypes.ANNOTATION_FIELD_DEF,
317            TokenTypes.RECORD_DEF,
318            TokenTypes.COMPACT_CTOR_DEF,
319        };
320    }
321
322    @Override
323    public int[] getRequiredTokens() {
324        return CommonUtil.EMPTY_INT_ARRAY;
325    }
326
327    @Override
328    public void visitToken(DetailAST ast) {
329        // ignore variable def tokens that are not field definitions
330        if (ast.getType() != TokenTypes.VARIABLE_DEF
331                || ast.getParent().getType() == TokenTypes.OBJBLOCK) {
332            DetailAST node = ast.findFirstToken(TokenTypes.MODIFIERS);
333            if (node == null) {
334                node = ast.findFirstToken(TokenTypes.ANNOTATIONS);
335            }
336            checkAnnotations(node, getExpectedAnnotationIndentation(node));
337        }
338    }
339
340    /**
341     * Returns an expected annotation indentation.
342     * The expected indentation should be the same as the indentation of the target node.
343     *
344     * @param node modifiers or annotations node.
345     * @return the annotation indentation.
346     */
347    private static int getExpectedAnnotationIndentation(DetailAST node) {
348        return node.getColumnNo();
349    }
350
351    /**
352     * Checks annotations positions in code:
353     * 1) Checks whether the annotations locations are correct.
354     * 2) Checks whether the annotations have the valid indentation level.
355     *
356     * @param modifierNode modifiers node.
357     * @param correctIndentation correct indentation of the annotation.
358     */
359    private void checkAnnotations(DetailAST modifierNode, int correctIndentation) {
360        DetailAST annotation = modifierNode.getFirstChild();
361
362        while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) {
363            final boolean hasParameters = isParameterized(annotation);
364
365            if (!isCorrectLocation(annotation, hasParameters)) {
366                log(annotation,
367                        MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation));
368            }
369            else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) {
370                log(annotation, MSG_KEY_ANNOTATION_LOCATION,
371                    getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation);
372            }
373            annotation = annotation.getNextSibling();
374        }
375    }
376
377    /**
378     * Checks whether an annotation has parameters.
379     *
380     * @param annotation annotation node.
381     * @return true if the annotation has parameters.
382     */
383    private static boolean isParameterized(DetailAST annotation) {
384        return TokenUtil.findFirstTokenByPredicate(annotation, ast -> {
385            return ast.getType() == TokenTypes.EXPR
386                || ast.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR;
387        }).isPresent();
388    }
389
390    /**
391     * Returns the name of the given annotation.
392     *
393     * @param annotation annotation node.
394     * @return annotation name.
395     */
396    private static String getAnnotationName(DetailAST annotation) {
397        DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT);
398        if (identNode == null) {
399            identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT);
400        }
401        return identNode.getText();
402    }
403
404    /**
405     * Checks whether an annotation has a correct location.
406     * Annotation location is considered correct
407     * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true.
408     * The method also:
409     * 1) checks parameterized annotation location considering
410     * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation};
411     * 2) checks parameterless annotation location considering
412     * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation};
413     * 3) checks annotation location;
414     *
415     * @param annotation annotation node.
416     * @param hasParams whether an annotation has parameters.
417     * @return true if the annotation has a correct location.
418     */
419    private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) {
420        final boolean allowingCondition;
421
422        if (hasParams) {
423            allowingCondition = allowSamelineParameterizedAnnotation;
424        }
425        else {
426            allowingCondition = allowSamelineSingleParameterlessAnnotation;
427        }
428        return allowSamelineMultipleAnnotations
429            || allowingCondition && !hasNodeBefore(annotation)
430            || !hasNodeBeside(annotation);
431    }
432
433    /**
434     * Checks whether an annotation node has any node before on the same line.
435     *
436     * @param annotation annotation node.
437     * @return true if an annotation node has any node before on the same line.
438     */
439    private static boolean hasNodeBefore(DetailAST annotation) {
440        final int annotationLineNo = annotation.getLineNo();
441        final DetailAST previousNode = annotation.getPreviousSibling();
442
443        return previousNode != null && annotationLineNo == previousNode.getLineNo();
444    }
445
446    /**
447     * Checks whether an annotation node has any node before or after on the same line.
448     *
449     * @param annotation annotation node.
450     * @return true if an annotation node has any node before or after on the same line.
451     */
452    private static boolean hasNodeBeside(DetailAST annotation) {
453        return hasNodeBefore(annotation) || hasNodeAfter(annotation);
454    }
455
456    /**
457     * Checks whether an annotation node has any node after on the same line.
458     *
459     * @param annotation annotation node.
460     * @return true if an annotation node has any node after on the same line.
461     */
462    private static boolean hasNodeAfter(DetailAST annotation) {
463        final int annotationLineNo = annotation.getLineNo();
464        DetailAST nextNode = annotation.getNextSibling();
465
466        if (nextNode == null) {
467            nextNode = annotation.getParent().getNextSibling();
468        }
469
470        return annotationLineNo == nextNode.getLineNo();
471    }
472
473}