001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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.CommonUtils; 027 028/** 029 * Check location of annotation on language elements. 030 * By default, Check enforce to locate annotations immediately after 031 * documentation block and before target element, annotation should be located 032 * on separate line from target element. 033 * <p> 034 * Attention: Annotations among modifiers are ignored (looks like false-negative) 035 * as there might be a problem with annotations for return types. 036 * </p> 037 * <pre>public @Nullable Long getStartTimeOrNull() { ... }</pre>. 038 * <p> 039 * Such annotations are better to keep close to type. 040 * Due to limitations, Checkstyle can not examine the target of an annotation. 041 * </p> 042 * 043 * <p> 044 * Example: 045 * </p> 046 * 047 * <pre> 048 * @Override 049 * @Nullable 050 * public String getNameIfPresent() { ... } 051 * </pre> 052 * 053 * <p> 054 * The check has the following options: 055 * </p> 056 * <ul> 057 * <li>allowSamelineMultipleAnnotations - to allow annotation to be located on 058 * the same line as the target element. Default value is false. 059 * </li> 060 * 061 * <li> 062 * allowSamelineSingleParameterlessAnnotation - to allow single parameterless 063 * annotation to be located on the same line as the target element. Default value is false. 064 * </li> 065 * 066 * <li> 067 * allowSamelineParameterizedAnnotation - to allow parameterized annotation 068 * to be located on the same line as the target element. Default value is false. 069 * </li> 070 * </ul> 071 * <br> 072 * <p> 073 * Example to allow single parameterless annotation on the same line: 074 * </p> 075 * <pre> 076 * @Override public int hashCode() { ... } 077 * </pre> 078 * 079 * <p>Use the following configuration: 080 * <pre> 081 * <module name="AnnotationLocation"> 082 * <property name="allowSamelineMultipleAnnotations" value="false"/> 083 * <property name="allowSamelineSingleParameterlessAnnotation" 084 * value="true"/> 085 * <property name="allowSamelineParameterizedAnnotation" value="false" 086 * /> 087 * </module> 088 * </pre> 089 * <br> 090 * <p> 091 * Example to allow multiple parameterized annotations on the same line: 092 * </p> 093 * <pre> 094 * @SuppressWarnings("deprecation") @Mock DataLoader loader; 095 * </pre> 096 * 097 * <p>Use the following configuration: 098 * <pre> 099 * <module name="AnnotationLocation"> 100 * <property name="allowSamelineMultipleAnnotations" value="true"/> 101 * <property name="allowSamelineSingleParameterlessAnnotation" 102 * value="true"/> 103 * <property name="allowSamelineParameterizedAnnotation" value="true" 104 * /> 105 * </module> 106 * </pre> 107 * <br> 108 * <p> 109 * Example to allow multiple parameterless annotations on the same line: 110 * </p> 111 * <pre> 112 * @Partial @Mock DataLoader loader; 113 * </pre> 114 * 115 * <p>Use the following configuration: 116 * <pre> 117 * <module name="AnnotationLocation"> 118 * <property name="allowSamelineMultipleAnnotations" value="true"/> 119 * <property name="allowSamelineSingleParameterlessAnnotation" 120 * value="true"/> 121 * <property name="allowSamelineParameterizedAnnotation" value="false" 122 * /> 123 * </module> 124 * </pre> 125 * <br> 126 * <p> 127 * The following example demonstrates how the check validates annotation of method parameters, 128 * catch parameters, foreach, for-loop variable definitions. 129 * </p> 130 * 131 * <p>Configuration: 132 * <pre> 133 * <module name="AnnotationLocation"> 134 * <property name="allowSamelineMultipleAnnotations" value="false"/> 135 * <property name="allowSamelineSingleParameterlessAnnotation" 136 * value="false"/> 137 * <property name="allowSamelineParameterizedAnnotation" value="false" 138 * /> 139 * <property name="tokens" value="VARIABLE_DEF, PARAMETER_DEF"/> 140 * </module> 141 * </pre> 142 * 143 * <p>Code example 144 * {@code 145 * ... 146 * public void test(@MyAnnotation String s) { // OK 147 * ... 148 * for (@MyAnnotation char c : s.toCharArray()) { ... } // OK 149 * ... 150 * try { ... } 151 * catch (@MyAnnotation Exception ex) { ... } // OK 152 * ... 153 * for (@MyAnnotation int i = 0; i < 10; i++) { ... } // OK 154 * ... 155 * MathOperation c = (@MyAnnotation int a, @MyAnnotation int b) -> a + b; // OK 156 * ... 157 * } 158 * } 159 * 160 * @author maxvetrenko 161 */ 162@StatelessCheck 163public class AnnotationLocationCheck extends AbstractCheck { 164 165 /** 166 * A key is pointing to the warning message text in "messages.properties" 167 * file. 168 */ 169 public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone"; 170 171 /** 172 * A key is pointing to the warning message text in "messages.properties" 173 * file. 174 */ 175 public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location"; 176 177 /** Array of single line annotation parents. */ 178 private static final int[] SINGLELINE_ANNOTATION_PARENTS = {TokenTypes.FOR_EACH_CLAUSE, 179 TokenTypes.PARAMETER_DEF, 180 TokenTypes.FOR_INIT, }; 181 182 /** 183 * If true, it allows single prameterless annotation to be located on the same line as 184 * target element. 185 */ 186 private boolean allowSamelineSingleParameterlessAnnotation = true; 187 188 /** 189 * If true, it allows parameterized annotation to be located on the same line as 190 * target element. 191 */ 192 private boolean allowSamelineParameterizedAnnotation; 193 194 /** 195 * If true, it allows annotation to be located on the same line as 196 * target element. 197 */ 198 private boolean allowSamelineMultipleAnnotations; 199 200 /** 201 * Sets if allow same line single parameterless annotation. 202 * @param allow User's value of allowSamelineSingleParameterlessAnnotation. 203 */ 204 public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) { 205 allowSamelineSingleParameterlessAnnotation = allow; 206 } 207 208 /** 209 * Sets if allow parameterized annotation to be located on the same line as 210 * target element. 211 * @param allow User's value of allowSamelineParameterizedAnnotation. 212 */ 213 public final void setAllowSamelineParameterizedAnnotation(boolean allow) { 214 allowSamelineParameterizedAnnotation = allow; 215 } 216 217 /** 218 * Sets if allow annotation to be located on the same line as 219 * target element. 220 * @param allow User's value of allowSamelineMultipleAnnotations. 221 */ 222 public final void setAllowSamelineMultipleAnnotations(boolean allow) { 223 allowSamelineMultipleAnnotations = allow; 224 } 225 226 @Override 227 public int[] getDefaultTokens() { 228 return new int[] { 229 TokenTypes.CLASS_DEF, 230 TokenTypes.INTERFACE_DEF, 231 TokenTypes.ENUM_DEF, 232 TokenTypes.METHOD_DEF, 233 TokenTypes.CTOR_DEF, 234 TokenTypes.VARIABLE_DEF, 235 }; 236 } 237 238 @Override 239 public int[] getAcceptableTokens() { 240 return new int[] { 241 TokenTypes.CLASS_DEF, 242 TokenTypes.INTERFACE_DEF, 243 TokenTypes.ENUM_DEF, 244 TokenTypes.METHOD_DEF, 245 TokenTypes.CTOR_DEF, 246 TokenTypes.VARIABLE_DEF, 247 TokenTypes.PARAMETER_DEF, 248 TokenTypes.ANNOTATION_DEF, 249 TokenTypes.TYPECAST, 250 TokenTypes.LITERAL_THROWS, 251 TokenTypes.IMPLEMENTS_CLAUSE, 252 TokenTypes.TYPE_ARGUMENT, 253 TokenTypes.LITERAL_NEW, 254 TokenTypes.DOT, 255 TokenTypes.ANNOTATION_FIELD_DEF, 256 }; 257 } 258 259 @Override 260 public int[] getRequiredTokens() { 261 return CommonUtils.EMPTY_INT_ARRAY; 262 } 263 264 @Override 265 public void visitToken(DetailAST ast) { 266 final DetailAST modifiersNode = ast.findFirstToken(TokenTypes.MODIFIERS); 267 268 if (hasAnnotations(modifiersNode)) { 269 checkAnnotations(modifiersNode, getExpectedAnnotationIndentation(modifiersNode)); 270 } 271 } 272 273 /** 274 * Checks whether a given modifier node has an annotation. 275 * @param modifierNode modifier node. 276 * @return true if the given modifier node has the annotation. 277 */ 278 private static boolean hasAnnotations(DetailAST modifierNode) { 279 return modifierNode != null 280 && modifierNode.findFirstToken(TokenTypes.ANNOTATION) != null; 281 } 282 283 /** 284 * Returns an expected annotation indentation. 285 * The expected indentation should be the same as the indentation of the node 286 * which is the parent of the target modifier node. 287 * @param modifierNode modifier node. 288 * @return the annotation indentation. 289 */ 290 private static int getExpectedAnnotationIndentation(DetailAST modifierNode) { 291 return modifierNode.getParent().getColumnNo(); 292 } 293 294 /** 295 * Checks annotations positions in code: 296 * 1) Checks whether the annotations locations are correct. 297 * 2) Checks whether the annotations have the valid indentation level. 298 * @param modifierNode modifiers node. 299 * @param correctIndentation correct indentation of the annotation. 300 */ 301 private void checkAnnotations(DetailAST modifierNode, int correctIndentation) { 302 DetailAST annotation = modifierNode.getFirstChild(); 303 304 while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) { 305 final boolean hasParameters = isParameterized(annotation); 306 307 if (!isCorrectLocation(annotation, hasParameters)) { 308 log(annotation.getLineNo(), 309 MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation)); 310 } 311 else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) { 312 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_LOCATION, 313 getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation); 314 } 315 annotation = annotation.getNextSibling(); 316 } 317 } 318 319 /** 320 * Checks whether an annotation has parameters. 321 * @param annotation annotation node. 322 * @return true if the annotation has parameters. 323 */ 324 private static boolean isParameterized(DetailAST annotation) { 325 return annotation.findFirstToken(TokenTypes.EXPR) != null; 326 } 327 328 /** 329 * Returns the name of the given annotation. 330 * @param annotation annotation node. 331 * @return annotation name. 332 */ 333 private static String getAnnotationName(DetailAST annotation) { 334 DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT); 335 if (identNode == null) { 336 identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT); 337 } 338 return identNode.getText(); 339 } 340 341 /** 342 * Checks whether an annotation has a correct location. 343 * Annotation location is considered correct 344 * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true. 345 * The method also: 346 * 1) checks parameterized annotation location considering 347 * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation}; 348 * 2) checks parameterless annotation location considering 349 * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation}; 350 * 3) checks annotation location considering the elements 351 * of {@link AnnotationLocationCheck#SINGLELINE_ANNOTATION_PARENTS}; 352 * @param annotation annotation node. 353 * @param hasParams whether an annotation has parameters. 354 * @return true if the annotation has a correct location. 355 */ 356 private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) { 357 final boolean allowingCondition; 358 359 if (hasParams) { 360 allowingCondition = allowSamelineParameterizedAnnotation; 361 } 362 else { 363 allowingCondition = allowSamelineSingleParameterlessAnnotation; 364 } 365 return allowSamelineMultipleAnnotations 366 || allowingCondition && !hasNodeBefore(annotation) 367 || !allowingCondition && (!hasNodeBeside(annotation) 368 || isAllowedPosition(annotation, SINGLELINE_ANNOTATION_PARENTS)); 369 } 370 371 /** 372 * Checks whether an annotation node has any node before on the same line. 373 * @param annotation annotation node. 374 * @return true if an annotation node has any node before on the same line. 375 */ 376 private static boolean hasNodeBefore(DetailAST annotation) { 377 final int annotationLineNo = annotation.getLineNo(); 378 final DetailAST previousNode = annotation.getPreviousSibling(); 379 380 return previousNode != null && annotationLineNo == previousNode.getLineNo(); 381 } 382 383 /** 384 * Checks whether an annotation node has any node before or after on the same line. 385 * @param annotation annotation node. 386 * @return true if an annotation node has any node before or after on the same line. 387 */ 388 private static boolean hasNodeBeside(DetailAST annotation) { 389 return hasNodeBefore(annotation) || hasNodeAfter(annotation); 390 } 391 392 /** 393 * Checks whether an annotation node has any node after on the same line. 394 * @param annotation annotation node. 395 * @return true if an annotation node has any node after on the same line. 396 */ 397 private static boolean hasNodeAfter(DetailAST annotation) { 398 final int annotationLineNo = annotation.getLineNo(); 399 DetailAST nextNode = annotation.getNextSibling(); 400 401 if (nextNode == null) { 402 nextNode = annotation.getParent().getNextSibling(); 403 } 404 405 return annotationLineNo == nextNode.getLineNo(); 406 } 407 408 /** 409 * Checks whether position of annotation is allowed. 410 * @param annotation annotation token. 411 * @param allowedPositions an array of allowed annotation positions. 412 * @return true if position of annotation is allowed. 413 */ 414 private static boolean isAllowedPosition(DetailAST annotation, int... allowedPositions) { 415 boolean allowed = false; 416 for (int position : allowedPositions) { 417 if (isInSpecificCodeBlock(annotation, position)) { 418 allowed = true; 419 break; 420 } 421 } 422 return allowed; 423 } 424 425 /** 426 * Checks whether the scope of a node is restricted to a specific code block. 427 * @param node node. 428 * @param blockType block type. 429 * @return true if the scope of a node is restricted to a specific code block. 430 */ 431 private static boolean isInSpecificCodeBlock(DetailAST node, int blockType) { 432 boolean returnValue = false; 433 for (DetailAST token = node.getParent(); token != null; token = token.getParent()) { 434 final int type = token.getType(); 435 if (type == blockType) { 436 returnValue = true; 437 break; 438 } 439 } 440 return returnValue; 441 } 442 443}