| //////////////////////////////////////////////////////////////////////////////// |
| // checkstyle: Checks Java source code for adherence to a set of rules. |
| // Copyright (C) 2001-2017 the original author or authors. |
| // |
| // 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 java.util.Arrays; |
| import java.util.Set; |
| |
| import antlr.collections.AST; |
| import com.puppycrawl.tools.checkstyle.FileStatefulCheck; |
| import com.puppycrawl.tools.checkstyle.api.AbstractCheck; |
| import com.puppycrawl.tools.checkstyle.api.DetailAST; |
| import com.puppycrawl.tools.checkstyle.utils.CommonUtils; |
| import com.puppycrawl.tools.checkstyle.utils.TokenUtils; |
| |
| /** |
| * <p> |
| * Checks for restricted tokens beneath other tokens. |
| * </p> |
| * <p> |
| * Examples of how to configure the check: |
| * </p> |
| * <pre> |
| * <!-- 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"/> |
| * <property name="maximumDepth" value="1"/> |
| * </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> |
| * |
| * <!-- Initializer 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> |
| * </pre> |
| * |
| * @author Tim Tyler <tim@tt1.org> |
| * @author Rick Giles |
| */ |
| @FileStatefulCheck |
| public class DescendantTokenCheck extends AbstractCheck { |
| |
| /** |
| * A key is pointing to the warning message text in "messages.properties" |
| * file. |
| */ |
| public static final String MSG_KEY_MIN = "descendant.token.min"; |
| |
| /** |
| * A key is pointing to the warning message text in "messages.properties" |
| * file. |
| */ |
| public static final String MSG_KEY_MAX = "descendant.token.max"; |
| |
| /** |
| * A key is pointing to the warning message text in "messages.properties" |
| * file. |
| */ |
| public static final String MSG_KEY_SUM_MIN = "descendant.token.sum.min"; |
| |
| /** |
| * A key is pointing to the warning message text in "messages.properties" |
| * file. |
| */ |
| public static final String MSG_KEY_SUM_MAX = "descendant.token.sum.max"; |
| |
| /** Minimum depth. */ |
| private int minimumDepth; |
| /** Maximum depth. */ |
| private int maximumDepth = Integer.MAX_VALUE; |
| /** Minimum number. */ |
| private int minimumNumber; |
| /** Maximum number. */ |
| private int maximumNumber = Integer.MAX_VALUE; |
| /** Whether to sum the number of tokens found. */ |
| private boolean sumTokenCounts; |
| /** Limited tokens. */ |
| private int[] limitedTokens = CommonUtils.EMPTY_INT_ARRAY; |
| /** Error message when minimum count not reached. */ |
| private String minimumMessage; |
| /** Error message when maximum count exceeded. */ |
| private String maximumMessage; |
| |
| /** |
| * Counts of descendant tokens. |
| * Indexed by (token ID - 1) for performance. |
| */ |
| private int[] counts = CommonUtils.EMPTY_INT_ARRAY; |
| |
| @Override |
| public int[] getDefaultTokens() { |
| return CommonUtils.EMPTY_INT_ARRAY; |
| } |
| |
| @Override |
| public int[] getRequiredTokens() { |
| return CommonUtils.EMPTY_INT_ARRAY; |
| } |
| |
| @Override |
| public void visitToken(DetailAST ast) { |
| //reset counts |
| Arrays.fill(counts, 0); |
| countTokens(ast, 0); |
| |
| if (sumTokenCounts) { |
| logAsTotal(ast); |
| } |
| else { |
| logAsSeparated(ast); |
| } |
| } |
| |
| /** |
| * Log violations for each Token. |
| * @param ast token |
| */ |
| private void logAsSeparated(DetailAST ast) { |
| // name of this token |
| final String name = TokenUtils.getTokenName(ast.getType()); |
| |
| for (int element : limitedTokens) { |
| final int tokenCount = counts[element - 1]; |
| if (tokenCount < minimumNumber) { |
| final String descendantName = TokenUtils.getTokenName(element); |
| |
| if (minimumMessage == null) { |
| minimumMessage = MSG_KEY_MIN; |
| } |
| log(ast.getLineNo(), ast.getColumnNo(), |
| minimumMessage, |
| String.valueOf(tokenCount), |
| String.valueOf(minimumNumber), |
| name, |
| descendantName); |
| } |
| if (tokenCount > maximumNumber) { |
| final String descendantName = TokenUtils.getTokenName(element); |
| |
| if (maximumMessage == null) { |
| maximumMessage = MSG_KEY_MAX; |
| } |
| log(ast.getLineNo(), ast.getColumnNo(), |
| maximumMessage, |
| String.valueOf(tokenCount), |
| String.valueOf(maximumNumber), |
| name, |
| descendantName); |
| } |
| } |
| } |
| |
| /** |
| * Log validation as one violation. |
| * @param ast current token |
| */ |
| private void logAsTotal(DetailAST ast) { |
| // name of this token |
| final String name = TokenUtils.getTokenName(ast.getType()); |
| |
| int total = 0; |
| for (int element : limitedTokens) { |
| total += counts[element - 1]; |
| } |
| if (total < minimumNumber) { |
| if (minimumMessage == null) { |
| minimumMessage = MSG_KEY_SUM_MIN; |
| } |
| log(ast.getLineNo(), ast.getColumnNo(), |
| minimumMessage, |
| String.valueOf(total), |
| String.valueOf(minimumNumber), name); |
| } |
| if (total > maximumNumber) { |
| if (maximumMessage == null) { |
| maximumMessage = MSG_KEY_SUM_MAX; |
| } |
| log(ast.getLineNo(), ast.getColumnNo(), |
| maximumMessage, |
| String.valueOf(total), |
| String.valueOf(maximumNumber), name); |
| } |
| } |
| |
| /** |
| * Counts the number of occurrences of descendant tokens. |
| * @param ast the root token for descendants. |
| * @param depth the maximum depth of the counted descendants. |
| */ |
| private void countTokens(AST ast, int depth) { |
| if (depth <= maximumDepth) { |
| //update count |
| if (depth >= minimumDepth) { |
| final int type = ast.getType(); |
| if (type <= counts.length) { |
| counts[type - 1]++; |
| } |
| } |
| AST child = ast.getFirstChild(); |
| final int nextDepth = depth + 1; |
| while (child != null) { |
| countTokens(child, nextDepth); |
| child = child.getNextSibling(); |
| } |
| } |
| } |
| |
| @Override |
| public int[] getAcceptableTokens() { |
| // Any tokens set by property 'tokens' are acceptable |
| final Set<String> tokenNames = getTokenNames(); |
| final int[] result = new int[tokenNames.size()]; |
| int index = 0; |
| for (String name : tokenNames) { |
| result[index] = TokenUtils.getTokenId(name); |
| index++; |
| } |
| return result; |
| } |
| |
| /** |
| * Sets the tokens which occurrence as descendant is limited. |
| * @param limitedTokensParam - list of tokens to ignore. |
| */ |
| public void setLimitedTokens(String... limitedTokensParam) { |
| limitedTokens = new int[limitedTokensParam.length]; |
| |
| int maxToken = 0; |
| for (int i = 0; i < limitedTokensParam.length; i++) { |
| limitedTokens[i] = TokenUtils.getTokenId(limitedTokensParam[i]); |
| if (limitedTokens[i] >= maxToken + 1) { |
| maxToken = limitedTokens[i]; |
| } |
| } |
| counts = new int[maxToken]; |
| } |
| |
| /** |
| * Sets the minimum depth for descendant counts. |
| * @param minimumDepth the minimum depth for descendant counts. |
| */ |
| public void setMinimumDepth(int minimumDepth) { |
| this.minimumDepth = minimumDepth; |
| } |
| |
| /** |
| * Sets the maximum depth for descendant counts. |
| * @param maximumDepth the maximum depth for descendant counts. |
| */ |
| public void setMaximumDepth(int maximumDepth) { |
| this.maximumDepth = maximumDepth; |
| } |
| |
| /** |
| * Sets a minimum count for descendants. |
| * @param minimumNumber the minimum count for descendants. |
| */ |
| public void setMinimumNumber(int minimumNumber) { |
| this.minimumNumber = minimumNumber; |
| } |
| |
| /** |
| * Sets a maximum count for descendants. |
| * @param maximumNumber the maximum count for descendants. |
| */ |
| public void setMaximumNumber(int maximumNumber) { |
| this.maximumNumber = maximumNumber; |
| } |
| |
| /** |
| * Sets the error message for minimum count not reached. |
| * @param message the error message for minimum count not reached. |
| * Used as a {@code MessageFormat} pattern with arguments |
| * <ul> |
| * <li>{0} - token count</li> |
| * <li>{1} - minimum number</li> |
| * <li>{2} - name of token</li> |
| * <li>{3} - name of limited token</li> |
| * </ul> |
| */ |
| public void setMinimumMessage(String message) { |
| minimumMessage = message; |
| } |
| |
| /** |
| * Sets the error message for maximum count exceeded. |
| * @param message the error message for maximum count exceeded. |
| * Used as a {@code MessageFormat} pattern with arguments |
| * <ul> |
| * <li>{0} - token count</li> |
| * <li>{1} - maximum number</li> |
| * <li>{2} - name of token</li> |
| * <li>{3} - name of limited token</li> |
| * </ul> |
| */ |
| |
| public void setMaximumMessage(String message) { |
| maximumMessage = message; |
| } |
| |
| /** |
| * Sets whether to use the sum of the tokens found, rather than the |
| * individual counts. |
| * @param sum whether to use the sum. |
| */ |
| public void setSumTokenCounts(boolean sum) { |
| sumTokenCounts = sum; |
| } |
| } |