001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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 * This check controls the style with the usage of 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 */ 319 private static <T extends Enum<T>> T getOption(final Class<T> enumClass, 320 final String value) { 321 try { 322 return Enum.valueOf(enumClass, value.trim().toUpperCase(Locale.ENGLISH)); 323 } 324 catch (final IllegalArgumentException iae) { 325 throw new IllegalArgumentException("unable to parse " + value, iae); 326 } 327 } 328 329 @Override 330 public int[] getDefaultTokens() { 331 return getRequiredTokens(); 332 } 333 334 @Override 335 public int[] getRequiredTokens() { 336 return new int[] { 337 TokenTypes.ANNOTATION, 338 }; 339 } 340 341 @Override 342 public int[] getAcceptableTokens() { 343 return getRequiredTokens(); 344 } 345 346 @Override 347 public void visitToken(final DetailAST ast) { 348 checkStyleType(ast); 349 checkCheckClosingParens(ast); 350 checkTrailingComma(ast); 351 } 352 353 /** 354 * Checks to see if the 355 * {@link ElementStyle AnnotationElementStyle} 356 * is correct. 357 * 358 * @param annotation the annotation token 359 */ 360 private void checkStyleType(final DetailAST annotation) { 361 switch (elementStyle) { 362 case COMPACT_NO_ARRAY: 363 checkCompactNoArrayStyle(annotation); 364 break; 365 case COMPACT: 366 checkCompactStyle(annotation); 367 break; 368 case EXPANDED: 369 checkExpandedStyle(annotation); 370 break; 371 case IGNORE: 372 default: 373 break; 374 } 375 } 376 377 /** 378 * Checks for expanded style type violations. 379 * 380 * @param annotation the annotation token 381 */ 382 private void checkExpandedStyle(final DetailAST annotation) { 383 final int valuePairCount = 384 annotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 385 386 if (valuePairCount == 0 && hasArguments(annotation)) { 387 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE, ElementStyle.EXPANDED); 388 } 389 } 390 391 /** 392 * Checks that annotation has arguments. 393 * 394 * @param annotation to check 395 * @return true if annotation has arguments, false otherwise 396 */ 397 private static boolean hasArguments(DetailAST annotation) { 398 final DetailAST firstToken = annotation.findFirstToken(TokenTypes.LPAREN); 399 return firstToken != null && firstToken.getNextSibling().getType() != TokenTypes.RPAREN; 400 } 401 402 /** 403 * Checks for compact style type violations. 404 * 405 * @param annotation the annotation token 406 */ 407 private void checkCompactStyle(final DetailAST annotation) { 408 final int valuePairCount = 409 annotation.getChildCount( 410 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 411 412 final DetailAST valuePair = 413 annotation.findFirstToken( 414 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 415 416 if (valuePairCount == 1 417 && ANNOTATION_ELEMENT_SINGLE_NAME.equals( 418 valuePair.getFirstChild().getText())) { 419 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE, 420 ElementStyle.COMPACT); 421 } 422 } 423 424 /** 425 * Checks for compact no array style type violations. 426 * 427 * @param annotation the annotation token 428 */ 429 private void checkCompactNoArrayStyle(final DetailAST annotation) { 430 final DetailAST arrayInit = 431 annotation.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 432 433 //in compact style with one value 434 if (arrayInit != null 435 && arrayInit.getChildCount(TokenTypes.EXPR) == 1) { 436 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE, 437 ElementStyle.COMPACT_NO_ARRAY); 438 } 439 //in expanded style with pairs 440 else { 441 DetailAST ast = annotation.getFirstChild(); 442 while (ast != null) { 443 final DetailAST nestedArrayInit = 444 ast.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 445 if (nestedArrayInit != null 446 && nestedArrayInit.getChildCount(TokenTypes.EXPR) == 1) { 447 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE, 448 ElementStyle.COMPACT_NO_ARRAY); 449 } 450 ast = ast.getNextSibling(); 451 } 452 } 453 } 454 455 /** 456 * Checks to see if the trailing comma is present if required or 457 * prohibited. 458 * 459 * @param annotation the annotation token 460 */ 461 private void checkTrailingComma(final DetailAST annotation) { 462 if (trailingArrayComma != TrailingArrayComma.IGNORE) { 463 DetailAST child = annotation.getFirstChild(); 464 465 while (child != null) { 466 DetailAST arrayInit = null; 467 468 if (child.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { 469 arrayInit = child.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 470 } 471 else if (child.getType() == TokenTypes.ANNOTATION_ARRAY_INIT) { 472 arrayInit = child; 473 } 474 475 if (arrayInit != null) { 476 logCommaViolation(arrayInit); 477 } 478 child = child.getNextSibling(); 479 } 480 } 481 } 482 483 /** 484 * Logs a trailing array comma violation if one exists. 485 * 486 * @param ast the array init 487 * {@link TokenTypes#ANNOTATION_ARRAY_INIT ANNOTATION_ARRAY_INIT}. 488 */ 489 private void logCommaViolation(final DetailAST ast) { 490 final DetailAST rCurly = ast.findFirstToken(TokenTypes.RCURLY); 491 492 //comma can be null if array is empty 493 final DetailAST comma = rCurly.getPreviousSibling(); 494 495 if (trailingArrayComma == TrailingArrayComma.ALWAYS) { 496 if (comma == null || comma.getType() != TokenTypes.COMMA) { 497 log(rCurly, MSG_KEY_ANNOTATION_TRAILING_COMMA_MISSING); 498 } 499 } 500 else if (comma != null && comma.getType() == TokenTypes.COMMA) { 501 log(comma, MSG_KEY_ANNOTATION_TRAILING_COMMA_PRESENT); 502 } 503 } 504 505 /** 506 * Checks to see if the closing parenthesis are present if required or 507 * prohibited. 508 * 509 * @param ast the annotation token 510 */ 511 private void checkCheckClosingParens(final DetailAST ast) { 512 if (closingParens != ClosingParens.IGNORE) { 513 final DetailAST paren = ast.getLastChild(); 514 515 if (closingParens == ClosingParens.ALWAYS) { 516 if (paren.getType() != TokenTypes.RPAREN) { 517 log(ast.getLineNo(), MSG_KEY_ANNOTATION_PARENS_MISSING); 518 } 519 } 520 else if (paren.getPreviousSibling().getType() == TokenTypes.LPAREN) { 521 log(ast.getLineNo(), MSG_KEY_ANNOTATION_PARENS_PRESENT); 522 } 523 } 524 } 525 526}