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