001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files 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.Optional; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 029 030/** 031 * <p> 032 * Checks that there is no whitespace after a token. 033 * More specifically, it checks that it is not followed by whitespace, 034 * or (if linebreaks are allowed) all characters on the line after are 035 * whitespace. To forbid linebreaks after a token, set property 036 * {@code allowLineBreaks} to {@code false}. 037 * </p> 038 * <p> 039 * The check processes 040 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ARRAY_DECLARATOR"> 041 * ARRAY_DECLARATOR</a> and 042 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INDEX_OP"> 043 * INDEX_OP</a> tokens specially from other tokens. Actually it is checked that 044 * there is no whitespace before these tokens, not after them. Space after the 045 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATIONS"> 046 * ANNOTATIONS</a> before 047 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ARRAY_DECLARATOR"> 048 * ARRAY_DECLARATOR</a> and 049 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INDEX_OP"> 050 * INDEX_OP</a> will be ignored. 051 * </p> 052 * <p> 053 * If the annotation is between the type and the array, the check will skip validation for spaces 054 * </p> 055 * <pre> 056 * public void foo(final char @NotNull [] param) {} // No violation 057 * </pre> 058 * <ul> 059 * <li> 060 * Property {@code allowLineBreaks} - Control whether whitespace is allowed 061 * if the token is at a linebreak. 062 * Type is {@code boolean}. 063 * Default value is {@code true}. 064 * </li> 065 * <li> 066 * Property {@code tokens} - tokens to check 067 * Type is {@code java.lang.String[]}. 068 * Validation type is {@code tokenSet}. 069 * Default value is: 070 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ARRAY_INIT"> 071 * ARRAY_INIT</a>, 072 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#AT"> 073 * AT</a>, 074 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INC"> 075 * INC</a>, 076 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DEC"> 077 * DEC</a>, 078 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_MINUS"> 079 * UNARY_MINUS</a>, 080 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_PLUS"> 081 * UNARY_PLUS</a>, 082 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BNOT"> 083 * BNOT</a>, 084 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LNOT"> 085 * LNOT</a>, 086 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DOT"> 087 * DOT</a>, 088 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ARRAY_DECLARATOR"> 089 * ARRAY_DECLARATOR</a>, 090 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INDEX_OP"> 091 * INDEX_OP</a>. 092 * </li> 093 * </ul> 094 * <p> 095 * To configure the check: 096 * </p> 097 * <pre> 098 * <module name="NoWhitespaceAfter"/> 099 * </pre> 100 * <p>Example:</p> 101 * <pre> 102 * class Test { 103 * 104 * public void lineBreak(String x) { 105 * Integer. 106 * parseInt(x); // Ok 107 * Integer.parseInt(x); // Ok 108 * } 109 * 110 * public void dotOperator(String s) { 111 * Integer.parseInt(s); // Ok 112 * Integer. parseInt(s); // violation, '.' is followed by whitespace 113 * } 114 * 115 * public void arrayDec() { 116 * int[] arr = arr; // Ok 117 * int [] arr = arr; // violation, int is followed by whitespace 118 * } 119 * 120 * public void bitwiseNot(int a) { 121 * a = ~ a; // violation '~' is followed by whitespace 122 * a = ~a; // Ok 123 * } 124 * } 125 * </pre> 126 * <p>To configure the check to forbid linebreaks after a DOT token: 127 * </p> 128 * <pre> 129 * <module name="NoWhitespaceAfter"> 130 * <property name="tokens" value="DOT"/> 131 * <property name="allowLineBreaks" value="false"/> 132 * </module> 133 * </pre> 134 * <p>Example:</p> 135 * <pre> 136 * class Test { 137 * 138 * public void lineBreak(String x) { 139 * Integer. 140 * parseInt(x); // violation, '.' is followed by whitespace 141 * Integer.parseInt(x); // Ok 142 * } 143 * 144 * public void dotOperator(String s) { 145 * Integer.parseInt(s); // Ok 146 * Integer. parseInt(s); // violation, '.' is followed by whitespace 147 * } 148 * 149 * public void arrayDec() { 150 * int[] arr = arr; // Ok 151 * int [] arr = arr; // Ok 152 * } 153 * 154 * public void bitwiseNot(int a) { 155 * a = ~ a; // Ok 156 * a = ~a; // Ok 157 * } 158 * } 159 * </pre> 160 * <p> 161 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 162 * </p> 163 * <p> 164 * Violation Message Keys: 165 * </p> 166 * <ul> 167 * <li> 168 * {@code ws.followed} 169 * </li> 170 * </ul> 171 * 172 * @since 3.0 173 */ 174@StatelessCheck 175public class NoWhitespaceAfterCheck extends AbstractCheck { 176 177 /** 178 * A key is pointing to the warning message text in "messages.properties" 179 * file. 180 */ 181 public static final String MSG_KEY = "ws.followed"; 182 183 /** Control whether whitespace is allowed if the token is at a linebreak. */ 184 private boolean allowLineBreaks = true; 185 186 @Override 187 public int[] getDefaultTokens() { 188 return new int[] { 189 TokenTypes.ARRAY_INIT, 190 TokenTypes.AT, 191 TokenTypes.INC, 192 TokenTypes.DEC, 193 TokenTypes.UNARY_MINUS, 194 TokenTypes.UNARY_PLUS, 195 TokenTypes.BNOT, 196 TokenTypes.LNOT, 197 TokenTypes.DOT, 198 TokenTypes.ARRAY_DECLARATOR, 199 TokenTypes.INDEX_OP, 200 }; 201 } 202 203 @Override 204 public int[] getAcceptableTokens() { 205 return new int[] { 206 TokenTypes.ARRAY_INIT, 207 TokenTypes.AT, 208 TokenTypes.INC, 209 TokenTypes.DEC, 210 TokenTypes.UNARY_MINUS, 211 TokenTypes.UNARY_PLUS, 212 TokenTypes.BNOT, 213 TokenTypes.LNOT, 214 TokenTypes.DOT, 215 TokenTypes.TYPECAST, 216 TokenTypes.ARRAY_DECLARATOR, 217 TokenTypes.INDEX_OP, 218 TokenTypes.LITERAL_SYNCHRONIZED, 219 TokenTypes.METHOD_REF, 220 }; 221 } 222 223 @Override 224 public int[] getRequiredTokens() { 225 return CommonUtil.EMPTY_INT_ARRAY; 226 } 227 228 /** 229 * Setter to control whether whitespace is allowed if the token is at a linebreak. 230 * 231 * @param allowLineBreaks whether whitespace should be 232 * flagged at linebreaks. 233 */ 234 public void setAllowLineBreaks(boolean allowLineBreaks) { 235 this.allowLineBreaks = allowLineBreaks; 236 } 237 238 @Override 239 public void visitToken(DetailAST ast) { 240 if (shouldCheckWhitespaceAfter(ast)) { 241 final DetailAST whitespaceFollowedAst = getWhitespaceFollowedNode(ast); 242 final int whitespaceColumnNo = getPositionAfter(whitespaceFollowedAst); 243 final int whitespaceLineNo = whitespaceFollowedAst.getLineNo(); 244 245 if (hasTrailingWhitespace(ast, whitespaceColumnNo, whitespaceLineNo)) { 246 log(ast, MSG_KEY, whitespaceFollowedAst.getText()); 247 } 248 } 249 } 250 251 /** 252 * For a visited ast node returns node that should be checked 253 * for not being followed by whitespace. 254 * 255 * @param ast 256 * , visited node. 257 * @return node before ast. 258 */ 259 private static DetailAST getWhitespaceFollowedNode(DetailAST ast) { 260 final DetailAST whitespaceFollowedAst; 261 switch (ast.getType()) { 262 case TokenTypes.TYPECAST: 263 whitespaceFollowedAst = ast.findFirstToken(TokenTypes.RPAREN); 264 break; 265 case TokenTypes.ARRAY_DECLARATOR: 266 whitespaceFollowedAst = getArrayDeclaratorPreviousElement(ast); 267 break; 268 case TokenTypes.INDEX_OP: 269 whitespaceFollowedAst = getIndexOpPreviousElement(ast); 270 break; 271 default: 272 whitespaceFollowedAst = ast; 273 } 274 return whitespaceFollowedAst; 275 } 276 277 /** 278 * Returns whether whitespace after a visited node should be checked. For example, whitespace 279 * is not allowed between a type and an array declarator (returns true), except when there is 280 * an annotation in between the type and array declarator (returns false). 281 * 282 * @param ast the visited node 283 * @return true if whitespace after ast should be checked 284 */ 285 private static boolean shouldCheckWhitespaceAfter(DetailAST ast) { 286 boolean checkWhitespace = true; 287 final DetailAST previousSibling = ast.getPreviousSibling(); 288 if (previousSibling != null && previousSibling.getType() == TokenTypes.ANNOTATIONS) { 289 checkWhitespace = false; 290 } 291 return checkWhitespace; 292 } 293 294 /** 295 * Gets position after token (place of possible redundant whitespace). 296 * 297 * @param ast Node representing token. 298 * @return position after token. 299 */ 300 private static int getPositionAfter(DetailAST ast) { 301 final int after; 302 // If target of possible redundant whitespace is in method definition. 303 if (ast.getType() == TokenTypes.IDENT 304 && ast.getNextSibling() != null 305 && ast.getNextSibling().getType() == TokenTypes.LPAREN) { 306 final DetailAST methodDef = ast.getParent(); 307 final DetailAST endOfParams = methodDef.findFirstToken(TokenTypes.RPAREN); 308 after = endOfParams.getColumnNo() + 1; 309 } 310 else { 311 after = ast.getColumnNo() + ast.getText().length(); 312 } 313 return after; 314 } 315 316 /** 317 * Checks if there is unwanted whitespace after the visited node. 318 * 319 * @param ast 320 * , visited node. 321 * @param whitespaceColumnNo 322 * , column number of a possible whitespace. 323 * @param whitespaceLineNo 324 * , line number of a possible whitespace. 325 * @return true if whitespace found. 326 */ 327 private boolean hasTrailingWhitespace(DetailAST ast, 328 int whitespaceColumnNo, int whitespaceLineNo) { 329 final boolean result; 330 final int astLineNo = ast.getLineNo(); 331 final int[] line = getLineCodePoints(astLineNo - 1); 332 if (astLineNo == whitespaceLineNo && whitespaceColumnNo < line.length) { 333 result = CommonUtil.isCodePointWhitespace(line, whitespaceColumnNo); 334 } 335 else { 336 result = !allowLineBreaks; 337 } 338 return result; 339 } 340 341 /** 342 * Returns proper argument for getPositionAfter method, it is a token after 343 * {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}, in can be {@link TokenTypes#RBRACK 344 * RBRACK}, {@link TokenTypes#IDENT IDENT} or an array type definition (literal). 345 * 346 * @param ast 347 * , {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} node. 348 * @return previous node by text order. 349 * @throws IllegalStateException if an unexpected token type is encountered. 350 */ 351 private static DetailAST getArrayDeclaratorPreviousElement(DetailAST ast) { 352 final DetailAST previousElement; 353 354 if (ast.getPreviousSibling() != null 355 && ast.getPreviousSibling().getType() == TokenTypes.ARRAY_DECLARATOR) { 356 // Covers higher dimension array declarations and initializations 357 previousElement = getPreviousElementOfMultiDimArray(ast); 358 } 359 else { 360 // first array index, is preceded with identifier or type 361 final DetailAST parent = ast.getParent(); 362 switch (parent.getType()) { 363 // generics 364 case TokenTypes.TYPE_UPPER_BOUNDS: 365 case TokenTypes.TYPE_LOWER_BOUNDS: 366 previousElement = ast.getPreviousSibling(); 367 break; 368 case TokenTypes.LITERAL_NEW: 369 case TokenTypes.TYPE_ARGUMENT: 370 case TokenTypes.DOT: 371 previousElement = getTypeLastNode(ast); 372 break; 373 // mundane array declaration, can be either java style or C style 374 case TokenTypes.TYPE: 375 previousElement = getPreviousNodeWithParentOfTypeAst(ast, parent); 376 break; 377 // java 8 method reference 378 case TokenTypes.METHOD_REF: 379 final DetailAST ident = getIdentLastToken(ast); 380 if (ident == null) { 381 // i.e. int[]::new 382 previousElement = ast.getParent().getFirstChild(); 383 } 384 else { 385 previousElement = ident; 386 } 387 break; 388 default: 389 throw new IllegalStateException("unexpected ast syntax " + parent); 390 } 391 } 392 return previousElement; 393 } 394 395 /** 396 * Gets the previous element of a second or higher dimension of an 397 * array declaration or initialization. 398 * 399 * @param leftBracket the token to get previous element of 400 * @return the previous element 401 */ 402 private static DetailAST getPreviousElementOfMultiDimArray(DetailAST leftBracket) { 403 final DetailAST previousRightBracket = leftBracket.getPreviousSibling().getLastChild(); 404 405 DetailAST ident = null; 406 // This will get us past the type ident, to the actual identifier 407 DetailAST parent = leftBracket.getParent().getParent(); 408 while (ident == null) { 409 ident = parent.findFirstToken(TokenTypes.IDENT); 410 parent = parent.getParent(); 411 } 412 413 final DetailAST previousElement; 414 if (ident.getColumnNo() > previousRightBracket.getColumnNo() 415 && ident.getColumnNo() < leftBracket.getColumnNo()) { 416 // C style and Java style ' int[] arr []' in same construct 417 previousElement = ident; 418 } 419 else { 420 // 'int[][] arr' or 'int arr[][]' 421 previousElement = previousRightBracket; 422 } 423 return previousElement; 424 } 425 426 /** 427 * Gets previous node for {@link TokenTypes#INDEX_OP INDEX_OP} token 428 * for usage in getPositionAfter method, it is a simplified copy of 429 * getArrayDeclaratorPreviousElement method. 430 * 431 * @param ast 432 * , {@link TokenTypes#INDEX_OP INDEX_OP} node. 433 * @return previous node by text order. 434 */ 435 private static DetailAST getIndexOpPreviousElement(DetailAST ast) { 436 final DetailAST result; 437 final DetailAST firstChild = ast.getFirstChild(); 438 if (firstChild.getType() == TokenTypes.INDEX_OP) { 439 // second or higher array index 440 result = firstChild.findFirstToken(TokenTypes.RBRACK); 441 } 442 else if (firstChild.getType() == TokenTypes.IDENT) { 443 result = firstChild; 444 } 445 else { 446 final DetailAST ident = getIdentLastToken(ast); 447 if (ident == null) { 448 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 449 // construction like new int[]{1}[0] 450 if (rparen == null) { 451 final DetailAST lastChild = firstChild.getLastChild(); 452 result = lastChild.findFirstToken(TokenTypes.RCURLY); 453 } 454 // construction like ((byte[]) pixels)[0] 455 else { 456 result = rparen; 457 } 458 } 459 else { 460 result = ident; 461 } 462 } 463 return result; 464 } 465 466 /** 467 * Searches parameter node for a type node. 468 * Returns it or its last node if it has an extended structure. 469 * 470 * @param ast 471 * , subject node. 472 * @return type node. 473 */ 474 private static DetailAST getTypeLastNode(DetailAST ast) { 475 final DetailAST typeLastNode; 476 final DetailAST parent = ast.getParent(); 477 final boolean isPrecededByTypeArgs = 478 parent.findFirstToken(TokenTypes.TYPE_ARGUMENTS) != null; 479 final Optional<DetailAST> objectArrayType = Optional.ofNullable(getIdentLastToken(ast)); 480 481 if (isPrecededByTypeArgs) { 482 typeLastNode = parent.findFirstToken(TokenTypes.TYPE_ARGUMENTS) 483 .findFirstToken(TokenTypes.GENERIC_END); 484 } 485 else if (objectArrayType.isPresent()) { 486 typeLastNode = objectArrayType.get(); 487 } 488 else { 489 typeLastNode = parent.getFirstChild(); 490 } 491 492 return typeLastNode; 493 } 494 495 /** 496 * Finds previous node by text order for an array declarator, 497 * which parent type is {@link TokenTypes#TYPE TYPE}. 498 * 499 * @param ast 500 * , array declarator node. 501 * @param parent 502 * , its parent node. 503 * @return previous node by text order. 504 */ 505 private static DetailAST getPreviousNodeWithParentOfTypeAst(DetailAST ast, DetailAST parent) { 506 final DetailAST previousElement; 507 final DetailAST ident = getIdentLastToken(parent.getParent()); 508 final DetailAST lastTypeNode = getTypeLastNode(ast); 509 final boolean isTypeCast = parent.getParent().getType() == TokenTypes.TYPECAST; 510 // sometimes there are ident-less sentences 511 // i.e. "(Object[]) null", but in casual case should be 512 // checked whether ident or lastTypeNode has preceding position 513 // determining if it is java style or C style 514 515 if (ident == null || isTypeCast || ident.getLineNo() > ast.getLineNo()) { 516 previousElement = lastTypeNode; 517 } 518 else if (ident.getLineNo() < ast.getLineNo()) { 519 previousElement = ident; 520 } 521 // ident and lastTypeNode lay on one line 522 else { 523 final int instanceOfSize = 13; 524 // +2 because ast has `[]` after the ident 525 if (ident.getColumnNo() >= ast.getColumnNo() + 2 526 // +13 because ident (at most 1 character) is followed by 527 // ' instanceof ' (12 characters) 528 || lastTypeNode.getColumnNo() >= ident.getColumnNo() + instanceOfSize) { 529 previousElement = lastTypeNode; 530 } 531 else { 532 previousElement = ident; 533 } 534 } 535 return previousElement; 536 } 537 538 /** 539 * Gets leftmost token of identifier. 540 * 541 * @param ast 542 * , token possibly possessing an identifier. 543 * @return leftmost token of identifier. 544 */ 545 private static DetailAST getIdentLastToken(DetailAST ast) { 546 final DetailAST result; 547 final DetailAST dot = getPrecedingDot(ast); 548 // method call case 549 if (dot == null || ast.getFirstChild().getType() == TokenTypes.METHOD_CALL) { 550 final DetailAST methodCall = ast.findFirstToken(TokenTypes.METHOD_CALL); 551 if (methodCall == null) { 552 result = ast.findFirstToken(TokenTypes.IDENT); 553 } 554 else { 555 result = methodCall.findFirstToken(TokenTypes.RPAREN); 556 } 557 } 558 // qualified name case 559 else { 560 result = dot.getFirstChild().getNextSibling(); 561 } 562 return result; 563 } 564 565 /** 566 * Gets the dot preceding a class member array index operation or class 567 * reference. 568 * 569 * @param leftBracket the ast we are checking 570 * @return dot preceding the left bracket 571 */ 572 private static DetailAST getPrecedingDot(DetailAST leftBracket) { 573 final DetailAST referencedClassDot = 574 leftBracket.getParent().findFirstToken(TokenTypes.DOT); 575 final DetailAST referencedMemberDot = leftBracket.findFirstToken(TokenTypes.DOT); 576 return Optional.ofNullable(referencedMemberDot).orElse(referencedClassDot); 577 578 } 579}