001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 * <div> 031 * Checks location of annotation on language elements. 032 * By default, Check enforce to locate annotations before target element, 033 * annotation should be located on separate line from target element. 034 * This check also verifies that the annotations are on the same indenting level 035 * as the annotated element if they are not on the same line. 036 * </div> 037 * 038 * <p> 039 * Attention: Elements that cannot have JavaDoc comments like local variables are not in the 040 * scope of this check even though a token type like {@code VARIABLE_DEF} would match them. 041 * </p> 042 * 043 * <p> 044 * Attention: Annotations among modifiers are ignored (looks like false-negative) 045 * as there might be a problem with annotations for return types: 046 * </p> 047 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 048 * public @Nullable Long getStartTimeOrNull() { ... } 049 * </code></pre></div> 050 * 051 * <p> 052 * Such annotations are better to keep close to type. 053 * Due to limitations, Checkstyle can not examine the target of an annotation. 054 * </p> 055 * 056 * <p> 057 * Example: 058 * </p> 059 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 060 * @Override 061 * @Nullable 062 * public String getNameIfPresent() { ... } 063 * </code></pre></div> 064 * 065 * <p> 066 * Notes: 067 * This check does <strong>not</strong> enforce annotations to be placed 068 * immediately after the documentation block. If that behavior is desired, consider also using 069 * <a href="https://checkstyle.org/checks/javadoc/invalidjavadocposition.html#InvalidJavadocPosition"> 070 * InvalidJavadocPosition</a>. 071 * </p> 072 * 073 * <ul> 074 * <li> 075 * Property {@code allowSamelineMultipleAnnotations} - Allow annotation(s) to be located on 076 * the same line as target element. 077 * Type is {@code boolean}. 078 * Default value is {@code false}. 079 * </li> 080 * <li> 081 * Property {@code allowSamelineParameterizedAnnotation} - Allow one and only parameterized 082 * annotation to be located on the same line as target element. 083 * Type is {@code boolean}. 084 * Default value is {@code false}. 085 * </li> 086 * <li> 087 * Property {@code allowSamelineSingleParameterlessAnnotation} - Allow single parameterless 088 * annotation to be located on the same line as target element. 089 * Type is {@code boolean}. 090 * Default value is {@code true}. 091 * </li> 092 * <li> 093 * Property {@code tokens} - tokens to check 094 * Type is {@code java.lang.String[]}. 095 * Validation type is {@code tokenSet}. 096 * Default value is: 097 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 098 * CLASS_DEF</a>, 099 * <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 139public 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}