From 6b290c095655f420a231b77e04c58011b3f792c1 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 7 Mar 2024 16:04:45 +0100 Subject: [PATCH] Fix unstable with-items formatting --- .../test/fixtures/ruff/statement/with.py | 50 ++- .../test/fixtures/ruff/statement/with_39.py | 9 +- crates/ruff_python_formatter/src/builders.rs | 1 - crates/ruff_python_formatter/src/lib.rs | 20 +- .../src/other/arguments.rs | 6 +- .../ruff_python_formatter/src/other/commas.rs | 11 +- .../src/other/with_item.rs | 129 ++++++-- .../src/statement/stmt_with.rs | 295 +++++++++++------- .../snapshots/format@statement__with.py.snap | 75 +++-- .../format@statement__with_39.py.snap | 21 +- 10 files changed, 367 insertions(+), 250 deletions(-) diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with.py index b222747733fe96..db4d7b77da1f0f 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with.py @@ -13,45 +13,40 @@ pass # trailing - with ( - a # a - , # comma - b # c - ): # colon + a # a + , # comma + b # c +): # colon pass - with ( - a # a - as # as - # own line - b # b - , # comma - c # c - ): # colon + a # a + as # as + # own line + b # b + , # comma + c # c +): # colon pass # body # body trailing own with ( - a # a - as # as - # own line - bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb # b + a # a + as # as + # own line + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb # b ): pass - -with (a,): # magic trailing comma +with (a, ): # magic trailing comma pass - with (a): # should remove brackets pass with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c: pass - # currently unparsable by black: https://github.com/psf/black/issues/3678 with (name_2 for name_0 in name_4): pass @@ -87,21 +82,21 @@ ): pass with ( - a # trailing same line comment + a # trailing same line comment # trailing own line comment as b ): pass -with (a # trailing same line comment +with (a # trailing same line comment # trailing own line comment ) as b: pass with ( (a - # trailing own line comment + # trailing own line comment ) - as # trailing as same line comment - b # trailing b same line comment + as # trailing as same line comment + b # trailing b same line comment ): pass with ( @@ -304,6 +299,9 @@ with anyio.CancelScope(shield=True) if get_running_loop() else contextlib.nullcontext(): pass +if True: + with anyio.CancelScope(shield=True) if get_running_loop() else contextlib.nullcontext() as c: + pass with Child(aaaaaaaaa, bbbbbbbbbbbbbbb, cccccc), Document(aaaaa, bbbbbbbbbb, ddddddddddddd): pass diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with_39.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with_39.py index e1b2da74e4ea01..51ed509976d484 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with_39.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with_39.py @@ -6,7 +6,6 @@ ): pass - # Black avoids parenthesizing the with because it can make all with items fit by just breaking # around parentheses. We don't implement this optimisation because it makes it difficult to see where # the different context managers start and end. @@ -37,7 +36,6 @@ ): pass - # Black avoids parentheses here because it can make the entire with # header fit without requiring parentheses to do so. # We don't implement this optimisation because it very difficult to see where @@ -66,7 +64,6 @@ with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c: pass - # Black parenthesizes this binary expression but also preserves the parentheses of the first with-item. # It does so because it prefers splitting already parenthesized context managers, even if it leads to more parentheses # like in this case. @@ -85,4 +82,8 @@ with (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c): pass - +with ( # outer comment + CtxManager1(), + CtxManager2(), +): + pass diff --git a/crates/ruff_python_formatter/src/builders.rs b/crates/ruff_python_formatter/src/builders.rs index 295d3b91edfd8f..3abdedd8600998 100644 --- a/crates/ruff_python_formatter/src/builders.rs +++ b/crates/ruff_python_formatter/src/builders.rs @@ -219,7 +219,6 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> { if let Some(last_end) = self.entries.position() { let magic_trailing_comma = has_magic_trailing_comma( TextRange::new(last_end, self.sequence_end), - self.fmt.options(), self.fmt.context(), ); diff --git a/crates/ruff_python_formatter/src/lib.rs b/crates/ruff_python_formatter/src/lib.rs index c4cf4a16c3d4cc..35f9209f0adfc2 100644 --- a/crates/ruff_python_formatter/src/lib.rs +++ b/crates/ruff_python_formatter/src/lib.rs @@ -212,14 +212,20 @@ if True: #[test] fn quick_test() { let source = r#" -def main() -> None: - if True: - some_very_long_variable_name_abcdefghijk = Foo() - some_very_long_variable_name_abcdefghijk = some_very_long_variable_name_abcdefghijk[ - some_very_long_variable_name_abcdefghijk.some_very_long_attribute_name - == "This is a very long string abcdefghijk" - ] +with ( + open( + "/etc/hosts" # This is an incredibly long comment that has been replaced for sanitization + ) +): + pass + +with open( + "/etc/hosts" # This is an incredibly long comment that has been replaced for sanitization +): + pass +with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c: + pass "#; let source_type = PySourceType::Python; let (tokens, comment_ranges) = tokens_and_ranges(source, source_type).unwrap(); diff --git a/crates/ruff_python_formatter/src/other/arguments.rs b/crates/ruff_python_formatter/src/other/arguments.rs index 62871cdf31b2d9..98a0a334be4b11 100644 --- a/crates/ruff_python_formatter/src/other/arguments.rs +++ b/crates/ruff_python_formatter/src/other/arguments.rs @@ -205,11 +205,7 @@ fn is_arguments_huggable(arguments: &Arguments, context: &PyFormatContext) -> bo // If the expression has a trailing comma, then we can't hug it. if options.magic_trailing_comma().is_respect() - && commas::has_magic_trailing_comma( - TextRange::new(arg.end(), arguments.end()), - options, - context, - ) + && commas::has_magic_trailing_comma(TextRange::new(arg.end(), arguments.end()), context) { return false; } diff --git a/crates/ruff_python_formatter/src/other/commas.rs b/crates/ruff_python_formatter/src/other/commas.rs index 326daba4255b50..a102e0d59e8789 100644 --- a/crates/ruff_python_formatter/src/other/commas.rs +++ b/crates/ruff_python_formatter/src/other/commas.rs @@ -1,17 +1,14 @@ +use ruff_formatter::FormatContext; use ruff_python_trivia::{SimpleToken, SimpleTokenKind, SimpleTokenizer}; use ruff_text_size::TextRange; use crate::prelude::*; -use crate::{MagicTrailingComma, PyFormatOptions}; +use crate::MagicTrailingComma; /// Returns `true` if the range ends with a magic trailing comma (and the magic trailing comma /// should be respected). -pub(crate) fn has_magic_trailing_comma( - range: TextRange, - options: &PyFormatOptions, - context: &PyFormatContext, -) -> bool { - match options.magic_trailing_comma() { +pub(crate) fn has_magic_trailing_comma(range: TextRange, context: &PyFormatContext) -> bool { + match context.options().magic_trailing_comma() { MagicTrailingComma::Respect => { let first_token = SimpleTokenizer::new(context.source(), range) .skip_trivia() diff --git a/crates/ruff_python_formatter/src/other/with_item.rs b/crates/ruff_python_formatter/src/other/with_item.rs index 3ea82b8a9df696..e349fee895b5b5 100644 --- a/crates/ruff_python_formatter/src/other/with_item.rs +++ b/crates/ruff_python_formatter/src/other/with_item.rs @@ -1,4 +1,4 @@ -use ruff_formatter::write; +use ruff_formatter::{write, FormatRuleWithOptions}; use ruff_python_ast::WithItem; use crate::comments::SourceComment; @@ -8,8 +8,57 @@ use crate::expression::parentheses::{ }; use crate::prelude::*; +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +pub enum WithItemLayout { + /// A with item that is the `with`s only context manager and its context expression is parenthesized. + /// + /// ```python + /// with ( + /// a + b + /// ) as b: + /// ... + /// ``` + /// + /// This layout is used independent of the target version. + SingleParenthesizedContextExpression, + + /// This layout is used when the target python version doesn't support parenthesized context managers. + Python38OrOlder, + + /// A with item where the `with` formatting adds parentheses around all context managers if necessary. + /// + /// ```python + /// with ( + /// a, + /// b, + /// ): pass + /// ``` + /// + /// This layout is generally used when the target version is Python 3.9 or newer but it is used + /// for Python 3.8 if the with item has a leading or trailing comment. + /// + /// ```python + /// with ( + /// # leading + /// a + // ): ... + /// ``` + #[default] + ParenthesizedContextManagers, +} + #[derive(Default)] -pub struct FormatWithItem; +pub struct FormatWithItem { + layout: WithItemLayout, +} + +impl FormatRuleWithOptions> for FormatWithItem { + type Options = WithItemLayout; + + fn with_options(self, options: Self::Options) -> Self { + Self { layout: options } + } +} impl FormatNodeRule for FormatWithItem { fn fmt_fields(&self, item: &WithItem, f: &mut PyFormatter) -> FormatResult<()> { @@ -28,40 +77,52 @@ impl FormatNodeRule for FormatWithItem { f.context().source(), ); - // Remove the parentheses of the `with_items` if the with statement adds parentheses - if f.context().node_level().is_parenthesized() { - if is_parenthesized { - // ...except if the with item is parenthesized, then use this with item as a preferred breaking point - // or when it has comments, then parenthesize it to prevent comments from moving. - maybe_parenthesize_expression( - context_expr, - item, - Parenthesize::IfBreaksOrIfRequired, - ) - .fmt(f)?; - } else { - context_expr - .format() - .with_options(Parentheses::Never) + match self.layout { + // Remove the parentheses of the `with_items` if the with statement adds parentheses + WithItemLayout::ParenthesizedContextManagers => { + if is_parenthesized { + // ...except if the with item is parenthesized, then use this with item as a preferred breaking point + // or when it has comments, then parenthesize it to prevent comments from moving. + maybe_parenthesize_expression( + context_expr, + item, + Parenthesize::IfBreaksOrIfRequired, + ) .fmt(f)?; + } else { + context_expr + .format() + .with_options(Parentheses::Never) + .fmt(f)?; + } + } + + WithItemLayout::SingleParenthesizedContextExpression => { + write!( + f, + [maybe_parenthesize_expression( + context_expr, + item, + Parenthesize::IfBreaks + )] + )?; + } + + WithItemLayout::Python38OrOlder => { + let parenthesize = if is_parenthesized { + Parenthesize::IfBreaks + } else { + Parenthesize::IfRequired + }; + write!( + f, + [maybe_parenthesize_expression( + context_expr, + item, + parenthesize + )] + )?; } - } else { - // Prefer keeping parentheses for already parenthesized expressions over - // parenthesizing other nodes. - let parenthesize = if is_parenthesized { - Parenthesize::IfBreaks - } else { - Parenthesize::IfRequired - }; - - write!( - f, - [maybe_parenthesize_expression( - context_expr, - item, - parenthesize - )] - )?; } if let Some(optional_vars) = optional_vars { diff --git a/crates/ruff_python_formatter/src/statement/stmt_with.rs b/crates/ruff_python_formatter/src/statement/stmt_with.rs index ce0af821f101d3..7543ab4798684f 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_with.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_with.rs @@ -1,6 +1,6 @@ -use ruff_formatter::{format_args, write, FormatError}; -use ruff_python_ast::AstNode; +use ruff_formatter::{format_args, write, FormatContext, FormatError}; use ruff_python_ast::StmtWith; +use ruff_python_ast::{AstNode, WithItem}; use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer}; use ruff_text_size::{Ranged, TextRange}; @@ -11,9 +11,10 @@ use crate::expression::parentheses::{ is_expression_parenthesized, optional_parentheses, parenthesized, }; use crate::other::commas; +use crate::other::with_item::WithItemLayout; use crate::prelude::*; use crate::statement::clause::{clause_body, clause_header, ClauseHeader}; -use crate::{PyFormatOptions, PythonVersion}; +use crate::PythonVersion; #[derive(Default)] pub struct FormatStmtWith; @@ -61,58 +62,59 @@ impl FormatNodeRule for FormatStmtWith { ] )?; - if parenthesized_comments.is_empty() { - let format_items = format_with(|f| { - let mut joiner = - f.join_comma_separated(with_stmt.body.first().unwrap().start()); + let layout = WithItemsLayout::from_statement( + with_stmt, + f.context(), + parenthesized_comments, + )?; + + match layout { + WithItemsLayout::SingleOptionalParentheses(single) => { + optional_parentheses(&single.format()).fmt(f) + } - for item in &with_stmt.items { - joiner.entry_with_line_separator( - item, - &item.format(), - soft_line_break_or_space(), + WithItemsLayout::SingleParenthesizedContextExpression(single) => single + .format() + .with_options(WithItemLayout::SingleParenthesizedContextExpression) + .fmt(f), + + WithItemsLayout::ParenthesizeIfExpands => { + parenthesize_if_expands(&format_with(|f| { + let mut joiner = f.join_comma_separated( + with_stmt.body.first().unwrap().start(), ); - } - joiner.finish() - }); - - match should_parenthesize(with_stmt, f.options(), f.context())? { - ParenthesizeWith::Optional => { - optional_parentheses(&format_items).fmt(f)?; - } - ParenthesizeWith::IfExpands => { - parenthesize_if_expands(&format_items).fmt(f)?; - } - ParenthesizeWith::UnlessCommented => { - if let [item] = with_stmt.items.as_slice() { - // This is similar to `maybe_parenthesize_expression`, but we're not - // dealing with an expression here, it's a `WithItem`. - if comments.has_leading(item) || comments.has_trailing(item) - { - parenthesized("(", &item.format(), ")").fmt(f)?; - } else { - item.format().fmt(f)?; - } - } else { - f.join_with(format_args![token(","), space()]) - .entries(with_stmt.items.iter().formatted()) - .finish()?; + + for item in &with_stmt.items { + joiner.entry_with_line_separator( + item, + &item.format(), + soft_line_break_or_space(), + ); } - } + joiner.finish() + })) + .fmt(f) } - } else { - let joined = format_with(|f: &mut PyFormatter| { - f.join_comma_separated(with_stmt.body.first().unwrap().start()) - .nodes(&with_stmt.items) - .finish() - }); - - parenthesized("(", &joined, ")") - .with_dangling_comments(parenthesized_comments) - .fmt(f)?; - } - Ok(()) + WithItemsLayout::Python38OrOlder => f + .join_with(format_args![token(","), space()]) + .entries(with_stmt.items.iter().map(|item| { + item.format().with_options(WithItemLayout::Python38OrOlder) + })) + .finish(), + + WithItemsLayout::Parenthesized => parenthesized( + "(", + &format_with(|f: &mut PyFormatter| { + f.join_comma_separated(with_stmt.body.first().unwrap().start()) + .nodes(&with_stmt.items) + .finish() + }), + ")", + ) + .with_dangling_comments(parenthesized_comments) + .fmt(f), + } }) ), clause_body(&with_stmt.body, colon_comments) @@ -130,92 +132,151 @@ impl FormatNodeRule for FormatStmtWith { } } -/// Determines whether the `with` items should be parenthesized (over parenthesizing each item), -/// and if so, which parenthesizing layout to use. -/// -/// Parenthesize `with` items if -/// * The last item has a trailing comma (implying that the with items were parenthesized in the source) -/// * There's more than one item and they're already parenthesized -/// * There's more than one item, the [`wrap_multiple_context_managers_in_parens`](is_wrap_multiple_context_managers_in_parens) preview style is enabled, -/// and the target python version is >= 3.9 -/// * There's a single non-parenthesized item. The function returns [`ParenthesizeWith::Optional`] -/// if the parentheses can be omitted if breaking around parenthesized sub-expressions is sufficient -/// to make the expression fit. It returns [`ParenthesizeWith::IfExpands`] otherwise. -/// * The only item is parenthesized and has comments. -fn should_parenthesize( - with: &StmtWith, - options: &PyFormatOptions, - context: &PyFormatContext, -) -> FormatResult { - if has_magic_trailing_comma(with, options, context) { - return Ok(ParenthesizeWith::IfExpands); - } +#[derive(Clone, Copy, Debug)] +enum WithItemsLayout<'a> { + /// The with statement has a single context manager. The with item's context expression is parenthesized. + /// + /// ```python + /// with ( + /// a + b + /// ) as b: + /// ... + /// ``` + /// + /// In this case, preserve the parentheses around the context expression instead of parenthesizing the entire + /// with item. + SingleParenthesizedContextExpression(&'a WithItem), - let can_parenthesize = options.target_version() >= PythonVersion::Py39 - || are_with_items_parenthesized(with, context)?; + /// It's a single with item. Use the optional parentheses layout (see [`optional_parentheses`]) + /// to mimic the `maybe_parenthesize_expression` behavior. + SingleOptionalParentheses(&'a WithItem), - if !can_parenthesize { - return Ok(ParenthesizeWith::UnlessCommented); - } + /// The target python version doesn't support parenthesized context managers because it is Python 3.8 or older. + /// + /// In this case, never add parentheses and join the with items with spaces. + /// + /// ```python + /// with ContextManager1( + /// aaaaaaaaaaaaaaa, b + /// ), ContextManager2(), ContextManager3(), ContextManager4(): + /// pass + /// ``` + Python38OrOlder, + + /// Wrap the with items in parentheses if they don't fit on a single line and join them by soft line breaks. + /// + /// ```python + /// with ( + /// ContextManager1(aaaaaaaaaaaaaaa, b), + /// ContextManager1(), + /// ContextManager1(), + /// ContextManager1(), + /// ): + /// pass + /// ``` + ParenthesizeIfExpands, + + /// Always parenthesize because the context managers open-parentheses have a trailing comment: + /// + /// ```python + /// with ( # comment + /// CtxManager() as example + /// ): + /// ... + /// ``` + /// + /// Or because it is a single item with a trailing or leading comment. + /// + /// ```python + /// with ( + /// # leading + /// CtxManager() + /// # trailing + /// ): pass + /// ``` + Parenthesized, +} + +impl<'a> WithItemsLayout<'a> { + fn from_statement( + with: &'a StmtWith, + context: &PyFormatContext, + parenthesized_comments: &[SourceComment], + ) -> FormatResult { + // The with statement already has parentheses around the entire with items. Guaranteed to be Python 3.9 or newer + // ``` + // with ( # comment + // CtxManager() as example + // ): + // pass + // ``` + if !parenthesized_comments.is_empty() { + return Ok(Self::Parenthesized); + } + + // A trailing comma at the end guarantees that the context managers are parenthesized and that it is Python 3.9 or newer syntax. + // ```python + // with ( # comment + // CtxManager() as example, + // ): + // pass + // ``` + if has_magic_trailing_comma(with, context) { + return Ok(Self::ParenthesizeIfExpands); + } - if let [single] = with.items.as_slice() { - return Ok( + if let [single] = with.items.as_slice() { // If the with item itself has comments (not the context expression), then keep the parentheses + // ```python + // with ( + // # leading + // a + // ): pass + // ``` if context.comments().has_leading(single) || context.comments().has_trailing(single) { - ParenthesizeWith::IfExpands + return Ok(Self::Parenthesized); } - // If it is the only expression and it has comments, then the with statement - // as well as the with item add parentheses - else if is_expression_parenthesized( + + // Preserve the parentheses around the context expression instead of parenthesizing the entire + // with items. + if is_expression_parenthesized( (&single.context_expr).into(), context.comments().ranges(), context.source(), ) { - // Preserve the parentheses around the context expression instead of parenthesizing the entire - // with items. - ParenthesizeWith::UnlessCommented - } else if can_omit_optional_parentheses(&single.context_expr, context) { - ParenthesizeWith::Optional - } else { - ParenthesizeWith::IfExpands - }, - ); - } - - // Always parenthesize multiple items - Ok(ParenthesizeWith::IfExpands) -} + return Ok(Self::SingleParenthesizedContextExpression(single)); + } + } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum ParenthesizeWith { - /// Don't wrap the with items in parentheses except if it is a single item - /// and it has leading or trailing comment. - /// - /// This is required because `are_with_items_parenthesized` cannot determine if - /// `with (expr)` is a parenthesized expression or a parenthesized with item. - UnlessCommented, + let can_parenthesize = context.options().target_version() >= PythonVersion::Py39 + || are_with_items_parenthesized(with, context)?; - /// Wrap the with items in optional parentheses - Optional, + // If the target version doesn't support parenthesized context managers and they aren't + // parenthesized by the user, bail out. + if !can_parenthesize { + return Ok(Self::Python38OrOlder); + } - /// Wrap the with items in parentheses if they expand - IfExpands, + Ok(match with.items.as_slice() { + [single] => { + if can_omit_optional_parentheses(&single.context_expr, context) { + Self::SingleOptionalParentheses(single) + } else { + Self::ParenthesizeIfExpands + } + } + // Always parenthesize multiple items + [..] => Self::ParenthesizeIfExpands, + }) + } } -fn has_magic_trailing_comma( - with: &StmtWith, - options: &PyFormatOptions, - context: &PyFormatContext, -) -> bool { +fn has_magic_trailing_comma(with: &StmtWith, context: &PyFormatContext) -> bool { let Some(last_item) = with.items.last() else { return false; }; - commas::has_magic_trailing_comma( - TextRange::new(last_item.end(), with.end()), - options, - context, - ) + commas::has_magic_trailing_comma(TextRange::new(last_item.end(), with.end()), context) } fn are_with_items_parenthesized(with: &StmtWith, context: &PyFormatContext) -> FormatResult { diff --git a/crates/ruff_python_formatter/tests/snapshots/format@statement__with.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@statement__with.py.snap index fb2a0b081c264b..312162387ad8f3 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@statement__with.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@statement__with.py.snap @@ -19,45 +19,40 @@ with ( pass # trailing - with ( - a # a - , # comma - b # c - ): # colon + a # a + , # comma + b # c +): # colon pass - with ( - a # a - as # as - # own line - b # b - , # comma - c # c - ): # colon + a # a + as # as + # own line + b # b + , # comma + c # c +): # colon pass # body # body trailing own with ( - a # a - as # as - # own line - bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb # b + a # a + as # as + # own line + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb # b ): pass - -with (a,): # magic trailing comma +with (a, ): # magic trailing comma pass - with (a): # should remove brackets pass with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c: pass - # currently unparsable by black: https://github.com/psf/black/issues/3678 with (name_2 for name_0 in name_4): pass @@ -93,21 +88,21 @@ with ( ): pass with ( - a # trailing same line comment + a # trailing same line comment # trailing own line comment as b ): pass -with (a # trailing same line comment +with (a # trailing same line comment # trailing own line comment ) as b: pass with ( (a - # trailing own line comment + # trailing own line comment ) - as # trailing as same line comment - b # trailing b same line comment + as # trailing as same line comment + b # trailing b same line comment ): pass with ( @@ -310,6 +305,9 @@ if True: with anyio.CancelScope(shield=True) if get_running_loop() else contextlib.nullcontext(): pass +if True: + with anyio.CancelScope(shield=True) if get_running_loop() else contextlib.nullcontext() as c: + pass with Child(aaaaaaaaa, bbbbbbbbbbbbbbb, cccccc), Document(aaaaa, bbbbbbbbbb, ddddddddddddd): pass @@ -347,14 +345,12 @@ with ( pass # trailing - with ( a, # a # comma b, # c ): # colon pass - with ( a as ( # a # as # own line @@ -373,20 +369,17 @@ with ( ): pass - with ( a, ): # magic trailing comma pass - with a: # should remove brackets pass with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c: pass - # currently unparsable by black: https://github.com/psf/black/issues/3678 with (name_2 for name_0 in name_4): pass @@ -661,6 +654,11 @@ if True: ) if get_running_loop() else contextlib.nullcontext(): pass +if True: + with anyio.CancelScope( + shield=True + ) if get_running_loop() else contextlib.nullcontext() as c: + pass with Child(aaaaaaaaa, bbbbbbbbbbbbbbb, cccccc), Document( aaaaa, bbbbbbbbbb, ddddddddddddd @@ -703,14 +701,12 @@ with ( pass # trailing - with ( a, # a # comma b, # c ): # colon pass - with ( a as ( # a # as # own line @@ -729,13 +725,11 @@ with ( ): pass - with ( a, ): # magic trailing comma pass - with a: # should remove brackets pass @@ -744,7 +738,6 @@ with ( ): pass - # currently unparsable by black: https://github.com/psf/black/issues/3678 with (name_2 for name_0 in name_4): pass @@ -1052,6 +1045,13 @@ if True: ): pass +if True: + with ( + anyio.CancelScope(shield=True) + if get_running_loop() + else contextlib.nullcontext() as c + ): + pass with ( Child(aaaaaaaaa, bbbbbbbbbbbbbbb, cccccc), @@ -1059,6 +1059,3 @@ with ( ): pass ``` - - - diff --git a/crates/ruff_python_formatter/tests/snapshots/format@statement__with_39.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@statement__with_39.py.snap index 5319f46100e147..53a842524dc983 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@statement__with_39.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@statement__with_39.py.snap @@ -12,7 +12,6 @@ if True: ): pass - # Black avoids parenthesizing the with because it can make all with items fit by just breaking # around parentheses. We don't implement this optimisation because it makes it difficult to see where # the different context managers start and end. @@ -43,7 +42,6 @@ if True: ): pass - # Black avoids parentheses here because it can make the entire with # header fit without requiring parentheses to do so. # We don't implement this optimisation because it very difficult to see where @@ -72,7 +70,6 @@ with xxxxxxxx.some_kind_of_method( with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c: pass - # Black parenthesizes this binary expression but also preserves the parentheses of the first with-item. # It does so because it prefers splitting already parenthesized context managers, even if it leads to more parentheses # like in this case. @@ -91,7 +88,11 @@ if True: with (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c): pass - +with ( # outer comment + CtxManager1(), + CtxManager2(), +): + pass ``` ## Outputs @@ -119,7 +120,6 @@ if True: ): pass - # Black avoids parenthesizing the with because it can make all with items fit by just breaking # around parentheses. We don't implement this optimisation because it makes it difficult to see where # the different context managers start and end. @@ -156,7 +156,6 @@ if True: ): pass - # Black avoids parentheses here because it can make the entire with # header fit without requiring parentheses to do so. # We don't implement this optimisation because it very difficult to see where @@ -193,7 +192,6 @@ with ( ): pass - # Black parenthesizes this binary expression but also preserves the parentheses of the first with-item. # It does so because it prefers splitting already parenthesized context managers, even if it leads to more parentheses # like in this case. @@ -217,7 +215,10 @@ with ( aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c ): pass -``` - - +with ( # outer comment + CtxManager1(), + CtxManager2(), +): + pass +```