Coverage Report - com.puppycrawl.tools.checkstyle.checks.annotation.SuppressWarningsCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
SuppressWarningsCheck
98%
52/53
75%
27/36
2.364
 
 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 java.util.regex.Matcher;
 22  
 
 23  
 import com.puppycrawl.tools.checkstyle.api.AnnotationUtility;
 24  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 25  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 26  
 import com.puppycrawl.tools.checkstyle.checks.AbstractFormatCheck;
 27  
 
 28  
 /**
 29  
  * <p>
 30  
  * This check allows you to specify what warnings that
 31  
  * {@link SuppressWarnings SuppressWarnings} is not
 32  
  * allowed to suppress.  You can also specify a list
 33  
  * of TokenTypes that the configured warning(s) cannot
 34  
  * be suppressed on.
 35  
  * </p>
 36  
  *
 37  
  * <p>
 38  
  * The {@link AbstractFormatCheck#setFormat warnings} property is a
 39  
  * regex pattern.  Any warning being suppressed matching
 40  
  * this pattern will be flagged.
 41  
  * </p>
 42  
  *
 43  
  * <p>
 44  
  * By default, any warning specified will be disallowed on
 45  
  * all legal TokenTypes unless otherwise specified via
 46  
  * the
 47  
  * {@link com.puppycrawl.tools.checkstyle.api.Check#setTokens(String[]) tokens}
 48  
  * property.
 49  
  *
 50  
  * Also, by default warnings that are empty strings or all
 51  
  * whitespace (regex: ^$|^\s+$) are flagged.  By specifying,
 52  
  * the format property these defaults no longer apply.
 53  
  * </p>
 54  
  *
 55  
  * <p>
 56  
  * Limitations:  This check does not consider conditionals
 57  
  * inside the SuppressWarnings annotation. <br/>
 58  
  * For example:
 59  
  * {@code @SuppressWarnings((false) ? (true) ? "unchecked" : "foo" : "unused")}
 60  
  * According to the above example, the "unused" warning is being suppressed
 61  
  * not the "unchecked" or "foo" warnings.  All of these warnings will be
 62  
  * considered and matched against regardless of what the conditional
 63  
  * evaluates to.
 64  
  * </p>
 65  
  *
 66  
  * <p>
 67  
  * This check can be configured so that the "unchecked"
 68  
  * and "unused" warnings cannot be suppressed on
 69  
  * anything but variable and parameter declarations.
 70  
  * See below of an example.
 71  
  * </p>
 72  
  *
 73  
  * <pre>
 74  
  * &lt;module name=&quot;SuppressWarnings&quot;&gt;
 75  
  *    &lt;property name=&quot;format&quot;
 76  
  *        value=&quot;^unchecked$|^unused$&quot;/&gt;
 77  
  *    &lt;property name=&quot;tokens&quot;
 78  
  *        value=&quot;
 79  
  *        CLASS_DEF,INTERFACE_DEF,ENUM_DEF,
 80  
  *        ANNOTATION_DEF,ANNOTATION_FIELD_DEF,
 81  
  *        ENUM_CONSTANT_DEF,METHOD_DEF,CTOR_DEF
 82  
  *        &quot;/&gt;
 83  
  * &lt;/module&gt;
 84  
  * </pre>
 85  
  * @author Travis Schneeberger
 86  
  */
 87  1
 public class SuppressWarningsCheck extends AbstractFormatCheck
 88  
 {
 89  
     /** {@link SuppressWarnings SuppressWarnings} annotation name */
 90  
     private static final String SUPPRESS_WARNINGS = "SuppressWarnings";
 91  
 
 92  
     /**
 93  
      * fully-qualified {@link SuppressWarnings SuppressWarnings}
 94  
      * annotation name
 95  
      */
 96  
     private static final String FQ_SUPPRESS_WARNINGS =
 97  
         "java.lang." + SUPPRESS_WARNINGS;
 98  
 
 99  
     /**
 100  
      * Ctor that specifies the default for the format property
 101  
      * as specified in the class javadocs.
 102  
      */
 103  
     public SuppressWarningsCheck()
 104  
     {
 105  21
         super("^$|^\\s+$");
 106  21
     }
 107  
 
 108  
     /** {@inheritDoc} */
 109  
     @Override
 110  
     public final int[] getDefaultTokens()
 111  
     {
 112  18
         return this.getAcceptableTokens();
 113  
     }
 114  
 
 115  
     /** {@inheritDoc} */
 116  
     @Override
 117  
     public final int[] getAcceptableTokens()
 118  
     {
 119  21
         return new int[] {
 120  
             TokenTypes.CLASS_DEF,
 121  
             TokenTypes.INTERFACE_DEF,
 122  
             TokenTypes.ENUM_DEF,
 123  
             TokenTypes.ANNOTATION_DEF,
 124  
             TokenTypes.ANNOTATION_FIELD_DEF,
 125  
             TokenTypes.ENUM_CONSTANT_DEF,
 126  
             TokenTypes.PARAMETER_DEF,
 127  
             TokenTypes.VARIABLE_DEF,
 128  
             TokenTypes.METHOD_DEF,
 129  
             TokenTypes.CTOR_DEF,
 130  
         };
 131  
     }
 132  
 
 133  
     /** {@inheritDoc} */
 134  
     @Override
 135  
     public void visitToken(final DetailAST aAST)
 136  
     {
 137  432
         final DetailAST annotation = this.getSuppressWarnings(aAST);
 138  
 
 139  432
         if (annotation == null) {
 140  95
             return;
 141  
         }
 142  
 
 143  337
         final DetailAST warningHolder =
 144  
             this.findWarningsHolder(annotation);
 145  
 
 146  337
         DetailAST warning = warningHolder.findFirstToken(TokenTypes.EXPR);
 147  
 
 148  
         //rare case with empty array ex: @SuppressWarnings({})
 149  337
         if (warning == null) {
 150  
             //check to see if empty warnings are forbidden -- are by default
 151  24
             this.logMatch(warningHolder.getLineNo(),
 152  
                 warningHolder.getColumnNo(), "");
 153  24
             return;
 154  
         }
 155  
 
 156  1115
         while (warning != null) {
 157  802
             if (warning.getType() == TokenTypes.EXPR) {
 158  401
                 final DetailAST fChild = warning.getFirstChild();
 159  
 
 160  
                 //typical case
 161  401
                 if (fChild.getType() == TokenTypes.STRING_LITERAL) {
 162  267
                     final String warningText =
 163  
                         this.removeQuotes(warning.getFirstChild().getText());
 164  267
                     this.logMatch(warning.getLineNo(),
 165  
                         warning.getColumnNo(), warningText);
 166  
 
 167  
      //conditional case
 168  
      //ex: @SuppressWarnings((false) ? (true) ? "unchecked" : "foo" : "unused")
 169  267
                 }
 170  134
                 else if (fChild.getType() == TokenTypes.QUESTION) {
 171  134
                     this.walkConditional(fChild);
 172  
                 }
 173  
                 else {
 174  0
                     assert false : "Should never get here, type: "
 175  
                         + fChild.getType() + " text: " + fChild.getText();
 176  
                 }
 177  
             }
 178  802
             warning = warning.getNextSibling();
 179  
         }
 180  313
     }
 181  
 
 182  
     /**
 183  
      * Gets the {@link SuppressWarnings SuppressWarnings} annotation
 184  
      * that is annotating the AST.  If the annotation does not exist
 185  
      * this method will return {@code null}.
 186  
      *
 187  
      * @param aAST the AST
 188  
      * @return the {@link SuppressWarnings SuppressWarnings} annotation
 189  
      */
 190  
     private DetailAST getSuppressWarnings(DetailAST aAST)
 191  
     {
 192  432
         final DetailAST annotation = AnnotationUtility.getAnnotation(
 193  
             aAST, SuppressWarningsCheck.SUPPRESS_WARNINGS);
 194  
 
 195  432
         return (annotation != null) ? annotation
 196  
             : AnnotationUtility.getAnnotation(
 197  
                 aAST, SuppressWarningsCheck.FQ_SUPPRESS_WARNINGS);
 198  
     }
 199  
 
 200  
     /**
 201  
      * This method looks for a warning that matches a configured expression.
 202  
      * If found it logs a violation at the given line and column number.
 203  
      *
 204  
      * @param aLineNo the line number
 205  
      * @param aColNum the column number
 206  
      * @param aWarningText the warning.
 207  
      */
 208  
     private void logMatch(final int aLineNo,
 209  
         final int aColNum, final String aWarningText)
 210  
     {
 211  702
         final Matcher matcher = this.getRegexp().matcher(aWarningText);
 212  702
         if (matcher.matches()) {
 213  431
             this.log(aLineNo, aColNum,
 214  
                 "suppressed.warning.not.allowed", aWarningText);
 215  
         }
 216  702
     }
 217  
 
 218  
     /**
 219  
      * Find the parent (holder) of the of the warnings (Expr).
 220  
      *
 221  
      * @param aAnnotation the annotation
 222  
      * @return a Token representing the expr.
 223  
      */
 224  
     private DetailAST findWarningsHolder(final DetailAST aAnnotation)
 225  
     {
 226  337
         final DetailAST annValuePair =
 227  
             aAnnotation.findFirstToken(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR);
 228  
         final DetailAST annArrayInit;
 229  
 
 230  337
         if (annValuePair != null) {
 231  111
             annArrayInit =
 232  
                 annValuePair.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT);
 233  
         }
 234  
         else {
 235  226
             annArrayInit =
 236  
                 aAnnotation.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT);
 237  
         }
 238  
 
 239  337
         if (annArrayInit != null) {
 240  222
             return annArrayInit;
 241  
         }
 242  
 
 243  115
         return aAnnotation;
 244  
     }
 245  
 
 246  
     /**
 247  
      * Strips a single double quote from the front and back of a string.
 248  
      *
 249  
      * For example:
 250  
      * <br/>
 251  
      * Input String = "unchecked"
 252  
      * <br/>
 253  
      * Output String = unchecked
 254  
      *
 255  
      * @param aWarning the warning string
 256  
      * @return the string without two quotes
 257  
      */
 258  
     private String removeQuotes(final String aWarning)
 259  
     {
 260  678
         assert aWarning != null : "the aWarning was null";
 261  678
         assert aWarning.charAt(0) == '"';
 262  678
         assert aWarning.charAt(aWarning.length() - 1) == '"';
 263  
 
 264  678
         return aWarning.substring(1, aWarning.length() - 1);
 265  
     }
 266  
 
 267  
     /**
 268  
      * Recursively walks a conditional expression checking the left
 269  
      * and right sides, checking for matches and
 270  
      * logging violations.
 271  
      *
 272  
      * @param aCond a Conditional type
 273  
      * {@link TokenTypes#QUESTION QUESTION}
 274  
      */
 275  
     private void walkConditional(final DetailAST aCond)
 276  
     {
 277  688
         if (aCond.getType() != TokenTypes.QUESTION) {
 278  411
             final String warningText =
 279  
                 this.removeQuotes(aCond.getText());
 280  411
             this.logMatch(aCond.getLineNo(), aCond.getColumnNo(), warningText);
 281  411
             return;
 282  
         }
 283  
 
 284  277
         this.walkConditional(this.getCondLeft(aCond));
 285  277
         this.walkConditional(this.getCondRight(aCond));
 286  277
     }
 287  
 
 288  
     /**
 289  
      * Retrieves the left side of a conditional.
 290  
      *
 291  
      * @param aCond aCond a conditional type
 292  
      * {@link TokenTypes#QUESTION QUESTION}
 293  
      * @return either the value
 294  
      * or another conditional
 295  
      */
 296  
     private DetailAST getCondLeft(final DetailAST aCond)
 297  
     {
 298  277
         final DetailAST colon = aCond.findFirstToken(TokenTypes.COLON);
 299  277
         return colon.getPreviousSibling();
 300  
     }
 301  
 
 302  
     /**
 303  
      * Retrieves the right side of a conditional.
 304  
      *
 305  
      * @param aCond a conditional type
 306  
      * {@link TokenTypes#QUESTION QUESTION}
 307  
      * @return either the value
 308  
      * or another conditional
 309  
      */
 310  
     private DetailAST getCondRight(final DetailAST aCond)
 311  
     {
 312  277
         final DetailAST colon = aCond.findFirstToken(TokenTypes.COLON);
 313  277
         return colon.getNextSibling();
 314  
     }
 315  
 }