1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2025 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 * <div> 31 * Checks location of annotation on language elements. 32 * By default, Check enforce to locate annotations before target element, 33 * annotation should be located on separate line from target element. 34 * This check also verifies that the annotations are on the same indenting level 35 * as the annotated element if they are not on the same line. 36 * </div> 37 * 38 * <p> 39 * Attention: Elements that cannot have JavaDoc comments like local variables are not in the 40 * scope of this check even though a token type like {@code VARIABLE_DEF} would match them. 41 * </p> 42 * 43 * <p> 44 * Attention: Annotations among modifiers are ignored (looks like false-negative) 45 * as there might be a problem with annotations for return types: 46 * </p> 47 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 48 * public @Nullable Long getStartTimeOrNull() { ... } 49 * </code></pre></div> 50 * 51 * <p> 52 * Such annotations are better to keep close to type. 53 * Due to limitations, Checkstyle can not examine the target of an annotation. 54 * </p> 55 * 56 * <p> 57 * Example: 58 * </p> 59 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 60 * @Override 61 * @Nullable 62 * public String getNameIfPresent() { ... } 63 * </code></pre></div> 64 * 65 * <p> 66 * Notes: 67 * This check does <strong>not</strong> enforce annotations to be placed 68 * immediately after the documentation block. If that behavior is desired, consider also using 69 * <a href="https://checkstyle.org/checks/javadoc/invalidjavadocposition.html#InvalidJavadocPosition"> 70 * InvalidJavadocPosition</a>. 71 * </p> 72 * 73 * <ul> 74 * <li> 75 * Property {@code allowSamelineMultipleAnnotations} - Allow annotation(s) to be located on 76 * the same line as target element. 77 * Type is {@code boolean}. 78 * Default value is {@code false}. 79 * </li> 80 * <li> 81 * Property {@code allowSamelineParameterizedAnnotation} - Allow one and only parameterized 82 * annotation to be located on the same line as target element. 83 * Type is {@code boolean}. 84 * Default value is {@code false}. 85 * </li> 86 * <li> 87 * Property {@code allowSamelineSingleParameterlessAnnotation} - Allow single parameterless 88 * annotation to be located on the same line as target element. 89 * Type is {@code boolean}. 90 * Default value is {@code true}. 91 * </li> 92 * <li> 93 * Property {@code tokens} - tokens to check 94 * Type is {@code java.lang.String[]}. 95 * Validation type is {@code tokenSet}. 96 * Default value is: 97 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 98 * CLASS_DEF</a>, 99 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 100 * INTERFACE_DEF</a>, 101 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PACKAGE_DEF"> 102 * PACKAGE_DEF</a>, 103 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF"> 104 * ENUM_CONSTANT_DEF</a>, 105 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 106 * ENUM_DEF</a>, 107 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 108 * METHOD_DEF</a>, 109 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 110 * CTOR_DEF</a>, 111 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 112 * VARIABLE_DEF</a>, 113 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF"> 114 * RECORD_DEF</a>, 115 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF"> 116 * COMPACT_CTOR_DEF</a>. 117 * </li> 118 * </ul> 119 * 120 * <p> 121 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 122 * </p> 123 * 124 * <p> 125 * Violation Message Keys: 126 * </p> 127 * <ul> 128 * <li> 129 * {@code annotation.location} 130 * </li> 131 * <li> 132 * {@code annotation.location.alone} 133 * </li> 134 * </ul> 135 * 136 * @since 6.0 137 */ 138 @StatelessCheck 139 public class AnnotationLocationCheck extends AbstractCheck { 140 141 /** 142 * A key is pointing to the warning message text in "messages.properties" 143 * file. 144 */ 145 public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone"; 146 147 /** 148 * A key is pointing to the warning message text in "messages.properties" 149 * file. 150 */ 151 public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location"; 152 153 /** 154 * Allow single parameterless annotation to be located on the same line as 155 * target element. 156 */ 157 private boolean allowSamelineSingleParameterlessAnnotation = true; 158 159 /** 160 * Allow one and only parameterized annotation to be located on the same line as 161 * target element. 162 */ 163 private boolean allowSamelineParameterizedAnnotation; 164 165 /** 166 * Allow annotation(s) to be located on the same line as 167 * target element. 168 */ 169 private boolean allowSamelineMultipleAnnotations; 170 171 /** 172 * Setter to allow single parameterless annotation to be located on the same line as 173 * target element. 174 * 175 * @param allow User's value of allowSamelineSingleParameterlessAnnotation. 176 * @since 6.1 177 */ 178 public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) { 179 allowSamelineSingleParameterlessAnnotation = allow; 180 } 181 182 /** 183 * Setter to allow one and only parameterized annotation to be located on the same line as 184 * target element. 185 * 186 * @param allow User's value of allowSamelineParameterizedAnnotation. 187 * @since 6.4 188 */ 189 public final void setAllowSamelineParameterizedAnnotation(boolean allow) { 190 allowSamelineParameterizedAnnotation = allow; 191 } 192 193 /** 194 * Setter to allow annotation(s) to be located on the same line as 195 * target element. 196 * 197 * @param allow User's value of allowSamelineMultipleAnnotations. 198 * @since 6.0 199 */ 200 public final void setAllowSamelineMultipleAnnotations(boolean allow) { 201 allowSamelineMultipleAnnotations = allow; 202 } 203 204 @Override 205 public int[] getDefaultTokens() { 206 return new int[] { 207 TokenTypes.CLASS_DEF, 208 TokenTypes.INTERFACE_DEF, 209 TokenTypes.PACKAGE_DEF, 210 TokenTypes.ENUM_CONSTANT_DEF, 211 TokenTypes.ENUM_DEF, 212 TokenTypes.METHOD_DEF, 213 TokenTypes.CTOR_DEF, 214 TokenTypes.VARIABLE_DEF, 215 TokenTypes.RECORD_DEF, 216 TokenTypes.COMPACT_CTOR_DEF, 217 }; 218 } 219 220 @Override 221 public int[] getAcceptableTokens() { 222 return new int[] { 223 TokenTypes.CLASS_DEF, 224 TokenTypes.INTERFACE_DEF, 225 TokenTypes.PACKAGE_DEF, 226 TokenTypes.ENUM_CONSTANT_DEF, 227 TokenTypes.ENUM_DEF, 228 TokenTypes.METHOD_DEF, 229 TokenTypes.CTOR_DEF, 230 TokenTypes.VARIABLE_DEF, 231 TokenTypes.ANNOTATION_DEF, 232 TokenTypes.ANNOTATION_FIELD_DEF, 233 TokenTypes.RECORD_DEF, 234 TokenTypes.COMPACT_CTOR_DEF, 235 }; 236 } 237 238 @Override 239 public int[] getRequiredTokens() { 240 return CommonUtil.EMPTY_INT_ARRAY; 241 } 242 243 @Override 244 public void visitToken(DetailAST ast) { 245 // ignore variable def tokens that are not field definitions 246 if (ast.getType() != TokenTypes.VARIABLE_DEF 247 || ast.getParent().getType() == TokenTypes.OBJBLOCK) { 248 DetailAST node = ast.findFirstToken(TokenTypes.MODIFIERS); 249 if (node == null) { 250 node = ast.findFirstToken(TokenTypes.ANNOTATIONS); 251 } 252 checkAnnotations(node, getExpectedAnnotationIndentation(node)); 253 } 254 } 255 256 /** 257 * Returns an expected annotation indentation. 258 * The expected indentation should be the same as the indentation of the target node. 259 * 260 * @param node modifiers or annotations node. 261 * @return the annotation indentation. 262 */ 263 private static int getExpectedAnnotationIndentation(DetailAST node) { 264 return node.getColumnNo(); 265 } 266 267 /** 268 * Checks annotations positions in code: 269 * 1) Checks whether the annotations locations are correct. 270 * 2) Checks whether the annotations have the valid indentation level. 271 * 272 * @param modifierNode modifiers node. 273 * @param correctIndentation correct indentation of the annotation. 274 */ 275 private void checkAnnotations(DetailAST modifierNode, int correctIndentation) { 276 DetailAST annotation = modifierNode.getFirstChild(); 277 278 while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) { 279 final boolean hasParameters = isParameterized(annotation); 280 281 if (!isCorrectLocation(annotation, hasParameters)) { 282 log(annotation, 283 MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation)); 284 } 285 else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) { 286 log(annotation, MSG_KEY_ANNOTATION_LOCATION, 287 getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation); 288 } 289 annotation = annotation.getNextSibling(); 290 } 291 } 292 293 /** 294 * Checks whether an annotation has parameters. 295 * 296 * @param annotation annotation node. 297 * @return true if the annotation has parameters. 298 */ 299 private static boolean isParameterized(DetailAST annotation) { 300 return TokenUtil.findFirstTokenByPredicate(annotation, ast -> { 301 return ast.getType() == TokenTypes.EXPR 302 || ast.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR; 303 }).isPresent(); 304 } 305 306 /** 307 * Returns the name of the given annotation. 308 * 309 * @param annotation annotation node. 310 * @return annotation name. 311 */ 312 private static String getAnnotationName(DetailAST annotation) { 313 DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT); 314 if (identNode == null) { 315 identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT); 316 } 317 return identNode.getText(); 318 } 319 320 /** 321 * Checks whether an annotation has a correct location. 322 * Annotation location is considered correct 323 * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true. 324 * The method also: 325 * 1) checks parameterized annotation location considering 326 * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation}; 327 * 2) checks parameterless annotation location considering 328 * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation}; 329 * 3) checks annotation location; 330 * 331 * @param annotation annotation node. 332 * @param hasParams whether an annotation has parameters. 333 * @return true if the annotation has a correct location. 334 */ 335 private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) { 336 final boolean allowingCondition; 337 338 if (hasParams) { 339 allowingCondition = allowSamelineParameterizedAnnotation; 340 } 341 else { 342 allowingCondition = allowSamelineSingleParameterlessAnnotation; 343 } 344 return allowSamelineMultipleAnnotations 345 || allowingCondition && !hasNodeBefore(annotation) 346 || !hasNodeBeside(annotation); 347 } 348 349 /** 350 * Checks whether an annotation node has any node before on the same line. 351 * 352 * @param annotation annotation node. 353 * @return true if an annotation node has any node before on the same line. 354 */ 355 private static boolean hasNodeBefore(DetailAST annotation) { 356 final int annotationLineNo = annotation.getLineNo(); 357 final DetailAST previousNode = annotation.getPreviousSibling(); 358 359 return previousNode != null && annotationLineNo == previousNode.getLineNo(); 360 } 361 362 /** 363 * Checks whether an annotation node has any node before or after on the same line. 364 * 365 * @param annotation annotation node. 366 * @return true if an annotation node has any node before or after on the same line. 367 */ 368 private static boolean hasNodeBeside(DetailAST annotation) { 369 return hasNodeBefore(annotation) || hasNodeAfter(annotation); 370 } 371 372 /** 373 * Checks whether an annotation node has any node after on the same line. 374 * 375 * @param annotation annotation node. 376 * @return true if an annotation node has any node after on the same line. 377 */ 378 private static boolean hasNodeAfter(DetailAST annotation) { 379 final int annotationLineNo = annotation.getLineNo(); 380 DetailAST nextNode = annotation.getNextSibling(); 381 382 if (nextNode == null) { 383 nextNode = annotation.getParent().getNextSibling(); 384 } 385 386 return annotationLineNo == nextNode.getLineNo(); 387 } 388 389 }