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.blocks;
21
22 import java.util.Arrays;
23 import java.util.Locale;
24 import java.util.Optional;
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.CommonUtil;
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
83
84
85
86
87
88
89 @StatelessCheck
90 public class RightCurlyCheck extends AbstractCheck {
91
92
93
94
95
96 public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before";
97
98
99
100
101
102 public static final String MSG_KEY_LINE_ALONE = "line.alone";
103
104
105
106
107
108 public static final String MSG_KEY_LINE_SAME = "line.same";
109
110
111
112
113 private RightCurlyOption option = RightCurlyOption.SAME;
114
115
116
117
118
119
120
121
122 public void setOption(String optionStr) {
123 option = RightCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
124 }
125
126 @Override
127 public int[] getDefaultTokens() {
128 return new int[] {
129 TokenTypes.LITERAL_TRY,
130 TokenTypes.LITERAL_CATCH,
131 TokenTypes.LITERAL_FINALLY,
132 TokenTypes.LITERAL_IF,
133 TokenTypes.LITERAL_ELSE,
134 };
135 }
136
137 @Override
138 public int[] getAcceptableTokens() {
139 return new int[] {
140 TokenTypes.LITERAL_TRY,
141 TokenTypes.LITERAL_CATCH,
142 TokenTypes.LITERAL_FINALLY,
143 TokenTypes.LITERAL_IF,
144 TokenTypes.LITERAL_ELSE,
145 TokenTypes.CLASS_DEF,
146 TokenTypes.METHOD_DEF,
147 TokenTypes.CTOR_DEF,
148 TokenTypes.LITERAL_FOR,
149 TokenTypes.LITERAL_WHILE,
150 TokenTypes.LITERAL_DO,
151 TokenTypes.STATIC_INIT,
152 TokenTypes.INSTANCE_INIT,
153 TokenTypes.ANNOTATION_DEF,
154 TokenTypes.ENUM_DEF,
155 TokenTypes.INTERFACE_DEF,
156 TokenTypes.RECORD_DEF,
157 TokenTypes.COMPACT_CTOR_DEF,
158 TokenTypes.LITERAL_SWITCH,
159 TokenTypes.LITERAL_CASE,
160 };
161 }
162
163 @Override
164 public int[] getRequiredTokens() {
165 return CommonUtil.EMPTY_INT_ARRAY;
166 }
167
168 @Override
169 public void visitToken(DetailAST ast) {
170 final Details details = Details.getDetails(ast);
171 final DetailAST rcurly = details.rcurly;
172
173 if (rcurly != null) {
174 final String violation = validate(details);
175 if (!violation.isEmpty()) {
176 log(rcurly, violation, "}", rcurly.getColumnNo() + 1);
177 }
178 }
179 }
180
181
182
183
184
185
186
187
188 private String validate(Details details) {
189 String violation = "";
190 if (shouldHaveLineBreakBefore(option, details)) {
191 violation = MSG_KEY_LINE_BREAK_BEFORE;
192 }
193 else if (shouldBeOnSameLine(option, details)) {
194 violation = MSG_KEY_LINE_SAME;
195 }
196 else if (shouldBeAloneOnLine(option, details, getLine(details.rcurly.getLineNo() - 1))) {
197 violation = MSG_KEY_LINE_ALONE;
198 }
199 return violation;
200 }
201
202
203
204
205
206
207
208
209 private static boolean shouldHaveLineBreakBefore(RightCurlyOption bracePolicy,
210 Details details) {
211 return bracePolicy == RightCurlyOption.SAME
212 && !hasLineBreakBefore(details.rcurly)
213 && !TokenUtil.areOnSameLine(details.lcurly, details.rcurly);
214 }
215
216
217
218
219
220
221
222
223 private static boolean shouldBeOnSameLine(RightCurlyOption bracePolicy, Details details) {
224 return bracePolicy == RightCurlyOption.SAME
225 && !details.shouldCheckLastRcurly
226 && !TokenUtil.areOnSameLine(details.rcurly, details.nextToken);
227 }
228
229
230
231
232
233
234
235
236
237 private static boolean shouldBeAloneOnLine(RightCurlyOption bracePolicy,
238 Details details,
239 String targetSrcLine) {
240 return bracePolicy == RightCurlyOption.ALONE
241 && shouldBeAloneOnLineWithAloneOption(details, targetSrcLine)
242 || (bracePolicy == RightCurlyOption.ALONE_OR_SINGLELINE
243 || details.shouldCheckLastRcurly)
244 && shouldBeAloneOnLineWithNotAloneOption(details, targetSrcLine);
245 }
246
247
248
249
250
251
252
253
254 private static boolean shouldBeAloneOnLineWithAloneOption(Details details,
255 String targetSrcLine) {
256 return !isAloneOnLine(details, targetSrcLine);
257 }
258
259
260
261
262
263
264
265
266
267 private static boolean shouldBeAloneOnLineWithNotAloneOption(Details details,
268 String targetSrcLine) {
269 return shouldBeAloneOnLineWithAloneOption(details, targetSrcLine)
270 && !isBlockAloneOnSingleLine(details);
271 }
272
273
274
275
276
277
278
279
280 private static boolean isAloneOnLine(Details details, String targetSrcLine) {
281 final DetailAST rcurly = details.rcurly;
282 final DetailAST nextToken = details.nextToken;
283 return (nextToken == null || !TokenUtil.areOnSameLine(rcurly, nextToken)
284 || skipDoubleBraceInstInit(details))
285 && CommonUtil.hasWhitespaceBefore(details.rcurly.getColumnNo(),
286 targetSrcLine);
287 }
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307 private static boolean skipDoubleBraceInstInit(Details details) {
308 boolean skipDoubleBraceInstInit = false;
309 final DetailAST tokenAfterNextToken = Details.getNextToken(details.nextToken);
310 if (tokenAfterNextToken != null) {
311 final DetailAST rcurly = details.rcurly;
312 skipDoubleBraceInstInit = rcurly.getParent().getParent()
313 .getType() == TokenTypes.INSTANCE_INIT
314 && details.nextToken.getType() == TokenTypes.RCURLY
315 && !TokenUtil.areOnSameLine(rcurly, Details.getNextToken(tokenAfterNextToken));
316 }
317 return skipDoubleBraceInstInit;
318 }
319
320
321
322
323
324
325
326 private static boolean isBlockAloneOnSingleLine(Details details) {
327 DetailAST nextToken = details.nextToken;
328
329 while (nextToken != null && nextToken.getType() == TokenTypes.LITERAL_ELSE) {
330 nextToken = Details.getNextToken(nextToken);
331 }
332
333
334 final int[] tokensWithBlockSibling = {
335 TokenTypes.DO_WHILE,
336 TokenTypes.LITERAL_FINALLY,
337 TokenTypes.LITERAL_CATCH,
338 };
339
340 if (TokenUtil.isOfType(nextToken, tokensWithBlockSibling)) {
341 final DetailAST parent = nextToken.getParent();
342 nextToken = Details.getNextToken(parent);
343 }
344
345 return TokenUtil.areOnSameLine(details.lcurly, details.rcurly)
346 && (nextToken == null || !TokenUtil.areOnSameLine(details.rcurly, nextToken)
347 || isRightcurlyFollowedBySemicolon(details));
348 }
349
350
351
352
353
354
355
356 private static boolean isRightcurlyFollowedBySemicolon(Details details) {
357 return details.nextToken.getType() == TokenTypes.SEMI;
358 }
359
360
361
362
363
364
365
366 private static boolean hasLineBreakBefore(DetailAST rightCurly) {
367 DetailAST previousToken = rightCurly.getPreviousSibling();
368 if (previousToken == null) {
369 previousToken = rightCurly.getParent();
370 }
371 return !TokenUtil.areOnSameLine(rightCurly, previousToken);
372 }
373
374
375
376
377 private static final class Details {
378
379
380
381
382 private static final int[] TOKENS_WITH_NO_CHILD_SLIST = {
383 TokenTypes.CLASS_DEF,
384 TokenTypes.ENUM_DEF,
385 TokenTypes.ANNOTATION_DEF,
386 TokenTypes.INTERFACE_DEF,
387 TokenTypes.RECORD_DEF,
388 };
389
390
391 private final DetailAST rcurly;
392
393 private final DetailAST lcurly;
394
395 private final DetailAST nextToken;
396
397 private final boolean shouldCheckLastRcurly;
398
399
400
401
402
403
404
405
406
407 private Details(DetailAST lcurly, DetailAST rcurly,
408 DetailAST nextToken, boolean shouldCheckLastRcurly) {
409 this.lcurly = lcurly;
410 this.rcurly = rcurly;
411 this.nextToken = nextToken;
412 this.shouldCheckLastRcurly = shouldCheckLastRcurly;
413 }
414
415
416
417
418
419
420
421 private static Details getDetails(DetailAST ast) {
422 final Details details;
423 switch (ast.getType()) {
424 case TokenTypes.LITERAL_TRY:
425 case TokenTypes.LITERAL_CATCH:
426 details = getDetailsForTryCatch(ast);
427 break;
428 case TokenTypes.LITERAL_IF:
429 details = getDetailsForIf(ast);
430 break;
431 case TokenTypes.LITERAL_DO:
432 details = getDetailsForDoLoops(ast);
433 break;
434 case TokenTypes.LITERAL_SWITCH:
435 details = getDetailsForSwitch(ast);
436 break;
437 case TokenTypes.LITERAL_CASE:
438 details = getDetailsForCase(ast);
439 break;
440 default:
441 details = getDetailsForOthers(ast);
442 break;
443 }
444 return details;
445 }
446
447
448
449
450
451
452
453 private static Details getDetailsForSwitch(DetailAST switchNode) {
454 final DetailAST lcurly = switchNode.findFirstToken(TokenTypes.LCURLY);
455 final DetailAST rcurly;
456 DetailAST nextToken = null;
457
458 if (isSwitchExpression(switchNode)) {
459 rcurly = null;
460 }
461 else {
462 rcurly = switchNode.getLastChild();
463 nextToken = getNextToken(switchNode);
464 }
465 return new Details(lcurly, rcurly, nextToken, true);
466 }
467
468
469
470
471
472
473
474 private static Details getDetailsForCase(DetailAST caseNode) {
475 final DetailAST caseParent = caseNode.getParent();
476 final int parentType = caseParent.getType();
477 final Optional<DetailAST> lcurly;
478 final DetailAST statementList;
479
480 if (parentType == TokenTypes.SWITCH_RULE) {
481 statementList = caseParent.findFirstToken(TokenTypes.SLIST);
482 lcurly = Optional.ofNullable(statementList);
483 }
484 else {
485 statementList = caseNode.getNextSibling();
486 lcurly = Optional.ofNullable(statementList)
487 .map(DetailAST::getFirstChild)
488 .filter(node -> node.getType() == TokenTypes.SLIST);
489 }
490 final DetailAST rcurly = lcurly.map(DetailAST::getLastChild)
491 .filter(child -> !isSwitchExpression(caseParent))
492 .orElse(null);
493 final Optional<DetailAST> nextToken =
494 Optional.ofNullable(lcurly.map(DetailAST::getNextSibling)
495 .orElseGet(() -> getNextToken(caseParent)));
496
497 return new Details(lcurly.orElse(null), rcurly, nextToken.orElse(null), true);
498 }
499
500
501
502
503
504
505
506 private static boolean isSwitchExpression(DetailAST switchNode) {
507 DetailAST currentNode = switchNode;
508 boolean ans = false;
509
510 while (currentNode != null) {
511 if (currentNode.getType() == TokenTypes.EXPR) {
512 ans = true;
513 }
514 currentNode = currentNode.getParent();
515 }
516 return ans;
517 }
518
519
520
521
522
523
524
525 private static Details getDetailsForTryCatch(DetailAST ast) {
526 final DetailAST lcurly;
527 DetailAST nextToken;
528 final int tokenType = ast.getType();
529 if (tokenType == TokenTypes.LITERAL_TRY) {
530 if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) {
531 lcurly = ast.getFirstChild().getNextSibling();
532 }
533 else {
534 lcurly = ast.getFirstChild();
535 }
536 nextToken = lcurly.getNextSibling();
537 }
538 else {
539 nextToken = ast.getNextSibling();
540 lcurly = ast.getLastChild();
541 }
542
543 final boolean shouldCheckLastRcurly;
544 if (nextToken == null) {
545 shouldCheckLastRcurly = true;
546 nextToken = getNextToken(ast);
547 }
548 else {
549 shouldCheckLastRcurly = false;
550 }
551
552 final DetailAST rcurly = lcurly.getLastChild();
553 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
554 }
555
556
557
558
559
560
561
562 private static Details getDetailsForIf(DetailAST ast) {
563 final boolean shouldCheckLastRcurly;
564 final DetailAST lcurly;
565 DetailAST nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
566
567 if (nextToken == null) {
568 shouldCheckLastRcurly = true;
569 nextToken = getNextToken(ast);
570 lcurly = ast.getLastChild();
571 }
572 else {
573 shouldCheckLastRcurly = false;
574 lcurly = nextToken.getPreviousSibling();
575 }
576
577 DetailAST rcurly = null;
578 if (lcurly.getType() == TokenTypes.SLIST) {
579 rcurly = lcurly.getLastChild();
580 }
581 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
582 }
583
584
585
586
587
588
589
590
591 private static Details getDetailsForOthers(DetailAST ast) {
592 DetailAST rcurly = null;
593 final DetailAST lcurly;
594 final int tokenType = ast.getType();
595 if (isTokenWithNoChildSlist(tokenType)) {
596 final DetailAST child = ast.getLastChild();
597 lcurly = child;
598 rcurly = child.getLastChild();
599 }
600 else {
601 lcurly = ast.findFirstToken(TokenTypes.SLIST);
602 if (lcurly != null) {
603
604 rcurly = lcurly.getLastChild();
605 }
606 }
607 return new Details(lcurly, rcurly, getNextToken(ast), true);
608 }
609
610
611
612
613
614
615
616
617 private static boolean isTokenWithNoChildSlist(int tokenType) {
618 return Arrays.stream(TOKENS_WITH_NO_CHILD_SLIST).anyMatch(token -> token == tokenType);
619 }
620
621
622
623
624
625
626
627 private static Details getDetailsForDoLoops(DetailAST ast) {
628 final DetailAST lcurly = ast.findFirstToken(TokenTypes.SLIST);
629 final DetailAST nextToken = ast.findFirstToken(TokenTypes.DO_WHILE);
630 DetailAST rcurly = null;
631 if (lcurly != null) {
632 rcurly = lcurly.getLastChild();
633 }
634 return new Details(lcurly, rcurly, nextToken, false);
635 }
636
637
638
639
640
641
642
643 private static DetailAST getNextToken(DetailAST ast) {
644 DetailAST next = null;
645 DetailAST parent = ast;
646 while (next == null && parent != null) {
647 next = parent.getNextSibling();
648 parent = parent.getParent();
649 }
650 return next;
651 }
652 }
653 }