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