001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.whitespace; 021 022import java.util.Locale; 023import java.util.function.UnaryOperator; 024 025import com.puppycrawl.tools.checkstyle.StatelessCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 030import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 031 032/** 033 * <div> 034 * Checks the policy on how to wrap lines on 035 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/opsummary.html"> 036 * operators</a>. 037 * </div> 038 * 039 * <p> 040 * See the <a href="https://docs.oracle.com/javase/specs/jls/se22/html/jls-15.html#jls-15.20.2"> 041 * Java Language Specification</a> for more information about {@code instanceof} operator. 042 * </p> 043 * 044 * @since 3.0 045 */ 046@StatelessCheck 047public class OperatorWrapCheck 048 extends AbstractCheck { 049 050 /** 051 * A key is pointing to the warning message text in "messages.properties" 052 * file. 053 */ 054 public static final String MSG_LINE_NEW = "line.new"; 055 056 /** 057 * A key is pointing to the warning message text in "messages.properties" 058 * file. 059 */ 060 public static final String MSG_LINE_PREVIOUS = "line.previous"; 061 062 /** Specify policy on how to wrap lines. */ 063 private WrapOption option = WrapOption.NL; 064 065 /** 066 * Creates a new {@code OperatorWrapCheck} instance. 067 */ 068 public OperatorWrapCheck() { 069 // no code by default 070 } 071 072 /** 073 * Setter to specify policy on how to wrap lines. 074 * 075 * @param optionStr string to decode option from 076 * @throws IllegalArgumentException if unable to decode 077 * @since 3.0 078 */ 079 public void setOption(String optionStr) { 080 option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 081 } 082 083 @Override 084 public int[] getDefaultTokens() { 085 return new int[] { 086 TokenTypes.QUESTION, // '?' 087 TokenTypes.COLON, // ':' (not reported for a case) 088 TokenTypes.EQUAL, // "==" 089 TokenTypes.NOT_EQUAL, // "!=" 090 TokenTypes.DIV, // '/' 091 TokenTypes.PLUS, // '+' (unary plus is UNARY_PLUS) 092 TokenTypes.MINUS, // '-' (unary minus is UNARY_MINUS) 093 TokenTypes.STAR, // '*' 094 TokenTypes.MOD, // '%' 095 TokenTypes.SR, // ">>" 096 TokenTypes.BSR, // ">>>" 097 TokenTypes.GE, // ">=" 098 TokenTypes.GT, // ">" 099 TokenTypes.SL, // "<<" 100 TokenTypes.LE, // "<=" 101 TokenTypes.LT, // '<' 102 TokenTypes.BXOR, // '^' 103 TokenTypes.BOR, // '|' 104 TokenTypes.LOR, // "||" 105 TokenTypes.BAND, // '&' 106 TokenTypes.LAND, // "&&" 107 TokenTypes.TYPE_EXTENSION_AND, 108 TokenTypes.LITERAL_INSTANCEOF, 109 }; 110 } 111 112 @Override 113 public int[] getAcceptableTokens() { 114 return new int[] { 115 TokenTypes.QUESTION, // '?' 116 TokenTypes.COLON, // ':' (not reported for a case) 117 TokenTypes.EQUAL, // "==" 118 TokenTypes.NOT_EQUAL, // "!=" 119 TokenTypes.DIV, // '/' 120 TokenTypes.PLUS, // '+' (unary plus is UNARY_PLUS) 121 TokenTypes.MINUS, // '-' (unary minus is UNARY_MINUS) 122 TokenTypes.STAR, // '*' 123 TokenTypes.MOD, // '%' 124 TokenTypes.SR, // ">>" 125 TokenTypes.BSR, // ">>>" 126 TokenTypes.GE, // ">=" 127 TokenTypes.GT, // ">" 128 TokenTypes.SL, // "<<" 129 TokenTypes.LE, // "<=" 130 TokenTypes.LT, // '<' 131 TokenTypes.BXOR, // '^' 132 TokenTypes.BOR, // '|' 133 TokenTypes.LOR, // "||" 134 TokenTypes.BAND, // '&' 135 TokenTypes.LAND, // "&&" 136 TokenTypes.LITERAL_INSTANCEOF, 137 TokenTypes.TYPE_EXTENSION_AND, 138 TokenTypes.ASSIGN, // '=' 139 TokenTypes.DIV_ASSIGN, // "/=" 140 TokenTypes.PLUS_ASSIGN, // "+=" 141 TokenTypes.MINUS_ASSIGN, // "-=" 142 TokenTypes.STAR_ASSIGN, // "*=" 143 TokenTypes.MOD_ASSIGN, // "%=" 144 TokenTypes.SR_ASSIGN, // ">>=" 145 TokenTypes.BSR_ASSIGN, // ">>>=" 146 TokenTypes.SL_ASSIGN, // "<<=" 147 TokenTypes.BXOR_ASSIGN, // "^=" 148 TokenTypes.BOR_ASSIGN, // "|=" 149 TokenTypes.BAND_ASSIGN, // "&=" 150 TokenTypes.METHOD_REF, // "::" 151 TokenTypes.LAMBDA, // "->" 152 }; 153 } 154 155 @Override 156 public int[] getRequiredTokens() { 157 return CommonUtil.EMPTY_INT_ARRAY; 158 } 159 160 @Override 161 public void visitToken(DetailAST ast) { 162 if (isTargetNode(ast)) { 163 if (option == WrapOption.NL && isNewLineModeViolation(ast)) { 164 log(ast, MSG_LINE_NEW, ast.getText()); 165 } 166 else if (option == WrapOption.EOL && isEndOfLineModeViolation(ast)) { 167 log(ast, MSG_LINE_PREVIOUS, ast.getText()); 168 } 169 } 170 } 171 172 /** 173 * Filters some false tokens that this check should ignore. 174 * 175 * @param node the node to check 176 * @return {@code true} for all nodes this check should validate 177 */ 178 private static boolean isTargetNode(DetailAST node) { 179 final boolean result; 180 if (node.getType() == TokenTypes.COLON) { 181 result = !isColonFromLabel(node); 182 } 183 else if (node.getType() == TokenTypes.STAR) { 184 // Unlike the import statement, the multiply operator always has children 185 result = node.hasChildren(); 186 } 187 else { 188 result = true; 189 } 190 return result; 191 } 192 193 /** 194 * Checks whether operator violates {@link WrapOption#NL} mode. 195 * 196 * @param ast the DetailAst of an operator 197 * @return {@code true} if mode does not match 198 */ 199 private static boolean isNewLineModeViolation(DetailAST ast) { 200 return TokenUtil.areOnSameLine(ast, getLeftNode(ast)) 201 && !TokenUtil.areOnSameLine(ast, getRightNode(ast)); 202 } 203 204 /** 205 * Checks whether operator violates {@link WrapOption#EOL} mode. 206 * 207 * @param ast the DetailAst of an operator 208 * @return {@code true} if mode does not match 209 */ 210 private static boolean isEndOfLineModeViolation(DetailAST ast) { 211 return !TokenUtil.areOnSameLine(ast, getLeftNode(ast)); 212 } 213 214 /** 215 * Checks if a node is {@link TokenTypes#COLON} from a label, switch case of default. 216 * 217 * @param node the node to check 218 * @return {@code true} if node matches 219 */ 220 private static boolean isColonFromLabel(DetailAST node) { 221 return TokenUtil.isOfType(node.getParent(), TokenTypes.LABELED_STAT, 222 TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT); 223 } 224 225 /** 226 * Checks if a node is {@link TokenTypes#ASSIGN} to a variable or resource. 227 * 228 * @param node the node to check 229 * @return {@code true} if node matches 230 */ 231 private static boolean isAssignToVariable(DetailAST node) { 232 return TokenUtil.isOfType(node.getParent(), TokenTypes.VARIABLE_DEF, TokenTypes.RESOURCE); 233 } 234 235 /** 236 * Returns the left neighbour of a binary operator. This is the rightmost 237 * grandchild of the left child or sibling. For the assign operator the return value is 238 * the variable name. 239 * 240 * @param node the binary operator 241 * @return nearest node from left 242 */ 243 private static DetailAST getLeftNode(DetailAST node) { 244 DetailAST result; 245 if (node.getFirstChild() == null || isAssignToVariable(node)) { 246 result = node.getPreviousSibling(); 247 } 248 else { 249 result = adjustParens(node.getFirstChild(), DetailAST::getNextSibling); 250 } 251 while (result.getLastChild() != null) { 252 result = result.getLastChild(); 253 } 254 return result; 255 } 256 257 /** 258 * Returns the right neighbour of a binary operator. This is the leftmost 259 * grandchild of the right child or sibling. For the ternary operator this 260 * is the node between {@code ?} and {@code :} . 261 * 262 * @param node the binary operator 263 * @return nearest node from right 264 */ 265 private static DetailAST getRightNode(DetailAST node) { 266 DetailAST result; 267 if (node.getLastChild() == null) { 268 result = node.getNextSibling(); 269 } 270 else { 271 final DetailAST rightNode; 272 if (node.getType() == TokenTypes.QUESTION) { 273 rightNode = node.findFirstToken(TokenTypes.COLON).getPreviousSibling(); 274 } 275 else { 276 rightNode = node.getLastChild(); 277 } 278 result = adjustParens(rightNode, DetailAST::getPreviousSibling); 279 } 280 281 if (!TokenUtil.isOfType(result, TokenTypes.ARRAY_INIT, TokenTypes.ANNOTATION_ARRAY_INIT)) { 282 while (result.getFirstChild() != null) { 283 result = result.getFirstChild(); 284 } 285 } 286 return result; 287 } 288 289 /** 290 * Finds matching parentheses among siblings. If the given node is not 291 * {@link TokenTypes#LPAREN} nor {@link TokenTypes#RPAREN}, the method adjusts nothing. 292 * This method is for handling case like {@code 293 * (condition && (condition 294 * || condition2 || condition3) && condition4 295 * && condition3) 296 * } 297 * 298 * @param node the node to adjust 299 * @param step the node transformer, should be {@link DetailAST#getPreviousSibling} 300 * or {@link DetailAST#getNextSibling} 301 * @return adjusted node 302 */ 303 private static DetailAST adjustParens(DetailAST node, UnaryOperator<DetailAST> step) { 304 DetailAST result = node; 305 int accumulator = 0; 306 while (true) { 307 if (result.getType() == TokenTypes.LPAREN) { 308 accumulator--; 309 } 310 else if (result.getType() == TokenTypes.RPAREN) { 311 accumulator++; 312 } 313 if (accumulator == 0) { 314 break; 315 } 316 result = step.apply(result); 317 } 318 return result; 319 } 320 321}