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