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