Author: dmasloff Date: 2025-01-02T21:52:01-08:00 New Revision: 1c997feff16860ab6b21c5c03dc7ca65f300967f
URL: https://github.com/llvm/llvm-project/commit/1c997feff16860ab6b21c5c03dc7ca65f300967f DIFF: https://github.com/llvm/llvm-project/commit/1c997feff16860ab6b21c5c03dc7ca65f300967f.diff LOG: [clang-format] Add option WrapNamespaceBodyWithNewlines (#106145) It wraps the body of namespace with additional newlines, turning this code: ``` namespace N { int function(); } ``` into the following: ``` namespace N { int function(); } ``` --------- Co-authored-by: Owen Pan <owenpi...@gmail.com> Added: Modified: clang/docs/ClangFormatStyleOptions.rst clang/docs/ReleaseNotes.rst clang/include/clang/Format/Format.h clang/lib/Format/Format.cpp clang/lib/Format/UnwrappedLineFormatter.cpp clang/unittests/Format/ConfigParseTest.cpp clang/unittests/Format/FormatTest.cpp Removed: ################################################################################ diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index d9b3f666df03c0..7bfaee4e2d35b9 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:`ΒΆ <WrapNamespaceBodyWithEmptyLines>` + 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 aca07e2ba9cf2d..2789a24ebf273d 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 bb34f2d33ac15e..9b7a633e0a1461 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<std::string> 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<FormatStyle> GetLanguageStyle(LanguageKind Language) const; diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index a5657f2d910f68..e51d7ac2e5b6c4 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -839,6 +839,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. @@ -1171,6 +1183,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 @@ -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 803c600cec44db..bc6766a47f5c70 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 b249bf073aa453..9c38dbbc51f0a1 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 22b6f7e1b62e2e..44b9dba2498900 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 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits