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 * Checks that a token is surrounded by whitespace. 030 * 031 * <p>By default the check will check the following operators: 032 * {@link TokenTypes#LITERAL_ASSERT ASSERT}, 033 * {@link TokenTypes#ASSIGN ASSIGN}, 034 * {@link TokenTypes#BAND BAND}, 035 * {@link TokenTypes#BAND_ASSIGN BAND_ASSIGN}, 036 * {@link TokenTypes#BOR BOR}, 037 * {@link TokenTypes#BOR_ASSIGN BOR_ASSIGN}, 038 * {@link TokenTypes#BSR BSR}, 039 * {@link TokenTypes#BSR_ASSIGN BSR_ASSIGN}, 040 * {@link TokenTypes#BXOR BXOR}, 041 * {@link TokenTypes#BXOR_ASSIGN BXOR_ASSIGN}, 042 * {@link TokenTypes#COLON COLON}, 043 * {@link TokenTypes#DIV DIV}, 044 * {@link TokenTypes#DIV_ASSIGN DIV_ASSIGN}, 045 * {@link TokenTypes#DO_WHILE DO_WHILE}, 046 * {@link TokenTypes#EQUAL EQUAL}, 047 * {@link TokenTypes#GE GE}, 048 * {@link TokenTypes#GT GT}, 049 * {@link TokenTypes#LAND LAND}, 050 * {@link TokenTypes#LCURLY LCURLY}, 051 * {@link TokenTypes#LE LE}, 052 * {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}, 053 * {@link TokenTypes#LITERAL_DO LITERAL_DO}, 054 * {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE}, 055 * {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY}, 056 * {@link TokenTypes#LITERAL_FOR LITERAL_FOR}, 057 * {@link TokenTypes#LITERAL_IF LITERAL_IF}, 058 * {@link TokenTypes#LITERAL_RETURN LITERAL_RETURN}, 059 * {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH}, 060 * {@link TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED}, 061 * {@link TokenTypes#LITERAL_TRY LITERAL_TRY}, 062 * {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}, 063 * {@link TokenTypes#LOR LOR}, 064 * {@link TokenTypes#LT LT}, 065 * {@link TokenTypes#MINUS MINUS}, 066 * {@link TokenTypes#MINUS_ASSIGN MINUS_ASSIGN}, 067 * {@link TokenTypes#MOD MOD}, 068 * {@link TokenTypes#MOD_ASSIGN MOD_ASSIGN}, 069 * {@link TokenTypes#NOT_EQUAL NOT_EQUAL}, 070 * {@link TokenTypes#PLUS PLUS}, 071 * {@link TokenTypes#PLUS_ASSIGN PLUS_ASSIGN}, 072 * {@link TokenTypes#QUESTION QUESTION}, 073 * {@link TokenTypes#RCURLY RCURLY}, 074 * {@link TokenTypes#SL SL}, 075 * {@link TokenTypes#SLIST SLIST}, 076 * {@link TokenTypes#SL_ASSIGN SL_ASSIGN}, 077 * {@link TokenTypes#SR SR}, 078 * {@link TokenTypes#SR_ASSIGN SR_ASSIGN}, 079 * {@link TokenTypes#STAR STAR}, 080 * {@link TokenTypes#STAR_ASSIGN STAR_ASSIGN}, 081 * {@link TokenTypes#LITERAL_ASSERT LITERAL_ASSERT}, 082 * {@link TokenTypes#TYPE_EXTENSION_AND TYPE_EXTENSION_AND}. 083 * 084 * <p>An example of how to configure the check is: 085 * 086 * <pre> 087 * <module name="WhitespaceAround"/> 088 * </pre> 089 * 090 * <p>An example of how to configure the check for whitespace only around 091 * assignment operators is: 092 * 093 * <pre> 094 * <module name="WhitespaceAround"> 095 * <property name="tokens" 096 * value="ASSIGN,DIV_ASSIGN,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,MOD_ASSIGN,SR_ASSIGN,BSR_ASSIGN,SL_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN,BAND_ASSIGN"/> 097 * </module> 098 * </pre> 099 * 100 * <p>In addition, this check can be configured to allow empty methods, types, 101 * for, while, do-while loops and constructor bodies. 102 * For example: 103 * 104 * <pre>{@code 105 * public MyClass() {} // empty constructor 106 * public void func() {} // empty method 107 * public interface Foo {} // empty interface 108 * public class Foo {} // empty class 109 * public enum Foo {} // empty enum 110 * MyClass c = new MyClass() {}; // empty anonymous class 111 * while (i = 1) {} // empty while loop 112 * for (int i = 1; i > 1; i++) {} // empty for loop 113 * do {} while (i = 1); // empty do-while loop 114 * public @interface Beta {} // empty annotation type 115 * }</pre> 116 * 117 * <p>To configure the check to allow empty method blocks use 118 * 119 * <pre> <property name="allowEmptyMethods" value="true" /></pre> 120 * 121 * <p>To configure the check to allow empty constructor blocks use 122 * 123 * <pre> <property name="allowEmptyConstructors" value="true" /></pre> 124 * 125 * <p>To configure the check to allow empty type blocks use 126 * 127 * <pre> <property name="allowEmptyTypes" value="true" /></pre> 128 * 129 * <p>To configure the check to allow empty loop blocks use 130 * 131 * <pre> <property name="allowEmptyLoops" value="true" /></pre> 132 * 133 * <p>Also, this check can be configured to ignore the colon in an enhanced for 134 * loop. The colon in an enhanced for loop is ignored by default 135 * 136 * <p>To configure the check to ignore the colon 137 * 138 * <pre> <property name="ignoreEnhancedForColon" value="true" /></pre> 139 * 140 * @author Oliver Burn 141 * @author maxvetrenko 142 * @author Andrei Selkin 143 */ 144public class WhitespaceAroundCheck extends Check { 145 146 /** 147 * A key is pointing to the warning message text in "messages.properties" 148 * file. 149 */ 150 public static final String WS_NOT_PRECEDED = "ws.notPreceded"; 151 152 /** 153 * A key is pointing to the warning message text in "messages.properties" 154 * file. 155 */ 156 public static final String WS_NOT_FOLLOWED = "ws.notFollowed"; 157 158 /** Whether or not empty constructor bodies are allowed. */ 159 private boolean allowEmptyConstructors; 160 /** Whether or not empty method bodies are allowed. */ 161 private boolean allowEmptyMethods; 162 /** Whether or not empty classes, enums and interfaces are allowed. */ 163 private boolean allowEmptyTypes; 164 /** Whether or not empty loops are allowed. */ 165 private boolean allowEmptyLoops; 166 /** Whether or not to ignore a colon in a enhanced for loop. */ 167 private boolean ignoreEnhancedForColon = true; 168 169 @Override 170 public int[] getDefaultTokens() { 171 return new int[] { 172 TokenTypes.ASSIGN, 173 TokenTypes.BAND, 174 TokenTypes.BAND_ASSIGN, 175 TokenTypes.BOR, 176 TokenTypes.BOR_ASSIGN, 177 TokenTypes.BSR, 178 TokenTypes.BSR_ASSIGN, 179 TokenTypes.BXOR, 180 TokenTypes.BXOR_ASSIGN, 181 TokenTypes.COLON, 182 TokenTypes.DIV, 183 TokenTypes.DIV_ASSIGN, 184 TokenTypes.DO_WHILE, 185 TokenTypes.EQUAL, 186 TokenTypes.GE, 187 TokenTypes.GT, 188 TokenTypes.LAND, 189 TokenTypes.LCURLY, 190 TokenTypes.LE, 191 TokenTypes.LITERAL_CATCH, 192 TokenTypes.LITERAL_DO, 193 TokenTypes.LITERAL_ELSE, 194 TokenTypes.LITERAL_FINALLY, 195 TokenTypes.LITERAL_FOR, 196 TokenTypes.LITERAL_IF, 197 TokenTypes.LITERAL_RETURN, 198 TokenTypes.LITERAL_SWITCH, 199 TokenTypes.LITERAL_SYNCHRONIZED, 200 TokenTypes.LITERAL_TRY, 201 TokenTypes.LITERAL_WHILE, 202 TokenTypes.LOR, 203 TokenTypes.LT, 204 TokenTypes.MINUS, 205 TokenTypes.MINUS_ASSIGN, 206 TokenTypes.MOD, 207 TokenTypes.MOD_ASSIGN, 208 TokenTypes.NOT_EQUAL, 209 TokenTypes.PLUS, 210 TokenTypes.PLUS_ASSIGN, 211 TokenTypes.QUESTION, 212 TokenTypes.RCURLY, 213 TokenTypes.SL, 214 TokenTypes.SLIST, 215 TokenTypes.SL_ASSIGN, 216 TokenTypes.SR, 217 TokenTypes.SR_ASSIGN, 218 TokenTypes.STAR, 219 TokenTypes.STAR_ASSIGN, 220 TokenTypes.LITERAL_ASSERT, 221 TokenTypes.TYPE_EXTENSION_AND, 222 }; 223 } 224 225 @Override 226 public int[] getAcceptableTokens() { 227 return new int[] { 228 TokenTypes.ASSIGN, 229 TokenTypes.BAND, 230 TokenTypes.BAND_ASSIGN, 231 TokenTypes.BOR, 232 TokenTypes.BOR_ASSIGN, 233 TokenTypes.BSR, 234 TokenTypes.BSR_ASSIGN, 235 TokenTypes.BXOR, 236 TokenTypes.BXOR_ASSIGN, 237 TokenTypes.COLON, 238 TokenTypes.DIV, 239 TokenTypes.DIV_ASSIGN, 240 TokenTypes.DO_WHILE, 241 TokenTypes.EQUAL, 242 TokenTypes.GE, 243 TokenTypes.GT, 244 TokenTypes.LAND, 245 TokenTypes.LCURLY, 246 TokenTypes.LE, 247 TokenTypes.LITERAL_CATCH, 248 TokenTypes.LITERAL_DO, 249 TokenTypes.LITERAL_ELSE, 250 TokenTypes.LITERAL_FINALLY, 251 TokenTypes.LITERAL_FOR, 252 TokenTypes.LITERAL_IF, 253 TokenTypes.LITERAL_RETURN, 254 TokenTypes.LITERAL_SWITCH, 255 TokenTypes.LITERAL_SYNCHRONIZED, 256 TokenTypes.LITERAL_TRY, 257 TokenTypes.LITERAL_WHILE, 258 TokenTypes.LOR, 259 TokenTypes.LT, 260 TokenTypes.MINUS, 261 TokenTypes.MINUS_ASSIGN, 262 TokenTypes.MOD, 263 TokenTypes.MOD_ASSIGN, 264 TokenTypes.NOT_EQUAL, 265 TokenTypes.PLUS, 266 TokenTypes.PLUS_ASSIGN, 267 TokenTypes.QUESTION, 268 TokenTypes.RCURLY, 269 TokenTypes.SL, 270 TokenTypes.SLIST, 271 TokenTypes.SL_ASSIGN, 272 TokenTypes.SR, 273 TokenTypes.SR_ASSIGN, 274 TokenTypes.STAR, 275 TokenTypes.STAR_ASSIGN, 276 TokenTypes.LITERAL_ASSERT, 277 TokenTypes.TYPE_EXTENSION_AND, 278 TokenTypes.WILDCARD_TYPE, 279 TokenTypes.GENERIC_START, 280 TokenTypes.GENERIC_END, 281 }; 282 } 283 284 @Override 285 public int[] getRequiredTokens() { 286 return ArrayUtils.EMPTY_INT_ARRAY; 287 } 288 289 /** 290 * Sets whether or not empty method bodies are allowed. 291 * @param allow {@code true} to allow empty method bodies. 292 */ 293 public void setAllowEmptyMethods(boolean allow) { 294 allowEmptyMethods = allow; 295 } 296 297 /** 298 * Sets whether or not empty constructor bodies are allowed. 299 * @param allow {@code true} to allow empty constructor bodies. 300 */ 301 public void setAllowEmptyConstructors(boolean allow) { 302 allowEmptyConstructors = allow; 303 } 304 305 /** 306 * Sets whether or not to ignore the whitespace around the 307 * colon in an enhanced for loop. 308 * @param ignore {@code true} to ignore enhanced for colon. 309 */ 310 public void setIgnoreEnhancedForColon(boolean ignore) { 311 ignoreEnhancedForColon = ignore; 312 } 313 314 /** 315 * Sets whether or not empty type bodies are allowed. 316 * @param allow {@code true} to allow empty type bodies. 317 */ 318 public void setAllowEmptyTypes(boolean allow) { 319 allowEmptyTypes = allow; 320 } 321 322 /** 323 * Sets whether or not empty loop bodies are allowed. 324 * @param allow {@code true} to allow empty loops bodies. 325 */ 326 public void setAllowEmptyLoops(boolean allow) { 327 allowEmptyLoops = allow; 328 } 329 330 @Override 331 public void visitToken(DetailAST ast) { 332 final int currentType = ast.getType(); 333 if (isNotRelevantSituation(ast, currentType)) { 334 return; 335 } 336 337 final String line = getLine(ast.getLineNo() - 1); 338 final int before = ast.getColumnNo() - 1; 339 final int after = ast.getColumnNo() + ast.getText().length(); 340 341 if (before >= 0 && !Character.isWhitespace(line.charAt(before))) { 342 log(ast.getLineNo(), ast.getColumnNo(), 343 WS_NOT_PRECEDED, ast.getText()); 344 } 345 346 if (after >= line.length()) { 347 return; 348 } 349 350 final char nextChar = line.charAt(after); 351 if (!Character.isWhitespace(nextChar) 352 // Check for "return;" 353 && !(currentType == TokenTypes.LITERAL_RETURN 354 && ast.getFirstChild().getType() == TokenTypes.SEMI) 355 && !isAnonymousInnerClassEnd(currentType, nextChar)) { 356 357 log(ast.getLineNo(), ast.getColumnNo() + ast.getText().length(), 358 WS_NOT_FOLLOWED, ast.getText()); 359 } 360 } 361 362 /** 363 * Check for "})" or "};" or "},". Happens with anon-inners 364 * @param currentType token 365 * @param nextChar next symbol 366 * @return true is that is end of anon inner class 367 */ 368 private static boolean isAnonymousInnerClassEnd(int currentType, char nextChar) { 369 return currentType == TokenTypes.RCURLY 370 && (nextChar == ')' 371 || nextChar == ';' 372 || nextChar == ',' 373 || nextChar == '.'); 374 } 375 376 /** 377 * Is ast not a target of Check. 378 * @param ast ast 379 * @param currentType type of ast 380 * @return true is ok to skip validation 381 */ 382 private boolean isNotRelevantSituation(DetailAST ast, int currentType) { 383 final int parentType = ast.getParent().getType(); 384 final boolean starImport = currentType == TokenTypes.STAR 385 && parentType == TokenTypes.DOT; 386 final boolean slistInsideCaseGroup = currentType == TokenTypes.SLIST 387 && parentType == TokenTypes.CASE_GROUP; 388 389 final boolean starImportOrSlistInsideCaseGroup = starImport || slistInsideCaseGroup; 390 final boolean colonOfCaseOrDefaultOrForEach = 391 isColonOfCaseOrDefault(currentType, parentType) 392 || isColonOfForEach(currentType, parentType); 393 final boolean emptyBlockOrType = isEmptyBlock(ast, parentType) 394 || allowEmptyTypes && isEmptyType(ast); 395 396 return starImportOrSlistInsideCaseGroup 397 || colonOfCaseOrDefaultOrForEach 398 || emptyBlockOrType 399 || isArrayInitialization(currentType, parentType); 400 } 401 402 /** 403 * Is empty block. 404 * @param ast ast 405 * @param parentType parent 406 * @return true is block is empty 407 */ 408 private boolean isEmptyBlock(DetailAST ast, int parentType) { 409 return isEmptyMethodBlock(ast, parentType) 410 || isEmptyCtorBlock(ast, parentType) 411 || isEmptyLoop(ast, parentType); 412 } 413 414 /** 415 * Tests if a given {@code DetailAST} is part of an empty block. 416 * An example empty block might look like the following 417 * <p> 418 * <pre> public void myMethod(int val) {}</pre> 419 * </p> 420 * In the above, the method body is an empty block ("{}"). 421 * 422 * @param ast the {@code DetailAST} to test. 423 * @param parentType the token type of {@code ast}'s parent. 424 * @param match the parent token type we're looking to match. 425 * @return {@code true} if {@code ast} makes up part of an 426 * empty block contained under a {@code match} token type 427 * node. 428 */ 429 private static boolean isEmptyBlock(DetailAST ast, int parentType, int match) { 430 final int type = ast.getType(); 431 if (type == TokenTypes.RCURLY) { 432 final DetailAST grandParent = ast.getParent().getParent(); 433 return parentType == TokenTypes.SLIST 434 && grandParent.getType() == match; 435 } 436 437 return type == TokenTypes.SLIST 438 && parentType == match 439 && ast.getFirstChild().getType() == TokenTypes.RCURLY; 440 } 441 442 /** 443 * Whether colon belongs to cases or defaults. 444 * @param currentType current 445 * @param parentType parent 446 * @return true if current token in colon of case or default tokens 447 */ 448 private static boolean isColonOfCaseOrDefault(int currentType, int parentType) { 449 return currentType == TokenTypes.COLON 450 && (parentType == TokenTypes.LITERAL_DEFAULT 451 || parentType == TokenTypes.LITERAL_CASE); 452 } 453 454 /** 455 * Whether colon belongs to for-each. 456 * @param currentType current 457 * @param parentType parent 458 * @return true if current token in colon of for-each token 459 */ 460 private boolean isColonOfForEach(int currentType, int parentType) { 461 return currentType == TokenTypes.COLON 462 && parentType == TokenTypes.FOR_EACH_CLAUSE 463 && ignoreEnhancedForColon; 464 } 465 466 /** 467 * Is array initialization. 468 * @param currentType current token 469 * @param parentType parent token 470 * @return true is current token inside array initialization 471 */ 472 private static boolean isArrayInitialization(int currentType, int parentType) { 473 return (currentType == TokenTypes.RCURLY 474 || currentType == TokenTypes.LCURLY) 475 && (parentType == TokenTypes.ARRAY_INIT 476 || parentType == TokenTypes.ANNOTATION_ARRAY_INIT); 477 } 478 479 /** 480 * Test if the given {@code DetailAST} is part of an allowed empty 481 * method block. 482 * @param ast the {@code DetailAST} to test. 483 * @param parentType the token type of {@code ast}'s parent. 484 * @return {@code true} if {@code ast} makes up part of an 485 * allowed empty method block. 486 */ 487 private boolean isEmptyMethodBlock(DetailAST ast, int parentType) { 488 return allowEmptyMethods 489 && isEmptyBlock(ast, parentType, TokenTypes.METHOD_DEF); 490 } 491 492 /** 493 * Test if the given {@code DetailAST} is part of an allowed empty 494 * constructor (ctor) block. 495 * @param ast the {@code DetailAST} to test. 496 * @param parentType the token type of {@code ast}'s parent. 497 * @return {@code true} if {@code ast} makes up part of an 498 * allowed empty constructor block. 499 */ 500 private boolean isEmptyCtorBlock(DetailAST ast, int parentType) { 501 return allowEmptyConstructors 502 && isEmptyBlock(ast, parentType, TokenTypes.CTOR_DEF); 503 } 504 505 /** 506 * 507 * @param ast ast the {@code DetailAST} to test. 508 * @param parentType the token type of {@code ast}'s parent. 509 * @return {@code true} if {@code ast} makes up part of an 510 * allowed empty loop block. 511 */ 512 private boolean isEmptyLoop(DetailAST ast, int parentType) { 513 return allowEmptyLoops 514 && (isEmptyBlock(ast, parentType, TokenTypes.LITERAL_FOR) 515 || isEmptyBlock(ast, 516 parentType, TokenTypes.LITERAL_WHILE) 517 || isEmptyBlock(ast, 518 parentType, TokenTypes.LITERAL_DO)); 519 } 520 521 /** 522 * Test if the given {@code DetailAST} is part of an empty block. 523 * An example empty block might look like the following 524 * <p> 525 * <pre> class Foo {}</pre> 526 * </p> 527 * 528 * @param ast ast the {@code DetailAST} to test. 529 * @return {@code true} if {@code ast} makes up part of an 530 * empty block contained under a {@code match} token type 531 * node. 532 */ 533 private static boolean isEmptyType(DetailAST ast) { 534 final int type = ast.getType(); 535 final DetailAST nextSibling = ast.getNextSibling(); 536 final DetailAST previousSibling = ast.getPreviousSibling(); 537 return type == TokenTypes.LCURLY 538 && nextSibling.getType() == TokenTypes.RCURLY 539 || type == TokenTypes.RCURLY 540 && previousSibling != null 541 && previousSibling.getType() == TokenTypes.LCURLY; 542 } 543}