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