Skip to content

Comments

feat(linter/plugins): rule options#16215

Merged
overlookmotel merged 23 commits intooxc-project:mainfrom
lilnasy:js-plugin-options
Dec 1, 2025
Merged

feat(linter/plugins): rule options#16215
overlookmotel merged 23 commits intooxc-project:mainfrom
lilnasy:js-plugin-options

Conversation

@lilnasy
Copy link
Contributor

@lilnasy lilnasy commented Nov 27, 2025

Big changes from the original approach

  • Options IDs are stored in their own arrays. This was laid out in refactor(linter/plugins): prepare for rule options #16158. The original approach was to store ruleId-optionId pairs as tuples in an array - several heap allocated objects and excessive pointer indirection.
  • The rust code populates the options by calling a new JS callback named setupConfigs(). I expect we would use this callback for more plugin related data, including settings which are currently implemented in a hacky way.
  • Investigate a refactor to avoid requiring a mutable reference to ExternalPluginStore everywhere.

Smaller Changes

  • Removed the extra fixture ("working version"). There were no meaningful changes compared to the first fixture. Probably slop.

Copilot AI review requested due to automatic review settings November 27, 2025 20:58
@lilnasy lilnasy marked this pull request as draft November 27, 2025 20:58
@github-actions github-actions bot added A-linter Area - Linter A-cli Area - CLI A-editor Area - Editor and Language Server A-linter-plugins Area - Linter JS plugins C-enhancement Category - New feature or request labels Nov 27, 2025
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 implements rule options support for external JavaScript plugins in the linter. It introduces an ExternalOptionsId type to track and pass rule-specific configuration options from Rust to JavaScript plugins, allowing JS plugin rules to receive configuration parameters similar to ESLint rule options.

Key changes:

  • Added ExternalOptionsId type and options storage to ExternalPluginStore with index 0 reserved for "no options"
  • Updated external rule signatures from (ExternalRuleId, AllowWarnDeny) to (ExternalRuleId, ExternalOptionsId, AllowWarnDeny) throughout the codebase
  • Implemented options serialization/deserialization flow between Rust and JavaScript using OnceLock for thread-safe caching

Reviewed changes

