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   * <p>
33   * Restricts the number of return statements in methods, constructors and lambda expressions.
34   * Ignores specified methods ({@code equals} by default).
35   * </p>
36   * <p>
37   * <b>max</b> property will only check returns in methods and lambdas that
38   * return a specific value (Ex: 'return 1;').
39   * </p>
40   * <p>
41   * <b>maxForVoid</b> property will only check returns in methods, constructors,
42   * and lambdas that have no return type (IE 'return;'). It will only count
43   * visible return statements. Return statements not normally written, but
44   * implied, at the end of the method/constructor definition will not be taken
45   * into account. To disallow "return;" in void return type methods, use a value
46   * of 0.
47   * </p>
48   * <p>
49   * Rationale: Too many return points can mean that code is
50   * attempting to do too much or may be difficult to understand.
51   * </p>
52   * <ul>
53   * <li>
54   * Property {@code format} - Specify method names to ignore.
55   * Type is {@code java.util.regex.Pattern}.
56   * Default value is {@code "^equals$"}.
57   * </li>
58   * <li>
59   * Property {@code max} - Specify maximum allowed number of return statements
60   * in non-void methods/lambdas.
61   * Type is {@code int}.
62   * Default value is {@code 2}.
63   * </li>
64   * <li>
65   * Property {@code maxForVoid} - Specify maximum allowed number of return statements
66   * in void methods/constructors/lambdas.
67   * Type is {@code int}.
68   * Default value is {@code 1}.
69   * </li>
70   * <li>
71   * Property {@code tokens} - tokens to check
72   * Type is {@code java.lang.String[]}.
73   * Validation type is {@code tokenSet}.
74   * Default value is:
75   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
76   * CTOR_DEF</a>,
77   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
78   * METHOD_DEF</a>,
79   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA">
80   * LAMBDA</a>.
81   * </li>
82   * </ul>
83   * <p>
84   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
85   * </p>
86   * <p>
87   * Violation Message Keys:
88   * </p>
89   * <ul>
90   * <li>
91   * {@code return.count}
92   * </li>
93   * <li>
94   * {@code return.countVoid}
95   * </li>
96   * </ul>
97   *
98   * @since 3.2
99   */
100 @FileStatefulCheck
101 public final class ReturnCountCheck extends AbstractCheck {
102 
103     /**
104      * A key is pointing to the warning message text in "messages.properties"
105      * file.
106      */
107     public static final String MSG_KEY = "return.count";
108     /**
109      * A key pointing to the warning message text in "messages.properties"
110      * file.
111      */
112     public static final String MSG_KEY_VOID = "return.countVoid";
113 
114     /** Stack of method contexts. */
115     private final Deque<Context> contextStack = new ArrayDeque<>();
116 
117     /** Specify method names to ignore. */
118     private Pattern format = Pattern.compile("^equals$");
119 
120     /** Specify maximum allowed number of return statements in non-void methods/lambdas. */
121     private int max = 2;
122     /** Specify maximum allowed number of return statements in void methods/constructors/lambdas. */
123     private int maxForVoid = 1;
124     /** Current method context. */
125     private Context context;
126 
127     @Override
128     public int[] getDefaultTokens() {
129         return new int[] {
130             TokenTypes.CTOR_DEF,
131             TokenTypes.METHOD_DEF,
132             TokenTypes.LAMBDA,
133             TokenTypes.LITERAL_RETURN,
134         };
135     }
136 
137     @Override
138     public int[] getRequiredTokens() {
139         return new int[] {TokenTypes.LITERAL_RETURN};
140     }
141 
142     @Override
143     public int[] getAcceptableTokens() {
144         return new int[] {
145             TokenTypes.CTOR_DEF,
146             TokenTypes.METHOD_DEF,
147             TokenTypes.LAMBDA,
148             TokenTypes.LITERAL_RETURN,
149         };
150     }
151 
152     /**
153      * Setter to specify method names to ignore.
154      *
155      * @param pattern a pattern.
156      * @since 3.4
157      */
158     public void setFormat(Pattern pattern) {
159         format = pattern;
160     }
161 
162     /**
163      * Setter to specify maximum allowed number of return statements
164      * in non-void methods/lambdas.
165      *
166      * @param max maximum allowed number of return statements.
167      * @since 3.2
168      */
169     public void setMax(int max) {
170         this.max = max;
171     }
172 
173     /**
174      * Setter to specify maximum allowed number of return statements
175      * in void methods/constructors/lambdas.
176      *
177      * @param maxForVoid maximum allowed number of return statements for void methods.
178      * @since 6.19
179      */
180     public void setMaxForVoid(int maxForVoid) {
181         this.maxForVoid = maxForVoid;
182     }
183 
184     @Override
185     public void beginTree(DetailAST rootAST) {
186         context = new Context(false);
187         contextStack.clear();
188     }
189 
190     @Override
191     public void visitToken(DetailAST ast) {
192         switch (ast.getType()) {
193             case TokenTypes.CTOR_DEF:
194             case TokenTypes.METHOD_DEF:
195                 visitMethodDef(ast);
196                 break;
197             case TokenTypes.LAMBDA:
198                 visitLambda();
199                 break;
200             case TokenTypes.LITERAL_RETURN:
201                 visitReturn(ast);
202                 break;
203             default:
204                 throw new IllegalStateException(ast.toString());
205         }
206     }
207 
208     @Override
209     public void leaveToken(DetailAST ast) {
210         switch (ast.getType()) {
211             case TokenTypes.CTOR_DEF:
212             case TokenTypes.METHOD_DEF:
213             case TokenTypes.LAMBDA:
214                 leave(ast);
215                 break;
216             case TokenTypes.LITERAL_RETURN:
217                 // Do nothing
218                 break;
219             default:
220                 throw new IllegalStateException(ast.toString());
221         }
222     }
223 
224     /**
225      * Creates new method context and places old one on the stack.
226      *
227      * @param ast method definition for check.
228      */
229     private void visitMethodDef(DetailAST ast) {
230         contextStack.push(context);
231         final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT);
232         final boolean check = !format.matcher(methodNameAST.getText()).find();
233         context = new Context(check);
234     }
235 
236     /**
237      * Checks number of return statements and restore previous context.
238      *
239      * @param ast node to leave.
240      */
241     private void leave(DetailAST ast) {
242         context.checkCount(ast);
243         context = contextStack.pop();
244     }
245 
246     /**
247      * Creates new lambda context and places old one on the stack.
248      */
249     private void visitLambda() {
250         contextStack.push(context);
251         context = new Context(true);
252     }
253 
254     /**
255      * Examines the return statement and tells context about it.
256      *
257      * @param ast return statement to check.
258      */
259     private void visitReturn(DetailAST ast) {
260         // we can't identify which max to use for lambdas, so we can only assign
261         // after the first return statement is seen
262         if (ast.getFirstChild().getType() == TokenTypes.SEMI) {
263             context.visitLiteralReturn(maxForVoid, Boolean.TRUE);
264         }
265         else {
266             context.visitLiteralReturn(max, Boolean.FALSE);
267         }
268     }
269 
270     /**
271      * Class to encapsulate information about one method.
272      */
273     private final class Context {
274 
275         /** Whether we should check this method or not. */
276         private final boolean checking;
277         /** Counter for return statements. */
278         private int count;
279         /** Maximum allowed number of return statements. */
280         private Integer maxAllowed;
281         /** Identifies if context is void. */
282         private boolean isVoidContext;
283 
284         /**
285          * Creates new method context.
286          *
287          * @param checking should we check this method or not
288          */
289         private Context(boolean checking) {
290             this.checking = checking;
291         }
292 
293         /**
294          * Increase the number of return statements and set context return type.
295          *
296          * @param maxAssigned Maximum allowed number of return statements.
297          * @param voidReturn Identifies if context is void.
298          */
299         public void visitLiteralReturn(int maxAssigned, Boolean voidReturn) {
300             isVoidContext = voidReturn;
301             maxAllowed = maxAssigned;
302 
303             ++count;
304         }
305 
306         /**
307          * Checks if number of return statements in the method are more
308          * than allowed.
309          *
310          * @param ast method def associated with this context.
311          */
312         public void checkCount(DetailAST ast) {
313             if (checking && maxAllowed != null && count > maxAllowed) {
314                 if (isVoidContext) {
315                     log(ast, MSG_KEY_VOID, count, maxAllowed);
316                 }
317                 else {
318                     log(ast, MSG_KEY, count, maxAllowed);
319                 }
320             }
321         }
322 
323     }
324 
325 }