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