Skip to content

feat(linter): PoC to Enforce config option validity for many rules#17199

Closed
connorshea wants to merge 9 commits intomainfrom
linter-enforce-config
Closed

feat(linter): PoC to Enforce config option validity for many rules#17199
connorshea wants to merge 9 commits intomainfrom
linter-enforce-config

Conversation

@connorshea
Copy link
Member

Basically, a proof-of-concept to add deny_unknown_fields on config structs across the linter and replace unwrap_or_default() with unwrap() in from_configuration, so it raises an error if the unwrapping fails. As part of this, I added a check for null to ensure that an unset value for config (e.g. "plugin/rule-name: "error") does not cause a failure.

I've only done the deny_unknown_fields part for rules where the DefaultRuleConfig was used.

Part of the goal here is to see if we can use this to help users find invalid configs/unsupported config options that get migrated from ESLint more easily. Ideally we would also ship a fuller JSON Schema that outputs schema definitions for all the lint rules' config options based on the config structs we've built out, but that is a bit more difficult than this change.

I think it may be worth seeing what effect this change has on the oxc-ecosystem-ci job. I definitely would not recommend merging this right now, though, as it'd be a bad idea to potentially break things right as JS plugins are going into alpha.

This is only applied to rules which use DefaultRuleConfig and have the appropriate serde attributes, and so are safe to use this with.

This will prevent users from passing invalid configuration options to these rules.
…nfiguration in that case.

To avoid using `unwrap_or_default()` on every rule, this allows us to handle the fallback logic for an empty configuration right here. And then we can `unwrap()` and have it error if the user passes an invalid config.
…rap_or_default() when deserializing configuration values.
@github-actions github-actions bot added the A-linter Area - Linter label Dec 21, 2025
@connorshea connorshea changed the title PoC: Enforce config option validity for many rules refactor(linter): PoC to Enforce config option validity for many rules Dec 21, 2025
@connorshea connorshea changed the title refactor(linter): PoC to Enforce config option validity for many rules feat(linter): PoC to Enforce config option validity for many rules Dec 21, 2025
@github-actions github-actions bot added the C-enhancement Category - New feature or request label Dec 21, 2025
@codspeed-hq
Copy link

codspeed-hq bot commented Dec 21, 2025

CodSpeed Performance Report

Merging #17199 will not alter performance

Comparing linter-enforce-config (1bf26cf) with main (ca65047)1

Summary

✅ 4 untouched
⏩ 41 skipped2

Footnotes

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

  2. 41 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.

@connorshea
Copy link
Member Author

connorshea commented Dec 21, 2025

Surprisingly, this didn't cause any new problems in the ecosystem ci...? https://github.com/oxc-project/oxc-ecosystem-ci/actions/runs/20404230629

I guess not many of them are configuring rules, admittedly, since they don't have a config file at all.

@camc314
Copy link
Contributor

camc314 commented Dec 21, 2025

This is a great start - but lets change our approach of how we implement this slighly?

I think the plan should look something like this:

  1. convert from_configuration to return a result
 impl Rule for AccessorPairs {
-    fn from_configuration(value: serde_json::Value) -> Self {
-        serde_json::from_value::<DefaultRuleConfig<AccessorPairs>>(value)
+    fn from_configuration(value: serde_json::Value) -> Result<Self, serde_json::error::Error> {
+        Ok(serde_json::from_value::<DefaultRuleConfig<AccessorPairs>>(value)
             .unwrap_or_default()
-            .into_inner()
+            .into_inner())
     }

(in this first version, we can just always return OK, and unwrap in the caller (here)):

https://github.com/Boshen/oxc/blob/main/crates/oxc_linter/src/config/rules.rs#L107

  1. convert a single rule to start returning an actual result, and implement the error handling in the config builder (probably want to store all errors, then propogate them up via the CLI)
  2. once we've tested 2, and all looks good, do it for all the other rules

…thod.

Will need to redo this one in another PR but this is just so I can reuse the code change.
@connorshea
Copy link
Member Author

This is a great start - but lets change our approach of how we implement this slighly?

I think the plan should look something like this:

1. convert `from_configuration` to return a result
 impl Rule for AccessorPairs {
-    fn from_configuration(value: serde_json::Value) -> Self {
-        serde_json::from_value::<DefaultRuleConfig<AccessorPairs>>(value)
+    fn from_configuration(value: serde_json::Value) -> Result<Self, serde_json::error::Error> {
+        Ok(serde_json::from_value::<DefaultRuleConfig<AccessorPairs>>(value)
             .unwrap_or_default()
-            .into_inner()
+            .into_inner())
     }

(in this first version, we can just always return OK, and unwrap in the caller (here)):

https://github.com/Boshen/oxc/blob/main/crates/oxc_linter/src/config/rules.rs#L107

2. convert a single rule to start returning an actual result, and implement the error handling in the config builder (probably want to store all errors, then propogate them up via the CLI)

3. once we've tested 2, and all looks good, do it for all the other rules

This makes sense to me, I've opened #17479 to implement the first step.

@connorshea connorshea closed this Dec 30, 2025
camc314 added a commit that referenced this pull request Dec 30, 2025
…lt` (#17479)

Built on top of #17478. 

Based on the changes I experimented with in #17199, and the suggestion
given by camc in that PR.

AI Disclosure: This was done almost entirely via find+replace and manual
updates to fix individual rules. I did use AI for the change in
0abc981 since I had trouble figuring
out a solution for that.

It may be easier to review this by going through it commit-by-commit.

Ran ecosystem CI on this branch and it looks like it works fine, no new
regressions
https://github.com/oxc-project/oxc-ecosystem-ci/actions/runs/20590908980

---------

Co-authored-by: Cameron Clark <cameron.clark@hey.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-enhancement Category - New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants