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 * <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/pmd_rules_java_design.html#simplifybooleanreturns">
048 *     PMD</a> rule.
049 * </p>
050 * <p>
051 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
052 * </p>
053 * <p>
054 * Violation Message Keys:
055 * </p>
056 * <ul>
057 * <li>
058 * {@code simplify.boolReturn}
059 * </li>
060 * </ul>
061 *
062 * @since 3.0
063 */
064@StatelessCheck
065public class SimplifyBooleanReturnCheck
066    extends AbstractCheck {
067
068    /**
069     * A key is pointing to the warning message text in "messages.properties"
070     * file.
071     */
072    public static final String MSG_KEY = "simplify.boolReturn";
073
074    @Override
075    public int[] getAcceptableTokens() {
076        return getRequiredTokens();
077    }
078
079    @Override
080    public int[] getDefaultTokens() {
081        return getRequiredTokens();
082    }
083
084    @Override
085    public int[] getRequiredTokens() {
086        return new int[] {TokenTypes.LITERAL_IF};
087    }
088
089    @Override
090    public void visitToken(DetailAST ast) {
091        // LITERAL_IF has the following four or five children:
092        // '('
093        // condition
094        // ')'
095        // thenStatement
096        // [ LITERAL_ELSE (with the elseStatement as a child) ]
097
098        // don't bother if this is not if then else
099        final DetailAST elseLiteral =
100            ast.findFirstToken(TokenTypes.LITERAL_ELSE);
101        if (elseLiteral != null) {
102            final DetailAST elseStatement = elseLiteral.getFirstChild();
103
104            // skip '(' and ')'
105            final DetailAST condition = ast.getFirstChild().getNextSibling();
106            final DetailAST thenStatement = condition.getNextSibling().getNextSibling();
107
108            if (canReturnOnlyBooleanLiteral(thenStatement)
109                && canReturnOnlyBooleanLiteral(elseStatement)) {
110                log(ast, MSG_KEY);
111            }
112        }
113    }
114
115    /**
116     * Returns if an AST is a return statement with a boolean literal
117     * or a compound statement that contains only such a return statement.
118     *
119     * <p>Returns {@code true} iff ast represents
120     * <pre>
121     * return true/false;
122     * </pre>
123     * or
124     * <pre>
125     * {
126     *   return true/false;
127     * }
128     * </pre>
129     *
130     * @param ast the syntax tree to check
131     * @return if ast is a return statement with a boolean literal.
132     */
133    private static boolean canReturnOnlyBooleanLiteral(DetailAST ast) {
134        boolean result = true;
135        if (!isBooleanLiteralReturnStatement(ast)) {
136            final DetailAST firstStatement = ast.getFirstChild();
137            result = isBooleanLiteralReturnStatement(firstStatement);
138        }
139        return result;
140    }
141
142    /**
143     * Returns if an AST is a return statement with a boolean literal.
144     *
145     * <p>Returns {@code true} iff ast represents
146     * <pre>
147     * return true/false;
148     * </pre>
149     *
150     * @param ast the syntax tree to check
151     * @return if ast is a return statement with a boolean literal.
152     */
153    private static boolean isBooleanLiteralReturnStatement(DetailAST ast) {
154        boolean booleanReturnStatement = false;
155
156        if (ast != null && ast.getType() == TokenTypes.LITERAL_RETURN) {
157            final DetailAST expr = ast.getFirstChild();
158
159            if (expr.getType() != TokenTypes.SEMI) {
160                final DetailAST value = expr.getFirstChild();
161                booleanReturnStatement = TokenUtil.isBooleanLiteralType(value.getType());
162            }
163        }
164        return booleanReturnStatement;
165    }
166}