From 8acc405dd30502bbbcdaf60a75ddd60dfa2b5a50 Mon Sep 17 00:00:00 2001 From: Rick Giles Date: Sun, 20 Jul 2003 13:57:58 +0000 Subject: [PATCH] Tim Tyler's check for descendant token counts --- .../checks/coding/DescendantTokenCheck.java | 367 ++++++++++++++++++ .../coding/DescendantTokenCheckTest.java | 87 +++++ .../checks/coding/messages.properties | 2 + 3 files changed, 456 insertions(+) create mode 100644 contrib/examples/checks/com/puppycrawl/tools/checkstyle/checks/coding/DescendantTokenCheck.java create mode 100644 contrib/examples/checks/com/puppycrawl/tools/checkstyle/checks/coding/DescendantTokenCheckTest.java create mode 100644 contrib/examples/checks/com/puppycrawl/tools/checkstyle/checks/coding/messages.properties diff --git a/contrib/examples/checks/com/puppycrawl/tools/checkstyle/checks/coding/DescendantTokenCheck.java b/contrib/examples/checks/com/puppycrawl/tools/checkstyle/checks/coding/DescendantTokenCheck.java new file mode 100644 index 000000000..3b8e3f193 --- /dev/null +++ b/contrib/examples/checks/com/puppycrawl/tools/checkstyle/checks/coding/DescendantTokenCheck.java @@ -0,0 +1,367 @@ +//////////////////////////////////////////////////////////////////////////////// +// checkstyle: Checks Java source code for adherence to a set of rules. +// Copyright (C) 2001-2003 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.coding; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.Set; + +import antlr.collections.AST; + +import com.puppycrawl.tools.checkstyle.api.Check; +import com.puppycrawl.tools.checkstyle.api.DetailAST; +import com.puppycrawl.tools.checkstyle.api.TokenTypes; + +/** + *

+ * Checks for restricted tokens beneath other tokens. + *

+ * Examples of how to configure the check: + *

