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