1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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 * <div>
35 * Checks the placement of right curly braces (<code>'}'</code>) for code blocks. This check
36 * supports if-else, try-catch-finally blocks, switch statements, switch cases, switch default,
37 * while-loops, for-loops, method definitions, class definitions, constructor definitions,
38 * instance, static initialization blocks, annotation definitions and enum definitions.
39 * For right curly brace of expression blocks of arrays, lambdas and class instances
40 * please follow issue
41 * <a href="https://github.com/checkstyle/checkstyle/issues/5945">#5945</a>.
42 * For right curly brace of enum constant please follow issue
43 * <a href="https://github.com/checkstyle/checkstyle/issues/7519">#7519</a>.
44 * </div>
45 *
46 * @since 3.0
47 */
48 @StatelessCheck
49 public class RightCurlyCheck extends AbstractCheck {
50
51 /**
52 * A key is pointing to the warning message text in "messages.properties"
53 * file.
54 */
55 public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before";
56
57 /**
58 * A key is pointing to the warning message text in "messages.properties"
59 * file.
60 */
61 public static final String MSG_KEY_LINE_ALONE = "line.alone";
62
63 /**
64 * A key is pointing to the warning message text in "messages.properties"
65 * file.
66 */
67 public static final String MSG_KEY_LINE_SAME = "line.same";
68
69 /**
70 * Specify the policy on placement of a right curly brace (<code>'}'</code>).
71 */
72 private RightCurlyOption option = RightCurlyOption.SAME;
73
74 /**
75 * Setter to specify the policy on placement of a right curly brace (<code>'}'</code>).
76 *
77 * @param optionStr string to decode option from
78 * @throws IllegalArgumentException if unable to decode
79 * @since 3.0
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 TokenTypes.LITERAL_DEFAULT,
120 };
121 }
122
123 @Override
124 public int[] getRequiredTokens() {
125 return CommonUtil.EMPTY_INT_ARRAY;
126 }
127
128 @Override
129 public void visitToken(DetailAST ast) {
130 final Details details = Details.getDetails(ast);
131 final DetailAST rcurly = details.rcurly();
132
133 if (rcurly != null) {
134 final String violation = validate(details);
135 if (!violation.isEmpty()) {
136 log(rcurly, violation, "}", rcurly.getColumnNo() + 1);
137 }
138 }
139 }
140
141 /**
142 * Does general validation.
143 *
144 * @param details for validation.
145 * @return violation message or empty string
146 * if there was no violation during validation.
147 */
148 private String validate(Details details) {
149 String violation = "";
150 if (shouldHaveLineBreakBefore(option, details)) {
151 violation = MSG_KEY_LINE_BREAK_BEFORE;
152 }
153 else if (shouldBeOnSameLine(option, details)) {
154 violation = MSG_KEY_LINE_SAME;
155 }
156 else if (shouldBeAloneOnLine(option, details, getLine(details.rcurly.getLineNo() - 1))) {
157 violation = MSG_KEY_LINE_ALONE;
158 }
159 return violation;
160 }
161
162 /**
163 * Checks whether a right curly should have a line break before.
164 *
165 * @param bracePolicy option for placing the right curly brace.
166 * @param details details for validation.
167 * @return true if a right curly should have a line break before.
168 */
169 private static boolean shouldHaveLineBreakBefore(RightCurlyOption bracePolicy,
170 Details details) {
171 return bracePolicy == RightCurlyOption.SAME
172 && !hasLineBreakBefore(details.rcurly())
173 && !TokenUtil.areOnSameLine(details.lcurly(), details.rcurly());
174 }
175
176 /**
177 * Checks that a right curly should be on the same line as the next statement.
178 *
179 * @param bracePolicy option for placing the right curly brace
180 * @param details Details for validation
181 * @return true if a right curly should be alone on a line.
182 */
183 private static boolean shouldBeOnSameLine(RightCurlyOption bracePolicy, Details details) {
184 return bracePolicy == RightCurlyOption.SAME
185 && !details.shouldCheckLastRcurly()
186 && !TokenUtil.areOnSameLine(details.rcurly(), details.nextToken());
187 }
188
189 /**
190 * Checks that a right curly should be alone on a line.
191 *
192 * @param bracePolicy option for placing the right curly brace
193 * @param details Details for validation
194 * @param targetSrcLine A string with contents of rcurly's line
195 * @return true if a right curly should be alone on a line.
196 */
197 private static boolean shouldBeAloneOnLine(RightCurlyOption bracePolicy,
198 Details details,
199 String targetSrcLine) {
200 return bracePolicy == RightCurlyOption.ALONE
201 && shouldBeAloneOnLineWithAloneOption(details, targetSrcLine)
202 || (bracePolicy == RightCurlyOption.ALONE_OR_SINGLELINE
203 || details.shouldCheckLastRcurly)
204 && shouldBeAloneOnLineWithNotAloneOption(details, targetSrcLine);
205 }
206
207 /**
208 * Whether right curly should be alone on line when ALONE option is used.
209 *
210 * @param details details for validation.
211 * @param targetSrcLine A string with contents of rcurly's line
212 * @return true, if right curly should be alone on line when ALONE option is used.
213 */
214 private static boolean shouldBeAloneOnLineWithAloneOption(Details details,
215 String targetSrcLine) {
216 return !isAloneOnLine(details, targetSrcLine);
217 }
218
219 /**
220 * Whether right curly should be alone on line when ALONE_OR_SINGLELINE or SAME option is used.
221 *
222 * @param details details for validation.
223 * @param targetSrcLine A string with contents of rcurly's line
224 * @return true, if right curly should be alone on line
225 * when ALONE_OR_SINGLELINE or SAME option is used.
226 */
227 private static boolean shouldBeAloneOnLineWithNotAloneOption(Details details,
228 String targetSrcLine) {
229 return shouldBeAloneOnLineWithAloneOption(details, targetSrcLine)
230 && !isBlockAloneOnSingleLine(details);
231 }
232
233 /**
234 * Checks whether right curly is alone on a line.
235 *
236 * @param details for validation.
237 * @param targetSrcLine A string with contents of rcurly's line
238 * @return true if right curly is alone on a line.
239 */
240 private static boolean isAloneOnLine(Details details, String targetSrcLine) {
241 final DetailAST rcurly = details.rcurly();
242 final DetailAST nextToken = details.nextToken();
243 return (nextToken == null || !TokenUtil.areOnSameLine(rcurly, nextToken)
244 || skipDoubleBraceInstInit(details))
245 && CommonUtil.hasWhitespaceBefore(details.rcurly().getColumnNo(),
246 targetSrcLine);
247 }
248
249 /**
250 * This method determines if the double brace initialization should be skipped over by the
251 * check. Double brace initializations are treated differently. The corresponding inner
252 * rcurly is treated as if it was alone on line even when it may be followed by another
253 * rcurly and a semi, raising no violations.
254 * <i>Please do note though that the line should not contain anything other than the following
255 * right curly and the semi following it or else violations will be raised.</i>
256 * Only the kind of double brace initializations shown in the following example code will be
257 * skipped over:<br>
258 * <pre>
259 * {@code Map<String, String> map = new LinkedHashMap<>() {{
260 * put("alpha", "man");
261 * }}; // no violation}
262 * </pre>
263 *
264 * @param details {@link Details} object containing the details relevant to the rcurly
265 * @return if the double brace initialization rcurly should be skipped over by the check
266 */
267 private static boolean skipDoubleBraceInstInit(Details details) {
268 boolean skipDoubleBraceInstInit = false;
269 final DetailAST tokenAfterNextToken = Details.getNextToken(details.nextToken());
270 if (tokenAfterNextToken != null) {
271 final DetailAST rcurly = details.rcurly();
272 skipDoubleBraceInstInit = rcurly.getParent().getParent()
273 .getType() == TokenTypes.INSTANCE_INIT
274 && details.nextToken().getType() == TokenTypes.RCURLY
275 && !TokenUtil.areOnSameLine(rcurly, Details.getNextToken(tokenAfterNextToken));
276 }
277 return skipDoubleBraceInstInit;
278 }
279
280 /**
281 * Checks whether block has a single-line format and is alone on a line.
282 *
283 * @param details for validation.
284 * @return true if block has single-line format and is alone on a line.
285 */
286 private static boolean isBlockAloneOnSingleLine(Details details) {
287 DetailAST nextToken = details.nextToken();
288
289 while (nextToken != null && nextToken.getType() == TokenTypes.LITERAL_ELSE) {
290 nextToken = Details.getNextToken(nextToken);
291 }
292
293 // sibling tokens should be allowed on a single line
294 final int[] tokensWithBlockSibling = {
295 TokenTypes.DO_WHILE,
296 TokenTypes.LITERAL_FINALLY,
297 TokenTypes.LITERAL_CATCH,
298 };
299
300 if (TokenUtil.isOfType(nextToken, tokensWithBlockSibling)) {
301 final DetailAST parent = nextToken.getParent();
302 nextToken = Details.getNextToken(parent);
303 }
304
305 return TokenUtil.areOnSameLine(details.lcurly(), details.rcurly())
306 && (nextToken == null || !TokenUtil.areOnSameLine(details.rcurly(), nextToken)
307 || isRightcurlyFollowedBySemicolon(details));
308 }
309
310 /**
311 * Checks whether the right curly is followed by a semicolon.
312 *
313 * @param details details for validation.
314 * @return true if the right curly is followed by a semicolon.
315 */
316 private static boolean isRightcurlyFollowedBySemicolon(Details details) {
317 return details.nextToken().getType() == TokenTypes.SEMI;
318 }
319
320 /**
321 * Checks if right curly has line break before.
322 *
323 * @param rightCurly right curly token.
324 * @return true, if right curly has line break before.
325 */
326 private static boolean hasLineBreakBefore(DetailAST rightCurly) {
327 DetailAST previousToken = rightCurly.getPreviousSibling();
328 if (previousToken == null) {
329 previousToken = rightCurly.getParent();
330 }
331 return !TokenUtil.areOnSameLine(rightCurly, previousToken);
332 }
333
334 /**
335 * Structure that contains all details for validation.
336 *
337 * @param lcurly the left curly token being analysed
338 * @param rcurly the matching right curly token
339 * @param nextToken the token following the right curly
340 * @param shouldCheckLastRcurly flag that indicates if the last right curly should be checked
341 */
342 private record Details(DetailAST lcurly, DetailAST rcurly,
343 DetailAST nextToken, boolean shouldCheckLastRcurly) {
344
345 /**
346 * Token types that identify tokens that will never have SLIST in their AST.
347 */
348 private static final int[] TOKENS_WITH_NO_CHILD_SLIST = {
349 TokenTypes.CLASS_DEF,
350 TokenTypes.ENUM_DEF,
351 TokenTypes.ANNOTATION_DEF,
352 TokenTypes.INTERFACE_DEF,
353 TokenTypes.RECORD_DEF,
354 };
355
356 /**
357 * Collects validation Details.
358 *
359 * @param ast a {@code DetailAST} value
360 * @return object containing all details to make a validation
361 */
362 private static Details getDetails(DetailAST ast) {
363 return switch (ast.getType()) {
364 case TokenTypes.LITERAL_TRY, TokenTypes.LITERAL_CATCH -> getDetailsForTryCatch(ast);
365 case TokenTypes.LITERAL_IF -> getDetailsForIf(ast);
366 case TokenTypes.LITERAL_DO -> getDetailsForDoLoops(ast);
367 case TokenTypes.LITERAL_SWITCH -> getDetailsForSwitch(ast);
368 case TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT ->
369 getDetailsForCaseOrDefault(ast);
370 default -> getDetailsForOthers(ast);
371 };
372 }
373
374 /**
375 * Collects details about switch statements and expressions.
376 *
377 * @param switchNode switch statement or expression to gather details about
378 * @return new Details about given switch statement or expression
379 */
380 private static Details getDetailsForSwitch(DetailAST switchNode) {
381 final DetailAST lcurly = switchNode.findFirstToken(TokenTypes.LCURLY);
382 final DetailAST rcurly;
383 DetailAST nextToken = null;
384 // skipping switch expression as check only handles statements
385 if (isSwitchExpression(switchNode)) {
386 rcurly = null;
387 }
388 else {
389 rcurly = switchNode.getLastChild();
390 nextToken = getNextToken(switchNode);
391 }
392 return new Details(lcurly, rcurly, nextToken, true);
393 }
394
395 /**
396 * Collects details about case and default statements.
397 *
398 * @param caseOrDefaultNode case or default statement to gather details about
399 * @return new Details about given case or default statement
400 */
401 private static Details getDetailsForCaseOrDefault(DetailAST caseOrDefaultNode) {
402 final DetailAST caseOrDefaultParent = caseOrDefaultNode.getParent();
403 final int parentType = caseOrDefaultParent.getType();
404 final Optional<DetailAST> lcurly;
405 final DetailAST statementList;
406
407 if (parentType == TokenTypes.SWITCH_RULE) {
408 statementList = caseOrDefaultParent.findFirstToken(TokenTypes.SLIST);
409 lcurly = Optional.ofNullable(statementList);
410 }
411 else {
412 statementList = caseOrDefaultNode.getNextSibling();
413 lcurly = Optional.ofNullable(statementList)
414 .map(DetailAST::getFirstChild)
415 .filter(node -> node.getType() == TokenTypes.SLIST);
416 }
417 final DetailAST rcurly = lcurly.map(DetailAST::getLastChild)
418 .filter(child -> !isSwitchExpression(caseOrDefaultParent))
419 .orElse(null);
420 final Optional<DetailAST> nextToken =
421 Optional.ofNullable(lcurly.map(DetailAST::getNextSibling)
422 .orElseGet(() -> getNextToken(caseOrDefaultParent)));
423
424 return new Details(lcurly.orElse(null), rcurly, nextToken.orElse(null), true);
425 }
426
427 /**
428 * Check whether switch is expression or not.
429 *
430 * @param switchNode switch statement or expression to provide detail
431 * @return true if it is a switch expression
432 */
433 private static boolean isSwitchExpression(DetailAST switchNode) {
434 DetailAST currentNode = switchNode;
435 boolean ans = false;
436
437 while (currentNode != null) {
438 if (currentNode.getType() == TokenTypes.EXPR) {
439 ans = true;
440 }
441 currentNode = currentNode.getParent();
442 }
443 return ans;
444 }
445
446 /**
447 * Collects validation details for LITERAL_TRY, and LITERAL_CATCH.
448 *
449 * @param ast a {@code DetailAST} value
450 * @return object containing all details to make a validation
451 */
452 private static Details getDetailsForTryCatch(DetailAST ast) {
453 final DetailAST lcurly;
454 DetailAST nextToken;
455 final int tokenType = ast.getType();
456 if (tokenType == TokenTypes.LITERAL_TRY) {
457 if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) {
458 lcurly = ast.getFirstChild().getNextSibling();
459 }
460 else {
461 lcurly = ast.getFirstChild();
462 }
463 nextToken = lcurly.getNextSibling();
464 }
465 else {
466 nextToken = ast.getNextSibling();
467 lcurly = ast.getLastChild();
468 }
469
470 final boolean shouldCheckLastRcurly;
471 if (nextToken == null) {
472 shouldCheckLastRcurly = true;
473 nextToken = getNextToken(ast);
474 }
475 else {
476 shouldCheckLastRcurly = false;
477 }
478
479 final DetailAST rcurly = lcurly.getLastChild();
480 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
481 }
482
483 /**
484 * Collects validation details for LITERAL_IF.
485 *
486 * @param ast a {@code DetailAST} value
487 * @return object containing all details to make a validation
488 */
489 private static Details getDetailsForIf(DetailAST ast) {
490 final boolean shouldCheckLastRcurly;
491 final DetailAST lcurly;
492 DetailAST nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
493
494 if (nextToken == null) {
495 shouldCheckLastRcurly = true;
496 nextToken = getNextToken(ast);
497 lcurly = ast.getLastChild();
498 }
499 else {
500 shouldCheckLastRcurly = false;
501 lcurly = nextToken.getPreviousSibling();
502 }
503
504 DetailAST rcurly = null;
505 if (lcurly.getType() == TokenTypes.SLIST) {
506 rcurly = lcurly.getLastChild();
507 }
508 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
509 }
510
511 /**
512 * Collects validation details for CLASS_DEF, RECORD_DEF, METHOD DEF, CTOR_DEF, STATIC_INIT,
513 * INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, and COMPACT_CTOR_DEF.
514 *
515 * @param ast a {@code DetailAST} value
516 * @return an object containing all details to make a validation
517 */
518 private static Details getDetailsForOthers(DetailAST ast) {
519 DetailAST rcurly = null;
520 final DetailAST lcurly;
521 final int tokenType = ast.getType();
522 if (isTokenWithNoChildSlist(tokenType)) {
523 final DetailAST child = ast.getLastChild();
524 lcurly = child;
525 rcurly = child.getLastChild();
526 }
527 else {
528 lcurly = ast.findFirstToken(TokenTypes.SLIST);
529 if (lcurly != null) {
530 // SLIST could be absent if method is abstract
531 rcurly = lcurly.getLastChild();
532 }
533 }
534 return new Details(lcurly, rcurly, getNextToken(ast), true);
535 }
536
537 /**
538 * Tests whether the provided tokenType will never have a SLIST as child in its AST.
539 * Like CLASS_DEF, ANNOTATION_DEF etc.
540 *
541 * @param tokenType the tokenType to test against.
542 * @return weather provided tokenType is definition token.
543 */
544 private static boolean isTokenWithNoChildSlist(int tokenType) {
545 return Arrays.stream(TOKENS_WITH_NO_CHILD_SLIST).anyMatch(token -> token == tokenType);
546 }
547
548 /**
549 * Collects validation details for LITERAL_DO loops' tokens.
550 *
551 * @param ast a {@code DetailAST} value
552 * @return an object containing all details to make a validation
553 */
554 private static Details getDetailsForDoLoops(DetailAST ast) {
555 final DetailAST lcurly = ast.findFirstToken(TokenTypes.SLIST);
556 final DetailAST nextToken = ast.findFirstToken(TokenTypes.DO_WHILE);
557 DetailAST rcurly = null;
558 if (lcurly != null) {
559 rcurly = lcurly.getLastChild();
560 }
561 return new Details(lcurly, rcurly, nextToken, false);
562 }
563
564 /**
565 * Finds next token after the given one.
566 *
567 * @param ast the given node.
568 * @return the token which represents next lexical item.
569 */
570 private static DetailAST getNextToken(DetailAST ast) {
571 DetailAST next = null;
572 DetailAST parent = ast;
573 while (next == null && parent != null) {
574 next = parent.getNextSibling();
575 parent = parent.getParent();
576 }
577 return next;
578 }
579 }
580 }