From cb24b0e8938df4412be22ce0a7f33eaca714bfbd Mon Sep 17 00:00:00 2001 From: Not-Sarthak Date: Sun, 10 May 2026 11:55:15 +0530 Subject: [PATCH] fix(autofmt): don't duplicate pretty continuation lines after multi-line source match When prettier_please breaks a statement at a different line position than the source did, the multi-line accumulator in write_partial_expr consumed every source line for the logical statement but the outer iterator kept walking pretty lines, re-emitting trailing continuations (e.g. `.clone();`) and advancing the source pointer past unrelated statements - producing invalid Rust. Convert pretty.lines() to a peekable iterator and, after emitting a multi-line verbatim source block whose accumulated content extends beyond the matched pretty line, consume the following pretty continuation lines whose content is already covered by the accumulated source. Fixes #5523. --- packages/autofmt/src/writer.rs | 26 ++++++++++++++++++- packages/autofmt/tests/samples.rs | 6 +++++ .../tests/samples/cfg_let_event_block.rsx | 24 +++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 packages/autofmt/tests/samples/cfg_let_event_block.rsx diff --git a/packages/autofmt/src/writer.rs b/packages/autofmt/src/writer.rs index 00c33180ce..b7ad40b3d2 100644 --- a/packages/autofmt/src/writer.rs +++ b/packages/autofmt/src/writer.rs @@ -888,7 +888,8 @@ impl<'a> Writer<'a> { if src_lines.peek().is_none() { out = pretty; } else { - for line in pretty.lines() { + let mut pretty_iter = pretty.lines().peekable(); + while let Some(line) = pretty_iter.next() { let trimmed = line.trim(); let compacted = line.replace(" ", "").replace(",", ""); @@ -1062,6 +1063,29 @@ impl<'a> Writer<'a> { out.push('\n'); } } + + // Skip pretty continuation lines whose content was already + // emitted as part of the verbatim multi-line source. + if acc.len() > compacted.len() { + let mut consumed_compact = compacted.clone(); + while consumed_compact.len() < acc.len() { + let Some(next_pretty) = pretty_iter.peek() else { + break; + }; + let next_trimmed = next_pretty.trim(); + if next_trimmed.is_empty() || next_trimmed.starts_with("//") { + break; + } + let next_compact = next_pretty.replace(" ", "").replace(",", ""); + let candidate = format!("{consumed_compact}{next_compact}"); + if acc.starts_with(&candidate) { + consumed_compact = candidate; + pretty_iter.next(); + continue; + } + break; + } + } } else { // Single line - output pretty line and capture inline comments out.push_str(line); diff --git a/packages/autofmt/tests/samples.rs b/packages/autofmt/tests/samples.rs index d38cfef931..a143f159bb 100644 --- a/packages/autofmt/tests/samples.rs +++ b/packages/autofmt/tests/samples.rs @@ -74,6 +74,7 @@ twoway![ commented_rsx_block_only, commented_rsx_block_between, commented_rsx_block_deep, + cfg_let_event_block, ]; fn assert_idempotent(src: &str) { @@ -109,3 +110,8 @@ fn comments_attr_expr_blocks_is_idempotent() { fn comments_expr_with_strings_is_idempotent() { assert_idempotent(include_str!("./samples/comments_expr_with_strings.rsx")); } + +#[test] +fn cfg_let_event_block_is_idempotent() { + assert_idempotent(include_str!("./samples/cfg_let_event_block.rsx")); +} diff --git a/packages/autofmt/tests/samples/cfg_let_event_block.rsx b/packages/autofmt/tests/samples/cfg_let_event_block.rsx new file mode 100644 index 0000000000..c4a2fe347b --- /dev/null +++ b/packages/autofmt/tests/samples/cfg_let_event_block.rsx @@ -0,0 +1,24 @@ +rsx! { + button { + onclick: { + #[cfg(target_os = "android")] + let create_photo_collection_gallery = create_photo_collection.clone(); + + move |_| { + uploading.set(true); + error.set(String::new()); + + #[cfg(target_os = "android")] + let create_photo_collection_gallery_call = + create_photo_collection_gallery.clone(); + + if let Err(err) = Err::<(), _>("x") { + error.set(format!("{}: {}", "err", err)); + uploading.set(false); + return; + } + } + }, + "x" + } +}