//////////////////////////////////////////////////////////////////////////////// // 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.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 */ 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) { 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; } }