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