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" file. 054 */ 055 public static final String MSG_KEY_LINE_ALONE = "line.alone"; 056 057 /** 058 * A key is pointing to the warning message text in "messages.properties" file. 059 */ 060 public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after"; 061 062 /** 063 * A key is pointing to the warning message text in "messages.properties" file. 064 */ 065 public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before"; 066 067 /** 068 * A key is pointing to the warning message text in "messages.properties" file. 069 */ 070 public static final String MSG_KEY_CONCISE_BLOCK = "empty.block.concise"; 071 072 /** 073 * A key is pointing to the warning message text in "messages.properties" file. 074 */ 075 public static final String MSG_KEY_LINE_SAME = "line.same"; 076 077 /** 078 * Creates a new {@code GoogleRightCurlyCheck} instance. 079 */ 080 public GoogleRightCurlyCheck() { 081 // no code by default 082 } 083 084 @Override 085 public int[] getDefaultTokens() { 086 return getRequiredTokens(); 087 } 088 089 @Override 090 public int[] getAcceptableTokens() { 091 return new int[] { 092 TokenTypes.LITERAL_IF, 093 TokenTypes.LITERAL_ELSE, 094 TokenTypes.LITERAL_TRY, 095 TokenTypes.LITERAL_CATCH, 096 TokenTypes.LITERAL_FINALLY, 097 TokenTypes.LITERAL_DO, 098 TokenTypes.CLASS_DEF, 099 TokenTypes.INTERFACE_DEF, 100 TokenTypes.RECORD_DEF, 101 TokenTypes.ANNOTATION_DEF, 102 TokenTypes.ENUM_DEF, 103 TokenTypes.METHOD_DEF, 104 TokenTypes.CTOR_DEF, 105 TokenTypes.COMPACT_CTOR_DEF, 106 TokenTypes.LITERAL_FOR, 107 TokenTypes.LITERAL_WHILE, 108 TokenTypes.LITERAL_SWITCH, 109 TokenTypes.LITERAL_CASE, 110 TokenTypes.LITERAL_DEFAULT, 111 TokenTypes.STATIC_INIT, 112 TokenTypes.INSTANCE_INIT, 113 TokenTypes.LITERAL_SYNCHRONIZED, 114 }; 115 } 116 117 @Override 118 public int[] getRequiredTokens() { 119 return getAcceptableTokens(); 120 } 121 122 @Override 123 public boolean isCommentNodesRequired() { 124 return true; 125 } 126 127 @Override 128 public void visitToken(DetailAST ast) { 129 DetailAST rightCurly = null; 130 switch (ast.getType()) { 131 case TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF, 132 TokenTypes.ANNOTATION_DEF, TokenTypes.RECORD_DEF, TokenTypes.ENUM_DEF -> { 133 final DetailAST child = 134 NullUtil.notNull(ast.findFirstToken(TokenTypes.OBJBLOCK)); 135 rightCurly = NullUtil.notNull(child.findFirstToken(TokenTypes.RCURLY)); 136 } 137 138 case TokenTypes.LITERAL_SWITCH -> rightCurly = ast.getLastChild(); 139 140 case TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT -> handleCaseAndDefault(ast); 141 142 default -> { 143 final DetailAST child = ast.findFirstToken(TokenTypes.SLIST); 144 if (child != null) { 145 rightCurly = child.getLastChild(); 146 } 147 } 148 } 149 if (rightCurly != null) { 150 checkRightBrace(ast, rightCurly); 151 } 152 } 153 154 /** 155 * Checks the right curly brace placement for {@code case} and 156 * {@code default} blocks, covering both old style {@code case X:} 157 * and new style {@code case X ->} switch syntax. 158 * 159 * <p>For old-style syntax, a case label may be followed by multiple 160 * {@code {}} blocks in sequence, and each such block's right curly 161 * brace is checked. For new-style syntax, the block following the 162 * arrow (e.g. {@code case X -> { ... }}) is checked, 163 * expression with no block (e.g. {@code case X -> expr;}) is skipped. 164 * 165 * @param ast the {@code case} or {@code default} token 166 */ 167 private void handleCaseAndDefault(DetailAST ast) { 168 DetailAST startToken = ast; 169 if (ast.getParent().getType() == TokenTypes.CASE_GROUP) { 170 final DetailAST nextSibling = startToken.getNextSibling(); 171 if (nextSibling != null) { 172 startToken = nextSibling.findFirstToken(TokenTypes.SLIST); 173 } 174 } 175 for (DetailAST current = startToken; current != null; 176 current = current.getNextSibling()) { 177 if (current.getType() == TokenTypes.SLIST) { 178 final DetailAST rightBrace = NullUtil.notNull(current.getLastChild()); 179 checkRightBrace(ast, rightBrace); 180 } 181 } 182 } 183 184 /** 185 * Logs violation message for given brace token. 186 * 187 * @param message the violation message key. 188 * @param brace the right curly brace. 189 */ 190 private void logViolations(String message, DetailAST brace) { 191 log(brace, message, brace.getText(), brace.getColumnNo() + 1); 192 } 193 194 /** 195 * Checks that a right curly brace is placed correctly. 196 * 197 * <p>If the block is part of a multi-block statement (e.g. {@code if/else}, 198 * {@code try/catch/finally}, or {@code do/while}), the closing brace must be 199 * on the same line as the next block's starting keyword. Otherwise, the 200 * brace must be alone on its own line, unless the block is empty, in which 201 * case the concise {@code {}} form is allowed. 202 * 203 * @param currentBlock the block whose right curly brace is being checked 204 * @param brace the right curly brace token 205 */ 206 private void checkRightBrace(DetailAST currentBlock, DetailAST brace) { 207 final DetailAST nextToken = getNextToken(brace); 208 final boolean hasContentAround = contentAround(brace, nextToken); 209 if (nextToken != null && isPartOfMultiBlock(currentBlock, nextToken)) { 210 checkMultiBlockStatement(currentBlock, brace, nextToken); 211 } 212 else if (currentBlock.getParent().getType() == TokenTypes.LITERAL_ELSE 213 || TokenUtil.isOfType(currentBlock, TokenTypes.LITERAL_ELSE, 214 TokenTypes.LITERAL_CATCH, TokenTypes.LITERAL_FINALLY)) { 215 if (hasContentAround) { 216 logViolations(MSG_KEY_LINE_ALONE, brace); 217 } 218 } 219 else if (isEmpty(brace)) { 220 verifyEmptyBlock(brace, nextToken); 221 } 222 else if (hasContentAround) { 223 logViolations(MSG_KEY_LINE_ALONE, brace); 224 } 225 } 226 227 /** 228 * Checks that the right curly brace of a multi-block statement (e.g. {@code if/else}, 229 * {@code try/catch/finally}, {@code do/while}) is placed correctly relative to the next block. 230 * 231 * @param currentBlock the current block 232 * @param brace the right curly brace 233 * @param nextBlock the next block in multi-block statement 234 */ 235 private void checkMultiBlockStatement(DetailAST currentBlock, DetailAST brace, 236 DetailAST nextBlock) { 237 if (TokenUtil.areOnSameLine(brace, nextBlock)) { 238 if (hasContentOnLeftSide(brace) && !(currentBlock.getType() == TokenTypes.LITERAL_DO 239 && isEmpty(brace))) { 240 logViolations(MSG_KEY_LINE_BREAK_BEFORE, brace); 241 } 242 } 243 else { 244 logViolations(MSG_KEY_LINE_SAME, brace); 245 } 246 } 247 248 /** 249 * Checks empty block which should be concise and alone. 250 * 251 * @param brace the right curly token. 252 * @param nextToken the token after right curly brace. 253 */ 254 private void verifyEmptyBlock(DetailAST brace, @Nullable DetailAST nextToken) { 255 if (isNotConcise(brace)) { 256 logViolations(MSG_KEY_CONCISE_BLOCK, brace); 257 } 258 else if (nextToken != null 259 && hasContentOnRightSide(brace, nextToken)) { 260 logViolations(MSG_KEY_LINE_BREAK_AFTER, brace); 261 } 262 } 263 264 /** 265 * Checks if the right curly has content around. 266 * 267 * @param brace the right curly brace 268 * @param nextToken the next token after right curly 269 * @return {@code true} if right curly has content on its left or right. 270 */ 271 private static boolean contentAround(DetailAST brace, @Nullable DetailAST nextToken) { 272 return nextToken != null 273 && hasContentOnRightSide(brace, nextToken) 274 || hasContentOnLeftSide(brace); 275 } 276 277 /** 278 * Checks whether the current block is part of a multi-block statement ({@code if/else}, 279 * {@code try/catch/finally}, or {@code do/while}). 280 * 281 * @param currentBlock the current block 282 * @param nextBlock the block following {@code ast} 283 * @return {@code true} if {@code ast} and {@code nextBlock} belong to 284 * the same multi-block statement 285 */ 286 private static boolean isPartOfMultiBlock(DetailAST currentBlock, DetailAST nextBlock) { 287 final int nextBlockType = nextBlock.getType(); 288 return switch (currentBlock.getType()) { 289 case TokenTypes.LITERAL_IF -> 290 nextBlockType == TokenTypes.LITERAL_ELSE; 291 case TokenTypes.LITERAL_TRY, TokenTypes.LITERAL_CATCH -> 292 nextBlockType == TokenTypes.LITERAL_CATCH 293 || nextBlockType == TokenTypes.LITERAL_FINALLY; 294 case TokenTypes.LITERAL_DO -> true; 295 default -> false; 296 }; 297 } 298 299 /** 300 * Checks if the block is not concise and has content on left side of right brace. 301 * 302 * @param brace the right curly brace token 303 * @return {@code true} if the brace has content on left. 304 */ 305 private static boolean hasContentOnLeftSide(DetailAST brace) { 306 DetailAST previousToken = brace.getPreviousSibling(); 307 if (previousToken == null) { 308 previousToken = brace.getParent(); 309 } 310 if (previousToken.getType() != TokenTypes.SLIST) { 311 while (previousToken.hasChildren()) { 312 previousToken = previousToken.getLastChild(); 313 } 314 } 315 316 return TokenUtil.areOnSameLine(brace, previousToken) 317 && !TokenUtil.isOfType(brace.getPreviousSibling(), 318 TokenTypes.ENUM_CONSTANT_DEF, TokenTypes.COMMA); 319 } 320 321 /** 322 * Checks if the right curly brace is part multi-block statement or no 323 * content on right side of right brace. 324 * 325 * @param brace the right curly brace token 326 * @param nextToken the next token of right curly. 327 * @return {@code true} if the brace is on the same line as the previous sibling 328 * or parent if no sibling exists 329 */ 330 private static boolean hasContentOnRightSide(DetailAST brace, DetailAST nextToken) { 331 final boolean nextIsValid = TokenUtil.isOfType(nextToken.getPreviousSibling(), 332 TokenTypes.EXPR, TokenTypes.VARIABLE_DEF, TokenTypes.ELIST, TokenTypes.LAMBDA) 333 || TokenUtil.isCommentType(nextToken.getType()); 334 return !nextIsValid && TokenUtil.areOnSameLine(brace, nextToken); 335 } 336 337 /** 338 * Checks if block is empty. 339 * 340 * @param brace the right curly brace. 341 * @return {@code true} if the block is empty. 342 */ 343 private static boolean isEmpty(DetailAST brace) { 344 final DetailAST previousSibling = brace.getPreviousSibling(); 345 return previousSibling == null || previousSibling.getType() == TokenTypes.LCURLY; 346 } 347 348 /** 349 * Checks if the block is not {@code K&R} style or concise {@code {}}. 350 * 351 * @param brace right curly token 352 * @return {@code true} if block is not concise. 353 */ 354 private static boolean isNotConcise(DetailAST brace) { 355 final DetailAST lcurly = brace.getParent(); 356 return lcurly.getLineNo() == brace.getLineNo() 357 && lcurly.getColumnNo() + 1 != brace.getColumnNo(); 358 } 359 360 /** 361 * Traverses up the AST to find the next sibling token after the right curly brace. 362 * 363 * @param node ast token 364 * @return the next sibling token, or {@code null} if none exists 365 */ 366 @Nullable 367 private static DetailAST getNextToken(DetailAST node) { 368 DetailAST current = node; 369 DetailAST nextToken = null; 370 while (current != null && nextToken == null) { 371 nextToken = current.getNextSibling(); 372 current = current.getParent(); 373 } 374 return nextToken; 375 } 376 377}