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 @@
+ 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. +
+| name | +description | +type | +default value | +
|---|---|---|---|
| excludedClasses | +pattern for qualified names of classes which ar allowed + to have main method. | +regular expression | +^$ | +
+ 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> ++
+ com.puppycrawl.tools.checkstyle.checks + +
++ TreeWalker + +
+
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 @@
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);
+ }
+}