mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-14 06:15:13 +00:00
Format Compare Op
<!-- Thank you for contributing to Ruff! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? - Does this pull request include references to any relevant issues? --> ## Summary This PR adds basic formatting for compare operations. The implementation currently breaks diffeently when nesting binary like expressions. I haven't yet figured out what Black's logic is in that case but I think that this by itself is already an improvement worth merging. <!-- What's the purpose of the change? What does it do, and why? --> ## Test Plan I added a few new tests <!-- How was it tested? -->
This commit is contained in:
parent
2142bf6141
commit
3e12bdff45
24 changed files with 735 additions and 244 deletions
|
@ -1,23 +1,96 @@
|
|||
use crate::comments::{leading_comments, Comments};
|
||||
use crate::expression::binary_like::{BinaryLayout, FormatBinaryLike};
|
||||
use crate::expression::parentheses::{
|
||||
default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize,
|
||||
};
|
||||
use crate::{not_yet_implemented_custom_text, FormatNodeRule, PyFormatter};
|
||||
|
||||
use crate::comments::Comments;
|
||||
use ruff_formatter::{write, Buffer, FormatResult};
|
||||
use rustpython_parser::ast::ExprCompare;
|
||||
use crate::prelude::*;
|
||||
use crate::FormatNodeRule;
|
||||
use ruff_formatter::{
|
||||
write, FormatError, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions,
|
||||
};
|
||||
use ruff_python_ast::prelude::Expr;
|
||||
use rustpython_parser::ast::{CmpOp, ExprCompare};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatExprCompare;
|
||||
pub struct FormatExprCompare {
|
||||
parentheses: Option<Parentheses>,
|
||||
}
|
||||
|
||||
impl FormatRuleWithOptions<ExprCompare, PyFormatContext<'_>> for FormatExprCompare {
|
||||
type Options = Option<Parentheses>;
|
||||
|
||||
fn with_options(mut self, options: Self::Options) -> Self {
|
||||
self.parentheses = options;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatNodeRule<ExprCompare> for FormatExprCompare {
|
||||
fn fmt_fields(&self, _item: &ExprCompare, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
write!(
|
||||
f,
|
||||
[not_yet_implemented_custom_text(
|
||||
"NOT_IMPLEMENTED_left < NOT_IMPLEMENTED_right"
|
||||
)]
|
||||
)
|
||||
fn fmt_fields(&self, item: &ExprCompare, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
item.fmt_binary(self.parentheses, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ast> FormatBinaryLike<'ast> for ExprCompare {
|
||||
type FormatOperator = FormatOwnedWithRule<CmpOp, FormatCmpOp, PyFormatContext<'ast>>;
|
||||
|
||||
fn binary_layout(&self) -> BinaryLayout {
|
||||
if self.ops.len() == 1 {
|
||||
match self.comparators.as_slice() {
|
||||
[right] => BinaryLayout::from_left_right(&self.left, right),
|
||||
[..] => BinaryLayout::Default,
|
||||
}
|
||||
} else {
|
||||
BinaryLayout::Default
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_default(&self, f: &mut PyFormatter<'ast, '_>) -> FormatResult<()> {
|
||||
let ExprCompare {
|
||||
range: _,
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
} = self;
|
||||
|
||||
let comments = f.context().comments().clone();
|
||||
|
||||
write!(f, [group(&left.format())])?;
|
||||
|
||||
assert_eq!(comparators.len(), ops.len());
|
||||
|
||||
for (operator, comparator) in ops.iter().zip(comparators) {
|
||||
let leading_comparator_comments = comments.leading_comments(comparator);
|
||||
if leading_comparator_comments.is_empty() {
|
||||
write!(f, [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(), group(&comparator.format())])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn left(&self) -> FormatResult<&Expr> {
|
||||
Ok(self.left.as_ref())
|
||||
}
|
||||
|
||||
fn right(&self) -> FormatResult<&Expr> {
|
||||
self.comparators.last().ok_or(FormatError::SyntaxError)
|
||||
}
|
||||
|
||||
fn operator(&self) -> Self::FormatOperator {
|
||||
let op = *self.ops.first().unwrap();
|
||||
op.into_format()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,6 +101,61 @@ impl NeedsParentheses for ExprCompare {
|
|||
source: &str,
|
||||
comments: &Comments,
|
||||
) -> Parentheses {
|
||||
default_expression_needs_parentheses(self.into(), parenthesize, source, comments)
|
||||
match default_expression_needs_parentheses(self.into(), parenthesize, source, comments) {
|
||||
parentheses @ Parentheses::Optional => match self.binary_layout() {
|
||||
BinaryLayout::Default => parentheses,
|
||||
|
||||
BinaryLayout::ExpandRight
|
||||
| BinaryLayout::ExpandLeft
|
||||
| BinaryLayout::ExpandRightThenLeft
|
||||
if self
|
||||
.comparators
|
||||
.last()
|
||||
.map_or(false, |right| comments.has_leading_comments(right)) =>
|
||||
{
|
||||
parentheses
|
||||
}
|
||||
_ => Parentheses::Custom,
|
||||
},
|
||||
parentheses => parentheses,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct FormatCmpOp;
|
||||
|
||||
impl<'ast> AsFormat<PyFormatContext<'ast>> for CmpOp {
|
||||
type Format<'a> = FormatRefWithRule<'a, CmpOp, FormatCmpOp, PyFormatContext<'ast>>;
|
||||
|
||||
fn format(&self) -> Self::Format<'_> {
|
||||
FormatRefWithRule::new(self, FormatCmpOp)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ast> IntoFormat<PyFormatContext<'ast>> for CmpOp {
|
||||
type Format = FormatOwnedWithRule<CmpOp, FormatCmpOp, PyFormatContext<'ast>>;
|
||||
|
||||
fn into_format(self) -> Self::Format {
|
||||
FormatOwnedWithRule::new(self, FormatCmpOp)
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatRule<CmpOp, PyFormatContext<'_>> for FormatCmpOp {
|
||||
fn fmt(&self, item: &CmpOp, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
|
||||
let operator = match item {
|
||||
CmpOp::Eq => "==",
|
||||
CmpOp::NotEq => "!=",
|
||||
CmpOp::Lt => "<",
|
||||
CmpOp::LtE => "<=",
|
||||
CmpOp::Gt => ">",
|
||||
CmpOp::GtE => ">=",
|
||||
CmpOp::Is => "is",
|
||||
CmpOp::IsNot => "is not",
|
||||
CmpOp::In => "in",
|
||||
CmpOp::NotIn => "not in",
|
||||
};
|
||||
|
||||
text(operator).fmt(f)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ impl FormatRule<Expr, PyFormatContext<'_>> for FormatExpr {
|
|||
Expr::Await(expr) => expr.format().fmt(f),
|
||||
Expr::Yield(expr) => expr.format().fmt(f),
|
||||
Expr::YieldFrom(expr) => expr.format().fmt(f),
|
||||
Expr::Compare(expr) => expr.format().fmt(f),
|
||||
Expr::Compare(expr) => expr.format().with_options(Some(parentheses)).fmt(f),
|
||||
Expr::Call(expr) => expr.format().fmt(f),
|
||||
Expr::FormattedValue(expr) => expr.format().fmt(f),
|
||||
Expr::JoinedStr(expr) => expr.format().fmt(f),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue