[clang-format] Add per-operator granularity for BreakBinaryOperations (#181051)
## Summary Extend `BreakBinaryOperations` to accept a structured YAML configuration with per-operator break rules and minimum chain length gating via `PerOperator`. - **Per-operator rules**: specify break style (`Never`, `OnePerLine`, `RespectPrecedence`) for specific operator groups - **Minimum chain length**: only trigger breaking when a chain has N or more operators - **Fully backward-compatible**: the simple scalar form (`BreakBinaryOperations: OnePerLine`) behaves identically to the current enum value RFC discussion: https://discourse.llvm.org/t/rfc-per-operator-granularity-for-breakbinaryoperations/89800 ### New YAML syntax ```yaml BreakBinaryOperations: Default: Never PerOperator: - Operators: ['&&', '||'] Style: OnePerLine MinChainLength: 3 - Operators: ['|'] Style: OnePerLine ``` ### Motivation `BreakBinaryOperations` operates at the level of all binary operators simultaneously. Enabling `OnePerLine` for `&&`/`||` condition chains also forces it on `+`, `|`, and other operators, which may not be desired. The only workaround today is `// clang-format off`. ## Test plan - [x] All existing `BreakBinaryOperations` unit tests updated and passing - [x] New tests for per-operator rules (`&&`/`||` OnePerLine + default Never) - [x] New tests for multiple operator groups (`&&`/`||` + `|`) - [x] New tests for `MinChainLength` gating (chain of 2 vs 3+) - [x] Config parse tests for structured YAML form - [x] RST documentation auto-generated via `dump_format_style.py` - [x] Release notes updated
This commit is contained in:
parent
5e0ef90141
commit
62e55b410c
@ -3613,42 +3613,110 @@ the configuration (without a prefix: ``Auto``).
|
||||
|
||||
.. _BreakBinaryOperations:
|
||||
|
||||
**BreakBinaryOperations** (``BreakBinaryOperationsStyle``) :versionbadge:`clang-format 20` :ref:`¶ <BreakBinaryOperations>`
|
||||
**BreakBinaryOperations** (``BreakBinaryOperationsOptions``) :versionbadge:`clang-format 20` :ref:`¶ <BreakBinaryOperations>`
|
||||
The break binary operations style to use.
|
||||
|
||||
Possible values:
|
||||
Nested configuration flags:
|
||||
|
||||
* ``BBO_Never`` (in configuration: ``Never``)
|
||||
Don't break binary operations
|
||||
Options for ``BreakBinaryOperations``.
|
||||
|
||||
.. code-block:: c++
|
||||
If specified as a simple string (e.g. ``OnePerLine``), it behaves like
|
||||
the original enum and applies to all binary operators.
|
||||
|
||||
aaa + bbbb * ccccc - ddddd +
|
||||
eeeeeeeeeeeeeeee;
|
||||
If specified as a struct, allows per-operator configuration:
|
||||
|
||||
* ``BBO_OnePerLine`` (in configuration: ``OnePerLine``)
|
||||
Binary operations will either be all on the same line, or each operation
|
||||
will have one line each.
|
||||
.. code-block:: yaml
|
||||
|
||||
.. code-block:: c++
|
||||
BreakBinaryOperations:
|
||||
Default: Never
|
||||
PerOperator:
|
||||
- Operators: ['&&', '||']
|
||||
Style: OnePerLine
|
||||
MinChainLength: 3
|
||||
|
||||
aaa +
|
||||
bbbb *
|
||||
ccccc -
|
||||
ddddd +
|
||||
eeeeeeeeeeeeeeee;
|
||||
* ``BreakBinaryOperationsStyle Default`` :versionbadge:`clang-format 23`
|
||||
|
||||
* ``BBO_RespectPrecedence`` (in configuration: ``RespectPrecedence``)
|
||||
Binary operations of a particular precedence that exceed the column
|
||||
limit will have one line each.
|
||||
The default break style for operators not covered by ``PerOperator``.
|
||||
|
||||
.. code-block:: c++
|
||||
Possible values:
|
||||
|
||||
aaa +
|
||||
bbbb * ccccc -
|
||||
ddddd +
|
||||
eeeeeeeeeeeeeeee;
|
||||
* ``BBO_Never`` (in configuration: ``Never``)
|
||||
Don't break binary operations
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
aaa + bbbb * ccccc - ddddd +
|
||||
eeeeeeeeeeeeeeee;
|
||||
|
||||
* ``BBO_OnePerLine`` (in configuration: ``OnePerLine``)
|
||||
Binary operations will either be all on the same line, or each operation
|
||||
will have one line each.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
aaa +
|
||||
bbbb *
|
||||
ccccc -
|
||||
ddddd +
|
||||
eeeeeeeeeeeeeeee;
|
||||
|
||||
* ``BBO_RespectPrecedence`` (in configuration: ``RespectPrecedence``)
|
||||
Binary operations of a particular precedence that exceed the column
|
||||
limit will have one line each.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
aaa +
|
||||
bbbb * ccccc -
|
||||
ddddd +
|
||||
eeeeeeeeeeeeeeee;
|
||||
|
||||
|
||||
* ``List of BinaryOperationBreakRules PerOperator`` Per-operator override rules.
|
||||
|
||||
* ``List of Strings Operators`` :versionbadge:`clang-format 23` The list of operators this rule applies to, e.g. ``&&``, ``||``, ``|``.
|
||||
Alternative spellings (e.g. ``and`` for ``&&``) are accepted.
|
||||
|
||||
* ``BreakBinaryOperationsStyle Style``
|
||||
The break style for these operators (defaults to ``OnePerLine``).
|
||||
|
||||
Possible values:
|
||||
|
||||
* ``BBO_Never`` (in configuration: ``Never``)
|
||||
Don't break binary operations
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
aaa + bbbb * ccccc - ddddd +
|
||||
eeeeeeeeeeeeeeee;
|
||||
|
||||
* ``BBO_OnePerLine`` (in configuration: ``OnePerLine``)
|
||||
Binary operations will either be all on the same line, or each operation
|
||||
will have one line each.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
aaa +
|
||||
bbbb *
|
||||
ccccc -
|
||||
ddddd +
|
||||
eeeeeeeeeeeeeeee;
|
||||
|
||||
* ``BBO_RespectPrecedence`` (in configuration: ``RespectPrecedence``)
|
||||
Binary operations of a particular precedence that exceed the column
|
||||
limit will have one line each.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
aaa +
|
||||
bbbb * ccccc -
|
||||
ddddd +
|
||||
eeeeeeeeeeeeeeee;
|
||||
|
||||
|
||||
* ``unsigned MinChainLength`` Minimum number of operands in a chain before the rule triggers.
|
||||
For example, ``a && b && c`` is a chain of length 3.
|
||||
``0`` means always break (when the line is too long).
|
||||
|
||||
|
||||
.. _BreakConstructorInitializers:
|
||||
|
||||
@ -408,6 +408,8 @@ clang-format
|
||||
'-'/'+' and the return type in Objective-C method declarations
|
||||
- Add ``AfterComma`` value to ``BreakConstructorInitializers`` to allow breaking
|
||||
constructor initializers after commas, keeping the colon on the same line.
|
||||
- Extend ``BreakBinaryOperations`` to accept a structured configuration with
|
||||
per-operator break rules and minimum chain length gating via ``PerOperator``.
|
||||
|
||||
libclang
|
||||
--------
|
||||
|
||||
@ -79,6 +79,8 @@ def to_yaml_type(typestr: str):
|
||||
return "Unsigned"
|
||||
elif typestr == "std::string":
|
||||
return "String"
|
||||
elif typestr == "tok::TokenKind":
|
||||
return "String"
|
||||
|
||||
match = re.match(r"std::vector<(.*)>$", typestr)
|
||||
if match:
|
||||
@ -152,7 +154,7 @@ class NestedField(object):
|
||||
|
||||
def __str__(self):
|
||||
if self.version:
|
||||
return "\n* ``%s`` :versionbadge:`clang-format %s`\n%s" % (
|
||||
return "\n* ``%s`` :versionbadge:`clang-format %s` %s" % (
|
||||
self.name,
|
||||
self.version,
|
||||
doxygen2rst(indent(self.comment, 2, indent_first_line=False)),
|
||||
@ -399,9 +401,32 @@ class OptionsReader:
|
||||
)
|
||||
)
|
||||
else:
|
||||
nested_struct.values.append(
|
||||
NestedField(field_type + " " + field_name, comment, version)
|
||||
)
|
||||
vec_match = re.match(r"std::vector<(.*)>$", field_type)
|
||||
if vec_match and vec_match.group(1) in nested_structs:
|
||||
inner_struct = nested_structs[vec_match.group(1)]
|
||||
display = "List of %ss %s" % (
|
||||
vec_match.group(1),
|
||||
field_name,
|
||||
)
|
||||
nested_struct.values.append(
|
||||
NestedField(display, comment, version)
|
||||
)
|
||||
nested_struct.values.extend(inner_struct.values)
|
||||
else:
|
||||
vec_match = re.match(r"std::vector<(.*)>$", field_type)
|
||||
if vec_match:
|
||||
display_type = "List of " + pluralize(
|
||||
to_yaml_type(vec_match.group(1))
|
||||
)
|
||||
else:
|
||||
display_type = field_type
|
||||
nested_struct.values.append(
|
||||
NestedField(
|
||||
display_type + " " + field_name,
|
||||
comment,
|
||||
version,
|
||||
)
|
||||
)
|
||||
version = None
|
||||
elif state == State.InEnum:
|
||||
if line.startswith("///"):
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
#define LLVM_CLANG_FORMAT_FORMAT_H
|
||||
|
||||
#include "clang/Basic/LangOptions.h"
|
||||
#include "clang/Basic/TokenKinds.h"
|
||||
#include "clang/Tooling/Core/Replacement.h"
|
||||
#include "clang/Tooling/Inclusions/IncludeStyle.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
@ -2450,9 +2451,84 @@ struct FormatStyle {
|
||||
BBO_RespectPrecedence
|
||||
};
|
||||
|
||||
/// A rule that specifies how to break a specific set of binary operators.
|
||||
/// \version 23
|
||||
struct BinaryOperationBreakRule {
|
||||
/// The list of operators this rule applies to, e.g. ``&&``, ``||``, ``|``.
|
||||
/// Alternative spellings (e.g. ``and`` for ``&&``) are accepted.
|
||||
std::vector<tok::TokenKind> Operators;
|
||||
/// The break style for these operators (defaults to ``OnePerLine``).
|
||||
BreakBinaryOperationsStyle Style;
|
||||
/// Minimum number of operands in a chain before the rule triggers.
|
||||
/// For example, ``a && b && c`` is a chain of length 3.
|
||||
/// ``0`` means always break (when the line is too long).
|
||||
unsigned MinChainLength;
|
||||
bool operator==(const BinaryOperationBreakRule &R) const {
|
||||
return Operators == R.Operators && Style == R.Style &&
|
||||
MinChainLength == R.MinChainLength;
|
||||
}
|
||||
bool operator!=(const BinaryOperationBreakRule &R) const {
|
||||
return !(*this == R);
|
||||
}
|
||||
};
|
||||
|
||||
/// Options for ``BreakBinaryOperations``.
|
||||
///
|
||||
/// If specified as a simple string (e.g. ``OnePerLine``), it behaves like
|
||||
/// the original enum and applies to all binary operators.
|
||||
///
|
||||
/// If specified as a struct, allows per-operator configuration:
|
||||
/// \code{.yaml}
|
||||
/// BreakBinaryOperations:
|
||||
/// Default: Never
|
||||
/// PerOperator:
|
||||
/// - Operators: ['&&', '||']
|
||||
/// Style: OnePerLine
|
||||
/// MinChainLength: 3
|
||||
/// \endcode
|
||||
/// \version 23
|
||||
struct BreakBinaryOperationsOptions {
|
||||
/// The default break style for operators not covered by ``PerOperator``.
|
||||
BreakBinaryOperationsStyle Default;
|
||||
/// Per-operator override rules.
|
||||
std::vector<BinaryOperationBreakRule> PerOperator;
|
||||
const BinaryOperationBreakRule *
|
||||
findRuleForOperator(tok::TokenKind Kind) const {
|
||||
for (const auto &Rule : PerOperator) {
|
||||
if (llvm::find(Rule.Operators, Kind) != Rule.Operators.end())
|
||||
return &Rule;
|
||||
// clang-format splits ">>" into two ">" tokens for template parsing.
|
||||
// Match ">" against ">>" rules so that per-operator rules for ">>"
|
||||
// (stream extraction / right shift) work correctly.
|
||||
if (Kind == tok::greater &&
|
||||
llvm::find(Rule.Operators, tok::greatergreater) !=
|
||||
Rule.Operators.end()) {
|
||||
return &Rule;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
BreakBinaryOperationsStyle getStyleForOperator(tok::TokenKind Kind) const {
|
||||
if (const auto *Rule = findRuleForOperator(Kind))
|
||||
return Rule->Style;
|
||||
return Default;
|
||||
}
|
||||
unsigned getMinChainLengthForOperator(tok::TokenKind Kind) const {
|
||||
if (const auto *Rule = findRuleForOperator(Kind))
|
||||
return Rule->MinChainLength;
|
||||
return 0;
|
||||
}
|
||||
bool operator==(const BreakBinaryOperationsOptions &R) const {
|
||||
return Default == R.Default && PerOperator == R.PerOperator;
|
||||
}
|
||||
bool operator!=(const BreakBinaryOperationsOptions &R) const {
|
||||
return !(*this == R);
|
||||
}
|
||||
};
|
||||
|
||||
/// The break binary operations style to use.
|
||||
/// \version 20
|
||||
BreakBinaryOperationsStyle BreakBinaryOperations;
|
||||
BreakBinaryOperationsOptions BreakBinaryOperations;
|
||||
|
||||
/// Different ways to break initializers.
|
||||
enum BreakConstructorInitializersStyle : int8_t {
|
||||
|
||||
@ -144,14 +144,48 @@ static bool startsNextOperand(const FormatToken &Current) {
|
||||
return isAlignableBinaryOperator(Previous) && !Current.isTrailingComment();
|
||||
}
|
||||
|
||||
// Returns the number of operands in the chain containing \c Op.
|
||||
// For example, `a && b && c` has 3 operands (and 2 operators).
|
||||
static unsigned getChainLength(const FormatToken &Op) {
|
||||
const FormatToken *Last = &Op;
|
||||
while (Last->NextOperator)
|
||||
Last = Last->NextOperator;
|
||||
return Last->OperatorIndex + 2;
|
||||
}
|
||||
|
||||
// Returns \c true if \c Current is a binary operation that must break.
|
||||
static bool mustBreakBinaryOperation(const FormatToken &Current,
|
||||
const FormatStyle &Style) {
|
||||
return Style.BreakBinaryOperations != FormatStyle::BBO_Never &&
|
||||
Current.CanBreakBefore &&
|
||||
(Style.BreakBeforeBinaryOperators == FormatStyle::BOS_None
|
||||
? startsNextOperand
|
||||
: isAlignableBinaryOperator)(Current);
|
||||
if (!Current.CanBreakBefore)
|
||||
return false;
|
||||
|
||||
// Determine the operator token: when breaking after the operator,
|
||||
// it is Current.Previous; when breaking before, it is Current itself.
|
||||
bool BreakBefore = Style.BreakBeforeBinaryOperators != FormatStyle::BOS_None;
|
||||
const FormatToken *OpToken = BreakBefore ? &Current : Current.Previous;
|
||||
|
||||
if (!OpToken)
|
||||
return false;
|
||||
|
||||
// Check that this is an alignable binary operator.
|
||||
if (BreakBefore) {
|
||||
if (!isAlignableBinaryOperator(Current))
|
||||
return false;
|
||||
} else if (!startsNextOperand(Current)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look up per-operator rule or fall back to Default.
|
||||
const auto OperatorBreakStyle =
|
||||
Style.BreakBinaryOperations.getStyleForOperator(OpToken->Tok.getKind());
|
||||
if (OperatorBreakStyle == FormatStyle::BBO_Never)
|
||||
return false;
|
||||
|
||||
// Check MinChainLength: if the chain is too short, don't force a break.
|
||||
const unsigned MinChain =
|
||||
Style.BreakBinaryOperations.getMinChainLengthForOperator(
|
||||
OpToken->Tok.getKind());
|
||||
return MinChain == 0 || getChainLength(*OpToken) >= MinChain;
|
||||
}
|
||||
|
||||
static bool opensProtoMessageField(const FormatToken &LessTok,
|
||||
|
||||
@ -31,6 +31,8 @@
|
||||
using clang::format::FormatStyle;
|
||||
|
||||
LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::RawStringFormat)
|
||||
LLVM_YAML_IS_SEQUENCE_VECTOR(FormatStyle::BinaryOperationBreakRule)
|
||||
LLVM_YAML_IS_SEQUENCE_VECTOR(clang::tok::TokenKind)
|
||||
|
||||
enum BracketAlignmentStyle : int8_t {
|
||||
BAS_Align,
|
||||
@ -275,6 +277,62 @@ struct ScalarEnumerationTraits<FormatStyle::BreakBinaryOperationsStyle> {
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct ScalarTraits<clang::tok::TokenKind> {
|
||||
static void output(const clang::tok::TokenKind &Value, void *,
|
||||
llvm::raw_ostream &Out) {
|
||||
if (const char *Spelling = clang::tok::getPunctuatorSpelling(Value))
|
||||
Out << Spelling;
|
||||
else
|
||||
Out << clang::tok::getTokenName(Value);
|
||||
}
|
||||
|
||||
static StringRef input(StringRef Scalar, void *,
|
||||
clang::tok::TokenKind &Value) {
|
||||
// Map operator spelling strings to tok::TokenKind.
|
||||
#define PUNCTUATOR(Name, Spelling) \
|
||||
if (Scalar == Spelling) { \
|
||||
Value = clang::tok::Name; \
|
||||
return {}; \
|
||||
}
|
||||
#include "clang/Basic/TokenKinds.def"
|
||||
return "unknown operator";
|
||||
}
|
||||
|
||||
static QuotingType mustQuote(StringRef) { return QuotingType::None; }
|
||||
};
|
||||
|
||||
template <> struct MappingTraits<FormatStyle::BinaryOperationBreakRule> {
|
||||
static void mapping(IO &IO, FormatStyle::BinaryOperationBreakRule &Value) {
|
||||
IO.mapOptional("Operators", Value.Operators);
|
||||
// Default to OnePerLine since a per-operator rule with Never is a no-op.
|
||||
if (!IO.outputting())
|
||||
Value.Style = FormatStyle::BBO_OnePerLine;
|
||||
IO.mapOptional("Style", Value.Style);
|
||||
IO.mapOptional("MinChainLength", Value.MinChainLength);
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct MappingTraits<FormatStyle::BreakBinaryOperationsOptions> {
|
||||
static void enumInput(IO &IO,
|
||||
FormatStyle::BreakBinaryOperationsOptions &Value) {
|
||||
IO.enumCase(Value, "Never",
|
||||
FormatStyle::BreakBinaryOperationsOptions(
|
||||
{FormatStyle::BBO_Never, {}}));
|
||||
IO.enumCase(Value, "OnePerLine",
|
||||
FormatStyle::BreakBinaryOperationsOptions(
|
||||
{FormatStyle::BBO_OnePerLine, {}}));
|
||||
IO.enumCase(Value, "RespectPrecedence",
|
||||
FormatStyle::BreakBinaryOperationsOptions(
|
||||
{FormatStyle::BBO_RespectPrecedence, {}}));
|
||||
}
|
||||
|
||||
static void mapping(IO &IO,
|
||||
FormatStyle::BreakBinaryOperationsOptions &Value) {
|
||||
IO.mapOptional("Default", Value.Default);
|
||||
IO.mapOptional("PerOperator", Value.PerOperator);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ScalarEnumerationTraits<FormatStyle::BreakConstructorInitializersStyle> {
|
||||
static void
|
||||
@ -1725,7 +1783,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
|
||||
LLVMStyle.BreakBeforeInlineASMColon = FormatStyle::BBIAS_OnlyMultiline;
|
||||
LLVMStyle.BreakBeforeTemplateCloser = false;
|
||||
LLVMStyle.BreakBeforeTernaryOperators = true;
|
||||
LLVMStyle.BreakBinaryOperations = FormatStyle::BBO_Never;
|
||||
LLVMStyle.BreakBinaryOperations = {FormatStyle::BBO_Never, {}};
|
||||
LLVMStyle.BreakConstructorInitializers = FormatStyle::BCIS_BeforeColon;
|
||||
LLVMStyle.BreakFunctionDefinitionParameters = false;
|
||||
LLVMStyle.BreakInheritanceList = FormatStyle::BILS_BeforeColon;
|
||||
|
||||
@ -3276,14 +3276,19 @@ public:
|
||||
parse(Precedence + 1);
|
||||
|
||||
int CurrentPrecedence = getCurrentPrecedence();
|
||||
if (Style.BreakBinaryOperations == FormatStyle::BBO_OnePerLine &&
|
||||
CurrentPrecedence > prec::Conditional &&
|
||||
if (CurrentPrecedence > prec::Conditional &&
|
||||
CurrentPrecedence < prec::PointerToMember) {
|
||||
// When BreakBinaryOperations is set to BreakAll,
|
||||
// all operations will be on the same line or on individual lines.
|
||||
// Override precedence to avoid adding fake parenthesis which could
|
||||
// group operations of a different precedence level on the same line
|
||||
CurrentPrecedence = prec::Additive;
|
||||
// When BreakBinaryOperations is globally OnePerLine (no per-operator
|
||||
// rules), flatten all precedence levels so that every operator is
|
||||
// treated equally for line-breaking purposes. With per-operator rules
|
||||
// we must preserve natural precedence so that higher-precedence
|
||||
// sub-expressions (e.g. `x << 8` inside a `|` chain) stay grouped;
|
||||
// mustBreakBinaryOperation() handles the forced breaks instead.
|
||||
if (Style.BreakBinaryOperations.PerOperator.empty() &&
|
||||
Style.BreakBinaryOperations.Default ==
|
||||
FormatStyle::BBO_OnePerLine) {
|
||||
CurrentPrecedence = prec::Additive;
|
||||
}
|
||||
}
|
||||
|
||||
if (Precedence == CurrentPrecedence && Current &&
|
||||
|
||||
@ -453,13 +453,58 @@ TEST(ConfigParseTest, ParsesConfiguration) {
|
||||
CHECK_PARSE("BreakBeforeBinaryOperators: true", BreakBeforeBinaryOperators,
|
||||
FormatStyle::BOS_All);
|
||||
|
||||
Style.BreakBinaryOperations = FormatStyle::BBO_Never;
|
||||
Style.BreakBinaryOperations.Default = FormatStyle::BBO_Never;
|
||||
CHECK_PARSE("BreakBinaryOperations: OnePerLine", BreakBinaryOperations,
|
||||
FormatStyle::BBO_OnePerLine);
|
||||
FormatStyle::BreakBinaryOperationsOptions(
|
||||
{FormatStyle::BBO_OnePerLine, {}}));
|
||||
CHECK_PARSE("BreakBinaryOperations: RespectPrecedence", BreakBinaryOperations,
|
||||
FormatStyle::BBO_RespectPrecedence);
|
||||
CHECK_PARSE("BreakBinaryOperations: Never", BreakBinaryOperations,
|
||||
FormatStyle::BBO_Never);
|
||||
FormatStyle::BreakBinaryOperationsOptions(
|
||||
{FormatStyle::BBO_RespectPrecedence, {}}));
|
||||
CHECK_PARSE(
|
||||
"BreakBinaryOperations: Never", BreakBinaryOperations,
|
||||
FormatStyle::BreakBinaryOperationsOptions({FormatStyle::BBO_Never, {}}));
|
||||
|
||||
// Structured form
|
||||
Style.BreakBinaryOperations.Default = FormatStyle::BBO_Never;
|
||||
CHECK_PARSE("BreakBinaryOperations:\n"
|
||||
" Default: OnePerLine",
|
||||
BreakBinaryOperations,
|
||||
FormatStyle::BreakBinaryOperationsOptions(
|
||||
{FormatStyle::BBO_OnePerLine, {}}));
|
||||
|
||||
Style.BreakBinaryOperations.Default = FormatStyle::BBO_Never;
|
||||
EXPECT_EQ(0, parseConfiguration("BreakBinaryOperations:\n"
|
||||
" Default: Never\n"
|
||||
" PerOperator:\n"
|
||||
" - Operators: ['&&', '||']\n"
|
||||
" Style: OnePerLine\n"
|
||||
" MinChainLength: 3",
|
||||
&Style)
|
||||
.value());
|
||||
EXPECT_EQ(Style.BreakBinaryOperations.Default, FormatStyle::BBO_Never);
|
||||
ASSERT_EQ(Style.BreakBinaryOperations.PerOperator.size(), 1u);
|
||||
std::vector<tok::TokenKind> ExpectedOps = {tok::ampamp, tok::pipepipe};
|
||||
EXPECT_EQ(Style.BreakBinaryOperations.PerOperator[0].Operators, ExpectedOps);
|
||||
EXPECT_EQ(Style.BreakBinaryOperations.PerOperator[0].Style,
|
||||
FormatStyle::BBO_OnePerLine);
|
||||
EXPECT_EQ(Style.BreakBinaryOperations.PerOperator[0].MinChainLength, 3u);
|
||||
|
||||
// Parse ">" and ">>" which have edge cases: clang-format splits ">>" into
|
||||
// two ">" tokens, so ">>" maps to tok::greatergreater while ">" maps to
|
||||
// tok::greater.
|
||||
Style.BreakBinaryOperations.Default = FormatStyle::BBO_Never;
|
||||
EXPECT_EQ(0, parseConfiguration("BreakBinaryOperations:\n"
|
||||
" Default: Never\n"
|
||||
" PerOperator:\n"
|
||||
" - Operators: ['>', '>>']\n"
|
||||
" Style: OnePerLine",
|
||||
&Style)
|
||||
.value());
|
||||
ASSERT_EQ(Style.BreakBinaryOperations.PerOperator.size(), 1u);
|
||||
std::vector<tok::TokenKind> ExpectedShiftOps = {tok::greater,
|
||||
tok::greatergreater};
|
||||
EXPECT_EQ(Style.BreakBinaryOperations.PerOperator[0].Operators,
|
||||
ExpectedShiftOps);
|
||||
|
||||
Style.BreakConstructorInitializers = FormatStyle::BCIS_BeforeColon;
|
||||
CHECK_PARSE("BreakConstructorInitializers: BeforeComma",
|
||||
|
||||
@ -28403,7 +28403,9 @@ TEST_F(FormatTest, SpaceBetweenKeywordAndLiteral) {
|
||||
|
||||
TEST_F(FormatTest, BreakBinaryOperations) {
|
||||
auto Style = getLLVMStyleWithColumns(60);
|
||||
EXPECT_EQ(Style.BreakBinaryOperations, FormatStyle::BBO_Never);
|
||||
FormatStyle::BreakBinaryOperationsOptions ExpectedDefault = {
|
||||
FormatStyle::BBO_Never, {}};
|
||||
EXPECT_EQ(Style.BreakBinaryOperations, ExpectedDefault);
|
||||
|
||||
// Logical operations
|
||||
verifyFormat("if (condition1 && condition2) {\n"
|
||||
@ -28442,7 +28444,7 @@ TEST_F(FormatTest, BreakBinaryOperations) {
|
||||
" longOperand_3_;",
|
||||
Style);
|
||||
|
||||
Style.BreakBinaryOperations = FormatStyle::BBO_OnePerLine;
|
||||
Style.BreakBinaryOperations.Default = FormatStyle::BBO_OnePerLine;
|
||||
|
||||
// Logical operations
|
||||
verifyFormat("if (condition1 && condition2) {\n"
|
||||
@ -28527,7 +28529,7 @@ TEST_F(FormatTest, BreakBinaryOperations) {
|
||||
" longOperand_3_;",
|
||||
Style);
|
||||
|
||||
Style.BreakBinaryOperations = FormatStyle::BBO_RespectPrecedence;
|
||||
Style.BreakBinaryOperations.Default = FormatStyle::BBO_RespectPrecedence;
|
||||
verifyFormat("result = op1 + op2 * op3 - op4;", Style);
|
||||
|
||||
verifyFormat("result = operand1 +\n"
|
||||
@ -28559,7 +28561,7 @@ TEST_F(FormatTest, BreakBinaryOperations) {
|
||||
" longOperand_3_;",
|
||||
Style);
|
||||
|
||||
Style.BreakBinaryOperations = FormatStyle::BBO_OnePerLine;
|
||||
Style.BreakBinaryOperations.Default = FormatStyle::BBO_OnePerLine;
|
||||
Style.BreakBeforeBinaryOperators = FormatStyle::BOS_NonAssignment;
|
||||
|
||||
// Logical operations
|
||||
@ -28640,7 +28642,7 @@ TEST_F(FormatTest, BreakBinaryOperations) {
|
||||
" >> longOperand_3_;",
|
||||
Style);
|
||||
|
||||
Style.BreakBinaryOperations = FormatStyle::BBO_RespectPrecedence;
|
||||
Style.BreakBinaryOperations.Default = FormatStyle::BBO_RespectPrecedence;
|
||||
verifyFormat("result = op1 + op2 * op3 - op4;", Style);
|
||||
|
||||
verifyFormat("result = operand1\n"
|
||||
@ -28673,6 +28675,112 @@ TEST_F(FormatTest, BreakBinaryOperations) {
|
||||
Style);
|
||||
}
|
||||
|
||||
TEST_F(FormatTest, BreakBinaryOperationsPerOperator) {
|
||||
auto Style = getLLVMStyleWithColumns(60);
|
||||
|
||||
// Per-operator override: && and || are OnePerLine, rest is Never (default).
|
||||
FormatStyle::BinaryOperationBreakRule LogicalRule;
|
||||
LogicalRule.Operators = {tok::ampamp, tok::pipepipe};
|
||||
LogicalRule.Style = FormatStyle::BBO_OnePerLine;
|
||||
LogicalRule.MinChainLength = 0;
|
||||
|
||||
Style.BreakBinaryOperations.Default = FormatStyle::BBO_Never;
|
||||
Style.BreakBinaryOperations.PerOperator = {LogicalRule};
|
||||
|
||||
// Logical operators break one-per-line when line is too long.
|
||||
verifyFormat("bool valid = isConnectionReady() &&\n"
|
||||
" isSessionNotExpired() &&\n"
|
||||
" hasRequiredPermission();",
|
||||
Style);
|
||||
|
||||
// Arithmetic operators stay with default (Never).
|
||||
verifyFormat("int total = unitBasePrice + shippingCostPerItem +\n"
|
||||
" applicableTaxAmount + handlingFeePerUnit;",
|
||||
Style);
|
||||
|
||||
// Short logical chain that fits stays on one line.
|
||||
verifyFormat("bool x = a && b && c;", Style);
|
||||
|
||||
// Multiple PerOperator groups: && and || plus | operators.
|
||||
FormatStyle::BinaryOperationBreakRule BitwiseOrRule;
|
||||
BitwiseOrRule.Operators = {tok::pipe};
|
||||
BitwiseOrRule.Style = FormatStyle::BBO_OnePerLine;
|
||||
BitwiseOrRule.MinChainLength = 0;
|
||||
|
||||
Style.BreakBinaryOperations.PerOperator = {LogicalRule, BitwiseOrRule};
|
||||
|
||||
// | operators should break one-per-line.
|
||||
verifyFormat("int flags = OPTION_VERBOSE_OUTPUT |\n"
|
||||
" OPTION_RECURSIVE_SCAN |\n"
|
||||
" OPTION_FORCE_OVERWRITE;",
|
||||
Style);
|
||||
|
||||
// && still works in multi-group configuration.
|
||||
verifyFormat("bool valid = isConnectionReady() &&\n"
|
||||
" isSessionNotExpired() &&\n"
|
||||
" hasRequiredPermission();",
|
||||
Style);
|
||||
|
||||
// + stays with default (Never) even with multi-group.
|
||||
verifyFormat("int total = unitBasePrice + shippingCostPerItem +\n"
|
||||
" applicableTaxAmount + handlingFeePerUnit;",
|
||||
Style);
|
||||
|
||||
// | OnePerLine with << sub-expressions: << stays grouped.
|
||||
Style.BreakBinaryOperations.PerOperator = {BitwiseOrRule};
|
||||
verifyFormat("std::uint32_t a = byte_buffer[0] |\n"
|
||||
" byte_buffer[1] << 8 |\n"
|
||||
" byte_buffer[2] << 16 |\n"
|
||||
" byte_buffer[3] << 24;",
|
||||
Style);
|
||||
|
||||
// >> (stream extraction) OnePerLine: clang-format splits >> into two >
|
||||
// tokens, but per-operator rules for >> must still work.
|
||||
FormatStyle::BinaryOperationBreakRule ShiftRightRule;
|
||||
ShiftRightRule.Operators = {tok::greatergreater};
|
||||
ShiftRightRule.Style = FormatStyle::BBO_OnePerLine;
|
||||
ShiftRightRule.MinChainLength = 0;
|
||||
|
||||
Style.BreakBinaryOperations.PerOperator = {ShiftRightRule};
|
||||
verifyFormat("in >>\n"
|
||||
" packet_id >>\n"
|
||||
" packet_version >>\n"
|
||||
" packet_number >>\n"
|
||||
" packet_scale;",
|
||||
Style);
|
||||
}
|
||||
|
||||
TEST_F(FormatTest, BreakBinaryOperationsMinChainLength) {
|
||||
auto Style = getLLVMStyleWithColumns(60);
|
||||
|
||||
// MinChainLength = 3: chains shorter than 3 don't force breaks.
|
||||
FormatStyle::BinaryOperationBreakRule LogicalRule;
|
||||
LogicalRule.Operators = {tok::ampamp, tok::pipepipe};
|
||||
LogicalRule.Style = FormatStyle::BBO_OnePerLine;
|
||||
LogicalRule.MinChainLength = 3;
|
||||
|
||||
Style.BreakBinaryOperations.Default = FormatStyle::BBO_Never;
|
||||
Style.BreakBinaryOperations.PerOperator = {LogicalRule};
|
||||
|
||||
// Chain of 2 — below MinChainLength, no forced one-per-line.
|
||||
verifyFormat("bool ok =\n"
|
||||
" isConnectionReady(cfg) && isSessionNotExpired(cfg);",
|
||||
Style);
|
||||
|
||||
// Chain of 3 — meets MinChainLength, one-per-line.
|
||||
verifyFormat("bool ok = isConnectionReady(cfg) &&\n"
|
||||
" isSessionNotExpired(cfg) &&\n"
|
||||
" hasRequiredPermission(cfg);",
|
||||
Style);
|
||||
|
||||
// Chain of 4 — above MinChainLength, one-per-line.
|
||||
verifyFormat("bool ok = isConnectionReady(cfg) &&\n"
|
||||
" isSessionNotExpired(cfg) &&\n"
|
||||
" hasRequiredPermission(cfg) &&\n"
|
||||
" isFeatureEnabled(cfg);",
|
||||
Style);
|
||||
}
|
||||
|
||||
TEST_F(FormatTest, RemoveEmptyLinesInUnwrappedLines) {
|
||||
auto Style = getLLVMStyle();
|
||||
Style.RemoveEmptyLinesInUnwrappedLines = true;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user