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