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 * @Override 057 * @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 * <module name="AnnotationLocation"/> 106 * </pre> 107 * <p> 108 * Example for above configuration: 109 * </p> 110 * <pre> 111 * @NotNull private boolean field1; //ok 112 * @Override public int hashCode() { return 1; } //ok 113 * @NotNull //ok 114 * private boolean field2; 115 * @Override //ok 116 * public boolean equals(Object obj) { return true; } 117 * @Mock DataLoader loader; //ok 118 * @SuppressWarnings("deprecation") DataLoader loader; //violation 119 * @SuppressWarnings("deprecation") public int foo() { return 1; } //violation 120 * @NotNull @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 * <module name="AnnotationLocation"> 127 * <property name="allowSamelineMultipleAnnotations" value="true"/> 128 * <property name="allowSamelineSingleParameterlessAnnotation" 129 * value="false"/> 130 * <property name="allowSamelineParameterizedAnnotation" value="false"/> 131 * </module> 132 * </pre> 133 * <p> 134 * Example to allow any location multiple annotations: 135 * </p> 136 * <pre> 137 * @NotNull private boolean field1; //ok 138 * @Override public int hashCode() { return 1; } //ok 139 * @NotNull //ok 140 * private boolean field2; 141 * @Override //ok 142 * public boolean equals(Object obj) { return true; } 143 * @Mock DataLoader loader; //ok 144 * @SuppressWarnings("deprecation") DataLoader loader; //ok 145 * @SuppressWarnings("deprecation") public int foo() { return 1; } //ok 146 * @NotNull @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 * <module name="AnnotationLocation"> 154 * <property name="allowSamelineMultipleAnnotations" value="false"/> 155 * <property name="allowSamelineSingleParameterlessAnnotation" 156 * value="false"/> 157 * <property name="allowSamelineParameterizedAnnotation" value="true"/> 158 * </module> 159 * </pre> 160 * <p> 161 * Example to allow only one and only parameterized annotation on the same line: 162 * </p> 163 * <pre> 164 * @NotNull private boolean field1; //violation 165 * @Override public int hashCode() { return 1; } //violation 166 * @NotNull //ok 167 * private boolean field2; 168 * @Override //ok 169 * public boolean equals(Object obj) { return true; } 170 * @Mock DataLoader loader; //violation 171 * @SuppressWarnings("deprecation") DataLoader loader; //ok 172 * @SuppressWarnings("deprecation") public int foo() { return 1; } //ok 173 * @NotNull @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 * <module name="AnnotationLocation"> 181 * <property name="tokens" value="METHOD_DEF"/> 182 * <property name="allowSamelineMultipleAnnotations" value="false"/> 183 * <property name="allowSamelineSingleParameterlessAnnotation" 184 * value="true"/> 185 * <property name="allowSamelineParameterizedAnnotation" value="false"/> 186 * </module> 187 * </pre> 188 * <p> 189 * Example for above configuration to check only methods: 190 * </p> 191 * <pre> 192 * @NotNull private boolean field1; //ok 193 * @Override public int hashCode() { return 1; } //ok 194 * @NotNull //ok 195 * private boolean field2; 196 * @Override //ok 197 * public boolean equals(Object obj) { return true; } 198 * @Mock DataLoader loader; //ok 199 * @SuppressWarnings("deprecation") DataLoader loader; //ok 200 * @SuppressWarnings("deprecation") public int foo() { return 1; } //violation 201 * @NotNull @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}