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