[clang] [clang-format] Add new option: WrapNamespaceBodyWithNewlines (PR #106145)

via cfe-commits cfe-commits at lists.llvm.org
Mon Sep 2 06:52:42 PDT 2024


https://github.com/dmasloff updated https://github.com/llvm/llvm-project/pull/106145

>From 219424f9cd3477d9290c8766eaa857234a1ae387 Mon Sep 17 00:00:00 2001
From: dmasloff <dmaslov24 at gmail.com>
Date: Mon, 26 Aug 2024 22:11:05 +0300
Subject: [PATCH 1/2] [clang-format] Add new option:
 WrapNamespaceBodyWithNewlines

---
 clang/docs/ClangFormatStyleOptions.rst      |  42 ++++
 clang/include/clang/Format/Format.h         |  40 +++-
 clang/lib/Format/Format.cpp                 |  15 ++
 clang/lib/Format/UnwrappedLineFormatter.cpp |  42 ++++
 clang/unittests/Format/FormatTest.cpp       | 205 ++++++++++++++++++++
 5 files changed, 343 insertions(+), 1 deletion(-)

diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index a427d7cd40fcdd..06ac88fc337983 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -6652,6 +6652,48 @@ the configuration (without a prefix: ``Auto``).
 
   For example: BOOST_PP_STRINGIZE
 
+.. _WrapNamespaceBodyWithEmptyLines:
+
+**WrapNamespaceBodyWithEmptyLines** (``WrapNamespaceBodyWithEmptyLinesStyle``) :versionbadge:`clang-format 19` :ref:`ΒΆ <WrapNamespaceBodyWithEmptyLines>`
+  Controls number of empty lines at the begging and at the end of
+  namespace definition.
+
+  Possible values:
+
+  * ``WNBWELS_Never`` (in configuration: ``Never``)
+    Removes all empty lines at the beginning and at the end of
+    namespace definition.
+
+    .. code-block:: c++
+
+      namespace N1 {
+      namespace N2
+        function();
+      }
+      }
+
+  * ``WNBWELS_Always`` (in configuration: ``Always``)
+    Always adds an empty line at the beginning and at the end of
+    namespace definition. MaxEmptyLinesToKeep is also applied, but
+    empty lines between consecutive namespace declarations are
+    always removed.
+
+    .. code-block:: c++
+
+      namespace N1 {
+      namespace N2 {
+
+        function();
+
+      }
+      }
+
+  * ``WNBWELS_Leave`` (in configuration: ``Leave``)
+    Keeps existing newlines at the beginning and at the end of
+    namespace definition using MaxEmptyLinesToKeep for formatting.
+
+
+
 .. END_FORMAT_STYLE_OPTIONS
 
 Adding additional style options
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index d8b62c7652a0f6..963c7cbe1f8809 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -5057,6 +5057,43 @@ struct FormatStyle {
   /// \version 11
   std::vector<std::string> WhitespaceSensitiveMacros;
 
+  /// Different styles for modify number of empty lines in
+  /// the beginning and at the of end of namespaces.
+  enum WrapNamespaceBodyWithEmptyLinesStyle : int8_t {
+    /// Removes all empty lines at the beginning and at the end of
+    /// namespace definition.
+    /// \code
+    ///   namespace N1 {
+    ///   namespace N2
+    ///     function();
+    ///   }
+    ///   }
+    /// \endcode
+    WNBWELS_Never,
+    /// Always adds an empty line at the beginning and at the end of
+    /// namespace definition. MaxEmptyLinesToKeep is also applied, but
+    /// empty lines between consecutive namespace declarations are
+    /// always removed.
+    /// \code
+    ///   namespace N1 {
+    ///   namespace N2 {
+    ///
+    ///     function();
+    ///
+    ///   }
+    ///   }
+    /// \endcode
+    WNBWELS_Always,
+    /// Keeps existing newlines at the beginning and at the end of
+    /// namespace definition using MaxEmptyLinesToKeep for formatting.
+    WNBWELS_Leave
+  };
+
+  /// Controls number of empty lines at the begging and at the end of
+  /// namespace definition.
+  /// \version 19
+  WrapNamespaceBodyWithEmptyLinesStyle WrapNamespaceBodyWithEmptyLines;
+
   bool operator==(const FormatStyle &R) const {
     return AccessModifierOffset == R.AccessModifierOffset &&
            AlignAfterOpenBracket == R.AlignAfterOpenBracket &&
@@ -5234,7 +5271,8 @@ struct FormatStyle {
            TypenameMacros == R.TypenameMacros && UseTab == R.UseTab &&
            VerilogBreakBetweenInstancePorts ==
                R.VerilogBreakBetweenInstancePorts &&
-           WhitespaceSensitiveMacros == R.WhitespaceSensitiveMacros;
+           WhitespaceSensitiveMacros == R.WhitespaceSensitiveMacros &&
+           WrapNamespaceBodyWithEmptyLines == R.WrapNamespaceBodyWithEmptyLines;
   }
 
   std::optional<FormatStyle> GetLanguageStyle(LanguageKind Language) const;
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index d2463b892fbb96..b0d2836e9c69c2 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -829,6 +829,18 @@ template <> struct ScalarEnumerationTraits<FormatStyle::UseTabStyle> {
   }
 };
 
+template <>
+struct ScalarEnumerationTraits<
+    FormatStyle::WrapNamespaceBodyWithEmptyLinesStyle> {
+  static void
+  enumeration(IO &IO,
+              FormatStyle::WrapNamespaceBodyWithEmptyLinesStyle &Value) {
+    IO.enumCase(Value, "Never", FormatStyle::WNBWELS_Never);
+    IO.enumCase(Value, "Always", FormatStyle::WNBWELS_Always);
+    IO.enumCase(Value, "Leave", FormatStyle::WNBWELS_Leave);
+  }
+};
+
 template <> struct MappingTraits<FormatStyle> {
   static void mapping(IO &IO, FormatStyle &Style) {
     // When reading, read the language first, we need it for getPredefinedStyle.
@@ -1154,6 +1166,8 @@ template <> struct MappingTraits<FormatStyle> {
                    Style.VerilogBreakBetweenInstancePorts);
     IO.mapOptional("WhitespaceSensitiveMacros",
                    Style.WhitespaceSensitiveMacros);
+    IO.mapOptional("WrapNamespaceBodyWithEmptyLines",
+                   Style.WrapNamespaceBodyWithEmptyLines);
 
     // If AlwaysBreakAfterDefinitionReturnType was specified but
     // BreakAfterReturnType was not, initialize the latter from the former for
@@ -1623,6 +1637,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
   LLVMStyle.WhitespaceSensitiveMacros.push_back("NS_SWIFT_NAME");
   LLVMStyle.WhitespaceSensitiveMacros.push_back("PP_STRINGIZE");
   LLVMStyle.WhitespaceSensitiveMacros.push_back("STRINGIZE");
+  LLVMStyle.WrapNamespaceBodyWithEmptyLines = FormatStyle::WNBWELS_Leave;
 
   LLVMStyle.PenaltyBreakAssignment = prec::Assignment;
   LLVMStyle.PenaltyBreakBeforeFirstCallParameter = 19;
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 1804c1437fd41d..e50f62b49acbcc 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -32,6 +32,26 @@ bool isRecordLBrace(const FormatToken &Tok) {
                      TT_StructLBrace, TT_UnionLBrace);
 }
 
+bool LineStartsNamespaceScope(const AnnotatedLine *Line,
+                              const AnnotatedLine *PreviousLine,
+                              const AnnotatedLine *PrevPrevLine) {
+  return PreviousLine &&
+         ((PreviousLine->Last->is(tok::l_brace) &&
+           PreviousLine->startsWithNamespace()) ||
+          (PrevPrevLine && PrevPrevLine->startsWithNamespace() &&
+           PreviousLine->startsWith(tok::l_brace)));
+}
+
+bool LineEndsNamespaceScope(const AnnotatedLine *Line,
+                            const SmallVectorImpl<AnnotatedLine *> &Lines) {
+  if (!Line)
+    return false;
+  const FormatToken *tok = Line->First;
+  if (!tok || tok->isNot(tok::r_brace))
+    return false;
+  return getNamespaceToken(Line, Lines) != nullptr;
+}
+
 /// Tracks the indent level of \c AnnotatedLines across levels.
 ///
 /// \c nextLine must be called for each \c AnnotatedLine, after which \c
@@ -1493,6 +1513,28 @@ static auto computeNewlines(const AnnotatedLine &Line,
     Newlines = 1;
   }
 
+  // Modify empty lines after "{" that opens namespace scope.
+  if (Style.WrapNamespaceBodyWithEmptyLines != FormatStyle::WNBWELS_Leave &&
+      LineStartsNamespaceScope(&Line, PreviousLine, PrevPrevLine)) {
+    if (Style.WrapNamespaceBodyWithEmptyLines == FormatStyle::WNBWELS_Never)
+      Newlines = std::min(Newlines, 1u);
+    else if (!Line.startsWithNamespace())
+      Newlines = std::max(Newlines, 2u);
+    else
+      Newlines = std::min(Newlines, 1u);
+  }
+
+  // Modify empty lines before "}" that closes namespace scope.
+  if (Style.WrapNamespaceBodyWithEmptyLines != FormatStyle::WNBWELS_Leave &&
+      LineEndsNamespaceScope(&Line, Lines)) {
+    if (Style.WrapNamespaceBodyWithEmptyLines == FormatStyle::WNBWELS_Never)
+      Newlines = std::min(Newlines, 1u);
+    else if (!LineEndsNamespaceScope(PreviousLine, Lines))
+      Newlines = std::max(Newlines, 2u);
+    else
+      Newlines = std::min(Newlines, 1u);
+  }
+
   // Insert or remove empty line before access specifiers.
   if (PreviousLine && RootToken.isAccessSpecifier()) {
     switch (Style.EmptyLineBeforeAccessModifier) {
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index a383a624434b1f..afb56add0c32ba 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -28084,6 +28084,211 @@ TEST_F(FormatTest, BreakBinaryOperations) {
                Style);
 }
 
+TEST_F(FormatTest, WrapNamespaceBodyWithEmptyLinesNever) {
+  FormatStyle Style = getLLVMStyle();
+  Style.FixNamespaceComments = false;
+  Style.ShortNamespaceLines = 0;
+  Style.MaxEmptyLinesToKeep = 2;
+  Style.WrapNamespaceBodyWithEmptyLines = FormatStyle::WNBWELS_Never;
+  Style.CompactNamespaces = false;
+
+  // Empty namespace
+  verifyNoChange("namespace N {};", Style);
+
+  // Single namespace
+  verifyNoChange("namespace N {\n"
+                 "int f1(int a) { return 2 * a; }\n"
+                 "};",
+                 Style);
+
+  // Nested namespace
+  verifyNoChange("namespace N1 {\n"
+                 "namespace N2 {\n"
+                 "namespace N3 {\n"
+                 "int f1() {\n"
+                 "  int a = 1;\n"
+                 "  return a;\n"
+                 "}\n"
+                 "}\n"
+                 "}\n"
+                 "}",
+                 Style);
+
+  Style.CompactNamespaces = true;
+
+  // Empty namespace
+  verifyNoChange("namespace N {\n};", Style);
+
+  // Single namespace
+  verifyNoChange("namespace N {\n"
+                 "int f1(int a) { return 2 * a; }\n"
+                 "};",
+                 Style);
+
+  // Nested namespace
+  verifyNoChange("namespace N1 { namespace N2 { namespace N3 {\n"
+                 "int f1() {\n"
+                 "  int a = 1;\n"
+                 "  return a;\n"
+                 "}\n"
+                 "}}}",
+                 Style);
+}
+
+TEST_F(FormatTest, WrapNamespaceBodyWithEmptyLinesAlways) {
+  FormatStyle Style = getLLVMStyle();
+  Style.FixNamespaceComments = false;
+  Style.ShortNamespaceLines = 0;
+  Style.MaxEmptyLinesToKeep = 0;
+  Style.WrapNamespaceBodyWithEmptyLines = FormatStyle::WNBWELS_Always;
+  Style.CompactNamespaces = false;
+
+  // Empty namespace
+  verifyNoChange("namespace N {};", Style);
+
+  // Single namespace
+  verifyNoChange("namespace N {\n\n"
+                 "int f1(int a) { return 2 * a; }\n\n"
+                 "};",
+                 Style);
+
+  // Nested namespace
+  verifyNoChange("namespace N1 {\n"
+                 "namespace N2 {\n"
+                 "namespace N3 {\n\n"
+                 "int f1() {\n"
+                 "  int a = 1;\n"
+                 "  return a;\n"
+                 "}\n\n"
+                 "}\n"
+                 "}\n"
+                 "}",
+                 Style);
+
+  Style.CompactNamespaces = true;
+
+  // Nested namespace
+  verifyNoChange("namespace N1 { namespace N2 { namespace N3 {\n\n"
+                 "int f1() {\n"
+                 "  int a = 1;\n"
+                 "  return a;\n"
+                 "}\n\n"
+                 "}}}",
+                 Style);
+
+  Style.MaxEmptyLinesToKeep = 2;
+  Style.CompactNamespaces = false;
+
+  // Empty namespace
+  verifyNoChange("namespace N {};", Style);
+
+  // Single namespace
+  verifyNoChange("namespace N {\n\n\n"
+                 "void function()\n\n\n"
+                 "};",
+                 Style);
+
+  // Nested namespace
+  verifyFormat("namespace N1 {\n"
+               "namespace N2 {\n"
+               "namespace N3 {\n\n\n"
+               "int f1() {\n"
+               "  int a = 1;\n"
+               "  return a;\n"
+               "}\n\n\n"
+               "}\n"
+               "}\n"
+               "}",
+               "namespace N1 {\n"
+               "namespace N2 {\n\n"
+               "namespace N3 {\n\n\n"
+               "int f1() {\n"
+               "  int a = 1;\n"
+               "  return a;\n"
+               "}\n\n\n"
+               "}\n\n"
+               "}\n"
+               "}",
+               Style);
+
+  Style.CompactNamespaces = true;
+
+  // Nested namespace
+  verifyNoChange("namespace N1 { namespace N2 { namespace N3 {\n\n\n"
+                 "int f1() {\n"
+                 "  int a = 1;\n"
+                 "  return a;\n"
+                 "}\n\n\n"
+                 "}}}",
+                 Style);
+}
+
+TEST_F(FormatTest, WrapNamespaceBodyWithEmptyLinesLeave) {
+  FormatStyle Style = getLLVMStyle();
+  Style.FixNamespaceComments = false;
+  Style.ShortNamespaceLines = 0;
+  Style.MaxEmptyLinesToKeep = 0;
+  Style.WrapNamespaceBodyWithEmptyLines = FormatStyle::WNBWELS_Leave;
+  Style.CompactNamespaces = false;
+
+  // Empty namespace
+  verifyNoChange("namespace N {};", Style);
+
+  // Single namespace
+  verifyNoChange("namespace N {\n"
+                 "int f1(int a) { return 2 * a; }\n"
+                 "};",
+                 Style);
+
+  // Nested namespace
+  verifyNoChange("namespace N1 {\n"
+                 "namespace N2 {\n"
+                 "namespace N3 {\n"
+                 "int f1() {\n"
+                 "  int a = 1;\n"
+                 "  return a;\n"
+                 "}\n"
+                 "}\n"
+                 "}\n"
+                 "}",
+                 Style);
+
+  Style.MaxEmptyLinesToKeep = 2;
+
+  // Single namespace
+  verifyNoChange("namespace N {\n\n\n"
+                 "int f1(int a) { return 2 * a; }\n\n\n"
+                 "};",
+                 Style);
+
+  // Nested namespace
+  verifyNoChange("namespace N1 {\n"
+                 "namespace N2 {\n\n"
+                 "namespace N3 {\n\n\n"
+                 "int f1() {\n"
+                 "  int a = 1;\n"
+                 "  return a;\n"
+                 "}\n\n\n"
+                 "}\n\n"
+                 "}\n"
+                 "}",
+                 Style);
+
+  Style.CompactNamespaces = true;
+
+  // Empty namespace
+  verifyNoChange("namespace N {\n};", Style);
+
+  // Nested namespace
+  verifyNoChange("namespace N1 { namespace N2 { namespace N3 {\n\n\n"
+                 "int f1() {\n"
+                 "  int a = 1;\n"
+                 "  return a;\n"
+                 "}\n\n\n"
+                 "}}}",
+                 Style);
+}
+
 } // namespace
 } // namespace test
 } // namespace format

