ruff/crates/ruff_python_formatter/tests/snapshots
Dylan 4e1cf5747a
Fluent formatting of method chains (#21369)
This PR implements a modification (in preview) to fluent formatting for
method chains: We break _at_ the first call instead of _after_.

For example, we have the following diff between `main` and this PR (with
`line-length=8` so I don't have to stretch out the text):

```diff
 x = (
-    df.merge()
+    df
+    .merge()
     .groupby()
     .agg()
     .filter()
 )
```

## Explanation of current implementation

Recall that we traverse the AST to apply formatting. A method chain,
while read left-to-right, is stored in the AST "in reverse". So if we
start with something like

```python
a.b.c.d().e.f()
```

then the first syntax node we meet is essentially `.f()`. So we have to
peek ahead. And we actually _already_ do this in our current fluent
formatting logic: we peek ahead to count how many calls we have in the
chain to see whether we should be using fluent formatting or now.

In this implementation, we actually _record_ this number inside the enum
for `CallChainLayout`. That is, we make the variant `Fluent` hold an
`AttributeState`. This state can either be:

- The number of call-like attributes preceding the current attribute
- The state `FirstCallOrSubscript` which means we are at the first
call-like attribute in the chain (reading from left to right)
- The state `BeforeFirstCallOrSubscript` which means we are in the
"first group" of attributes, preceding that first call.

In our example, here's what it looks like at each attribute:

```
a.b.c.d().e.f @ Fluent(CallsOrSubscriptsPreceding(1))
a.b.c.d().e @ Fluent(CallsOrSubscriptsPreceding(1))
a.b.c.d @ Fluent(FirstCallOrSubscript)
a.b.c @ Fluent(BeforeFirstCallOrSubscript)
a.b @ Fluent(BeforeFirstCallOrSubscript)
```

Now, as we descend down from the parent expression, we pass along this
little piece of state and modify it as we go to track where we are. This
state doesn't do anything except when we are in `FirstCallOrSubscript`,
in which case we add a soft line break.

Closes #8598

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-12-15 09:29:50 -06:00
..
black_compatibility@cases__allow_empty_first_line.py.snap
black_compatibility@cases__backslash_before_indent.py.snap
black_compatibility@cases__cantfit.py.snap Update Black tests (#20794) 2025-10-14 10:14:59 -04:00
black_compatibility@cases__class_blank_parentheses.py.snap
black_compatibility@cases__comment_after_escaped_newline.py.snap
black_compatibility@cases__comments2.py.snap
black_compatibility@cases__comments6.py.snap
black_compatibility@cases__comments9.py.snap
black_compatibility@cases__comments_in_blocks.py.snap
black_compatibility@cases__comments_in_double_parens.py.snap
black_compatibility@cases__composition.py.snap
black_compatibility@cases__composition_no_trailing_comma.py.snap
black_compatibility@cases__conditional_expression.py.snap
black_compatibility@cases__docstring_no_string_normalization.py.snap
black_compatibility@cases__dummy_implementations.py.snap
black_compatibility@cases__empty_lines.py.snap
black_compatibility@cases__expression.py.snap
black_compatibility@cases__f_docstring.py.snap
black_compatibility@cases__fmtonoff.py.snap
black_compatibility@cases__fmtonoff4.py.snap
black_compatibility@cases__fmtonoff5.py.snap
black_compatibility@cases__fmtpass_imports.py.snap
black_compatibility@cases__fmtskip5.py.snap
black_compatibility@cases__fmtskip9.py.snap
black_compatibility@cases__fmtskip10.py.snap Respect `fmt: skip` for compound statements on single line (#20633) 2025-11-18 12:02:09 -06:00
black_compatibility@cases__form_feeds.py.snap
black_compatibility@cases__fstring.py.snap Avoid reusing nested, interpolated quotes before Python 3.12 (#20930) 2025-10-17 08:49:16 -04:00
black_compatibility@cases__fstring_quotations.py.snap Update Black tests (#20794) 2025-10-14 10:14:59 -04:00
black_compatibility@cases__funcdef_return_type_trailing_comma.py.snap
black_compatibility@cases__function.py.snap
black_compatibility@cases__function2.py.snap
black_compatibility@cases__function_trailing_comma.py.snap
black_compatibility@cases__generics_wrapping.py.snap Update Black tests (#20794) 2025-10-14 10:14:59 -04:00
black_compatibility@cases__ignore_pyi.pyi.snap
black_compatibility@cases__keep_newline_after_match.py.snap
black_compatibility@cases__line_ranges_diff_edge_case.py.snap
black_compatibility@cases__line_ranges_fmt_off_decorator.py.snap
black_compatibility@cases__long_strings__type_annotations.py.snap Update Black tests (#20794) 2025-10-14 10:14:59 -04:00
black_compatibility@cases__long_strings_flag_disabled.py.snap
black_compatibility@cases__multiline_consecutive_open_parentheses_ignore.py.snap
black_compatibility@cases__nested_stub.pyi.snap
black_compatibility@cases__pattern_matching_style.py.snap
black_compatibility@cases__pattern_matching_trailing_comma.py.snap
black_compatibility@cases__pattern_matching_with_if_stmt.py.snap
black_compatibility@cases__pep604_union_types_line_breaks.py.snap Update Black tests (#20794) 2025-10-14 10:14:59 -04:00
black_compatibility@cases__pep_572_remove_parens.py.snap
black_compatibility@cases__pep_701.py.snap Update Black tests (#20794) 2025-10-14 10:14:59 -04:00
black_compatibility@cases__prefer_rhs_split.py.snap
black_compatibility@cases__prefer_rhs_split_reformatted.py.snap Update Black tests (#20794) 2025-10-14 10:14:59 -04:00
black_compatibility@cases__preview_cantfit.py.snap
black_compatibility@cases__preview_cantfit_string.py.snap
black_compatibility@cases__preview_comments7.py.snap Update Black tests (#20794) 2025-10-14 10:14:59 -04:00
black_compatibility@cases__preview_hug_parens_with_braces_and_square_brackets.py.snap
black_compatibility@cases__preview_hug_parens_with_braces_and_square_brackets_no_ll1.py.snap
black_compatibility@cases__preview_import_line_collapse.py.snap Update Black tests (#20794) 2025-10-14 10:14:59 -04:00
black_compatibility@cases__preview_long_dict_values.py.snap Fluent formatting of method chains (#21369) 2025-12-15 09:29:50 -06:00
black_compatibility@cases__preview_long_strings.py.snap Keep lambda parameters on one line and parenthesize the body if it expands (#21385) 2025-12-12 12:02:25 -05:00
black_compatibility@cases__preview_long_strings__east_asian_width.py.snap
black_compatibility@cases__preview_long_strings__edge_case.py.snap
black_compatibility@cases__preview_long_strings__regression.py.snap Update Black tests (#20794) 2025-10-14 10:14:59 -04:00
black_compatibility@cases__preview_long_strings__type_annotations.py.snap
black_compatibility@cases__preview_multiline_strings.py.snap Keep lambda parameters on one line and parenthesize the body if it expands (#21385) 2025-12-12 12:02:25 -05:00
black_compatibility@cases__preview_remove_multiline_lone_list_item_parens.py.snap Update Black tests (#20794) 2025-10-14 10:14:59 -04:00
black_compatibility@cases__preview_return_annotation_brackets_string.py.snap
black_compatibility@cases__preview_wrap_comprehension_in.py.snap Update Black tests (#20794) 2025-10-14 10:14:59 -04:00
black_compatibility@cases__raw_docstring.py.snap
black_compatibility@cases__raw_docstring_no_string_normalization.py.snap
black_compatibility@cases__remove_await_parens.py.snap
black_compatibility@cases__remove_except_parens.py.snap
black_compatibility@cases__remove_for_brackets.py.snap
black_compatibility@cases__remove_lone_list_item_parens.py.snap Update Black tests (#20794) 2025-10-14 10:14:59 -04:00
black_compatibility@cases__remove_newline_after_code_block_open.py.snap
black_compatibility@cases__remove_redundant_parens_in_case_guard.py.snap
black_compatibility@cases__return_annotation_brackets.py.snap
black_compatibility@cases__single_line_format_skip_with_multiple_comments.py.snap
black_compatibility@cases__stub.pyi.snap
black_compatibility@cases__torture.py.snap
black_compatibility@cases__trailing_commas_in_leading_parts.py.snap
black_compatibility@cases__tupleassign.py.snap
black_compatibility@cases__type_param_defaults.py.snap Update Black tests (#20794) 2025-10-14 10:14:59 -04:00
black_compatibility@miscellaneous__blackd_diff.py.snap
black_compatibility@miscellaneous__debug_visitor.py.snap
black_compatibility@miscellaneous__string_quotes.py.snap
format@blank_line_before_class_docstring.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@carriage_return__string.py.snap
format@docstring.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@docstring_chaperones.py.snap
format@docstring_code_examples.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@docstring_code_examples_crlf.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@docstring_code_examples_dynamic_line_width.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@docstring_newlines.py.snap
format@docstring_non_visible_characters.py.snap
format@docstring_tab_indentation.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@empty_multiple_trailing_newlines.py.snap
format@empty_now_newline.py.snap
format@empty_trailing_newline.py.snap
format@empty_whitespace.py.snap
format@expression__annotated_assign.py.snap
format@expression__attribute.py.snap Avoid syntax error when formatting attribute expressions with outer parentheses, parenthesized value, and trailing comment on value (#20418) 2025-11-17 09:11:36 -06:00
format@expression__await.py.snap Fluent formatting of method chains (#21369) 2025-12-15 09:29:50 -06:00
format@expression__binary.py.snap
format@expression__binary_implicit_string.py.snap
format@expression__binary_pow_spacing.py.snap
format@expression__boolean_operation.py.snap
format@expression__bytes.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@expression__call.py.snap Fluent formatting of method chains (#21369) 2025-12-15 09:29:50 -06:00
format@expression__compare.py.snap
format@expression__dict.py.snap
format@expression__dict_comp.py.snap
format@expression__fstring.py.snap Avoid reusing nested, interpolated quotes before Python 3.12 (#20930) 2025-10-17 08:49:16 -04:00
format@expression__fstring_preview.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@expression__generator_exp.py.snap
format@expression__hug.py.snap
format@expression__if.py.snap
format@expression__join_implicit_concatenated_string.py.snap
format@expression__join_implicit_concatenated_string_assignment.py.snap
format@expression__join_implicit_concatenated_string_preserve.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@expression__lambda.py.snap Fluent formatting of method chains (#21369) 2025-12-15 09:29:50 -06:00
format@expression__list.py.snap
format@expression__list_comp.py.snap
format@expression__named_expr.py.snap
format@expression__number.py.snap
format@expression__optional_parentheses_comments.py.snap
format@expression__set_comp.py.snap
format@expression__slice.py.snap
format@expression__split_empty_brackets.py.snap Fluent formatting of method chains (#21369) 2025-12-15 09:29:50 -06:00
format@expression__starred.py.snap
format@expression__string.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@expression__subscript.py.snap
format@expression__tstring.py.snap
format@expression__tuple.py.snap
format@expression__unary.py.snap Fix panic when formatting comments in unary expressions (#21501) 2025-11-18 10:48:14 -05:00
format@expression__unsplittable.py.snap
format@expression__yield.py.snap
format@expression__yield_from.py.snap
format@f-string-carriage-return-newline.py.snap
format@fluent.py.snap Fluent formatting of method chains (#21369) 2025-12-15 09:29:50 -06:00
format@fmt_on_off__comments.py.snap
format@fmt_on_off__empty_file.py.snap
format@fmt_on_off__fmt_off_docstring.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@fmt_on_off__fmt_off_unclosed_deep_nested_trailing_comment.py.snap
format@fmt_on_off__fmt_off_unclosed_trailing_comment.py.snap
format@fmt_on_off__form_feed.py.snap
format@fmt_on_off__indent.py.snap Restore `indent.py` (#21094) 2025-10-27 10:34:29 +00:00
format@fmt_on_off__last_statement.py.snap
format@fmt_on_off__mixed_space_and_tab.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@fmt_on_off__newlines.py.snap
format@fmt_on_off__no_fmt_on.py.snap
format@fmt_on_off__off_on_off_on.py.snap
format@fmt_on_off__simple.py.snap
format@fmt_on_off__trailing_comments.py.snap
format@fmt_on_off__trailing_semicolon.py.snap
format@fmt_on_off__yapf.py.snap
format@fmt_skip__compound_one_liners.py.snap Respect `fmt: skip` for compound statements on single line (#20633) 2025-11-18 12:02:09 -06:00
format@fmt_skip__decorators.py.snap
format@fmt_skip__docstrings.py.snap Respect `fmt: skip` for compound statements on single line (#20633) 2025-11-18 12:02:09 -06:00
format@fmt_skip__match.py.snap
format@fmt_skip__or_else.py.snap
format@fmt_skip__parentheses.py.snap
format@fmt_skip__reason.py.snap
format@fmt_skip__trailing_semi.py.snap
format@fmt_skip__type_params.py.snap
format@form_feed.py.snap Adjust own-line comment placement between branches (#21185) 2025-11-17 07:30:34 -06:00
format@module_dangling_comment1.py.snap
format@module_dangling_comment2.py.snap
format@multiline_string_deviations.py.snap Keep lambda parameters on one line and parenthesize the body if it expands (#21385) 2025-12-12 12:02:25 -05:00
format@newlines.py.snap Allow newlines after function headers without docstrings (#21110) 2025-10-31 14:53:40 -04:00
format@newlines.pyi.snap
format@notebook_docstring.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@parentheses__call_chains.py.snap Fluent formatting of method chains (#21369) 2025-12-15 09:29:50 -06:00
format@parentheses__expression_parentheses_comments.py.snap Fix panic when formatting comments in unary expressions (#21501) 2025-11-18 10:48:14 -05:00
format@parentheses__nested.py.snap
format@parentheses__opening_parentheses_comment_empty.py.snap
format@parentheses__opening_parentheses_comment_value.py.snap
format@pattern__pattern_maybe_parenthesize.py.snap
format@pattern_match_regression_brackets.py.snap
format@preview.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@quote_style.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@range_formatting__ancestory.py.snap
format@range_formatting__clause_header.py.snap Fix finding keyword range for clause header after statement ending with semicolon (#21067) 2025-10-27 09:52:17 -05:00
format@range_formatting__comment_only_range.py.snap
format@range_formatting__decorators.py.snap
format@range_formatting__docstring_code_examples.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@range_formatting__empty_file.py.snap
format@range_formatting__empty_range.py.snap
format@range_formatting__end_of_file.py.snap
format@range_formatting__fmt_on_off.py.snap
format@range_formatting__indent.py.snap Allow newlines after function headers without docstrings (#21110) 2025-10-31 14:53:40 -04:00
format@range_formatting__leading_comments.py.snap
format@range_formatting__leading_trailing_comments.py.snap
format@range_formatting__module.py.snap
format@range_formatting__parentheses.py.snap
format@range_formatting__range_narrowing.py.snap
format@range_formatting__regressions.py.snap
format@range_formatting__same_line_body.py.snap
format@range_formatting__stub.pyi.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@range_formatting__trailing_comments.py.snap
format@range_formatting__whitespace_only_range.py.snap
format@skip_magic_trailing_comma.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@statement__ann_assign.py.snap
format@statement__assert.py.snap
format@statement__assign.py.snap
format@statement__assignment_split_value_first.py.snap
format@statement__aug_assign.py.snap
format@statement__break.py.snap
format@statement__class_definition.py.snap
format@statement__delete.py.snap
format@statement__ellipsis.pyi.snap
format@statement__for.py.snap
format@statement__function.py.snap
format@statement__global.py.snap
format@statement__if.py.snap Adjust own-line comment placement between branches (#21185) 2025-11-17 07:30:34 -06:00
format@statement__import.py.snap
format@statement__import_from.py.snap
format@statement__long_type_annotations.py.snap
format@statement__match.py.snap Avoid extra parentheses for long `match` patterns with `as` captures (#21176) 2025-11-03 17:06:52 -05:00
format@statement__module_comment.py.snap
format@statement__nonlocal.py.snap
format@statement__raise.py.snap
format@statement__return.py.snap
format@statement__return_annotation.py.snap
format@statement__return_type_no_parameters.py.snap
format@statement__return_type_parameters.py.snap
format@statement__stub_functions_trailing_comments.py.snap
format@statement__top_level.py.snap
format@statement__top_level.pyi.snap
format@statement__try.py.snap Remove parentheses around multiple exception types on Python 3.14+ (#20768) 2025-10-14 11:17:45 -04:00
format@statement__type_alias.py.snap
format@statement__while.py.snap Adjust own-line comment placement between branches (#21185) 2025-11-17 07:30:34 -06:00
format@statement__with.py.snap Fix syntax error false positives on parenthesized context managers (#20846) 2025-10-13 14:13:27 -04:00
format@statement__with_39.py.snap
format@stub_files__blank_line_after_nested_stub_class.pyi.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@stub_files__blank_line_after_nested_stub_class_eof.pyi.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@stub_files__comments.pyi.snap
format@stub_files__decorated_class_after_function.pyi.snap
format@stub_files__nesting.pyi.snap
format@stub_files__suite.pyi.snap
format@stub_files__top_level.pyi.snap
format@tab_width.py.snap Update default and latest Python versions for 3.14 (#20725) 2025-10-07 12:23:11 -04:00
format@trailing_comments.py.snap
format@trivia.py.snap