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