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