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