001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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.CommonUtil; 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 */ 134@StatelessCheck 135public class NeedBracesCheck extends AbstractCheck { 136 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 CommonUtil.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 isInAnnotationField = isInAnnotationField(ast); 208 final boolean skipStatement = isSkipStatement(ast); 209 final boolean skipEmptyLoopBody = allowEmptyLoopBody && isEmptyLoopBody(ast); 210 211 if (slistAST == null && !isElseIf && !isInAnnotationField 212 && !skipStatement && !skipEmptyLoopBody) { 213 log(ast.getLineNo(), MSG_KEY_NEED_BRACES, ast.getText()); 214 } 215 } 216 217 /** 218 * Checks if ast is in an annotation field. 219 * @param ast ast to test. 220 * @return true if current ast is part of an annotation field. 221 */ 222 private static boolean isInAnnotationField(DetailAST ast) { 223 boolean isDefaultInAnnotation = false; 224 if (ast.getParent().getType() == TokenTypes.ANNOTATION_FIELD_DEF) { 225 isDefaultInAnnotation = true; 226 } 227 return isDefaultInAnnotation; 228 } 229 230 /** 231 * Checks if current statement can be skipped by "need braces" warning. 232 * @param statement if, for, while, do-while, lambda, else, case, default statements. 233 * @return true if current statement can be skipped by Check. 234 */ 235 private boolean isSkipStatement(DetailAST statement) { 236 return allowSingleLineStatement && isSingleLineStatement(statement); 237 } 238 239 /** 240 * Checks if current loop statement does not have body, e.g.: 241 * <p> 242 * {@code 243 * while (value.incrementValue() < 5); 244 * ... 245 * for(int i = 0; i < 10; value.incrementValue()); 246 * } 247 * </p> 248 * @param ast ast token. 249 * @return true if current loop statement does not have body. 250 */ 251 private static boolean isEmptyLoopBody(DetailAST ast) { 252 boolean noBodyLoop = false; 253 254 if (ast.getType() == TokenTypes.LITERAL_FOR 255 || ast.getType() == TokenTypes.LITERAL_WHILE) { 256 DetailAST currentToken = ast.getFirstChild(); 257 while (currentToken.getNextSibling() != null) { 258 currentToken = currentToken.getNextSibling(); 259 } 260 noBodyLoop = currentToken.getType() == TokenTypes.EMPTY_STAT; 261 } 262 return noBodyLoop; 263 } 264 265 /** 266 * Checks if current statement is single-line statement, e.g.: 267 * <p> 268 * {@code 269 * if (obj.isValid()) return true; 270 * } 271 * </p> 272 * <p> 273 * {@code 274 * while (obj.isValid()) return true; 275 * } 276 * </p> 277 * @param statement if, for, while, do-while, lambda, else, case, default statements. 278 * @return true if current statement is single-line statement. 279 */ 280 private static boolean isSingleLineStatement(DetailAST statement) { 281 final boolean result; 282 283 switch (statement.getType()) { 284 case TokenTypes.LITERAL_IF: 285 result = isSingleLineIf(statement); 286 break; 287 case TokenTypes.LITERAL_FOR: 288 result = isSingleLineFor(statement); 289 break; 290 case TokenTypes.LITERAL_DO: 291 result = isSingleLineDoWhile(statement); 292 break; 293 case TokenTypes.LITERAL_WHILE: 294 result = isSingleLineWhile(statement); 295 break; 296 case TokenTypes.LAMBDA: 297 result = isSingleLineLambda(statement); 298 break; 299 case TokenTypes.LITERAL_CASE: 300 result = isSingleLineCase(statement); 301 break; 302 case TokenTypes.LITERAL_DEFAULT: 303 result = isSingleLineDefault(statement); 304 break; 305 default: 306 result = isSingleLineElse(statement); 307 break; 308 } 309 310 return result; 311 } 312 313 /** 314 * Checks if current while statement is single-line statement, e.g.: 315 * <p> 316 * {@code 317 * while (obj.isValid()) return true; 318 * } 319 * </p> 320 * @param literalWhile {@link TokenTypes#LITERAL_WHILE while statement}. 321 * @return true if current while statement is single-line statement. 322 */ 323 private static boolean isSingleLineWhile(DetailAST literalWhile) { 324 boolean result = false; 325 if (literalWhile.getParent().getType() == TokenTypes.SLIST) { 326 final DetailAST block = literalWhile.getLastChild().getPreviousSibling(); 327 result = literalWhile.getLineNo() == block.getLineNo(); 328 } 329 return result; 330 } 331 332 /** 333 * Checks if current do-while statement is single-line statement, e.g.: 334 * <p> 335 * {@code 336 * do this.notify(); while (o != null); 337 * } 338 * </p> 339 * @param literalDo {@link TokenTypes#LITERAL_DO do-while statement}. 340 * @return true if current do-while statement is single-line statement. 341 */ 342 private static boolean isSingleLineDoWhile(DetailAST literalDo) { 343 boolean result = false; 344 if (literalDo.getParent().getType() == TokenTypes.SLIST) { 345 final DetailAST block = literalDo.getFirstChild(); 346 result = block.getLineNo() == literalDo.getLineNo(); 347 } 348 return result; 349 } 350 351 /** 352 * Checks if current for statement is single-line statement, e.g.: 353 * <p> 354 * {@code 355 * for (int i = 0; ; ) this.notify(); 356 * } 357 * </p> 358 * @param literalFor {@link TokenTypes#LITERAL_FOR for statement}. 359 * @return true if current for statement is single-line statement. 360 */ 361 private static boolean isSingleLineFor(DetailAST literalFor) { 362 boolean result = false; 363 if (literalFor.getLastChild().getType() == TokenTypes.EMPTY_STAT) { 364 result = true; 365 } 366 else if (literalFor.getParent().getType() == TokenTypes.SLIST) { 367 result = literalFor.getLineNo() == literalFor.getLastChild().getLineNo(); 368 } 369 return result; 370 } 371 372 /** 373 * Checks if current if statement is single-line statement, e.g.: 374 * <p> 375 * {@code 376 * if (obj.isValid()) return true; 377 * } 378 * </p> 379 * @param literalIf {@link TokenTypes#LITERAL_IF if statement}. 380 * @return true if current if statement is single-line statement. 381 */ 382 private static boolean isSingleLineIf(DetailAST literalIf) { 383 boolean result = false; 384 if (literalIf.getParent().getType() == TokenTypes.SLIST) { 385 final DetailAST literalIfLastChild = literalIf.getLastChild(); 386 final DetailAST block; 387 if (literalIfLastChild.getType() == TokenTypes.LITERAL_ELSE) { 388 block = literalIfLastChild.getPreviousSibling(); 389 } 390 else { 391 block = literalIfLastChild; 392 } 393 final DetailAST ifCondition = literalIf.findFirstToken(TokenTypes.EXPR); 394 result = ifCondition.getLineNo() == block.getLineNo(); 395 } 396 return result; 397 } 398 399 /** 400 * Checks if current lambda statement is single-line statement, e.g.: 401 * <p> 402 * {@code 403 * Runnable r = () -> System.out.println("Hello, world!"); 404 * } 405 * </p> 406 * @param lambda {@link TokenTypes#LAMBDA lambda statement}. 407 * @return true if current lambda statement is single-line statement. 408 */ 409 private static boolean isSingleLineLambda(DetailAST lambda) { 410 final DetailAST block = lambda.getLastChild(); 411 return lambda.getLineNo() == block.getLineNo(); 412 } 413 414 /** 415 * Checks if current case statement is single-line statement, e.g.: 416 * <p> 417 * {@code 418 * case 1: doSomeStuff(); break; 419 * case 2: doSomeStuff(); break; 420 * case 3: ; 421 * } 422 * </p> 423 * @param literalCase {@link TokenTypes#LITERAL_CASE case statement}. 424 * @return true if current case statement is single-line statement. 425 */ 426 private static boolean isSingleLineCase(DetailAST literalCase) { 427 boolean result = false; 428 final DetailAST slist = literalCase.getNextSibling(); 429 if (slist == null) { 430 result = true; 431 } 432 else { 433 final DetailAST caseBreak = slist.findFirstToken(TokenTypes.LITERAL_BREAK); 434 if (caseBreak != null) { 435 final DetailAST block = slist.getFirstChild(); 436 final boolean atOneLine = literalCase.getLineNo() == block.getLineNo(); 437 result = atOneLine && block.getLineNo() == caseBreak.getLineNo(); 438 } 439 } 440 return result; 441 } 442 443 /** 444 * Checks if current default statement is single-line statement, e.g.: 445 * <p> 446 * {@code 447 * default: doSomeStuff(); 448 * } 449 * </p> 450 * @param literalDefault {@link TokenTypes#LITERAL_DEFAULT default statement}. 451 * @return true if current default statement is single-line statement. 452 */ 453 private static boolean isSingleLineDefault(DetailAST literalDefault) { 454 boolean result = false; 455 final DetailAST slist = literalDefault.getNextSibling(); 456 if (slist == null) { 457 result = true; 458 } 459 else { 460 final DetailAST block = slist.getFirstChild(); 461 if (block != null && block.getType() != TokenTypes.SLIST) { 462 result = literalDefault.getLineNo() == block.getLineNo(); 463 } 464 } 465 return result; 466 } 467 468 /** 469 * Checks if current else statement is single-line statement, e.g.: 470 * <p> 471 * {@code 472 * else doSomeStuff(); 473 * } 474 * </p> 475 * @param literalElse {@link TokenTypes#LITERAL_ELSE else statement}. 476 * @return true if current else statement is single-line statement. 477 */ 478 private static boolean isSingleLineElse(DetailAST literalElse) { 479 final DetailAST block = literalElse.getFirstChild(); 480 return literalElse.getLineNo() == block.getLineNo(); 481 } 482 483}