[clang] [clang-format] Add new option: WrapNamespaceBodyWithEmptyLines (PR #106901)

via cfe-commits cfe-commits at lists.llvm.org
Sun Sep 1 04:41:37 PDT 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: None (dmasloff)

<details>
<summary>Changes</summary>

I would like to suggest a new clang-format option for llvm-project - WrapNamespaceBodyWithNewlines. I think it can be added to upstream since it is used by many popular public repositories, for example, [ytsaurus](https://github.com/ytsaurus/ytsaurus/). You can look through their style guide at this page: https://github.com/ytsaurus/ytsaurus/blob/main/yt/styleguide/cpp.md#namespaces

As you can see from the name of the option it wraps the body of namespace with additional newlines turning this code:
```
namespace N {
int function();
} 
 ```
into that:
```
namespace N {

int function();

} 
 ```
Looking forward to your advices and recommendations

---
Full diff: https://github.com/llvm/llvm-project/pull/106901.diff


5 Files Affected:

- (modified) clang/docs/ClangFormatStyleOptions.rst (+42) 
- (modified) clang/include/clang/Format/Format.h (+39-1) 
- (modified) clang/lib/Format/Format.cpp (+15) 
- (modified) clang/lib/Format/UnwrappedLineFormatter.cpp (+42) 
- (modified) clang/unittests/Format/FormatTest.cpp (+205) 


``````````diff
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

``````````

</details>


https://github.com/llvm/llvm-project/pull/106901


More information about the cfe-commits mailing list