Skip to content

perf(formatter): process ImportDeclarations in a run#22079

Merged
graphite-app[bot] merged 1 commit into
mainfrom
om/05-02-perf_formatter_process_importdeclaration_s_in_a_run
May 7, 2026
Merged

perf(formatter): process ImportDeclarations in a run#22079
graphite-app[bot] merged 1 commit into
mainfrom
om/05-02-perf_formatter_process_importdeclaration_s_in_a_run

Conversation

@overlookmotel

@overlookmotel overlookmotel commented May 2, 2026

Copy link
Copy Markdown
Member

Follow-on after #22065. Small perf optimization to collecting ImportDeclarations for sorting.

Previously, FormatStatementsWithImports::fmt had a loop where it checked each statement for whether it's an ImportDeclaration, and also checked every other statement to see if it's the first statement after an ImportDeclaration.

ImportDeclarations comprise a minority of the statements in a file. So instead have 2 nested loops:

  1. Main loop over Statements.
  2. Inner loop which consumes a run of ImportDeclarations, before yielding back to the main loop.

This has the following effects:

  • Avoids the "am I the first statement after an ImportDeclaration?" check on all other statements.
  • Makes the flow more predictable for the branch predictor.
  • Moves processing a chunk of ImportDeclarations into its own function, making the logic clearer.

We could build on this new structure, if we chose, by:

  1. Skipping sorting imports if there's only 1 import in a chunk.
  2. Collecting info about imports (e.g. specifiers) at AST level, instead of the sorting transform having to figure it out by sifting through the IR.
  3. Doing first-pass formatting of imports into a temporary buffer, so that sorted imports can be output directly into the main buffer, avoiding copying them all twice, which is what we do now (format into buffer, copy into temp Vec, copy back into buffer).

overlookmotel commented May 2, 2026

Copy link
Copy Markdown
Member Author

How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • 0-merge - adds this PR to the back of the merge queue
  • hotfix - for urgent changes, fast-track this PR to the front of the merge queue

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@codspeed-hq

codspeed-hq Bot commented May 2, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 44 untouched benchmarks
⏩ 7 skipped benchmarks1


Comparing om/05-02-perf_formatter_process_importdeclaration_s_in_a_run (d74d656) with main (b036558)

Open in CodSpeed

Footnotes

  1. 7 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@overlookmotel overlookmotel marked this pull request as ready for review May 2, 2026 12:43
Copilot AI review requested due to automatic review settings May 2, 2026 12:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors top-level statement formatting in oxc_formatter so import-sorting work is done per consecutive run of ImportDeclarations instead of re-checking chunk boundaries on every statement. It fits into the formatter’s existing import-sorting IR transform by keeping the same sorting behavior while reducing per-statement branching in Program formatting.

Changes:

  • Replaced the single-loop chunk-tracking logic in FormatStatementsWithImports::fmt with a main loop plus a helper that consumes consecutive import declarations.
  • Added format_import_decls_with_sort to format one import run, invoke sort_imports_chunk, and hand back the next non-import statement.
  • Kept existing statement span handling for decorated exports and unchanged formatting behavior outside import runs.

Comment thread crates/oxc_formatter/src/print/program.rs Outdated
Comment thread crates/oxc_formatter/src/print/program.rs Outdated
@overlookmotel overlookmotel force-pushed the om/05-02-refactor_formatter_remove_sortimportstransform_abstraction branch from ec0cdc2 to 81c2f78 Compare May 2, 2026 12:48
@overlookmotel overlookmotel force-pushed the om/05-02-perf_formatter_process_importdeclaration_s_in_a_run branch from f8ece1f to 3c40162 Compare May 2, 2026 12:48
@overlookmotel overlookmotel force-pushed the om/05-02-refactor_formatter_remove_sortimportstransform_abstraction branch from 81c2f78 to d71939d Compare May 2, 2026 12:50
@overlookmotel overlookmotel force-pushed the om/05-02-perf_formatter_process_importdeclaration_s_in_a_run branch from 3c40162 to a483536 Compare May 2, 2026 12:50
@camc314 camc314 added the A-formatter Area - Formatter label May 2, 2026
@overlookmotel overlookmotel added the C-performance Category - Solution not expected to change functional behavior, only performance label May 3, 2026
@graphite-app graphite-app Bot force-pushed the om/05-02-refactor_formatter_remove_sortimportstransform_abstraction branch from d71939d to 79aa43d Compare May 3, 2026 20:49
@graphite-app graphite-app Bot force-pushed the om/05-02-perf_formatter_process_importdeclaration_s_in_a_run branch from a483536 to 76b99a0 Compare May 3, 2026 20:50
@graphite-app graphite-app Bot changed the base branch from om/05-02-refactor_formatter_remove_sortimportstransform_abstraction to graphite-base/22079 May 7, 2026 02:23
@graphite-app graphite-app Bot force-pushed the om/05-02-perf_formatter_process_importdeclaration_s_in_a_run branch from 76b99a0 to f3e02e9 Compare May 7, 2026 02:28
@graphite-app graphite-app Bot force-pushed the graphite-base/22079 branch from 79aa43d to b036558 Compare May 7, 2026 02:28
@graphite-app graphite-app Bot changed the base branch from graphite-base/22079 to main May 7, 2026 02:28
@graphite-app graphite-app Bot force-pushed the om/05-02-perf_formatter_process_importdeclaration_s_in_a_run branch from f3e02e9 to d74d656 Compare May 7, 2026 02:28

@leaysgur leaysgur left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏻 🙏🏻 🚀

@leaysgur leaysgur added the 0-merge Merge with Graphite Merge Queue label May 7, 2026

leaysgur commented May 7, 2026

Copy link
Copy Markdown
Member

Merge activity

Follow-on after #22065. Small perf optimization to collecting `ImportDeclaration`s for sorting.

Previously, `FormatStatementsWithImports::fmt` had a loop where it checked each statement for whether it's an `ImportDeclaration`, and also checked every _other_ statement to see if it's the first statement _after_ an `ImportDeclaration`.

`ImportDeclaration`s comprise a minority of the statements in a file. So instead have 2 nested loops:

1. Main loop over `Statement`s.
2. Inner loop which consumes a run of `ImportDeclaration`s, before yielding back to the main loop.

This has the following effects:

- Avoids the "am I the first statement after an `ImportDeclaration`?" check on all other statements.
- Makes the flow more predictable for the branch predictor.
- Moves processing a chunk of `ImportDeclaration`s into its own function, making the logic clearer.

We could build on this new structure, if we chose, by:

1. Skipping sorting imports if there's only 1 import in a chunk.
2. Collecting info about imports (e.g. specifiers) at AST level, instead of the sorting transform having to figure it out by sifting through the IR.
3. Doing first-pass formatting of imports into a temporary buffer, so that sorted imports can be output directly into the main buffer, avoiding copying them all twice, which is what we do now (format into buffer, copy into temp `Vec`, copy back into buffer).
@graphite-app graphite-app Bot force-pushed the om/05-02-perf_formatter_process_importdeclaration_s_in_a_run branch from d74d656 to 32255b1 Compare May 7, 2026 02:48
@graphite-app graphite-app Bot merged commit 32255b1 into main May 7, 2026
28 checks passed
@graphite-app graphite-app Bot removed the 0-merge Merge with Graphite Merge Queue label May 7, 2026
@graphite-app graphite-app Bot deleted the om/05-02-perf_formatter_process_importdeclaration_s_in_a_run branch May 7, 2026 02:52
graphite-app Bot pushed a commit that referenced this pull request May 7, 2026
graphite-app Bot pushed a commit that referenced this pull request May 7, 2026
Follow-on after #22079. Just correct a comment which was erroneous (oops, my fault).
camc314 added a commit that referenced this pull request May 11, 2026
# Oxlint
### 💥 BREAKING CHANGES