Copilot reviewed 23 out of 24 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/oxc_linter/src/external_plugin_store.rs Added ExternalOptionsId type and options storage with index 0 reserved for empty/null options
crates/oxc_linter/src/external_linter.rs Updated callback signature to include options_ids parameter
crates/oxc_linter/src/lib.rs Updated external rule handling to pass options IDs to JS side
crates/oxc_linter/src/config/rules.rs Modified override_rules to track options alongside severity; added comprehensive tests
crates/oxc_linter/src/config/config_store.rs Updated rule storage and override logic to handle options IDs; added override precedence test
crates/oxc_linter/src/config/config_builder.rs Updated builder to accept mutable reference to external plugin store for options registration
crates/oxc_linter/src/config/mod.rs Updated test to use mutable external plugin store
crates/oxc_linter/src/tester.rs Updated to pass mutable reference to external plugin store
crates/oxc_language_server/src/linter/server_linter.rs Updated config building calls to use mutable external plugin store
tasks/benchmark/benches/linter.rs Updated benchmark to use mutable external plugin store
napi/playground/src/lib.rs Updated playground to properly share external plugin store between config building and linter creation
apps/oxlint/src/run.rs Added OnceLock-based caching for serialized options JSON and export function for JS
apps/oxlint/src/lint.rs Added serialization of external options after config building
apps/oxlint/src/js_plugins/external_linter.rs Updated wrapper to forward options IDs to JS callback
apps/oxlint/src-js/plugins/options.ts Added options initialization logic and storage
apps/oxlint/src-js/plugins/load.ts Duplicated options initialization logic (should be removed)
apps/oxlint/src-js/plugins/lint.ts Added lazy options initialization and options ID handling; duplicate ruleIndex assignment
apps/oxlint/src-js/cli.ts Updated wrapper to forward options IDs parameter
apps/oxlint/src-js/bindings.js Exported getExternalRuleOptions function
apps/oxlint/src-js/bindings.d.ts Added TypeScript declaration for getExternalRuleOptions
apps/oxlint/test/fixtures/custom_plugin_with_options/* Added integration test fixture for external plugin with options

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

@codspeed-hq
Copy link

codspeed-hq bot commented Nov 27, 2025

CodSpeed Performance Report

Merging #16215 will not alter performance

Comparing lilnasy:js-plugin-options (736841b) with main (decf2ad)1

Summary

✅ 42 untouched
⏩ 3 skipped2

Footnotes

  1. No successful run was found on main (10a1737) during the generation of this report, so decf2ad 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.

@lilnasy lilnasy force-pushed the js-plugin-options branch 2 times, most recently from a7a3ca1 to 9c2d8a4 Compare November 27, 2025 21:11
@overlookmotel
Copy link
Member

Just a note: As I mentioned on Discord, I don't think we should worry about merging options with default options in this first PR - can leave that to a follow-on. But FYI I've implemented the merging logic in #16217 because I needed it for the rule tester.

@lilnasy lilnasy force-pushed the js-plugin-options branch 2 times, most recently from d62762c to 3c46139 Compare November 28, 2025 13:01
@overlookmotel
Copy link
Member

overlookmotel commented Nov 28, 2025

Refactor to avoid requiring a mutable reference to ExternalPluginStore everywhere.

I know it was me that said this should be possible, but I could very well be wrong. If it's not possible, don't worry about it.

@lilnasy lilnasy force-pushed the js-plugin-options branch 2 times, most recently from dfb4439 to 1b4cd57 Compare November 28, 2025 19:21
@lilnasy lilnasy marked this pull request as ready for review November 28, 2025 20:25
@lilnasy lilnasy requested a review from Copilot November 28, 2025 20:25
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

Copilot reviewed 23 out of 24 changed files in this pull request and generated 2 comments.


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

Personally I try to avoid `.into()` as it's not clear from the code "into what?". `T::from` is equivalent, but more explicit.
Make sure severity is as expected. `is_warn_deny()` returns true for either `Warn` or `Deny`. We want to be more specific.
Dereference during destructuring.
Unlike in JS, in Rust you can have multiple bindings in same scope with same name. The old binding is destroyed when the new one is created. This is quite idiomatic in Rust.
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.

I've pushed some changes. All nits and code style apart from these ones which are more substantive:

Even those ones are all still minor though.

Great work! This will be in today's release.

@overlookmotel overlookmotel merged commit 1651806 into oxc-project:main Dec 1, 2025
28 checks passed
graphite-app bot pushed a commit that referenced this pull request Dec 1, 2025
Follow-on after #16215.

Options for each rule is always an array. Enforce this in type system by storing options as `Vec<serde_json::Value>` instead of a single `serde_json::Value` which is always a `Value::Array`.

I'll try to alter `ESLintRule`'s `config` field to be a `Vec<serde_json::Value>` too in another PR to follow.
@lilnasy lilnasy deleted the js-plugin-options branch December 1, 2025 18:34
graphite-app bot pushed a commit that referenced this pull request Dec 1, 2025
Follow-on after #16215. Test that options can be set in overrides.
graphite-app bot pushed a commit that referenced this pull request Dec 1, 2025
Closes #15630.

* #16170 added support for default options defined in the rule.
* #16215 added support for options defined in configs.
* #16217 implemented the logic for merging the 2 but only applied it in `RuleTester`.

Bring these elements together, and support merging default options from rule into options provided in config.
taearls pushed a commit to taearls/oxc that referenced this pull request Dec 11, 2025
- Implements oxc-project#14825
- Rebased oxc-project#15822. Refactored. Pruned the slop. Tests passing.

### Big changes from the original approach
- [x] Options IDs are stored in their own arrays. This was laid out in
oxc-project#16158. The original approach was to store `ruleId`-`optionId` pairs as
tuples in an array - several heap allocated objects and excessive
pointer indirection.
- [x] The rust code populates the options by calling a new JS callback
named `setupConfigs()`. I expect we would use this callback for more
plugin related data, including settings which are currently implemented
in a hacky way.
- [ ] Investigate a refactor to avoid requiring a mutable reference to
`ExternalPluginStore` everywhere.

<details>
<summary>

#### Smaller Changes

</summary>

- Removed the extra fixture ("working version"). There were no
meaningful changes compared to the first fixture. Probably slop.

</details>

---------

Co-authored-by: Peter Wagenet <peter@wagenet.us>
Co-authored-by: overlookmotel <theoverlookmotel@gmail.com>
taearls pushed a commit to taearls/oxc that referenced this pull request Dec 11, 2025
…6336)

Follow-on after oxc-project#16215.

Options for each rule is always an array. Enforce this in type system by storing options as `Vec<serde_json::Value>` instead of a single `serde_json::Value` which is always a `Value::Array`.

I'll try to alter `ESLintRule`'s `config` field to be a `Vec<serde_json::Value>` too in another PR to follow.
taearls pushed a commit to taearls/oxc that referenced this pull request Dec 11, 2025
taearls pushed a commit to taearls/oxc that referenced this pull request Dec 11, 2025
…#16358)

Closes oxc-project#15630.

* oxc-project#16170 added support for default options defined in the rule.
* oxc-project#16215 added support for options defined in configs.
* oxc-project#16217 implemented the logic for merging the 2 but only applied it in `RuleTester`.

Bring these elements together, and support merging default options from rule into options provided in config.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-cli Area - CLI A-editor Area - Editor and Language Server A-linter Area - Linter A-linter-plugins Area - Linter JS plugins C-enhancement Category - New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants