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:
53 * The check assumes that there is no unreachable code in the {@code case}.
54 * </p>
55 *
56 * <p>
57 * A {@code case} whose code ends in an infinite loop is not flagged, e.g.
58 * {@code while (true) {}}, {@code for (;;) {}}, {@code for (;true;) {}}
59 * or {@code do {} while (true);}.
60 * </p>
61 *
62 * @since 3.4
63 */
64 @StatelessCheck
65 public class FallThroughCheck extends AbstractCheck {
66
67 /**
68 * A key is pointing to the warning message text in "messages.properties"
69 * file.
70 */
71 public static final String MSG_FALL_THROUGH = "fall.through";
72
73 /**
74 * A key is pointing to the warning message text in "messages.properties"
75 * file.
76 */
77 public static final String MSG_FALL_THROUGH_LAST = "fall.through.last";
78
79 /** Control whether the last case group must be checked. */
80 private boolean checkLastCaseGroup;
81
82 /**
83 * Define the RegExp to match the relief comment that suppresses
84 * the warning about a fall through.
85 */
86 private Pattern reliefPattern = Pattern.compile("falls?[ -]?thr(u|ough)");
87
88 /**
89 * Creates a new {@code FallThroughCheck} instance.
90 */
91 public FallThroughCheck() {
92 // no code by default
93 }
94
95 @Override
96 public int[] getDefaultTokens() {
97 return getRequiredTokens();
98 }
99
100 @Override
101 public int[] getRequiredTokens() {
102 return new int[] {TokenTypes.CASE_GROUP};
103 }
104
105 @Override
106 public int[] getAcceptableTokens() {
107 return getRequiredTokens();
108 }
109
110 @Override
111 public boolean isCommentNodesRequired() {
112 return true;
113 }
114
115 /**
116 * Setter to define the RegExp to match the relief comment that suppresses
117 * the warning about a fall through.
118 *
119 * @param pattern
120 * The regular expression pattern.
121 * @since 4.0
122 */
123 public void setReliefPattern(Pattern pattern) {
124 reliefPattern = pattern;
125 }
126
127 /**
128 * Setter to control whether the last case group must be checked.
129 *
130 * @param value new value of the property.
131 * @since 4.0
132 */
133 public void setCheckLastCaseGroup(boolean value) {
134 checkLastCaseGroup = value;
135 }
136
137 @Override
138 public void visitToken(DetailAST ast) {
139 final DetailAST nextGroup = ast.getNextSibling();
140 final boolean isLastGroup = nextGroup.getType() != TokenTypes.CASE_GROUP;
141 if (!isLastGroup || checkLastCaseGroup) {
142 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
143
144 if (slist != null && !CheckUtil.isTerminated(slist) && !hasFallThroughComment(ast)) {
145 if (isLastGroup) {
146 log(ast, MSG_FALL_THROUGH_LAST);
147 }
148 else {
149 log(nextGroup, MSG_FALL_THROUGH);
150 }
151 }
152 }
153 }
154
155 /**
156 * Determines if the fall through case between {@code currentCase} and
157 * {@code nextCase} is relieved by an appropriate comment.
158 *
159 * <p>Handles</p>
160 * <pre>
161 * case 1:
162 * /* FALLTHRU */ case 2:
163 *
164 * switch(i) {
165 * default:
166 * /* FALLTHRU */}
167 *
168 * case 1:
169 * // FALLTHRU
170 * case 2:
171 *
172 * switch(i) {
173 * default:
174 * // FALLTHRU
175 * </pre>
176 *
177 * @param currentCase AST of the case that falls through to the next case.
178 * @return True if a relief comment was found
179 */
180 private boolean hasFallThroughComment(DetailAST currentCase) {
181 final DetailAST nextSibling = currentCase.getNextSibling();
182 final DetailAST ast;
183 if (nextSibling.getType() == TokenTypes.CASE_GROUP) {
184 ast = nextSibling.getFirstChild();
185 }
186 else {
187 ast = currentCase;
188 }
189 return hasReliefComment(ast);
190 }
191
192 /**
193 * Check if there is any fall through comment.
194 *
195 * @param ast ast to check
196 * @return true if relief comment found
197 */
198 private boolean hasReliefComment(DetailAST ast) {
199 final DetailAST nonCommentAst = CheckUtil.getNextNonCommentAst(ast);
200 boolean result = false;
201 if (nonCommentAst != null) {
202 final int prevLineNumber = nonCommentAst.getPreviousSibling().getLineNo();
203 result = Stream.iterate(nonCommentAst.getPreviousSibling(),
204 Objects::nonNull,
205 DetailAST::getPreviousSibling)
206 .takeWhile(sibling -> sibling.getLineNo() == prevLineNumber)
207 .map(DetailAST::getFirstChild)
208 .filter(Objects::nonNull)
209 .anyMatch(firstChild -> reliefPattern.matcher(firstChild.getText()).find());
210 }
211 return result;
212 }
213
214 }