From 1d8759d5df8b29d095a073431e368d29715e89b9 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 3 Aug 2023 21:28:05 -0400 Subject: [PATCH] Generalize comment-after-bracket handling to lists, sets, etc. (#6320) ## Summary We already support preserving the end-of-line comment in calls and type parameters, as in: ```python foo( # comment bar, ) ``` This PR adds the same behavior for lists, sets, comprehensions, etc., such that we preserve: ```python [ # comment 1, 2, 3, ] ``` And related cases. --- crates/ruff_formatter/src/builders.rs | 2 +- .../fixtures/ruff/expression/dict_comp.py | 7 +++ .../fixtures/ruff/expression/generator_exp.py | 6 +++ .../test/fixtures/ruff/expression/list.py | 12 +++++ .../src/comments/placement.rs | 36 +++++++------- .../src/expression/expr_dict.rs | 27 ++++++----- .../src/expression/expr_dict_comp.rs | 17 +++++-- .../src/expression/expr_generator_exp.rs | 26 ++++++++-- .../src/expression/expr_list.rs | 19 ++++---- .../src/expression/expr_list_comp.rs | 26 ++++++++-- .../src/expression/expr_set.rs | 17 +++++-- .../src/expression/expr_set_comp.rs | 24 +++++++--- .../src/expression/expr_tuple.rs | 31 +++++++----- .../src/expression/parentheses.rs | 47 +++++++++++++++---- .../snapshots/format@expression__dict.py.snap | 3 +- .../format@expression__dict_comp.py.snap | 18 +++++-- .../format@expression__generator_exp.py.snap | 13 +++++ .../snapshots/format@expression__list.py.snap | 24 +++++++++- .../format@expression__tuple.py.snap | 6 +-- .../snapshots/format@statement__raise.py.snap | 3 +- 20 files changed, 265 insertions(+), 99 deletions(-) diff --git a/crates/ruff_formatter/src/builders.rs b/crates/ruff_formatter/src/builders.rs index 3819952cf9..9c5e41d9f5 100644 --- a/crates/ruff_formatter/src/builders.rs +++ b/crates/ruff_formatter/src/builders.rs @@ -478,7 +478,7 @@ impl std::fmt::Debug for LineSuffix<'_, Context> { } /// Inserts a boundary for line suffixes that forces the printer to print all pending line suffixes. -/// Helpful if a line sufix shouldn't pass a certain point. +/// Helpful if a line suffix shouldn't pass a certain point. /// /// ## Examples /// diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/dict_comp.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/dict_comp.py index cf4df74d92..76e1e30f7b 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/dict_comp.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/dict_comp.py @@ -86,3 +86,10 @@ selected_choices = { k: str(v) for vvvvvvvvvvvvvvvvvvvvvvv in value if str(v) not in self.choices.field.empty_values } + +{ + k: v + for ( # foo + + x, aaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaay) in z +} diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/generator_exp.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/generator_exp.py index f586dcd7fb..fd7072ca26 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/generator_exp.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/generator_exp.py @@ -25,3 +25,9 @@ len( # trailing ) ) + +len( + # leading + a for b in c + # trailing +) diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/list.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/list.py index 209cc0be84..4478fc6e22 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/list.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/list.py @@ -32,3 +32,15 @@ c1 = [ # trailing open bracket 2, # trailing item # leading close bracket ] # trailing close bracket + + +[ # end-of-line comment +] + +[ # end-of-line comment + # own-line comment +] + +[ # end-of-line comment + 1 +] diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index 7653fa4fc2..2c77b02031 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -2,10 +2,7 @@ use std::cmp::Ordering; use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::whitespace::indentation; -use ruff_python_ast::{ - self as ast, Comprehension, Expr, ExprAttribute, ExprBinOp, ExprIfExp, ExprSlice, ExprStarred, - MatchCase, Parameters, Ranged, -}; +use ruff_python_ast::{self as ast, Comprehension, Expr, MatchCase, Parameters, Ranged}; use ruff_python_trivia::{ indentation_at_offset, PythonWhitespace, SimpleToken, SimpleTokenKind, SimpleTokenizer, }; @@ -50,9 +47,9 @@ pub(super) fn place_comment<'a>( locator, ) } - AnyNodeRef::ExprDict(_) | AnyNodeRef::Keyword(_) => { - handle_dict_unpacking_comment(comment, locator) - } + AnyNodeRef::Keyword(_) => handle_dict_unpacking_comment(comment, locator), + AnyNodeRef::ExprDict(_) => handle_dict_unpacking_comment(comment, locator) + .then_with(|comment| handle_bracketed_end_of_line_comment(comment, locator)), AnyNodeRef::ExprIfExp(expr_if) => handle_expr_if_comment(comment, expr_if, locator), AnyNodeRef::ExprSlice(expr_slice) => handle_slice_comments(comment, expr_slice, locator), AnyNodeRef::ExprStarred(starred) => { @@ -77,6 +74,13 @@ pub(super) fn place_comment<'a>( handle_leading_class_with_decorators_comment(comment, class_def) } AnyNodeRef::StmtImportFrom(import_from) => handle_import_from_comment(comment, import_from), + AnyNodeRef::ExprList(_) + | AnyNodeRef::ExprSet(_) + | AnyNodeRef::ExprGeneratorExp(_) + | AnyNodeRef::ExprListComp(_) + | AnyNodeRef::ExprSetComp(_) + | AnyNodeRef::ExprDictComp(_) + | AnyNodeRef::ExprTuple(_) => handle_bracketed_end_of_line_comment(comment, locator), _ => CommentPlacement::Default(comment), }) } @@ -633,7 +637,7 @@ fn handle_parameters_separator_comment<'a>( /// ``` fn handle_trailing_binary_expression_left_or_operator_comment<'a>( comment: DecoratedComment<'a>, - binary_expression: &'a ExprBinOp, + binary_expression: &'a ast::ExprBinOp, locator: &Locator, ) -> CommentPlacement<'a> { // Only if there's a preceding node (in which case, the preceding node is `left`). @@ -797,10 +801,10 @@ fn handle_module_level_own_line_comment_before_class_or_function_comment<'a>( /// ``` fn handle_slice_comments<'a>( comment: DecoratedComment<'a>, - expr_slice: &'a ExprSlice, + expr_slice: &'a ast::ExprSlice, locator: &Locator, ) -> CommentPlacement<'a> { - let ExprSlice { + let ast::ExprSlice { range: _, lower, upper, @@ -907,7 +911,7 @@ fn handle_leading_class_with_decorators_comment<'a>( } /// Handles comments between `**` and the variable name in dict unpacking -/// It attaches these to the appropriate value node +/// It attaches these to the appropriate value node. /// /// ```python /// { @@ -945,7 +949,7 @@ fn handle_dict_unpacking_comment<'a>( // if the remaining tokens from the previous node are exactly `**`, // re-assign the comment to the one that follows the stars - let mut count = 0; + let mut count = 0u32; // we start from the preceding node but we skip its token if let Some(token) = tokens.next() { @@ -992,7 +996,7 @@ fn handle_dict_unpacking_comment<'a>( /// ``` fn handle_attribute_comment<'a>( comment: DecoratedComment<'a>, - attribute: &'a ExprAttribute, + attribute: &'a ast::ExprAttribute, ) -> CommentPlacement<'a> { debug_assert!( comment.preceding_node().is_some(), @@ -1039,10 +1043,10 @@ fn handle_attribute_comment<'a>( /// happens if the comments are in a weird position but it also doesn't hurt handling it. fn handle_expr_if_comment<'a>( comment: DecoratedComment<'a>, - expr_if: &'a ExprIfExp, + expr_if: &'a ast::ExprIfExp, locator: &Locator, ) -> CommentPlacement<'a> { - let ExprIfExp { + let ast::ExprIfExp { range: _, test, body, @@ -1096,7 +1100,7 @@ fn handle_expr_if_comment<'a>( /// ``` fn handle_trailing_expression_starred_star_end_of_line_comment<'a>( comment: DecoratedComment<'a>, - starred: &'a ExprStarred, + starred: &'a ast::ExprStarred, ) -> CommentPlacement<'a> { if comment.line_position().is_own_line() { return CommentPlacement::Default(comment); diff --git a/crates/ruff_python_formatter/src/expression/expr_dict.rs b/crates/ruff_python_formatter/src/expression/expr_dict.rs index c86b3ca03d..366f0ac511 100644 --- a/crates/ruff_python_formatter/src/expression/expr_dict.rs +++ b/crates/ruff_python_formatter/src/expression/expr_dict.rs @@ -1,14 +1,17 @@ -use crate::builders::empty_parenthesized_with_dangling_comments; -use crate::comments::leading_comments; -use crate::expression::parentheses::{parenthesized, NeedsParentheses, OptionalParentheses}; -use crate::prelude::*; -use crate::FormatNodeRule; use ruff_formatter::{format_args, write}; use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::Ranged; use ruff_python_ast::{Expr, ExprDict}; use ruff_text_size::TextRange; +use crate::builders::empty_parenthesized_with_dangling_comments; +use crate::comments::leading_comments; +use crate::expression::parentheses::{ + parenthesized_with_dangling_comments, NeedsParentheses, OptionalParentheses, +}; +use crate::prelude::*; +use crate::FormatNodeRule; + #[derive(Default)] pub struct FormatExprDict; @@ -64,14 +67,12 @@ impl FormatNodeRule for FormatExprDict { debug_assert_eq!(keys.len(), values.len()); + let comments = f.context().comments().clone(); + let dangling = comments.dangling_comments(item); + if values.is_empty() { - let comments = f.context().comments().clone(); - return empty_parenthesized_with_dangling_comments( - text("{"), - comments.dangling_comments(item), - text("}"), - ) - .fmt(f); + return empty_parenthesized_with_dangling_comments(text("{"), dangling, text("}")) + .fmt(f); } let format_pairs = format_with(|f| { @@ -85,7 +86,7 @@ impl FormatNodeRule for FormatExprDict { joiner.finish() }); - parenthesized("{", &format_pairs, "}").fmt(f) + parenthesized_with_dangling_comments("{", dangling, &format_pairs, "}").fmt(f) } fn fmt_dangling_comments(&self, _node: &ExprDict, _f: &mut PyFormatter) -> FormatResult<()> { diff --git a/crates/ruff_python_formatter/src/expression/expr_dict_comp.rs b/crates/ruff_python_formatter/src/expression/expr_dict_comp.rs index 0a17f3bfc1..0c1f0d53a2 100644 --- a/crates/ruff_python_formatter/src/expression/expr_dict_comp.rs +++ b/crates/ruff_python_formatter/src/expression/expr_dict_comp.rs @@ -1,7 +1,3 @@ -use crate::context::PyFormatContext; -use crate::expression::parentheses::{parenthesized, NeedsParentheses, OptionalParentheses}; -use crate::AsFormat; -use crate::{FormatNodeRule, FormattedIterExt, PyFormatter}; use ruff_formatter::prelude::{ format_args, format_with, group, soft_line_break_or_space, space, text, }; @@ -9,6 +5,13 @@ use ruff_formatter::{write, Buffer, FormatResult}; use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::ExprDictComp; +use crate::context::PyFormatContext; +use crate::expression::parentheses::{ + parenthesized_with_dangling_comments, NeedsParentheses, OptionalParentheses, +}; +use crate::AsFormat; +use crate::{FormatNodeRule, FormattedIterExt, PyFormatter}; + #[derive(Default)] pub struct FormatExprDictComp; @@ -27,10 +30,14 @@ impl FormatNodeRule for FormatExprDictComp { .finish() }); + let comments = f.context().comments().clone(); + let dangling = comments.dangling_comments(item); + write!( f, - [parenthesized( + [parenthesized_with_dangling_comments( "{", + dangling, &group(&format_args!( group(&key.format()), text(":"), diff --git a/crates/ruff_python_formatter/src/expression/expr_generator_exp.rs b/crates/ruff_python_formatter/src/expression/expr_generator_exp.rs index 3113a49475..8dc2a48bc1 100644 --- a/crates/ruff_python_formatter/src/expression/expr_generator_exp.rs +++ b/crates/ruff_python_formatter/src/expression/expr_generator_exp.rs @@ -1,12 +1,14 @@ +use ruff_formatter::{format_args, write, Buffer, FormatResult, FormatRuleWithOptions}; +use ruff_python_ast::node::AnyNodeRef; +use ruff_python_ast::ExprGeneratorExp; + +use crate::comments::leading_comments; use crate::context::PyFormatContext; -use crate::expression::parentheses::parenthesized; +use crate::expression::parentheses::parenthesized_with_dangling_comments; use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; use crate::prelude::*; use crate::AsFormat; use crate::{FormatNodeRule, PyFormatter}; -use ruff_formatter::{format_args, write, Buffer, FormatResult, FormatRuleWithOptions}; -use ruff_python_ast::node::AnyNodeRef; -use ruff_python_ast::ExprGeneratorExp; #[derive(Eq, PartialEq, Debug, Default)] pub enum GeneratorExpParentheses { @@ -48,10 +50,14 @@ impl FormatNodeRule for FormatExprGeneratorExp { .finish() }); + let comments = f.context().comments().clone(); + let dangling = comments.dangling_comments(item); + if self.parentheses == GeneratorExpParentheses::StripIfOnlyFunctionArg { write!( f, [ + leading_comments(dangling), group(&elt.format()), soft_line_break_or_space(), group(&joined), @@ -60,8 +66,9 @@ impl FormatNodeRule for FormatExprGeneratorExp { } else { write!( f, - [parenthesized( + [parenthesized_with_dangling_comments( "(", + dangling, &format_args!( group(&elt.format()), soft_line_break_or_space(), @@ -72,6 +79,15 @@ impl FormatNodeRule for FormatExprGeneratorExp { ) } } + + fn fmt_dangling_comments( + &self, + _node: &ExprGeneratorExp, + _f: &mut PyFormatter, + ) -> FormatResult<()> { + // Handled as part of `fmt_fields` + Ok(()) + } } impl NeedsParentheses for ExprGeneratorExp { diff --git a/crates/ruff_python_formatter/src/expression/expr_list.rs b/crates/ruff_python_formatter/src/expression/expr_list.rs index 85fa33326a..73482f3814 100644 --- a/crates/ruff_python_formatter/src/expression/expr_list.rs +++ b/crates/ruff_python_formatter/src/expression/expr_list.rs @@ -1,10 +1,14 @@ -use crate::builders::empty_parenthesized_with_dangling_comments; -use crate::expression::parentheses::{parenthesized, NeedsParentheses, OptionalParentheses}; -use crate::prelude::*; -use crate::FormatNodeRule; +use ruff_formatter::prelude::{format_with, text}; use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::{ExprList, Ranged}; +use crate::builders::empty_parenthesized_with_dangling_comments; +use crate::expression::parentheses::{ + parenthesized_with_dangling_comments, NeedsParentheses, OptionalParentheses, +}; +use crate::prelude::*; +use crate::FormatNodeRule; + #[derive(Default)] pub struct FormatExprList; @@ -24,18 +28,13 @@ impl FormatNodeRule for FormatExprList { .fmt(f); } - debug_assert!( - dangling.is_empty(), - "A non-empty expression list has dangling comments" - ); - let items = format_with(|f| { f.join_comma_separated(item.end()) .nodes(elts.iter()) .finish() }); - parenthesized("[", &items, "]").fmt(f) + parenthesized_with_dangling_comments("[", dangling, &items, "]").fmt(f) } fn fmt_dangling_comments(&self, _node: &ExprList, _f: &mut PyFormatter) -> FormatResult<()> { diff --git a/crates/ruff_python_formatter/src/expression/expr_list_comp.rs b/crates/ruff_python_formatter/src/expression/expr_list_comp.rs index 4763f6b4ee..22dc263376 100644 --- a/crates/ruff_python_formatter/src/expression/expr_list_comp.rs +++ b/crates/ruff_python_formatter/src/expression/expr_list_comp.rs @@ -1,11 +1,14 @@ +use ruff_formatter::{format_args, write, FormatResult}; +use ruff_python_ast::node::AnyNodeRef; +use ruff_python_ast::ExprListComp; + use crate::context::PyFormatContext; -use crate::expression::parentheses::{parenthesized, NeedsParentheses, OptionalParentheses}; +use crate::expression::parentheses::{ + parenthesized_with_dangling_comments, NeedsParentheses, OptionalParentheses, +}; use crate::prelude::*; use crate::AsFormat; use crate::{FormatNodeRule, PyFormatter}; -use ruff_formatter::{format_args, write, Buffer, FormatResult}; -use ruff_python_ast::node::AnyNodeRef; -use ruff_python_ast::ExprListComp; #[derive(Default)] pub struct FormatExprListComp; @@ -24,10 +27,14 @@ impl FormatNodeRule for FormatExprListComp { .finish() }); + let comments = f.context().comments().clone(); + let dangling = comments.dangling_comments(item); + write!( f, - [parenthesized( + [parenthesized_with_dangling_comments( "[", + dangling, &group(&format_args![ group(&elt.format()), soft_line_break_or_space(), @@ -37,6 +44,15 @@ impl FormatNodeRule for FormatExprListComp { )] ) } + + fn fmt_dangling_comments( + &self, + _node: &ExprListComp, + _f: &mut PyFormatter, + ) -> FormatResult<()> { + // Handled as part of `fmt_fields` + Ok(()) + } } impl NeedsParentheses for ExprListComp { diff --git a/crates/ruff_python_formatter/src/expression/expr_set.rs b/crates/ruff_python_formatter/src/expression/expr_set.rs index 8d3ffa17e9..66795cb1a9 100644 --- a/crates/ruff_python_formatter/src/expression/expr_set.rs +++ b/crates/ruff_python_formatter/src/expression/expr_set.rs @@ -1,8 +1,9 @@ +use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::{ExprSet, Ranged}; -use ruff_python_ast::node::AnyNodeRef; - -use crate::expression::parentheses::{parenthesized, NeedsParentheses, OptionalParentheses}; +use crate::expression::parentheses::{ + parenthesized_with_dangling_comments, NeedsParentheses, OptionalParentheses, +}; use crate::prelude::*; use crate::FormatNodeRule; @@ -21,7 +22,15 @@ impl FormatNodeRule for FormatExprSet { .finish() }); - parenthesized("{", &joined, "}").fmt(f) + let comments = f.context().comments().clone(); + let dangling = comments.dangling_comments(item); + + parenthesized_with_dangling_comments("{", dangling, &joined, "}").fmt(f) + } + + fn fmt_dangling_comments(&self, _node: &ExprSet, _f: &mut PyFormatter) -> FormatResult<()> { + // Handled as part of `fmt_fields` + Ok(()) } } diff --git a/crates/ruff_python_formatter/src/expression/expr_set_comp.rs b/crates/ruff_python_formatter/src/expression/expr_set_comp.rs index 71704a33c6..429177dd5d 100644 --- a/crates/ruff_python_formatter/src/expression/expr_set_comp.rs +++ b/crates/ruff_python_formatter/src/expression/expr_set_comp.rs @@ -1,12 +1,15 @@ -use crate::context::PyFormatContext; -use crate::expression::parentheses::{parenthesized, NeedsParentheses, OptionalParentheses}; -use crate::prelude::*; -use crate::AsFormat; -use crate::{FormatNodeRule, PyFormatter}; use ruff_formatter::{format_args, write, Buffer, FormatResult}; use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::ExprSetComp; +use crate::context::PyFormatContext; +use crate::expression::parentheses::{ + parenthesized_with_dangling_comments, NeedsParentheses, OptionalParentheses, +}; +use crate::prelude::*; +use crate::AsFormat; +use crate::{FormatNodeRule, PyFormatter}; + #[derive(Default)] pub struct FormatExprSetComp; @@ -24,10 +27,14 @@ impl FormatNodeRule for FormatExprSetComp { .finish() }); + let comments = f.context().comments().clone(); + let dangling = comments.dangling_comments(item); + write!( f, - [parenthesized( + [parenthesized_with_dangling_comments( "{", + dangling, &group(&format_args!( group(&elt.format()), soft_line_break_or_space(), @@ -37,6 +44,11 @@ impl FormatNodeRule for FormatExprSetComp { )] ) } + + fn fmt_dangling_comments(&self, _node: &ExprSetComp, _f: &mut PyFormatter) -> FormatResult<()> { + // Handled as part of `fmt_fields` + Ok(()) + } } impl NeedsParentheses for ExprSetComp { diff --git a/crates/ruff_python_formatter/src/expression/expr_tuple.rs b/crates/ruff_python_formatter/src/expression/expr_tuple.rs index 7a4e3dee37..f9880b1eca 100644 --- a/crates/ruff_python_formatter/src/expression/expr_tuple.rs +++ b/crates/ruff_python_formatter/src/expression/expr_tuple.rs @@ -6,7 +6,9 @@ use ruff_formatter::{format_args, write, FormatRuleWithOptions}; use ruff_python_ast::node::AnyNodeRef; use crate::builders::{empty_parenthesized_with_dangling_comments, parenthesize_if_expands}; -use crate::expression::parentheses::{parenthesized, NeedsParentheses, OptionalParentheses}; +use crate::expression::parentheses::{ + parenthesized_with_dangling_comments, NeedsParentheses, OptionalParentheses, +}; use crate::prelude::*; #[derive(Eq, PartialEq, Debug, Default)] @@ -31,7 +33,6 @@ pub enum TupleParentheses { /// Handle the special cases where we don't include parentheses at all. /// - /// /// Black never formats tuple targets of for loops with parentheses if inside a comprehension. /// For example, tuple targets will always be formatted on the same line, except when an element supports /// line-breaking in an un-parenthesized context. @@ -104,6 +105,9 @@ impl FormatNodeRule for FormatExprTuple { ctx: _, } = item; + let comments = f.context().comments().clone(); + let dangling = comments.dangling_comments(item); + // Handle the edge cases of an empty tuple and a tuple with one element // // there can be dangling comments, and they can be in two @@ -116,13 +120,8 @@ impl FormatNodeRule for FormatExprTuple { // In all other cases comments get assigned to a list element match elts.as_slice() { [] => { - let comments = f.context().comments().clone(); - return empty_parenthesized_with_dangling_comments( - text("("), - comments.dangling_comments(item), - text(")"), - ) - .fmt(f); + return empty_parenthesized_with_dangling_comments(text("("), dangling, text(")")) + .fmt(f); } [single] => match self.parentheses { TupleParentheses::Preserve @@ -133,7 +132,13 @@ impl FormatNodeRule for FormatExprTuple { _ => // A single element tuple always needs parentheses and a trailing comma, except when inside of a subscript { - parenthesized("(", &format_args![single.format(), text(",")], ")").fmt(f) + parenthesized_with_dangling_comments( + "(", + dangling, + &format_args![single.format(), text(",")], + ")", + ) + .fmt(f) } }, // If the tuple has parentheses, we generally want to keep them. The exception are for @@ -142,9 +147,11 @@ impl FormatNodeRule for FormatExprTuple { // Unlike other expression parentheses, tuple parentheses are part of the range of the // tuple itself. _ if is_parenthesized(*range, elts, f.context().source()) - && self.parentheses != TupleParentheses::NeverPreserve => + && !(self.parentheses == TupleParentheses::NeverPreserve + && dangling.is_empty()) => { - parenthesized("(", &ExprSequence::new(item), ")").fmt(f) + parenthesized_with_dangling_comments("(", dangling, &ExprSequence::new(item), ")") + .fmt(f) } _ => match self.parentheses { TupleParentheses::Never => { diff --git a/crates/ruff_python_formatter/src/expression/parentheses.rs b/crates/ruff_python_formatter/src/expression/parentheses.rs index abe7cc9d62..027e6d4951 100644 --- a/crates/ruff_python_formatter/src/expression/parentheses.rs +++ b/crates/ruff_python_formatter/src/expression/parentheses.rs @@ -1,10 +1,10 @@ -use ruff_python_ast::Ranged; - use ruff_formatter::prelude::tag::Condition; use ruff_formatter::{format_args, write, Argument, Arguments}; use ruff_python_ast::node::AnyNodeRef; +use ruff_python_ast::Ranged; use ruff_python_trivia::{first_non_trivia_token, SimpleToken, SimpleTokenKind, SimpleTokenizer}; +use crate::comments::{dangling_comments, SourceComment}; use crate::context::{NodeLevel, WithNodeLevel}; use crate::prelude::*; @@ -110,6 +110,26 @@ where { FormatParenthesized { left, + comments: &[], + content: Argument::new(content), + right, + } +} + +/// Formats `content` enclosed by the `left` and `right` parentheses, along with any dangling +/// comments that on the parentheses themselves. +pub(crate) fn parenthesized_with_dangling_comments<'content, 'ast, Content>( + left: &'static str, + comments: &'content [SourceComment], + content: &'content Content, + right: &'static str, +) -> FormatParenthesized<'content, 'ast> +where + Content: Format>, +{ + FormatParenthesized { + left, + comments, content: Argument::new(content), right, } @@ -117,6 +137,7 @@ where pub(crate) struct FormatParenthesized<'content, 'ast> { left: &'static str, + comments: &'content [SourceComment], content: Argument<'content, PyFormatContext<'ast>>, right: &'static str, } @@ -124,12 +145,22 @@ pub(crate) struct FormatParenthesized<'content, 'ast> { impl<'ast> Format> for FormatParenthesized<'_, 'ast> { fn fmt(&self, f: &mut Formatter>) -> FormatResult<()> { let inner = format_with(|f| { - group(&format_args![ - text(self.left), - &soft_block_indent(&Arguments::from(&self.content)), - text(self.right) - ]) - .fmt(f) + if self.comments.is_empty() { + group(&format_args![ + text(self.left), + &soft_block_indent(&Arguments::from(&self.content)), + text(self.right) + ]) + .fmt(f) + } else { + group(&format_args![ + text(self.left), + &line_suffix(&dangling_comments(self.comments)), + &group(&soft_block_indent(&Arguments::from(&self.content))), + text(self.right) + ]) + .fmt(f) + } }); let current_level = f.context().node_level(); diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__dict.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__dict.py.snap index a799fe15a8..e27a63126d 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__dict.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__dict.py.snap @@ -70,8 +70,7 @@ x={ # dangling end of line comment ## Output ```py # before -{ - # open +{ # open key: value # key # colon # value } # close # after diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__dict_comp.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__dict_comp.py.snap index 651e8e5644..595ee6f0ac 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__dict_comp.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__dict_comp.py.snap @@ -92,6 +92,13 @@ selected_choices = { k: str(v) for vvvvvvvvvvvvvvvvvvvvvvv in value if str(v) not in self.choices.field.empty_values } + +{ + k: v + for ( # foo + + x, aaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaay) in z +} ``` ## Output @@ -201,11 +208,9 @@ selected_choices = { } # Leading -{ - # Leading +{ # Leading k: v # Trailing - for a, a, a, a, a, a, a, a, a, a, ( - # Trailing + for a, a, a, a, a, a, a, a, a, a, ( # Trailing a, a, a, @@ -241,6 +246,11 @@ selected_choices = { for vvvvvvvvvvvvvvvvvvvvvvv in value if str(v) not in self.choices.field.empty_values } + +{ + k: v + for (x, aaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaayaaaay) in z # foo +} ``` diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__generator_exp.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__generator_exp.py.snap index 2bc38c773c..54cff0fdb7 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__generator_exp.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__generator_exp.py.snap @@ -31,6 +31,12 @@ len( # trailing ) ) + +len( + # leading + a for b in c + # trailing +) ``` ## Output @@ -56,6 +62,13 @@ f((1) for _ in (a)) # black keeps these atm, but intends to remove them in the future: # https://github.com/psf/black/issues/2943 +len( + # leading + a + for b in c + # trailing +) + len( # leading a diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__list.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__list.py.snap index 5d72b05ea7..e94d3cf33f 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__list.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__list.py.snap @@ -38,6 +38,18 @@ c1 = [ # trailing open bracket 2, # trailing item # leading close bracket ] # trailing close bracket + + +[ # end-of-line comment +] + +[ # end-of-line comment + # own-line comment +] + +[ # end-of-line comment + 1 +] ``` ## Output @@ -66,14 +78,22 @@ b3 = [ ] # Comment placement in non-empty lists -c1 = [ - # trailing open bracket +c1 = [ # trailing open bracket # leading item 1, # between 2, # trailing item # leading close bracket ] # trailing close bracket + + +[] # end-of-line comment + +[ # end-of-line comment + # own-line comment +] + +[1] # end-of-line comment ``` diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__tuple.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__tuple.py.snap index e398db039b..5e818a39cb 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__tuple.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__tuple.py.snap @@ -248,14 +248,12 @@ f4 = ( # end-of-line ) # trailing # Comments in other tuples -g1 = ( - # a +g1 = ( # a # b 1, # c # d ) # e -g2 = ( - # a +g2 = ( # a # b 1, # c # d diff --git a/crates/ruff_python_formatter/tests/snapshots/format@statement__raise.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@statement__raise.py.snap index 1f50cb8320..db7ed62b73 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@statement__raise.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@statement__raise.py.snap @@ -204,8 +204,7 @@ raise hello( # sould I stay here # just a comment here ) # trailing comment -raise ( - # sould I stay here +raise ( # sould I stay here test, # just a comment here ) # trailing comment