com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck Maven / Gradle / Ivy
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2021 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.Arrays;
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 semi-colon.
* 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.
*
*
*
* To configure the check:
*
*
* <module name="ParenPad"/>
*
*
* Example:
*
*
* class Foo {
*
* int n;
*
* public void fun() { // OK
* bar( 1); // violation, space after left parenthesis
* }
*
* public void bar(int k ) { // violation, space before right parenthesis
* while (k > 0) { // OK
* }
*
* Test obj = new Test(k); // OK
* }
*
* public void fun2() { // OK
* switch( n) { // violation, space after left parenthesis
* case 2:
* bar(n); // OK
* default:
* break;
* }
* }
*
* }
*
*
* To configure the check to require spaces for the
* parentheses of constructor, method, and super constructor calls:
*
*
* <module name="ParenPad">
* <property name="tokens" value="LITERAL_FOR, LITERAL_CATCH,
* SUPER_CTOR_CALL"/>
* <property name="option" value="space"/>
* </module>
*
*
* Example:
*
*
* class Foo {
*
* int x;
*
* public Foo(int n) {
* }
*
* public void fun() {
* try {
* System.out.println(x);
* } catch( IOException e) { // violation, no space before right parenthesis
* } catch( Exception e ) { // OK
* }
*
* for ( int i = 0; i < x; i++ ) { // OK
* }
* }
*
* }
*
* class Bar extends Foo {
*
* public Bar() {
* super(1 ); // violation, no space after left parenthesis
* }
*
* public Bar(int k) {
* super( k ); // OK
*
* for ( int i = 0; i < k; i++) { // violation, no space before right parenthesis
* }
* }
*
* }
*
*
* The following cases are not checked:
*
*
* for ( ; i < j; i++, j--) // no check after left parenthesis
* for (Iterator it = xs.iterator(); it.hasNext(); ) // no check before right parenthesis
* try (Closeable resource = acquire(); ) // no check before right parenthesis
*
*
* 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 {
/**
* The array of Acceptable Tokens.
*/
private final int[] acceptableTokens;
/**
* Initializes and sorts acceptableTokens to make binary search over it possible.
*/
public ParenPadCheck() {
acceptableTokens = makeAcceptableTokens();
Arrays.sort(acceptableTokens);
}
@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 semi-colon.
*
* @param ast the token to check
* @return whether a token is preceded by a semi-colon
*/
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) {
boolean result = false;
if (Arrays.binarySearch(acceptableTokens, ast.getType()) >= 0) {
result = true;
}
return result;
}
/**
* 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,
};
}
/**
* 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;
}
}