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