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.CommonUtil; 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 */ 161@StatelessCheck 162public class AnnotationLocationCheck extends AbstractCheck { 163 164 /** 165 * A key is pointing to the warning message text in "messages.properties" 166 * file. 167 */ 168 public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone"; 169 170 /** 171 * A key is pointing to the warning message text in "messages.properties" 172 * file. 173 */ 174 public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location"; 175 176 /** Array of single line annotation parents. */ 177 private static final int[] SINGLELINE_ANNOTATION_PARENTS = {TokenTypes.FOR_EACH_CLAUSE, 178 TokenTypes.PARAMETER_DEF, 179 TokenTypes.FOR_INIT, }; 180 181 /** 182 * If true, it allows single prameterless annotation to be located on the same line as 183 * target element. 184 */ 185 private boolean allowSamelineSingleParameterlessAnnotation = true; 186 187 /** 188 * If true, it allows parameterized annotation to be located on the same line as 189 * target element. 190 */ 191 private boolean allowSamelineParameterizedAnnotation; 192 193 /** 194 * If true, it allows annotation to be located on the same line as 195 * target element. 196 */ 197 private boolean allowSamelineMultipleAnnotations; 198 199 /** 200 * Sets if allow same line single parameterless annotation. 201 * @param allow User's value of allowSamelineSingleParameterlessAnnotation. 202 */ 203 public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) { 204 allowSamelineSingleParameterlessAnnotation = allow; 205 } 206 207 /** 208 * Sets if allow parameterized annotation to be located on the same line as 209 * target element. 210 * @param allow User's value of allowSamelineParameterizedAnnotation. 211 */ 212 public final void setAllowSamelineParameterizedAnnotation(boolean allow) { 213 allowSamelineParameterizedAnnotation = allow; 214 } 215 216 /** 217 * Sets if allow annotation to be located on the same line as 218 * target element. 219 * @param allow User's value of allowSamelineMultipleAnnotations. 220 */ 221 public final void setAllowSamelineMultipleAnnotations(boolean allow) { 222 allowSamelineMultipleAnnotations = allow; 223 } 224 225 @Override 226 public int[] getDefaultTokens() { 227 return new int[] { 228 TokenTypes.CLASS_DEF, 229 TokenTypes.INTERFACE_DEF, 230 TokenTypes.ENUM_DEF, 231 TokenTypes.METHOD_DEF, 232 TokenTypes.CTOR_DEF, 233 TokenTypes.VARIABLE_DEF, 234 }; 235 } 236 237 @Override 238 public int[] getAcceptableTokens() { 239 return new int[] { 240 TokenTypes.CLASS_DEF, 241 TokenTypes.INTERFACE_DEF, 242 TokenTypes.ENUM_DEF, 243 TokenTypes.METHOD_DEF, 244 TokenTypes.CTOR_DEF, 245 TokenTypes.VARIABLE_DEF, 246 TokenTypes.PARAMETER_DEF, 247 TokenTypes.ANNOTATION_DEF, 248 TokenTypes.TYPECAST, 249 TokenTypes.LITERAL_THROWS, 250 TokenTypes.IMPLEMENTS_CLAUSE, 251 TokenTypes.TYPE_ARGUMENT, 252 TokenTypes.LITERAL_NEW, 253 TokenTypes.DOT, 254 TokenTypes.ANNOTATION_FIELD_DEF, 255 }; 256 } 257 258 @Override 259 public int[] getRequiredTokens() { 260 return CommonUtil.EMPTY_INT_ARRAY; 261 } 262 263 @Override 264 public void visitToken(DetailAST ast) { 265 final DetailAST modifiersNode = ast.findFirstToken(TokenTypes.MODIFIERS); 266 267 if (hasAnnotations(modifiersNode)) { 268 checkAnnotations(modifiersNode, getExpectedAnnotationIndentation(modifiersNode)); 269 } 270 } 271 272 /** 273 * Checks whether a given modifier node has an annotation. 274 * @param modifierNode modifier node. 275 * @return true if the given modifier node has the annotation. 276 */ 277 private static boolean hasAnnotations(DetailAST modifierNode) { 278 return modifierNode != null 279 && modifierNode.findFirstToken(TokenTypes.ANNOTATION) != null; 280 } 281 282 /** 283 * Returns an expected annotation indentation. 284 * The expected indentation should be the same as the indentation of the node 285 * which is the parent of the target modifier node. 286 * @param modifierNode modifier node. 287 * @return the annotation indentation. 288 */ 289 private static int getExpectedAnnotationIndentation(DetailAST modifierNode) { 290 return modifierNode.getParent().getColumnNo(); 291 } 292 293 /** 294 * Checks annotations positions in code: 295 * 1) Checks whether the annotations locations are correct. 296 * 2) Checks whether the annotations have the valid indentation level. 297 * @param modifierNode modifiers node. 298 * @param correctIndentation correct indentation of the annotation. 299 */ 300 private void checkAnnotations(DetailAST modifierNode, int correctIndentation) { 301 DetailAST annotation = modifierNode.getFirstChild(); 302 303 while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) { 304 final boolean hasParameters = isParameterized(annotation); 305 306 if (!isCorrectLocation(annotation, hasParameters)) { 307 log(annotation.getLineNo(), 308 MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation)); 309 } 310 else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) { 311 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_LOCATION, 312 getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation); 313 } 314 annotation = annotation.getNextSibling(); 315 } 316 } 317 318 /** 319 * Checks whether an annotation has parameters. 320 * @param annotation annotation node. 321 * @return true if the annotation has parameters. 322 */ 323 private static boolean isParameterized(DetailAST annotation) { 324 return annotation.findFirstToken(TokenTypes.EXPR) != null; 325 } 326 327 /** 328 * Returns the name of the given annotation. 329 * @param annotation annotation node. 330 * @return annotation name. 331 */ 332 private static String getAnnotationName(DetailAST annotation) { 333 DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT); 334 if (identNode == null) { 335 identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT); 336 } 337 return identNode.getText(); 338 } 339 340 /** 341 * Checks whether an annotation has a correct location. 342 * Annotation location is considered correct 343 * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true. 344 * The method also: 345 * 1) checks parameterized annotation location considering 346 * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation}; 347 * 2) checks parameterless annotation location considering 348 * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation}; 349 * 3) checks annotation location considering the elements 350 * of {@link AnnotationLocationCheck#SINGLELINE_ANNOTATION_PARENTS}; 351 * @param annotation annotation node. 352 * @param hasParams whether an annotation has parameters. 353 * @return true if the annotation has a correct location. 354 */ 355 private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) { 356 final boolean allowingCondition; 357 358 if (hasParams) { 359 allowingCondition = allowSamelineParameterizedAnnotation; 360 } 361 else { 362 allowingCondition = allowSamelineSingleParameterlessAnnotation; 363 } 364 return allowSamelineMultipleAnnotations 365 || allowingCondition && !hasNodeBefore(annotation) 366 || !allowingCondition && (!hasNodeBeside(annotation) 367 || isAllowedPosition(annotation, SINGLELINE_ANNOTATION_PARENTS)); 368 } 369 370 /** 371 * Checks whether an annotation node has any node before on the same line. 372 * @param annotation annotation node. 373 * @return true if an annotation node has any node before on the same line. 374 */ 375 private static boolean hasNodeBefore(DetailAST annotation) { 376 final int annotationLineNo = annotation.getLineNo(); 377 final DetailAST previousNode = annotation.getPreviousSibling(); 378 379 return previousNode != null && annotationLineNo == previousNode.getLineNo(); 380 } 381 382 /** 383 * Checks whether an annotation node has any node before or after on the same line. 384 * @param annotation annotation node. 385 * @return true if an annotation node has any node before or after on the same line. 386 */ 387 private static boolean hasNodeBeside(DetailAST annotation) { 388 return hasNodeBefore(annotation) || hasNodeAfter(annotation); 389 } 390 391 /** 392 * Checks whether an annotation node has any node after on the same line. 393 * @param annotation annotation node. 394 * @return true if an annotation node has any node after on the same line. 395 */ 396 private static boolean hasNodeAfter(DetailAST annotation) { 397 final int annotationLineNo = annotation.getLineNo(); 398 DetailAST nextNode = annotation.getNextSibling(); 399 400 if (nextNode == null) { 401 nextNode = annotation.getParent().getNextSibling(); 402 } 403 404 return annotationLineNo == nextNode.getLineNo(); 405 } 406 407 /** 408 * Checks whether position of annotation is allowed. 409 * @param annotation annotation token. 410 * @param allowedPositions an array of allowed annotation positions. 411 * @return true if position of annotation is allowed. 412 */ 413 private static boolean isAllowedPosition(DetailAST annotation, int... allowedPositions) { 414 boolean allowed = false; 415 for (int position : allowedPositions) { 416 if (isInSpecificCodeBlock(annotation, position)) { 417 allowed = true; 418 break; 419 } 420 } 421 return allowed; 422 } 423 424 /** 425 * Checks whether the scope of a node is restricted to a specific code block. 426 * @param node node. 427 * @param blockType block type. 428 * @return true if the scope of a node is restricted to a specific code block. 429 */ 430 private static boolean isInSpecificCodeBlock(DetailAST node, int blockType) { 431 boolean returnValue = false; 432 for (DetailAST token = node.getParent(); token != null; token = token.getParent()) { 433 final int type = token.getType(); 434 if (type == blockType) { 435 returnValue = true; 436 break; 437 } 438 } 439 return returnValue; 440 } 441 442}