Skip to content

Comments

feat(linter): Implement rule docs and config support for rules with tuple config options.#18372

Merged
graphite-app[bot] merged 1 commit intomainfrom
retry-default-rule-config
Jan 22, 2026
Merged

feat(linter): Implement rule docs and config support for rules with tuple config options.#18372
graphite-app[bot] merged 1 commit intomainfrom
retry-default-rule-config

Conversation

@connorshea
Copy link
Member

@connorshea connorshea commented Jan 22, 2026

This was built out of the ashes of #16555. Implements necessary work for #16023.

This introduces a new TupleRuleConfig. Rather than forcing DefaultRuleConfig to bend to our will as attempted previously, this PR adds a distinct rule config setup that is built to handle tuple-based rule configs.

This PR updates 3 rules to use TupleRuleConfig:

  • eslint/eqeqeq
  • eslint/sort_keys
  • eslint/yoda

It also adds snapshot validation tests to ensure that they error as-expected when given an invalid config option.

The only real change here is in eslint/eqeqeq. We previously allowed passing only an object to the rule config (due to #8790), but that is not valid in the original rule, and also makes implementing proper config option validation quite a bit more difficult. I had removed support for this in #16560, but that was part of the previous, doomed attempt at using DefaultRuleConfig with tuples. So it never got merged, and I'm redoing it here.

This is a necessary part of the change for this PR, as the alternative is to make the config options parsing/validation implementation considerably more complex. This also brings us back in line with ESLint's behavior for this rule.


AI Disclosure: I used the code and tests from #16555 as the basis for the changes in rule.rs, then let Claude Opus 4.5 run with it and guided it toward the TupleRuleConfig concept. After I got a satisfactory TupleRuleConfig implementation, I then manually implemented the rest of the changes in this PR by copying the files over from #16555, applying the TupleRuleConfig rename, and then carefully going through the diffs to make sure nothing was deleted that shouldn't have been.

I have added tests to ensure that the config validation for tuple rules works, and have entirely avoided changing the unit tests in any of the 3 modified rules (other than the one change for eqeqeq I noted above).


Updated docs for the changed rules

Note that the sort-keys docs are entirely unchanged as they were correct prior to this, so I've excluded them.

yoda:

## Configuration

### The 1st option

type: `"never" | "always"`

#### `"never"`

The default `"never"` option can have exception options in an object literal, via `exceptRange` and `onlyEquality`.

#### `"always"`

The `"always"` option requires that literal values must always come first in comparisons.

### The 2nd option

This option is an object with the following properties:

#### exceptRange

type: `boolean`

default: `false`

If the `"exceptRange"` property is `true`, the rule _allows_ yoda conditions
in range comparisons which are wrapped directly in parentheses, including the
parentheses of an `if` or `while` condition.
A _range_ comparison tests whether a variable is inside or outside the range
between two literal values.

#### onlyEquality

type: `boolean`

default: `false`

If the `"onlyEquality"` property is `true`, the rule reports yoda
conditions _only_ for the equality operators `==` and `===`. The `onlyEquality`
option allows a superset of the exceptions which `exceptRange` allows, thus
both options are not useful together.

eqeqeq:

## Configuration

### The 1st option

type: `"always" | "smart"`

#### `"always"`

Always require triple-equal comparisons, `===`/`!==`.
This is the default.

#### `"smart"`

Allow certain safe comparisons to use `==`/`!=` (`typeof`, literals, nullish).

### The 2nd option

This option is an object with the following properties:

#### null

type: `"always" | "never" | "ignore"`

##### `"always"`

Always require triple-equals when comparing with null, `=== null`/`!== null`.
This is the default.

##### `"never"`

Never require triple-equals when comparing with null, always use `== null`/`!= null`.

##### `"ignore"`

Ignore null comparisons, allow either `== null`/`!= null` or `=== null`/`!== null`.

Copilot AI review requested due to automatic review settings January 22, 2026 01:17
@connorshea connorshea requested a review from camc314 as a code owner January 22, 2026 01:17
@connorshea connorshea changed the title feat(linter): Implement rule docs and config support for tuple config options. feat(linter): Implement rule docs and config support for rules with tuple config options. Jan 22, 2026
@github-actions github-actions bot added A-linter Area - Linter A-cli Area - CLI C-enhancement Category - New feature or request labels Jan 22, 2026
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 pull request implements support for tuple-based ESLint rule configurations by introducing a new TupleRuleConfig type. Rather than forcing the existing DefaultRuleConfig to handle tuple configurations, this PR creates a dedicated wrapper that properly deserializes ESLint-style tuple configs like ["option", { "key": "value" }].

Changes:

  • Introduced TupleRuleConfig type for handling tuple-based rule configurations with proper validation and error reporting
  • Migrated three rules (eslint/eqeqeq, eslint/sort-keys, eslint/yoda) to use the new tuple config system
  • Added comprehensive unit tests for both DefaultRuleConfig and TupleRuleConfig deserialization
  • Added snapshot tests to validate error messages for invalid tuple configurations

Reviewed changes

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

Show a summary per file
File Description
crates/oxc_linter/src/rule.rs Added TupleRuleConfig type and comprehensive tests for config deserialization
crates/oxc_linter/src/rules/eslint/yoda.rs Refactored to use TupleRuleConfig with enum-based config instead of boolean flags
crates/oxc_linter/src/rules/eslint/sort_keys.rs Migrated to TupleRuleConfig, removed manual config parsing
crates/oxc_linter/src/rules/eslint/eqeqeq.rs Migrated to TupleRuleConfig, simplified config struct, removed object-only config support
crates/oxc_linter/tests/rule_configuration_documentation_test.rs Removed eslint/yoda from the list of rules without schema (now has proper schema)
apps/oxlint/fixtures/valid_complex_config/.oxlintrc.json Added test cases for the three migrated tuple config rules
apps/oxlint/fixtures/invalid_config_tuple_rules/.oxlintrc.json Added test fixture with invalid configurations to verify error messages
apps/oxlint/src/snapshots/fixtures__valid_complex_config_@oxlint.snap Updated snapshot to reflect 3 additional rules with valid configs
apps/oxlint/src/snapshots/fixtures__invalid_config_tuple_rules_@oxlint.snap New snapshot showing error messages for invalid tuple configurations
apps/oxlint/src/lint.rs Added test case for invalid tuple rule configurations

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

@codspeed-hq
Copy link

codspeed-hq bot commented Jan 22, 2026

CodSpeed Performance Report

Merging this PR will not alter performance

Comparing retry-default-rule-config (5986690) with main (52b5003)1

Summary

✅ 4 untouched benchmarks
⏩ 41 skipped benchmarks2

Footnotes

  1. No successful run was found on main (ffec86b) during the generation of this report, so 52b5003 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 connorshea requested a review from Boshen January 22, 2026 01:31
@Boshen Boshen added the 0-merge Merge with Graphite Merge Queue label Jan 22, 2026
Copy link
Member

Boshen commented Jan 22, 2026

Merge activity

…uple config options. (#18372)

This was built out of the ashes of #16555. Implements necessary work for #16023.

This introduces a new TupleRuleConfig. Rather than forcing DefaultRuleConfig to bend to our will as attempted previously, this PR adds a distinct rule config setup that is built to handle tuple-based rule configs.

This PR updates 3 rules to use TupleRuleConfig:

- `eslint/eqeqeq`
- `eslint/sort_keys`
- `eslint/yoda`

It also adds snapshot validation tests to ensure that they error as-expected when given an invalid config option.

The only real change here is in `eslint/eqeqeq`. We previously allowed passing _only_ an object to the rule config (due to #8790), but that is not valid in the original rule, and also makes implementing proper config option validation quite a bit more difficult. I had removed support for this in #16560, but that was part of the previous, doomed attempt at using DefaultRuleConfig with tuples. So it never got merged, and I'm redoing it here.

This is a necessary part of the change for this PR, as the alternative is to make the config options parsing/validation implementation _considerably_ more complex. This also brings us back in line with ESLint's behavior for this rule.

---

AI Disclosure: I used the code and tests from #16555 as the basis for the changes in `rule.rs`, then let Claude Opus 4.5 run with it and guided it toward the TupleRuleConfig concept. After I got a satisfactory TupleRuleConfig implementation, I then manually implemented the rest of the changes in this PR by copying the files over from #16555, applying the TupleRuleConfig rename, and then carefully going through the diffs to make sure nothing was deleted that shouldn't have been.

I have added tests to ensure that the config validation for tuple rules works, and have entirely avoided changing the unit tests in any of the 3 modified rules (other than the one change for eqeqeq I noted above).

---

<details>
<summary>Updated docs for the changed rules</summary>

Note that the sort-keys docs are entirely unchanged as they were correct prior to this, so I've excluded them.

yoda:
```md
## Configuration

### The 1st option

type: `"never" | "always"`

#### `"never"`

The default `"never"` option can have exception options in an object literal, via `exceptRange` and `onlyEquality`.

#### `"always"`

The `"always"` option requires that literal values must always come first in comparisons.

### The 2nd option

This option is an object with the following properties:

#### exceptRange

type: `boolean`

default: `false`

If the `"exceptRange"` property is `true`, the rule _allows_ yoda conditions
in range comparisons which are wrapped directly in parentheses, including the
parentheses of an `if` or `while` condition.
A _range_ comparison tests whether a variable is inside or outside the range
between two literal values.

#### onlyEquality

type: `boolean`

default: `false`

If the `"onlyEquality"` property is `true`, the rule reports yoda
conditions _only_ for the equality operators `==` and `===`. The `onlyEquality`
option allows a superset of the exceptions which `exceptRange` allows, thus
both options are not useful together.
```

eqeqeq:

```md
## Configuration

### The 1st option

type: `"always" | "smart"`

#### `"always"`

Always require triple-equal comparisons, `===`/`!==`.
This is the default.

#### `"smart"`

Allow certain safe comparisons to use `==`/`!=` (`typeof`, literals, nullish).

### The 2nd option

This option is an object with the following properties:

#### null

type: `"always" | "never" | "ignore"`

##### `"always"`

Always require triple-equals when comparing with null, `=== null`/`!== null`.
This is the default.

##### `"never"`

Never require triple-equals when comparing with null, always use `== null`/`!= null`.

##### `"ignore"`

Ignore null comparisons, allow either `== null`/`!= null` or `=== null`/`!== null`.
```

</details>
@graphite-app graphite-app bot force-pushed the retry-default-rule-config branch from 5986690 to 2fbceae Compare January 22, 2026 05:27
@graphite-app graphite-app bot merged commit 2fbceae into main Jan 22, 2026
21 checks passed
@graphite-app graphite-app bot deleted the retry-default-rule-config branch January 22, 2026 05:33
@graphite-app graphite-app bot removed the 0-merge Merge with Graphite Merge Queue label Jan 22, 2026
overlookmotel pushed a commit that referenced this pull request Jan 26, 2026
# Oxlint
### 💥 BREAKING CHANGES

- 777fc40 ast: [**BREAKING**] Add `Ident` type (#18354) (Boshen)

### 🚀 Features

- 34c3ec3 linter/prefer-logical-operator-over-ternary: Implement fixer
(#18545) (camc314)
- 019e0aa linter/valid-typeof: Add suggestions if type is misspelled
(#18543) (camchenry)
- 704c8eb linter/use-isnan: Add more specific error message for
equality/inequality (#18542) (camchenry)
- 1e99ace linter/use-isnan: Support more `indexOf` cases and improve
diagnostic messages (#18537) (camchenry)
- bffd134 linter/text-encoding-identifier-case: Add `withDash` option
(#18533) (camc314)
- 993fd2b parser: Parse unambiguous await with better error messages
(#18480) (Boshen)
- b4b6247 linter/plugins: `RuleTester` support settings (#18445)
(overlookmotel)
- 15d69dc linter: Implement react/display-name rule (#18426) (camchenry)
- 2fbceae linter: Implement rule docs and config support for rules with
tuple config options. (#18372) (connorshea)
- 8db0e78 linter/plugins: Handle BOMs (#18376) (overlookmotel)
- 6ac09e2 linter/plugins: Support source text not being at start of
buffer (#18375) (overlookmotel)
- fc3c86b linter: Update 125 rules to raise errors when provided with
invalid config options. (#18104) (connorshea)
- 2cc6ad2 linter/plugins: Add `ecmaFeatures` to `parserOptions` (#18313)
(overlookmotel)

### 🐛 Bug Fixes

- 2acf568 linter/plugins: Keep `Infinity` in rule default options
(#18550) (overlookmotel)
- 332d2ef linter/plugins: Add `jsx` property to
`parserOptions.ecmaFeatures` (#18549) (overlookmotel)
- 7d9bb1b linter: Update `eslint/func-names` to error on invalid rule
config options, improve docs. (#18510) (connorshea)
- 9c67974 linter: Improve the jsx-a11y/no-noninteractive-tabindex rule
to match original rule logic better (#17848) (connorshea)
- 75e7163 vscode: Support json5 for oxfmt (#18502) (Sysix)
- c205b0d ast: Remove `ThisExpression` from `TSModuleReference` (#18489)
(Boshen)
- c51339a oxlint/lsp: Respect code action `source.fixAll` as an alias
for `source.fixAll.oxc` (#18366) (Sysix)
- 3c0e9b9 oxlint/lsp: Skip dangerous fixes/suggestions for "fix all"
code action and command (#18364) (Sysix)
- c44c093 linter: Fix behavior of unicorn/catch-error-name to match
original rule (#18209) (connorshea)
- 9c65aff linter/jsx-a11y: Change `no-autofocus` autofix to suggestion
(#18155) (Ben Lowery)
- 235c820 linter/unicorn: Fix `prefer-array-some` autofix for
`.filter().length` pattern (#18153) (Ben Lowery)
- a9925dc linter: Mark fixes in `unicorn/no-null` rule as dangerous.
(#18436) (connorshea)
- cee29b4 linter: Remove confusing scope from
`react/only-export-components` rule diagnostics. (#18434) (connorshea)
- aed3669 parser: Parse HTML-like comments in unambiguous mode (#18442)
(Boshen)
- b8a371d linter: Fix the path used in the gitlab format output (#18165)
(connorshea)
- e046ea6 linter: `vue/no-lifecycle-after-await` skip looking into arrow
functions (#18302) (Sysix)
- a9bfbcf linter: Compatibility issue with `DiagnosticData` type in
ESLint (#18396) (루밀LuMir)
- 10ab424 linter: `react/no_array_index_key` continue search for other
attributes (#18409) (Lonami)
- 9d776d4 linter: Update `import/no-cycle` rule to error on invalid
config options. (#18330) (connorshea)
- c163231 linter: Update eslint/sort-imports to validate options.
(#18378) (connorshea)
- 79bbcff linter: Update `eslint/func-style` to error on invalid
configuration options. (#18390) (connorshea)
- b871235 linter/plugins: Fix identifying "use strict" directives in
scope analysis (#18402) (overlookmotel)
- 5985141 linter: Update `jest/prefer-lowercase-title` rule to error on
invalid config options. (#18332) (connorshea)
- faca4b5 linter/plugins: Tokenize `let`, `static` and `yield` as
`Keyword`s (#18368) (overlookmotel)
- a3914fd linter/plugins: Allow line number passed to `report` to be 1
over line count (#18341) (overlookmotel)
- 88e0896 linter: Update `typescript/no-restricted-types` rule to error
on invalid config options. (#18329) (connorshea)
- 9eec600 linter: Update `react/jsx-fragments` rule to raise an error on
invalid configuration options (#18111) (connorshea)
- 0fa969d linter: Update `react/no-will-update-set-state` to error on
invalid config options (#18112) (connorshea)
- 70e7be4 linter: Update `import/no-unassigned-import` to raise an error
when passed invalid config options. (#18108) (connorshea)
- 496cac7 linter: Update `unicorn/explicit-length-check` to raise an
error when passed invalid config options. (#18107) (connorshea)
- 080b1ec linter: Update 5 more rules to error on invalid config
options. (#18113) (connorshea)
- c5d05dd linter: Update 11 rules to raise an error on invalid config
options. (#18109) (connorshea)
- 9e359d4 linter/plugins: Set all properties on global vars objects
(#18317) (overlookmotel)
- 39c7f32 linter/plugins: Set `writeable` flag on variables where
defined as globals (#18316) (overlookmotel)
- a570693 linter/plugins: Fix `CatchClause` scopes (#18312)
(overlookmotel)
- 8c98e69 linter: `vitest/prefer-describe-function-title`: Check earlier
to avoid false positive (#18177) (Jovi De Croock)
- 44be0eb linter/plugins: Set scope analyse settings based on source
type (#18306) (overlookmotel)
- b9a14fd vscode: Update package.json to restrict a few more config
options. (#18270) (Connor Shea)
- c1260cb vscode: Update version info formatting. (#18274) (connorshea)
- 2f68dc6 vscode: Update notification for client restart to specify
tool. (#18273) (connorshea)

### ⚡ Performance

- dc931ba linter/no-inner-declarations: Skip scope flags lookup in
modules (#18249) (overlookmotel)
- 07618a7 linter: Turn off `scope_build_child_ids` for SemanticBuilder
(#18360) (Dunqing)
- 1aac079 linter/exhaustive-deps: Simplify the logic of checking if the
identifier it is a dependency of hook (#18350) (Dunqing)
- 591d522 linter/block-scoped-var: Avoid `iter_all_scope_child_ids` by
walking references/redeclarations scope ancestors (#18335) (Dunqing)
- 2eefd6d linter/plugins: Remove branch from token parsing (#18369)
(overlookmotel)

### 📚 Documentation

- 698c21d linter: Modernize docs for various React rules (#18559)
(connorshea)
- 314a47c linter: Clarify the `no-find-dom-node` rule with a note that
the method was removed in React 19. (#18556) (connorshea)
- 5eff704 linter: Update `no-inner-declarations` to fix config option
docs (#18511) (connorshea)
- dd5d2f6 linter: Improve diagnostic message in `valid_typeof` rule.
(#18507) (connorshea)
- 8ccd853 npm: Update package homepage URLs and add keywords (#18509)
(Boshen)
- 4958233 linter: Add missing "What it does" section in
prefer-reflect-apply rule. (#18475) (connorshea)
- 2fa83a4 linter: Improve the docs for import/unambiguous. (#18474)
(connorshea)
- 7b1505c linter: Improve docs for `oxc/only-used-in-recursion` rule.
(#18473) (connorshea)
- ab506d6 linter/plugins: Correct comment (#18456) (overlookmotel)
- 4565c73 linter: `react/display-name`: add docs for config options
(#18430) (camchenry)
- b95a89f linter: Fix docs for the curly rule. (#18374) (connorshea)
- f675eb4 linter: Fix the `react/only-export-components` rule docs.
(#18319) (connorshea)
- 704db95 linter: "no-unused-vars" extend ignored files section for
svelte and astro files (#18304) (Sysix)
- 3af4a88 linter: Add "Examples" headers to rules missing them (#18266)
(connorshea)
# Oxfmt
### 💥 BREAKING CHANGES

- 777fc40 ast: [**BREAKING**] Add `Ident` type (#18354) (Boshen)

### 🚀 Features

- d71c15d oxfmt: Enable tailwind sort inside xxx-in-js (#18417)
(leaysgur)
- 52b5003 formatter,oxfmt: Support Angular `@Component({ template,
styles })` (#18324) (leaysgur)

### 🐛 Bug Fixes

- 224140c oxfmt: Canonicalize `..` component in config path (#18570)
(leaysgur)
- 30b467e formatter: Preserve trailing comments before the semicolon in
class methods without a body (#18446) (Dunqing)
- c205b0d ast: Remove `ThisExpression` from `TSModuleReference` (#18489)
(Boshen)
- 164bbd7 formatter: Preserve trailing comments inside ternary alternate
branch (#18433) (Dunqing)
- 1c50800 formatter: Use HTML entity escaping for JSX attribute strings
(#18385) (Boshen)
- 4e156d2 formatter: Preserve parentheses for `in` expressions in arrow
function block bodies (#18352) (Boshen)
- 7e6c15b oxfmt: Increase Tailwind CSS test timeout for Windows CI
(#18339) (Boshen)
- 29966eb formatter/dead-code-removal: Handle tailwind sorting (#18321)
(leaysgur)
- 29f41be formatter: Only expand mapped types when newline immediately
follows opening brace (#18087) (Boshen)
- 2194552 formatter: Relocate leading comments for single-element
union/intersection types (#18083) (Boshen)

### ⚡ Performance

- 85ab400 formatter: Store `AstNodes` itself instead of `&'a AstNodes`
as the `parent` field of `AstNode` (#18428) (Dunqing)
- 194d384 formatter: Reduce AstNode size by 8 bytes using
following_span_start (#18347) (Dunqing)
- b2df8fb oxfmt: Enable tailwind plugin only for relevant parser
(#18418) (leaysgur)

### 📚 Documentation

- 8ccd853 npm: Update package homepage URLs and add keywords (#18509)
(Boshen)

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-cli Area - CLI 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