+ *
+ * <!-- String literal equality check -->
+ * <module name="DescendantToken">
+ *     <property name="tokens" value="EQUAL,NOT_EQUAL"/>
+ *     <property name="limitedTokens" value="STRING_LITERAL"/>
+ *     <property name="maximumNumber" value="0"/>
+ * </module>
+ *
+ * <!-- Switch with no default -->
+ * <module name="DescendantToken">
+ *     <property name="tokens" value="LITERAL_SWITCH"/>
+ *     <property name="maximumDepth" value="2"/>
+ *     <property name="limitedTokens" value="LITERAL_DEFAULT"/>
+ *     <property name="minimumNumber" value="1"/>
+ * </module>
+ *
+ * <!-- Assert statement may have side effects -->
+ * <module name="DescendantToken">
+ *     <property name="tokens" value="LITERAL_ASSERT"/>
+ *     <property name="limitedTokens" value="ASSIGN,DEC,INC,POST_DEC,POST_INC,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,DIV_ASSIGN,MOD_ASSIGN,BSR_ASSIGN,SR_ASSIGN,SL_ASSIGN,BAND_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN,METHOD_CALL"/>
+ *     <property name="maximumNumber" value="0"/>
+ * </module>
+ *
+ * <!-- Initialiser in for performs no setup - use while instead? -->
+ * <module name="DescendantToken">
+ *     <property name="tokens" value="FOR_INIT"/>
+ *     <property name="limitedTokens" value="EXPR"/>
+ *     <property name="minimumNumber" value="1"/>
+ * </module>
+ *
+ * <!-- Condition in for performs no check -->
+ * <module name="DescendantToken">
+ *     <property name="tokens" value="FOR_CONDITION"/>
+ *     <property name="limitedTokens" value="EXPR"/>
+ *     <property name="minimumNumber" value="1"/>
+ * </module>
+ *
+ * <!-- Switch within switch -->
+ * <module name="DescendantToken">
+ *     <property name="tokens" value="LITERAL_SWITCH"/>
+ *     <property name="limitedTokens" value="LITERAL_SWITCH"/>
+ *     <property name="maximumNumber" value="0"/>
+ *     <property name="minimumDepth" value="1"/>
+ * </module>
+ *
+ * <!-- Return from within a catch or finally block -->
+ * <module name="DescendantToken">
+ *     <property name="tokens" value="LITERAL_FINALLY,LITERAL_CATCH"/>
+ *     <property name="limitedTokens" value="LITERAL_RETURN"/>
+ *     <property name="maximumNumber" value="0"/>
+ * </module>
+ *
+ * <!-- Try within catch or finally block -->
+ * <module name="DescendantToken">
+ *     <property name="tokens" value="LITERAL_CATCH,LITERAL_FINALLY"/>
+ *     <property name="limitedTokens" value="LITERAL_TRY"/>
+ *     <property name="maximumNumber" value="0"/>
+ * </module>
+ *
+ * <!-- Too many cases within a switch -->
+ * <module name="DescendantToken">
+ *     <property name="tokens" value="LITERAL_SWITCH"/>
+ *     <property name="limitedTokens" value="LITERAL_CASE"/>
+ *     <property name="maximumDepth" value="2"/>
+ *     <property name="maximumNumber" value="10"/>
+ * </module>
+ *
+ * <!-- Too many local variables within a method -->
+ * <module name="DescendantToken">
+ *     <property name="tokens" value="METHOD_DEF"/>
+ *     <property name="limitedTokens" value="VARIABLE_DEF"/>
+ *     <property name="maximumDepth" value="2"/>
+ *     <property name="maximumNumber" value="10"/>
+ * </module>
+ *
+ * <!-- Too many returns from within a method -->
+ * <module name="DescendantToken">
+ *     <property name="tokens" value="METHOD_DEF"/>
+ *     <property name="limitedTokens" value="LITERAL_RETURN"/>
+ *     <property name="maximumNumber" value="3"/>
+ * </module>
+ *
+ * <!-- Too many fields within an interface -->
+ * <module name="DescendantToken">
+ *     <property name="tokens" value="INTERFACE_DEF"/>
+ *     <property name="limitedTokens" value="VARIABLE_DEF"/>
+ *     <property name="maximumDepth" value="2"/>
+ *     <property name="maximumNumber" value="0"/>
+ * </module>
+ *
+ * <!-- Limit the number of exceptions a method can throw -->
+ * <module name="DescendantToken">
+ *     <property name="tokens" value="LITERAL_THROWS"/>
+ *     <property name="limitedTokens" value="IDENT"/>
+ *     <property name="maximumNumber" value="1"/>
+ * </module>
+ *
+ * <!-- Limit the number of expressions in a method -->
+ * <module name="DescendantToken">
+ *     <property name="tokens" value="METHOD_DEF"/>
+ *     <property name="limitedTokens" value="EXPR"/>
+ *     <property name="maximumNumber" value="200"/>
+ * </module>
+ *
+ * <!-- Disallow empty statements -->
+ * <module name="DescendantToken">
+ *     <property name="tokens" value="EMPTY_STAT"/>
+ *     <property name="limitedTokens" value="EMPTY_STAT"/>
+ *     <property name="maximumNumber" value="0"/>
+ *     <property name="maximumDepth" value="0"/>
+ *     <property name="maximumMessage" value="Empty statement is not allowed."/>
+ * </module>
+ *
+ * <!-- Too many fields within a class -->
+ * <module name="DescendantToken">
+ *     <property name="tokens" value="CLASS_DEF"/>
+ *     <property name="limitedTokens" value="VARIABLE_DEF"/>
+ *     <property name="maximumDepth" value="2"/>
+ *     <property name="maximumNumber" value="10"/>
+ * </module>
+ * 
+ *

