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