1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.coding;
21
22 import java.util.HashSet;
23 import java.util.Objects;
24 import java.util.Optional;
25 import java.util.Set;
26 import java.util.regex.Pattern;
27 import java.util.stream.Stream;
28
29 import com.puppycrawl.tools.checkstyle.StatelessCheck;
30 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
31 import com.puppycrawl.tools.checkstyle.api.DetailAST;
32 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
33 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 @StatelessCheck
61 public class FallThroughCheck extends AbstractCheck {
62
63
64
65
66
67 public static final String MSG_FALL_THROUGH = "fall.through";
68
69
70
71
72
73 public static final String MSG_FALL_THROUGH_LAST = "fall.through.last";
74
75
76 private boolean checkLastCaseGroup;
77
78
79
80
81
82 private Pattern reliefPattern = Pattern.compile("falls?[ -]?thr(u|ough)");
83
84 @Override
85 public int[] getDefaultTokens() {
86 return getRequiredTokens();
87 }
88
89 @Override
90 public int[] getRequiredTokens() {
91 return new int[] {TokenTypes.CASE_GROUP};
92 }
93
94 @Override
95 public int[] getAcceptableTokens() {
96 return getRequiredTokens();
97 }
98
99 @Override
100 public boolean isCommentNodesRequired() {
101 return true;
102 }
103
104
105
106
107
108
109
110
111
112 public void setReliefPattern(Pattern pattern) {
113 reliefPattern = pattern;
114 }
115
116
117
118
119
120
121
122 public void setCheckLastCaseGroup(boolean value) {
123 checkLastCaseGroup = value;
124 }
125
126 @Override
127 public void visitToken(DetailAST ast) {
128 final DetailAST nextGroup = ast.getNextSibling();
129 final boolean isLastGroup = nextGroup.getType() != TokenTypes.CASE_GROUP;
130 if (!isLastGroup || checkLastCaseGroup) {
131 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
132
133 if (slist != null && !isTerminated(slist, true, true, new HashSet<>())
134 && !hasFallThroughComment(ast)) {
135 if (isLastGroup) {
136 log(ast, MSG_FALL_THROUGH_LAST);
137 }
138 else {
139 log(nextGroup, MSG_FALL_THROUGH);
140 }
141 }
142 }
143 }
144
145
146
147
148
149
150
151
152
153
154
155
156
157 private boolean isTerminated(final DetailAST ast, boolean useBreak,
158 boolean useContinue, Set<String> labelsForCurrentSwitchScope) {
159
160 return switch (ast.getType()) {
161 case TokenTypes.LITERAL_RETURN, TokenTypes.LITERAL_YIELD,
162 TokenTypes.LITERAL_THROW -> true;
163 case TokenTypes.LITERAL_BREAK -> useBreak
164 || hasLabel(ast, labelsForCurrentSwitchScope);
165 case TokenTypes.LITERAL_CONTINUE -> useContinue
166 || hasLabel(ast, labelsForCurrentSwitchScope);
167 case TokenTypes.SLIST -> checkSlist(ast, useBreak, useContinue,
168 labelsForCurrentSwitchScope);
169 case TokenTypes.LITERAL_IF -> checkIf(ast, useBreak, useContinue,
170 labelsForCurrentSwitchScope);
171 case TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO ->
172 checkLoop(ast, labelsForCurrentSwitchScope);
173 case TokenTypes.LITERAL_TRY -> checkTry(ast, useBreak, useContinue,
174 labelsForCurrentSwitchScope);
175 case TokenTypes.LITERAL_SWITCH -> checkSwitch(ast, useContinue,
176 labelsForCurrentSwitchScope);
177 case TokenTypes.LITERAL_SYNCHRONIZED ->
178 checkSynchronized(ast, useBreak, useContinue,
179 labelsForCurrentSwitchScope);
180 case TokenTypes.LABELED_STAT -> {
181 labelsForCurrentSwitchScope.add(ast.getFirstChild().getText());
182 yield isTerminated(ast.getLastChild(), useBreak, useContinue,
183 labelsForCurrentSwitchScope);
184 }
185 default -> false;
186 };
187 }
188
189
190
191
192
193
194
195
196 private static boolean hasLabel(DetailAST statement, Set<String> labelsForCurrentSwitchScope) {
197 return Optional.ofNullable(statement)
198 .map(DetailAST::getFirstChild)
199 .filter(child -> child.getType() == TokenTypes.IDENT)
200 .map(DetailAST::getText)
201 .filter(label -> !labelsForCurrentSwitchScope.contains(label))
202 .isPresent();
203 }
204
205
206
207
208
209
210
211
212
213
214
215 private boolean checkSlist(final DetailAST slistAst, boolean useBreak,
216 boolean useContinue, Set<String> labels) {
217 DetailAST lastStmt = slistAst.getLastChild();
218
219 if (lastStmt.getType() == TokenTypes.RCURLY) {
220 lastStmt = lastStmt.getPreviousSibling();
221 }
222
223 while (TokenUtil.isOfType(lastStmt, TokenTypes.SINGLE_LINE_COMMENT,
224 TokenTypes.BLOCK_COMMENT_BEGIN)) {
225 lastStmt = lastStmt.getPreviousSibling();
226 }
227
228 return lastStmt != null
229 && isTerminated(lastStmt, useBreak, useContinue, labels);
230 }
231
232
233
234
235
236
237
238
239
240
241
242 private boolean checkIf(final DetailAST ast, boolean useBreak,
243 boolean useContinue, Set<String> labels) {
244 final DetailAST thenStmt = getNextNonCommentAst(ast.findFirstToken(TokenTypes.RPAREN));
245
246 final DetailAST elseStmt = getNextNonCommentAst(thenStmt);
247
248 return elseStmt != null
249 && isTerminated(thenStmt, useBreak, useContinue, labels)
250 && isTerminated(elseStmt.getLastChild(), useBreak, useContinue, labels);
251 }
252
253
254
255
256
257
258
259 private static DetailAST getNextNonCommentAst(DetailAST ast) {
260 DetailAST nextSibling = ast.getNextSibling();
261 while (TokenUtil.isOfType(nextSibling, TokenTypes.SINGLE_LINE_COMMENT,
262 TokenTypes.BLOCK_COMMENT_BEGIN)) {
263 nextSibling = nextSibling.getNextSibling();
264 }
265 return nextSibling;
266 }
267
268
269
270
271
272
273
274
275
276 private boolean checkLoop(final DetailAST ast, Set<String> labels) {
277 final DetailAST loopBody;
278 if (ast.getType() == TokenTypes.LITERAL_DO) {
279 final DetailAST lparen = ast.findFirstToken(TokenTypes.DO_WHILE);
280 loopBody = lparen.getPreviousSibling();
281 }
282 else {
283 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
284 loopBody = rparen.getNextSibling();
285 }
286 return isTerminated(loopBody, false, false, labels);
287 }
288
289
290
291
292
293
294
295
296
297
298
299 private boolean checkTry(final DetailAST ast, boolean useBreak,
300 boolean useContinue, Set<String> labels) {
301 final DetailAST finalStmt = ast.getLastChild();
302 boolean isTerminated = finalStmt.getType() == TokenTypes.LITERAL_FINALLY
303 && isTerminated(finalStmt.findFirstToken(TokenTypes.SLIST),
304 useBreak, useContinue, labels);
305
306 if (!isTerminated) {
307 DetailAST firstChild = ast.getFirstChild();
308
309 if (firstChild.getType() == TokenTypes.RESOURCE_SPECIFICATION) {
310 firstChild = firstChild.getNextSibling();
311 }
312
313 isTerminated = isTerminated(firstChild,
314 useBreak, useContinue, labels);
315
316 DetailAST catchStmt = ast.findFirstToken(TokenTypes.LITERAL_CATCH);
317 while (catchStmt != null
318 && isTerminated
319 && catchStmt.getType() == TokenTypes.LITERAL_CATCH) {
320 final DetailAST catchBody =
321 catchStmt.findFirstToken(TokenTypes.SLIST);
322 isTerminated = isTerminated(catchBody, useBreak, useContinue, labels);
323 catchStmt = catchStmt.getNextSibling();
324 }
325 }
326 return isTerminated;
327 }
328
329
330
331
332
333
334
335
336
337
338 private boolean checkSwitch(DetailAST literalSwitchAst,
339 boolean useContinue, Set<String> labels) {
340 DetailAST caseGroup = literalSwitchAst.findFirstToken(TokenTypes.CASE_GROUP);
341 boolean isTerminated = caseGroup != null;
342 while (isTerminated && caseGroup.getType() != TokenTypes.RCURLY) {
343 final DetailAST caseBody =
344 caseGroup.findFirstToken(TokenTypes.SLIST);
345 isTerminated = caseBody != null
346 && isTerminated(caseBody, false, useContinue, labels);
347 caseGroup = caseGroup.getNextSibling();
348 }
349 return isTerminated;
350 }
351
352
353
354
355
356
357
358
359
360
361
362 private boolean checkSynchronized(final DetailAST synchronizedAst, boolean useBreak,
363 boolean useContinue, Set<String> labels) {
364 return isTerminated(
365 synchronizedAst.findFirstToken(TokenTypes.SLIST), useBreak, useContinue, labels);
366 }
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393 private boolean hasFallThroughComment(DetailAST currentCase) {
394 final DetailAST nextSibling = currentCase.getNextSibling();
395 final DetailAST ast;
396 if (nextSibling.getType() == TokenTypes.CASE_GROUP) {
397 ast = nextSibling.getFirstChild();
398 }
399 else {
400 ast = currentCase;
401 }
402 return hasReliefComment(ast);
403 }
404
405
406
407
408
409
410
411 private boolean hasReliefComment(DetailAST ast) {
412 final DetailAST nonCommentAst = getNextNonCommentAst(ast);
413 boolean result = false;
414 if (nonCommentAst != null) {
415 final int prevLineNumber = nonCommentAst.getPreviousSibling().getLineNo();
416 result = Stream.iterate(nonCommentAst.getPreviousSibling(),
417 Objects::nonNull,
418 DetailAST::getPreviousSibling)
419 .takeWhile(sibling -> sibling.getLineNo() == prevLineNumber)
420 .map(DetailAST::getFirstChild)
421 .filter(Objects::nonNull)
422 .anyMatch(firstChild -> reliefPattern.matcher(firstChild.getText()).find());
423 }
424 return result;
425 }
426
427 }