[clang] [clang-format] Add KwBreakBeforeCaseLabel (PR #227668)

Anan Yablonko via cfe-commits cfe-commits at lists.llvm.org
Thu Oct 1 04:06:35 PDT 2026


https://github.com/ananski-the-3rd updated https://github.com/llvm/llvm-project/pull/227668

>From c3adbdbaa334142233ac7f6449b41cfd1d8b78b9 Mon Sep 17 00:00:00 2001
From: "anan.yablonko" <anan.yablonko at skillz-edu.org>
Date: Wed, 30 Sep 2026 13:52:16 +0300
Subject: [PATCH] [clang-format] Add KwBreakBeforeCaseLabel

---
 clang/docs/ClangFormatStyleOptions.md       | 17 +++++++
 clang/include/clang/Format/Format.h         | 15 +++++++
 clang/lib/Format/Format.cpp                 |  2 +
 clang/lib/Format/UnwrappedLineFormatter.cpp |  8 ++++
 clang/lib/Format/UnwrappedLineParser.cpp    | 19 +++++++-
 clang/lib/Format/UnwrappedLineParser.h      |  1 +
 clang/unittests/Format/ConfigParseTest.cpp  |  1 +
 clang/unittests/Format/FormatTest.cpp       | 50 +++++++++++++++++++++
 8 files changed, 112 insertions(+), 1 deletion(-)

diff --git a/clang/docs/ClangFormatStyleOptions.md b/clang/docs/ClangFormatStyleOptions.md
index 81984ff185e53..ae50a75886a84 100644
--- a/clang/docs/ClangFormatStyleOptions.md
+++ b/clang/docs/ClangFormatStyleOptions.md
@@ -5431,6 +5431,23 @@ the configuration (without a prefix: `Auto`).
   newlines. (See
   www.gnu.org/prep/standards/html_node/Formatting.html#:~:text=formfeed.)
 
+(kwbreakbeforecaselabel)=
+
+**KwBreakBeforeCaseLabel** (`Boolean`) {ref}`¶ <KwBreakBeforeCaseLabel>`
+
+: Keep the break keyword and the following case label on the same line.
+
+  ```c++
+  false:                         true:
+  switch (foo) {                 switch (foo) {
+    case 1:                        case 1:
+      bar();                        bar();
+      break;                        break; case 2:
+    case 2:                         bar();
+      bar();                       }
+  }
+  ```
+
 (lambdabodyindentation)=
 
 **LambdaBodyIndentation** (`LambdaBodyIndentationKind`) {versionbadge}`clang-format 13` {ref}`¶ <LambdaBodyIndentation>`
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 6d4fa6e8ee2a7..1574585439971 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -3791,6 +3791,20 @@ struct FormatStyle {
   /// \version 20
   bool KeepFormFeed;
 
+  /// Keep the break keyword and the following case label on the same line.
+  ///
+  /// \code
+  ///    false:                         true:
+  ///    switch (foo) {                 switch (foo) {
+  ///      case 1:                        case 1:
+  ///        bar();                        bar();
+  ///        break;                        break; case 2:
+  ///      case 2:                         bar();
+  ///        bar();                       }
+  ///    }
+  /// \endcode
+  bool KwBreakBeforeCaseLabel;
+
   /// Indentation logic for lambda bodies.
   enum LambdaBodyIndentationKind : int8_t {
     /// Align lambda body relative to the lambda signature. This is the default.
@@ -6259,6 +6273,7 @@ struct FormatStyle {
            JavaScriptWrapImports == R.JavaScriptWrapImports &&
            KeepEmptyLines == R.KeepEmptyLines &&
            KeepFormFeed == R.KeepFormFeed && Language == R.Language &&
+           KwBreakBeforeCaseLabel == R.KwBreakBeforeCaseLabel &&
            LambdaBodyIndentation == R.LambdaBodyIndentation &&
            LineEnding == R.LineEnding && MacroBlockBegin == R.MacroBlockBegin &&
            MacroBlockEnd == R.MacroBlockEnd && Macros == R.Macros &&
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 4c78c1dbe9f80..910ac9684e31f 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1418,6 +1418,7 @@ template <> struct MappingTraits<FormatStyle> {
     IO.mapOptional("JavaScriptWrapImports", Style.JavaScriptWrapImports);
     IO.mapOptional("KeepEmptyLines", Style.KeepEmptyLines);
     IO.mapOptional("KeepFormFeed", Style.KeepFormFeed);
+    IO.mapOptional("KwBreakBeforeCaseLabel", Style.KwBreakBeforeCaseLabel);
     IO.mapOptional("LambdaBodyIndentation", Style.LambdaBodyIndentation);
     IO.mapOptional("LineEnding", Style.LineEnding);
     IO.mapOptional("MacroBlockBegin", Style.MacroBlockBegin);
@@ -1998,6 +1999,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
       /*AtStartOfFile=*/true,
   };
   LLVMStyle.KeepFormFeed = false;
+  LLVMStyle.KwBreakBeforeCaseLabel = false;
   LLVMStyle.LambdaBodyIndentation = FormatStyle::LBI_Signature;
   LLVMStyle.Language = Language;
   LLVMStyle.LineEnding = FormatStyle::LE_DeriveLF;
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index f005f228328ce..43634e4d1d958 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -281,6 +281,14 @@ class LineJoiner {
         return MergedLines;
     }
 
+    // Handle case labels preceded by a break statement
+    if (Style.KwBreakBeforeCaseLabel &&
+        NextLine.First->isOneOf(tok::kw_case, tok::kw_default) &&
+        TheLine->endsWith(tok::semi, tok::kw_break) &&
+        TheLine->Level <= NextLine.Level) {
+      return 1;
+    }
+
     const auto *PreviousLine = I != AnnotatedLines.begin() ? I[-1] : nullptr;
 
     // Handle blocks where the brace has already been wrapped.
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 4825e825af1fa..b8b025d9d7b1e 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -344,6 +344,17 @@ bool UnwrappedLineParser::precededByCommentOrPPDirective() const {
          (Previous->IsMultiline || Previous->NewlinesBefore > 0);
 }
 
+bool UnwrappedLineParser::linePrecededByKwBreak() const {
+  if (Lines.empty() || Lines.back().Tokens.empty())
+    return false;
+  const UnwrappedLine &PreviousLine = Lines.back();
+
+  if (PreviousLine.Level > Line->Level)
+    return false;
+
+  return PreviousLine.Tokens.front().Tok->is(tok::kw_break);
+}
+
 /// Parses a level, that is ???.
 /// \param OpeningBrace Opening brace (\p nullptr if absent) of that level.
 /// \param IfKind The \p if statement kind in the level.
@@ -3472,6 +3483,11 @@ void UnwrappedLineParser::parseLabel(bool IsGotoLabel) {
   const auto OldLineLevel = Line->Level;
   auto &Level = Line->Level;
 
+  if (!IsGotoLabel && Style.KwBreakBeforeCaseLabel && linePrecededByKwBreak()) {
+    assert(OldLineLevel > 0);
+    Lines.back().Level = OldLineLevel - 1;
+  }
+
   if (IsGotoLabel && IndentGotoLabel == FormatStyle::IGLS_NoIndent)
     Level = 0;
 
@@ -3488,7 +3504,8 @@ void UnwrappedLineParser::parseLabel(bool IsGotoLabel) {
     parseBlock();
     if (FormatTok->is(tok::kw_break)) {
       if (Style.BraceWrapping.AfterControlStatement ==
-          FormatStyle::BWACS_Always) {
+              FormatStyle::BWACS_Always ||
+          Style.KwBreakBeforeCaseLabel) {
         addUnwrappedLine();
         if (!Style.IndentCaseBlocks &&
             Style.BreakBeforeBraces == FormatStyle::BS_Whitesmiths) {
diff --git a/clang/lib/Format/UnwrappedLineParser.h b/clang/lib/Format/UnwrappedLineParser.h
index 5b93c8f346d75..3225e44a064c5 100644
--- a/clang/lib/Format/UnwrappedLineParser.h
+++ b/clang/lib/Format/UnwrappedLineParser.h
@@ -124,6 +124,7 @@ class UnwrappedLineParser {
   void reset();
   void parseFile();
   bool precededByCommentOrPPDirective() const;
+  bool linePrecededByKwBreak() const;
   bool parseLevel(const FormatToken *OpeningBrace = nullptr,
                   IfStmtKind *IfKind = nullptr,
                   FormatToken **IfLeftBrace = nullptr);
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 86511edb9d40d..0c37acc439239 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -202,6 +202,7 @@ TEST(ConfigParseTest, ParsesConfigurationBools) {
   CHECK_PARSE_BOOL_FIELD(KeepEmptyLines.AtStartOfBlock,
                          "KeepEmptyLinesAtTheStartOfBlocks");
   CHECK_PARSE_BOOL(KeepFormFeed);
+  CHECK_PARSE_BOOL(KwBreakBeforeCaseLabel);
   CHECK_PARSE_BOOL(ObjCSpaceAfterMethodDeclarationPrefix);
   CHECK_PARSE_BOOL(ObjCSpaceAfterProperty);
   CHECK_PARSE_BOOL(ObjCSpaceBeforeProtocolList);
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index bb630da34d7d9..dc69b83fe1c7a 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -2835,6 +2835,56 @@ TEST_F(FormatTest, FormatsSwitchStatement) {
                Style);
 }
 
+TEST_F(FormatTest, FormatsSwitchStatementKwBreakBeforeCase) {
+  FormatStyle Style = getLLVMStyle();
+  Style.IndentCaseLabels = true;
+  Style.KwBreakBeforeCaseLabel = true;
+
+  verifyFormat("switch (n) {\n"
+               "  break; case 0:;\n"
+               "}",
+               Style);
+
+  verifyFormat("switch (n) {\n"
+               "  break; case 0: {\n"
+               "    return false;\n"
+               "  }\n"
+               "  break; case 1:\n"
+               "  break; default: {\n"
+               "    return true;\n"
+               "  }\n"
+               "}",
+               Style);
+
+  verifyFormat("switch (n) {\n"
+               "  case 0: {\n"
+               "    return false;\n"
+               "  }\n"
+               "  break; case 1:\n"
+               "    while (x)\n"
+               "      break;\n"
+               "  case 2:\n"
+               "  break; default: {\n"
+               "    return true;\n"
+               "  }\n"
+               "}",
+               "switch (n) {\n"
+               "case 0: {\n"
+               "  return false;\n"
+               "}\n"
+               "  break;\n"
+               "case 1:\n"
+               "  while (x)\n"
+               "    break;\n"
+               "case 2:\n"
+               "  break;\n"
+               "default: {\n"
+               "  return true;\n"
+               "}\n"
+               "}",
+               Style);
+}
+
 TEST_F(FormatTest, CaseRanges) {
   verifyFormat("switch (x) {\n"
                "case 'A' ... 'Z':\n"



More information about the cfe-commits mailing list