Skip to content

Patch ts.sys when the parser module loads - #250

Merged
NullVoxPopuli merged 1 commit into
ember-tooling:mainfrom
wagenet:wagenet/eep-patchts-ordering
Aug 19, 2026
Merged

Patch ts.sys when the parser module loads#250
NullVoxPopuli merged 1 commit into
ember-tooling:mainfrom
wagenet:wagenet/eep-patchts-ordering

Conversation

@wagenet

@wagenet wagenet commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

typescript-eslint copies ts.sys by value when it builds a program or its project service:

// @typescript-eslint/project-service/dist/createProjectService.js
const system = { ...tsserver.sys, clearImmediate, clearTimeout, ... };

That happens on the first type-aware parse in the process, which may be a plain .ts file that never reaches this parser. patchTs() ran from parseForESLint, so with the config shape the README documents — .gjs/.gts here, everything else left to @typescript-eslint/parser — linting a .ts file first hands TypeScript an unpatched host. The initial file scan and module resolutions run without .gts/.gjs support, and a cross-file .gts import stays an error type for the rest of the run:

src/uses-dep.gts
  3:7   Unsafe assignment of an error typed value        @typescript-eslint/no-unsafe-assignment
  3:13  Unsafe construction of a(n) `error` type value   @typescript-eslint/no-unsafe-call
  4:14  Unsafe assignment of an error typed value        @typescript-eslint/no-unsafe-assignment
  4:33  Unsafe call of a(n) `error` type typed value     @typescript-eslint/no-unsafe-call
  4:37  Unsafe member access .greet on an `error` value  @typescript-eslint/no-unsafe-member-access

Same three files with a .gts first: clean. Both parserOptions.project and parserOptions.projectService are affected. It isn't an extraFileExtensions mismatch — the fixture declares extraFileExtensions: ['.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 .gts to show up.

The cost, stated plainly: importing this parser now replaces ts.sys process-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 patched ts.sys, and the replaceExtensions pass over every .ts TypeScript reads starts earlier.

A narrower variant that installs the wrappers at load but leaves them delegating until parseForESLint arms them fixes projectService only: by the time it arms, classic project mode 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.js asserts the invariant: importing the parser entry and doing nothing else leaves ts.sys reporting a .gts's virtual .mts twin, reading it transformed, and offering it during a directory scan. All three assertions fail on main.

test-projects/parser-order covers it end to end with the README's config shape, linting src/plain.ts in a pass of its own so the ordering can't drift, and asserting the file count so a mis-invocation can't pass vacuously. Runs project and projectService.

Verified locally against the CI matrix using the same pnpm.overrides edits CI makes — every cell fails without the change and passes with it:

5.3 5.4 5.5 5.6 5.7 5.8 5.9 6
@typescript-eslint/parser ^8 ✗→✓ ✗→✓ ✗→✓ ✗→✓ ✗→✓ ✗→✓ ✗→✓ ✗→✓

Same 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's test-projects/* route .js/.ts through this parser too, which is why nothing caught this. That routing also fixes an unrelated problem: @typescript-eslint/parser hands TypeScript the source as ESLint read it, so a .ts file importing from a .gts gets an error type on every TypeScript version, patch or no patch. Added a short README note recommending the **/*.{js,ts} override — happy to split it out.

Notes

  • No changeset, per RELEASE.md. Could someone add the bug label?
  • Stop .gts counting against TypeScript's non-TS program size limit #247's "Known limitation" section describes this hazard as deliberately left alone. If both land, that section can go and its getFileSize patch stops being order-dependent; I'll update it there.
  • Separate in-flight work on replaceExtensions performance 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.

@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

🏎️ Benchmark Comparison

Parse

Benchmark Control (p50) Experiment (p50) Δ
🟢 gts small 937.42 ms 900.85 ms -3.9%
gts medium 1590.53 ms 1605.42 ms +0.9%
🔴 gts large 769.04 ms 807.65 ms +5.0%
gjs small 782.56 ms 784.59 ms +0.3%
gjs medium 1354.45 ms 1380.21 ms +1.9%
gjs large 634.43 ms 628.56 ms -0.9%
🔴 hbs small 129.16 ms 138.88 ms +7.5%
🟢 hbs medium 742.03 ms 722.35 ms -2.7%
🟢 hbs large 847.28 ms 826.78 ms -2.4%

🟢 faster · 🔴 slower · 🟠 slightly slower · ⚪ within 2%

