diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/list_comp.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/list_comp.py new file mode 100644 index 00000000000000..13f6704e731056 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/list_comp.py @@ -0,0 +1,25 @@ +[i for i in []] + +[i for i in [1,]] + +[ + a # a + for # for + c # c + in # in + e # e +] + +[ + # before a + a # a + # before for + for # for + # before c + c # c + # before in + in # in + # before e + e + # before end +] diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index 207b9a68a87099..522827171294f7 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use ruff_text_size::{TextLen, TextRange}; +use ruff_text_size::{TextLen, TextRange, TextSize}; use rustpython_parser::ast; use rustpython_parser::ast::{Expr, ExprIfExp, ExprSlice, Ranged}; @@ -40,6 +40,7 @@ pub(super) fn place_comment<'a>( handle_slice_comments, handle_attribute_comment, handle_expr_if_comment, + handle_comprehension_comment, ]; for handler in HANDLERS { comment = match handler(comment, locator) { @@ -1233,6 +1234,124 @@ fn find_only_token_str_in_range(range: TextRange, locator: &Locator, token_str: token } +// Handle comments inside comprehensions, e.g. +// +// ```python +// [ +// a +// for # dangling on the comprehension +// b +// # dangling on the comprehension +// in # dangling on comprehension.iter +// # leading on the iter +// c +// # dangling on comprehension.if.n +// if # dangling on comprehension.if.n +// d +// ] +// ``` +fn handle_comprehension_comment<'a>( + comment: DecoratedComment<'a>, + locator: &Locator, +) -> CommentPlacement<'a> { + fn find_if_token(locator: &Locator, start: TextSize, end: TextSize) -> Option { + let mut tokens = + SimpleTokenizer::new(locator.contents(), TextRange::new(start, end)).skip_trivia(); + tokens.next().map(|t| t.start()) + } + + let AnyNodeRef::Comprehension(comprehension) = comment.enclosing_node() else { + return CommentPlacement::Default(comment); + }; + let is_own_line = comment.line_position().is_own_line(); + + if comment.slice().end() < comprehension.target.range().start() { + return if is_own_line { + // own line comments are correctly assigned as leading the target + CommentPlacement::Default(comment) + } else { + // after the `for` + CommentPlacement::dangling(comment.enclosing_node(), comment) + }; + } + + let mut tokens = SimpleTokenizer::new( + locator.contents(), + TextRange::new( + comprehension.target.range().end(), + comprehension.iter.range().start(), + ), + ) + .skip_trivia(); + let Some(in_token) = tokens.next() else { + // Should always have an `in` + debug_assert!(false); + return CommentPlacement::Default(comment); + }; + + if comment.slice().start() < in_token.start() { + // attach as dangling comments on the target + // (to be rendered as leading on the "in") + return if is_own_line { + CommentPlacement::dangling(comment.enclosing_node(), comment) + } else { + // correctly trailing on the target + CommentPlacement::Default(comment) + }; + } + + if comment.slice().start() < comprehension.iter.range().start() { + // attach as dangling comments on the iter + + return if is_own_line { + // after the `in` and own line: leading on the iter + CommentPlacement::leading((&comprehension.iter).into(), comment) + } else { + // after the `in` but same line, turn into trailin on the `in` token + CommentPlacement::dangling((&comprehension.iter).into(), comment) + }; + } + + let mut last_end = comprehension.iter.range().end(); + + for if_node in &comprehension.ifs { + // [ + // a + // for + // c + // in + // e + // # above if <-- find these own-line between previous and `if` token + // if # if <-- find these end-of-line between `if` and if node (`f`) + // # above f <-- already correctly assigned as leading `f` + // f # f <-- already correctly assigned as trailing `f` + // # above if2 + // if # if2 + // # above g + // g # g + // ] + let Some(if_token_start) = find_if_token(locator, last_end, if_node.range().end()) else { + // there should always be an `if` here + debug_assert!(false); + return CommentPlacement::Default(comment); + }; + if is_own_line { + if last_end < comment.slice().start() && comment.slice().start() < if_token_start { + return CommentPlacement::dangling((if_node).into(), comment); + } + } else { + if if_token_start < comment.slice().start() + && comment.slice().start() < if_node.range().start() + { + return CommentPlacement::dangling((if_node).into(), comment); + } + } + last_end = if_node.range().end(); + } + + return CommentPlacement::Default(comment); +} + /// Returns `true` if `right` is `Some` and `left` and `right` are referentially equal. fn are_same_optional<'a, T>(left: AnyNodeRef, right: Option) -> bool where 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 5bc6a3017aa0c4..1f43915e88c948 100644 --- a/crates/ruff_python_formatter/src/expression/expr_list_comp.rs +++ b/crates/ruff_python_formatter/src/expression/expr_list_comp.rs @@ -1,8 +1,11 @@ use crate::comments::Comments; use crate::expression::parentheses::{ - default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize, + default_expression_needs_parentheses, parenthesized, NeedsParentheses, Parentheses, + Parenthesize, }; -use crate::{not_yet_implemented_custom_text, FormatNodeRule, PyFormatter}; +use crate::prelude::*; +use crate::AsFormat; +use crate::{FormatNodeRule, PyFormatter}; use ruff_formatter::{write, Buffer, FormatResult}; use rustpython_parser::ast::ExprListComp; @@ -10,11 +13,25 @@ use rustpython_parser::ast::ExprListComp; pub struct FormatExprListComp; impl FormatNodeRule for FormatExprListComp { - fn fmt_fields(&self, _item: &ExprListComp, f: &mut PyFormatter) -> FormatResult<()> { + fn fmt_fields(&self, item: &ExprListComp, f: &mut PyFormatter) -> FormatResult<()> { + let ExprListComp { + range: _, + elt, + generators, + } = item; + + let joined = format_with(|f| { + f.join_with(soft_line_break_or_space()) + .entries(generators.iter().formatted()) + .finish() + }); + write!( f, - [not_yet_implemented_custom_text( - "[NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []]" + [parenthesized( + "[", + &group(&self::format_args!(&elt.format(), space(), group(&joined))), + "]" )] ) } diff --git a/crates/ruff_python_formatter/src/other/comprehension.rs b/crates/ruff_python_formatter/src/other/comprehension.rs index edf64d8bee9dda..9b8c4dd0a28e55 100644 --- a/crates/ruff_python_formatter/src/other/comprehension.rs +++ b/crates/ruff_python_formatter/src/other/comprehension.rs @@ -1,12 +1,115 @@ -use crate::{not_yet_implemented, FormatNodeRule, PyFormatter}; +use crate::comments::{leading_comments, trailing_comments, SourceComment}; +use crate::prelude::*; +use crate::AsFormat; +use crate::{FormatNodeRule, PyFormatter}; use ruff_formatter::{write, Buffer, FormatResult}; -use rustpython_parser::ast::Comprehension; +use rustpython_parser::ast::{Comprehension, Ranged}; #[derive(Default)] pub struct FormatComprehension; impl FormatNodeRule for FormatComprehension { fn fmt_fields(&self, item: &Comprehension, f: &mut PyFormatter) -> FormatResult<()> { - write!(f, [not_yet_implemented(item)]) + let Comprehension { + range: _, + target, + iter, + ifs, + is_async, + } = item; + + let comments = f.context().comments().clone(); + let leading_item_comments = comments.leading_comments(item); + + // TODO: why is this needed? + if let Some(leading_item_comment) = leading_item_comments.first() { + if leading_item_comment.line_position().is_own_line() { + hard_line_break().fmt(f)?; + } + } + leading_comments(leading_item_comments).fmt(f)?; + + if *is_async { + write!(f, [text("async"), soft_line_break_or_space()])?; + } + + let dangling_item_comments = comments.dangling_comments(item); + + let (before_target_comments, before_in_comments) = dangling_item_comments.split_at( + dangling_item_comments + .partition_point(|comment| comment.slice().end() < target.range().start()), + ); + + let trailing_in_comments = comments.dangling_comments(iter); + write!( + f, + [ + text("for"), + trailing_comments(before_target_comments), + group(&self::format_args!( + soft_line_break_or_space(), + &target.format(), + soft_line_break_or_space(), + leading_comments(before_in_comments), + text("in"), + trailing_comments(trailing_in_comments), + soft_line_break_or_space(), + iter.format(), + )), + ] + )?; + if !ifs.is_empty() { + let joined = format_with(|f| { + let mut joiner = f.join_with(soft_line_break_or_space()); + for if_ in ifs { + let dangling_if_comments = comments.dangling_comments(if_); + + let (own_line_if_comments, end_of_line_if_comments) = dangling_if_comments + .split_at( + dangling_if_comments + .partition_point(|comment| comment.line_position().is_own_line()), + ); + joiner.entry(&group(&self::format_args!( + leading_comments(own_line_if_comments), + text("if"), + trailing_comments(end_of_line_if_comments), + soft_line_break_or_space(), + if_.format(), + ))); + } + joiner.finish() + }); + + write!(f, [soft_line_break_or_space(), group(&joined)])?; + } + Ok(()) + } + + fn fmt_leading_comments( + &self, + _item: &Comprehension, + _f: &mut PyFormatter, + ) -> FormatResult<()> { + Ok(()) + } +} + +#[derive(Debug)] +struct FormatLeadingCommentsSpacing<'a> { + comments: &'a [SourceComment], +} + +impl Format> for FormatLeadingCommentsSpacing<'_> { + fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { + if let Some(first) = self.comments.first() { + if first.line_position().is_own_line() { + // Insert a newline after the colon so the comment ends up on its own line + hard_line_break().fmt(f)?; + } else { + // Insert the two spaces between the colon and the end-of-line comment after the colon + write!(f, [space(), space()])?; + } + } + Ok(()) } } diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_37__python37.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_37__python37.py.snap index 785d395e0920f6..a02ef09549eafb 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_37__python37.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_37__python37.py.snap @@ -42,7 +42,7 @@ def make_arange(n): ```diff --- Black +++ Ruff -@@ -2,29 +2,21 @@ +@@ -2,29 +2,24 @@ def f(): @@ -60,13 +60,14 @@ def make_arange(n): async def func(): if test: -- out_batched = [ + out_batched = [ - i - async for i in aitertools._async_map( - self.async_inc, arange(8), batch_size=3 - ) -- ] -+ out_batched = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] ++ i async ++ for i in aitertools._async_map(self.async_inc, arange(8), batch_size=3) + ] def awaited_generator_value(n): @@ -95,7 +96,10 @@ def g(): async def func(): if test: - out_batched = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] + out_batched = [ + i async + for i in aitertools._async_map(self.async_inc, arange(8), batch_size=3) + ] def awaited_generator_value(n): diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__pep_572.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__pep_572.py.snap index a5be560338791a..a24dff1ce034ca 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__pep_572.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__pep_572.py.snap @@ -76,7 +76,7 @@ while x := f(x): -y0 = (y1 := f(x)) -foo(x=(y := f(x))) +[NOT_YET_IMPLEMENTED_ExprNamedExpr, y**2, y**3] -+filtered_data = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] ++filtered_data = [y for x in data if (NOT_YET_IMPLEMENTED_ExprNamedExpr) is None] +(NOT_YET_IMPLEMENTED_ExprNamedExpr) +y0 = NOT_YET_IMPLEMENTED_ExprNamedExpr +foo(x=(NOT_YET_IMPLEMENTED_ExprNamedExpr)) @@ -150,7 +150,7 @@ if (NOT_YET_IMPLEMENTED_ExprNamedExpr) is None: if NOT_YET_IMPLEMENTED_ExprNamedExpr: pass [NOT_YET_IMPLEMENTED_ExprNamedExpr, y**2, y**3] -filtered_data = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] +filtered_data = [y for x in data if (NOT_YET_IMPLEMENTED_ExprNamedExpr) is None] (NOT_YET_IMPLEMENTED_ExprNamedExpr) y0 = NOT_YET_IMPLEMENTED_ExprNamedExpr foo(x=(NOT_YET_IMPLEMENTED_ExprNamedExpr)) diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__comments2.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__comments2.py.snap index e7ae03c930383c..a9bf795789a744 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__comments2.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__comments2.py.snap @@ -224,36 +224,44 @@ instruction()#comment with bad spacing if ( self._proc is not None # has the child process finished? -@@ -114,25 +122,9 @@ - # yup +@@ -115,23 +123,31 @@ arg3=True, ) -- lcomp = [ + lcomp = [ - element for element in collection if element is not None # yup # yup # right -- ] -- lcomp2 = [ -- # hello ++ element for # yup ++ element ++ in ++ collection # yup ++ if element is not None # right + ] + lcomp2 = [ + # hello - element -- # yup -- for element in collection -- # right ++ element + # yup + for element in collection + # right - if element is not None -- ] -- lcomp3 = [ -- # This one is actually too long to fit in a single line. ++ if ++ element ++ is not None + ] + lcomp3 = [ + # This one is actually too long to fit in a single line. - element.split("\n", 1)[0] -- # yup -- for element in collection.select_elements() -- # right ++ element.split("\n", 1)[0] + # yup + for element in collection.select_elements() + # right - if element is not None -- ] -+ lcomp = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] -+ lcomp2 = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] -+ lcomp3 = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] ++ if ++ element ++ is not None + ] while True: if False: - continue -@@ -143,7 +135,10 @@ +@@ -143,7 +159,10 @@ # let's return return Node( syms.simple_stmt, @@ -265,7 +273,7 @@ instruction()#comment with bad spacing ) -@@ -158,7 +153,11 @@ +@@ -158,7 +177,11 @@ class Test: def _init_host(self, parsed) -> None: @@ -407,9 +415,33 @@ short # yup arg3=True, ) - lcomp = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] - lcomp2 = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] - lcomp3 = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] + lcomp = [ + element for # yup + element + in + collection # yup + if element is not None # right + ] + lcomp2 = [ + # hello + element + # yup + for element in collection + # right + if + element + is not None + ] + lcomp3 = [ + # This one is actually too long to fit in a single line. + element.split("\n", 1)[0] + # yup + for element in collection.select_elements() + # right + if + element + is not None + ] while True: if False: continue diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__comments3.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__comments3.py.snap index 2812b9d37a68cc..71d23ea6fa54c2 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__comments3.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__comments3.py.snap @@ -60,22 +60,22 @@ def func(): ```diff --- Black +++ Ruff -@@ -6,14 +6,7 @@ - x = """ - a really long string +@@ -8,11 +8,13 @@ """ -- lcomp3 = [ -- # This one is actually too long to fit in a single line. + lcomp3 = [ + # This one is actually too long to fit in a single line. - element.split("\n", 1)[0] -- # yup -- for element in collection.select_elements() -- # right ++ element.split("\n", 1)[0] + # yup + for element in collection.select_elements() + # right - if element is not None -- ] -+ lcomp3 = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] ++ if ++ element ++ is not None + ] # Capture each of the exceptions in the MultiError along with each of their causes and contexts if isinstance(exc_value, MultiError): - embedded = [] ``` ## Ruff Output @@ -89,7 +89,16 @@ def func(): x = """ a really long string """ - lcomp3 = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] + lcomp3 = [ + # This one is actually too long to fit in a single line. + element.split("\n", 1)[0] + # yup + for element in collection.select_elements() + # right + if + element + is not None + ] # Capture each of the exceptions in the MultiError along with each of their causes and contexts if isinstance(exc_value, MultiError): embedded = [] diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap index b743d9c8085275..b41b2acfa461a6 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap @@ -347,10 +347,14 @@ last_call() -{(i**2) for i in (1, 2, 3)} -{(i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))} -{((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)} --[i for i in (1, 2, 3)] --[(i**2) for i in (1, 2, 3)] --[(i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))] --[((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)] ++{NOT_IMPLEMENTED_set_value for value in NOT_IMPLEMENTED_set} ++{NOT_IMPLEMENTED_set_value for value in NOT_IMPLEMENTED_set} ++{NOT_IMPLEMENTED_set_value for value in NOT_IMPLEMENTED_set} ++{NOT_IMPLEMENTED_set_value for value in NOT_IMPLEMENTED_set} + [i for i in (1, 2, 3)] + [(i**2) for i in (1, 2, 3)] + [(i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))] + [((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)] -{i: 0 for i in (1, 2, 3)} -{i: j for i, j in ((1, "a"), (2, "b"), (3, "c"))} -{a: b * 2 for a, b in dictionary.items()} @@ -359,14 +363,6 @@ last_call() - k: v - for k, v in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension -} -+{NOT_IMPLEMENTED_set_value for value in NOT_IMPLEMENTED_set} -+{NOT_IMPLEMENTED_set_value for value in NOT_IMPLEMENTED_set} -+{NOT_IMPLEMENTED_set_value for value in NOT_IMPLEMENTED_set} -+{NOT_IMPLEMENTED_set_value for value in NOT_IMPLEMENTED_set} -+[NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] -+[NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] -+[NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] -+[NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] +{NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value for key, value in NOT_IMPLEMENTED_dict} +{NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value for key, value in NOT_IMPLEMENTED_dict} +{NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value for key, value in NOT_IMPLEMENTED_dict} @@ -397,6 +393,9 @@ last_call() - int, - float, - dict[str, int], +-] +-very_long_variable_name_filters: t.List[ +- t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]], + ( + str, + int, @@ -404,9 +403,6 @@ last_call() + dict[str, int], + ) ] --very_long_variable_name_filters: t.List[ -- t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]], --] -xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore - sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) -) @@ -686,10 +682,10 @@ str or None if (1 if True else 2) else str or bytes or None {NOT_IMPLEMENTED_set_value for value in NOT_IMPLEMENTED_set} {NOT_IMPLEMENTED_set_value for value in NOT_IMPLEMENTED_set} {NOT_IMPLEMENTED_set_value for value in NOT_IMPLEMENTED_set} -[NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] -[NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] -[NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] -[NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] +[i for i in (1, 2, 3)] +[(i**2) for i in (1, 2, 3)] +[(i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))] +[((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)] {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value for key, value in NOT_IMPLEMENTED_dict} {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value for key, value in NOT_IMPLEMENTED_dict} {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value for key, value in NOT_IMPLEMENTED_dict} diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__power_op_spacing.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__power_op_spacing.py.snap index 40cab8fa6b7e38..d315462fd0af4b 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__power_op_spacing.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__power_op_spacing.py.snap @@ -75,7 +75,7 @@ return np.divide( ```diff --- Black +++ Ruff -@@ -15,38 +15,38 @@ +@@ -15,7 +15,7 @@ b = 5 ** f() c = -(5**2) d = 5 ** f["hi"] @@ -84,21 +84,16 @@ return np.divide( f = f() ** 5 g = a.b**c.d h = 5 ** funcs.f() - i = funcs.f() ** 5 - j = super().name ** 5 --k = [(2**idx, value) for idx, value in pairs] -+k = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] - l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001) +@@ -26,7 +26,7 @@ m = [([2**63], [1, 2**63])] n = count <= 10**5 o = settings(max_examples=10**6) -p = {(k, k**2): v**2 for k, v in pairs} --q = [10**i for i in range(6)] +p = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value for key, value in NOT_IMPLEMENTED_dict} -+q = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] + q = [10**i for i in range(6)] r = x**y - a = 5.0**~4.0 +@@ -34,7 +34,7 @@ b = 5.0 ** f() c = -(5.0**2.0) d = 5.0 ** f["hi"] @@ -107,21 +102,15 @@ return np.divide( f = f() ** 5.0 g = a.b**c.d h = 5.0 ** funcs.f() - i = funcs.f() ** 5.0 - j = super().name ** 5.0 --k = [(2.0**idx, value) for idx, value in pairs] -+k = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] - l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001) +@@ -45,7 +45,7 @@ m = [([2.0**63.0], [1.0, 2**63.0])] n = count <= 10**5.0 o = settings(max_examples=10**6.0) -p = {(k, k**2): v**2.0 for k, v in pairs} --q = [10.5**i for i in range(6)] +p = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value for key, value in NOT_IMPLEMENTED_dict} -+q = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] + q = [10.5**i for i in range(6)] - # WE SHOULD DEFINITELY NOT EAT THESE COMMENTS (https://github.com/psf/black/issues/2873) @@ -55,9 +55,11 @@ view.variance, # type: ignore[union-attr] view.sum_of_weights, # type: ignore[union-attr] @@ -164,13 +153,13 @@ g = a.b**c.d h = 5 ** funcs.f() i = funcs.f() ** 5 j = super().name ** 5 -k = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] +k = [(2**idx, value) for idx, value in pairs] l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001) m = [([2**63], [1, 2**63])] n = count <= 10**5 o = settings(max_examples=10**6) p = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value for key, value in NOT_IMPLEMENTED_dict} -q = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] +q = [10**i for i in range(6)] r = x**y a = 5.0**~4.0 @@ -183,13 +172,13 @@ g = a.b**c.d h = 5.0 ** funcs.f() i = funcs.f() ** 5.0 j = super().name ** 5.0 -k = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] +k = [(2.0**idx, value) for idx, value in pairs] l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001) m = [([2.0**63.0], [1.0, 2**63.0])] n = count <= 10**5.0 o = settings(max_examples=10**6.0) p = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value for key, value in NOT_IMPLEMENTED_dict} -q = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] +q = [10.5**i for i in range(6)] # WE SHOULD DEFINITELY NOT EAT THESE COMMENTS (https://github.com/psf/black/issues/2873) diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__slices.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__slices.py.snap index cfa0bebf04e640..848f65c25ea20f 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__slices.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__slices.py.snap @@ -43,7 +43,7 @@ ham[lower + offset : upper + offset] ```diff --- Black +++ Ruff -@@ -4,28 +4,34 @@ +@@ -4,19 +4,21 @@ slice[d::d] slice[0] slice[-1] @@ -68,19 +68,11 @@ ham[lower + offset : upper + offset] +slice[ + (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []) : x +] -+slice[ -+ :: [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] -+] ++slice[ :: [i for i in range(42)]] async def f(): -- slice[await x : [i async for i in arange(42)] : 42] -+ slice[ -+ await x : [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] : 42 -+ ] - - - # These are from PEP-8: +@@ -27,5 +29,5 @@ ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:] ham[lower:upper], ham[lower:upper:], ham[lower::step] # ham[lower+offset : upper+offset] @@ -112,15 +104,11 @@ slice[not so_simple : 1 < val <= 10] slice[ (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []) : x ] -slice[ - :: [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] -] +slice[ :: [i for i in range(42)]] async def f(): - slice[ - await x : [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] : 42 - ] + slice[await x : [i async for i in arange(42)] : 42] # These are from PEP-8: diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__binary.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__binary.py.snap index 40a69332f5efeb..a15bc1a9727e59 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__binary.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__binary.py.snap @@ -277,10 +277,9 @@ aaaaaaaaaaaaaa + { dddddddddddddddd, eeeeeee, } -( - aaaaaaaaaaaaaa - + [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []] -) +aaaaaaaaaaaaaa + [ + a for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +] ( aaaaaaaaaaaaaa + (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []) diff --git a/crates/ruff_python_formatter/tests/snapshots/format@statement__list_comp.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@statement__list_comp.py.snap new file mode 100644 index 00000000000000..343c422c2220eb --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/format@statement__list_comp.py.snap @@ -0,0 +1,70 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/list_comp.py +--- +## Input +```py +[i for i in []] + +[i for i in [1,]] + +[ + a # a + for # for + c # c + in # in + e # e +] + +[ + # before a + a # a + # before for + for # for + # before c + c # c + # before in + in # in + # before e + e + # before end +] +``` + +## Output +```py +[i for i in []] + +[ + i for + i + in + [ + 1, + ] +] + +[ + a for # a # for + c # c + in # in + e # e +] + +[ + # before a + a # a + # before for + for # for + # before c + c # c + # before in + in # in + # before e + e + # before end +] +``` + + +