>From 8c34acba55003b439e235ca5ec9913168c0a4ff0 Mon Sep 17 00:00:00 2001
From: dmasloff <dmaslov24 at gmail.com>
Date: Mon, 2 Sep 2024 16:50:47 +0300
Subject: [PATCH 2/2] FormatTests update

---
 clang/unittests/Format/FormatTest.cpp | 89 +++++++++++++++++++--------
 1 file changed, 64 insertions(+), 25 deletions(-)

diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index b39b86b7e2d3f5..991a8b1c351108 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -28137,7 +28137,8 @@ TEST_F(FormatTest, WrapNamespaceBodyWithEmptyLinesNever) {
   Style.CompactNamespaces = true;
 
   // Empty namespace
-  verifyNoChange("namespace N {\n};", Style);
+  verifyNoChange("namespace N {\n"
+                 "};", Style);
 
   // Single namespace
   verifyNoChange("namespace N {\n"
@@ -28167,19 +28168,23 @@ TEST_F(FormatTest, WrapNamespaceBodyWithEmptyLinesAlways) {
   verifyNoChange("namespace N {};", Style);
 
   // Single namespace
-  verifyNoChange("namespace N {\n\n"
-                 "int f1(int a) { return 2 * a; }\n\n"
+  verifyNoChange("namespace N {\n"
+                 "\n"
+                 "int f1(int a) { return 2 * a; }\n"
+                 "\n"
                  "};",
                  Style);
 
   // Nested namespace
   verifyNoChange("namespace N1 {\n"
                  "namespace N2 {\n"
-                 "namespace N3 {\n\n"
+                 "namespace N3 {\n"
+                 "\n"
                  "int f1() {\n"
                  "  int a = 1;\n"
                  "  return a;\n"
-                 "}\n\n"
+                 "}\n"
+                 "\n"
                  "}\n"
                  "}\n"
                  "}",
@@ -28188,11 +28193,13 @@ TEST_F(FormatTest, WrapNamespaceBodyWithEmptyLinesAlways) {
   Style.CompactNamespaces = true;
 
   // Nested namespace
-  verifyNoChange("namespace N1 { namespace N2 { namespace N3 {\n\n"
+  verifyNoChange("namespace N1 { namespace N2 { namespace N3 {\n"
+                 "\n"
                  "int f1() {\n"
                  "  int a = 1;\n"
                  "  return a;\n"
-                 "}\n\n"
+                 "}\n"
+                 "\n"
                  "}}}",
                  Style);
 
@@ -28203,30 +28210,44 @@ TEST_F(FormatTest, WrapNamespaceBodyWithEmptyLinesAlways) {
   verifyNoChange("namespace N {};", Style);
 
   // Single namespace
-  verifyNoChange("namespace N {\n\n\n"
-                 "void function()\n\n\n"
+  verifyNoChange("namespace N {\n"
+                 "\n"
+                 "\n"
+                 "void function()\n"
+                 "\n"
+                 "\n"
                  "};",
                  Style);
 
   // Nested namespace
   verifyFormat("namespace N1 {\n"
                "namespace N2 {\n"
-               "namespace N3 {\n\n\n"
+               "namespace N3 {\n"
+               "\n"
+               "\n"
                "int f1() {\n"
                "  int a = 1;\n"
                "  return a;\n"
-               "}\n\n\n"
+               "}\n"
+               "\n"
+               "\n"
                "}\n"
                "}\n"
                "}",
                "namespace N1 {\n"
-               "namespace N2 {\n\n"
-               "namespace N3 {\n\n\n"
+               "namespace N2 {\n"
+               "\n"
+               "namespace N3 {\n"
+               "\n"
+               "\n"
                "int f1() {\n"
                "  int a = 1;\n"
                "  return a;\n"
-               "}\n\n\n"
-               "}\n\n"
+               "}\n"
+               "\n"
+               "\n"
+               "}\n"
+               "\n"
                "}\n"
                "}",
                Style);
@@ -28234,11 +28255,15 @@ TEST_F(FormatTest, WrapNamespaceBodyWithEmptyLinesAlways) {
   Style.CompactNamespaces = true;
 
   // Nested namespace
-  verifyNoChange("namespace N1 { namespace N2 { namespace N3 {\n\n\n"
+  verifyNoChange("namespace N1 { namespace N2 { namespace N3 {\n"
+                 "\n"
+                 "\n"
                  "int f1() {\n"
                  "  int a = 1;\n"
                  "  return a;\n"
-                 "}\n\n\n"
+                 "}\n"
+                 "\n"
+                 "\n"
                  "}}}",
                  Style);
 }
@@ -28276,20 +28301,30 @@ TEST_F(FormatTest, WrapNamespaceBodyWithEmptyLinesLeave) {
   Style.MaxEmptyLinesToKeep = 2;
 
   // Single namespace
-  verifyNoChange("namespace N {\n\n\n"
-                 "int f1(int a) { return 2 * a; }\n\n\n"
+  verifyNoChange("namespace N {\n"
+                 "\n"
+                 "\n"
+                 "int f1(int a) { return 2 * a; }\n"
+                 "\n"
+                 "\n"
                  "};",
                  Style);
 
   // Nested namespace
   verifyNoChange("namespace N1 {\n"
-                 "namespace N2 {\n\n"
-                 "namespace N3 {\n\n\n"
+                 "namespace N2 {\n"
+                 "\n"
+                 "namespace N3 {\n"
+                 "\n"
+                 "\n"
                  "int f1() {\n"
                  "  int a = 1;\n"
                  "  return a;\n"
-                 "}\n\n\n"
-                 "}\n\n"
+                 "}\n"
+                 "\n"
+                 "\n"
+                 "}\n"
+                 "\n"
                  "}\n"
                  "}",
                  Style);
@@ -28300,11 +28335,15 @@ TEST_F(FormatTest, WrapNamespaceBodyWithEmptyLinesLeave) {
   verifyNoChange("namespace N {\n};", Style);
 
   // Nested namespace
-  verifyNoChange("namespace N1 { namespace N2 { namespace N3 {\n\n\n"
+  verifyNoChange("namespace N1 { namespace N2 { namespace N3 {\n"
+                 "\n"
+                 "\n"
                  "int f1() {\n"
                  "  int a = 1;\n"
                  "  return a;\n"
-                 "}\n\n\n"
+                 "}\n"
+                 "\n"
+                 "\n"
                  "}}}",
                  Style);
 }



More information about the cfe-commits mailing list