001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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.coding; 021 022import java.util.ArrayDeque; 023import java.util.Deque; 024import java.util.regex.Pattern; 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 * Restricts the number of return statements in methods, constructors and lambda expressions. 034 * Ignores specified methods ({@code equals} by default). 035 * </p> 036 * <p> 037 * <b>max</b> property will only check returns in methods and lambdas that 038 * return a specific value (Ex: 'return 1;'). 039 * </p> 040 * <p> 041 * <b>maxForVoid</b> property will only check returns in methods, constructors, 042 * and lambdas that have no return type (IE 'return;'). It will only count 043 * visible return statements. Return statements not normally written, but 044 * implied, at the end of the method/constructor definition will not be taken 045 * into account. To disallow "return;" in void return type methods, use a value 046 * of 0. 047 * </p> 048 * <p> 049 * Rationale: Too many return points can mean that code is 050 * attempting to do too much or may be difficult to understand. 051 * </p> 052 * <ul> 053 * <li> 054 * Property {@code max} - Specify maximum allowed number of return statements 055 * in non-void methods/lambdas. 056 * Type is {@code int}. 057 * Default value is {@code 2}. 058 * </li> 059 * <li> 060 * Property {@code maxForVoid} - Specify maximum allowed number of return statements 061 * in void methods/constructors/lambdas. 062 * Type is {@code int}. 063 * Default value is {@code 1}. 064 * </li> 065 * <li> 066 * Property {@code format} - Specify method names to ignore. 067 * Type is {@code java.util.regex.Pattern}. 068 * Default value is {@code "^equals$"}. 069 * </li> 070 * <li> 071 * Property {@code tokens} - tokens to check 072 * Type is {@code java.lang.String[]}. 073 * Validation type is {@code tokenSet}. 074 * Default value is: 075 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 076 * CTOR_DEF</a>, 077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 078 * METHOD_DEF</a>, 079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA"> 080 * LAMBDA</a>. 081 * </li> 082 * </ul> 083 * <p> 084 * To configure the check so that it doesn't allow more than three return statements per method 085 * (ignoring the {@code equals()} method): 086 * </p> 087 * <pre> 088 * <module name="ReturnCount"> 089 * <property name="max" value="3"/> 090 * </module> 091 * </pre> 092 * <p> 093 * Example: 094 * </p> 095 * <pre> 096 * public class MyClass { 097 * public int sign(int x) { 098 * if (x < 0) 099 * return -1; 100 * if (x == 0) 101 * return 1; 102 * return 0; 103 * } // OK 104 * public int badSign(int x) { 105 * if (x < -2) 106 * return -2; 107 * if (x == 0) 108 * return 0; 109 * if (x > 2) 110 * return 2; 111 * return 1; 112 * } // violation, more than three return statements 113 * } 114 * </pre> 115 * <p> 116 * To configure the check so that it doesn't allow any return statements per void method: 117 * </p> 118 * <pre> 119 * <module name="ReturnCount"> 120 * <property name="maxForVoid" value="0"/> 121 * </module> 122 * </pre> 123 * <p> 124 * Example: 125 * </p> 126 * <pre> 127 * public class MyClass { 128 * public void firstMethod(int x) { 129 * } // OK 130 * 131 * public void badMethod(int x) { 132 * return; 133 * } // violation, return statements per void method 134 * } 135 * </pre> 136 * <p> 137 * To configure the check so that it doesn't allow more than 2 return statements per method 138 * (ignoring the {@code equals()} method) and more than 1 return statements per void method: 139 * </p> 140 * <pre> 141 * <module name="ReturnCount"> 142 * <property name="max" value="2"/> 143 * <property name="maxForVoid" value="1"/> 144 * </module> 145 * </pre> 146 * <p> 147 * Example: 148 * </p> 149 * <pre> 150 * public class MyClass { 151 * public void firstMethod() { 152 * } // OK 153 * 154 * public void secondMethod() { 155 * return; 156 * } // OK 157 * 158 * public void badMethod(int x) { 159 * if (x == 0) 160 * return; 161 * return; 162 * } // violation, more than one return statements 163 * 164 * public int sign(int x) { 165 * if (x < 0) 166 * return -1; 167 * return 0; 168 * } // OK 169 * 170 * public int badSign(int x) { 171 * if (x < 0) 172 * return -1; 173 * if (x == 0) 174 * return 1; 175 * return 0; 176 * } // violation, more than two return statements in methods 177 * } 178 * </pre> 179 * <p> 180 * To configure the check so that it doesn't allow more than three 181 * return statements per method for all methods: 182 * </p> 183 * <pre> 184 * <module name="ReturnCount"> 185 * <property name="max" value="3"/> 186 * <property name="format" value="^$"/> 187 * </module> 188 * </pre> 189 * <p> 190 * Example: 191 * </p> 192 * <pre> 193 * public class MyClass { 194 * public int sign(int x) { 195 * if (x < 0) 196 * return -1; 197 * if (x == 0) 198 * return 1; 199 * return 0; 200 * } // OK 201 * 202 * public int badSign(int x) { 203 * if (x < -2) 204 * return -2; 205 * if (x == 0) 206 * return 0; 207 * if (x > 2) 208 * return 2; 209 * return 1; 210 * } // violation, more than three return statements per method 211 * } 212 * </pre> 213 * <p> 214 * To configure the check so that it doesn't allow any return statements in constructors, 215 * more than one return statement in all lambda expressions and more than two return 216 * statements in methods: 217 * </p> 218 * <pre> 219 * <module name="ReturnCount"> 220 * <property name="maxForVoid" value="0"/> 221 * <property name="tokens" value="CTOR_DEF"/> 222 * </module> 223 * <module name="ReturnCount"> 224 * <property name="max" value="1"/> 225 * <property name="tokens" value="LAMBDA"/> 226 * </module> 227 * <module name="ReturnCount"> 228 * <property name="max" value="2"/> 229 * <property name="tokens" value="METHOD_DEF"/> 230 * </module> 231 * </pre> 232 * <p> 233 * Example: 234 * </p> 235 * <pre> 236 * import java.util.function.Predicate; 237 * 238 * public class Test { 239 * public Test() { 240 * } // OK 241 * 242 * public Test(int i) { 243 * return; // violation, max allowed for constructors is 0 244 * } 245 * 246 * final Predicate<Integer> p = i -> { 247 * if (i > 5) { 248 * return true; 249 * } 250 * return false; 251 * }; // violation, max allowed for lambdas is 1 252 * 253 * final Predicate<Integer> q = i -> { 254 * return i > 5; 255 * }; // OK 256 * 257 * public int sign(int x) { 258 * if (x > 0) 259 * return -1; 260 * return 0; 261 * } // OK 262 * 263 * public int badSign(int x) { 264 * if (x < 0) 265 * return -1; 266 * if (x == 0) 267 * return 1; 268 * return 0; 269 * } // violation, more than two return statements in methods 270 * } 271 * </pre> 272 * <p> 273 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 274 * </p> 275 * <p> 276 * Violation Message Keys: 277 * </p> 278 * <ul> 279 * <li> 280 * {@code return.count} 281 * </li> 282 * <li> 283 * {@code return.countVoid} 284 * </li> 285 * </ul> 286 * 287 * @since 3.2 288 */ 289@FileStatefulCheck 290public final class ReturnCountCheck extends AbstractCheck { 291 292 /** 293 * A key is pointing to the warning message text in "messages.properties" 294 * file. 295 */ 296 public static final String MSG_KEY = "return.count"; 297 /** 298 * A key pointing to the warning message text in "messages.properties" 299 * file. 300 */ 301 public static final String MSG_KEY_VOID = "return.countVoid"; 302 303 /** Stack of method contexts. */ 304 private final Deque<Context> contextStack = new ArrayDeque<>(); 305 306 /** Specify method names to ignore. */ 307 private Pattern format = Pattern.compile("^equals$"); 308 309 /** Specify maximum allowed number of return statements in non-void methods/lambdas. */ 310 private int max = 2; 311 /** Specify maximum allowed number of return statements in void methods/constructors/lambdas. */ 312 private int maxForVoid = 1; 313 /** Current method context. */ 314 private Context context; 315 316 @Override 317 public int[] getDefaultTokens() { 318 return new int[] { 319 TokenTypes.CTOR_DEF, 320 TokenTypes.METHOD_DEF, 321 TokenTypes.LAMBDA, 322 TokenTypes.LITERAL_RETURN, 323 }; 324 } 325 326 @Override 327 public int[] getRequiredTokens() { 328 return new int[] {TokenTypes.LITERAL_RETURN}; 329 } 330 331 @Override 332 public int[] getAcceptableTokens() { 333 return new int[] { 334 TokenTypes.CTOR_DEF, 335 TokenTypes.METHOD_DEF, 336 TokenTypes.LAMBDA, 337 TokenTypes.LITERAL_RETURN, 338 }; 339 } 340 341 /** 342 * Setter to specify method names to ignore. 343 * 344 * @param pattern a pattern. 345 */ 346 public void setFormat(Pattern pattern) { 347 format = pattern; 348 } 349 350 /** 351 * Setter to specify maximum allowed number of return statements 352 * in non-void methods/lambdas. 353 * 354 * @param max maximum allowed number of return statements. 355 */ 356 public void setMax(int max) { 357 this.max = max; 358 } 359 360 /** 361 * Setter to specify maximum allowed number of return statements 362 * in void methods/constructors/lambdas. 363 * 364 * @param maxForVoid maximum allowed number of return statements for void methods. 365 */ 366 public void setMaxForVoid(int maxForVoid) { 367 this.maxForVoid = maxForVoid; 368 } 369 370 @Override 371 public void beginTree(DetailAST rootAST) { 372 context = new Context(false); 373 contextStack.clear(); 374 } 375 376 @Override 377 public void visitToken(DetailAST ast) { 378 switch (ast.getType()) { 379 case TokenTypes.CTOR_DEF: 380 case TokenTypes.METHOD_DEF: 381 visitMethodDef(ast); 382 break; 383 case TokenTypes.LAMBDA: 384 visitLambda(); 385 break; 386 case TokenTypes.LITERAL_RETURN: 387 visitReturn(ast); 388 break; 389 default: 390 throw new IllegalStateException(ast.toString()); 391 } 392 } 393 394 @Override 395 public void leaveToken(DetailAST ast) { 396 switch (ast.getType()) { 397 case TokenTypes.CTOR_DEF: 398 case TokenTypes.METHOD_DEF: 399 case TokenTypes.LAMBDA: 400 leave(ast); 401 break; 402 case TokenTypes.LITERAL_RETURN: 403 // Do nothing 404 break; 405 default: 406 throw new IllegalStateException(ast.toString()); 407 } 408 } 409 410 /** 411 * Creates new method context and places old one on the stack. 412 * 413 * @param ast method definition for check. 414 */ 415 private void visitMethodDef(DetailAST ast) { 416 contextStack.push(context); 417 final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT); 418 final boolean check = !format.matcher(methodNameAST.getText()).find(); 419 context = new Context(check); 420 } 421 422 /** 423 * Checks number of return statements and restore previous context. 424 * 425 * @param ast node to leave. 426 */ 427 private void leave(DetailAST ast) { 428 context.checkCount(ast); 429 context = contextStack.pop(); 430 } 431 432 /** 433 * Creates new lambda context and places old one on the stack. 434 */ 435 private void visitLambda() { 436 contextStack.push(context); 437 context = new Context(true); 438 } 439 440 /** 441 * Examines the return statement and tells context about it. 442 * 443 * @param ast return statement to check. 444 */ 445 private void visitReturn(DetailAST ast) { 446 // we can't identify which max to use for lambdas, so we can only assign 447 // after the first return statement is seen 448 if (ast.getFirstChild().getType() == TokenTypes.SEMI) { 449 context.visitLiteralReturn(maxForVoid, Boolean.TRUE); 450 } 451 else { 452 context.visitLiteralReturn(max, Boolean.FALSE); 453 } 454 } 455 456 /** 457 * Class to encapsulate information about one method. 458 */ 459 private class Context { 460 461 /** Whether we should check this method or not. */ 462 private final boolean checking; 463 /** Counter for return statements. */ 464 private int count; 465 /** Maximum allowed number of return statements. */ 466 private Integer maxAllowed; 467 /** Identifies if context is void. */ 468 private boolean isVoidContext; 469 470 /** 471 * Creates new method context. 472 * 473 * @param checking should we check this method or not. 474 */ 475 /* package */ Context(boolean checking) { 476 this.checking = checking; 477 } 478 479 /** 480 * Increase the number of return statements and set context return type. 481 * 482 * @param maxAssigned Maximum allowed number of return statements. 483 * @param voidReturn Identifies if context is void. 484 */ 485 public void visitLiteralReturn(int maxAssigned, Boolean voidReturn) { 486 isVoidContext = voidReturn; 487 maxAllowed = maxAssigned; 488 489 ++count; 490 } 491 492 /** 493 * Checks if number of return statements in the method are more 494 * than allowed. 495 * 496 * @param ast method def associated with this context. 497 */ 498 public void checkCount(DetailAST ast) { 499 if (checking && maxAllowed != null && count > maxAllowed) { 500 if (isVoidContext) { 501 log(ast, MSG_KEY_VOID, count, maxAllowed); 502 } 503 else { 504 log(ast, MSG_KEY, count, maxAllowed); 505 } 506 } 507 } 508 509 } 510 511}