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-2018 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  
     /**
 145  
      * Defines the two styles for defining
 146  
      * elements in an annotation.
 147  
      *
 148  
      * @author Travis Schneeberger
 149  
      */
 150  6
     public enum TrailingArrayComma {
 151  
 
 152  
         /**
 153  
          * With comma example
 154  
          *
 155  
          * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>.
 156  
          */
 157  1
         ALWAYS,
 158  
 
 159  
         /**
 160  
          * Without comma example
 161  
          *
 162  
          * <pre>@SuppressWarnings(value={"unchecked","unused"})</pre>.
 163  
          */
 164  1
         NEVER,
 165  
 
 166  
         /**
 167  
          * Mixed styles.
 168  
          */
 169  1
         IGNORE,
 170  
 
 171  
     }
 172  
 
 173  
     /**
 174  
      * Defines the two styles for defining
 175  
      * elements in an annotation.
 176  
      *
 177  
      * @author Travis Schneeberger
 178  
      */
 179  6
     public enum ClosingParens {
 180  
 
 181  
         /**
 182  
          * With parens example
 183  
          *
 184  
          * <pre>@Deprecated()</pre>.
 185  
          */
 186  1
         ALWAYS,
 187  
 
 188  
         /**
 189  
          * Without parens example
 190  
          *
 191  
          * <pre>@Deprecated</pre>.
 192  
          */
 193  1
         NEVER,
 194  
 
 195  
         /**
 196  
          * Mixed styles.
 197  
          */
 198  1
         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  22
     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  22
     private TrailingArrayComma trailingArrayComma = TrailingArrayComma.NEVER;
 256  
 
 257  
     /**
 258  
      * Closing parens option.
 259  
      * @see #setClosingParens(String)
 260  
      */
 261  22
     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  13
         elementStyle = getOption(ElementStyle.class, style);
 271  12
     }
 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  12
         trailingArrayComma = getOption(TrailingArrayComma.class, comma);
 281  12
     }
 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  13
         closingParens = getOption(ClosingParens.class, parens);
 291  13
     }
 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  38
             return Enum.valueOf(enumClass, value.trim().toUpperCase(Locale.ENGLISH));
 304  
         }
 305  1
         catch (final IllegalArgumentException iae) {
 306  1
             throw new IllegalArgumentException("unable to parse " + value, iae);
 307  
         }
 308  
     }
 309  
 
 310  
     @Override
 311  
     public int[] getDefaultTokens() {
 312  34
         return getRequiredTokens();
 313  
     }
 314  
 
 315  
     @Override
 316  
     public int[] getRequiredTokens() {
 317  73
         return new int[] {
 318  
             TokenTypes.ANNOTATION,
 319  
         };
 320  
     }
 321  
 
 322  
     @Override
 323  
     public int[] getAcceptableTokens() {
 324  5
         return getRequiredTokens();
 325  
     }
 326  
 
 327  
     @Override
 328  
     public void visitToken(final DetailAST ast) {
 329  164
         checkStyleType(ast);
 330  164
         checkCheckClosingParens(ast);
 331  164
         checkTrailingComma(ast);
 332  164
     }
 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  164
         switch (elementStyle) {
 343  
             case COMPACT_NO_ARRAY:
 344  24
                 checkCompactNoArrayStyle(annotation);
 345  24
                 break;
 346  
             case COMPACT:
 347  22
                 checkCompactStyle(annotation);
 348  22
                 break;
 349  
             case EXPANDED:
 350  22
                 checkExpandedStyle(annotation);
 351  22
                 break;
 352  
             case IGNORE:
 353  
             default:
 354  
                 break;
 355  
         }
 356  164
     }
 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  22
         final int valuePairCount =
 365  22
             annotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR);
 366  
 
 367  22
         if (valuePairCount == 0
 368  14
             && annotation.branchContains(TokenTypes.EXPR)) {
 369  7
             log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE,
 370  
                 ElementStyle.EXPANDED);
 371  
         }
 372  22
     }
 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  22
         final int valuePairCount =
 381  22
             annotation.getChildCount(
 382  
                 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR);
 383  
 
 384  22
         final DetailAST valuePair =
 385  22
             annotation.findFirstToken(
 386  
                 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR);
 387  
 
 388  22
         if (valuePairCount == 1
 389  6
             && ANNOTATION_ELEMENT_SINGLE_NAME.equals(
 390  6
                 valuePair.getFirstChild().getText())) {
 391  2
             log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE,
 392  
                 ElementStyle.COMPACT);
 393  
         }
 394  22
     }
 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  24
         final DetailAST arrayInit =
 403  24
             annotation.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT);
 404  
 
 405  24
         final int valuePairCount =
 406  24
             annotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR);
 407  
 
 408  
         //in compact style with one value
 409  24
         if (arrayInit != null
 410  6
             && arrayInit.getChildCount(TokenTypes.EXPR) == 1) {
 411  3
             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  21
         else if (valuePairCount == 1) {
 416  7
             final DetailAST valuePair =
 417  7
                     annotation.findFirstToken(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR);
 418  7
             final DetailAST nestedArrayInit =
 419  7
                 valuePair.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT);
 420  
 
 421  7
             if (nestedArrayInit != null
 422  6
                 && ANNOTATION_ELEMENT_SINGLE_NAME.equals(
 423  6
                     valuePair.getFirstChild().getText())
 424  3
                     && nestedArrayInit.getChildCount(TokenTypes.EXPR) == 1) {
 425  2
                 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE,
 426  
                     ElementStyle.COMPACT_NO_ARRAY);
 427  
             }
 428  
         }
 429  24
     }
 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  164
         if (trailingArrayComma != TrailingArrayComma.IGNORE) {
 439  31
             DetailAST child = annotation.getFirstChild();
 440  
 
 441  210
             while (child != null) {
 442  179
                 DetailAST arrayInit = null;
 443  
 
 444  179
                 if (child.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) {
 445  32
                     arrayInit = child.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT);
 446  
                 }
 447  147
                 else if (child.getType() == TokenTypes.ANNOTATION_ARRAY_INIT) {
 448  11
                     arrayInit = child;
 449  
                 }
 450  
 
 451  179
                 if (arrayInit != null) {
 452  43
                     logCommaViolation(arrayInit);
 453  
                 }
 454  179
                 child = child.getNextSibling();
 455  179
             }
 456  
         }
 457  164
     }
 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  43
         final DetailAST rCurly = ast.findFirstToken(TokenTypes.RCURLY);
 467  
 
 468  
         //comma can be null if array is empty
 469  43
         final DetailAST comma = rCurly.getPreviousSibling();
 470  
 
 471  43
         if (trailingArrayComma == TrailingArrayComma.ALWAYS
 472  17
             && (comma == null || comma.getType() != TokenTypes.COMMA)) {
 473  26
             log(rCurly.getLineNo(),
 474  13
                 rCurly.getColumnNo(), MSG_KEY_ANNOTATION_TRAILING_COMMA_MISSING);
 475  
         }
 476  30
         else if (trailingArrayComma == TrailingArrayComma.NEVER
 477  17
             && comma != null && comma.getType() == TokenTypes.COMMA) {
 478  16
             log(comma.getLineNo(),
 479  8
                 comma.getColumnNo(), MSG_KEY_ANNOTATION_TRAILING_COMMA_PRESENT);
 480  
         }
 481  43
     }
 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  164
         if (closingParens != ClosingParens.IGNORE) {
 491  45
             final DetailAST paren = ast.getLastChild();
 492  45
             final boolean parenExists = paren.getType() == TokenTypes.RPAREN;
 493  
 
 494  45
             if (closingParens == ClosingParens.ALWAYS
 495  
                 && !parenExists) {
 496  3
                 log(ast.getLineNo(), MSG_KEY_ANNOTATION_PARENS_MISSING);
 497  
             }
 498  42
             else if (closingParens == ClosingParens.NEVER
 499  
                      && parenExists
 500  20
                      && !ast.branchContains(TokenTypes.EXPR)
 501  6
                      && !ast.branchContains(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR)
 502  5
                      && !ast.branchContains(TokenTypes.ANNOTATION_ARRAY_INIT)) {
 503  3
                 log(ast.getLineNo(), MSG_KEY_ANNOTATION_PARENS_PRESENT);
 504  
             }
 505  
         }
 506  164
     }
 507  
 
 508  
 }