
com.puppycrawl.tools.checkstyle.checks.whitespace.OperatorWrapCheck 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 and other text files for adherence to a set of rules.
// Copyright (C) 2001-2025 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.Locale;
import java.util.function.UnaryOperator;
import com.puppycrawl.tools.checkstyle.StatelessCheck;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
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 how to wrap lines on
*
* operators.
*
*
*
* See the
* Java Language Specification for more information about {@code instanceof} operator.
*
*
* -
* Property {@code option} - Specify policy on how to wrap lines.
* Type is {@code com.puppycrawl.tools.checkstyle.checks.whitespace.WrapOption}.
* Default value is {@code nl}.
*
* -
* Property {@code tokens} - tokens to check
* Type is {@code java.lang.String[]}.
* Validation type is {@code tokenSet}.
* Default value is:
*
* QUESTION,
*
* COLON,
*
* EQUAL,
*
* NOT_EQUAL,
*
* DIV,
*
* PLUS,
*
* MINUS,
*
* STAR,
*
* MOD,
*
* SR,
*
* BSR,
*
* GE,
*
* GT,
*
* SL,
*
* LE,
*
* LT,
*
* BXOR,
*
* BOR,
*
* LOR,
*
* BAND,
*
* LAND,
*
* TYPE_EXTENSION_AND,
*
* LITERAL_INSTANCEOF.
*
*
*
*
* Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
*
*
*
* Violation Message Keys:
*
*
* -
* {@code line.new}
*
* -
* {@code line.previous}
*
*
*
* @since 3.0
*/
@StatelessCheck
public class OperatorWrapCheck
extends AbstractCheck {
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_LINE_NEW = "line.new";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_LINE_PREVIOUS = "line.previous";
/** Specify policy on how to wrap lines. */
private WrapOption option = WrapOption.NL;
/**
* Setter to specify policy on how to wrap lines.
*
* @param optionStr string to decode option from
* @throws IllegalArgumentException if unable to decode
* @since 3.0
*/
public void setOption(String optionStr) {
option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
}
@Override
public int[] getDefaultTokens() {
return new int[] {
TokenTypes.QUESTION, // '?'
TokenTypes.COLON, // ':' (not reported for a case)
TokenTypes.EQUAL, // "=="
TokenTypes.NOT_EQUAL, // "!="
TokenTypes.DIV, // '/'
TokenTypes.PLUS, // '+' (unary plus is UNARY_PLUS)
TokenTypes.MINUS, // '-' (unary minus is UNARY_MINUS)
TokenTypes.STAR, // '*'
TokenTypes.MOD, // '%'
TokenTypes.SR, // ">>"
TokenTypes.BSR, // ">>>"
TokenTypes.GE, // ">="
TokenTypes.GT, // ">"
TokenTypes.SL, // "<<"
TokenTypes.LE, // "<="
TokenTypes.LT, // '<'
TokenTypes.BXOR, // '^'
TokenTypes.BOR, // '|'
TokenTypes.LOR, // "||"
TokenTypes.BAND, // '&'
TokenTypes.LAND, // "&&"
TokenTypes.TYPE_EXTENSION_AND,
TokenTypes.LITERAL_INSTANCEOF,
};
}
@Override
public int[] getAcceptableTokens() {
return new int[] {
TokenTypes.QUESTION, // '?'
TokenTypes.COLON, // ':' (not reported for a case)
TokenTypes.EQUAL, // "=="
TokenTypes.NOT_EQUAL, // "!="
TokenTypes.DIV, // '/'
TokenTypes.PLUS, // '+' (unary plus is UNARY_PLUS)
TokenTypes.MINUS, // '-' (unary minus is UNARY_MINUS)
TokenTypes.STAR, // '*'
TokenTypes.MOD, // '%'
TokenTypes.SR, // ">>"
TokenTypes.BSR, // ">>>"
TokenTypes.GE, // ">="
TokenTypes.GT, // ">"
TokenTypes.SL, // "<<"
TokenTypes.LE, // "<="
TokenTypes.LT, // '<'
TokenTypes.BXOR, // '^'
TokenTypes.BOR, // '|'
TokenTypes.LOR, // "||"
TokenTypes.BAND, // '&'
TokenTypes.LAND, // "&&"
TokenTypes.LITERAL_INSTANCEOF,
TokenTypes.TYPE_EXTENSION_AND,
TokenTypes.ASSIGN, // '='
TokenTypes.DIV_ASSIGN, // "/="
TokenTypes.PLUS_ASSIGN, // "+="
TokenTypes.MINUS_ASSIGN, // "-="
TokenTypes.STAR_ASSIGN, // "*="
TokenTypes.MOD_ASSIGN, // "%="
TokenTypes.SR_ASSIGN, // ">>="
TokenTypes.BSR_ASSIGN, // ">>>="
TokenTypes.SL_ASSIGN, // "<<="
TokenTypes.BXOR_ASSIGN, // "^="
TokenTypes.BOR_ASSIGN, // "|="
TokenTypes.BAND_ASSIGN, // "&="
TokenTypes.METHOD_REF, // "::"
};
}
@Override
public int[] getRequiredTokens() {
return CommonUtil.EMPTY_INT_ARRAY;
}
@Override
public void visitToken(DetailAST ast) {
if (isTargetNode(ast)) {
if (option == WrapOption.NL && isNewLineModeViolation(ast)) {
log(ast, MSG_LINE_NEW, ast.getText());
}
else if (option == WrapOption.EOL && isEndOfLineModeViolation(ast)) {
log(ast, MSG_LINE_PREVIOUS, ast.getText());
}
}
}
/**
* Filters some false tokens that this check should ignore.
*
* @param node the node to check
* @return {@code true} for all nodes this check should validate
*/
private static boolean isTargetNode(DetailAST node) {
final boolean result;
if (node.getType() == TokenTypes.COLON) {
result = !isColonFromLabel(node);
}
else if (node.getType() == TokenTypes.STAR) {
// Unlike the import statement, the multiply operator always has children
result = node.hasChildren();
}
else {
result = true;
}
return result;
}
/**
* Checks whether operator violates {@link WrapOption#NL} mode.
*
* @param ast the DetailAst of an operator
* @return {@code true} if mode does not match
*/
private static boolean isNewLineModeViolation(DetailAST ast) {
return TokenUtil.areOnSameLine(ast, getLeftNode(ast))
&& !TokenUtil.areOnSameLine(ast, getRightNode(ast));
}
/**
* Checks whether operator violates {@link WrapOption#EOL} mode.
*
* @param ast the DetailAst of an operator
* @return {@code true} if mode does not match
*/
private static boolean isEndOfLineModeViolation(DetailAST ast) {
return !TokenUtil.areOnSameLine(ast, getLeftNode(ast));
}
/**
* Checks if a node is {@link TokenTypes#COLON} from a label, switch case of default.
*
* @param node the node to check
* @return {@code true} if node matches
*/
private static boolean isColonFromLabel(DetailAST node) {
return TokenUtil.isOfType(node.getParent(), TokenTypes.LABELED_STAT,
TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT);
}
/**
* Checks if a node is {@link TokenTypes#ASSIGN} to a variable or resource.
*
* @param node the node to check
* @return {@code true} if node matches
*/
private static boolean isAssignToVariable(DetailAST node) {
return TokenUtil.isOfType(node.getParent(), TokenTypes.VARIABLE_DEF, TokenTypes.RESOURCE);
}
/**
* Returns the left neighbour of a binary operator. This is the rightmost
* grandchild of the left child or sibling. For the assign operator the return value is
* the variable name.
*
* @param node the binary operator
* @return nearest node from left
*/
private static DetailAST getLeftNode(DetailAST node) {
DetailAST result;
if (node.getFirstChild() == null || isAssignToVariable(node)) {
result = node.getPreviousSibling();
}
else if (isInPatternDefinition(node)) {
result = node.getFirstChild();
}
else {
result = adjustParens(node.getFirstChild(), DetailAST::getNextSibling);
}
while (result.getLastChild() != null) {
result = result.getLastChild();
}
return result;
}
/**
* Ascends AST to determine if given node is part of a pattern
* definition.
*
* @param node the node to check
* @return true if node is in pattern definition
*/
private static boolean isInPatternDefinition(DetailAST node) {
DetailAST parent = node;
final int[] tokensToStopOn = {
// token we are looking for
TokenTypes.PATTERN_DEF,
// tokens that mean we can stop looking
TokenTypes.EXPR,
TokenTypes.RESOURCE,
TokenTypes.COMPILATION_UNIT,
};
do {
parent = parent.getParent();
} while (!TokenUtil.isOfType(parent, tokensToStopOn));
return parent.getType() == TokenTypes.PATTERN_DEF;
}
/**
* Returns the right neighbour of a binary operator. This is the leftmost
* grandchild of the right child or sibling. For the ternary operator this
* is the node between {@code ?} and {@code :} .
*
* @param node the binary operator
* @return nearest node from right
*/
private static DetailAST getRightNode(DetailAST node) {
DetailAST result;
if (node.getLastChild() == null) {
result = node.getNextSibling();
}
else {
final DetailAST rightNode;
if (node.getType() == TokenTypes.QUESTION) {
rightNode = node.findFirstToken(TokenTypes.COLON).getPreviousSibling();
}
else {
rightNode = node.getLastChild();
}
result = adjustParens(rightNode, DetailAST::getPreviousSibling);
}
if (!TokenUtil.isOfType(result, TokenTypes.ARRAY_INIT, TokenTypes.ANNOTATION_ARRAY_INIT)) {
while (result.getFirstChild() != null) {
result = result.getFirstChild();
}
}
return result;
}
/**
* Finds matching parentheses among siblings. If the given node is not
* {@link TokenTypes#LPAREN} nor {@link TokenTypes#RPAREN}, the method adjusts nothing.
* This method is for handling case like {@code
* (condition && (condition
* || condition2 || condition3) && condition4
* && condition3)
* }
*
* @param node the node to adjust
* @param step the node transformer, should be {@link DetailAST#getPreviousSibling}
* or {@link DetailAST#getNextSibling}
* @return adjusted node
*/
private static DetailAST adjustParens(DetailAST node, UnaryOperator step) {
DetailAST result = node;
int accumulator = 0;
while (true) {
if (result.getType() == TokenTypes.LPAREN) {
accumulator--;
}
else if (result.getType() == TokenTypes.RPAREN) {
accumulator++;
}
if (accumulator == 0) {
break;
}
result = step.apply(result);
}
return result;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy