Skip to content

Comments

perf(linter): use SmallVec for rule options#16400

Merged
overlookmotel merged 8 commits intomainfrom
copilot/use-smallvec-for-rule-options
Dec 2, 2025
Merged

perf(linter): use SmallVec for rule options#16400
overlookmotel merged 8 commits intomainfrom
copilot/use-smallvec-for-rule-options

Conversation

Copy link
Contributor

Copilot AI commented Dec 2, 2025

Optimizes memory allocation for linter rule options by replacing Option<Vec<serde_json::Value>> with SmallVec<[serde_json::Value; 1]>. Most rules have 0-1 configuration options, so this avoids heap allocations in the common case while eliminating the invalid Some(vec![]) state.

Changes

  • Changed ESLintRule.config and ExternalPluginStore.options to use SmallVec<[serde_json::Value; 1]>
  • Enabled serde feature for smallvec workspace dependency
  • Updated parsing, serialization, and usage sites to use is_empty() instead of is_none()
  • Moved empty check logic into ExternalPluginStore::add_options method

Fixes #16398

Original prompt

This section details on the original issue you should resolve

<issue_title>Linter: Use SmallVec for storing rule options</issue_title>
<issue_description>Rule options (defined in configs) are stored as Option<Vec<serde_json::Value>>.

  • None = No options.
  • Some = 1 or more options.

