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