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