[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 <owenpiano@gmail.com>
This commit is contained in:
dmasloff 2025-01-03 08:52:01 +03:00 committed by GitHub
parent b6c06d1a8d
commit 1c997feff1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 244 additions and 1 deletions

View File

@ -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

View File

@ -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
--------

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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();

View File

@ -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