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