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      /**
43       * Default constructor.
44       */
45      public IfThenThrowElseCheck() {
46          // nothing to initialize
47      }
48  
49      @Override
50      public int[] getDefaultTokens() {
51          return this.getRequiredTokens();
52      }
53  
54      @Override
55      public int[] getAcceptableTokens() {
56          return this.getRequiredTokens();
57      }
58  
59      @Override
60      public int[] getRequiredTokens() {
61          return new int[] {TokenTypes.LITERAL_IF};
62      }
63  
64      @Override
65      public void visitToken(final DetailAST ast) {
66          final DetailAST branch = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
67          if (branch != null
68              && IfThenThrowElseCheck.alwaysThrows(IfThenThrowElseCheck.thenBranch(ast))) {
69              this.log(
70                  ast.getLineNo(),
71                  "Avoid ''else'' when ''then'' branch ends with ''throw''"
72              );
73          }
74      }
75  
76      private static DetailAST thenBranch(final DetailAST ast) {
77          final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
78          DetailAST result = null;
79          if (rparen != null) {
80              result = rparen.getNextSibling();
81          }
82          return result;
83      }
84  
85      private static boolean alwaysThrows(final DetailAST node) {
86          final boolean result;
87          if (node == null) {
88              result = false;
89          } else if (node.getType() == TokenTypes.LITERAL_THROW) {
90              result = true;
91          } else if (node.getType() == TokenTypes.SLIST) {
92              result = IfThenThrowElseCheck.endsWithThrow(node);
93          } else {
94              result = false;
95          }
96          return result;
97      }
98  
99      private static boolean endsWithThrow(final DetailAST slist) {
100         DetailAST last = slist.getLastChild();
101         while (last != null && last.getType() == TokenTypes.RCURLY) {
102             last = last.getPreviousSibling();
103         }
104         return last != null && last.getType() == TokenTypes.LITERAL_THROW;
105     }
106 }