View Javadoc
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 javax.annotation.Nullable;
23  
24  import com.puppycrawl.tools.checkstyle.StatelessCheck;
25  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
26  import com.puppycrawl.tools.checkstyle.api.DetailAST;
27  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
28  import com.puppycrawl.tools.checkstyle.utils.NullUtil;
29  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
30  
31  /**
32   * <div>
33   * Checks the placement of right curly braces (<code>'}'</code>) for code blocks, following
34   * the <a href="https://google.github.io/styleguide/javaguide.html#s4.1-braces">
35   *     Google Java Style Guide </a>.
36   * <p>
37   * For nonempty blocks the right curly brace must begin its own line,
38   * unless it is followed by {@code else}, {@code catch}, {@code finally}, or a comma,
39   * in which case no line break follows it.
40   * </p>
41   * <p>
42   * For empty blocks, either {@code K&R} style or the concise {@code {}} form is
43   * allowed, except within a multi-block statement ({@code if/else}, {@code try/catch/finally}).
44   * </p>
45   * </div>
46   *
47   * @since 14.2.0
48   */
49  @StatelessCheck
50  public class GoogleRightCurlyCheck extends AbstractCheck {
51  
52      /**
53       * A key is pointing to the warning message text in "messages.properties"
54       * file.
55       */
56      public static final String MSG_KEY_LINE_ALONE = "line.alone";
57  
58      /**
59       * A key is pointing to the warning message text in "messages.properties"
60       * file.
61       */
62      public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after";
63  
64      /**
65       * A key is pointing to the warning message text in "messages.properties"
66       * file.
67       */
68      public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before";
69  
70      /**
71       * A key is pointing to the warning message text in "messages.properties"
72       * file.
73       */
74      public static final String MSG_KEY_CONCISE_BLOCK = "empty.block.concise";
75  
76      /**
77       * A key is pointing to the warning message text in "messages.properties"
78       * file.
79       */
80      public static final String MSG_KEY_LINE_SAME = "line.same";
81  
82      /**
83       * Creates a new {@code GoogleRightCurlyCheck} instance.
84       */
85      public GoogleRightCurlyCheck() {
86          // no code by default
87      }
88  
89      @Override
90      public int[] getDefaultTokens() {
91          return getRequiredTokens();
92      }
93  
94      @Override
95      public int[] getAcceptableTokens() {
96          return new int[] {
97              TokenTypes.LITERAL_IF,
98              TokenTypes.LITERAL_ELSE,
99              TokenTypes.LITERAL_TRY,
100             TokenTypes.LITERAL_CATCH,
101             TokenTypes.LITERAL_FINALLY,
102             TokenTypes.LITERAL_DO,
103             TokenTypes.CLASS_DEF,
104             TokenTypes.INTERFACE_DEF,
105             TokenTypes.RECORD_DEF,
106             TokenTypes.ANNOTATION_DEF,
107             TokenTypes.ENUM_DEF,
108             TokenTypes.METHOD_DEF,
109             TokenTypes.CTOR_DEF,
110             TokenTypes.COMPACT_CTOR_DEF,
111             TokenTypes.LITERAL_FOR,
112             TokenTypes.LITERAL_WHILE,
113             TokenTypes.LITERAL_SWITCH,
114             TokenTypes.LITERAL_CASE,
115             TokenTypes.LITERAL_DEFAULT,
116             TokenTypes.STATIC_INIT,
117             TokenTypes.INSTANCE_INIT,
118             TokenTypes.LITERAL_SYNCHRONIZED,
119         };
120     }
121 
122     @Override
123     public int[] getRequiredTokens() {
124         return getAcceptableTokens();
125     }
126 
127     @Override
128     public boolean isCommentNodesRequired() {
129         return true;
130     }
131 
132     @Override
133     public void visitToken(DetailAST ast) {
134         DetailAST rightCurly = null;
135         switch (ast.getType()) {
136             case TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF,
137                  TokenTypes.ANNOTATION_DEF, TokenTypes.RECORD_DEF, TokenTypes.ENUM_DEF -> {
138                 final DetailAST child =
139                         NullUtil.notNull(ast.findFirstToken(TokenTypes.OBJBLOCK));
140                 rightCurly = NullUtil.notNull(child.findFirstToken(TokenTypes.RCURLY));
141             }
142 
143             case TokenTypes.LITERAL_SWITCH -> rightCurly = ast.getLastChild();
144 
145             case TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT -> handleCaseAndDefault(ast);
146 
147             default -> {
148                 final DetailAST child = ast.findFirstToken(TokenTypes.SLIST);
149                 if (child != null) {
150                     rightCurly = child.getLastChild();
151                 }
152             }
153         }
154         if (rightCurly != null) {
155             checkRightBrace(ast, rightCurly);
156         }
157     }
158 
159     /**
160      * Checks the right curly brace placement for {@code case} and
161      * {@code default} blocks, covering both old style {@code case X:}
162      * and new style {@code case X ->} switch syntax.
163      *
164      * <p>For old-style syntax, a case label may be followed by multiple
165      * {@code {}} blocks in sequence, and each such block's right curly
166      * brace is checked. For new-style syntax, the block following the
167      * arrow (e.g. {@code case X -> { ... }}) is checked,
168      * expression with no block (e.g. {@code case X -> expr;}) is skipped.
169      *
170      * @param ast the {@code case} or {@code default} token
171      */
172     private void handleCaseAndDefault(DetailAST ast) {
173         DetailAST startToken = ast;
174         if (ast.getParent().getType() == TokenTypes.CASE_GROUP) {
175             final DetailAST nextSibling = startToken.getNextSibling();
176             if (nextSibling != null) {
177                 startToken = nextSibling.findFirstToken(TokenTypes.SLIST);
178             }
179         }
180         for (DetailAST current = startToken; current != null;
181              current = current.getNextSibling()) {
182             if (current.getType() == TokenTypes.SLIST) {
183                 final DetailAST rightBrace = NullUtil.notNull(current.getLastChild());
184                 checkRightBrace(ast, rightBrace);
185             }
186         }
187     }
188 
189     /**
190      * Logs violation message for given brace token.
191      *
192      * @param message the violation message key.
193      * @param brace the right curly brace.
194      */
195     private void logViolations(String message, DetailAST brace) {
196         log(brace, message, brace.getText(), brace.getColumnNo() + 1);
197     }
198 
199     /**
200      * Checks that a right curly brace is placed correctly.
201      *
202      * <p>If the block is part of a multi-block statement (e.g. {@code if/else},
203      * {@code try/catch/finally}, or {@code do/while}), the closing brace must be
204      * on the same line as the next block's starting keyword. Otherwise, the
205      * brace must be alone on its own line, unless the block is empty, in which
206      * case the concise {@code {}} form is allowed.
207      *
208      * @param currentBlock the block whose right curly brace is being checked
209      * @param brace the right curly brace token
210      */
211     private void checkRightBrace(DetailAST currentBlock, DetailAST brace) {
212         final DetailAST nextToken = getNextToken(brace);
213         final boolean hasContentAround = contentAround(brace, nextToken);
214         if (nextToken != null && isPartOfMultiBlock(currentBlock, nextToken)) {
215             checkMultiBlockStatement(currentBlock, brace, nextToken);
216         }
217         else if (currentBlock.getParent().getType() == TokenTypes.LITERAL_ELSE
218                 || TokenUtil.isOfType(currentBlock, TokenTypes.LITERAL_ELSE,
219                 TokenTypes.LITERAL_CATCH, TokenTypes.LITERAL_FINALLY)) {
220             if (hasContentAround) {
221                 logViolations(MSG_KEY_LINE_ALONE, brace);
222             }
223         }
224         else if (isEmpty(brace)) {
225             verifyEmptyBlock(brace, nextToken);
226         }
227         else if (hasContentAround) {
228             logViolations(MSG_KEY_LINE_ALONE, brace);
229         }
230     }
231 
232     /**
233      * Checks that the right curly brace of a multi-block statement (e.g. {@code if/else},
234      * {@code try/catch/finally}, {@code do/while}) is placed correctly relative to the next block.
235      *
236      * @param currentBlock the current block
237      * @param brace the right curly brace
238      * @param nextBlock the next block in multi-block statement
239      */
240     private void checkMultiBlockStatement(DetailAST currentBlock, DetailAST brace,
241         DetailAST nextBlock) {
242         if (TokenUtil.areOnSameLine(brace, nextBlock)) {
243             if (hasContentOnLeftSide(brace) && !(currentBlock.getType() == TokenTypes.LITERAL_DO
244                     && isEmpty(brace))) {
245                 logViolations(MSG_KEY_LINE_BREAK_BEFORE, brace);
246             }
247         }
248         else {
249             logViolations(MSG_KEY_LINE_SAME, brace);
250         }
251     }
252 
253     /**
254      * Checks empty block which should be concise and alone.
255      *
256      * @param brace the right curly token.
257      * @param nextToken the token after right curly brace.
258      */
259     private void verifyEmptyBlock(DetailAST brace, @Nullable DetailAST nextToken) {
260         if (isNotConcise(brace)) {
261             logViolations(MSG_KEY_CONCISE_BLOCK, brace);
262         }
263         else if (nextToken != null
264                 && hasContentOnRightSide(brace, nextToken)) {
265             logViolations(MSG_KEY_LINE_BREAK_AFTER, brace);
266         }
267     }
268 
269     /**
270      * Checks if the right curly has content around.
271      *
272      * @param brace the right curly brace
273      * @param nextToken the next token after right curly
274      * @return {@code true} if right curly has content on its left or right.
275      */
276     private static boolean contentAround(DetailAST brace, @Nullable DetailAST nextToken) {
277         return nextToken != null
278                 && hasContentOnRightSide(brace, nextToken)
279                 || hasContentOnLeftSide(brace);
280     }
281 
282     /**
283      * Checks whether the current block is part
284      * of a multi-block statement ({@code if/else},
285      * {@code try/catch/finally}, or {@code do/while}).
286      *
287      * @param currentBlock the current block
288      * @param nextBlock the block following {@code ast}
289      * @return {@code true} if {@code ast} and {@code nextBlock} belong to
290      *         the same multi-block statement
291      */
292     private static boolean isPartOfMultiBlock(DetailAST currentBlock, DetailAST nextBlock) {
293         final int nextBlockType = nextBlock.getType();
294         return switch (currentBlock.getType()) {
295             case TokenTypes.LITERAL_IF ->
296                 nextBlockType == TokenTypes.LITERAL_ELSE;
297             case TokenTypes.LITERAL_TRY, TokenTypes.LITERAL_CATCH ->
298                 nextBlockType == TokenTypes.LITERAL_CATCH
299                     || nextBlockType == TokenTypes.LITERAL_FINALLY;
300             case TokenTypes.LITERAL_DO -> true;
301             default -> false;
302         };
303     }
304 
305     /**
306      * Checks if the block is not concise and has content on left side of right brace.
307      *
308      * @param brace the right curly brace token
309      * @return {@code true} if the brace has content on left.
310      */
311     private static boolean hasContentOnLeftSide(DetailAST brace) {
312         DetailAST previousToken = brace.getPreviousSibling();
313         if (previousToken == null) {
314             previousToken = brace.getParent();
315         }
316         if (previousToken.getType() != TokenTypes.SLIST) {
317             while (previousToken.hasChildren()) {
318                 previousToken = previousToken.getLastChild();
319             }
320         }
321 
322         return TokenUtil.areOnSameLine(brace, previousToken)
323                 && !TokenUtil.isOfType(brace.getPreviousSibling(),
324                 TokenTypes.ENUM_CONSTANT_DEF, TokenTypes.COMMA);
325     }
326 
327     /**
328      * Checks if the right curly brace is part multi-block statement or no
329      * content on right side of right brace.
330      *
331      * @param brace the right curly brace token
332      * @param nextToken the next token of right curly.
333      * @return {@code true} if the brace is on the same line as the previous sibling
334      *     or parent if no sibling exists
335      */
336     private static boolean hasContentOnRightSide(DetailAST brace, DetailAST nextToken) {
337         final boolean nextIsValid = TokenUtil.isOfType(nextToken.getPreviousSibling(),
338                 TokenTypes.EXPR, TokenTypes.VARIABLE_DEF, TokenTypes.ELIST, TokenTypes.LAMBDA)
339                 || TokenUtil.isCommentType(nextToken.getType());
340         return !nextIsValid && TokenUtil.areOnSameLine(brace, nextToken);
341     }
342 
343     /**
344      * Checks if block is empty.
345      *
346      * @param brace the right curly brace.
347      * @return {@code true} if the block is empty.
348      */
349     private static boolean isEmpty(DetailAST brace) {
350         final DetailAST previousSibling = brace.getPreviousSibling();
351         return previousSibling == null || previousSibling.getType() == TokenTypes.LCURLY;
352     }
353 
354     /**
355      * Checks if the block is not {@code K&R} style or concise {@code {}}.
356      *
357      * @param brace right curly token
358      * @return {@code true} if block is not concise.
359      */
360     private static boolean isNotConcise(DetailAST brace) {
361         final DetailAST lcurly = brace.getParent();
362         return lcurly.getLineNo() == brace.getLineNo()
363                 && lcurly.getColumnNo() + 1 != brace.getColumnNo();
364     }
365 
366     /**
367      * Traverses up the AST to find the next sibling token after the right curly brace.
368      *
369      * @param node ast token
370      * @return the next sibling token, or {@code null} if none exists
371      */
372     @Nullable
373     private static DetailAST getNextToken(DetailAST node) {
374         DetailAST current = node;
375         DetailAST nextToken = null;
376         while (current != null && nextToken == null) {
377             nextToken = current.getNextSibling();
378             current = current.getParent();
379         }
380         return nextToken;
381     }
382 
383 }