@barefootjs/jsx 0.31.2 → 0.31.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/jsx-adapter.d.ts.map +1 -1
- package/dist/adapters/template-imports.d.ts +27 -15
- package/dist/adapters/template-imports.d.ts.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/debug.d.ts.map +1 -1
- package/dist/identifier-pattern.d.ts +62 -0
- package/dist/identifier-pattern.d.ts.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +280 -119
- package/dist/ir-to-client-js/collect-elements.d.ts +23 -2
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-event-delegation.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/event-delegation.d.ts.map +1 -1
- package/dist/ir-to-client-js/csr-substitute.d.ts +12 -1
- package/dist/ir-to-client-js/csr-substitute.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts +20 -12
- package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
- package/dist/ir-to-client-js/imports.d.ts.map +1 -1
- package/dist/ir-to-client-js/prop-handling.d.ts +25 -2
- package/dist/ir-to-client-js/prop-handling.d.ts.map +1 -1
- package/dist/ir-to-client-js/reactivity.d.ts +30 -2
- package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
- package/dist/ir-to-client-js/rewrite-props-object.d.ts.map +1 -1
- package/dist/ir-to-client-js/utils.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/module-exports.d.ts.map +1 -1
- package/dist/prop-rewrite.d.ts +1 -1
- package/dist/relocate.d.ts.map +1 -1
- package/dist/scope/binding-scope.d.ts +179 -0
- package/dist/scope/binding-scope.d.ts.map +1 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/binding-scope-preamble-shadowing.test.ts +115 -0
- package/src/__tests__/binding-scope-ratchet.test.ts +194 -0
- package/src/__tests__/binding-scope.test.ts +200 -0
- package/src/__tests__/csr-materialize-loop-preamble-shadow.test.ts +76 -0
- package/src/__tests__/csr-substitute-enclosing-scope.test.ts +77 -0
- package/src/__tests__/identifier-pattern.test.ts +170 -0
- package/src/__tests__/let-type-annotation.test.ts +208 -0
- package/src/__tests__/loop-child-reactive-attr-const-shadow.test.ts +107 -0
- package/src/__tests__/rewrite-dynamic-imports.test.ts +98 -0
- package/src/adapters/jsx-adapter.ts +34 -5
- package/src/adapters/template-imports.ts +93 -0
- package/src/analyzer.ts +20 -3
- package/src/debug.ts +4 -3
- package/src/identifier-pattern.ts +79 -0
- package/src/index.ts +5 -1
- package/src/ir-to-client-js/collect-elements.ts +44 -15
- package/src/ir-to-client-js/control-flow/plan/build-event-delegation.ts +2 -1
- package/src/ir-to-client-js/control-flow/stringify/event-delegation.ts +2 -1
- package/src/ir-to-client-js/csr-substitute.ts +19 -2
- package/src/ir-to-client-js/html-template.ts +37 -31
- package/src/ir-to-client-js/imports.ts +2 -1
- package/src/ir-to-client-js/prop-handling.ts +28 -1
- package/src/ir-to-client-js/reactivity.ts +54 -4
- package/src/ir-to-client-js/rewrite-props-object.ts +2 -1
- package/src/ir-to-client-js/utils.ts +9 -8
- package/src/jsx-to-ir.ts +187 -73
- package/src/module-exports.ts +3 -2
- package/src/prop-rewrite.ts +1 -1
- package/src/relocate.ts +2 -1
- package/src/scope/binding-scope.ts +238 -0
- package/src/types.ts +8 -0
package/src/jsx-to-ir.ts
CHANGED
|
@@ -62,6 +62,8 @@ import { iterateJsTokens, replaceInExprContexts } from './scanner/js-scanner.ts'
|
|
|
62
62
|
import { reconstructAsSegments } from './strip-types.ts'
|
|
63
63
|
import { templatePartsToJsExpr } from './template-parts.ts'
|
|
64
64
|
import { toHTMLAttrName, decodeEntities } from '@barefootjs/shared'
|
|
65
|
+
import { BindingScope } from './scope/binding-scope.ts'
|
|
66
|
+
import { identifierPattern, identifierCallPattern } from './identifier-pattern.ts'
|
|
65
67
|
|
|
66
68
|
// =============================================================================
|
|
67
69
|
// Transform Context
|
|
@@ -102,12 +104,18 @@ interface TransformContext {
|
|
|
102
104
|
* `_p.<local>` (#2524 CSR half: `_p` is always caller-keyed).
|
|
103
105
|
*/
|
|
104
106
|
_destructuredPropAliases?: Map<string, string> | null
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
+
/**
|
|
108
|
+
* The active `.map()`/callback binding stack (#2482 Stage 1a) — replaces
|
|
109
|
+
* the former mutable per-name `Set<string>` mutated in lockstep with
|
|
110
|
+
* `transformMapCall` entry/exit. `enterLoopRow`/`enterCallback` return a
|
|
111
|
+
* NEW `BindingScope`; restoring the saved parent reference on exit is
|
|
112
|
+
* the whole mechanism (no `.delete()` bookkeeping to get wrong).
|
|
113
|
+
*/
|
|
114
|
+
scope: BindingScope
|
|
107
115
|
/**
|
|
108
116
|
* Count of enclosing `.map()` loops (0 = outermost), incremented/
|
|
109
117
|
* decremented in lockstep with entering/leaving `transformMapCall`.
|
|
110
|
-
* Unlike `
|
|
118
|
+
* Unlike `scope` (a binding stack that can gain several bound names for
|
|
111
119
|
* ONE loop level via destructuring), this is a plain per-level
|
|
112
120
|
* counter — the single source of truth `IRLoop.depth` is stamped
|
|
113
121
|
* from, so every adapter's `data-key`/`data-key-N` suffix derives
|
|
@@ -133,8 +141,9 @@ interface TransformContext {
|
|
|
133
141
|
/**
|
|
134
142
|
* Memoized free-refs binding environment. Built lazily by
|
|
135
143
|
* `makeBindingEnv` and reused across every `resolveFreeRefs` call as
|
|
136
|
-
* long as `
|
|
137
|
-
* `
|
|
144
|
+
* long as `scope`'s bound VALUE names are unchanged. Invalidated by
|
|
145
|
+
* serializing `scope.valueBoundNames()` into `_bindingEnvLoopKey` and
|
|
146
|
+
* comparing on read.
|
|
138
147
|
*/
|
|
139
148
|
_bindingEnv?: BindingEnvironment
|
|
140
149
|
_bindingEnvLoopKey?: string
|
|
@@ -525,15 +534,21 @@ function rewriteBarePropRefs(text: string, expr: ts.Node, ctx: TransformContext)
|
|
|
525
534
|
let propNames = getDestructuredPropNames(ctx)
|
|
526
535
|
if (!propNames) return dateLowered === text ? undefined : dateLowered
|
|
527
536
|
// #2222: a name bound as an enclosing loop callback's item/index param
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
//
|
|
531
|
-
//
|
|
532
|
-
//
|
|
533
|
-
//
|
|
534
|
-
//
|
|
535
|
-
|
|
536
|
-
|
|
537
|
+
// (or, during the return-expression transform window, a preamble-
|
|
538
|
+
// declared local — #2482 Stage 1a Commit 2 re-enters `ctx.scope` with
|
|
539
|
+
// the preamble for that window) refers to the loop binding, not the
|
|
540
|
+
// prop, at THIS transform position — maintained by `transformMapCall`
|
|
541
|
+
// as it enters/leaves each callback, so this guard is scope-accurate
|
|
542
|
+
// rather than the coarse whole-component exclusion the SSR adapters use
|
|
543
|
+
// (#2221). This is a SHADOW-GUARD query — every `ScopeBindingSource`
|
|
544
|
+
// qualifies, so it reads all-sources `boundNames()`, not
|
|
545
|
+
// `valueBoundNames()` (see that method's doc comment on
|
|
546
|
+
// `BindingScope` for the two-consumer-classes split). Filter into a
|
|
547
|
+
// fresh set — `getDestructuredPropNames` caches its set on ctx and must
|
|
548
|
+
// not be mutated.
|
|
549
|
+
const shadowingNames = ctx.scope.boundNames()
|
|
550
|
+
if (shadowingNames.size > 0) {
|
|
551
|
+
const filtered = new Set([...propNames].filter(n => !shadowingNames.has(n)))
|
|
537
552
|
if (filtered.size === 0) return dateLowered === text ? undefined : dateLowered
|
|
538
553
|
propNames = filtered
|
|
539
554
|
}
|
|
@@ -662,24 +677,24 @@ function createTransformContext(analyzer: AnalyzerContext): TransformContext {
|
|
|
662
677
|
spreadIdCounter: 0,
|
|
663
678
|
isRoot: true,
|
|
664
679
|
insideComponentChildren: false,
|
|
665
|
-
|
|
680
|
+
scope: BindingScope.EMPTY,
|
|
666
681
|
loopDepth: 0,
|
|
667
682
|
patterns: {
|
|
668
683
|
signals: analyzer.signals.map(s => ({
|
|
669
684
|
getter: s.getter,
|
|
670
|
-
pattern:
|
|
685
|
+
pattern: identifierCallPattern(s.getter),
|
|
671
686
|
})),
|
|
672
687
|
memos: analyzer.memos.map(m => ({
|
|
673
688
|
name: m.name,
|
|
674
|
-
pattern:
|
|
689
|
+
pattern: identifierCallPattern(m.name),
|
|
675
690
|
})),
|
|
676
691
|
props: analyzer.propsParams
|
|
677
692
|
.filter(p => p.name !== 'children')
|
|
678
|
-
.map(p => ({ name: p.name, pattern:
|
|
693
|
+
.map(p => ({ name: p.name, pattern: identifierPattern(p.name) })),
|
|
679
694
|
constants: analyzer.localConstants.map(c => ({
|
|
680
695
|
name: c.name,
|
|
681
696
|
value: c.value,
|
|
682
|
-
pattern:
|
|
697
|
+
pattern: identifierPattern(c.name),
|
|
683
698
|
})),
|
|
684
699
|
},
|
|
685
700
|
getJS(node: ts.Node): string {
|
|
@@ -791,21 +806,27 @@ function generateSpreadSlotId(ctx: TransformContext): string {
|
|
|
791
806
|
/**
|
|
792
807
|
* Build the binding environment for `resolveFreeRefs` from the current
|
|
793
808
|
* transform context. The analyzer's collected bindings (signals, memos,
|
|
794
|
-
* props, locals, imports) plus the active loop
|
|
795
|
-
*
|
|
796
|
-
*
|
|
809
|
+
* props, locals, imports) plus the active loop-bound VALUE names (item/
|
|
810
|
+
* index/destructure — see `BindingScope.valueBoundNames`'s doc comment
|
|
811
|
+
* for why preamble locals are excluded here, #2482 Stage 1a Commit 2)
|
|
812
|
+
* form the resolution frame; the TypeChecker, when available, is
|
|
813
|
+
* forwarded so library getters carrying the Reactive<T> brand are
|
|
814
|
+
* recognised.
|
|
797
815
|
*
|
|
798
816
|
* Memoized on `ctx`: the returned env is identity-stable as long as
|
|
799
|
-
* `
|
|
800
|
-
* binding-table cache in `free-refs.ts` warm across every
|
|
801
|
-
* the same loop scope. `
|
|
802
|
-
* visitor enters / leaves `.map()` callbacks
|
|
803
|
-
*
|
|
817
|
+
* `ctx.scope`'s bound VALUE names are unchanged, which keeps the
|
|
818
|
+
* `WeakMap`-keyed binding-table cache in `free-refs.ts` warm across every
|
|
819
|
+
* expression in the same loop scope. `ctx.scope` is REASSIGNED (never
|
|
820
|
+
* mutated) as the visitor enters / leaves `.map()` callbacks (#2482 Stage
|
|
821
|
+
* 1a), so we serialize its bound names into a key rather than relying on
|
|
822
|
+
* object identity — a sibling loop at the same nesting level would
|
|
823
|
+
* otherwise get a spurious cache miss/hit mismatch across restores.
|
|
804
824
|
*/
|
|
805
825
|
function makeBindingEnv(ctx: TransformContext): BindingEnvironment {
|
|
806
|
-
const
|
|
826
|
+
const boundNames = ctx.scope.valueBoundNames()
|
|
827
|
+
const loopKey = boundNames.size === 0
|
|
807
828
|
? ''
|
|
808
|
-
: Array.from(
|
|
829
|
+
: Array.from(boundNames).sort().join('\0')
|
|
809
830
|
if (ctx._bindingEnv && ctx._bindingEnvLoopKey === loopKey) {
|
|
810
831
|
return ctx._bindingEnv
|
|
811
832
|
}
|
|
@@ -820,9 +841,11 @@ function makeBindingEnv(ctx: TransformContext): BindingEnvironment {
|
|
|
820
841
|
localFunctions: a.localFunctions,
|
|
821
842
|
imports: a.imports,
|
|
822
843
|
ambientGlobals: a.ambientGlobals,
|
|
823
|
-
//
|
|
824
|
-
//
|
|
825
|
-
|
|
844
|
+
// `valueBoundNames()` returns a per-instance set that is never
|
|
845
|
+
// mutated (cached on the immutable `BindingScope`) — a stable
|
|
846
|
+
// snapshot even if `ctx.scope` is later reassigned by an enclosing
|
|
847
|
+
// visitor frame, which swaps the instance rather than mutating it.
|
|
848
|
+
loopParams: boundNames,
|
|
826
849
|
checker: a.checker,
|
|
827
850
|
}
|
|
828
851
|
ctx._bindingEnv = env
|
|
@@ -2169,9 +2192,17 @@ function transformExpressionInner(
|
|
|
2169
2192
|
const reactive = isReactiveExpression(exprText, ctx, expr) || isReactiveOrigin(origin)
|
|
2170
2193
|
// @client expressions always need slotId and are treated as reactive for client-side evaluation
|
|
2171
2194
|
// Expressions inside loops that reference the loop parameter need slotId
|
|
2172
|
-
// so fine-grained effects can target them for per-item signal updates
|
|
2173
|
-
|
|
2174
|
-
|
|
2195
|
+
// so fine-grained effects can target them for per-item signal updates.
|
|
2196
|
+
// REACTIVITY/slotId classifier — value bindings only (item/index/
|
|
2197
|
+
// destructure), NOT preamble locals: those already get their own
|
|
2198
|
+
// dedicated slot/region-patch machinery (#2447), so folding them in
|
|
2199
|
+
// here would double-allocate and (via `hasDynamicContent` reading this
|
|
2200
|
+
// same `reactive`-adjacent signal) move an unrelated row-root slotId
|
|
2201
|
+
// decision — see `BindingScope.valueBoundNames`'s doc comment
|
|
2202
|
+
// (#2482 Stage 1a Commit 2).
|
|
2203
|
+
const scopeValueNames = ctx.scope.valueBoundNames()
|
|
2204
|
+
const refsLoopParam = scopeValueNames.size > 0
|
|
2205
|
+
&& Array.from(scopeValueNames).some(p => identifierPattern(p).test(exprText))
|
|
2175
2206
|
|
|
2176
2207
|
// Compute AST-derived flags. `callsReactive` recognises signal-getter / memo
|
|
2177
2208
|
// calls even inside deeper expressions (e.g., `format(count())`); `hasCalls`
|
|
@@ -2237,7 +2268,11 @@ function transformJsxFunctionCall(
|
|
|
2237
2268
|
const substitutedGetJS = (node: ts.Node) => {
|
|
2238
2269
|
let text = baseGetJS(node)
|
|
2239
2270
|
for (const [paramName, argExpr] of substitutions) {
|
|
2240
|
-
|
|
2271
|
+
// Replacer function, not the string form: `argExpr` is arbitrary
|
|
2272
|
+
// caller-supplied JS text and may itself contain `$` sequences
|
|
2273
|
+
// (`$&`, `$1`, …) that `String.replace`'s string-replacement form
|
|
2274
|
+
// would interpret specially instead of inserting literally (#2592).
|
|
2275
|
+
text = text.replace(identifierPattern(paramName, 'g'), () => argExpr)
|
|
2241
2276
|
}
|
|
2242
2277
|
return text
|
|
2243
2278
|
}
|
|
@@ -2289,7 +2324,11 @@ function transformMultiReturnJsxFunctionCall(
|
|
|
2289
2324
|
const substitutedGetJS = (node: ts.Node) => {
|
|
2290
2325
|
let text = baseGetJS(node)
|
|
2291
2326
|
for (const [paramName, argExpr] of substitutions) {
|
|
2292
|
-
|
|
2327
|
+
// Replacer function, not the string form: `argExpr` is arbitrary
|
|
2328
|
+
// caller-supplied JS text and may itself contain `$` sequences
|
|
2329
|
+
// (`$&`, `$1`, …) that `String.replace`'s string-replacement form
|
|
2330
|
+
// would interpret specially instead of inserting literally (#2592).
|
|
2331
|
+
text = text.replace(identifierPattern(paramName, 'g'), () => argExpr)
|
|
2293
2332
|
}
|
|
2294
2333
|
return text
|
|
2295
2334
|
}
|
|
@@ -4021,8 +4060,13 @@ function transformMapCall(
|
|
|
4021
4060
|
method: 'map' | 'flatMap' = 'map'
|
|
4022
4061
|
): IRLoop | null {
|
|
4023
4062
|
// Capture nesting depth before we register this map's own params.
|
|
4024
|
-
// ctx.
|
|
4025
|
-
|
|
4063
|
+
// ctx.scope is populated by the *outer* map; if any VALUE names (item/
|
|
4064
|
+
// index/destructure) are bound we are inside one. Reads
|
|
4065
|
+
// `valueBoundNames()`, not `boundNames()`, for consistency with the
|
|
4066
|
+
// other structural/reactivity consumers (#2482 Stage 1a Commit 2) —
|
|
4067
|
+
// though a bound outer-loop row always carries at least one value
|
|
4068
|
+
// binding regardless, so this can't actually change the answer.
|
|
4069
|
+
const isNested = ctx.scope.valueBoundNames().size > 0
|
|
4026
4070
|
// Diagnostic count at entry — the structural net at the scalar fallthrough
|
|
4027
4071
|
// de-dups against refusals fired DURING this call (leaf-wiring, DSL gates),
|
|
4028
4072
|
// never against unrelated diagnostics recorded before it.
|
|
@@ -4322,17 +4366,17 @@ function transformMapCall(
|
|
|
4322
4366
|
}
|
|
4323
4367
|
}
|
|
4324
4368
|
|
|
4325
|
-
// Register loop
|
|
4326
|
-
//
|
|
4327
|
-
//
|
|
4328
|
-
// `
|
|
4329
|
-
//
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4369
|
+
// Register loop-bound names so expressions referencing them get slotId,
|
|
4370
|
+
// by entering a new BindingScope frame for this row (#2482 Stage 1a).
|
|
4371
|
+
// For destructured patterns, the frame binds the individual binding
|
|
4372
|
+
// names — `\b${param}\b` never matches a bare name like `cfg` when
|
|
4373
|
+
// `param` is `[, cfg]`, which would otherwise leave reactive-expression
|
|
4374
|
+
// detection silently broken for destructured callbacks.
|
|
4375
|
+
// `savedScope` is restored (not deleted-from) at the matching exit site
|
|
4376
|
+
// below, so a nested loop reusing a param name can never corrupt the
|
|
4377
|
+
// outer scope.
|
|
4378
|
+
const savedScope = ctx.scope
|
|
4379
|
+
ctx.scope = ctx.scope.enterLoopRow({ param, index, paramBindings })
|
|
4336
4380
|
ctx.loopDepth++
|
|
4337
4381
|
|
|
4338
4382
|
// Logical control flow (`cond && <X/>`, `a ?? themeLogo()`) as the map
|
|
@@ -4459,6 +4503,51 @@ function transformMapCall(
|
|
|
4459
4503
|
(s): s is ts.ReturnStatement => ts.isReturnStatement(s) && s.expression != null
|
|
4460
4504
|
)
|
|
4461
4505
|
: undefined
|
|
4506
|
+
|
|
4507
|
+
// #2482 Stage 1a Commit 2 — ordering note: the STRUCTURED preamble
|
|
4508
|
+
// (`MapCallbackPreamble`, built further down by
|
|
4509
|
+
// `preambleFromValueStatements` / `buildPreambleSegments`, whichever
|
|
4510
|
+
// branch below the return-expression shape takes) isn't known until
|
|
4511
|
+
// well AFTER this point. But `transformNode(returnExpr, ctx)` just
|
|
4512
|
+
// below walks the return expression's own child expressions RIGHT
|
|
4513
|
+
// NOW — before that happens — so a preamble-declared local shadowing
|
|
4514
|
+
// an outer const/prop must already be bound in `ctx.scope`, or
|
|
4515
|
+
// `tryResolveTemplateSpanFromConst` / `tryResolveIdentifierAsTemplateLiteral`
|
|
4516
|
+
// / `rewriteBarePropRefs` bake the OUTER value into every row instead
|
|
4517
|
+
// of leaving the row-local unresolved (#2222-family, the const/prop-
|
|
4518
|
+
// shadow bug class). Fix: a lightweight declared-names-only pre-scan
|
|
4519
|
+
// — the exact same "statements before the return" shape
|
|
4520
|
+
// `preambleFromValueStatements`/`buildPreambleSegments` scan below —
|
|
4521
|
+
// re-enters the row's `BindingScope` frame (off `savedScope`, the
|
|
4522
|
+
// pre-loop parent, so this REPLACES the param/index-only frame
|
|
4523
|
+
// rather than stacking a second one) with the preamble included, for
|
|
4524
|
+
// the duration of the return-expression transform. Restored back to
|
|
4525
|
+
// the preamble-less row scope right after that window closes (see
|
|
4526
|
+
// `rowScopeBeforePreamble` below) — everything past this window
|
|
4527
|
+
// (the flatMap fallbacks, and the whole rest of `transformMapCall`
|
|
4528
|
+
// after this `if (ts.isBlock(body))` branch) must keep observing
|
|
4529
|
+
// EXACTLY the old (Commit 1) membership, since slotId-allocation
|
|
4530
|
+
// classifiers there read `ctx.scope.valueBoundNames()`, which never
|
|
4531
|
+
// included preamble names to begin with — only shadow guards need
|
|
4532
|
+
// the widened view, and only for this window.
|
|
4533
|
+
let rowScopeBeforePreamble: BindingScope | null = null
|
|
4534
|
+
if (returnStmt) {
|
|
4535
|
+
const preambleNames = new Set<string>()
|
|
4536
|
+
for (const stmt of body.statements) {
|
|
4537
|
+
if (stmt === returnStmt) break
|
|
4538
|
+
collectPreambleDeclaredNames(stmt, preambleNames)
|
|
4539
|
+
}
|
|
4540
|
+
if (preambleNames.size > 0) {
|
|
4541
|
+
rowScopeBeforePreamble = ctx.scope
|
|
4542
|
+
ctx.scope = savedScope.enterLoopRow({
|
|
4543
|
+
param,
|
|
4544
|
+
index,
|
|
4545
|
+
paramBindings,
|
|
4546
|
+
preamble: { declaredNames: [...preambleNames] },
|
|
4547
|
+
})
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4550
|
+
|
|
4462
4551
|
if (returnStmt && returnStmt.expression) {
|
|
4463
4552
|
let returnExpr = returnStmt.expression
|
|
4464
4553
|
while (ts.isParenthesizedExpression(returnExpr)) {
|
|
@@ -4617,6 +4706,14 @@ function transformMapCall(
|
|
|
4617
4706
|
}
|
|
4618
4707
|
}
|
|
4619
4708
|
|
|
4709
|
+
// Window closed — restore the preamble-less row scope so every
|
|
4710
|
+
// consumer past this point (the flatMap fallbacks immediately
|
|
4711
|
+
// below, and the rest of `transformMapCall` after this branch)
|
|
4712
|
+
// keeps observing EXACTLY the pre-rework membership.
|
|
4713
|
+
if (rowScopeBeforePreamble) {
|
|
4714
|
+
ctx.scope = rowScopeBeforePreamble
|
|
4715
|
+
}
|
|
4716
|
+
|
|
4620
4717
|
// flatMap block body fallback: compile JSX inline when children
|
|
4621
4718
|
// couldn't be extracted via the standard single-return path. A pure
|
|
4622
4719
|
// single-`return <call>` projection is NOT taken here — it lowers to
|
|
@@ -4697,13 +4794,10 @@ function transformMapCall(
|
|
|
4697
4794
|
)
|
|
4698
4795
|
}
|
|
4699
4796
|
|
|
4700
|
-
//
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
ctx.loopParams.delete(param)
|
|
4705
|
-
}
|
|
4706
|
-
if (index) ctx.loopParams.delete(index)
|
|
4797
|
+
// Restore the parent scope — the BindingScope twin of the old
|
|
4798
|
+
// "unregister loop params" delete block, but by reference rather than
|
|
4799
|
+
// by name, so it can never miss an entry the add-site added.
|
|
4800
|
+
ctx.scope = savedScope
|
|
4707
4801
|
ctx.loopDepth--
|
|
4708
4802
|
}
|
|
4709
4803
|
|
|
@@ -6134,10 +6228,14 @@ function tryResolveTemplateSpanFromConst(
|
|
|
6134
6228
|
// ${IDENT}
|
|
6135
6229
|
if (ts.isIdentifier(expr)) {
|
|
6136
6230
|
// #2222-family: inside a loop callback the name may be the loop's
|
|
6137
|
-
// item/index binding shadowing a same-named const —
|
|
6138
|
-
// const would bake the outer value into every row.
|
|
6139
|
-
// the bare-expression path, which sees the loop
|
|
6140
|
-
|
|
6231
|
+
// item/index/preamble binding shadowing a same-named const —
|
|
6232
|
+
// resolving the const would bake the outer value into every row.
|
|
6233
|
+
// Fall back to the bare-expression path, which sees the loop
|
|
6234
|
+
// binding. SHADOW-GUARD query — `isBound` (all sources), not
|
|
6235
|
+
// `valueBoundNames()` — see `BindingScope.valueBoundNames`'s doc
|
|
6236
|
+
// comment for the two-consumer-classes split (#2482 Stage 1a
|
|
6237
|
+
// Commit 2).
|
|
6238
|
+
if (ctx.scope.isBound(expr.text)) return null
|
|
6141
6239
|
const constInfo = findLocalConst(expr.text, ctx.analyzer)
|
|
6142
6240
|
if (!constInfo) return null
|
|
6143
6241
|
const ast = parseConstInitializer(constInfo)
|
|
@@ -6154,7 +6252,7 @@ function tryResolveTemplateSpanFromConst(
|
|
|
6154
6252
|
// Same loop-shadowing guard as the ${IDENT} arm: `tone[k]` inside
|
|
6155
6253
|
// `items.map((tone) => …)` must read the row's `tone`, not a
|
|
6156
6254
|
// same-named module/component record const.
|
|
6157
|
-
if (ctx.
|
|
6255
|
+
if (ctx.scope.isBound(expr.expression.text)) return null
|
|
6158
6256
|
const constInfo = findLocalConst(expr.expression.text, ctx.analyzer)
|
|
6159
6257
|
if (!constInfo) return null
|
|
6160
6258
|
const ast = parseConstInitializer(constInfo)
|
|
@@ -6326,9 +6424,13 @@ function tryResolveIdentifierAsTemplateLiteral(
|
|
|
6326
6424
|
// outer const's literal into the IR here bakes the same hard-coded
|
|
6327
6425
|
// value into EVERY adapter's output (e.g. `key={label}` inside
|
|
6328
6426
|
// `.map((label) => ...)` becoming a constant duplicate key).
|
|
6329
|
-
// `ctx.
|
|
6330
|
-
// names and index included
|
|
6331
|
-
|
|
6427
|
+
// `ctx.scope` is the live loop-binding stack (destructured binding
|
|
6428
|
+
// names and index included — plus, during the return-expression
|
|
6429
|
+
// transform window, the preamble's declared names once
|
|
6430
|
+
// `transformMapCall` re-enters the row scope with them, #2482 Stage 1a
|
|
6431
|
+
// Commit 2), so the guard is scope-accurate. This is a SHADOW-GUARD
|
|
6432
|
+
// query — reads `isBound` (all sources), not `valueBoundNames()`.
|
|
6433
|
+
if (ctx.scope.isBound(ident.text)) return null
|
|
6332
6434
|
const constInfo = findLocalConst(ident.text, ctx.analyzer)
|
|
6333
6435
|
if (!constInfo) return null
|
|
6334
6436
|
const ast = parseConstInitializer(constInfo)
|
|
@@ -7002,11 +7104,16 @@ function isSignalOrMemoArray(array: string, ctx: TransformContext): boolean {
|
|
|
7002
7104
|
* Used by conditional transforms to assign slotId for per-item signal reactivity.
|
|
7003
7105
|
* NOT added to isReactiveExpression to avoid promoting text expressions
|
|
7004
7106
|
* like {item.name} to reactive (they use a separate slotId path).
|
|
7107
|
+
*
|
|
7108
|
+
* REACTIVITY/slotId classifier — reads `valueBoundNames()` (item/index/
|
|
7109
|
+
* destructure only), NOT preamble locals; see `BindingScope.valueBoundNames`'s
|
|
7110
|
+
* doc comment (#2482 Stage 1a Commit 2).
|
|
7005
7111
|
*/
|
|
7006
7112
|
function referencesLoopParam(expr: string, ctx: TransformContext): boolean {
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7113
|
+
const boundNames = ctx.scope.valueBoundNames()
|
|
7114
|
+
if (boundNames.size === 0) return false
|
|
7115
|
+
for (const p of boundNames) {
|
|
7116
|
+
if (identifierPattern(p).test(expr)) return true
|
|
7010
7117
|
}
|
|
7011
7118
|
return false
|
|
7012
7119
|
}
|
|
@@ -7129,11 +7236,18 @@ function hasReactiveAttributes(attrs: IRAttribute[], ctx: TransformContext): boo
|
|
|
7129
7236
|
if (isSignalOrMemoReference(valueToCheck, ctx) || isPropsReference(valueToCheck, ctx)) {
|
|
7130
7237
|
return true
|
|
7131
7238
|
}
|
|
7132
|
-
// Check if attribute references any active loop
|
|
7133
|
-
// loop root elements need a slotId so className can be updated
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7239
|
+
// Check if attribute references any active loop-bound VALUE names —
|
|
7240
|
+
// loop root elements need a slotId so className can be updated
|
|
7241
|
+
// reactively. REACTIVITY/slotId classifier — value bindings only
|
|
7242
|
+
// (item/index/destructure), not preamble locals; see
|
|
7243
|
+
// `BindingScope.valueBoundNames`'s doc comment (#2482 Stage 1a
|
|
7244
|
+
// Commit 2) — this is the exact check whose over-widening flipped
|
|
7245
|
+
// the `tag-cloud`/`preamble-cells` conformance fixtures before the
|
|
7246
|
+
// source-filtered query existed.
|
|
7247
|
+
const scopeValueNames = ctx.scope.valueBoundNames()
|
|
7248
|
+
if (scopeValueNames.size > 0) {
|
|
7249
|
+
for (const p of scopeValueNames) {
|
|
7250
|
+
if (identifierPattern(p).test(valueToCheck)) return true
|
|
7137
7251
|
}
|
|
7138
7252
|
}
|
|
7139
7253
|
}
|
package/src/module-exports.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { ComponentIR, ParamInfo } from './types.ts'
|
|
9
|
+
import { identifierPattern } from './identifier-pattern.ts'
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Emit module-level exports for local declarations and `export { ... } [from '...']`
|
|
@@ -140,7 +141,7 @@ export function findReachableNames(
|
|
|
140
141
|
const queue: string[] = []
|
|
141
142
|
|
|
142
143
|
for (const name of allNames) {
|
|
143
|
-
if (
|
|
144
|
+
if (identifierPattern(name).test(primaryRefs)) {
|
|
144
145
|
reachable.add(name)
|
|
145
146
|
queue.push(name)
|
|
146
147
|
}
|
|
@@ -150,7 +151,7 @@ export function findReachableNames(
|
|
|
150
151
|
const current = queue.shift()!
|
|
151
152
|
const body = bodyMap.get(current) || ''
|
|
152
153
|
for (const name of allNames) {
|
|
153
|
-
if (!reachable.has(name) &&
|
|
154
|
+
if (!reachable.has(name) && identifierPattern(name).test(body)) {
|
|
154
155
|
reachable.add(name)
|
|
155
156
|
queue.push(name)
|
|
156
157
|
}
|
package/src/prop-rewrite.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* stack, so `items.map((title) => title.a)` never turns into the
|
|
15
15
|
* syntactically invalid `.map((_p.title) => _p.title.a)` when `title`
|
|
16
16
|
* is also a prop. (Names bound by loop callbacks that ENCLOSE the
|
|
17
|
-
* expression are the caller's job — see the `ctx.
|
|
17
|
+
* expression are the caller's job — see the `ctx.scope` filter in
|
|
18
18
|
* `jsx-to-ir.ts`'s `rewriteBarePropRefs` wrapper, #2222.)
|
|
19
19
|
*/
|
|
20
20
|
|
package/src/relocate.ts
CHANGED
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
} from './adapters/interface.ts'
|
|
23
23
|
import { tsNodeToParsedExpr, type ParsedExpr } from './expression-parser.ts'
|
|
24
24
|
import { prepareLoweringMatchers, type LoweringMatcher } from './lowering-registry.ts'
|
|
25
|
+
import { identifierPattern } from './identifier-pattern.ts'
|
|
25
26
|
|
|
26
27
|
export interface RelocateEnv {
|
|
27
28
|
/**
|
|
@@ -713,7 +714,7 @@ function scanRefsByName(
|
|
|
713
714
|
): Map<string, ts.Identifier[]> {
|
|
714
715
|
const result = new Map<string, ts.Identifier[]>()
|
|
715
716
|
for (const name of bindings.keys()) {
|
|
716
|
-
const re =
|
|
717
|
+
const re = identifierPattern(name)
|
|
717
718
|
if (re.test(text)) result.set(name, [])
|
|
718
719
|
}
|
|
719
720
|
return result
|