@barefootjs/go-template 0.31.10 → 0.33.0
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/emit-context.d.ts +16 -0
- package/dist/adapter/emit-context.d.ts.map +1 -1
- package/dist/adapter/expr/helper-inline.d.ts.map +1 -1
- package/dist/adapter/go-template-adapter.d.ts +75 -1
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +165 -15
- package/dist/adapter/lib/types.d.ts +13 -0
- package/dist/adapter/lib/types.d.ts.map +1 -1
- package/dist/adapter/memo/memo-compute.d.ts.map +1 -1
- package/dist/adapter/memo/memo-value.d.ts.map +1 -1
- package/dist/adapter/memo/template-interp.d.ts.map +1 -1
- package/dist/adapter/props/prop-types.d.ts.map +1 -1
- package/dist/adapter/spread/spread-codegen.d.ts.map +1 -1
- package/dist/adapter/value/parsed-literal-to-go.d.ts.map +1 -1
- package/dist/adapter/value/value-lowering.d.ts +15 -12
- package/dist/adapter/value/value-lowering.d.ts.map +1 -1
- package/dist/index.js +166 -17
- package/dist/render-divergences.d.ts.map +1 -1
- package/dist/vite.js +259 -52
- package/package.json +5 -5
- package/src/__tests__/go-template-adapter.test.ts +246 -6
- package/src/adapter/emit-context.ts +14 -0
- package/src/adapter/expr/helper-inline.ts +4 -2
- package/src/adapter/go-template-adapter.ts +296 -9
- package/src/adapter/lib/types.ts +10 -0
- package/src/adapter/memo/memo-compute.ts +12 -1
- package/src/adapter/memo/memo-value.ts +3 -0
- package/src/adapter/memo/template-interp.ts +5 -0
- package/src/adapter/props/prop-types.ts +26 -1
- package/src/adapter/spread/spread-codegen.ts +8 -0
- package/src/adapter/value/parsed-literal-to-go.ts +7 -0
- package/src/adapter/value/value-lowering.ts +96 -22
- package/src/render-divergences.ts +2 -23
|
@@ -232,6 +232,9 @@ export function objectLiteralToGoMap(ctx: GoEmitContext, expr: ParsedExpr): stri
|
|
|
232
232
|
if (expr.kind !== 'object-literal') return null
|
|
233
233
|
const entries: string[] = []
|
|
234
234
|
for (const prop of expr.properties) {
|
|
235
|
+
// A spread (`{ ...t }`, #2696 Step 2) has no static key to bake as a Go
|
|
236
|
+
// map entry — bail, same as any other unsupported shape.
|
|
237
|
+
if (prop.kind === 'spread') return null
|
|
235
238
|
if (prop.shorthand) return null
|
|
236
239
|
const val = parsedLiteralToGo(ctx, prop.value)
|
|
237
240
|
if (val === null) return null
|
|
@@ -390,40 +393,43 @@ function resolveMapJoinBaseAsGo(
|
|
|
390
393
|
return null
|
|
391
394
|
}
|
|
392
395
|
|
|
396
|
+
/** `.map`/`.filter` → the runtime evaluator function that composes it. Both
|
|
397
|
+
* take `items any` and return `[]any` (`eval.go`), so nesting one call's
|
|
398
|
+
* result straight into another's `items` argument is exactly `.a().b()`'s
|
|
399
|
+
* JS semantics — no expression-tree fusion needed (#2696 review). */
|
|
400
|
+
const CALLBACK_EVAL_FUNC: Record<string, string> = { map: 'bf.MapEval', filter: 'bf.FilterEval' }
|
|
401
|
+
|
|
393
402
|
/**
|
|
394
|
-
* Lower
|
|
395
|
-
*
|
|
396
|
-
* <
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
* `bf.
|
|
400
|
-
* calls. Shared by a memo's derived value (`memoInitialFromParsedBody`'s
|
|
401
|
-
* concatenation-chain arm) and a SIGNAL's own initializer
|
|
402
|
-
* (`convertInitialValue`'s `string` branch, #2492).
|
|
403
|
+
* Lower ONE `.map(cb)`/`.filter(cb)` step to a runtime-evaluator call over an
|
|
404
|
+
* already-resolved `itemsGo` Go expression: `bf.MapEval(<itemsGo>,
|
|
405
|
+
* "<projJSON>", "<param>", <envMap>)` (or `bf.FilterEval` for `.filter`).
|
|
406
|
+
* Shared by {@link resolveMapChainAsGo} (an intermediate step feeding the
|
|
407
|
+
* NEXT step's `items`) and {@link mapJoinChainToGo} (the outermost step,
|
|
408
|
+
* whose result feeds `bf.Join`).
|
|
403
409
|
*
|
|
404
|
-
* @returns the Go expression, or null when the
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
* a captured free variable doesn't resolve, or the separator isn't a
|
|
408
|
-
* string literal (a dynamic separator — out of scope for this arm).
|
|
410
|
+
* @returns the Go expression, or null when the callback body isn't
|
|
411
|
+
* representable to the runtime evaluator (`serializeParsedExpr` refusal)
|
|
412
|
+
* or a captured free variable doesn't resolve.
|
|
409
413
|
*/
|
|
410
|
-
|
|
414
|
+
function callbackStepToGo(
|
|
411
415
|
ctx: GoEmitContext,
|
|
412
|
-
|
|
416
|
+
method: string,
|
|
417
|
+
itemsGo: string,
|
|
418
|
+
arrow: Extract<ParsedExpr, { kind: 'arrow' }>,
|
|
413
419
|
signals: { getter: string; initialValue: string; type?: TypeInfo; parsed?: ParsedExpr }[],
|
|
414
420
|
propsParams: { name: string; sourceName?: string }[],
|
|
415
421
|
propFallbackVars: ReadonlyMap<string, PropFallbackVar>,
|
|
416
422
|
): string | null {
|
|
417
|
-
const
|
|
418
|
-
if (
|
|
423
|
+
const evalFunc = CALLBACK_EVAL_FUNC[method]
|
|
424
|
+
if (!evalFunc) return null
|
|
419
425
|
|
|
420
426
|
const knownGetterNames = new Set(signals.map(s => s.getter))
|
|
421
|
-
const materialized = materializeGetterCalls(
|
|
427
|
+
const materialized = materializeGetterCalls(arrow.body, knownGetterNames)
|
|
422
428
|
const projJSON = serializeParsedExpr(materialized)
|
|
423
429
|
if (projJSON === null) return null
|
|
424
430
|
|
|
425
|
-
const paramName =
|
|
426
|
-
const freeVars = freeVarsInBody(materialized, new Set(
|
|
431
|
+
const paramName = arrow.params[0] ?? '_'
|
|
432
|
+
const freeVars = freeVarsInBody(materialized, new Set(arrow.params))
|
|
427
433
|
const envEntries: string[] = []
|
|
428
434
|
for (const name of freeVars) {
|
|
429
435
|
const sig = signals.find(s => s.getter === name)
|
|
@@ -440,6 +446,74 @@ export function mapJoinChainToGo(
|
|
|
440
446
|
}
|
|
441
447
|
const envMap = `map[string]any{${envEntries.join(', ')}}`
|
|
442
448
|
|
|
449
|
+
return `${evalFunc}(${itemsGo}, "${escapeGoString(projJSON)}", ${JSON.stringify(paramName)}, ${envMap})`
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Resolve a `.map()`/`.filter()` CHAIN of any depth to a Go expression:
|
|
454
|
+
* peel off callback-method layers one at a time (`asCallbackMethodCall`),
|
|
455
|
+
* composing a `bf.MapEval`/`bf.FilterEval` call around the PREVIOUS layer's
|
|
456
|
+
* result for each one, down to the base receiver
|
|
457
|
+
* ({@link resolveMapJoinBaseAsGo} — a signal call, prop field, or array
|
|
458
|
+
* literal). `rows().map(t => ({ id: t.id, … })).map(r => r.id)` composes as
|
|
459
|
+
* `bf.MapEval(bf.MapEval(<rows>, "{id:t.id,…}", "t", {}), "r.id", "r", {})`
|
|
460
|
+
* — before this, only a chain whose FIRST layer already matched a base
|
|
461
|
+
* shape resolved; a nested map/filter layer silently fell to `null` (#2696
|
|
462
|
+
* review: `map-object-literal-body`'s two chained `.map()`s).
|
|
463
|
+
*
|
|
464
|
+
* @returns the Go expression, or null when the base receiver doesn't
|
|
465
|
+
* resolve or any layer's callback isn't representable
|
|
466
|
+
* ({@link callbackStepToGo}).
|
|
467
|
+
*/
|
|
468
|
+
function resolveMapChainAsGo(
|
|
469
|
+
ctx: GoEmitContext,
|
|
470
|
+
expr: ParsedExpr,
|
|
471
|
+
signals: { getter: string; initialValue: string; type?: TypeInfo; parsed?: ParsedExpr }[],
|
|
472
|
+
propsParams: { name: string; sourceName?: string }[],
|
|
473
|
+
propFallbackVars: ReadonlyMap<string, PropFallbackVar>,
|
|
474
|
+
): string | null {
|
|
475
|
+
const cb = asCallbackMethodCall(expr)
|
|
476
|
+
if (cb && cb.method in CALLBACK_EVAL_FUNC) {
|
|
477
|
+
const innerGo = resolveMapChainAsGo(ctx, cb.object, signals, propsParams, propFallbackVars)
|
|
478
|
+
if (innerGo === null) return null
|
|
479
|
+
return callbackStepToGo(ctx, cb.method, innerGo, cb.arrow, signals, propsParams, propFallbackVars)
|
|
480
|
+
}
|
|
481
|
+
return resolveMapJoinBaseAsGo(ctx, expr, signals, propsParams)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Lower a `.map(cb).join(sep)` chain (matched by {@link matchMapJoinChain})
|
|
486
|
+
* to a `bf.Join(<chain>, <sep>)` Go expression, where `<chain>` is the
|
|
487
|
+
* chain's OWN `.map()` step composed by {@link callbackStepToGo} over
|
|
488
|
+
* whatever {@link resolveMapChainAsGo} resolves its receiver to (a base
|
|
489
|
+
* shape, or one or more nested `.map()`/`.filter()` layers) — the
|
|
490
|
+
* constructor-source analogue of `matchFilterArmMemo`'s `bf.FilterEval`
|
|
491
|
+
* emit (`memo-compute.ts`), reusing the SAME runtime evaluator functions
|
|
492
|
+
* (`MapEval`/`FilterEval`, `eval.go`; `Join`, `bf.go`) the template-position
|
|
493
|
+
* `bf_map_eval`/`bf_join` lowering already calls. Shared by a memo's
|
|
494
|
+
* derived value (`memoInitialFromParsedBody`'s concatenation-chain arm and
|
|
495
|
+
* bare-chain arm) and a SIGNAL's own initializer (`convertInitialValue`'s
|
|
496
|
+
* `string` branch, #2492).
|
|
497
|
+
*
|
|
498
|
+
* @returns the Go expression, or null when the receiver doesn't resolve
|
|
499
|
+
* ({@link resolveMapChainAsGo}), the projection body isn't representable
|
|
500
|
+
* to the runtime evaluator (`serializeParsedExpr` refusal), a captured
|
|
501
|
+
* free variable doesn't resolve, or the separator isn't a string literal
|
|
502
|
+
* (a dynamic separator — out of scope for this arm).
|
|
503
|
+
*/
|
|
504
|
+
export function mapJoinChainToGo(
|
|
505
|
+
ctx: GoEmitContext,
|
|
506
|
+
chain: { object: ParsedExpr; arrow: Extract<ParsedExpr, { kind: 'arrow' }>; sepArg?: ParsedExpr },
|
|
507
|
+
signals: { getter: string; initialValue: string; type?: TypeInfo; parsed?: ParsedExpr }[],
|
|
508
|
+
propsParams: { name: string; sourceName?: string }[],
|
|
509
|
+
propFallbackVars: ReadonlyMap<string, PropFallbackVar>,
|
|
510
|
+
): string | null {
|
|
511
|
+
const itemsGo = resolveMapChainAsGo(ctx, chain.object, signals, propsParams, propFallbackVars)
|
|
512
|
+
if (itemsGo === null) return null
|
|
513
|
+
|
|
514
|
+
const mapGo = callbackStepToGo(ctx, 'map', itemsGo, chain.arrow, signals, propsParams, propFallbackVars)
|
|
515
|
+
if (mapGo === null) return null
|
|
516
|
+
|
|
443
517
|
let sepGo: string
|
|
444
518
|
if (!chain.sepArg) {
|
|
445
519
|
sepGo = JSON.stringify(',')
|
|
@@ -449,5 +523,5 @@ export function mapJoinChainToGo(
|
|
|
449
523
|
return null
|
|
450
524
|
}
|
|
451
525
|
|
|
452
|
-
return `bf.Join(
|
|
526
|
+
return `bf.Join(${mapGo}, ${sepGo})`
|
|
453
527
|
}
|
|
@@ -18,27 +18,6 @@
|
|
|
18
18
|
import type { RenderDivergences } from '@barefootjs/jsx'
|
|
19
19
|
|
|
20
20
|
export const renderDivergences: RenderDivergences = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// the NAME collision — never on whether `extractPropFallback` actually
|
|
24
|
-
// matched a supported `props.x ?? <default>` shape. For a non-idempotent
|
|
25
|
-
// derivation (`createSignal((props.count ?? 1) * 2)`) the fallback
|
|
26
|
-
// extractor correctly declines to fold `* 2` into the struct default, but
|
|
27
|
-
// the `continue` fires anyway on the name match alone, so the emitted
|
|
28
|
-
// struct field silently drops the `* 2` and the signal's initial value
|
|
29
|
-
// renders as the raw prop instead of its derived value. Not a one-liner:
|
|
30
|
-
// two Go struct fields can't share an identifier, so simply removing the
|
|
31
|
-
// skip emits a duplicate field — the real fix needs its own PR.
|
|
32
|
-
'signal-prop-same-name-derived':
|
|
33
|
-
'self-derived signal collides with its prop field name in the generated Go props struct — the non-idempotent `* 2` derivation is dropped and the signal renders the raw prop value instead (https://github.com/piconic-ai/barefootjs/issues/2683)',
|
|
34
|
-
// #2685 review: same #2683 bug, one hop of const indirection removed —
|
|
35
|
-
// the props-struct field-name collision is keyed on the SIGNAL's name,
|
|
36
|
-
// not on how its initializer reaches the prop, so
|
|
37
|
-
// `const mid = props.count; createSignal((mid ?? 1) * 2)` collides
|
|
38
|
-
// exactly like the direct-access form above. This is go's PRE-EXISTING
|
|
39
|
-
// #2683 defect surfacing through a new fixture, not a regression from
|
|
40
|
-
// the #2685 review fix (which lands correctly on every other
|
|
41
|
-
// template-stash adapter — see those adapters' conformance runs).
|
|
42
|
-
'signal-prop-same-name-via-const-derived':
|
|
43
|
-
'self-derived signal (reached through a component-scope const) collides with its prop field name in the generated Go props struct — the non-idempotent `* 2` derivation is dropped and the signal renders the raw prop value instead (https://github.com/piconic-ai/barefootjs/issues/2683)',
|
|
21
|
+
'signal-object-spread-init':
|
|
22
|
+
'PRE-EXISTING, unrelated to the #2696 Step 2 spread work this fixture pins: a `derived`-classified signal/memo whose value is an OBJECT literal has no live-template-expression lowering on Go — unlike the other six template-stash backends (e.g. minijinja emits `{% set merged = dict(base, done=true) %}`), Go always bakes an object-typed signal/memo field into Go SOURCE at `NewXxxProps` constructor time (`convertInitialValue`/`parsedLiteralToGo`), and that baker is STATIC-only (identifier/call/member operands defer, `parsed-literal-to-go.ts`\'s own docstring) — it cannot reference a live prop at all. Reproduced identically with the spread REMOVED (`createSignal({ id: base.id, done: true })`), confirming the gap predates and is independent of spread: the signal seeds `nil` and every field read (`.Merged.ID`/`.Merged.Done`) reads the Go zero value regardless of `initialTodos`. Graduate by teaching the baker to emit prop-referencing Go expressions (https://github.com/piconic-ai/barefootjs/issues/2700).',
|
|
44
23
|
}
|