Skip to content

Commit ceb3a94

Browse files
committed
Refactor needs parentheses
1 parent 33a9177 commit ceb3a94

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+711
-725
lines changed

crates/ruff_python_formatter/src/builders.rs

+46-21
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
use ruff_text_size::{TextRange, TextSize};
2+
use rustpython_parser::ast::Ranged;
3+
4+
use ruff_formatter::{format_args, write, Argument, Arguments};
5+
16
use crate::context::NodeLevel;
27
use crate::prelude::*;
3-
use crate::trivia::{first_non_trivia_token, lines_after, skip_trailing_trivia, Token, TokenKind};
4-
use ruff_formatter::{format_args, write, Argument, Arguments};
5-
use ruff_text_size::TextSize;
6-
use rustpython_parser::ast::Ranged;
8+
use crate::trivia::{lines_after, skip_trailing_trivia, SimpleTokenizer, Token, TokenKind};
9+
use crate::MagicTrailingComma;
710

811
/// Adds parentheses and indents `content` if it doesn't fit on a line.
912
pub(crate) fn parenthesize_if_expands<'ast, T>(content: &T) -> ParenthesizeIfExpands<'_, 'ast>
@@ -53,16 +56,22 @@ pub(crate) trait PyFormatterExtensions<'ast, 'buf> {
5356
/// A builder that separates each element by a `,` and a [`soft_line_break_or_space`].
5457
/// It emits a trailing `,` that is only shown if the enclosing group expands. It forces the enclosing
5558
/// group to expand if the last item has a trailing `comma` and the magical comma option is enabled.
56-
fn join_comma_separated<'fmt>(&'fmt mut self) -> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf>;
59+
fn join_comma_separated<'fmt>(
60+
&'fmt mut self,
61+
sequence_end: TextSize,
62+
) -> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf>;
5763
}
5864

5965
impl<'buf, 'ast> PyFormatterExtensions<'ast, 'buf> for PyFormatter<'ast, 'buf> {
6066
fn join_nodes<'fmt>(&'fmt mut self, level: NodeLevel) -> JoinNodesBuilder<'fmt, 'ast, 'buf> {
6167
JoinNodesBuilder::new(self, level)
6268
}
6369

64-
fn join_comma_separated<'fmt>(&'fmt mut self) -> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
65-
JoinCommaSeparatedBuilder::new(self)
70+
fn join_comma_separated<'fmt>(
71+
&'fmt mut self,
72+
sequence_end: TextSize,
73+
) -> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
74+
JoinCommaSeparatedBuilder::new(self, sequence_end)
6675
}
6776
}
6877

@@ -194,18 +203,20 @@ pub(crate) struct JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
194203
result: FormatResult<()>,
195204
fmt: &'fmt mut PyFormatter<'ast, 'buf>,
196205
end_of_last_entry: Option<TextSize>,
206+
sequence_end: TextSize,
197207
/// We need to track whether we have more than one entry since a sole entry doesn't get a
198208
/// magic trailing comma even when expanded
199209
len: usize,
200210
}
201211

