com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck 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
The newest version!
///////////////////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
// Copyright (C) 2001-2024 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.whitespace;
import java.util.BitSet;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
/**
*
* Checks the policy on the padding of parentheses; that is whether a space is required
* after a left parenthesis and before a right parenthesis, or such spaces are
* forbidden. No check occurs at the right parenthesis after an empty for
* iterator, at the left parenthesis before an empty for initialization, or at
* the right parenthesis of a try-with-resources resource specification where
* the last resource variable has a trailing semicolon.
* Use Check
*
* EmptyForIteratorPad to validate empty for iterators and
*
* EmptyForInitializerPad to validate empty for initializers.
* Typecasts are also not checked, as there is
*
* TypecastParenPad to validate them.
*
*
* -
* Property {@code option} - Specify policy on how to pad parentheses.
* Type is {@code com.puppycrawl.tools.checkstyle.checks.whitespace.PadOption}.
* Default value is {@code nospace}.
*
* -
* Property {@code tokens} - tokens to check
* Type is {@code java.lang.String[]}.
* Validation type is {@code tokenSet}.
* Default value is:
*
* ANNOTATION,
*
* ANNOTATION_FIELD_DEF,
*
* CTOR_CALL,
*
* CTOR_DEF,
*
* DOT,
*
* ENUM_CONSTANT_DEF,
*
* EXPR,
*
* LITERAL_CATCH,
*
* LITERAL_DO,
*
* LITERAL_FOR,
*
* LITERAL_IF,
*
* LITERAL_NEW,
*
* LITERAL_SWITCH,
*
* LITERAL_SYNCHRONIZED,
*
* LITERAL_WHILE,
*
* METHOD_CALL,
*
* METHOD_DEF,
*
* QUESTION,
*
* RESOURCE_SPECIFICATION,
*
* SUPER_CTOR_CALL,
*
* LAMBDA,
*
* RECORD_DEF,
*
* RECORD_PATTERN_DEF.
*
*
*
*
* Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
*
*
*
* Violation Message Keys:
*
*
* -
* {@code ws.followed}
*
* -
* {@code ws.notFollowed}
*
* -
* {@code ws.notPreceded}
*
* -
* {@code ws.preceded}
*
*
*
* @since 3.0
*/
public class ParenPadCheck extends AbstractParenPadCheck {
/**
* Tokens that this check handles.
*/
private final BitSet acceptableTokens;
/**
* Initializes acceptableTokens.
*/
public ParenPadCheck() {
acceptableTokens = TokenUtil.asBitSet(makeAcceptableTokens());
}
@Override
public int[] getDefaultTokens() {
return makeAcceptableTokens();
}
@Override
public int[] getAcceptableTokens() {
return makeAcceptableTokens();
}
@Override
public int[] getRequiredTokens() {
return CommonUtil.EMPTY_INT_ARRAY;
}
@Override
public void visitToken(DetailAST ast) {
switch (ast.getType()) {
case TokenTypes.METHOD_CALL:
processLeft(ast);
processRight(ast.findFirstToken(TokenTypes.RPAREN));
break;
case TokenTypes.DOT:
case TokenTypes.EXPR:
case TokenTypes.QUESTION:
processExpression(ast);
break;
case TokenTypes.LITERAL_FOR:
visitLiteralFor(ast);
break;
case TokenTypes.ANNOTATION:
case TokenTypes.ENUM_CONSTANT_DEF:
case TokenTypes.LITERAL_NEW:
case TokenTypes.LITERAL_SYNCHRONIZED:
case TokenTypes.LAMBDA:
visitTokenWithOptionalParentheses(ast);
break;
case TokenTypes.RESOURCE_SPECIFICATION:
visitResourceSpecification(ast);
break;
default:
processLeft(ast.findFirstToken(TokenTypes.LPAREN));
processRight(ast.findFirstToken(TokenTypes.RPAREN));
}
}
/**
* Checks parens in token which may not contain parens, e.g.
* {@link TokenTypes#ENUM_CONSTANT_DEF}, {@link TokenTypes#ANNOTATION}
* {@link TokenTypes#LITERAL_SYNCHRONIZED}, {@link TokenTypes#LITERAL_NEW} and
* {@link TokenTypes#LAMBDA}.
*
* @param ast the token to check.
*/
private void visitTokenWithOptionalParentheses(DetailAST ast) {
final DetailAST parenAst = ast.findFirstToken(TokenTypes.LPAREN);
if (parenAst != null) {
processLeft(parenAst);
processRight(ast.findFirstToken(TokenTypes.RPAREN));
}
}
/**
* Checks parens in {@link TokenTypes#RESOURCE_SPECIFICATION}.
*
* @param ast the token to check.
*/
private void visitResourceSpecification(DetailAST ast) {
processLeft(ast.findFirstToken(TokenTypes.LPAREN));
final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
if (!hasPrecedingSemiColon(rparen)) {
processRight(rparen);
}
}
/**
* Checks that a token is preceded by a semicolon.
*
* @param ast the token to check
* @return whether a token is preceded by a semicolon
*/
private static boolean hasPrecedingSemiColon(DetailAST ast) {
return ast.getPreviousSibling().getType() == TokenTypes.SEMI;
}
/**
* Checks parens in {@link TokenTypes#LITERAL_FOR}.
*
* @param ast the token to check.
*/
private void visitLiteralFor(DetailAST ast) {
final DetailAST lparen = ast.findFirstToken(TokenTypes.LPAREN);
if (!isPrecedingEmptyForInit(lparen)) {
processLeft(lparen);
}
final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
if (!isFollowsEmptyForIterator(rparen)) {
processRight(rparen);
}
}
/**
* Checks parens inside {@link TokenTypes#EXPR}, {@link TokenTypes#QUESTION}
* and {@link TokenTypes#METHOD_CALL}.
*
* @param ast the token to check.
*/
private void processExpression(DetailAST ast) {
DetailAST currentNode = ast.getFirstChild();
while (currentNode != null) {
if (currentNode.getType() == TokenTypes.LPAREN) {
processLeft(currentNode);
}
else if (currentNode.getType() == TokenTypes.RPAREN && !isInTypecast(currentNode)) {
processRight(currentNode);
}
else if (currentNode.hasChildren() && !isAcceptableToken(currentNode)) {
// Traverse all subtree tokens which will never be configured
// to be launched in visitToken()
currentNode = currentNode.getFirstChild();
continue;
}
// Go up after processing the last child
while (currentNode.getNextSibling() == null && currentNode.getParent() != ast) {
currentNode = currentNode.getParent();
}
currentNode = currentNode.getNextSibling();
}
}
/**
* Checks whether AcceptableTokens contains the given ast.
*
* @param ast the token to check.
* @return true if the ast is in AcceptableTokens.
*/
private boolean isAcceptableToken(DetailAST ast) {
return acceptableTokens.get(ast.getType());
}
/**
* Returns array of acceptable tokens.
*
* @return acceptableTokens.
*/
private static int[] makeAcceptableTokens() {
return new int[] {TokenTypes.ANNOTATION,
TokenTypes.ANNOTATION_FIELD_DEF,
TokenTypes.CTOR_CALL,
TokenTypes.CTOR_DEF,
TokenTypes.DOT,
TokenTypes.ENUM_CONSTANT_DEF,
TokenTypes.EXPR,
TokenTypes.LITERAL_CATCH,
TokenTypes.LITERAL_DO,
TokenTypes.LITERAL_FOR,
TokenTypes.LITERAL_IF,
TokenTypes.LITERAL_NEW,
TokenTypes.LITERAL_SWITCH,
TokenTypes.LITERAL_SYNCHRONIZED,
TokenTypes.LITERAL_WHILE,
TokenTypes.METHOD_CALL,
TokenTypes.METHOD_DEF,
TokenTypes.QUESTION,
TokenTypes.RESOURCE_SPECIFICATION,
TokenTypes.SUPER_CTOR_CALL,
TokenTypes.LAMBDA,
TokenTypes.RECORD_DEF,
TokenTypes.RECORD_PATTERN_DEF,
};
}
/**
* Checks whether {@link TokenTypes#RPAREN} is a closing paren
* of a {@link TokenTypes#TYPECAST}.
*
* @param ast of a {@link TokenTypes#RPAREN} to check.
* @return true if ast is a closing paren of a {@link TokenTypes#TYPECAST}.
*/
private static boolean isInTypecast(DetailAST ast) {
boolean result = false;
if (ast.getParent().getType() == TokenTypes.TYPECAST) {
final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN);
if (TokenUtil.areOnSameLine(firstRparen, ast)
&& firstRparen.getColumnNo() == ast.getColumnNo()) {
result = true;
}
}
return result;
}
/**
* Checks that a token follows an empty for iterator.
*
* @param ast the token to check
* @return whether a token follows an empty for iterator
*/
private static boolean isFollowsEmptyForIterator(DetailAST ast) {
boolean result = false;
final DetailAST parent = ast.getParent();
// Only traditional for statements are examined, not for-each statements
if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
final DetailAST forIterator =
parent.findFirstToken(TokenTypes.FOR_ITERATOR);
result = !forIterator.hasChildren();
}
return result;
}
/**
* Checks that a token precedes an empty for initializer.
*
* @param ast the token to check
* @return whether a token precedes an empty for initializer
*/
private static boolean isPrecedingEmptyForInit(DetailAST ast) {
boolean result = false;
final DetailAST parent = ast.getParent();
// Only traditional for statements are examined, not for-each statements
if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
final DetailAST forIterator =
parent.findFirstToken(TokenTypes.FOR_INIT);
result = !forIterator.hasChildren();
}
return result;
}
}