001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 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 java.util.Locale; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028 029/** 030 * <p> 031 * Checks the style of elements in annotations. 032 * </p> 033 * <p> 034 * Annotations have three element styles starting with the least verbose. 035 * </p> 036 * <ul> 037 * <li> 038 * {@code ElementStyleOption.COMPACT_NO_ARRAY} 039 * </li> 040 * <li> 041 * {@code ElementStyleOption.COMPACT} 042 * </li> 043 * <li> 044 * {@code ElementStyleOption.EXPANDED} 045 * </li> 046 * </ul> 047 * <p> 048 * To not enforce an element style a {@code ElementStyleOption.IGNORE} type is provided. 049 * The desired style can be set through the {@code elementStyle} property. 050 * </p> 051 * <p> 052 * Using the {@code ElementStyleOption.EXPANDED} style is more verbose. 053 * The expanded version is sometimes referred to as "named parameters" in other languages. 054 * </p> 055 * <p> 056 * Using the {@code ElementStyleOption.COMPACT} style is less verbose. 057 * This style can only be used when there is an element called 'value' which is either 058 * the sole element or all other elements have default values. 059 * </p> 060 * <p> 061 * Using the {@code ElementStyleOption.COMPACT_NO_ARRAY} style is less verbose. 062 * It is similar to the {@code ElementStyleOption.COMPACT} style but single value arrays are 063 * flagged. 064 * With annotations a single value array does not need to be placed in an array initializer. 065 * </p> 066 * <p> 067 * The ending parenthesis are optional when using annotations with no elements. 068 * To always require ending parenthesis use the {@code ClosingParensOption.ALWAYS} type. 069 * To never have ending parenthesis use the {@code ClosingParensOption.NEVER} type. 070 * To not enforce a closing parenthesis preference a {@code ClosingParensOption.IGNORE} type is 071 * provided. 072 * Set this through the {@code closingParens} property. 073 * </p> 074 * <p> 075 * Annotations also allow you to specify arrays of elements in a standard format. 076 * As with normal arrays, a trailing comma is optional. 077 * To always require a trailing comma use the {@code TrailingArrayCommaOption.ALWAYS} type. 078 * To never have a trailing comma use the {@code TrailingArrayCommaOption.NEVER} type. 079 * To not enforce a trailing array comma preference a {@code TrailingArrayCommaOption.IGNORE} type 080 * is provided. Set this through the {@code trailingArrayComma} property. 081 * </p> 082 * <p> 083 * By default, the {@code ElementStyleOption} is set to {@code COMPACT_NO_ARRAY}, 084 * the {@code TrailingArrayCommaOption} is set to {@code NEVER}, 085 * and the {@code ClosingParensOption} is set to {@code NEVER}. 086 * </p> 087 * <p> 088 * According to the JLS, it is legal to include a trailing comma 089 * in arrays used in annotations but Sun's Java 5 & 6 compilers will not 090 * compile with this syntax. This may in be a bug in Sun's compilers 091 * since eclipse 3.4's built-in compiler does allow this syntax as 092 * defined in the JLS. Note: this was tested with compilers included with 093 * JDK versions 1.5.0.17 and 1.6.0.11 and the compiler included with eclipse 3.4.1. 094 * </p> 095 * <p> 096 * See <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-9.html#jls-9.7"> 097 * Java Language specification, §9.7</a>. 098 * </p> 099 * <ul> 100 * <li> 101 * Property {@code elementStyle} - Define the annotation element styles. 102 * Type is {@code 103 * com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck$ElementStyleOption}. 104 * Default value is {@code compact_no_array}. 105 * </li> 106 * <li> 107 * Property {@code closingParens} - Define the policy for ending parenthesis. 108 * Type is {@code 109 * com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck$ClosingParensOption}. 110 * Default value is {@code never}. 111 * </li> 112 * <li> 113 * Property {@code trailingArrayComma} - Define the policy for trailing comma in arrays. 114 * Type is {@code 115 * com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck$TrailingArrayCommaOption}. 116 * Default value is {@code never}. 117 * </li> 118 * </ul> 119 * <p> 120 * To configure the check: 121 * </p> 122 * <pre> 123 * <module name="AnnotationUseStyle"/> 124 * </pre> 125 * <p> 126 * Example: 127 * </p> 128 * <pre> 129 * @SuppressWarnings("unchecked") // OK 130 * @Deprecated // OK 131 * @SomeArrays({"unchecked","unused"}) // OK 132 * public class TestOne 133 * { 134 * 135 * } 136 * 137 * @SuppressWarnings(value={"unchecked"}) // Violation - parameter 'value' shouldn't be used 138 * @Deprecated() // Violation - cannot have a closing parenthesis 139 * @SomeArrays(value={"unchecked","unused",}) // Violation - cannot have a trailing array comma 140 * class TestTwo { 141 * 142 * } 143 * </pre> 144 * <p> 145 * To configure the check to enforce an {@code expanded} style, 146 * with a closing parenthesis and a trailing array comma set to {@code never}. 147 * </p> 148 * <pre> 149 * <module name="AnnotationUseStyle"> 150 * <property name="elementStyle" value="expanded"/> 151 * <property name="closingParens" value="never"/> 152 * <property name="trailingArrayComma" value="never"/> 153 * </module> 154 * </pre> 155 * <p> 156 * Example: 157 * </p> 158 * <pre> 159 * @SuppressWarnings("unchecked") // Violation - parameters should be referenced 160 * @Deprecated // OK 161 * @SomeArrays({"unchecked","unused"}) // Violation - parameters should be referenced 162 * public class TestOne 163 * { 164 * 165 * } 166 * 167 * @SuppressWarnings(value={"unchecked"}) // OK 168 * @Deprecated() // Violation - cannot have a closing parenthesis 169 * @SomeArrays(value={"unchecked","unused",}) // Violation - cannot have a trailing array comma 170 * class TestTwo { 171 * 172 * } 173 * </pre> 174 * <p> 175 * To configure the check to enforce a {@code compact} style, 176 * with always including a closing parenthesis and ignoring a trailing array comma. 177 * </p> 178 * <pre> 179 * <module name="AnnotationUseStyle"> 180 * <property name="elementStyle" value="compact"/> 181 * <property name="closingParens" value="always"/> 182 * <property name="trailingArrayComma" value="ignore"/> 183 * </module> 184 * </pre> 185 * <p> 186 * Example: 187 * </p> 188 * <pre> 189 * @SuppressWarnings("unchecked") // OK 190 * @Deprecated // Violation - must have a closing parenthesis 191 * @SomeArrays({"unchecked","unused"}) // OK 192 * public class TestOne 193 * { 194 * 195 * } 196 * 197 * @SuppressWarnings(value={"unchecked"}) // Violation - parameter 'value' shouldn't be used 198 * @Deprecated() // OK 199 * @SomeArrays(value={"unchecked","unused",}) // Violation - parameter 'value' shouldn't be used 200 * class TestTwo { 201 * 202 * } 203 * </pre> 204 * <p> 205 * To configure the check to enforce a trailing array comma, 206 * with ignoring the elementStyle and a closing parenthesis. 207 * </p> 208 * <pre> 209 * <module name="AnnotationUseStyle"> 210 * <property name="elementStyle" value="ignore"/> 211 * <property name="closingParens" value="ignore"/> 212 * <property name="trailingArrayComma" value="always"/> 213 * </module> 214 * </pre> 215 * <p> 216 * Example: 217 * </p> 218 * <pre> 219 * @SuppressWarnings("unchecked") // OK 220 * @Deprecated // OK 221 * @SomeArrays({"unchecked","unused"}) // Violation - must have a trailing array comma 222 * public class TestOne 223 * { 224 * 225 * } 226 * 227 * @SuppressWarnings(value={"unchecked"}) // Violation - must have a trailing array comma 228 * @Deprecated() // OK 229 * @SomeArrays(value={"unchecked","unused",}) // OK 230 * class TestTwo { 231 * 232 * } 233 * </pre> 234 * <p> 235 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 236 * </p> 237 * <p> 238 * Violation Message Keys: 239 * </p> 240 * <ul> 241 * <li> 242 * {@code annotation.incorrect.style} 243 * </li> 244 * <li> 245 * {@code annotation.parens.missing} 246 * </li> 247 * <li> 248 * {@code annotation.parens.present} 249 * </li> 250 * <li> 251 * {@code annotation.trailing.comma.missing} 252 * </li> 253 * <li> 254 * {@code annotation.trailing.comma.present} 255 * </li> 256 * </ul> 257 * 258 * @since 5.0 259 * 260 */ 261@StatelessCheck 262public final class AnnotationUseStyleCheck extends AbstractCheck { 263 264 /** 265 * Defines the styles for defining elements in an annotation. 266 */ 267 public enum ElementStyleOption { 268 269 /** 270 * Expanded example 271 * 272 * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>. 273 */ 274 EXPANDED, 275 276 /** 277 * Compact example 278 * 279 * <pre>@SuppressWarnings({"unchecked","unused",})</pre> 280 * <br>or<br> 281 * <pre>@SuppressWarnings("unchecked")</pre>. 282 */ 283 COMPACT, 284 285 /** 286 * Compact example 287 * 288 * <pre>@SuppressWarnings("unchecked")</pre>. 289 */ 290 COMPACT_NO_ARRAY, 291 292 /** 293 * Mixed styles. 294 */ 295 IGNORE, 296 297 } 298 299 /** 300 * Defines the two styles for defining 301 * elements in an annotation. 302 * 303 */ 304 public enum TrailingArrayCommaOption { 305 306 /** 307 * With comma example 308 * 309 * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>. 310 */ 311 ALWAYS, 312 313 /** 314 * Without comma example 315 * 316 * <pre>@SuppressWarnings(value={"unchecked","unused"})</pre>. 317 */ 318 NEVER, 319 320 /** 321 * Mixed styles. 322 */ 323 IGNORE, 324 325 } 326 327 /** 328 * Defines the two styles for defining 329 * elements in an annotation. 330 * 331 */ 332 public enum ClosingParensOption { 333 334 /** 335 * With parens example 336 * 337 * <pre>@Deprecated()</pre>. 338 */ 339 ALWAYS, 340 341 /** 342 * Without parens example 343 * 344 * <pre>@Deprecated</pre>. 345 */ 346 NEVER, 347 348 /** 349 * Mixed styles. 350 */ 351 IGNORE, 352 353 } 354 355 /** 356 * A key is pointing to the warning message text in "messages.properties" 357 * file. 358 */ 359 public static final String MSG_KEY_ANNOTATION_INCORRECT_STYLE = 360 "annotation.incorrect.style"; 361 362 /** 363 * A key is pointing to the warning message text in "messages.properties" 364 * file. 365 */ 366 public static final String MSG_KEY_ANNOTATION_PARENS_MISSING = 367 "annotation.parens.missing"; 368 369 /** 370 * A key is pointing to the warning message text in "messages.properties" 371 * file. 372 */ 373 public static final String MSG_KEY_ANNOTATION_PARENS_PRESENT = 374 "annotation.parens.present"; 375 376 /** 377 * A key is pointing to the warning message text in "messages.properties" 378 * file. 379 */ 380 public static final String MSG_KEY_ANNOTATION_TRAILING_COMMA_MISSING = 381 "annotation.trailing.comma.missing"; 382 383 /** 384 * A key is pointing to the warning message text in "messages.properties" 385 * file. 386 */ 387 public static final String MSG_KEY_ANNOTATION_TRAILING_COMMA_PRESENT = 388 "annotation.trailing.comma.present"; 389 390 /** 391 * The element name used to receive special linguistic support 392 * for annotation use. 393 */ 394 private static final String ANNOTATION_ELEMENT_SINGLE_NAME = 395 "value"; 396 397 /** 398 * Define the annotation element styles. 399 */ 400 private ElementStyleOption elementStyle = ElementStyleOption.COMPACT_NO_ARRAY; 401 402 // defaulting to NEVER because of the strange compiler behavior 403 /** 404 * Define the policy for trailing comma in arrays. 405 */ 406 private TrailingArrayCommaOption trailingArrayComma = TrailingArrayCommaOption.NEVER; 407 408 /** 409 * Define the policy for ending parenthesis. 410 */ 411 private ClosingParensOption closingParens = ClosingParensOption.NEVER; 412 413 /** 414 * Setter to define the annotation element styles. 415 * 416 * @param style string representation 417 */ 418 public void setElementStyle(final String style) { 419 elementStyle = getOption(ElementStyleOption.class, style); 420 } 421 422 /** 423 * Setter to define the policy for trailing comma in arrays. 424 * 425 * @param comma string representation 426 */ 427 public void setTrailingArrayComma(final String comma) { 428 trailingArrayComma = getOption(TrailingArrayCommaOption.class, comma); 429 } 430 431 /** 432 * Setter to define the policy for ending parenthesis. 433 * 434 * @param parens string representation 435 */ 436 public void setClosingParens(final String parens) { 437 closingParens = getOption(ClosingParensOption.class, parens); 438 } 439 440 /** 441 * Retrieves an {@link Enum Enum} type from a @{link String String}. 442 * 443 * @param <T> the enum type 444 * @param enumClass the enum class 445 * @param value the string representing the enum 446 * @return the enum type 447 * @throws IllegalArgumentException when unable to parse value 448 */ 449 private static <T extends Enum<T>> T getOption(final Class<T> enumClass, 450 final String value) { 451 try { 452 return Enum.valueOf(enumClass, value.trim().toUpperCase(Locale.ENGLISH)); 453 } 454 catch (final IllegalArgumentException iae) { 455 throw new IllegalArgumentException("unable to parse " + value, iae); 456 } 457 } 458 459 @Override 460 public int[] getDefaultTokens() { 461 return getRequiredTokens(); 462 } 463 464 @Override 465 public int[] getRequiredTokens() { 466 return new int[] { 467 TokenTypes.ANNOTATION, 468 }; 469 } 470 471 @Override 472 public int[] getAcceptableTokens() { 473 return getRequiredTokens(); 474 } 475 476 @Override 477 public void visitToken(final DetailAST ast) { 478 checkStyleType(ast); 479 checkCheckClosingParensOption(ast); 480 checkTrailingComma(ast); 481 } 482 483 /** 484 * Checks to see if the 485 * {@link ElementStyleOption AnnotationElementStyleOption} 486 * is correct. 487 * 488 * @param annotation the annotation token 489 */ 490 private void checkStyleType(final DetailAST annotation) { 491 switch (elementStyle) { 492 case COMPACT_NO_ARRAY: 493 checkCompactNoArrayStyle(annotation); 494 break; 495 case COMPACT: 496 checkCompactStyle(annotation); 497 break; 498 case EXPANDED: 499 checkExpandedStyle(annotation); 500 break; 501 case IGNORE: 502 default: 503 break; 504 } 505 } 506 507 /** 508 * Checks for expanded style type violations. 509 * 510 * @param annotation the annotation token 511 */ 512 private void checkExpandedStyle(final DetailAST annotation) { 513 final int valuePairCount = 514 annotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 515 516 if (valuePairCount == 0 && hasArguments(annotation)) { 517 log(annotation, MSG_KEY_ANNOTATION_INCORRECT_STYLE, ElementStyleOption.EXPANDED); 518 } 519 } 520 521 /** 522 * Checks that annotation has arguments. 523 * 524 * @param annotation to check 525 * @return true if annotation has arguments, false otherwise 526 */ 527 private static boolean hasArguments(DetailAST annotation) { 528 final DetailAST firstToken = annotation.findFirstToken(TokenTypes.LPAREN); 529 return firstToken != null && firstToken.getNextSibling().getType() != TokenTypes.RPAREN; 530 } 531 532 /** 533 * Checks for compact style type violations. 534 * 535 * @param annotation the annotation token 536 */ 537 private void checkCompactStyle(final DetailAST annotation) { 538 final int valuePairCount = 539 annotation.getChildCount( 540 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 541 542 final DetailAST valuePair = 543 annotation.findFirstToken( 544 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 545 546 if (valuePairCount == 1 547 && ANNOTATION_ELEMENT_SINGLE_NAME.equals( 548 valuePair.getFirstChild().getText())) { 549 log(annotation, MSG_KEY_ANNOTATION_INCORRECT_STYLE, 550 ElementStyleOption.COMPACT); 551 } 552 } 553 554 /** 555 * Checks for compact no array style type violations. 556 * 557 * @param annotation the annotation token 558 */ 559 private void checkCompactNoArrayStyle(final DetailAST annotation) { 560 final DetailAST arrayInit = 561 annotation.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 562 563 // in compact style with one value 564 if (arrayInit != null 565 && arrayInit.getChildCount(TokenTypes.EXPR) == 1) { 566 log(annotation, MSG_KEY_ANNOTATION_INCORRECT_STYLE, 567 ElementStyleOption.COMPACT_NO_ARRAY); 568 } 569 // in expanded style with pairs 570 else { 571 DetailAST ast = annotation.getFirstChild(); 572 while (ast != null) { 573 final DetailAST nestedArrayInit = 574 ast.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 575 if (nestedArrayInit != null 576 && nestedArrayInit.getChildCount(TokenTypes.EXPR) == 1) { 577 log(annotation, MSG_KEY_ANNOTATION_INCORRECT_STYLE, 578 ElementStyleOption.COMPACT_NO_ARRAY); 579 } 580 ast = ast.getNextSibling(); 581 } 582 } 583 } 584 585 /** 586 * Checks to see if the trailing comma is present if required or 587 * prohibited. 588 * 589 * @param annotation the annotation token 590 */ 591 private void checkTrailingComma(final DetailAST annotation) { 592 if (trailingArrayComma != TrailingArrayCommaOption.IGNORE) { 593 DetailAST child = annotation.getFirstChild(); 594 595 while (child != null) { 596 DetailAST arrayInit = null; 597 598 if (child.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { 599 arrayInit = child.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 600 } 601 else if (child.getType() == TokenTypes.ANNOTATION_ARRAY_INIT) { 602 arrayInit = child; 603 } 604 605 if (arrayInit != null) { 606 logCommaViolation(arrayInit); 607 } 608 child = child.getNextSibling(); 609 } 610 } 611 } 612 613 /** 614 * Logs a trailing array comma violation if one exists. 615 * 616 * @param ast the array init 617 * {@link TokenTypes#ANNOTATION_ARRAY_INIT ANNOTATION_ARRAY_INIT}. 618 */ 619 private void logCommaViolation(final DetailAST ast) { 620 final DetailAST rCurly = ast.findFirstToken(TokenTypes.RCURLY); 621 622 // comma can be null if array is empty 623 final DetailAST comma = rCurly.getPreviousSibling(); 624 625 if (trailingArrayComma == TrailingArrayCommaOption.NEVER) { 626 if (comma != null && comma.getType() == TokenTypes.COMMA) { 627 log(comma, MSG_KEY_ANNOTATION_TRAILING_COMMA_PRESENT); 628 } 629 } 630 else if (comma == null || comma.getType() != TokenTypes.COMMA) { 631 log(rCurly, MSG_KEY_ANNOTATION_TRAILING_COMMA_MISSING); 632 } 633 } 634 635 /** 636 * Checks to see if the closing parenthesis are present if required or 637 * prohibited. 638 * 639 * @param ast the annotation token 640 */ 641 private void checkCheckClosingParensOption(final DetailAST ast) { 642 if (closingParens != ClosingParensOption.IGNORE) { 643 final DetailAST paren = ast.getLastChild(); 644 645 if (closingParens == ClosingParensOption.NEVER) { 646 if (paren.getPreviousSibling().getType() == TokenTypes.LPAREN) { 647 log(ast, MSG_KEY_ANNOTATION_PARENS_PRESENT); 648 } 649 } 650 else if (paren.getType() != TokenTypes.RPAREN) { 651 log(ast, MSG_KEY_ANNOTATION_PARENS_MISSING); 652 } 653 } 654 } 655 656}