Skip to content

Conversation

@overlookmotel
Copy link
Member

@overlookmotel overlookmotel commented Jul 16, 2025

Small perf optimization.

Presumably all paths will usually have the same prefix e.g.:

  • /Users/bozo/projects/best-project-ever
  • /Users/bozo/projects/best-project-ever/src/components
  • /Users/bozo/projects/best-project-ever/src/server

When finding directories to search for nested configs, stop walking up to parent directories when find a directory that's already in the HashSet. We know that dir's parent, grandparent, etc will already be in the set too, so no need to check them again.

@github-actions github-actions bot added A-cli Area - CLI C-performance Category - Solution not expected to change functional behavior, only performance labels Jul 16, 2025
Copy link
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 hot fixes, skip the queue and merge this PR next

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.

@overlookmotel overlookmotel marked this pull request as ready for review July 16, 2025 17:03
@overlookmotel overlookmotel requested a review from camc314 as a code owner July 16, 2025 17:03
@overlookmotel
Copy link
Member Author

I assume this'd be an improvement, but we don't have benchmarks for oxlint in CI. @camc314 How do you normally test changes like this to make sure they do have the desired effect?

@overlookmotel
Copy link
Member Author

@camc314 Just a nudge on this one. Only reason I questioned whether it'd be a perf improvement is because of the comments above the code this PR changes. But "logically" it seems quite obvious that avoiding pointlessly iterating more than you need to should be a gain.

@Boshen Boshen requested a review from camchenry July 30, 2025 01:24
Copy link
Member

@camchenry camchenry left a comment

Choose a reason for hiding this comment

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

This looks correct to me, I don't believe this should result in any configuration differences. This will only break out of the loop if it's a path that we've already processed (and therefore already know its nearest config).

I ran a benchmark on this locally. I used the latest oxlint 1.9 and built this PR with cargo build -p oxlint --release and compared the two on my M1 laptop. The project used for testing is the https://github.com/microsoft/fluentui repository at b5fa2f0ae70bc472c0603a3927ac68e65b702525. My local copy replaces every .eslintrc file with an .oxlintrc.json file containing {}. In total, there are 260 .oxlintrc.json files at various levels of the hierarchy (according to find . | rg oxlintrc | wc -l), and there are about 14836 lintable JS/TS files. These were the results:

Command Mean [ms] Min [ms] Max [ms] Relative
oxlint-dev . --silent 404.0 ± 22.8 375.0 463.5 1.00
oxlint . --silent 444.2 ± 22.7 415.4 497.4 1.10 ± 0.08

On average, this PR is 10% faster than the current oxlint 1.9.0 version on the fluentui project (with eslintrc files replaced with oxlintrc). The fact that the minimum times are significantly different also gives me some confidence that this really does significantly reduce the work that we have to do in large monorepos with nested configs.

@camchenry
Copy link
Member

For comparison, I also tested on microsoft/vscode (without any changes, just using the code as-is) to see how it fairs on a project without any nested configs. The difference was not significant, depending on the order that I ran the benchmarks one or the other could be faster.

@overlookmotel
Copy link
Member Author

@camchenry Thanks very much for delving into this. Holy smokes! It had never occurred to me that a project might have 260 config files, or that this change would have anything more than a microscopic perf benefit.

@overlookmotel overlookmotel added the 0-merge Merge with Graphite Merge Queue label Jul 30, 2025
Copy link
Member Author

overlookmotel commented Jul 30, 2025

Merge activity

