RedundantReturnCheck.java
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2019 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.github.sevntu.checkstyle.checks.coding;
import java.util.ArrayList;
import java.util.List;
import com.github.sevntu.checkstyle.SevntuUtil;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
/**
* <p>
* Highlights usage of redundant returns inside constructors and methods with void
* result.
* </p>
* <p>
* For example:
* </p>
* <p>
* 1. Non-empty constructor
* </p>
* <pre>
* public HelloWorld(){
* doStuff();
* return;
* }</pre>
* <p>
* 2. Method with void result
* </p>
* <pre>
* public void testMethod1(){
* doStuff();
* return;
* }</pre>
* <p>
* However, if your IDE does not support breakpoints on the method entry, you
* can allow the use of redundant returns in constructors and methods with void
* result without code except for 'return;'.
* </p>
* <p>
* For example:
* </p>
* <p>
* 1. Empty constructor
* </p>
* <pre>
* public HelloWorld(){
* return;
* }</pre>
* <p>
* 2. Method with void result and empty body
* </p>
* <pre>
* public void testMethod1(){
* return;
* }</pre>
* @author <a href="mailto:fishh1991@gmail.com">Troshin Sergey</a>
* @author <a href="mailto:maxvetrenko2241@gmail.com">Max Vetrenko</a>
* @author <a href="mailto:nesterenko-aleksey@list.ru">Alexey Nesterenko</a>
* @since 1.8.0
*/
public class RedundantReturnCheck extends AbstractCheck {
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY = "redundant.return.check";
/**
* If True, allow 'return' in empty constructors and methods that return void.
*/
private boolean allowReturnInEmptyMethodsAndConstructors;
/**
* Setter for allowReturnInEmptyMethodsAndConstructors.
* @param allowEmptyBlocks allow 'return' in empty constructors and methods that return void.
*/
public void setAllowReturnInEmptyMethodsAndConstructors(boolean allowEmptyBlocks) {
allowReturnInEmptyMethodsAndConstructors = allowEmptyBlocks;
}
@Override
public int[] getDefaultTokens() {
return new int[] {
TokenTypes.CTOR_DEF,
TokenTypes.METHOD_DEF,
};
}
@Override
public int[] getAcceptableTokens() {
return getDefaultTokens();
}
@Override
public int[] getRequiredTokens() {
return getDefaultTokens();
}
@Override
public void visitToken(DetailAST ast) {
final DetailAST blockAst = ast.getLastChild();
switch (ast.getType()) {
case TokenTypes.CTOR_DEF:
if (!ignoreLonelyReturn(blockAst) && hasNonEmptyBody(ast)) {
final List<DetailAST> redundantReturns = getRedundantReturns(blockAst);
log(redundantReturns);
}
break;
case TokenTypes.METHOD_DEF:
if (!ignoreLonelyReturn(blockAst) && isVoidMethodWithNonEmptyBody(ast)) {
final List<DetailAST> redundantReturns = getRedundantReturns(blockAst);
log(redundantReturns);
}
break;
default:
SevntuUtil.reportInvalidToken(ast.getType());
break;
}
}
/**
* Ignores method or constructor if it contains <b>only</b> return statement
* in its body.
* @param objectBlockAst The token to examine.
* @return true if the block can be ignored.
*/
private boolean ignoreLonelyReturn(DetailAST objectBlockAst) {
return allowReturnInEmptyMethodsAndConstructors
&& objectBlockAst.getFirstChild() != null
&& objectBlockAst.getFirstChild().getType()
== TokenTypes.LITERAL_RETURN;
}
/**
* Checks if method's or ctor's body is not empty.
* @param defAst The token to examine.
* @return true if body is not empty.
*/
private static boolean hasNonEmptyBody(DetailAST defAst) {
return defAst.getLastChild().getChildCount() > 1;
}
/**
* Checks if method is void and has a body.
* @param methodDefAst The token to examine.
* @return true if void method has non-empty body.
*/
private static boolean isVoidMethodWithNonEmptyBody(DetailAST methodDefAst) {
return methodDefAst.getLastChild().getType() == TokenTypes.SLIST
&& methodDefAst.findFirstToken(TokenTypes.TYPE)
.findFirstToken(TokenTypes.LITERAL_VOID) != null
&& hasNonEmptyBody(methodDefAst);
}
/**
* Puts violation on each redundant return met in object block of
* method or ctor.
* @param redundantReturnsAst The token to examine.
*/
private void log(List<DetailAST> redundantReturnsAst) {
for (DetailAST redundantLiteralReturnAst : redundantReturnsAst) {
log(redundantLiteralReturnAst, MSG_KEY);
}
}
/**
* Returns the list of redundant returns found in method's or ctor's
* object block.
* @param objectBlockAst
* - a method or constructor object block
* @return list of redundant returns or empty list if none were found
*/
private static List<DetailAST> getRedundantReturns(DetailAST objectBlockAst) {
final List<DetailAST> redundantReturns = new ArrayList<>();
final int placeForRedundantReturn = objectBlockAst
.getLastChild().getPreviousSibling().getType();
if (placeForRedundantReturn == TokenTypes.LITERAL_RETURN) {
final DetailAST lastChildAst = objectBlockAst.getLastChild();
final DetailAST redundantReturnAst = lastChildAst.getPreviousSibling();
redundantReturns.add(redundantReturnAst);
}
else if (placeForRedundantReturn == TokenTypes.LITERAL_TRY
&& !getRedundantReturnsInTryCatchBlock(objectBlockAst
.findFirstToken(TokenTypes.LITERAL_TRY)).isEmpty()) {
final List<DetailAST> redundantsAst = getRedundantReturnsInTryCatchBlock(objectBlockAst
.findFirstToken(TokenTypes.LITERAL_TRY));
redundantReturns.addAll(redundantsAst);
}
return redundantReturns;
}
/**
* Returns the list of redundant returns found in try, catch, finally object blocks.
* @param tryAst
* - Ast that contain a try node.
* @return list of redundant returns or empty list if none were found
*/
private static List<DetailAST> getRedundantReturnsInTryCatchBlock(DetailAST tryAst) {
final List<DetailAST> redundantReturns = new ArrayList<>();
DetailAST tryBlockAst = null;
if (tryAst.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) {
tryBlockAst = tryAst.getFirstChild().getNextSibling();
}
else {
tryBlockAst = tryAst.getFirstChild();
}
DetailAST redundantReturnAst =
getRedundantReturnInBlock(tryBlockAst.getLastChild().getPreviousSibling());
// if redundant return is in try block
if (redundantReturnAst != null) {
redundantReturns.add(redundantReturnAst);
}
DetailAST blockAst = tryBlockAst;
// look for redundant returns in all catches
for (DetailAST catchBlockAst = getNextCatchBlock(blockAst);
catchBlockAst != null;
catchBlockAst = getNextCatchBlock(blockAst)) {
final DetailAST lastStatementOfCatchBlockAst = catchBlockAst
.getLastChild().getLastChild().getPreviousSibling();
if (lastStatementOfCatchBlockAst != null) {
redundantReturnAst = getRedundantReturnInBlock(lastStatementOfCatchBlockAst);
if (redundantReturnAst != null) {
redundantReturns.add(redundantReturnAst);
}
}
blockAst = blockAst.getNextSibling();
}
// if redundant return is in finally block
if (blockAst.getNextSibling() != null) {
final DetailAST afterCatchBlockAst = blockAst.getNextSibling().getLastChild()
.getLastChild();
redundantReturnAst = getRedundantReturnInBlock(afterCatchBlockAst.getPreviousSibling());
if (redundantReturnAst != null) {
redundantReturns.add(redundantReturnAst);
}
}
return redundantReturns;
}
/**
* Gets next catch block in try block if exists.
* @param blockAst The token to examine.
* @return next found catchBlockAst, if no catch was found - returns null
*/
private static DetailAST getNextCatchBlock(DetailAST blockAst) {
DetailAST catchBlockAst = null;
if (blockAst.getNextSibling() != null
&& blockAst.getNextSibling().getType() == TokenTypes.LITERAL_CATCH) {
catchBlockAst = blockAst.getNextSibling();
}
return catchBlockAst;
}
/**
* Returns redundant return from try-catch-finally block.
* @param statementAst
* - a place where the redundantReturn is expected.
* @return redundant literal return if found, else null.
*/
private static DetailAST getRedundantReturnInBlock(DetailAST statementAst) {
DetailAST redundantReturnAst = null;
if (statementAst != null) {
if (statementAst.getType() == TokenTypes.LITERAL_RETURN) {
redundantReturnAst = statementAst;
}
else {
if (statementAst.getFirstChild() != null) {
final DetailAST foundRedundantReturnAst =
findRedundantReturnInCatch(statementAst);
if (foundRedundantReturnAst != null) {
redundantReturnAst = foundRedundantReturnAst;
}
}
}
}
return redundantReturnAst;
}
/**
* Looks for literal return in the last statement of a catch block.
* @param lastStatementInCatchBlockAst The token to examine.
* @return redundant literal return, if there's no one - returns null
*/
private static DetailAST findRedundantReturnInCatch(DetailAST lastStatementInCatchBlockAst) {
DetailAST redundantReturnAst = null;
DetailAST currentNodeAst = lastStatementInCatchBlockAst;
DetailAST returnAst = null;
DetailAST toVisitAst = SevntuUtil.getNextSubTreeNode(currentNodeAst, currentNodeAst);
while (toVisitAst != null) {
if (toVisitAst.getType() == TokenTypes.OBJBLOCK) {
while (toVisitAst.getNextSibling() == null) {
toVisitAst = toVisitAst.getParent();
}
toVisitAst = toVisitAst.getNextSibling();
}
else if (isFinalReturn(toVisitAst)) {
returnAst = toVisitAst;
while (toVisitAst != null
&& toVisitAst.getParent() != currentNodeAst.getLastChild()) {
toVisitAst = toVisitAst.getParent();
}
if (toVisitAst != null) {
redundantReturnAst = returnAst;
}
toVisitAst = returnAst;
}
toVisitAst = SevntuUtil.getNextSubTreeNode(toVisitAst, currentNodeAst);
}
currentNodeAst = SevntuUtil.getNextSubTreeNode(currentNodeAst,
lastStatementInCatchBlockAst);
return redundantReturnAst;
}
/**
* Checks if the {@code ast} is the final return statement.
* @param ast the AST to examine.
* @return {@code true} if the {@code ast} is the final return statement.
*/
private static boolean isFinalReturn(DetailAST ast) {
return (ast.getParent().getParent().getNextSibling() == null
|| ast.getParent().getParent().getNextSibling().getType()
== TokenTypes.RCURLY)
&& ast.getType() == TokenTypes.LITERAL_RETURN
&& ast.getParent().getNextSibling() == null;
}
}