https://github.com/arkq updated https://github.com/llvm/llvm-project/pull/224223

>From 3c1f903cf060c7b2cd7eec47743018f7e623db1a Mon Sep 17 00:00:00 2001
From: Arkadiusz Bokowy <[email protected]>
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/docs/ClangFormatStyleOptions.md       | 11 +++++
 clang/include/clang/Format/Format.h         | 13 +++++-
 clang/lib/Format/ContinuationIndenter.cpp   |  6 ++-
 clang/lib/Format/Format.cpp                 |  2 +
 clang/lib/Format/UnwrappedLineFormatter.cpp |  3 +-
 clang/unittests/Format/ConfigParseTest.cpp  |  1 +
 clang/unittests/Format/FormatTest.cpp       | 47 +++++++++++++++++++++
 7 files changed, 79 insertions(+), 4 deletions(-)

diff --git a/clang/docs/ClangFormatStyleOptions.md 
b/clang/docs/ClangFormatStyleOptions.md
index 81984ff185e539..cd3c7b60391a0d 100644
--- a/clang/docs/ClangFormatStyleOptions.md
+++ b/clang/docs/ClangFormatStyleOptions.md
@@ -5388,11 +5388,22 @@ the configuration (without a prefix: `Auto`).
 
   ```c++
   KeepEmptyLines:
+    AtEndOfBlock: false
     AtEndOfFile: false
     AtStartOfBlock: false
     AtStartOfFile: false
   ```
 
+  - `bool AtEndOfBlock` Keep empty lines at end of a block.
+
+    ```c++
+    true:                                  false:
+    if (foo) {                     vs.     if (foo) {
+      bar();                                 bar();
+                                           }
+    }
+    ```
+
   - `bool AtEndOfFile` Keep empty lines at end of file.
 
   - `bool AtStartOfBlock` Keep empty lines at start of a block.
diff --git a/clang/include/clang/Format/Format.h 
b/clang/include/clang/Format/Format.h
index baf56a99379579..a5b6e76d06b084 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/ContinuationIndenter.cpp 
b/clang/lib/Format/ContinuationIndenter.cpp
index aa264c1487bce8..97efbc2e6043af 100644
--- a/clang/lib/Format/ContinuationIndenter.cpp
+++ b/clang/lib/Format/ContinuationIndenter.cpp
@@ -1309,8 +1309,10 @@ unsigned 
ContinuationIndenter::addTokenOnNewLine(LineState &State,
         !Current.MatchingParen->Children.empty()) {
       // lambdas and arrow functions are expressions, thus their r_brace is not
       // on its own line, and thus not covered by UnwrappedLineFormatter's 
logic
-      // about removing empty lines on closing blocks. Special case them here.
-      MaxEmptyLinesToKeep = 1;
+      // about removing empty lines on closing blocks. Special case them here
+      // with an exception if the KeepEmptyLines.AtEndOfBlock is used.
+      if (!Style.KeepEmptyLines.AtEndOfBlock)
+        MaxEmptyLinesToKeep = 1;
     }
     const unsigned Newlines =
         std::max(1u, std::min(Current.NewlinesBefore, MaxEmptyLinesToKeep));
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 4c78c1dbe9f80f..c49144f3291de1 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 f005f228328ce8..41a8aa1f96f05e 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 86511edb9d40db..32839712e297af 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 5aed37aa56d524..f102c46a05972c 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,51 @@ TEST_F(FormatTest, KeepEmptyLinesAtEOF) {
   verifyFormat(Code, "int i;\n\n\n", Style);
 }
 
+TEST_F(FormatTest, KeepEmptyLinesAtStartOfBlock) {
+  FormatStyle Style = getLLVMStyle();
+  Style.AllowShortFunctionsOnASingleLine =
+      FormatStyle::ShortFunctionStyle::setEmptyAndInline();
+  Style.KeepEmptyLines.AtStartOfBlock = true;
+  Style.MaxEmptyLinesToKeep = 2;
+
+  verifyFormat("void foo() {\n"
+               "\n\n"
+               "  int i;\n"
+               "}",
+               "void foo() {\n"
+               "\n\n\n\n"
+               "  int i;\n"
+               "}",
+               Style);
+}
+
+TEST_F(FormatTest, KeepEmptyLinesAtEndOfBlock) {
+  FormatStyle Style = getLLVMStyle();
+  Style.AllowShortFunctionsOnASingleLine =
+      FormatStyle::ShortFunctionStyle::setEmptyAndInline();
+  Style.KeepEmptyLines.AtEndOfBlock = true;
+  Style.MaxEmptyLinesToKeep = 2;
+
+  verifyFormat("void foo() {\n"
+               "  int i;\n"
+               "\n\n"
+               "}",
+               "void foo() {\n"
+               "  int i;\n"
+               "\n\n\n\n"
+               "}",
+               Style);
+  verifyFormat("foo([]() {\n"
+               "  int i;\n"
+               "\n\n"
+               "});",
+               "foo([]() {\n"
+               "  int i;\n"
+               "\n\n\n\n"
+               "});",
+               Style);
+}
+
 TEST_F(FormatTest, SpaceAfterUDL) {
   verifyFormat("auto c = (4s).count();");
   verifyFormat("auto x = 5s .count() == 5;");

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to