001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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 * <div>
033 * Restricts the number of return statements in methods, constructors and lambda expressions.
034 * Ignores specified methods ({@code equals} by default).
035 * </div>
036 *
037 * <p>
038 * <b>max</b> property will only check returns in methods and lambdas that
039 * return a specific value (Ex: 'return 1;').
040 * </p>
041 *
042 * <p>
043 * <b>maxForVoid</b> property will only check returns in methods, constructors,
044 * and lambdas that have no return type (IE 'return;'). It will only count
045 * visible return statements. Return statements not normally written, but
046 * implied, at the end of the method/constructor definition will not be taken
047 * into account. To disallow "return;" in void return type methods, use a value
048 * of 0.
049 * </p>
050 *
051 * <p>
052 * Rationale: Too many return points can mean that code is
053 * attempting to do too much or may be difficult to understand.
054 * </p>
055 * <ul>
056 * <li>
057 * Property {@code format} - Specify method names to ignore.
058 * Type is {@code java.util.regex.Pattern}.
059 * Default value is {@code "^equals$"}.
060 * </li>
061 * <li>
062 * Property {@code max} - Specify maximum allowed number of return statements
063 * in non-void methods/lambdas.
064 * Type is {@code int}.
065 * Default value is {@code 2}.
066 * </li>
067 * <li>
068 * Property {@code maxForVoid} - Specify maximum allowed number of return statements
069 * in void methods/constructors/lambdas.
070 * Type is {@code int}.
071 * Default value is {@code 1}.
072 * </li>
073 * <li>
074 * Property {@code tokens} - tokens to check
075 * Type is {@code java.lang.String[]}.
076 * Validation type is {@code tokenSet}.
077 * Default value is:
078 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
079 * CTOR_DEF</a>,
080 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
081 * METHOD_DEF</a>,
082 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA">
083 * LAMBDA</a>.
084 * </li>
085 * </ul>
086 *
087 * <p>
088 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
089 * </p>
090 *
091 * <p>
092 * Violation Message Keys:
093 * </p>
094 * <ul>
095 * <li>
096 * {@code return.count}
097 * </li>
098 * <li>
099 * {@code return.countVoid}
100 * </li>
101 * </ul>
102 *
103 * @since 3.2
104 */
105@FileStatefulCheck
106public final class ReturnCountCheck extends AbstractCheck {
107
108    /**
109     * A key is pointing to the warning message text in "messages.properties"
110     * file.
111     */
112    public static final String MSG_KEY = "return.count";
113    /**
114     * A key pointing to the warning message text in "messages.properties"
115     * file.
116     */
117    public static final String MSG_KEY_VOID = "return.countVoid";
118
119    /** Stack of method contexts. */
120    private final Deque<Context> contextStack = new ArrayDeque<>();
121
122    /** Specify method names to ignore. */
123    private Pattern format = Pattern.compile("^equals$");
124
125    /** Specify maximum allowed number of return statements in non-void methods/lambdas. */
126    private int max = 2;
127    /** Specify maximum allowed number of return statements in void methods/constructors/lambdas. */
128    private int maxForVoid = 1;
129    /** Current method context. */
130    private Context context;
131
132    @Override
133    public int[] getDefaultTokens() {
134        return new int[] {
135            TokenTypes.CTOR_DEF,
136            TokenTypes.METHOD_DEF,
137            TokenTypes.LAMBDA,
138            TokenTypes.LITERAL_RETURN,
139        };
140    }
141
142    @Override
143    public int[] getRequiredTokens() {
144        return new int[] {TokenTypes.LITERAL_RETURN};
145    }
146
147    @Override
148    public int[] getAcceptableTokens() {
149        return new int[] {
150            TokenTypes.CTOR_DEF,
151            TokenTypes.METHOD_DEF,
152            TokenTypes.LAMBDA,
153            TokenTypes.LITERAL_RETURN,
154        };
155    }
156
157    /**
158     * Setter to specify method names to ignore.
159     *
160     * @param pattern a pattern.
161     * @since 3.4
162     */
163    public void setFormat(Pattern pattern) {
164        format = pattern;
165    }
166
167    /**
168     * Setter to specify maximum allowed number of return statements
169     * in non-void methods/lambdas.
170     *
171     * @param max maximum allowed number of return statements.
172     * @since 3.2
173     */
174    public void setMax(int max) {
175        this.max = max;
176    }
177
178    /**
179     * Setter to specify maximum allowed number of return statements
180     * in void methods/constructors/lambdas.
181     *
182     * @param maxForVoid maximum allowed number of return statements for void methods.
183     * @since 6.19
184     */
185    public void setMaxForVoid(int maxForVoid) {
186        this.maxForVoid = maxForVoid;
187    }
188
189    @Override
190    public void beginTree(DetailAST rootAST) {
191        context = new Context(false);
192        contextStack.clear();
193    }
194
195    @Override
196    public void visitToken(DetailAST ast) {
197        switch (ast.getType()) {
198            case TokenTypes.CTOR_DEF,
199                 TokenTypes.METHOD_DEF -> visitMethodDef(ast);
200            case TokenTypes.LAMBDA -> visitLambda();
201            case TokenTypes.LITERAL_RETURN -> visitReturn(ast);
202            default -> throw new IllegalStateException(ast.toString());
203        }
204    }
205
206    @Override
207    public void leaveToken(DetailAST ast) {
208        switch (ast.getType()) {
209            case TokenTypes.CTOR_DEF:
210            case TokenTypes.METHOD_DEF:
211            case TokenTypes.LAMBDA:
212                leave(ast);
213                break;
214            case TokenTypes.LITERAL_RETURN:
215                // Do nothing
216                break;
217            default:
218                throw new IllegalStateException(ast.toString());
219        }
220    }
221
222    /**
223     * Creates new method context and places old one on the stack.
224     *
225     * @param ast method definition for check.
226     */
227    private void visitMethodDef(DetailAST ast) {
228        contextStack.push(context);
229        final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT);
230        final boolean check = !format.matcher(methodNameAST.getText()).find();
231        context = new Context(check);
232    }
233
234    /**
235     * Checks number of return statements and restore previous context.
236     *
237     * @param ast node to leave.
238     */
239    private void leave(DetailAST ast) {
240        context.checkCount(ast);
241        context = contextStack.pop();
242    }
243
244    /**
245     * Creates new lambda context and places old one on the stack.
246     */
247    private void visitLambda() {
248        contextStack.push(context);
249        context = new Context(true);
250    }
251
252    /**
253     * Examines the return statement and tells context about it.
254     *
255     * @param ast return statement to check.
256     */
257    private void visitReturn(DetailAST ast) {
258        // we can't identify which max to use for lambdas, so we can only assign
259        // after the first return statement is seen
260        if (ast.getFirstChild().getType() == TokenTypes.SEMI) {
261            context.visitLiteralReturn(maxForVoid, Boolean.TRUE);
262        }
263        else {
264            context.visitLiteralReturn(max, Boolean.FALSE);
265        }
266    }
267
268    /**
269     * Class to encapsulate information about one method.
270     */
271    private final class Context {
272
273        /** Whether we should check this method or not. */
274        private final boolean checking;
275        /** Counter for return statements. */
276        private int count;
277        /** Maximum allowed number of return statements. */
278        private Integer maxAllowed;
279        /** Identifies if context is void. */
280        private boolean isVoidContext;
281
282        /**
283         * Creates new method context.
284         *
285         * @param checking should we check this method or not
286         */
287        private Context(boolean checking) {
288            this.checking = checking;
289        }
290
291        /**
292         * Increase the number of return statements and set context return type.
293         *
294         * @param maxAssigned Maximum allowed number of return statements.
295         * @param voidReturn Identifies if context is void.
296         */
297        public void visitLiteralReturn(int maxAssigned, Boolean voidReturn) {
298            isVoidContext = voidReturn;
299            maxAllowed = maxAssigned;
300
301            ++count;
302        }
303
304        /**
305         * Checks if number of return statements in the method are more
306         * than allowed.
307         *
308         * @param ast method def associated with this context.
309         */
310        public void checkCount(DetailAST ast) {
311            if (checking && maxAllowed != null && count > maxAllowed) {
312                if (isVoidContext) {
313                    log(ast, MSG_KEY_VOID, count, maxAllowed);
314                }
315                else {
316                    log(ast, MSG_KEY, count, maxAllowed);
317                }
318            }
319        }
320
321    }
322
323}