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