[clang] Implement KeepEmptyLines.AtEndOfBlock feature (PR #224223)
Arkadiusz Bokowy via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 17 00:57:46 PDT 2026
https://github.com/arkq created https://github.com/llvm/llvm-project/pull/224223
Add support for the AtEndOfBlock option in KeepEmptyLines configuration, which controls whether empty lines are preserved before closing braces. This feature works in pair with AtStartOfBlock for comprehensive control of empty lines around block boundaries.
The default is false, which should maintain backward compatibility by removing empty lines before closing braces. When set to true, empty lines before closing braces are preserved respecting MaxEmptyLinesToKeep limit.
Closes #22073 and #171003
>From 05d7eab9a780742471b2c5c7a46b7fc5aa5218cf Mon Sep 17 00:00:00 2001
From: Arkadiusz Bokowy <arkadiusz.bokowy at gmail.com>
Date: Thu, 17 Sep 2026 09:47:42 +0200
Subject: [PATCH] Implement KeepEmptyLines.AtEndOfBlock feature
Add support for the AtEndOfBlock option in KeepEmptyLines configuration,
which controls whether empty lines are preserved before closing braces.
This feature works in pair with AtStartOfBlock for comprehensive control
of empty lines around block boundaries.
The default is false, which should maintain backward compatibility by
removing empty lines before closing braces. When set to true, empty lines
before closing braces are preserved respecting MaxEmptyLinesToKeep limit.
Closes #22073 and #171003
---
clang/include/clang/Format/Format.h | 13 ++++++++++-
clang/lib/Format/Format.cpp | 2 ++
clang/lib/Format/UnwrappedLineFormatter.cpp | 3 ++-
clang/unittests/Format/ConfigParseTest.cpp | 1 +
clang/unittests/Format/FormatTest.cpp | 25 +++++++++++++++++++++
5 files changed, 42 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index baf56a9937957..a5b6e76d06b08 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -3750,11 +3750,21 @@ struct FormatStyle {
///
/// \code
/// KeepEmptyLines:
+ /// AtEndOfBlock: false
/// AtEndOfFile: false
/// AtStartOfBlock: false
/// AtStartOfFile: false
/// \endcode
struct KeepEmptyLinesStyle {
+ /// Keep empty lines at end of a block.
+ /// \code
+ /// true: false:
+ /// if (foo) { vs. if (foo) {
+ /// bar(); bar();
+ /// }
+ /// }
+ /// \endcode
+ bool AtEndOfBlock;
/// Keep empty lines at end of file.
bool AtEndOfFile;
/// Keep empty lines at start of a block.
@@ -3769,7 +3779,8 @@ struct FormatStyle {
/// Keep empty lines at start of file.
bool AtStartOfFile;
bool operator==(const KeepEmptyLinesStyle &R) const {
- return AtEndOfFile == R.AtEndOfFile &&
+ return AtEndOfBlock == R.AtEndOfBlock &&
+ AtEndOfFile == R.AtEndOfFile &&
AtStartOfBlock == R.AtStartOfBlock &&
AtStartOfFile == R.AtStartOfFile;
}
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 4c78c1dbe9f80..c49144f3291de 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -517,6 +517,7 @@ template <> struct ScalarEnumerationTraits<FormatStyle::JavaScriptQuoteStyle> {
template <> struct MappingTraits<FormatStyle::KeepEmptyLinesStyle> {
static void mapping(IO &IO, FormatStyle::KeepEmptyLinesStyle &Value) {
+ IO.mapOptional("AtEndOfBlock", Value.AtEndOfBlock);
IO.mapOptional("AtEndOfFile", Value.AtEndOfFile);
IO.mapOptional("AtStartOfBlock", Value.AtStartOfBlock);
IO.mapOptional("AtStartOfFile", Value.AtStartOfFile);
@@ -1993,6 +1994,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
LLVMStyle.JavaScriptQuotes = FormatStyle::JSQS_Leave;
LLVMStyle.JavaScriptWrapImports = true;
LLVMStyle.KeepEmptyLines = {
+ /*AtEndOfBlock=*/false,
/*AtEndOfFile=*/false,
/*AtStartOfBlock=*/true,
/*AtStartOfFile=*/true,
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index f005f228328ce..41a8aa1f96f05 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -1657,7 +1657,8 @@ static auto computeNewlines(const AnnotatedLine &Line,
(!RootToken.Next ||
(RootToken.Next->is(tok::semi) && !RootToken.Next->Next)) &&
// Do not remove empty lines before namespace closing "}".
- !getNamespaceToken(&Line, Lines)) {
+ !getNamespaceToken(&Line, Lines) &&
+ !Style.KeepEmptyLines.AtEndOfBlock) {
Newlines = std::min(Newlines, 1u);
}
// Remove empty lines at the start of nested blocks (lambdas/arrow functions)
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 86511edb9d40d..32839712e297a 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -254,6 +254,7 @@ TEST(ConfigParseTest, ParsesConfigurationBools) {
CHECK_PARSE_NESTED_BOOL(BraceWrapping, SplitEmptyFunction);
CHECK_PARSE_NESTED_BOOL(BraceWrapping, SplitEmptyRecord);
CHECK_PARSE_NESTED_BOOL(BraceWrapping, SplitEmptyNamespace);
+ CHECK_PARSE_NESTED_BOOL(KeepEmptyLines, AtEndOfBlock);
CHECK_PARSE_NESTED_BOOL(KeepEmptyLines, AtEndOfFile);
CHECK_PARSE_NESTED_BOOL(KeepEmptyLines, AtStartOfBlock);
CHECK_PARSE_NESTED_BOOL(KeepEmptyLines, AtStartOfFile);
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 5aed37aa56d52..39f37e996bfee 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -170,6 +170,7 @@ TEST_F(FormatTest, RemovesEmptyLines) {
CustomStyle.BreakBeforeBraces = FormatStyle::BS_Custom;
CustomStyle.BraceWrapping.AfterNamespace = true;
CustomStyle.KeepEmptyLines.AtStartOfBlock = false;
+ CustomStyle.KeepEmptyLines.AtEndOfBlock = false;
verifyFormat("namespace N\n"
"{\n"
"\n"
@@ -397,6 +398,7 @@ TEST_F(FormatTest, RemovesEmptyLines) {
Style.BraceWrapping.AfterClass = true;
Style.BraceWrapping.AfterFunction = true;
Style.KeepEmptyLines.AtStartOfBlock = false;
+ Style.KeepEmptyLines.AtEndOfBlock = false;
verifyFormat("class Foo\n"
"{\n"
@@ -25609,6 +25611,29 @@ TEST_F(FormatTest, KeepEmptyLinesAtEOF) {
verifyFormat(Code, "int i;\n\n\n", Style);
}
+TEST_F(FormatTest, KeepEmptyLinesAtStartOfBlock) {
+ FormatStyle Style = getLLVMStyle();
+ Style.KeepEmptyLines.AtStartOfBlock = true;
+
+ constexpr StringRef Code("namespace {\n"
+ "\n"
+ "int i;\n"
+ "} // namespace");
+ verifyNoChange(Code, Style);
+}
+
+
+TEST_F(FormatTest, KeepEmptyLinesAtEndOfBlock) {
+ FormatStyle Style = getLLVMStyle();
+ Style.KeepEmptyLines.AtEndOfBlock = true;
+
+ constexpr StringRef Code("namespace {\n"
+ "int i;\n"
+ "\n"
+ "} // namespace");
+ verifyNoChange(Code, Style);
+}
+
TEST_F(FormatTest, SpaceAfterUDL) {
verifyFormat("auto c = (4s).count();");
verifyFormat("auto x = 5s .count() == 5;");
More information about the cfe-commits
mailing list