202212
impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
203-
fn new(f: &'fmt mut PyFormatter<'ast, 'buf>) -> Self {
213+
fn new(f: &'fmt mut PyFormatter<'ast, 'buf>, sequence_end: TextSize) -> Self {
204214
Self {
205215
fmt: f,
206216
result: Ok(()),
207217
end_of_last_entry: None,
208218
len: 0,
219+
sequence_end,
209220
}
210221
}
211222

@@ -236,7 +247,7 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
236247
where
237248
T: Ranged,
238249
F: Format<PyFormatContext<'ast>>,
239-
I: Iterator<Item = (T, F)>,
250+
I: IntoIterator<Item = (T, F)>,
240251
{
241252
for (node, content) in entries {
242253
self.entry(&node, &content);
@@ -248,7 +259,7 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
248259
pub(crate) fn nodes<'a, T, I>(&mut self, entries: I) -> &mut Self
249260
where
250261
T: Ranged + AsFormat<PyFormatContext<'ast>> + 'a,
251-
I: Iterator<Item = &'a T>,
262+
I: IntoIterator<Item = &'a T>,
252263
{
253264
for node in entries {
254265
self.entry(node, &node.format());
@@ -260,14 +271,26 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
260271
pub(crate) fn finish(&mut self) -> FormatResult<()> {
261272
self.result.and_then(|_| {
262273
if let Some(last_end) = self.end_of_last_entry.take() {
263-
let magic_trailing_comma = self.fmt.options().magic_trailing_comma().is_respect()
264-
&& matches!(
265-
first_non_trivia_token(last_end, self.fmt.context().source()),
266-
Some(Token {
267-
kind: TokenKind::Comma,
268-
..
269-
})
270-
);
274+
let magic_trailing_comma = match self.fmt.options().magic_trailing_comma() {
275+
MagicTrailingComma::Respect => {
276+
let first_token = SimpleTokenizer::new(
277+
self.fmt.context().source(),
278+
TextRange::new(last_end, self.sequence_end),
279+
)
280+
.skip_trivia()
281+
// Skip over any closing parentheses belonging to the expression
282+
.find(|token| token.kind() != TokenKind::RParen);
283+
284+
matches!(
285+
first_token,
286+
Some(Token {
287+
kind: TokenKind::Comma,
288+
..
289+
})
290+
)
291+
}
292+
MagicTrailingComma::Ignore => false,
293+
};
271294

272295
// If there is a single entry, only keep the magic trailing comma, don't add it if
273296
// it wasn't there. If there is more than one entry, always add it.
@@ -287,13 +310,15 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
287310

288311
#[cfg(test)]
289312
mod tests {
313+
use rustpython_parser::ast::ModModule;
314+
use rustpython_parser::Parse;
315+
316+
use ruff_formatter::format;
317+
290318
use crate::comments::Comments;
291319
use crate::context::{NodeLevel, PyFormatContext};
292320
use crate::prelude::*;
293321
use crate::PyFormatOptions;
294-
use ruff_formatter::format;
295-
use rustpython_parser::ast::ModModule;
296-
use rustpython_parser::Parse;
297322

298323
fn format_ranged(level: NodeLevel) -> String {
299324
let source = r#"

crates/ruff_python_formatter/src/expression/expr_attribute.rs

+21-38
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
use crate::comments::{leading_comments, trailing_comments, Comments};
2-
use crate::expression::parentheses::{
3-
default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize,
4-
};
1+
use rustpython_parser::ast::{Constant, Expr, ExprAttribute, ExprConstant};
2+
3+
use ruff_formatter::write;
4+
use ruff_python_ast::node::AnyNodeRef;
5+
6+
use crate::comments::{leading_comments, trailing_comments};
7+
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses};
58
use crate::prelude::*;
69
use crate::FormatNodeRule;
7-
use ruff_formatter::write;
8-
use rustpython_parser::ast::{Constant, Expr, ExprAttribute, ExprConstant};
910

1011
#[derive(Default)]
1112
pub struct FormatExprAttribute;
@@ -35,7 +36,7 @@ impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {
3536
dangling_comments.split_at(leading_attribute_comments_start);
3637

3738
if needs_parentheses {
38-
value.format().with_options(Parenthesize::Always).fmt(f)?;
39+
value.format().with_options(Parentheses::Always).fmt(f)?;
3940
} else if let Expr::Attribute(expr_attribute) = value.as_ref() {
4041
// We're in a attribute chain (`a.b.c`). The outermost node adds parentheses if
4142
// required, the inner ones don't need them so we skip the `Expr` formatting that
@@ -71,41 +72,23 @@ impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {
7172
}
7273
}
7374

74-
/// Checks if there are any own line comments in an attribute chain (a.b.c). This method is
75-
/// recursive up to the innermost expression that the attribute chain starts behind.
76-
fn has_breaking_comments_attribute_chain(
77-
expr_attribute: &ExprAttribute,
78-
comments: &Comments,
79-
) -> bool {
80-
if comments
81-
.dangling_comments(expr_attribute)
82-
.iter()
83-
.any(|comment| comment.line_position().is_own_line())
84-
|| comments.has_trailing_own_line_comments(expr_attribute)
85-
{
86-
return true;
87-
}
88-
89-
if let Expr::Attribute(inner) = expr_attribute.value.as_ref() {
90-
return has_breaking_comments_attribute_chain(inner, comments);
91-
}
92-
93-
return comments.has_trailing_own_line_comments(expr_attribute.value.as_ref());
94-
}
95-
9675
impl NeedsParentheses for ExprAttribute {
9776
fn needs_parentheses(
9877
&self,
99-
parenthesize: Parenthesize,
78+
parent: AnyNodeRef,
10079
context: &PyFormatContext,
101-
) -> Parentheses {
102-
if has_breaking_comments_attribute_chain(self, context.comments()) {
103-
return Parentheses::Always;
104-
}
105-
106-
match default_expression_needs_parentheses(self.into(), parenthesize, context) {
107-
Parentheses::Optional => Parentheses::Never,
108-
parentheses => parentheses,
80+
) -> OptionalParentheses {
81+
// Checks if there are any own line comments in an attribute chain (a.b.c).
82+
if context
83+
.comments()
84+
.dangling_comments(self)
85+
.iter()
86+
.any(|comment| comment.line_position().is_own_line())
87+
|| context.comments().has_trailing_own_line_comments(self)
88+
{
89+
OptionalParentheses::Always
90+
} else {
91+
self.value.needs_parentheses(parent, context)
10992
}
11093
}
11194
}

crates/ruff_python_formatter/src/expression/expr_await.rs

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
use crate::context::PyFormatContext;
2-
use crate::expression::parentheses::{
3-
default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize,
4-
};
2+
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
53
use crate::{AsFormat, FormatNodeRule, PyFormatter};
64
use ruff_formatter::prelude::{space, text};
75
use ruff_formatter::{write, Buffer, FormatResult};
6+
use ruff_python_ast::node::AnyNodeRef;
87
use rustpython_parser::ast::ExprAwait;
98

109
#[derive(Default)]
@@ -20,9 +19,9 @@ impl FormatNodeRule<ExprAwait> for FormatExprAwait {
2019
impl NeedsParentheses for ExprAwait {
2120
fn needs_parentheses(
2221
&self,
23-
parenthesize: Parenthesize,
24-
context: &PyFormatContext,
25-
) -> Parentheses {
26-
default_expression_needs_parentheses(self.into(), parenthesize, context)
22+
_parent: AnyNodeRef,
23+
_context: &PyFormatContext,
24+
) -> OptionalParentheses {
25+
OptionalParentheses::Multiline
2726
}
2827
}

crates/ruff_python_formatter/src/expression/expr_bin_op.rs

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
use crate::comments::{trailing_comments, trailing_node_comments};
22
use crate::expression::parentheses::{
3-
default_expression_needs_parentheses, in_parentheses_only_group, is_expression_parenthesized,
4-
NeedsParentheses, Parenthesize,
3+
in_parentheses_only_group, is_expression_parenthesized, NeedsParentheses, OptionalParentheses,
54
};
65
use crate::expression::Parentheses;
76
use crate::prelude::*;
87
use crate::FormatNodeRule;
98
use ruff_formatter::{write, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions};
10-
use ruff_python_ast::node::AstNode;
9+
use ruff_python_ast::node::{AnyNodeRef, AstNode};
1110
use rustpython_parser::ast::{
1211
Constant, Expr, ExprAttribute, ExprBinOp, ExprConstant, ExprUnaryOp, Operator, UnaryOp,
1312
};
@@ -175,9 +174,9 @@ impl FormatRule<Operator, PyFormatContext<'_>> for FormatOperator {
175174
impl NeedsParentheses for ExprBinOp {
176175
fn needs_parentheses(
177176
&self,
178-
parenthesize: Parenthesize,
179-
context: &PyFormatContext,
180-
) -> Parentheses {
181-
default_expression_needs_parentheses(self.into(), parenthesize, context)
177+
_parent: AnyNodeRef,
178+
_context: &PyFormatContext,
179+
) -> OptionalParentheses {
180+
OptionalParentheses::Multiline
182181
}
183182
}

crates/ruff_python_formatter/src/expression/expr_bool_op.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use crate::comments::leading_comments;
22
use crate::expression::parentheses::{
3-
default_expression_needs_parentheses, in_parentheses_only_group, NeedsParentheses, Parentheses,
4-
Parenthesize,
3+
in_parentheses_only_group, NeedsParentheses, OptionalParentheses, Parentheses,
54
};
65
use crate::prelude::*;
76
use ruff_formatter::{write, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions};
7+
use ruff_python_ast::node::AnyNodeRef;
88
use rustpython_parser::ast::{BoolOp, ExprBoolOp};
99

1010
#[derive(Default)]
@@ -70,10 +70,10 @@ impl FormatNodeRule<ExprBoolOp> for FormatExprBoolOp {
7070
impl NeedsParentheses for ExprBoolOp {
7171
fn needs_parentheses(
7272
&self,
73-
parenthesize: Parenthesize,
74-
context: &PyFormatContext,
75-
) -> Parentheses {
76-
default_expression_needs_parentheses(self.into(), parenthesize, context)
73+
_parent: AnyNodeRef,
74+
_context: &PyFormatContext,
75+
) -> OptionalParentheses {
76+
OptionalParentheses::Multiline
7777
}
7878
}
7979

0 commit comments

Comments
 (0)