Coverage Report - com.puppycrawl.tools.checkstyle.checks.DescendantTokenCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
DescendantTokenCheck
100%
89/89
100%
36/36
2.2
 
 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;
 21  
 
 22  
 import java.util.Arrays;
 23  
 import java.util.Set;
 24  
 
 25  
 import antlr.collections.AST;
 26  
 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
 27  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 28  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 29  
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 30  
 import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
 31  
 
 32  
 /**
 33  
  * <p>
 34  
  * Checks for restricted tokens beneath other tokens.
 35  
  * </p>
 36  
  * <p>
 37  
  * Examples of how to configure the check:
 38  
  * </p>
 39  
  * <pre>
 40  
  * &lt;!-- String literal equality check --&gt;
 41  
  * &lt;module name="DescendantToken"&gt;
 42  
  *     &lt;property name="tokens" value="EQUAL,NOT_EQUAL"/&gt;
 43  
  *     &lt;property name="limitedTokens" value="STRING_LITERAL"/&gt;
 44  
  *     &lt;property name="maximumNumber" value="0"/&gt;
 45  
  *     &lt;property name="maximumDepth" value="1"/&gt;
 46  
  * &lt;/module&gt;
 47  
  *
 48  
  * &lt;!-- Switch with no default --&gt;
 49  
  * &lt;module name="DescendantToken"&gt;
 50  
  *     &lt;property name="tokens" value="LITERAL_SWITCH"/&gt;
 51  
  *     &lt;property name="maximumDepth" value="2"/&gt;
 52  
  *     &lt;property name="limitedTokens" value="LITERAL_DEFAULT"/&gt;
 53  
  *     &lt;property name="minimumNumber" value="1"/&gt;
 54  
  * &lt;/module&gt;
 55  
  *
 56  
  * &lt;!-- Assert statement may have side effects --&gt;
 57  
  * &lt;module name="DescendantToken"&gt;
 58  
  *     &lt;property name="tokens" value="LITERAL_ASSERT"/&gt;
 59  
  *     &lt;property name="limitedTokens" value="ASSIGN,DEC,INC,POST_DEC,
 60  
  *     POST_INC,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,DIV_ASSIGN,MOD_ASSIGN,
 61  
  *     BSR_ASSIGN,SR_ASSIGN,SL_ASSIGN,BAND_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN,
 62  
  *     METHOD_CALL"/&gt;
 63  
  *     &lt;property name="maximumNumber" value="0"/&gt;
 64  
  * &lt;/module&gt;
 65  
  *
 66  
  * &lt;!-- Initializer in for performs no setup - use while instead? --&gt;
 67  
  * &lt;module name="DescendantToken"&gt;
 68  
  *     &lt;property name="tokens" value="FOR_INIT"/&gt;
 69  
  *     &lt;property name="limitedTokens" value="EXPR"/&gt;
 70  
  *     &lt;property name="minimumNumber" value="1"/&gt;
 71  
  * &lt;/module&gt;
 72  
  *
 73  
  * &lt;!-- Condition in for performs no check --&gt;
 74  
  * &lt;module name="DescendantToken"&gt;
 75  
  *     &lt;property name="tokens" value="FOR_CONDITION"/&gt;
 76  
  *     &lt;property name="limitedTokens" value="EXPR"/&gt;
 77  
  *     &lt;property name="minimumNumber" value="1"/&gt;
 78  
  * &lt;/module&gt;
 79  
  *
 80  
  * &lt;!-- Switch within switch --&gt;
 81  
  * &lt;module name="DescendantToken"&gt;
 82  
  *     &lt;property name="tokens" value="LITERAL_SWITCH"/&gt;
 83  
  *     &lt;property name="limitedTokens" value="LITERAL_SWITCH"/&gt;
 84  
  *     &lt;property name="maximumNumber" value="0"/&gt;
 85  
  *     &lt;property name="minimumDepth" value="1"/&gt;
 86  
  * &lt;/module&gt;
 87  
  *
 88  
  * &lt;!-- Return from within a catch or finally block --&gt;
 89  
  * &lt;module name="DescendantToken"&gt;
 90  
  *     &lt;property name="tokens" value="LITERAL_FINALLY,LITERAL_CATCH"/&gt;
 91  
  *     &lt;property name="limitedTokens" value="LITERAL_RETURN"/&gt;
 92  
  *     &lt;property name="maximumNumber" value="0"/&gt;
 93  
  * &lt;/module&gt;
 94  
  *
 95  
  * &lt;!-- Try within catch or finally block --&gt;
 96  
  * &lt;module name="DescendantToken"&gt;
 97  
  *     &lt;property name="tokens" value="LITERAL_CATCH,LITERAL_FINALLY"/&gt;
 98  
  *     &lt;property name="limitedTokens" value="LITERAL_TRY"/&gt;
 99  
  *     &lt;property name="maximumNumber" value="0"/&gt;
 100  
  * &lt;/module&gt;
 101  
  *
 102  
  * &lt;!-- Too many cases within a switch --&gt;
 103  
  * &lt;module name="DescendantToken"&gt;
 104  
  *     &lt;property name="tokens" value="LITERAL_SWITCH"/&gt;
 105  
  *     &lt;property name="limitedTokens" value="LITERAL_CASE"/&gt;
 106  
  *     &lt;property name="maximumDepth" value="2"/&gt;
 107  
  *     &lt;property name="maximumNumber" value="10"/&gt;
 108  
  * &lt;/module&gt;
 109  
  *
 110  
  * &lt;!-- Too many local variables within a method --&gt;
 111  
  * &lt;module name="DescendantToken"&gt;
 112  
  *     &lt;property name="tokens" value="METHOD_DEF"/&gt;
 113  
  *     &lt;property name="limitedTokens" value="VARIABLE_DEF"/&gt;
 114  
  *     &lt;property name="maximumDepth" value="2"/&gt;
 115  
  *     &lt;property name="maximumNumber" value="10"/&gt;
 116  
  * &lt;/module&gt;
 117  
  *
 118  
  * &lt;!-- Too many returns from within a method --&gt;
 119  
  * &lt;module name="DescendantToken"&gt;
 120  
  *     &lt;property name="tokens" value="METHOD_DEF"/&gt;
 121  
  *     &lt;property name="limitedTokens" value="LITERAL_RETURN"/&gt;
 122  
  *     &lt;property name="maximumNumber" value="3"/&gt;
 123  
  * &lt;/module&gt;
 124  
  *
 125  
  * &lt;!-- Too many fields within an interface --&gt;
 126  
  * &lt;module name="DescendantToken"&gt;
 127  
  *     &lt;property name="tokens" value="INTERFACE_DEF"/&gt;
 128  
  *     &lt;property name="limitedTokens" value="VARIABLE_DEF"/&gt;
 129  
  *     &lt;property name="maximumDepth" value="2"/&gt;
 130  
  *     &lt;property name="maximumNumber" value="0"/&gt;
 131  
  * &lt;/module&gt;
 132  
  *
 133  
  * &lt;!-- Limit the number of exceptions a method can throw --&gt;
 134  
  * &lt;module name="DescendantToken"&gt;
 135  
  *     &lt;property name="tokens" value="LITERAL_THROWS"/&gt;
 136  
  *     &lt;property name="limitedTokens" value="IDENT"/&gt;
 137  
  *     &lt;property name="maximumNumber" value="1"/&gt;
 138  
  * &lt;/module&gt;
 139  
  *
 140  
  * &lt;!-- Limit the number of expressions in a method --&gt;
 141  
  * &lt;module name="DescendantToken"&gt;
 142  
  *     &lt;property name="tokens" value="METHOD_DEF"/&gt;
 143  
  *     &lt;property name="limitedTokens" value="EXPR"/&gt;
 144  
  *     &lt;property name="maximumNumber" value="200"/&gt;
 145  
  * &lt;/module&gt;
 146  
  *
 147  
  * &lt;!-- Disallow empty statements --&gt;
 148  
  * &lt;module name="DescendantToken"&gt;
 149  
  *     &lt;property name="tokens" value="EMPTY_STAT"/&gt;
 150  
  *     &lt;property name="limitedTokens" value="EMPTY_STAT"/&gt;
 151  
  *     &lt;property name="maximumNumber" value="0"/&gt;
 152  
  *     &lt;property name="maximumDepth" value="0"/&gt;
 153  
  *     &lt;property name="maximumMessage"
 154  
  *         value="Empty statement is not allowed."/&gt;
 155  
  * &lt;/module&gt;
 156  
  *
 157  
  * &lt;!-- Too many fields within a class --&gt;
 158  
  * &lt;module name="DescendantToken"&gt;
 159  
  *     &lt;property name="tokens" value="CLASS_DEF"/&gt;
 160  
  *     &lt;property name="limitedTokens" value="VARIABLE_DEF"/&gt;
 161  
  *     &lt;property name="maximumDepth" value="2"/&gt;
 162  
  *     &lt;property name="maximumNumber" value="10"/&gt;
 163  
  * &lt;/module&gt;
 164  
  * </pre>
 165  
  *
 166  
  * @author Tim Tyler &lt;tim@tt1.org&gt;
 167  
  * @author Rick Giles
 168  
  */
 169  
 @FileStatefulCheck
 170  45
 public class DescendantTokenCheck extends AbstractCheck {
 171  
 
 172  
     /**
 173  
      * A key is pointing to the warning message text in "messages.properties"
 174  
      * file.
 175  
      */
 176  
     public static final String MSG_KEY_MIN = "descendant.token.min";
 177  
 
 178  
     /**
 179  
      * A key is pointing to the warning message text in "messages.properties"
 180  
      * file.
 181  
      */
 182  
     public static final String MSG_KEY_MAX = "descendant.token.max";
 183  
 
 184  
     /**
 185  
      * A key is pointing to the warning message text in "messages.properties"
 186  
      * file.
 187  
      */
 188  
     public static final String MSG_KEY_SUM_MIN = "descendant.token.sum.min";
 189  
 
 190  
     /**
 191  
      * A key is pointing to the warning message text in "messages.properties"
 192  
      * file.
 193  
      */
 194  
     public static final String MSG_KEY_SUM_MAX = "descendant.token.sum.max";
 195  
 
 196  
     /** Minimum depth. */
 197  
     private int minimumDepth;
 198  
     /** Maximum depth. */
 199  45
     private int maximumDepth = Integer.MAX_VALUE;
 200  
     /** Minimum number. */
 201  
     private int minimumNumber;
 202  
     /** Maximum number. */
 203  45
     private int maximumNumber = Integer.MAX_VALUE;
 204  
     /** Whether to sum the number of tokens found. */
 205  
     private boolean sumTokenCounts;
 206  
     /** Limited tokens. */
 207  45
     private int[] limitedTokens = CommonUtils.EMPTY_INT_ARRAY;
 208  
     /** Error message when minimum count not reached. */
 209  
     private String minimumMessage;
 210  
     /** Error message when maximum count exceeded. */
 211  
     private String maximumMessage;
 212  
 
 213  
     /**
 214  
      * Counts of descendant tokens.
 215  
      * Indexed by (token ID - 1) for performance.
 216  
      */
 217  45
     private int[] counts = CommonUtils.EMPTY_INT_ARRAY;
 218  
 
 219  
     @Override
 220  
     public int[] getDefaultTokens() {
 221  6
         return getRequiredTokens();
 222  
     }
 223  
 
 224  
     @Override
 225  
     public int[] getRequiredTokens() {
 226  88
         return CommonUtils.EMPTY_INT_ARRAY;
 227  
     }
 228  
 
 229  
     @Override
 230  
     public void visitToken(DetailAST ast) {
 231  
         //reset counts
 232  73
         Arrays.fill(counts, 0);
 233  73
         countTokens(ast, 0);
 234  
 
 235  73
         if (sumTokenCounts) {
 236  28
             logAsTotal(ast);
 237  
         }
 238  
         else {
 239  45
             logAsSeparated(ast);
 240  
         }
 241  73
     }
 242  
 
 243  
     /**
 244  
      * Log violations for each Token.
 245  
      * @param ast token
 246  
      */
 247  
     private void logAsSeparated(DetailAST ast) {
 248  
         // name of this token
 249  45
         final String name = TokenUtils.getTokenName(ast.getType());
 250  
 
 251  105
         for (int element : limitedTokens) {
 252  60
             final int tokenCount = counts[element - 1];
 253  60
             if (tokenCount < minimumNumber) {
 254  2
                 final String descendantName = TokenUtils.getTokenName(element);
 255  
 
 256  2
                 if (minimumMessage == null) {
 257  1
                     minimumMessage = MSG_KEY_MIN;
 258  
                 }
 259  4
                 log(ast.getLineNo(), ast.getColumnNo(),
 260  
                         minimumMessage,
 261  2
                         String.valueOf(tokenCount),
 262  2
                         String.valueOf(minimumNumber),
 263  
                         name,
 264  
                         descendantName);
 265  
             }
 266  60
             if (tokenCount > maximumNumber) {
 267  33
                 final String descendantName = TokenUtils.getTokenName(element);
 268  
 
 269  33
                 if (maximumMessage == null) {
 270  3
                     maximumMessage = MSG_KEY_MAX;
 271  
                 }
 272  66
                 log(ast.getLineNo(), ast.getColumnNo(),
 273  
                         maximumMessage,
 274  33
                         String.valueOf(tokenCount),
 275  33
                         String.valueOf(maximumNumber),
 276  
                         name,
 277  
                         descendantName);
 278  
             }
 279  
         }
 280  45
     }
 281  
 
 282  
     /**
 283  
      * Log validation as one violation.
 284  
      * @param ast current token
 285  
      */
 286  
     private void logAsTotal(DetailAST ast) {
 287  
         // name of this token
 288  28
         final String name = TokenUtils.getTokenName(ast.getType());
 289  
 
 290  28
         int total = 0;
 291  84
         for (int element : limitedTokens) {
 292  56
             total += counts[element - 1];
 293  
         }
 294  28
         if (total < minimumNumber) {
 295  14
             if (minimumMessage == null) {
 296  1
                 minimumMessage = MSG_KEY_SUM_MIN;
 297  
             }
 298  28
             log(ast.getLineNo(), ast.getColumnNo(),
 299  
                     minimumMessage,
 300  14
                     String.valueOf(total),
 301  14
                     String.valueOf(minimumNumber), name);
 302  
         }
 303  28
         if (total > maximumNumber) {
 304  8
             if (maximumMessage == null) {
 305  1
                 maximumMessage = MSG_KEY_SUM_MAX;
 306  
             }
 307  16
             log(ast.getLineNo(), ast.getColumnNo(),
 308  
                     maximumMessage,
 309  8
                     String.valueOf(total),
 310  8
                     String.valueOf(maximumNumber), name);
 311  
         }
 312  28
     }
 313  
 
 314  
     /**
 315  
      * Counts the number of occurrences of descendant tokens.
 316  
      * @param ast the root token for descendants.
 317  
      * @param depth the maximum depth of the counted descendants.
 318  
      */
 319  
     private void countTokens(AST ast, int depth) {
 320  465
         if (depth <= maximumDepth) {
 321  
             //update count
 322  402
             if (depth >= minimumDepth) {
 323  392
                 final int type = ast.getType();
 324  392
                 if (type <= counts.length) {
 325  373
                     counts[type - 1]++;
 326  
                 }
 327  
             }
 328  402
             AST child = ast.getFirstChild();
 329  402
             final int nextDepth = depth + 1;
 330  794
             while (child != null) {
 331  392
                 countTokens(child, nextDepth);
 332  392
                 child = child.getNextSibling();
 333  
             }
 334  
         }
 335  465
     }
 336  
 
 337  
     @Override
 338  
     public int[] getAcceptableTokens() {
 339  
         // Any tokens set by property 'tokens' are acceptable
 340  42
         final Set<String> tokenNames = getTokenNames();
 341  42
         final int[] result = new int[tokenNames.size()];
 342  42
         int index = 0;
 343  42
         for (String name : tokenNames) {
 344  51
             result[index] = TokenUtils.getTokenId(name);
 345  51
             index++;
 346  51
         }
 347  42
         return result;
 348  
     }
 349  
 
 350  
     /**
 351  
      * Sets the tokens which occurrence as descendant is limited.
 352  
      * @param limitedTokensParam - list of tokens to ignore.
 353  
      */
 354  
     public void setLimitedTokens(String... limitedTokensParam) {
 355  38
         limitedTokens = new int[limitedTokensParam.length];
 356  
 
 357  38
         int maxToken = 0;
 358  102
         for (int i = 0; i < limitedTokensParam.length; i++) {
 359  64
             limitedTokens[i] = TokenUtils.getTokenId(limitedTokensParam[i]);
 360  64
             if (limitedTokens[i] >= maxToken + 1) {
 361  46
                 maxToken = limitedTokens[i];
 362  
             }
 363  
         }
 364  38
         counts = new int[maxToken];
 365  38
     }
 366  
 
 367  
     /**
 368  
      * Sets the minimum depth for descendant counts.
 369  
      * @param minimumDepth the minimum depth for descendant counts.
 370  
      */
 371  
     public void setMinimumDepth(int minimumDepth) {
 372  2
         this.minimumDepth = minimumDepth;
 373  2
     }
 374  
 
 375  
     /**
 376  
      * Sets the maximum depth for descendant counts.
 377  
      * @param maximumDepth the maximum depth for descendant counts.
 378  
      */
 379  
     public void setMaximumDepth(int maximumDepth) {
 380  20
         this.maximumDepth = maximumDepth;
 381  20
     }
 382  
 
 383  
     /**
 384  
      * Sets a minimum count for descendants.
 385  
      * @param minimumNumber the minimum count for descendants.
 386  
      */
 387  
     public void setMinimumNumber(int minimumNumber) {
 388  8
         this.minimumNumber = minimumNumber;
 389  8
     }
 390  
 
 391  
     /**
 392  
       * Sets a maximum count for descendants.
 393  
       * @param maximumNumber the maximum count for descendants.
 394  
       */
 395  
     public void setMaximumNumber(int maximumNumber) {
 396  30
         this.maximumNumber = maximumNumber;
 397  30
     }
 398  
 
 399  
     /**
 400  
      * Sets the error message for minimum count not reached.
 401  
      * @param message the error message for minimum count not reached.
 402  
      *     Used as a {@code MessageFormat} pattern with arguments
 403  
      *     <ul>
 404  
      *     <li>{0} - token count</li>
 405  
      *     <li>{1} - minimum number</li>
 406  
      *     <li>{2} - name of token</li>
 407  
      *     <li>{3} - name of limited token</li>
 408  
      *     </ul>
 409  
      */
 410  
     public void setMinimumMessage(String message) {
 411  2
         minimumMessage = message;
 412  2
     }
 413  
 
 414  
     /**
 415  
      * Sets the error message for maximum count exceeded.
 416  
      * @param message the error message for maximum count exceeded.
 417  
      *     Used as a {@code MessageFormat} pattern with arguments
 418  
      * <ul>
 419  
      * <li>{0} - token count</li>
 420  
      * <li>{1} - maximum number</li>
 421  
      * <li>{2} - name of token</li>
 422  
      * <li>{3} - name of limited token</li>
 423  
      * </ul>
 424  
      */
 425  
 
 426  
     public void setMaximumMessage(String message) {
 427  10
         maximumMessage = message;
 428  10
     }
 429  
 
 430  
     /**
 431  
      * Sets whether to use the sum of the tokens found, rather than the
 432  
      * individual counts.
 433  
      * @param sum whether to use the sum.
 434  
      */
 435  
     public void setSumTokenCounts(boolean sum) {
 436  5
         sumTokenCounts = sum;
 437  5
     }
 438  
 }