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" file. 087 */ 088 public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone"; 089 090 /** 091 * A key is pointing to the warning message text in "messages.properties" file. 092 */ 093 public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location"; 094 095 /** 096 * Allow single parameterless annotation to be located on the same line as target element. 097 */ 098 private boolean allowSamelineSingleParameterlessAnnotation = true; 099 100 /** 101 * Allow one and only parameterized annotation to be located on the same line as target element. 102 */ 103 private boolean allowSamelineParameterizedAnnotation; 104 105 /** 106 * Allow annotation(s) to be located on the same line as target element. 107 */ 108 private boolean allowSamelineMultipleAnnotations; 109 110 /** 111 * Creates a new {@code AnnotationLocationCheck} instance. 112 */ 113 public AnnotationLocationCheck() { 114 // no code by default 115 } 116 117 /** 118 * Setter to allow single parameterless annotation to be located on the same line as 119 * target element. 120 * 121 * @param allow User's value of allowSamelineSingleParameterlessAnnotation. 122 * @since 6.1 123 */ 124 public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) { 125 allowSamelineSingleParameterlessAnnotation = allow; 126 } 127 128 /** 129 * Setter to allow one and only parameterized annotation to be located on the same line as 130 * target element. 131 * 132 * @param allow User's value of allowSamelineParameterizedAnnotation. 133 * @since 6.4 134 */ 135 public final void setAllowSamelineParameterizedAnnotation(boolean allow) { 136 allowSamelineParameterizedAnnotation = allow; 137 } 138 139 /** 140 * Setter to allow annotation(s) to be located on the same line as target element. 141 * 142 * @param allow User's value of allowSamelineMultipleAnnotations. 143 * @since 6.0 144 */ 145 public final void setAllowSamelineMultipleAnnotations(boolean allow) { 146 allowSamelineMultipleAnnotations = allow; 147 } 148 149 @Override 150 public int[] getDefaultTokens() { 151 return new int[] { 152 TokenTypes.CLASS_DEF, 153 TokenTypes.INTERFACE_DEF, 154 TokenTypes.PACKAGE_DEF, 155 TokenTypes.ENUM_CONSTANT_DEF, 156 TokenTypes.ENUM_DEF, 157 TokenTypes.METHOD_DEF, 158 TokenTypes.CTOR_DEF, 159 TokenTypes.VARIABLE_DEF, 160 TokenTypes.RECORD_DEF, 161 TokenTypes.COMPACT_CTOR_DEF, 162 TokenTypes.MODULE_DEF, 163 }; 164 } 165 166 @Override 167 public int[] getAcceptableTokens() { 168 return new int[] { 169 TokenTypes.CLASS_DEF, 170 TokenTypes.INTERFACE_DEF, 171 TokenTypes.PACKAGE_DEF, 172 TokenTypes.ENUM_CONSTANT_DEF, 173 TokenTypes.ENUM_DEF, 174 TokenTypes.METHOD_DEF, 175 TokenTypes.CTOR_DEF, 176 TokenTypes.VARIABLE_DEF, 177 TokenTypes.ANNOTATION_DEF, 178 TokenTypes.ANNOTATION_FIELD_DEF, 179 TokenTypes.RECORD_DEF, 180 TokenTypes.COMPACT_CTOR_DEF, 181 TokenTypes.MODULE_DEF, 182 }; 183 } 184 185 @Override 186 public int[] getRequiredTokens() { 187 return CommonUtil.EMPTY_INT_ARRAY; 188 } 189 190 @Override 191 public void visitToken(DetailAST ast) { 192 // ignore variable def tokens that are not field definitions 193 if (ast.getType() != TokenTypes.VARIABLE_DEF 194 || ast.getParent().getType() == TokenTypes.OBJBLOCK 195 || ast.getParent().getType() == TokenTypes.COMPACT_COMPILATION_UNIT) { 196 final DetailAST node = getAnnotationParent(ast); 197 checkAnnotations(node, getExpectedAnnotationIndentation(node)); 198 } 199 } 200 201 /** 202 * Returns the node holding the annotations of the given ast. 203 * 204 * @param annotatedNodeAst node being visited. 205 * @return the MODIFIERS node, or, if absent, the ANNOTATIONS node. 206 * 207 * @notNull because absence of MODIFIERS on such a token means it is a package declaration, 208 * which by grammar always carries an ANNOTATIONS node. 209 */ 210 private static DetailAST getAnnotationParent(DetailAST annotatedNodeAst) { 211 DetailAST node = annotatedNodeAst.findFirstToken(TokenTypes.MODIFIERS); 212 if (node == null) { 213 node = NullUtil.notNull(annotatedNodeAst.findFirstToken(TokenTypes.ANNOTATIONS)); 214 } 215 return node; 216 } 217 218 /** 219 * Returns an expected annotation indentation. 220 * The expected indentation should be the same as the indentation of the target node. 221 * 222 * @param node modifiers or annotations node. 223 * @return the annotation indentation. 224 */ 225 private static int getExpectedAnnotationIndentation(DetailAST node) { 226 return node.getColumnNo(); 227 } 228 229 /** 230 * Checks annotations positions in code: 231 * 1) Checks whether the annotations locations are correct. 232 * 2) Checks whether the annotations have the valid indentation level. 233 * 234 * @param annotationParent node. 235 * @param correctIndentation correct indentation of the annotation. 236 */ 237 private void checkAnnotations(DetailAST annotationParent, int correctIndentation) { 238 DetailAST annotation = annotationParent.getFirstChild(); 239 240 while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) { 241 final boolean hasParameters = isParameterized(annotation); 242 243 if (!isCorrectLocation(annotation, hasParameters)) { 244 log(annotation, 245 MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation)); 246 } 247 else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) { 248 log(annotation, MSG_KEY_ANNOTATION_LOCATION, 249 getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation); 250 } 251 annotation = annotation.getNextSibling(); 252 } 253 } 254 255 /** 256 * Checks whether an annotation has parameters. 257 * 258 * @param annotation annotation node. 259 * @return true if the annotation has parameters. 260 */ 261 private static boolean isParameterized(DetailAST annotation) { 262 return TokenUtil.findFirstTokenByPredicate(annotation, ast -> { 263 return ast.getType() == TokenTypes.EXPR 264 || ast.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR; 265 }).isPresent(); 266 } 267 268 /** 269 * Returns the name of the given annotation. 270 * 271 * @param annotation annotation node. 272 * @return annotation name. 273 * 274 * @notNull because method operates only on annotation ast nodes; 275 * absence of IDENT as a direct child of the annotation means 276 * it has a fully qualified name expressed via DOT and IDENT. 277 */ 278 private static String getAnnotationName(DetailAST annotation) { 279 DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT); 280 if (identNode == null) { 281 final DetailAST dotNode = NullUtil.notNull(annotation.findFirstToken(TokenTypes.DOT)); 282 identNode = NullUtil.notNull(dotNode.findFirstToken(TokenTypes.IDENT)); 283 } 284 return identNode.getText(); 285 } 286 287 /** 288 * Checks whether an annotation has a correct location. 289 * Annotation location is considered correct 290 * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true. 291 * The method also: 292 * 1) checks parameterized annotation location considering 293 * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation}; 294 * 2) checks parameterless annotation location considering 295 * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation}; 296 * 3) checks annotation location; 297 * 298 * @param annotation annotation node. 299 * @param hasParams whether an annotation has parameters. 300 * @return true if the annotation has a correct location. 301 */ 302 private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) { 303 final boolean allowingCondition; 304 305 if (hasParams) { 306 allowingCondition = allowSamelineParameterizedAnnotation; 307 } 308 else { 309 allowingCondition = allowSamelineSingleParameterlessAnnotation; 310 } 311 return allowSamelineMultipleAnnotations 312 || allowingCondition && !hasNodeBefore(annotation) 313 || !hasNodeBeside(annotation); 314 } 315 316 /** 317 * Checks whether an annotation node has any node before on the same line. 318 * 319 * @param annotation annotation node. 320 * @return true if an annotation node has any node before on the same line. 321 */ 322 private static boolean hasNodeBefore(DetailAST annotation) { 323 final int annotationLineNo = annotation.getLineNo(); 324 final DetailAST previousNode = annotation.getPreviousSibling(); 325 326 return previousNode != null && annotationLineNo == previousNode.getLineNo(); 327 } 328 329 /** 330 * Checks whether an annotation node has any node before or after on the same line. 331 * 332 * @param annotation annotation node. 333 * @return true if an annotation node has any node before or after on the same line. 334 */ 335 private static boolean hasNodeBeside(DetailAST annotation) { 336 return hasNodeBefore(annotation) || hasNodeAfter(annotation); 337 } 338 339 /** 340 * Checks whether an annotation node has any node after on the same line. 341 * 342 * @param annotation annotation node. 343 * @return true if an annotation node has any node after on the same line. 344 */ 345 private static boolean hasNodeAfter(DetailAST annotation) { 346 final int annotationLineNo = annotation.getLineNo(); 347 DetailAST nextNode = annotation.getNextSibling(); 348 349 if (nextNode == null) { 350 nextNode = annotation.getParent().getNextSibling(); 351 } 352 353 return annotationLineNo == nextNode.getLineNo(); 354 } 355 356}