Thanks to visit codestin.com
Credit goes to github.com

Skip to content

dioxus-autofmt: formatting is not idempotent — oscillates between two states #5508

@micahkepe

Description

@micahkepe

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions