001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 * <div> 031 * Checks the style of elements in annotations. 032 * </div> 033 * 034 * <p> 035 * Annotations have three element styles starting with the least verbose. 036 * </p> 037 * <ul> 038 * <li> 039 * {@code ElementStyleOption.COMPACT_NO_ARRAY} 040 * </li> 041 * <li> 042 * {@code ElementStyleOption.COMPACT} 043 * </li> 044 * <li> 045 * {@code ElementStyleOption.EXPANDED} 046 * </li> 047 * </ul> 048 * 049 * <p> 050 * To not enforce an element style a {@code ElementStyleOption.IGNORE} type is provided. 051 * The desired style can be set through the {@code elementStyle} property. 052 * </p> 053 * 054 * <p> 055 * Using the {@code ElementStyleOption.EXPANDED} style is more verbose. 056 * The expanded version is sometimes referred to as "named parameters" in other languages. 057 * </p> 058 * 059 * <p> 060 * Using the {@code ElementStyleOption.COMPACT} style is less verbose. 061 * This style can only be used when there is an element called 'value' which is either 062 * the sole element or all other elements have default values. 063 * </p> 064 * 065 * <p> 066 * Using the {@code ElementStyleOption.COMPACT_NO_ARRAY} style is less verbose. 067 * It is similar to the {@code ElementStyleOption.COMPACT} style but single value arrays are 068 * flagged. 069 * With annotations a single value array does not need to be placed in an array initializer. 070 * </p> 071 * 072 * <p> 073 * The ending parenthesis are optional when using annotations with no elements. 074 * To always require ending parenthesis use the {@code ClosingParensOption.ALWAYS} type. 075 * To never have ending parenthesis use the {@code ClosingParensOption.NEVER} type. 076 * To not enforce a closing parenthesis preference a {@code ClosingParensOption.IGNORE} type is 077 * provided. 078 * Set this through the {@code closingParens} property. 079 * </p> 080 * 081 * <p> 082 * Annotations also allow you to specify arrays of elements in a standard format. 083 * As with normal arrays, a trailing comma is optional. 084 * To always require a trailing comma use the {@code TrailingArrayCommaOption.ALWAYS} type. 085 * To never have a trailing comma use the {@code TrailingArrayCommaOption.NEVER} type. 086 * To not enforce a trailing array comma preference a {@code TrailingArrayCommaOption.IGNORE} type 087 * is provided. Set this through the {@code trailingArrayComma} property. 088 * </p> 089 * 090 * <p> 091 * By default, the {@code ElementStyleOption} is set to {@code COMPACT_NO_ARRAY}, 092 * the {@code TrailingArrayCommaOption} is set to {@code NEVER}, 093 * and the {@code ClosingParensOption} is set to {@code NEVER}. 094 * </p> 095 * 096 * <p> 097 * According to the JLS, it is legal to include a trailing comma 098 * in arrays used in annotations but Sun's Java 5 & 6 compilers will not 099 * 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 * 110 * @since 5.0 111 */ 112@StatelessCheck 113public final class AnnotationUseStyleCheck extends AbstractCheck { 114 115 /** 116 * Defines the styles for defining elements in an annotation. 117 */ 118 public enum ElementStyleOption { 119 120 /** 121 * Expanded example 122 * 123 * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>. 124 */ 125 EXPANDED, 126 127 /** 128 * Compact example 129 * 130 * <pre>@SuppressWarnings({"unchecked","unused",})</pre> 131 * <br>or<br> 132 * <pre>@SuppressWarnings("unchecked")</pre>. 133 */ 134 COMPACT, 135 136 /** 137 * Compact example 138 * 139 * <pre>@SuppressWarnings("unchecked")</pre>. 140 */ 141 COMPACT_NO_ARRAY, 142 143 /** 144 * Mixed styles. 145 */ 146 IGNORE, 147 148 } 149 150 /** 151 * Defines the two styles for defining 152 * elements in an annotation. 153 * 154 */ 155 public enum TrailingArrayCommaOption { 156 157 /** 158 * With comma example 159 * 160 * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>. 161 */ 162 ALWAYS, 163 164 /** 165 * Without comma example 166 * 167 * <pre>@SuppressWarnings(value={"unchecked","unused"})</pre>. 168 */ 169 NEVER, 170 171 /** 172 * Mixed styles. 173 */ 174 IGNORE, 175 176 } 177 178 /** 179 * Defines the two styles for defining 180 * elements in an annotation. 181 * 182 */ 183 public enum ClosingParensOption { 184 185 /** 186 * With parens example 187 * 188 * <pre>@Deprecated()</pre>. 189 */ 190 ALWAYS, 191 192 /** 193 * Without parens example 194 * 195 * <pre>@Deprecated</pre>. 196 */ 197 NEVER, 198 199 /** 200 * Mixed styles. 201 */ 202 IGNORE, 203 204 } 205 206 /** 207 * A key is pointing to the warning message text in "messages.properties" 208 * file. 209 */ 210 public static final String MSG_KEY_ANNOTATION_INCORRECT_STYLE = 211 "annotation.incorrect.style"; 212 213 /** 214 * A key is pointing to the warning message text in "messages.properties" 215 * file. 216 */ 217 public static final String MSG_KEY_ANNOTATION_PARENS_MISSING = 218 "annotation.parens.missing"; 219 220 /** 221 * A key is pointing to the warning message text in "messages.properties" 222 * file. 223 */ 224 public static final String MSG_KEY_ANNOTATION_PARENS_PRESENT = 225 "annotation.parens.present"; 226 227 /** 228 * A key is pointing to the warning message text in "messages.properties" 229 * file. 230 */ 231 public static final String MSG_KEY_ANNOTATION_TRAILING_COMMA_MISSING = 232 "annotation.trailing.comma.missing"; 233 234 /** 235 * A key is pointing to the warning message text in "messages.properties" 236 * file. 237 */ 238 public static final String MSG_KEY_ANNOTATION_TRAILING_COMMA_PRESENT = 239 "annotation.trailing.comma.present"; 240 241 /** 242 * The element name used to receive special linguistic support 243 * for annotation use. 244 */ 245 private static final String ANNOTATION_ELEMENT_SINGLE_NAME = 246 "value"; 247 248 /** 249 * Define the annotation element styles. 250 */ 251 private ElementStyleOption elementStyle = ElementStyleOption.COMPACT_NO_ARRAY; 252 253 // defaulting to NEVER because of the strange compiler behavior 254 /** 255 * Define the policy for trailing comma in arrays. 256 */ 257 private TrailingArrayCommaOption trailingArrayComma = TrailingArrayCommaOption.NEVER; 258 259 /** 260 * Define the policy for ending parenthesis. 261 */ 262 private ClosingParensOption closingParens = ClosingParensOption.NEVER; 263 264 /** 265 * Setter to define the annotation element styles. 266 * 267 * @param style string representation 268 * @since 5.0 269 */ 270 public void setElementStyle(final String style) { 271 elementStyle = getOption(ElementStyleOption.class, style); 272 } 273 274 /** 275 * Setter to define the policy for trailing comma in arrays. 276 * 277 * @param comma string representation 278 * @since 5.0 279 */ 280 public void setTrailingArrayComma(final String comma) { 281 trailingArrayComma = getOption(TrailingArrayCommaOption.class, comma); 282 } 283 284 /** 285 * Setter to define the policy for ending parenthesis. 286 * 287 * @param parens string representation 288 * @since 5.0 289 */ 290 public void setClosingParens(final String parens) { 291 closingParens = getOption(ClosingParensOption.class, parens); 292 } 293 294 /** 295 * Retrieves an {@link Enum Enum} type from a @{link String String}. 296 * 297 * @param <T> the enum type 298 * @param enumClass the enum class 299 * @param value the string representing the enum 300 * @return the enum type 301 * @throws IllegalArgumentException when unable to parse value 302 */ 303 private static <T extends Enum<T>> T getOption(final Class<T> enumClass, 304 final String value) { 305 try { 306 return Enum.valueOf(enumClass, value.trim().toUpperCase(Locale.ENGLISH)); 307 } 308 catch (final IllegalArgumentException iae) { 309 throw new IllegalArgumentException("unable to parse " + value, iae); 310 } 311 } 312 313 @Override 314 public int[] getDefaultTokens() { 315 return getRequiredTokens(); 316 } 317 318 @Override 319 public int[] getRequiredTokens() { 320 return new int[] { 321 TokenTypes.ANNOTATION, 322 }; 323 } 324 325 @Override 326 public int[] getAcceptableTokens() { 327 return getRequiredTokens(); 328 } 329 330 @Override 331 public void visitToken(final DetailAST ast) { 332 checkStyleType(ast); 333 checkCheckClosingParensOption(ast); 334 checkTrailingComma(ast); 335 } 336 337 /** 338 * Checks to see if the 339 * {@link ElementStyleOption AnnotationElementStyleOption} 340 * is correct. 341 * 342 * @param annotation the annotation token 343 * @noinspection EnhancedSwitchMigration 344 * @noinspectionreason Until #17674 345 */ 346 private void checkStyleType(final DetailAST annotation) { 347 switch (elementStyle) { 348 case COMPACT_NO_ARRAY: 349 checkCompactNoArrayStyle(annotation); 350 break; 351 case COMPACT: 352 checkCompactStyle(annotation); 353 break; 354 case EXPANDED: 355 checkExpandedStyle(annotation); 356 break; 357 case IGNORE: 358 default: 359 break; 360 } 361 } 362 363 /** 364 * Checks for expanded style type violations. 365 * 366 * @param annotation the annotation token 367 */ 368 private void checkExpandedStyle(final DetailAST annotation) { 369 final int valuePairCount = 370 annotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 371 372 if (valuePairCount == 0 && hasArguments(annotation)) { 373 log(annotation, MSG_KEY_ANNOTATION_INCORRECT_STYLE, ElementStyleOption.EXPANDED); 374 } 375 } 376 377 /** 378 * Checks that annotation has arguments. 379 * 380 * @param annotation to check 381 * @return true if annotation has arguments, false otherwise 382 */ 383 private static boolean hasArguments(DetailAST annotation) { 384 final DetailAST firstToken = annotation.findFirstToken(TokenTypes.LPAREN); 385 return firstToken != null && firstToken.getNextSibling().getType() != TokenTypes.RPAREN; 386 } 387 388 /** 389 * Checks for compact style type violations. 390 * 391 * @param annotation the annotation token 392 */ 393 private void checkCompactStyle(final DetailAST annotation) { 394 final int valuePairCount = 395 annotation.getChildCount( 396 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 397 398 final DetailAST valuePair = 399 annotation.findFirstToken( 400 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 401 402 if (valuePairCount == 1 403 && ANNOTATION_ELEMENT_SINGLE_NAME.equals( 404 valuePair.getFirstChild().getText())) { 405 log(annotation, MSG_KEY_ANNOTATION_INCORRECT_STYLE, 406 ElementStyleOption.COMPACT); 407 } 408 } 409 410 /** 411 * Checks for compact no array style type violations. 412 * 413 * @param annotation the annotation token 414 */ 415 private void checkCompactNoArrayStyle(final DetailAST annotation) { 416 final DetailAST arrayInit = 417 annotation.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 418 419 // in compact style with one value 420 if (arrayInit != null 421 && arrayInit.getChildCount(TokenTypes.EXPR) == 1) { 422 log(annotation, MSG_KEY_ANNOTATION_INCORRECT_STYLE, 423 ElementStyleOption.COMPACT_NO_ARRAY); 424 } 425 // in expanded style with pairs 426 else { 427 DetailAST ast = annotation.getFirstChild(); 428 while (ast != null) { 429 final DetailAST nestedArrayInit = 430 ast.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 431 if (nestedArrayInit != null 432 && nestedArrayInit.getChildCount(TokenTypes.EXPR) == 1) { 433 log(annotation, MSG_KEY_ANNOTATION_INCORRECT_STYLE, 434 ElementStyleOption.COMPACT_NO_ARRAY); 435 } 436 ast = ast.getNextSibling(); 437 } 438 } 439 } 440 441 /** 442 * Checks to see if the trailing comma is present if required or 443 * prohibited. 444 * 445 * @param annotation the annotation token 446 */ 447 private void checkTrailingComma(final DetailAST annotation) { 448 if (trailingArrayComma != TrailingArrayCommaOption.IGNORE) { 449 DetailAST child = annotation.getFirstChild(); 450 451 while (child != null) { 452 DetailAST arrayInit = null; 453 454 if (child.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { 455 arrayInit = child.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 456 } 457 else if (child.getType() == TokenTypes.ANNOTATION_ARRAY_INIT) { 458 arrayInit = child; 459 } 460 461 if (arrayInit != null) { 462 logCommaViolation(arrayInit); 463 } 464 child = child.getNextSibling(); 465 } 466 } 467 } 468 469 /** 470 * Logs a trailing array comma violation if one exists. 471 * 472 * @param ast the array init 473 * {@link TokenTypes#ANNOTATION_ARRAY_INIT ANNOTATION_ARRAY_INIT}. 474 */ 475 private void logCommaViolation(final DetailAST ast) { 476 final DetailAST rCurly = ast.findFirstToken(TokenTypes.RCURLY); 477 478 // comma can be null if array is empty 479 final DetailAST comma = rCurly.getPreviousSibling(); 480 481 if (trailingArrayComma == TrailingArrayCommaOption.NEVER) { 482 if (comma != null && comma.getType() == TokenTypes.COMMA) { 483 log(comma, MSG_KEY_ANNOTATION_TRAILING_COMMA_PRESENT); 484 } 485 } 486 else if (comma == null || comma.getType() != TokenTypes.COMMA) { 487 log(rCurly, MSG_KEY_ANNOTATION_TRAILING_COMMA_MISSING); 488 } 489 } 490 491 /** 492 * Checks to see if the closing parenthesis are present if required or 493 * prohibited. 494 * 495 * @param ast the annotation token 496 */ 497 private void checkCheckClosingParensOption(final DetailAST ast) { 498 if (closingParens != ClosingParensOption.IGNORE) { 499 final DetailAST paren = ast.getLastChild(); 500 501 if (closingParens == ClosingParensOption.NEVER) { 502 if (paren.getPreviousSibling().getType() == TokenTypes.LPAREN) { 503 log(ast, MSG_KEY_ANNOTATION_PARENS_PRESENT); 504 } 505 } 506 else if (paren.getType() != TokenTypes.RPAREN) { 507 log(ast, MSG_KEY_ANNOTATION_PARENS_MISSING); 508 } 509 } 510 } 511 512}