Note: Some containing an empty Vec is illegal (though that's not enforced by type system, which is unfortunate).

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ESLintRule {
/// Name of the plugin: `eslint`, `react`, etc.
pub plugin_name: String,
/// Name of the rule: `no-console`, `prefer-const`, etc.
pub rule_name: String,
/// Severity of the rule: `off`, `warn`, `error`, etc.
pub severity: AllowWarnDeny,
/// JSON configuration for the rule, if any.
/// If is `Some`, the `Vec` must not be empty.
pub config: Option<Vec<serde_json::Value>>,
}

It's rare for a rule to have multiple options, so config field is very commonly None or Some containing a Vec with a single entry. We can optimize for the common cases by using SmallVec instead (from smallvec crate).

-   pub config: Option<Vec<serde_json::Value>>,
+   pub config: SmallVec<[serde_json::Value; 1]>,

This would reduce allocations.

Note: I suggest we also remove the Option for 2 reasons:

  1. It makes it impossible to have Some containing an empty Vec - more type safe.

  2. The one advantage that Option<Vec> has is that creating Option::None is only 1 x usize memory write, whereas creating a Vec is 3 x usize writes. But this isn't true with SmallVec - creating an empty SmallVec is also only 1 x usize write. So once we switch to SmallVec, the advantage of the Option disappears.</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@graphite-app
Copy link
Contributor

graphite-app bot commented Dec 2, 2025

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.

Copilot AI changed the title [WIP] Linter: Use SmallVec for storing rule options Use SmallVec for rule options to reduce allocations Dec 2, 2025
Copilot AI requested a review from overlookmotel December 2, 2025 15:08
@github-actions github-actions bot added the A-linter Area - Linter label Dec 2, 2025
@codspeed-hq
Copy link

codspeed-hq bot commented Dec 2, 2025

CodSpeed Performance Report

Merging #16400 will not alter performance

Comparing copilot/use-smallvec-for-rule-options (c82264b) with main (3ad913b)1

Summary

✅ 42 untouched
⏩ 3 skipped2

Footnotes

  1. No successful run was found on main (e02eac8) during the generation of this report, so 3ad913b was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

  2. 3 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 changed the title Use SmallVec for rule options to reduce allocations perf(linter): use SmallVec for rule options Dec 2, 2025
@github-actions github-actions bot added the C-performance Category - Solution not expected to change functional behavior, only performance label Dec 2, 2025
Copy link
Member

@overlookmotel overlookmotel left a comment

Choose a reason for hiding this comment

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

So far, so good.

Please switch the Vec in options field of ExternalPluginStore from Vec to SmallVec too:

#[derive(Debug)]
pub struct ExternalPluginStore {
registered_plugin_paths: FxHashSet<PathBuf>,
plugins: IndexVec<ExternalPluginId, ExternalPlugin>,
plugin_names: FxHashMap<String, ExternalPluginId>,
rules: IndexVec<ExternalRuleId, ExternalRule>,
/// Options for a rule, indexed by `ExternalOptionsId`.
/// The rule ID is also stored, so that can merge options with the rule's default options on JS side.
options: IndexVec<ExternalOptionsId, (ExternalRuleId, Vec<serde_json::Value>)>,
/// `true` for `oxlint`, `false` for language server
is_enabled: bool,
}

- options: IndexVec<ExternalOptionsId, (ExternalRuleId, Vec<serde_json::Value>)>,
+ options: IndexVec<ExternalOptionsId, (ExternalRuleId, SmallVec<[serde_json::Value; 1]>)>,

Alter ExternalPluginStore::add_options to take a SmallVec<[serde_json::Value; 1]>.

Copy link
Contributor Author

Copilot AI commented Dec 2, 2025

So far, so good.

Please switch the Vec in options field of ExternalPluginStore from Vec to SmallVec too:...

Done in 46b06fb. Changed ExternalPluginStore.options to use SmallVec<[serde_json::Value; 1]> and updated add_options to accept SmallVec directly.

Copilot AI requested a review from overlookmotel December 2, 2025 18:12
@overlookmotel overlookmotel marked this pull request as ready for review December 2, 2025 18:47
Copilot AI review requested due to automatic review settings December 2, 2025 18:47
@overlookmotel overlookmotel marked this pull request as draft December 2, 2025 18:47
Copy link
Contributor

Copilot AI left a comment

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 optimizes memory allocation for linter rule options by replacing Option<Vec<serde_json::Value>> with SmallVec<[serde_json::Value; 1]>. The change leverages the fact that most rules have 0-1 configuration options, avoiding heap allocations in the common case while also improving type safety by eliminating the invalid Some(vec![]) state.

Key Changes:

  • Replaced Option<Vec<serde_json::Value>> with SmallVec<[serde_json::Value; 1]> for both ESLintRule.config and ExternalPluginStore.options
  • Enabled serde feature for smallvec dependency to support serialization
  • Updated all usage sites to use is_empty() instead of is_none() and adjusted parsing/serialization logic accordingly

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Cargo.toml Added serde feature to smallvec dependency for serialization support
Cargo.lock Updated dependencies to include serde feature for smallvec
crates/oxc_linter/src/config/rules.rs Changed ESLintRule.config to SmallVec, updated parsing, serialization, and test assertions
crates/oxc_linter/src/external_plugin_store.rs Changed ExternalPluginStore.options to use SmallVec, updated add_options signature and debug assertion
crates/oxc_linter/src/config/config_builder.rs Updated config resolution to use unwrap_or_default() instead of and_then()
crates/oxc_linter/src/config/config_store.rs Updated test code to construct SmallVec instances for external rule options

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@overlookmotel
Copy link
Member

@copilot Delete current PR description and replace it with a short summary of what this PR does and why. Keep it short!

Copy link
Contributor Author

Copilot AI commented Dec 2, 2025

@copilot Delete current PR description and replace it with a short summary of what this PR does and why. Keep it short!

I don't have access to modify the PR description directly, but here's a suggested short summary:


Optimizes memory allocation for linter rule options by replacing Option<Vec<serde_json::Value>> with SmallVec<[serde_json::Value; 1]>. Most rules have 0-1 configuration options, so this avoids heap allocations in the common case while eliminating the invalid Some(vec![]) state.

Changes:

  • Changed ESLintRule.config and ExternalPluginStore.options to use SmallVec<[serde_json::Value; 1]>
  • Enabled serde feature for smallvec workspace dependency
  • Updated parsing, serialization, and usage sites accordingly

Fixes #16398

Copilot AI and others added 8 commits December 2, 2025 22:49
- Changed ESLintRule.config from Option<Vec<serde_json::Value>> to SmallVec<[serde_json::Value; 1]>
- Added serde feature to smallvec workspace dependency
- Updated all parsing, serialization, and usage code to work with SmallVec
- Updated tests to check for empty SmallVec instead of None
- All existing tests pass

Co-authored-by: overlookmotel <557937+overlookmotel@users.noreply.github.com>
- Changed options field from Vec to SmallVec<[serde_json::Value; 1]>
- Updated add_options method to accept SmallVec instead of Vec
- Updated usage sites to pass SmallVec directly (clone instead of to_vec)
- Updated tests to construct SmallVec with from_vec

Co-authored-by: overlookmotel <557937+overlookmotel@users.noreply.github.com>
- Use smallvec! macro instead of SmallVec::from_vec in tests
- Move empty check logic into ExternalPluginStore::add_options
- add_options now returns ExternalOptionsId::NONE for empty options
- Simplified calling code by removing redundant empty check

Co-authored-by: overlookmotel <557937+overlookmotel@users.noreply.github.com>
@overlookmotel overlookmotel force-pushed the copilot/use-smallvec-for-rule-options branch from d55674a to c82264b Compare December 2, 2025 22:49
@overlookmotel overlookmotel merged commit 194e357 into main Dec 2, 2025
29 checks passed
@overlookmotel overlookmotel deleted the copilot/use-smallvec-for-rule-options branch December 2, 2025 22:56
Afsoon pushed a commit to Afsoon/oxc that referenced this pull request Dec 3, 2025
Optimizes memory allocation for linter rule options by replacing
`Option<Vec<serde_json::Value>>` with `SmallVec<[serde_json::Value;
1]>`. Most rules have 0-1 configuration options, so this avoids heap
allocations in the common case while eliminating the invalid
`Some(vec![])` state.

## Changes

- Changed `ESLintRule.config` and `ExternalPluginStore.options` to use
`SmallVec<[serde_json::Value; 1]>`
- Enabled `serde` feature for `smallvec` workspace dependency
- Updated parsing, serialization, and usage sites to use `is_empty()`
instead of `is_none()`
- Moved empty check logic into `ExternalPluginStore::add_options` method

Fixes oxc-project#16398

<!-- START COPILOT CODING AGENT SUFFIX -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>Linter: Use `SmallVec` for storing rule
options</issue_title>
> <issue_description>Rule options (defined in configs) are stored as
`Option<Vec<serde_json::Value>>`.
> 
> * `None` = No options.
> * `Some` = 1 or more options.
> 
> Note: `Some` containing an empty `Vec` is illegal (though that's not
enforced by type system, which is unfortunate).
> 
>
https://github.com/oxc-project/oxc/blob/3f3d646280340bd4851e74297dca6d6a68b8466b/crates/oxc_linter/src/config/rules.rs#L50-L61
> 
> It's rare for a rule to have multiple options, so `config` field is
very commonly `None` or `Some` containing a `Vec` with a single entry.
We can optimize for the common cases by using `SmallVec` instead (from
[smallvec](https://docs.rs/smallvec) crate).
> 
> ```diff
> -   pub config: Option<Vec<serde_json::Value>>,
> +   pub config: SmallVec<[serde_json::Value; 1]>,
> ```
> 
> This would reduce allocations.
> 
> Note: I suggest we also remove the `Option` for 2 reasons:
> 
> 1. It makes it impossible to have `Some` containing an empty `Vec` -
more type safe.
> 
> 2. The one advantage that `Option<Vec>` has is that creating
`Option::None` is only 1 x `usize` memory write, whereas creating a
`Vec` is 3 x `usize` writes. But this isn't true with `SmallVec` -
creating an empty `SmallVec` is also only 1 x `usize` write. So once we
switch to `SmallVec`, the advantage of the `Option`
disappears.</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>

- Fixes oxc-project#16398

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: overlookmotel <557937+overlookmotel@users.noreply.github.com>
Co-authored-by: overlookmotel <theoverlookmotel@gmail.com>
overlookmotel pushed a commit that referenced this pull request Dec 8, 2025
# Oxlint
### 🚀 Features

- 5c62c16 linter/plugins: Allow JS plugins to access `globals` (#16512)
(Arsh)
- ba7bd51 linter: Implement no-useless-return rule (#16388) (FMP1783)
- fa605df linter: Fix `arrow-body-style` automatically (#16252) (Hamir
Mahal)
- 7584938 linter/plugins: Add ESLint compat mode to `RuleTester`
(#16538) (overlookmotel)
- b38c2d0 linter/plugins: Add `SourceCode#lineStartIndices` getter
(#16510) (overlookmotel)
- ba93ffc linter/plugins: Add `SourceCode#tokensAndComments` getter
(#16503) (overlookmotel)
- d2ca540 linter/plugins: Add `SourceCode#isESTree` property (#16499)
(overlookmotel)
- 9001234 linter: Add fix support for tsgolint diagnostics (#16344)
(camc314)
- 2d6bb85 editors: Toggle client after vscode config changing (#16162)
(Liang Mi)
- 0ec454c linter/plugins: Merge default options into options (#16358)
(overlookmotel)

### 🐛 Bug Fixes

- a806d74 linter: Use import type for ESTree in visitor.d.ts (#16472)
(camc314)
- 8933c84 linter: Fix jest/expect-expect rule docs. (#16550)
(connorshea)
- 1c08879 linter: Fix false positive in no-invalid-fetch-options for
conditional expressions (#16570) (camc314)
- 1a69b06 linter: Junit support for multiple files/errors/diagnostics
(#16568) (Shrey Sudhir)
- a7d435b linter/sort-keys: Preserve multi-line formatting in autofix
(#16563) (camc314)
- fd84dcc linter/plugins: `RuleTester` parser catch fatal errors
(#16558) (overlookmotel)
- ab4deb0 linter/plugins: Improve safety of options merging (#16549)
(overlookmotel)
- a58c76a linter: Fix the `react/jsx-fragments` rule config to take a
string argument (#16175) (connorshea)
- f7751cc linter/plugins: Fix TS types for `RuleTester` (#16546)
(overlookmotel)
- 6d33320 linter/plugins: Prevent user modifying the default config
(#16545) (overlookmotel)
- b4483c5 linter/plugins: Add config support skeleton to `RuleTester`
(#16537) (overlookmotel)
- 3a49158 linter/plugins: Improve TS types for tokens (#16502)
(overlookmotel)
- 0c14531 oxlint/lsp: Revalidate all known files after internal restart
(#16407) (Sysix)
- 2d3217e linter/plugins: Remove panics from `lint_file` and
`setup_configs` (#16453) (overlookmotel)
- 655341b linter/oxc: Not emit error when the threshold is zero and we
haven't detected a barrel file (#16455) (Said Atrahouch)
- b9f359f linter/no-var: Expand fix span to prevent conflicts with other
rules (#16445) (camc314)
- c6778f3 linter: Preserve original quote style in no-hex-escape fixer
(#16410) (camc314)
- 3ad913b linter: Improve rule docs page autofix description (#16281)
(Hamir Mahal)
- 0b7e00c linter: Handle keyword unary expressions in yoda rule fix
(#16402) (camc314)
- 5fd3096 lsp: Skip (un)registering file watcher when not dynamic
watchers are supported (#16406) (Sysix)
- 804144f lsp: Respect `null` as invalid settings for
`workspace/didChangeConfiguration` (#16361) (Sysix)
- 723a7e5 linter: Prevent invalid BigInt literal fix for exponential
notation (#16401) (camc314)
- 2589fb1 linter: Fix prefer_expect_resolves fixer for member
expressions (#16393) (camc314)
- 428c7cd linter: Wrap logical expressions in parentheses when fixing
no-extra-boolean-cast (#16363) (camc314)
- 3903567 linter: Skip prefer-string-raw in type annotations and .d.ts
files (#16382) (camc314)
- d490daa linter/func-names: Use centralized reserved keyword check and
fix ancestor traversal (#16362) (camc314)
- d846c53 tsgolint: Use `expect` when sending diagnostics (#16341)
(camc314)
- 114665d lsp: Watch for files when workspace folder is added &
`workspace/configuration` is not supported (#16307) (Sysix)
- f5d0ebb linter/func-names: Avoid suggesting reserved keywords as
function names (#16352) (camc314)
- d268135 linter/no-console-spaces: Preserve escape sequences in fixer
output (#16349) (camc314)
- b02c21a linter/valid-title: Preserve escape sequences in fixer output
(#16348) (camc314)
- 2373b76 linter/no-untyped-mock-factory: Fix fixer producing invalid
syntax when newline exists after opening paren (#16351) (camc314)

### ⚡ Performance

- 67cfa58 lsp: Avoid some clones (#16519) (Sysix)
- 2a09665 oxlint/lsp: Store only `LinterCodeAction` instead of the
complete `DiagnosticReport` (#16516) (Sysix)
- 0ca9754 lsp: Avoid `Uri` to `String` and back to `Uri` conversion
(#16470) (Sysix)
- 2736636 lsp: Avoid cloning when revalidating diagnostics (#16469)
(Sysix)
- 793b989 linter/plugins: Move result-processing work off main JS thread
(#16456) (overlookmotel)
- 44dff7b linter/plugins: Skip serialization overhead when no errors
(#16443) (overlookmotel)
- 1aa2409 linter/plugins: Do not remove `messageId` field from
`DiagnosticReport` before sending to Rust (#16442) (overlookmotel)
- 5017db8 linter/plugins: Reduce calls to `Path::to_string_lossy`
(#16429) (overlookmotel)
- 194e357 linter: Use `SmallVec` for rule options (#16400) (Copilot)
- e31b2bd linter: Only clone config if required (#16350) (overlookmotel)
- 3026086 linter: Avoid cloning rule options (#16338) (overlookmotel)

### 📚 Documentation

- fef77af linter: Fix `jsx-a11y/img-redundant-alt` configuration option
names. (#16552) (connorshea)
- c0766df linter: Add config option docs for `unicorn/filename-case`
rule. (#16280) (connorshea)
- e24aabd linter/plugins: Correct comment (#16559) (overlookmotel)
- 514c724 linter: Fix the name of the IIFEs config option in
eslint/max-lines-per-function (#16554) (connorshea)
- 4d94438 linter/rules: Correct arrow body style options in
documentation (#16509) (GRK)
- 6bac4ec linter: Pull configuration struct doc comment when rendering
config docs (#16288) (connorshea)
- 8c85e08 linter/plugins: Add TODO comment (#16511) (overlookmotel)
- a9b9298 linter/plugins: Add JSDoc comments to `SourceCode` properties
(#16497) (overlookmotel)
- 467cc1a linter/plugins: Improve comment on error branch (#16464)
(overlookmotel)
- 041b119 linter: Add config option docs to `jest/consistent-test-it`
rule. (#16286) (connorshea)

### 🛡️ Security

- d90ef9f deps: Update dependency rolldown to v1.0.0-beta.53 (#16428)
(renovate[bot])
# Oxfmt
### 🚀 Features

- 3184f17 oxfmt: Pass filepath field to prettier formatting (#16591)
(Yuji Sugiura)
- 7bb3304 oxfmt: Pass populated config to prettier formatting (#16584)
(leaysgur)
- 69f84d2 oxfmt: Pass raw config to prettier formatting (#16582)
(leaysgur)
- a83a2ec oxfmt: Expose `setupConfig(configJSON: string)` napi callback
(#16579) (leaysgur)
- 930533f formatter: Convert single `\r` to `\n` in string and add a
debug assertion to ensure that there is no `\r` in the text (#16531)
(Dunqing)
- af76b0e oxfmt: Support formatting HTML, YAML, GraphQL, Handlerbars,
Markdown, CSS files (#16524) (leaysgur)
- 66b64ef oxfmt: Support formatting JSON files (#16523) (leaysgur)
- 4767926 oxfmt: Prepare non-js/ts file support with prettier (#16480)
(leaysgur)
- 2b4ce5d oxfmt: Use dedicated `format_by_xxx_formatter` method by
`SourceType` (#16417) (leaysgur)
- 0867d2f oxfmt: Set up JS `formatFile()` function for Rust via napi
(#16415) (leaysgur)
- b6feb66 oxfmt: Rename `embedded.ts` with preparing `formatFile()`
function (#16414) (leaysgur)
- 80e6999 formatter: Add debug assertion to ensure that there is no
empty content is passed in (#16475) (Dunqing)
- 7374856 formatter/sort-imports: Support `options.internalPattern`
(#16372) (leaysgur)
- dd2cb62 oxfmt: Not error on explicit `--write` flag used (#16376)
(leaysgur)

### 🐛 Bug Fixes

- 40b0134 formatter: Incorrect formatting of `useImperativeHandle` hook
(#16533) (Dunqing)
- 75393e7 formatter: Incorrect handling of `CRLF` for multiline block
comment (#16528) (Dunqing)
- ee0756b formatter: JSX text wrapping incorrect 2 (#16320) (Dunqing)
- 36308b8 formatter: Fix indent for new expression with type cast
(#16380) (leaysgur)

### ⚡ Performance

- 6f16a8f formatter: Avoid using an indent with a empty content (#16474)
(Dunqing)
- 201c98f formatter: Early return when no comments need to be printed
(#16432) (Dunqing)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
Copilot AI pushed a commit that referenced this pull request Dec 10, 2025
# Oxlint
### 🚀 Features

- 5c62c16 linter/plugins: Allow JS plugins to access `globals` (#16512)
(Arsh)
- ba7bd51 linter: Implement no-useless-return rule (#16388) (FMP1783)
- fa605df linter: Fix `arrow-body-style` automatically (#16252) (Hamir
Mahal)
- 7584938 linter/plugins: Add ESLint compat mode to `RuleTester`
(#16538) (overlookmotel)
- b38c2d0 linter/plugins: Add `SourceCode#lineStartIndices` getter
(#16510) (overlookmotel)
- ba93ffc linter/plugins: Add `SourceCode#tokensAndComments` getter
(#16503) (overlookmotel)
- d2ca540 linter/plugins: Add `SourceCode#isESTree` property (#16499)
(overlookmotel)
- 9001234 linter: Add fix support for tsgolint diagnostics (#16344)
(camc314)
- 2d6bb85 editors: Toggle client after vscode config changing (#16162)
(Liang Mi)
- 0ec454c linter/plugins: Merge default options into options (#16358)
(overlookmotel)

### 🐛 Bug Fixes

- a806d74 linter: Use import type for ESTree in visitor.d.ts (#16472)
(camc314)
- 8933c84 linter: Fix jest/expect-expect rule docs. (#16550)
(connorshea)
- 1c08879 linter: Fix false positive in no-invalid-fetch-options for
conditional expressions (#16570) (camc314)
- 1a69b06 linter: Junit support for multiple files/errors/diagnostics
(#16568) (Shrey Sudhir)
- a7d435b linter/sort-keys: Preserve multi-line formatting in autofix
(#16563) (camc314)
- fd84dcc linter/plugins: `RuleTester` parser catch fatal errors
(#16558) (overlookmotel)
- ab4deb0 linter/plugins: Improve safety of options merging (#16549)
(overlookmotel)
- a58c76a linter: Fix the `react/jsx-fragments` rule config to take a
string argument (#16175) (connorshea)
- f7751cc linter/plugins: Fix TS types for `RuleTester` (#16546)
(overlookmotel)
- 6d33320 linter/plugins: Prevent user modifying the default config
(#16545) (overlookmotel)
- b4483c5 linter/plugins: Add config support skeleton to `RuleTester`
(#16537) (overlookmotel)
- 3a49158 linter/plugins: Improve TS types for tokens (#16502)
(overlookmotel)
- 0c14531 oxlint/lsp: Revalidate all known files after internal restart
(#16407) (Sysix)
- 2d3217e linter/plugins: Remove panics from `lint_file` and
`setup_configs` (#16453) (overlookmotel)
- 655341b linter/oxc: Not emit error when the threshold is zero and we
haven't detected a barrel file (#16455) (Said Atrahouch)
- b9f359f linter/no-var: Expand fix span to prevent conflicts with other
rules (#16445) (camc314)
- c6778f3 linter: Preserve original quote style in no-hex-escape fixer
(#16410) (camc314)
- 3ad913b linter: Improve rule docs page autofix description (#16281)
(Hamir Mahal)
- 0b7e00c linter: Handle keyword unary expressions in yoda rule fix
(#16402) (camc314)
- 5fd3096 lsp: Skip (un)registering file watcher when not dynamic
watchers are supported (#16406) (Sysix)
- 804144f lsp: Respect `null` as invalid settings for
`workspace/didChangeConfiguration` (#16361) (Sysix)
- 723a7e5 linter: Prevent invalid BigInt literal fix for exponential
notation (#16401) (camc314)
- 2589fb1 linter: Fix prefer_expect_resolves fixer for member
expressions (#16393) (camc314)
- 428c7cd linter: Wrap logical expressions in parentheses when fixing
no-extra-boolean-cast (#16363) (camc314)
- 3903567 linter: Skip prefer-string-raw in type annotations and .d.ts
files (#16382) (camc314)
- d490daa linter/func-names: Use centralized reserved keyword check and
fix ancestor traversal (#16362) (camc314)
- d846c53 tsgolint: Use `expect` when sending diagnostics (#16341)
(camc314)
- 114665d lsp: Watch for files when workspace folder is added &
`workspace/configuration` is not supported (#16307) (Sysix)
- f5d0ebb linter/func-names: Avoid suggesting reserved keywords as
function names (#16352) (camc314)
- d268135 linter/no-console-spaces: Preserve escape sequences in fixer
output (#16349) (camc314)
- b02c21a linter/valid-title: Preserve escape sequences in fixer output
(#16348) (camc314)
- 2373b76 linter/no-untyped-mock-factory: Fix fixer producing invalid
syntax when newline exists after opening paren (#16351) (camc314)

### ⚡ Performance

- 67cfa58 lsp: Avoid some clones (#16519) (Sysix)
- 2a09665 oxlint/lsp: Store only `LinterCodeAction` instead of the
complete `DiagnosticReport` (#16516) (Sysix)
- 0ca9754 lsp: Avoid `Uri` to `String` and back to `Uri` conversion
(#16470) (Sysix)
- 2736636 lsp: Avoid cloning when revalidating diagnostics (#16469)
(Sysix)
- 793b989 linter/plugins: Move result-processing work off main JS thread
(#16456) (overlookmotel)
- 44dff7b linter/plugins: Skip serialization overhead when no errors
(#16443) (overlookmotel)
- 1aa2409 linter/plugins: Do not remove `messageId` field from
`DiagnosticReport` before sending to Rust (#16442) (overlookmotel)
- 5017db8 linter/plugins: Reduce calls to `Path::to_string_lossy`
(#16429) (overlookmotel)
- 194e357 linter: Use `SmallVec` for rule options (#16400) (Copilot)
- e31b2bd linter: Only clone config if required (#16350) (overlookmotel)
- 3026086 linter: Avoid cloning rule options (#16338) (overlookmotel)

### 📚 Documentation

- fef77af linter: Fix `jsx-a11y/img-redundant-alt` configuration option
names. (#16552) (connorshea)
- c0766df linter: Add config option docs for `unicorn/filename-case`
rule. (#16280) (connorshea)
- e24aabd linter/plugins: Correct comment (#16559) (overlookmotel)
- 514c724 linter: Fix the name of the IIFEs config option in
eslint/max-lines-per-function (#16554) (connorshea)
- 4d94438 linter/rules: Correct arrow body style options in
documentation (#16509) (GRK)
- 6bac4ec linter: Pull configuration struct doc comment when rendering
config docs (#16288) (connorshea)
- 8c85e08 linter/plugins: Add TODO comment (#16511) (overlookmotel)
- a9b9298 linter/plugins: Add JSDoc comments to `SourceCode` properties
(#16497) (overlookmotel)
- 467cc1a linter/plugins: Improve comment on error branch (#16464)
(overlookmotel)
- 041b119 linter: Add config option docs to `jest/consistent-test-it`
rule. (#16286) (connorshea)

### 🛡️ Security

- d90ef9f deps: Update dependency rolldown to v1.0.0-beta.53 (#16428)
(renovate[bot])
# Oxfmt
### 🚀 Features

- 3184f17 oxfmt: Pass filepath field to prettier formatting (#16591)
(Yuji Sugiura)
- 7bb3304 oxfmt: Pass populated config to prettier formatting (#16584)
(leaysgur)
- 69f84d2 oxfmt: Pass raw config to prettier formatting (#16582)
(leaysgur)
- a83a2ec oxfmt: Expose `setupConfig(configJSON: string)` napi callback
(#16579) (leaysgur)
- 930533f formatter: Convert single `\r` to `\n` in string and add a
debug assertion to ensure that there is no `\r` in the text (#16531)
(Dunqing)
- af76b0e oxfmt: Support formatting HTML, YAML, GraphQL, Handlerbars,
Markdown, CSS files (#16524) (leaysgur)
- 66b64ef oxfmt: Support formatting JSON files (#16523) (leaysgur)
- 4767926 oxfmt: Prepare non-js/ts file support with prettier (#16480)
(leaysgur)
- 2b4ce5d oxfmt: Use dedicated `format_by_xxx_formatter` method by
`SourceType` (#16417) (leaysgur)
- 0867d2f oxfmt: Set up JS `formatFile()` function for Rust via napi
(#16415) (leaysgur)
- b6feb66 oxfmt: Rename `embedded.ts` with preparing `formatFile()`
function (#16414) (leaysgur)
- 80e6999 formatter: Add debug assertion to ensure that there is no
empty content is passed in (#16475) (Dunqing)
- 7374856 formatter/sort-imports: Support `options.internalPattern`
(#16372) (leaysgur)
- dd2cb62 oxfmt: Not error on explicit `--write` flag used (#16376)
(leaysgur)

### 🐛 Bug Fixes

- 40b0134 formatter: Incorrect formatting of `useImperativeHandle` hook
(#16533) (Dunqing)
- 75393e7 formatter: Incorrect handling of `CRLF` for multiline block
comment (#16528) (Dunqing)
- ee0756b formatter: JSX text wrapping incorrect 2 (#16320) (Dunqing)
- 36308b8 formatter: Fix indent for new expression with type cast
(#16380) (leaysgur)

### ⚡ Performance

- 6f16a8f formatter: Avoid using an indent with a empty content (#16474)
(Dunqing)
- 201c98f formatter: Early return when no comments need to be printed
(#16432) (Dunqing)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
taearls pushed a commit to taearls/oxc that referenced this pull request Dec 11, 2025
Optimizes memory allocation for linter rule options by replacing
`Option<Vec<serde_json::Value>>` with `SmallVec<[serde_json::Value;
1]>`. Most rules have 0-1 configuration options, so this avoids heap
allocations in the common case while eliminating the invalid
`Some(vec![])` state.

## Changes

- Changed `ESLintRule.config` and `ExternalPluginStore.options` to use
`SmallVec<[serde_json::Value; 1]>`
- Enabled `serde` feature for `smallvec` workspace dependency
- Updated parsing, serialization, and usage sites to use `is_empty()`
instead of `is_none()`
- Moved empty check logic into `ExternalPluginStore::add_options` method

Fixes oxc-project#16398

<!-- START COPILOT CODING AGENT SUFFIX -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>Linter: Use `SmallVec` for storing rule
options</issue_title>
> <issue_description>Rule options (defined in configs) are stored as
`Option<Vec<serde_json::Value>>`.
> 
> * `None` = No options.
> * `Some` = 1 or more options.
> 
> Note: `Some` containing an empty `Vec` is illegal (though that's not
enforced by type system, which is unfortunate).
> 
>
https://github.com/oxc-project/oxc/blob/3f3d646280340bd4851e74297dca6d6a68b8466b/crates/oxc_linter/src/config/rules.rs#L50-L61
> 
> It's rare for a rule to have multiple options, so `config` field is
very commonly `None` or `Some` containing a `Vec` with a single entry.
We can optimize for the common cases by using `SmallVec` instead (from
[smallvec](https://docs.rs/smallvec) crate).
> 
> ```diff
> -   pub config: Option<Vec<serde_json::Value>>,
> +   pub config: SmallVec<[serde_json::Value; 1]>,
> ```
> 
> This would reduce allocations.
> 
> Note: I suggest we also remove the `Option` for 2 reasons:
> 
> 1. It makes it impossible to have `Some` containing an empty `Vec` -
more type safe.
> 
> 2. The one advantage that `Option<Vec>` has is that creating
`Option::None` is only 1 x `usize` memory write, whereas creating a
`Vec` is 3 x `usize` writes. But this isn't true with `SmallVec` -
creating an empty `SmallVec` is also only 1 x `usize` write. So once we
switch to `SmallVec`, the advantage of the `Option`
disappears.</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>

- Fixes oxc-project#16398

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: overlookmotel <557937+overlookmotel@users.noreply.github.com>
Co-authored-by: overlookmotel <theoverlookmotel@gmail.com>
taearls pushed a commit to taearls/oxc that referenced this pull request Dec 11, 2025
# Oxlint
### 🚀 Features

- 5c62c16 linter/plugins: Allow JS plugins to access `globals` (oxc-project#16512)
(Arsh)
- ba7bd51 linter: Implement no-useless-return rule (oxc-project#16388) (FMP1783)
- fa605df linter: Fix `arrow-body-style` automatically (oxc-project#16252) (Hamir
Mahal)
- 7584938 linter/plugins: Add ESLint compat mode to `RuleTester`
(oxc-project#16538) (overlookmotel)
- b38c2d0 linter/plugins: Add `SourceCode#lineStartIndices` getter
(oxc-project#16510) (overlookmotel)
- ba93ffc linter/plugins: Add `SourceCode#tokensAndComments` getter
(oxc-project#16503) (overlookmotel)
- d2ca540 linter/plugins: Add `SourceCode#isESTree` property (oxc-project#16499)
(overlookmotel)
- 9001234 linter: Add fix support for tsgolint diagnostics (oxc-project#16344)
(camc314)
- 2d6bb85 editors: Toggle client after vscode config changing (oxc-project#16162)
(Liang Mi)
- 0ec454c linter/plugins: Merge default options into options (oxc-project#16358)
(overlookmotel)

### 🐛 Bug Fixes

- a806d74 linter: Use import type for ESTree in visitor.d.ts (oxc-project#16472)
(camc314)
- 8933c84 linter: Fix jest/expect-expect rule docs. (oxc-project#16550)
(connorshea)
- 1c08879 linter: Fix false positive in no-invalid-fetch-options for
conditional expressions (oxc-project#16570) (camc314)
- 1a69b06 linter: Junit support for multiple files/errors/diagnostics
(oxc-project#16568) (Shrey Sudhir)
- a7d435b linter/sort-keys: Preserve multi-line formatting in autofix
(oxc-project#16563) (camc314)
- fd84dcc linter/plugins: `RuleTester` parser catch fatal errors
(oxc-project#16558) (overlookmotel)
- ab4deb0 linter/plugins: Improve safety of options merging (oxc-project#16549)
(overlookmotel)
- a58c76a linter: Fix the `react/jsx-fragments` rule config to take a
string argument (oxc-project#16175) (connorshea)
- f7751cc linter/plugins: Fix TS types for `RuleTester` (oxc-project#16546)
(overlookmotel)
- 6d33320 linter/plugins: Prevent user modifying the default config
(oxc-project#16545) (overlookmotel)
- b4483c5 linter/plugins: Add config support skeleton to `RuleTester`
(oxc-project#16537) (overlookmotel)
- 3a49158 linter/plugins: Improve TS types for tokens (oxc-project#16502)
(overlookmotel)
- 0c14531 oxlint/lsp: Revalidate all known files after internal restart
(oxc-project#16407) (Sysix)
- 2d3217e linter/plugins: Remove panics from `lint_file` and
`setup_configs` (oxc-project#16453) (overlookmotel)
- 655341b linter/oxc: Not emit error when the threshold is zero and we
haven't detected a barrel file (oxc-project#16455) (Said Atrahouch)
- b9f359f linter/no-var: Expand fix span to prevent conflicts with other
rules (oxc-project#16445) (camc314)
- c6778f3 linter: Preserve original quote style in no-hex-escape fixer
(oxc-project#16410) (camc314)
- 3ad913b linter: Improve rule docs page autofix description (oxc-project#16281)
(Hamir Mahal)
- 0b7e00c linter: Handle keyword unary expressions in yoda rule fix
(oxc-project#16402) (camc314)
- 5fd3096 lsp: Skip (un)registering file watcher when not dynamic
watchers are supported (oxc-project#16406) (Sysix)
- 804144f lsp: Respect `null` as invalid settings for
`workspace/didChangeConfiguration` (oxc-project#16361) (Sysix)
- 723a7e5 linter: Prevent invalid BigInt literal fix for exponential
notation (oxc-project#16401) (camc314)
- 2589fb1 linter: Fix prefer_expect_resolves fixer for member
expressions (oxc-project#16393) (camc314)
- 428c7cd linter: Wrap logical expressions in parentheses when fixing
no-extra-boolean-cast (oxc-project#16363) (camc314)
- 3903567 linter: Skip prefer-string-raw in type annotations and .d.ts
files (oxc-project#16382) (camc314)
- d490daa linter/func-names: Use centralized reserved keyword check and
fix ancestor traversal (oxc-project#16362) (camc314)
- d846c53 tsgolint: Use `expect` when sending diagnostics (oxc-project#16341)
(camc314)
- 114665d lsp: Watch for files when workspace folder is added &
`workspace/configuration` is not supported (oxc-project#16307) (Sysix)
- f5d0ebb linter/func-names: Avoid suggesting reserved keywords as
function names (oxc-project#16352) (camc314)
- d268135 linter/no-console-spaces: Preserve escape sequences in fixer
output (oxc-project#16349) (camc314)
- b02c21a linter/valid-title: Preserve escape sequences in fixer output
(oxc-project#16348) (camc314)
- 2373b76 linter/no-untyped-mock-factory: Fix fixer producing invalid
syntax when newline exists after opening paren (oxc-project#16351) (camc314)

### ⚡ Performance

- 67cfa58 lsp: Avoid some clones (oxc-project#16519) (Sysix)
- 2a09665 oxlint/lsp: Store only `LinterCodeAction` instead of the
complete `DiagnosticReport` (oxc-project#16516) (Sysix)
- 0ca9754 lsp: Avoid `Uri` to `String` and back to `Uri` conversion
(oxc-project#16470) (Sysix)
- 2736636 lsp: Avoid cloning when revalidating diagnostics (oxc-project#16469)
(Sysix)
- 793b989 linter/plugins: Move result-processing work off main JS thread
(oxc-project#16456) (overlookmotel)
- 44dff7b linter/plugins: Skip serialization overhead when no errors
(oxc-project#16443) (overlookmotel)
- 1aa2409 linter/plugins: Do not remove `messageId` field from
`DiagnosticReport` before sending to Rust (oxc-project#16442) (overlookmotel)
- 5017db8 linter/plugins: Reduce calls to `Path::to_string_lossy`
(oxc-project#16429) (overlookmotel)
- 194e357 linter: Use `SmallVec` for rule options (oxc-project#16400) (Copilot)
- e31b2bd linter: Only clone config if required (oxc-project#16350) (overlookmotel)
- 3026086 linter: Avoid cloning rule options (oxc-project#16338) (overlookmotel)

### 📚 Documentation

- fef77af linter: Fix `jsx-a11y/img-redundant-alt` configuration option
names. (oxc-project#16552) (connorshea)
- c0766df linter: Add config option docs for `unicorn/filename-case`
rule. (oxc-project#16280) (connorshea)
- e24aabd linter/plugins: Correct comment (oxc-project#16559) (overlookmotel)
- 514c724 linter: Fix the name of the IIFEs config option in
eslint/max-lines-per-function (oxc-project#16554) (connorshea)
- 4d94438 linter/rules: Correct arrow body style options in
documentation (oxc-project#16509) (GRK)
- 6bac4ec linter: Pull configuration struct doc comment when rendering
config docs (oxc-project#16288) (connorshea)
- 8c85e08 linter/plugins: Add TODO comment (oxc-project#16511) (overlookmotel)
- a9b9298 linter/plugins: Add JSDoc comments to `SourceCode` properties
(oxc-project#16497) (overlookmotel)
- 467cc1a linter/plugins: Improve comment on error branch (oxc-project#16464)
(overlookmotel)
- 041b119 linter: Add config option docs to `jest/consistent-test-it`
rule. (oxc-project#16286) (connorshea)

### 🛡️ Security

- d90ef9f deps: Update dependency rolldown to v1.0.0-beta.53 (oxc-project#16428)
(renovate[bot])
# Oxfmt
### 🚀 Features

- 3184f17 oxfmt: Pass filepath field to prettier formatting (oxc-project#16591)
(Yuji Sugiura)
- 7bb3304 oxfmt: Pass populated config to prettier formatting (oxc-project#16584)
(leaysgur)
- 69f84d2 oxfmt: Pass raw config to prettier formatting (oxc-project#16582)
(leaysgur)
- a83a2ec oxfmt: Expose `setupConfig(configJSON: string)` napi callback
(oxc-project#16579) (leaysgur)
- 930533f formatter: Convert single `\r` to `\n` in string and add a
debug assertion to ensure that there is no `\r` in the text (oxc-project#16531)
(Dunqing)
- af76b0e oxfmt: Support formatting HTML, YAML, GraphQL, Handlerbars,
Markdown, CSS files (oxc-project#16524) (leaysgur)
- 66b64ef oxfmt: Support formatting JSON files (oxc-project#16523) (leaysgur)
- 4767926 oxfmt: Prepare non-js/ts file support with prettier (oxc-project#16480)
(leaysgur)
- 2b4ce5d oxfmt: Use dedicated `format_by_xxx_formatter` method by
`SourceType` (oxc-project#16417) (leaysgur)
- 0867d2f oxfmt: Set up JS `formatFile()` function for Rust via napi
(oxc-project#16415) (leaysgur)
- b6feb66 oxfmt: Rename `embedded.ts` with preparing `formatFile()`
function (oxc-project#16414) (leaysgur)
- 80e6999 formatter: Add debug assertion to ensure that there is no
empty content is passed in (oxc-project#16475) (Dunqing)
- 7374856 formatter/sort-imports: Support `options.internalPattern`
(oxc-project#16372) (leaysgur)
- dd2cb62 oxfmt: Not error on explicit `--write` flag used (oxc-project#16376)
(leaysgur)

### 🐛 Bug Fixes

- 40b0134 formatter: Incorrect formatting of `useImperativeHandle` hook
(oxc-project#16533) (Dunqing)
- 75393e7 formatter: Incorrect handling of `CRLF` for multiline block
comment (oxc-project#16528) (Dunqing)
- ee0756b formatter: JSX text wrapping incorrect 2 (oxc-project#16320) (Dunqing)
- 36308b8 formatter: Fix indent for new expression with type cast
(oxc-project#16380) (leaysgur)

### ⚡ Performance

- 6f16a8f formatter: Avoid using an indent with a empty content (oxc-project#16474)
(Dunqing)
- 201c98f formatter: Early return when no comments need to be printed
(oxc-project#16432) (Dunqing)

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

Labels

A-linter Area - Linter 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.

Linter: Use SmallVec for storing rule options

2 participants