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 * To configure the check: 101 * </p> 102 * <pre> 103 * <module name="CyclomaticComplexity"/> 104 * </pre> 105 * <p> 106 * Example: 107 * </p> 108 * <pre> 109 * class CyclomaticComplexity { 110 * // Cyclomatic Complexity = 11 111 * int a, b, c, d, n; 112 * public void foo() { // 1, function declaration 113 * if (a == 1) { // 2, if 114 * fun1(); 115 * } else if (a == b // 3, if 116 * && a == c) { // 4, && operator 117 * if (c == 2) { // 5, if 118 * fun2(); 119 * } 120 * } else if (a == d) { // 6, if 121 * try { 122 * fun4(); 123 * } catch (Exception e) { // 7, catch 124 * } 125 * } else { 126 * switch(n) { 127 * case 1: // 8, case 128 * fun1(); 129 * break; 130 * case 2: // 9, case 131 * fun2(); 132 * break; 133 * case 3: // 10, case 134 * fun3(); 135 * break; 136 * default: 137 * break; 138 * } 139 * } 140 * d = a < 0 ? -1 : 1; // 11, ternary operator 141 * } 142 * } 143 * </pre> 144 * <p> 145 * To configure the check with a threshold of 4 and check only for while and do-while loops: 146 * </p> 147 * <pre> 148 * <module name="CyclomaticComplexity"> 149 * <property name="max" value="4"/> 150 * <property name="tokens" value="LITERAL_WHILE, LITERAL_DO"/> 151 * </module> 152 * </pre> 153 * <p> 154 * Example: 155 * </p> 156 * <pre> 157 * class CyclomaticComplexity { 158 * // Cyclomatic Complexity = 5 159 * int a, b, c, d; 160 * public void foo() { // 1, function declaration 161 * while (a < b // 2, while 162 * && a > c) { 163 * fun(); 164 * } 165 * if (a == b) { 166 * do { // 3, do 167 * fun(); 168 * } while (d); 169 * } else if (c == d) { 170 * while (c > 0) { // 4, while 171 * fun(); 172 * } 173 * do { // 5, do-while 174 * fun(); 175 * } while (a); 176 * } 177 * } 178 * } 179 * </pre> 180 * <p> 181 * To configure the check to consider switch-case block as one decision point. 182 * </p> 183 * <pre> 184 * <module name="CyclomaticComplexity"> 185 * <property name="switchBlockAsSingleDecisionPoint" value="true"/> 186 * </module> 187 * </pre> 188 * <p> 189 * Example: 190 * </p> 191 * <pre> 192 * class CyclomaticComplexity { 193 * // Cyclomatic Complexity = 11 194 * int a, b, c, d, e, n; 195 * public void foo() { // 1, function declaration 196 * if (a == b) { // 2, if 197 * fun1(); 198 * } else if (a == 0 // 3, if 199 * && b == c) { // 4, && operator 200 * if (c == -1) { // 5, if 201 * fun2(); 202 * } 203 * } else if (a == c // 6, if 204 * || a == d) { // 7, || operator 205 * fun3(); 206 * } else if (d == e) { // 8, if 207 * try { 208 * fun4(); 209 * } catch (Exception e) { // 9, catch 210 * } 211 * } else { 212 * switch(n) { // 10, switch 213 * case 1: 214 * fun1(); 215 * break; 216 * case 2: 217 * fun2(); 218 * break; 219 * default: 220 * break; 221 * } 222 * } 223 * a = a > 0 ? b : c; // 11, ternary operator 224 * } 225 * } 226 * </pre> 227 * <p> 228 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 229 * </p> 230 * <p> 231 * Violation Message Keys: 232 * </p> 233 * <ul> 234 * <li> 235 * {@code cyclomaticComplexity} 236 * </li> 237 * </ul> 238 * 239 * @since 3.2 240 */ 241@FileStatefulCheck 242public class CyclomaticComplexityCheck 243 extends AbstractCheck { 244 245 /** 246 * A key is pointing to the warning message text in "messages.properties" 247 * file. 248 */ 249 public static final String MSG_KEY = "cyclomaticComplexity"; 250 251 /** The initial current value. */ 252 private static final BigInteger INITIAL_VALUE = BigInteger.ONE; 253 254 /** Default allowed complexity. */ 255 private static final int DEFAULT_COMPLEXITY_VALUE = 10; 256 257 /** Stack of values - all but the current value. */ 258 private final Deque<BigInteger> valueStack = new ArrayDeque<>(); 259 260 /** Control whether to treat the whole switch block as a single decision point. */ 261 private boolean switchBlockAsSingleDecisionPoint; 262 263 /** The current value. */ 264 private BigInteger currentValue = INITIAL_VALUE; 265 266 /** Specify the maximum threshold allowed. */ 267 private int max = DEFAULT_COMPLEXITY_VALUE; 268 269 /** 270 * Setter to control whether to treat the whole switch block as a single decision point. 271 * 272 * @param switchBlockAsSingleDecisionPoint whether to treat the whole switch 273 * block as a single decision point. 274 */ 275 public void setSwitchBlockAsSingleDecisionPoint(boolean switchBlockAsSingleDecisionPoint) { 276 this.switchBlockAsSingleDecisionPoint = switchBlockAsSingleDecisionPoint; 277 } 278 279 /** 280 * Setter to specify the maximum threshold allowed. 281 * 282 * @param max the maximum threshold 283 */ 284 public final void setMax(int max) { 285 this.max = max; 286 } 287 288 @Override 289 public int[] getDefaultTokens() { 290 return new int[] { 291 TokenTypes.CTOR_DEF, 292 TokenTypes.METHOD_DEF, 293 TokenTypes.INSTANCE_INIT, 294 TokenTypes.STATIC_INIT, 295 TokenTypes.LITERAL_WHILE, 296 TokenTypes.LITERAL_DO, 297 TokenTypes.LITERAL_FOR, 298 TokenTypes.LITERAL_IF, 299 TokenTypes.LITERAL_SWITCH, 300 TokenTypes.LITERAL_CASE, 301 TokenTypes.LITERAL_CATCH, 302 TokenTypes.QUESTION, 303 TokenTypes.LAND, 304 TokenTypes.LOR, 305 TokenTypes.COMPACT_CTOR_DEF, 306 }; 307 } 308 309 @Override 310 public int[] getAcceptableTokens() { 311 return new int[] { 312 TokenTypes.CTOR_DEF, 313 TokenTypes.METHOD_DEF, 314 TokenTypes.INSTANCE_INIT, 315 TokenTypes.STATIC_INIT, 316 TokenTypes.LITERAL_WHILE, 317 TokenTypes.LITERAL_DO, 318 TokenTypes.LITERAL_FOR, 319 TokenTypes.LITERAL_IF, 320 TokenTypes.LITERAL_SWITCH, 321 TokenTypes.LITERAL_CASE, 322 TokenTypes.LITERAL_CATCH, 323 TokenTypes.QUESTION, 324 TokenTypes.LAND, 325 TokenTypes.LOR, 326 TokenTypes.COMPACT_CTOR_DEF, 327 }; 328 } 329 330 @Override 331 public final int[] getRequiredTokens() { 332 return new int[] { 333 TokenTypes.CTOR_DEF, 334 TokenTypes.METHOD_DEF, 335 TokenTypes.INSTANCE_INIT, 336 TokenTypes.STATIC_INIT, 337 TokenTypes.COMPACT_CTOR_DEF, 338 }; 339 } 340 341 @Override 342 public void visitToken(DetailAST ast) { 343 switch (ast.getType()) { 344 case TokenTypes.CTOR_DEF: 345 case TokenTypes.METHOD_DEF: 346 case TokenTypes.INSTANCE_INIT: 347 case TokenTypes.STATIC_INIT: 348 case TokenTypes.COMPACT_CTOR_DEF: 349 visitMethodDef(); 350 break; 351 default: 352 visitTokenHook(ast); 353 } 354 } 355 356 @Override 357 public void leaveToken(DetailAST ast) { 358 switch (ast.getType()) { 359 case TokenTypes.CTOR_DEF: 360 case TokenTypes.METHOD_DEF: 361 case TokenTypes.INSTANCE_INIT: 362 case TokenTypes.STATIC_INIT: 363 case TokenTypes.COMPACT_CTOR_DEF: 364 leaveMethodDef(ast); 365 break; 366 default: 367 break; 368 } 369 } 370 371 /** 372 * Hook called when visiting a token. Will not be called the method 373 * definition tokens. 374 * 375 * @param ast the token being visited 376 */ 377 private void visitTokenHook(DetailAST ast) { 378 if (switchBlockAsSingleDecisionPoint) { 379 if (ast.getType() != TokenTypes.LITERAL_CASE) { 380 incrementCurrentValue(BigInteger.ONE); 381 } 382 } 383 else if (ast.getType() != TokenTypes.LITERAL_SWITCH) { 384 incrementCurrentValue(BigInteger.ONE); 385 } 386 } 387 388 /** 389 * Process the end of a method definition. 390 * 391 * @param ast the token representing the method definition 392 */ 393 private void leaveMethodDef(DetailAST ast) { 394 final BigInteger bigIntegerMax = BigInteger.valueOf(max); 395 if (currentValue.compareTo(bigIntegerMax) > 0) { 396 log(ast, MSG_KEY, currentValue, bigIntegerMax); 397 } 398 popValue(); 399 } 400 401 /** 402 * Increments the current value by a specified amount. 403 * 404 * @param amount the amount to increment by 405 */ 406 private void incrementCurrentValue(BigInteger amount) { 407 currentValue = currentValue.add(amount); 408 } 409 410 /** Push the current value on the stack. */ 411 private void pushValue() { 412 valueStack.push(currentValue); 413 currentValue = INITIAL_VALUE; 414 } 415 416 /** 417 * Pops a value off the stack and makes it the current value. 418 */ 419 private void popValue() { 420 currentValue = valueStack.pop(); 421 } 422 423 /** Process the start of the method definition. */ 424 private void visitMethodDef() { 425 pushValue(); 426 } 427 428}