Coverage Report - com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationLocationCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
AnnotationLocationCheck
100%
65/65
100%
54/54
2.167
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.annotation;
 21  
 
 22  
 import com.puppycrawl.tools.checkstyle.StatelessCheck;
 23  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 24  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 25  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 26  
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 27  
 
 28  
 /**
 29  
  * Check location of annotation on language elements.
 30  
  * By default, Check enforce to locate annotations immediately after
 31  
  * documentation block and before target element, annotation should be located
 32  
  * on separate line from target element.
 33  
  * <p>
 34  
  * Attention: Annotations among modifiers are ignored (looks like false-negative)
 35  
  * as there might be a problem with annotations for return types.
 36  
  * </p>
 37  
  * <pre>public @Nullable Long getStartTimeOrNull() { ... }</pre>.
 38  
  * <p>
 39  
  * Such annotations are better to keep close to type.
 40  
  * Due to limitations, Checkstyle can not examine the target of an annotation.
 41  
  * </p>
 42  
  *
 43  
  * <p>
 44  
  * Example:
 45  
  * </p>
 46  
  *
 47  
  * <pre>
 48  
  * &#64;Override
 49  
  * &#64;Nullable
 50  
  * public String getNameIfPresent() { ... }
 51  
  * </pre>
 52  
  *
 53  
  * <p>
 54  
  * The check has the following options:
 55  
  * </p>
 56  
  * <ul>
 57  
  * <li>allowSamelineMultipleAnnotations - to allow annotation to be located on
 58  
  * the same line as the target element. Default value is false.
 59  
  * </li>
 60  
  *
 61  
  * <li>
 62  
  * allowSamelineSingleParameterlessAnnotation - to allow single parameterless
 63  
  * annotation to be located on the same line as the target element. Default value is false.
 64  
  * </li>
 65  
  *
 66  
  * <li>
 67  
  * allowSamelineParameterizedAnnotation - to allow parameterized annotation
 68  
  * to be located on the same line as the target element. Default value is false.
 69  
  * </li>
 70  
  * </ul>
 71  
  * <br>
 72  
  * <p>
 73  
  * Example to allow single parameterless annotation on the same line:
 74  
  * </p>
 75  
  * <pre>
 76  
  * &#64;Override public int hashCode() { ... }
 77  
  * </pre>
 78  
  *
 79  
  * <p>Use the following configuration:
 80  
  * <pre>
 81  
  * &lt;module name=&quot;AnnotationLocation&quot;&gt;
 82  
  *    &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;false&quot;/&gt;
 83  
  *    &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
 84  
  *    value=&quot;true&quot;/&gt;
 85  
  *    &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;false&quot;
 86  
  *    /&gt;
 87  
  * &lt;/module&gt;
 88  
  * </pre>
 89  
  * <br>
 90  
  * <p>
 91  
  * Example to allow multiple parameterized annotations on the same line:
 92  
  * </p>
 93  
  * <pre>
 94  
  * &#64;SuppressWarnings("deprecation") &#64;Mock DataLoader loader;
 95  
  * </pre>
 96  
  *
 97  
  * <p>Use the following configuration:
 98  
  * <pre>
 99  
  * &lt;module name=&quot;AnnotationLocation&quot;&gt;
 100  
  *    &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;true&quot;/&gt;
 101  
  *    &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
 102  
  *    value=&quot;true&quot;/&gt;
 103  
  *    &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;true&quot;
 104  
  *    /&gt;
 105  
  * &lt;/module&gt;
 106  
  * </pre>
 107  
  * <br>
 108  
  * <p>
 109  
  * Example to allow multiple parameterless annotations on the same line:
 110  
  * </p>
 111  
  * <pre>
 112  
  * &#64;Partial &#64;Mock DataLoader loader;
 113  
  * </pre>
 114  
  *
 115  
  * <p>Use the following configuration:
 116  
  * <pre>
 117  
  * &lt;module name=&quot;AnnotationLocation&quot;&gt;
 118  
  *    &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;true&quot;/&gt;
 119  
  *    &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
 120  
  *    value=&quot;true&quot;/&gt;
 121  
  *    &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;false&quot;
 122  
  *    /&gt;
 123  
  * &lt;/module&gt;
 124  
  * </pre>
 125  
  * <br>
 126  
  * <p>
 127  
  * The following example demonstrates how the check validates annotation of method parameters,
 128  
  * catch parameters, foreach, for-loop variable definitions.
 129  
  * </p>
 130  
  *
 131  
  * <p>Configuration:
 132  
  * <pre>
 133  
  * &lt;module name=&quot;AnnotationLocation&quot;&gt;
 134  
  *    &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;false&quot;/&gt;
 135  
  *    &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
 136  
  *    value=&quot;false&quot;/&gt;
 137  
  *    &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;false&quot;
 138  
  *    /&gt;
 139  
  *    &lt;property name=&quot;tokens&quot; value=&quot;VARIABLE_DEF, PARAMETER_DEF&quot;/&gt;
 140  
  * &lt;/module&gt;
 141  
  * </pre>
 142  
  *
 143  
  * <p>Code example
 144  
  * {@code
 145  
  * ...
 146  
  * public void test(&#64;MyAnnotation String s) { // OK
 147  
  *   ...
 148  
  *   for (&#64;MyAnnotation char c : s.toCharArray()) { ... }  // OK
 149  
  *   ...
 150  
  *   try { ... }
 151  
  *   catch (&#64;MyAnnotation Exception ex) { ... } // OK
 152  
  *   ...
 153  
  *   for (&#64;MyAnnotation int i = 0; i &lt; 10; i++) { ... } // OK
 154  
  *   ...
 155  
  *   MathOperation c = (&#64;MyAnnotation int a, &#64;MyAnnotation int b) -&gt; a + b; // OK
 156  
  *   ...
 157  
  * }
 158  
  * }
 159  
  *
 160  
  * @author maxvetrenko
 161  
  */
 162  
 @StatelessCheck
 163  26
 public class AnnotationLocationCheck extends AbstractCheck {
 164  
     /**
 165  
      * A key is pointing to the warning message text in "messages.properties"
 166  
      * file.
 167  
      */
 168  
     public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone";
 169  
 
 170  
     /**
 171  
      * A key is pointing to the warning message text in "messages.properties"
 172  
      * file.
 173  
      */
 174  
     public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location";
 175  
 
 176  
     /** Array of single line annotation parents. */
 177  2
     private static final int[] SINGLELINE_ANNOTATION_PARENTS = {TokenTypes.FOR_EACH_CLAUSE,
 178  
                                                                 TokenTypes.PARAMETER_DEF,
 179  
                                                                 TokenTypes.FOR_INIT, };
 180  
 
 181  
     /**
 182  
      * If true, it allows single prameterless annotation to be located on the same line as
 183  
      * target element.
 184  
      */
 185  26
     private boolean allowSamelineSingleParameterlessAnnotation = true;
 186  
 
 187  
     /**
 188  
      * If true, it allows parameterized annotation to be located on the same line as
 189  
      * target element.
 190  
      */
 191  
     private boolean allowSamelineParameterizedAnnotation;
 192  
 
 193  
     /**
 194  
      * If true, it allows annotation to be located on the same line as
 195  
      * target element.
 196  
      */
 197  
     private boolean allowSamelineMultipleAnnotations;
 198  
 
 199  
     /**
 200  
      * Sets if allow same line single parameterless annotation.
 201  
      * @param allow User's value of allowSamelineSingleParameterlessAnnotation.
 202  
      */
 203  
     public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) {
 204  7
         allowSamelineSingleParameterlessAnnotation = allow;
 205  7
     }
 206  
 
 207  
     /**
 208  
      * Sets if allow parameterized annotation to be located on the same line as
 209  
      * target element.
 210  
      * @param allow User's value of allowSamelineParameterizedAnnotation.
 211  
      */
 212  
     public final void setAllowSamelineParameterizedAnnotation(boolean allow) {
 213  6
         allowSamelineParameterizedAnnotation = allow;
 214  6
     }
 215  
 
 216  
     /**
 217  
      * Sets if allow annotation to be located on the same line as
 218  
      * target element.
 219  
      * @param allow User's value of allowSamelineMultipleAnnotations.
 220  
      */
 221  
     public final void setAllowSamelineMultipleAnnotations(boolean allow) {
 222  7
         allowSamelineMultipleAnnotations = allow;
 223  7
     }
 224  
 
 225  
     @Override
 226  
     public int[] getDefaultTokens() {
 227  13
         return new int[] {
 228  
             TokenTypes.CLASS_DEF,
 229  
             TokenTypes.INTERFACE_DEF,
 230  
             TokenTypes.ENUM_DEF,
 231  
             TokenTypes.METHOD_DEF,
 232  
             TokenTypes.CTOR_DEF,
 233  
             TokenTypes.VARIABLE_DEF,
 234  
         };
 235  
     }
 236  
 
 237  
     @Override
 238  
     public int[] getAcceptableTokens() {
 239  12
         return new int[] {
 240  
             TokenTypes.CLASS_DEF,
 241  
             TokenTypes.INTERFACE_DEF,
 242  
             TokenTypes.ENUM_DEF,
 243  
             TokenTypes.METHOD_DEF,
 244  
             TokenTypes.CTOR_DEF,
 245  
             TokenTypes.VARIABLE_DEF,
 246  
             TokenTypes.PARAMETER_DEF,
 247  
             TokenTypes.ANNOTATION_DEF,
 248  
             TokenTypes.TYPECAST,
 249  
             TokenTypes.LITERAL_THROWS,
 250  
             TokenTypes.IMPLEMENTS_CLAUSE,
 251  
             TokenTypes.TYPE_ARGUMENT,
 252  
             TokenTypes.LITERAL_NEW,
 253  
             TokenTypes.DOT,
 254  
             TokenTypes.ANNOTATION_FIELD_DEF,
 255  
         };
 256  
     }
 257  
 
 258  
     @Override
 259  
     public int[] getRequiredTokens() {
 260  27
         return CommonUtils.EMPTY_INT_ARRAY;
 261  
     }
 262  
 
 263  
     @Override
 264  
     public void visitToken(DetailAST ast) {
 265  140
         final DetailAST modifiersNode = ast.findFirstToken(TokenTypes.MODIFIERS);
 266  
 
 267  140
         if (hasAnnotations(modifiersNode)) {
 268  90
             checkAnnotations(modifiersNode, getExpectedAnnotationIndentation(modifiersNode));
 269  
         }
 270  140
     }
 271  
 
 272  
     /**
 273  
      * Checks whether a given modifier node has an annotation.
 274  
      * @param modifierNode modifier node.
 275  
      * @return true if the given modifier node has the annotation.
 276  
      */
 277  
     private static boolean hasAnnotations(DetailAST modifierNode) {
 278  280
         return modifierNode != null
 279  115
             && modifierNode.findFirstToken(TokenTypes.ANNOTATION) != null;
 280  
     }
 281  
 
 282  
     /**
 283  
      * Returns an expected annotation indentation.
 284  
      * The expected indentation should be the same as the indentation of the node
 285  
      * which is the parent of the target modifier node.
 286  
      * @param modifierNode modifier node.
 287  
      * @return the annotation indentation.
 288  
      */
 289  
     private static int getExpectedAnnotationIndentation(DetailAST modifierNode) {
 290  90
         return modifierNode.getParent().getColumnNo();
 291  
     }
 292  
 
 293  
     /**
 294  
      * Checks annotations positions in code:
 295  
      * 1) Checks whether the annotations locations are correct.
 296  
      * 2) Checks whether the annotations have the valid indentation level.
 297  
      * @param modifierNode modifiers node.
 298  
      * @param correctIndentation correct indentation of the annotation.
 299  
      */
 300  
     private void checkAnnotations(DetailAST modifierNode, int correctIndentation) {
 301  90
         DetailAST annotation = modifierNode.getFirstChild();
 302  
 
 303  242
         while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) {
 304  152
             final boolean hasParameters = isParameterized(annotation);
 305  
 
 306  152
             if (!isCorrectLocation(annotation, hasParameters)) {
 307  18
                 log(annotation.getLineNo(),
 308  9
                         MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation));
 309  
             }
 310  143
             else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) {
 311  52
                 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_LOCATION,
 312  26
                     getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation);
 313  
             }
 314  152
             annotation = annotation.getNextSibling();
 315  152
         }
 316  90
     }
 317  
 
 318  
     /**
 319  
      * Checks whether an annotation has parameters.
 320  
      * @param annotation annotation node.
 321  
      * @return true if the annotation has parameters.
 322  
      */
 323  
     private static boolean isParameterized(DetailAST annotation) {
 324  152
         return annotation.findFirstToken(TokenTypes.EXPR) != null;
 325  
     }
 326  
 
 327  
     /**
 328  
      * Returns the name of the given annotation.
 329  
      * @param annotation annotation node.
 330  
      * @return annotation name.
 331  
      */
 332  
     private static String getAnnotationName(DetailAST annotation) {
 333  35
         DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT);
 334  35
         if (identNode == null) {
 335  1
             identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT);
 336  
         }
 337  35
         return identNode.getText();
 338  
     }
 339  
 
 340  
     /**
 341  
      * Checks whether an annotation has a correct location.
 342  
      * Annotation location is considered correct
 343  
      * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true.
 344  
      * The method also:
 345  
      * 1) checks parameterized annotation location considering
 346  
      * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation};
 347  
      * 2) checks parameterless annotation location considering
 348  
      * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation};
 349  
      * 3) checks annotation location considering the elements
 350  
      * of {@link AnnotationLocationCheck#SINGLELINE_ANNOTATION_PARENTS};
 351  
      * @param annotation annotation node.
 352  
      * @param hasParams whether an annotation has parameters.
 353  
      * @return true if the annotation has a correct location.
 354  
      */
 355  
     private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) {
 356  
         final boolean allowingCondition;
 357  
 
 358  152
         if (hasParams) {
 359  4
             allowingCondition = allowSamelineParameterizedAnnotation;
 360  
         }
 361  
         else {
 362  148
             allowingCondition = allowSamelineSingleParameterlessAnnotation;
 363  
         }
 364  304
         return allowSamelineMultipleAnnotations
 365  87
             || allowingCondition && !hasNodeBefore(annotation)
 366  20
             || !allowingCondition && (!hasNodeBeside(annotation)
 367  13
             || isAllowedPosition(annotation, SINGLELINE_ANNOTATION_PARENTS));
 368  
     }
 369  
 
 370  
     /**
 371  
      * Checks whether an annotation node has any node before on the same line.
 372  
      * @param annotation annotation node.
 373  
      * @return true if an annotation node has any node before on the same line.
 374  
      */
 375  
     private static boolean hasNodeBefore(DetailAST annotation) {
 376  138
         final int annotationLineNo = annotation.getLineNo();
 377  138
         final DetailAST previousNode = annotation.getPreviousSibling();
 378  
 
 379  138
         return previousNode != null && annotationLineNo == previousNode.getLineNo();
 380  
     }
 381  
 
 382  
     /**
 383  
      * Checks whether an annotation node has any node before or after on the same line.
 384  
      * @param annotation annotation node.
 385  
      * @return true if an annotation node has any node before or after on the same line.
 386  
      */
 387  
     private static boolean hasNodeBeside(DetailAST annotation) {
 388  20
         return hasNodeBefore(annotation) || hasNodeAfter(annotation);
 389  
     }
 390  
 
 391  
     /**
 392  
      * Checks whether an annotation node has any node after on the same line.
 393  
      * @param annotation annotation node.
 394  
      * @return true if an annotation node has any node after on the same line.
 395  
      */
 396  
     private static boolean hasNodeAfter(DetailAST annotation) {
 397  18
         final int annotationLineNo = annotation.getLineNo();
 398  18
         DetailAST nextNode = annotation.getNextSibling();
 399  
 
 400  18
         if (nextNode == null) {
 401  12
             nextNode = annotation.getParent().getNextSibling();
 402  
         }
 403  
 
 404  18
         return annotationLineNo == nextNode.getLineNo();
 405  
     }
 406  
 
 407  
     /**
 408  
      * Checks whether position of annotation is allowed.
 409  
      * @param annotation annotation token.
 410  
      * @param allowedPositions an array of allowed annotation positions.
 411  
      * @return true if position of annotation is allowed.
 412  
      */
 413  
     private static boolean isAllowedPosition(DetailAST annotation, int... allowedPositions) {
 414  13
         boolean allowed = false;
 415  34
         for (int position : allowedPositions) {
 416  30
             if (isInSpecificCodeBlock(annotation, position)) {
 417  9
                 allowed = true;
 418  9
                 break;
 419  
             }
 420  
         }
 421  13
         return allowed;
 422  
     }
 423  
 
 424  
     /**
 425  
      * Checks whether the scope of a node is restricted to a specific code block.
 426  
      * @param node node.
 427  
      * @param blockType block type.
 428  
      * @return true if the scope of a node is restricted to a specific code block.
 429  
      */
 430  
     private static boolean isInSpecificCodeBlock(DetailAST node, int blockType) {
 431  30
         boolean returnValue = false;
 432  157
         for (DetailAST token = node.getParent(); token != null; token = token.getParent()) {
 433  136
             final int type = token.getType();
 434  136
             if (type == blockType) {
 435  9
                 returnValue = true;
 436  9
                 break;
 437  
             }
 438  
         }
 439  30
         return returnValue;
 440  
     }
 441  
 }