feat(linter/plugins): rule options#16215
Conversation
There was a problem hiding this comment.
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
ExternalOptionsIdtype and options storage toExternalPluginStorewith 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
OnceLockfor 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 Performance ReportMerging #16215 will not alter performanceComparing Summary
Footnotes
|
a7a3ca1 to
9c2d8a4
Compare
|
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. |
d62762c to
3c46139
Compare
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. |
3c46139 to
ae52e34
Compare
dfb4439 to
1b4cd57
Compare
There was a problem hiding this comment.
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.
53da13d to
736841b
Compare
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.
Follow-on after #16215. Test that options can be set in overrides.
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.
- 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>
…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.
…16357) Follow-on after oxc-project#16215. Test that options can be set in overrides.
…#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.
Big changes from the original approach
ruleId-optionIdpairs as tuples in an array - several heap allocated objects and excessive pointer indirection.setupConfigs(). I expect we would use this callback for more plugin related data, including settings which are currently implemented in a hacky way.ExternalPluginStoreeverywhere.Smaller Changes