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