diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index d9b3f666df03c..7bfaee4e2d35b 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -6843,6 +6843,45 @@ the configuration (without a prefix: ``Auto``). For example: BOOST_PP_STRINGIZE +.. _WrapNamespaceBodyWithEmptyLines: + +**WrapNamespaceBodyWithEmptyLines** (``WrapNamespaceBodyWithEmptyLinesStyle``) :versionbadge:`clang-format 20` :ref:`ΒΆ ` + Wrap namespace body with empty lines. + + Possible values: + + * ``WNBWELS_Never`` (in configuration: ``Never``) + Remove all empty lines at the beginning and the end of namespace body. + + .. code-block:: c++ + + namespace N1 { + namespace N2 + function(); + } + } + + * ``WNBWELS_Always`` (in configuration: ``Always``) + Always have at least one empty line at the beginning and the end of + namespace body except that the number of empty lines between consecutive + nested namespace definitions is not increased. + + .. code-block:: c++ + + namespace N1 { + namespace N2 { + + function(); + + } + } + + * ``WNBWELS_Leave`` (in configuration: ``Leave``) + Keep existing newlines at the beginning and the end of namespace body. + ``MaxEmptyLinesToKeep`` still applies. + + + .. END_FORMAT_STYLE_OPTIONS Adding additional style options diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index aca07e2ba9cf2..2789a24ebf273 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -1127,6 +1127,7 @@ clang-format - Adds ``AllowShortNamespacesOnASingleLine`` option. - Adds ``VariableTemplates`` option. - Adds support for bash globstar in ``.clang-format-ignore``. +- Adds ``WrapNamespaceBodyWithEmptyLines`` option. libclang -------- diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index bb34f2d33ac15..9b7a633e0a146 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -5143,6 +5143,39 @@ struct FormatStyle { /// \version 11 std::vector WhitespaceSensitiveMacros; + /// Different styles for wrapping namespace body with empty lines. + enum WrapNamespaceBodyWithEmptyLinesStyle : int8_t { + /// Remove all empty lines at the beginning and the end of namespace body. + /// \code + /// namespace N1 { + /// namespace N2 + /// function(); + /// } + /// } + /// \endcode + WNBWELS_Never, + /// Always have at least one empty line at the beginning and the end of + /// namespace body except that the number of empty lines between consecutive + /// nested namespace definitions is not increased. + /// \code + /// namespace N1 { + /// namespace N2 { + /// + /// function(); + /// + /// } + /// } + /// \endcode + WNBWELS_Always, + /// Keep existing newlines at the beginning and the end of namespace body. + /// ``MaxEmptyLinesToKeep`` still applies. + WNBWELS_Leave + }; + + /// Wrap namespace body with empty lines. + /// \version 20 + WrapNamespaceBodyWithEmptyLinesStyle WrapNamespaceBodyWithEmptyLines; + bool operator==(const FormatStyle &R) const { return AccessModifierOffset == R.AccessModifierOffset && AlignAfterOpenBracket == R.AlignAfterOpenBracket && @@ -5326,7 +5359,8 @@ struct FormatStyle { UseTab == R.UseTab && VariableTemplates == R.VariableTemplates && VerilogBreakBetweenInstancePorts == R.VerilogBreakBetweenInstancePorts && - WhitespaceSensitiveMacros == R.WhitespaceSensitiveMacros; + WhitespaceSensitiveMacros == R.WhitespaceSensitiveMacros && + WrapNamespaceBodyWithEmptyLines == R.WrapNamespaceBodyWithEmptyLines; } std::optional GetLanguageStyle(LanguageKind Language) const; diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index a5657f2d910f6..e51d7ac2e5b6c 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -839,6 +839,18 @@ template <> struct ScalarEnumerationTraits { } }; +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 { static void mapping(IO &IO, FormatStyle &Style) { // When reading, read the language first, we need it for getPredefinedStyle. @@ -1171,6 +1183,8 @@ template <> struct MappingTraits { 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 @@ -1639,6 +1653,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 803c600cec44d..bc6766a47f5c7 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -1584,6 +1584,23 @@ static auto computeNewlines(const AnnotatedLine &Line, Newlines = 1; } + if (Style.WrapNamespaceBodyWithEmptyLines != FormatStyle::WNBWELS_Leave) { + // Modify empty lines after TT_NamespaceLBrace. + if (PreviousLine && PreviousLine->endsWith(TT_NamespaceLBrace)) { + if (Style.WrapNamespaceBodyWithEmptyLines == FormatStyle::WNBWELS_Never) + Newlines = 1; + else if (!Line.startsWithNamespace()) + Newlines = std::max(Newlines, 2u); + } + // Modify empty lines before TT_NamespaceRBrace. + if (Line.startsWith(TT_NamespaceRBrace)) { + if (Style.WrapNamespaceBodyWithEmptyLines == FormatStyle::WNBWELS_Never) + Newlines = 1; + else if (!PreviousLine->startsWith(TT_NamespaceRBrace)) + Newlines = std::max(Newlines, 2u); + } + } + // Insert or remove empty line before access specifiers. if (PreviousLine && RootToken.isAccessSpecifier()) { switch (Style.EmptyLineBeforeAccessModifier) { diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index b249bf073aa45..9c38dbbc51f0a 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -865,6 +865,13 @@ TEST(ConfigParseTest, ParsesConfiguration) { CHECK_PARSE("SortUsingDeclarations: true", SortUsingDeclarations, FormatStyle::SUD_LexicographicNumeric); + CHECK_PARSE("WrapNamespaceBodyWithEmptyLines: Never", + WrapNamespaceBodyWithEmptyLines, FormatStyle::WNBWELS_Never); + CHECK_PARSE("WrapNamespaceBodyWithEmptyLines: Always", + WrapNamespaceBodyWithEmptyLines, FormatStyle::WNBWELS_Always); + CHECK_PARSE("WrapNamespaceBodyWithEmptyLines: Leave", + WrapNamespaceBodyWithEmptyLines, FormatStyle::WNBWELS_Leave); + // FIXME: This is required because parsing a configuration simply overwrites // the first N elements of the list instead of resetting it. Style.ForEachMacros.clear(); diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index 22b6f7e1b62e2..44b9dba249890 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -28427,6 +28427,136 @@ TEST_F(FormatTest, ShortNamespacesOption) { Style); } +TEST_F(FormatTest, WrapNamespaceBodyWithEmptyLinesNever) { + auto Style = getLLVMStyle(); + Style.FixNamespaceComments = false; + Style.MaxEmptyLinesToKeep = 2; + Style.WrapNamespaceBodyWithEmptyLines = FormatStyle::WNBWELS_Never; + + // Empty namespace. + verifyFormat("namespace N {}", Style); + + // Single namespace. + verifyFormat("namespace N {\n" + "int f1(int a) { return 2 * a; }\n" + "}", + "namespace N {\n" + "\n" + "\n" + "int f1(int a) { return 2 * a; }\n" + "\n" + "\n" + "}", + Style); + + // Nested namespace. + verifyFormat("namespace N1 {\n" + "namespace N2 {\n" + "int a = 1;\n" + "}\n" + "}", + "namespace N1 {\n" + "\n" + "\n" + "namespace N2 {\n" + "\n" + "int a = 1;\n" + "\n" + "}\n" + "\n" + "\n" + "}", + Style); + + Style.CompactNamespaces = true; + + verifyFormat("namespace N1 { namespace N2 {\n" + "int a = 1;\n" + "}}", + "namespace N1 { namespace N2 {\n" + "\n" + "\n" + "int a = 1;\n" + "\n" + "\n" + "}}", + Style); +} + +TEST_F(FormatTest, WrapNamespaceBodyWithEmptyLinesAlways) { + auto Style = getLLVMStyle(); + Style.FixNamespaceComments = false; + Style.MaxEmptyLinesToKeep = 2; + Style.WrapNamespaceBodyWithEmptyLines = FormatStyle::WNBWELS_Always; + + // Empty namespace. + verifyFormat("namespace N {}", Style); + + // Single namespace. + verifyFormat("namespace N {\n" + "\n" + "int f1(int a) { return 2 * a; }\n" + "\n" + "}", + "namespace N {\n" + "int f1(int a) { return 2 * a; }\n" + "}", + Style); + + // Nested namespace. + verifyFormat("namespace N1 {\n" + "namespace N2 {\n" + "\n" + "int a = 1;\n" + "\n" + "}\n" + "}", + "namespace N1 {\n" + "namespace N2 {\n" + "int a = 1;\n" + "}\n" + "}", + Style); + + verifyFormat("namespace N1 {\n" + "\n" + "namespace N2 {\n" + "\n" + "\n" + "int a = 1;\n" + "\n" + "\n" + "}\n" + "\n" + "}", + "namespace N1 {\n" + "\n" + "namespace N2 {\n" + "\n" + "\n" + "\n" + "int a = 1;\n" + "\n" + "\n" + "\n" + "}\n" + "\n" + "}", + Style); + + Style.CompactNamespaces = true; + + verifyFormat("namespace N1 { namespace N2 {\n" + "\n" + "int a = 1;\n" + "\n" + "}}", + "namespace N1 { namespace N2 {\n" + "int a = 1;\n" + "}}", + Style); +} + } // namespace } // namespace test } // namespace format