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.blocks; 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 for braces around code blocks. 031 * </p> 032 * <p> By default the check will check the following blocks: 033 * {@link TokenTypes#LITERAL_DO LITERAL_DO}, 034 * {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE}, 035 * {@link TokenTypes#LITERAL_FOR LITERAL_FOR}, 036 * {@link TokenTypes#LITERAL_IF LITERAL_IF}, 037 * {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}. 038 * </p> 039 * <p> 040 * An example of how to configure the check is: 041 * </p> 042 * <pre> 043 * <module name="NeedBraces"/> 044 * </pre> 045 * <p> An example of how to configure the check for {@code if} and 046 * {@code else} blocks is: 047 * </p> 048 * <pre> 049 * <module name="NeedBraces"> 050 * <property name="tokens" value="LITERAL_IF, LITERAL_ELSE"/> 051 * </module> 052 * </pre> 053 * Check has the following options: 054 * <p><b>allowSingleLineStatement</b> which allows single-line statements without braces, e.g.:</p> 055 * <p> 056 * {@code 057 * if (obj.isValid()) return true; 058 * } 059 * </p> 060 * <p> 061 * {@code 062 * while (obj.isValid()) return true; 063 * } 064 * </p> 065 * <p> 066 * {@code 067 * do this.notify(); while (o != null); 068 * } 069 * </p> 070 * <p> 071 * {@code 072 * for (int i = 0; ; ) this.notify(); 073 * } 074 * </p> 075 * <p><b>allowEmptyLoopBody</b> which allows loops with empty bodies, e.g.:</p> 076 * <p> 077 * {@code 078 * while (value.incrementValue() < 5); 079 * } 080 * </p> 081 * <p> 082 * {@code 083 * for(int i = 0; i < 10; value.incrementValue()); 084 * } 085 * </p> 086 * <p>Default value for allowEmptyLoopBody option is <b>false</b>.</p> 087 * <p> 088 * To configure the Check to allow {@code case, default} single-line statements 089 * without braces: 090 * </p> 091 * 092 * <pre> 093 * <module name="NeedBraces"> 094 * <property name="tokens" value="LITERAL_CASE, LITERAL_DEFAULT"/> 095 * <property name="allowSingleLineStatement" value="true"/> 096 * </module> 097 * </pre> 098 * 099 * <p> 100 * Such statements would be allowed: 101 * </p> 102 * 103 * <pre> 104 * {@code 105 * switch (num) { 106 * case 1: counter++; break; // OK 107 * case 6: counter += 10; break; // OK 108 * default: counter = 100; break; // OK 109 * } 110 * } 111 * </pre> 112 * <p> 113 * To configure the Check to allow {@code while, for} loops with empty bodies: 114 * </p> 115 * 116 * <pre> 117 * <module name="NeedBraces"> 118 * <property name="allowEmptyLoopBody" value="true"/> 119 * </module> 120 * </pre> 121 * 122 * <p> 123 * Such statements would be allowed: 124 * </p> 125 * 126 * <pre> 127 * {@code 128 * while (value.incrementValue() < 5); // OK 129 * for(int i = 0; i < 10; value.incrementValue()); // OK 130 * } 131 * </pre> 132 * 133 * @author Rick Giles 134 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 135 * @author <a href="mailto:andreyselkin@gmail.com">Andrei Selkin</a> 136 */ 137public class NeedBracesCheck extends Check { 138 /** 139 * A key is pointing to the warning message text in "messages.properties" 140 * file. 141 */ 142 public static final String MSG_KEY_NEED_BRACES = "needBraces"; 143 144 /** 145 * Check's option for skipping single-line statements. 146 */ 147 private boolean allowSingleLineStatement; 148 149 /** 150 * Check's option for allowing loops with empty body. 151 */ 152 private boolean allowEmptyLoopBody; 153 154 /** 155 * Setter. 156 * @param allowSingleLineStatement Check's option for skipping single-line statements 157 */ 158 public void setAllowSingleLineStatement(boolean allowSingleLineStatement) { 159 this.allowSingleLineStatement = allowSingleLineStatement; 160 } 161 162 /** 163 * Sets whether to allow empty loop body. 164 * @param allowEmptyLoopBody Check's option for allowing loops with empty body. 165 */ 166 public void setAllowEmptyLoopBody(boolean allowEmptyLoopBody) { 167 this.allowEmptyLoopBody = allowEmptyLoopBody; 168 } 169 170 @Override 171 public int[] getDefaultTokens() { 172 return new int[] { 173 TokenTypes.LITERAL_DO, 174 TokenTypes.LITERAL_ELSE, 175 TokenTypes.LITERAL_FOR, 176 TokenTypes.LITERAL_IF, 177 TokenTypes.LITERAL_WHILE, 178 }; 179 } 180 181 @Override 182 public int[] getAcceptableTokens() { 183 return new int[] { 184 TokenTypes.LITERAL_DO, 185 TokenTypes.LITERAL_ELSE, 186 TokenTypes.LITERAL_FOR, 187 TokenTypes.LITERAL_IF, 188 TokenTypes.LITERAL_WHILE, 189 TokenTypes.LITERAL_CASE, 190 TokenTypes.LITERAL_DEFAULT, 191 TokenTypes.LAMBDA, 192 }; 193 } 194 195 @Override 196 public int[] getRequiredTokens() { 197 return ArrayUtils.EMPTY_INT_ARRAY; 198 } 199 200 @Override 201 public void visitToken(DetailAST ast) { 202 final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST); 203 boolean isElseIf = false; 204 if (ast.getType() == TokenTypes.LITERAL_ELSE 205 && ast.findFirstToken(TokenTypes.LITERAL_IF) != null) { 206 isElseIf = true; 207 } 208 209 final boolean skipStatement = isSkipStatement(ast); 210 final boolean skipEmptyLoopBody = allowEmptyLoopBody && isEmptyLoopBody(ast); 211 212 if (slistAST == null && !isElseIf && !skipStatement && !skipEmptyLoopBody) { 213 log(ast.getLineNo(), MSG_KEY_NEED_BRACES, ast.getText()); 214 } 215 } 216 217 /** 218 * Checks if current statement can be skipped by "need braces" warning. 219 * @param statement if, for, while, do-while, lambda, else, case, default statements. 220 * @return true if current statement can be skipped by Check. 221 */ 222 private boolean isSkipStatement(DetailAST statement) { 223 return allowSingleLineStatement && isSingleLineStatement(statement); 224 } 225 226 /** 227 * Checks if current loop statement does not have body, e.g.: 228 * <p> 229 * {@code 230 * while (value.incrementValue() < 5); 231 * ... 232 * for(int i = 0; i < 10; value.incrementValue()); 233 * } 234 * </p> 235 * @param ast ast token. 236 * @return true if current loop statement does not have body. 237 */ 238 private boolean isEmptyLoopBody(DetailAST ast) { 239 boolean noBodyLoop = false; 240 241 if (ast.getType() == TokenTypes.LITERAL_FOR 242 || ast.getType() == TokenTypes.LITERAL_WHILE) { 243 DetailAST currentToken = ast.getFirstChild(); 244 while (currentToken.getNextSibling() != null) { 245 currentToken = currentToken.getNextSibling(); 246 } 247 noBodyLoop = currentToken.getType() == TokenTypes.EMPTY_STAT; 248 } 249 return noBodyLoop; 250 } 251 252 /** 253 * Checks if current statement is single-line statement, e.g.: 254 * <p> 255 * {@code 256 * if (obj.isValid()) return true; 257 * } 258 * </p> 259 * <p> 260 * {@code 261 * while (obj.isValid()) return true; 262 * } 263 * </p> 264 * @param statement if, for, while, do-while, lambda, else, case, default statements. 265 * @return true if current statement is single-line statement. 266 */ 267 private static boolean isSingleLineStatement(DetailAST statement) { 268 boolean result; 269 270 switch (statement.getType()) { 271 case TokenTypes.LITERAL_IF: 272 result = isSingleLineIf(statement); 273 break; 274 case TokenTypes.LITERAL_FOR: 275 result = isSingleLineFor(statement); 276 break; 277 case TokenTypes.LITERAL_DO: 278 result = isSingleLineDoWhile(statement); 279 break; 280 case TokenTypes.LITERAL_WHILE: 281 result = isSingleLineWhile(statement); 282 break; 283 case TokenTypes.LAMBDA: 284 result = isSingleLineLambda(statement); 285 break; 286 case TokenTypes.LITERAL_CASE: 287 result = isSingleLineCase(statement); 288 break; 289 case TokenTypes.LITERAL_DEFAULT: 290 result = isSingleLineDefault(statement); 291 break; 292 default: 293 result = isSingleLineElse(statement); 294 break; 295 } 296 297 return result; 298 } 299 300 /** 301 * Checks if current while statement is single-line statement, e.g.: 302 * <p> 303 * {@code 304 * while (obj.isValid()) return true; 305 * } 306 * </p> 307 * @param literalWhile {@link TokenTypes#LITERAL_WHILE while statement}. 308 * @return true if current while statement is single-line statement. 309 */ 310 private static boolean isSingleLineWhile(DetailAST literalWhile) { 311 boolean result = false; 312 if (literalWhile.getParent().getType() == TokenTypes.SLIST 313 && literalWhile.getLastChild().getType() != TokenTypes.SLIST) { 314 final DetailAST block = literalWhile.getLastChild().getPreviousSibling(); 315 result = literalWhile.getLineNo() == block.getLineNo(); 316 } 317 return result; 318 } 319 320 /** 321 * Checks if current do-while statement is single-line statement, e.g.: 322 * <p> 323 * {@code 324 * do this.notify(); while (o != null); 325 * } 326 * </p> 327 * @param literalDo {@link TokenTypes#LITERAL_DO do-while statement}. 328 * @return true if current do-while statement is single-line statement. 329 */ 330 private static boolean isSingleLineDoWhile(DetailAST literalDo) { 331 boolean result = false; 332 if (literalDo.getParent().getType() == TokenTypes.SLIST 333 && literalDo.getFirstChild().getType() != TokenTypes.SLIST) { 334 final DetailAST block = literalDo.getFirstChild(); 335 result = block.getLineNo() == literalDo.getLineNo(); 336 } 337 return result; 338 } 339 340 /** 341 * Checks if current for statement is single-line statement, e.g.: 342 * <p> 343 * {@code 344 * for (int i = 0; ; ) this.notify(); 345 * } 346 * </p> 347 * @param literalFor {@link TokenTypes#LITERAL_FOR for statement}. 348 * @return true if current for statement is single-line statement. 349 */ 350 private static boolean isSingleLineFor(DetailAST literalFor) { 351 boolean result = false; 352 if (literalFor.getLastChild().getType() == TokenTypes.EMPTY_STAT) { 353 result = true; 354 } 355 else if (literalFor.getParent().getType() == TokenTypes.SLIST 356 && literalFor.getLastChild().getType() != TokenTypes.SLIST) { 357 final DetailAST block = findExpressionBlockInForLoop(literalFor); 358 if (block == null) { 359 result = literalFor.getLineNo() == literalFor.getLastChild().getLineNo(); 360 } 361 else { 362 result = literalFor.getLineNo() == block.getLineNo(); 363 } 364 } 365 return result; 366 } 367 368 /** 369 * Detects and returns expression block in classical and enhanced for loops. 370 * 371 * @param literalFor parent for loop literal 372 * @return expression block 373 */ 374 private static DetailAST findExpressionBlockInForLoop(DetailAST literalFor) { 375 final DetailAST forEachClause = literalFor.findFirstToken(TokenTypes.FOR_EACH_CLAUSE); 376 final DetailAST firstToken; 377 if (forEachClause == null) { 378 firstToken = literalFor.findFirstToken(TokenTypes.EXPR); 379 } 380 else { 381 firstToken = forEachClause.findFirstToken(TokenTypes.EXPR); 382 } 383 return firstToken; 384 } 385 386 /** 387 * Checks if current if statement is single-line statement, e.g.: 388 * <p> 389 * {@code 390 * if (obj.isValid()) return true; 391 * } 392 * </p> 393 * @param literalIf {@link TokenTypes#LITERAL_IF if statement}. 394 * @return true if current if statement is single-line statement. 395 */ 396 private static boolean isSingleLineIf(DetailAST literalIf) { 397 boolean result = false; 398 final DetailAST ifCondition = literalIf.findFirstToken(TokenTypes.EXPR); 399 if (literalIf.getParent().getType() == TokenTypes.SLIST) { 400 final DetailAST literalIfLastChild = literalIf.getLastChild(); 401 final DetailAST block; 402 if (literalIfLastChild.getType() == TokenTypes.LITERAL_ELSE) { 403 block = literalIfLastChild.getPreviousSibling(); 404 } 405 else { 406 block = literalIfLastChild; 407 } 408 result = ifCondition.getLineNo() == block.getLineNo(); 409 } 410 return result; 411 } 412 413 /** 414 * Checks if current lambda statement is single-line statement, e.g.: 415 * <p> 416 * {@code 417 * Runnable r = () -> System.out.println("Hello, world!"); 418 * } 419 * </p> 420 * @param lambda {@link TokenTypes#LAMBDA lambda statement}. 421 * @return true if current lambda statement is single-line statement. 422 */ 423 private static boolean isSingleLineLambda(DetailAST lambda) { 424 boolean result = false; 425 final DetailAST block = lambda.getLastChild(); 426 if (block.getType() != TokenTypes.SLIST) { 427 result = lambda.getLineNo() == block.getLineNo(); 428 } 429 return result; 430 } 431 432 /** 433 * Checks if current case statement is single-line statement, e.g.: 434 * <p> 435 * {@code 436 * case 1: doSomeStuff(); break; 437 * case 2: doSomeStuff(); break; 438 * } 439 * </p> 440 * @param literalCase {@link TokenTypes#LITERAL_CASE case statement}. 441 * @return true if current case statement is single-line statement. 442 */ 443 private static boolean isSingleLineCase(DetailAST literalCase) { 444 boolean result = false; 445 final DetailAST slist = literalCase.getNextSibling(); 446 final DetailAST block = slist.getFirstChild(); 447 if (block.getType() != TokenTypes.SLIST) { 448 final DetailAST caseBreak = slist.findFirstToken(TokenTypes.LITERAL_BREAK); 449 final boolean atOneLine = literalCase.getLineNo() == block.getLineNo(); 450 if (caseBreak != null) { 451 result = atOneLine && block.getLineNo() == caseBreak.getLineNo(); 452 } 453 } 454 return result; 455 } 456 457 /** 458 * Checks if current default statement is single-line statement, e.g.: 459 * <p> 460 * {@code 461 * default: doSomeStuff(); 462 * } 463 * </p> 464 * @param literalDefault {@link TokenTypes#LITERAL_DEFAULT default statement}. 465 * @return true if current default statement is single-line statement. 466 */ 467 private static boolean isSingleLineDefault(DetailAST literalDefault) { 468 boolean result = false; 469 final DetailAST slist = literalDefault.getNextSibling(); 470 final DetailAST block = slist.getFirstChild(); 471 if (block.getType() != TokenTypes.SLIST) { 472 result = literalDefault.getLineNo() == block.getLineNo(); 473 } 474 return result; 475 } 476 477 /** 478 * Checks if current else statement is single-line statement, e.g.: 479 * <p> 480 * {@code 481 * else doSomeStuff(); 482 * } 483 * </p> 484 * @param literalElse {@link TokenTypes#LITERAL_ELSE else statement}. 485 * @return true if current else statement is single-line statement. 486 */ 487 private static boolean isSingleLineElse(DetailAST literalElse) { 488 boolean result = false; 489 final DetailAST block = literalElse.getFirstChild(); 490 if (block.getType() != TokenTypes.SLIST) { 491 result = literalElse.getLineNo() == block.getLineNo(); 492 } 493 return result; 494 } 495}