001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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 * <p> 034 * Checks the policy on how to wrap lines on operators. 035 * </p> 036 * <ul> 037 * <li> 038 * Property {@code option} - Specify policy on how to wrap lines. 039 * Type is {@code com.puppycrawl.tools.checkstyle.checks.whitespace.WrapOption}. 040 * Default value is {@code nl}. 041 * </li> 042 * <li> 043 * Property {@code tokens} - tokens to check 044 * Type is {@code java.lang.String[]}. 045 * Validation type is {@code tokenSet}. 046 * Default value is: 047 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION"> 048 * QUESTION</a>, 049 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COLON"> 050 * COLON</a>, 051 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EQUAL"> 052 * EQUAL</a>, 053 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NOT_EQUAL"> 054 * NOT_EQUAL</a>, 055 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV"> 056 * DIV</a>, 057 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS"> 058 * PLUS</a>, 059 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS"> 060 * MINUS</a>, 061 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR"> 062 * STAR</a>, 063 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MOD"> 064 * MOD</a>, 065 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SR"> 066 * SR</a>, 067 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BSR"> 068 * BSR</a>, 069 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GE"> 070 * GE</a>, 071 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GT"> 072 * GT</a>, 073 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SL"> 074 * SL</a>, 075 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LE"> 076 * LE</a>, 077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LT"> 078 * LT</a>, 079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BXOR"> 080 * BXOR</a>, 081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BOR"> 082 * BOR</a>, 083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LOR"> 084 * LOR</a>, 085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BAND"> 086 * BAND</a>, 087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAND"> 088 * LAND</a>, 089 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPE_EXTENSION_AND"> 090 * TYPE_EXTENSION_AND</a>, 091 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_INSTANCEOF"> 092 * LITERAL_INSTANCEOF</a>. 093 * </li> 094 * </ul> 095 * <p> 096 * To configure the check: 097 * </p> 098 * <pre> 099 * <module name="OperatorWrap"/> 100 * </pre> 101 * <p> 102 * Example: 103 * </p> 104 * <pre> 105 * class Test { 106 * public static void main(String[] args) { 107 * String s = "Hello" + 108 * "World"; // violation, '+' should be on new line 109 * 110 * if (10 == 111 * 20) { // violation, '==' should be on new line. 112 * // body 113 * } 114 * if (10 115 * == 116 * 20) { // ok 117 * // body 118 * } 119 * 120 * int c = 10 / 121 * 5; // violation, '/' should be on new line. 122 * 123 * int d = c 124 * + 10; // ok 125 * } 126 * 127 * } 128 * </pre> 129 * <p> 130 * To configure the check for assignment operators at the end of a line: 131 * </p> 132 * <pre> 133 * <module name="OperatorWrap"> 134 * <property name="tokens" 135 * value="ASSIGN,DIV_ASSIGN,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,MOD_ASSIGN, 136 * SR_ASSIGN,BSR_ASSIGN,SL_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN,BAND_ASSIGN"/> 137 * <property name="option" value="eol"/> 138 * </module> 139 * </pre> 140 * <p> 141 * Example: 142 * </p> 143 * <pre> 144 * class Test { 145 * public static void main(String[] args) { 146 * int b 147 * = 10; // violation, '=' should be on previous line 148 * int c = 149 * 10; // ok 150 * b 151 * += 10; // violation, '+=' should be on previous line 152 * b += 153 * 10; // ok 154 * c 155 * *= 10; // violation, '*=' should be on previous line 156 * c *= 157 * 10; // ok 158 * c 159 * -= 5; // violation, '-=' should be on previous line 160 * c -= 161 * 5; // ok 162 * c 163 * /= 2; // violation, '/=' should be on previous line 164 * c 165 * %= 1; // violation, '%=' should be on previous line 166 * c 167 * >>= 1; // violation, '>>=' should be on previous line 168 * c 169 * >>>= 1; // violation, '>>>=' should be on previous line 170 * } 171 * public void myFunction() { 172 * c 173 * ^= 1; // violation, '^=' should be on previous line 174 * c 175 * |= 1; // violation, '|=' should be on previous line 176 * c 177 * &=1 ; // violation, '&=' should be on previous line 178 * c 179 * <<= 1; // violation, '<<=' should be on previous line 180 * } 181 * } 182 * </pre> 183 * <p> 184 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 185 * </p> 186 * <p> 187 * Violation Message Keys: 188 * </p> 189 * <ul> 190 * <li> 191 * {@code line.new} 192 * </li> 193 * <li> 194 * {@code line.previous} 195 * </li> 196 * </ul> 197 * 198 * @since 3.0 199 */ 200@StatelessCheck 201public class OperatorWrapCheck 202 extends AbstractCheck { 203 204 /** 205 * A key is pointing to the warning message text in "messages.properties" 206 * file. 207 */ 208 public static final String MSG_LINE_NEW = "line.new"; 209 210 /** 211 * A key is pointing to the warning message text in "messages.properties" 212 * file. 213 */ 214 public static final String MSG_LINE_PREVIOUS = "line.previous"; 215 216 /** Specify policy on how to wrap lines. */ 217 private WrapOption option = WrapOption.NL; 218 219 /** 220 * Setter to specify policy on how to wrap lines. 221 * 222 * @param optionStr string to decode option from 223 * @throws IllegalArgumentException if unable to decode 224 */ 225 public void setOption(String optionStr) { 226 option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 227 } 228 229 @Override 230 public int[] getDefaultTokens() { 231 return new int[] { 232 TokenTypes.QUESTION, // '?' 233 TokenTypes.COLON, // ':' (not reported for a case) 234 TokenTypes.EQUAL, // "==" 235 TokenTypes.NOT_EQUAL, // "!=" 236 TokenTypes.DIV, // '/' 237 TokenTypes.PLUS, // '+' (unary plus is UNARY_PLUS) 238 TokenTypes.MINUS, // '-' (unary minus is UNARY_MINUS) 239 TokenTypes.STAR, // '*' 240 TokenTypes.MOD, // '%' 241 TokenTypes.SR, // ">>" 242 TokenTypes.BSR, // ">>>" 243 TokenTypes.GE, // ">=" 244 TokenTypes.GT, // ">" 245 TokenTypes.SL, // "<<" 246 TokenTypes.LE, // "<=" 247 TokenTypes.LT, // '<' 248 TokenTypes.BXOR, // '^' 249 TokenTypes.BOR, // '|' 250 TokenTypes.LOR, // "||" 251 TokenTypes.BAND, // '&' 252 TokenTypes.LAND, // "&&" 253 TokenTypes.TYPE_EXTENSION_AND, 254 TokenTypes.LITERAL_INSTANCEOF, 255 }; 256 } 257 258 @Override 259 public int[] getAcceptableTokens() { 260 return new int[] { 261 TokenTypes.QUESTION, // '?' 262 TokenTypes.COLON, // ':' (not reported for a case) 263 TokenTypes.EQUAL, // "==" 264 TokenTypes.NOT_EQUAL, // "!=" 265 TokenTypes.DIV, // '/' 266 TokenTypes.PLUS, // '+' (unary plus is UNARY_PLUS) 267 TokenTypes.MINUS, // '-' (unary minus is UNARY_MINUS) 268 TokenTypes.STAR, // '*' 269 TokenTypes.MOD, // '%' 270 TokenTypes.SR, // ">>" 271 TokenTypes.BSR, // ">>>" 272 TokenTypes.GE, // ">=" 273 TokenTypes.GT, // ">" 274 TokenTypes.SL, // "<<" 275 TokenTypes.LE, // "<=" 276 TokenTypes.LT, // '<' 277 TokenTypes.BXOR, // '^' 278 TokenTypes.BOR, // '|' 279 TokenTypes.LOR, // "||" 280 TokenTypes.BAND, // '&' 281 TokenTypes.LAND, // "&&" 282 TokenTypes.LITERAL_INSTANCEOF, 283 TokenTypes.TYPE_EXTENSION_AND, 284 TokenTypes.ASSIGN, // '=' 285 TokenTypes.DIV_ASSIGN, // "/=" 286 TokenTypes.PLUS_ASSIGN, // "+=" 287 TokenTypes.MINUS_ASSIGN, // "-=" 288 TokenTypes.STAR_ASSIGN, // "*=" 289 TokenTypes.MOD_ASSIGN, // "%=" 290 TokenTypes.SR_ASSIGN, // ">>=" 291 TokenTypes.BSR_ASSIGN, // ">>>=" 292 TokenTypes.SL_ASSIGN, // "<<=" 293 TokenTypes.BXOR_ASSIGN, // "^=" 294 TokenTypes.BOR_ASSIGN, // "|=" 295 TokenTypes.BAND_ASSIGN, // "&=" 296 TokenTypes.METHOD_REF, // "::" 297 }; 298 } 299 300 @Override 301 public int[] getRequiredTokens() { 302 return CommonUtil.EMPTY_INT_ARRAY; 303 } 304 305 @Override 306 public void visitToken(DetailAST ast) { 307 if (isTargetNode(ast)) { 308 if (option == WrapOption.NL && isNewLineModeViolation(ast)) { 309 log(ast, MSG_LINE_NEW, ast.getText()); 310 } 311 else if (option == WrapOption.EOL && isEndOfLineModeViolation(ast)) { 312 log(ast, MSG_LINE_PREVIOUS, ast.getText()); 313 } 314 } 315 } 316 317 /** 318 * Filters some false tokens that this check should ignore. 319 * 320 * @param node the node to check 321 * @return {@code true} for all nodes this check should validate 322 */ 323 private static boolean isTargetNode(DetailAST node) { 324 final boolean result; 325 if (node.getType() == TokenTypes.COLON) { 326 result = !isColonFromLabel(node); 327 } 328 else if (node.getType() == TokenTypes.STAR) { 329 // Unlike the import statement, the multiply operator always has children 330 result = node.hasChildren(); 331 } 332 else { 333 result = true; 334 } 335 return result; 336 } 337 338 /** 339 * Checks whether operator violates {@link WrapOption#NL} mode. 340 * 341 * @param ast the DetailAst of an operator 342 * @return {@code true} if mode does not match 343 */ 344 private static boolean isNewLineModeViolation(DetailAST ast) { 345 return TokenUtil.areOnSameLine(ast, getLeftNode(ast)) 346 && !TokenUtil.areOnSameLine(ast, getRightNode(ast)); 347 } 348 349 /** 350 * Checks whether operator violates {@link WrapOption#EOL} mode. 351 * 352 * @param ast the DetailAst of an operator 353 * @return {@code true} if mode does not match 354 */ 355 private static boolean isEndOfLineModeViolation(DetailAST ast) { 356 return !TokenUtil.areOnSameLine(ast, getLeftNode(ast)); 357 } 358 359 /** 360 * Checks if a node is {@link TokenTypes#COLON} from a label, switch case of default. 361 * 362 * @param node the node to check 363 * @return {@code true} if node matches 364 */ 365 private static boolean isColonFromLabel(DetailAST node) { 366 return TokenUtil.isOfType(node.getParent(), TokenTypes.LABELED_STAT, 367 TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT); 368 } 369 370 /** 371 * Checks if a node is {@link TokenTypes#ASSIGN} to a variable or resource. 372 * 373 * @param node the node to check 374 * @return {@code true} if node matches 375 */ 376 private static boolean isAssignToVariable(DetailAST node) { 377 return TokenUtil.isOfType(node.getParent(), TokenTypes.VARIABLE_DEF, TokenTypes.RESOURCE); 378 } 379 380 /** 381 * Returns the left neighbour of a binary operator. This is the rightmost 382 * grandchild of the left child or sibling. For the assign operator the return value is 383 * the variable name. 384 * 385 * @param node the binary operator 386 * @return nearest node from left 387 */ 388 private static DetailAST getLeftNode(DetailAST node) { 389 DetailAST result; 390 if (node.getFirstChild() == null || isAssignToVariable(node)) { 391 result = node.getPreviousSibling(); 392 } 393 else if (isInPatternDefinition(node)) { 394 result = node.getFirstChild(); 395 } 396 else { 397 result = adjustParens(node.getFirstChild(), DetailAST::getNextSibling); 398 } 399 while (result.getLastChild() != null) { 400 result = result.getLastChild(); 401 } 402 return result; 403 } 404 405 /** 406 * Ascends AST to determine if given node is part of a pattern 407 * definition. 408 * 409 * @param node the node to check 410 * @return true if node is in pattern definition 411 */ 412 private static boolean isInPatternDefinition(DetailAST node) { 413 DetailAST parent = node; 414 final int[] tokensToStopOn = { 415 // token we are looking for 416 TokenTypes.PATTERN_DEF, 417 // tokens that mean we can stop looking 418 TokenTypes.EXPR, 419 TokenTypes.RESOURCE, 420 TokenTypes.COMPILATION_UNIT, 421 }; 422 423 do { 424 parent = parent.getParent(); 425 } while (!TokenUtil.isOfType(parent, tokensToStopOn)); 426 return parent.getType() == TokenTypes.PATTERN_DEF; 427 } 428 429 /** 430 * Returns the right neighbour of a binary operator. This is the leftmost 431 * grandchild of the right child or sibling. For the ternary operator this 432 * is the node between {@code ?} and {@code :} . 433 * 434 * @param node the binary operator 435 * @return nearest node from right 436 */ 437 private static DetailAST getRightNode(DetailAST node) { 438 DetailAST result; 439 if (node.getLastChild() == null) { 440 result = node.getNextSibling(); 441 } 442 else { 443 final DetailAST rightNode; 444 if (node.getType() == TokenTypes.QUESTION) { 445 rightNode = node.findFirstToken(TokenTypes.COLON).getPreviousSibling(); 446 } 447 else { 448 rightNode = node.getLastChild(); 449 } 450 result = adjustParens(rightNode, DetailAST::getPreviousSibling); 451 } 452 453 if (!TokenUtil.isOfType(result, TokenTypes.ARRAY_INIT, TokenTypes.ANNOTATION_ARRAY_INIT)) { 454 while (result.getFirstChild() != null) { 455 result = result.getFirstChild(); 456 } 457 } 458 return result; 459 } 460 461 /** 462 * Finds matching parentheses among siblings. If the given node is not 463 * {@link TokenTypes#LPAREN} nor {@link TokenTypes#RPAREN}, the method adjusts nothing. 464 * This method is for handling case like {@code 465 * (condition && (condition 466 * || condition2 || condition3) && condition4 467 * && condition3) 468 * } 469 * 470 * @param node the node to adjust 471 * @param step the node transformer, should be {@link DetailAST#getPreviousSibling} 472 * or {@link DetailAST#getNextSibling} 473 * @return adjusted node 474 */ 475 private static DetailAST adjustParens(DetailAST node, UnaryOperator<DetailAST> step) { 476 DetailAST result = node; 477 int accumulator = 0; 478 while (true) { 479 if (result.getType() == TokenTypes.LPAREN) { 480 accumulator--; 481 } 482 else if (result.getType() == TokenTypes.RPAREN) { 483 accumulator++; 484 } 485 if (accumulator == 0) { 486 break; 487 } 488 result = step.apply(result); 489 } 490 return result; 491 } 492 493}