[clang-format] Add LeaveAll to the BreakAfterAttributes option (#187204)

Closes #163537
This commit is contained in:
owenca 2026-03-18 23:14:38 -07:00 committed by GitHub
parent f554fcfd0b
commit 79042e701b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 66 additions and 13 deletions

View File

@ -2727,7 +2727,7 @@ the configuration (without a prefix: ``Auto``).
Possible values:
* ``ABS_Always`` (in configuration: ``Always``)
Always break after attributes.
Always break after the last attribute of the group.
.. code-block:: c++
@ -2758,7 +2758,7 @@ the configuration (without a prefix: ``Auto``).
}
* ``ABS_Leave`` (in configuration: ``Leave``)
Leave the line breaking after attributes as is.
Leave the line breaking after the last attribute of the group as is.
.. code-block:: c++
@ -2784,8 +2784,24 @@ the configuration (without a prefix: ``Auto``).
return;
}
* ``ABS_LeaveAll`` (in configuration: ``LeaveAll``)
Same as ``Leave`` except that it applies to all attributes of the group.
.. code-block:: c++
[[deprecated("Don't use this version")]]
[[nodiscard]]
bool foo() {
return true;
}
[[deprecated("Don't use this version")]]
[[nodiscard]] bool bar() {
return true;
}
* ``ABS_Never`` (in configuration: ``Never``)
Never break after attributes.
Never break after the last attribute of the group.
.. code-block:: c++

View File

@ -1717,9 +1717,10 @@ struct FormatStyle {
/// \version 18
bool BreakAdjacentStringLiterals;
/// Different ways to break after attributes.
/// Different ways to break after the last attribute of a group before a
/// declaration or control statement.
enum AttributeBreakingStyle : int8_t {
/// Always break after attributes.
/// Always break after the last attribute of the group.
/// \code
/// [[maybe_unused]]
/// const int i;
@ -1748,7 +1749,7 @@ struct FormatStyle {
/// }
/// \endcode
ABS_Always,
/// Leave the line breaking after attributes as is.
/// Leave the line breaking after the last attribute of the group as is.
/// \code
/// [[maybe_unused]] const int i;
/// [[gnu::const]] [[maybe_unused]]
@ -1773,7 +1774,21 @@ struct FormatStyle {
/// }
/// \endcode
ABS_Leave,
/// Never break after attributes.
/// Same as ``Leave`` except that it applies to all attributes of the group.
/// \code
/// [[deprecated("Don't use this version")]]
/// [[nodiscard]]
/// bool foo() {
/// return true;
/// }
///
/// [[deprecated("Don't use this version")]]
/// [[nodiscard]] bool bar() {
/// return true;
/// }
/// \endcode
ABS_LeaveAll,
/// Never break after the last attribute of the group.
/// \code
/// [[maybe_unused]] const int i;
/// [[gnu::const]] [[maybe_unused]] int j;

View File

@ -121,6 +121,7 @@ struct ScalarEnumerationTraits<FormatStyle::AttributeBreakingStyle> {
static void enumeration(IO &IO, FormatStyle::AttributeBreakingStyle &Value) {
IO.enumCase(Value, "Always", FormatStyle::ABS_Always);
IO.enumCase(Value, "Leave", FormatStyle::ABS_Leave);
IO.enumCase(Value, "LeaveAll", FormatStyle::ABS_LeaveAll);
IO.enumCase(Value, "Never", FormatStyle::ABS_Never);
}
};

View File

@ -28,10 +28,10 @@ static bool mustBreakAfterAttributes(const FormatToken &Tok,
switch (Style.BreakAfterAttributes) {
case FormatStyle::ABS_Always:
return true;
case FormatStyle::ABS_Leave:
return Tok.NewlinesBefore > 0;
default:
case FormatStyle::ABS_Never:
return false;
default: // ABS_Leave and ABS_LeaveAll
return Tok.NewlinesBefore > 0;
}
}
@ -4121,6 +4121,7 @@ void TokenAnnotator::calculateFormattingInformation(AnnotatedLine &Line) const {
}
if (!LineIsFunctionDeclaration) {
Line.ReturnTypeWrapped = false;
// Annotate */&/&& in `operator` function calls as binary operators.
for (const auto *Tok = FirstNonComment; Tok; Tok = Tok->Next) {
if (Tok->isNot(tok::kw_operator))
@ -5706,7 +5707,7 @@ static bool isAllmanLambdaBrace(const FormatToken &Tok) {
Tok.isNoneOf(TT_ObjCBlockLBrace, TT_DictLiteral);
}
bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line,
bool TokenAnnotator::mustBreakBefore(AnnotatedLine &Line,
const FormatToken &Right) const {
if (Right.NewlinesBefore > 1 && Style.MaxEmptyLinesToKeep > 0 &&
(!Style.RemoveEmptyLinesInUnwrappedLines || &Right == Line.First)) {
@ -6183,6 +6184,12 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line,
return true;
}
if (Style.BreakAfterAttributes == FormatStyle::ABS_LeaveAll &&
Left.is(TT_AttributeRSquare) && Right.NewlinesBefore > 0) {
Line.ReturnTypeWrapped = true;
return true;
}
return false;
}

View File

@ -247,8 +247,7 @@ private:
bool spaceRequiredBefore(const AnnotatedLine &Line,
const FormatToken &Right) const;
bool mustBreakBefore(const AnnotatedLine &Line,
const FormatToken &Right) const;
bool mustBreakBefore(AnnotatedLine &Line, const FormatToken &Right) const;
bool canBreakBefore(const AnnotatedLine &Line,
const FormatToken &Right) const;

View File

@ -1196,6 +1196,8 @@ TEST(ConfigParseTest, ParsesConfiguration) {
FormatStyle::ABS_Always);
CHECK_PARSE("BreakAfterAttributes: Leave", BreakAfterAttributes,
FormatStyle::ABS_Leave);
CHECK_PARSE("BreakAfterAttributes: LeaveAll", BreakAfterAttributes,
FormatStyle::ABS_LeaveAll);
CHECK_PARSE("BreakAfterAttributes: Never", BreakAfterAttributes,
FormatStyle::ABS_Never);

View File

@ -28321,6 +28321,19 @@ TEST_F(FormatTest, BreakAfterAttributes) {
EXPECT_EQ(Style.BreakAfterAttributes, FormatStyle::ABS_Leave);
verifyNoChange(Code, Style);
Style.BreakAfterAttributes = FormatStyle::ABS_LeaveAll;
verifyNoChange("[[deprecated(\"Don't use this version\")]]\n"
"[[nodiscard]]\n"
"bool foo() {\n"
" return true;\n"
"}\n"
"\n"
"[[deprecated(\"Don't use this version\")]]\n"
"[[nodiscard]] bool bar() {\n"
" return true;\n"
"}",
Style);
Style.BreakAfterAttributes = FormatStyle::ABS_Never;
verifyFormat("[[maybe_unused]] const int i;\n"
"[[foo([[]])]] [[maybe_unused]] int j;\n"