@barefootjs/go-template 0.31.4 → 0.31.5
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/adapter/go-template-adapter.d.ts +52 -8
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +30 -36
- package/dist/adapter/lib/compile-state.d.ts +6 -2
- package/dist/adapter/lib/compile-state.d.ts.map +1 -1
- package/dist/index.js +30 -36
- package/dist/vite.js +150 -111
- package/package.json +5 -5
- package/src/adapter/go-template-adapter.ts +127 -63
- package/src/adapter/lib/compile-state.ts +6 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/go-template",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.5",
|
|
4
4
|
"description": "Go html/template adapter for BarefootJS - generates Go template files from IR",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"directory": "packages/adapter-go-template"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@barefootjs/shared": "0.31.
|
|
52
|
+
"@barefootjs/shared": "0.31.5"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@barefootjs/jsx": ">=0.2.0",
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@barefootjs/adapter-tests": "0.1.0",
|
|
70
|
-
"@barefootjs/client": "0.31.
|
|
71
|
-
"@barefootjs/jsx": "0.31.
|
|
72
|
-
"@barefootjs/vite": "0.31.
|
|
70
|
+
"@barefootjs/client": "0.31.5",
|
|
71
|
+
"@barefootjs/jsx": "0.31.5",
|
|
72
|
+
"@barefootjs/vite": "0.31.5",
|
|
73
73
|
"vite": "^6.0.0"
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -32,6 +32,7 @@ import type {
|
|
|
32
32
|
IRMetadata,
|
|
33
33
|
TemplatePrimitiveRegistry,
|
|
34
34
|
LoopBindingPathSegment,
|
|
35
|
+
LoopBindingSource,
|
|
35
36
|
} from '@barefootjs/jsx'
|
|
36
37
|
import {
|
|
37
38
|
BaseAdapter,
|
|
@@ -74,6 +75,7 @@ import {
|
|
|
74
75
|
resolveStaticLoopSource,
|
|
75
76
|
collectLoopBoundNames,
|
|
76
77
|
evaluateStaticLiteral,
|
|
78
|
+
BindingScope,
|
|
77
79
|
} from '@barefootjs/jsx'
|
|
78
80
|
import { findInterpolationEnd } from '@barefootjs/jsx/scanner'
|
|
79
81
|
import { BF_REGION, escapeHtml } from '@barefootjs/shared'
|
|
@@ -263,7 +265,37 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
263
265
|
* populated it, not correctness across passes.
|
|
264
266
|
*/
|
|
265
267
|
private bakedStaticChildLoopCache = new Map<string, BakedStaticChildLoop | null>()
|
|
266
|
-
|
|
268
|
+
/**
|
|
269
|
+
* The one threaded, immutable scope of "names bound by an enclosing loop
|
|
270
|
+
* callback" (#2482 Stage 3 — replaces this adapter's own order-sensitive
|
|
271
|
+
* loop-param stack). Entered via `enterLoopRow(loop)` in `renderLoop` /
|
|
272
|
+
* `renderUnrolledStaticElementLoop`, bracketing preamble conversion,
|
|
273
|
+
* children rendering, AND the key-anchor (`loopItemMarker`) conversion —
|
|
274
|
+
* mirrors the Stage 1a/1b `ctx.scope` precedent in `jsx-to-ir.ts` and the
|
|
275
|
+
* Stage 2 template-string-adapter migration. `IRLoop` already structurally
|
|
276
|
+
* satisfies `LoopBindingSource` (`param`/`index`/`paramBindings`/
|
|
277
|
+
* `preamble`), so most call sites pass the loop node straight through.
|
|
278
|
+
*
|
|
279
|
+
* ONE Go-specific wrinkle `BindingScope`'s generic 'item' semantics don't
|
|
280
|
+
* know about: a `.keys()`-shape loop's callback param is the RANGE INDEX,
|
|
281
|
+
* not the range value — Go's `{{range}}` dot context there is the
|
|
282
|
+
* (discarded) value, not the key, so the key must never win the
|
|
283
|
+
* depth-0-item "resolves to `.`" fast path `isCurrentLoopItem` grants a
|
|
284
|
+
* plain `.map()` param. `renderLoop` enters such a loop's scope with an
|
|
285
|
+
* overridden EMPTY `param`, mirroring the historical empty-string push
|
|
286
|
+
* onto the old stack for the same shape — the key name stays
|
|
287
|
+
* resolvable only through `loopVarRefCount`'s existing `$name` machinery,
|
|
288
|
+
* unchanged by this migration.
|
|
289
|
+
*
|
|
290
|
+
* Destructured-binding ACCESSOR TEXT (`id` → `$__bf_item0.Id`) is Go-
|
|
291
|
+
* specific rendering payload `ScopeBinding` has no field for (by design —
|
|
292
|
+
* `BindingScope` only carries EXISTENCE/kind, not per-adapter accessor
|
|
293
|
+
* strings), so `loopBindingStack` stays as the accessor source of truth,
|
|
294
|
+
* pushed/popped in lockstep with `scope` at the same points. `scope`
|
|
295
|
+
* subsumes its EXISTENCE role (shadow-guard / dot-vs-`$name` decisions)
|
|
296
|
+
* everywhere except the accessor lookup itself.
|
|
297
|
+
*/
|
|
298
|
+
private scope: BindingScope = BindingScope.EMPTY
|
|
267
299
|
/**
|
|
268
300
|
* Stack of `IRLoop.depth` values (innermost last), pushed/popped around
|
|
269
301
|
* `renderChildren(loop.children)` in `renderLoop`. `renderAttributes`
|
|
@@ -450,6 +482,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
450
482
|
this.state.referencedDerivedConsts = new Set()
|
|
451
483
|
this.state.templateVarCounter = 0
|
|
452
484
|
this.state.pendingChildrenDefines = []
|
|
485
|
+
this.scope = BindingScope.EMPTY
|
|
453
486
|
this.primeCompileState(ir)
|
|
454
487
|
this.state.stringValueNames = collectStringValueNames(ir)
|
|
455
488
|
// #2448: self-register this component's derived-memo dependencies, so a
|
|
@@ -3757,8 +3790,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
3757
3790
|
// field that never exists. Mirrors the string-const inlining above.
|
|
3758
3791
|
const inlinedNum = this.resolveModuleNumericConst(name)
|
|
3759
3792
|
if (inlinedNum !== null) return inlinedNum
|
|
3760
|
-
|
|
3761
|
-
if (currentLoopParam && name === currentLoopParam) return '.'
|
|
3793
|
+
if (this.isCurrentLoopItem(name)) return '.'
|
|
3762
3794
|
// An *outer* loop's value variable (we're in a nested loop) is in scope as
|
|
3763
3795
|
// the Go range variable `$name` declared by that loop's `{{range … := …}}`;
|
|
3764
3796
|
// the inner dot no longer refers to it, and it's not a root field.
|
|
@@ -3858,18 +3890,31 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
3858
3890
|
return false
|
|
3859
3891
|
}
|
|
3860
3892
|
|
|
3893
|
+
/**
|
|
3894
|
+
* True when `name` is bound as the CURRENT (innermost) loop row's own
|
|
3895
|
+
* plain `.map()` item param — i.e. it resolves to Go template's dot
|
|
3896
|
+
* context (`.`), not a `$name` range variable or a root field. `depth 0`
|
|
3897
|
+
* is `scope`'s innermost frame; `source === 'item'` excludes a
|
|
3898
|
+
* destructured binding (source `'destructure'`, resolved through
|
|
3899
|
+
* `loopBindingStack` instead — see `scope`'s field docstring) and a
|
|
3900
|
+
* `.keys()`-shape loop's key param (never bound as `'item'` at all —
|
|
3901
|
+
* `renderLoop` enters that shape's scope with an overridden empty param).
|
|
3902
|
+
*/
|
|
3903
|
+
private isCurrentLoopItem(name: string): boolean {
|
|
3904
|
+
const hit = this.scope.lookup(name)
|
|
3905
|
+
return hit !== null && hit.depth === 0 && hit.binding.source === 'item'
|
|
3906
|
+
}
|
|
3907
|
+
|
|
3861
3908
|
/**
|
|
3862
3909
|
* True when `name` is a loop value variable from an enclosing (not the
|
|
3863
|
-
* current) loop — i.e. it
|
|
3864
|
-
*
|
|
3865
|
-
* the
|
|
3910
|
+
* current) loop — i.e. it's bound in `scope` as an OUTER frame's own
|
|
3911
|
+
* plain item param (`depth > 0`, source `'item'`). Such a reference
|
|
3912
|
+
* resolves to the Go range variable `$name`, not the inner dot or the
|
|
3913
|
+
* root data.
|
|
3866
3914
|
*/
|
|
3867
3915
|
private isOuterLoopParam(name: string): boolean {
|
|
3868
|
-
const
|
|
3869
|
-
|
|
3870
|
-
if (this.loopParamStack[i] === name) return true
|
|
3871
|
-
}
|
|
3872
|
-
return false
|
|
3916
|
+
const hit = this.scope.lookup(name)
|
|
3917
|
+
return hit !== null && hit.depth > 0 && hit.binding.source === 'item'
|
|
3873
3918
|
}
|
|
3874
3919
|
|
|
3875
3920
|
/**
|
|
@@ -3880,7 +3925,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
3880
3925
|
* rebinds. Outside any loop the root *is* the dot, so we emit `.Field`.
|
|
3881
3926
|
*/
|
|
3882
3927
|
private rootFieldRef(name: string): string {
|
|
3883
|
-
const prefix = this.
|
|
3928
|
+
const prefix = this.inLoop ? '$.' : '.'
|
|
3884
3929
|
return `${prefix}${capitalizeFieldName(name)}`
|
|
3885
3930
|
}
|
|
3886
3931
|
|
|
@@ -3922,9 +3967,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
3922
3967
|
* auto-escaping.
|
|
3923
3968
|
*/
|
|
3924
3969
|
private resolveModuleStringConst(name: string): string | null {
|
|
3925
|
-
if (this.
|
|
3926
|
-
return null
|
|
3927
|
-
}
|
|
3970
|
+
if (this.isCurrentLoopItem(name)) return null
|
|
3928
3971
|
if (this.loopVarRefCount.has(name)) return null
|
|
3929
3972
|
if (this.isOuterLoopParam(name)) return null
|
|
3930
3973
|
const value = this.state.moduleStringConsts.get(name)
|
|
@@ -3940,9 +3983,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
3940
3983
|
* range variable that shadows a const name still wins.
|
|
3941
3984
|
*/
|
|
3942
3985
|
private resolveModuleNumericConst(name: string): string | null {
|
|
3943
|
-
if (this.
|
|
3944
|
-
return null
|
|
3945
|
-
}
|
|
3986
|
+
if (this.isCurrentLoopItem(name)) return null
|
|
3946
3987
|
if (this.loopVarRefCount.has(name)) return null
|
|
3947
3988
|
if (this.isOuterLoopParam(name)) return null
|
|
3948
3989
|
const c = this.state.localConstants.find(
|
|
@@ -4033,7 +4074,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
4033
4074
|
const slice = this.state.memoBackedLoopSlice.get(object.callee.name)
|
|
4034
4075
|
if (slice) {
|
|
4035
4076
|
// Root field, so reach it through `$.` inside a loop.
|
|
4036
|
-
const prefix = this.
|
|
4077
|
+
const prefix = this.inLoop ? '$.' : '.'
|
|
4037
4078
|
return `len ${prefix}${slice}`
|
|
4038
4079
|
}
|
|
4039
4080
|
}
|
|
@@ -4094,8 +4135,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
4094
4135
|
// template syntax, let alone a reachable field (PR #2089 review). For
|
|
4095
4136
|
// identifier keys (snake_case included) the two functions agree, so
|
|
4096
4137
|
// nothing previously reachable changes shape.
|
|
4097
|
-
|
|
4098
|
-
if (object.kind === 'identifier' && currentLoopParam && object.name === currentLoopParam) {
|
|
4138
|
+
if (object.kind === 'identifier' && this.isCurrentLoopItem(object.name)) {
|
|
4099
4139
|
return `.${goFieldNameForKey(property)}`
|
|
4100
4140
|
}
|
|
4101
4141
|
|
|
@@ -5466,22 +5506,18 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
5466
5506
|
|
|
5467
5507
|
/**
|
|
5468
5508
|
* Whether `name` at the CURRENT emission position is bound by an enclosing
|
|
5469
|
-
* loop callback — its item param
|
|
5470
|
-
*
|
|
5471
|
-
* (`
|
|
5472
|
-
*
|
|
5509
|
+
* loop callback — its item param, an outer loop's range variable, a
|
|
5510
|
+
* destructured binding name, an index var, or a preamble local. `scope`
|
|
5511
|
+
* (`BindingScope`, #2482 Stage 3) covers all of those EXCEPT a
|
|
5512
|
+
* `.keys()`-shape loop's own key param (never bound in `scope` at all —
|
|
5513
|
+
* see `scope`'s field docstring), which stays reachable only through
|
|
5514
|
+
* `loopVarRefCount` — so both are still consulted. Shared by the
|
|
5473
5515
|
* string-keyed fast paths (#2236, #2242 Copilot review) that resolve raw
|
|
5474
5516
|
* `jsExpr` text before `identifier()`'s own guards can see it. Mirrors the
|
|
5475
5517
|
* checks in `resolveModuleStringConst` / `resolveModuleNumericConst`.
|
|
5476
5518
|
*/
|
|
5477
5519
|
private isLoopShadowedName(name: string): boolean {
|
|
5478
|
-
return (
|
|
5479
|
-
(this.loopParamStack.length > 0 &&
|
|
5480
|
-
this.loopParamStack[this.loopParamStack.length - 1] === name) ||
|
|
5481
|
-
this.loopVarRefCount.has(name) ||
|
|
5482
|
-
this.isOuterLoopParam(name) ||
|
|
5483
|
-
this.loopBindingStack.some(bindings => bindings.has(name))
|
|
5484
|
-
)
|
|
5520
|
+
return this.scope.isBound(name) || this.loopVarRefCount.has(name)
|
|
5485
5521
|
}
|
|
5486
5522
|
|
|
5487
5523
|
/**
|
|
@@ -5871,8 +5907,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
5871
5907
|
}
|
|
5872
5908
|
const inlined = this.resolveModuleStringConst(expr.name)
|
|
5873
5909
|
if (inlined !== null) return plain(inlined)
|
|
5874
|
-
|
|
5875
|
-
if (currentLoopParam && expr.name === currentLoopParam) {
|
|
5910
|
+
if (this.isCurrentLoopItem(expr.name)) {
|
|
5876
5911
|
return plain('.')
|
|
5877
5912
|
}
|
|
5878
5913
|
// Outer loop value variable (nested loop) → its range var `$name`.
|
|
@@ -5971,11 +6006,8 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
5971
6006
|
return plain(this.rootFieldRef(expr.property))
|
|
5972
6007
|
}
|
|
5973
6008
|
|
|
5974
|
-
{
|
|
5975
|
-
|
|
5976
|
-
if (expr.object.kind === 'identifier' && currentLoopParam && expr.object.name === currentLoopParam) {
|
|
5977
|
-
return plain(`.${capitalizeFieldName(expr.property)}`)
|
|
5978
|
-
}
|
|
6009
|
+
if (expr.object.kind === 'identifier' && this.isCurrentLoopItem(expr.object.name)) {
|
|
6010
|
+
return plain(`.${capitalizeFieldName(expr.property)}`)
|
|
5979
6011
|
}
|
|
5980
6012
|
|
|
5981
6013
|
const obj = this.renderConditionExpr(expr.object)
|
|
@@ -6241,6 +6273,14 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6241
6273
|
if (this.bakedStaticChildLoopCache.has(markerId)) {
|
|
6242
6274
|
return this.bakedStaticChildLoopCache.get(markerId) ?? null
|
|
6243
6275
|
}
|
|
6276
|
+
// #2482 Stage 3: this method is called from TWO sites outside the live
|
|
6277
|
+
// `renderLoop` tree walk (`generateNewPropsFunction`'s Input-struct
|
|
6278
|
+
// field list and constructor-generation pass, per the docstring above)
|
|
6279
|
+
// where there is no live `this.scope` to consult — so, unlike
|
|
6280
|
+
// `renderLoop`'s own `analyzeBakeableStaticElementLoop` call, this stays
|
|
6281
|
+
// on the coarse whole-component shadow-name Set `primeCompileState`
|
|
6282
|
+
// populates. A genuinely-legitimate surviving use of the pre-#2482
|
|
6283
|
+
// device (flagged for Stage 4 rather than migrated here).
|
|
6244
6284
|
const result = analyzeBakeableStaticChildLoop(
|
|
6245
6285
|
{ props: childComponent.props, loopArrayParsed: arrayParsed, loopParam: param, loopKey: key },
|
|
6246
6286
|
this.state.localConstants,
|
|
@@ -6337,19 +6377,33 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6337
6377
|
// exact (conservative) acceptance gate. `null` here means the shape
|
|
6338
6378
|
// isn't (yet) bakeable this way; the existing gates below keep firing
|
|
6339
6379
|
// exactly as before.
|
|
6380
|
+
// #2482 Stage 3: `renderLoop` is a live tree-walk, so `this.scope` (not
|
|
6381
|
+
// yet entered for THIS loop's own row — that happens further below)
|
|
6382
|
+
// gives the position-accurate ancestor-shadowing answer directly, unlike
|
|
6383
|
+
// `getBakedStaticChildLoop` above, which is memoized across sites that
|
|
6384
|
+
// run OUTSIDE the tree walk and so must keep its coarse whole-component
|
|
6385
|
+
// shadow-name Set (see that method's own comment).
|
|
6340
6386
|
const bakedElementLoop = loop.childComponent
|
|
6341
6387
|
? null
|
|
6342
6388
|
: analyzeBakeableStaticElementLoop(
|
|
6343
6389
|
loop,
|
|
6344
6390
|
this.state.localConstants,
|
|
6345
|
-
{ isNameShadowed:
|
|
6391
|
+
{ isNameShadowed: this.scope.asShadowPredicate() },
|
|
6346
6392
|
)
|
|
6347
6393
|
if (bakedElementLoop) {
|
|
6348
6394
|
return this.renderUnrolledStaticElementLoop(loop, bakedElementLoop.items)
|
|
6349
6395
|
}
|
|
6350
6396
|
|
|
6351
6397
|
const arrayName = loop.array.trim()
|
|
6352
|
-
|
|
6398
|
+
// #2482 Stage 4: guard against an ENCLOSING loop's own item param
|
|
6399
|
+
// shadowing a same-named module/component const (`items.map(items =>
|
|
6400
|
+
// items.map(...))`) — without this, a bare-identifier array reference
|
|
6401
|
+
// that resolves to the outer loop's per-row value could misresolve to
|
|
6402
|
+
// the shadowed const below and raise a false BF101. `this.scope` here
|
|
6403
|
+
// is the position-accurate ancestor scope (see the comment above
|
|
6404
|
+
// `bakedElementLoop`) — same shadow-guard shape as
|
|
6405
|
+
// `prop-handling.ts`'s `expandDynamicPropValue`.
|
|
6406
|
+
if (bakedChildLoop === null && /^[A-Za-z_$][\w$]*$/.test(arrayName) && !this.scope.isBound(arrayName)) {
|
|
6353
6407
|
const arrayConst = this.state.localConstants.find(c => c.name === arrayName)
|
|
6354
6408
|
if (arrayConst && !arrayConst.isModule && arrayConst.parsed && !this.isStringExpr(arrayConst.parsed, new Set())) {
|
|
6355
6409
|
this.state.errors.push({
|
|
@@ -6423,15 +6477,26 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6423
6477
|
// of the row-scoped field) (#2487).
|
|
6424
6478
|
const wasInLoopOuter = this.inLoop
|
|
6425
6479
|
this.inLoop = true
|
|
6426
|
-
//
|
|
6427
|
-
//
|
|
6428
|
-
//
|
|
6429
|
-
//
|
|
6430
|
-
//
|
|
6431
|
-
//
|
|
6432
|
-
//
|
|
6433
|
-
//
|
|
6434
|
-
//
|
|
6480
|
+
// Enter this loop's row scope (#2482 Stage 3). `IRLoop` already
|
|
6481
|
+
// structurally satisfies `LoopBindingSource`, so the plain/destructured
|
|
6482
|
+
// cases pass `loop` straight through — `enterLoopRow` binds
|
|
6483
|
+
// `paramBindings` (destructure) OR `param` (plain item) automatically.
|
|
6484
|
+
// `.keys()` is the one shape `BindingScope`'s generic 'item' semantics
|
|
6485
|
+
// don't fit: the callback param there IS the index, not the row value
|
|
6486
|
+
// (the dot context is the discarded value), so it must never win
|
|
6487
|
+
// `isCurrentLoopItem`'s dot-shortcut — enter with an overridden empty
|
|
6488
|
+
// `param`, mirroring the historical empty-string push onto the old
|
|
6489
|
+
// loop-param stack for the same shape. The key stays reachable only
|
|
6490
|
+
// via `loopVarRefCount`'s `$name` bookkeeping below, unchanged by this
|
|
6491
|
+
// migration.
|
|
6492
|
+
const scopeLoop: LoopBindingSource = loop.iterationShape === 'keys' ? { ...loop, param: '' } : loop
|
|
6493
|
+
const prevScope = this.scope
|
|
6494
|
+
this.scope = prevScope.enterLoopRow(scopeLoop)
|
|
6495
|
+
// Track Go template loop variables. The range *index* variable needs
|
|
6496
|
+
// `$name` notation and goes on `loopVarRefCount` (the range *value*
|
|
6497
|
+
// variable's dot-context resolution now comes from `scope` above).
|
|
6498
|
+
// Ref-counting (not a flat Set) keeps nested loops with the same index
|
|
6499
|
+
// var name from clobbering the outer entry on cleanup.
|
|
6435
6500
|
const addedLoopVars: string[] = []
|
|
6436
6501
|
// A `.map()` callback preamble lowers to one `{{$cls := …}}` per-row
|
|
6437
6502
|
// variable per declaration (#2447). Registering the names on
|
|
@@ -6446,23 +6511,19 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6446
6511
|
}
|
|
6447
6512
|
let pushedBindingMap = false
|
|
6448
6513
|
if (supportableDestructure) {
|
|
6449
|
-
// Bindings resolve against the synthetic `$__bf_item` range var
|
|
6450
|
-
// a loop param (the param is a pattern, not a name).
|
|
6514
|
+
// Bindings resolve against the synthetic `$__bf_item` range var.
|
|
6451
6515
|
const built = this.buildDestructureBindingMap(loop, rangeValue)
|
|
6452
6516
|
this.loopBindingStack.push(built.bindings)
|
|
6453
6517
|
this.loopRestExcludeStack.push(built.restExcludes)
|
|
6454
6518
|
pushedBindingMap = true
|
|
6455
|
-
this.loopParamStack.push('')
|
|
6456
6519
|
if (rangeIndex !== '_') {
|
|
6457
6520
|
this.loopVarRefCount.set(rangeIndex, (this.loopVarRefCount.get(rangeIndex) ?? 0) + 1)
|
|
6458
6521
|
addedLoopVars.push(rangeIndex)
|
|
6459
6522
|
}
|
|
6460
6523
|
} else if (loop.iterationShape === 'keys') {
|
|
6461
|
-
this.loopParamStack.push('')
|
|
6462
6524
|
this.loopVarRefCount.set(param, (this.loopVarRefCount.get(param) ?? 0) + 1)
|
|
6463
6525
|
addedLoopVars.push(param)
|
|
6464
6526
|
} else {
|
|
6465
|
-
this.loopParamStack.push(param)
|
|
6466
6527
|
if (rangeIndex !== '_') {
|
|
6467
6528
|
this.loopVarRefCount.set(rangeIndex, (this.loopVarRefCount.get(rangeIndex) ?? 0) + 1)
|
|
6468
6529
|
addedLoopVars.push(rangeIndex)
|
|
@@ -6487,9 +6548,9 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6487
6548
|
this.loopKeyDepthStack.pop()
|
|
6488
6549
|
this.loopWrapperStack.pop()
|
|
6489
6550
|
this.loopScalarItemStack.pop()
|
|
6490
|
-
// Build the per-item anchor marker while the
|
|
6491
|
-
//
|
|
6492
|
-
// range item (`.` context) like `data-key` does —
|
|
6551
|
+
// Build the per-item anchor marker while the row scope is still active,
|
|
6552
|
+
// so a `bodyIsItemConditional` key expression resolves against the
|
|
6553
|
+
// range item (`.` context) like `data-key` does — restoring first would
|
|
6493
6554
|
// rewrite `t.id` to `.T.ID` instead of `.ID`.
|
|
6494
6555
|
const itemMarker = this.loopItemMarker(loop)
|
|
6495
6556
|
for (const v of addedLoopVars) {
|
|
@@ -6497,7 +6558,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6497
6558
|
if (rc <= 0) this.loopVarRefCount.delete(v)
|
|
6498
6559
|
else this.loopVarRefCount.set(v, rc)
|
|
6499
6560
|
}
|
|
6500
|
-
this.
|
|
6561
|
+
this.scope = prevScope
|
|
6501
6562
|
if (pushedBindingMap) {
|
|
6502
6563
|
this.loopBindingStack.pop()
|
|
6503
6564
|
this.loopRestExcludeStack.pop()
|
|
@@ -6578,7 +6639,11 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6578
6639
|
this.loopWrapperStack.push(false)
|
|
6579
6640
|
this.loopKeyDepthStack.push(loop.depth)
|
|
6580
6641
|
this.loopScalarItemStack.push(this.scalarLiteralLoopGoType(loop.arrayParsed, loop.itemType) !== null)
|
|
6581
|
-
|
|
6642
|
+
// The bake gate (`analyzeBakeableStaticElementLoop`) already refused a
|
|
6643
|
+
// destructured or `.keys()`-shape param, so `loop` binds via `enterLoopRow`
|
|
6644
|
+
// unadjusted — always a plain item (#2482 Stage 3).
|
|
6645
|
+
const prevScope = this.scope
|
|
6646
|
+
this.scope = prevScope.enterLoopRow(loop)
|
|
6582
6647
|
|
|
6583
6648
|
let body = ''
|
|
6584
6649
|
for (const item of items) {
|
|
@@ -6603,7 +6668,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6603
6668
|
}
|
|
6604
6669
|
}
|
|
6605
6670
|
|
|
6606
|
-
this.
|
|
6671
|
+
this.scope = prevScope
|
|
6607
6672
|
this.loopScalarItemStack.pop()
|
|
6608
6673
|
this.loopKeyDepthStack.pop()
|
|
6609
6674
|
this.loopWrapperStack.pop()
|
|
@@ -6947,8 +7012,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6947
7012
|
// (#2087), which lifted the flat-object-only restriction for the
|
|
6948
7013
|
// destructure-residual fixtures below.
|
|
6949
7014
|
const trimmed = value.expr.trim()
|
|
6950
|
-
|
|
6951
|
-
if (currentLoopParam && trimmed === currentLoopParam) {
|
|
7015
|
+
if (this.isCurrentLoopItem(trimmed)) {
|
|
6952
7016
|
// The row's keys are Go-cased by the field-access contract: the
|
|
6953
7017
|
// same lowering that makes `attrs.id` emit `{{.ID}}`
|
|
6954
7018
|
// (`goFieldNameForKey` → `capitalizeFieldName`) requires the
|
|
@@ -6,8 +6,12 @@
|
|
|
6
6
|
*
|
|
7
7
|
* NOT included (deliberately): cross-compile child-shape registries
|
|
8
8
|
* (`childComponentShapes`, `childContextConsumers`, populated before a parent
|
|
9
|
-
* compiles), the render-recursion cursor
|
|
10
|
-
* `filterExprDepth`, …), and constant config (`options`,
|
|
9
|
+
* compiles), the render-recursion cursor state (`scope: BindingScope`,
|
|
10
|
+
* `loopBindingStack`, `filterExprDepth`, …), and constant config (`options`,
|
|
11
|
+
* `templatePrimitives`). #2482 Stage 4: `scope` replaced the old mutable
|
|
12
|
+
* stack this comment used to name here (see the field's own docstring on
|
|
13
|
+
* `GoTemplateAdapter` for the full migration story) — this file never held
|
|
14
|
+
* that stack itself, only described where it lived.
|
|
11
15
|
*/
|
|
12
16
|
|
|
13
17
|
import type {
|