001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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 * <p> 031 * Checks location of annotation on language elements. 032 * By default, Check enforce to locate annotations immediately after 033 * documentation block and before target element, annotation should be located 034 * on separate line from target element. This check also verifies that the annotations 035 * are on the same indenting level as the annotated element if they are not on the same line. 036 * </p> 037 * <p> 038 * Attention: Elements that cannot have JavaDoc comments like local variables are not in the 039 * scope of this check even though a token type like {@code VARIABLE_DEF} would match them. 040 * </p> 041 * <p> 042 * Attention: Annotations among modifiers are ignored (looks like false-negative) 043 * as there might be a problem with annotations for return types: 044 * </p> 045 * <pre> 046 * public @Nullable Long getStartTimeOrNull() { ... } 047 * </pre> 048 * <p> 049 * Such annotations are better to keep close to type. 050 * Due to limitations, Checkstyle can not examine the target of an annotation. 051 * </p> 052 * <p> 053 * Example: 054 * </p> 055 * <pre> 056 * @Override 057 * @Nullable 058 * public String getNameIfPresent() { ... } 059 * </pre> 060 * <ul> 061 * <li> 062 * Property {@code allowSamelineMultipleAnnotations} - Allow annotation(s) to be located on 063 * the same line as target element. 064 * Type is {@code boolean}. 065 * Default value is {@code false}. 066 * </li> 067 * <li> 068 * Property {@code allowSamelineParameterizedAnnotation} - Allow one and only parameterized 069 * annotation to be located on the same line as target element. 070 * Type is {@code boolean}. 071 * Default value is {@code false}. 072 * </li> 073 * <li> 074 * Property {@code allowSamelineSingleParameterlessAnnotation} - Allow single parameterless 075 * annotation to be located on the same line as target element. 076 * Type is {@code boolean}. 077 * Default value is {@code true}. 078 * </li> 079 * <li> 080 * Property {@code tokens} - tokens to check 081 * Type is {@code java.lang.String[]}. 082 * Validation type is {@code tokenSet}. 083 * Default value is: 084 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 085 * CLASS_DEF</a>, 086 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 087 * INTERFACE_DEF</a>, 088 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PACKAGE_DEF"> 089 * PACKAGE_DEF</a>, 090 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF"> 091 * ENUM_CONSTANT_DEF</a>, 092 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 093 * ENUM_DEF</a>, 094 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 095 * METHOD_DEF</a>, 096 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 097 * CTOR_DEF</a>, 098 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 099 * 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 124public 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}