001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2022 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 * Example:
094 * </p>
095 * <pre>
096 * public class MyClass {
097 *   public int sign(int x) {
098 *     if (x &lt; 0)
099 *       return -1;
100 *     if (x == 0)
101 *       return 1;
102 *     return 0;
103 *   } // OK
104 *   public int badSign(int x) {
105 *     if (x &lt; -2)
106 *       return -2;
107 *     if (x == 0)
108 *       return 0;
109 *     if (x &gt; 2)
110 *       return 2;
111 *     return 1;
112 *   } // violation, more than three return statements
113 * }
114 * </pre>
115 * <p>
116 * To configure the check so that it doesn't allow any return statements per void method:
117 * </p>
118 * <pre>
119 * &lt;module name=&quot;ReturnCount&quot;&gt;
120 *   &lt;property name=&quot;maxForVoid&quot; value=&quot;0&quot;/&gt;
121 * &lt;/module&gt;
122 * </pre>
123 * <p>
124 * Example:
125 * </p>
126 * <pre>
127 * public class MyClass {
128 *   public void firstMethod(int x) {
129 *   } // OK
130 *
131 *   public void badMethod(int x) {
132 *     return;
133 *   } // violation, return statements per void method
134 * }
135 * </pre>
136 * <p>
137 * To configure the check so that it doesn't allow more than 2 return statements per method
138 * (ignoring the {@code equals()} method) and more than 1 return statements per void method:
139 * </p>
140 * <pre>
141 * &lt;module name=&quot;ReturnCount&quot;&gt;
142 *   &lt;property name=&quot;max&quot; value=&quot;2&quot;/&gt;
143 *   &lt;property name=&quot;maxForVoid&quot; value=&quot;1&quot;/&gt;
144 * &lt;/module&gt;
145 * </pre>
146 * <p>
147 * Example:
148 * </p>
149 * <pre>
150 * public class MyClass {
151 *   public void firstMethod() {
152 *   } // OK
153 *
154 *   public void secondMethod() {
155 *     return;
156 *   } // OK
157 *
158 *   public void badMethod(int x) {
159 *     if (x == 0)
160 *       return;
161 *     return;
162 *   } // violation, more than one return statements
163 *
164 *   public int sign(int x) {
165 *     if (x &lt; 0)
166 *       return -1;
167 *     return 0;
168 *   } // OK
169 *
170 *   public int badSign(int x) {
171 *     if (x &lt; 0)
172 *       return -1;
173 *     if (x == 0)
174 *       return 1;
175 *     return 0;
176 *   } // violation, more than two return statements in methods
177 * }
178 * </pre>
179 * <p>
180 * To configure the check so that it doesn't allow more than three
181 * return statements per method for all methods:
182 * </p>
183 * <pre>
184 * &lt;module name=&quot;ReturnCount&quot;&gt;
185 *   &lt;property name=&quot;max&quot; value=&quot;3&quot;/&gt;
186 *   &lt;property name=&quot;format&quot; value=&quot;^$&quot;/&gt;
187 * &lt;/module&gt;
188 * </pre>
189 * <p>
190 * Example:
191 * </p>
192 * <pre>
193 * public class MyClass {
194 *   public int sign(int x) {
195 *     if (x &lt; 0)
196 *       return -1;
197 *     if (x == 0)
198 *       return 1;
199 *     return 0;
200 *   } // OK
201 *
202 *   public int badSign(int x) {
203 *     if (x &lt; -2)
204 *       return -2;
205 *     if (x == 0)
206 *       return 0;
207 *     if (x &gt; 2)
208 *       return 2;
209 *     return 1;
210 *   } // violation, more than three return statements per method
211 * }
212 * </pre>
213 * <p>
214 * To configure the check so that it doesn't allow any return statements in constructors,
215 * more than one return statement in all lambda expressions and more than two return
216 * statements in methods:
217 * </p>
218 * <pre>
219 * &lt;module name=&quot;ReturnCount&quot;&gt;
220 *   &lt;property name=&quot;maxForVoid&quot; value=&quot;0&quot;/&gt;
221 *   &lt;property name=&quot;tokens&quot; value=&quot;CTOR_DEF&quot;/&gt;
222 * &lt;/module&gt;
223 * &lt;module name=&quot;ReturnCount&quot;&gt;
224 *   &lt;property name=&quot;max&quot; value=&quot;1&quot;/&gt;
225 *   &lt;property name=&quot;tokens&quot; value=&quot;LAMBDA&quot;/&gt;
226 * &lt;/module&gt;
227 * &lt;module name=&quot;ReturnCount&quot;&gt;
228 *   &lt;property name=&quot;max&quot; value=&quot;2&quot;/&gt;
229 *   &lt;property name=&quot;tokens&quot; value=&quot;METHOD_DEF&quot;/&gt;
230 * &lt;/module&gt;
231 * </pre>
232 * <p>
233 * Example:
234 * </p>
235 * <pre>
236 * import java.util.function.Predicate;
237 *
238 * public class Test {
239 *   public Test() {
240 *   } // OK
241 *
242 *   public Test(int i) {
243 *     return; // violation, max allowed for constructors is 0
244 *   }
245 *
246 *   final Predicate&lt;Integer&gt; p = i -&gt; {
247 *     if (i &gt; 5) {
248 *       return true;
249 *     }
250 *     return false;
251 *   }; // violation, max allowed for lambdas is 1
252 *
253 *   final Predicate&lt;Integer&gt; q = i -&gt; {
254 *     return i &gt; 5;
255 *   }; // OK
256 *
257 *   public int sign(int x) {
258 *     if (x &gt; 0)
259 *       return -1;
260 *     return 0;
261 *   } // OK
262 *
263 *   public int badSign(int x) {
264 *     if (x &lt; 0)
265 *       return -1;
266 *     if (x == 0)
267 *       return 1;
268 *     return 0;
269 *   } // violation, more than two return statements in methods
270 * }
271 * </pre>
272 * <p>
273 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
274 * </p>
275 * <p>
276 * Violation Message Keys:
277 * </p>
278 * <ul>
279 * <li>
280 * {@code return.count}
281 * </li>
282 * <li>
283 * {@code return.countVoid}
284 * </li>
285 * </ul>
286 *
287 * @since 3.2
288 */
289@FileStatefulCheck
290public final class ReturnCountCheck extends AbstractCheck {
291
292    /**
293     * A key is pointing to the warning message text in "messages.properties"
294     * file.
295     */
296    public static final String MSG_KEY = "return.count";
297    /**
298     * A key pointing to the warning message text in "messages.properties"
299     * file.
300     */
301    public static final String MSG_KEY_VOID = "return.countVoid";
302
303    /** Stack of method contexts. */
304    private final Deque<Context> contextStack = new ArrayDeque<>();
305
306    /** Specify method names to ignore. */
307    private Pattern format = Pattern.compile("^equals$");
308
309    /** Specify maximum allowed number of return statements in non-void methods/lambdas. */
310    private int max = 2;
311    /** Specify maximum allowed number of return statements in void methods/constructors/lambdas. */
312    private int maxForVoid = 1;
313    /** Current method context. */
314    private Context context;
315
316    @Override
317    public int[] getDefaultTokens() {
318        return new int[] {
319            TokenTypes.CTOR_DEF,
320            TokenTypes.METHOD_DEF,
321            TokenTypes.LAMBDA,
322            TokenTypes.LITERAL_RETURN,
323        };
324    }
325
326    @Override
327    public int[] getRequiredTokens() {
328        return new int[] {TokenTypes.LITERAL_RETURN};
329    }
330
331    @Override
332    public int[] getAcceptableTokens() {
333        return new int[] {
334            TokenTypes.CTOR_DEF,
335            TokenTypes.METHOD_DEF,
336            TokenTypes.LAMBDA,
337            TokenTypes.LITERAL_RETURN,
338        };
339    }
340
341    /**
342     * Setter to specify method names to ignore.
343     *
344     * @param pattern a pattern.
345     */
346    public void setFormat(Pattern pattern) {
347        format = pattern;
348    }
349
350    /**
351     * Setter to specify maximum allowed number of return statements
352     * in non-void methods/lambdas.
353     *
354     * @param max maximum allowed number of return statements.
355     */
356    public void setMax(int max) {
357        this.max = max;
358    }
359
360    /**
361     * Setter to specify maximum allowed number of return statements
362     * in void methods/constructors/lambdas.
363     *
364     * @param maxForVoid maximum allowed number of return statements for void methods.
365     */
366    public void setMaxForVoid(int maxForVoid) {
367        this.maxForVoid = maxForVoid;
368    }
369
370    @Override
371    public void beginTree(DetailAST rootAST) {
372        context = new Context(false);
373        contextStack.clear();
374    }
375
376    @Override
377    public void visitToken(DetailAST ast) {
378        switch (ast.getType()) {
379            case TokenTypes.CTOR_DEF:
380            case TokenTypes.METHOD_DEF:
381                visitMethodDef(ast);
382                break;
383            case TokenTypes.LAMBDA:
384                visitLambda();
385                break;
386            case TokenTypes.LITERAL_RETURN:
387                visitReturn(ast);
388                break;
389            default:
390                throw new IllegalStateException(ast.toString());
391        }
392    }
393
394    @Override
395    public void leaveToken(DetailAST ast) {
396        switch (ast.getType()) {
397            case TokenTypes.CTOR_DEF:
398            case TokenTypes.METHOD_DEF:
399            case TokenTypes.LAMBDA:
400                leave(ast);
401                break;
402            case TokenTypes.LITERAL_RETURN:
403                // Do nothing
404                break;
405            default:
406                throw new IllegalStateException(ast.toString());
407        }
408    }
409
410    /**
411     * Creates new method context and places old one on the stack.
412     *
413     * @param ast method definition for check.
414     */
415    private void visitMethodDef(DetailAST ast) {
416        contextStack.push(context);
417        final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT);
418        final boolean check = !format.matcher(methodNameAST.getText()).find();
419        context = new Context(check);
420    }
421
422    /**
423     * Checks number of return statements and restore previous context.
424     *
425     * @param ast node to leave.
426     */
427    private void leave(DetailAST ast) {
428        context.checkCount(ast);
429        context = contextStack.pop();
430    }
431
432    /**
433     * Creates new lambda context and places old one on the stack.
434     */
435    private void visitLambda() {
436        contextStack.push(context);
437        context = new Context(true);
438    }
439
440    /**
441     * Examines the return statement and tells context about it.
442     *
443     * @param ast return statement to check.
444     */
445    private void visitReturn(DetailAST ast) {
446        // we can't identify which max to use for lambdas, so we can only assign
447        // after the first return statement is seen
448        if (ast.getFirstChild().getType() == TokenTypes.SEMI) {
449            context.visitLiteralReturn(maxForVoid, Boolean.TRUE);
450        }
451        else {
452            context.visitLiteralReturn(max, Boolean.FALSE);
453        }
454    }
455
456    /**
457     * Class to encapsulate information about one method.
458     */
459    private class Context {
460
461        /** Whether we should check this method or not. */
462        private final boolean checking;
463        /** Counter for return statements. */
464        private int count;
465        /** Maximum allowed number of return statements. */
466        private Integer maxAllowed;
467        /** Identifies if context is void. */
468        private boolean isVoidContext;
469
470        /**
471         * Creates new method context.
472         *
473         * @param checking should we check this method or not.
474         */
475        /* package */ Context(boolean checking) {
476            this.checking = checking;
477        }
478
479        /**
480         * Increase the number of return statements and set context return type.
481         *
482         * @param maxAssigned Maximum allowed number of return statements.
483         * @param voidReturn Identifies if context is void.
484         */
485        public void visitLiteralReturn(int maxAssigned, Boolean voidReturn) {
486            isVoidContext = voidReturn;
487            maxAllowed = maxAssigned;
488
489            ++count;
490        }
491
492        /**
493         * Checks if number of return statements in the method are more
494         * than allowed.
495         *
496         * @param ast method def associated with this context.
497         */
498        public void checkCount(DetailAST ast) {
499            if (checking && maxAllowed != null && count > maxAllowed) {
500                if (isVoidContext) {
501                    log(ast, MSG_KEY_VOID, count, maxAllowed);
502                }
503                else {
504                    log(ast, MSG_KEY, count, maxAllowed);
505                }
506            }
507        }
508
509    }
510
511}