Refactor with statement formatting to have explicit layouts (#10296) · astral-sh/ruff@a56d42f (original) (raw)
1
``
`-
use ruff_formatter::write;
`
``
1
`+
use ruff_formatter::{write, FormatRuleWithOptions};
`
2
2
`use ruff_python_ast::WithItem;
`
3
3
``
4
4
`use crate::comments::SourceComment;
`
`@@ -8,8 +8,66 @@ use crate::expression::parentheses::{
`
8
8
`};
`
9
9
`use crate::prelude::*;
`
10
10
``
``
11
`+
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
`
``
12
`+
pub enum WithItemLayout {
`
``
13
`` +
/// A with item that is the with
s only context manager and its context expression is parenthesized.
``
``
14
`+
///
`
``
15
/// ```python
``
16
`+
/// with (
`
``
17
`+
/// a + b
`
``
18
`+
/// ) as b:
`
``
19
`+
/// ...
`
``
20
/// ```
``
21
`+
///
`
``
22
`+
/// This layout is used independent of the target version.
`
``
23
`+
SingleParenthesizedContextManager,
`
``
24
+
``
25
`+
/// This layout is used when the target python version doesn't support parenthesized context managers and
`
``
26
`+
/// it's either a single, unparenthesized with item or multiple items.
`
``
27
`+
///
`
``
28
/// ```python
``
29
`+
/// with a + b:
`
``
30
`+
/// ...
`
``
31
`+
///
`
``
32
`+
/// with a, b:
`
``
33
`+
/// ...
`
``
34
/// ```
``
35
`+
Python38OrOlder,
`
``
36
+
``
37
`` +
/// A with item where the with
formatting adds parentheses around all context managers if necessary.
``
``
38
`+
///
`
``
39
/// ```python
``
40
`+
/// with (
`
``
41
`+
/// a,
`
``
42
`+
/// b,
`
``
43
`+
/// ): pass
`
``
44
/// ```
``
45
`+
///
`
``
46
`+
/// This layout is generally used when the target version is Python 3.9 or newer, but it is used
`
``
47
`+
/// for Python 3.8 if the with item has a leading or trailing comment.
`
``
48
`+
///
`
``
49
/// ```python
``
50
`+
/// with (
`
``
51
`+
/// # leading
`
``
52
`+
/// a
`
``
53
`+
// ): ...
`
``
54
/// ```
``
55
`+
#[default]
`
``
56
`+
ParenthesizedContextManagers,
`
``
57
`+
}
`
``
58
+
11
59
`#[derive(Default)]
`
12
``
`-
pub struct FormatWithItem;
`
``
60
`+
pub struct FormatWithItem {
`
``
61
`+
layout: WithItemLayout,
`
``
62
`+
}
`
``
63
+
``
64
`+
impl FormatRuleWithOptions<WithItem, PyFormatContext<'_>> for FormatWithItem {
`
``
65
`+
type Options = WithItemLayout;
`
``
66
+
``
67
`+
fn with_options(self, options: Self::Options) -> Self {
`
``
68
`+
Self { layout: options }
`
``
69
`+
}
`
``
70
`+
}
`
13
71
``
14
72
`impl FormatNodeRule for FormatWithItem {
`
15
73
`fn fmt_fields(&self, item: &WithItem, f: &mut PyFormatter) -> FormatResult<()> {
`
`@@ -28,40 +86,52 @@ impl FormatNodeRule for FormatWithItem {
`
28
86
` f.context().source(),
`
29
87
`);
`
30
88
``
31
``
`` -
// Remove the parentheses of the with_items
if the with statement adds parentheses
``
32
``
`-
if f.context().node_level().is_parenthesized() {
`
33
``
`-
if is_parenthesized {
`
34
``
`-
// ...except if the with item is parenthesized, then use this with item as a preferred breaking point
`
35
``
`-
// or when it has comments, then parenthesize it to prevent comments from moving.
`
36
``
`-
maybe_parenthesize_expression(
`
37
``
`-
context_expr,
`
38
``
`-
item,
`
39
``
`-
Parenthesize::IfBreaksOrIfRequired,
`
40
``
`-
)
`
41
``
`-
.fmt(f)?;
`
42
``
`-
} else {
`
43
``
`-
context_expr
`
44
``
`-
.format()
`
45
``
`-
.with_options(Parentheses::Never)
`
``
89
`+
match self.layout {
`
``
90
`` +
// Remove the parentheses of the with_items
if the with statement adds parentheses
``
``
91
`+
WithItemLayout::ParenthesizedContextManagers => {
`
``
92
`+
if is_parenthesized {
`
``
93
`+
// ...except if the with item is parenthesized, then use this with item as a preferred breaking point
`
``
94
`+
// or when it has comments, then parenthesize it to prevent comments from moving.
`
``
95
`+
maybe_parenthesize_expression(
`
``
96
`+
context_expr,
`
``
97
`+
item,
`
``
98
`+
Parenthesize::IfBreaksOrIfRequired,
`
``
99
`+
)
`
46
100
`.fmt(f)?;
`
``
101
`+
} else {
`
``
102
`+
context_expr
`
``
103
`+
.format()
`
``
104
`+
.with_options(Parentheses::Never)
`
``
105
`+
.fmt(f)?;
`
``
106
`+
}
`
``
107
`+
}
`
``
108
+
``
109
`+
WithItemLayout::SingleParenthesizedContextManager => {
`
``
110
`+
write!(
`
``
111
`+
f,
`
``
112
`+
[maybe_parenthesize_expression(
`
``
113
`+
context_expr,
`
``
114
`+
item,
`
``
115
`+
Parenthesize::IfBreaks
`
``
116
`+
)]
`
``
117
`+
)?;
`
``
118
`+
}
`
``
119
+
``
120
`+
WithItemLayout::Python38OrOlder => {
`
``
121
`+
let parenthesize = if is_parenthesized {
`
``
122
`+
Parenthesize::IfBreaks
`
``
123
`+
} else {
`
``
124
`+
Parenthesize::IfRequired
`
``
125
`+
};
`
``
126
`+
write!(
`
``
127
`+
f,
`
``
128
`+
[maybe_parenthesize_expression(
`
``
129
`+
context_expr,
`
``
130
`+
item,
`
``
131
`+
parenthesize
`
``
132
`+
)]
`
``
133
`+
)?;
`
47
134
`}
`
48
``
`-
} else {
`
49
``
`-
// Prefer keeping parentheses for already parenthesized expressions over
`
50
``
`-
// parenthesizing other nodes.
`
51
``
`-
let parenthesize = if is_parenthesized {
`
52
``
`-
Parenthesize::IfBreaks
`
53
``
`-
} else {
`
54
``
`-
Parenthesize::IfRequired
`
55
``
`-
};
`
56
``
-
57
``
`-
write!(
`
58
``
`-
f,
`
59
``
`-
[maybe_parenthesize_expression(
`
60
``
`-
context_expr,
`
61
``
`-
item,
`
62
``
`-
parenthesize
`
63
``
`-
)]
`
64
``
`-
)?;
`
65
135
`}
`
66
136
``
67
137
`if let Some(optional_vars) = optional_vars {
`