View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.checkstyle;
6   
7   import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
8   import com.puppycrawl.tools.checkstyle.api.DetailAST;
9   import com.puppycrawl.tools.checkstyle.api.TokenTypes;
10  
11  /**
12   * Forbids an {@code else} branch when the {@code then} branch of an
13   * {@code if} statement ends with a {@code throw}.
14   *
15   * <p>When the {@code then} branch unconditionally throws an exception
16   * the control flow never reaches the code that follows the
17   * {@code if}/{@code else} pair, so the {@code else} keyword adds no
18   * information and only deepens nesting. Remove the {@code else} and
19   * leave the alternative body at the original indentation level:</p>
20   *
21   * <pre>
22   * // wrong
23   * if (x &lt; 0) {
24   *     throw new IllegalArgumentException("negative");
25   * } else {
26   *     process(x);
27   * }
28   * // right
29   * if (x &lt; 0) {
30   *     throw new IllegalArgumentException("negative");
31   * }
32   * process(x);
33   * </pre>
34   *
35   * <p>See <a href="https://www.yegor256.com/2015/01/21/if-then-throw-else.html">
36   * "If-Then-Throw-Else"</a> for the rationale.</p>
37   *
38   * @since 0.24
39   */
40  public final class IfThenThrowElseCheck extends AbstractCheck {
41  
42      @Override
43      public int[] getDefaultTokens() {
44          return this.getRequiredTokens();
45      }
46  
47      @Override
48      public int[] getAcceptableTokens() {
49          return this.getRequiredTokens();
50      }
51  
52      @Override
53      public int[] getRequiredTokens() {
54          return new int[] {TokenTypes.LITERAL_IF};
55      }
56  
57      @Override
58      public void visitToken(final DetailAST ast) {
59          final DetailAST branch = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
60          if (branch != null
61              && IfThenThrowElseCheck.alwaysThrows(IfThenThrowElseCheck.thenBranch(ast))) {
62              this.log(
63                  ast.getLineNo(),
64                  "Avoid ''else'' when ''then'' branch ends with ''throw''"
65              );
66          }
67      }
68  
69      /**
70       * Locates the statement or block that forms the {@code then} branch
71       * of the given {@code if}.
72       * @param ast The {@code LITERAL_IF} node
73       * @return The first statement inside the {@code then} branch
74       */
75      private static DetailAST thenBranch(final DetailAST ast) {
76          final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
77          DetailAST result = null;
78          if (rparen != null) {
79              result = rparen.getNextSibling();
80          }
81          return result;
82      }
83  
84      /**
85       * Tells whether control flow exits the given node through an
86       * unconditional {@code throw}.
87       * @param node The node to inspect
88       * @return True when the last statement is {@code throw}
89       */
90      private static boolean alwaysThrows(final DetailAST node) {
91          final boolean result;
92          if (node == null) {
93              result = false;
94          } else if (node.getType() == TokenTypes.LITERAL_THROW) {
95              result = true;
96          } else if (node.getType() == TokenTypes.SLIST) {
97              result = IfThenThrowElseCheck.endsWithThrow(node);
98          } else {
99              result = false;
100         }
101         return result;
102     }
103 
104     /**
105      * Checks whether the last meaningful statement inside a
106      * {@code SLIST} is a {@code throw}.
107      * @param slist The {@code SLIST} node
108      * @return True when the last statement is {@code throw}
109      */
110     private static boolean endsWithThrow(final DetailAST slist) {
111         DetailAST last = slist.getLastChild();
112         while (last != null && last.getType() == TokenTypes.RCURLY) {
113             last = last.getPreviousSibling();
114         }
115         return last != null && last.getType() == TokenTypes.LITERAL_THROW;
116     }
117 }