Skip to content

Commit 0571ee5

Browse files
authored
[fix] Multiline call chains with multi-statement blocks (#896)
* Fixtures * [fix] Multiline call chains with multi-statement blocks
1 parent 20007cd commit 0571ee5

3 files changed

Lines changed: 97 additions & 10 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
some_array.map { |x| a; b }.select { }
2+
3+
some_array.map { |x|
4+
a
5+
b
6+
}.select { }
7+
8+
foo.bar.baz { |x| a; b }
9+
10+
foo.bar do |x| a; b end.select { }
11+
12+
foo.items.map { p(_1) }.each { _1.call! }
13+
14+
foo.items.map { p(_1) }.last
15+
16+
hashes.sort_by { |hsh| hsh[:start_time] }.reverse
17+
18+
-> { a; b }.bar.baz
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
some_array
2+
.map { |x|
3+
a
4+
b
5+
}
6+
.select { }
7+
8+
some_array
9+
.map { |x|
10+
a
11+
b
12+
}
13+
.select { }
14+
15+
foo.bar.baz { |x|
16+
a
17+
b
18+
}
19+
20+
foo
21+
.bar do |x|
22+
a
23+
b
24+
end
25+
.select { }
26+
27+
foo.items.map { p(_1) }.each { _1.call! }
28+
29+
foo.items.map { p(_1) }.last
30+
31+
hashes.sort_by { |hsh| hsh[:start_time] }.reverse
32+
33+
-> {
34+
a
35+
b
36+
}
37+
.bar
38+
.baz

librubyfmt/src/format_prism.rs

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2270,9 +2270,9 @@ fn format_call_chain_segments<'src>(
22702270
})
22712271
});
22722272

2273-
let is_user_multilined = call_chain_elements_are_user_multilined(ps, &chain_elements);
2273+
let should_multiline = call_chain_should_be_multilined(ps, &chain_elements);
22742274

2275-
ps.breakable_call_chain_of(is_user_multilined, |ps| {
2275+
ps.breakable_call_chain_of(should_multiline, |ps| {
22762276
// Recurse and format previous segments inside this breakable
22772277
format_call_chain_segments(ps, segments);
22782278

@@ -2426,10 +2426,7 @@ fn format_call_body<'src>(
24262426
}
24272427
}
24282428

2429-
fn call_chain_elements_are_user_multilined(
2430-
ps: &ParserState,
2431-
call_chain_elements: &[prism::Node],
2432-
) -> bool {
2429+
fn call_chain_should_be_multilined(ps: &ParserState, call_chain_elements: &[prism::Node]) -> bool {
24332430
// Making a mutable copy since we may pop some items off later
24342431
let mut call_chain_elements = call_chain_elements;
24352432

@@ -2475,7 +2472,9 @@ fn call_chain_elements_are_user_multilined(
24752472
let has_comment_between_expression_and_call =
24762473
ps.has_comments_in_line(leading_expr_end_line + 1, first_call_start_line + 1);
24772474

2478-
if !has_comment_between_expression_and_call {
2475+
if !has_comment_between_expression_and_call
2476+
&& !element_forces_chain_to_multiline(&call_chain_elements[0])
2477+
{
24792478
call_chain_elements = &call_chain_elements[1..];
24802479
}
24812480
}
@@ -2514,18 +2513,50 @@ fn call_chain_elements_are_user_multilined(
25142513
}
25152514
};
25162515

2517-
call_chain_elements[1..].iter().any(|cce| {
2518-
start_line
2516+
call_chain_elements[1..].iter().enumerate().any(|(i, cce)| {
2517+
let operator_on_new_line = start_line
25192518
!= cce
25202519
.as_call_node()
25212520
.unwrap()
25222521
.call_operator_loc()
25232522
.map_or(start_line, |loc| {
25242523
ps.get_line_number_for_offset(loc.start_offset())
2525-
})
2524+
});
2525+
if operator_on_new_line {
2526+
return true;
2527+
}
2528+
2529+
// If the *previous* element will be forced to render multi-line (a do/end
2530+
// block, a brace block with multiple statements, or the equivalent on a
2531+
// lambda receiver), the chain itself must break too — otherwise the next
2532+
// pass would see the calls on different lines and rewrite the chain,
2533+
// costing idempotency.
2534+
element_forces_chain_to_multiline(&call_chain_elements[i])
25262535
})
25272536
}
25282537

2538+
fn element_forces_chain_to_multiline(element: &prism::Node) -> bool {
2539+
if let Some(block) = element
2540+
.as_call_node()
2541+
.and_then(|c| c.block())
2542+
.and_then(|b| b.as_block_node())
2543+
{
2544+
return block_body_renders_multiline(block.opening_loc().as_slice(), block.body());
2545+
} else if let Some(lambda) = element.as_lambda_node() {
2546+
return block_body_renders_multiline(lambda.opening_loc().as_slice(), lambda.body());
2547+
}
2548+
2549+
false
2550+
}
2551+
2552+
fn block_body_renders_multiline(opening: &[u8], body: Option<prism::Node>) -> bool {
2553+
if opening == b"do" {
2554+
return true;
2555+
}
2556+
body.and_then(|n| n.as_statements_node())
2557+
.is_some_and(|statements| statements.body().len() > 1)
2558+
}
2559+
25292560
/// Finds an appropriate starting loc for a call node inside a call chain.
25302561
/// In the middle of a chain, the node's `location().start_offset()` is always the
25312562
/// beginning of the chain, since `receiver()` is the entirety of the chain so far.

0 commit comments

Comments
 (0)