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