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