+ * @author Tim Tyler <tim@tt1.org> + * @author Rick Giles + */ +public class DescendantTokenCheck extends Check +{ + /** minimum depth */ + private int mMinimumDepth = 0; + + /** maximum depth */ + private int mMaximumDepth = Integer.MAX_VALUE; + + /** minimum number */ + private int mMinimumNumber = 0; + + /** maximum number */ + private int mMaximumNumber = Integer.MAX_VALUE; + + /** limited tokens */ + private int[] mLimitedTokens = new int[0]; + + /** error message when minimum count not reached */ + private String mMinimumMessage = "descendant.token.min"; + + /** error message when maximum count exceeded */ + private String mMaximumMessage = "descendant.token.max"; + + /** + * Counts of descendant tokens. + * Indexed by (token ID - 1) for performance. + */ + private int[] mCounts = new int[0]; + + /** @see com.puppycrawl.tools.checkstyle.api.Check#getDefaultTokens() */ + public int[] getDefaultTokens() + { + return new int[0]; + } + + /** @see com.puppycrawl.tools.checkstyle.api.Check */ + public void visitToken(DetailAST aAST) + { + //reset counts + Arrays.fill(mCounts, 0); + + countTokens(aAST, 0); + + // name of this token + final String name = TokenTypes.getTokenName(aAST.getType()); + + for (int i = 0; i < mLimitedTokens.length; i++) { + final int tokenCount = mCounts[mLimitedTokens[i] - 1]; + if (tokenCount < mMinimumNumber) { + final String descendantName = + TokenTypes.getTokenName(mLimitedTokens[i]); + log( + aAST.getLineNo(), + aAST.getColumnNo(), + mMinimumMessage, + new String[] { + "" + tokenCount, + "" + mMinimumNumber, + name, + descendantName, + }); + } + if (tokenCount > mMaximumNumber) { + final String descendantName = + TokenTypes.getTokenName(mLimitedTokens[i]); + log( + aAST.getLineNo(), + aAST.getColumnNo(), + mMaximumMessage, + new String[] { + "" + tokenCount, + "" + mMaximumNumber, + name, + descendantName, + }); + } + } + } + + /** + * Counts the number of occurrences of descendant tokens. + * @param aAST the root token for descendants. + * @param aDepth the maximum depth of the counted descendants. + */ + private void countTokens(AST aAST, int aDepth) + { + if (aDepth <= mMaximumDepth) { + //update count + if (aDepth >= mMinimumDepth) { + final int type = aAST.getType(); + if (type <= mCounts.length) { + mCounts[type - 1]++; + } + } + AST child = aAST.getFirstChild(); + final int nextDepth = aDepth + 1; + while (child != null) { + countTokens(child, nextDepth); + child = child.getNextSibling(); + } + } + } + + /** @see com.puppycrawl.tools.checkstyle.api.Check */ + public int[] getAcceptableTokens() + { + // Any tokens set by property 'tokens' are acceptable + final Set tokenNames = getTokenNames(); + final int[] result = new int[tokenNames.size()]; + int i = 0; + final Iterator it = tokenNames.iterator(); + while (it.hasNext()) { + final String name = (String) it.next(); + result[i] = TokenTypes.getTokenId(name); + i++; + } + return result; + } + + /** + * Sets the tokens to ignore in the check. + * @param aLimitedTokens - list of tokens to ignore. + */ + public void setLimitedTokens(String[] aLimitedTokens) + { + mLimitedTokens = new int[aLimitedTokens.length]; + + int maxToken = 0; + for (int i = 0; i < aLimitedTokens.length; i++) { + mLimitedTokens[i] = TokenTypes.getTokenId(aLimitedTokens[i]); + if (mLimitedTokens[i] > maxToken) { + maxToken = mLimitedTokens[i]; + } + } + mCounts = new int[maxToken]; + } + + /** + * Sets the mimimum depth for descendant counts. + * @param aMinimumDepth the mimimum depth for descendant counts. + */ + public void setMinimumDepth(int aMinimumDepth) + { + mMinimumDepth = aMinimumDepth; + } + + /** + * Sets the maximum depth for descendant counts. + * @param aMaximumDepth the maximum depth for descendant counts. + */ + public void setMaximumDepth(int aMaximumDepth) + { + mMaximumDepth = aMaximumDepth; + } + + /** + * Sets a minimum count for descendants. + * @param aMinimumNumber the minimum count for descendants. + */ + public void setMinimumNumber(int aMinimumNumber) + { + mMinimumNumber = aMinimumNumber; + } + + /** + * Sets a maximum count for descendants. + * @param aMaximumNumber the maximum count for descendants. + */ + public void setMaximumNumber(int aMaximumNumber) + { + mMaximumNumber = aMaximumNumber; + } + + /** + * Sets the error message for minimum count not reached. + * @param aMessage the error message for minimum count not reached. + * Used as a MessageFormat pattern with arguments + *

+ */ + public void setMinimumMessage(String aMessage) + { + mMinimumMessage = aMessage; + } + + /** + * Sets the error message for maximum count exceeded. + * @param aMessage the error message for maximum count exceeded. + * Used as a MessageFormat pattern with arguments + * + */ + + public void setMaximumMessage(String aMessage) + { + mMaximumMessage = aMessage; + } +} diff --git a/contrib/examples/checks/com/puppycrawl/tools/checkstyle/checks/coding/DescendantTokenCheckTest.java b/contrib/examples/checks/com/puppycrawl/tools/checkstyle/checks/coding/DescendantTokenCheckTest.java new file mode 100644 index 000000000..0ca400a2b --- /dev/null +++ b/contrib/examples/checks/com/puppycrawl/tools/checkstyle/checks/coding/DescendantTokenCheckTest.java @@ -0,0 +1,87 @@ +package com.puppycrawl.tools.checkstyle.checks.coding; + +import com.puppycrawl.tools.checkstyle.BaseCheckTestCase; +import com.puppycrawl.tools.checkstyle.DefaultConfiguration; + +public class DescendantTokenCheckTest + extends BaseCheckTestCase +{ + public void testDefault() + throws Exception + { + final DefaultConfiguration checkConfig = + createCheckConfig(DescendantTokenCheck.class); + final String[] expected = {}; + verify(checkConfig, getPath("InputIllegalTokens.java"), expected); + } + + public void testMaximumNumber() + throws Exception + { + final DefaultConfiguration checkConfig = + createCheckConfig(DescendantTokenCheck.class); + checkConfig.addAttribute("tokens", "LITERAL_NATIVE"); + checkConfig.addAttribute("limitedTokens", "LITERAL_NATIVE"); + checkConfig.addAttribute("maximumNumber", "0"); + final String[] expected = { + "20:12: Count of 1 for 'LITERAL_NATIVE' descendant 'LITERAL_NATIVE' exceeds maximum count 0.", + }; + verify(checkConfig, getPath("InputIllegalTokens.java"), expected); + } + + public void testMessage() + throws Exception + { + final DefaultConfiguration checkConfig = + createCheckConfig(DescendantTokenCheck.class); + checkConfig.addAttribute("tokens", "LITERAL_NATIVE"); + checkConfig.addAttribute("limitedTokens", "LITERAL_NATIVE"); + checkConfig.addAttribute("maximumNumber", "0"); + checkConfig.addAttribute("maximumMessage", "Using ''native'' is not allowed."); + final String[] expected = { + "20:12: Using 'native' is not allowed.", + }; + verify(checkConfig, getPath("InputIllegalTokens.java"), expected); + } + + public void testMinimumNumber() + throws Exception + { + final DefaultConfiguration checkConfig = + createCheckConfig(DescendantTokenCheck.class); + checkConfig.addAttribute("tokens", "LITERAL_SWITCH"); + checkConfig.addAttribute("limitedTokens", "LITERAL_DEFAULT"); + checkConfig.addAttribute("minimumNumber", "2"); + final String[] expected = { + "11:9: Count of 1 for 'LITERAL_SWITCH' descendant 'LITERAL_DEFAULT' is less than minimum count 2.", + }; + verify(checkConfig, getPath("InputIllegalTokens.java"), expected); + } + + public void testMinimumDepth() + throws Exception + { + final DefaultConfiguration checkConfig = + createCheckConfig(DescendantTokenCheck.class); + checkConfig.addAttribute("tokens", "LITERAL_SWITCH"); + checkConfig.addAttribute("limitedTokens", "LITERAL_DEFAULT"); + checkConfig.addAttribute("maximumNumber", "0"); + checkConfig.addAttribute("minimumDepth", "3"); + final String[] expected = {}; + verify(checkConfig, getPath("InputIllegalTokens.java"), expected); + } + + public void testMaximumDepth() + throws Exception + { + final DefaultConfiguration checkConfig = + createCheckConfig(DescendantTokenCheck.class); + checkConfig.addAttribute("tokens", "LITERAL_SWITCH"); + checkConfig.addAttribute("limitedTokens", "LITERAL_DEFAULT"); + checkConfig.addAttribute("maximumNumber", "0"); + checkConfig.addAttribute("maximumDepth", "1"); + final String[] expected = {}; + verify(checkConfig, getPath("InputIllegalTokens.java"), expected); + } +} + diff --git a/contrib/examples/checks/com/puppycrawl/tools/checkstyle/checks/coding/messages.properties b/contrib/examples/checks/com/puppycrawl/tools/checkstyle/checks/coding/messages.properties new file mode 100644 index 000000000..d46afb8a8 --- /dev/null +++ b/contrib/examples/checks/com/puppycrawl/tools/checkstyle/checks/coding/messages.properties @@ -0,0 +1,2 @@ +descendant.token.min=Count of {0} for ''{2}'' descendant ''{3}'' is less than minimum count {1}. +descendant.token.max=Count of {0} for ''{2}'' descendant ''{3}'' exceeds maximum count {1}. \ No newline at end of file