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