001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2016 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 examin target of 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 * Check have following options: 054 * </p> 055 * <ul> 056 * <li>allowSamelineMultipleAnnotations - to allow annotation to be located on 057 * the same line as 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 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 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 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 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 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, getAnnotationLevel(modifiersNode)); 267 } 268 } 269 270 /** 271 * Some javadoc. 272 * @param modifierNode Some javadoc. 273 * @return Some javadoc. 274 */ 275 private static boolean hasAnnotations(DetailAST modifierNode) { 276 return modifierNode != null && modifierNode.findFirstToken(TokenTypes.ANNOTATION) != null; 277 } 278 279 /** 280 * Some javadoc. 281 * @param modifierNode Some javadoc. 282 * @return Some javadoc. 283 */ 284 private static int getAnnotationLevel(DetailAST modifierNode) { 285 return modifierNode.getParent().getColumnNo(); 286 } 287 288 /** 289 * Some javadoc. 290 * @param modifierNode Some javadoc. 291 * @param correctLevel Some javadoc. 292 */ 293 private void checkAnnotations(DetailAST modifierNode, int correctLevel) { 294 DetailAST annotation = modifierNode.getFirstChild(); 295 296 while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) { 297 final boolean hasParameters = isParameterized(annotation); 298 299 if (!isCorrectLocation(annotation, hasParameters)) { 300 log(annotation.getLineNo(), 301 MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation)); 302 } 303 else if (annotation.getColumnNo() != correctLevel && !hasNodeBefore(annotation)) { 304 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_LOCATION, 305 getAnnotationName(annotation), annotation.getColumnNo(), correctLevel); 306 } 307 annotation = annotation.getNextSibling(); 308 } 309 } 310 311 /** 312 * Some javadoc. 313 * @param annotation Some javadoc. 314 * @return Some javadoc. 315 */ 316 private static boolean isParameterized(DetailAST annotation) { 317 return annotation.findFirstToken(TokenTypes.EXPR) != null; 318 } 319 320 /** 321 * Some javadoc. 322 * @param annotation Some javadoc. 323 * @return Some javadoc. 324 */ 325 private static String getAnnotationName(DetailAST annotation) { 326 DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT); 327 if (identNode == null) { 328 identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT); 329 } 330 return identNode.getText(); 331 } 332 333 /** 334 * Some javadoc. 335 * @param annotation Some javadoc. 336 * @param hasParams Some javadoc. 337 * @return Some javadoc. 338 */ 339 private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) { 340 final boolean allowingCondition; 341 342 if (hasParams) { 343 allowingCondition = allowSamelineParameterizedAnnotation; 344 } 345 else { 346 allowingCondition = allowSamelineSingleParameterlessAnnotation; 347 } 348 return allowSamelineMultipleAnnotations 349 || allowingCondition && !hasNodeBefore(annotation) 350 || !allowingCondition && (!hasNodeBeside(annotation) 351 || isAllowedPosition(annotation, SINGLELINE_ANNOTATION_PARENTS)); 352 } 353 354 /** 355 * Some javadoc. 356 * @param annotation Some javadoc. 357 * @return Some javadoc. 358 */ 359 private static boolean hasNodeBefore(DetailAST annotation) { 360 final int annotationLineNo = annotation.getLineNo(); 361 final DetailAST previousNode = annotation.getPreviousSibling(); 362 363 return previousNode != null && annotationLineNo == previousNode.getLineNo(); 364 } 365 366 /** 367 * Some javadoc. 368 * @param annotation Some javadoc. 369 * @return Some javadoc. 370 */ 371 private static boolean hasNodeBeside(DetailAST annotation) { 372 return hasNodeBefore(annotation) || hasNodeAfter(annotation); 373 } 374 375 /** 376 * Some javadoc. 377 * @param annotation Some javadoc. 378 * @return Some javadoc. 379 */ 380 private static boolean hasNodeAfter(DetailAST annotation) { 381 final int annotationLineNo = annotation.getLineNo(); 382 DetailAST nextNode = annotation.getNextSibling(); 383 384 if (nextNode == null) { 385 nextNode = annotation.getParent().getNextSibling(); 386 } 387 388 return annotationLineNo == nextNode.getLineNo(); 389 } 390 391 /** 392 * Checks whether position of annotation is allowed. 393 * @param annotation annotation token. 394 * @param allowedPositions an array of allowed annotation positions. 395 * @return true if position of annotation is allowed. 396 */ 397 public static boolean isAllowedPosition(DetailAST annotation, int... allowedPositions) { 398 boolean allowed = false; 399 for (int position : allowedPositions) { 400 if (isInSpecificCodeBlock(annotation, position)) { 401 allowed = true; 402 break; 403 } 404 } 405 return allowed; 406 } 407 408 /** 409 * Checks whether the scope of a node is restricted to a specific code block. 410 * @param node node. 411 * @param blockType block type. 412 * @return true if the scope of a node is restricted to a specific code block. 413 */ 414 private static boolean isInSpecificCodeBlock(DetailAST node, int blockType) { 415 boolean returnValue = false; 416 for (DetailAST token = node.getParent(); token != null; token = token.getParent()) { 417 final int type = token.getType(); 418 if (type == blockType) { 419 returnValue = true; 420 break; 421 } 422 } 423 return returnValue; 424 } 425}