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