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