- 00ce512 oxlint/lsp: [**BREAKING**] Don't fix suggestions on fixAll
code actions & command (#22195) (Sysix)

### 🚀 Features

- 0eeceaf linter/no-unused-vars: Rename parameter with initializer
(#22308) (camc314)
- fa0232b linter/no-unused-vars: Add param rename suggestion (#22285)
(Ryota Misumi)
- ae59305 linter/promise/no-promise-in-callback: Add
`exemptDeclarations` option (#22275) (Mikhail Baev)
- 60bed4a linter: Extends `no-redundant-roles` and
`prefer-tag-over-role` support roles (#22069) (mehm8128)
- 545c80f linter/eslint: Implement `prefer-regex-literals` rule (#22192)
(Mikhail Baev)
- cf86d7a linter: Bulk suppression (#19328) (Said Atrahouch)
- 23abd22 linter/jsx-a11y: Implement
no-noninteractive-element-to-interactive-role (#21264) (Pedro Tainha)
- fbb8f22 linter: Support `ignores` in overrides (#22148) (camc314)
- 5a4414d oxlint/lsp: Support `rulesCustomization` lsp option (#21858)
(Sysix)

### 🐛 Bug Fixes

- 610f4c7 linter/no-unused-vars: Avoid renaming captured vars (#22310)
(camc314)
- 6b50f23 oxlint/cli: Load root config by searching up parent
directories (#22272) (Sysix)
- 31a5de7 linter: Rename override `ignores` to `excludeFiles` (#22283)
(camc314)
- 26d5d7b linter: Add missing vitest/valid-describe-callback
functionality (#22279) (camchenry)
- 784530f linter: `valid-title`: detect `String.raw` strings (#22271)
(Sysix)
- 080d90e linter: Move `no-debugger` fix to suggestion (#22256) (Sysix)
- 25b7017 linter: Undocument override `ignores` option (#22213)
(camc314)
- 7bb00dd linter: Fix role-has-required-aria-props (#22097) (mehm8128)
- d25279e linter/disable-directives: Improve parsing of names,
descriptions (#22184) (camc314)
- a59e447 linter/disable-directives: Ignore invalid enable suffixes
(#22179) (camc314)
- aafef0f ci: Disable bulk supression test on big endian (#22175)
(camc314)
- 281daec linter/vue/define-props-destructuring: Add
`only-when-assigned` config opt (#22142) (camc314)
- 46ab679 linter/plugins: Trim leading newline for partial sources
(#20928) (bab)
- 29ff6d9 linter: Update docs for no_alias_methods rule to be
Vitest-specific and add toThrowError alias (#22129) (camchenry)

### ⚡ Performance

- 9414bee linter/role-has-required-aria-props: Avoid intermediate vec
(#22212) (camc314)
- 3883ea3 linter/no-useless-escape: Drop unnecessary Vec collect
(#22171) (connorshea)
- 42c3029 linter/check-property-names: Replace split-collect-pop-join
with rfind (#22172) (connorshea)
- 9551d53 linter: Remove unnecessary Vec collect in CFG edge traversal
(#22167) (connorshea)
- 26fa2fc linter/aria-role: Remove unnecessary string allocations in run
method (#22168) (connorshea)
- c9ce045 linter/getter-return: Remove unnecessary Vec collect in CFG
edge traversal (#22166) (connorshea)
- 72bd846 linter/no-this-in-sfc: Reorder cheap name check, avoid String
allocation (#22164) (connorshea)

### 📚 Documentation

- 4da212a linter/no-unused-vars: Add docs to
`rename_unused_function_parameter` (#22311) (camc314)
- 27c4628 linter/forbid-dom-props: Escape jsx examples in lint rule docs
(#22254) (4MBL)
- 3f81147 linter: Improve the `react/jsx-key` rule docs. (#22162)
(connorshea)
- 07f03cc linter/consistent-return: Add note about `noImplicitReturns`
coverage (#22156) (camc314)
- 7c1e049 oxlint/lsp: Improve autogenerated lsp docs (#22154) (Sysix)
- 87b3e38 linter: Update docs to be vitest-specific for
consistent-test-it (#22128) (camchenry)
# Oxfmt
### 💥 BREAKING CHANGES

- 5c6c390 oxfmt: [**BREAKING**] Respect more git ignore options, align
with Oxlint (#22210) (leaysgur)

### 🚀 Features

- 6e8e818 oxfmt: Experimental .svelte support (#21700) (leaysgur)

### 🐛 Bug Fixes

- e2a20b6 formatter: Add space after commas in import attributes
(#22274) (Leonabcd123)

### ⚡ Performance

- b756682 oxfmt: Optimize nested config prescan (#22232) (Jovi De
Croock)
- f14e81e formatter/sort_imports: Skip sort for single import runs
(#22204) (leaysgur)
- 32255b1 formatter: Process `ImportDeclaration`s in a run (#22079)
(overlookmotel)

### 📚 Documentation

- 4da6f4c formatter: Correct comment (#22217) (overlookmotel)
- ef3507d formatter/sort_imports: Refresh docs (#22203) (leaysgur)

Co-authored-by: Cameron <cameron.clark@hey.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-formatter Area - Formatter C-performance Category - Solution not expected to change functional behavior, only performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants