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