001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.blocks; 021 022import javax.annotation.Nullable; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.NullUtil; 029import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 030 031/** 032 * <div> 033 * Checks the placement of right curly braces (<code>'}'</code>) for code blocks, following 034 * the <a href="https://google.github.io/styleguide/javaguide.html#s4.1-braces"> 035 * Google Java Style Guide </a>. 036 * <p> 037 * For nonempty blocks the right curly brace must begin its own line, 038 * unless it is followed by {@code else}, {@code catch}, {@code finally}, or a comma, 039 * in which case no line break follows it. 040 * </p> 041 * <p> 042 * For empty blocks, either {@code K&R} style or the concise {@code {}} form is 043 * allowed, except within a multi-block statement ({@code if/else}, {@code try/catch/finally}). 044 * </p> 045 * </div> 046 * 047 * @since 14.2.0 048 */ 049@StatelessCheck 050public class GoogleRightCurlyCheck extends AbstractCheck { 051 052 /** 053 * A key is pointing to the warning message text in "messages.properties" 054 * file. 055 */ 056 public static final String MSG_KEY_LINE_ALONE = "line.alone"; 057 058 /** 059 * A key is pointing to the warning message text in "messages.properties" 060 * file. 061 */ 062 public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after"; 063 064 /** 065 * A key is pointing to the warning message text in "messages.properties" 066 * file. 067 */ 068 public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before"; 069 070 /** 071 * A key is pointing to the warning message text in "messages.properties" 072 * file. 073 */ 074 public static final String MSG_KEY_CONCISE_BLOCK = "empty.block.concise"; 075 076 /** 077 * A key is pointing to the warning message text in "messages.properties" 078 * file. 079 */ 080 public static final String MSG_KEY_LINE_SAME = "line.same"; 081 082 /** 083 * Creates a new {@code GoogleRightCurlyCheck} instance. 084 */ 085 public GoogleRightCurlyCheck() { 086 // no code by default 087 } 088 089 @Override 090 public int[] getDefaultTokens() { 091 return getRequiredTokens(); 092 } 093 094 @Override 095 public int[] getAcceptableTokens() { 096 return new int[] { 097 TokenTypes.LITERAL_IF, 098 TokenTypes.LITERAL_ELSE, 099 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}