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