001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2022 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 * To configure the check:
051 * </p>
052 * <pre>
053 * &lt;module name=&quot;SimplifyBooleanReturn&quot;/&gt;
054 * </pre>
055 * <p>Example:</p>
056 * <pre>
057 * public class Test {
058 *
059 *  private boolean cond;
060 *  private Foo a;
061 *  private Foo b;
062 *
063 *  public boolean check1() {
064 *   if (cond) { // violation, can be simplified
065 *     return true;
066 *   }
067 *   else {
068 *     return false;
069 *   }
070 *  }
071 *
072 *  // Ok, simplified version of check1()
073 *  public boolean check2() {
074 *   return cond;
075 *  }
076 *
077 *  // violations, can be simplified
078 *  public boolean check3() {
079 *   if (cond == true) { // can be simplified to "if (cond)"
080 *     return false;
081 *   }
082 *   else {
083 *     return true; // can be simplified to "return !cond"
084 *   }
085 *  }
086 *
087 *  // Ok, can be simplified but doesn't return a Boolean
088 *  public Foo choose1() {
089 *   if (cond) {
090 *     return a;
091 *   }
092 *   else {
093 *     return b;
094 *   }
095 *  }
096 *
097 *  // Ok, simplified version of choose1()
098 *  public Foo choose2() {
099 *   return cond ? a: b;
100 *  }
101 *
102 * }
103 * </pre>
104 * <p>
105 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
106 * </p>
107 * <p>
108 * Violation Message Keys:
109 * </p>
110 * <ul>
111 * <li>
112 * {@code simplify.boolReturn}
113 * </li>
114 * </ul>
115 *
116 * @since 3.0
117 */
118@StatelessCheck
119public class SimplifyBooleanReturnCheck
120    extends AbstractCheck {
121
122    /**
123     * A key is pointing to the warning message text in "messages.properties"
124     * file.
125     */
126    public static final String MSG_KEY = "simplify.boolReturn";
127
128    @Override
129    public int[] getAcceptableTokens() {
130        return getRequiredTokens();
131    }
132
133    @Override
134    public int[] getDefaultTokens() {
135        return getRequiredTokens();
136    }
137
138    @Override
139    public int[] getRequiredTokens() {
140        return new int[] {TokenTypes.LITERAL_IF};
141    }
142
143    @Override
144    public void visitToken(DetailAST ast) {
145        // LITERAL_IF has the following four or five children:
146        // '('
147        // condition
148        // ')'
149        // thenStatement
150        // [ LITERAL_ELSE (with the elseStatement as a child) ]
151
152        // don't bother if this is not if then else
153        final DetailAST elseLiteral =
154            ast.findFirstToken(TokenTypes.LITERAL_ELSE);
155        if (elseLiteral != null) {
156            final DetailAST elseStatement = elseLiteral.getFirstChild();
157
158            // skip '(' and ')'
159            final DetailAST condition = ast.getFirstChild().getNextSibling();
160            final DetailAST thenStatement = condition.getNextSibling().getNextSibling();
161
162            if (canReturnOnlyBooleanLiteral(thenStatement)
163                && canReturnOnlyBooleanLiteral(elseStatement)) {
164                log(ast, MSG_KEY);
165            }
166        }
167    }
168
169    /**
170     * Returns if an AST is a return statement with a boolean literal
171     * or a compound statement that contains only such a return statement.
172     *
173     * <p>Returns {@code true} iff ast represents
174     * <pre>
175     * return true/false;
176     * </pre>
177     * or
178     * <pre>
179     * {
180     *   return true/false;
181     * }
182     * </pre>
183     *
184     * @param ast the syntax tree to check
185     * @return if ast is a return statement with a boolean literal.
186     */
187    private static boolean canReturnOnlyBooleanLiteral(DetailAST ast) {
188        boolean result = true;
189        if (!isBooleanLiteralReturnStatement(ast)) {
190            final DetailAST firstStatement = ast.getFirstChild();
191            result = isBooleanLiteralReturnStatement(firstStatement);
192        }
193        return result;
194    }
195
196    /**
197     * Returns if an AST is a return statement with a boolean literal.
198     *
199     * <p>Returns {@code true} iff ast represents
200     * <pre>
201     * return true/false;
202     * </pre>
203     *
204     * @param ast the syntax tree to check
205     * @return if ast is a return statement with a boolean literal.
206     */
207    private static boolean isBooleanLiteralReturnStatement(DetailAST ast) {
208        boolean booleanReturnStatement = false;
209
210        if (ast != null && ast.getType() == TokenTypes.LITERAL_RETURN) {
211            final DetailAST expr = ast.getFirstChild();
212
213            if (expr.getType() != TokenTypes.SEMI) {
214                final DetailAST value = expr.getFirstChild();
215                booleanReturnStatement = TokenUtil.isBooleanLiteralType(value.getType());
216            }
217        }
218        return booleanReturnStatement;
219    }
220}