Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork727
feat(linter/plugins): rule options#16215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:main
Are you sure you want to change the base?
Conversation
There was a problem hiding this 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 anExternalOptionsId 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 | AddedExternalOptionsId type and options storage with index 0 reserved for empty/null options |
crates/oxc_linter/src/external_linter.rs | Updated callback signature to includeoptions_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 | Modifiedoverride_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 | AddedOnceLock-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; duplicateruleIndex assignment |
apps/oxlint/src-js/cli.ts | Updated wrapper to forward options IDs parameter |
apps/oxlint/src-js/bindings.js | ExportedgetExternalRuleOptions function |
apps/oxlint/src-js/bindings.d.ts | Added TypeScript declaration forgetExternalRuleOptions |
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.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
apps/oxlint/src/run.rs Outdated
| /// JS callable function to retrieve the serialized external rule options. | ||
| /// Returns a JSON string of options arrays. Called once from JS after creating the external linter. | ||
| #[cfg(all(feature ="napi", target_pointer_width ="64", target_endian ="little"))] | ||
| #[napi] | ||
| pubfnget_external_rule_options() ->Option<String>{ | ||
| EXTERNAL_OPTIONS_JSON.get().cloned() |
CopilotAINov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The documentation comment states "Returns a JSON string of options arrays" but the function returnsOption<String>, which can beNone. The comment should clarify whenNone is returned (e.g., "Returns a JSON string of options arrays, orNone if options haven't been set yet").
codspeed-hqbot commentedNov 27, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
CodSpeed Performance ReportMerging#16215 willnot alter performanceComparing Summary
Footnotes
|
a7a3ca1 to9c2d8a4Compareoverlookmotel commentedNov 27, 2025
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 to3c46139Compareoverlookmotel commentedNov 28, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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. |
| // TODO: refactor this elsewhere. | ||
| // This code is in the oxlint app, not in oxc_linter crate | ||
| ifletSome(ref external_linter) = external_linter{ | ||
| ifletErr(err) = external_plugin_store.setup_configs(external_linter){ | ||
| print_and_flush_stdout( | ||
| stdout, | ||
| &format!("Failed to setup external plugin options: {err}\n"), | ||
| ); | ||
| returnCliRunResult::InvalidOptionConfig; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I could use help here. I exposedsetup_configs() for theoxlint crate by putting it onExternalPluginStore only because it's the first place I found. I could do with a few pointers on separation of concerns regarding these structs.
overlookmotelNov 29, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Yeah, the types are a bit of a convoluted mess here. I'm tied up today. Will take a look tomorrow.
There was a problem hiding this 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.
Uh oh!
There was an error while loading.Please reload this page.
| * Populates Rust-resolved configuration options on the JS side. | ||
| * Called from Rust side after all configuration options have been resolved. | ||
| * | ||
| * Note: the name `setupConfigs` is currently incorrect, as we only populate rule options. | ||
| * The intention is for this function to transfer all configurations in a multi-config workspace. | ||
| * The configuration relevant to each file would then be resolved on the JS side. | ||
| * | ||
| *@param optionsJSON - JSON serialization of an array containing all rule options across all configurations. | ||
| *@returns "ok" on success, or error message on failure | ||
| */ | ||
| exportfunctionsetupConfigs(optionsJSON:string):string{ |
CopilotAINov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
[nitpick] The comment states "Note: the namesetupConfigs is currently incorrect, as we only populate rule options." While this is acknowledged as a TODO, consider renaming tosetupOptions orsetupRuleOptions to accurately reflect the current functionality. This would make the code clearer until multi-config workspace support is implemented.
| *PopulatesRust-resolvedconfigurationoptionsontheJSside. | |
| *CalledfromRustsideafterallconfigurationoptionshavebeenresolved. | |
| * | |
| *Note:thename`setupConfigs`iscurrentlyincorrect,asweonlypopulateruleoptions. | |
| *Theintentionisforthisfunctiontotransferallconfigurationsinamulti-configworkspace. | |
| *TheconfigurationrelevanttoeachfilewouldthenberesolvedontheJSside. | |
| * | |
| * @paramoptionsJSON-JSONserializationofanarraycontainingallruleoptionsacrossallconfigurations. | |
| * @returns"ok"onsuccess,orerrormessageonfailure | |
| */ | |
| exportfunctionsetupConfigs(optionsJSON:string):string{ | |
| *PopulatesRust-resolvedruleoptionsontheJSside. | |
| *CalledfromRustsideafterallruleoptionshavebeenresolved. | |
| * | |
| *Theintentionisforthisfunctiontoeventuallytransferallconfigurationsinamulti-configworkspace. | |
| *TheconfigurationrelevanttoeachfilewouldthenberesolvedontheJSside. | |
| * | |
| * @paramoptionsJSON-JSONserializationofanarraycontainingallruleoptionsacrossallconfigurations. | |
| * @returns"ok"onsuccess,orerrormessageonfailure | |
| */ | |
| exportfunctionsetupOptions(optionsJSON:string):string{ |
| // If the rule has no user-provided options, use the plugin-provided default | ||
| // options (which falls back to `DEFAULT_OPTIONS`) | ||
| ruleDetails.options= | ||
| optionsId===DEFAULT_OPTIONS_ID ?ruleDetails.defaultOptions :allOptions[optionsId]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
@overlookmotel You mentioned this could be simplified toallOptions[optionsId]. How would plugin-provided default options get used in that case?
| constel=parsed[i]; | ||
| if(!isArray(el))thrownewTypeError("Each options entry must be an array",{cause:el}); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This is a debug check because I this validation is done probably done on the rust side as well. I need to confirm this.
Uh oh!
There was an error while loading.Please reload this page.
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