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