com.puppycrawl.tools.checkstyle.checks.coding.ReturnCountCheck Maven / Gradle / Ivy
Show all versions of checkstyle Show documentation
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2021 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle.checks.coding;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.regex.Pattern;
import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
/**
*
* Restricts the number of return statements in methods, constructors and lambda expressions.
* Ignores specified methods ({@code equals} by default).
*
*
* max property will only check returns in methods and lambdas that
* return a specific value (Ex: 'return 1;').
*
*
* maxForVoid property will only check returns in methods, constructors,
* and lambdas that have no return type (IE 'return;'). It will only count
* visible return statements. Return statements not normally written, but
* implied, at the end of the method/constructor definition will not be taken
* into account. To disallow "return;" in void return type methods, use a value
* of 0.
*
*
* Rationale: Too many return points can mean that code is
* attempting to do too much or may be difficult to understand.
*
*
* -
* Property {@code max} - Specify maximum allowed number of return statements
* in non-void methods/lambdas.
* Type is {@code int}.
* Default value is {@code 2}.
*
* -
* Property {@code maxForVoid} - Specify maximum allowed number of return statements
* in void methods/constructors/lambdas.
* Type is {@code int}.
* Default value is {@code 1}.
*
* -
* Property {@code format} - Specify method names to ignore.
* Type is {@code java.util.regex.Pattern}.
* Default value is {@code "^equals$"}.
*
* -
* Property {@code tokens} - tokens to check
* Type is {@code java.lang.String[]}.
* Validation type is {@code tokenSet}.
* Default value is:
*
* CTOR_DEF,
*
* METHOD_DEF,
*
* LAMBDA.
*
*
*
* To configure the check so that it doesn't allow more than three return statements per method
* (ignoring the {@code equals()} method):
*
*
* <module name="ReturnCount">
* <property name="max" value="3"/>
* </module>
*
*
* To configure the check so that it doesn't allow any return statements per void method:
*
*
* <module name="ReturnCount">
* <property name="maxForVoid" value="0"/>
* </module>
*
*
* To configure the check so that it doesn't allow more than 2 return statements per method
* (ignoring the {@code equals()} method) and more than 1 return statements per void method:
*
*
* <module name="ReturnCount">
* <property name="max" value="2"/>
* <property name="maxForVoid" value="1"/>
* </module>
*
*
* To configure the check so that it doesn't allow more than three
* return statements per method for all methods:
*
*
* <module name="ReturnCount">
* <property name="max" value="3"/>
* <property name="format" value="^$"/>
* </module>
*
*
* To configure the check so that it doesn't allow any return statements in constructors,
* more than one return statement in all lambda expressions and more than two return
* statements in methods:
*
*
* <module name="ReturnCount">
* <property name="max" value="0"/>
* <property name="tokens" value="CTOR_DEF"/>
* </module>
* <module name="ReturnCount">
* <property name="max" value="1"/>
* <property name="tokens" value="LAMBDA"/>
* </module>
* <module name="ReturnCount">
* <property name="max" value="2"/>
* <property name="tokens" value="METHOD_DEF"/>
* </module>
*
*
* Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
*
*
* Violation Message Keys:
*
*
* -
* {@code return.count}
*
* -
* {@code return.countVoid}
*
*
*
* @since 3.2
*/
@FileStatefulCheck
public final class ReturnCountCheck extends AbstractCheck {
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY = "return.count";
/**
* A key pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY_VOID = "return.countVoid";
/** Stack of method contexts. */
private final Deque contextStack = new ArrayDeque<>();
/** Specify method names to ignore. */
private Pattern format = Pattern.compile("^equals$");
/** Specify maximum allowed number of return statements in non-void methods/lambdas. */
private int max = 2;
/** Specify maximum allowed number of return statements in void methods/constructors/lambdas. */
private int maxForVoid = 1;
/** Current method context. */
private Context context;
@Override
public int[] getDefaultTokens() {
return new int[] {
TokenTypes.CTOR_DEF,
TokenTypes.METHOD_DEF,
TokenTypes.LAMBDA,
TokenTypes.LITERAL_RETURN,
};
}
@Override
public int[] getRequiredTokens() {
return new int[] {TokenTypes.LITERAL_RETURN};
}
@Override
public int[] getAcceptableTokens() {
return new int[] {
TokenTypes.CTOR_DEF,
TokenTypes.METHOD_DEF,
TokenTypes.LAMBDA,
TokenTypes.LITERAL_RETURN,
};
}
/**
* Setter to specify method names to ignore.
*
* @param pattern a pattern.
*/
public void setFormat(Pattern pattern) {
format = pattern;
}
/**
* Setter to specify maximum allowed number of return statements
* in non-void methods/lambdas.
*
* @param max maximum allowed number of return statements.
*/
public void setMax(int max) {
this.max = max;
}
/**
* Setter to specify maximum allowed number of return statements
* in void methods/constructors/lambdas.
*
* @param maxForVoid maximum allowed number of return statements for void methods.
*/
public void setMaxForVoid(int maxForVoid) {
this.maxForVoid = maxForVoid;
}
@Override
public void beginTree(DetailAST rootAST) {
context = new Context(false);
contextStack.clear();
}
@Override
public void visitToken(DetailAST ast) {
switch (ast.getType()) {
case TokenTypes.CTOR_DEF:
case TokenTypes.METHOD_DEF:
visitMethodDef(ast);
break;
case TokenTypes.LAMBDA:
visitLambda();
break;
case TokenTypes.LITERAL_RETURN:
visitReturn(ast);
break;
default:
throw new IllegalStateException(ast.toString());
}
}
@Override
public void leaveToken(DetailAST ast) {
switch (ast.getType()) {
case TokenTypes.CTOR_DEF:
case TokenTypes.METHOD_DEF:
case TokenTypes.LAMBDA:
leave(ast);
break;
case TokenTypes.LITERAL_RETURN:
// Do nothing
break;
default:
throw new IllegalStateException(ast.toString());
}
}
/**
* Creates new method context and places old one on the stack.
*
* @param ast method definition for check.
*/
private void visitMethodDef(DetailAST ast) {
contextStack.push(context);
final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT);
final boolean check = !format.matcher(methodNameAST.getText()).find();
context = new Context(check);
}
/**
* Checks number of return statements and restore previous context.
*
* @param ast node to leave.
*/
private void leave(DetailAST ast) {
context.checkCount(ast);
context = contextStack.pop();
}
/**
* Creates new lambda context and places old one on the stack.
*/
private void visitLambda() {
contextStack.push(context);
context = new Context(true);
}
/**
* Examines the return statement and tells context about it.
*
* @param ast return statement to check.
*/
private void visitReturn(DetailAST ast) {
// we can't identify which max to use for lambdas, so we can only assign
// after the first return statement is seen
if (ast.getFirstChild().getType() == TokenTypes.SEMI) {
context.visitLiteralReturn(maxForVoid, true);
}
else {
context.visitLiteralReturn(max, false);
}
}
/**
* Class to encapsulate information about one method.
*/
private class Context {
/** Whether we should check this method or not. */
private final boolean checking;
/** Counter for return statements. */
private int count;
/** Maximum allowed number of return statements. */
private Integer maxAllowed;
/** Identifies if context is void. */
private boolean isVoidContext;
/**
* Creates new method context.
*
* @param checking should we check this method or not.
*/
/* package */ Context(boolean checking) {
this.checking = checking;
}
/**
* Increase the number of return statements and set context return type.
*
* @param maxAssigned Maximum allowed number of return statements.
* @param voidReturn Identifies if context is void.
*/
public void visitLiteralReturn(int maxAssigned, Boolean voidReturn) {
isVoidContext = voidReturn;
maxAllowed = maxAssigned;
++count;
}
/**
* Checks if number of return statements in the method are more
* than allowed.
*
* @param ast method def associated with this context.
*/
public void checkCount(DetailAST ast) {
if (checking && maxAllowed != null && count > maxAllowed) {
if (isVoidContext) {
log(ast, MSG_KEY_VOID, count, maxAllowed);
}
else {
log(ast, MSG_KEY, count, maxAllowed);
}
}
}
}
}