mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 12:55:05 +00:00
Formatter: Implicit concatenation in compare expressions
## Summary This PR implements the logic for breaking implicit concatenated strings before compare expressions by building on top of #7145 The main change is a new `BinaryLike` enum that has the `BinaryExpression` and `CompareExpression` variants. Supporting both variants requires some downstream changes but doesn't introduce any new concepts. ## Test Plan I added a few more tests. The compatibility improvements are minor but we now perfectly match black on twine 🥳 **PR** | project | similarity index | total files | changed files | |--------------|------------------:|------------------:|------------------:| | cpython | 0.76083 | 1789 | 1632 | | django | 0.99966 | 2760 | 58 | | transformers | 0.99928 | 2587 | 454 | | **twine** | 1.00000 | 33 | 0 | <-- improved | typeshed | 0.99978 | 3496 | 2173 | | **warehouse** | 0.99824 | 648 | 22 | <-- improved | zulip | 0.99948 | 1437 | 28 | **Base** | project | similarity index | total files | changed files | |--------------|------------------:|------------------:|------------------:| | cpython | 0.76083 | 1789 | 1633 | | django | 0.99966 | 2760 | 58 | | transformers | 0.99928 | 2587 | 454 | | twine | 0.99982 | 33 | 1 | | typeshed | 0.99978 | 3496 | 2173 | | warehouse | 0.99823 | 648 | 23 | | zulip | 0.99948 | 1437 | 28 |
This commit is contained in:
parent
1d5c4b0a14
commit
c260762900
6 changed files with 444 additions and 160 deletions
|
@ -1,62 +1,21 @@
|
|||
use ruff_formatter::{write, FormatOwnedWithRule, FormatRefWithRule};
|
||||
use ruff_formatter::{FormatOwnedWithRule, FormatRefWithRule};
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::{CmpOp, ExprCompare};
|
||||
use ruff_python_ast::{CmpOp, Expr, ExprCompare};
|
||||
|
||||
use crate::comments::{leading_comments, SourceComment};
|
||||
use crate::expression::parentheses::{
|
||||
in_parentheses_only_group, in_parentheses_only_soft_line_break_or_space, NeedsParentheses,
|
||||
OptionalParentheses,
|
||||
};
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::binary_like::BinaryLike;
|
||||
use crate::expression::expr_constant::is_multiline_string;
|
||||
use crate::expression::has_parentheses;
|
||||
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatExprCompare;
|
||||
|
||||
impl FormatNodeRule<ExprCompare> for FormatExprCompare {
|
||||
#[inline]
|
||||
fn fmt_fields(&self, item: &ExprCompare, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let ExprCompare {
|
||||
range: _,
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
} = item;
|
||||
|
||||
let comments = f.context().comments().clone();
|
||||
|
||||
let inner = format_with(|f| {
|
||||
write!(f, [in_parentheses_only_group(&left.format())])?;
|
||||
|
||||
assert_eq!(comparators.len(), ops.len());
|
||||
|
||||
for (operator, comparator) in ops.iter().zip(comparators) {
|
||||
let leading_comparator_comments = comments.leading(comparator);
|
||||
if leading_comparator_comments.is_empty() {
|
||||
write!(f, [in_parentheses_only_soft_line_break_or_space()])?;
|
||||
} else {
|
||||
// Format the expressions leading comments **before** the operator
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
hard_line_break(),
|
||||
leading_comments(leading_comparator_comments)
|
||||
]
|
||||
)?;
|
||||
}
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
operator.format(),
|
||||
space(),
|
||||
in_parentheses_only_group(&comparator.format())
|
||||
]
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
in_parentheses_only_group(&inner).fmt(f)
|
||||
BinaryLike::CompareExpression(item).fmt(f)
|
||||
}
|
||||
|
||||
fn fmt_dangling_comments(
|
||||
|
@ -73,9 +32,24 @@ impl NeedsParentheses for ExprCompare {
|
|||
fn needs_parentheses(
|
||||
&self,
|
||||
_parent: AnyNodeRef,
|
||||
_context: &PyFormatContext,
|
||||
context: &PyFormatContext,
|
||||
) -> OptionalParentheses {
|
||||
OptionalParentheses::Multiline
|
||||
if let Expr::Constant(constant) = self.left.as_ref() {
|
||||
// Multiline strings are guaranteed to never fit, avoid adding unnecessary parentheses
|
||||
if !constant.value.is_implicit_concatenated()
|
||||
&& is_multiline_string(constant, context.source())
|
||||
&& !context.comments().has(self.left.as_ref())
|
||||
&& self.comparators.first().is_some_and(|right| {
|
||||
has_parentheses(right, context).is_some() && !context.comments().has(right)
|
||||
})
|
||||
{
|
||||
OptionalParentheses::Never
|
||||
} else {
|
||||
OptionalParentheses::Multiline
|
||||
}
|
||||
} else {
|
||||
OptionalParentheses::Multiline
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue