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