1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2024 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 /////////////////////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.whitespace; 21 22 import java.util.Locale; 23 import java.util.function.UnaryOperator; 24 25 import com.puppycrawl.tools.checkstyle.StatelessCheck; 26 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 27 import com.puppycrawl.tools.checkstyle.api.DetailAST; 28 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 29 import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 30 import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 31 32 /** 33 * <p> 34 * Checks the policy on how to wrap lines on 35 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/opsummary.html"> 36 * operators</a>. 37 * </p> 38 * <p> 39 * See the <a href="https://docs.oracle.com/javase/specs/jls/se22/html/jls-15.html#jls-15.20.2"> 40 * Java Language Specification</a> for more information about {@code instanceof} operator. 41 * </p> 42 * <ul> 43 * <li> 44 * Property {@code option} - Specify policy on how to wrap lines. 45 * Type is {@code com.puppycrawl.tools.checkstyle.checks.whitespace.WrapOption}. 46 * Default value is {@code nl}. 47 * </li> 48 * <li> 49 * Property {@code tokens} - tokens to check 50 * Type is {@code java.lang.String[]}. 51 * Validation type is {@code tokenSet}. 52 * Default value is: 53 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION"> 54 * QUESTION</a>, 55 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COLON"> 56 * COLON</a>, 57 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EQUAL"> 58 * EQUAL</a>, 59 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NOT_EQUAL"> 60 * NOT_EQUAL</a>, 61 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV"> 62 * DIV</a>, 63 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS"> 64 * PLUS</a>, 65 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS"> 66 * MINUS</a>, 67 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR"> 68 * STAR</a>, 69 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MOD"> 70 * MOD</a>, 71 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SR"> 72 * SR</a>, 73 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BSR"> 74 * BSR</a>, 75 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GE"> 76 * GE</a>, 77 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GT"> 78 * GT</a>, 79 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SL"> 80 * SL</a>, 81 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LE"> 82 * LE</a>, 83 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LT"> 84 * LT</a>, 85 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BXOR"> 86 * BXOR</a>, 87 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BOR"> 88 * BOR</a>, 89 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LOR"> 90 * LOR</a>, 91 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BAND"> 92 * BAND</a>, 93 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAND"> 94 * LAND</a>, 95 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPE_EXTENSION_AND"> 96 * TYPE_EXTENSION_AND</a>, 97 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_INSTANCEOF"> 98 * LITERAL_INSTANCEOF</a>. 99 * </li> 100 * </ul> 101 * <p> 102 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 103 * </p> 104 * <p> 105 * Violation Message Keys: 106 * </p> 107 * <ul> 108 * <li> 109 * {@code line.new} 110 * </li> 111 * <li> 112 * {@code line.previous} 113 * </li> 114 * </ul> 115 * 116 * @since 3.0 117 */ 118 @StatelessCheck 119 public class OperatorWrapCheck 120 extends AbstractCheck { 121 122 /** 123 * A key is pointing to the warning message text in "messages.properties" 124 * file. 125 */ 126 public static final String MSG_LINE_NEW = "line.new"; 127 128 /** 129 * A key is pointing to the warning message text in "messages.properties" 130 * file. 131 */ 132 public static final String MSG_LINE_PREVIOUS = "line.previous"; 133 134 /** Specify policy on how to wrap lines. */ 135 private WrapOption option = WrapOption.NL; 136 137 /** 138 * Setter to specify policy on how to wrap lines. 139 * 140 * @param optionStr string to decode option from 141 * @throws IllegalArgumentException if unable to decode 142 * @since 3.0 143 */ 144 public void setOption(String optionStr) { 145 option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 146 } 147 148 @Override 149 public int[] getDefaultTokens() { 150 return new int[] { 151 TokenTypes.QUESTION, // '?' 152 TokenTypes.COLON, // ':' (not reported for a case) 153 TokenTypes.EQUAL, // "==" 154 TokenTypes.NOT_EQUAL, // "!=" 155 TokenTypes.DIV, // '/' 156 TokenTypes.PLUS, // '+' (unary plus is UNARY_PLUS) 157 TokenTypes.MINUS, // '-' (unary minus is UNARY_MINUS) 158 TokenTypes.STAR, // '*' 159 TokenTypes.MOD, // '%' 160 TokenTypes.SR, // ">>" 161 TokenTypes.BSR, // ">>>" 162 TokenTypes.GE, // ">=" 163 TokenTypes.GT, // ">" 164 TokenTypes.SL, // "<<" 165 TokenTypes.LE, // "<=" 166 TokenTypes.LT, // '<' 167 TokenTypes.BXOR, // '^' 168 TokenTypes.BOR, // '|' 169 TokenTypes.LOR, // "||" 170 TokenTypes.BAND, // '&' 171 TokenTypes.LAND, // "&&" 172 TokenTypes.TYPE_EXTENSION_AND, 173 TokenTypes.LITERAL_INSTANCEOF, 174 }; 175 } 176 177 @Override 178 public int[] getAcceptableTokens() { 179 return new int[] { 180 TokenTypes.QUESTION, // '?' 181 TokenTypes.COLON, // ':' (not reported for a case) 182 TokenTypes.EQUAL, // "==" 183 TokenTypes.NOT_EQUAL, // "!=" 184 TokenTypes.DIV, // '/' 185 TokenTypes.PLUS, // '+' (unary plus is UNARY_PLUS) 186 TokenTypes.MINUS, // '-' (unary minus is UNARY_MINUS) 187 TokenTypes.STAR, // '*' 188 TokenTypes.MOD, // '%' 189 TokenTypes.SR, // ">>" 190 TokenTypes.BSR, // ">>>" 191 TokenTypes.GE, // ">=" 192 TokenTypes.GT, // ">" 193 TokenTypes.SL, // "<<" 194 TokenTypes.LE, // "<=" 195 TokenTypes.LT, // '<' 196 TokenTypes.BXOR, // '^' 197 TokenTypes.BOR, // '|' 198 TokenTypes.LOR, // "||" 199 TokenTypes.BAND, // '&' 200 TokenTypes.LAND, // "&&" 201 TokenTypes.LITERAL_INSTANCEOF, 202 TokenTypes.TYPE_EXTENSION_AND, 203 TokenTypes.ASSIGN, // '=' 204 TokenTypes.DIV_ASSIGN, // "/=" 205 TokenTypes.PLUS_ASSIGN, // "+=" 206 TokenTypes.MINUS_ASSIGN, // "-=" 207 TokenTypes.STAR_ASSIGN, // "*=" 208 TokenTypes.MOD_ASSIGN, // "%=" 209 TokenTypes.SR_ASSIGN, // ">>=" 210 TokenTypes.BSR_ASSIGN, // ">>>=" 211 TokenTypes.SL_ASSIGN, // "<<=" 212 TokenTypes.BXOR_ASSIGN, // "^=" 213 TokenTypes.BOR_ASSIGN, // "|=" 214 TokenTypes.BAND_ASSIGN, // "&=" 215 TokenTypes.METHOD_REF, // "::" 216 }; 217 } 218 219 @Override 220 public int[] getRequiredTokens() { 221 return CommonUtil.EMPTY_INT_ARRAY; 222 } 223 224 @Override 225 public void visitToken(DetailAST ast) { 226 if (isTargetNode(ast)) { 227 if (option == WrapOption.NL && isNewLineModeViolation(ast)) { 228 log(ast, MSG_LINE_NEW, ast.getText()); 229 } 230 else if (option == WrapOption.EOL && isEndOfLineModeViolation(ast)) { 231 log(ast, MSG_LINE_PREVIOUS, ast.getText()); 232 } 233 } 234 } 235 236 /** 237 * Filters some false tokens that this check should ignore. 238 * 239 * @param node the node to check 240 * @return {@code true} for all nodes this check should validate 241 */ 242 private static boolean isTargetNode(DetailAST node) { 243 final boolean result; 244 if (node.getType() == TokenTypes.COLON) { 245 result = !isColonFromLabel(node); 246 } 247 else if (node.getType() == TokenTypes.STAR) { 248 // Unlike the import statement, the multiply operator always has children 249 result = node.hasChildren(); 250 } 251 else { 252 result = true; 253 } 254 return result; 255 } 256 257 /** 258 * Checks whether operator violates {@link WrapOption#NL} mode. 259 * 260 * @param ast the DetailAst of an operator 261 * @return {@code true} if mode does not match 262 */ 263 private static boolean isNewLineModeViolation(DetailAST ast) { 264 return TokenUtil.areOnSameLine(ast, getLeftNode(ast)) 265 && !TokenUtil.areOnSameLine(ast, getRightNode(ast)); 266 } 267 268 /** 269 * Checks whether operator violates {@link WrapOption#EOL} mode. 270 * 271 * @param ast the DetailAst of an operator 272 * @return {@code true} if mode does not match 273 */ 274 private static boolean isEndOfLineModeViolation(DetailAST ast) { 275 return !TokenUtil.areOnSameLine(ast, getLeftNode(ast)); 276 } 277 278 /** 279 * Checks if a node is {@link TokenTypes#COLON} from a label, switch case of default. 280 * 281 * @param node the node to check 282 * @return {@code true} if node matches 283 */ 284 private static boolean isColonFromLabel(DetailAST node) { 285 return TokenUtil.isOfType(node.getParent(), TokenTypes.LABELED_STAT, 286 TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT); 287 } 288 289 /** 290 * Checks if a node is {@link TokenTypes#ASSIGN} to a variable or resource. 291 * 292 * @param node the node to check 293 * @return {@code true} if node matches 294 */ 295 private static boolean isAssignToVariable(DetailAST node) { 296 return TokenUtil.isOfType(node.getParent(), TokenTypes.VARIABLE_DEF, TokenTypes.RESOURCE); 297 } 298 299 /** 300 * Returns the left neighbour of a binary operator. This is the rightmost 301 * grandchild of the left child or sibling. For the assign operator the return value is 302 * the variable name. 303 * 304 * @param node the binary operator 305 * @return nearest node from left 306 */ 307 private static DetailAST getLeftNode(DetailAST node) { 308 DetailAST result; 309 if (node.getFirstChild() == null || isAssignToVariable(node)) { 310 result = node.getPreviousSibling(); 311 } 312 else if (isInPatternDefinition(node)) { 313 result = node.getFirstChild(); 314 } 315 else { 316 result = adjustParens(node.getFirstChild(), DetailAST::getNextSibling); 317 } 318 while (result.getLastChild() != null) { 319 result = result.getLastChild(); 320 } 321 return result; 322 } 323 324 /** 325 * Ascends AST to determine if given node is part of a pattern 326 * definition. 327 * 328 * @param node the node to check 329 * @return true if node is in pattern definition 330 */ 331 private static boolean isInPatternDefinition(DetailAST node) { 332 DetailAST parent = node; 333 final int[] tokensToStopOn = { 334 // token we are looking for 335 TokenTypes.PATTERN_DEF, 336 // tokens that mean we can stop looking 337 TokenTypes.EXPR, 338 TokenTypes.RESOURCE, 339 TokenTypes.COMPILATION_UNIT, 340 }; 341 342 do { 343 parent = parent.getParent(); 344 } while (!TokenUtil.isOfType(parent, tokensToStopOn)); 345 return parent.getType() == TokenTypes.PATTERN_DEF; 346 } 347 348 /** 349 * Returns the right neighbour of a binary operator. This is the leftmost 350 * grandchild of the right child or sibling. For the ternary operator this 351 * is the node between {@code ?} and {@code :} . 352 * 353 * @param node the binary operator 354 * @return nearest node from right 355 */ 356 private static DetailAST getRightNode(DetailAST node) { 357 DetailAST result; 358 if (node.getLastChild() == null) { 359 result = node.getNextSibling(); 360 } 361 else { 362 final DetailAST rightNode; 363 if (node.getType() == TokenTypes.QUESTION) { 364 rightNode = node.findFirstToken(TokenTypes.COLON).getPreviousSibling(); 365 } 366 else { 367 rightNode = node.getLastChild(); 368 } 369 result = adjustParens(rightNode, DetailAST::getPreviousSibling); 370 } 371 372 if (!TokenUtil.isOfType(result, TokenTypes.ARRAY_INIT, TokenTypes.ANNOTATION_ARRAY_INIT)) { 373 while (result.getFirstChild() != null) { 374 result = result.getFirstChild(); 375 } 376 } 377 return result; 378 } 379 380 /** 381 * Finds matching parentheses among siblings. If the given node is not 382 * {@link TokenTypes#LPAREN} nor {@link TokenTypes#RPAREN}, the method adjusts nothing. 383 * This method is for handling case like {@code 384 * (condition && (condition 385 * || condition2 || condition3) && condition4 386 * && condition3) 387 * } 388 * 389 * @param node the node to adjust 390 * @param step the node transformer, should be {@link DetailAST#getPreviousSibling} 391 * or {@link DetailAST#getNextSibling} 392 * @return adjusted node 393 */ 394 private static DetailAST adjustParens(DetailAST node, UnaryOperator<DetailAST> step) { 395 DetailAST result = node; 396 int accumulator = 0; 397 while (true) { 398 if (result.getType() == TokenTypes.LPAREN) { 399 accumulator--; 400 } 401 else if (result.getType() == TokenTypes.RPAREN) { 402 accumulator++; 403 } 404 if (accumulator == 0) { 405 break; 406 } 407 result = step.apply(result); 408 } 409 return result; 410 } 411 412 }