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 }; 173 } 174 175 @Override 176 public int[] getRequiredTokens() { 177 return CommonUtil.EMPTY_INT_ARRAY; 178 } 179 180 @Override 181 public void visitToken(DetailAST ast) { 182 // ignore variable def tokens that are not field definitions 183 if (ast.getType() != TokenTypes.VARIABLE_DEF 184 || ast.getParent().getType() == TokenTypes.OBJBLOCK) { 185 DetailAST node = ast.findFirstToken(TokenTypes.MODIFIERS); 186 if (node == null) { 187 node = ast.findFirstToken(TokenTypes.ANNOTATIONS); 188 } 189 checkAnnotations(node, getExpectedAnnotationIndentation(node)); 190 } 191 } 192 193 /** 194 * Returns an expected annotation indentation. 195 * The expected indentation should be the same as the indentation of the target node. 196 * 197 * @param node modifiers or annotations node. 198 * @return the annotation indentation. 199 */ 200 private static int getExpectedAnnotationIndentation(DetailAST node) { 201 return node.getColumnNo(); 202 } 203 204 /** 205 * Checks annotations positions in code: 206 * 1) Checks whether the annotations locations are correct. 207 * 2) Checks whether the annotations have the valid indentation level. 208 * 209 * @param modifierNode modifiers node. 210 * @param correctIndentation correct indentation of the annotation. 211 */ 212 private void checkAnnotations(DetailAST modifierNode, int correctIndentation) { 213 DetailAST annotation = modifierNode.getFirstChild(); 214 215 while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) { 216 final boolean hasParameters = isParameterized(annotation); 217 218 if (!isCorrectLocation(annotation, hasParameters)) { 219 log(annotation, 220 MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation)); 221 } 222 else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) { 223 log(annotation, MSG_KEY_ANNOTATION_LOCATION, 224 getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation); 225 } 226 annotation = annotation.getNextSibling(); 227 } 228 } 229 230 /** 231 * Checks whether an annotation has parameters. 232 * 233 * @param annotation annotation node. 234 * @return true if the annotation has parameters. 235 */ 236 private static boolean isParameterized(DetailAST annotation) { 237 return TokenUtil.findFirstTokenByPredicate(annotation, ast -> { 238 return ast.getType() == TokenTypes.EXPR 239 || ast.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR; 240 }).isPresent(); 241 } 242 243 /** 244 * Returns the name of the given annotation. 245 * 246 * @param annotation annotation node. 247 * @return annotation name. 248 */ 249 private static String getAnnotationName(DetailAST annotation) { 250 DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT); 251 if (identNode == null) { 252 identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT); 253 } 254 return identNode.getText(); 255 } 256 257 /** 258 * Checks whether an annotation has a correct location. 259 * Annotation location is considered correct 260 * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true. 261 * The method also: 262 * 1) checks parameterized annotation location considering 263 * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation}; 264 * 2) checks parameterless annotation location considering 265 * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation}; 266 * 3) checks annotation location; 267 * 268 * @param annotation annotation node. 269 * @param hasParams whether an annotation has parameters. 270 * @return true if the annotation has a correct location. 271 */ 272 private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) { 273 final boolean allowingCondition; 274 275 if (hasParams) { 276 allowingCondition = allowSamelineParameterizedAnnotation; 277 } 278 else { 279 allowingCondition = allowSamelineSingleParameterlessAnnotation; 280 } 281 return allowSamelineMultipleAnnotations 282 || allowingCondition && !hasNodeBefore(annotation) 283 || !hasNodeBeside(annotation); 284 } 285 286 /** 287 * Checks whether an annotation node has any node before on the same line. 288 * 289 * @param annotation annotation node. 290 * @return true if an annotation node has any node before on the same line. 291 */ 292 private static boolean hasNodeBefore(DetailAST annotation) { 293 final int annotationLineNo = annotation.getLineNo(); 294 final DetailAST previousNode = annotation.getPreviousSibling(); 295 296 return previousNode != null && annotationLineNo == previousNode.getLineNo(); 297 } 298 299 /** 300 * Checks whether an annotation node has any node before or after on the same line. 301 * 302 * @param annotation annotation node. 303 * @return true if an annotation node has any node before or after on the same line. 304 */ 305 private static boolean hasNodeBeside(DetailAST annotation) { 306 return hasNodeBefore(annotation) || hasNodeAfter(annotation); 307 } 308 309 /** 310 * Checks whether an annotation node has any node after on the same line. 311 * 312 * @param annotation annotation node. 313 * @return true if an annotation node has any node after on the same line. 314 */ 315 private static boolean hasNodeAfter(DetailAST annotation) { 316 final int annotationLineNo = annotation.getLineNo(); 317 DetailAST nextNode = annotation.getNextSibling(); 318 319 if (nextNode == null) { 320 nextNode = annotation.getParent().getNextSibling(); 321 } 322 323 return annotationLineNo == nextNode.getLineNo(); 324 } 325 326}