[clang] [clang-format] Add KwBreakBeforeCaseLabel (PR #227656)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Sep 30 03:55:33 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-format
Author: Anan Yablonko (ananski-the-3rd)
<details>
<summary>Changes</summary>
---
Full diff: https://github.com/llvm/llvm-project/pull/227656.diff
8 Files Affected:
- (modified) clang/docs/ClangFormatStyleOptions.md (+17)
- (modified) clang/include/clang/Format/Format.h (+15)
- (modified) clang/lib/Format/Format.cpp (+58-1)
- (modified) clang/lib/Format/UnwrappedLineFormatter.cpp (+9)
- (modified) clang/lib/Format/UnwrappedLineParser.cpp (+17-1)
- (modified) clang/lib/Format/UnwrappedLineParser.h (+1)
- (modified) clang/unittests/Format/ConfigParseTest.cpp (+1)
- (modified) clang/unittests/Format/FormatTest.cpp (+53)
``````````diff
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..447254147423f 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1409,7 +1409,7 @@ template <> struct MappingTraits<FormatStyle> {
IO.mapOptional("IndentWidth", Style.IndentWidth);
IO.mapOptional("IndentWrappedFunctionNames",
Style.IndentWrappedFunctionNames);
- IO.mapOptional("InsertBraces", Style.InsertBraces);
+ IO.mapOptional("InsertBraces", Style.InsertBraces);
IO.mapOptional("InsertNewlineAtEOF", Style.InsertNewlineAtEOF);
IO.mapOptional("InsertTrailingCommas", Style.InsertTrailingCommas);
IO.mapOptional("IntegerLiteralSeparator", Style.IntegerLiteralSeparator);
@@ -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;
@@ -2794,6 +2796,53 @@ class BracesRemover : public TokenAnalyzer {
}
};
+class KwBreakInserter : public TokenAnalyzer {
+public:
+ KwBreakInserter(const Environment &Env, const FormatStyle &Style)
+ : TokenAnalyzer(Env, Style) {}
+
+ std::pair<tooling::Replacements, unsigned>
+ analyze(TokenAnnotator &Annotator,
+ SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
+ FormatTokenLexer &Tokens) override {
+ AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
+ tooling::Replacements Result;
+ insertKwBreak(AnnotatedLines, Result);
+ return {Result, 0};
+ }
+
+private:
+ static constexpr StringRef KwBreak = "break;";
+
+ void insertKwBreak(SmallVectorImpl<AnnotatedLine *> &Lines,
+ tooling::Replacements &Result) {
+
+ const auto &SourceMgr = Env.getSourceManager();
+ const auto *End = Lines.end();
+ for (const auto *I = Lines.begin(); I != End; ++I) {
+ const auto &Line = *I;
+ if (!Line->Children.empty())
+ insertKwBreak(Line->Children, Result);
+ if (!Line->Affected || !Line->startsWith(tok::kw_case))
+ continue;
+
+ const AnnotatedLine *Prev = nullptr;
+ for (auto IReverse = std::make_reverse_iterator(I);
+ IReverse != Lines.rend(); ++IReverse) {
+ if (!(*IReverse)->isComment()) {
+ Prev = *IReverse;
+ break;
+ }
+ }
+ if (!Prev || Prev->getLastNonComment()->isNot(tok::l_brace))
+ continue;
+ cantFail(Result.add(tooling::Replacement(
+ SourceMgr, Line->getFirstNonComment()->Tok.getLocation(), 0,
+ KwBreak)));
+ }
+ }
+};
+
class SemiRemover : public TokenAnalyzer {
public:
SemiRemover(const Environment &Env, const FormatStyle &Style)
@@ -4408,6 +4457,14 @@ reformat(const FormatStyle &Style, StringRef Code,
});
}
+ if (Style.KwBreakBeforeCaseLabel) {
+ FormatStyle S = Expanded;
+ S.KwBreakBeforeCaseLabel = true;
+ Passes.emplace_back([&, S = std::move(S)](const Environment &Env) {
+ return KwBreakInserter(Env, S).process(/*SkipAnnotation=*/true);
+ });
+ }
+
if (Style.EnumTrailingComma != FormatStyle::ETC_Leave) {
Passes.emplace_back([&](const Environment &Env) {
return EnumTrailingCommaEditor(Env, Expanded)
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index f005f228328ce..b046585ac0d02 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -281,6 +281,15 @@ class LineJoiner {
return MergedLines;
}
+ // Handle case labels preceeeded 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..d56381f7b8242 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -344,6 +344,18 @@ bool UnwrappedLineParser::precededByCommentOrPPDirective() const {
(Previous->IsMultiline || Previous->NewlinesBefore > 0);
}
+bool UnwrappedLineParser::linePrecededByKwBreak() const {
+ const UnwrappedLine &previousLine = Lines.back();
+
+ if (Lines.empty() || previousLine.Tokens.empty())
+ return false;
+
+ 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 +3484,9 @@ void UnwrappedLineParser::parseLabel(bool IsGotoLabel) {
const auto OldLineLevel = Line->Level;
auto &Level = Line->Level;
+ if (!IsGotoLabel && Style.KwBreakBeforeCaseLabel && linePrecededByKwBreak())
+ Lines.back().Level = Level - 1;
+
if (IsGotoLabel && IndentGotoLabel == FormatStyle::IGLS_NoIndent)
Level = 0;
@@ -3488,7 +3503,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..543f717288f77 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -2835,6 +2835,59 @@ 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);
+
+ Style.IndentCaseLabels = true;
+ Style.IndentCaseBlocks = false;
+ Style.KwBreakBeforeCaseLabel = true;
+ verifyFormat("switch (n) {\n"
+ " break; 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"
``````````
</details>
https://github.com/llvm/llvm-project/pull/227656
More information about the cfe-commits
mailing list