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 * Note that this check does <strong>not</strong> enforce annotations to be placed 037 * immediately after Javadoc comments. 038 * If that behavior is desired, consider also using 039 * {@code InvalidJavadocPosition}. 040 * </div> 041 * 042 * <p> 043 * Attention: Elements that cannot have JavaDoc comments like local variables are not in the 044 * scope of this check even though a token type like {@code VARIABLE_DEF} would match them. 045 * </p> 046 * 047 * <p> 048 * Attention: Annotations among modifiers are ignored (looks like false-negative) 049 * as there might be a problem with annotations for return types: 050 * </p> 051 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 052 * public @Nullable Long getStartTimeOrNull() { ... } 053 * </code></pre></div> 054 * 055 * <p> 056 * Such annotations are better to keep close to type. 057 * Due to limitations, Checkstyle can not examine the target of an annotation. 058 * </p> 059 * 060 * <p> 061 * Example: 062 * </p> 063 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 064 * @Override 065 * @Nullable 066 * public String getNameIfPresent() { ... } 067 * </code></pre></div> 068 * <ul> 069 * <li> 070 * Property {@code allowSamelineMultipleAnnotations} - Allow annotation(s) to be located on 071 * the same line as target element. 072 * Type is {@code boolean}. 073 * Default value is {@code false}. 074 * </li> 075 * <li> 076 * Property {@code allowSamelineParameterizedAnnotation} - Allow one and only parameterized 077 * annotation to be located on the same line as target element. 078 * Type is {@code boolean}. 079 * Default value is {@code false}. 080 * </li> 081 * <li> 082 * Property {@code allowSamelineSingleParameterlessAnnotation} - Allow single parameterless 083 * annotation to be located on the same line as target element. 084 * Type is {@code boolean}. 085 * Default value is {@code true}. 086 * </li> 087 * <li> 088 * Property {@code tokens} - tokens to check 089 * Type is {@code java.lang.String[]}. 090 * Validation type is {@code tokenSet}. 091 * Default value is: 092 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 093 * CLASS_DEF</a>, 094 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 095 * INTERFACE_DEF</a>, 096 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PACKAGE_DEF"> 097 * PACKAGE_DEF</a>, 098 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF"> 099 * ENUM_CONSTANT_DEF</a>, 100 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 101 * ENUM_DEF</a>, 102 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 103 * METHOD_DEF</a>, 104 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 105 * CTOR_DEF</a>, 106 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 107 * VARIABLE_DEF</a>, 108 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF"> 109 * RECORD_DEF</a>, 110 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF"> 111 * COMPACT_CTOR_DEF</a>. 112 * </li> 113 * </ul> 114 * 115 * <p> 116 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 117 * </p> 118 * 119 * <p> 120 * Violation Message Keys: 121 * </p> 122 * <ul> 123 * <li> 124 * {@code annotation.location} 125 * </li> 126 * <li> 127 * {@code annotation.location.alone} 128 * </li> 129 * </ul> 130 * 131 * @since 6.0 132 */ 133@StatelessCheck 134public class AnnotationLocationCheck extends AbstractCheck { 135 136 /** 137 * A key is pointing to the warning message text in "messages.properties" 138 * file. 139 */ 140 public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone"; 141 142 /** 143 * A key is pointing to the warning message text in "messages.properties" 144 * file. 145 */ 146 public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location"; 147 148 /** 149 * Allow single parameterless annotation to be located on the same line as 150 * target element. 151 */ 152 private boolean allowSamelineSingleParameterlessAnnotation = true; 153 154 /** 155 * Allow one and only parameterized annotation to be located on the same line as 156 * target element. 157 */ 158 private boolean allowSamelineParameterizedAnnotation; 159 160 /** 161 * Allow annotation(s) to be located on the same line as 162 * target element. 163 */ 164 private boolean allowSamelineMultipleAnnotations; 165 166 /** 167 * Setter to allow single parameterless annotation to be located on the same line as 168 * target element. 169 * 170 * @param allow User's value of allowSamelineSingleParameterlessAnnotation. 171 * @since 6.1 172 */ 173 public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) { 174 allowSamelineSingleParameterlessAnnotation = allow; 175 } 176 177 /** 178 * Setter to allow one and only parameterized annotation to be located on the same line as 179 * target element. 180 * 181 * @param allow User's value of allowSamelineParameterizedAnnotation. 182 * @since 6.4 183 */ 184 public final void setAllowSamelineParameterizedAnnotation(boolean allow) { 185 allowSamelineParameterizedAnnotation = allow; 186 } 187 188 /** 189 * Setter to allow annotation(s) to be located on the same line as 190 * target element. 191 * 192 * @param allow User's value of allowSamelineMultipleAnnotations. 193 * @since 6.0 194 */ 195 public final void setAllowSamelineMultipleAnnotations(boolean allow) { 196 allowSamelineMultipleAnnotations = allow; 197 } 198 199 @Override 200 public int[] getDefaultTokens() { 201 return new int[] { 202 TokenTypes.CLASS_DEF, 203 TokenTypes.INTERFACE_DEF, 204 TokenTypes.PACKAGE_DEF, 205 TokenTypes.ENUM_CONSTANT_DEF, 206 TokenTypes.ENUM_DEF, 207 TokenTypes.METHOD_DEF, 208 TokenTypes.CTOR_DEF, 209 TokenTypes.VARIABLE_DEF, 210 TokenTypes.RECORD_DEF, 211 TokenTypes.COMPACT_CTOR_DEF, 212 }; 213 } 214 215 @Override 216 public int[] getAcceptableTokens() { 217 return new int[] { 218 TokenTypes.CLASS_DEF, 219 TokenTypes.INTERFACE_DEF, 220 TokenTypes.PACKAGE_DEF, 221 TokenTypes.ENUM_CONSTANT_DEF, 222 TokenTypes.ENUM_DEF, 223 TokenTypes.METHOD_DEF, 224 TokenTypes.CTOR_DEF, 225 TokenTypes.VARIABLE_DEF, 226 TokenTypes.ANNOTATION_DEF, 227 TokenTypes.ANNOTATION_FIELD_DEF, 228 TokenTypes.RECORD_DEF, 229 TokenTypes.COMPACT_CTOR_DEF, 230 }; 231 } 232 233 @Override 234 public int[] getRequiredTokens() { 235 return CommonUtil.EMPTY_INT_ARRAY; 236 } 237 238 @Override 239 public void visitToken(DetailAST ast) { 240 // ignore variable def tokens that are not field definitions 241 if (ast.getType() != TokenTypes.VARIABLE_DEF 242 || ast.getParent().getType() == TokenTypes.OBJBLOCK) { 243 DetailAST node = ast.findFirstToken(TokenTypes.MODIFIERS); 244 if (node == null) { 245 node = ast.findFirstToken(TokenTypes.ANNOTATIONS); 246 } 247 checkAnnotations(node, getExpectedAnnotationIndentation(node)); 248 } 249 } 250 251 /** 252 * Returns an expected annotation indentation. 253 * The expected indentation should be the same as the indentation of the target node. 254 * 255 * @param node modifiers or annotations node. 256 * @return the annotation indentation. 257 */ 258 private static int getExpectedAnnotationIndentation(DetailAST node) { 259 return node.getColumnNo(); 260 } 261 262 /** 263 * Checks annotations positions in code: 264 * 1) Checks whether the annotations locations are correct. 265 * 2) Checks whether the annotations have the valid indentation level. 266 * 267 * @param modifierNode modifiers node. 268 * @param correctIndentation correct indentation of the annotation. 269 */ 270 private void checkAnnotations(DetailAST modifierNode, int correctIndentation) { 271 DetailAST annotation = modifierNode.getFirstChild(); 272 273 while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) { 274 final boolean hasParameters = isParameterized(annotation); 275 276 if (!isCorrectLocation(annotation, hasParameters)) { 277 log(annotation, 278 MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation)); 279 } 280 else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) { 281 log(annotation, MSG_KEY_ANNOTATION_LOCATION, 282 getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation); 283 } 284 annotation = annotation.getNextSibling(); 285 } 286 } 287 288 /** 289 * Checks whether an annotation has parameters. 290 * 291 * @param annotation annotation node. 292 * @return true if the annotation has parameters. 293 */ 294 private static boolean isParameterized(DetailAST annotation) { 295 return TokenUtil.findFirstTokenByPredicate(annotation, ast -> { 296 return ast.getType() == TokenTypes.EXPR 297 || ast.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR; 298 }).isPresent(); 299 } 300 301 /** 302 * Returns the name of the given annotation. 303 * 304 * @param annotation annotation node. 305 * @return annotation name. 306 */ 307 private static String getAnnotationName(DetailAST annotation) { 308 DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT); 309 if (identNode == null) { 310 identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT); 311 } 312 return identNode.getText(); 313 } 314 315 /** 316 * Checks whether an annotation has a correct location. 317 * Annotation location is considered correct 318 * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true. 319 * The method also: 320 * 1) checks parameterized annotation location considering 321 * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation}; 322 * 2) checks parameterless annotation location considering 323 * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation}; 324 * 3) checks annotation location; 325 * 326 * @param annotation annotation node. 327 * @param hasParams whether an annotation has parameters. 328 * @return true if the annotation has a correct location. 329 */ 330 private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) { 331 final boolean allowingCondition; 332 333 if (hasParams) { 334 allowingCondition = allowSamelineParameterizedAnnotation; 335 } 336 else { 337 allowingCondition = allowSamelineSingleParameterlessAnnotation; 338 } 339 return allowSamelineMultipleAnnotations 340 || allowingCondition && !hasNodeBefore(annotation) 341 || !hasNodeBeside(annotation); 342 } 343 344 /** 345 * Checks whether an annotation node has any node before on the same line. 346 * 347 * @param annotation annotation node. 348 * @return true if an annotation node has any node before on the same line. 349 */ 350 private static boolean hasNodeBefore(DetailAST annotation) { 351 final int annotationLineNo = annotation.getLineNo(); 352 final DetailAST previousNode = annotation.getPreviousSibling(); 353 354 return previousNode != null && annotationLineNo == previousNode.getLineNo(); 355 } 356 357 /** 358 * Checks whether an annotation node has any node before or after on the same line. 359 * 360 * @param annotation annotation node. 361 * @return true if an annotation node has any node before or after on the same line. 362 */ 363 private static boolean hasNodeBeside(DetailAST annotation) { 364 return hasNodeBefore(annotation) || hasNodeAfter(annotation); 365 } 366 367 /** 368 * Checks whether an annotation node has any node after on the same line. 369 * 370 * @param annotation annotation node. 371 * @return true if an annotation node has any node after on the same line. 372 */ 373 private static boolean hasNodeAfter(DetailAST annotation) { 374 final int annotationLineNo = annotation.getLineNo(); 375 DetailAST nextNode = annotation.getNextSibling(); 376 377 if (nextNode == null) { 378 nextNode = annotation.getParent().getNextSibling(); 379 } 380 381 return annotationLineNo == nextNode.getLineNo(); 382 } 383 384}