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 * @param allowLineBreaks whether whitespace should be 163 * flagged at linebreaks. 164 */ 165 public void setAllowLineBreaks(boolean allowLineBreaks) { 166 this.allowLineBreaks = allowLineBreaks; 167 } 168 169 @Override 170 public void visitToken(DetailAST ast) { 171 final DetailAST whitespaceFollowedAst = getWhitespaceFollowedNode(ast); 172 173 if (shouldCheckWhitespaceAfter(whitespaceFollowedAst)) { 174 final int whitespaceColumnNo = getPositionAfter(whitespaceFollowedAst); 175 final int whitespaceLineNo = whitespaceFollowedAst.getLineNo(); 176 177 if (hasTrailingWhitespace(ast, whitespaceColumnNo, whitespaceLineNo)) { 178 log(ast, MSG_KEY, whitespaceFollowedAst.getText()); 179 } 180 } 181 } 182 183 /** 184 * For a visited ast node returns node that should be checked 185 * for not being followed by whitespace. 186 * @param ast 187 * , visited node. 188 * @return node before ast. 189 */ 190 private static DetailAST getWhitespaceFollowedNode(DetailAST ast) { 191 final DetailAST whitespaceFollowedAst; 192 switch (ast.getType()) { 193 case TokenTypes.TYPECAST: 194 whitespaceFollowedAst = ast.findFirstToken(TokenTypes.RPAREN); 195 break; 196 case TokenTypes.ARRAY_DECLARATOR: 197 whitespaceFollowedAst = getArrayDeclaratorPreviousElement(ast); 198 break; 199 case TokenTypes.INDEX_OP: 200 whitespaceFollowedAst = getIndexOpPreviousElement(ast); 201 break; 202 default: 203 whitespaceFollowedAst = ast; 204 } 205 return whitespaceFollowedAst; 206 } 207 208 /** 209 * Returns whether whitespace after a visited node should be checked. For example, whitespace 210 * is not allowed between a type and an array declarator (returns true), except when there is 211 * an annotation in between the type and array declarator (returns false). 212 * @param ast the visited node 213 * @return true if whitespace after ast should be checked 214 */ 215 private static boolean shouldCheckWhitespaceAfter(DetailAST ast) { 216 boolean checkWhitespace = true; 217 final DetailAST sibling = ast.getNextSibling(); 218 if (sibling != null) { 219 if (sibling.getType() == TokenTypes.ANNOTATIONS) { 220 checkWhitespace = false; 221 } 222 else if (sibling.getType() == TokenTypes.ARRAY_DECLARATOR) { 223 checkWhitespace = sibling.getFirstChild().getType() != TokenTypes.ANNOTATIONS; 224 } 225 } 226 return checkWhitespace; 227 } 228 229 /** 230 * Gets position after token (place of possible redundant whitespace). 231 * @param ast Node representing token. 232 * @return position after token. 233 */ 234 private static int getPositionAfter(DetailAST ast) { 235 final int after; 236 //If target of possible redundant whitespace is in method definition. 237 if (ast.getType() == TokenTypes.IDENT 238 && ast.getNextSibling() != null 239 && ast.getNextSibling().getType() == TokenTypes.LPAREN) { 240 final DetailAST methodDef = ast.getParent(); 241 final DetailAST endOfParams = methodDef.findFirstToken(TokenTypes.RPAREN); 242 after = endOfParams.getColumnNo() + 1; 243 } 244 else { 245 after = ast.getColumnNo() + ast.getText().length(); 246 } 247 return after; 248 } 249 250 /** 251 * Checks if there is unwanted whitespace after the visited node. 252 * @param ast 253 * , visited node. 254 * @param whitespaceColumnNo 255 * , column number of a possible whitespace. 256 * @param whitespaceLineNo 257 * , line number of a possible whitespace. 258 * @return true if whitespace found. 259 */ 260 private boolean hasTrailingWhitespace(DetailAST ast, 261 int whitespaceColumnNo, int whitespaceLineNo) { 262 final boolean result; 263 final int astLineNo = ast.getLineNo(); 264 final String line = getLine(astLineNo - 1); 265 if (astLineNo == whitespaceLineNo && whitespaceColumnNo < line.length()) { 266 result = Character.isWhitespace(line.charAt(whitespaceColumnNo)); 267 } 268 else { 269 result = !allowLineBreaks; 270 } 271 return result; 272 } 273 274 /** 275 * Returns proper argument for getPositionAfter method, it is a token after 276 * {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}, in can be {@link TokenTypes#RBRACK 277 * RBRACK}, {@link TokenTypes#IDENT IDENT} or an array type definition (literal). 278 * @param ast 279 * , {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} node. 280 * @return previous node by text order. 281 */ 282 private static DetailAST getArrayDeclaratorPreviousElement(DetailAST ast) { 283 final DetailAST previousElement; 284 final DetailAST firstChild = ast.getFirstChild(); 285 if (firstChild.getType() == TokenTypes.ARRAY_DECLARATOR) { 286 // second or higher array index 287 previousElement = firstChild.findFirstToken(TokenTypes.RBRACK); 288 } 289 else { 290 // first array index, is preceded with identifier or type 291 final DetailAST parent = getFirstNonArrayDeclaratorParent(ast); 292 switch (parent.getType()) { 293 // generics 294 case TokenTypes.TYPE_ARGUMENT: 295 final DetailAST wildcard = parent.findFirstToken(TokenTypes.WILDCARD_TYPE); 296 if (wildcard == null) { 297 // usual generic type argument like <char[]> 298 previousElement = getTypeLastNode(ast); 299 } 300 else { 301 // constructions with wildcard like <? extends String[]> 302 previousElement = getTypeLastNode(ast.getFirstChild()); 303 } 304 break; 305 // 'new' is a special case with its own subtree structure 306 case TokenTypes.LITERAL_NEW: 307 previousElement = getTypeLastNode(parent); 308 break; 309 // mundane array declaration, can be either java style or C style 310 case TokenTypes.TYPE: 311 previousElement = getPreviousNodeWithParentOfTypeAst(ast, parent); 312 break; 313 // i.e. boolean[].class 314 case TokenTypes.DOT: 315 previousElement = getTypeLastNode(ast); 316 break; 317 // java 8 method reference 318 case TokenTypes.METHOD_REF: 319 final DetailAST ident = getIdentLastToken(ast); 320 if (ident == null) { 321 //i.e. int[]::new 322 previousElement = ast.getFirstChild(); 323 } 324 else { 325 previousElement = ident; 326 } 327 break; 328 default: 329 throw new IllegalStateException("unexpected ast syntax " + parent); 330 } 331 } 332 return previousElement; 333 } 334 335 /** 336 * Gets previous node for {@link TokenTypes#INDEX_OP INDEX_OP} token 337 * for usage in getPositionAfter method, it is a simplified copy of 338 * getArrayDeclaratorPreviousElement method. 339 * @param ast 340 * , {@link TokenTypes#INDEX_OP INDEX_OP} node. 341 * @return previous node by text order. 342 */ 343 private static DetailAST getIndexOpPreviousElement(DetailAST ast) { 344 final DetailAST result; 345 final DetailAST firstChild = ast.getFirstChild(); 346 if (firstChild.getType() == TokenTypes.INDEX_OP) { 347 // second or higher array index 348 result = firstChild.findFirstToken(TokenTypes.RBRACK); 349 } 350 else { 351 final DetailAST ident = getIdentLastToken(ast); 352 if (ident == null) { 353 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 354 // construction like new int[]{1}[0] 355 if (rparen == null) { 356 final DetailAST lastChild = firstChild.getLastChild(); 357 result = lastChild.findFirstToken(TokenTypes.RCURLY); 358 } 359 // construction like ((byte[]) pixels)[0] 360 else { 361 result = rparen; 362 } 363 } 364 else { 365 result = ident; 366 } 367 } 368 return result; 369 } 370 371 /** 372 * Get node that owns {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} sequence. 373 * @param ast 374 * , {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} node. 375 * @return owner node. 376 */ 377 private static DetailAST getFirstNonArrayDeclaratorParent(DetailAST ast) { 378 DetailAST parent = ast.getParent(); 379 while (parent.getType() == TokenTypes.ARRAY_DECLARATOR) { 380 parent = parent.getParent(); 381 } 382 return parent; 383 } 384 385 /** 386 * Searches parameter node for a type node. 387 * Returns it or its last node if it has an extended structure. 388 * @param ast 389 * , subject node. 390 * @return type node. 391 */ 392 private static DetailAST getTypeLastNode(DetailAST ast) { 393 DetailAST result = ast.findFirstToken(TokenTypes.TYPE_ARGUMENTS); 394 if (result == null) { 395 result = getIdentLastToken(ast); 396 if (result == null) { 397 //primitive literal expected 398 result = ast.getFirstChild(); 399 } 400 } 401 else { 402 result = result.findFirstToken(TokenTypes.GENERIC_END); 403 } 404 return result; 405 } 406 407 /** 408 * Finds previous node by text order for an array declarator, 409 * which parent type is {@link TokenTypes#TYPE TYPE}. 410 * @param ast 411 * , array declarator node. 412 * @param parent 413 * , its parent node. 414 * @return previous node by text order. 415 */ 416 private static DetailAST getPreviousNodeWithParentOfTypeAst(DetailAST ast, DetailAST parent) { 417 final DetailAST previousElement; 418 final DetailAST ident = getIdentLastToken(parent.getParent()); 419 final DetailAST lastTypeNode = getTypeLastNode(ast); 420 // sometimes there are ident-less sentences 421 // i.e. "(Object[]) null", but in casual case should be 422 // checked whether ident or lastTypeNode has preceding position 423 // determining if it is java style or C style 424 if (ident == null || ident.getLineNo() > ast.getLineNo()) { 425 previousElement = lastTypeNode; 426 } 427 else if (ident.getLineNo() < ast.getLineNo()) { 428 previousElement = ident; 429 } 430 //ident and lastTypeNode lay on one line 431 else { 432 final int instanceOfSize = 13; 433 // +2 because ast has `[]` after the ident 434 if (ident.getColumnNo() >= ast.getColumnNo() + 2 435 // +13 because ident (at most 1 character) is followed by 436 // ' instanceof ' (12 characters) 437 || lastTypeNode.getColumnNo() >= ident.getColumnNo() + instanceOfSize) { 438 previousElement = lastTypeNode; 439 } 440 else { 441 previousElement = ident; 442 } 443 } 444 return previousElement; 445 } 446 447 /** 448 * Gets leftmost token of identifier. 449 * @param ast 450 * , token possibly possessing an identifier. 451 * @return leftmost token of identifier. 452 */ 453 private static DetailAST getIdentLastToken(DetailAST ast) { 454 final DetailAST result; 455 final DetailAST dot = ast.findFirstToken(TokenTypes.DOT); 456 // method call case 457 if (dot == null) { 458 final DetailAST methodCall = ast.findFirstToken(TokenTypes.METHOD_CALL); 459 if (methodCall == null) { 460 result = ast.findFirstToken(TokenTypes.IDENT); 461 } 462 else { 463 result = methodCall.findFirstToken(TokenTypes.RPAREN); 464 } 465 } 466 // qualified name case 467 else { 468 result = dot.getFirstChild().getNextSibling(); 469 } 470 return result; 471 } 472 473}