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