001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 */ 302 private static DetailAST getArrayDeclaratorPreviousElement(DetailAST ast) { 303 final DetailAST previousElement; 304 final DetailAST firstChild = ast.getFirstChild(); 305 if (firstChild.getType() == TokenTypes.ARRAY_DECLARATOR) { 306 // second or higher array index 307 previousElement = firstChild.findFirstToken(TokenTypes.RBRACK); 308 } 309 else { 310 // first array index, is preceded with identifier or type 311 final DetailAST parent = getFirstNonArrayDeclaratorParent(ast); 312 switch (parent.getType()) { 313 // generics 314 case TokenTypes.TYPE_ARGUMENT: 315 final DetailAST wildcard = parent.findFirstToken(TokenTypes.WILDCARD_TYPE); 316 if (wildcard == null) { 317 // usual generic type argument like <char[]> 318 previousElement = getTypeLastNode(ast); 319 } 320 else { 321 // constructions with wildcard like <? extends String[]> 322 previousElement = getTypeLastNode(ast.getFirstChild()); 323 } 324 break; 325 // 'new' is a special case with its own subtree structure 326 case TokenTypes.LITERAL_NEW: 327 previousElement = getTypeLastNode(parent); 328 break; 329 // mundane array declaration, can be either java style or C style 330 case TokenTypes.TYPE: 331 previousElement = getPreviousNodeWithParentOfTypeAst(ast, parent); 332 break; 333 // i.e. boolean[].class 334 case TokenTypes.DOT: 335 previousElement = getTypeLastNode(ast); 336 break; 337 // java 8 method reference 338 case TokenTypes.METHOD_REF: 339 final DetailAST ident = getIdentLastToken(ast); 340 if (ident == null) { 341 // i.e. int[]::new 342 previousElement = ast.getFirstChild(); 343 } 344 else { 345 previousElement = ident; 346 } 347 break; 348 default: 349 throw new IllegalStateException("unexpected ast syntax " + parent); 350 } 351 } 352 return previousElement; 353 } 354 355 /** 356 * Gets previous node for {@link TokenTypes#INDEX_OP INDEX_OP} token 357 * for usage in getPositionAfter method, it is a simplified copy of 358 * getArrayDeclaratorPreviousElement method. 359 * 360 * @param ast 361 * , {@link TokenTypes#INDEX_OP INDEX_OP} node. 362 * @return previous node by text order. 363 */ 364 private static DetailAST getIndexOpPreviousElement(DetailAST ast) { 365 final DetailAST result; 366 final DetailAST firstChild = ast.getFirstChild(); 367 if (firstChild.getType() == TokenTypes.INDEX_OP) { 368 // second or higher array index 369 result = firstChild.findFirstToken(TokenTypes.RBRACK); 370 } 371 else { 372 final DetailAST ident = getIdentLastToken(ast); 373 if (ident == null) { 374 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 375 // construction like new int[]{1}[0] 376 if (rparen == null) { 377 final DetailAST lastChild = firstChild.getLastChild(); 378 result = lastChild.findFirstToken(TokenTypes.RCURLY); 379 } 380 // construction like ((byte[]) pixels)[0] 381 else { 382 result = rparen; 383 } 384 } 385 else { 386 result = ident; 387 } 388 } 389 return result; 390 } 391 392 /** 393 * Get node that owns {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} sequence. 394 * 395 * @param ast 396 * , {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} node. 397 * @return owner node. 398 */ 399 private static DetailAST getFirstNonArrayDeclaratorParent(DetailAST ast) { 400 DetailAST parent = ast.getParent(); 401 while (parent.getType() == TokenTypes.ARRAY_DECLARATOR) { 402 parent = parent.getParent(); 403 } 404 return parent; 405 } 406 407 /** 408 * Searches parameter node for a type node. 409 * Returns it or its last node if it has an extended structure. 410 * 411 * @param ast 412 * , subject node. 413 * @return type node. 414 */ 415 private static DetailAST getTypeLastNode(DetailAST ast) { 416 DetailAST result = ast.findFirstToken(TokenTypes.TYPE_ARGUMENTS); 417 if (result == null) { 418 result = getIdentLastToken(ast); 419 if (result == null) { 420 // primitive literal expected 421 result = ast.getFirstChild(); 422 } 423 } 424 else { 425 result = result.findFirstToken(TokenTypes.GENERIC_END); 426 } 427 return result; 428 } 429 430 /** 431 * Finds previous node by text order for an array declarator, 432 * which parent type is {@link TokenTypes#TYPE TYPE}. 433 * 434 * @param ast 435 * , array declarator node. 436 * @param parent 437 * , its parent node. 438 * @return previous node by text order. 439 */ 440 private static DetailAST getPreviousNodeWithParentOfTypeAst(DetailAST ast, DetailAST parent) { 441 final DetailAST previousElement; 442 final DetailAST ident = getIdentLastToken(parent.getParent()); 443 final DetailAST lastTypeNode = getTypeLastNode(ast); 444 // sometimes there are ident-less sentences 445 // i.e. "(Object[]) null", but in casual case should be 446 // checked whether ident or lastTypeNode has preceding position 447 // determining if it is java style or C style 448 if (ident == null || ident.getLineNo() > ast.getLineNo()) { 449 previousElement = lastTypeNode; 450 } 451 else if (ident.getLineNo() < ast.getLineNo()) { 452 previousElement = ident; 453 } 454 // ident and lastTypeNode lay on one line 455 else { 456 final int instanceOfSize = 13; 457 // +2 because ast has `[]` after the ident 458 if (ident.getColumnNo() >= ast.getColumnNo() + 2 459 // +13 because ident (at most 1 character) is followed by 460 // ' instanceof ' (12 characters) 461 || lastTypeNode.getColumnNo() >= ident.getColumnNo() + instanceOfSize) { 462 previousElement = lastTypeNode; 463 } 464 else { 465 previousElement = ident; 466 } 467 } 468 return previousElement; 469 } 470 471 /** 472 * Gets leftmost token of identifier. 473 * 474 * @param ast 475 * , token possibly possessing an identifier. 476 * @return leftmost token of identifier. 477 */ 478 private static DetailAST getIdentLastToken(DetailAST ast) { 479 final DetailAST result; 480 final DetailAST dot = ast.findFirstToken(TokenTypes.DOT); 481 // method call case 482 if (dot == null) { 483 final DetailAST methodCall = ast.findFirstToken(TokenTypes.METHOD_CALL); 484 if (methodCall == null) { 485 result = ast.findFirstToken(TokenTypes.IDENT); 486 } 487 else { 488 result = methodCall.findFirstToken(TokenTypes.RPAREN); 489 } 490 } 491 // qualified name case 492 else { 493 result = dot.getFirstChild().getNextSibling(); 494 } 495 return result; 496 } 497 498}