001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2023 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.coding;
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.TokenUtil;
027
028/**
029 * <p>
030 * Checks for over-complicated boolean return statements.
031 * For example the following code
032 * </p>
033 * <pre>
034 * if (valid())
035 *   return false;
036 * else
037 *   return true;
038 * </pre>
039 * <p>
040 * could be written as
041 * </p>
042 * <pre>
043 * return !valid();
044 * </pre>
045 * <p>
046 * The idea for this Check has been shamelessly stolen from the equivalent
047 * <a href="https://pmd.github.io/">PMD</a> rule.
048 * </p>
049 * <p>
050 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
051 * </p>
052 * <p>
053 * Violation Message Keys:
054 * </p>
055 * <ul>
056 * <li>
057 * {@code simplify.boolReturn}
058 * </li>
059 * </ul>
060 *
061 * @since 3.0
062 */
063@StatelessCheck
064public class SimplifyBooleanReturnCheck
065    extends AbstractCheck {
066
067    /**
068     * A key is pointing to the warning message text in "messages.properties"
069     * file.
070     */
071    public static final String MSG_KEY = "simplify.boolReturn";
072
073    @Override
074    public int[] getAcceptableTokens() {
075        return getRequiredTokens();
076    }
077
078    @Override
079    public int[] getDefaultTokens() {
080        return getRequiredTokens();
081    }
082
083    @Override
084    public int[] getRequiredTokens() {
085        return new int[] {TokenTypes.LITERAL_IF};
086    }
087
088    @Override
089    public void visitToken(DetailAST ast) {
090        // LITERAL_IF has the following four or five children:
091        // '('
092        // condition
093        // ')'
094        // thenStatement
095        // [ LITERAL_ELSE (with the elseStatement as a child) ]
096
097        // don't bother if this is not if then else
098        final DetailAST elseLiteral =
099            ast.findFirstToken(TokenTypes.LITERAL_ELSE);
100        if (elseLiteral != null) {
101            final DetailAST elseStatement = elseLiteral.getFirstChild();
102
103            // skip '(' and ')'
104            final DetailAST condition = ast.getFirstChild().getNextSibling();
105            final DetailAST thenStatement = condition.getNextSibling().getNextSibling();
106
107            if (canReturnOnlyBooleanLiteral(thenStatement)
108                && canReturnOnlyBooleanLiteral(elseStatement)) {
109                log(ast, MSG_KEY);
110            }
111        }
112    }
113
114    /**
115     * Returns if an AST is a return statement with a boolean literal
116     * or a compound statement that contains only such a return statement.
117     *
118     * <p>Returns {@code true} iff ast represents
119     * <pre>
120     * return true/false;
121     * </pre>
122     * or
123     * <pre>
124     * {
125     *   return true/false;
126     * }
127     * </pre>
128     *
129     * @param ast the syntax tree to check
130     * @return if ast is a return statement with a boolean literal.
131     */
132    private static boolean canReturnOnlyBooleanLiteral(DetailAST ast) {
133        boolean result = true;
134        if (!isBooleanLiteralReturnStatement(ast)) {
135            final DetailAST firstStatement = ast.getFirstChild();
136            result = isBooleanLiteralReturnStatement(firstStatement);
137        }
138        return result;
139    }
140
141    /**
142     * Returns if an AST is a return statement with a boolean literal.
143     *
144     * <p>Returns {@code true} iff ast represents
145     * <pre>
146     * return true/false;
147     * </pre>
148     *
149     * @param ast the syntax tree to check
150     * @return if ast is a return statement with a boolean literal.
151     */
152    private static boolean isBooleanLiteralReturnStatement(DetailAST ast) {
153        boolean booleanReturnStatement = false;
154
155        if (ast != null && ast.getType() == TokenTypes.LITERAL_RETURN) {
156            final DetailAST expr = ast.getFirstChild();
157
158            if (expr.getType() != TokenTypes.SEMI) {
159                final DetailAST value = expr.getFirstChild();
160                booleanReturnStatement = TokenUtil.isBooleanLiteralType(value.getType());
161            }
162        }
163        return booleanReturnStatement;
164    }
165}