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