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