perf(linter): Run no-underscore-dangle only when relevant node types are present#23867
Merged
Conversation
…ode types
The rule's `run` used a non-empty wildcard arm and an OR-pattern arm, which
prevented `linter_codegen` from deriving `NODE_TYPES`, so the rule was
dispatched to every node in the AST. Rewrite `run` as a flat match with one
bare `AstKind::Variant(_)` arm per node type and an empty `_ => {}`, letting
codegen emit the 7 concrete node types the rule cares about. Also add a cheap
always-allowed early-out in `check_member` before walking the object expression.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… key helper Move each match arm's condition into an arm guard (the codegen's match detector collects the AST type regardless of guards, so NODE_TYPES is unaffected) and collapse the repetitive method/object/field bodies into a shared `check_property_key` helper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merging this PR will not alter performance
Comparing Footnotes
|
This was referenced Jun 27, 2026
camc314
added a commit
that referenced
this pull request
Jun 28, 2026
…n on specific node types (#23871) Generated with Claude Code, reviewed and tested by me. Similar concept to #23868 and #23867, gates the following rules behind specific AST Nodes in their `run` methods to ensure that the rules are only applied when the corresponding AST Node is present. Most of these regressed when the jest-vitest rules were split into separate rule files, and the AST Node codegen isn't smart enough to handle the separation, so the codegen ran these rules on every node in the entire codebase. Narrow each rule's `run` to the node kinds it actually handles so codegen generates the correct `NODE_TYPES`: - jest/vitest require-hook -> [CallExpression, Program] - jest/vitest prefer-mock-promise-shorthand -> [CallExpression] - jest/vitest prefer-mock-return-shorthand -> [CallExpression] On the vscode codebase, this cuts each rule's invocations from 15.25M to 870-880K (~17x fewer calls) with no behavior change. Before: ``` Rule timings: Rule Time (ms) Relative Calls Source ------------------------------------ ---------- -------- -------- ------ jest/require-hook 250.566 15.6% 15251059 native vitest/require-hook 247.498 15.4% 15251059 native jest/prefer-mock-promise-shorthand 223.433 13.9% 15251059 native jest/prefer-mock-return-shorthand 222.515 13.8% 15251059 native vitest/prefer-mock-promise-shorthand 219.438 13.6% 15251059 native vitest/prefer-mock-return-shorthand 218.652 13.6% 15251059 native ``` After: ``` Rule timings: Rule Time (ms) Relative Calls Source ------------------------------------ ---------- -------- ------ ------ jest/require-hook 92.802 37.9% 881757 native vitest/require-hook 79.677 32.5% 881757 native jest/prefer-mock-promise-shorthand 19.238 7.9% 870119 native jest/prefer-mock-return-shorthand 16.029 6.5% 870119 native vitest/prefer-mock-return-shorthand 15.878 6.5% 870119 native vitest/prefer-mock-promise-shorthand 13.962 5.7% 870119 native ``` Overall: 91,506,354 calls down to 5,243,990 calls, a 94.3% reduction. I also tried this change with promise/always-return, which has the same problem, but changing it resulted in a very minor difference in the code behavior for the VS Code repo (which our tests did not catch), and so I left that alone for now. All of these were verified by the tests passing and the violations before and after matching on the VS Code codebase. The one downside here is that the rules now duplicate the `node.kind()` guard check that exists in the `run` method each one calls. We can also remove the guards from the shared `run` methods if preferred. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Cameron <cameron.clark@hey.com>
camc314
added a commit
that referenced
this pull request
Jun 28, 2026
…evant node types (#23868) Similar to the change in #23867. This was generated using Claude Code after I noticed this rule was one of the slowest on the vscode repo. The change was reviewed and tested by me, all violations in the vscode codebase are identical before and after this change, and all tests pass before and after, behavior should be identical. --- Claude's explanation of the change: The rule's `run` called `run_on_regex_node` and then did its own `match node.kind()` returning a tuple, so no `linter_codegen` detector fired and `NODE_TYPES` was derived as `None`. This PR rewrites `run` to a flat `match node.kind()` with one bare `AstKind::Variant(_)` arm per node type and an empty `_ => {}`, letting codegen emit the 3 concrete node types the rule cares about (`RegExpLiteral`, `CallExpression`, `NewExpression`). ~~Also dedupe the `is_regexp_callee` check, which previously ran twice per Call/New expression (once inside `run_on_regex_node`'s guard, once before `check_static_arguments`). The new `check_regexp_call` helper checks it once, then runs `run_on_arguments` for directly-supported args and `check_static_arguments` for concatenated static strings — behavior-identical, since `run_on_regex_node` for Call/New is itself `if is_regexp_callee { run_on_arguments(..) }`.~~ ---- Results from running `oxlint --debug=timings --quiet -A all -D="eslint/prefer-named-capture-group"` on the vscode repo are below. Before: ``` Rule timings: Rule Time (ms) Relative Calls Source --------------------------------- ---------- -------- -------- ------ eslint/prefer-named-capture-group 239.165 100.0% 15251059 native ``` After: ``` Rule timings: Rule Time (ms) Relative Calls Source --------------------------------- ---------- -------- ------ ------ eslint/prefer-named-capture-group 25.791 100.0% 954008 native ``` The # of calls decreases by 93.7% on the vscode repo (from 15,251,059 to 954,008). The rule very consistently returns timings of 20-30ms now on my Mac. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Cameron <cameron.clark@hey.com>
camc314
added a commit
that referenced
this pull request
Jun 29, 2026
# Oxlint ### 💥 BREAKING CHANGES - 88f4455 str: [**BREAKING**] `Str` and `Ident` methods take `&GetAllocator` (#23781) (overlookmotel) ### 🚀 Features - f2091b3 ast: Unify old and new `AstBuilder`s (#23875) (overlookmotel) - 1c8f50c linter: Add schema for `eslint/no-restricted-import` (#23642) (Sysix) ### 🐛 Bug Fixes - 7cb85c4 linter/eslint/no-negated-condition: Add autofix for negated conditions (#23825) (Yagiz Nizipli) - f7d1f50 oxlint, oxfmt: Enable `disable_old_builder` Cargo feature for `oxc_ast` crate (#23886) (overlookmotel) - d891990 linter/jsx-a11y/role-supports-aria-props: Ignore nullish prop values (#23865) (Mikhail Baev) - 94b6599 linter: Deduplicate missing plugin errors (#23853) (camc314) - eff3eff linter/oxc/branches-sharing-code: Avoid else-if false positives (#23843) (camc314) - 2a2d3b9 linter/eslint/prefer-destructuring: Skip `AssignmentExpression` autofixes (#23818) (camc314) - ddc24ae linter/eslint/id-length: Respect checkGeneric for mapped type keys (#23802) (bab) - cd89202 linter/react/exhaustive-deps: Skip wrapper expression when analyzing hook initializers (#23793) (camc314) - 20e8285 linter/unicorn/prefer-native-coercion-function: Allow ts type predicates (#23774) (camc314) - d86f60b lsp: Normalize user config path to watch pattern (#23723) (Sysix) - 52032cf linter: Newline-terminate tsgolint errors (#23762) (Mikhail Baev) - 368fda7 linter/eslint/no-warning-comments: Avoid dropping generated regex patterns (#23741) (camc314) - ce44fbd linter/valid-title: Escape disallowed words regex (#23742) (camc314) - 3100d11 linter/prefer-called-exactly-once-with: Avoid out-of-bounds slice panic at end of file (#23625) (Jerry Zhao) - 742be36 refactor/node/handle-callback-err: Reject invalid regex config (#23740) (camc314) - d7be179 linter/eslint/no-restricted-globals: Handle shadowed locals (#23736) (camc314) - b3b1ff8 linter/vitest/expect-expect: Handle global vitest detection correctly (#23734) (camc314) ### ⚡ Performance - 68f9472 linter/jsx-a11y: Skip lowercasing non-aria attribute names (#23906) (Lawrence Lin) - b9312b4 linter/unicorn/prefer-export-from: Use keyed binding lookup (#23893) (Marius Schulz) - cd5204e linter/typescript/no-unsafe-declaration-merging: Use keyed binding lookup (#23894) (Marius Schulz) - e948498 linter/eslint/prefer-named-capture-group: Only dispatch for relevant node types (#23868) (Connor Shea) - 4ac7a8e linter/eslint/max-depth: Derive node types (#23896) (Connor Shea) - daeed09 linter/eslint/no-restricted-globals: Only scan unresolved references (#23890) (camc314) - e808514 linter/jest-vitest: Speed up no-standalone-expect (#23883) (camc314) - 8b165e5 linter/react/exhaustive-deps: Skip non-reactive calls early (#23882) (camc314) - 54005e7 linter/eslint/no-unused-vars: Precompute exported bindings (#23881) (camc314) - 9bc2f8c linter/unicorn/prefer-number-properties: Speed up global checks (#23880) (camc314) - 4ff104f linter: Optimize `require-hook` and `prefer-mock-*` rules to run on specific node types (#23871) (Connor Shea) - cc2213b linter: Run `no-underscore-dangle` only when relevant node types are present (#23867) (Connor Shea) - 3e55c21 linter/promise/always-return: Narrow to function node types (#23878) (Connor Shea) - 7136182 linter/jest-vitest: Speed up no-commented-out-tests (#23864) (camc314) - f138264 linter/eslint/no-script-url: Match javascript: prefix without allocating (#23861) (Lawrence Lin) - 7ef6895 linter/react/no-array-index-key: Delay index symbol lookup (#23857) (camc314) - 26bc171 linter/react/no-array-index-key: Match callback methods directly (#23856) (camc314) - 44fbbda linter/jsx-a11y/interactive-supports-focus: Check cheap conditions first (#23854) (camc314) - 84a5aa3 linter/eslint/no-extend-native: Skip lowercase references early (#23851) (camc314) - 88a74b2 linter/eslint/no-nonoctal-decimal-escape: Scan decimal escapes as bytes (#23850) (camc314) - fca69a8 linter: Skip traversal without this expressions (#23845) (camc314) - 838fd63 linter: Reduce preallocation for per-file diagnostics `Vec` (#23705) (Marius Schulz) - 417b506 linter/typescript/array-type: Remove full source text clone (#23751) (Marius Schulz) ### 📚 Documentation - 57e4469 linter/unicorn: Update prefer-dom-node-text-content rationale (#23933) (Mikhail Baev) - 3d61dea all: Correct capitalization in comments (#23887) (overlookmotel) ### 🛡️ Security - 3cdd18f deps: Update npm packages (#23690) (renovate[bot]) # Oxfmt ### 💥 BREAKING CHANGES - 259e0cd oxfmt,formatter_graphql: [**BREAKING**] Support draft syntax with removing prettier fallback (#23326) (leaysgur) - accbc49 oxfmt: [**BREAKING**] Format `parser:css,less,scss` files + css-in-js by `oxc_formatter_css` (#23321) (leaysgur) ### 🚀 Features - dffa4b3 formatter_css: Implement `oxc_formatter_css` (#23320) (leaysgur) - 01de9ec oxfmt: Format `parser:graphql` files by `oxc_formatter_graphql` (#23318) (leaysgur) - 4e66212 formatter_graphql: Implement oxc_formatter_graphql (#23317) (leaysgur) ### 🐛 Bug Fixes - 67325ae formatter_css: Handle frontmatter language (#23819) (leaysgur) - 3f355e5 formatter_graphql: Improve major prettier diffs (#23419) (leaysgur) - 48e2d78 formatter_css: Improve major prettier diffs (#23327) (leaysgur) - 8c07cad all: Enable `disable_old_builder` Cargo feature for `oxc_ast` crate in tests (#23888) (overlookmotel) - f7d1f50 oxlint, oxfmt: Enable `disable_old_builder` Cargo feature for `oxc_ast` crate (#23886) (overlookmotel) - d86f60b lsp: Normalize user config path to watch pattern (#23723) (Sysix) ### ⚡ Performance - 4ddcba0 formatter_core: Add printable-ASCII fast path to TextWidth (#23913) (Lawrence Lin) ### 📚 Documentation - b4d0dc9 oxfmt,formatter,formatter_css,formatter_core: Update AGENTS.md (#23814) (leaysgur) Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com> Co-authored-by: Cameron <cameron.clark@hey.com>
camc314
added a commit
that referenced
this pull request
Jul 2, 2026
… run only on less common node types (#23897) Somewhat similar to #23867 and #23868. This was generated using Claude Code after I noticed this rule was one of the slowest on the vscode repo. I have reviewed and tested the change by running it on the vscode codebase, and the # of violations before and after is identical. All tests also pass. I've also had Claude run a battery of tests on code it generated to test as many scenarios as possible, to ensure that there were no differences in output on main vs. this branch, and confirmed that there were indeed no differences. ## What changed The rule's `NODE_TYPES` were already derived, but two of them — `IdentifierName` and `IdentifierReference` — are among the most common node types in any program (every property name, every variable read). The destructuring cases dispatched on those identifiers and then walked *up* to their parents to detect destructuring patterns. This PR instead dispatches directly on the rare parent nodes and reads the key/binding: | Old dispatch (common) | New dispatch (rare) | Covers | |---|---|---| | `IdentifierName` | `BindingProperty` (key) + `AssignmentTargetPropertyProperty` (name) | `const {innerText} = node`, `({innerText: text} = node)` | | `IdentifierReference` | `AssignmentTargetPropertyIdentifier` (binding) | `({innerText} = node)` | `StaticMemberExpression` (`node.innerText`) is unchanged. ## Why it's behavior-identical - The only `IdentifierName` that can be a direct child of `BindingProperty`/`AssignmentTargetPropertyProperty` is the key (values bottom out in `BindingIdentifier`/`IdentifierReference`). The old grandparent set always matched (`ObjectPattern`/`ObjectAssignmentTarget`), so that branch always fired — matching the new unconditional trigger. Matching only `PropertyKey::StaticIdentifier` preserves the computed-key exclusion (`{[innerText]: text}`). - The `AssignmentTargetPropertyIdentifier` arm keeps the same parent-of-`ObjectAssignmentTarget` check. The old non-APTI path was dead: a rest target (`({...innerText} = node)`) goes through `AssignmentTargetRest`, never a direct `ObjectAssignmentTarget` child. The rule test snapshot is byte-identical (no `.snap` change). ## Results Running `oxlint --debug=timings --quiet -A all -W unicorn/prefer-dom-node-text-content` on the vscode repo. Before: ``` Found 245 warnings and 47 errors. Rule Time (ms) Relative Calls Source ------------------------------------ ---------- -------- ------- ------ unicorn/prefer-dom-node-text-content 116.624 100.0% 5996077 native ``` After: ``` Found 245 warnings and 47 errors. Rule Time (ms) Relative Calls Source ------------------------------------ ---------- -------- ------- ------ unicorn/prefer-dom-node-text-content 23.631 100.0% 1437765 native ``` Violations are unchanged (245 warnings, 47 errors). Calls drop 76% (5,996,077 → 1,437,765) and rule time drops ~80% (116ms → 23ms). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Cameron <cameron.clark@hey.com>
camc314
added a commit
that referenced
this pull request
Jul 3, 2026
… run only on less common node types (#23897) Somewhat similar to #23867 and #23868. This was generated using Claude Code after I noticed this rule was one of the slowest on the vscode repo. I have reviewed and tested the change by running it on the vscode codebase, and the # of violations before and after is identical. All tests also pass. I've also had Claude run a battery of tests on code it generated to test as many scenarios as possible, to ensure that there were no differences in output on main vs. this branch, and confirmed that there were indeed no differences. ## What changed The rule's `NODE_TYPES` were already derived, but two of them — `IdentifierName` and `IdentifierReference` — are among the most common node types in any program (every property name, every variable read). The destructuring cases dispatched on those identifiers and then walked *up* to their parents to detect destructuring patterns. This PR instead dispatches directly on the rare parent nodes and reads the key/binding: | Old dispatch (common) | New dispatch (rare) | Covers | |---|---|---| | `IdentifierName` | `BindingProperty` (key) + `AssignmentTargetPropertyProperty` (name) | `const {innerText} = node`, `({innerText: text} = node)` | | `IdentifierReference` | `AssignmentTargetPropertyIdentifier` (binding) | `({innerText} = node)` | `StaticMemberExpression` (`node.innerText`) is unchanged. ## Why it's behavior-identical - The only `IdentifierName` that can be a direct child of `BindingProperty`/`AssignmentTargetPropertyProperty` is the key (values bottom out in `BindingIdentifier`/`IdentifierReference`). The old grandparent set always matched (`ObjectPattern`/`ObjectAssignmentTarget`), so that branch always fired — matching the new unconditional trigger. Matching only `PropertyKey::StaticIdentifier` preserves the computed-key exclusion (`{[innerText]: text}`). - The `AssignmentTargetPropertyIdentifier` arm keeps the same parent-of-`ObjectAssignmentTarget` check. The old non-APTI path was dead: a rest target (`({...innerText} = node)`) goes through `AssignmentTargetRest`, never a direct `ObjectAssignmentTarget` child. The rule test snapshot is byte-identical (no `.snap` change). ## Results Running `oxlint --debug=timings --quiet -A all -W unicorn/prefer-dom-node-text-content` on the vscode repo. Before: ``` Found 245 warnings and 47 errors. Rule Time (ms) Relative Calls Source ------------------------------------ ---------- -------- ------- ------ unicorn/prefer-dom-node-text-content 116.624 100.0% 5996077 native ``` After: ``` Found 245 warnings and 47 errors. Rule Time (ms) Relative Calls Source ------------------------------------ ---------- -------- ------- ------ unicorn/prefer-dom-node-text-content 23.631 100.0% 1437765 native ``` Violations are unchanged (245 warnings, 47 errors). Calls drop 76% (5,996,077 → 1,437,765) and rule time drops ~80% (116ms → 23ms). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Cameron <cameron.clark@hey.com>
camc314
added a commit
that referenced
this pull request
Jul 3, 2026
…s are present (#23867) Generated with Claude Code, tested and reviewed by me. This was one of the most time-consuming rules on the vscode codebase before this change (oxlint 1.71.0), and drops considerably after this change. The `run` method had an OR pattern arm that the codegen didn't understand, so that was split. The `run` method also had a wildcard arm that was non-empty, which caused linter_codegen to bail out of detecting what NODE_TYPES were worth checking. Removing that allows linter_codegen to generate the NODE_TYPES properly and limit this rule to only running on relevant files/nodes. **Simple Comparison** Before (`npx oxlint --debug=timings --quiet -W all` on vscode repo): ``` Rule Time (ms) Relative Calls Source ------------------------------------------------------- ---------- -------- -------- ------ eslint/no-underscore-dangle 259.949 5.3% 15251059 native ``` After: ``` Rule timings: Rule Time (ms) Relative Calls Source ------------------------------------------------------- ---------- -------- -------- ------ eslint/no-underscore-dangle 115.713 2.0% 2938968 native ``` Result: `15,251,059` calls down to `2,938,968` (80.7% fewer calls). I've re-run this a number of times and confirmed that the time savings are consistent thanks to the reduction in call count. Running the before/after on the VS Code repo with just this rule enabled also results in the same exact # of lint violations being found, and all existing tests pass. We should probably consider either updating the codegen to handle ORs in AST node arms, or else look for other usages of this pattern to fix them. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Cameron <cameron.clark@hey.com>
camc314
added a commit
that referenced
this pull request
Jul 3, 2026
…n on specific node types (#23871) Generated with Claude Code, reviewed and tested by me. Similar concept to #23868 and #23867, gates the following rules behind specific AST Nodes in their `run` methods to ensure that the rules are only applied when the corresponding AST Node is present. Most of these regressed when the jest-vitest rules were split into separate rule files, and the AST Node codegen isn't smart enough to handle the separation, so the codegen ran these rules on every node in the entire codebase. Narrow each rule's `run` to the node kinds it actually handles so codegen generates the correct `NODE_TYPES`: - jest/vitest require-hook -> [CallExpression, Program] - jest/vitest prefer-mock-promise-shorthand -> [CallExpression] - jest/vitest prefer-mock-return-shorthand -> [CallExpression] On the vscode codebase, this cuts each rule's invocations from 15.25M to 870-880K (~17x fewer calls) with no behavior change. Before: ``` Rule timings: Rule Time (ms) Relative Calls Source ------------------------------------ ---------- -------- -------- ------ jest/require-hook 250.566 15.6% 15251059 native vitest/require-hook 247.498 15.4% 15251059 native jest/prefer-mock-promise-shorthand 223.433 13.9% 15251059 native jest/prefer-mock-return-shorthand 222.515 13.8% 15251059 native vitest/prefer-mock-promise-shorthand 219.438 13.6% 15251059 native vitest/prefer-mock-return-shorthand 218.652 13.6% 15251059 native ``` After: ``` Rule timings: Rule Time (ms) Relative Calls Source ------------------------------------ ---------- -------- ------ ------ jest/require-hook 92.802 37.9% 881757 native vitest/require-hook 79.677 32.5% 881757 native jest/prefer-mock-promise-shorthand 19.238 7.9% 870119 native jest/prefer-mock-return-shorthand 16.029 6.5% 870119 native vitest/prefer-mock-return-shorthand 15.878 6.5% 870119 native vitest/prefer-mock-promise-shorthand 13.962 5.7% 870119 native ``` Overall: 91,506,354 calls down to 5,243,990 calls, a 94.3% reduction. I also tried this change with promise/always-return, which has the same problem, but changing it resulted in a very minor difference in the code behavior for the VS Code repo (which our tests did not catch), and so I left that alone for now. All of these were verified by the tests passing and the violations before and after matching on the VS Code codebase. The one downside here is that the rules now duplicate the `node.kind()` guard check that exists in the `run` method each one calls. We can also remove the guards from the shared `run` methods if preferred. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Cameron <cameron.clark@hey.com>
camc314
added a commit
that referenced
this pull request
Jul 3, 2026
…evant node types (#23868) Similar to the change in #23867. This was generated using Claude Code after I noticed this rule was one of the slowest on the vscode repo. The change was reviewed and tested by me, all violations in the vscode codebase are identical before and after this change, and all tests pass before and after, behavior should be identical. --- Claude's explanation of the change: The rule's `run` called `run_on_regex_node` and then did its own `match node.kind()` returning a tuple, so no `linter_codegen` detector fired and `NODE_TYPES` was derived as `None`. This PR rewrites `run` to a flat `match node.kind()` with one bare `AstKind::Variant(_)` arm per node type and an empty `_ => {}`, letting codegen emit the 3 concrete node types the rule cares about (`RegExpLiteral`, `CallExpression`, `NewExpression`). ~~Also dedupe the `is_regexp_callee` check, which previously ran twice per Call/New expression (once inside `run_on_regex_node`'s guard, once before `check_static_arguments`). The new `check_regexp_call` helper checks it once, then runs `run_on_arguments` for directly-supported args and `check_static_arguments` for concatenated static strings — behavior-identical, since `run_on_regex_node` for Call/New is itself `if is_regexp_callee { run_on_arguments(..) }`.~~ ---- Results from running `oxlint --debug=timings --quiet -A all -D="eslint/prefer-named-capture-group"` on the vscode repo are below. Before: ``` Rule timings: Rule Time (ms) Relative Calls Source --------------------------------- ---------- -------- -------- ------ eslint/prefer-named-capture-group 239.165 100.0% 15251059 native ``` After: ``` Rule timings: Rule Time (ms) Relative Calls Source --------------------------------- ---------- -------- ------ ------ eslint/prefer-named-capture-group 25.791 100.0% 954008 native ``` The # of calls decreases by 93.7% on the vscode repo (from 15,251,059 to 954,008). The rule very consistently returns timings of 20-30ms now on my Mac. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Cameron <cameron.clark@hey.com>
camc314
added a commit
that referenced
this pull request
Jul 3, 2026
# Oxlint ### 💥 BREAKING CHANGES - 88f4455 str: [**BREAKING**] `Str` and `Ident` methods take `&GetAllocator` (#23781) (overlookmotel) ### 🚀 Features - f2091b3 ast: Unify old and new `AstBuilder`s (#23875) (overlookmotel) - 1c8f50c linter: Add schema for `eslint/no-restricted-import` (#23642) (Sysix) ### 🐛 Bug Fixes - 7cb85c4 linter/eslint/no-negated-condition: Add autofix for negated conditions (#23825) (Yagiz Nizipli) - f7d1f50 oxlint, oxfmt: Enable `disable_old_builder` Cargo feature for `oxc_ast` crate (#23886) (overlookmotel) - d891990 linter/jsx-a11y/role-supports-aria-props: Ignore nullish prop values (#23865) (Mikhail Baev) - 94b6599 linter: Deduplicate missing plugin errors (#23853) (camc314) - eff3eff linter/oxc/branches-sharing-code: Avoid else-if false positives (#23843) (camc314) - 2a2d3b9 linter/eslint/prefer-destructuring: Skip `AssignmentExpression` autofixes (#23818) (camc314) - ddc24ae linter/eslint/id-length: Respect checkGeneric for mapped type keys (#23802) (bab) - cd89202 linter/react/exhaustive-deps: Skip wrapper expression when analyzing hook initializers (#23793) (camc314) - 20e8285 linter/unicorn/prefer-native-coercion-function: Allow ts type predicates (#23774) (camc314) - d86f60b lsp: Normalize user config path to watch pattern (#23723) (Sysix) - 52032cf linter: Newline-terminate tsgolint errors (#23762) (Mikhail Baev) - 368fda7 linter/eslint/no-warning-comments: Avoid dropping generated regex patterns (#23741) (camc314) - ce44fbd linter/valid-title: Escape disallowed words regex (#23742) (camc314) - 3100d11 linter/prefer-called-exactly-once-with: Avoid out-of-bounds slice panic at end of file (#23625) (Jerry Zhao) - 742be36 refactor/node/handle-callback-err: Reject invalid regex config (#23740) (camc314) - d7be179 linter/eslint/no-restricted-globals: Handle shadowed locals (#23736) (camc314) - b3b1ff8 linter/vitest/expect-expect: Handle global vitest detection correctly (#23734) (camc314) ### ⚡ Performance - 68f9472 linter/jsx-a11y: Skip lowercasing non-aria attribute names (#23906) (Lawrence Lin) - b9312b4 linter/unicorn/prefer-export-from: Use keyed binding lookup (#23893) (Marius Schulz) - cd5204e linter/typescript/no-unsafe-declaration-merging: Use keyed binding lookup (#23894) (Marius Schulz) - e948498 linter/eslint/prefer-named-capture-group: Only dispatch for relevant node types (#23868) (Connor Shea) - 4ac7a8e linter/eslint/max-depth: Derive node types (#23896) (Connor Shea) - daeed09 linter/eslint/no-restricted-globals: Only scan unresolved references (#23890) (camc314) - e808514 linter/jest-vitest: Speed up no-standalone-expect (#23883) (camc314) - 8b165e5 linter/react/exhaustive-deps: Skip non-reactive calls early (#23882) (camc314) - 54005e7 linter/eslint/no-unused-vars: Precompute exported bindings (#23881) (camc314) - 9bc2f8c linter/unicorn/prefer-number-properties: Speed up global checks (#23880) (camc314) - 4ff104f linter: Optimize `require-hook` and `prefer-mock-*` rules to run on specific node types (#23871) (Connor Shea) - cc2213b linter: Run `no-underscore-dangle` only when relevant node types are present (#23867) (Connor Shea) - 3e55c21 linter/promise/always-return: Narrow to function node types (#23878) (Connor Shea) - 7136182 linter/jest-vitest: Speed up no-commented-out-tests (#23864) (camc314) - f138264 linter/eslint/no-script-url: Match javascript: prefix without allocating (#23861) (Lawrence Lin) - 7ef6895 linter/react/no-array-index-key: Delay index symbol lookup (#23857) (camc314) - 26bc171 linter/react/no-array-index-key: Match callback methods directly (#23856) (camc314) - 44fbbda linter/jsx-a11y/interactive-supports-focus: Check cheap conditions first (#23854) (camc314) - 84a5aa3 linter/eslint/no-extend-native: Skip lowercase references early (#23851) (camc314) - 88a74b2 linter/eslint/no-nonoctal-decimal-escape: Scan decimal escapes as bytes (#23850) (camc314) - fca69a8 linter: Skip traversal without this expressions (#23845) (camc314) - 838fd63 linter: Reduce preallocation for per-file diagnostics `Vec` (#23705) (Marius Schulz) - 417b506 linter/typescript/array-type: Remove full source text clone (#23751) (Marius Schulz) ### 📚 Documentation - 57e4469 linter/unicorn: Update prefer-dom-node-text-content rationale (#23933) (Mikhail Baev) - 3d61dea all: Correct capitalization in comments (#23887) (overlookmotel) ### 🛡️ Security - 3cdd18f deps: Update npm packages (#23690) (renovate[bot]) # Oxfmt ### 💥 BREAKING CHANGES - 259e0cd oxfmt,formatter_graphql: [**BREAKING**] Support draft syntax with removing prettier fallback (#23326) (leaysgur) - accbc49 oxfmt: [**BREAKING**] Format `parser:css,less,scss` files + css-in-js by `oxc_formatter_css` (#23321) (leaysgur) ### 🚀 Features - dffa4b3 formatter_css: Implement `oxc_formatter_css` (#23320) (leaysgur) - 01de9ec oxfmt: Format `parser:graphql` files by `oxc_formatter_graphql` (#23318) (leaysgur) - 4e66212 formatter_graphql: Implement oxc_formatter_graphql (#23317) (leaysgur) ### 🐛 Bug Fixes - 67325ae formatter_css: Handle frontmatter language (#23819) (leaysgur) - 3f355e5 formatter_graphql: Improve major prettier diffs (#23419) (leaysgur) - 48e2d78 formatter_css: Improve major prettier diffs (#23327) (leaysgur) - 8c07cad all: Enable `disable_old_builder` Cargo feature for `oxc_ast` crate in tests (#23888) (overlookmotel) - f7d1f50 oxlint, oxfmt: Enable `disable_old_builder` Cargo feature for `oxc_ast` crate (#23886) (overlookmotel) - d86f60b lsp: Normalize user config path to watch pattern (#23723) (Sysix) ### ⚡ Performance - 4ddcba0 formatter_core: Add printable-ASCII fast path to TextWidth (#23913) (Lawrence Lin) ### 📚 Documentation - b4d0dc9 oxfmt,formatter,formatter_css,formatter_core: Update AGENTS.md (#23814) (leaysgur) Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com> Co-authored-by: Cameron <cameron.clark@hey.com>
camc314
added a commit
that referenced
this pull request
Jul 3, 2026
… run only on less common node types (#23897) Somewhat similar to #23867 and #23868. This was generated using Claude Code after I noticed this rule was one of the slowest on the vscode repo. I have reviewed and tested the change by running it on the vscode codebase, and the # of violations before and after is identical. All tests also pass. I've also had Claude run a battery of tests on code it generated to test as many scenarios as possible, to ensure that there were no differences in output on main vs. this branch, and confirmed that there were indeed no differences. ## What changed The rule's `NODE_TYPES` were already derived, but two of them — `IdentifierName` and `IdentifierReference` — are among the most common node types in any program (every property name, every variable read). The destructuring cases dispatched on those identifiers and then walked *up* to their parents to detect destructuring patterns. This PR instead dispatches directly on the rare parent nodes and reads the key/binding: | Old dispatch (common) | New dispatch (rare) | Covers | |---|---|---| | `IdentifierName` | `BindingProperty` (key) + `AssignmentTargetPropertyProperty` (name) | `const {innerText} = node`, `({innerText: text} = node)` | | `IdentifierReference` | `AssignmentTargetPropertyIdentifier` (binding) | `({innerText} = node)` | `StaticMemberExpression` (`node.innerText`) is unchanged. ## Why it's behavior-identical - The only `IdentifierName` that can be a direct child of `BindingProperty`/`AssignmentTargetPropertyProperty` is the key (values bottom out in `BindingIdentifier`/`IdentifierReference`). The old grandparent set always matched (`ObjectPattern`/`ObjectAssignmentTarget`), so that branch always fired — matching the new unconditional trigger. Matching only `PropertyKey::StaticIdentifier` preserves the computed-key exclusion (`{[innerText]: text}`). - The `AssignmentTargetPropertyIdentifier` arm keeps the same parent-of-`ObjectAssignmentTarget` check. The old non-APTI path was dead: a rest target (`({...innerText} = node)`) goes through `AssignmentTargetRest`, never a direct `ObjectAssignmentTarget` child. The rule test snapshot is byte-identical (no `.snap` change). ## Results Running `oxlint --debug=timings --quiet -A all -W unicorn/prefer-dom-node-text-content` on the vscode repo. Before: ``` Found 245 warnings and 47 errors. Rule Time (ms) Relative Calls Source ------------------------------------ ---------- -------- ------- ------ unicorn/prefer-dom-node-text-content 116.624 100.0% 5996077 native ``` After: ``` Found 245 warnings and 47 errors. Rule Time (ms) Relative Calls Source ------------------------------------ ---------- -------- ------- ------ unicorn/prefer-dom-node-text-content 23.631 100.0% 1437765 native ``` Violations are unchanged (245 warnings, 47 errors). Calls drop 76% (5,996,077 → 1,437,765) and rule time drops ~80% (116ms → 23ms). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Cameron <cameron.clark@hey.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Generated with Claude Code, tested and reviewed by me. This was one of the most time-consuming rules on the vscode codebase before this change (oxlint 1.71.0), and drops considerably after this change.
The
runmethod had an OR pattern arm that the codegen didn't understand, so that was split. Therunmethod also had a wildcard arm that was non-empty, which caused linter_codegen to bail out of detecting what NODE_TYPES were worth checking. Removing that allows linter_codegen to generate the NODE_TYPES properly and limit this rule to only running on relevant files/nodes.Simple Comparison
Before (
npx oxlint --debug=timings --quiet -W allon vscode repo):After:
Result:
15,251,059calls down to2,938,968(80.7% fewer calls).I've re-run this a number of times and confirmed that the time savings are consistent thanks to the reduction in call count.
Running the before/after on the VS Code repo with just this rule enabled also results in the same exact # of lint violations being found, and all existing tests pass.
We should probably consider either updating the codegen to handle ORs in AST node arms, or else look for other usages of this pattern to fix them.