Skip to content

Commit 94566e5

Browse files
thomasklemmclaude
andauthored
Fix stabby lambdas dropping semicolons in multi-statement bodies (#872)
* Fix stabby lambdas dropping semicolons in multi-statement bodies Stabby lambdas (`->`) with multiple statements separated by semicolons were formatted inline with soft newlines, causing the semicolons to be silently dropped and producing invalid Ruby. Now uses the same multi-statement expansion logic as regular brace blocks: hard newlines force multi-statement bodies to expand to multiple lines. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Extract format_brace_block_body helper to deduplicate block formatting Replaces duplicated multi-statement body logic in format_block_node and format_lambda_node with a shared helper. Also eliminates unsafe .unwrap() calls by using if-let pattern matching instead of is_some_and + unwrap. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c031d6b commit 94566e5

3 files changed

Lines changed: 85 additions & 63 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-> { a; b }
2+
3+
->(x) { puts x; x + 1 }
4+
5+
->(p) { p.id; {kpis: {}, daily_summary: []} }
6+
7+
x = -> { first; second; third }
8+
9+
-> { only_one }
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-> {
2+
a
3+
b
4+
}
5+
6+
-> (x) {
7+
puts(x)
8+
x + 1
9+
}
10+
11+
-> (p) {
12+
p.id
13+
{kpis: {}, daily_summary: []}
14+
}
15+
16+
x = -> {
17+
first
18+
second
19+
third
20+
}
21+
22+
-> { only_one }

librubyfmt/src/format_prism.rs

Lines changed: 54 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2703,6 +2703,50 @@ fn format_assoc_splat_node<'src>(
27032703
});
27042704
}
27052705

2706+
fn format_brace_block_body<'src>(
2707+
ps: &mut ParserState<'src>,
2708+
body: Option<prism::Node<'src>>,
2709+
opening_start_offset: usize,
2710+
closing_end_offset: usize,
2711+
end_offset: usize,
2712+
) {
2713+
if let Some(body) = body {
2714+
if let Some(statements_node) = body.as_statements_node() {
2715+
let statements = statements_node.body();
2716+
if statements.len() > 1 {
2717+
ps.emit_newline();
2718+
ps.emit_indent();
2719+
ps.with_start_of_line(false, |ps| {
2720+
let mut peekable = statements.iter().peekable();
2721+
while let Some(node) = peekable.next() {
2722+
format_node(ps, node);
2723+
ps.emit_soft_newline();
2724+
if peekable.peek().is_some() {
2725+
ps.emit_soft_indent();
2726+
}
2727+
}
2728+
ps.shift_comments();
2729+
});
2730+
} else {
2731+
ps.with_start_of_line(false, |ps| {
2732+
if let Some(node) = statements.first() {
2733+
ps.emit_soft_newline();
2734+
ps.emit_soft_indent();
2735+
format_node(ps, node);
2736+
ps.emit_soft_newline();
2737+
}
2738+
});
2739+
}
2740+
}
2741+
} else if ps.has_comment_in_offset_span(opening_start_offset, closing_end_offset) {
2742+
ps.emit_soft_newline();
2743+
}
2744+
2745+
ps.dedent(|ps| ps.emit_soft_indent());
2746+
ps.wind_dumping_comments_until_offset(end_offset);
2747+
ps.shift_comments();
2748+
}
2749+
27062750
fn format_block_node<'src>(ps: &mut ParserState<'src>, block_node: prism::BlockNode<'src>) {
27072751
if block_node.opening_loc().as_slice() == b"do" {
27082752
ps.new_block(|ps| {
@@ -2746,50 +2790,13 @@ fn format_block_node<'src>(ps: &mut ParserState<'src>, block_node: prism::BlockN
27462790
format_node(ps, parameters);
27472791
}
27482792

2749-
if let Some(body) = block_node.body() {
2750-
let has_multiple_statements = body
2751-
.as_statements_node()
2752-
.is_some_and(|statements_node| statements_node.body().len() > 1);
2753-
if has_multiple_statements {
2754-
ps.emit_newline();
2755-
ps.emit_indent();
2756-
ps.with_start_of_line(false, |ps| {
2757-
let statements = body.as_statements_node().unwrap().body();
2758-
let mut peekable = statements.iter().peekable();
2759-
while let Some(node) = peekable.next() {
2760-
format_node(ps, node);
2761-
ps.emit_soft_newline();
2762-
if peekable.peek().is_some() {
2763-
ps.emit_soft_indent();
2764-
}
2765-
}
2766-
ps.shift_comments();
2767-
});
2768-
} else {
2769-
ps.with_start_of_line(false, |ps| {
2770-
if let Some(node) = body.as_statements_node().unwrap().body().first() {
2771-
ps.emit_soft_newline();
2772-
ps.emit_soft_indent();
2773-
format_node(ps, node);
2774-
ps.emit_soft_newline();
2775-
}
2776-
});
2777-
}
2778-
} else if ps.has_comment_in_offset_span(
2793+
format_brace_block_body(
2794+
ps,
2795+
block_node.body(),
27792796
block_node.opening_loc().start_offset(),
27802797
block_node.closing_loc().end_offset(),
2781-
) {
2782-
// Even if there's no `body` node -- that is, there are no statements in the block --
2783-
// we still need to look for comments and multiline if they're present.
2784-
// Note that this is a soft newline, which are special-cased in breakables to correctly handle
2785-
// comments, so we use one here instead of a hard newline.
2786-
ps.emit_soft_newline();
2787-
}
2788-
2789-
// `inline_breakable_of` doesn't handle the indentation for the closing delimeter for us.
2790-
ps.dedent(|ps| ps.emit_soft_indent());
2791-
ps.wind_dumping_comments_until_offset(block_node.location().end_offset());
2792-
ps.shift_comments();
2798+
block_node.location().end_offset(),
2799+
);
27932800
});
27942801
}
27952802
}
@@ -4318,29 +4325,13 @@ fn format_lambda_node<'src>(ps: &mut ParserState<'src>, lambda_node: prism::Lamb
43184325
} else {
43194326
ps.emit_space();
43204327
ps.inline_breakable_of(BreakableDelims::for_brace_block(), |ps| {
4321-
if let Some(body) = lambda_node.body() {
4322-
ps.with_start_of_line(false, |ps| {
4323-
let statements = body.as_statements_node().unwrap().body();
4324-
if !statements.is_empty() {
4325-
ps.emit_soft_newline();
4326-
for node in statements.iter() {
4327-
ps.emit_soft_indent();
4328-
format_node(ps, node);
4329-
ps.emit_soft_newline();
4330-
}
4331-
ps.shift_comments();
4332-
}
4333-
});
4334-
} else if ps.has_comment_in_offset_span(
4328+
format_brace_block_body(
4329+
ps,
4330+
lambda_node.body(),
43354331
lambda_node.opening_loc().start_offset(),
43364332
lambda_node.closing_loc().end_offset(),
4337-
) {
4338-
ps.emit_soft_newline();
4339-
}
4340-
4341-
ps.dedent(|ps| ps.emit_soft_indent());
4342-
ps.wind_dumping_comments_until_offset(lambda_node.location().end_offset());
4343-
ps.shift_comments();
4333+
lambda_node.location().end_offset(),
4334+
);
43444335
});
43454336
}
43464337
});

0 commit comments

Comments
 (0)