Skip to content

perf(linter/unicorn/prefer-dom-node-text-content): change dispatch to run only on less common node types#23897

Merged
camc314 merged 2 commits into
mainfrom
perf/prefer-dom-node-text-content-dispatch
Jul 2, 2026
Merged

perf(linter/unicorn/prefer-dom-node-text-content): change dispatch to run only on less common node types#23897
camc314 merged 2 commits into
mainfrom
perf/prefer-dom-node-text-content-dispatch

Conversation

@connorshea

@connorshea connorshea commented Jun 28, 2026

Copy link
Copy Markdown
Member

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

@github-actions github-actions Bot added the A-linter Area - Linter label Jun 28, 2026
@connorshea connorshea force-pushed the perf/prefer-dom-node-text-content-dispatch branch from d51dc63 to e30436a Compare June 28, 2026 14:48
@connorshea connorshea marked this pull request as ready for review June 28, 2026 14:49
@connorshea connorshea requested a review from camc314 as a code owner June 28, 2026 14:49
@connorshea connorshea changed the title perf(linter/unicorn/prefer-dom-node-text-content): only dispatch for relevant node types perf(linter/unicorn/prefer-dom-node-text-content): change dispatch to run only on less common node types Jun 28, 2026
@codspeed-hq

codspeed-hq Bot commented Jun 28, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 5 untouched benchmarks
⏩ 66 skipped benchmarks1


Comparing perf/prefer-dom-node-text-content-dispatch (a2f43ff) with main (fdbd34d)

Open in CodSpeed

Footnotes

  1. 66 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.

@camc314 camc314 self-assigned this Jun 28, 2026
…cturing nodes instead of identifiers

The rule's destructuring cases dispatched on `IdentifierName` and
`IdentifierReference` — two of the most common node types (every property
name and variable read) — then walked up to their parents to detect
destructuring patterns. Instead, dispatch directly on the rare parent
nodes (`BindingProperty`, `AssignmentTargetPropertyProperty`,
`AssignmentTargetPropertyIdentifier`) and read the key/binding.

Behavior is identical:
- The only `IdentifierName` that can be a direct child of `BindingProperty`
  /`AssignmentTargetPropertyProperty` is the key, and the old grandparent
  set always matched (`ObjectPattern`/`ObjectAssignmentTarget`), so that
  branch always fired. Matching only `PropertyKey::StaticIdentifier`
  preserves the computed-key exclusion.
- 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.

On the vscode codebase, violations are unchanged (245 warnings, 47 errors),
calls drop 76% (5,996,077 -> 1,437,765), and rule time drops ~80%
(116ms -> 23ms).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@connorshea connorshea force-pushed the perf/prefer-dom-node-text-content-dispatch branch from e30436a to fb728a0 Compare June 30, 2026 03:57

@camc314 camc314 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice work!

@camc314 camc314 merged commit dc8fd9a into main Jul 2, 2026
28 checks passed
@camc314 camc314 deleted the perf/prefer-dom-node-text-content-dispatch branch July 2, 2026 16:00
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
… 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-linter Area - Linter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants