diff --git a/docs/config_misc.html b/docs/config_misc.html index b37408819..e15a31017 100644 --- a/docs/config_misc.html +++ b/docs/config_misc.html @@ -34,6 +34,9 @@
  • Translation
  • +
  • + UncommentedMain +
  • UpperEll
  • @@ -283,6 +286,57 @@

    Checker

    +

    UncommentedMain

    Description

    +

    + Checks for uncommented main() methods (debugging leftovers). +

    +

    Rationale: main() method can be often used for + debug puposes. Thus most of main() method should be + removed/commented out of the sources. +

    +

    Properties

    + + + + + + + + + + + + + +
    namedescriptiontypedefault value
    excludedClassespattern for qualified names of classes which ar allowed + to have main method.regular expression^$
    +

    Examples

    +

    + To configure the check: +

    +
    +<module name="UncommentedMain"/>
    +      
    +

    + To configure the check to allow main method for all classes with + "Main" name: +

    +
    +<module name="UncommentedMain">
    +    <property name="excludedClasses" value="\.Main$"/>
    +</module>
    +      
    +

    Package

    +

    + com.puppycrawl.tools.checkstyle.checks + +

    +

    Parent Module

    +

    + TreeWalker + +

    +

    UpperEll

    Description

    Checks that long constants are defined with an upper ell. That is ' diff --git a/docs/releasenotes.html b/docs/releasenotes.html index e126d022f..8ca4fc3aa 100644 --- a/docs/releasenotes.html +++ b/docs/releasenotes.html @@ -65,6 +65,10 @@

  • MagicNumberCheck now checks array initializers. (request 745949)
  • +
  • Added check for uncommented main + methods (debugging leftovers). (module UncommentedMain, + request 732257)
  • +

    diff --git a/src/checkstyle/com/puppycrawl/tools/checkstyle/checks/UncommentedMainCheck.java b/src/checkstyle/com/puppycrawl/tools/checkstyle/checks/UncommentedMainCheck.java new file mode 100644 index 000000000..b7cb038ce --- /dev/null +++ b/src/checkstyle/com/puppycrawl/tools/checkstyle/checks/UncommentedMainCheck.java @@ -0,0 +1,253 @@ +//////////////////////////////////////////////////////////////////////////////// +// checkstyle: Checks Java source code for adherence to a set of rules. +// Copyright (C) 2001-2002 Oliver Burn +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//////////////////////////////////////////////////////////////////////////////// +package com.puppycrawl.tools.checkstyle.checks; + +import com.puppycrawl.tools.checkstyle.api.Check; +import com.puppycrawl.tools.checkstyle.api.DetailAST; +import com.puppycrawl.tools.checkstyle.api.FullIdent; +import com.puppycrawl.tools.checkstyle.api.TokenTypes; +import com.puppycrawl.tools.checkstyle.api.Utils; + +import org.apache.commons.beanutils.ConversionException; +import org.apache.regexp.RE; +import org.apache.regexp.RESyntaxException; + +/** + * Detects uncommented main methods. Basically detects + * any main method, since if it is detectable + * that means it is uncommented. + * + *

    + * <module name="UncommentedMain"/>
    + * 
    + * + * @author Michael Yui + * @author o_sukhodolsky + */ +public class UncommentedMainCheck + extends Check +{ + /** the pattern to exclude classes from the check */ + private String mExcludedClasses = "^$"; + /** compiled regexp to exclude classes from check */ + private RE mExcludedClassesRE = Utils.createRE(mExcludedClasses); + /** current class name */ + private String mCurrentClass; + /** current package */ + private FullIdent mPackage; + /** class definition depth */ + private int mClassDepth; + + /** + * Set the excluded classes pattern. + * @param aExcludedClasses a String value + * @throws ConversionException unable to parse aExcludedClasses + */ + public void setExcludedClasses(String aExcludedClasses) + throws ConversionException + { + try { + mExcludedClasses = aExcludedClasses; + mExcludedClassesRE = Utils.getRE(mExcludedClasses); + } + catch (RESyntaxException e) { + throw new ConversionException("unable to parse " + + mExcludedClasses, + e); + } + } + + /** @see Check */ + public int[] getDefaultTokens() + { + return new int[] { + TokenTypes.METHOD_DEF, + TokenTypes.CLASS_DEF, + TokenTypes.PACKAGE_DEF, + }; + } + + /** @see Check */ + public int[] getRequiredTokens() + { + return getDefaultTokens(); + } + + /** @see Check */ + public void beginTree(DetailAST aRootAST) + { + mPackage = FullIdent.createFullIdent(null); + mCurrentClass = null; + mClassDepth = 0; + } + + /** @see Check */ + public void leaveToken(DetailAST aAst) + { + if (aAst.getType() == TokenTypes.CLASS_DEF) { + if (mClassDepth == 1) { + mCurrentClass = null; + } + mClassDepth--; + } + } + + /** @see Check */ + public void visitToken(DetailAST aAst) + { + switch (aAst.getType()) { + case TokenTypes.PACKAGE_DEF: + visitPackageDef(aAst); + break; + case TokenTypes.CLASS_DEF: + visitClassDef(aAst); + break; + case TokenTypes.METHOD_DEF: + visitMethodDef(aAst); + break; + default: + throw new IllegalStateException(aAst.toString()); + } + } + + /** + * Sets current package. + * @param aPackage node for package definition + */ + private void visitPackageDef(DetailAST aPackage) + { + mPackage = + FullIdent.createFullIdent((DetailAST) aPackage.getFirstChild()); + } + + /** + * If not inner class then change current class name. + * @param aClass node for class definition + */ + private void visitClassDef(DetailAST aClass) + { + // we are not use inner classes because they can not + // have static methods + if (mClassDepth == 0) { + DetailAST ident = aClass.findFirstToken(TokenTypes.IDENT); + mCurrentClass = mPackage.getText() + "." + ident.getText(); + mClassDepth++; + } + return; + } + + /** + * Checks method definition if this is + * public static void main(String[]). + * @param aMethod method definition node + */ + private void visitMethodDef(DetailAST aMethod) + { + if (mClassDepth != 1) { + // method in inner class or in interface definition + return; + } + + if (checkClassName() + && checkName(aMethod) + && checkModifiers(aMethod) + && checkType(aMethod) + && checkParams(aMethod)) + { + log(aMethod.getLineNo(), "uncommented.main"); + } + } + + /** + * Checks that current class is not excluded + * @return true if check passed, false otherwise + */ + private boolean checkClassName() + { + return !mExcludedClassesRE.match(mCurrentClass); + } + + /** + * Checks that method name is @quot;main@quot;. + * @param aMethod the METHOD_DEF node + * @return true if check passed, false otherwise + */ + private boolean checkName(DetailAST aMethod) + { + DetailAST ident = aMethod.findFirstToken(TokenTypes.IDENT); + return "main".equals(ident.getText()); + } + + /** + * Checks that method has final and static modifiers. + * @param aMethod the METHOD_DEF node + * @return true if check passed, false otherwise + */ + private boolean checkModifiers(DetailAST aMethod) + { + DetailAST modifiers = aMethod.findFirstToken(TokenTypes.MODIFIERS); + + return modifiers.branchContains(TokenTypes.LITERAL_PUBLIC) + && modifiers.branchContains(TokenTypes.LITERAL_STATIC); + } + + /** + * Checks that return type is void. + * @param aMethod the METHOD_DEF node + * @return true if check passed, false otherwise + */ + private boolean checkType(DetailAST aMethod) + { + DetailAST type = + (DetailAST) aMethod.findFirstToken(TokenTypes.TYPE).getFirstChild(); + return type.getType() == TokenTypes.LITERAL_VOID; + } + + /** + * Checks that method has only String[] param + * @param aMethod the METHOD_DEF node + * @return true if check passed, false otherwise + */ + private boolean checkParams(DetailAST aMethod) + { + DetailAST params = aMethod.findFirstToken(TokenTypes.PARAMETERS); + if (params.getChildCount() != 1) { + return false; + } + DetailAST paramType = ((DetailAST) params.getFirstChild()) + .findFirstToken(TokenTypes.TYPE); + DetailAST arrayDecl = + paramType.findFirstToken(TokenTypes.ARRAY_DECLARATOR); + if (arrayDecl == null) { + return false; + } + + DetailAST arrayType = (DetailAST) arrayDecl.getFirstChild(); + + if (arrayType.getType() == TokenTypes.IDENT + || arrayType.getType() == TokenTypes.DOT) + { + FullIdent type = FullIdent.createFullIdent(arrayType); + return ("String".equals(type.getText()) + || "java.lang.String".equals(type.getText())); + } + + return false; + } +} diff --git a/src/checkstyle/com/puppycrawl/tools/checkstyle/checks/messages.properties b/src/checkstyle/com/puppycrawl/tools/checkstyle/checks/messages.properties index 70f9613c0..8d51a7921 100644 --- a/src/checkstyle/com/puppycrawl/tools/checkstyle/checks/messages.properties +++ b/src/checkstyle/com/puppycrawl/tools/checkstyle/checks/messages.properties @@ -97,3 +97,5 @@ indentation.child.error={0} child at indentation level {1} not at correct indent interface.type=interfaces should describe a type and hence have methods. array.trailing.comma=Array should contain trailing comma. + +uncommented.main=Uncommented main method found. diff --git a/src/testinputs/com/puppycrawl/tools/checkstyle/InputUncommentedMain.java b/src/testinputs/com/puppycrawl/tools/checkstyle/InputUncommentedMain.java new file mode 100644 index 000000000..54f02f7de --- /dev/null +++ b/src/testinputs/com/puppycrawl/tools/checkstyle/InputUncommentedMain.java @@ -0,0 +1,91 @@ +//////////////////////////////////////////////////////////////////////////////// +// Test case file for checkstyle. +// Created: 2003 +//////////////////////////////////////////////////////////////////////////////// +package com.puppycrawl.tools.checkstyle; + +/** + * Test case for UncommentedMainCheck + * @author o_sukhodolsky + */ +public class InputUncommentedMain +{ + // uncommented main + public static void main(String[] args) + { + System.out.println("InputUncommentedMain.main()"); + } +} + +class Main +{ + // uncommented main in class Main + public static void main(String[] args) + { + System.out.println("Main.main()"); + } +} + +class test1 +{ + // one more uncommented main + public static void main(java.lang.String[] args) + { + System.out.println("test1.main()"); + } +} + +class test2 +{ + // wrong arg type + public static void main(int args) + { + System.out.println("test2.main()"); + } +} + +class test3 +{ + // no-public main + static void main(String[] args) + { + System.out.println("test3.main()"); + } +} + +class test4 +{ + // non-static main + public void main(String[] args) + { + System.out.println("test4.main()"); + } +} + +class test5 +{ + // wrong return type + public static int main(String[] args) + { + System.out.println("test5.main()"); + return 1; + } +} + +class test6 +{ + // too many params + public static void main(String[] args, int param) + { + System.out.println("test6.main()"); + } +} + +class test7 +{ + // main w/o params + public static void main() + { + System.out.println("test7.main()"); + } +} diff --git a/src/tests/com/puppycrawl/tools/checkstyle/AllTests.java b/src/tests/com/puppycrawl/tools/checkstyle/AllTests.java index 089b3172e..37532779e 100644 --- a/src/tests/com/puppycrawl/tools/checkstyle/AllTests.java +++ b/src/tests/com/puppycrawl/tools/checkstyle/AllTests.java @@ -56,6 +56,7 @@ import com.puppycrawl.tools.checkstyle.checks.TabCharacterCheckTest; import com.puppycrawl.tools.checkstyle.checks.TodoCommentCheckTest; import com.puppycrawl.tools.checkstyle.checks.TranslationCheckTest; import com.puppycrawl.tools.checkstyle.checks.TypeNameCheckTest; +import com.puppycrawl.tools.checkstyle.checks.UncommentedMainCheckTest; import com.puppycrawl.tools.checkstyle.checks.UnusedImportsCheckTest; import com.puppycrawl.tools.checkstyle.checks.UpperEllCheckTest; import com.puppycrawl.tools.checkstyle.checks.VisibilityModifierCheckTest; @@ -148,6 +149,7 @@ public class AllTests { suite.addTest(new TestSuite(TranslationCheckTest.class)); suite.addTest(new TestSuite(LeftCurlyCheckTest.class)); suite.addTest(new TestSuite(TypeNameCheckTest.class)); + suite.addTest(new TestSuite(UncommentedMainCheckTest.class)); suite.addTest(new TestSuite(UnusedImportsCheckTest.class)); suite.addTest(new TestSuite(UpperEllCheckTest.class)); suite.addTest(new TestSuite(UtilsTest.class)); diff --git a/src/tests/com/puppycrawl/tools/checkstyle/checks/UncommentedMainCheckTest.java b/src/tests/com/puppycrawl/tools/checkstyle/checks/UncommentedMainCheckTest.java new file mode 100644 index 000000000..63515e969 --- /dev/null +++ b/src/tests/com/puppycrawl/tools/checkstyle/checks/UncommentedMainCheckTest.java @@ -0,0 +1,34 @@ +package com.puppycrawl.tools.checkstyle.checks; + +import com.puppycrawl.tools.checkstyle.BaseCheckTestCase; +import com.puppycrawl.tools.checkstyle.DefaultConfiguration; + +public class UncommentedMainCheckTest + extends BaseCheckTestCase +{ + public void testDefaults() + throws Exception + { + final DefaultConfiguration checkConfig = + createCheckConfig(UncommentedMainCheck.class); + final String[] expected = { + "14: Uncommented main method found.", + "23: Uncommented main method found.", + "32: Uncommented main method found.", + }; + verify(checkConfig, getPath("InputUncommentedMain.java"), expected); + } + + public void testExcludedClasses() + throws Exception + { + final DefaultConfiguration checkConfig = + createCheckConfig(UncommentedMainCheck.class); + checkConfig.addAttribute("excludedClasses", "\\.Main$"); + final String[] expected = { + "14: Uncommented main method found.", + "32: Uncommented main method found.", + }; + verify(checkConfig, getPath("InputUncommentedMain.java"), expected); + } +}