Skip to content

perf(linter/plugins): create global prop vars at top level of modules#22928

Merged
graphite-app[bot] merged 1 commit into
mainfrom
om/06-02-perf_linter_plugins_create_global_prop_vars_at_top_level_of_modules
Jun 3, 2026
Merged

perf(linter/plugins): create global prop vars at top level of modules#22928
graphite-app[bot] merged 1 commit into
mainfrom
om/06-02-perf_linter_plugins_create_global_prop_vars_at_top_level_of_modules

Conversation

@overlookmotel

@overlookmotel overlookmotel commented Jun 3, 2026

Copy link
Copy Markdown
Member

Oxlint's JS-side code is processed by a TSDown plugin that transforms global property accesses to consts imported from globals.ts. e.g.:

function foo(obj) {
  return Object.keys(obj);
}

is transformed to:

import { ObjectKeys } from "./utils/globals.ts";

function foo(obj) {
  return ObjectKeys(obj);
}

The purpose of this transform is:

1. Better perf - avoid a property lookup/check on each call.
2. More robust in the face of user code (plugins) altering globals e.g. Object.keys = function myWeirdFunction() {}.

While delving into the assembly that V8 produces while reviewing #22238, I discovered that in fact the transform was having a negative effect on perf, because TSDown puts the global consts in a separate chunk globals.js. Because the consts are in a different module, this actually makes them more costly to access, rather than less.

In fact, it turns out that TurboFan doesn't insert a check at all in the original code - it instead has machinery to trigger de-opt if the global is reassigned. So the original perf rationale for this transform was completely misguided - it doesn't in fact produce a perf gain at all! We still want to keep it for the robustness advantage, but we don't want it to be perf loss, as it was before this PR.

This PR fixes the perf by instead defining consts inline in each file. The example above is now transformed to:

const ObjectKeys = Object.keys;

function foo(obj) {
  return ObjectKeys(obj);
}

This also removes the need to manually maintain the globals.ts file. It's now automatic.

Additionally, improve the transform in a few minor ways:

  1. Support deeper nested global props e.g. Object.prototype.toString.
  2. Skip converting e.g. Array.prototype.slice.call(arguments) (because .call requires this, so ArrayPrototypeSliceCall(arguments) is incorrect).
  3. Do replace methods which take this (e.g. Promise.resolve) when they're not used in a method call.

Note: The robustness advantage of this transform may appear to be marginal, and not worth the bother of having this transform at all. Personally, I think it's worthwhile. Oxlint JS-side code has access to a slice of Rust's memory as a Uint8Array. Writing to that buffer could cause UB, reading/writing out of bounds, or other serious problems on Rust side, so IMO it's critical to protect against that in every way we can.

overlookmotel commented Jun 3, 2026

Copy link
Copy Markdown
Member Author

How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • 0-merge - adds this PR to the back of the merge queue
  • hotfix - for urgent changes, fast-track this PR to the front of the merge queue

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions github-actions Bot added A-linter Area - Linter A-cli Area - CLI A-linter-plugins Area - Linter JS plugins labels Jun 3, 2026
@overlookmotel overlookmotel self-assigned this Jun 3, 2026
@overlookmotel overlookmotel force-pushed the om/06-02-perf_linter_plugins_create_global_prop_vars_at_top_level_of_modules branch from e0ae4b4 to 52a0fb8 Compare June 3, 2026 00:41
@overlookmotel overlookmotel marked this pull request as ready for review June 3, 2026 00:41
@overlookmotel overlookmotel requested a review from camc314 as a code owner June 3, 2026 00:41
Copilot AI review requested due to automatic review settings June 3, 2026 00:41

Copilot AI 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.

Pull request overview

This PR updates Oxlint’s TSDown replace-globals transform to inline global-property aliases as top-level const declarations within each transformed module (instead of importing them from src-js/utils/globals.ts), eliminating the cross-module access penalty while retaining the robustness benefit of detaching global methods.

Changes:

  • Generate per-module const declarations like const ObjectKeys = Object.keys; and rewrite member accesses to use those locals.
  • Extend handling to support chained global property accesses (e.g. Object.prototype.toString) and preserve trailing .call/.apply/.bind on detached functions.
  • Remove the now-unneeded apps/oxlint/src-js/utils/globals.ts.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
apps/oxlint/tsdown_plugins/replace_globals.ts Reworks the transform to generate inline top-level const aliases and improves handling of chained accesses / call-sites.
apps/oxlint/src-js/utils/globals.ts Deletes the manual globals-export list file, since aliases are now generated inline per module.

Comment thread apps/oxlint/tsdown_plugins/replace_globals.ts
@overlookmotel overlookmotel added the 0-merge Merge with Graphite Merge Queue label Jun 3, 2026

overlookmotel commented Jun 3, 2026

Copy link
Copy Markdown
Member Author

Merge activity

…#22928)

