001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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 * (2 by default). 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 * Default value is {@code 2}. 057 * </li> 058 * <li> 059 * Property {@code maxForVoid} - Specify maximum allowed number of return statements 060 * in void methods/constructors/lambdas. 061 * Default value is {@code 1}. 062 * </li> 063 * <li> 064 * Property {@code format} - Specify method names to ignore. 065 * Default value is {@code "^equals$"}. 066 * </li> 067 * <li> 068 * Property {@code tokens} - tokens to check Default value is: 069 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 070 * CTOR_DEF</a>, 071 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 072 * METHOD_DEF</a>, 073 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA"> 074 * LAMBDA</a>. 075 * </li> 076 * </ul> 077 * <p> 078 * To configure the check so that it doesn't allow more than three return statements per method 079 * (ignoring the {@code equals()} method): 080 * </p> 081 * <pre> 082 * <module name="ReturnCount"> 083 * <property name="max" value="3"/> 084 * </module> 085 * </pre> 086 * <p> 087 * To configure the check so that it doesn't allow any return statements per void method: 088 * </p> 089 * <pre> 090 * <module name="ReturnCount"> 091 * <property name="maxForVoid" value="0"/> 092 * </module> 093 * </pre> 094 * <p> 095 * To configure the check so that it doesn't allow more than 2 return statements per method 096 * (ignoring the {@code equals()} method) and more than 1 return statements per void method: 097 * </p> 098 * <pre> 099 * <module name="ReturnCount"> 100 * <property name="max" value="2"/> 101 * <property name="maxForVoid" value="1"/> 102 * </module> 103 * </pre> 104 * <p> 105 * To configure the check so that it doesn't allow more than three 106 * return statements per method for all methods: 107 * </p> 108 * <pre> 109 * <module name="ReturnCount"> 110 * <property name="max" value="3"/> 111 * <property name="format" value="^$"/> 112 * </module> 113 * </pre> 114 * <p> 115 * To configure the check so that it doesn't allow any return statements in constructors, 116 * more than one return statement in all lambda expressions and more than two return 117 * statements in methods: 118 * </p> 119 * <pre> 120 * <module name="ReturnCount"> 121 * <property name="max" value="0"/> 122 * <property name="tokens" value="CTOR_DEF"/> 123 * </module> 124 * <module name="ReturnCount"> 125 * <property name="max" value="1"/> 126 * <property name="tokens" value="LAMBDA"/> 127 * </module> 128 * <module name="ReturnCount"> 129 * <property name="max" value="2"/> 130 * <property name="tokens" value="METHOD_DEF"/> 131 * </module> 132 * </pre> 133 * 134 * @since 3.2 135 */ 136@FileStatefulCheck 137public final class ReturnCountCheck extends AbstractCheck { 138 139 /** 140 * A key is pointing to the warning message text in "messages.properties" 141 * file. 142 */ 143 public static final String MSG_KEY = "return.count"; 144 /** 145 * A key pointing to the warning message text in "messages.properties" 146 * file. 147 */ 148 public static final String MSG_KEY_VOID = "return.countVoid"; 149 150 /** Stack of method contexts. */ 151 private final Deque<Context> contextStack = new ArrayDeque<>(); 152 153 /** Specify method names to ignore. */ 154 private Pattern format = Pattern.compile("^equals$"); 155 156 /** Specify maximum allowed number of return statements in non-void methods/lambdas. */ 157 private int max = 2; 158 /** Specify maximum allowed number of return statements in void methods/constructors/lambdas. */ 159 private int maxForVoid = 1; 160 /** Current method context. */ 161 private Context context; 162 163 @Override 164 public int[] getDefaultTokens() { 165 return new int[] { 166 TokenTypes.CTOR_DEF, 167 TokenTypes.METHOD_DEF, 168 TokenTypes.LAMBDA, 169 TokenTypes.LITERAL_RETURN, 170 }; 171 } 172 173 @Override 174 public int[] getRequiredTokens() { 175 return new int[] {TokenTypes.LITERAL_RETURN}; 176 } 177 178 @Override 179 public int[] getAcceptableTokens() { 180 return new int[] { 181 TokenTypes.CTOR_DEF, 182 TokenTypes.METHOD_DEF, 183 TokenTypes.LAMBDA, 184 TokenTypes.LITERAL_RETURN, 185 }; 186 } 187 188 /** 189 * Setter to specify method names to ignore. 190 * @param pattern a pattern. 191 */ 192 public void setFormat(Pattern pattern) { 193 format = pattern; 194 } 195 196 /** 197 * Setter to specify maximum allowed number of return statements 198 * in non-void methods/lambdas. 199 * @param max maximum allowed number of return statements. 200 */ 201 public void setMax(int max) { 202 this.max = max; 203 } 204 205 /** 206 * Setter to specify maximum allowed number of return statements 207 * in void methods/constructors/lambdas. 208 * @param maxForVoid maximum allowed number of return statements for void methods. 209 */ 210 public void setMaxForVoid(int maxForVoid) { 211 this.maxForVoid = maxForVoid; 212 } 213 214 @Override 215 public void beginTree(DetailAST rootAST) { 216 context = new Context(false); 217 contextStack.clear(); 218 } 219 220 @Override 221 public void visitToken(DetailAST ast) { 222 switch (ast.getType()) { 223 case TokenTypes.CTOR_DEF: 224 case TokenTypes.METHOD_DEF: 225 visitMethodDef(ast); 226 break; 227 case TokenTypes.LAMBDA: 228 visitLambda(); 229 break; 230 case TokenTypes.LITERAL_RETURN: 231 visitReturn(ast); 232 break; 233 default: 234 throw new IllegalStateException(ast.toString()); 235 } 236 } 237 238 @Override 239 public void leaveToken(DetailAST ast) { 240 switch (ast.getType()) { 241 case TokenTypes.CTOR_DEF: 242 case TokenTypes.METHOD_DEF: 243 case TokenTypes.LAMBDA: 244 leave(ast); 245 break; 246 case TokenTypes.LITERAL_RETURN: 247 // Do nothing 248 break; 249 default: 250 throw new IllegalStateException(ast.toString()); 251 } 252 } 253 254 /** 255 * Creates new method context and places old one on the stack. 256 * @param ast method definition for check. 257 */ 258 private void visitMethodDef(DetailAST ast) { 259 contextStack.push(context); 260 final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT); 261 final boolean check = !format.matcher(methodNameAST.getText()).find(); 262 context = new Context(check); 263 } 264 265 /** 266 * Checks number of return statements and restore previous context. 267 * @param ast node to leave. 268 */ 269 private void leave(DetailAST ast) { 270 context.checkCount(ast); 271 context = contextStack.pop(); 272 } 273 274 /** 275 * Creates new lambda context and places old one on the stack. 276 */ 277 private void visitLambda() { 278 contextStack.push(context); 279 context = new Context(true); 280 } 281 282 /** 283 * Examines the return statement and tells context about it. 284 * @param ast return statement to check. 285 */ 286 private void visitReturn(DetailAST ast) { 287 // we can't identify which max to use for lambdas, so we can only assign 288 // after the first return statement is seen 289 if (ast.getFirstChild().getType() == TokenTypes.SEMI) { 290 context.visitLiteralReturn(maxForVoid, true); 291 } 292 else { 293 context.visitLiteralReturn(max, false); 294 } 295 } 296 297 /** 298 * Class to encapsulate information about one method. 299 */ 300 private class Context { 301 302 /** Whether we should check this method or not. */ 303 private final boolean checking; 304 /** Counter for return statements. */ 305 private int count; 306 /** Maximum allowed number of return statements. */ 307 private Integer maxAllowed; 308 /** Identifies if context is void. */ 309 private boolean isVoidContext; 310 311 /** 312 * Creates new method context. 313 * @param checking should we check this method or not. 314 */ 315 /* package */ Context(boolean checking) { 316 this.checking = checking; 317 } 318 319 /** 320 * Increase the number of return statements and set context return type. 321 * @param maxAssigned Maximum allowed number of return statements. 322 * @param voidReturn Identifies if context is void. 323 */ 324 public void visitLiteralReturn(int maxAssigned, Boolean voidReturn) { 325 isVoidContext = voidReturn; 326 maxAllowed = maxAssigned; 327 328 ++count; 329 } 330 331 /** 332 * Checks if number of return statements in the method are more 333 * than allowed. 334 * @param ast method def associated with this context. 335 */ 336 public void checkCount(DetailAST ast) { 337 if (checking && maxAllowed != null && count > maxAllowed) { 338 if (isVoidContext) { 339 log(ast, MSG_KEY_VOID, count, maxAllowed); 340 } 341 else { 342 log(ast, MSG_KEY, count, maxAllowed); 343 } 344 } 345 } 346 347 } 348 349}