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