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 an option <b>allowSingleLineStatement</b> which allows single-line 054 * statements without braces, e.g.: 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> 076 * To configure the Check to allow {@code case, default} single-line statements 077 * without braces: 078 * </p> 079 * 080 * <pre> 081 * <module name="NeedBraces"> 082 * <property name="tokens" value="LITERAL_CASE, LITERAL_DEFAULT"/> 083 * <property name="allowSingleLineStatement" value="true"/> 084 * </module> 085 * </pre> 086 * 087 * <p> 088 * Such statements would be allowed: 089 * </p> 090 * 091 * <pre> 092 * {@code 093 * switch (num) { 094 * case 1: counter++; break; // OK 095 * case 6: counter += 10; break; // OK 096 * default: counter = 100; break; // OK 097 * } 098 * } 099 * </pre> 100 * 101 * 102 * @author Rick Giles 103 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 104 */ 105public class NeedBracesCheck extends Check { 106 /** 107 * A key is pointing to the warning message text in "messages.properties" 108 * file. 109 */ 110 public static final String MSG_KEY_NEED_BRACES = "needBraces"; 111 112 /** 113 * Check's option for skipping single-line statements. 114 */ 115 private boolean allowSingleLineStatement; 116 117 /** 118 * Setter. 119 * @param allowSingleLineStatement Check's option for skipping single-line statements 120 */ 121 public void setAllowSingleLineStatement(boolean allowSingleLineStatement) { 122 this.allowSingleLineStatement = allowSingleLineStatement; 123 } 124 125 @Override 126 public int[] getDefaultTokens() { 127 return new int[] { 128 TokenTypes.LITERAL_DO, 129 TokenTypes.LITERAL_ELSE, 130 TokenTypes.LITERAL_FOR, 131 TokenTypes.LITERAL_IF, 132 TokenTypes.LITERAL_WHILE, 133 }; 134 } 135 136 @Override 137 public int[] getAcceptableTokens() { 138 return new int[] { 139 TokenTypes.LITERAL_DO, 140 TokenTypes.LITERAL_ELSE, 141 TokenTypes.LITERAL_FOR, 142 TokenTypes.LITERAL_IF, 143 TokenTypes.LITERAL_WHILE, 144 TokenTypes.LITERAL_CASE, 145 TokenTypes.LITERAL_DEFAULT, 146 TokenTypes.LAMBDA, 147 }; 148 } 149 150 @Override 151 public int[] getRequiredTokens() { 152 return ArrayUtils.EMPTY_INT_ARRAY; 153 } 154 155 @Override 156 public void visitToken(DetailAST ast) { 157 final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST); 158 boolean isElseIf = false; 159 if (ast.getType() == TokenTypes.LITERAL_ELSE 160 && ast.findFirstToken(TokenTypes.LITERAL_IF) != null) { 161 isElseIf = true; 162 } 163 164 final boolean skipStatement = isSkipStatement(ast); 165 166 if (slistAST == null && !isElseIf && !skipStatement) { 167 log(ast.getLineNo(), MSG_KEY_NEED_BRACES, ast.getText()); 168 } 169 } 170 171 /** 172 * Checks if current statement can be skipped by "need braces" warning. 173 * @param statement if, for, while, do-while, lambda, else, case, default statements. 174 * @return true if current statement can be skipped by Check. 175 */ 176 private boolean isSkipStatement(DetailAST statement) { 177 return allowSingleLineStatement && isSingleLineStatement(statement); 178 } 179 180 /** 181 * Checks if current statement is single-line statement, e.g.: 182 * <p> 183 * {@code 184 * if (obj.isValid()) return true; 185 * } 186 * </p> 187 * <p> 188 * {@code 189 * while (obj.isValid()) return true; 190 * } 191 * </p> 192 * @param statement if, for, while, do-while, lambda, else, case, default statements. 193 * @return true if current statement is single-line statement. 194 */ 195 private static boolean isSingleLineStatement(DetailAST statement) { 196 boolean result; 197 198 switch (statement.getType()) { 199 case TokenTypes.LITERAL_IF: 200 result = isSingleLineIf(statement); 201 break; 202 case TokenTypes.LITERAL_FOR: 203 result = isSingleLineFor(statement); 204 break; 205 case TokenTypes.LITERAL_DO: 206 result = isSingleLineDoWhile(statement); 207 break; 208 case TokenTypes.LITERAL_WHILE: 209 result = isSingleLineWhile(statement); 210 break; 211 case TokenTypes.LAMBDA: 212 result = isSingleLineLambda(statement); 213 break; 214 case TokenTypes.LITERAL_CASE: 215 result = isSingleLineCase(statement); 216 break; 217 case TokenTypes.LITERAL_DEFAULT: 218 result = isSingleLineDefault(statement); 219 break; 220 default: 221 result = isSingleLineElse(statement); 222 break; 223 } 224 225 return result; 226 } 227 228 /** 229 * Checks if current while statement is single-line statement, e.g.: 230 * <p> 231 * {@code 232 * while (obj.isValid()) return true; 233 * } 234 * </p> 235 * @param literalWhile {@link TokenTypes#LITERAL_WHILE while statement}. 236 * @return true if current while statement is single-line statement. 237 */ 238 private static boolean isSingleLineWhile(DetailAST literalWhile) { 239 boolean result = false; 240 if (literalWhile.getParent().getType() == TokenTypes.SLIST 241 && literalWhile.getLastChild().getType() != TokenTypes.SLIST) { 242 final DetailAST block = literalWhile.getLastChild().getPreviousSibling(); 243 result = literalWhile.getLineNo() == block.getLineNo(); 244 } 245 return result; 246 } 247 248 /** 249 * Checks if current do-while statement is single-line statement, e.g.: 250 * <p> 251 * {@code 252 * do this.notify(); while (o != null); 253 * } 254 * </p> 255 * @param literalDo {@link TokenTypes#LITERAL_DO do-while statement}. 256 * @return true if current do-while statement is single-line statement. 257 */ 258 private static boolean isSingleLineDoWhile(DetailAST literalDo) { 259 boolean result = false; 260 if (literalDo.getParent().getType() == TokenTypes.SLIST 261 && literalDo.getFirstChild().getType() != TokenTypes.SLIST) { 262 final DetailAST block = literalDo.getFirstChild(); 263 result = block.getLineNo() == literalDo.getLineNo(); 264 } 265 return result; 266 } 267 268 /** 269 * Checks if current for statement is single-line statement, e.g.: 270 * <p> 271 * {@code 272 * for (int i = 0; ; ) this.notify(); 273 * } 274 * </p> 275 * @param literalFor {@link TokenTypes#LITERAL_FOR for statement}. 276 * @return true if current for statement is single-line statement. 277 */ 278 private static boolean isSingleLineFor(DetailAST literalFor) { 279 boolean result = false; 280 if (literalFor.getLastChild().getType() == TokenTypes.EMPTY_STAT) { 281 result = true; 282 } 283 else if (literalFor.getParent().getType() == TokenTypes.SLIST 284 && literalFor.getLastChild().getType() != TokenTypes.SLIST) { 285 final DetailAST block = findExpressionBlockInForLoop(literalFor); 286 result = literalFor.getLineNo() == block.getLineNo(); 287 } 288 return result; 289 } 290 291 /** 292 * Detects and returns expression block in classical and enhanced for loops. 293 * 294 * @param literalFor parent for loop literal 295 * @return expression block 296 */ 297 private static DetailAST findExpressionBlockInForLoop(DetailAST literalFor) { 298 final DetailAST forEachClause = literalFor.findFirstToken(TokenTypes.FOR_EACH_CLAUSE); 299 final DetailAST firstToken; 300 if (forEachClause != null) { 301 firstToken = forEachClause.findFirstToken(TokenTypes.EXPR); 302 } 303 else { 304 firstToken = literalFor.findFirstToken(TokenTypes.EXPR); 305 } 306 return firstToken; 307 } 308 309 /** 310 * Checks if current if statement is single-line statement, e.g.: 311 * <p> 312 * {@code 313 * if (obj.isValid()) return true; 314 * } 315 * </p> 316 * @param literalIf {@link TokenTypes#LITERAL_IF if statement}. 317 * @return true if current if statement is single-line statement. 318 */ 319 private static boolean isSingleLineIf(DetailAST literalIf) { 320 boolean result = false; 321 final DetailAST ifCondition = literalIf.findFirstToken(TokenTypes.EXPR); 322 if (literalIf.getParent().getType() == TokenTypes.SLIST) { 323 final DetailAST literalIfLastChild = literalIf.getLastChild(); 324 final DetailAST block; 325 if (literalIfLastChild.getType() == TokenTypes.LITERAL_ELSE) { 326 block = literalIfLastChild.getPreviousSibling(); 327 } 328 else { 329 block = literalIfLastChild; 330 } 331 result = ifCondition.getLineNo() == block.getLineNo(); 332 } 333 return result; 334 } 335 336 /** 337 * Checks if current lambda statement is single-line statement, e.g.: 338 * <p> 339 * {@code 340 * Runnable r = () -> System.out.println("Hello, world!"); 341 * } 342 * </p> 343 * @param lambda {@link TokenTypes#LAMBDA lambda statement}. 344 * @return true if current lambda statement is single-line statement. 345 */ 346 private static boolean isSingleLineLambda(DetailAST lambda) { 347 boolean result = false; 348 final DetailAST block = lambda.getLastChild(); 349 if (block.getType() != TokenTypes.SLIST) { 350 result = lambda.getLineNo() == block.getLineNo(); 351 } 352 return result; 353 } 354 355 /** 356 * Checks if current case statement is single-line statement, e.g.: 357 * <p> 358 * {@code 359 * case 1: doSomeStuff(); break; 360 * case 2: doSomeStuff(); break; 361 * } 362 * </p> 363 * @param literalCase {@link TokenTypes#LITERAL_CASE case statement}. 364 * @return true if current case statement is single-line statement. 365 */ 366 private static boolean isSingleLineCase(DetailAST literalCase) { 367 boolean result = false; 368 final DetailAST slist = literalCase.getNextSibling(); 369 final DetailAST block = slist.getFirstChild(); 370 if (block.getType() != TokenTypes.SLIST) { 371 final DetailAST caseBreak = slist.findFirstToken(TokenTypes.LITERAL_BREAK); 372 final boolean atOneLine = literalCase.getLineNo() == block.getLineNo(); 373 if (caseBreak != null) { 374 result = atOneLine && block.getLineNo() == caseBreak.getLineNo(); 375 } 376 } 377 return result; 378 } 379 380 /** 381 * Checks if current default statement is single-line statement, e.g.: 382 * <p> 383 * {@code 384 * default: doSomeStuff(); 385 * } 386 * </p> 387 * @param literalDefault {@link TokenTypes#LITERAL_DEFAULT default statement}. 388 * @return true if current default statement is single-line statement. 389 */ 390 private static boolean isSingleLineDefault(DetailAST literalDefault) { 391 boolean result = false; 392 final DetailAST slist = literalDefault.getNextSibling(); 393 final DetailAST block = slist.getFirstChild(); 394 if (block.getType() != TokenTypes.SLIST) { 395 result = literalDefault.getLineNo() == block.getLineNo(); 396 } 397 return result; 398 } 399 400 /** 401 * Checks if current else statement is single-line statement, e.g.: 402 * <p> 403 * {@code 404 * else doSomeStuff(); 405 * } 406 * </p> 407 * @param literalElse {@link TokenTypes#LITERAL_ELSE else statement}. 408 * @return true if current else statement is single-line statement. 409 */ 410 private static boolean isSingleLineElse(DetailAST literalElse) { 411 boolean result = false; 412 final DetailAST block = literalElse.getFirstChild(); 413 if (block.getType() != TokenTypes.SLIST) { 414 result = literalElse.getLineNo() == block.getLineNo(); 415 } 416 return result; 417 } 418}