001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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 * 076 * @since 3.2 077 */ 078@FileStatefulCheck 079public class CyclomaticComplexityCheck 080 extends AbstractCheck { 081 082 /** 083 * A key is pointing to the warning message text in "messages.properties" 084 * file. 085 */ 086 public static final String MSG_KEY = "cyclomaticComplexity"; 087 088 /** The initial current value. */ 089 private static final BigInteger INITIAL_VALUE = BigInteger.ONE; 090 091 /** Default allowed complexity. */ 092 private static final int DEFAULT_COMPLEXITY_VALUE = 10; 093 094 /** Stack of values - all but the current value. */ 095 private final Deque<BigInteger> valueStack = new ArrayDeque<>(); 096 097 /** Control whether to treat the whole switch block as a single decision point. */ 098 private boolean switchBlockAsSingleDecisionPoint; 099 100 /** The current value. */ 101 private BigInteger currentValue = INITIAL_VALUE; 102 103 /** Specify the maximum threshold allowed. */ 104 private int max = DEFAULT_COMPLEXITY_VALUE; 105 106 /** 107 * Creates a new {@code CyclomaticComplexityCheck} instance. 108 */ 109 public CyclomaticComplexityCheck() { 110 // no code by default 111 } 112 113 /** 114 * Setter to control whether to treat the whole switch block as a single decision point. 115 * 116 * @param switchBlockAsSingleDecisionPoint whether to treat the whole switch 117 * block as a single decision point. 118 * @since 6.11 119 */ 120 public void setSwitchBlockAsSingleDecisionPoint(boolean switchBlockAsSingleDecisionPoint) { 121 this.switchBlockAsSingleDecisionPoint = switchBlockAsSingleDecisionPoint; 122 } 123 124 /** 125 * Setter to specify the maximum threshold allowed. 126 * 127 * @param max the maximum threshold 128 * @since 3.2 129 */ 130 public final void setMax(int max) { 131 this.max = max; 132 } 133 134 @Override 135 public int[] getDefaultTokens() { 136 return new int[] { 137 TokenTypes.CTOR_DEF, 138 TokenTypes.METHOD_DEF, 139 TokenTypes.INSTANCE_INIT, 140 TokenTypes.STATIC_INIT, 141 TokenTypes.LITERAL_WHILE, 142 TokenTypes.LITERAL_DO, 143 TokenTypes.LITERAL_FOR, 144 TokenTypes.LITERAL_IF, 145 TokenTypes.LITERAL_SWITCH, 146 TokenTypes.LITERAL_CASE, 147 TokenTypes.LITERAL_CATCH, 148 TokenTypes.QUESTION, 149 TokenTypes.LAND, 150 TokenTypes.LOR, 151 TokenTypes.COMPACT_CTOR_DEF, 152 TokenTypes.LITERAL_WHEN, 153 }; 154 } 155 156 @Override 157 public int[] getAcceptableTokens() { 158 return new int[] { 159 TokenTypes.CTOR_DEF, 160 TokenTypes.METHOD_DEF, 161 TokenTypes.INSTANCE_INIT, 162 TokenTypes.STATIC_INIT, 163 TokenTypes.LITERAL_WHILE, 164 TokenTypes.LITERAL_DO, 165 TokenTypes.LITERAL_FOR, 166 TokenTypes.LITERAL_IF, 167 TokenTypes.LITERAL_SWITCH, 168 TokenTypes.LITERAL_CASE, 169 TokenTypes.LITERAL_CATCH, 170 TokenTypes.QUESTION, 171 TokenTypes.LAND, 172 TokenTypes.LOR, 173 TokenTypes.COMPACT_CTOR_DEF, 174 TokenTypes.LITERAL_WHEN, 175 }; 176 } 177 178 @Override 179 public final int[] getRequiredTokens() { 180 return new int[] { 181 TokenTypes.CTOR_DEF, 182 TokenTypes.METHOD_DEF, 183 TokenTypes.INSTANCE_INIT, 184 TokenTypes.STATIC_INIT, 185 TokenTypes.COMPACT_CTOR_DEF, 186 }; 187 } 188 189 @Override 190 public void visitToken(DetailAST ast) { 191 switch (ast.getType()) { 192 case TokenTypes.CTOR_DEF, 193 TokenTypes.METHOD_DEF, 194 TokenTypes.INSTANCE_INIT, 195 TokenTypes.STATIC_INIT, 196 TokenTypes.COMPACT_CTOR_DEF -> visitMethodDef(); 197 198 default -> visitTokenHook(ast); 199 } 200 } 201 202 @Override 203 public void leaveToken(DetailAST ast) { 204 switch (ast.getType()) { 205 case TokenTypes.CTOR_DEF, 206 TokenTypes.METHOD_DEF, 207 TokenTypes.INSTANCE_INIT, 208 TokenTypes.STATIC_INIT, 209 TokenTypes.COMPACT_CTOR_DEF -> leaveMethodDef(ast); 210 211 default -> { 212 // Do nothing 213 } 214 } 215 } 216 217 /** 218 * Hook called when visiting a token. Will not be called the method 219 * definition tokens. 220 * 221 * @param ast the token being visited 222 */ 223 private void visitTokenHook(DetailAST ast) { 224 if (switchBlockAsSingleDecisionPoint) { 225 if (!ScopeUtil.isInBlockOf(ast, TokenTypes.LITERAL_SWITCH)) { 226 incrementCurrentValue(BigInteger.ONE); 227 } 228 } 229 else if (ast.getType() != TokenTypes.LITERAL_SWITCH) { 230 incrementCurrentValue(BigInteger.ONE); 231 } 232 } 233 234 /** 235 * Process the end of a method definition. 236 * 237 * @param ast the token representing the method definition 238 */ 239 private void leaveMethodDef(DetailAST ast) { 240 final BigInteger bigIntegerMax = BigInteger.valueOf(max); 241 if (currentValue.compareTo(bigIntegerMax) > 0) { 242 log(ast, MSG_KEY, currentValue, bigIntegerMax); 243 } 244 popValue(); 245 } 246 247 /** 248 * Increments the current value by a specified amount. 249 * 250 * @param amount the amount to increment by 251 */ 252 private void incrementCurrentValue(BigInteger amount) { 253 currentValue = currentValue.add(amount); 254 } 255 256 /** Push the current value on the stack. */ 257 private void pushValue() { 258 valueStack.push(currentValue); 259 currentValue = INITIAL_VALUE; 260 } 261 262 /** 263 * Pops a value off the stack and makes it the current value. 264 */ 265 private void popValue() { 266 currentValue = valueStack.pop(); 267 } 268 269 /** Process the start of the method definition. */ 270 private void visitMethodDef() { 271 pushValue(); 272 } 273 274}