Oxlint's JS-side code is processed by a TSDown plugin that transforms global property accesses to `const`s imported from `globals.ts`. e.g.:

```js
function foo(obj) {
  return Object.keys(obj);
}
```

is transformed to:

```js
import { ObjectKeys } from "./utils/globals.ts";

function foo(obj) {
  return ObjectKeys(obj);
}
```

The purpose of this transform is:

1\. Better perf - avoid a property lookup/check on each call.
2\. More robust in the face of user code (plugins) altering globals e.g. `Object.keys = function myWeirdFunction() {}`.

While delving into the assembly that V8 produces while reviewing #22238, I discovered that in fact the transform was having a _negative_ effect on perf, because TSDown puts the global `const`s in a separate chunk `global.js`. Because the consts are in a different module, this actually makes them _more_ costly to access, rather than less.

In fact, it turns out that TurboFan doesn't insert a check at all in the original code - it instead has machinery to trigger de-opt if the global is reassigned. So the original perf rationale for this transform was completely misguided - it doesn't in fact produce a perf gain at all! We still want to keep it for the robustness advantage, but we don't want it to be perf _loss_, as it was before this PR.

This PR fixes the perf by instead defining consts inline in each file. The example above is now transformed to:

```js
const ObjectKeys = Object.keys;

function foo(obj) {
  return ObjectKeys(obj);
}
```

This also removes the need to manually maintain the `globals.ts` file. It's now automatic.

Additionally, improve the transform in a few minor ways:

1. Support deeper nested global props e.g. `Object.prototype.toString`.
2. Skip converting e.g. `Array.prototype.slice.call(arguments)` (because `.call` requires `this`, so `ArrayPrototypeSliceCall(arguments)` is incorrect).
3. Do replace methods which take `this` (e.g. `Promise.resolve`) when they're not used in a method call.

Note: The robustness advantage of this transform may appear to be marginal, and not worth the bother of having this transform at all. Personally, I think it's worthwhile. Oxlint JS-side code has access to a slice of Rust's memory as a `Uint8Array`. Writing to that buffer could cause UB, reading/writing out of bounds, or other serious problems on Rust side, so IMO it's critical to protect against that in every way we can.
@graphite-app graphite-app Bot force-pushed the om/06-02-refactor_linter_config_do_not_import_from_globals.ts_manually branch from 5235247 to 1f96534 Compare June 3, 2026 00:53
@graphite-app graphite-app Bot force-pushed the om/06-02-perf_linter_plugins_create_global_prop_vars_at_top_level_of_modules branch from 52a0fb8 to 0b7ce7e Compare June 3, 2026 00:54
Base automatically changed from om/06-02-refactor_linter_config_do_not_import_from_globals.ts_manually to main June 3, 2026 00:58
@graphite-app graphite-app Bot removed the 0-merge Merge with Graphite Merge Queue label Jun 3, 2026
@graphite-app graphite-app Bot merged commit 0b7ce7e into main Jun 3, 2026
29 checks passed
@graphite-app graphite-app Bot deleted the om/06-02-perf_linter_plugins_create_global_prop_vars_at_top_level_of_modules branch June 3, 2026 00:59
Boshen added a commit that referenced this pull request Jun 8, 2026
# Oxlint
### 🚀 Features

