Input
import * as Sentry from '@sentry/browser'
import type { BrowserOptions } from '@sentry/browser'
Config
Oxfmt output
Oxfmt version: 0.55.0
Running oxfmt twice on the same file produces alternating outputs — it never converges:
Pass 1:
import type { BrowserOptions } from "@sentry/browser";
import * as Sentry from "@sentry/browser";
Pass 2 (reverts back to original):
import * as Sentry from "@sentry/browser";
import type { BrowserOptions } from "@sentry/browser";
Oxfmt playground link
No response
Prettier output
Prettier version: 3.x
Prettier does not sort imports natively. Expected behavior from oxfmt: the output should be stable after the first pass (idempotent), regardless of which order is chosen.
Prettier playground link
No response
Additional notes
- The bug only occurs with "order": "desc". Switching to "order": "asc" produces stable, idempotent output.
- The two imports resolve to the same module path (@sentry/browser), so the sort is a tie. With desc, this tie-break is unstable and oscillates on every run.
AI investigation:
fn sort_indices_by_source(
indices: &mut [usize],
imports: &[SortableImport],
options: &SortImportsOptions,
) {
indices.sort_by(|&a, &b| {
natord::compare(&imports[a].normalized_source, &imports[b].normalized_source)
});
if options.order.is_desc() {
indices.reverse();
}
}
The sort uses only normalized_source (the module path) as the key. When two imports share the same path (@sentry/browser), natord::compare returns Equal, making their
order unstable — it depends on input order.
Then .reverse() is called on the whole slice for desc. This reversal flips the relative order of equal elements each time, because:
- After sort_by with all-equal keys, the two imports are in their original input order (e.g. wildcard, type)
- .reverse() makes them type, wildcard
- Next run, input is type, wildcard, sort_by preserves that, .reverse() makes them wildcard, type again
The fix is to sort in descending order directly in the comparator instead of sorting asc then reversing:
indices.sort_by(|&a, &b| {
let cmp = natord::compare(&imports[a].normalized_source, &imports[b].normalized_source);
if options.order.is_desc() { cmp.reverse() } else { cmp }
});
Input
Config
{ "sortImports": { "order": "desc" } }Oxfmt output
Oxfmt version: 0.55.0
Running oxfmt twice on the same file produces alternating outputs — it never converges:
Pass 1:
Pass 2 (reverts back to original):
Oxfmt playground link
No response
Prettier output
Prettier version: 3.x
Prettier does not sort imports natively. Expected behavior from oxfmt: the output should be stable after the first pass (idempotent), regardless of which order is chosen.
Prettier playground link
No response
Additional notes
AI investigation:
The sort uses only normalized_source (the module path) as the key. When two imports share the same path (@sentry/browser), natord::compare returns Equal, making their
order unstable — it depends on input order.
Then .reverse() is called on the whole slice for desc. This reversal flips the relative order of equal elements each time, because:
The fix is to sort in descending order directly in the comparator instead of sorting asc then reversing: