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