Full mitata output
clk: ~3.08 GHz
cpu: AMD EPYC 7763 64-Core Processor
runtime: node 24.19.0 (x64-linux)

benchmark                   avg (min … max) p75 / p99    (min … top 1%)
------------------------------------------- -------------------------------
gts small (control)          971.89 ms/iter    1.01 s      █               
                       (821.95 ms … 1.26 s)    1.12 s ▅ ▅ ▅█  ▅▅ ▅▅  ▅    ▅
                    ( 13.00 mb …  88.58 mb)  42.52 mb █▁█▁██▁▁██▁██▁▁█▁▁▁▁█

gts small (experiment)       942.28 ms/iter 972.04 ms   ██                 
                       (812.12 ms … 1.26 s)    1.11 s ▅ ██  ▅▅ ▅ ▅ ▅      ▅
                    ( 13.07 mb …  88.09 mb)  42.37 mb █▁██▁▁██▁█▁█▁█▁▁▁▁▁▁█

                             ┌                                            ┐
                              ╷         ┌───────────┬────┐                ╷
         gts small (control)  ├─────────┤           │    ├────────────────┤
                              ╵         └───────────┴────┘                ╵
                             ╷    ┌─────────────┬───┐                  ╷
      gts small (experiment) ├────┤             │   ├──────────────────┤
                             ╵    └─────────────┴───┘                  ╵
                             └                                            ┘
                             812.12 ms           967.69 ms           1.12 s

summary
  gts small (experiment)
   1.03x faster than gts small (control)

------------------------------------------- -------------------------------
gts medium (control)            1.63 s/iter    1.65 s      █               
                          (1.49 s … 2.08 s)    1.67 s ▅    █ ▅▅  ▅ ▅ ▅  ▅▅▅
                    ( 79.94 mb …  99.75 mb)  84.32 mb █▁▁▁▁█▁██▁▁█▁█▁█▁▁███

gts medium (experiment)         1.64 s/iter    1.64 s   █        █         
                          (1.52 s … 2.11 s)    1.69 s ▅ █   ▅▅  ▅█   ▅   ▅▅
                    ( 78.47 mb …  98.70 mb)  83.93 mb █▁█▁▁▁██▁▁██▁▁▁█▁▁▁██

                             ┌                                            ┐
                             ╷          ┌────────────────────┬────┐    ╷
        gts medium (control) ├──────────┤                    │    ├────┤
                             ╵          └────────────────────┴────┘    ╵
                                    ╷   ┌───────────────────────┬         ╷
     gts medium (experiment)        ├───┤                       │─────────┤
                                    ╵   └───────────────────────┴         ╵
                             └                                            ┘
                             1.49 s              1.59 s              1.69 s

summary
  gts medium (control)
   1.01x faster than gts medium (experiment)

------------------------------------------- -------------------------------
gts large (control)          815.64 ms/iter 814.28 ms     █   █            
                       (733.63 ms … 1.22 s) 847.85 ms ▅ ▅▅█ ▅ █     ▅   ▅ ▅
                    ( 15.01 mb …  86.17 mb)  38.32 mb █▁███▁█▁█▁▁▁▁▁█▁▁▁█▁█

gts large (experiment)       853.98 ms/iter 853.84 ms   ██                 
                       (765.43 ms … 1.19 s) 930.92 ms ▅ ██ ▅ ▅ ▅ ▅   ▅    ▅
                    ( 14.86 mb …  87.09 mb)  38.09 mb █▁██▁█▁█▁█▁█▁▁▁█▁▁▁▁█

                             ┌                                            ┐
                             ╷   ┌──────────────┬      ╷
         gts large (control) ├───┤              │──────┤
                             ╵   └──────────────┴      ╵
                                    ╷   ┌───────────────┬                 ╷
      gts large (experiment)        ├───┤               │─────────────────┤
                                    ╵   └───────────────┴                 ╵
                             └                                            ┘
                             733.63 ms         832.28 ms          930.92 ms

summary
  gts large (control)
   1.05x faster than gts large (experiment)

------------------------------------------- -------------------------------
gjs small (control)          822.19 ms/iter 829.00 ms       █              
                       (735.84 ms … 1.12 s) 892.02 ms ▅▅▅▅▅ █    ▅▅     ▅ ▅
                    ( 29.30 mb …  86.01 mb)  37.06 mb █████▁█▁▁▁▁██▁▁▁▁▁█▁█

