| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| AnnotationUseStyleCheck |
|
| 3.6666666666666665;3.667 | ||||
| AnnotationUseStyleCheck$ClosingParens |
|
| 3.6666666666666665;3.667 | ||||
| AnnotationUseStyleCheck$ElementStyle |
|
| 3.6666666666666665;3.667 | ||||
| AnnotationUseStyleCheck$TrailingArrayComma |
|
| 3.6666666666666665;3.667 |
| 1 | //////////////////////////////////////////////////////////////////////////////// | |
| 2 | // checkstyle: Checks Java source code for adherence to a set of rules. | |
| 3 | // Copyright (C) 2001-2014 Oliver Burn | |
| 4 | // | |
| 5 | // This library is free software; you can redistribute it and/or | |
| 6 | // modify it under the terms of the GNU Lesser General Public | |
| 7 | // License as published by the Free Software Foundation; either | |
| 8 | // version 2.1 of the License, or (at your option) any later version. | |
| 9 | // | |
| 10 | // This library is distributed in the hope that it will be useful, | |
| 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 13 | // Lesser General Public License for more details. | |
| 14 | // | |
| 15 | // You should have received a copy of the GNU Lesser General Public | |
| 16 | // License along with this library; if not, write to the Free Software | |
| 17 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| 18 | //////////////////////////////////////////////////////////////////////////////// | |
| 19 | package com.puppycrawl.tools.checkstyle.checks.annotation; | |
| 20 | ||
| 21 | import org.apache.commons.beanutils.ConversionException; | |
| 22 | ||
| 23 | import com.puppycrawl.tools.checkstyle.api.Check; | |
| 24 | import com.puppycrawl.tools.checkstyle.api.DetailAST; | |
| 25 | import com.puppycrawl.tools.checkstyle.api.TokenTypes; | |
| 26 | ||
| 27 | /** | |
| 28 | * This check controls the style with the usage of annotations. | |
| 29 | * | |
| 30 | * <p> | |
| 31 | * Annotations have three element styles starting with the least verbose. | |
| 32 | * <ul> | |
| 33 | * <li>{@link ElementStyle#COMPACT_NO_ARRAY COMPACT_NO_ARRAY}</li> | |
| 34 | * <li>{@link ElementStyle#COMPACT COMPACT}</li> | |
| 35 | * <li>{@link ElementStyle#EXPANDED EXPANDED}</li> | |
| 36 | * </ul> | |
| 37 | * To not enforce an element style | |
| 38 | * a {@link ElementStyle#IGNORE IGNORE} type is provided. The desired style | |
| 39 | * can be set through the <code>elementStyle</code> property. | |
| 40 | * </p> | |
| 41 | * | |
| 42 | * <p> | |
| 43 | * Using the EXPANDED style is more verbose. The expanded version | |
| 44 | * is sometimes referred to as "named parameters" in other languages. | |
| 45 | * </p> | |
| 46 | * | |
| 47 | * <p> | |
| 48 | * Using the COMPACT style is less verbose. This style can only | |
| 49 | * be used when there is an element called 'value' which is either | |
| 50 | * the sole element or all other elements have default valuess. | |
| 51 | * </p> | |
| 52 | * | |
| 53 | * <p> | |
| 54 | * Using the COMPACT_NO_ARRAY style is less verbose. It is similar | |
| 55 | * to the COMPACT style but single value arrays are flagged. With | |
| 56 | * annotations a single value array does not need to be placed in an | |
| 57 | * array initializer. This style can only be used when there is an | |
| 58 | * element called 'value' which is either the sole element or all other | |
| 59 | * elements have default values. | |
| 60 | * </p> | |
| 61 | * | |
| 62 | * <p> | |
| 63 | * The ending parenthesis are optional when using annotations with no elements. | |
| 64 | * To always require ending parenthesis use the | |
| 65 | * {@link ClosingParens#ALWAYS ALWAYS} type. To never have ending parenthesis | |
| 66 | * use the {@link ClosingParens#NEVER NEVER} type. To not enforce a | |
| 67 | * closing parenthesis preference a {@link ClosingParens#IGNORE IGNORE} type is | |
| 68 | * provided. Set this through the <code>closingParens</code> property. | |
| 69 | * </p> | |
| 70 | * | |
| 71 | * <p> | |
| 72 | * Annotations also allow you to specify arrays of elements in a standard | |
| 73 | * format. As with normal arrays, a trailing comma is optional. To always | |
| 74 | * require a trailing comma use the {@link TrailingArrayComma#ALWAYS ALWAYS} | |
| 75 | * type. To never have a trailing comma use the | |
| 76 | * {@link TrailingArrayComma#NEVER NEVER} type. To not enforce a trailing | |
| 77 | * array comma preference a {@link TrailingArrayComma#IGNORE IGNORE} type | |
| 78 | * is provided. Set this through the <code>trailingArrayComma</code> property. | |
| 79 | * </p> | |
| 80 | * | |
| 81 | * <p> | |
| 82 | * By default the ElementStyle is set to EXPANDED, the TrailingArrayComma | |
| 83 | * is set to NEVER, and the ClosingParans is set to ALWAYS. | |
| 84 | * </p> | |
| 85 | * | |
| 86 | * <p> | |
| 87 | * According to the JLS, it is legal to include a trailing comma | |
| 88 | * in arrays used in annotations but Sun's Java 5 & 6 compilers will not | |
| 89 | * compile with this syntax. This may in be a bug in Sun's compilers | |
| 90 | * since eclipse 3.4's built-in compiler does allow this syntax as | |
| 91 | * defined in the JLS. Note: this was tested with compilers included with | |
| 92 | * JDK versions 1.5.0.17 and 1.6.0.11 and the compiler included with eclipse | |
| 93 | * 3.4.1. | |
| 94 | * | |
| 95 | * See <a | |
| 96 | * href="http://java.sun.com/docs/books/jls/third_edition/html/j3TOC.html"> | |
| 97 | * Java Language specification, sections 9.7</a>. | |
| 98 | * </p> | |
| 99 | * | |
| 100 | * <p> | |
| 101 | * An example shown below is set to enforce an EXPANDED style, with a | |
| 102 | * trailing array comma set to NEVER and always including the closing | |
| 103 | * parenthesis. | |
| 104 | * </p> | |
| 105 | * | |
| 106 | * <pre> | |
| 107 | * <module name="AnnotationUseStyle"> | |
| 108 | * <property name="ElementStyle" | |
| 109 | * value="EXPANDED"/> | |
| 110 | * <property name="TrailingArrayComma" | |
| 111 | * value="NEVER"/> | |
| 112 | * <property name="ClosingParens" | |
| 113 | * value="ALWAYS"/> | |
| 114 | * </module> | |
| 115 | * </pre> | |
| 116 | * | |
| 117 | * @author Travis Schneeberger | |
| 118 | */ | |
| 119 | 10 | public final class AnnotationUseStyleCheck extends Check |
| 120 | { | |
| 121 | /** | |
| 122 | * the element name used to receive special linguistic support | |
| 123 | * for annotation use. | |
| 124 | */ | |
| 125 | private static final String ANNOTATION_ELEMENT_SINGLE_NAME = | |
| 126 | "value"; | |
| 127 | ||
| 128 | //not extending AbstractOptionCheck because check | |
| 129 | //has more than one option type. | |
| 130 | ||
| 131 | /** @see #setElementStyle(String) */ | |
| 132 | 10 | private ElementStyle mStyle = ElementStyle.COMPACT_NO_ARRAY; |
| 133 | ||
| 134 | //defaulting to NEVER because of the strange compiler behavior | |
| 135 | /** @see #setTrailingArrayComma(String) */ | |
| 136 | 10 | private TrailingArrayComma mComma = TrailingArrayComma.NEVER; |
| 137 | ||
| 138 | /** @see #setClosingParans(String) */ | |
| 139 | 10 | private ClosingParens mParens = ClosingParens.NEVER; |
| 140 | ||
| 141 | /** | |
| 142 | * Sets the ElementStyle from a string. | |
| 143 | * | |
| 144 | * @param aStyle string representation | |
| 145 | * @throws ConversionException if cannot convert string. | |
| 146 | */ | |
| 147 | public void setElementStyle(final String aStyle) | |
| 148 | { | |
| 149 | 10 | this.mStyle = this.getOption(ElementStyle.class, aStyle); |
| 150 | 10 | } |
| 151 | ||
| 152 | /** | |
| 153 | * Sets the TrailingArrayComma from a string. | |
| 154 | * | |
| 155 | * @param aComma string representation | |
| 156 | * @throws ConversionException if cannot convert string. | |
| 157 | */ | |
| 158 | public void setTrailingArrayComma(final String aComma) | |
| 159 | { | |
| 160 | 10 | this.mComma = this.getOption(TrailingArrayComma.class, aComma); |
| 161 | 10 | } |
| 162 | ||
| 163 | /** | |
| 164 | * Sets the ClosingParens from a string. | |
| 165 | * | |
| 166 | * @param aParens string representation | |
| 167 | * @throws ConversionException if cannot convert string. | |
| 168 | */ | |
| 169 | public void setClosingParens(final String aParens) | |
| 170 | { | |
| 171 | 10 | this.mParens = this.getOption(ClosingParens.class, aParens); |
| 172 | 10 | } |
| 173 | ||
| 174 | /** | |
| 175 | * Retrieves an {@link Enum Enum} type from a @{link String String}. | |
| 176 | * @param <T> the enum type | |
| 177 | * @param aEnumClass the enum class | |
| 178 | * @param aString the string representing the enum | |
| 179 | * @return the enum type | |
| 180 | */ | |
| 181 | private <T extends Enum<T>> T getOption(final Class<T> aEnumClass, | |
| 182 | final String aString) | |
| 183 | { | |
| 184 | try { | |
| 185 | 30 | return Enum.valueOf(aEnumClass, aString.trim().toUpperCase()); |
| 186 | } | |
| 187 | 0 | catch (final IllegalArgumentException iae) { |
| 188 | 0 | throw new ConversionException("unable to parse " + aString, iae); |
| 189 | } | |
| 190 | } | |
| 191 | ||
| 192 | /** {@inheritDoc} */ | |
| 193 | @Override | |
| 194 | public int[] getDefaultTokens() | |
| 195 | { | |
| 196 | 10 | return this.getRequiredTokens(); |
| 197 | } | |
| 198 | ||
| 199 | /** {@inheritDoc} */ | |
| 200 | @Override | |
| 201 | public int[] getRequiredTokens() | |
| 202 | { | |
| 203 | 10 | return new int[] { |
| 204 | TokenTypes.ANNOTATION, | |
| 205 | }; | |
| 206 | } | |
| 207 | ||
| 208 | /** {@inheritDoc} */ | |
| 209 | @Override | |
| 210 | public int[] getAcceptableTokens() | |
| 211 | { | |
| 212 | 0 | return this.getRequiredTokens(); |
| 213 | } | |
| 214 | ||
| 215 | /** {@inheritDoc} */ | |
| 216 | @Override | |
| 217 | public void visitToken(final DetailAST aAST) | |
| 218 | { | |
| 219 | 150 | this.checkStyleType(aAST); |
| 220 | 150 | this.checkCheckClosingParens(aAST); |
| 221 | 150 | this.checkTrailingComma(aAST); |
| 222 | 150 | } |
| 223 | ||
| 224 | /** | |
| 225 | * Checks to see if the | |
| 226 | * {@link ElementStyle AnnotationElementStyle} | |
| 227 | * is correct. | |
| 228 | * | |
| 229 | * @param aAnnotation the annotation token | |
| 230 | */ | |
| 231 | private void checkStyleType(final DetailAST aAnnotation) | |
| 232 | { | |
| 233 | 150 | if (ElementStyle.IGNORE.equals(this.mStyle) |
| 234 | || this.mStyle == null) | |
| 235 | { | |
| 236 | 90 | return; |
| 237 | } | |
| 238 | ||
| 239 | 60 | if (ElementStyle.COMPACT_NO_ARRAY.equals(this.mStyle)) { |
| 240 | 20 | this.checkCompactNoArrayStyle(aAnnotation); |
| 241 | } | |
| 242 | 40 | else if (ElementStyle.COMPACT.equals(this.mStyle)) { |
| 243 | 20 | this.checkCompactStyle(aAnnotation); |
| 244 | } | |
| 245 | 20 | else if (ElementStyle.EXPANDED.equals(this.mStyle)) { |
| 246 | 20 | this.checkExpandedStyle(aAnnotation); |
| 247 | } | |
| 248 | 60 | } |
| 249 | ||
| 250 | /** | |
| 251 | * Checks for expanded style type violations. | |
| 252 | * | |
| 253 | * @param aAnnotation the annotation token | |
| 254 | */ | |
| 255 | private void checkExpandedStyle(final DetailAST aAnnotation) | |
| 256 | { | |
| 257 | 20 | final int valuePairCount = |
| 258 | aAnnotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); | |
| 259 | ||
| 260 | 20 | if (valuePairCount == 0 |
| 261 | && aAnnotation.branchContains(TokenTypes.EXPR)) | |
| 262 | { | |
| 263 | 7 | this.log(aAnnotation.getLineNo(), "annotation.incorrect.style", |
| 264 | ElementStyle.EXPANDED); | |
| 265 | } | |
| 266 | 20 | } |
| 267 | ||
| 268 | /** | |
| 269 | * Checks for compact style type violations. | |
| 270 | * | |
| 271 | * @param aAnnotation the annotation token | |
| 272 | */ | |
| 273 | private void checkCompactStyle(final DetailAST aAnnotation) | |
| 274 | { | |
| 275 | 20 | final int valuePairCount = |
| 276 | aAnnotation.getChildCount( | |
| 277 | TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); | |
| 278 | ||
| 279 | 20 | final DetailAST valuePair = |
| 280 | aAnnotation.findFirstToken( | |
| 281 | TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); | |
| 282 | ||
| 283 | 20 | if (valuePairCount == 1 |
| 284 | && AnnotationUseStyleCheck.ANNOTATION_ELEMENT_SINGLE_NAME.equals( | |
| 285 | valuePair.getFirstChild().getText())) | |
| 286 | { | |
| 287 | 2 | this.log(aAnnotation.getLineNo(), "annotation.incorrect.style", |
| 288 | ElementStyle.COMPACT); | |
| 289 | } | |
| 290 | 20 | } |
| 291 | ||
| 292 | /** | |
| 293 | * Checks for compact no array style type violations. | |
| 294 | * | |
| 295 | * @param aAnnotation the annotation token | |
| 296 | */ | |
| 297 | private void checkCompactNoArrayStyle(final DetailAST aAnnotation) | |
| 298 | { | |
| 299 | 20 | final DetailAST arrayInit = |
| 300 | aAnnotation.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); | |
| 301 | ||
| 302 | 20 | final int valuePairCount = |
| 303 | aAnnotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); | |
| 304 | ||
| 305 | 20 | final DetailAST valuePair = |
| 306 | aAnnotation.findFirstToken(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); | |
| 307 | ||
| 308 | //in compact style with one value | |
| 309 | 20 | if (arrayInit != null |
| 310 | && arrayInit.getChildCount(TokenTypes.EXPR) == 1) | |
| 311 | { | |
| 312 | 3 | this.log(aAnnotation.getLineNo(), "annotation.incorrect.style", |
| 313 | ElementStyle.COMPACT_NO_ARRAY); | |
| 314 | } | |
| 315 | //in expanded style with one value and the correct element name | |
| 316 | 17 | else if (valuePairCount == 1) { |
| 317 | 5 | final DetailAST nestedArrayInit = |
| 318 | valuePair.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); | |
| 319 | ||
| 320 | 5 | if (nestedArrayInit != null |
| 321 | && AnnotationUseStyleCheck. | |
| 322 | ANNOTATION_ELEMENT_SINGLE_NAME.equals( | |
| 323 | valuePair.getFirstChild().getText()) | |
| 324 | && nestedArrayInit.getChildCount(TokenTypes.EXPR) == 1) | |
| 325 | { | |
| 326 | 2 | this.log(aAnnotation.getLineNo(), "annotation.incorrect.style", |
| 327 | ElementStyle.COMPACT_NO_ARRAY); | |
| 328 | } | |
| 329 | } | |
| 330 | 20 | } |
| 331 | ||
| 332 | /** | |
| 333 | * Checks to see if the trailing comma is present if required or | |
| 334 | * prohibited. | |
| 335 | * | |
| 336 | * @param aAnnotation the annotation token | |
| 337 | */ | |
| 338 | private void checkTrailingComma(final DetailAST aAnnotation) | |
| 339 | { | |
| 340 | 150 | if (TrailingArrayComma.IGNORE.equals(this.mComma) |
| 341 | || this.mComma == null) | |
| 342 | { | |
| 343 | 120 | return; |
| 344 | } | |
| 345 | ||
| 346 | 30 | DetailAST child = aAnnotation.getFirstChild(); |
| 347 | ||
| 348 | 204 | while (child != null) { |
| 349 | 174 | DetailAST arrayInit = null; |
| 350 | ||
| 351 | 174 | if (child.getType() |
| 352 | == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) | |
| 353 | { | |
| 354 | 32 | arrayInit = |
| 355 | child.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); | |
| 356 | } | |
| 357 | 142 | else if (child.getType() == TokenTypes.ANNOTATION_ARRAY_INIT) { |
| 358 | 10 | arrayInit = child; |
| 359 | } | |
| 360 | ||
| 361 | 174 | if (arrayInit != null) { |
| 362 | 42 | this.logCommaViolation(arrayInit); |
| 363 | } | |
| 364 | 174 | child = child.getNextSibling(); |
| 365 | 174 | } |
| 366 | 30 | } |
| 367 | ||
| 368 | /** | |
| 369 | * logs a trailing array comma violation if one exists. | |
| 370 | * | |
| 371 | * @param aAST the array init | |
| 372 | * {@link TokenTypes#ANNOTATION_ARRAY_INIT ANNOTATION_ARRAY_INIT}. | |
| 373 | */ | |
| 374 | private void logCommaViolation(final DetailAST aAST) | |
| 375 | { | |
| 376 | 42 | final DetailAST rCurly = aAST.findFirstToken(TokenTypes.RCURLY); |
| 377 | ||
| 378 | //comma can be null if array is empty | |
| 379 | 42 | final DetailAST comma = rCurly.getPreviousSibling(); |
| 380 | ||
| 381 | 42 | if (TrailingArrayComma.ALWAYS.equals(this.mComma) |
| 382 | && (comma == null || comma.getType() != TokenTypes.COMMA)) | |
| 383 | { | |
| 384 | 13 | this.log(rCurly.getLineNo(), |
| 385 | rCurly.getColumnNo(), "annotation.trailing.comma.missing"); | |
| 386 | } | |
| 387 | 29 | else if (TrailingArrayComma.NEVER.equals(this.mComma) |
| 388 | && comma != null && comma.getType() == TokenTypes.COMMA) | |
| 389 | { | |
| 390 | 8 | this.log(comma.getLineNo(), |
| 391 | comma.getColumnNo(), "annotation.trailing.comma.present"); | |
| 392 | } | |
| 393 | 42 | } |
| 394 | ||
| 395 | /** | |
| 396 | * Checks to see if the closing parenthesis are present if required or | |
| 397 | * prohibited. | |
| 398 | * | |
| 399 | * @param aAST the annotation token | |
| 400 | */ | |
| 401 | private void checkCheckClosingParens(final DetailAST aAST) | |
| 402 | { | |
| 403 | 150 | if (ClosingParens.IGNORE.equals(this.mParens) |
| 404 | || this.mParens == null) | |
| 405 | { | |
| 406 | 110 | return; |
| 407 | } | |
| 408 | ||
| 409 | 40 | final DetailAST paren = aAST.getLastChild(); |
| 410 | 40 | final boolean parenExists = paren.getType() == TokenTypes.RPAREN; |
| 411 | ||
| 412 | 40 | if (ClosingParens.ALWAYS.equals(this.mParens) |
| 413 | && !parenExists) | |
| 414 | { | |
| 415 | 3 | this.log(aAST.getLineNo(), "annotation.parens.missing"); |
| 416 | } | |
| 417 | 37 | else if (ClosingParens.NEVER.equals(this.mParens) |
| 418 | && !aAST.branchContains(TokenTypes.EXPR) | |
| 419 | && parenExists) | |
| 420 | { | |
| 421 | 3 | this.log(aAST.getLineNo(), "annotation.parens.present"); |
| 422 | } | |
| 423 | 40 | } |
| 424 | ||
| 425 | /** | |
| 426 | * Defines the styles for defining elements in an annotation. | |
| 427 | * @author Travis Schneeberger | |
| 428 | */ | |
| 429 | 6 | public static enum ElementStyle { |
| 430 | ||
| 431 | /** | |
| 432 | * expanded example | |
| 433 | * | |
| 434 | * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>. | |
| 435 | */ | |
| 436 | 1 | EXPANDED, |
| 437 | ||
| 438 | /** | |
| 439 | * compact example | |
| 440 | * | |
| 441 | * <pre>@SuppressWarnings({"unchecked","unused",})</pre> | |
| 442 | * <br/>or<br/> | |
| 443 | * <pre>@SuppressWarnings("unchecked")</pre>. | |
| 444 | */ | |
| 445 | 1 | COMPACT, |
| 446 | ||
| 447 | /** | |
| 448 | * compact example.] | |
| 449 | * | |
| 450 | * <pre>@SuppressWarnings("unchecked")</pre>. | |
| 451 | */ | |
| 452 | 1 | COMPACT_NO_ARRAY, |
| 453 | ||
| 454 | /** | |
| 455 | * mixed styles. | |
| 456 | */ | |
| 457 | 1 | IGNORE, |
| 458 | } | |
| 459 | ||
| 460 | /** | |
| 461 | * Defines the two styles for defining | |
| 462 | * elements in an annotation. | |
| 463 | * | |
| 464 | * @author Travis Schneeberger | |
| 465 | */ | |
| 466 | 5 | public static enum TrailingArrayComma { |
| 467 | ||
| 468 | /** | |
| 469 | * with comma example | |
| 470 | * | |
| 471 | * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>. | |
| 472 | */ | |
| 473 | 1 | ALWAYS, |
| 474 | ||
| 475 | /** | |
| 476 | * without comma example | |
| 477 | * | |
| 478 | * <pre>@SuppressWarnings(value={"unchecked","unused"})</pre>. | |
| 479 | */ | |
| 480 | 1 | NEVER, |
| 481 | ||
| 482 | /** | |
| 483 | * mixed styles. | |
| 484 | */ | |
| 485 | 1 | IGNORE, |
| 486 | } | |
| 487 | ||
| 488 | /** | |
| 489 | * Defines the two styles for defining | |
| 490 | * elements in an annotation. | |
| 491 | * | |
| 492 | * @author Travis Schneeberger | |
| 493 | */ | |
| 494 | 5 | public static enum ClosingParens { |
| 495 | ||
| 496 | /** | |
| 497 | * with parens example | |
| 498 | * | |
| 499 | * <pre>@Deprecated()</pre>. | |
| 500 | */ | |
| 501 | 1 | ALWAYS, |
| 502 | ||
| 503 | /** | |
| 504 | * without parens example | |
| 505 | * | |
| 506 | * <pre>@Deprecated</pre>. | |
| 507 | */ | |
| 508 | 1 | NEVER, |
| 509 | ||
| 510 | /** | |
| 511 | * mixed styles. | |
| 512 | */ | |
| 513 | 1 | IGNORE, |
| 514 | } | |
| 515 | } |