- e805174 linter: Add schema for `jest/vitest/max-expects` (#23105)
(Sysix)
- 7850577 linter: Add schema for `jest/vitest/expect-expect` (#23104)
(Sysix)
- 75f641a linter: Add schema for `jest/vitest/consistent-test-it`
(#23103) (Sysix)
- 5125f89 linter/unicorn: Support no-null `checkArguments` option
(#23098) (camc314)
- b8b9797 linter: Add schema for `import-max-dependencies` (#23096)
(Sysix)
- 65cb47a linter/eslint: Support no-unused-expressions
`ignoreDirectives` option (#23097) (camc314)
- f6c36d5 linter: Add schema for `import/prefer-default-export` (#23091)
(Sysix)
- 0d4a5d1 linter: Add schema for `eslint/sort-vars` (#23090) (Sysix)
- fdb5bf5 linter: Add schema for `eslint/radix` (#23082) (Sysix)
- 05b4dcf linter: Add schema for `eslint/prefer-const` (#23081) (Sysix)
- 5a06c4d linter/vue: Implement next-tick-style rule (#23041) (Alex
Peshkov)
- e38a36a linter: Add schema for `eslint/operator-assignment` (#23080)
(Sysix)
- 907cee7 linter: Add schema for `eslint/no-warning-comments` (#23075)
(Sysix)
- 9470bb2 linter: Add schema for `eslint/no-unused-vars` (#23073)
(Sysix)
- 234b5cf linter: Add schema for `eslint/no-shadow` (#23072) (Sysix)
- de0dd8b linter: Add schema for `eslint/no-restricted-exports` (#23020)
(Sysix)
- faa3e0d linter: Add schema for `eslint/no-param-reassign` (#23018)
(Sysix)
- dbc9c27 linter: Add schema for `eslint/no-magic-numbers` (#23017)
(Sysix)
- 38d3569 linter: Add schema for `eslint/no-inner-declarations` (#23016)
(Sysix)
- 008fa41 linter: Add schema for `eslint/no-constant-condition` (#22991)
(Sysix)
- ca44623 linter: Add schema for `eslint/no-empty-function` (#22988)
(Sysix)
- 43eb04d linter: Add schema for `eslint/id-match` (#22987) (Sysix)
- a800f27 linter: Add schema for `eslint/capitalized-comments` (#22984)
(Sysix)
- 96e2d32 linter: Add schema for `eslint/id-length` (#22963) (Sysix)
- 545493f linter: Add schema for `eslint/complexity` (#22960) (Sysix)
- 5f0b558 linter: Add schema for `eslint/class-methods-use-this`
(#22959) (Sysix)
- 719b720 linter: Add schema for simple rule configurations (#22948)
(Sysix)
- fd00966 linter: Add right schema for `eslint/max-*` rules (#22923)
(Sysix)
- 1226d78 linter: Fill schema with rule configurations (#22907) (Sysix)
- 8f423c1 linter/vue: Implement `require-direct-export` rule (#17623)
(yefan)
- 78e915b linter/vue: Implement no-reserved-props rule (#22914) (bab)
- 0f200a9 linter/vue: Implement require-prop-types rule (#22083) (Alex
Peshkov)
- 5da9da9 linter/vue: Implement no-reserved-keys rule (#21780) (bab)
- 75e14a8 linter/vue: Implement prop-name-casing rule (#22892) (bab)
- 85efabf semantic: Make building the class table optional, off by
default (#22862) (Boshen)

### 🐛 Bug Fixes

- a49b0cf linter/no-map-spread: Remove ineffective autofix (#22956)
(camc314)
- cf53285 parser: Report reserved type-declaration names in the parser
(#23035) (Boshen)
- 0383e61 linter: Fix schema for rules without a config (#22946) (Sysix)
- 4d722e0 parser: Report duplicate switch `default` clause in the parser
(#23012) (Boshen)
- 6cb34b8 linter/plugins: Make spreading `Token` instances keep `loc`
property (#22947) (Nicolas Le Cam)
- 27de044 linter/plugins: Make spreading `Comment` instances keep `loc`
property (#22238) (Nicolas Le Cam)
- 742fd0b linter/double-comparisons: Make fixer a suggestion (#22968)
(camc314)
- 93f4494 linter: Respect default child config plugin when extending
parent config (#22903) (Sysix)
- 594ed86 linter: Deny unknown options for some rules (#22924) (Sysix)
- 3253038 linter/expect-expect: Align default rule options (#22890)
(camc314)
- bbe44ea linter: Respect default plugins from extended config (#22896)
(Sysix)

### ⚡ Performance

- 0b7ce7e linter/plugins: Create global prop vars at top level of
modules (#22928) (overlookmotel)
- 0f7c319 linter/plugins: Define class `#loc` setter functions as
`const`s (#22919) (overlookmotel)

### 📚 Documentation

- 7b0380d linter: Remove preserve-caught-error note (#22994) (camc314)
- dadafe3 oxlint, oxfmt: Mention migrate skills in npm READMEs (#22965)
(Boshen)
# Oxfmt
### 🚀 Features

- 3da77e0 oxfmt: Format `parser:json5` files by `oxc_formatter_json`
(#22990) (leaysgur)
- c786f0d oxfmt: Format `parser:jsonc` files by `oxc_formatter_json`
(#22913) (leaysgur)
- 27a6db8 formatter_json: Implement jsonc variant (#22912) (leaysgur)

### 🐛 Bug Fixes

- 2aedd52 oxfmt: Avoid JS promise rejects for all TSFN call sites
(#23107) (leaysgur)
- 01e0871 formatter,formatter_json: Handle PS/LS as line terminator
(#22978) (leaysgur)
- 23902d9 formatter_json: Handle CR only line breaks (#22977) (leaysgur)
- 136b72b formatter_json: Use line_suffix for line comment outside array
(#22931) (leaysgur)
- 44e40fa formatter_json: Expand line comment inside array (#22911)
(leaysgur)
- 2c86896 formatter_json: Avoid example binary name collision (#22904)
(camc314)

### 📚 Documentation

- cc69d8d formatter_json: Update AGENTS.md (#22981) (leaysgur)
- 0490721 formatter_json: Update AGENTS.md (#22976) (leaysgur)
- dadafe3 oxlint, oxfmt: Mention migrate skills in npm READMEs (#22965)
(Boshen)
- f88961a oxfmt: Annotate each config option with supported languages
(#22953) (leaysgur)
- 7e514bf formatter_json: Update AGENTS.md (#22930) (leaysgur)

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 A-linter-plugins Area - Linter JS plugins

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants