feat(fmt): add sortNamedImports and sortNamedExports options#33313
Conversation
fibibot
left a comment
There was a problem hiding this comment.
Substance walks clean — two Option<bool> fields plumbed through FmtOptionsConfig → SerializedFmtConfig → workspace merge → JSON schema → dprint config builder, with a spec test that exercises both the default sort behavior and the disabled-via-config behavior.
What I verified
SortOrder::CaseInsensitive↔SortOrder::Maintainmapping incli/tools/fmt.rs:1454-1467matches the actual dprint-plugin-typescript option semantics.CaseInsensitiveis what dprint already uses by default;Maintainpreserves source order. The body text says "opt out of dprint's case-insensitive sort," soMaintainis the right value whensort_named_imports: falseis set.- Default
truein the JSON schema means existingdeno fmtusers see no change in behavior — the only way the new sort behavior fires is if a user explicitly setsfalse. ✓ - Workspace merging at
libs/config/workspace/mod.rs:2106-2123uses the standardmember_config.or(root_config)pattern, so child workspace members override root. Consistent with how every otherFmtOptionsConfigfield merges. ✓ is_default()updated to include the two new fields — important soFmtOptionsConfig::is_default()returns true when neither is set, preventing spurious config-changed reports. ✓malformed_config/error.outupdated to includesortNamedImports/sortNamedExportsin theexpected one oflist — the deserializer correctly rejects unknown fields and lists known ones. ✓- Spec test design:
preserves_order_when_disableduses the new config and--checkto assert no formatting needed;sorts_by_defaultuses--no-configand asserts--checkfails (exit code 1). Both halves of the toggle exercised cleanly.
Holding COMMENT for two reasons
1. CI is blocked on maintainer authorization
mergeable_state: blocked, and the workflow run from 2026-04-17 is conclusion: "action_required" — a first-time-contributor PR needs a maintainer to authorize the CI workflow before it'll run. Currently only license/cla (passed) and lint title (passed) have status. Build / test / spec shards haven't executed at all.
Without CI, I can't confirm the JSON schema is well-formed (the schema validator runs in CI), the cargo test -p deno_config --lib fmt parse test passes, or that the new spec test actually runs cleanly. Worth pinging a maintainer to authorize the workflow run so CI can land.
2. Escalation: two new public deno.json fmt config fields
This adds sortNamedImports and sortNamedExports as public deno.json config keys. Per fibibot's policy, new user-facing config surface — even one defaulting to true for backwards compat — warrants a maintainer's call on the field naming, the Option<bool> shape (vs e.g. "sortNamedImports": "caseInsensitive" | "maintain" | "none" for future extensibility), and the precedent for letting users opt out of more dprint defaults via similar flags. cc @bartlomieju.
Substance is APPROVE-shape pending those two — happy to come back once CI is unblocked and a maintainer signs off on the surface.
Expose dprint-plugin-typescript's `importDeclaration.sortNamedImports`
and `exportDeclaration.sortNamedExports` settings via `deno.json`.
Both default to `true` (case-insensitive sort, unchanged behavior).
Motivation: `deno fmt` sorts named specifiers case-insensitively, which
conflicts with other tooling that sorts case-sensitively (e.g. Biome's
alphanumeric order). The only prior workaround was
`// deno-fmt-ignore` per import or `// deno-fmt-ignore-file`, both too
coarse. These options let users opt out of `deno fmt`'s sort and
delegate ordering to another tool.
```jsonc
{
"fmt": {
"sortNamedImports": false,
"sortNamedExports": false
}
}
```
b2a9aca to
762a5f4
Compare
Deno Individual Contributor License AgreementAll contributors have signed the CLA. Thank you! This is an automated message from CLA Assistant |
…mports-exports # Conflicts: # tests/specs/fmt/malformed_config/error.out
bartlomieju
left a comment
There was a problem hiding this comment.
Thanks for this — the implementation is clean and well-tested, and the motivation (delegating specifier order to another formatter) is solid. One design suggestion before this lands.
The config currently exposes these as booleans (true → CaseInsensitive, false → Maintain), which drops dprint's third SortOrder variant, CaseSensitive. That matters for this PR's own motivation: Biome sorts case-sensitively (uppercase before lowercase), so with a bool a user still can't make deno fmt produce Biome's order — they can only disable sorting and run both tools.
For comparison, Prettier core doesn't sort named specifiers at all (it delegates to plugins). The Prettier sort plugins model this as two axes — importOrderSortSpecifiers (on/off) plus importOrderCaseSensitive (case sensitivity) — precisely because case-sensitive sorting is a real, wanted mode. dprint already encodes all three states in one SortOrder enum.
So I'd suggest exposing a three-way string enum that maps 1:1 onto dprint, e.g.:
{
"fmt": {
"sortNamedImports": "maintain" | "caseSensitive" | "caseInsensitive", // default "caseInsensitive"
"sortNamedExports": "maintain" | "caseSensitive" | "caseInsensitive"
}
}This keeps current behavior as the default, covers the Biome case directly, and avoids a slightly awkward config migration later (bool → enum). It'd follow the existing pattern of enum-valued fmt options like quoteProps/proseWrap rather than the bool fields.
If you'd rather keep it a bool for simplicity, that's defensible, but then it's worth a doc note that case-sensitive ordering isn't reachable. Happy to help with either direction.
| ); | ||
| } | ||
|
|
||
| if let Some(sort_named_imports) = options.sort_named_imports { |
There was a problem hiding this comment.
Consider mapping the config field directly onto dprint's three-way SortOrder (maintain/caseSensitive/caseInsensitive) instead of bool → CaseInsensitive/Maintain. As written the CaseSensitive variant is unreachable, which is the exact ordering Biome uses. See the summary comment for rationale.
There was a problem hiding this comment.
Good call — done in a4fcc69. Both options are now three-way string enums mapping 1:1 onto dprint's SortOrder:
{
"fmt": {
"sortNamedImports": "maintain" | "caseSensitive" | "caseInsensitive",
"sortNamedExports": "maintain" | "caseSensitive" | "caseInsensitive"
}
}caseInsensitive stays the default, so existing behavior is unchanged, and caseSensitive (Biome's order) is now reachable. Modeled it after the existing enum-valued fmt options (quoteProps/proseWrap). The spec test now covers all three modes, including one case that proves caseSensitive produces a different order than the default caseInsensitive.
There was a problem hiding this comment.
Good call — done in a4fcc69. Both options are now three-way string enums mapping 1:1 onto dprint's SortOrder:
{
"fmt": {
"sortNamedImports": "maintain" | "caseSensitive" | "caseInsensitive",
"sortNamedExports": "maintain" | "caseSensitive" | "caseInsensitive"
}
}caseInsensitive stays the default, so existing behavior is unchanged, and caseSensitive (Biome's order) is now reachable. Modeled it after the existing enum-valued fmt options (quoteProps/proseWrap). The spec test now covers all three modes, including one case that proves caseSensitive produces a different order than the default caseInsensitive.
Per review feedback, change both options from a bool to a string enum
that maps 1:1 onto dprint's `SortOrder`:
"sortNamedImports": "maintain" | "caseSensitive" | "caseInsensitive"
"sortNamedExports": "maintain" | "caseSensitive" | "caseInsensitive"
The previous bool could only reach `caseInsensitive` (the default) and
`maintain`, leaving dprint's `caseSensitive` ordering unreachable. That
is exactly the order Biome produces (uppercase before lowercase), which
this feature was meant to delegate to. The enum exposes all three states,
keeps `caseInsensitive` as the unchanged default, and follows the pattern
of the existing enum-valued fmt options (quoteProps, proseWrap).
Spec test now covers all three modes and proves caseSensitive is distinct
from the default caseInsensitive ordering.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…mports-exports # Conflicts: # cli/schemas/config-file.v1.json # libs/config/deno_json/mod.rs # libs/config/workspace/mod.rs # tests/specs/fmt/malformed_config/error.out
Adds two
deno fmtconfig options,sortNamedImportsandsortNamedExports,that control how named specifiers are ordered within
import/exportstatements. Each is a three-way string enum mapping 1:1 onto
dprint-plugin-typescript's
SortOrder:caseInsensitivesorts ignoring case,caseSensitivesorts with uppercase before lowercase (ASCII order), andmaintainpreserves the source order.The default remains
caseInsensitive, matching dprint's existing behavior. Whenneither option is set the formatter builder is left untouched, so unconfigured
projects see no change in output and nothing is reordered unless explicitly
configured.
{ "fmt": { "sortNamedImports": "maintain", "sortNamedExports": "caseSensitive" } }deno fmtpreviously always sorted named specifiers case-insensitively, whichconflicts with other tooling that sorts case-sensitively. For example, Biome
sorts alphanumerically with uppercase before lowercase, so running both tools
leaves files in an unstable state where each undoes the other's changes. The
only prior workaround was
// deno-fmt-ignoreper import or// deno-fmt-ignore-file, both too coarse. These options let users eitherdelegate named-specifier ordering to another formatter (
maintain) or match itsorder directly (
caseSensitive) without giving updeno fmt.An earlier revision exposed these as booleans (
truefor caseInsensitive,falsefor maintain), which left dprint's third variant,caseSensitive,unreachable, even though that is exactly the order this feature is meant to
interoperate with. The string enum exposes all three states, keeps
caseInsensitiveas the default, and avoids an awkward bool to enum migrationlater.
The options are wired through
FmtOptionsConfig,SerializedFmtConfig,workspace merging, and the JSON schema, following the existing pattern of
enum-valued fmt options like
quotePropsandproseWrap. A new spec exercisesall three modes:
maintainpreserves an unsorted file under--check, thedefault
caseInsensitivereorders it, acaseSensitive-ordered file passes--checkundercaseSensitive, and that same file fails the defaultcaseInsensitivecheck, proving the two sort modes are distinct.