Problem
dioxus-autofmt 0.7.6 (used by dx fmt and custom tooling via try_fmt_file/apply_formats) is not idempotent — running the formatter twice on the same file can produce different output, causing it to oscillate between two states instead of reaching a fixed point.
Root Causes
1. write_attribute_if_chain unconditionally inlines if/else attribute values
In writer.rs, write_attribute_if_chain always writes if cond { val } else { val } on a single line regardless of length. When values are long strings (e.g. Tailwind CSS classes), this produces lines far exceeding the 80-char threshold. On the next formatting pass, write_rsx_block's ShortOptimization logic (which checks formatted.len() <= 80 and attr_len + indent_level * 4 < 80) makes different decisions about line breaking — and the cycle repeats.
2. Spurious blank lines inserted between nested components
The formatter sometimes inserts empty lines between nested component children. For example:
// Before formatting:
Pagination {
PaginationContent {
PaginationPrevious { ... }
// After formatting:
Pagination {
PaginationContent {
PaginationPrevious { ... }
On the next pass these blank lines cause different formatting decisions.
3. Short single-attribute elements oscillate between oneliner and expanded form
Elements like i { class: "fa-solid fa-check" } get expanded to multiline on one pass, then the next pass sees them as short enough to collapse, and so on.
Minimal Repro (Issue 1)
use dioxus::prelude::*;
#[component]
fn Example() -> Element {
let is_active = true;
rsx! {
button {
class: if is_active {
"w-full text-left px-3 py-1 text-xs font-mono text-primary bg-select"
} else {
"w-full text-left px-3 py-1 text-xs font-mono text-secondary hover:bg-select hover:text-primary"
},
"Click me"
}
}
}
Running dx fmt twice produces different output each time — the if/else gets collapsed to one massive line on the first pass, then the formatter tries to break it again on the second.
Minimal Repro (Issue 2)
use dioxus::prelude::*;
#[component]
fn Example() -> Element {
rsx! {
Pagination {
PaginationContent {
PaginationPrevious { onclick: move |_| on_prev(()), }
PaginationNext { onclick: move |_| on_next(()), }
}
}
}
}
Environment
- dioxus-autofmt 0.7.6
- Also reproduced with
dx fmt from dioxus-cli 0.7.5
Workaround
Extract long conditional class strings into constants so they don't exceed the 80-char threshold when inlined.
Problem
dioxus-autofmt0.7.6 (used bydx fmtand custom tooling viatry_fmt_file/apply_formats) is not idempotent — running the formatter twice on the same file can produce different output, causing it to oscillate between two states instead of reaching a fixed point.Root Causes
1.
write_attribute_if_chainunconditionally inlinesif/elseattribute valuesIn
writer.rs,write_attribute_if_chainalways writesif cond { val } else { val }on a single line regardless of length. When values are long strings (e.g. Tailwind CSS classes), this produces lines far exceeding the 80-char threshold. On the next formatting pass,write_rsx_block'sShortOptimizationlogic (which checksformatted.len() <= 80andattr_len + indent_level * 4 < 80) makes different decisions about line breaking — and the cycle repeats.2. Spurious blank lines inserted between nested components
The formatter sometimes inserts empty lines between nested component children. For example:
On the next pass these blank lines cause different formatting decisions.
3. Short single-attribute elements oscillate between oneliner and expanded form
Elements like
i { class: "fa-solid fa-check" }get expanded to multiline on one pass, then the next pass sees them as short enough to collapse, and so on.Minimal Repro (Issue 1)
Running
dx fmttwice produces different output each time — theif/elsegets collapsed to one massive line on the first pass, then the formatter tries to break it again on the second.Minimal Repro (Issue 2)
Environment
dx fmtfrom dioxus-cli 0.7.5Workaround
Extract long conditional class strings into constants so they don't exceed the 80-char threshold when inlined.