001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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 org.apache.commons.lang3.ArrayUtils; 023 024import com.puppycrawl.tools.checkstyle.api.Check; 025import com.puppycrawl.tools.checkstyle.api.DetailAST; 026import com.puppycrawl.tools.checkstyle.api.TokenTypes; 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 line breaks are allowed) all characters on the line after are 033 * whitespace. To forbid line breaks 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#BNOT BNOT}, 039 * {@link TokenTypes#DEC DEC}, 040 * {@link TokenTypes#DOT DOT}, 041 * {@link TokenTypes#INC INC}, 042 * {@link TokenTypes#LNOT LNOT}, 043 * {@link TokenTypes#UNARY_MINUS UNARY_MINUS}, 044 * {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}, 045 * {@link TokenTypes#UNARY_PLUS UNARY_PLUS}. It also supports the operator 046 * {@link TokenTypes#TYPECAST TYPECAST}. 047 * </p> 048 * <p> 049 * An example of how to configure the check is: 050 * </p> 051 * <pre> 052 * <module name="NoWhitespaceAfter"/> 053 * </pre> 054 * <p> An example of how to configure the check to forbid line breaks after 055 * a {@link TokenTypes#DOT DOT} token is: 056 * </p> 057 * <pre> 058 * <module name="NoWhitespaceAfter"> 059 * <property name="tokens" value="DOT"/> 060 * <property name="allowLineBreaks" value="false"/> 061 * </module> 062 * </pre> 063 * @author Rick Giles 064 * @author lkuehne 065 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 066 */ 067public class NoWhitespaceAfterCheck extends Check { 068 069 /** 070 * A key is pointing to the warning message text in "messages.properties" 071 * file. 072 */ 073 public static final String MSG_KEY = "ws.followed"; 074 075 /** Whether whitespace is allowed if the AST is at a linebreak. */ 076 private boolean allowLineBreaks = true; 077 078 @Override 079 public int[] getDefaultTokens() { 080 return new int[] { 081 TokenTypes.ARRAY_INIT, 082 TokenTypes.INC, 083 TokenTypes.DEC, 084 TokenTypes.UNARY_MINUS, 085 TokenTypes.UNARY_PLUS, 086 TokenTypes.BNOT, 087 TokenTypes.LNOT, 088 TokenTypes.DOT, 089 TokenTypes.ARRAY_DECLARATOR, 090 }; 091 } 092 093 @Override 094 public int[] getAcceptableTokens() { 095 return new int[] { 096 TokenTypes.ARRAY_INIT, 097 TokenTypes.INC, 098 TokenTypes.DEC, 099 TokenTypes.UNARY_MINUS, 100 TokenTypes.UNARY_PLUS, 101 TokenTypes.BNOT, 102 TokenTypes.LNOT, 103 TokenTypes.DOT, 104 TokenTypes.TYPECAST, 105 TokenTypes.ARRAY_DECLARATOR, 106 TokenTypes.GENERIC_START, 107 TokenTypes.GENERIC_END, 108 }; 109 } 110 111 @Override 112 public int[] getRequiredTokens() { 113 return ArrayUtils.EMPTY_INT_ARRAY; 114 } 115 116 @Override 117 public void visitToken(DetailAST ast) { 118 final DetailAST astNode = getPreceded(ast); 119 final String line = getLine(ast.getLineNo() - 1); 120 final int after = getPositionAfter(astNode); 121 122 if ((after >= line.length() || Character.isWhitespace(line.charAt(after))) 123 && hasRedundantWhitespace(line, after)) { 124 log(astNode.getLineNo(), after, 125 MSG_KEY, astNode.getText()); 126 } 127 } 128 129 /** 130 * Gets possible place where redundant whitespace could be. 131 * @param ast Node representing token. 132 * @return possible place of redundant whitespace. 133 */ 134 private static DetailAST getPreceded(DetailAST ast) { 135 DetailAST preceded; 136 137 switch (ast.getType()) { 138 case TokenTypes.TYPECAST: 139 preceded = ast.findFirstToken(TokenTypes.RPAREN); 140 break; 141 case TokenTypes.ARRAY_DECLARATOR: 142 preceded = getArrayTypeOrIdentifier(ast); 143 break; 144 default: 145 preceded = ast; 146 } 147 return preceded; 148 } 149 150 /** 151 * Gets position after token (place of possible redundant whitespace). 152 * @param ast Node representing token. 153 * @return position after token. 154 */ 155 private static int getPositionAfter(DetailAST ast) { 156 int after; 157 //If target of possible redundant whitespace is in method definition 158 if (ast.getType() == TokenTypes.IDENT 159 && ast.getNextSibling() != null 160 && ast.getNextSibling().getType() == TokenTypes.LPAREN) { 161 final DetailAST methodDef = ast.getParent(); 162 final DetailAST endOfParams = methodDef.findFirstToken(TokenTypes.RPAREN); 163 after = endOfParams.getColumnNo() + 1; 164 } 165 else { 166 after = ast.getColumnNo() + ast.getText().length(); 167 } 168 return after; 169 } 170 171 /** 172 * Gets target place of possible redundant whitespace (array's type or identifier) 173 * after which {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} is set. 174 * @param arrayDeclarator {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} 175 * @return target place before possible redundant whitespace. 176 */ 177 private static DetailAST getArrayTypeOrIdentifier(DetailAST arrayDeclarator) { 178 DetailAST typeOrIdent = arrayDeclarator; 179 if (isArrayInstantiation(arrayDeclarator)) { 180 typeOrIdent = arrayDeclarator.getParent().getFirstChild(); 181 } 182 else if (isMultiDimensionalArray(arrayDeclarator)) { 183 if (isCStyleMultiDimensionalArrayDeclaration(arrayDeclarator)) { 184 if (arrayDeclarator.getParent().getType() != TokenTypes.ARRAY_DECLARATOR) { 185 typeOrIdent = getArrayIdentifier(arrayDeclarator); 186 } 187 } 188 else { 189 DetailAST arrayIdentifier = arrayDeclarator.getFirstChild(); 190 while (arrayIdentifier != null) { 191 typeOrIdent = arrayIdentifier; 192 arrayIdentifier = arrayIdentifier.getFirstChild(); 193 } 194 } 195 } 196 else { 197 if (isCStyleArrayDeclaration(arrayDeclarator)) { 198 typeOrIdent = getArrayIdentifier(arrayDeclarator); 199 } 200 else { 201 if (isArrayUsedAsTypeForGenericBoundedWildcard(arrayDeclarator)) { 202 typeOrIdent = arrayDeclarator.getParent(); 203 } 204 else { 205 typeOrIdent = arrayDeclarator.getFirstChild(); 206 } 207 } 208 } 209 return typeOrIdent; 210 } 211 212 /** 213 * Gets array identifier, e.g.: 214 * <p> 215 * {@code 216 * int[] someArray; 217 * } 218 * </p> 219 * <p> 220 * someArray is identifier. 221 * </p> 222 * @param arrayDeclarator {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} 223 * @return array identifier. 224 */ 225 private static DetailAST getArrayIdentifier(DetailAST arrayDeclarator) { 226 return arrayDeclarator.getParent().getNextSibling(); 227 } 228 229 /** 230 * Checks if current array is multidimensional. 231 * @param arrayDeclaration {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} 232 * @return true if current array is multidimensional. 233 */ 234 private static boolean isMultiDimensionalArray(DetailAST arrayDeclaration) { 235 return arrayDeclaration.getParent().getType() == TokenTypes.ARRAY_DECLARATOR 236 || arrayDeclaration.getFirstChild().getType() == TokenTypes.ARRAY_DECLARATOR; 237 } 238 239 /** 240 * Checks if current array declaration is part of array instantiation. 241 * @param arrayDeclaration {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} 242 * @return true if current array declaration is part of array instantiation. 243 */ 244 private static boolean isArrayInstantiation(DetailAST arrayDeclaration) { 245 return arrayDeclaration.getParent().getType() == TokenTypes.LITERAL_NEW; 246 } 247 248 /** 249 * Checks if current array is used as type for generic bounded wildcard. 250 * <p> 251 * E.g. {@code <? extends String[]>} or {@code <? super Object[]>}. 252 * </p> 253 * @param arrayDeclarator {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} 254 * @return true if current array is used as type for generic bounded wildcard. 255 */ 256 private static boolean isArrayUsedAsTypeForGenericBoundedWildcard(DetailAST arrayDeclarator) { 257 final int firstChildType = arrayDeclarator.getFirstChild().getType(); 258 return firstChildType == TokenTypes.TYPE_UPPER_BOUNDS 259 || firstChildType == TokenTypes.TYPE_LOWER_BOUNDS; 260 } 261 262 /** 263 * Control whether whitespace is flagged at line breaks. 264 * @param allowLineBreaks whether whitespace should be 265 * flagged at line breaks. 266 */ 267 public void setAllowLineBreaks(boolean allowLineBreaks) { 268 this.allowLineBreaks = allowLineBreaks; 269 } 270 271 /** 272 * Checks if current array is declared in C style, e.g.: 273 * <p> 274 * {@code 275 * int array[] = { ... }; //C style 276 * } 277 * </p> 278 * <p> 279 * {@code 280 * int[] array = { ... }; //Java style 281 * } 282 * </p> 283 * @param arrayDeclaration {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} 284 * @return true if array is declared in C style 285 */ 286 private static boolean isCStyleArrayDeclaration(DetailAST arrayDeclaration) { 287 boolean result = false; 288 final DetailAST identifier = getArrayIdentifier(arrayDeclaration); 289 if (identifier != null) { 290 final int arrayDeclarationStart = arrayDeclaration.getColumnNo(); 291 final int identifierEnd = identifier.getColumnNo() + identifier.getText().length(); 292 result = arrayDeclarationStart == identifierEnd 293 || arrayDeclarationStart > identifierEnd; 294 } 295 return result; 296 } 297 298 /** 299 * Works with multidimensional arrays. 300 * @param arrayDeclaration {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} 301 * @return true if multidimensional array is declared in C style. 302 */ 303 private static boolean isCStyleMultiDimensionalArrayDeclaration(DetailAST arrayDeclaration) { 304 boolean result = false; 305 DetailAST parentArrayDeclaration = arrayDeclaration; 306 while (parentArrayDeclaration != null) { 307 if (parentArrayDeclaration.getParent() != null 308 && parentArrayDeclaration.getParent().getType() == TokenTypes.TYPE) { 309 result = isCStyleArrayDeclaration(parentArrayDeclaration); 310 } 311 parentArrayDeclaration = parentArrayDeclaration.getParent(); 312 } 313 return result; 314 } 315 316 /** 317 * Checks if current line has redundant whitespace after specified index. 318 * @param line line of java source. 319 * @param after specified index. 320 * @return true if line contains redundant whitespace. 321 */ 322 private boolean hasRedundantWhitespace(String line, int after) { 323 boolean result = !allowLineBreaks; 324 for (int i = after + 1; !result && i < line.length(); i++) { 325 if (!Character.isWhitespace(line.charAt(i))) { 326 result = true; 327 } 328 } 329 return result; 330 } 331}