Commit 9d4544e
Share options.locals by reference so scope.crawl mutations are visible
This fixes two smoke-test failures from commit 571a94d:
{{fn}} as keyword (but it is shadowed): it works
{{on}} as keyword (but it is shadowed): it works
These `.gjs` tests declare a JS-level lexical (`const fn = ...` or
`const on = ...`) and expect the template reference to resolve to the
shadowing local, not the built-in keyword. They passed on main but
regressed on the PR because of how `preprocess` wired
`template.blockParams`.
## Root cause
`babel-plugin-ember-template-compilation` passes `options.locals` as a
live `readOnlyArray` proxy over its internal `ScopeLocals` array. The
plugin order at precompile time is, roughly:
[embroider macros, scope.crawl, AutoImportBuiltins, ...other ember
strict-mode transforms]
`scope.crawl` walks the template, detects `PathExpression` upvars that
have matching JS-level bindings (via `astNodeHasBinding` and Babel scope
lookup), and calls `scope.add(name)` for each — mutating the underlying
locals array. When it runs *before* `AutoImportBuiltins`, its mutations
need to be visible through `template.blockParams` so that
`AutoImportBuiltins`' `trackLocals.hasLocal('fn')` returns `true` for a
shadowed lexical and skips the rewrite.
Main achieves this by passing `options.locals` directly to
`b.template({ blockParams: options.locals, ... })` in
`handlebars-node-visitors.ts`. The assignment is by reference: the
Template literally points at the proxy, and later reads see the latest
state of the underlying array in real time.
The PR's preprocess was instead doing:
template.blockParams = [...options.locals];
That takes a *snapshot* of the proxy at preprocess-start time (when it
is still empty). When `scope.crawl` later mutates the underlying array,
`template.blockParams` stays frozen at `[]`. `AutoImportBuiltins` then
sees an empty locals table, fails to detect the shadow, calls
`jsutils.bindImport('fn')` which — because the JS scope has `const fn`
— imports `@ember/helper#fn` under a renamed local like `fn0`, and
rewrites `node.path.original = 'fn0'`. The `buildLegacyPath` setter
rebuilds `head` to `VarHead{ name: 'fn0' }`, the v2 normalizer resolves
`fn0` as a lexical, and the runtime ends up calling the imported helper
instead of the shadowing `const fn`. Hence the failure.
## Fix
Assign `options.locals` to `template.blockParams` by reference, matching
main's behavior. The post-plugin "refresh" block is no longer needed
since the live proxy already tracks mutations.
Also drops the `parts` accessor cast-free behavior: the narrowing uses
`if (options.locals)` rather than a length check, so an empty array
still points at the proxy and picks up later mutations.
## Verified
- Smoke tests: `{{fn}}/{{on}} as keyword (but it is shadowed)` — pass in
all 3 classic-basics scenarios (19/19 tests, 0 fail × 3).
- Browser test suite: 9138/9138 pass.
- Lint, prettier, build:types, type-check:internals — all clean.
- `auto-import-builtins.ts` unchanged from main.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>1 parent 571a94d commit 9d4544e
3 files changed
Lines changed: 31 additions & 35 deletions
File tree
- packages
- @ember/template-compiler/lib/plugins
- @glimmer/syntax/lib/parser
Lines changed: 1 addition & 18 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | | - | |
71 | | - | |
| 54 | + | |
72 | 55 | | |
73 | 56 | | |
74 | 57 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
5 | | - | |
| 4 | + | |
| 5 | + | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
9 | | - | |
| 8 | + | |
| 9 | + | |
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| |||
Lines changed: 26 additions & 13 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| |||
472 | 473 | | |
473 | 474 | | |
474 | 475 | | |
| 476 | + | |
| 477 | + | |
475 | 478 | | |
476 | 479 | | |
477 | 480 | | |
| |||
485 | 488 | | |
486 | 489 | | |
487 | 490 | | |
| 491 | + | |
488 | 492 | | |
489 | 493 | | |
490 | 494 | | |
| |||
495 | 499 | | |
496 | 500 | | |
497 | 501 | | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
498 | 510 | | |
499 | 511 | | |
500 | 512 | | |
| |||
730 | 742 | | |
731 | 743 | | |
732 | 744 | | |
733 | | - | |
734 | | - | |
735 | | - | |
736 | | - | |
737 | | - | |
738 | | - | |
739 | | - | |
| 745 | + | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
740 | 759 | | |
741 | 760 | | |
742 | 761 | | |
| |||
748 | 767 | | |
749 | 768 | | |
750 | 769 | | |
751 | | - | |
752 | | - | |
753 | | - | |
754 | | - | |
755 | | - | |
756 | | - | |
757 | 770 | | |
758 | 771 | | |
0 commit comments