1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2024 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 /////////////////////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.annotation; 21 22 import com.puppycrawl.tools.checkstyle.StatelessCheck; 23 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 24 import com.puppycrawl.tools.checkstyle.api.DetailAST; 25 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 26 import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 27 import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 28 29 /** 30 * <div> 31 * Checks location of annotation on language elements. 32 * By default, Check enforce to locate annotations immediately after 33 * documentation block and before target element, annotation should be located 34 * on separate line from target element. This check also verifies that the annotations 35 * are on the same indenting level as the annotated element if they are not on the same line. 36 * </div> 37 * 38 * <p> 39 * Attention: Elements that cannot have JavaDoc comments like local variables are not in the 40 * scope of this check even though a token type like {@code VARIABLE_DEF} would match them. 41 * </p> 42 * 43 * <p> 44 * Attention: Annotations among modifiers are ignored (looks like false-negative) 45 * as there might be a problem with annotations for return types: 46 * </p> 47 * <pre> 48 * public @Nullable Long getStartTimeOrNull() { ... } 49 * </pre> 50 * 51 * <p> 52 * Such annotations are better to keep close to type. 53 * Due to limitations, Checkstyle can not examine the target of an annotation. 54 * </p> 55 * 56 * <p> 57 * Example: 58 * </p> 59 * <pre> 60 * @Override 61 * @Nullable 62 * public String getNameIfPresent() { ... } 63 * </pre> 64 * <ul> 65 * <li> 66 * Property {@code allowSamelineMultipleAnnotations} - Allow annotation(s) to be located on 67 * the same line as target element. 68 * Type is {@code boolean}. 69 * Default value is {@code false}. 70 * </li> 71 * <li> 72 * Property {@code allowSamelineParameterizedAnnotation} - Allow one and only parameterized 73 * annotation to be located on the same line as target element. 74 * Type is {@code boolean}. 75 * Default value is {@code false}. 76 * </li> 77 * <li> 78 * Property {@code allowSamelineSingleParameterlessAnnotation} - Allow single parameterless 79 * annotation to be located on the same line as target element. 80 * Type is {@code boolean}. 81 * Default value is {@code true}. 82 * </li> 83 * <li> 84 * Property {@code tokens} - tokens to check 85 * Type is {@code java.lang.String[]}. 86 * Validation type is {@code tokenSet}. 87 * Default value is: 88 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 89 * CLASS_DEF</a>, 90 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 91 * INTERFACE_DEF</a>, 92 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PACKAGE_DEF"> 93 * PACKAGE_DEF</a>, 94 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF"> 95 * ENUM_CONSTANT_DEF</a>, 96 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 97 * ENUM_DEF</a>, 98 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 99 * METHOD_DEF</a>, 100 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 101 * CTOR_DEF</a>, 102 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 103 * VARIABLE_DEF</a>, 104 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF"> 105 * RECORD_DEF</a>, 106 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF"> 107 * COMPACT_CTOR_DEF</a>. 108 * </li> 109 * </ul> 110 * 111 * <p> 112 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 113 * </p> 114 * 115 * <p> 116 * Violation Message Keys: 117 * </p> 118 * <ul> 119 * <li> 120 * {@code annotation.location} 121 * </li> 122 * <li> 123 * {@code annotation.location.alone} 124 * </li> 125 * </ul> 126 * 127 * @since 6.0 128 */ 129 @StatelessCheck 130 public class AnnotationLocationCheck extends AbstractCheck { 131 132 /** 133 * A key is pointing to the warning message text in "messages.properties" 134 * file. 135 */ 136 public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone"; 137 138 /** 139 * A key is pointing to the warning message text in "messages.properties" 140 * file. 141 */ 142 public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location"; 143 144 /** 145 * Allow single parameterless annotation to be located on the same line as 146 * target element. 147 */ 148 private boolean allowSamelineSingleParameterlessAnnotation = true; 149 150 /** 151 * Allow one and only parameterized annotation to be located on the same line as 152 * target element. 153 */ 154 private boolean allowSamelineParameterizedAnnotation; 155 156 /** 157 * Allow annotation(s) to be located on the same line as 158 * target element. 159 */ 160 private boolean allowSamelineMultipleAnnotations; 161 162 /** 163 * Setter to allow single parameterless annotation to be located on the same line as 164 * target element. 165 * 166 * @param allow User's value of allowSamelineSingleParameterlessAnnotation. 167 * @since 6.1 168 */ 169 public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) { 170 allowSamelineSingleParameterlessAnnotation = allow; 171 } 172 173 /** 174 * Setter to allow one and only parameterized annotation to be located on the same line as 175 * target element. 176 * 177 * @param allow User's value of allowSamelineParameterizedAnnotation. 178 * @since 6.4 179 */ 180 public final void setAllowSamelineParameterizedAnnotation(boolean allow) { 181 allowSamelineParameterizedAnnotation = allow; 182 } 183 184 /** 185 * Setter to allow annotation(s) to be located on the same line as 186 * target element. 187 * 188 * @param allow User's value of allowSamelineMultipleAnnotations. 189 * @since 6.0 190 */ 191 public final void setAllowSamelineMultipleAnnotations(boolean allow) { 192 allowSamelineMultipleAnnotations = allow; 193 } 194 195 @Override 196 public int[] getDefaultTokens() { 197 return new int[] { 198 TokenTypes.CLASS_DEF, 199 TokenTypes.INTERFACE_DEF, 200 TokenTypes.PACKAGE_DEF, 201 TokenTypes.ENUM_CONSTANT_DEF, 202 TokenTypes.ENUM_DEF, 203 TokenTypes.METHOD_DEF, 204 TokenTypes.CTOR_DEF, 205 TokenTypes.VARIABLE_DEF, 206 TokenTypes.RECORD_DEF, 207 TokenTypes.COMPACT_CTOR_DEF, 208 }; 209 } 210 211 @Override 212 public int[] getAcceptableTokens() { 213 return new int[] { 214 TokenTypes.CLASS_DEF, 215 TokenTypes.INTERFACE_DEF, 216 TokenTypes.PACKAGE_DEF, 217 TokenTypes.ENUM_CONSTANT_DEF, 218 TokenTypes.ENUM_DEF, 219 TokenTypes.METHOD_DEF, 220 TokenTypes.CTOR_DEF, 221 TokenTypes.VARIABLE_DEF, 222 TokenTypes.ANNOTATION_DEF, 223 TokenTypes.ANNOTATION_FIELD_DEF, 224 TokenTypes.RECORD_DEF, 225 TokenTypes.COMPACT_CTOR_DEF, 226 }; 227 } 228 229 @Override 230 public int[] getRequiredTokens() { 231 return CommonUtil.EMPTY_INT_ARRAY; 232 } 233 234 @Override 235 public void visitToken(DetailAST ast) { 236 // ignore variable def tokens that are not field definitions 237 if (ast.getType() != TokenTypes.VARIABLE_DEF 238 || ast.getParent().getType() == TokenTypes.OBJBLOCK) { 239 DetailAST node = ast.findFirstToken(TokenTypes.MODIFIERS); 240 if (node == null) { 241 node = ast.findFirstToken(TokenTypes.ANNOTATIONS); 242 } 243 checkAnnotations(node, getExpectedAnnotationIndentation(node)); 244 } 245 } 246 247 /** 248 * Returns an expected annotation indentation. 249 * The expected indentation should be the same as the indentation of the target node. 250 * 251 * @param node modifiers or annotations node. 252 * @return the annotation indentation. 253 */ 254 private static int getExpectedAnnotationIndentation(DetailAST node) { 255 return node.getColumnNo(); 256 } 257 258 /** 259 * Checks annotations positions in code: 260 * 1) Checks whether the annotations locations are correct. 261 * 2) Checks whether the annotations have the valid indentation level. 262 * 263 * @param modifierNode modifiers node. 264 * @param correctIndentation correct indentation of the annotation. 265 */ 266 private void checkAnnotations(DetailAST modifierNode, int correctIndentation) { 267 DetailAST annotation = modifierNode.getFirstChild(); 268 269 while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) { 270 final boolean hasParameters = isParameterized(annotation); 271 272 if (!isCorrectLocation(annotation, hasParameters)) { 273 log(annotation, 274 MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation)); 275 } 276 else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) { 277 log(annotation, MSG_KEY_ANNOTATION_LOCATION, 278 getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation); 279 } 280 annotation = annotation.getNextSibling(); 281 } 282 } 283 284 /** 285 * Checks whether an annotation has parameters. 286 * 287 * @param annotation annotation node. 288 * @return true if the annotation has parameters. 289 */ 290 private static boolean isParameterized(DetailAST annotation) { 291 return TokenUtil.findFirstTokenByPredicate(annotation, ast -> { 292 return ast.getType() == TokenTypes.EXPR 293 || ast.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR; 294 }).isPresent(); 295 } 296 297 /** 298 * Returns the name of the given annotation. 299 * 300 * @param annotation annotation node. 301 * @return annotation name. 302 */ 303 private static String getAnnotationName(DetailAST annotation) { 304 DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT); 305 if (identNode == null) { 306 identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT); 307 } 308 return identNode.getText(); 309 } 310 311 /** 312 * Checks whether an annotation has a correct location. 313 * Annotation location is considered correct 314 * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true. 315 * The method also: 316 * 1) checks parameterized annotation location considering 317 * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation}; 318 * 2) checks parameterless annotation location considering 319 * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation}; 320 * 3) checks annotation location; 321 * 322 * @param annotation annotation node. 323 * @param hasParams whether an annotation has parameters. 324 * @return true if the annotation has a correct location. 325 */ 326 private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) { 327 final boolean allowingCondition; 328 329 if (hasParams) { 330 allowingCondition = allowSamelineParameterizedAnnotation; 331 } 332 else { 333 allowingCondition = allowSamelineSingleParameterlessAnnotation; 334 } 335 return allowSamelineMultipleAnnotations 336 || allowingCondition && !hasNodeBefore(annotation) 337 || !hasNodeBeside(annotation); 338 } 339 340 /** 341 * Checks whether an annotation node has any node before on the same line. 342 * 343 * @param annotation annotation node. 344 * @return true if an annotation node has any node before on the same line. 345 */ 346 private static boolean hasNodeBefore(DetailAST annotation) { 347 final int annotationLineNo = annotation.getLineNo(); 348 final DetailAST previousNode = annotation.getPreviousSibling(); 349 350 return previousNode != null && annotationLineNo == previousNode.getLineNo(); 351 } 352 353 /** 354 * Checks whether an annotation node has any node before or after on the same line. 355 * 356 * @param annotation annotation node. 357 * @return true if an annotation node has any node before or after on the same line. 358 */ 359 private static boolean hasNodeBeside(DetailAST annotation) { 360 return hasNodeBefore(annotation) || hasNodeAfter(annotation); 361 } 362 363 /** 364 * Checks whether an annotation node has any node after on the same line. 365 * 366 * @param annotation annotation node. 367 * @return true if an annotation node has any node after on the same line. 368 */ 369 private static boolean hasNodeAfter(DetailAST annotation) { 370 final int annotationLineNo = annotation.getLineNo(); 371 DetailAST nextNode = annotation.getNextSibling(); 372 373 if (nextNode == null) { 374 nextNode = annotation.getParent().getNextSibling(); 375 } 376 377 return annotationLineNo == nextNode.getLineNo(); 378 } 379 380 }