View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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   * (2 by default). 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 be indication that code is
50   * attempting to do too much or may be difficult to understand.
51   * </p>
52   *
53   * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
54   */
55  @FileStatefulCheck
56  public final class ReturnCountCheck extends AbstractCheck {
57  
58      /**
59       * A key is pointing to the warning message text in "messages.properties"
60       * file.
61       */
62      public static final String MSG_KEY = "return.count";
63      /**
64       * A key pointing to the warning message text in "messages.properties"
65       * file.
66       */
67      public static final String MSG_KEY_VOID = "return.countVoid";
68  
69      /** Stack of method contexts. */
70      private final Deque<Context> contextStack = new ArrayDeque<>();
71  
72      /** The regexp to match against. */
73      private Pattern format = Pattern.compile("^equals$");
74  
75      /** Maximum allowed number of return statements. */
76      private int max = 2;
77      /** Maximum allowed number of return statements for void methods. */
78      private int maxForVoid = 1;
79      /** Current method context. */
80      private Context context;
81  
82      @Override
83      public int[] getDefaultTokens() {
84          return new int[] {
85              TokenTypes.CTOR_DEF,
86              TokenTypes.METHOD_DEF,
87              TokenTypes.LAMBDA,
88              TokenTypes.LITERAL_RETURN,
89          };
90      }
91  
92      @Override
93      public int[] getRequiredTokens() {
94          return new int[] {TokenTypes.LITERAL_RETURN};
95      }
96  
97      @Override
98      public int[] getAcceptableTokens() {
99          return new int[] {
100             TokenTypes.CTOR_DEF,
101             TokenTypes.METHOD_DEF,
102             TokenTypes.LAMBDA,
103             TokenTypes.LITERAL_RETURN,
104         };
105     }
106 
107     /**
108      * Set the format for the specified regular expression.
109      * @param pattern a pattern.
110      */
111     public void setFormat(Pattern pattern) {
112         format = pattern;
113     }
114 
115     /**
116      * Setter for max property.
117      * @param max maximum allowed number of return statements.
118      */
119     public void setMax(int max) {
120         this.max = max;
121     }
122 
123     /**
124      * Setter for maxForVoid property.
125      * @param maxForVoid maximum allowed number of return statements for void methods.
126      */
127     public void setMaxForVoid(int maxForVoid) {
128         this.maxForVoid = maxForVoid;
129     }
130 
131     @Override
132     public void beginTree(DetailAST rootAST) {
133         context = new Context(false);
134         contextStack.clear();
135     }
136 
137     @Override
138     public void visitToken(DetailAST ast) {
139         switch (ast.getType()) {
140             case TokenTypes.CTOR_DEF:
141             case TokenTypes.METHOD_DEF:
142                 visitMethodDef(ast);
143                 break;
144             case TokenTypes.LAMBDA:
145                 visitLambda();
146                 break;
147             case TokenTypes.LITERAL_RETURN:
148                 visitReturn(ast);
149                 break;
150             default:
151                 throw new IllegalStateException(ast.toString());
152         }
153     }
154 
155     @Override
156     public void leaveToken(DetailAST ast) {
157         switch (ast.getType()) {
158             case TokenTypes.CTOR_DEF:
159             case TokenTypes.METHOD_DEF:
160             case TokenTypes.LAMBDA:
161                 leave(ast);
162                 break;
163             case TokenTypes.LITERAL_RETURN:
164                 // Do nothing
165                 break;
166             default:
167                 throw new IllegalStateException(ast.toString());
168         }
169     }
170 
171     /**
172      * Creates new method context and places old one on the stack.
173      * @param ast method definition for check.
174      */
175     private void visitMethodDef(DetailAST ast) {
176         contextStack.push(context);
177         final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT);
178         final boolean check = !format.matcher(methodNameAST.getText()).find();
179         context = new Context(check);
180     }
181 
182     /**
183      * Checks number of return statements and restore previous context.
184      * @param ast node to leave.
185      */
186     private void leave(DetailAST ast) {
187         context.checkCount(ast);
188         context = contextStack.pop();
189     }
190 
191     /**
192      * Creates new lambda context and places old one on the stack.
193      */
194     private void visitLambda() {
195         contextStack.push(context);
196         context = new Context(true);
197     }
198 
199     /**
200      * Examines the return statement and tells context about it.
201      * @param ast return statement to check.
202      */
203     private void visitReturn(DetailAST ast) {
204         // we can't identify which max to use for lambdas, so we can only assign
205         // after the first return statement is seen
206         if (ast.getFirstChild().getType() == TokenTypes.SEMI) {
207             context.visitLiteralReturn(maxForVoid, true);
208         }
209         else {
210             context.visitLiteralReturn(max, false);
211         }
212     }
213 
214     /**
215      * Class to encapsulate information about one method.
216      * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
217      */
218     private class Context {
219         /** Whether we should check this method or not. */
220         private final boolean checking;
221         /** Counter for return statements. */
222         private int count;
223         /** Maximum allowed number of return statements. */
224         private Integer maxAllowed;
225         /** Identifies if context is void. */
226         private boolean isVoidContext;
227 
228         /**
229          * Creates new method context.
230          * @param checking should we check this method or not.
231          */
232         Context(boolean checking) {
233             this.checking = checking;
234         }
235 
236         /**
237          * Increase the number of return statements and set context return type.
238          * @param maxAssigned Maximum allowed number of return statements.
239          * @param voidReturn Identifies if context is void.
240          */
241         public void visitLiteralReturn(int maxAssigned, Boolean voidReturn) {
242             isVoidContext = voidReturn;
243             if (maxAllowed == null) {
244                 maxAllowed = maxAssigned;
245             }
246 
247             ++count;
248         }
249 
250         /**
251          * Checks if number of return statements in the method are more
252          * than allowed.
253          * @param ast method def associated with this context.
254          */
255         public void checkCount(DetailAST ast) {
256             if (checking && maxAllowed != null && count > maxAllowed) {
257                 if (isVoidContext) {
258                     log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY_VOID, count, maxAllowed);
259                 }
260                 else {
261                     log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY, count, maxAllowed);
262                 }
263             }
264         }
265     }
266 }