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