//////////////////////////////////////////////////////////////////////////////// // checkstyle: Checks Java source code for adherence to a set of rules. // Copyright (C) 2001-2017 the original author or authors. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// package com.puppycrawl.tools.checkstyle.checks.blocks; import com.puppycrawl.tools.checkstyle.api.AbstractCheck; import com.puppycrawl.tools.checkstyle.api.DetailAST; import com.puppycrawl.tools.checkstyle.api.TokenTypes; import com.puppycrawl.tools.checkstyle.utils.CommonUtils; /** * <p> * Checks for braces around code blocks. * </p> * <p> By default the check will check the following blocks: * {@link TokenTypes#LITERAL_DO LITERAL_DO}, * {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE}, * {@link TokenTypes#LITERAL_FOR LITERAL_FOR}, * {@link TokenTypes#LITERAL_IF LITERAL_IF}, * {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}. * </p> * <p> * An example of how to configure the check is: * </p> * <pre> * <module name="NeedBraces"/> * </pre> * <p> An example of how to configure the check for {@code if} and * {@code else} blocks is: * </p> * <pre> * <module name="NeedBraces"> * <property name="tokens" value="LITERAL_IF, LITERAL_ELSE"/> * </module> * </pre> * Check has the following options: * <p><b>allowSingleLineStatement</b> which allows single-line statements without braces, e.g.:</p> * <p> * {@code * if (obj.isValid()) return true; * } * </p> * <p> * {@code * while (obj.isValid()) return true; * } * </p> * <p> * {@code * do this.notify(); while (o != null); * } * </p> * <p> * {@code * for (int i = 0; ; ) this.notify(); * } * </p> * <p><b>allowEmptyLoopBody</b> which allows loops with empty bodies, e.g.:</p> * <p> * {@code * while (value.incrementValue() < 5); * } * </p> * <p> * {@code * for(int i = 0; i < 10; value.incrementValue()); * } * </p> * <p>Default value for allowEmptyLoopBody option is <b>false</b>.</p> * <p> * To configure the Check to allow {@code case, default} single-line statements * without braces: * </p> * * <pre> * <module name="NeedBraces"> * <property name="tokens" value="LITERAL_CASE, LITERAL_DEFAULT"/> * <property name="allowSingleLineStatement" value="true"/> * </module> * </pre> * * <p> * Such statements would be allowed: * </p> * * <pre> * {@code * switch (num) { * case 1: counter++; break; // OK * case 6: counter += 10; break; // OK * default: counter = 100; break; // OK * } * } * </pre> * <p> * To configure the Check to allow {@code while, for} loops with empty bodies: * </p> * * <pre> * <module name="NeedBraces"> * <property name="allowEmptyLoopBody" value="true"/> * </module> * </pre> * * <p> * Such statements would be allowed: * </p> * * <pre> * {@code * while (value.incrementValue() < 5); // OK * for(int i = 0; i < 10; value.incrementValue()); // OK * } * </pre> * * @author Rick Giles * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> * @author <a href="mailto:andreyselkin@gmail.com">Andrei Selkin</a> */ public class NeedBracesCheck extends AbstractCheck { /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_KEY_NEED_BRACES = "needBraces"; /** * Check's option for skipping single-line statements. */ private boolean allowSingleLineStatement; /** * Check's option for allowing loops with empty body. */ private boolean allowEmptyLoopBody; /** * Setter. * @param allowSingleLineStatement Check's option for skipping single-line statements */ public void setAllowSingleLineStatement(boolean allowSingleLineStatement) { this.allowSingleLineStatement = allowSingleLineStatement; } /** * Sets whether to allow empty loop body. * @param allowEmptyLoopBody Check's option for allowing loops with empty body. */ public void setAllowEmptyLoopBody(boolean allowEmptyLoopBody) { this.allowEmptyLoopBody = allowEmptyLoopBody; } @Override public int[] getDefaultTokens() { return new int[] { TokenTypes.LITERAL_DO, TokenTypes.LITERAL_ELSE, TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_IF, TokenTypes.LITERAL_WHILE, }; } @Override public int[] getAcceptableTokens() { return new int[] { TokenTypes.LITERAL_DO, TokenTypes.LITERAL_ELSE, TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_IF, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT, TokenTypes.LAMBDA, }; } @Override public int[] getRequiredTokens() { return CommonUtils.EMPTY_INT_ARRAY; } @Override public void visitToken(DetailAST ast) { final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST); boolean isElseIf = false; if (ast.getType() == TokenTypes.LITERAL_ELSE && ast.findFirstToken(TokenTypes.LITERAL_IF) != null) { isElseIf = true; } final boolean isDefaultInAnnotation = isDefaultInAnnotation(ast); final boolean skipStatement = isSkipStatement(ast); final boolean skipEmptyLoopBody = allowEmptyLoopBody && isEmptyLoopBody(ast); if (slistAST == null && !isElseIf && !isDefaultInAnnotation && !skipStatement && !skipEmptyLoopBody) { log(ast.getLineNo(), MSG_KEY_NEED_BRACES, ast.getText()); } } /** * Checks if ast is default in annotation * @param ast ast to test. * @return true if current ast is default and it is part of annotation. */ private boolean isDefaultInAnnotation(DetailAST ast) { boolean isDefaultInAnnotation = false; if (ast.getType() == TokenTypes.LITERAL_DEFAULT && ast.getParent().getType() == TokenTypes.ANNOTATION_FIELD_DEF) { isDefaultInAnnotation = true; } return isDefaultInAnnotation; } /** * Checks if current statement can be skipped by "need braces" warning. * @param statement if, for, while, do-while, lambda, else, case, default statements. * @return true if current statement can be skipped by Check. */ private boolean isSkipStatement(DetailAST statement) { return allowSingleLineStatement && isSingleLineStatement(statement); } /** * Checks if current loop statement does not have body, e.g.: * <p> * {@code * while (value.incrementValue() < 5); * ... * for(int i = 0; i < 10; value.incrementValue()); * } * </p> * @param ast ast token. * @return true if current loop statement does not have body. */ private static boolean isEmptyLoopBody(DetailAST ast) { boolean noBodyLoop = false; if (ast.getType() == TokenTypes.LITERAL_FOR || ast.getType() == TokenTypes.LITERAL_WHILE) { DetailAST currentToken = ast.getFirstChild(); while (currentToken.getNextSibling() != null) { currentToken = currentToken.getNextSibling(); } noBodyLoop = currentToken.getType() == TokenTypes.EMPTY_STAT; } return noBodyLoop; } /** * Checks if current statement is single-line statement, e.g.: * <p> * {@code * if (obj.isValid()) return true; * } * </p> * <p> * {@code * while (obj.isValid()) return true; * } * </p> * @param statement if, for, while, do-while, lambda, else, case, default statements. * @return true if current statement is single-line statement. */ private static boolean isSingleLineStatement(DetailAST statement) { final boolean result; switch (statement.getType()) { case TokenTypes.LITERAL_IF: result = isSingleLineIf(statement); break; case TokenTypes.LITERAL_FOR: result = isSingleLineFor(statement); break; case TokenTypes.LITERAL_DO: result = isSingleLineDoWhile(statement); break; case TokenTypes.LITERAL_WHILE: result = isSingleLineWhile(statement); break; case TokenTypes.LAMBDA: result = isSingleLineLambda(statement); break; case TokenTypes.LITERAL_CASE: result = isSingleLineCase(statement); break; case TokenTypes.LITERAL_DEFAULT: result = isSingleLineDefault(statement); break; default: result = isSingleLineElse(statement); break; } return result; } /** * Checks if current while statement is single-line statement, e.g.: * <p> * {@code * while (obj.isValid()) return true; * } * </p> * @param literalWhile {@link TokenTypes#LITERAL_WHILE while statement}. * @return true if current while statement is single-line statement. */ private static boolean isSingleLineWhile(DetailAST literalWhile) { boolean result = false; if (literalWhile.getParent().getType() == TokenTypes.SLIST && literalWhile.getLastChild().getType() != TokenTypes.SLIST) { final DetailAST block = literalWhile.getLastChild().getPreviousSibling(); result = literalWhile.getLineNo() == block.getLineNo(); } return result; } /** * Checks if current do-while statement is single-line statement, e.g.: * <p> * {@code * do this.notify(); while (o != null); * } * </p> * @param literalDo {@link TokenTypes#LITERAL_DO do-while statement}. * @return true if current do-while statement is single-line statement. */ private static boolean isSingleLineDoWhile(DetailAST literalDo) { boolean result = false; if (literalDo.getParent().getType() == TokenTypes.SLIST && literalDo.getFirstChild().getType() != TokenTypes.SLIST) { final DetailAST block = literalDo.getFirstChild(); result = block.getLineNo() == literalDo.getLineNo(); } return result; } /** * Checks if current for statement is single-line statement, e.g.: * <p> * {@code * for (int i = 0; ; ) this.notify(); * } * </p> * @param literalFor {@link TokenTypes#LITERAL_FOR for statement}. * @return true if current for statement is single-line statement. */ private static boolean isSingleLineFor(DetailAST literalFor) { boolean result = false; if (literalFor.getLastChild().getType() == TokenTypes.EMPTY_STAT) { result = true; } else if (literalFor.getParent().getType() == TokenTypes.SLIST && literalFor.getLastChild().getType() != TokenTypes.SLIST) { result = literalFor.getLineNo() == literalFor.getLastChild().getLineNo(); } return result; } /** * Checks if current if statement is single-line statement, e.g.: * <p> * {@code * if (obj.isValid()) return true; * } * </p> * @param literalIf {@link TokenTypes#LITERAL_IF if statement}. * @return true if current if statement is single-line statement. */ private static boolean isSingleLineIf(DetailAST literalIf) { boolean result = false; if (literalIf.getParent().getType() == TokenTypes.SLIST) { final DetailAST literalIfLastChild = literalIf.getLastChild(); final DetailAST block; if (literalIfLastChild.getType() == TokenTypes.LITERAL_ELSE) { block = literalIfLastChild.getPreviousSibling(); } else { block = literalIfLastChild; } final DetailAST ifCondition = literalIf.findFirstToken(TokenTypes.EXPR); result = ifCondition.getLineNo() == block.getLineNo(); } return result; } /** * Checks if current lambda statement is single-line statement, e.g.: * <p> * {@code * Runnable r = () -> System.out.println("Hello, world!"); * } * </p> * @param lambda {@link TokenTypes#LAMBDA lambda statement}. * @return true if current lambda statement is single-line statement. */ private static boolean isSingleLineLambda(DetailAST lambda) { boolean result = false; final DetailAST block = lambda.getLastChild(); if (block.getType() != TokenTypes.SLIST) { result = lambda.getLineNo() == block.getLineNo(); } return result; } /** * Checks if current case statement is single-line statement, e.g.: * <p> * {@code * case 1: doSomeStuff(); break; * case 2: doSomeStuff(); break; * case 3: ; * } * </p> * @param literalCase {@link TokenTypes#LITERAL_CASE case statement}. * @return true if current case statement is single-line statement. */ private static boolean isSingleLineCase(DetailAST literalCase) { boolean result = false; final DetailAST slist = literalCase.getNextSibling(); if (slist == null) { result = true; } else { final DetailAST block = slist.getFirstChild(); if (block.getType() != TokenTypes.SLIST) { final DetailAST caseBreak = slist.findFirstToken(TokenTypes.LITERAL_BREAK); if (caseBreak != null) { final boolean atOneLine = literalCase.getLineNo() == block.getLineNo(); result = atOneLine && block.getLineNo() == caseBreak.getLineNo(); } } } return result; } /** * Checks if current default statement is single-line statement, e.g.: * <p> * {@code * default: doSomeStuff(); * } * </p> * @param literalDefault {@link TokenTypes#LITERAL_DEFAULT default statement}. * @return true if current default statement is single-line statement. */ private static boolean isSingleLineDefault(DetailAST literalDefault) { boolean result = false; final DetailAST slist = literalDefault.getNextSibling(); if (slist == null) { result = true; } else { final DetailAST block = slist.getFirstChild(); if (block != null && block.getType() != TokenTypes.SLIST) { result = literalDefault.getLineNo() == block.getLineNo(); } } return result; } /** * Checks if current else statement is single-line statement, e.g.: * <p> * {@code * else doSomeStuff(); * } * </p> * @param literalElse {@link TokenTypes#LITERAL_ELSE else statement}. * @return true if current else statement is single-line statement. */ private static boolean isSingleLineElse(DetailAST literalElse) { boolean result = false; final DetailAST block = literalElse.getFirstChild(); if (block.getType() != TokenTypes.SLIST) { result = literalElse.getLineNo() == block.getLineNo(); } return result; } }