…d configs (#12329)

Small perf optimization.

Presumably all paths will usually have the same prefix e.g.:

* `/Users/bozo/projects/best-project-ever`
* `/Users/bozo/projects/best-project-ever/src/components`
* `/Users/bozo/projects/best-project-ever/src/server`

When finding directories to search for nested configs, stop walking up to parent directories when find a directory that's already in the `HashSet`. We know that dir's parent, grandparent, etc will already be in the set too, so no need to check them again.
@graphite-app graphite-app bot force-pushed the 07-16-perf_linter_reduce_iterations_when_collecting_directories_for_nested_configs branch from 618d3d5 to 693673b Compare July 30, 2025 10:05
@graphite-app graphite-app bot merged commit 693673b into main Jul 30, 2025
15 checks passed
@graphite-app graphite-app bot deleted the 07-16-perf_linter_reduce_iterations_when_collecting_directories_for_nested_configs branch July 30, 2025 10:13
@graphite-app graphite-app bot removed the 0-merge Merge with Graphite Merge Queue label Jul 30, 2025
camc314 added a commit that referenced this pull request Aug 6, 2025
## [1.10.0] - 2025-08-06

### 🚀 Features

- 44ac5a1 linter: Add eslint/no-unassigned-vars rule (#11365) (yefan)
- ce6eeee linter: Add `eslint/prefer-destructuring` rule (#12721)
(yefan)
- 9b35600 linter/jsx-a11y: Add support for mapped attributes in label
association checks (#12805) (camc314)
- 5475075 vscode/language_server: Add `tsConfigPath` option (#12484)
(Sysix)
- a754f7a linter: Support `countVoidThis` option in `max-params` rule
(#12604) (yefan)

### 🐛 Bug Fixes

- 2c1dab6 linter/no-unassigned-vars: False positive with variables in
for loop (#12833) (camc314)
- 5a24574 linter/func-style: Fix more false positives (#12828) (camc314)
- 33a7320 linter/no-throw-literal: Fix unconditional recursion in
`could_be_error` (#12819) (camc314)
- a3aec6a linter/explicit-module-boundary-types: Debug assertion fail
with top level return (#12820) (camc314)
- 6efe457 linter/no-empty-function: Respect allow options for functions
and arrow functions (#12814) (camc314)
- 1c21c46 linter/new-cap: Fix panic with computed member expr (#12804)
(camc314)
- 45206dd linter: Apply fix span offset after fixing the section source
text (#12758) (Sysix)
- 1e97e35 linter/unicorn/prefer-structured-clone: Update Default
implementation for `PreferStructuredCloneConfig` (#12791) (camc314)
- d382159 linter/unicorn/prefer-object-from-entries: Update Default
implementation for `PreferObjectFromEntriesConfig` (#12790) (camc314)
- b07d29c linter/typescript/no-this-alias: Update Default implementation
for `NoThisAliasConfig` (#12789) (camc314)
- 0db34ab linter/react/jsx-filename-case: Update Default implementation
for `JsxFilenameExtensionConfig` (#12788) (camc314)
- ff84eff linter/jest/prefer-lowercase-title: Update Default
implementation for `PreferLowercaseTitleConfig` (#12787) (camc314)
- 5175c6d linter/jest/no-large-snapshots: Update Default implementation
for `NoLargeSnapshotsConfig` (#12786) (camc314)
- 0eaebcd linter/jest/no-deprecated-functions: Update Default
implementation for `JestConfig` (#12785) (camc314)
- 4265db7 linter/import/no-anonymous-default-export: Update Default
implementation for `NoAnonymousDefaultExport` config (#12784) (camc314)
- 6a360e3 linter/import/extensions: Update Default implementation for
ExtensionsConfig (#12783) (camc314)
- 42c8f29 linter: Default options for `eslint/no-else-return` (#12762)
(Sysix)
- 4eac511 linter: Default options for `eslint/no-unneeded-ternary`
(#12761) (Sysix)
- 9c01dbf linter: Default options for `eslint/new-cap` (#12760) (Sysix)
- b25406f linter/explicit-function-return-types: Update default values
in ExplicitFunctionReturnTypeConfig (#12718) (camc314)
- ce5876d parser: Validate inner expression of type assertions in
assignment targets (#12614) (camc314)
- 5383331 linter/explicit-mod-boundary-types: False positive with jsx
elements (#12678) (camc314)
- d0e99b5 linter/explicit-mod-boundary-types: False positive with call
expressions (#12677) (camc314)
- 525137e linter: Add missing options to no-inner-declarations (#12661)
(camc314)
- fc4a327 linter: No-unused-vars false positive with class property
initializers (#12660) (camc314)
- 6af8631 linter/no-unused-vars: False positive with chain expression
(#12609) (camc314)
- 744ef52 linter: Correct `array-type` handling of `default:
'array-simple'` (#12607) (yefan)

### 🚜 Refactor

- 3f37ed1 linter: Replace `lazy_static` with `std::sync::LazyLock`
(#12822) (Copilot)
- 69fd08d semantic: Improve unused label tracking and add debug
assertions (#12812) (camc314)
- 030e397 linter: Simplify parsing CLI args (#12802) (overlookmotel)
- c0e224a linter: Store `ExternalRuleId` in `OxlintOverrides` not raw
names (#12502) (camc314)
- 61587e4 linter: Correct comment (#12792) (overlookmotel)
- 5adcb98 linter: Use `u32` to keep track of last fixed source text
position (#12696) (Sysix)
- 77acc11 linter, transformer: Use `Scoping::symbol_is_unused` (#12666)
(overlookmotel)
- ecf1cff language_server: Simplify offset adjustment by using
`Message.move_offset` (#12647) (Sysix)
- 7695393 linter: Simplify offset adjustment by using
`Message.move_offset` (#12595) (Sysix)
- b36dc92 linter: Refactor large arrays to reduce binary size (#12603)
(Boshen)
- 3b9f1f0 linter: Update iter_outer_expressions to take AstNodes
reference (#12583) (camc314)

### 📚 Documentation

- e760fd4 linter: Complete linter rules documentation with missing "Why
is this bad?" sections (#12757) (Copilot)
- 514322c rust: Add minimal documentation to example files in crates
directory (#12731) (Copilot)
- 1d910d5 linter: Fix typescript/consistent-type-imports rule options to
match TypeScript ESLint (#12707) (Copilot)
- 45e2fe8 rust: Fix typos and grammar mistakes in Rust documentation
comments (#12715) (Copilot)
- 7660a88 linter: Improve linter rule documentation with "Why is this
bad?" sections and enhanced examples (#12711) (Copilot)
- de1de35 rust: Add comprehensive README.md documentation for all Rust
crates (#12706) (Copilot)

### ⚡ Performance

- 09ae2a9 linter: Eliminate unnecessary Iterator::collect() allocations
(#12776) (Copilot)
- 693673b linter: Reduce iterations when collecting directories for
nested configs (#12329) (overlookmotel)

### 🎨 Styling

- cacbd1e linter/no-empty-function: Order match arms consistently
(#12815) (camc314)
- c15da81 codegen, formatter, linter, minifier, transformer: Re-order
imports (#12725) (Copilot)

### 🧪 Testing

- 3957fcc linter/no-undef: Add test case for `TSImportType` (#12800)
(camc314)
- d8ccff7 oxlint: Add `Tester::test_fix` mehod (#12754) (Sysix)
- c6bfb8a linter: Add rule configuration consistency test (#12744)
(camc314)
- 2ceb835 linter: Fix offset for partical source texts (#12594) (Sysix)

Co-authored-by: camc314 <18101008+camc314@users.noreply.github.com>
taearls pushed a commit to taearls/oxc that referenced this pull request Aug 12, 2025
## [1.10.0] - 2025-08-06

### 🚀 Features

- 44ac5a1 linter: Add eslint/no-unassigned-vars rule (oxc-project#11365) (yefan)
- ce6eeee linter: Add `eslint/prefer-destructuring` rule (oxc-project#12721)
(yefan)
- 9b35600 linter/jsx-a11y: Add support for mapped attributes in label
association checks (oxc-project#12805) (camc314)
- 5475075 vscode/language_server: Add `tsConfigPath` option (oxc-project#12484)
(Sysix)
- a754f7a linter: Support `countVoidThis` option in `max-params` rule
(oxc-project#12604) (yefan)

### 🐛 Bug Fixes

- 2c1dab6 linter/no-unassigned-vars: False positive with variables in
for loop (oxc-project#12833) (camc314)
- 5a24574 linter/func-style: Fix more false positives (oxc-project#12828) (camc314)
- 33a7320 linter/no-throw-literal: Fix unconditional recursion in
`could_be_error` (oxc-project#12819) (camc314)
- a3aec6a linter/explicit-module-boundary-types: Debug assertion fail
with top level return (oxc-project#12820) (camc314)
- 6efe457 linter/no-empty-function: Respect allow options for functions
and arrow functions (oxc-project#12814) (camc314)
- 1c21c46 linter/new-cap: Fix panic with computed member expr (oxc-project#12804)
(camc314)
- 45206dd linter: Apply fix span offset after fixing the section source
text (oxc-project#12758) (Sysix)
- 1e97e35 linter/unicorn/prefer-structured-clone: Update Default
implementation for `PreferStructuredCloneConfig` (oxc-project#12791) (camc314)
- d382159 linter/unicorn/prefer-object-from-entries: Update Default
implementation for `PreferObjectFromEntriesConfig` (oxc-project#12790) (camc314)
- b07d29c linter/typescript/no-this-alias: Update Default implementation
for `NoThisAliasConfig` (oxc-project#12789) (camc314)
- 0db34ab linter/react/jsx-filename-case: Update Default implementation
for `JsxFilenameExtensionConfig` (oxc-project#12788) (camc314)
- ff84eff linter/jest/prefer-lowercase-title: Update Default
implementation for `PreferLowercaseTitleConfig` (oxc-project#12787) (camc314)
- 5175c6d linter/jest/no-large-snapshots: Update Default implementation
for `NoLargeSnapshotsConfig` (oxc-project#12786) (camc314)
- 0eaebcd linter/jest/no-deprecated-functions: Update Default
implementation for `JestConfig` (oxc-project#12785) (camc314)
- 4265db7 linter/import/no-anonymous-default-export: Update Default
implementation for `NoAnonymousDefaultExport` config (oxc-project#12784) (camc314)
- 6a360e3 linter/import/extensions: Update Default implementation for
ExtensionsConfig (oxc-project#12783) (camc314)
- 42c8f29 linter: Default options for `eslint/no-else-return` (oxc-project#12762)
(Sysix)
- 4eac511 linter: Default options for `eslint/no-unneeded-ternary`
(oxc-project#12761) (Sysix)
- 9c01dbf linter: Default options for `eslint/new-cap` (oxc-project#12760) (Sysix)
- b25406f linter/explicit-function-return-types: Update default values
in ExplicitFunctionReturnTypeConfig (oxc-project#12718) (camc314)
- ce5876d parser: Validate inner expression of type assertions in
assignment targets (oxc-project#12614) (camc314)
- 5383331 linter/explicit-mod-boundary-types: False positive with jsx
elements (oxc-project#12678) (camc314)
- d0e99b5 linter/explicit-mod-boundary-types: False positive with call
expressions (oxc-project#12677) (camc314)
- 525137e linter: Add missing options to no-inner-declarations (oxc-project#12661)
(camc314)
- fc4a327 linter: No-unused-vars false positive with class property
initializers (oxc-project#12660) (camc314)
- 6af8631 linter/no-unused-vars: False positive with chain expression
(oxc-project#12609) (camc314)
- 744ef52 linter: Correct `array-type` handling of `default:
'array-simple'` (oxc-project#12607) (yefan)

### 🚜 Refactor

- 3f37ed1 linter: Replace `lazy_static` with `std::sync::LazyLock`
(oxc-project#12822) (Copilot)
- 69fd08d semantic: Improve unused label tracking and add debug
assertions (oxc-project#12812) (camc314)
- 030e397 linter: Simplify parsing CLI args (oxc-project#12802) (overlookmotel)
- c0e224a linter: Store `ExternalRuleId` in `OxlintOverrides` not raw
names (oxc-project#12502) (camc314)
- 61587e4 linter: Correct comment (oxc-project#12792) (overlookmotel)
- 5adcb98 linter: Use `u32` to keep track of last fixed source text
position (oxc-project#12696) (Sysix)
- 77acc11 linter, transformer: Use `Scoping::symbol_is_unused` (oxc-project#12666)
(overlookmotel)
- ecf1cff language_server: Simplify offset adjustment by using
`Message.move_offset` (oxc-project#12647) (Sysix)
- 7695393 linter: Simplify offset adjustment by using
`Message.move_offset` (oxc-project#12595) (Sysix)
- b36dc92 linter: Refactor large arrays to reduce binary size (oxc-project#12603)
(Boshen)
- 3b9f1f0 linter: Update iter_outer_expressions to take AstNodes
reference (oxc-project#12583) (camc314)

### 📚 Documentation

- e760fd4 linter: Complete linter rules documentation with missing "Why
is this bad?" sections (oxc-project#12757) (Copilot)
- 514322c rust: Add minimal documentation to example files in crates
directory (oxc-project#12731) (Copilot)
- 1d910d5 linter: Fix typescript/consistent-type-imports rule options to
match TypeScript ESLint (oxc-project#12707) (Copilot)
- 45e2fe8 rust: Fix typos and grammar mistakes in Rust documentation
comments (oxc-project#12715) (Copilot)
- 7660a88 linter: Improve linter rule documentation with "Why is this
bad?" sections and enhanced examples (oxc-project#12711) (Copilot)
- de1de35 rust: Add comprehensive README.md documentation for all Rust
crates (oxc-project#12706) (Copilot)

### ⚡ Performance

- 09ae2a9 linter: Eliminate unnecessary Iterator::collect() allocations
(oxc-project#12776) (Copilot)
- 693673b linter: Reduce iterations when collecting directories for
nested configs (oxc-project#12329) (overlookmotel)

### 🎨 Styling

- cacbd1e linter/no-empty-function: Order match arms consistently
(oxc-project#12815) (camc314)
- c15da81 codegen, formatter, linter, minifier, transformer: Re-order
imports (oxc-project#12725) (Copilot)

### 🧪 Testing

- 3957fcc linter/no-undef: Add test case for `TSImportType` (oxc-project#12800)
(camc314)
- d8ccff7 oxlint: Add `Tester::test_fix` mehod (oxc-project#12754) (Sysix)
- c6bfb8a linter: Add rule configuration consistency test (oxc-project#12744)
(camc314)
- 2ceb835 linter: Fix offset for partical source texts (oxc-project#12594) (Sysix)

Co-authored-by: camc314 <18101008+camc314@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-cli Area - CLI 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.

3 participants