001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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 a 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 * Default value is {@code 10}. 064 * </li> 065 * <li> 066 * Property {@code switchBlockAsSingleDecisionPoint} - Control whether to treat 067 * the whole switch block as a single decision point. 068 * Default value is {@code false}. 069 * </li> 070 * <li> 071 * Property {@code tokens} - tokens to check 072 * Default value is: 073 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE"> 074 * LITERAL_WHILE</a>, 075 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO"> 076 * LITERAL_DO</a>, 077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR"> 078 * LITERAL_FOR</a>, 079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF"> 080 * LITERAL_IF</a>, 081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH"> 082 * LITERAL_SWITCH</a>, 083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CASE"> 084 * LITERAL_CASE</a>, 085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH"> 086 * LITERAL_CATCH</a>, 087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION"> 088 * QUESTION</a>, 089 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAND"> 090 * LAND</a>, 091 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LOR"> 092 * LOR</a>. 093 * </li> 094 * </ul> 095 * <p> 096 * To configure the check: 097 * </p> 098 * <pre> 099 * <module name="CyclomaticComplexity"/> 100 * </pre> 101 * <p> 102 * To configure the check with a threshold of 15: 103 * </p> 104 * <pre> 105 * <module name="CyclomaticComplexity"> 106 * <property name="max" value="15"/> 107 * </module> 108 * </pre> 109 * <p> 110 * Explanation on how complexity is calculated (switchBlockAsSingleDecisionPoint is set to false): 111 * </p> 112 * <pre> 113 * class CC { 114 * // Cyclomatic Complexity = 12 115 * public void doSmth() { // 1 116 * if (a == b) { // 2 117 * if (a1 == b1 // 3 118 * && c1 == d1) { // 4 119 * fiddle(); 120 * } 121 * else if (a2 == b2 // 5 122 * || c1 < d1) { // 6 123 * fiddle(); 124 * } 125 * else { 126 * fiddle(); 127 * } 128 * } 129 * else if (c == d) { // 7 130 * while (c == d) { // 8 131 * fiddle(); 132 * } 133 * } 134 * else if (e == f) { 135 * for (n = 0; n < h // 9 136 * || n < 6; n++) { // 10 137 * fiddle(); 138 * } 139 * } 140 * else { 141 * switch (z) { 142 * case 1: // 11 143 * fiddle(); 144 * break; 145 * case 2: // 12 146 * fiddle(); 147 * break; 148 * default: 149 * fiddle(); 150 * break; 151 * } 152 * } 153 * } 154 * } 155 * </pre> 156 * <p> 157 * Explanation on how complexity is calculated (switchBlockAsSingleDecisionPoint is set to true): 158 * </p> 159 * <pre> 160 * class SwitchExample { 161 * // Cyclomatic Complexity = 2 162 * public void doSmth() { // 1 163 * int z = 1; 164 * switch (z) { // 2 165 * case 1: 166 * foo1(); 167 * break; 168 * case 2: 169 * foo2(); 170 * break; 171 * default: 172 * fooDefault(); 173 * break; 174 * } 175 * } 176 * } 177 * </pre> 178 * 179 * @since 3.2 180 */ 181@FileStatefulCheck 182public class CyclomaticComplexityCheck 183 extends AbstractCheck { 184 185 /** 186 * A key is pointing to the warning message text in "messages.properties" 187 * file. 188 */ 189 public static final String MSG_KEY = "cyclomaticComplexity"; 190 191 /** The initial current value. */ 192 private static final BigInteger INITIAL_VALUE = BigInteger.ONE; 193 194 /** Default allowed complexity. */ 195 private static final int DEFAULT_COMPLEXITY_VALUE = 10; 196 197 /** Stack of values - all but the current value. */ 198 private final Deque<BigInteger> valueStack = new ArrayDeque<>(); 199 200 /** Control whether to treat the whole switch block as a single decision point. */ 201 private boolean switchBlockAsSingleDecisionPoint; 202 203 /** The current value. */ 204 private BigInteger currentValue = INITIAL_VALUE; 205 206 /** Specify the maximum threshold allowed. */ 207 private int max = DEFAULT_COMPLEXITY_VALUE; 208 209 /** 210 * Setter to control whether to treat the whole switch block as a single decision point. 211 * 212 * @param switchBlockAsSingleDecisionPoint whether to treat the whole switch 213 * block as a single decision point. 214 */ 215 public void setSwitchBlockAsSingleDecisionPoint(boolean switchBlockAsSingleDecisionPoint) { 216 this.switchBlockAsSingleDecisionPoint = switchBlockAsSingleDecisionPoint; 217 } 218 219 /** 220 * Setter to specify the maximum threshold allowed. 221 * 222 * @param max the maximum threshold 223 */ 224 public final void setMax(int max) { 225 this.max = max; 226 } 227 228 @Override 229 public int[] getDefaultTokens() { 230 return new int[] { 231 TokenTypes.CTOR_DEF, 232 TokenTypes.METHOD_DEF, 233 TokenTypes.INSTANCE_INIT, 234 TokenTypes.STATIC_INIT, 235 TokenTypes.LITERAL_WHILE, 236 TokenTypes.LITERAL_DO, 237 TokenTypes.LITERAL_FOR, 238 TokenTypes.LITERAL_IF, 239 TokenTypes.LITERAL_SWITCH, 240 TokenTypes.LITERAL_CASE, 241 TokenTypes.LITERAL_CATCH, 242 TokenTypes.QUESTION, 243 TokenTypes.LAND, 244 TokenTypes.LOR, 245 }; 246 } 247 248 @Override 249 public int[] getAcceptableTokens() { 250 return new int[] { 251 TokenTypes.CTOR_DEF, 252 TokenTypes.METHOD_DEF, 253 TokenTypes.INSTANCE_INIT, 254 TokenTypes.STATIC_INIT, 255 TokenTypes.LITERAL_WHILE, 256 TokenTypes.LITERAL_DO, 257 TokenTypes.LITERAL_FOR, 258 TokenTypes.LITERAL_IF, 259 TokenTypes.LITERAL_SWITCH, 260 TokenTypes.LITERAL_CASE, 261 TokenTypes.LITERAL_CATCH, 262 TokenTypes.QUESTION, 263 TokenTypes.LAND, 264 TokenTypes.LOR, 265 }; 266 } 267 268 @Override 269 public final int[] getRequiredTokens() { 270 return new int[] { 271 TokenTypes.CTOR_DEF, 272 TokenTypes.METHOD_DEF, 273 TokenTypes.INSTANCE_INIT, 274 TokenTypes.STATIC_INIT, 275 }; 276 } 277 278 @Override 279 public void visitToken(DetailAST ast) { 280 switch (ast.getType()) { 281 case TokenTypes.CTOR_DEF: 282 case TokenTypes.METHOD_DEF: 283 case TokenTypes.INSTANCE_INIT: 284 case TokenTypes.STATIC_INIT: 285 visitMethodDef(); 286 break; 287 default: 288 visitTokenHook(ast); 289 } 290 } 291 292 @Override 293 public void leaveToken(DetailAST ast) { 294 switch (ast.getType()) { 295 case TokenTypes.CTOR_DEF: 296 case TokenTypes.METHOD_DEF: 297 case TokenTypes.INSTANCE_INIT: 298 case TokenTypes.STATIC_INIT: 299 leaveMethodDef(ast); 300 break; 301 default: 302 break; 303 } 304 } 305 306 /** 307 * Hook called when visiting a token. Will not be called the method 308 * definition tokens. 309 * 310 * @param ast the token being visited 311 */ 312 private void visitTokenHook(DetailAST ast) { 313 if (switchBlockAsSingleDecisionPoint) { 314 if (ast.getType() != TokenTypes.LITERAL_CASE) { 315 incrementCurrentValue(BigInteger.ONE); 316 } 317 } 318 else if (ast.getType() != TokenTypes.LITERAL_SWITCH) { 319 incrementCurrentValue(BigInteger.ONE); 320 } 321 } 322 323 /** 324 * Process the end of a method definition. 325 * 326 * @param ast the token representing the method definition 327 */ 328 private void leaveMethodDef(DetailAST ast) { 329 final BigInteger bigIntegerMax = BigInteger.valueOf(max); 330 if (currentValue.compareTo(bigIntegerMax) > 0) { 331 log(ast, MSG_KEY, currentValue, bigIntegerMax); 332 } 333 popValue(); 334 } 335 336 /** 337 * Increments the current value by a specified amount. 338 * 339 * @param amount the amount to increment by 340 */ 341 private void incrementCurrentValue(BigInteger amount) { 342 currentValue = currentValue.add(amount); 343 } 344 345 /** Push the current value on the stack. */ 346 private void pushValue() { 347 valueStack.push(currentValue); 348 currentValue = INITIAL_VALUE; 349 } 350 351 /** 352 * Pops a value off the stack and makes it the current value. 353 */ 354 private void popValue() { 355 currentValue = valueStack.pop(); 356 } 357 358 /** Process the start of the method definition. */ 359 private void visitMethodDef() { 360 pushValue(); 361 } 362 363}