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