001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029 030/** 031 * <p> 032 * Restricts the number of return statements in methods, constructors and lambda expressions 033 * (2 by default). Ignores specified methods ({@code equals()} by default). 034 * </p> 035 * <p> 036 * <b>max</b> property will only check returns in methods and lambdas that 037 * return a specific value (Ex: 'return 1;'). 038 * </p> 039 * <p> 040 * <b>maxForVoid</b> property will only check returns in methods, constructors, 041 * and lambdas that have no return type (IE 'return;'). It will only count 042 * visible return statements. Return statements not normally written, but 043 * implied, at the end of the method/constructor definition will not be taken 044 * into account. To disallow "return;" in void return type methods, use a value 045 * of 0. 046 * </p> 047 * <p> 048 * Rationale: Too many return points can be indication that code is 049 * attempting to do too much or may be difficult to understand. 050 * </p> 051 * 052 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> 053 */ 054public final class ReturnCountCheck extends AbstractCheck { 055 056 /** 057 * A key is pointing to the warning message text in "messages.properties" 058 * file. 059 */ 060 public static final String MSG_KEY = "return.count"; 061 062 /** Stack of method contexts. */ 063 private final Deque<Context> contextStack = new ArrayDeque<>(); 064 065 /** The regexp to match against. */ 066 private Pattern format = Pattern.compile("^equals$"); 067 068 /** Maximum allowed number of return statements. */ 069 private int max = 2; 070 /** Maximum allowed number of return statements for void methods. */ 071 private int maxForVoid = 1; 072 /** Current method context. */ 073 private Context context; 074 075 @Override 076 public int[] getDefaultTokens() { 077 return new int[] { 078 TokenTypes.CTOR_DEF, 079 TokenTypes.METHOD_DEF, 080 TokenTypes.LAMBDA, 081 TokenTypes.LITERAL_RETURN, 082 }; 083 } 084 085 @Override 086 public int[] getRequiredTokens() { 087 return new int[] {TokenTypes.LITERAL_RETURN}; 088 } 089 090 @Override 091 public int[] getAcceptableTokens() { 092 return new int[] { 093 TokenTypes.CTOR_DEF, 094 TokenTypes.METHOD_DEF, 095 TokenTypes.LAMBDA, 096 TokenTypes.LITERAL_RETURN, 097 }; 098 } 099 100 /** 101 * Set the format for the specified regular expression. 102 * @param pattern a pattern. 103 */ 104 public void setFormat(Pattern pattern) { 105 format = pattern; 106 } 107 108 /** 109 * Setter for max property. 110 * @param max maximum allowed number of return statements. 111 */ 112 public void setMax(int max) { 113 this.max = max; 114 } 115 116 /** 117 * Setter for maxForVoid property. 118 * @param maxForVoid maximum allowed number of return statements for void methods. 119 */ 120 public void setMaxForVoid(int maxForVoid) { 121 this.maxForVoid = maxForVoid; 122 } 123 124 @Override 125 public void beginTree(DetailAST rootAST) { 126 context = new Context(false); 127 contextStack.clear(); 128 } 129 130 @Override 131 public void visitToken(DetailAST ast) { 132 switch (ast.getType()) { 133 case TokenTypes.CTOR_DEF: 134 case TokenTypes.METHOD_DEF: 135 visitMethodDef(ast); 136 break; 137 case TokenTypes.LAMBDA: 138 visitLambda(); 139 break; 140 case TokenTypes.LITERAL_RETURN: 141 visitReturn(ast); 142 break; 143 default: 144 throw new IllegalStateException(ast.toString()); 145 } 146 } 147 148 @Override 149 public void leaveToken(DetailAST ast) { 150 switch (ast.getType()) { 151 case TokenTypes.CTOR_DEF: 152 case TokenTypes.METHOD_DEF: 153 case TokenTypes.LAMBDA: 154 leave(ast); 155 break; 156 case TokenTypes.LITERAL_RETURN: 157 // Do nothing 158 break; 159 default: 160 throw new IllegalStateException(ast.toString()); 161 } 162 } 163 164 /** 165 * Creates new method context and places old one on the stack. 166 * @param ast method definition for check. 167 */ 168 private void visitMethodDef(DetailAST ast) { 169 contextStack.push(context); 170 final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT); 171 final boolean check = !format.matcher(methodNameAST.getText()).find(); 172 context = new Context(check); 173 } 174 175 /** 176 * Checks number of return statements and restore previous context. 177 * @param ast node to leave. 178 */ 179 private void leave(DetailAST ast) { 180 context.checkCount(ast); 181 context = contextStack.pop(); 182 } 183 184 /** 185 * Creates new lambda context and places old one on the stack. 186 */ 187 private void visitLambda() { 188 contextStack.push(context); 189 context = new Context(true); 190 } 191 192 /** 193 * Examines the return statement and tells context about it. 194 * @param ast return statement to check. 195 */ 196 private void visitReturn(DetailAST ast) { 197 // we can't identify which max to use for lambdas, so we can only assign 198 // after the first return statement is seen 199 if (ast.getFirstChild().getType() == TokenTypes.SEMI) { 200 context.visitLiteralReturn(maxForVoid); 201 } 202 else { 203 context.visitLiteralReturn(max); 204 } 205 } 206 207 /** 208 * Class to encapsulate information about one method. 209 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> 210 */ 211 private class Context { 212 /** Whether we should check this method or not. */ 213 private final boolean checking; 214 /** Counter for return statements. */ 215 private int count; 216 /** Maximum allowed number of return statements. */ 217 private Integer maxAllowed; 218 219 /** 220 * Creates new method context. 221 * @param checking should we check this method or not. 222 */ 223 Context(boolean checking) { 224 this.checking = checking; 225 } 226 227 /** 228 * Increase the number of return statements. 229 * @param maxAssigned Maximum allowed number of return statements. 230 */ 231 public void visitLiteralReturn(int maxAssigned) { 232 if (maxAllowed == null) { 233 maxAllowed = maxAssigned; 234 } 235 236 ++count; 237 } 238 239 /** 240 * Checks if number of return statements in the method are more 241 * than allowed. 242 * @param ast method def associated with this context. 243 */ 244 public void checkCount(DetailAST ast) { 245 if (checking && maxAllowed != null && count > maxAllowed) { 246 log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY, count, maxAllowed); 247 } 248 } 249 } 250}