001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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 * <div> 033 * Restricts the number of return statements in methods, constructors and lambda expressions. 034 * Ignores specified methods ({@code equals} by default). 035 * </div> 036 * 037 * <p> 038 * <b>max</b> property will only check returns in methods and lambdas that 039 * return a specific value (Ex: 'return 1;'). 040 * </p> 041 * 042 * <p> 043 * <b>maxForVoid</b> property will only check returns in methods, constructors, 044 * and lambdas that have no return type (IE 'return;'). It will only count 045 * visible return statements. Return statements not normally written, but 046 * implied, at the end of the method/constructor definition will not be taken 047 * into account. To disallow "return;" in void return type methods, use a value 048 * of 0. 049 * </p> 050 * 051 * <p> 052 * Rationale: Too many return points can mean that code is 053 * attempting to do too much or may be difficult to understand. 054 * </p> 055 * 056 * @since 3.2 057 */ 058@FileStatefulCheck 059public final class ReturnCountCheck extends AbstractCheck { 060 061 /** 062 * A key is pointing to the warning message text in "messages.properties" 063 * file. 064 */ 065 public static final String MSG_KEY = "return.count"; 066 /** 067 * A key pointing to the warning message text in "messages.properties" 068 * file. 069 */ 070 public static final String MSG_KEY_VOID = "return.countVoid"; 071 072 /** Stack of method contexts. */ 073 private final Deque<Context> contextStack = new ArrayDeque<>(); 074 075 /** Specify method names to ignore. */ 076 private Pattern format = Pattern.compile("^equals$"); 077 078 /** Specify maximum allowed number of return statements in non-void methods/lambdas. */ 079 private int max = 2; 080 /** Specify maximum allowed number of return statements in void methods/constructors/lambdas. */ 081 private int maxForVoid = 1; 082 /** Current method context. */ 083 private Context context; 084 085 /** 086 * Creates a new {@code ReturnCountCheck} instance. 087 */ 088 public ReturnCountCheck() { 089 // no code by default 090 } 091 092 @Override 093 public int[] getDefaultTokens() { 094 return new int[] { 095 TokenTypes.CTOR_DEF, 096 TokenTypes.METHOD_DEF, 097 TokenTypes.LAMBDA, 098 TokenTypes.LITERAL_RETURN, 099 }; 100 } 101 102 @Override 103 public int[] getRequiredTokens() { 104 return new int[] {TokenTypes.LITERAL_RETURN}; 105 } 106 107 @Override 108 public int[] getAcceptableTokens() { 109 return new int[] { 110 TokenTypes.CTOR_DEF, 111 TokenTypes.METHOD_DEF, 112 TokenTypes.LAMBDA, 113 TokenTypes.LITERAL_RETURN, 114 }; 115 } 116 117 /** 118 * Setter to specify method names to ignore. 119 * 120 * @param pattern a pattern. 121 * @since 3.4 122 */ 123 public void setFormat(Pattern pattern) { 124 format = pattern; 125 } 126 127 /** 128 * Setter to specify maximum allowed number of return statements 129 * in non-void methods/lambdas. 130 * 131 * @param max maximum allowed number of return statements. 132 * @since 3.2 133 */ 134 public void setMax(int max) { 135 this.max = max; 136 } 137 138 /** 139 * Setter to specify maximum allowed number of return statements 140 * in void methods/constructors/lambdas. 141 * 142 * @param maxForVoid maximum allowed number of return statements for void methods. 143 * @since 6.19 144 */ 145 public void setMaxForVoid(int maxForVoid) { 146 this.maxForVoid = maxForVoid; 147 } 148 149 @Override 150 public void beginTree(DetailAST rootAST) { 151 context = new Context(false); 152 contextStack.clear(); 153 } 154 155 @Override 156 public void visitToken(DetailAST ast) { 157 switch (ast.getType()) { 158 case TokenTypes.CTOR_DEF, 159 TokenTypes.METHOD_DEF -> visitMethodDef(ast); 160 case TokenTypes.LAMBDA -> visitLambda(); 161 case TokenTypes.LITERAL_RETURN -> visitReturn(ast); 162 default -> throw new IllegalStateException(ast.toString()); 163 } 164 } 165 166 @Override 167 public void leaveToken(DetailAST ast) { 168 switch (ast.getType()) { 169 case TokenTypes.CTOR_DEF, 170 TokenTypes.METHOD_DEF, 171 TokenTypes.LAMBDA -> leave(ast); 172 case TokenTypes.LITERAL_RETURN -> { 173 // Do nothing 174 } 175 default -> throw new IllegalStateException(ast.toString()); 176 } 177 } 178 179 /** 180 * Creates new method context and places old one on the stack. 181 * 182 * @param ast method definition for check. 183 */ 184 private void visitMethodDef(DetailAST ast) { 185 contextStack.push(context); 186 final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT); 187 final boolean check = !format.matcher(methodNameAST.getText()).find(); 188 context = new Context(check); 189 } 190 191 /** 192 * Checks number of return statements and restore previous context. 193 * 194 * @param ast node to leave. 195 */ 196 private void leave(DetailAST ast) { 197 context.checkCount(ast); 198 context = contextStack.pop(); 199 } 200 201 /** 202 * Creates new lambda context and places old one on the stack. 203 */ 204 private void visitLambda() { 205 contextStack.push(context); 206 context = new Context(true); 207 } 208 209 /** 210 * Examines the return statement and tells context about it. 211 * 212 * @param ast return statement to check. 213 */ 214 private void visitReturn(DetailAST ast) { 215 // we can't identify which max to use for lambdas, so we can only assign 216 // after the first return statement is seen 217 if (ast.getFirstChild().getType() == TokenTypes.SEMI) { 218 context.visitLiteralReturn(maxForVoid, Boolean.TRUE); 219 } 220 else { 221 context.visitLiteralReturn(max, Boolean.FALSE); 222 } 223 } 224 225 /** 226 * Class to encapsulate information about one method. 227 */ 228 private final class Context { 229 230 /** Whether we should check this method or not. */ 231 private final boolean checking; 232 /** Counter for return statements. */ 233 private int count; 234 /** Maximum allowed number of return statements. */ 235 private Integer maxAllowed; 236 /** Identifies if context is void. */ 237 private boolean isVoidContext; 238 239 /** 240 * Creates new method context. 241 * 242 * @param checking should we check this method or not 243 */ 244 private Context(boolean checking) { 245 this.checking = checking; 246 } 247 248 /** 249 * Increase the number of return statements and set context return type. 250 * 251 * @param maxAssigned Maximum allowed number of return statements. 252 * @param voidReturn Identifies if context is void. 253 */ 254 /* package */ void visitLiteralReturn(int maxAssigned, Boolean voidReturn) { 255 isVoidContext = voidReturn; 256 maxAllowed = maxAssigned; 257 258 ++count; 259 } 260 261 /** 262 * Checks if number of return statements in the method are more 263 * than allowed. 264 * 265 * @param ast method def associated with this context. 266 */ 267 /* package */ void checkCount(DetailAST ast) { 268 if (checking && maxAllowed != null && count > maxAllowed) { 269 if (isVoidContext) { 270 log(ast, MSG_KEY_VOID, count, maxAllowed); 271 } 272 else { 273 log(ast, MSG_KEY, count, maxAllowed); 274 } 275 } 276 } 277 278 } 279 280}