Patch ts.sys when the parser module loads - #250
Merged
NullVoxPopuli merged 1 commit intoAug 19, 2026
Conversation
Contributor
🏎️ Benchmark ComparisonParse
Full mitata output
Full mitata output |
|
|
||
| `@typescript-eslint/parser` hands TypeScript the file's source as ESLint read it, | ||
| so a `.ts` file that imports from a `.gts` gets an `error` type for the import — | ||
| the specifier is never rewritten to something TypeScript can resolve, and rules |
Member
There was a problem hiding this comment.
this is too much detail. a reader of this readme is not going to care
typescript-eslint copies ts.sys by value when it builds a program or its project service, on the first type-aware parse in the process. Calling patchTs() from parseForESLint misses that copy whenever the first file linted is a plain .ts handled by @typescript-eslint/parser — the setup the README documents. TypeScript then scans and resolves against an unpatched host, and cross-file .gts imports stay an `error` type for the rest of the run, so the no-unsafe-* rules fire on every use of them. ESLint resolves every parser named in a config before it lints anything, so patching at module load costs no extra work in the common case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
wagenet
force-pushed
the
wagenet/eep-patchts-ordering
branch
from
August 19, 2026 16:57
a21c586 to
fc79839
Compare
NullVoxPopuli
approved these changes
Aug 19, 2026
Merged
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.
typescript-eslint copies
ts.sysby value when it builds a program or its project service:That happens on the first type-aware parse in the process, which may be a plain
.tsfile that never reaches this parser.patchTs()ran fromparseForESLint, so with the config shape the README documents —.gjs/.gtshere, everything else left to@typescript-eslint/parser— linting a.tsfile first hands TypeScript an unpatched host. The initial file scan and module resolutions run without.gts/.gjssupport, and a cross-file.gtsimport stays anerrortype for the rest of the run:Same three files with a
.gtsfirst: clean. BothparserOptions.projectandparserOptions.projectServiceare affected. It isn't anextraFileExtensionsmismatch — the fixture declaresextraFileExtensions: ['.gts', '.gjs']and still reproduces.The change
Call
patchTs()at module load. ESLint resolves every parser named in a config before it lints anything, so the module is loaded first either way; this stops the patch from waiting for a.gtsto show up.The cost, stated plainly: importing this parser now replaces
ts.sysprocess-wide, including in runs that never lint type-aware. For most setups that changes when, not whether — but a.gjs-only non-type-aware config would newly get a patchedts.sys, and thereplaceExtensionspass over every.tsTypeScript reads starts earlier.A narrower variant that installs the wrappers at load but leaves them delegating until
parseForESLintarms them fixesprojectServiceonly: by the time it arms, classicprojectmode has already scanned the directory and cached the failed lookups. If eager-always is too broad, the alternative is an explicit opt-in call from the consuming config — happy to rework it that way.Tests
tests/ts-patch-load-order.test.jsasserts the invariant: importing the parser entry and doing nothing else leavests.sysreporting a.gts's virtual.mtstwin, reading it transformed, and offering it during a directory scan. All three assertions fail onmain.test-projects/parser-ordercovers it end to end with the README's config shape, lintingsrc/plain.tsin a pass of its own so the ordering can't drift, and asserting the file count so a mis-invocation can't pass vacuously. RunsprojectandprojectService.Verified locally against the CI matrix using the same
pnpm.overridesedits CI makes — every cell fails without the change and passes with it:@typescript-eslint/parser^8Same result on ^6 (6.21.0), ^7 (7.18.0) and canary (8.67.1-alpha.18) against TS 5.9.
README
eslint-plugin-ember's configs and this repo'stest-projects/*route.js/.tsthrough this parser too, which is why nothing caught this. That routing also fixes an unrelated problem:@typescript-eslint/parserhands TypeScript the source as ESLint read it, so a.tsfile importing from a.gtsgets anerrortype on every TypeScript version, patch or no patch. Added a short README note recommending the**/*.{js,ts}override — happy to split it out.Notes
RELEASE.md. Could someone add the bug label?getFileSizepatch stops being order-dependent; I'll update it there.replaceExtensionsperformance touches the same function, which this makes run earlier and over more files.Written by Claude (Anthropic's Claude Code) in Peter Wagenet's checkout and opened from his account. The analysis, code and tests are Claude's, not his. Questions here will reach him.