com.puppycrawl.tools.checkstyle.checks.DescendantTokenCheck Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of checkstyle Show documentation
Show all versions of checkstyle Show documentation
Checkstyle is a development tool to help programmers write Java code
that adheres to a coding standard
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2016 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;
/**
*
* 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"/>
* <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>
*
*
* @author Tim Tyler <[email protected]>
* @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 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
*
* - {0} - token count
* - {1} - minimum number
* - {2} - name of token
* - {3} - name of limited token
*
*/
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
*
* - {0} - token count
* - {1} - maximum number
* - {2} - name of token
* - {3} - name of limited token
*
*/
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;
}
}