@barefootjs/go-template 0.17.1 → 0.18.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.
@@ -1 +1 @@
1
- {"version":3,"file":"type-codegen.d.ts","sourceRoot":"","sources":["../../../src/adapter/type/type-codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAE/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEvD;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,aAAa,EAClB,QAAQ,EAAE,QAAQ,EAClB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAsCR;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAa3E;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWxD"}
1
+ {"version":3,"file":"type-codegen.d.ts","sourceRoot":"","sources":["../../../src/adapter/type/type-codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAE/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEvD;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,aAAa,EAClB,QAAQ,EAAE,QAAQ,EAClB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAkDR;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAgB3E;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWxD"}
@@ -3,13 +3,22 @@
3
3
  * for baking a signal's inline initial value into the SSR data context.
4
4
  *
5
5
  * Covers scalar literals, a unary-minus number, arrays of those, and object
6
- * literals baked against a concrete local struct.
6
+ * literals baked against a concrete local struct — including, per #2087, a
7
+ * struct property whose OWN value is a nested array or object literal
8
+ * (`{ id, cells: ['a', 'b'] }`, `{ id, user: { name: 'Ada' } }`): the struct's
9
+ * declared property TYPE (looked up from `ctx.state.currentTypeDefinitions`)
10
+ * threads through so a typed nested array bakes via the normal array branch
11
+ * below, and a nested INLINE object (one with no named Go struct —
12
+ * `typeInfoToGo`'s `'object'` case always falls back to
13
+ * `map[string]interface{}`) bakes as a capitalized-key Go map literal instead
14
+ * — the same convention `test-render.ts`'s harness-prop baking already uses
15
+ * for object elements, since `html/template`'s map field access is an exact
16
+ * case-sensitive `MapIndex`.
7
17
  *
8
18
  * Contract is **null-means-defer**: returns null for anything not reproduced
9
19
  * exactly — an object whose target type isn't a known struct, a key the struct
10
- * doesn't declare, a nested object/array property value, an empty array, an
11
- * identifier/call, or a numeric literal missing its `raw` token. The caller
12
- * then keeps `nil`.
20
+ * doesn't declare, an empty array, an identifier/call, or a numeric literal
21
+ * missing its `raw` token. The caller then keeps `nil`.
13
22
  */
14
23
  import type { ParsedExpr, TypeInfo } from '@barefootjs/jsx';
15
24
  import type { GoEmitContext } from '../emit-context.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"parsed-literal-to-go.d.ts","sourceRoot":"","sources":["../../../src/adapter/value/parsed-literal-to-go.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAE3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAGvD,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,UAAU,EAChB,QAAQ,CAAC,EAAE,QAAQ,GAClB,MAAM,GAAG,IAAI,CAsEf"}
1
+ {"version":3,"file":"parsed-literal-to-go.d.ts","sourceRoot":"","sources":["../../../src/adapter/value/parsed-literal-to-go.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAkB,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAE3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAmDvD,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,UAAU,EAChB,QAAQ,CAAC,EAAE,QAAQ,GAClB,MAAM,GAAG,IAAI,CAsFf"}
package/dist/build.js CHANGED
@@ -378,7 +378,7 @@ import {
378
378
  emitAttrValue,
379
379
  augmentInheritedPropAccesses,
380
380
  collectContextConsumers,
381
- isLowerableObjectRestDestructure,
381
+ isLowerableLoopDestructure,
382
382
  collectModuleStringConsts as collectModuleStringConstsShared,
383
383
  prepareLoweringMatchers,
384
384
  envSignalReaderFor,
@@ -463,6 +463,13 @@ function capitalizeFieldName(name) {
463
463
  }
464
464
  return name.charAt(0).toUpperCase() + name.slice(1);
465
465
  }
466
+ function goFieldNameForKey(key) {
467
+ const parts = key.split(/[^A-Za-z0-9_]+/).filter(Boolean);
468
+ if (parts.length === 0)
469
+ return "Field";
470
+ const name = parts.map(capitalizeFieldName).join("");
471
+ return /^[0-9]/.test(name) ? `Field${name}` : name;
472
+ }
466
473
  function slotIdToFieldSuffix(slotId) {
467
474
  const cleanId = slotId.startsWith("^") ? slotId.slice(1) : slotId;
468
475
  const match = cleanId.match(/^s(\d+)$/);
@@ -1035,9 +1042,12 @@ function forEachValueChild(n, visit) {
1035
1042
  // src/adapter/expr/url-builder.ts
1036
1043
  import {
1037
1044
  parseExpression,
1038
- stringifyParsedExpr
1045
+ stringifyParsedExpr,
1046
+ isValidHelperId
1039
1047
  } from "@barefootjs/jsx";
1040
- var GO_HELPER_NAMES = { query: "bf_query" };
1048
+ function goHelperName(helper) {
1049
+ return isValidHelperId(helper) ? `bf_${helper}` : null;
1050
+ }
1041
1051
  var BOOL_COMPARISON_OPS = new Set([
1042
1052
  "==",
1043
1053
  "===",
@@ -1080,7 +1090,7 @@ function lowerRegisteredCall(ctx, jsExpr, preParsed) {
1080
1090
  return null;
1081
1091
  }
1082
1092
  function renderLoweringNode(ctx, node) {
1083
- const helper = GO_HELPER_NAMES[node.helper];
1093
+ const helper = goHelperName(node.helper);
1084
1094
  if (!helper)
1085
1095
  return null;
1086
1096
  const lowerExpr = (n) => ctx.convertExpressionToGo(stringifyParsedExpr(n), undefined, n);
@@ -1120,7 +1130,7 @@ function typeInfoToGo(ctx, typeInfo, defaultValue) {
1120
1130
  case "object":
1121
1131
  return "map[string]interface{}";
1122
1132
  case "interface":
1123
- if (typeInfo.raw && ctx.state.localTypeNames.has(typeInfo.raw)) {
1133
+ if (typeInfo.raw && (ctx.state.localStructFields.has(typeInfo.raw) || ctx.state.localTypeAliases.has(typeInfo.raw))) {
1124
1134
  return typeInfo.raw;
1125
1135
  }
1126
1136
  if (typeInfo.raw) {
@@ -1153,7 +1163,7 @@ function tsTypeStringToGo(ctx, tsType) {
1153
1163
  const arrayMatch = t.match(/^Array<(.+)>$/);
1154
1164
  if (arrayMatch)
1155
1165
  return `[]${tsTypeStringToGo(ctx, arrayMatch[1])}`;
1156
- if (ctx.state.localTypeNames.has(t))
1166
+ if (ctx.state.localStructFields.has(t) || ctx.state.localTypeAliases.has(t))
1157
1167
  return t;
1158
1168
  return "interface{}";
1159
1169
  }
@@ -1175,6 +1185,24 @@ function inferTypeFromValue(value) {
1175
1185
  }
1176
1186
 
1177
1187
  // src/adapter/value/parsed-literal-to-go.ts
1188
+ function structPropertyType(ctx, structGoType, key) {
1189
+ const td = ctx.state.currentTypeDefinitions.find((t) => t.name === structGoType);
1190
+ return td?.properties?.find((p) => p.name === key)?.type;
1191
+ }
1192
+ function bakeInlineObjectAsGoMap(ctx, expr) {
1193
+ if (expr.kind !== "object-literal")
1194
+ return null;
1195
+ const entries = [];
1196
+ for (const prop of expr.properties) {
1197
+ if (prop.shorthand)
1198
+ return null;
1199
+ const go = prop.value.kind === "object-literal" ? bakeInlineObjectAsGoMap(ctx, prop.value) : parsedLiteralToGo(ctx, prop.value);
1200
+ if (go === null)
1201
+ return null;
1202
+ entries.push(`${JSON.stringify(goFieldNameForKey(prop.key))}: ${go}`);
1203
+ }
1204
+ return `map[string]interface{}{${entries.join(", ")}}`;
1205
+ }
1178
1206
  function parsedLiteralToGo(ctx, expr, typeInfo) {
1179
1207
  if (expr.kind === "unary" && expr.op === "-" && expr.argument.kind === "literal" && expr.argument.literalType === "number") {
1180
1208
  return expr.argument.raw !== undefined ? `-${expr.argument.raw}` : null;
@@ -1215,9 +1243,16 @@ function parsedLiteralToGo(ctx, expr, typeInfo) {
1215
1243
  const goField = structFields.get(prop.key);
1216
1244
  if (!goField)
1217
1245
  return null;
1218
- if (prop.value.kind === "object-literal" || prop.value.kind === "array-literal")
1219
- return null;
1220
- const go = parsedLiteralToGo(ctx, prop.value);
1246
+ const propType = structPropertyType(ctx, goType, prop.key);
1247
+ let go;
1248
+ if (prop.value.kind === "array-literal") {
1249
+ go = parsedLiteralToGo(ctx, prop.value, propType);
1250
+ } else if (prop.value.kind === "object-literal") {
1251
+ const nestedGoType = propType ? typeInfoToGo(ctx, propType) : undefined;
1252
+ go = nestedGoType && ctx.state.localStructFields.has(nestedGoType) ? parsedLiteralToGo(ctx, prop.value, propType) : bakeInlineObjectAsGoMap(ctx, prop.value);
1253
+ } else {
1254
+ go = parsedLiteralToGo(ctx, prop.value);
1255
+ }
1221
1256
  if (go === null)
1222
1257
  return null;
1223
1258
  entries.push(`${goField}: ${go}`);
@@ -2455,8 +2490,10 @@ class GoTemplateAdapter extends BaseAdapter {
2455
2490
  inLoop = false;
2456
2491
  loopParamStack = [];
2457
2492
  loopScalarItemStack = [];
2493
+ loopWrapperStack = [];
2458
2494
  loopVarRefCount = new Map;
2459
2495
  loopBindingStack = [];
2496
+ loopRestExcludeStack = [];
2460
2497
  childComponentShapes = new Map;
2461
2498
  childContextConsumers = new Map;
2462
2499
  constructor(options = {}) {
@@ -2649,11 +2686,22 @@ ${scriptRegistrations}${templateBody}
2649
2686
  contextFieldName(c) {
2650
2687
  return capitalizeFieldName(c.localName);
2651
2688
  }
2689
+ isMapRootedContextChain(node) {
2690
+ if (node.kind === "identifier") {
2691
+ return this.state.contextConsumers.some((c) => c.localName === node.name && c.defaultKind === "object");
2692
+ }
2693
+ if (node.kind === "member" && !node.computed) {
2694
+ return this.isMapRootedContextChain(node.object);
2695
+ }
2696
+ return false;
2697
+ }
2652
2698
  contextConsumerGoType(c) {
2653
2699
  if (typeof c.defaultValue === "number")
2654
2700
  return "int";
2655
2701
  if (typeof c.defaultValue === "boolean")
2656
2702
  return "bool";
2703
+ if (c.defaultKind === "object")
2704
+ return "map[string]interface{}";
2657
2705
  return "string";
2658
2706
  }
2659
2707
  contextConsumerGoDefault(c) {
@@ -2663,6 +2711,8 @@ ${scriptRegistrations}${templateBody}
2663
2711
  return String(c.defaultValue);
2664
2712
  if (typeof c.defaultValue === "string")
2665
2713
  return `"${escapeGoString(c.defaultValue)}"`;
2714
+ if (c.defaultKind === "object")
2715
+ return "map[string]interface{}{}";
2666
2716
  return '""';
2667
2717
  }
2668
2718
  nonCollidingContextConsumers(taken) {
@@ -2709,12 +2759,15 @@ ${goFields.join(`
2709
2759
  }
2710
2760
  structFieldsFor(td) {
2711
2761
  const fields = [];
2762
+ const seenGoNames = new Set;
2712
2763
  for (const prop of td.properties ?? []) {
2713
- if (!GO_IDENTIFIER.test(prop.name))
2764
+ const goName = goFieldNameForKey(prop.name);
2765
+ if (seenGoNames.has(goName))
2714
2766
  continue;
2767
+ seenGoNames.add(goName);
2715
2768
  fields.push({
2716
2769
  tsName: prop.name,
2717
- goName: capitalizeFieldName(prop.name),
2770
+ goName,
2718
2771
  goType: typeInfoToGo(this.emitCtx, prop.type)
2719
2772
  });
2720
2773
  }
@@ -2942,10 +2995,10 @@ ${goFields.join(`
2942
2995
  }
2943
2996
  return "interface{}";
2944
2997
  }
2945
- collectBodyChildInstances(bodyChildren) {
2998
+ collectBodyChildInstances(bodyChildren, propsParams = []) {
2946
2999
  const result = [];
2947
3000
  for (const child of bodyChildren) {
2948
- this.collectStaticChildInstancesRecursive(child, result, false, new Map);
3001
+ this.collectStaticChildInstancesRecursive(child, result, false, new Map, propsParams);
2949
3002
  }
2950
3003
  return result;
2951
3004
  }
@@ -3115,7 +3168,7 @@ ${goFields.join(`
3115
3168
  for (const c of this.nonCollidingContextConsumers(takenInit)) {
3116
3169
  const field = this.contextFieldName(c);
3117
3170
  const def = this.contextConsumerGoDefault(c);
3118
- const defaulted = c.defaultValue === null || def === '""' || def === "0" || def === "false" ? `in.${field}` : applyGoFallback(`in.${field}`, def);
3171
+ const defaulted = c.defaultValue === null || def === '""' || def === "0" || def === "false" || def === "map[string]interface{}{}" ? `in.${field}` : applyGoFallback(`in.${field}`, def);
3119
3172
  lines.push(` ${field}: ${defaulted},`);
3120
3173
  }
3121
3174
  this.emitStaticChildInstances(lines, ir);
@@ -3124,7 +3177,7 @@ ${goFields.join(`
3124
3177
  lines.push("}");
3125
3178
  }
3126
3179
  emitStaticChildInstances(lines, ir) {
3127
- const staticChildren = this.collectStaticChildInstances(ir.root);
3180
+ const staticChildren = this.collectStaticChildInstances(ir.root, ir.metadata.propsParams);
3128
3181
  for (const child of staticChildren) {
3129
3182
  lines.push(` ${child.fieldName}: New${child.name}Props(${child.name}Input{`);
3130
3183
  lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
@@ -3256,7 +3309,7 @@ ${goFields.join(`
3256
3309
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
3257
3310
  const wrapperType = this.loopBodyWrapperName(componentName, nested);
3258
3311
  const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
3259
- const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
3312
+ const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren, ir.metadata.propsParams);
3260
3313
  for (const child of bodyChildInstances) {
3261
3314
  const childVar = `child_${child.fieldName}`;
3262
3315
  lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
@@ -3333,7 +3386,7 @@ ${goFields.join(`
3333
3386
  const wrapperType = this.loopBodyWrapperName(componentName, nested);
3334
3387
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
3335
3388
  const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
3336
- const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
3389
+ const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren, ir.metadata.propsParams);
3337
3390
  for (const child of bodyChildInstances) {
3338
3391
  const childVar = `child_${child.fieldName}`;
3339
3392
  lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
@@ -3573,7 +3626,7 @@ ${goFields.join(`
3573
3626
  lines.push(` ${nested.name}s []${elemType} \`json:"${jsonTag}"\``);
3574
3627
  }
3575
3628
  }
3576
- const staticChildren = this.collectStaticChildInstances(ir.root);
3629
+ const staticChildren = this.collectStaticChildInstances(ir.root, ir.metadata.propsParams);
3577
3630
  for (const child of staticChildren) {
3578
3631
  lines.push(` ${child.fieldName} ${child.name}Props \`json:"-"\``);
3579
3632
  }
@@ -3585,9 +3638,9 @@ ${goFields.join(`
3585
3638
  toJsonTag(name) {
3586
3639
  return name.charAt(0).toLowerCase() + name.slice(1);
3587
3640
  }
3588
- collectStaticChildInstances(node) {
3641
+ collectStaticChildInstances(node, propsParams = []) {
3589
3642
  const result = [];
3590
- this.collectStaticChildInstancesRecursive(node, result, false, new Map);
3643
+ this.collectStaticChildInstancesRecursive(node, result, false, new Map, propsParams);
3591
3644
  return result;
3592
3645
  }
3593
3646
  extractTextChildren(children) {
@@ -3632,12 +3685,12 @@ ${goFields.join(`
3632
3685
  return null;
3633
3686
  return withSentinel.split(GoTemplateAdapter.SCOPE_SENTINEL).map((seg) => JSON.stringify(seg)).join(" + scopeID + ");
3634
3687
  }
3635
- collectStaticChildInstancesRecursive(node, result, inLoop, providerCtx) {
3688
+ collectStaticChildInstancesRecursive(node, result, inLoop, providerCtx, propsParams = []) {
3636
3689
  if (node.type === "component") {
3637
3690
  const comp = node;
3638
3691
  if (comp.dynamicTag) {
3639
3692
  for (const child of comp.children) {
3640
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3693
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3641
3694
  }
3642
3695
  return;
3643
3696
  }
@@ -3655,69 +3708,111 @@ ${goFields.join(`
3655
3708
  contextBindings: providerCtx.size > 0 ? providerCtx : undefined
3656
3709
  });
3657
3710
  for (const child of effectiveChildren) {
3658
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3711
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3659
3712
  }
3660
3713
  }
3661
3714
  if (comp.name === "Portal" && comp.children) {
3662
3715
  for (const child of comp.children) {
3663
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3716
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3664
3717
  }
3665
3718
  }
3666
3719
  } else if (node.type === "loop") {
3667
3720
  const loop = node;
3668
3721
  for (const child of loop.children) {
3669
- this.collectStaticChildInstancesRecursive(child, result, true, providerCtx);
3722
+ this.collectStaticChildInstancesRecursive(child, result, inLoop || !!loop.childComponent, providerCtx, propsParams);
3670
3723
  }
3671
3724
  } else if (node.type === "element") {
3672
3725
  const element = node;
3673
3726
  for (const child of element.children) {
3674
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3727
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3675
3728
  }
3676
3729
  } else if (node.type === "fragment") {
3677
3730
  const fragment = node;
3678
3731
  for (const child of fragment.children) {
3679
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3732
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3680
3733
  }
3681
3734
  } else if (node.type === "conditional") {
3682
3735
  const cond = node;
3683
- this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop, providerCtx);
3736
+ this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop, providerCtx, propsParams);
3684
3737
  if (cond.whenFalse) {
3685
- this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx);
3738
+ this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx, propsParams);
3686
3739
  }
3687
3740
  } else if (node.type === "if-statement") {
3688
3741
  const stmt = node;
3689
- this.collectStaticChildInstancesRecursive(stmt.consequent, result, inLoop, providerCtx);
3742
+ this.collectStaticChildInstancesRecursive(stmt.consequent, result, inLoop, providerCtx, propsParams);
3690
3743
  if (stmt.alternate) {
3691
- this.collectStaticChildInstancesRecursive(stmt.alternate, result, inLoop, providerCtx);
3744
+ this.collectStaticChildInstancesRecursive(stmt.alternate, result, inLoop, providerCtx, propsParams);
3692
3745
  }
3693
3746
  } else if (node.type === "provider") {
3694
3747
  const p = node;
3695
- const childCtx = this.extendProviderContext(providerCtx, p);
3748
+ const childCtx = this.extendProviderContext(providerCtx, p, propsParams);
3696
3749
  for (const child of p.children) {
3697
- this.collectStaticChildInstancesRecursive(child, result, inLoop, childCtx);
3750
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, childCtx, propsParams);
3698
3751
  }
3699
3752
  } else if (node.type === "async") {
3700
3753
  const a = node;
3701
- this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop, providerCtx);
3754
+ this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop, providerCtx, propsParams);
3702
3755
  for (const child of a.children) {
3703
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3756
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3704
3757
  }
3705
3758
  }
3706
3759
  }
3707
- extendProviderContext(current, p) {
3760
+ extendProviderContext(current, p, propsParams) {
3708
3761
  const v = p.valueProp?.value;
3709
- if (!v || v.kind !== "literal")
3710
- return current;
3711
- let goLit = null;
3712
- if (typeof v.value === "string")
3713
- goLit = `"${escapeGoString(v.value)}"`;
3714
- else if (typeof v.value === "number" || typeof v.value === "boolean")
3715
- goLit = String(v.value);
3716
- if (goLit === null)
3762
+ if (!v)
3717
3763
  return current;
3718
- const next = new Map(current);
3719
- next.set(p.contextName, goLit);
3720
- return next;
3764
+ if (v.kind === "literal") {
3765
+ let goLit = null;
3766
+ if (typeof v.value === "string")
3767
+ goLit = `"${escapeGoString(v.value)}"`;
3768
+ else if (typeof v.value === "number" || typeof v.value === "boolean")
3769
+ goLit = String(v.value);
3770
+ if (goLit === null)
3771
+ return current;
3772
+ const next = new Map(current);
3773
+ next.set(p.contextName, goLit);
3774
+ return next;
3775
+ }
3776
+ if (v.kind === "expression" && v.parsed) {
3777
+ const goMap = this.providerObjectValueToGoMap(v.parsed, propsParams);
3778
+ if (goMap !== null) {
3779
+ const next = new Map(current);
3780
+ next.set(p.contextName, goMap);
3781
+ return next;
3782
+ }
3783
+ }
3784
+ return current;
3785
+ }
3786
+ providerObjectValueToGoMap(parsed, propsParams) {
3787
+ if (parsed.kind !== "object-literal")
3788
+ return null;
3789
+ const entries = [];
3790
+ for (const prop of parsed.properties) {
3791
+ if (prop.shorthand)
3792
+ return null;
3793
+ const goVal = this.lowerProviderMapMemberValue(prop.value, propsParams);
3794
+ if (goVal === null)
3795
+ return null;
3796
+ entries.push(`${JSON.stringify(prop.key)}: ${goVal}`);
3797
+ }
3798
+ if (entries.length === 0)
3799
+ return null;
3800
+ return `map[string]interface{}{${entries.join(", ")}}`;
3801
+ }
3802
+ lowerProviderMapMemberValue(node, propsParams) {
3803
+ if (node.kind === "object-literal")
3804
+ return objectLiteralToGoMap(this.emitCtx, node);
3805
+ const literal = parsedLiteralToGo(this.emitCtx, node);
3806
+ if (literal !== null)
3807
+ return literal;
3808
+ if (node.kind === "logical" && node.op === "??" && node.right.kind === "object-literal" && node.right.properties.length === 0 && node.left.kind === "member" && !node.left.computed && node.left.object.kind === "identifier" && node.left.object.name === this.state.propsObjectName) {
3809
+ const propName = node.left.property;
3810
+ if (propsParams.some((param) => param.name === propName)) {
3811
+ const fieldRef = `in.${capitalizeFieldName(propName)}`;
3812
+ return `func() map[string]interface{} { ` + `if m := bf.AsMap(${fieldRef}); m != nil { return m }; ` + `return map[string]interface{}{} }()`;
3813
+ }
3814
+ }
3815
+ return null;
3721
3816
  }
3722
3817
  templatePartsToGoCode(parts, propsParams) {
3723
3818
  const segments = [];
@@ -4187,15 +4282,19 @@ ${goFields.join(`
4187
4282
  if (objHO && (objHO.method === "find" || objHO.method === "findLast")) {
4188
4283
  const findResult = this.renderHigherOrderExpr(objHO, emit);
4189
4284
  if (findResult) {
4190
- return `{{with ${findResult}}}{{.${capitalizeFieldName(property)}}}{{end}}`;
4285
+ return `{{with ${findResult}}}{{.${goFieldNameForKey(property)}}}{{end}}`;
4191
4286
  }
4192
- const templateBlock = this.renderFindTemplateBlock(objHO, emit, capitalizeFieldName(property));
4287
+ const templateBlock = this.renderFindTemplateBlock(objHO, emit, goFieldNameForKey(property));
4193
4288
  if (templateBlock)
4194
4289
  return templateBlock;
4195
4290
  }
4196
4291
  if (object.kind === "identifier" && this.state.propsObjectName && object.name === this.state.propsObjectName) {
4197
4292
  return this.rootFieldRef(property);
4198
4293
  }
4294
+ if (this.isMapRootedContextChain(object)) {
4295
+ const objGo = emit(object);
4296
+ return `bf_get ${wrapIfMultiToken(objGo)} ${JSON.stringify(property)}`;
4297
+ }
4199
4298
  if (object.kind === "identifier") {
4200
4299
  const staticValue = this.resolveStaticRecordLiteralIndex(`${object.name}.${property}`);
4201
4300
  if (staticValue !== null)
@@ -4203,12 +4302,12 @@ ${goFields.join(`
4203
4302
  }
4204
4303
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
4205
4304
  if (object.kind === "identifier" && currentLoopParam && object.name === currentLoopParam) {
4206
- return `.${capitalizeFieldName(property)}`;
4305
+ return `.${goFieldNameForKey(property)}`;
4207
4306
  }
4208
4307
  const obj = emit(object);
4209
4308
  if (property === "length")
4210
4309
  return `len ${obj}`;
4211
- return `${obj}.${capitalizeFieldName(property)}`;
4310
+ return `${obj}.${goFieldNameForKey(property)}`;
4212
4311
  }
4213
4312
  indexAccess(object, index, emit) {
4214
4313
  return `index ${wrapIfMultiToken(emit(object))} ${wrapIfMultiToken(emit(index))}`;
@@ -4300,7 +4399,16 @@ ${goFields.join(`
4300
4399
  return `bf_arr ${parts.join(" ")}`;
4301
4400
  }
4302
4401
  objectLiteral(_properties, raw, _emit) {
4303
- return this.unsupported(raw, "object literal");
4402
+ this.state.errors.push({
4403
+ code: "BF101",
4404
+ severity: "error",
4405
+ message: `Expression not supported: ${raw}`,
4406
+ loc: this.makeLoc(),
4407
+ suggestion: {
4408
+ message: `Go templates have no object/map literal syntax, so the \`?? {}\` fallback can't render server-side. ${GO_REMEDIATION_OPTIONS}`
4409
+ }
4410
+ });
4411
+ return `""`;
4304
4412
  }
4305
4413
  static PREDICATE_METHODS = new Set([
4306
4414
  "filter",
@@ -4513,6 +4621,9 @@ ${goFields.join(`
4513
4621
  }
4514
4622
  }
4515
4623
  flatMethod(object, depth, emit) {
4624
+ if (typeof depth === "object") {
4625
+ return `bf_flat_dynamic ${wrapIfMultiToken(emit(object))} ${wrapIfMultiToken(emit(depth.expr))}`;
4626
+ }
4516
4627
  const d = depth === "infinity" ? -1 : depth;
4517
4628
  return `bf_flat ${wrapIfMultiToken(emit(object))} ${d}`;
4518
4629
  }
@@ -5211,23 +5322,44 @@ ${goFields.join(`
5211
5322
  return plain(expr.raw);
5212
5323
  }
5213
5324
  }
5325
+ buildSegmentAccessor(base, segments) {
5326
+ let acc = base;
5327
+ for (const seg of segments) {
5328
+ acc = seg.kind === "field" ? `${acc}.${goFieldNameForKey(seg.key)}` : `(index ${acc} ${seg.index})`;
5329
+ }
5330
+ return acc;
5331
+ }
5214
5332
  buildDestructureBindingMap(loop, rangeVar) {
5215
- const m = new Map;
5333
+ const bindings = new Map;
5334
+ const restExcludes = new Map;
5335
+ const base = `$${rangeVar}`;
5216
5336
  for (const b of loop.paramBindings ?? []) {
5217
- if (b.rest) {
5218
- m.set(b.name, `$${rangeVar}`);
5337
+ const parent = this.buildSegmentAccessor(base, b.segments ?? []);
5338
+ if (!b.rest) {
5339
+ bindings.set(b.name, parent);
5340
+ } else if (b.rest.kind === "array") {
5341
+ bindings.set(b.name, `(bf_slice ${parent} ${b.rest.from})`);
5219
5342
  } else {
5220
- m.set(b.name, `$${rangeVar}.${capitalizeFieldName(b.path.slice(1))}`);
5343
+ bindings.set(b.name, parent);
5344
+ restExcludes.set(b.name, { parent, excludeKeys: b.rest.exclude.map((k) => k.key) });
5221
5345
  }
5222
5346
  }
5223
- return m;
5347
+ return { bindings, restExcludes };
5348
+ }
5349
+ lookupRestExclude(name) {
5350
+ for (let i = this.loopRestExcludeStack.length - 1;i >= 0; i--) {
5351
+ const info = this.loopRestExcludeStack[i].get(name);
5352
+ if (info)
5353
+ return info;
5354
+ }
5355
+ return;
5224
5356
  }
5225
5357
  renderLoop(loop) {
5226
5358
  if (loop.clientOnly) {
5227
5359
  return `{{bfComment "loop:${loop.markerId}"}}{{bfComment "/loop:${loop.markerId}"}}`;
5228
5360
  }
5229
5361
  const destructure = !!(loop.paramBindings && loop.paramBindings.length > 0);
5230
- const supportableDestructure = destructure && isLowerableObjectRestDestructure(loop);
5362
+ const supportableDestructure = destructure && isLowerableLoopDestructure(loop);
5231
5363
  if (destructure && !supportableDestructure) {
5232
5364
  this.state.errors.push({
5233
5365
  code: "BF104",
@@ -5242,6 +5374,21 @@ ${goFields.join(`
5242
5374
  }
5243
5375
  });
5244
5376
  }
5377
+ const arrayName = loop.array.trim();
5378
+ if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
5379
+ const arrayConst = this.state.localConstants.find((c) => c.name === arrayName);
5380
+ if (arrayConst && !arrayConst.isModule && arrayConst.parsed && !this.isStringExpr(arrayConst.parsed, new Set)) {
5381
+ this.state.errors.push({
5382
+ code: "BF101",
5383
+ severity: "error",
5384
+ message: `Loop array \`${arrayName}\` is a local computed value (\`${arrayConst.value}\`) that the Go template adapter cannot bind as a template variable — only a string-derived local resolves to a generated struct field.`,
5385
+ loc: loop.loc ?? this.makeLoc(),
5386
+ suggestion: {
5387
+ message: "Pre-compute the array server-side and pass it as a prop, or mark the loop position as @client-only so it runs in JS on the client."
5388
+ }
5389
+ });
5390
+ }
5391
+ }
5245
5392
  let goArray = this.convertExpressionToGo(loop.array);
5246
5393
  const param = loop.param;
5247
5394
  let index = loop.index || "_";
@@ -5251,15 +5398,16 @@ ${goFields.join(`
5251
5398
  rangeIndex = param;
5252
5399
  rangeValue = "_";
5253
5400
  }
5254
- const childComponent = this.findChildComponent(loop.children);
5255
- if (childComponent) {
5256
- goArray = `.${childComponent.name}s`;
5401
+ if (loop.childComponent) {
5402
+ goArray = `.${loop.childComponent.name}s`;
5257
5403
  }
5258
5404
  this.inLoop = true;
5259
5405
  const addedLoopVars = [];
5260
5406
  let pushedBindingMap = false;
5261
5407
  if (supportableDestructure) {
5262
- this.loopBindingStack.push(this.buildDestructureBindingMap(loop, rangeValue));
5408
+ const built = this.buildDestructureBindingMap(loop, rangeValue);
5409
+ this.loopBindingStack.push(built.bindings);
5410
+ this.loopRestExcludeStack.push(built.restExcludes);
5263
5411
  pushedBindingMap = true;
5264
5412
  this.loopParamStack.push("");
5265
5413
  if (rangeIndex !== "_") {
@@ -5278,7 +5426,9 @@ ${goFields.join(`
5278
5426
  }
5279
5427
  }
5280
5428
  this.loopScalarItemStack.push(this.scalarLiteralLoopGoType(loop.arrayParsed, loop.itemType) !== null);
5429
+ this.loopWrapperStack.push(!!loop.childComponent);
5281
5430
  const children = this.renderChildren(loop.children);
5431
+ this.loopWrapperStack.pop();
5282
5432
  this.loopScalarItemStack.pop();
5283
5433
  const itemMarker = this.loopItemMarker(loop);
5284
5434
  for (const v of addedLoopVars) {
@@ -5289,8 +5439,10 @@ ${goFields.join(`
5289
5439
  this.loopVarRefCount.set(v, rc);
5290
5440
  }
5291
5441
  this.loopParamStack.pop();
5292
- if (pushedBindingMap)
5442
+ if (pushedBindingMap) {
5293
5443
  this.loopBindingStack.pop();
5444
+ this.loopRestExcludeStack.pop();
5445
+ }
5294
5446
  this.inLoop = false;
5295
5447
  if (loop.sortComparator) {
5296
5448
  const sortEmit = (e) => this.renderParsedExpr(e);
@@ -5319,24 +5471,6 @@ ${goFields.join(`
5319
5471
  }
5320
5472
  return "";
5321
5473
  }
5322
- findChildComponent(nodes) {
5323
- for (const node of nodes) {
5324
- if (node.type === "component") {
5325
- return node;
5326
- }
5327
- if (node.type === "element" && node.children) {
5328
- const found = this.findChildComponent(node.children);
5329
- if (found)
5330
- return found;
5331
- }
5332
- if (node.type === "fragment" && node.children) {
5333
- const found = this.findChildComponent(node.children);
5334
- if (found)
5335
- return found;
5336
- }
5337
- }
5338
- return null;
5339
- }
5340
5474
  queueDynamicChildrenDefine(comp) {
5341
5475
  const effectiveChildren = comp.children.length > 0 ? comp.children : this.jsxChildrenPropNodes(comp.props);
5342
5476
  if (effectiveChildren.length === 0)
@@ -5384,7 +5518,7 @@ ${goFields.join(`
5384
5518
  return this.renderChildren(comp.children);
5385
5519
  }
5386
5520
  let templateCall;
5387
- if (this.inLoop) {
5521
+ if (this.inLoop && (this.loopWrapperStack[this.loopWrapperStack.length - 1] ?? false)) {
5388
5522
  const loopBodyDefine = this.queueLoopBodyChildrenDefine(comp);
5389
5523
  if (loopBodyDefine) {
5390
5524
  const bodyData = this.loopScalarItemStack[this.loopScalarItemStack.length - 1] ? ".BfLoopItem" : ".";
@@ -5392,6 +5526,12 @@ ${goFields.join(`
5392
5526
  } else {
5393
5527
  templateCall = `{{template "${comp.name}" .}}`;
5394
5528
  }
5529
+ } else if (this.inLoop && comp.slotId) {
5530
+ const suffix = slotIdToFieldSuffix(comp.slotId);
5531
+ const loopBodyDefine = this.queueLoopBodyChildrenDefine(comp);
5532
+ templateCall = loopBodyDefine ? `{{template "${comp.name}" (bf_with_children $.${comp.name}${suffix} (bf_tmpl "${loopBodyDefine}" .))}}` : `{{template "${comp.name}" $.${comp.name}${suffix}}}`;
5533
+ } else if (this.inLoop) {
5534
+ templateCall = `{{template "${comp.name}" .}}`;
5395
5535
  } else if (comp.slotId) {
5396
5536
  const suffix = slotIdToFieldSuffix(comp.slotId);
5397
5537
  const childrenDefine = this.queueDynamicChildrenDefine(comp);
@@ -5487,6 +5627,12 @@ ${children}`;
5487
5627
  if (currentLoopParam && trimmed === currentLoopParam) {
5488
5628
  return `{{bf_spread_attrs .}}`;
5489
5629
  }
5630
+ const restInfo = this.lookupRestExclude(trimmed);
5631
+ if (restInfo) {
5632
+ const excludeArgs = restInfo.excludeKeys.map((k) => JSON.stringify(k)).join(" ");
5633
+ const omitArgs = excludeArgs ? `${restInfo.parent} ${excludeArgs}` : restInfo.parent;
5634
+ return `{{bf_spread_attrs (bf_omit ${omitArgs})}}`;
5635
+ }
5490
5636
  const goExpr = this.convertExpressionToGo(value.expr);
5491
5637
  return `{{bf_spread_attrs ${goExpr}}}`;
5492
5638
  }