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