Skip to content

the-mike/biome-usesortedattributes-bug

Repository files navigation

Biome useSortedAttributes JSX duplication repro

Minimal reproduction for a bug in @biomejs/biome 2.4.10 where the assist/source/useSortedAttributes action duplicates one JSX attribute and silently drops another when both an outer JSX element and one of its nested JSX-valued attributes have unsorted attributes.

The duplicated output is then immediately flagged as lint/suspicious/noDuplicateJsxProps by the same Biome run, so the auto-fix produces source that fails Biome's own linter.

The full upstream issue draft (ready to paste into https://github.com/biomejs/biome/issues/new) lives at UPSTREAM_ISSUE.md.

Versions

  • @biomejs/biome 2.4.10
  • ultracite 7.4.3 (extends ultracite/biome/core + ultracite/biome/react)
  • React 19, Vite 8, TypeScript 6 — none of these matter for the bug; Biome operates on source text.

Reproduce

The fastest path is the bundled script — it installs deps on first run, restores the pristine input, runs the auto-fix, asserts that noDuplicateJsxProps was emitted, prints the diff, and restores the pristine state for the next run:

./reproduce.sh

Exit codes: 0 = bug reproduced, 1 = bug missing (Biome may have fixed it — investigate), 2 = setup error.

Manual equivalent:

bun install
cp src/Repro.pristine.tsx src/Repro.tsx
bun x biome check --write --only=assist/source/useSortedAttributes src/Repro.tsx
bun x biome check src/Repro.tsx
# → src/Repro.tsx:5:17 lint/suspicious/noDuplicateJsxProps

The minimal trigger (16 lines)

src/Repro.pristine.tsx:

declare const Outer: (p: { z: string; a: unknown }) => unknown;
declare const Inner: (p: { z: number; a: number }) => unknown;

export function Repro() {
  return (
    <Outer
      z="x"
      a={
        <Inner
          z={1}
          a={2}
        />
      }
    />
  );
}

After bun x biome check --write --only=assist/source/useSortedAttributes src/Repro.tsx:

declare const Outer: (p: { z: string; a: unknown }) => unknown;
declare const Inner: (p: { z: number; a: number }) => unknown;

export function Repro() {
  return <Outer a={<Inner a={2} z={1} />} a={<Inner a={2} z={1} />} />;
}

The <Outer> element ends up with a duplicated and z dropped entirely.

Trigger surface

Bisected against the minimal repro:

Variant Outer attrs Inner attrs Outer needs sort Inner needs sort Bug fires?
Both unsorted (the repro) z, a z, a
Outer sorted, inner unsorted a, z z, a
Outer unsorted, inner sorted z, a a, z
Both sorted a, z a, z
Outer unsorted, value is a string/number/identifier z, a n/a n/a

The bug requires the assist to produce a code-fix on both the outer and a nested JSX element in the same fix pass. With anything else — single side, sorted side, no nested JSX — the fix runs cleanly.

Which Biome subcommand triggers it

Command Formatter Linter Assist Bug fires?
biome format --write src/Repro.tsx
biome lint --write src/Repro.tsx
biome check --write src/Repro.tsx
biome check --write --only=assist/source/useSortedAttributes src/Repro.tsx (off) (off) ✅ (only this action)
bun x ultracite fix src/Repro.tsx (calls biome check --write internally)

biome format and biome lint do not run assist actions (source) — only check and ci do. The --only=assist/source/useSortedAttributes form is the cleanest minimal command for an upstream issue: it shows the bug fires even when every other rule and action is disabled.

Suspected mechanism

use_sorted_attributes.rs on main constructs the code-fix by zipping originals with sorted versions and calling replace_node_discard_trivia in positional pairs. When two useSortedAttributes mutations target nested elements (outer + nested) in the same pass, the byte-range tracking on the inner subtree appears to confuse one of the outer's slot replacements: the deletion silently no-ops, leaving the original in place, while the sibling slot's replacement is lost. See UPSTREAM_ISSUE.md for the full hypothesis.

Workaround

Disable the assist in biome.jsonc:

{
  "assist": {
    "actions": {
      "source": {
        "useSortedAttributes": "off"
      }
    }
  }
}

This is surgical — formatter, linter, and every other assist action keep working.

Related upstream issues

No public issue matches this exact failure mode (searched 2026-04-09). Adjacent bugs in the "Biome auto-fix corrupts JSX" family:

Files

  • src/Repro.pristine.tsx — frozen 16-line minimal trigger. Restore over src/Repro.tsx before each run.
  • src/Repro.tsx — working copy.
  • reproduce.sh — pass/fail oracle.
  • UPSTREAM_ISSUE.md — ready-to-paste GitHub issue draft.
  • biome.jsonc — extends ultracite presets; useSortedAttributes is in its default-enabled state.
  • package.json — pins @biomejs/biome 2.4.10 and ultracite 7.4.3.

About

Minimal reproduction for a bug in [`@biomejs/biome`](https://github.com/biomejs/biome) **2.4.10** where the `assist/source/useSortedAttributes` action **duplicates** one JSX attribute and **silently drops** another when both an outer JSX element and one of its nested JSX-valued attributes have unsorted attributes.v

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors