1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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 java.util.Objects;
23 import java.util.regex.Pattern;
24 import java.util.stream.Stream;
25
26 import com.puppycrawl.tools.checkstyle.StatelessCheck;
27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28 import com.puppycrawl.tools.checkstyle.api.DetailAST;
29 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30 import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
31
32 /**
33 * <div>
34 * Checks for fall-through in {@code switch} statements.
35 * Finds locations where a {@code case} <b>contains</b> Java code but lacks a
36 * {@code break}, {@code return}, {@code yield}, {@code throw} or {@code continue} statement.
37 * </div>
38 *
39 * <p>
40 * The check honors special comments to suppress the warning.
41 * By default, the texts
42 * "fallthru", "fall thru", "fall-thru",
43 * "fallthrough", "fall through", "fall-through"
44 * "fallsthrough", "falls through", "falls-through" (case-sensitive).
45 * The comment containing these words must be all on one line,
46 * and must be on the last non-empty line before the {@code case} triggering
47 * the warning or on the same line before the {@code case}(ugly, but possible).
48 * Any other comment may follow on the same line.
49 * </p>
50 *
51 * <p>
52 * Note: The check assumes that there is no unreachable code in the {@code case}.
53 * </p>
54 *
55 * @since 3.4
56 */
57 @StatelessCheck
58 public class FallThroughCheck extends AbstractCheck {
59
60 /**
61 * A key is pointing to the warning message text in "messages.properties"
62 * file.
63 */
64 public static final String MSG_FALL_THROUGH = "fall.through";
65
66 /**
67 * A key is pointing to the warning message text in "messages.properties"
68 * file.
69 */
70 public static final String MSG_FALL_THROUGH_LAST = "fall.through.last";
71
72 /** Control whether the last case group must be checked. */
73 private boolean checkLastCaseGroup;
74
75 /**
76 * Define the RegExp to match the relief comment that suppresses
77 * the warning about a fall through.
78 */
79 private Pattern reliefPattern = Pattern.compile("falls?[ -]?thr(u|ough)");
80
81 @Override
82 public int[] getDefaultTokens() {
83 return getRequiredTokens();
84 }
85
86 @Override
87 public int[] getRequiredTokens() {
88 return new int[] {TokenTypes.CASE_GROUP};
89 }
90
91 @Override
92 public int[] getAcceptableTokens() {
93 return getRequiredTokens();
94 }
95
96 @Override
97 public boolean isCommentNodesRequired() {
98 return true;
99 }
100
101 /**
102 * Setter to define the RegExp to match the relief comment that suppresses
103 * the warning about a fall through.
104 *
105 * @param pattern
106 * The regular expression pattern.
107 * @since 4.0
108 */
109 public void setReliefPattern(Pattern pattern) {
110 reliefPattern = pattern;
111 }
112
113 /**
114 * Setter to control whether the last case group must be checked.
115 *
116 * @param value new value of the property.
117 * @since 4.0
118 */
119 public void setCheckLastCaseGroup(boolean value) {
120 checkLastCaseGroup = value;
121 }
122
123 @Override
124 public void visitToken(DetailAST ast) {
125 final DetailAST nextGroup = ast.getNextSibling();
126 final boolean isLastGroup = nextGroup.getType() != TokenTypes.CASE_GROUP;
127 if (!isLastGroup || checkLastCaseGroup) {
128 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
129
130 if (slist != null && !CheckUtil.isTerminated(slist) && !hasFallThroughComment(ast)) {
131 if (isLastGroup) {
132 log(ast, MSG_FALL_THROUGH_LAST);
133 }
134 else {
135 log(nextGroup, MSG_FALL_THROUGH);
136 }
137 }
138 }
139 }
140
141 /**
142 * Determines if the fall through case between {@code currentCase} and
143 * {@code nextCase} is relieved by an appropriate comment.
144 *
145 * <p>Handles</p>
146 * <pre>
147 * case 1:
148 * /* FALLTHRU */ case 2:
149 *
150 * switch(i) {
151 * default:
152 * /* FALLTHRU */}
153 *
154 * case 1:
155 * // FALLTHRU
156 * case 2:
157 *
158 * switch(i) {
159 * default:
160 * // FALLTHRU
161 * </pre>
162 *
163 * @param currentCase AST of the case that falls through to the next case.
164 * @return True if a relief comment was found
165 */
166 private boolean hasFallThroughComment(DetailAST currentCase) {
167 final DetailAST nextSibling = currentCase.getNextSibling();
168 final DetailAST ast;
169 if (nextSibling.getType() == TokenTypes.CASE_GROUP) {
170 ast = nextSibling.getFirstChild();
171 }
172 else {
173 ast = currentCase;
174 }
175 return hasReliefComment(ast);
176 }
177
178 /**
179 * Check if there is any fall through comment.
180 *
181 * @param ast ast to check
182 * @return true if relief comment found
183 */
184 private boolean hasReliefComment(DetailAST ast) {
185 final DetailAST nonCommentAst = CheckUtil.getNextNonCommentAst(ast);
186 boolean result = false;
187 if (nonCommentAst != null) {
188 final int prevLineNumber = nonCommentAst.getPreviousSibling().getLineNo();
189 result = Stream.iterate(nonCommentAst.getPreviousSibling(),
190 Objects::nonNull,
191 DetailAST::getPreviousSibling)
192 .takeWhile(sibling -> sibling.getLineNo() == prevLineNumber)
193 .map(DetailAST::getFirstChild)
194 .filter(Objects::nonNull)
195 .anyMatch(firstChild -> reliefPattern.matcher(firstChild.getText()).find());
196 }
197 return result;
198 }
199
200 }