Skip to content

fix(autofmt): stop dropping comments on attributes, spreads, control flow and empty macros - #5883

Open
nicoburns wants to merge 5 commits into
mainfrom
devin/1791038588-autofmt-comment-tests
Open

nicoburns wants to merge 5 commits into
mainfrom
devin/1791038588-autofmt-comment-tests

Conversation

@nicoburns

@nicoburns nicoburns commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

Summary

dx fmt deleted // comments in a number of positions, and in a few of them wrote output that no longer parses. This fixes those and adds a comment test matrix for the block syntax. It is the bottom of a stack: #5730 and #5731 build on the refactor in the first commit.

Output that no longer parsed (the comment swallows the closing brace):

// before                            // on main
div {                                div { class: "a", // note "child" }
    class: "a", // note
    "child"
}

Comments that were deleted

  • after an attribute of an element short enough to collapse onto one line, and after the last attribute of an element with no children
  • above or after a ..spread
  • above a node or attribute when the line in between has trailing whitespace
  • in for / if / else bodies: after the opening brace, before the closing brace, in a body containing nothing else, and after the closing brace of an else if chain
  • between the closing brace of a branch and its else
  • the only thing in an rsx! call, or after its opening delimiter (also for an rsx! nested in an expression)
  • after anything on a line containing multi-byte characters (columns were used as byte offsets)
  • in the middle of a construct: between the name of an element or component and its {, between the name of an attribute and its value, inside the branches of an if attribute value, and inside the header of a for or an if

Smaller changes in behaviour

  • A comment-only body keeps one blank line between its comments, and indents them with the configured indentation rather than four spaces.
  • An if with empty bodies is written if a {} else {} rather than with a blank line in each body.
  • div {..attrs, (spread only, children below) gets its missing space.
  • A long if attribute value in an element with one attribute per line starts on the line of its name (class: if a {). It was written below the name at the same indentation.

How

  • First commit, no behaviour change: the comment helpers took a &Brace but only used the positions of its two tokens. They now take a BodyDelimiters { open, close }, so they work for any delimited body.

  • for / if bodies go through the same open-comment, comment-only-body and closing-comment helpers as elements (write_block_body, write_comment_only_body, write_closing_line).

  • Attributes with comments are written one per line. The exception is a comment after the last attribute when children follow, which keeps the existing div { class: "a", // note layout.

  • A comment can't share a line with else, so comments before one are left in place and the else goes on its own line (write_else):

    if a {
        "a"
    } // after the brace
    // above the else
    else {
        "b"
    }
  • A nested rsx! with a comment after its opening brace is always written across several lines, so the existing expression comment pass finds the line to attach it to.

  • text_before / text_after convert a column to a byte offset before slicing a source line.

  • has_leading_comments replaces three copies of the "are there comments above this" loop, which stopped at whitespace-only lines.

  • Comments in the middle of a construct (last commit). These constructs are rebuilt from their tokens, so each needs a place to put a comment:

    div { // was between `div` and `{`
        class:
            // was between `class:` and its value; the value is still formatted
            "a",
        id: if a {
            // inside a branch; the chain is still formatted
            "x"
        } else {
            "y" // after the value of a branch
        },
    }
    for item in items
        // inside a header: the header is written as it is in the source, re-indented
        .iter()
    {
        div {}
    }

    A header or condition is only taken from the source if line_comments, a small scanner that skips strings, characters and block comments, finds a comment in it.

Tests

  • Idempotency samples: comments_attributes, comments_control_flow, comments_positions.
  • Idempotency sample comments_constructs, and messy input comments-constructs-4sp / comments-constructs-tab, for comments in the middle of a construct.
  • Messy input, with spaces and with tabs: comments-messy-4sp, comments-messy-tab.

All of these fail on main.

Not fixed here

  • /* */ comments are still deleted everywhere (dx fmt eats block comments #2751).
  • Comments inside the arguments of a call in an attribute value (format!(a, // note …) are deleted when the expression is reflowed.
  • With tabs or split_line_attributes, expressions that contain comments and a nested rsx! are not idempotent, and with split_line_attributes can lose comments.

Link to Devin session: https://dioxus.staging.devinenterprise.com/sessions/99eedb36d44345dc8ce0f3ed5c460bd6
Open in Devin Desktop: https://dioxus.staging.devinenterprise.com/desktop/session/99eedb36d44345dc8ce0f3ed5c460bd6?variant=devin-insiders
Requested by: @nicoburns

…brace

The comment helpers took a `&Brace`, but only ever used the positions of
its two tokens. Pass those positions instead so the same helpers can be
used for any delimited body. No behaviour change.
…empty macros

dx fmt deleted comments, and in a few cases wrote code that no longer
parsed, when they were:

- after an attribute of an element short enough to be put on one line
  (the comment swallowed the rest of the line, including the closing brace)
- after the last attribute of an element with no children
- above or after a spread
- after the opening brace or before the closing brace of a for/if body,
  in a for/if body with nothing else in it, or after the closing brace of
  an else-if chain
- the only thing in an rsx! call, or after its opening brace
- after anything on a line containing multi-byte characters

Also keep one blank line between the comments of a comment-only body,
indent those comments with the configured indentation rather than four
spaces, and write empty if bodies as `{}`.
@staging-devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access that start with 'Devin'.

  • Disable automatic comment, CI, and merge conflict monitoring

… whitespace

A line that only had whitespace on it ended the search for the comments
above a node or attribute, so the element was collapsed onto one line and
the comments were dropped. Use one helper for the three copies of that
search.
@staging-devin-ai-integration
staging-devin-ai-integration Bot added this pull request to stack #5884 October 3, 2026 14:58
…e of a nested rsx!

A comment between the closing brace of a branch and its `else` was
deleted. It is now left where it is, with the `else` on its own line.

A comment after the opening brace of an `rsx!` inside an expression was
deleted when the body was short enough to be written on that same line.
…d for/if headers

Comments in the middle of a construct were dropped, as the construct is rebuilt from its
tokens. This keeps them in four places:

- between the name of an element or component and its opening brace, by moving them after
  the brace
- between the name of an attribute and its value, by writing the value on a line of its
  own below them
- inside the branches of an `if` attribute value, which is still formatted
- inside the header of a `for` or an `if`, by writing the header as it is in the source

A long `if` attribute value on a line of its own now also starts on the line of its name,
rather than below it at the same indentation.
@staging-devin-ai-integration
staging-devin-ai-integration Bot force-pushed the devin/1791038588-autofmt-comment-tests branch from 820709a to fc50406 Compare October 3, 2026 15:28

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant