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