Coverage Report - com.puppycrawl.tools.checkstyle.checks.SuppressWarningsHolder
 
Classes in this File Line Coverage Branch Coverage Complexity
SuppressWarningsHolder
90%
99/109
66%
66/100
4.667
SuppressWarningsHolder$Entry
100%
12/12
N/A
4.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;
 20  
 
 21  
 import com.google.common.collect.ImmutableList;
 22  
 import com.google.common.collect.Lists;
 23  
 
 24  
 import com.puppycrawl.tools.checkstyle.api.Check;
 25  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 26  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 27  
 
 28  
 import org.apache.commons.beanutils.ConversionException;
 29  
 
 30  
 import java.util.HashMap;
 31  
 import java.util.LinkedList;
 32  
 import java.util.List;
 33  
 import java.util.Map;
 34  
 
 35  
 /**
 36  
  * Maintains a set of check suppressions from {@link SuppressWarnings}
 37  
  * annotations.
 38  
  * @author Trevor Robinson
 39  
  */
 40  2
 public class SuppressWarningsHolder
 41  
     extends Check
 42  
 {
 43  
     /**
 44  
      * Optional prefix for warning suppressions that are only intended to be
 45  
      * recognized by checkstyle. For instance, to suppress {@code
 46  
      * FallThroughCheck} only in checkstyle (and not in javac), use the
 47  
      * suppression {@code "checkstyle:fallthrough"}. To suppress the warning in
 48  
      * both tools, just use {@code "fallthrough"}.
 49  
      */
 50  
     public static final String CHECKSTYLE_PREFIX = "checkstyle:";
 51  
 
 52  
     /** java.lang namespace prefix, which is stripped from SuppressWarnings */
 53  
     private static final String JAVA_LANG_PREFIX = "java.lang.";
 54  
 
 55  
     /** suffix to be removed from subclasses of Check */
 56  
     private static final String CHECK_SUFFIX = "Check";
 57  
 
 58  
     /** a map from check source names to suppression aliases */
 59  1
     private static final Map<String, String> CHECK_ALIAS_MAP =
 60  
         new HashMap<String, String>();
 61  
 
 62  
     /**
 63  
      * a thread-local holder for the list of suppression entries for the last
 64  
      * file parsed
 65  
      */
 66  1
     private static final ThreadLocal<List<Entry>> ENTRIES =
 67  
         new ThreadLocal<List<Entry>>();
 68  
 
 69  
     /** records a particular suppression for a region of a file */
 70  2
     private static class Entry
 71  
     {
 72  
         /** the source name of the suppressed check */
 73  
         private final String mCheckName;
 74  
         /** the suppression region for the check */
 75  
         private final int mFirstLine, mFirstColumn, mLastLine, mLastColumn;
 76  
 
 77  
         /**
 78  
          * Constructs a new suppression region entry.
 79  
          * @param aCheckName the source name of the suppressed check
 80  
          * @param aFirstLine the first line of the suppression region
 81  
          * @param aFirstColumn the first column of the suppression region
 82  
          * @param aLastLine the last line of the suppression region
 83  
          * @param aLastColumn the last column of the suppression region
 84  
          */
 85  
         public Entry(String aCheckName, int aFirstLine, int aFirstColumn,
 86  
             int aLastLine, int aLastColumn)
 87  24
         {
 88  24
             mCheckName = aCheckName;
 89  24
             mFirstLine = aFirstLine;
 90  24
             mFirstColumn = aFirstColumn;
 91  24
             mLastLine = aLastLine;
 92  24
             mLastColumn = aLastColumn;
 93  24
         }
 94  
 
 95  
         /** @return the source name of the suppressed check */
 96  
         public String getCheckName()
 97  
         {
 98  102
             return mCheckName;
 99  
         }
 100  
 
 101  
         /** @return the first line of the suppression region */
 102  
         public int getFirstLine()
 103  
         {
 104  126
             return mFirstLine;
 105  
         }
 106  
 
 107  
         /** @return the first column of the suppression region */
 108  
         public int getFirstColumn()
 109  
         {
 110  1
             return mFirstColumn;
 111  
         }
 112  
 
 113  
         /** @return the last line of the suppression region */
 114  
         public int getLastLine()
 115  
         {
 116  165
             return mLastLine;
 117  
         }
 118  
 
 119  
         /** @return the last column of the suppression region */
 120  
         public int getLastColumn()
 121  
         {
 122  5
             return mLastColumn;
 123  
         }
 124  
     }
 125  
 
 126  
     /**
 127  
      * Returns the default alias for the source name of a check, which is the
 128  
      * source name in lower case with any dotted prefix or "Check" suffix
 129  
      * removed.
 130  
      * @param aSourceName the source name of the check (generally the class
 131  
      *        name)
 132  
      * @return the default alias for the given check
 133  
      */
 134  
     public static String getDefaultAlias(String aSourceName)
 135  
     {
 136  9
         final int startIndex = aSourceName.lastIndexOf('.') + 1;
 137  9
         int endIndex = aSourceName.length();
 138  9
         if (aSourceName.endsWith(CHECK_SUFFIX)) {
 139  9
             endIndex -= CHECK_SUFFIX.length();
 140  
         }
 141  9
         return aSourceName.substring(startIndex, endIndex).toLowerCase();
 142  
     }
 143  
 
 144  
     /**
 145  
      * Returns the alias for the source name of a check. If an alias has been
 146  
      * explicitly registered via {@link #registerAlias(String, String)}, that
 147  
      * alias is returned; otherwise, the default alias is used.
 148  
      * @param aSourceName the source name of the check (generally the class
 149  
      *        name)
 150  
      * @return the current alias for the given check
 151  
      */
 152  
     public static String getAlias(String aSourceName)
 153  
     {
 154  10
         String checkAlias = CHECK_ALIAS_MAP.get(aSourceName);
 155  10
         if (checkAlias == null) {
 156  9
             checkAlias = getDefaultAlias(aSourceName);
 157  
         }
 158  10
         return checkAlias;
 159  
     }
 160  
 
 161  
     /**
 162  
      * Registers an alias for the source name of a check.
 163  
      * @param aSourceName the source name of the check (generally the class
 164  
      *        name)
 165  
      * @param aCheckAlias the alias used in {@link SuppressWarnings} annotations
 166  
      */
 167  
     public static void registerAlias(String aSourceName, String aCheckAlias)
 168  
     {
 169  2
         CHECK_ALIAS_MAP.put(aSourceName, aCheckAlias);
 170  2
     }
 171  
 
 172  
     /**
 173  
      * Registers a list of source name aliases based on a comma-separated list
 174  
      * of {@code source=alias} items, such as {@code
 175  
      * com.puppycrawl.tools.checkstyle.checks.sizes.ParameterNumberCheck=
 176  
      * paramnum}.
 177  
      * @param aAliasList the list of comma-separated alias assigments
 178  
      */
 179  
     public void setAliasList(String aAliasList)
 180  
     {
 181  4
         for (String sourceAlias : aAliasList.split(",")) {
 182  2
             final int index = sourceAlias.indexOf("=");
 183  2
             if (index > 0) {
 184  2
                 registerAlias(sourceAlias.substring(0, index), sourceAlias
 185  
                     .substring(index + 1));
 186  
             }
 187  0
             else if (sourceAlias.length() > 0) {
 188  0
                 throw new ConversionException(
 189  
                     "'=' expected in alias list item: " + sourceAlias);
 190  
             }
 191  
         }
 192  2
     }
 193  
 
 194  
     /**
 195  
      * Checks for a suppression of a check with the given source name and
 196  
      * location in the last file processed.
 197  
      * @param aSourceName the source name of the check
 198  
      * @param aLine the line number of the check
 199  
      * @param aColumn the column number of the check
 200  
      * @return whether the check with the given name is suppressed at the given
 201  
      *         source location
 202  
      */
 203  
     public static boolean isSuppressed(String aSourceName, int aLine,
 204  
         int aColumn)
 205  
     {
 206  10
         final List<Entry> entries = ENTRIES.get();
 207  10
         final String checkAlias = getAlias(aSourceName);
 208  10
         if (entries != null && checkAlias != null) {
 209  10
             for (Entry entry : entries) {
 210  102
                 final boolean afterStart =
 211  
                     entry.getFirstLine() < aLine
 212  
                         || (entry.getFirstLine() == aLine && entry
 213  
                             .getFirstColumn() <= aColumn);
 214  102
                 final boolean beforeEnd =
 215  
                     entry.getLastLine() > aLine
 216  
                         || (entry.getLastLine() == aLine && entry
 217  
                             .getLastColumn() >= aColumn);
 218  102
                 final boolean nameMatches =
 219  
                     entry.getCheckName().equals(checkAlias);
 220  102
                 if (afterStart && beforeEnd && nameMatches) {
 221  5
                     return true;
 222  
                 }
 223  97
             }
 224  
         }
 225  5
         return false;
 226  
     }
 227  
 
 228  
     @Override
 229  
     public int[] getDefaultTokens()
 230  
     {
 231  2
         return new int[] {TokenTypes.ANNOTATION};
 232  
     }
 233  
 
 234  
     @Override
 235  
     public void beginTree(DetailAST aRootAST)
 236  
     {
 237  2
         ENTRIES.set(new LinkedList<Entry>());
 238  2
     }
 239  
 
 240  
     @Override
 241  
     public void visitToken(DetailAST aAST)
 242  
     {
 243  
         // check whether annotation is SuppressWarnings
 244  
         // expected children: AT ( IDENT | DOT ) LPAREN <values> RPAREN
 245  24
         String identifier = getIdentifier(getNthChild(aAST, 1));
 246  24
         if (identifier.startsWith(JAVA_LANG_PREFIX)) {
 247  2
             identifier = identifier.substring(JAVA_LANG_PREFIX.length());
 248  
         }
 249  24
         if ("SuppressWarnings".equals(identifier)) {
 250  
 
 251  
             // get values of annotation
 252  24
             List<String> values = null;
 253  24
             final DetailAST lparenAST = aAST.findFirstToken(TokenTypes.LPAREN);
 254  24
             if (lparenAST != null) {
 255  24
                 final DetailAST nextAST = lparenAST.getNextSibling();
 256  24
                 if (nextAST != null) {
 257  24
                     final int nextType = nextAST.getType();
 258  24
                     switch (nextType) {
 259  
                     case TokenTypes.EXPR:
 260  
                     case TokenTypes.ANNOTATION_ARRAY_INIT:
 261  22
                         values = getAnnotationValues(nextAST);
 262  22
                         break;
 263  
 
 264  
                     case TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR:
 265  
                         // expected children: IDENT ASSIGN ( EXPR |
 266  
                         // ANNOTATION_ARRAY_INIT )
 267  2
                         values = getAnnotationValues(getNthChild(nextAST, 2));
 268  2
                         break;
 269  
 
 270  
                     case TokenTypes.RPAREN:
 271  
                         // no value present (not valid Java)
 272  0
                         break;
 273  
 
 274  
                     default:
 275  
                         // unknown annotation value type (new syntax?)
 276  
                     }
 277  
                 }
 278  
             }
 279  24
             if (values == null) {
 280  0
                 log(aAST, "suppress.warnings.missing.value");
 281  0
                 return;
 282  
             }
 283  
 
 284  
             // get target of annotation
 285  24
             DetailAST targetAST = null;
 286  24
             DetailAST parentAST = aAST.getParent();
 287  24
             if (parentAST != null) {
 288  24
                 switch (parentAST.getType()) {
 289  
                 case TokenTypes.MODIFIERS:
 290  
                 case TokenTypes.ANNOTATIONS:
 291  24
                     parentAST = parentAST.getParent();
 292  24
                     if (parentAST != null) {
 293  24
                         switch (parentAST.getType()) {
 294  
                         case TokenTypes.PACKAGE_DEF:
 295  
                         case TokenTypes.CLASS_DEF:
 296  
                         case TokenTypes.INTERFACE_DEF:
 297  
                         case TokenTypes.ENUM_DEF:
 298  
                         case TokenTypes.CTOR_DEF:
 299  
                         case TokenTypes.METHOD_DEF:
 300  
                         case TokenTypes.PARAMETER_DEF:
 301  
                         case TokenTypes.VARIABLE_DEF:
 302  24
                             targetAST = parentAST;
 303  24
                             break;
 304  
 
 305  
                         default:
 306  
                             // unexpected target type
 307  
                         }
 308  
                     }
 309  
                     break;
 310  
 
 311  
                 default:
 312  
                     // unexpected container type
 313  
                 }
 314  
             }
 315  24
             if (targetAST == null) {
 316  0
                 log(aAST, "suppress.warnings.invalid.target");
 317  
             }
 318  
 
 319  
             // get text range of target
 320  24
             final int firstLine = targetAST.getLineNo();
 321  24
             final int firstColumn = targetAST.getColumnNo();
 322  24
             final DetailAST nextAST = targetAST.getNextSibling();
 323  
             final int lastLine, lastColumn;
 324  24
             if (nextAST != null) {
 325  22
                 lastLine = nextAST.getLineNo();
 326  22
                 lastColumn = nextAST.getColumnNo() - 1;
 327  
             }
 328  
             else {
 329  2
                 lastLine = Integer.MAX_VALUE;
 330  2
                 lastColumn = Integer.MAX_VALUE;
 331  
             }
 332  
 
 333  
             // add suppression entries for listed checks
 334  24
             final List<Entry> entries = ENTRIES.get();
 335  24
             if (entries != null) {
 336  24
                 for (String value : values) {
 337  
                     // strip off the checkstyle-only prefix if present
 338  24
                     if (value.startsWith(CHECKSTYLE_PREFIX)) {
 339  2
                         value = value.substring(CHECKSTYLE_PREFIX.length());
 340  
                     }
 341  24
                     entries.add(new Entry(value, firstLine, firstColumn,
 342  
                         lastLine, lastColumn));
 343  
                 }
 344  
             }
 345  
         }
 346  24
     }
 347  
 
 348  
     /**
 349  
      * Returns the n'th child of an AST node.
 350  
      * @param aAST the AST node to get the child of
 351  
      * @param aIndex the index of the child to get
 352  
      * @return the n'th child of the given AST node, or {@code null} if none
 353  
      */
 354  
     private static DetailAST getNthChild(DetailAST aAST, int aIndex)
 355  
     {
 356  26
         DetailAST child = aAST.getFirstChild();
 357  26
         if (child != null) {
 358  54
             for (int i = 0; i < aIndex && child != null; ++i) {
 359  28
                 child = child.getNextSibling();
 360  
             }
 361  
         }
 362  26
         return child;
 363  
     }
 364  
 
 365  
     /**
 366  
      * Returns the Java identifier represented by an AST.
 367  
      * @param aAST an AST node for an IDENT or DOT
 368  
      * @return the Java identifier represented by the given AST subtree
 369  
      * @throws IllegalArgumentException if the AST is invalid
 370  
      */
 371  
     private static String getIdentifier(DetailAST aAST)
 372  
     {
 373  32
         if (aAST != null) {
 374  32
             if (aAST.getType() == TokenTypes.IDENT) {
 375  28
                 return aAST.getText();
 376  
             }
 377  4
             else if (aAST.getType() == TokenTypes.DOT) {
 378  4
                 return getIdentifier(aAST.getFirstChild()) + "."
 379  
                     + getIdentifier(aAST.getLastChild());
 380  
             }
 381  
         }
 382  0
         throw new IllegalArgumentException("Identifier AST expected: " + aAST);
 383  
     }
 384  
 
 385  
     /**
 386  
      * Returns the literal string expression represented by an AST.
 387  
      * @param aAST an AST node for an EXPR
 388  
      * @return the Java string represented by the given AST expression
 389  
      * @throws IllegalArgumentException if the AST is invalid
 390  
      */
 391  
     private static String getStringExpr(DetailAST aAST)
 392  
     {
 393  24
         if (aAST != null && aAST.getType() == TokenTypes.EXPR) {
 394  24
             final DetailAST firstChild = aAST.getFirstChild();
 395  24
             if (firstChild.getType() == TokenTypes.STRING_LITERAL) {
 396  
                 // NOTE: escaped characters are not unescaped
 397  24
                 final String quotedText = firstChild.getText();
 398  24
                 return quotedText.substring(1, quotedText.length() - 1);
 399  
             }
 400  0
             throw new IllegalArgumentException("String literal AST expected: "
 401  
                 + firstChild);
 402  
         }
 403  0
         throw new IllegalArgumentException("Expression AST expected: " + aAST);
 404  
     }
 405  
 
 406  
     /**
 407  
      * Returns the annotation values represented by an AST.
 408  
      * @param aAST an AST node for an EXPR or ANNOTATION_ARRAY_INIT
 409  
      * @return the list of Java string represented by the given AST for an
 410  
      *         expression or annotation array initializer
 411  
      * @throws IllegalArgumentException if the AST is invalid
 412  
      */
 413  
     private static List<String> getAnnotationValues(DetailAST aAST)
 414  
     {
 415  24
         switch (aAST.getType()) {
 416  
         case TokenTypes.EXPR:
 417  22
             return ImmutableList.of(getStringExpr(aAST));
 418  
 
 419  
         case TokenTypes.ANNOTATION_ARRAY_INIT:
 420  2
             final List<String> valueList = Lists.newLinkedList();
 421  2
             DetailAST childAST = aAST.getFirstChild();
 422  6
             while (childAST != null) {
 423  4
                 if (childAST.getType() == TokenTypes.EXPR) {
 424  2
                     valueList.add(getStringExpr(childAST));
 425  
                 }
 426  4
                 childAST = childAST.getNextSibling();
 427  
             }
 428  2
             return valueList;
 429  
 
 430  
         default:
 431  
         }
 432  0
         throw new IllegalArgumentException(
 433  
             "Expression or annotation array initializer AST expected: " + aAST);
 434  
     }
 435  
 }