001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 * Setter to specify policy on how to wrap lines. 067 * 068 * @param optionStr string to decode option from 069 * @throws IllegalArgumentException if unable to decode 070 * @since 3.0 071 */ 072 public void setOption(String optionStr) { 073 option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 074 } 075 076 @Override 077 public int[] getDefaultTokens() { 078 return new int[] { 079 TokenTypes.QUESTION, // '?' 080 TokenTypes.COLON, // ':' (not reported for a case) 081 TokenTypes.EQUAL, // "==" 082 TokenTypes.NOT_EQUAL, // "!=" 083 TokenTypes.DIV, // '/' 084 TokenTypes.PLUS, // '+' (unary plus is UNARY_PLUS) 085 TokenTypes.MINUS, // '-' (unary minus is UNARY_MINUS) 086 TokenTypes.STAR, // '*' 087 TokenTypes.MOD, // '%' 088 TokenTypes.SR, // ">>" 089 TokenTypes.BSR, // ">>>" 090 TokenTypes.GE, // ">=" 091 TokenTypes.GT, // ">" 092 TokenTypes.SL, // "<<" 093 TokenTypes.LE, // "<=" 094 TokenTypes.LT, // '<' 095 TokenTypes.BXOR, // '^' 096 TokenTypes.BOR, // '|' 097 TokenTypes.LOR, // "||" 098 TokenTypes.BAND, // '&' 099 TokenTypes.LAND, // "&&" 100 TokenTypes.TYPE_EXTENSION_AND, 101 TokenTypes.LITERAL_INSTANCEOF, 102 }; 103 } 104 105 @Override 106 public int[] getAcceptableTokens() { 107 return new int[] { 108 TokenTypes.QUESTION, // '?' 109 TokenTypes.COLON, // ':' (not reported for a case) 110 TokenTypes.EQUAL, // "==" 111 TokenTypes.NOT_EQUAL, // "!=" 112 TokenTypes.DIV, // '/' 113 TokenTypes.PLUS, // '+' (unary plus is UNARY_PLUS) 114 TokenTypes.MINUS, // '-' (unary minus is UNARY_MINUS) 115 TokenTypes.STAR, // '*' 116 TokenTypes.MOD, // '%' 117 TokenTypes.SR, // ">>" 118 TokenTypes.BSR, // ">>>" 119 TokenTypes.GE, // ">=" 120 TokenTypes.GT, // ">" 121 TokenTypes.SL, // "<<" 122 TokenTypes.LE, // "<=" 123 TokenTypes.LT, // '<' 124 TokenTypes.BXOR, // '^' 125 TokenTypes.BOR, // '|' 126 TokenTypes.LOR, // "||" 127 TokenTypes.BAND, // '&' 128 TokenTypes.LAND, // "&&" 129 TokenTypes.LITERAL_INSTANCEOF, 130 TokenTypes.TYPE_EXTENSION_AND, 131 TokenTypes.ASSIGN, // '=' 132 TokenTypes.DIV_ASSIGN, // "/=" 133 TokenTypes.PLUS_ASSIGN, // "+=" 134 TokenTypes.MINUS_ASSIGN, // "-=" 135 TokenTypes.STAR_ASSIGN, // "*=" 136 TokenTypes.MOD_ASSIGN, // "%=" 137 TokenTypes.SR_ASSIGN, // ">>=" 138 TokenTypes.BSR_ASSIGN, // ">>>=" 139 TokenTypes.SL_ASSIGN, // "<<=" 140 TokenTypes.BXOR_ASSIGN, // "^=" 141 TokenTypes.BOR_ASSIGN, // "|=" 142 TokenTypes.BAND_ASSIGN, // "&=" 143 TokenTypes.METHOD_REF, // "::" 144 }; 145 } 146 147 @Override 148 public int[] getRequiredTokens() { 149 return CommonUtil.EMPTY_INT_ARRAY; 150 } 151 152 @Override 153 public void visitToken(DetailAST ast) { 154 if (isTargetNode(ast)) { 155 if (option == WrapOption.NL && isNewLineModeViolation(ast)) { 156 log(ast, MSG_LINE_NEW, ast.getText()); 157 } 158 else if (option == WrapOption.EOL && isEndOfLineModeViolation(ast)) { 159 log(ast, MSG_LINE_PREVIOUS, ast.getText()); 160 } 161 } 162 } 163 164 /** 165 * Filters some false tokens that this check should ignore. 166 * 167 * @param node the node to check 168 * @return {@code true} for all nodes this check should validate 169 */ 170 private static boolean isTargetNode(DetailAST node) { 171 final boolean result; 172 if (node.getType() == TokenTypes.COLON) { 173 result = !isColonFromLabel(node); 174 } 175 else if (node.getType() == TokenTypes.STAR) { 176 // Unlike the import statement, the multiply operator always has children 177 result = node.hasChildren(); 178 } 179 else { 180 result = true; 181 } 182 return result; 183 } 184 185 /** 186 * Checks whether operator violates {@link WrapOption#NL} mode. 187 * 188 * @param ast the DetailAst of an operator 189 * @return {@code true} if mode does not match 190 */ 191 private static boolean isNewLineModeViolation(DetailAST ast) { 192 return TokenUtil.areOnSameLine(ast, getLeftNode(ast)) 193 && !TokenUtil.areOnSameLine(ast, getRightNode(ast)); 194 } 195 196 /** 197 * Checks whether operator violates {@link WrapOption#EOL} mode. 198 * 199 * @param ast the DetailAst of an operator 200 * @return {@code true} if mode does not match 201 */ 202 private static boolean isEndOfLineModeViolation(DetailAST ast) { 203 return !TokenUtil.areOnSameLine(ast, getLeftNode(ast)); 204 } 205 206 /** 207 * Checks if a node is {@link TokenTypes#COLON} from a label, switch case of default. 208 * 209 * @param node the node to check 210 * @return {@code true} if node matches 211 */ 212 private static boolean isColonFromLabel(DetailAST node) { 213 return TokenUtil.isOfType(node.getParent(), TokenTypes.LABELED_STAT, 214 TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT); 215 } 216 217 /** 218 * Checks if a node is {@link TokenTypes#ASSIGN} to a variable or resource. 219 * 220 * @param node the node to check 221 * @return {@code true} if node matches 222 */ 223 private static boolean isAssignToVariable(DetailAST node) { 224 return TokenUtil.isOfType(node.getParent(), TokenTypes.VARIABLE_DEF, TokenTypes.RESOURCE); 225 } 226 227 /** 228 * Returns the left neighbour of a binary operator. This is the rightmost 229 * grandchild of the left child or sibling. For the assign operator the return value is 230 * the variable name. 231 * 232 * @param node the binary operator 233 * @return nearest node from left 234 */ 235 private static DetailAST getLeftNode(DetailAST node) { 236 DetailAST result; 237 if (node.getFirstChild() == null || isAssignToVariable(node)) { 238 result = node.getPreviousSibling(); 239 } 240 else if (isInPatternDefinition(node)) { 241 result = node.getFirstChild(); 242 } 243 else { 244 result = adjustParens(node.getFirstChild(), DetailAST::getNextSibling); 245 } 246 while (result.getLastChild() != null) { 247 result = result.getLastChild(); 248 } 249 return result; 250 } 251 252 /** 253 * Ascends AST to determine if given node is part of a pattern 254 * definition. 255 * 256 * @param node the node to check 257 * @return true if node is in pattern definition 258 */ 259 private static boolean isInPatternDefinition(DetailAST node) { 260 DetailAST parent = node; 261 final int[] tokensToStopOn = { 262 // token we are looking for 263 TokenTypes.PATTERN_DEF, 264 // tokens that mean we can stop looking 265 TokenTypes.EXPR, 266 TokenTypes.RESOURCE, 267 TokenTypes.COMPILATION_UNIT, 268 }; 269 270 do { 271 parent = parent.getParent(); 272 } while (!TokenUtil.isOfType(parent, tokensToStopOn)); 273 return parent.getType() == TokenTypes.PATTERN_DEF; 274 } 275 276 /** 277 * Returns the right neighbour of a binary operator. This is the leftmost 278 * grandchild of the right child or sibling. For the ternary operator this 279 * is the node between {@code ?} and {@code :} . 280 * 281 * @param node the binary operator 282 * @return nearest node from right 283 */ 284 private static DetailAST getRightNode(DetailAST node) { 285 DetailAST result; 286 if (node.getLastChild() == null) { 287 result = node.getNextSibling(); 288 } 289 else { 290 final DetailAST rightNode; 291 if (node.getType() == TokenTypes.QUESTION) { 292 rightNode = node.findFirstToken(TokenTypes.COLON).getPreviousSibling(); 293 } 294 else { 295 rightNode = node.getLastChild(); 296 } 297 result = adjustParens(rightNode, DetailAST::getPreviousSibling); 298 } 299 300 if (!TokenUtil.isOfType(result, TokenTypes.ARRAY_INIT, TokenTypes.ANNOTATION_ARRAY_INIT)) { 301 while (result.getFirstChild() != null) { 302 result = result.getFirstChild(); 303 } 304 } 305 return result; 306 } 307 308 /** 309 * Finds matching parentheses among siblings. If the given node is not 310 * {@link TokenTypes#LPAREN} nor {@link TokenTypes#RPAREN}, the method adjusts nothing. 311 * This method is for handling case like {@code 312 * (condition && (condition 313 * || condition2 || condition3) && condition4 314 * && condition3) 315 * } 316 * 317 * @param node the node to adjust 318 * @param step the node transformer, should be {@link DetailAST#getPreviousSibling} 319 * or {@link DetailAST#getNextSibling} 320 * @return adjusted node 321 */ 322 private static DetailAST adjustParens(DetailAST node, UnaryOperator<DetailAST> step) { 323 DetailAST result = node; 324 int accumulator = 0; 325 while (true) { 326 if (result.getType() == TokenTypes.LPAREN) { 327 accumulator--; 328 } 329 else if (result.getType() == TokenTypes.RPAREN) { 330 accumulator++; 331 } 332 if (accumulator == 0) { 333 break; 334 } 335 result = step.apply(result); 336 } 337 return result; 338 } 339 340}