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