001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
034 * if (valid())
035 *   return false;
036 * else
037 *   return true;
038 * </code></pre></div>
039 *
040 * <p>
041 * could be written as
042 * </p>
043 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
044 * return !valid();
045 * </code></pre></div>
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 * @since 3.0
054 */
055@StatelessCheck
056public class SimplifyBooleanReturnCheck
057    extends AbstractCheck {
058
059    /**
060     * A key is pointing to the warning message text in "messages.properties"
061     * file.
062     */
063    public static final String MSG_KEY = "simplify.boolReturn";
064
065    /**
066     * Creates a new {@code SimplifyBooleanReturnCheck} instance.
067     */
068    public SimplifyBooleanReturnCheck() {
069        // no code by default
070    }
071
072    @Override
073    public int[] getAcceptableTokens() {
074        return getRequiredTokens();
075    }
076
077    @Override
078    public int[] getDefaultTokens() {
079        return getRequiredTokens();
080    }
081
082    @Override
083    public int[] getRequiredTokens() {
084        return new int[] {TokenTypes.LITERAL_IF};
085    }
086
087    @Override
088    public void visitToken(DetailAST ast) {
089        // LITERAL_IF has the following four or five children:
090        // '('
091        // condition
092        // ')'
093        // thenStatement
094        // [ LITERAL_ELSE (with the elseStatement as a child) ]
095
096        // don't bother if this is not if then else
097        final DetailAST elseLiteral =
098            ast.findFirstToken(TokenTypes.LITERAL_ELSE);
099        if (elseLiteral != null) {
100            final DetailAST elseStatement = elseLiteral.getFirstChild();
101
102            // skip '(' and ')'
103            final DetailAST condition = ast.getFirstChild().getNextSibling();
104            final DetailAST thenStatement = condition.getNextSibling().getNextSibling();
105
106            if (canReturnOrYieldOnlyBooleanLiteral(thenStatement)
107                && canReturnOrYieldOnlyBooleanLiteral(elseStatement)) {
108                log(ast, MSG_KEY);
109            }
110        }
111    }
112
113    /**
114     * Returns if an AST is a return or a yield statement with a boolean literal
115     * or a compound statement that contains only such a return or a yield statement.
116     *
117     * <p>Returns {@code true} iff ast represents
118     * <pre>
119     * return/yield true/false;
120     * </pre>
121     * or
122     * <pre>
123     * {
124     *   return/yield true/false;
125     * }
126     * </pre>
127     *
128     * @param ast the syntax tree to check
129     * @return if ast is a return or a yield statement with a boolean literal.
130     */
131    private static boolean canReturnOrYieldOnlyBooleanLiteral(DetailAST ast) {
132        boolean result = true;
133        if (!isBooleanLiteralReturnOrYieldStatement(ast)) {
134            final DetailAST firstStatement = ast.getFirstChild();
135            result = isBooleanLiteralReturnOrYieldStatement(firstStatement);
136        }
137        return result;
138    }
139
140    /**
141     * Returns if an AST is a return or a yield statement with a boolean literal.
142     *
143     * <p>Returns {@code true} iff ast represents
144     * <pre>
145     * return/yield true/false;
146     * </pre>
147     *
148     * @param ast the syntax tree to check
149     * @return if ast is a return or a yield statement with a boolean literal.
150     */
151    private static boolean isBooleanLiteralReturnOrYieldStatement(DetailAST ast) {
152        boolean booleanReturnStatement = false;
153
154        if (ast != null && (ast.getType() == TokenTypes.LITERAL_RETURN
155                                || ast.getType() == TokenTypes.LITERAL_YIELD)) {
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
166}