View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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 antlr.collections.AST;
23  import com.puppycrawl.tools.checkstyle.StatelessCheck;
24  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
25  import com.puppycrawl.tools.checkstyle.api.DetailAST;
26  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
27  
28  /**
29   * <p>
30   * Checks for overly complicated boolean return statements.
31   * Idea shamelessly stolen from the equivalent PMD rule (pmd.sourceforge.net).
32   * </p>
33   * <p>
34   * An example of how to configure the check is:
35   * </p>
36   * <pre>
37   * &lt;module name="SimplifyBooleanReturn"/&gt;
38   * </pre>
39   * @author Lars K├╝hne
40   */
41  @StatelessCheck
42  public class SimplifyBooleanReturnCheck
43      extends AbstractCheck {
44  
45      /**
46       * A key is pointing to the warning message text in "messages.properties"
47       * file.
48       */
49      public static final String MSG_KEY = "simplify.boolReturn";
50  
51      @Override
52      public int[] getAcceptableTokens() {
53          return getRequiredTokens();
54      }
55  
56      @Override
57      public int[] getDefaultTokens() {
58          return getRequiredTokens();
59      }
60  
61      @Override
62      public int[] getRequiredTokens() {
63          return new int[] {TokenTypes.LITERAL_IF};
64      }
65  
66      @Override
67      public void visitToken(DetailAST ast) {
68          // LITERAL_IF has the following four or five children:
69          // '('
70          // condition
71          // ')'
72          // thenStatement
73          // [ LITERAL_ELSE (with the elseStatement as a child) ]
74  
75          // don't bother if this is not if then else
76          final AST elseLiteral =
77              ast.findFirstToken(TokenTypes.LITERAL_ELSE);
78          if (elseLiteral != null) {
79              final AST elseStatement = elseLiteral.getFirstChild();
80  
81              // skip '(' and ')'
82              final AST condition = ast.getFirstChild().getNextSibling();
83              final AST thenStatement = condition.getNextSibling().getNextSibling();
84  
85              if (canReturnOnlyBooleanLiteral(thenStatement)
86                  && canReturnOnlyBooleanLiteral(elseStatement)) {
87                  log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY);
88              }
89          }
90      }
91  
92      /**
93       * Returns if an AST is a return statement with a boolean literal
94       * or a compound statement that contains only such a return statement.
95       *
96       * <p>Returns {@code true} iff ast represents
97       * <br/>
98       * <pre>
99       * return true/false;
100      * </pre>
101      * or
102      * <br/>
103      * <pre>
104      * {
105      *   return true/false;
106      * }
107      * </pre>
108      *
109      * @param ast the syntax tree to check
110      * @return if ast is a return statement with a boolean literal.
111      */
112     private static boolean canReturnOnlyBooleanLiteral(AST ast) {
113         boolean result = true;
114         if (!isBooleanLiteralReturnStatement(ast)) {
115             final AST firstStatement = ast.getFirstChild();
116             result = isBooleanLiteralReturnStatement(firstStatement);
117         }
118         return result;
119     }
120 
121     /**
122      * Returns if an AST is a return statement with a boolean literal.
123      *
124      * <p>Returns {@code true} iff ast represents
125      * <br/>
126      * <pre>
127      * return true/false;
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 isBooleanLiteralReturnStatement(AST ast) {
134         boolean booleanReturnStatement = false;
135 
136         if (ast != null && ast.getType() == TokenTypes.LITERAL_RETURN) {
137             final AST expr = ast.getFirstChild();
138 
139             if (expr.getType() != TokenTypes.SEMI) {
140                 final AST value = expr.getFirstChild();
141                 booleanReturnStatement = isBooleanLiteralType(value.getType());
142             }
143         }
144         return booleanReturnStatement;
145     }
146 
147     /**
148      * Checks if a token type is a literal true or false.
149      * @param tokenType the TokenType
150      * @return true iff tokenType is LITERAL_TRUE or LITERAL_FALSE
151      */
152     private static boolean isBooleanLiteralType(final int tokenType) {
153         final boolean isTrue = tokenType == TokenTypes.LITERAL_TRUE;
154         final boolean isFalse = tokenType == TokenTypes.LITERAL_FALSE;
155         return isTrue || isFalse;
156     }
157 }