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