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