Skip to content

Match transformForLint masking to ember-estree's toPlaceholderJS (fixes per-file program rebuilds)#233

Merged
NullVoxPopuli merged 1 commit into
ember-tooling:mainfrom
NullVoxPopuli-ai-agent:fix/placeholder-masking-parity
Jul 3, 2026
Merged

Match transformForLint masking to ember-estree's toPlaceholderJS (fixes per-file program rebuilds)#233
NullVoxPopuli merged 1 commit into
ember-tooling:mainfrom
NullVoxPopuli-ai-agent:fix/placeholder-masking-parity

Conversation

@NullVoxPopuli-ai-agent

Copy link
Copy Markdown

Fixes #229 (the performance/memory half, with a strong lead on the rest).

Root cause

For type-aware linting, every .gts/.gjs file gets a JS placeholder text twice, by two different implementations:

typescript-estree hashes the code passed to parseForESLint against what the watch program last read from disk (getWatchProgramsForProjects). Since the two maskings differ, every file with a non-empty template hashes differently — which fires the file-changed watch callback and silently rebuilds the entire TS program inside getProgram(), once per .gts/.gjs file linted. It's invisible in debug logs ("Found existing program for file" prints either way); the tell is that services.program is a new object on every parse.

On a large codebase that's an O(project) rebuild × thousands of files — hour-long runs — and the allocation churn (× --concurrency workers) accounts for the tens-of-GB memory. It also plausibly feeds the non-deterministic type errors, since checker state now depends on per-worker lint order.

How the two fixes diverged

#226 and #230 were the same escape-overflow bug in the two copies of this logic. Through v0.14.0 both sides escaped `/$ identically, so bytes matched except on overflow files. The fixes chose different maskings (blank-all vs blank-`$), so as of v0.14.1/v0.14.2 every templated file mismatches.

Fix

Blank only `/$ in maskTemplateContentForLint, byte-matching ember-estree 0.6.10. This remains overflow-safe for the #226 case (1:1 replacement, placeholder never outgrows the region).

Verified against a minimal 3-file typed-lint project: before, services.program was a distinct object after every parse; after, all parses share one program.

Regression test

tests/placeholder-parity.test.js asserts transformForLint(code).output is byte-identical to the toTree placeholder across the #226 snippet, dollar-heavy expression templates, emoji/CJK, CRLF, and multi-template modules — so the two implementations can't silently drift again. Longer-term it may be worth exporting toPlaceholderJS from ember-estree and deleting the copy here.

🤖 Generated with Claude Code

typescript-eslint hashes the code the parser passes in against what the
watch program last read from disk (via the patched ts.sys.readFile). The
two texts came from different masking strategies: toPlaceholderJS keeps
template content and blanks only backticks/dollars, while transformForLint
blanked every non-newline character. Any .gts/.gjs file with a non-empty
template therefore hashed differently, which fired the file-changed watch
callback and silently rebuilt the entire TS program inside getProgram() —
once per file linted. On large codebases that is hour-long lint runs and
tens of GB of memory (ember-tooling#229).

Blank only backticks/dollars, byte-matching ember-estree 0.6.10, and add a
parity regression test so the two implementations cannot drift again. This
masking is still overflow-safe for the ember-tooling#226 case: replacement is 1:1, so
the placeholder never outgrows the original region.

Fixes ember-tooling#229

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@NullVoxPopuli NullVoxPopuli left a comment

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.

good news is that all the other tests added over the last few days still pass

@NullVoxPopuli NullVoxPopuli merged commit af36c10 into ember-tooling:main Jul 3, 2026
40 checks passed
@NullVoxPopuli NullVoxPopuli deleted the fix/placeholder-masking-parity branch July 3, 2026 19:59
@github-actions github-actions Bot mentioned this pull request Jul 3, 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.

Memory leak, performance issues and usability issues

2 participants