001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.metrics; 021 022import java.math.BigInteger; 023import java.util.ArrayDeque; 024import java.util.Deque; 025 026import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 027import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 030 031/** 032 * <p> 033 * Checks cyclomatic complexity against a specified limit. It is a measure of 034 * the minimum number of possible paths through the source and therefore the 035 * number of required tests, it is not about quality of code! It is only 036 * applied to methods, c-tors, 037 * <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html"> 038 * static initializers and instance initializers</a>. 039 * </p> 040 * <p> 041 * The complexity is equal to the number of decision points {@code + 1}. 042 * Decision points: {@code if}, {@code while}, {@code do}, {@code for}, 043 * {@code ?:}, {@code catch}, {@code switch}, {@code case} statements and 044 * operators {@code &&} and {@code ||} in the body of target. 045 * </p> 046 * <p> 047 * By pure theory level 1-4 is considered easy to test, 5-7 OK, 8-10 consider 048 * re-factoring to ease testing, and 11+ re-factor now as testing will be painful. 049 * </p> 050 * <p> 051 * When it comes to code quality measurement by this metric level 10 is very 052 * good level as a ultimate target (that is hard to archive). Do not be ashamed 053 * to have complexity level 15 or even higher, but keep it below 20 to catch 054 * really bad-designed code automatically. 055 * </p> 056 * <p> 057 * Please use Suppression to avoid violations on cases that could not be split 058 * in few methods without damaging readability of code or encapsulation. 059 * </p> 060 * <ul> 061 * <li> 062 * Property {@code max} - Specify the maximum threshold allowed. 063 * Type is {@code int}. 064 * Default value is {@code 10}. 065 * </li> 066 * <li> 067 * Property {@code switchBlockAsSingleDecisionPoint} - Control whether to treat 068 * the whole switch block as a single decision point. 069 * Type is {@code boolean}. 070 * Default value is {@code false}. 071 * </li> 072 * <li> 073 * Property {@code tokens} - tokens to check 074 * Type is {@code java.lang.String[]}. 075 * Validation type is {@code tokenSet}. 076 * Default value is: 077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE"> 078 * LITERAL_WHILE</a>, 079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO"> 080 * LITERAL_DO</a>, 081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR"> 082 * LITERAL_FOR</a>, 083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF"> 084 * LITERAL_IF</a>, 085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH"> 086 * LITERAL_SWITCH</a>, 087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CASE"> 088 * LITERAL_CASE</a>, 089 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH"> 090 * LITERAL_CATCH</a>, 091 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION"> 092 * QUESTION</a>, 093 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAND"> 094 * LAND</a>, 095 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LOR"> 096 * LOR</a>. 097 * </li> 098 * </ul> 099 * <p> 100 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 101 * </p> 102 * <p> 103 * Violation Message Keys: 104 * </p> 105 * <ul> 106 * <li> 107 * {@code cyclomaticComplexity} 108 * </li> 109 * </ul> 110 * 111 * @since 3.2 112 */ 113@FileStatefulCheck 114public class CyclomaticComplexityCheck 115 extends AbstractCheck { 116 117 /** 118 * A key is pointing to the warning message text in "messages.properties" 119 * file. 120 */ 121 public static final String MSG_KEY = "cyclomaticComplexity"; 122 123 /** The initial current value. */ 124 private static final BigInteger INITIAL_VALUE = BigInteger.ONE; 125 126 /** Default allowed complexity. */ 127 private static final int DEFAULT_COMPLEXITY_VALUE = 10; 128 129 /** Stack of values - all but the current value. */ 130 private final Deque<BigInteger> valueStack = new ArrayDeque<>(); 131 132 /** Control whether to treat the whole switch block as a single decision point. */ 133 private boolean switchBlockAsSingleDecisionPoint; 134 135 /** The current value. */ 136 private BigInteger currentValue = INITIAL_VALUE; 137 138 /** Specify the maximum threshold allowed. */ 139 private int max = DEFAULT_COMPLEXITY_VALUE; 140 141 /** 142 * Setter to control whether to treat the whole switch block as a single decision point. 143 * 144 * @param switchBlockAsSingleDecisionPoint whether to treat the whole switch 145 * block as a single decision point. 146 * @since 6.11 147 */ 148 public void setSwitchBlockAsSingleDecisionPoint(boolean switchBlockAsSingleDecisionPoint) { 149 this.switchBlockAsSingleDecisionPoint = switchBlockAsSingleDecisionPoint; 150 } 151 152 /** 153 * Setter to specify the maximum threshold allowed. 154 * 155 * @param max the maximum threshold 156 * @since 3.2 157 */ 158 public final void setMax(int max) { 159 this.max = max; 160 } 161 162 @Override 163 public int[] getDefaultTokens() { 164 return new int[] { 165 TokenTypes.CTOR_DEF, 166 TokenTypes.METHOD_DEF, 167 TokenTypes.INSTANCE_INIT, 168 TokenTypes.STATIC_INIT, 169 TokenTypes.LITERAL_WHILE, 170 TokenTypes.LITERAL_DO, 171 TokenTypes.LITERAL_FOR, 172 TokenTypes.LITERAL_IF, 173 TokenTypes.LITERAL_SWITCH, 174 TokenTypes.LITERAL_CASE, 175 TokenTypes.LITERAL_CATCH, 176 TokenTypes.QUESTION, 177 TokenTypes.LAND, 178 TokenTypes.LOR, 179 TokenTypes.COMPACT_CTOR_DEF, 180 }; 181 } 182 183 @Override 184 public int[] getAcceptableTokens() { 185 return new int[] { 186 TokenTypes.CTOR_DEF, 187 TokenTypes.METHOD_DEF, 188 TokenTypes.INSTANCE_INIT, 189 TokenTypes.STATIC_INIT, 190 TokenTypes.LITERAL_WHILE, 191 TokenTypes.LITERAL_DO, 192 TokenTypes.LITERAL_FOR, 193 TokenTypes.LITERAL_IF, 194 TokenTypes.LITERAL_SWITCH, 195 TokenTypes.LITERAL_CASE, 196 TokenTypes.LITERAL_CATCH, 197 TokenTypes.QUESTION, 198 TokenTypes.LAND, 199 TokenTypes.LOR, 200 TokenTypes.COMPACT_CTOR_DEF, 201 }; 202 } 203 204 @Override 205 public final int[] getRequiredTokens() { 206 return new int[] { 207 TokenTypes.CTOR_DEF, 208 TokenTypes.METHOD_DEF, 209 TokenTypes.INSTANCE_INIT, 210 TokenTypes.STATIC_INIT, 211 TokenTypes.COMPACT_CTOR_DEF, 212 }; 213 } 214 215 @Override 216 public void visitToken(DetailAST ast) { 217 switch (ast.getType()) { 218 case TokenTypes.CTOR_DEF: 219 case TokenTypes.METHOD_DEF: 220 case TokenTypes.INSTANCE_INIT: 221 case TokenTypes.STATIC_INIT: 222 case TokenTypes.COMPACT_CTOR_DEF: 223 visitMethodDef(); 224 break; 225 default: 226 visitTokenHook(ast); 227 } 228 } 229 230 @Override 231 public void leaveToken(DetailAST ast) { 232 switch (ast.getType()) { 233 case TokenTypes.CTOR_DEF: 234 case TokenTypes.METHOD_DEF: 235 case TokenTypes.INSTANCE_INIT: 236 case TokenTypes.STATIC_INIT: 237 case TokenTypes.COMPACT_CTOR_DEF: 238 leaveMethodDef(ast); 239 break; 240 default: 241 break; 242 } 243 } 244 245 /** 246 * Hook called when visiting a token. Will not be called the method 247 * definition tokens. 248 * 249 * @param ast the token being visited 250 */ 251 private void visitTokenHook(DetailAST ast) { 252 if (switchBlockAsSingleDecisionPoint) { 253 if (ast.getType() != TokenTypes.LITERAL_CASE) { 254 incrementCurrentValue(BigInteger.ONE); 255 } 256 } 257 else if (ast.getType() != TokenTypes.LITERAL_SWITCH) { 258 incrementCurrentValue(BigInteger.ONE); 259 } 260 } 261 262 /** 263 * Process the end of a method definition. 264 * 265 * @param ast the token representing the method definition 266 */ 267 private void leaveMethodDef(DetailAST ast) { 268 final BigInteger bigIntegerMax = BigInteger.valueOf(max); 269 if (currentValue.compareTo(bigIntegerMax) > 0) { 270 log(ast, MSG_KEY, currentValue, bigIntegerMax); 271 } 272 popValue(); 273 } 274 275 /** 276 * Increments the current value by a specified amount. 277 * 278 * @param amount the amount to increment by 279 */ 280 private void incrementCurrentValue(BigInteger amount) { 281 currentValue = currentValue.add(amount); 282 } 283 284 /** Push the current value on the stack. */ 285 private void pushValue() { 286 valueStack.push(currentValue); 287 currentValue = INITIAL_VALUE; 288 } 289 290 /** 291 * Pops a value off the stack and makes it the current value. 292 */ 293 private void popValue() { 294 currentValue = valueStack.pop(); 295 } 296 297 /** Process the start of the method definition. */ 298 private void visitMethodDef() { 299 pushValue(); 300 } 301 302}