1 //////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code for adherence to a set of rules. 3 // Copyright (C) 2001-2019 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 //////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.annotation; 21 22 import java.util.Locale; 23 24 import org.apache.commons.beanutils.ConversionException; 25 26 import com.puppycrawl.tools.checkstyle.StatelessCheck; 27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 28 import com.puppycrawl.tools.checkstyle.api.DetailAST; 29 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 30 31 /** 32 * <p> 33 * This check controls the style with the usage of annotations. 34 * </p> 35 * <p> 36 * Annotations have three element styles starting with the least verbose. 37 * </p> 38 * <ul> 39 * <li> 40 * {@code ElementStyle.COMPACT_NO_ARRAY} 41 * </li> 42 * <li> 43 * {@code ElementStyle.COMPACT} 44 * </li> 45 * <li> 46 * {@code ElementStyle.EXPANDED} 47 * </li> 48 * </ul> 49 * <p> 50 * To not enforce an element style a {@code ElementStyle.IGNORE} type is provided. 51 * The desired style can be set through the {@code elementStyle} property. 52 * </p> 53 * <p> 54 * Using the {@code ElementStyle.EXPANDED} style is more verbose. 55 * The expanded version is sometimes referred to as "named parameters" in other languages. 56 * </p> 57 * <p> 58 * Using the {@code ElementStyle.COMPACT} style is less verbose. 59 * This style can only be used when there is an element called 'value' which is either 60 * the sole element or all other elements have default values. 61 * </p> 62 * <p> 63 * Using the {@code ElementStyle.COMPACT_NO_ARRAY} style is less verbose. 64 * It is similar to the {@code ElementStyle.COMPACT} style but single value arrays are flagged. 65 * With annotations a single value array does not need to be placed in an array initializer. 66 * This style can only be used when there is an element called 'value' which is either 67 * the sole element or all other elements have default values. 68 * </p> 69 * <p> 70 * The ending parenthesis are optional when using annotations with no elements. 71 * To always require ending parenthesis use the {@code ClosingParens.ALWAYS} type. 72 * To never have ending parenthesis use the {@code ClosingParens.NEVER} type. 73 * To not enforce a closing parenthesis preference a {@code ClosingParens.IGNORE} type is provided. 74 * Set this through the {@code closingParens} property. 75 * </p> 76 * <p> 77 * Annotations also allow you to specify arrays of elements in a standard format. 78 * As with normal arrays, a trailing comma is optional. 79 * To always require a trailing comma use the {@code TrailingArrayComma.ALWAYS} type. 80 * To never have a trailing comma use the {@code TrailingArrayComma.NEVER} type. 81 * To not enforce a trailing array comma preference a {@code TrailingArrayComma.IGNORE} type 82 * is provided. Set this through the {@code trailingArrayComma} property. 83 * </p> 84 * <p> 85 * By default the {@code ElementStyle} is set to {@code COMPACT_NO_ARRAY}, 86 * the {@code TrailingArrayComma} is set to {@code NEVER}, 87 * and the {@code ClosingParens} is set to {@code NEVER}. 88 * </p> 89 * <p> 90 * According to the JLS, it is legal to include a trailing comma 91 * in arrays used in annotations but Sun's Java 5 & 6 compilers will not 92 * compile with this syntax. This may in be a bug in Sun's compilers 93 * since eclipse 3.4's built-in compiler does allow this syntax as 94 * defined in the JLS. Note: this was tested with compilers included with 95 * JDK versions 1.5.0.17 and 1.6.0.11 and the compiler included with eclipse 3.4.1. 96 * </p> 97 * <p> 98 * See <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-9.html#jls-9.7"> 99 * Java Language specification, §9.7</a>. 100 * </p> 101 * <ul> 102 * <li> 103 * Property {@code elementStyle} - Define the annotation element styles. 104 * Default value is {@code compact_no_array}. 105 * </li> 106 * <li> 107 * Property {@code closingParens} - Define the policy for ending parenthesis. 108 * Default value is {@code never}. 109 * </li> 110 * <li> 111 * Property {@code trailingArrayComma} - Define the policy for trailing comma in arrays. 112 * Default value is {@code never}. 113 * </li> 114 * </ul> 115 * <p> 116 * To configure the check: 117 * </p> 118 * <pre> 119 * <module name="AnnotationUseStyle"/> 120 * </pre> 121 * <p> 122 * To configure the check to enforce an {@code expanded} style, 123 * with a trailing array comma set to {@code never} 124 * and always including the closing parenthesis. 125 * </p> 126 * <pre> 127 * <module name="AnnotationUseStyle"> 128 * <property name="elementStyle" value="expanded"/> 129 * <property name="trailingArrayComma" value="never"/> 130 * <property name="closingParens" value="always"/> 131 * </module> 132 * </pre> 133 * 134 * @since 5.0 135 * 136 */ 137 @StatelessCheck 138 public final class AnnotationUseStyleCheck extends AbstractCheck { 139 140 /** 141 * Defines the styles for defining elements in an annotation. 142 */ 143 public enum ElementStyle { 144 145 /** 146 * Expanded example 147 * 148 * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>. 149 */ 150 EXPANDED, 151 152 /** 153 * Compact example 154 * 155 * <pre>@SuppressWarnings({"unchecked","unused",})</pre> 156 * <br>or<br> 157 * <pre>@SuppressWarnings("unchecked")</pre>. 158 */ 159 COMPACT, 160 161 /** 162 * Compact example.] 163 * 164 * <pre>@SuppressWarnings("unchecked")</pre>. 165 */ 166 COMPACT_NO_ARRAY, 167 168 /** 169 * Mixed styles. 170 */ 171 IGNORE, 172 173 } 174 175 /** 176 * Defines the two styles for defining 177 * elements in an annotation. 178 * 179 */ 180 public enum TrailingArrayComma { 181 182 /** 183 * With comma example 184 * 185 * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>. 186 */ 187 ALWAYS, 188 189 /** 190 * Without comma example 191 * 192 * <pre>@SuppressWarnings(value={"unchecked","unused"})</pre>. 193 */ 194 NEVER, 195 196 /** 197 * Mixed styles. 198 */ 199 IGNORE, 200 201 } 202 203 /** 204 * Defines the two styles for defining 205 * elements in an annotation. 206 * 207 */ 208 public enum ClosingParens { 209 210 /** 211 * With parens example 212 * 213 * <pre>@Deprecated()</pre>. 214 */ 215 ALWAYS, 216 217 /** 218 * Without parens example 219 * 220 * <pre>@Deprecated</pre>. 221 */ 222 NEVER, 223 224 /** 225 * Mixed styles. 226 */ 227 IGNORE, 228 229 } 230 231 /** 232 * A key is pointing to the warning message text in "messages.properties" 233 * file. 234 */ 235 public static final String MSG_KEY_ANNOTATION_INCORRECT_STYLE = 236 "annotation.incorrect.style"; 237 238 /** 239 * A key is pointing to the warning message text in "messages.properties" 240 * file. 241 */ 242 public static final String MSG_KEY_ANNOTATION_PARENS_MISSING = 243 "annotation.parens.missing"; 244 245 /** 246 * A key is pointing to the warning message text in "messages.properties" 247 * file. 248 */ 249 public static final String MSG_KEY_ANNOTATION_PARENS_PRESENT = 250 "annotation.parens.present"; 251 252 /** 253 * A key is pointing to the warning message text in "messages.properties" 254 * file. 255 */ 256 public static final String MSG_KEY_ANNOTATION_TRAILING_COMMA_MISSING = 257 "annotation.trailing.comma.missing"; 258 259 /** 260 * A key is pointing to the warning message text in "messages.properties" 261 * file. 262 */ 263 public static final String MSG_KEY_ANNOTATION_TRAILING_COMMA_PRESENT = 264 "annotation.trailing.comma.present"; 265 266 /** 267 * The element name used to receive special linguistic support 268 * for annotation use. 269 */ 270 private static final String ANNOTATION_ELEMENT_SINGLE_NAME = 271 "value"; 272 273 /** 274 * Define the annotation element styles. 275 */ 276 private ElementStyle elementStyle = ElementStyle.COMPACT_NO_ARRAY; 277 278 //defaulting to NEVER because of the strange compiler behavior 279 /** 280 * Define the policy for trailing comma in arrays. 281 */ 282 private TrailingArrayComma trailingArrayComma = TrailingArrayComma.NEVER; 283 284 /** 285 * Define the policy for ending parenthesis. 286 */ 287 private ClosingParens closingParens = ClosingParens.NEVER; 288 289 /** 290 * Setter to define the annotation element styles. 291 * 292 * @param style string representation 293 * @throws ConversionException if cannot convert string. 294 */ 295 public void setElementStyle(final String style) { 296 elementStyle = getOption(ElementStyle.class, style); 297 } 298 299 /** 300 * Setter to define the policy for trailing comma in arrays. 301 * 302 * @param comma string representation 303 * @throws ConversionException if cannot convert string. 304 */ 305 public void setTrailingArrayComma(final String comma) { 306 trailingArrayComma = getOption(TrailingArrayComma.class, comma); 307 } 308 309 /** 310 * Setter to define the policy for ending parenthesis. 311 * 312 * @param parens string representation 313 * @throws ConversionException if cannot convert string. 314 */ 315 public void setClosingParens(final String parens) { 316 closingParens = getOption(ClosingParens.class, parens); 317 } 318 319 /** 320 * Retrieves an {@link Enum Enum} type from a @{link String String}. 321 * @param <T> the enum type 322 * @param enumClass the enum class 323 * @param value the string representing the enum 324 * @return the enum type 325 */ 326 private static <T extends Enum<T>> T getOption(final Class<T> enumClass, 327 final String value) { 328 try { 329 return Enum.valueOf(enumClass, value.trim().toUpperCase(Locale.ENGLISH)); 330 } 331 catch (final IllegalArgumentException iae) { 332 throw new IllegalArgumentException("unable to parse " + value, iae); 333 } 334 } 335 336 @Override 337 public int[] getDefaultTokens() { 338 return getRequiredTokens(); 339 } 340 341 @Override 342 public int[] getRequiredTokens() { 343 return new int[] { 344 TokenTypes.ANNOTATION, 345 }; 346 } 347 348 @Override 349 public int[] getAcceptableTokens() { 350 return getRequiredTokens(); 351 } 352 353 @Override 354 public void visitToken(final DetailAST ast) { 355 checkStyleType(ast); 356 checkCheckClosingParens(ast); 357 checkTrailingComma(ast); 358 } 359 360 /** 361 * Checks to see if the 362 * {@link ElementStyle AnnotationElementStyle} 363 * is correct. 364 * 365 * @param annotation the annotation token 366 */ 367 private void checkStyleType(final DetailAST annotation) { 368 switch (elementStyle) { 369 case COMPACT_NO_ARRAY: 370 checkCompactNoArrayStyle(annotation); 371 break; 372 case COMPACT: 373 checkCompactStyle(annotation); 374 break; 375 case EXPANDED: 376 checkExpandedStyle(annotation); 377 break; 378 case IGNORE: 379 default: 380 break; 381 } 382 } 383 384 /** 385 * Checks for expanded style type violations. 386 * 387 * @param annotation the annotation token 388 */ 389 private void checkExpandedStyle(final DetailAST annotation) { 390 final int valuePairCount = 391 annotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 392 393 if (valuePairCount == 0 394 && annotation.branchContains(TokenTypes.EXPR)) { 395 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE, 396 ElementStyle.EXPANDED); 397 } 398 } 399 400 /** 401 * Checks for compact style type violations. 402 * 403 * @param annotation the annotation token 404 */ 405 private void checkCompactStyle(final DetailAST annotation) { 406 final int valuePairCount = 407 annotation.getChildCount( 408 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 409 410 final DetailAST valuePair = 411 annotation.findFirstToken( 412 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 413 414 if (valuePairCount == 1 415 && ANNOTATION_ELEMENT_SINGLE_NAME.equals( 416 valuePair.getFirstChild().getText())) { 417 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE, 418 ElementStyle.COMPACT); 419 } 420 } 421 422 /** 423 * Checks for compact no array style type violations. 424 * 425 * @param annotation the annotation token 426 */ 427 private void checkCompactNoArrayStyle(final DetailAST annotation) { 428 final DetailAST arrayInit = 429 annotation.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 430 431 final int valuePairCount = 432 annotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 433 434 //in compact style with one value 435 if (arrayInit != null 436 && arrayInit.getChildCount(TokenTypes.EXPR) == 1) { 437 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE, 438 ElementStyle.COMPACT_NO_ARRAY); 439 } 440 //in expanded style with one value and the correct element name 441 else if (valuePairCount == 1) { 442 final DetailAST valuePair = 443 annotation.findFirstToken(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 444 final DetailAST nestedArrayInit = 445 valuePair.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 446 447 if (nestedArrayInit != null 448 && ANNOTATION_ELEMENT_SINGLE_NAME.equals( 449 valuePair.getFirstChild().getText()) 450 && nestedArrayInit.getChildCount(TokenTypes.EXPR) == 1) { 451 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE, 452 ElementStyle.COMPACT_NO_ARRAY); 453 } 454 } 455 } 456 457 /** 458 * Checks to see if the trailing comma is present if required or 459 * prohibited. 460 * 461 * @param annotation the annotation token 462 */ 463 private void checkTrailingComma(final DetailAST annotation) { 464 if (trailingArrayComma != TrailingArrayComma.IGNORE) { 465 DetailAST child = annotation.getFirstChild(); 466 467 while (child != null) { 468 DetailAST arrayInit = null; 469 470 if (child.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { 471 arrayInit = child.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 472 } 473 else if (child.getType() == TokenTypes.ANNOTATION_ARRAY_INIT) { 474 arrayInit = child; 475 } 476 477 if (arrayInit != null) { 478 logCommaViolation(arrayInit); 479 } 480 child = child.getNextSibling(); 481 } 482 } 483 } 484 485 /** 486 * Logs a trailing array comma violation if one exists. 487 * 488 * @param ast the array init 489 * {@link TokenTypes#ANNOTATION_ARRAY_INIT ANNOTATION_ARRAY_INIT}. 490 */ 491 private void logCommaViolation(final DetailAST ast) { 492 final DetailAST rCurly = ast.findFirstToken(TokenTypes.RCURLY); 493 494 //comma can be null if array is empty 495 final DetailAST comma = rCurly.getPreviousSibling(); 496 497 if (trailingArrayComma == TrailingArrayComma.ALWAYS) { 498 if (comma == null || comma.getType() != TokenTypes.COMMA) { 499 log(rCurly, MSG_KEY_ANNOTATION_TRAILING_COMMA_MISSING); 500 } 501 } 502 else if (comma != null && comma.getType() == TokenTypes.COMMA) { 503 log(comma, MSG_KEY_ANNOTATION_TRAILING_COMMA_PRESENT); 504 } 505 } 506 507 /** 508 * Checks to see if the closing parenthesis are present if required or 509 * prohibited. 510 * 511 * @param ast the annotation token 512 */ 513 private void checkCheckClosingParens(final DetailAST ast) { 514 if (closingParens != ClosingParens.IGNORE) { 515 final DetailAST paren = ast.getLastChild(); 516 final boolean parenExists = paren.getType() == TokenTypes.RPAREN; 517 518 if (closingParens == ClosingParens.ALWAYS) { 519 if (!parenExists) { 520 log(ast.getLineNo(), MSG_KEY_ANNOTATION_PARENS_MISSING); 521 } 522 } 523 else if (parenExists 524 && !ast.branchContains(TokenTypes.EXPR) 525 && !ast.branchContains(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) 526 && !ast.branchContains(TokenTypes.ANNOTATION_ARRAY_INIT)) { 527 log(ast.getLineNo(), MSG_KEY_ANNOTATION_PARENS_PRESENT); 528 } 529 } 530 } 531 532 }