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 < 0) {
24 * throw new IllegalArgumentException("negative");
25 * } else {
26 * process(x);
27 * }
28 * // right
29 * if (x < 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 }