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