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