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