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