Coverage Report - com.puppycrawl.tools.checkstyle.checks.UncommentedMainCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
UncommentedMainCheck
100%
65/65
100%
38/38
2.062
 
 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;
 21  
 
 22  
 import java.util.Optional;
 23  
 import java.util.regex.Pattern;
 24  
 
 25  
 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
 26  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 27  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 28  
 import com.puppycrawl.tools.checkstyle.api.FullIdent;
 29  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 30  
 
 31  
 /**
 32  
  * Detects uncommented main methods. Basically detects
 33  
  * any main method, since if it is detectable
 34  
  * that means it is uncommented.
 35  
  *
 36  
  * <pre class="body">
 37  
  * &lt;module name=&quot;UncommentedMain&quot;/&gt;
 38  
  * </pre>
 39  
  *
 40  
  * @author Michael Yui
 41  
  * @author o_sukhodolsky
 42  
  */
 43  
 @FileStatefulCheck
 44  19
 public class UncommentedMainCheck
 45  
     extends AbstractCheck {
 46  
 
 47  
     /**
 48  
      * A key is pointing to the warning message text in "messages.properties"
 49  
      * file.
 50  
      */
 51  
     public static final String MSG_KEY = "uncommented.main";
 52  
 
 53  
     /** Compiled regexp to exclude classes from check. */
 54  19
     private Pattern excludedClasses = Pattern.compile("^$");
 55  
     /** Current class name. */
 56  
     private String currentClass;
 57  
     /** Current package. */
 58  
     private FullIdent packageName;
 59  
     /** Class definition depth. */
 60  
     private int classDepth;
 61  
 
 62  
     /**
 63  
      * Set the excluded classes pattern.
 64  
      * @param excludedClasses a pattern
 65  
      */
 66  
     public void setExcludedClasses(Pattern excludedClasses) {
 67  3
         this.excludedClasses = excludedClasses;
 68  3
     }
 69  
 
 70  
     @Override
 71  
     public int[] getAcceptableTokens() {
 72  6
         return getRequiredTokens();
 73  
     }
 74  
 
 75  
     @Override
 76  
     public int[] getDefaultTokens() {
 77  30
         return getRequiredTokens();
 78  
     }
 79  
 
 80  
     @Override
 81  
     public int[] getRequiredTokens() {
 82  66
         return new int[] {
 83  
             TokenTypes.METHOD_DEF,
 84  
             TokenTypes.CLASS_DEF,
 85  
             TokenTypes.PACKAGE_DEF,
 86  
         };
 87  
     }
 88  
 
 89  
     @Override
 90  
     public void beginTree(DetailAST rootAST) {
 91  10
         packageName = FullIdent.createFullIdent(null);
 92  10
         currentClass = null;
 93  10
         classDepth = 0;
 94  10
     }
 95  
 
 96  
     @Override
 97  
     public void leaveToken(DetailAST ast) {
 98  93
         if (ast.getType() == TokenTypes.CLASS_DEF) {
 99  39
             classDepth--;
 100  
         }
 101  93
     }
 102  
 
 103  
     @Override
 104  
     public void visitToken(DetailAST ast) {
 105  
 
 106  94
         switch (ast.getType()) {
 107  
             case TokenTypes.PACKAGE_DEF:
 108  10
                 visitPackageDef(ast);
 109  10
                 break;
 110  
             case TokenTypes.CLASS_DEF:
 111  39
                 visitClassDef(ast);
 112  39
                 break;
 113  
             case TokenTypes.METHOD_DEF:
 114  44
                 visitMethodDef(ast);
 115  44
                 break;
 116  
             default:
 117  1
                 throw new IllegalStateException(ast.toString());
 118  
         }
 119  93
     }
 120  
 
 121  
     /**
 122  
      * Sets current package.
 123  
      * @param packageDef node for package definition
 124  
      */
 125  
     private void visitPackageDef(DetailAST packageDef) {
 126  20
         packageName = FullIdent.createFullIdent(packageDef.getLastChild()
 127  10
                 .getPreviousSibling());
 128  10
     }
 129  
 
 130  
     /**
 131  
      * If not inner class then change current class name.
 132  
      * @param classDef node for class definition
 133  
      */
 134  
     private void visitClassDef(DetailAST classDef) {
 135  
         // we are not use inner classes because they can not
 136  
         // have static methods
 137  39
         if (classDepth == 0) {
 138  34
             final DetailAST ident = classDef.findFirstToken(TokenTypes.IDENT);
 139  34
             currentClass = packageName.getText() + "." + ident.getText();
 140  34
             classDepth++;
 141  
         }
 142  39
     }
 143  
 
 144  
     /**
 145  
      * Checks method definition if this is
 146  
      * {@code public static void main(String[])}.
 147  
      * @param method method definition node
 148  
      */
 149  
     private void visitMethodDef(DetailAST method) {
 150  44
         if (classDepth == 1
 151  
                 // method not in inner class or in interface definition
 152  42
                 && checkClassName()
 153  40
                 && checkName(method)
 154  33
                 && checkModifiers(method)
 155  26
                 && checkType(method)
 156  24
                 && checkParams(method)) {
 157  17
             log(method.getLineNo(), MSG_KEY);
 158  
         }
 159  44
     }
 160  
 
 161  
     /**
 162  
      * Checks that current class is not excluded.
 163  
      * @return true if check passed, false otherwise
 164  
      */
 165  
     private boolean checkClassName() {
 166  42
         return !excludedClasses.matcher(currentClass).find();
 167  
     }
 168  
 
 169  
     /**
 170  
      * Checks that method name is @quot;main@quot;.
 171  
      * @param method the METHOD_DEF node
 172  
      * @return true if check passed, false otherwise
 173  
      */
 174  
     private static boolean checkName(DetailAST method) {
 175  40
         final DetailAST ident = method.findFirstToken(TokenTypes.IDENT);
 176  40
         return "main".equals(ident.getText());
 177  
     }
 178  
 
 179  
     /**
 180  
      * Checks that method has final and static modifiers.
 181  
      * @param method the METHOD_DEF node
 182  
      * @return true if check passed, false otherwise
 183  
      */
 184  
     private static boolean checkModifiers(DetailAST method) {
 185  33
         final DetailAST modifiers =
 186  33
             method.findFirstToken(TokenTypes.MODIFIERS);
 187  
 
 188  66
         return modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null
 189  31
             && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
 190  
     }
 191  
 
 192  
     /**
 193  
      * Checks that return type is {@code void}.
 194  
      * @param method the METHOD_DEF node
 195  
      * @return true if check passed, false otherwise
 196  
      */
 197  
     private static boolean checkType(DetailAST method) {
 198  26
         final DetailAST type =
 199  26
             method.findFirstToken(TokenTypes.TYPE).getFirstChild();
 200  26
         return type.getType() == TokenTypes.LITERAL_VOID;
 201  
     }
 202  
 
 203  
     /**
 204  
      * Checks that method has only {@code String[]} or only {@code String...} param.
 205  
      * @param method the METHOD_DEF node
 206  
      * @return true if check passed, false otherwise
 207  
      */
 208  
     private static boolean checkParams(DetailAST method) {
 209  24
         boolean checkPassed = false;
 210  24
         final DetailAST params = method.findFirstToken(TokenTypes.PARAMETERS);
 211  
 
 212  24
         if (params.getChildCount() == 1) {
 213  20
             final DetailAST parameterType = params.getFirstChild().findFirstToken(TokenTypes.TYPE);
 214  40
             final Optional<DetailAST> arrayDecl = Optional.ofNullable(
 215  20
                 parameterType.findFirstToken(TokenTypes.ARRAY_DECLARATOR));
 216  40
             final Optional<DetailAST> varargs = Optional.ofNullable(
 217  20
                 params.getFirstChild().findFirstToken(TokenTypes.ELLIPSIS));
 218  
 
 219  20
             if (arrayDecl.isPresent()) {
 220  16
                 checkPassed = isStringType(arrayDecl.get().getFirstChild());
 221  
             }
 222  4
             else if (varargs.isPresent()) {
 223  2
                 checkPassed = isStringType(parameterType.getFirstChild());
 224  
             }
 225  
         }
 226  24
         return checkPassed;
 227  
     }
 228  
 
 229  
     /**
 230  
      * Whether the type is java.lang.String.
 231  
      * @param typeAst the type to check.
 232  
      * @return true, if the type is java.lang.String.
 233  
      */
 234  
     private static boolean isStringType(DetailAST typeAst) {
 235  18
         final FullIdent type = FullIdent.createFullIdent(typeAst);
 236  36
         return "String".equals(type.getText())
 237  3
             || "java.lang.String".equals(type.getText());
 238  
     }
 239  
 }