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