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