gjs small (experiment)       814.31 ms/iter 822.46 ms    █  █    █         
                       (739.04 ms … 1.11 s) 892.37 ms ▅▅▅█  █▅   █        ▅
                    ( 29.40 mb …  85.82 mb)  37.09 mb ████▁▁██▁▁▁█▁▁▁▁▁▁▁▁█

                             ┌                                            ┐
                             ╷    ┌───────────────────┬─┐                 ╷
         gjs small (control) ├────┤                   │ ├─────────────────┤
                             ╵    └───────────────────┴─┘                 ╵
                              ╷   ┌─────────────────┬─┐                   ╷
      gjs small (experiment)  ├───┤                 │ ├───────────────────┤
                              ╵   └─────────────────┴─┘                   ╵
                             └                                            ┘
                             735.84 ms         814.11 ms          892.37 ms

summary
  gjs small (experiment)
   1.01x faster than gjs small (control)

------------------------------------------- -------------------------------
gjs medium (control)            1.41 s/iter    1.43 s       █              
                          (1.31 s … 1.82 s)    1.46 s       █              
                    ( 20.99 mb …  90.42 mb)  71.60 mb █▁█▁▁██▁▁█▁█▁▁▁▁▁██▁█

gjs medium (experiment)         1.43 s/iter    1.45 s                     █
                          (1.31 s … 1.85 s)    1.46 s          █          █
                    ( 18.93 mb …  89.49 mb)  70.64 mb █▁▁▁▁█▁█▁██▁▁█▁▁█▁▁▁█

                             ┌                                            ┐
                             ╷          ┌───────────────────┬─────┐       ╷
        gjs medium (control) ├──────────┤                   │     ├───────┤
                             ╵          └───────────────────┴─────┘       ╵
                             ╷               ┌────────────────────┬─────┐╷
     gjs medium (experiment) ├───────────────┤                    │     ├┤
                             ╵               └────────────────────┴─────┘╵
                             └                                            ┘
                             1.31 s              1.38 s              1.46 s

summary
  gjs medium (control)
   1.01x faster than gjs medium (experiment)

------------------------------------------- -------------------------------
gjs large (control)          674.80 ms/iter 641.24 ms   █                  
                    (617.97 ms … 978.54 ms) 747.48 ms   ███                
                    ( 69.25 kb …  70.28 mb)  43.86 mb █████▁▁▁▁▁▁█▁▁▁▁▁▁▁▁█

gjs large (experiment)       667.87 ms/iter 631.51 ms   ██                 
                    (612.83 ms … 966.50 ms) 754.21 ms █ ██                 
                    (395.55 kb …  70.27 mb)  43.78 mb ████▁▁▁▁█▁▁▁▁▁▁▁▁▁▁▁█

                             ┌                                            ┐
                               ╷  ┌──────────────┬                      ╷
         gjs large (control)   ├──┤              │──────────────────────┤
                               ╵  └──────────────┴                      ╵
                             ╷ ┌───────────────┬                          ╷
      gjs large (experiment) ├─┤               │──────────────────────────┤
                             ╵ └───────────────┴                          ╵
                             └                                            ┘
                             612.83 ms         683.52 ms          754.21 ms

summary
  gjs large (experiment)
   1.01x faster than gjs large (control)

------------------------------------------- -------------------------------
hbs small (control)          146.79 ms/iter 151.65 ms       █              
                    (116.94 ms … 271.89 ms) 157.30 ms ▅▅ ▅▅ █   ▅    ▅ ▅ ▅▅
                    ( 51.35 mb …  56.97 mb)  53.82 mb ██▁██▁█▁▁▁█▁▁▁▁█▁█▁██

hbs small (experiment)       151.05 ms/iter 151.41 ms        █   █    █    
                    (112.35 ms … 294.18 ms) 161.74 ms ▅ ▅   ▅█   █    █  ▅▅
                    ( 51.23 mb …  57.34 mb)  54.17 mb █▁█▁▁▁██▁▁▁█▁▁▁▁█▁▁██

                             ┌                                            ┐
                                 ╷     ┌────────────────────┬────┐    ╷
         hbs small (control)     ├─────┤                    │    ├────┤
                                 ╵     └────────────────────┴────┘    ╵
                             ╷            ┌─────────────────────┬┐        ╷
      hbs small (experiment) ├────────────┤                     │├────────┤
                             ╵            └─────────────────────┴┘        ╵
                             └                                            ┘
                             112.35 ms         137.04 ms          161.74 ms

summary
  hbs small (control)
   1.03x faster than hbs small (experiment)

