001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 * Default value is {@code false}. 065 * </li> 066 * <li> 067 * Property {@code allowSamelineSingleParameterlessAnnotation} - Allow single parameterless 068 * annotation to be located on the same line as target element. 069 * Default value is {@code true}. 070 * </li> 071 * <li> 072 * Property {@code allowSamelineParameterizedAnnotation} - Allow one and only parameterized 073 * annotation to be located on the same line as target element. 074 * Default value is {@code false}. 075 * </li> 076 * <li> 077 * Property {@code tokens} - tokens to check 078 * Default value is: 079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 080 * CLASS_DEF</a>, 081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 082 * INTERFACE_DEF</a>, 083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PACKAGE_DEF"> 084 * PACKAGE_DEF</a>, 085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF"> 086 * ENUM_CONSTANT_DEF</a>, 087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 088 * ENUM_DEF</a>, 089 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 090 * METHOD_DEF</a>, 091 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 092 * CTOR_DEF</a>, 093 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 094 * VARIABLE_DEF</a>. 095 * </li> 096 * </ul> 097 * <p> 098 * Example to allow multiple annotations on the same line 099 * </p> 100 * <pre> 101 * @SuppressWarnings("deprecation") @Mock DataLoader loader; // no violations 102 * </pre> 103 * <p> 104 * Use the following configuration: 105 * </p> 106 * <pre> 107 * <module name="AnnotationLocation"> 108 * <property name="allowSamelineMultipleAnnotations" value="true"/> 109 * <property name="allowSamelineSingleParameterlessAnnotation" 110 * value="false"/> 111 * <property name="allowSamelineParameterizedAnnotation" value="false"/> 112 * </module> 113 * </pre> 114 * <p> 115 * Example to allow one single parameterless annotation on the same line 116 * </p> 117 * <pre> 118 * @Override public int hashCode() { ... } // no violations 119 * @SuppressWarnings("deprecation") public int foo() { ... } // violation 120 * </pre> 121 * <p> 122 * Use the following configuration: 123 * </p> 124 * <pre> 125 * <module name="AnnotationLocation"> 126 * <property name="allowSamelineMultipleAnnotations" value="false"/> 127 * <property name="allowSamelineSingleParameterlessAnnotation" 128 * value="true"/> 129 * <property name="allowSamelineParameterizedAnnotation" value="false"/> 130 * </module> 131 * </pre> 132 * <p> 133 * Example to allow only one and only parameterized annotation on the same line 134 * </p> 135 * <pre> 136 * @SuppressWarnings("deprecation") DataLoader loader; // no violations 137 * @Mock DataLoader loader; // violation 138 * </pre> 139 * <p> 140 * Use the following configuration: 141 * </p> 142 * <pre> 143 * <module name="AnnotationLocation"> 144 * <property name="allowSamelineMultipleAnnotations" value="false"/> 145 * <property name="allowSamelineSingleParameterlessAnnotation" 146 * value="false"/> 147 * <property name="allowSamelineParameterizedAnnotation" value="true"/> 148 * </module> 149 * </pre> 150 * 151 * @since 6.0 152 */ 153@StatelessCheck 154public class AnnotationLocationCheck extends AbstractCheck { 155 156 /** 157 * A key is pointing to the warning message text in "messages.properties" 158 * file. 159 */ 160 public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone"; 161 162 /** 163 * A key is pointing to the warning message text in "messages.properties" 164 * file. 165 */ 166 public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location"; 167 168 /** 169 * Allow single parameterless annotation to be located on the same line as 170 * target element. 171 */ 172 private boolean allowSamelineSingleParameterlessAnnotation = true; 173 174 /** 175 * Allow one and only parameterized annotation to be located on the same line as 176 * target element. 177 */ 178 private boolean allowSamelineParameterizedAnnotation; 179 180 /** 181 * Allow annotation(s) to be located on the same line as 182 * target element. 183 */ 184 private boolean allowSamelineMultipleAnnotations; 185 186 /** 187 * Setter to allow single parameterless annotation to be located on the same line as 188 * target element. 189 * @param allow User's value of allowSamelineSingleParameterlessAnnotation. 190 */ 191 public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) { 192 allowSamelineSingleParameterlessAnnotation = allow; 193 } 194 195 /** 196 * Setter to allow one and only parameterized annotation to be located on the same line as 197 * target element. 198 * @param allow User's value of allowSamelineParameterizedAnnotation. 199 */ 200 public final void setAllowSamelineParameterizedAnnotation(boolean allow) { 201 allowSamelineParameterizedAnnotation = allow; 202 } 203 204 /** 205 * Setter to allow annotation(s) to be located on the same line as 206 * target element. 207 * @param allow User's value of allowSamelineMultipleAnnotations. 208 */ 209 public final void setAllowSamelineMultipleAnnotations(boolean allow) { 210 allowSamelineMultipleAnnotations = allow; 211 } 212 213 @Override 214 public int[] getDefaultTokens() { 215 return new int[] { 216 TokenTypes.CLASS_DEF, 217 TokenTypes.INTERFACE_DEF, 218 TokenTypes.PACKAGE_DEF, 219 TokenTypes.ENUM_CONSTANT_DEF, 220 TokenTypes.ENUM_DEF, 221 TokenTypes.METHOD_DEF, 222 TokenTypes.CTOR_DEF, 223 TokenTypes.VARIABLE_DEF, 224 }; 225 } 226 227 @Override 228 public int[] getAcceptableTokens() { 229 return new int[] { 230 TokenTypes.CLASS_DEF, 231 TokenTypes.INTERFACE_DEF, 232 TokenTypes.PACKAGE_DEF, 233 TokenTypes.ENUM_CONSTANT_DEF, 234 TokenTypes.ENUM_DEF, 235 TokenTypes.METHOD_DEF, 236 TokenTypes.CTOR_DEF, 237 TokenTypes.VARIABLE_DEF, 238 TokenTypes.ANNOTATION_DEF, 239 TokenTypes.ANNOTATION_FIELD_DEF, 240 }; 241 } 242 243 @Override 244 public int[] getRequiredTokens() { 245 return CommonUtil.EMPTY_INT_ARRAY; 246 } 247 248 @Override 249 public void visitToken(DetailAST ast) { 250 // ignore variable def tokens that are not field definitions 251 if (ast.getType() != TokenTypes.VARIABLE_DEF 252 || ast.getParent().getType() == TokenTypes.OBJBLOCK) { 253 DetailAST node = ast.findFirstToken(TokenTypes.MODIFIERS); 254 if (node == null) { 255 node = ast.findFirstToken(TokenTypes.ANNOTATIONS); 256 } 257 checkAnnotations(node, getExpectedAnnotationIndentation(node)); 258 } 259 } 260 261 /** 262 * Returns an expected annotation indentation. 263 * The expected indentation should be the same as the indentation of the target node. 264 * @param node modifiers or annotations node. 265 * @return the annotation indentation. 266 */ 267 private static int getExpectedAnnotationIndentation(DetailAST node) { 268 return node.getColumnNo(); 269 } 270 271 /** 272 * Checks annotations positions in code: 273 * 1) Checks whether the annotations locations are correct. 274 * 2) Checks whether the annotations have the valid indentation level. 275 * @param modifierNode modifiers node. 276 * @param correctIndentation correct indentation of the annotation. 277 */ 278 private void checkAnnotations(DetailAST modifierNode, int correctIndentation) { 279 DetailAST annotation = modifierNode.getFirstChild(); 280 281 while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) { 282 final boolean hasParameters = isParameterized(annotation); 283 284 if (!isCorrectLocation(annotation, hasParameters)) { 285 log(annotation.getLineNo(), 286 MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation)); 287 } 288 else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) { 289 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_LOCATION, 290 getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation); 291 } 292 annotation = annotation.getNextSibling(); 293 } 294 } 295 296 /** 297 * Checks whether an annotation has parameters. 298 * @param annotation annotation node. 299 * @return true if the annotation has parameters. 300 */ 301 private static boolean isParameterized(DetailAST annotation) { 302 return TokenUtil.findFirstTokenByPredicate(annotation, ast -> { 303 return ast.getType() == TokenTypes.EXPR 304 || ast.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR; 305 }).isPresent(); 306 } 307 308 /** 309 * Returns the name of the given annotation. 310 * @param annotation annotation node. 311 * @return annotation name. 312 */ 313 private static String getAnnotationName(DetailAST annotation) { 314 DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT); 315 if (identNode == null) { 316 identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT); 317 } 318 return identNode.getText(); 319 } 320 321 /** 322 * Checks whether an annotation has a correct location. 323 * Annotation location is considered correct 324 * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true. 325 * The method also: 326 * 1) checks parameterized annotation location considering 327 * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation}; 328 * 2) checks parameterless annotation location considering 329 * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation}; 330 * 3) checks annotation location; 331 * @param annotation annotation node. 332 * @param hasParams whether an annotation has parameters. 333 * @return true if the annotation has a correct location. 334 */ 335 private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) { 336 final boolean allowingCondition; 337 338 if (hasParams) { 339 allowingCondition = allowSamelineParameterizedAnnotation; 340 } 341 else { 342 allowingCondition = allowSamelineSingleParameterlessAnnotation; 343 } 344 return allowSamelineMultipleAnnotations 345 || allowingCondition && !hasNodeBefore(annotation) 346 || !hasNodeBeside(annotation); 347 } 348 349 /** 350 * Checks whether an annotation node has any node before on the same line. 351 * @param annotation annotation node. 352 * @return true if an annotation node has any node before on the same line. 353 */ 354 private static boolean hasNodeBefore(DetailAST annotation) { 355 final int annotationLineNo = annotation.getLineNo(); 356 final DetailAST previousNode = annotation.getPreviousSibling(); 357 358 return previousNode != null && annotationLineNo == previousNode.getLineNo(); 359 } 360 361 /** 362 * Checks whether an annotation node has any node before or after on the same line. 363 * @param annotation annotation node. 364 * @return true if an annotation node has any node before or after on the same line. 365 */ 366 private static boolean hasNodeBeside(DetailAST annotation) { 367 return hasNodeBefore(annotation) || hasNodeAfter(annotation); 368 } 369 370 /** 371 * Checks whether an annotation node has any node after on the same line. 372 * @param annotation annotation node. 373 * @return true if an annotation node has any node after on the same line. 374 */ 375 private static boolean hasNodeAfter(DetailAST annotation) { 376 final int annotationLineNo = annotation.getLineNo(); 377 DetailAST nextNode = annotation.getNextSibling(); 378 379 if (nextNode == null) { 380 nextNode = annotation.getParent().getNextSibling(); 381 } 382 383 return annotationLineNo == nextNode.getLineNo(); 384 } 385 386}