View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.coding;
21  
22  import java.util.ArrayDeque;
23  import java.util.Deque;
24  import java.util.regex.Pattern;
25  
26  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
27  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28  import com.puppycrawl.tools.checkstyle.api.DetailAST;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  
31  /**
32   * <div>
33   * Restricts the number of return statements in methods, constructors and lambda expressions.
34   * Ignores specified methods ({@code equals} by default).
35   * </div>
36   *
37   * <p>
38   * <b>max</b> property will only check returns in methods and lambdas that
39   * return a specific value (Ex: 'return 1;').
40   * </p>
41   *
42   * <p>
43   * <b>maxForVoid</b> property will only check returns in methods, constructors,
44   * and lambdas that have no return type (IE 'return;'). It will only count
45   * visible return statements. Return statements not normally written, but
46   * implied, at the end of the method/constructor definition will not be taken
47   * into account. To disallow "return;" in void return type methods, use a value
48   * of 0.
49   * </p>
50   *
51   * <p>
52   * Rationale: Too many return points can mean that code is
53   * attempting to do too much or may be difficult to understand.
54   * </p>
55   * <ul>
56   * <li>
57   * Property {@code format} - Specify method names to ignore.
58   * Type is {@code java.util.regex.Pattern}.
59   * Default value is {@code "^equals$"}.
60   * </li>
61   * <li>
62   * Property {@code max} - Specify maximum allowed number of return statements
63   * in non-void methods/lambdas.
64   * Type is {@code int}.
65   * Default value is {@code 2}.
66   * </li>
67   * <li>
68   * Property {@code maxForVoid} - Specify maximum allowed number of return statements
69   * in void methods/constructors/lambdas.
70   * Type is {@code int}.
71   * Default value is {@code 1}.
72   * </li>
73   * <li>
74   * Property {@code tokens} - tokens to check
75   * Type is {@code java.lang.String[]}.
76   * Validation type is {@code tokenSet}.
77   * Default value is:
78   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
79   * CTOR_DEF</a>,
80   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
81   * METHOD_DEF</a>,
82   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA">
83   * LAMBDA</a>.
84   * </li>
85   * </ul>
86   *
87   * <p>
88   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
89   * </p>
90   *
91   * <p>
92   * Violation Message Keys:
93   * </p>
94   * <ul>
95   * <li>
96   * {@code return.count}
97   * </li>
98   * <li>
99   * {@code return.countVoid}
100  * </li>
101  * </ul>
102  *
103  * @since 3.2
104  */
105 @FileStatefulCheck
106 public 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 }