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