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