001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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 * Check location of annotation on language elements. 031 * By default, Check enforce to locate annotations immediately after 032 * documentation block and before target element, annotation should be located 033 * on separate line from target element. 034 * <p> 035 * Attention: Annotations among modifiers are ignored (looks like false-negative) 036 * as there might be a problem with annotations for return types. 037 * </p> 038 * <pre>public @Nullable Long getStartTimeOrNull() { ... }</pre>. 039 * <p> 040 * Such annotations are better to keep close to type. 041 * Due to limitations, Checkstyle can not examine the target of an annotation. 042 * </p> 043 * 044 * <p> 045 * Example: 046 * </p> 047 * 048 * <pre> 049 * @Override 050 * @Nullable 051 * public String getNameIfPresent() { ... } 052 * </pre> 053 * 054 * <p> 055 * The check has the following options: 056 * </p> 057 * <ul> 058 * <li>allowSamelineMultipleAnnotations - to allow annotation to be located on 059 * the same line as the target element. Default value is false. 060 * </li> 061 * 062 * <li> 063 * allowSamelineSingleParameterlessAnnotation - to allow single parameterless 064 * annotation to be located on the same line as the target element. Default value is false. 065 * </li> 066 * 067 * <li> 068 * allowSamelineParameterizedAnnotation - to allow parameterized annotation 069 * to be located on the same line as the target element. Default value is false. 070 * </li> 071 * </ul> 072 * <br> 073 * <p> 074 * Example to allow single parameterless annotation on the same line: 075 * </p> 076 * <pre> 077 * @Override public int hashCode() { ... } 078 * </pre> 079 * 080 * <p>Use the following configuration: 081 * <pre> 082 * <module name="AnnotationLocation"> 083 * <property name="allowSamelineMultipleAnnotations" value="false"/> 084 * <property name="allowSamelineSingleParameterlessAnnotation" 085 * value="true"/> 086 * <property name="allowSamelineParameterizedAnnotation" value="false" 087 * /> 088 * </module> 089 * </pre> 090 * <br> 091 * <p> 092 * Example to allow multiple parameterized annotations on the same line: 093 * </p> 094 * <pre> 095 * @SuppressWarnings("deprecation") @Mock DataLoader loader; 096 * </pre> 097 * 098 * <p>Use the following configuration: 099 * <pre> 100 * <module name="AnnotationLocation"> 101 * <property name="allowSamelineMultipleAnnotations" value="true"/> 102 * <property name="allowSamelineSingleParameterlessAnnotation" 103 * value="true"/> 104 * <property name="allowSamelineParameterizedAnnotation" value="true" 105 * /> 106 * </module> 107 * </pre> 108 * <br> 109 * <p> 110 * Example to allow multiple parameterless annotations on the same line: 111 * </p> 112 * <pre> 113 * @Partial @Mock DataLoader loader; 114 * </pre> 115 * 116 * <p>Use the following configuration: 117 * <pre> 118 * <module name="AnnotationLocation"> 119 * <property name="allowSamelineMultipleAnnotations" value="true"/> 120 * <property name="allowSamelineSingleParameterlessAnnotation" 121 * value="true"/> 122 * <property name="allowSamelineParameterizedAnnotation" value="false" 123 * /> 124 * </module> 125 * </pre> 126 * <br> 127 * <p> 128 * The following example demonstrates how the check validates annotation of method parameters, 129 * catch parameters, foreach, for-loop variable definitions. 130 * </p> 131 * 132 * <p>Configuration: 133 * <pre> 134 * <module name="AnnotationLocation"> 135 * <property name="allowSamelineMultipleAnnotations" value="false"/> 136 * <property name="allowSamelineSingleParameterlessAnnotation" 137 * value="false"/> 138 * <property name="allowSamelineParameterizedAnnotation" value="false" 139 * /> 140 * <property name="tokens" value="VARIABLE_DEF, PARAMETER_DEF"/> 141 * </module> 142 * </pre> 143 * 144 * <p>Code example 145 * {@code 146 * ... 147 * public void test(@MyAnnotation String s) { // OK 148 * ... 149 * for (@MyAnnotation char c : s.toCharArray()) { ... } // OK 150 * ... 151 * try { ... } 152 * catch (@MyAnnotation Exception ex) { ... } // OK 153 * ... 154 * for (@MyAnnotation int i = 0; i < 10; i++) { ... } // OK 155 * ... 156 * MathOperation c = (@MyAnnotation int a, @MyAnnotation int b) -> a + b; // OK 157 * ... 158 * } 159 * } 160 * 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.ANNOTATION_FIELD_DEF, 250 }; 251 } 252 253 @Override 254 public int[] getRequiredTokens() { 255 return CommonUtil.EMPTY_INT_ARRAY; 256 } 257 258 @Override 259 public void visitToken(DetailAST ast) { 260 final DetailAST modifiersNode = ast.findFirstToken(TokenTypes.MODIFIERS); 261 checkAnnotations(modifiersNode, getExpectedAnnotationIndentation(modifiersNode)); 262 } 263 264 /** 265 * Returns an expected annotation indentation. 266 * The expected indentation should be the same as the indentation of the node 267 * which is the parent of the target modifier node. 268 * @param modifierNode modifier node. 269 * @return the annotation indentation. 270 */ 271 private static int getExpectedAnnotationIndentation(DetailAST modifierNode) { 272 return modifierNode.getParent().getColumnNo(); 273 } 274 275 /** 276 * Checks annotations positions in code: 277 * 1) Checks whether the annotations locations are correct. 278 * 2) Checks whether the annotations have the valid indentation level. 279 * @param modifierNode modifiers node. 280 * @param correctIndentation correct indentation of the annotation. 281 */ 282 private void checkAnnotations(DetailAST modifierNode, int correctIndentation) { 283 DetailAST annotation = modifierNode.getFirstChild(); 284 285 while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) { 286 final boolean hasParameters = isParameterized(annotation); 287 288 if (!isCorrectLocation(annotation, hasParameters)) { 289 log(annotation.getLineNo(), 290 MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation)); 291 } 292 else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) { 293 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_LOCATION, 294 getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation); 295 } 296 annotation = annotation.getNextSibling(); 297 } 298 } 299 300 /** 301 * Checks whether an annotation has parameters. 302 * @param annotation annotation node. 303 * @return true if the annotation has parameters. 304 */ 305 private static boolean isParameterized(DetailAST annotation) { 306 return TokenUtil.findFirstTokenByPredicate(annotation, ast -> { 307 return ast.getType() == TokenTypes.EXPR 308 || ast.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR; 309 }).isPresent(); 310 } 311 312 /** 313 * Returns the name of the given annotation. 314 * @param annotation annotation node. 315 * @return annotation name. 316 */ 317 private static String getAnnotationName(DetailAST annotation) { 318 DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT); 319 if (identNode == null) { 320 identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT); 321 } 322 return identNode.getText(); 323 } 324 325 /** 326 * Checks whether an annotation has a correct location. 327 * Annotation location is considered correct 328 * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true. 329 * The method also: 330 * 1) checks parameterized annotation location considering 331 * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation}; 332 * 2) checks parameterless annotation location considering 333 * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation}; 334 * 3) checks annotation location considering the elements 335 * of {@link AnnotationLocationCheck#SINGLELINE_ANNOTATION_PARENTS}; 336 * @param annotation annotation node. 337 * @param hasParams whether an annotation has parameters. 338 * @return true if the annotation has a correct location. 339 */ 340 private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) { 341 final boolean allowingCondition; 342 343 if (hasParams) { 344 allowingCondition = allowSamelineParameterizedAnnotation; 345 } 346 else { 347 allowingCondition = allowSamelineSingleParameterlessAnnotation; 348 } 349 return allowSamelineMultipleAnnotations 350 || allowingCondition && !hasNodeBefore(annotation) 351 || !allowingCondition && (!hasNodeBeside(annotation) 352 || isAllowedPosition(annotation, SINGLELINE_ANNOTATION_PARENTS)); 353 } 354 355 /** 356 * Checks whether an annotation node has any node before on the same line. 357 * @param annotation annotation node. 358 * @return true if an annotation node has any node before on the same line. 359 */ 360 private static boolean hasNodeBefore(DetailAST annotation) { 361 final int annotationLineNo = annotation.getLineNo(); 362 final DetailAST previousNode = annotation.getPreviousSibling(); 363 364 return previousNode != null && annotationLineNo == previousNode.getLineNo(); 365 } 366 367 /** 368 * Checks whether an annotation node has any node before or after on the same line. 369 * @param annotation annotation node. 370 * @return true if an annotation node has any node before or after on the same line. 371 */ 372 private static boolean hasNodeBeside(DetailAST annotation) { 373 return hasNodeBefore(annotation) || hasNodeAfter(annotation); 374 } 375 376 /** 377 * Checks whether an annotation node has any node after on the same line. 378 * @param annotation annotation node. 379 * @return true if an annotation node has any node after on the same line. 380 */ 381 private static boolean hasNodeAfter(DetailAST annotation) { 382 final int annotationLineNo = annotation.getLineNo(); 383 DetailAST nextNode = annotation.getNextSibling(); 384 385 if (nextNode == null) { 386 nextNode = annotation.getParent().getNextSibling(); 387 } 388 389 return annotationLineNo == nextNode.getLineNo(); 390 } 391 392 /** 393 * Checks whether position of annotation is allowed. 394 * @param annotation annotation token. 395 * @param allowedPositions an array of allowed annotation positions. 396 * @return true if position of annotation is allowed. 397 */ 398 private static boolean isAllowedPosition(DetailAST annotation, int... allowedPositions) { 399 boolean allowed = false; 400 for (int position : allowedPositions) { 401 if (isInSpecificCodeBlock(annotation, position)) { 402 allowed = true; 403 break; 404 } 405 } 406 return allowed; 407 } 408 409 /** 410 * Checks whether the scope of a node is restricted to a specific code block. 411 * @param node node. 412 * @param blockType block type. 413 * @return true if the scope of a node is restricted to a specific code block. 414 */ 415 private static boolean isInSpecificCodeBlock(DetailAST node, int blockType) { 416 boolean returnValue = false; 417 for (DetailAST token = node.getParent(); token != null; token = token.getParent()) { 418 final int type = token.getType(); 419 if (type == blockType) { 420 returnValue = true; 421 break; 422 } 423 } 424 return returnValue; 425 } 426 427}