Coverage Report - com.puppycrawl.tools.checkstyle.checks.metrics.CyclomaticComplexityCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
CyclomaticComplexityCheck
100%
41/41
100%
12/12
1.923
 
 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.metrics;
 21  
 
 22  
 import java.math.BigInteger;
 23  
 import java.util.ArrayDeque;
 24  
 import java.util.Deque;
 25  
 
 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.api.TokenTypes;
 30  
 
 31  
 /**
 32  
  * Checks cyclomatic complexity against a specified limit. The complexity is
 33  
  * measured by the number of "if", "while", "do", "for", "?:", "catch",
 34  
  * "switch", "case", "&&" and "||" statements (plus one) in the body of
 35  
  * the member. It is a measure of the minimum number of possible paths through
 36  
  * the source and therefore the number of required tests. Generally 1-4 is
 37  
  * considered good, 5-7 ok, 8-10 consider re-factoring, and 11+ re-factor now!
 38  
  *
 39  
  * <p>Check has following properties:
 40  
  *
 41  
  * <p><b>switchBlockAsSingleDecisionPoint</b> - controls whether to treat the whole switch
 42  
  * block as a single decision point. Default value is <b>false</b>
 43  
  *
 44  
  *
 45  
  * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
 46  
  * @author Oliver Burn
 47  
  * @author <a href="mailto:andreyselkin@gmail.com">Andrei Selkin</a>
 48  
  */
 49  
 @FileStatefulCheck
 50  14
 public class CyclomaticComplexityCheck
 51  
     extends AbstractCheck {
 52  
 
 53  
     /**
 54  
      * A key is pointing to the warning message text in "messages.properties"
 55  
      * file.
 56  
      */
 57  
     public static final String MSG_KEY = "cyclomaticComplexity";
 58  
 
 59  
     /** The initial current value. */
 60  1
     private static final BigInteger INITIAL_VALUE = BigInteger.ONE;
 61  
 
 62  
     /** Default allowed complexity. */
 63  
     private static final int DEFAULT_COMPLEXITY_VALUE = 10;
 64  
 
 65  
     /** Stack of values - all but the current value. */
 66  14
     private final Deque<BigInteger> valueStack = new ArrayDeque<>();
 67  
 
 68  
     /** Whether to treat the whole switch block as a single decision point.*/
 69  
     private boolean switchBlockAsSingleDecisionPoint;
 70  
 
 71  
     /** The current value. */
 72  14
     private BigInteger currentValue = INITIAL_VALUE;
 73  
 
 74  
     /** Threshold to report error for. */
 75  14
     private int max = DEFAULT_COMPLEXITY_VALUE;
 76  
 
 77  
     /**
 78  
      * Sets whether to treat the whole switch block as a single decision point.
 79  
      * @param switchBlockAsSingleDecisionPoint whether to treat the whole switch
 80  
      *                                          block as a single decision point.
 81  
      */
 82  
     public void setSwitchBlockAsSingleDecisionPoint(boolean switchBlockAsSingleDecisionPoint) {
 83  2
         this.switchBlockAsSingleDecisionPoint = switchBlockAsSingleDecisionPoint;
 84  2
     }
 85  
 
 86  
     /**
 87  
      * Set the maximum threshold allowed.
 88  
      *
 89  
      * @param max the maximum threshold
 90  
      */
 91  
     public final void setMax(int max) {
 92  6
         this.max = max;
 93  6
     }
 94  
 
 95  
     @Override
 96  
     public int[] getDefaultTokens() {
 97  21
         return new int[] {
 98  
             TokenTypes.CTOR_DEF,
 99  
             TokenTypes.METHOD_DEF,
 100  
             TokenTypes.INSTANCE_INIT,
 101  
             TokenTypes.STATIC_INIT,
 102  
             TokenTypes.LITERAL_WHILE,
 103  
             TokenTypes.LITERAL_DO,
 104  
             TokenTypes.LITERAL_FOR,
 105  
             TokenTypes.LITERAL_IF,
 106  
             TokenTypes.LITERAL_SWITCH,
 107  
             TokenTypes.LITERAL_CASE,
 108  
             TokenTypes.LITERAL_CATCH,
 109  
             TokenTypes.QUESTION,
 110  
             TokenTypes.LAND,
 111  
             TokenTypes.LOR,
 112  
         };
 113  
     }
 114  
 
 115  
     @Override
 116  
     public int[] getAcceptableTokens() {
 117  6
         return new int[] {
 118  
             TokenTypes.CTOR_DEF,
 119  
             TokenTypes.METHOD_DEF,
 120  
             TokenTypes.INSTANCE_INIT,
 121  
             TokenTypes.STATIC_INIT,
 122  
             TokenTypes.LITERAL_WHILE,
 123  
             TokenTypes.LITERAL_DO,
 124  
             TokenTypes.LITERAL_FOR,
 125  
             TokenTypes.LITERAL_IF,
 126  
             TokenTypes.LITERAL_SWITCH,
 127  
             TokenTypes.LITERAL_CASE,
 128  
             TokenTypes.LITERAL_CATCH,
 129  
             TokenTypes.QUESTION,
 130  
             TokenTypes.LAND,
 131  
             TokenTypes.LOR,
 132  
         };
 133  
     }
 134  
 
 135  
     @Override
 136  
     public final int[] getRequiredTokens() {
 137  22
         return new int[] {
 138  
             TokenTypes.CTOR_DEF,
 139  
             TokenTypes.METHOD_DEF,
 140  
             TokenTypes.INSTANCE_INIT,
 141  
             TokenTypes.STATIC_INIT,
 142  
         };
 143  
     }
 144  
 
 145  
     @Override
 146  
     public void visitToken(DetailAST ast) {
 147  54
         switch (ast.getType()) {
 148  
             case TokenTypes.CTOR_DEF:
 149  
             case TokenTypes.METHOD_DEF:
 150  
             case TokenTypes.INSTANCE_INIT:
 151  
             case TokenTypes.STATIC_INIT:
 152  14
                 visitMethodDef();
 153  14
                 break;
 154  
             default:
 155  40
                 visitTokenHook(ast);
 156  
         }
 157  54
     }
 158  
 
 159  
     @Override
 160  
     public void leaveToken(DetailAST ast) {
 161  54
         switch (ast.getType()) {
 162  
             case TokenTypes.CTOR_DEF:
 163  
             case TokenTypes.METHOD_DEF:
 164  
             case TokenTypes.INSTANCE_INIT:
 165  
             case TokenTypes.STATIC_INIT:
 166  14
                 leaveMethodDef(ast);
 167  14
                 break;
 168  
             default:
 169  
                 break;
 170  
         }
 171  54
     }
 172  
 
 173  
     /**
 174  
      * Hook called when visiting a token. Will not be called the method
 175  
      * definition tokens.
 176  
      *
 177  
      * @param ast the token being visited
 178  
      */
 179  
     private void visitTokenHook(DetailAST ast) {
 180  40
         if (switchBlockAsSingleDecisionPoint) {
 181  5
             if (ast.getType() != TokenTypes.LITERAL_CASE) {
 182  1
                 incrementCurrentValue(BigInteger.ONE);
 183  
             }
 184  
         }
 185  35
         else if (ast.getType() != TokenTypes.LITERAL_SWITCH) {
 186  32
             incrementCurrentValue(BigInteger.ONE);
 187  
         }
 188  40
     }
 189  
 
 190  
     /**
 191  
      * Process the end of a method definition.
 192  
      *
 193  
      * @param ast the token representing the method definition
 194  
      */
 195  
     private void leaveMethodDef(DetailAST ast) {
 196  14
         final BigInteger bigIntegerMax = BigInteger.valueOf(max);
 197  14
         if (currentValue.compareTo(bigIntegerMax) > 0) {
 198  12
             log(ast, MSG_KEY, currentValue, bigIntegerMax);
 199  
         }
 200  14
         popValue();
 201  14
     }
 202  
 
 203  
     /**
 204  
      * Increments the current value by a specified amount.
 205  
      *
 206  
      * @param amount the amount to increment by
 207  
      */
 208  
     private void incrementCurrentValue(BigInteger amount) {
 209  33
         currentValue = currentValue.add(amount);
 210  33
     }
 211  
 
 212  
     /** Push the current value on the stack. */
 213  
     private void pushValue() {
 214  14
         valueStack.push(currentValue);
 215  14
         currentValue = INITIAL_VALUE;
 216  14
     }
 217  
 
 218  
     /**
 219  
      * Pops a value off the stack and makes it the current value.
 220  
      */
 221  
     private void popValue() {
 222  14
         currentValue = valueStack.pop();
 223  14
     }
 224  
 
 225  
     /** Process the start of the method definition. */
 226  
     private void visitMethodDef() {
 227  14
         pushValue();
 228  14
     }
 229  
 }