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