Coverage Report - com.puppycrawl.tools.checkstyle.checks.coding.ReturnCountCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
ReturnCountCheck
100%
47/47
100%
11/11
2
ReturnCountCheck$Context
100%
13/13
100%
10/10
2
 
 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  22
 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  22
     private final Deque<Context> contextStack = new ArrayDeque<>();
 71  
 
 72  
     /** The regexp to match against. */
 73  22
     private Pattern format = Pattern.compile("^equals$");
 74  
 
 75  
     /** Maximum allowed number of return statements. */
 76  22
     private int max = 2;
 77  
     /** Maximum allowed number of return statements for void methods. */
 78  22
     private int maxForVoid = 1;
 79  
     /** Current method context. */
 80  
     private Context context;
 81  
 
 82  
     @Override
 83  
     public int[] getDefaultTokens() {
 84  29
         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  42
         return new int[] {TokenTypes.LITERAL_RETURN};
 95  
     }
 96  
 
 97  
     @Override
 98  
     public int[] getAcceptableTokens() {
 99  11
         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  2
         format = pattern;
 113  2
     }
 114  
 
 115  
     /**
 116  
      * Setter for max property.
 117  
      * @param max maximum allowed number of return statements.
 118  
      */
 119  
     public void setMax(int max) {
 120  8
         this.max = max;
 121  8
     }
 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  3
         this.maxForVoid = maxForVoid;
 129  3
     }
 130  
 
 131  
     @Override
 132  
     public void beginTree(DetailAST rootAST) {
 133  10
         context = new Context(false);
 134  10
         contextStack.clear();
 135  10
     }
 136  
 
 137  
     @Override
 138  
     public void visitToken(DetailAST ast) {
 139  193
         switch (ast.getType()) {
 140  
             case TokenTypes.CTOR_DEF:
 141  
             case TokenTypes.METHOD_DEF:
 142  29
                 visitMethodDef(ast);
 143  29
                 break;
 144  
             case TokenTypes.LAMBDA:
 145  18
                 visitLambda();
 146  18
                 break;
 147  
             case TokenTypes.LITERAL_RETURN:
 148  145
                 visitReturn(ast);
 149  145
                 break;
 150  
             default:
 151  1
                 throw new IllegalStateException(ast.toString());
 152  
         }
 153  192
     }
 154  
 
 155  
     @Override
 156  
     public void leaveToken(DetailAST ast) {
 157  192
         switch (ast.getType()) {
 158  
             case TokenTypes.CTOR_DEF:
 159  
             case TokenTypes.METHOD_DEF:
 160  
             case TokenTypes.LAMBDA:
 161  46
                 leave(ast);
 162  46
                 break;
 163  
             case TokenTypes.LITERAL_RETURN:
 164  
                 // Do nothing
 165  145
                 break;
 166  
             default:
 167  1
                 throw new IllegalStateException(ast.toString());
 168  
         }
 169  191
     }
 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  29
         contextStack.push(context);
 177  29
         final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT);
 178  29
         final boolean check = !format.matcher(methodNameAST.getText()).find();
 179  29
         context = new Context(check);
 180  29
     }
 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  46
         context.checkCount(ast);
 188  46
         context = contextStack.pop();
 189  46
     }
 190  
 
 191  
     /**
 192  
      * Creates new lambda context and places old one on the stack.
 193  
      */
 194  
     private void visitLambda() {
 195  18
         contextStack.push(context);
 196  18
         context = new Context(true);
 197  18
     }
 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  145
         if (ast.getFirstChild().getType() == TokenTypes.SEMI) {
 207  40
             context.visitLiteralReturn(maxForVoid, true);
 208  
         }
 209  
         else {
 210  105
             context.visitLiteralReturn(max, false);
 211  
         }
 212  145
     }
 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  57
         Context(boolean checking) {
 233  57
             this.checking = checking;
 234  57
         }
 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  145
             isVoidContext = voidReturn;
 243  145
             if (maxAllowed == null) {
 244  45
                 maxAllowed = maxAssigned;
 245  
             }
 246  
 
 247  145
             ++count;
 248  145
         }
 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  46
             if (checking && maxAllowed != null && count > maxAllowed) {
 257  24
                 if (isVoidContext) {
 258  10
                     log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY_VOID, count, maxAllowed);
 259  
                 }
 260  
                 else {
 261  14
                     log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY, count, maxAllowed);
 262  
                 }
 263  
             }
 264  46
         }
 265  
     }
 266  
 }