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