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 withs 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 {

`