------------------------------------------- -------------------------------
hbs medium (control)         778.40 ms/iter 763.17 ms    █                 
                       (730.02 ms … 1.04 s) 813.47 ms  █ █                 
                    (  7.56 mb …  82.32 mb)  32.59 mb ████▁█▁▁█▁▁▁▁▁▁▁▁▁▁██

hbs medium (experiment)      756.38 ms/iter 741.30 ms   █                  
                    (709.34 ms … 986.19 ms) 800.26 ms   ██                 
                    (  8.97 mb …  82.26 mb)  38.02 mb ████▁█▁█▁▁▁▁▁▁▁▁▁▁█▁█

                             ┌                                            ┐
                                      ╷ ┌──────────────────┬              ╷
        hbs medium (control)          ├─┤                  │──────────────┤
                                      ╵ └──────────────────┴              ╵
                             ╷   ┌───────────────┬                  ╷
     hbs medium (experiment) ├───┤               │──────────────────┤
                             ╵   └───────────────┴                  ╵
                             └                                            ┘
                             709.34 ms         761.41 ms          813.47 ms

summary
  hbs medium (experiment)
   1.03x faster than hbs medium (control)

------------------------------------------- -------------------------------
hbs large (control)          874.35 ms/iter 856.31 ms    █                 
                       (836.44 ms … 1.12 s) 900.99 ms    ██                
                    ( 53.41 mb …  65.10 mb)  55.53 mb █████▁██▁▁▁▁▁▁▁▁▁▁▁▁█

hbs large (experiment)       855.09 ms/iter 832.86 ms █                    
                       (824.88 ms … 1.08 s) 894.09 ms █▂▂                  
                    ( 53.30 mb …  64.86 mb)  55.47 mb ███▆▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▆

                             ┌                                            ┐
                                    ╷  ┌──────────────────┬               ╷
         hbs large (control)        ├──┤                  │───────────────┤
                                    ╵  └──────────────────┴               ╵
                             ┌─────────────────┬                      ╷
      hbs large (experiment) │                 │──────────────────────┤
                             └─────────────────┴                      ╵
                             └                                            ┘
                             824.88 ms         862.93 ms          900.99 ms

summary
  hbs large (experiment)
   1.02x faster than hbs large (control)
### Project mode (type-aware)
Benchmark Control (p50) Experiment (p50) Δ
project warm parse x150 613.24 ms 607.12 ms -1.0%
project parse+typed x150 654.54 ms 650.29 ms -0.6%
projectService warm parse x150 629.02 ms 633.33 ms +0.7%
projectService parse+typed x150 680.08 ms 677.00 ms -0.5%

🟢 faster · 🔴 slower · 🟠 slightly slower · ⚪ within 2%

Full mitata output
clk: ~2.99 GHz
cpu: AMD EPYC 7763 64-Core Processor
runtime: node 24.19.0 (x64-linux)

benchmark                                   avg (min … max) p75 / p99    (min … top 1%)
----------------------------------------------------------- -------------------------------
project warm parse x150 (control)            616.58 ms/iter 629.21 ms           █          
                                    (590.83 ms … 643.37 ms) 636.24 ms █▁▁█▁▁██▁██▁▁█▁▁▁██▁█
                                  gc(284.58 ms … 299.41 ms) 113.38 mb (111.33 mb…114.82 mb)

project warm parse x150 (experiment)         609.89 ms/iter 619.80 ms          █           
                                    (593.68 ms … 628.60 ms) 622.03 ms ███▁▁█▁▁▁█▁▁▁█▁▁█▁███
                                  gc(283.77 ms … 294.98 ms) 111.76 mb (111.28 mb…112.58 mb)

                                             ┌                                            ┐
                                             ╷             ┌───────────┬───────────┐      ╷
           project warm parse x150 (control) ├─────────────┤           │           ├──────┤
                                             ╵             └───────────┴───────────┘      ╵
                                                ╷  ┌────────────┬─────────┐ ╷
        project warm parse x150 (experiment)    ├──┤            │         ├─┤
                                                ╵  └────────────┴─────────┘ ╵
                                             └                                            ┘
                                             590.83 ms         613.54 ms          636.24 ms

summary
  project warm parse x150 (experiment)
   1.01x faster than project warm parse x150 (control)

