@barefootjs/go-template 0.32.0 → 0.33.1
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/expr/helper-inline.d.ts.map +1 -1
- package/dist/adapter/go-template-adapter.d.ts +1 -1
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +65 -11
- 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/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/conformance-pins.d.ts.map +1 -1
- package/dist/index.js +72 -13
- package/dist/render-divergences.d.ts.map +1 -1
- package/dist/vite.js +161 -48
- package/package.json +5 -5
- package/src/__tests__/go-template-adapter.test.ts +6 -0
- package/src/adapter/expr/helper-inline.ts +4 -2
- package/src/adapter/go-template-adapter.ts +42 -7
- 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/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/conformance-pins.ts +7 -0
- package/src/render-divergences.ts +6 -1
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
asCallbackMethodCall,
|
|
59
59
|
sortComparatorFromArrow,
|
|
60
60
|
emitParsedExpr,
|
|
61
|
+
groupObjectLiteralSegments,
|
|
61
62
|
emitIRNode,
|
|
62
63
|
emitAttrValue,
|
|
63
64
|
augmentInheritedPropAccesses,
|
|
@@ -1370,6 +1371,10 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
1370
1371
|
if (el.kind !== 'object-literal') return null
|
|
1371
1372
|
const seen = new Set<string>()
|
|
1372
1373
|
for (const prop of el.properties) {
|
|
1374
|
+
// A spread (`{ ...t, a: 1 }`, #2696 Step 2) isn't a plain per-key
|
|
1375
|
+
// scalar shape this fast path bakes — fall back (the caller's
|
|
1376
|
+
// `ts.createSourceFile` path, or refusal) rather than mis-baking.
|
|
1377
|
+
if (prop.kind === 'spread') return null
|
|
1373
1378
|
if (prop.shorthand) return null
|
|
1374
1379
|
const key = prop.key
|
|
1375
1380
|
if (!GO_IDENTIFIER.test(key)) return null
|
|
@@ -3401,6 +3406,10 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
3401
3406
|
if (parsed.kind !== 'object-literal') return null
|
|
3402
3407
|
const entries: string[] = []
|
|
3403
3408
|
for (const prop of parsed.properties) {
|
|
3409
|
+
// A spread (`{ ...t }`, #2696 Step 2) isn't a per-key member this Go
|
|
3410
|
+
// struct-literal lowering can express — fail the whole value, same as
|
|
3411
|
+
// any other unsupported member (the consumer keeps its default).
|
|
3412
|
+
if (prop.kind === 'spread') return null
|
|
3404
3413
|
if (prop.shorthand) return null
|
|
3405
3414
|
const goVal = this.lowerProviderMapMemberValue(prop.value, propsParams)
|
|
3406
3415
|
if (goVal === null) return null
|
|
@@ -4991,12 +5000,33 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
4991
5000
|
return `bf_arr ${parts.join(' ')}`
|
|
4992
5001
|
}
|
|
4993
5002
|
|
|
4994
|
-
objectLiteral(
|
|
4995
|
-
//
|
|
4996
|
-
// (
|
|
4997
|
-
//
|
|
4998
|
-
//
|
|
4999
|
-
//
|
|
5003
|
+
objectLiteral(properties: ObjectLiteralProperty[], raw: string, emit: (e: ParsedExpr) => string): string {
|
|
5004
|
+
// A POPULATED literal is reachable here only in VALUE position (a
|
|
5005
|
+
// `.map()` receiver/callback body, an array-literal element, …) —
|
|
5006
|
+
// `isSupportedValue` admits it when every property value is itself
|
|
5007
|
+
// supported (expression-parser.ts, `checkSupport`'s `pos` parameter).
|
|
5008
|
+
// `text/template` has no map-literal syntax, so lower through the
|
|
5009
|
+
// variadic `bf_map` runtime helper (the object counterpart of
|
|
5010
|
+
// `arrayLiteral`'s `bf_arr`) instead of trying to spell a literal.
|
|
5011
|
+
if (properties.length > 0) {
|
|
5012
|
+
const wrap = (rendered: string) => (rendered.includes(' ') ? `(${rendered})` : rendered)
|
|
5013
|
+
const literalOf = (run: readonly Extract<ObjectLiteralProperty, { kind: 'prop' }>[]) =>
|
|
5014
|
+
`bf_map ${run.map(p => `"${escapeGoString(p.key)}" ${wrap(emit(p.value))}`).join(' ')}`
|
|
5015
|
+
if (!properties.some(p => p.kind === 'spread')) {
|
|
5016
|
+
return literalOf(properties as Extract<ObjectLiteralProperty, { kind: 'prop' }>[])
|
|
5017
|
+
}
|
|
5018
|
+
// Spread (`{ ...t, editing: false }`, #2696 Step 2): `bf_merge`
|
|
5019
|
+
// (runtime/eval.go, a `bf_map` sibling) is a variadic, null-safe map
|
|
5020
|
+
// merge — a non-map argument (nil included) is skipped rather than
|
|
5021
|
+
// panicking, matching JS's null/undefined-spread no-op — folding
|
|
5022
|
+
// every segment (a `bf_map` run, or a spread's own emitted value) in
|
|
5023
|
+
// ONE call, later segments winning, exactly JS spread's semantics.
|
|
5024
|
+
const segments = groupObjectLiteralSegments(properties, literalOf, e => wrap(emit(e)))
|
|
5025
|
+
return `bf_merge ${segments.join(' ')}`
|
|
5026
|
+
}
|
|
5027
|
+
// The EMPTY object literal (`?? {}`) reaches here as `??`'s right
|
|
5028
|
+
// operand (expression-parser.ts, `logical` case) — a RENDERED-position
|
|
5029
|
+
// admission, not a value-position one. Unlike the sibling adapters, Go
|
|
5000
5030
|
// can't silently fall back to a safe sentinel text: `this.unsupported`'s
|
|
5001
5031
|
// `[UNSUPPORTED: …]` marker would be spliced into a Go template action
|
|
5002
5032
|
// (e.g. as an `or`/`and` operand) and break template parsing, and the
|
|
@@ -6306,7 +6336,12 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6306
6336
|
// (icon registries, variant/size class maps), so this fully covers them.
|
|
6307
6337
|
const carried = constInfo.parsed
|
|
6308
6338
|
if (carried?.kind === 'object-literal') {
|
|
6309
|
-
|
|
6339
|
+
// A spread entry (`{ ...t }`, #2696 Step 2) has no static key to match
|
|
6340
|
+
// against — skip it, same as before this kind existed (a record const
|
|
6341
|
+
// containing spread already couldn't reach here as a whole).
|
|
6342
|
+
const hit = carried.properties.find(
|
|
6343
|
+
(prop): prop is Extract<ObjectLiteralProperty, { kind: 'prop' }> => prop.kind === 'prop' && prop.key === key,
|
|
6344
|
+
)
|
|
6310
6345
|
if (hit && hit.value.kind === 'literal') {
|
|
6311
6346
|
if (hit.value.literalType === 'string') return JSON.stringify(hit.value.value)
|
|
6312
6347
|
if (hit.value.literalType === 'number') return hit.value.raw ?? String(hit.value.value)
|
|
@@ -592,6 +592,17 @@ export function memoInitialFromParsedBody(
|
|
|
592
592
|
if (concatGo !== null) return concatGo
|
|
593
593
|
}
|
|
594
594
|
|
|
595
|
+
// () => <object>.map(cb).join(sep) as the memo's WHOLE body (no `+`
|
|
596
|
+
// concatenation) — `map-object-literal-body`'s `ids` memo: `rows().map(t
|
|
597
|
+
// => ({ id: t.id, done: false })).map(r => r.id).join(',')`. The `+`-chain
|
|
598
|
+
// arm above only reaches `matchMapJoinChain` as ONE leaf of a
|
|
599
|
+
// concatenation; a bare chain memo never entered it (#2696 review).
|
|
600
|
+
const bareChain = matchMapJoinChain(body)
|
|
601
|
+
if (bareChain) {
|
|
602
|
+
const chainGo = mapJoinChainToGo(ctx, bareChain, signals, propsParams, propFallbackVars)
|
|
603
|
+
if (chainGo !== null) return chainGo
|
|
604
|
+
}
|
|
605
|
+
|
|
595
606
|
return null
|
|
596
607
|
}
|
|
597
608
|
|
|
@@ -954,7 +965,7 @@ export function collectPropsReadByCtorInit(
|
|
|
954
965
|
e.elements.forEach(el => visit(el, bound))
|
|
955
966
|
return
|
|
956
967
|
case 'object-literal':
|
|
957
|
-
for (const p of e.properties) visit(p.value, bound)
|
|
968
|
+
for (const p of e.properties) visit(p.kind === 'spread' ? p.expr : p.value, bound)
|
|
958
969
|
return
|
|
959
970
|
case 'array-method':
|
|
960
971
|
visit(e.object, bound)
|
|
@@ -181,6 +181,9 @@ export function computeObjectMemoInitialValue(
|
|
|
181
181
|
const env: CtorLowerEnv = { searchParamsVars: new Set(), params: new Map() }
|
|
182
182
|
const entries: string[] = []
|
|
183
183
|
for (const prop of retObj.properties) {
|
|
184
|
+
// A spread (`{ ...t }`, #2696 Step 2) has no per-key `.Params.<Field>`
|
|
185
|
+
// accessor to match against — bail, same as any other unsupported shape.
|
|
186
|
+
if (prop.kind === 'spread') return null
|
|
184
187
|
// Bail on a shorthand property (`return { tag }`): its value is a bare
|
|
185
188
|
// identifier whose name need not match a `.Params.<Field>` accessor.
|
|
186
189
|
if (prop.shorthand) return null
|
|
@@ -200,6 +200,11 @@ function recordIndexInterpolationToGo(
|
|
|
200
200
|
|
|
201
201
|
const entries: { key: string; value: { kind: 'number' | 'string'; text: string } }[] = []
|
|
202
202
|
for (const prop of parsedConst.properties) {
|
|
203
|
+
// A spread (`{ ...t }`, #2696 Step 2) has no static key/value pair this
|
|
204
|
+
// record-index lowering can bake — bail, same as any other unsupported
|
|
205
|
+
// member (a record const containing spread already couldn't reach here
|
|
206
|
+
// as a whole before this kind existed).
|
|
207
|
+
if (prop.kind === 'spread') return null
|
|
203
208
|
const v = prop.value
|
|
204
209
|
if (v.kind === 'literal' && v.literalType === 'number') {
|
|
205
210
|
entries.push({ key: prop.key, value: { kind: 'number', text: v.raw ?? String(v.value) } })
|
|
@@ -143,6 +143,10 @@ function parsedObjectLiteralToGoMap(parsed: ParsedExpr | undefined): string | nu
|
|
|
143
143
|
if (!parsed || parsed.kind !== 'object-literal') return null
|
|
144
144
|
const entries: string[] = []
|
|
145
145
|
for (const prop of parsed.properties) {
|
|
146
|
+
// A spread (`{ ...t }`, #2696 Step 2) has no static key/value pair this
|
|
147
|
+
// conservative Go-map bake can express — bail, same as any other
|
|
148
|
+
// unsupported shape.
|
|
149
|
+
if (prop.kind === 'spread') return null
|
|
146
150
|
// Reject a numeric key (`{ 1: 'a' }`); `keyKind` distinguishes it from a
|
|
147
151
|
// string `'1'` key.
|
|
148
152
|
if (prop.keyKind === 'numeric') return null
|
|
@@ -386,6 +390,10 @@ function objectLiteralToGoSpreadMap(
|
|
|
386
390
|
): string | null {
|
|
387
391
|
const entries: string[] = []
|
|
388
392
|
for (const prop of obj.properties) {
|
|
393
|
+
// A spread (`{ ...t }`, #2696 Step 2) isn't a per-key entry this
|
|
394
|
+
// conditional-spread inline lowering can express — bail (the docstring
|
|
395
|
+
// already declares spread out-of-scope for this function).
|
|
396
|
+
if (prop.kind === 'spread') return null
|
|
389
397
|
// Shorthand (`{ describedBy }`) is unsupported.
|
|
390
398
|
if (prop.shorthand) return null
|
|
391
399
|
// Reject a numeric key (`{ 1: x }`); `keyKind` distinguishes it from a
|
|
@@ -63,6 +63,9 @@ function bakeInlineObjectAsGoMap(ctx: GoEmitContext, expr: ParsedExpr): string |
|
|
|
63
63
|
if (expr.kind !== 'object-literal') return null
|
|
64
64
|
const entries: string[] = []
|
|
65
65
|
for (const prop of expr.properties) {
|
|
66
|
+
// A spread (`{ ...t }`, #2696 Step 2) has no static key to bake as a Go
|
|
67
|
+
// map entry — bail, same as any other unsupported shape.
|
|
68
|
+
if (prop.kind === 'spread') return null
|
|
66
69
|
if (prop.shorthand) return null
|
|
67
70
|
const go =
|
|
68
71
|
prop.value.kind === 'object-literal'
|
|
@@ -150,6 +153,10 @@ export function parsedLiteralToGo(
|
|
|
150
153
|
if (!structFields) return null
|
|
151
154
|
const entries: string[] = []
|
|
152
155
|
for (const prop of expr.properties) {
|
|
156
|
+
// A spread (`{ ...t }`, #2696 Step 2) has no static key to match a
|
|
157
|
+
// struct field against — defer the whole object, same as any other
|
|
158
|
+
// unsupported shape.
|
|
159
|
+
if (prop.kind === 'spread') return null
|
|
153
160
|
// A shorthand `{ a }` carries an identifier value → lowers to null below
|
|
154
161
|
// and defers the whole object.
|
|
155
162
|
const goField = structFields.get(prop.key)
|
|
@@ -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
|
}
|
package/src/conformance-pins.ts
CHANGED
|
@@ -52,4 +52,11 @@ export const conformancePins: ConformancePins = {
|
|
|
52
52
|
'filter-nested-find-predicate': [{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2320' }],
|
|
53
53
|
'date-method-uncatalogued': [{ code: 'BF021', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2356' }],
|
|
54
54
|
'rich-prop-client-read': [{ code: 'BF049', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2648' }],
|
|
55
|
+
// #2667: a ternary/array LITERALLY WRAPPING JSX at a non-children prop
|
|
56
|
+
// position (e.g. `header={cond ? <a/> : <b/>}`) is refused ahead of
|
|
57
|
+
// `adapter.generate()` in the shared jsx-to-ir.ts phase, so it is pinned
|
|
58
|
+
// identically on every adapter (including Hono) — same reasoning as
|
|
59
|
+
// `rich-prop-client-read` above.
|
|
60
|
+
'jsx-element-prop-ternary': [{ code: 'BF021', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2667' }],
|
|
61
|
+
'jsx-element-prop-array': [{ code: 'BF021', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2667' }],
|
|
55
62
|
}
|
|
@@ -17,4 +17,9 @@
|
|
|
17
17
|
|
|
18
18
|
import type { RenderDivergences } from '@barefootjs/jsx'
|
|
19
19
|
|
|
20
|
-
export const renderDivergences: RenderDivergences = {
|
|
20
|
+
export const renderDivergences: RenderDivergences = {
|
|
21
|
+
'jsx-element-prop-fragment-conditional':
|
|
22
|
+
'Go-specific half of the fragment-wrapped-conditional prop shape, tracked as https://github.com/piconic-ai/barefootjs/issues/2703 (distinct from #2702, the cross-adapter hydrate-time bug). #2702 is a hydrate-time-only bug (SSR is correct on every adapter, confirmed for Go by direct `adapter.generate()` inspection of the raw template TEXT, which does contain `{{if .Cond}}<a bf-c="^s0">x</a>{{else}}...{{end}}`-shaped markup). But the REAL Go render of this exact fixture (`go-template-adapter.test.ts`, real `go run`) produces an EMPTY `<header>` — `<header bf="s1"><!--bf:s0--><!--/--></header>` instead of the reference `<a>x</a>` — i.e. the `header` prop\'s jsx-children payload never reaches the child (`Card`) component\'s slot construction (`.CardSlot1`) at all for this NAMED-prop shape, even though the same conditional-in-fragment payload renders correctly when passed via `children` instead (`jsx-element-prop-children-escape`). Root cause not yet isolated to a specific emitter function — only the reproducible symptom is pinned here. Every other adapter (Hono, ERB, Jinja, Mojolicious, MiniJinja, Twig, Xslate, Blade) renders this fixture correctly; verified by running each adapter\'s own JSX Conformance Tests for this fixture id.',
|
|
23
|
+
'signal-object-spread-init':
|
|
24
|
+
'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).',
|
|
25
|
+
}
|