perf(formatter): process ImportDeclarations in a run#22079
Conversation
How to use the Graphite Merge QueueAdd either label to this PR to merge it via 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. |
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
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::fmtwith a main loop plus a helper that consumes consecutive import declarations. - Added
format_import_decls_with_sortto format one import run, invokesort_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.
ec0cdc2 to
81c2f78
Compare
f8ece1f to
3c40162
Compare
81c2f78 to
d71939d
Compare
3c40162 to
a483536
Compare
d71939d to
79aa43d
Compare
a483536 to
76b99a0
Compare
76b99a0 to
f3e02e9
Compare
79aa43d to
b036558
Compare
f3e02e9 to
d74d656
Compare
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).
d74d656 to
32255b1
Compare
One of the optimizations proposed in #22079.
Follow-on after #22079. Just correct a comment which was erroneous (oops, my fault).
# 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>

Follow-on after #22065. Small perf optimization to collecting
ImportDeclarations for sorting.Previously,
FormatStatementsWithImports::fmthad a loop where it checked each statement for whether it's anImportDeclaration, and also checked every other statement to see if it's the first statement after anImportDeclaration.ImportDeclarations comprise a minority of the statements in a file. So instead have 2 nested loops:Statements.ImportDeclarations, before yielding back to the main loop.This has the following effects:
ImportDeclaration?" check on all other statements.ImportDeclarations into its own function, making the logic clearer.We could build on this new structure, if we chose, by:
Vec, copy back into buffer).