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