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 * <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            case TokenTypes.METHOD_DEF:
200                visitMethodDef(ast);
201                break;
202            case TokenTypes.LAMBDA:
203                visitLambda();
204                break;
205            case TokenTypes.LITERAL_RETURN:
206                visitReturn(ast);
207                break;
208            default:
209                throw new IllegalStateException(ast.toString());
210        }
211    }
212
213    @Override
214    public void leaveToken(DetailAST ast) {
215        switch (ast.getType()) {
216            case TokenTypes.CTOR_DEF:
217            case TokenTypes.METHOD_DEF:
218            case TokenTypes.LAMBDA:
219                leave(ast);
220                break;
221            case TokenTypes.LITERAL_RETURN:
222                // Do nothing
223                break;
224            default:
225                throw new IllegalStateException(ast.toString());
226        }
227    }
228
229    /**
230     * Creates new method context and places old one on the stack.
231     *
232     * @param ast method definition for check.
233     */
234    private void visitMethodDef(DetailAST ast) {
235        contextStack.push(context);
236        final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT);
237        final boolean check = !format.matcher(methodNameAST.getText()).find();
238        context = new Context(check);
239    }
240
241    /**
242     * Checks number of return statements and restore previous context.
243     *
244     * @param ast node to leave.
245     */
246    private void leave(DetailAST ast) {
247        context.checkCount(ast);
248        context = contextStack.pop();
249    }
250
251    /**
252     * Creates new lambda context and places old one on the stack.
253     */
254    private void visitLambda() {
255        contextStack.push(context);
256        context = new Context(true);
257    }
258
259    /**
260     * Examines the return statement and tells context about it.
261     *
262     * @param ast return statement to check.
263     */
264    private void visitReturn(DetailAST ast) {
265        // we can't identify which max to use for lambdas, so we can only assign
266        // after the first return statement is seen
267        if (ast.getFirstChild().getType() == TokenTypes.SEMI) {
268            context.visitLiteralReturn(maxForVoid, Boolean.TRUE);
269        }
270        else {
271            context.visitLiteralReturn(max, Boolean.FALSE);
272        }
273    }
274
275    /**
276     * Class to encapsulate information about one method.
277     */
278    private final class Context {
279
280        /** Whether we should check this method or not. */
281        private final boolean checking;
282        /** Counter for return statements. */
283        private int count;
284        /** Maximum allowed number of return statements. */
285        private Integer maxAllowed;
286        /** Identifies if context is void. */
287        private boolean isVoidContext;
288
289        /**
290         * Creates new method context.
291         *
292         * @param checking should we check this method or not
293         */
294        private Context(boolean checking) {
295            this.checking = checking;
296        }
297
298        /**
299         * Increase the number of return statements and set context return type.
300         *
301         * @param maxAssigned Maximum allowed number of return statements.
302         * @param voidReturn Identifies if context is void.
303         */
304        public void visitLiteralReturn(int maxAssigned, Boolean voidReturn) {
305            isVoidContext = voidReturn;
306            maxAllowed = maxAssigned;
307
308            ++count;
309        }
310
311        /**
312         * Checks if number of return statements in the method are more
313         * than allowed.
314         *
315         * @param ast method def associated with this context.
316         */
317        public void checkCount(DetailAST ast) {
318            if (checking && maxAllowed != null && count > maxAllowed) {
319                if (isVoidContext) {
320                    log(ast, MSG_KEY_VOID, count, maxAllowed);
321                }
322                else {
323                    log(ast, MSG_KEY, count, maxAllowed);
324                }
325            }
326        }
327
328    }
329
330}