----------------------------------------------------------- -------------------------------
project parse+typed x150 (control)           655.74 ms/iter 661.43 ms              █       
                                    (614.29 ms … 696.78 ms) 674.01 ms ▆▁▁▁▁▁▁▆▆▁▁▁▁█▆▆▆▁▁▆▆
                                  gc(289.71 ms … 308.20 ms) 118.61 mb (114.19 mb…122.89 mb)

project parse+typed x150 (experiment)        654.85 ms/iter 661.61 ms          █           
                                    (635.16 ms … 692.75 ms) 668.82 ms ██▁█▁▁▁█▁█▁█▁█▁▁█▁█▁█
                                  gc(293.73 ms … 304.55 ms) 117.34 mb (116.07 mb…121.64 mb)

                                             ┌                                            ┐
                                             ╷                 ┌────────────┬────┐        ╷
          project parse+typed x150 (control) ├─────────────────┤            │    ├────────┤
                                             ╵                 └────────────┴────┘        ╵
                                                             ╷   ┌──────────┬────┐    ╷
       project parse+typed x150 (experiment)                 ├───┤          │    ├────┤
                                                             ╵   └──────────┴────┘    ╵
                                             └                                            ┘
                                             614.29 ms         644.15 ms          674.01 ms

summary
  project parse+typed x150 (experiment)
   1x faster than project parse+typed x150 (control)

----------------------------------------------------------- -------------------------------
projectService warm parse x150 (control)     631.63 ms/iter 633.12 ms     █     ▃          
                                    (620.56 ms … 651.40 ms) 642.73 ms ▆▁▁▆█▁▁▁▆▁█▆▁▁▁▁▁▁▆▁▆
                                  gc(293.85 ms … 307.44 ms) 115.08 mb (113.26 mb…116.01 mb)

projectService warm parse x150 (experiment)  634.61 ms/iter 643.12 ms   █                  
                                    (618.28 ms … 651.63 ms) 650.00 ms █▁██▁█▁▁▁█▁▁▁▁████▁▁█
                                  gc(296.68 ms … 304.77 ms) 111.63 mb (110.70 mb…112.12 mb)

                                             ┌                                            ┐
                                                ╷     ┌─────────┬─┐             ╷
    projectService warm parse x150 (control)    ├─────┤         │ ├─────────────┤
                                                ╵     └─────────┴─┘             ╵
                                             ╷    ┌─────────────────┬───────────┐         ╷
 projectService warm parse x150 (experiment) ├────┤                 │           ├─────────┤
                                             ╵    └─────────────────┴───────────┘         ╵
                                             └                                            ┘
                                             618.28 ms         634.14 ms          650.00 ms

summary
  projectService warm parse x150 (control)
   1x faster than projectService warm parse x150 (experiment)

----------------------------------------------------------- -------------------------------
projectService parse+typed x150 (control)    684.05 ms/iter 692.49 ms █ █   █████ █ █  █  █
                                    (656.61 ms … 713.55 ms) 708.25 ms █▁█▁▁▁█████▁█▁█▁▁█▁▁█
                                  gc(299.45 ms … 320.40 ms) 119.72 mb (118.58 mb…123.66 mb)

projectService parse+typed x150 (experiment) 685.27 ms/iter 689.50 ms       █              
                                    (664.79 ms … 731.90 ms) 706.39 ms █▁█████▁▁▁▁██▁▁▁▁█▁▁█
                                  gc(309.15 ms … 316.57 ms) 118.69 mb (117.19 mb…122.16 mb)

                                             ┌                                            ┐
                                             ╷            ┌──────────┬──────┐             ╷
   projectService parse+typed x150 (control) ├────────────┤          │      ├─────────────┤
                                             ╵            └──────────┴──────┘             ╵
                                                    ╷    ┌────────────┬───┐             ╷
projectService parse+typed x150 (experiment)        ├────┤            │   ├─────────────┤
                                                    ╵    └────────────┴───┘             ╵
                                             └                                            ┘
                                             656.61 ms         682.43 ms          708.25 ms

summary
  projectService parse+typed x150 (control)
   1x faster than projectService parse+typed x150 (experiment)

@NullVoxPopuli NullVoxPopuli added the bug Something isn't working label Aug 19, 2026
Comment thread README.md Outdated

`@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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this is too much detail. a reader of this readme is not going to care

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

<3

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
wagenet force-pushed the wagenet/eep-patchts-ordering branch from a21c586 to fc79839 Compare August 19, 2026 16:57
@NullVoxPopuli
NullVoxPopuli merged commit 5331475 into ember-tooling:main Aug 19, 2026
38 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants