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