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