1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2024 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 /////////////////////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.coding; 21 22 import java.util.ArrayDeque; 23 import java.util.Deque; 24 import java.util.regex.Pattern; 25 26 import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 28 import com.puppycrawl.tools.checkstyle.api.DetailAST; 29 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 30 31 /** 32 * <div> 33 * Restricts the number of return statements in methods, constructors and lambda expressions. 34 * Ignores specified methods ({@code equals} by default). 35 * </div> 36 * 37 * <p> 38 * <b>max</b> property will only check returns in methods and lambdas that 39 * return a specific value (Ex: 'return 1;'). 40 * </p> 41 * 42 * <p> 43 * <b>maxForVoid</b> property will only check returns in methods, constructors, 44 * and lambdas that have no return type (IE 'return;'). It will only count 45 * visible return statements. Return statements not normally written, but 46 * implied, at the end of the method/constructor definition will not be taken 47 * into account. To disallow "return;" in void return type methods, use a value 48 * of 0. 49 * </p> 50 * 51 * <p> 52 * Rationale: Too many return points can mean that code is 53 * attempting to do too much or may be difficult to understand. 54 * </p> 55 * <ul> 56 * <li> 57 * Property {@code format} - Specify method names to ignore. 58 * Type is {@code java.util.regex.Pattern}. 59 * Default value is {@code "^equals$"}. 60 * </li> 61 * <li> 62 * Property {@code max} - Specify maximum allowed number of return statements 63 * in non-void methods/lambdas. 64 * Type is {@code int}. 65 * Default value is {@code 2}. 66 * </li> 67 * <li> 68 * Property {@code maxForVoid} - Specify maximum allowed number of return statements 69 * in void methods/constructors/lambdas. 70 * Type is {@code int}. 71 * Default value is {@code 1}. 72 * </li> 73 * <li> 74 * Property {@code tokens} - tokens to check 75 * Type is {@code java.lang.String[]}. 76 * Validation type is {@code tokenSet}. 77 * Default value is: 78 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 79 * CTOR_DEF</a>, 80 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 81 * METHOD_DEF</a>, 82 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA"> 83 * LAMBDA</a>. 84 * </li> 85 * </ul> 86 * 87 * <p> 88 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 89 * </p> 90 * 91 * <p> 92 * Violation Message Keys: 93 * </p> 94 * <ul> 95 * <li> 96 * {@code return.count} 97 * </li> 98 * <li> 99 * {@code return.countVoid} 100 * </li> 101 * </ul> 102 * 103 * @since 3.2 104 */ 105 @FileStatefulCheck 106 public final class ReturnCountCheck extends AbstractCheck { 107 108 /** 109 * A key is pointing to the warning message text in "messages.properties" 110 * file. 111 */ 112 public static final String MSG_KEY = "return.count"; 113 /** 114 * A key pointing to the warning message text in "messages.properties" 115 * file. 116 */ 117 public static final String MSG_KEY_VOID = "return.countVoid"; 118 119 /** Stack of method contexts. */ 120 private final Deque<Context> contextStack = new ArrayDeque<>(); 121 122 /** Specify method names to ignore. */ 123 private Pattern format = Pattern.compile("^equals$"); 124 125 /** Specify maximum allowed number of return statements in non-void methods/lambdas. */ 126 private int max = 2; 127 /** Specify maximum allowed number of return statements in void methods/constructors/lambdas. */ 128 private int maxForVoid = 1; 129 /** Current method context. */ 130 private Context context; 131 132 @Override 133 public int[] getDefaultTokens() { 134 return new int[] { 135 TokenTypes.CTOR_DEF, 136 TokenTypes.METHOD_DEF, 137 TokenTypes.LAMBDA, 138 TokenTypes.LITERAL_RETURN, 139 }; 140 } 141 142 @Override 143 public int[] getRequiredTokens() { 144 return new int[] {TokenTypes.LITERAL_RETURN}; 145 } 146 147 @Override 148 public int[] getAcceptableTokens() { 149 return new int[] { 150 TokenTypes.CTOR_DEF, 151 TokenTypes.METHOD_DEF, 152 TokenTypes.LAMBDA, 153 TokenTypes.LITERAL_RETURN, 154 }; 155 } 156 157 /** 158 * Setter to specify method names to ignore. 159 * 160 * @param pattern a pattern. 161 * @since 3.4 162 */ 163 public void setFormat(Pattern pattern) { 164 format = pattern; 165 } 166 167 /** 168 * Setter to specify maximum allowed number of return statements 169 * in non-void methods/lambdas. 170 * 171 * @param max maximum allowed number of return statements. 172 * @since 3.2 173 */ 174 public void setMax(int max) { 175 this.max = max; 176 } 177 178 /** 179 * Setter to specify maximum allowed number of return statements 180 * in void methods/constructors/lambdas. 181 * 182 * @param maxForVoid maximum allowed number of return statements for void methods. 183 * @since 6.19 184 */ 185 public void setMaxForVoid(int maxForVoid) { 186 this.maxForVoid = maxForVoid; 187 } 188 189 @Override 190 public void beginTree(DetailAST rootAST) { 191 context = new Context(false); 192 contextStack.clear(); 193 } 194 195 @Override 196 public void visitToken(DetailAST ast) { 197 switch (ast.getType()) { 198 case TokenTypes.CTOR_DEF: 199 case TokenTypes.METHOD_DEF: 200 visitMethodDef(ast); 201 break; 202 case TokenTypes.LAMBDA: 203 visitLambda(); 204 break; 205 case TokenTypes.LITERAL_RETURN: 206 visitReturn(ast); 207 break; 208 default: 209 throw new IllegalStateException(ast.toString()); 210 } 211 } 212 213 @Override 214 public void leaveToken(DetailAST ast) { 215 switch (ast.getType()) { 216 case TokenTypes.CTOR_DEF: 217 case TokenTypes.METHOD_DEF: 218 case TokenTypes.LAMBDA: 219 leave(ast); 220 break; 221 case TokenTypes.LITERAL_RETURN: 222 // Do nothing 223 break; 224 default: 225 throw new IllegalStateException(ast.toString()); 226 } 227 } 228 229 /** 230 * Creates new method context and places old one on the stack. 231 * 232 * @param ast method definition for check. 233 */ 234 private void visitMethodDef(DetailAST ast) { 235 contextStack.push(context); 236 final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT); 237 final boolean check = !format.matcher(methodNameAST.getText()).find(); 238 context = new Context(check); 239 } 240 241 /** 242 * Checks number of return statements and restore previous context. 243 * 244 * @param ast node to leave. 245 */ 246 private void leave(DetailAST ast) { 247 context.checkCount(ast); 248 context = contextStack.pop(); 249 } 250 251 /** 252 * Creates new lambda context and places old one on the stack. 253 */ 254 private void visitLambda() { 255 contextStack.push(context); 256 context = new Context(true); 257 } 258 259 /** 260 * Examines the return statement and tells context about it. 261 * 262 * @param ast return statement to check. 263 */ 264 private void visitReturn(DetailAST ast) { 265 // we can't identify which max to use for lambdas, so we can only assign 266 // after the first return statement is seen 267 if (ast.getFirstChild().getType() == TokenTypes.SEMI) { 268 context.visitLiteralReturn(maxForVoid, Boolean.TRUE); 269 } 270 else { 271 context.visitLiteralReturn(max, Boolean.FALSE); 272 } 273 } 274 275 /** 276 * Class to encapsulate information about one method. 277 */ 278 private final class Context { 279 280 /** Whether we should check this method or not. */ 281 private final boolean checking; 282 /** Counter for return statements. */ 283 private int count; 284 /** Maximum allowed number of return statements. */ 285 private Integer maxAllowed; 286 /** Identifies if context is void. */ 287 private boolean isVoidContext; 288 289 /** 290 * Creates new method context. 291 * 292 * @param checking should we check this method or not 293 */ 294 private Context(boolean checking) { 295 this.checking = checking; 296 } 297 298 /** 299 * Increase the number of return statements and set context return type. 300 * 301 * @param maxAssigned Maximum allowed number of return statements. 302 * @param voidReturn Identifies if context is void. 303 */ 304 public void visitLiteralReturn(int maxAssigned, Boolean voidReturn) { 305 isVoidContext = voidReturn; 306 maxAllowed = maxAssigned; 307 308 ++count; 309 } 310 311 /** 312 * Checks if number of return statements in the method are more 313 * than allowed. 314 * 315 * @param ast method def associated with this context. 316 */ 317 public void checkCount(DetailAST ast) { 318 if (checking && maxAllowed != null && count > maxAllowed) { 319 if (isVoidContext) { 320 log(ast, MSG_KEY_VOID, count, maxAllowed); 321 } 322 else { 323 log(ast, MSG_KEY, count, maxAllowed); 324 } 325 } 326 } 327 328 } 329 330 }