001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 * @Deprecated // OK 130 * @SomeArrays(pooches={DOGS.LEO}) // Violation - COMPACT_NO_ARRAY 131 * @SuppressWarnings({""}) // Violation - COMPACT_NO_ARRAY 132 * public class TestOne 133 * { 134 * 135 * } 136 * 137 * @SomeArrays(pooches={DOGS.LEO}, um={}, test={"bleh"}) // Violation - COMPACT_NO_ARRAY 138 * @SuppressWarnings("") // OK 139 * @Deprecated() // Violation - cannot have closing parenthesis 140 * class TestTwo { 141 * 142 * } 143 * </pre> 144 * <p> 145 * To configure the check to enforce an {@code expanded} style, 146 * with a trailing array comma set to {@code never} 147 * and always including the closing parenthesis. 148 * </p> 149 * <pre> 150 * <module name="AnnotationUseStyle"> 151 * <property name="elementStyle" value="expanded"/> 152 * <property name="trailingArrayComma" value="never"/> 153 * <property name="closingParens" value="always"/> 154 * </module> 155 * </pre> 156 * <p> 157 * Example: 158 * </p> 159 * <pre> 160 * @Deprecated // Violation - must have closing parenthesis 161 * @SomeArrays(pooches={DOGS.LEO}) // OK 162 * @SuppressWarnings({""}) // Violation - EXPANDED 163 * public class TestOne 164 * { 165 * 166 * } 167 * 168 * @SomeArrays(pooches={DOGS.LEO}, um={}, test={"bleh"}) // OK 169 * @SuppressWarnings("") // Violation - EXPANDED 170 * @Deprecated() // OK 171 * class TestTwo { 172 * 173 * } 174 * </pre> 175 * <p> 176 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 177 * </p> 178 * <p> 179 * Violation Message Keys: 180 * </p> 181 * <ul> 182 * <li> 183 * {@code annotation.incorrect.style} 184 * </li> 185 * <li> 186 * {@code annotation.parens.missing} 187 * </li> 188 * <li> 189 * {@code annotation.parens.present} 190 * </li> 191 * <li> 192 * {@code annotation.trailing.comma.missing} 193 * </li> 194 * <li> 195 * {@code annotation.trailing.comma.present} 196 * </li> 197 * </ul> 198 * 199 * @since 5.0 200 * 201 */ 202@StatelessCheck 203public final class AnnotationUseStyleCheck extends AbstractCheck { 204 205 /** 206 * Defines the styles for defining elements in an annotation. 207 */ 208 public enum ElementStyleOption { 209 210 /** 211 * Expanded example 212 * 213 * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>. 214 */ 215 EXPANDED, 216 217 /** 218 * Compact example 219 * 220 * <pre>@SuppressWarnings({"unchecked","unused",})</pre> 221 * <br>or<br> 222 * <pre>@SuppressWarnings("unchecked")</pre>. 223 */ 224 COMPACT, 225 226 /** 227 * Compact example 228 * 229 * <pre>@SuppressWarnings("unchecked")</pre>. 230 */ 231 COMPACT_NO_ARRAY, 232 233 /** 234 * Mixed styles. 235 */ 236 IGNORE, 237 238 } 239 240 /** 241 * Defines the two styles for defining 242 * elements in an annotation. 243 * 244 */ 245 public enum TrailingArrayCommaOption { 246 247 /** 248 * With comma example 249 * 250 * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>. 251 */ 252 ALWAYS, 253 254 /** 255 * Without comma example 256 * 257 * <pre>@SuppressWarnings(value={"unchecked","unused"})</pre>. 258 */ 259 NEVER, 260 261 /** 262 * Mixed styles. 263 */ 264 IGNORE, 265 266 } 267 268 /** 269 * Defines the two styles for defining 270 * elements in an annotation. 271 * 272 */ 273 public enum ClosingParensOption { 274 275 /** 276 * With parens example 277 * 278 * <pre>@Deprecated()</pre>. 279 */ 280 ALWAYS, 281 282 /** 283 * Without parens example 284 * 285 * <pre>@Deprecated</pre>. 286 */ 287 NEVER, 288 289 /** 290 * Mixed styles. 291 */ 292 IGNORE, 293 294 } 295 296 /** 297 * A key is pointing to the warning message text in "messages.properties" 298 * file. 299 */ 300 public static final String MSG_KEY_ANNOTATION_INCORRECT_STYLE = 301 "annotation.incorrect.style"; 302 303 /** 304 * A key is pointing to the warning message text in "messages.properties" 305 * file. 306 */ 307 public static final String MSG_KEY_ANNOTATION_PARENS_MISSING = 308 "annotation.parens.missing"; 309 310 /** 311 * A key is pointing to the warning message text in "messages.properties" 312 * file. 313 */ 314 public static final String MSG_KEY_ANNOTATION_PARENS_PRESENT = 315 "annotation.parens.present"; 316 317 /** 318 * A key is pointing to the warning message text in "messages.properties" 319 * file. 320 */ 321 public static final String MSG_KEY_ANNOTATION_TRAILING_COMMA_MISSING = 322 "annotation.trailing.comma.missing"; 323 324 /** 325 * A key is pointing to the warning message text in "messages.properties" 326 * file. 327 */ 328 public static final String MSG_KEY_ANNOTATION_TRAILING_COMMA_PRESENT = 329 "annotation.trailing.comma.present"; 330 331 /** 332 * The element name used to receive special linguistic support 333 * for annotation use. 334 */ 335 private static final String ANNOTATION_ELEMENT_SINGLE_NAME = 336 "value"; 337 338 /** 339 * Define the annotation element styles. 340 */ 341 private ElementStyleOption elementStyle = ElementStyleOption.COMPACT_NO_ARRAY; 342 343 // defaulting to NEVER because of the strange compiler behavior 344 /** 345 * Define the policy for trailing comma in arrays. 346 */ 347 private TrailingArrayCommaOption trailingArrayComma = TrailingArrayCommaOption.NEVER; 348 349 /** 350 * Define the policy for ending parenthesis. 351 */ 352 private ClosingParensOption closingParens = ClosingParensOption.NEVER; 353 354 /** 355 * Setter to define the annotation element styles. 356 * 357 * @param style string representation 358 */ 359 public void setElementStyle(final String style) { 360 elementStyle = getOption(ElementStyleOption.class, style); 361 } 362 363 /** 364 * Setter to define the policy for trailing comma in arrays. 365 * 366 * @param comma string representation 367 */ 368 public void setTrailingArrayComma(final String comma) { 369 trailingArrayComma = getOption(TrailingArrayCommaOption.class, comma); 370 } 371 372 /** 373 * Setter to define the policy for ending parenthesis. 374 * 375 * @param parens string representation 376 */ 377 public void setClosingParens(final String parens) { 378 closingParens = getOption(ClosingParensOption.class, parens); 379 } 380 381 /** 382 * Retrieves an {@link Enum Enum} type from a @{link String String}. 383 * 384 * @param <T> the enum type 385 * @param enumClass the enum class 386 * @param value the string representing the enum 387 * @return the enum type 388 * @throws IllegalArgumentException when unable to parse value 389 */ 390 private static <T extends Enum<T>> T getOption(final Class<T> enumClass, 391 final String value) { 392 try { 393 return Enum.valueOf(enumClass, value.trim().toUpperCase(Locale.ENGLISH)); 394 } 395 catch (final IllegalArgumentException iae) { 396 throw new IllegalArgumentException("unable to parse " + value, iae); 397 } 398 } 399 400 @Override 401 public int[] getDefaultTokens() { 402 return getRequiredTokens(); 403 } 404 405 @Override 406 public int[] getRequiredTokens() { 407 return new int[] { 408 TokenTypes.ANNOTATION, 409 }; 410 } 411 412 @Override 413 public int[] getAcceptableTokens() { 414 return getRequiredTokens(); 415 } 416 417 @Override 418 public void visitToken(final DetailAST ast) { 419 checkStyleType(ast); 420 checkCheckClosingParensOption(ast); 421 checkTrailingComma(ast); 422 } 423 424 /** 425 * Checks to see if the 426 * {@link ElementStyleOption AnnotationElementStyleOption} 427 * is correct. 428 * 429 * @param annotation the annotation token 430 */ 431 private void checkStyleType(final DetailAST annotation) { 432 switch (elementStyle) { 433 case COMPACT_NO_ARRAY: 434 checkCompactNoArrayStyle(annotation); 435 break; 436 case COMPACT: 437 checkCompactStyle(annotation); 438 break; 439 case EXPANDED: 440 checkExpandedStyle(annotation); 441 break; 442 case IGNORE: 443 default: 444 break; 445 } 446 } 447 448 /** 449 * Checks for expanded style type violations. 450 * 451 * @param annotation the annotation token 452 */ 453 private void checkExpandedStyle(final DetailAST annotation) { 454 final int valuePairCount = 455 annotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 456 457 if (valuePairCount == 0 && hasArguments(annotation)) { 458 log(annotation, MSG_KEY_ANNOTATION_INCORRECT_STYLE, ElementStyleOption.EXPANDED); 459 } 460 } 461 462 /** 463 * Checks that annotation has arguments. 464 * 465 * @param annotation to check 466 * @return true if annotation has arguments, false otherwise 467 */ 468 private static boolean hasArguments(DetailAST annotation) { 469 final DetailAST firstToken = annotation.findFirstToken(TokenTypes.LPAREN); 470 return firstToken != null && firstToken.getNextSibling().getType() != TokenTypes.RPAREN; 471 } 472 473 /** 474 * Checks for compact style type violations. 475 * 476 * @param annotation the annotation token 477 */ 478 private void checkCompactStyle(final DetailAST annotation) { 479 final int valuePairCount = 480 annotation.getChildCount( 481 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 482 483 final DetailAST valuePair = 484 annotation.findFirstToken( 485 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 486 487 if (valuePairCount == 1 488 && ANNOTATION_ELEMENT_SINGLE_NAME.equals( 489 valuePair.getFirstChild().getText())) { 490 log(annotation, MSG_KEY_ANNOTATION_INCORRECT_STYLE, 491 ElementStyleOption.COMPACT); 492 } 493 } 494 495 /** 496 * Checks for compact no array style type violations. 497 * 498 * @param annotation the annotation token 499 */ 500 private void checkCompactNoArrayStyle(final DetailAST annotation) { 501 final DetailAST arrayInit = 502 annotation.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 503 504 // in compact style with one value 505 if (arrayInit != null 506 && arrayInit.getChildCount(TokenTypes.EXPR) == 1) { 507 log(annotation, MSG_KEY_ANNOTATION_INCORRECT_STYLE, 508 ElementStyleOption.COMPACT_NO_ARRAY); 509 } 510 // in expanded style with pairs 511 else { 512 DetailAST ast = annotation.getFirstChild(); 513 while (ast != null) { 514 final DetailAST nestedArrayInit = 515 ast.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 516 if (nestedArrayInit != null 517 && nestedArrayInit.getChildCount(TokenTypes.EXPR) == 1) { 518 log(annotation, MSG_KEY_ANNOTATION_INCORRECT_STYLE, 519 ElementStyleOption.COMPACT_NO_ARRAY); 520 } 521 ast = ast.getNextSibling(); 522 } 523 } 524 } 525 526 /** 527 * Checks to see if the trailing comma is present if required or 528 * prohibited. 529 * 530 * @param annotation the annotation token 531 */ 532 private void checkTrailingComma(final DetailAST annotation) { 533 if (trailingArrayComma != TrailingArrayCommaOption.IGNORE) { 534 DetailAST child = annotation.getFirstChild(); 535 536 while (child != null) { 537 DetailAST arrayInit = null; 538 539 if (child.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { 540 arrayInit = child.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 541 } 542 else if (child.getType() == TokenTypes.ANNOTATION_ARRAY_INIT) { 543 arrayInit = child; 544 } 545 546 if (arrayInit != null) { 547 logCommaViolation(arrayInit); 548 } 549 child = child.getNextSibling(); 550 } 551 } 552 } 553 554 /** 555 * Logs a trailing array comma violation if one exists. 556 * 557 * @param ast the array init 558 * {@link TokenTypes#ANNOTATION_ARRAY_INIT ANNOTATION_ARRAY_INIT}. 559 */ 560 private void logCommaViolation(final DetailAST ast) { 561 final DetailAST rCurly = ast.findFirstToken(TokenTypes.RCURLY); 562 563 // comma can be null if array is empty 564 final DetailAST comma = rCurly.getPreviousSibling(); 565 566 if (trailingArrayComma == TrailingArrayCommaOption.ALWAYS) { 567 if (comma == null || comma.getType() != TokenTypes.COMMA) { 568 log(rCurly, MSG_KEY_ANNOTATION_TRAILING_COMMA_MISSING); 569 } 570 } 571 else if (comma != null && comma.getType() == TokenTypes.COMMA) { 572 log(comma, MSG_KEY_ANNOTATION_TRAILING_COMMA_PRESENT); 573 } 574 } 575 576 /** 577 * Checks to see if the closing parenthesis are present if required or 578 * prohibited. 579 * 580 * @param ast the annotation token 581 */ 582 private void checkCheckClosingParensOption(final DetailAST ast) { 583 if (closingParens != ClosingParensOption.IGNORE) { 584 final DetailAST paren = ast.getLastChild(); 585 586 if (closingParens == ClosingParensOption.ALWAYS) { 587 if (paren.getType() != TokenTypes.RPAREN) { 588 log(ast, MSG_KEY_ANNOTATION_PARENS_MISSING); 589 } 590 } 591 else if (paren.getPreviousSibling().getType() == TokenTypes.LPAREN) { 592 log(ast, MSG_KEY_ANNOTATION_PARENS_PRESENT); 593 } 594 } 595 } 596 597}