1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.coding;
21  
22  import com.puppycrawl.tools.checkstyle.StatelessCheck;
23  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24  import com.puppycrawl.tools.checkstyle.api.DetailAST;
25  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26  
27  /**
28   * <p>
29   * Checks for overly complicated boolean return statements.
30   * Idea shamelessly stolen from the equivalent PMD rule (pmd.sourceforge.net).
31   * </p>
32   * <p>
33   * An example of how to configure the check is:
34   * </p>
35   * <pre>
36   * &lt;module name="SimplifyBooleanReturn"/&gt;
37   * </pre>
38   */
39  @StatelessCheck
40  public class SimplifyBooleanReturnCheck
41      extends AbstractCheck {
42  
43      /**
44       * A key is pointing to the warning message text in "messages.properties"
45       * file.
46       */
47      public static final String MSG_KEY = "simplify.boolReturn";
48  
49      @Override
50      public int[] getAcceptableTokens() {
51          return getRequiredTokens();
52      }
53  
54      @Override
55      public int[] getDefaultTokens() {
56          return getRequiredTokens();
57      }
58  
59      @Override
60      public int[] getRequiredTokens() {
61          return new int[] {TokenTypes.LITERAL_IF};
62      }
63  
64      @Override
65      public void visitToken(DetailAST ast) {
66          // LITERAL_IF has the following four or five children:
67          // '('
68          // condition
69          // ')'
70          // thenStatement
71          // [ LITERAL_ELSE (with the elseStatement as a child) ]
72  
73          // don't bother if this is not if then else
74          final DetailAST elseLiteral =
75              ast.findFirstToken(TokenTypes.LITERAL_ELSE);
76          if (elseLiteral != null) {
77              final DetailAST elseStatement = elseLiteral.getFirstChild();
78  
79              // skip '(' and ')'
80              final DetailAST condition = ast.getFirstChild().getNextSibling();
81              final DetailAST thenStatement = condition.getNextSibling().getNextSibling();
82  
83              if (canReturnOnlyBooleanLiteral(thenStatement)
84                  && canReturnOnlyBooleanLiteral(elseStatement)) {
85                  log(ast, MSG_KEY);
86              }
87          }
88      }
89  
90      /**
91       * Returns if an AST is a return statement with a boolean literal
92       * or a compound statement that contains only such a return statement.
93       *
94       * <p>Returns {@code true} iff ast represents
95       * <br/>
96       * <pre>
97       * return true/false;
98       * </pre>
99       * or
100      * <br/>
101      * <pre>
102      * {
103      *   return true/false;
104      * }
105      * </pre>
106      *
107      * @param ast the syntax tree to check
108      * @return if ast is a return statement with a boolean literal.
109      */
110     private static boolean canReturnOnlyBooleanLiteral(DetailAST ast) {
111         boolean result = true;
112         if (!isBooleanLiteralReturnStatement(ast)) {
113             final DetailAST firstStatement = ast.getFirstChild();
114             result = isBooleanLiteralReturnStatement(firstStatement);
115         }
116         return result;
117     }
118 
119     /**
120      * Returns if an AST is a return statement with a boolean literal.
121      *
122      * <p>Returns {@code true} iff ast represents
123      * <br/>
124      * <pre>
125      * return true/false;
126      * </pre>
127      *
128      * @param ast the syntax tree to check
129      * @return if ast is a return statement with a boolean literal.
130      */
131     private static boolean isBooleanLiteralReturnStatement(DetailAST ast) {
132         boolean booleanReturnStatement = false;
133 
134         if (ast != null && ast.getType() == TokenTypes.LITERAL_RETURN) {
135             final DetailAST expr = ast.getFirstChild();
136 
137             if (expr.getType() != TokenTypes.SEMI) {
138                 final DetailAST value = expr.getFirstChild();
139                 booleanReturnStatement = isBooleanLiteralType(value.getType());
140             }
141         }
142         return booleanReturnStatement;
143     }
144 
145     /**
146      * Checks if a token type is a literal true or false.
147      * @param tokenType the TokenType
148      * @return true iff tokenType is LITERAL_TRUE or LITERAL_FALSE
149      */
150     private static boolean isBooleanLiteralType(final int tokenType) {
151         final boolean isTrue = tokenType == TokenTypes.LITERAL_TRUE;
152         final boolean isFalse = tokenType == TokenTypes.LITERAL_FALSE;
153         return isTrue || isFalse;
154     }
155 
156 }