001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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.StatelessCheck; 023import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 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 */ 137@StatelessCheck 138public class NeedBracesCheck extends AbstractCheck { 139 140 /** 141 * A key is pointing to the warning message text in "messages.properties" 142 * file. 143 */ 144 public static final String MSG_KEY_NEED_BRACES = "needBraces"; 145 146 /** 147 * Check's option for skipping single-line statements. 148 */ 149 private boolean allowSingleLineStatement; 150 151 /** 152 * Check's option for allowing loops with empty body. 153 */ 154 private boolean allowEmptyLoopBody; 155 156 /** 157 * Setter. 158 * @param allowSingleLineStatement Check's option for skipping single-line statements 159 */ 160 public void setAllowSingleLineStatement(boolean allowSingleLineStatement) { 161 this.allowSingleLineStatement = allowSingleLineStatement; 162 } 163 164 /** 165 * Sets whether to allow empty loop body. 166 * @param allowEmptyLoopBody Check's option for allowing loops with empty body. 167 */ 168 public void setAllowEmptyLoopBody(boolean allowEmptyLoopBody) { 169 this.allowEmptyLoopBody = allowEmptyLoopBody; 170 } 171 172 @Override 173 public int[] getDefaultTokens() { 174 return new int[] { 175 TokenTypes.LITERAL_DO, 176 TokenTypes.LITERAL_ELSE, 177 TokenTypes.LITERAL_FOR, 178 TokenTypes.LITERAL_IF, 179 TokenTypes.LITERAL_WHILE, 180 }; 181 } 182 183 @Override 184 public int[] getAcceptableTokens() { 185 return new int[] { 186 TokenTypes.LITERAL_DO, 187 TokenTypes.LITERAL_ELSE, 188 TokenTypes.LITERAL_FOR, 189 TokenTypes.LITERAL_IF, 190 TokenTypes.LITERAL_WHILE, 191 TokenTypes.LITERAL_CASE, 192 TokenTypes.LITERAL_DEFAULT, 193 TokenTypes.LAMBDA, 194 }; 195 } 196 197 @Override 198 public int[] getRequiredTokens() { 199 return CommonUtils.EMPTY_INT_ARRAY; 200 } 201 202 @Override 203 public void visitToken(DetailAST ast) { 204 final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST); 205 boolean isElseIf = false; 206 if (ast.getType() == TokenTypes.LITERAL_ELSE 207 && ast.findFirstToken(TokenTypes.LITERAL_IF) != null) { 208 isElseIf = true; 209 } 210 final boolean isDefaultInAnnotation = isDefaultInAnnotation(ast); 211 final boolean skipStatement = isSkipStatement(ast); 212 final boolean skipEmptyLoopBody = allowEmptyLoopBody && isEmptyLoopBody(ast); 213 214 if (slistAST == null && !isElseIf && !isDefaultInAnnotation 215 && !skipStatement && !skipEmptyLoopBody) { 216 log(ast.getLineNo(), MSG_KEY_NEED_BRACES, ast.getText()); 217 } 218 } 219 220 /** 221 * Checks if ast is the default token of an annotation field. 222 * @param ast ast to test. 223 * @return true if current ast is default and it is part of annotation. 224 */ 225 private static boolean isDefaultInAnnotation(DetailAST ast) { 226 boolean isDefaultInAnnotation = false; 227 if (ast.getType() == TokenTypes.LITERAL_DEFAULT 228 && ast.getParent().getType() == TokenTypes.ANNOTATION_FIELD_DEF) { 229 isDefaultInAnnotation = true; 230 } 231 return isDefaultInAnnotation; 232 } 233 234 /** 235 * Checks if current statement can be skipped by "need braces" warning. 236 * @param statement if, for, while, do-while, lambda, else, case, default statements. 237 * @return true if current statement can be skipped by Check. 238 */ 239 private boolean isSkipStatement(DetailAST statement) { 240 return allowSingleLineStatement && isSingleLineStatement(statement); 241 } 242 243 /** 244 * Checks if current loop statement does not have body, e.g.: 245 * <p> 246 * {@code 247 * while (value.incrementValue() < 5); 248 * ... 249 * for(int i = 0; i < 10; value.incrementValue()); 250 * } 251 * </p> 252 * @param ast ast token. 253 * @return true if current loop statement does not have body. 254 */ 255 private static boolean isEmptyLoopBody(DetailAST ast) { 256 boolean noBodyLoop = false; 257 258 if (ast.getType() == TokenTypes.LITERAL_FOR 259 || ast.getType() == TokenTypes.LITERAL_WHILE) { 260 DetailAST currentToken = ast.getFirstChild(); 261 while (currentToken.getNextSibling() != null) { 262 currentToken = currentToken.getNextSibling(); 263 } 264 noBodyLoop = currentToken.getType() == TokenTypes.EMPTY_STAT; 265 } 266 return noBodyLoop; 267 } 268 269 /** 270 * Checks if current statement is single-line statement, e.g.: 271 * <p> 272 * {@code 273 * if (obj.isValid()) return true; 274 * } 275 * </p> 276 * <p> 277 * {@code 278 * while (obj.isValid()) return true; 279 * } 280 * </p> 281 * @param statement if, for, while, do-while, lambda, else, case, default statements. 282 * @return true if current statement is single-line statement. 283 */ 284 private static boolean isSingleLineStatement(DetailAST statement) { 285 final boolean result; 286 287 switch (statement.getType()) { 288 case TokenTypes.LITERAL_IF: 289 result = isSingleLineIf(statement); 290 break; 291 case TokenTypes.LITERAL_FOR: 292 result = isSingleLineFor(statement); 293 break; 294 case TokenTypes.LITERAL_DO: 295 result = isSingleLineDoWhile(statement); 296 break; 297 case TokenTypes.LITERAL_WHILE: 298 result = isSingleLineWhile(statement); 299 break; 300 case TokenTypes.LAMBDA: 301 result = isSingleLineLambda(statement); 302 break; 303 case TokenTypes.LITERAL_CASE: 304 result = isSingleLineCase(statement); 305 break; 306 case TokenTypes.LITERAL_DEFAULT: 307 result = isSingleLineDefault(statement); 308 break; 309 default: 310 result = isSingleLineElse(statement); 311 break; 312 } 313 314 return result; 315 } 316 317 /** 318 * Checks if current while statement is single-line statement, e.g.: 319 * <p> 320 * {@code 321 * while (obj.isValid()) return true; 322 * } 323 * </p> 324 * @param literalWhile {@link TokenTypes#LITERAL_WHILE while statement}. 325 * @return true if current while statement is single-line statement. 326 */ 327 private static boolean isSingleLineWhile(DetailAST literalWhile) { 328 boolean result = false; 329 if (literalWhile.getParent().getType() == TokenTypes.SLIST 330 && literalWhile.getLastChild().getType() != TokenTypes.SLIST) { 331 final DetailAST block = literalWhile.getLastChild().getPreviousSibling(); 332 result = literalWhile.getLineNo() == block.getLineNo(); 333 } 334 return result; 335 } 336 337 /** 338 * Checks if current do-while statement is single-line statement, e.g.: 339 * <p> 340 * {@code 341 * do this.notify(); while (o != null); 342 * } 343 * </p> 344 * @param literalDo {@link TokenTypes#LITERAL_DO do-while statement}. 345 * @return true if current do-while statement is single-line statement. 346 */ 347 private static boolean isSingleLineDoWhile(DetailAST literalDo) { 348 boolean result = false; 349 if (literalDo.getParent().getType() == TokenTypes.SLIST 350 && literalDo.getFirstChild().getType() != TokenTypes.SLIST) { 351 final DetailAST block = literalDo.getFirstChild(); 352 result = block.getLineNo() == literalDo.getLineNo(); 353 } 354 return result; 355 } 356 357 /** 358 * Checks if current for statement is single-line statement, e.g.: 359 * <p> 360 * {@code 361 * for (int i = 0; ; ) this.notify(); 362 * } 363 * </p> 364 * @param literalFor {@link TokenTypes#LITERAL_FOR for statement}. 365 * @return true if current for statement is single-line statement. 366 */ 367 private static boolean isSingleLineFor(DetailAST literalFor) { 368 boolean result = false; 369 if (literalFor.getLastChild().getType() == TokenTypes.EMPTY_STAT) { 370 result = true; 371 } 372 else if (literalFor.getParent().getType() == TokenTypes.SLIST 373 && literalFor.getLastChild().getType() != TokenTypes.SLIST) { 374 result = literalFor.getLineNo() == literalFor.getLastChild().getLineNo(); 375 } 376 return result; 377 } 378 379 /** 380 * Checks if current if statement is single-line statement, e.g.: 381 * <p> 382 * {@code 383 * if (obj.isValid()) return true; 384 * } 385 * </p> 386 * @param literalIf {@link TokenTypes#LITERAL_IF if statement}. 387 * @return true if current if statement is single-line statement. 388 */ 389 private static boolean isSingleLineIf(DetailAST literalIf) { 390 boolean result = false; 391 if (literalIf.getParent().getType() == TokenTypes.SLIST) { 392 final DetailAST literalIfLastChild = literalIf.getLastChild(); 393 final DetailAST block; 394 if (literalIfLastChild.getType() == TokenTypes.LITERAL_ELSE) { 395 block = literalIfLastChild.getPreviousSibling(); 396 } 397 else { 398 block = literalIfLastChild; 399 } 400 final DetailAST ifCondition = literalIf.findFirstToken(TokenTypes.EXPR); 401 result = ifCondition.getLineNo() == block.getLineNo(); 402 } 403 return result; 404 } 405 406 /** 407 * Checks if current lambda statement is single-line statement, e.g.: 408 * <p> 409 * {@code 410 * Runnable r = () -> System.out.println("Hello, world!"); 411 * } 412 * </p> 413 * @param lambda {@link TokenTypes#LAMBDA lambda statement}. 414 * @return true if current lambda statement is single-line statement. 415 */ 416 private static boolean isSingleLineLambda(DetailAST lambda) { 417 boolean result = false; 418 final DetailAST block = lambda.getLastChild(); 419 if (block.getType() != TokenTypes.SLIST) { 420 result = lambda.getLineNo() == block.getLineNo(); 421 } 422 return result; 423 } 424 425 /** 426 * Checks if current case statement is single-line statement, e.g.: 427 * <p> 428 * {@code 429 * case 1: doSomeStuff(); break; 430 * case 2: doSomeStuff(); break; 431 * case 3: ; 432 * } 433 * </p> 434 * @param literalCase {@link TokenTypes#LITERAL_CASE case statement}. 435 * @return true if current case statement is single-line statement. 436 */ 437 private static boolean isSingleLineCase(DetailAST literalCase) { 438 boolean result = false; 439 final DetailAST slist = literalCase.getNextSibling(); 440 if (slist == null) { 441 result = true; 442 } 443 else { 444 final DetailAST block = slist.getFirstChild(); 445 if (block.getType() != TokenTypes.SLIST) { 446 final DetailAST caseBreak = slist.findFirstToken(TokenTypes.LITERAL_BREAK); 447 if (caseBreak != null) { 448 final boolean atOneLine = literalCase.getLineNo() == block.getLineNo(); 449 result = atOneLine && block.getLineNo() == caseBreak.getLineNo(); 450 } 451 } 452 } 453 return result; 454 } 455 456 /** 457 * Checks if current default statement is single-line statement, e.g.: 458 * <p> 459 * {@code 460 * default: doSomeStuff(); 461 * } 462 * </p> 463 * @param literalDefault {@link TokenTypes#LITERAL_DEFAULT default statement}. 464 * @return true if current default statement is single-line statement. 465 */ 466 private static boolean isSingleLineDefault(DetailAST literalDefault) { 467 boolean result = false; 468 final DetailAST slist = literalDefault.getNextSibling(); 469 if (slist == null) { 470 result = true; 471 } 472 else { 473 final DetailAST block = slist.getFirstChild(); 474 if (block != null && block.getType() != TokenTypes.SLIST) { 475 result = literalDefault.getLineNo() == block.getLineNo(); 476 } 477 } 478 return result; 479 } 480 481 /** 482 * Checks if current else statement is single-line statement, e.g.: 483 * <p> 484 * {@code 485 * else doSomeStuff(); 486 * } 487 * </p> 488 * @param literalElse {@link TokenTypes#LITERAL_ELSE else statement}. 489 * @return true if current else statement is single-line statement. 490 */ 491 private static boolean isSingleLineElse(DetailAST literalElse) { 492 boolean result = false; 493 final DetailAST block = literalElse.getFirstChild(); 494 if (block.getType() != TokenTypes.SLIST) { 495 result = literalElse.getLineNo() == block.getLineNo(); 496 } 497 return result; 498 } 499 500}