`dx fmt` grows indentation of multi-line raw string literals inside deeply nested closures on every run (non-idempotent)
Bug description
dx fmt is not idempotent when a multi-line raw string literal (r#"..."#) appears inside a deeply nested block (e.g. a closure passed to spawn, inside a match arm, inside an rsx! element handler). Running dx fmt repeatedly on an already-formatted file keeps increasing the indentation of the string's content on every single run, instead of converging to a stable output. Since the content between the raw string's newlines is real string data (this pattern is commonly used to build up a JS snippet passed to document::eval), this also silently corrupts the string's value, not just its on-screen indentation.
We noticed this because our team's Claude Code PostToolUse hook runs dx fmt -f <file> after every edit. Over many edits to the same file, one particular raw-string literal grew to 1000+ characters of leading whitespace before we noticed.
Steps to reproduce
- Save the file below as
repro.rs(no Cargo project needed — reproduces even with a baredx fmt -f). - Run
dx fmt -f repro.rsthree times in a row, diffing after each run.
use dioxus::prelude::*;
#[component]
fn App() -> Element {
rsx! {
div {
for _ in 0..1 {
button {
onclick: move |_evt| {
spawn(async move {
match Ok::<(), ()>(()) {
Ok(()) => {
let script = format!(
r#"
requestAnimationFrame(() => {{
console.log("restore");
}});
"#
);
let _ = script;
}
Err(_) => {}
}
});
},
"click"
}
}
}
}
}Actual behavior
The indentation of the raw string's body grows on every single run (measured as line length in characters, including leading whitespace):
requestAnimationFrame(() => {{ line length |
console.log(...) line length |
|
|---|---|---|
| run 1 | 86 | 83 |
| run 2 | 102 | 99 |
| run 3 | 118 | 115 |
(+16 characters per run, unbounded — it never converges.)
Expected behavior
dx fmt should be idempotent: formatting an already-formatted file should produce no diff. At minimum, it should not keep growing indentation without bound across repeated runs.
Environment
dx --version:dioxus 0.7.10 (57d6794)rustc --version:rustc 1.95.0- OS: Linux x86_64
Additional notes
We also observed a related (but distinct) non-idempotency bug in 0.7.5 involving multi-line continuations of a comparison inside an rsx! attribute value (e.g. checked: some_call()\n== Other::Variant,), which appears to have been fixed between 0.7.5 and 0.7.10. The raw-string case above is still present in 0.7.10.
As a workaround, we refactored the affected code to extract the raw string literal into a shallowly-nested top-level function instead of inlining it inside the deeply nested closure, which avoids triggering the bug.
Source: DioxusLabs/dioxus