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