@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.
@@ -38,7 +38,7 @@ import {
38
38
  emitAttrValue,
39
39
  augmentInheritedPropAccesses,
40
40
  collectContextConsumers,
41
- isLowerableObjectRestDestructure,
41
+ isLowerableLoopDestructure,
42
42
  collectModuleStringConsts as collectModuleStringConstsShared,
43
43
  prepareLoweringMatchers,
44
44
  envSignalReaderFor,
@@ -123,6 +123,13 @@ function capitalizeFieldName(name) {
123
123
  }
124
124
  return name.charAt(0).toUpperCase() + name.slice(1);
125
125
  }
126
+ function goFieldNameForKey(key) {
127
+ const parts = key.split(/[^A-Za-z0-9_]+/).filter(Boolean);
128
+ if (parts.length === 0)
129
+ return "Field";
130
+ const name = parts.map(capitalizeFieldName).join("");
131
+ return /^[0-9]/.test(name) ? `Field${name}` : name;
132
+ }
126
133
  function slotIdToFieldSuffix(slotId) {
127
134
  const cleanId = slotId.startsWith("^") ? slotId.slice(1) : slotId;
128
135
  const match = cleanId.match(/^s(\d+)$/);
@@ -695,9 +702,12 @@ function forEachValueChild(n, visit) {
695
702
  // src/adapter/expr/url-builder.ts
696
703
  import {
697
704
  parseExpression,
698
- stringifyParsedExpr
705
+ stringifyParsedExpr,
706
+ isValidHelperId
699
707
  } from "@barefootjs/jsx";
700
- var GO_HELPER_NAMES = { query: "bf_query" };
708
+ function goHelperName(helper) {
709
+ return isValidHelperId(helper) ? `bf_${helper}` : null;
710
+ }
701
711
  var BOOL_COMPARISON_OPS = new Set([
702
712
  "==",
703
713
  "===",
@@ -740,7 +750,7 @@ function lowerRegisteredCall(ctx, jsExpr, preParsed) {
740
750
  return null;
741
751
  }
742
752
  function renderLoweringNode(ctx, node) {
743
- const helper = GO_HELPER_NAMES[node.helper];
753
+ const helper = goHelperName(node.helper);
744
754
  if (!helper)
745
755
  return null;
746
756
  const lowerExpr = (n) => ctx.convertExpressionToGo(stringifyParsedExpr(n), undefined, n);
@@ -780,7 +790,7 @@ function typeInfoToGo(ctx, typeInfo, defaultValue) {
780
790
  case "object":
781
791
  return "map[string]interface{}";
782
792
  case "interface":
783
- if (typeInfo.raw && ctx.state.localTypeNames.has(typeInfo.raw)) {
793
+ if (typeInfo.raw && (ctx.state.localStructFields.has(typeInfo.raw) || ctx.state.localTypeAliases.has(typeInfo.raw))) {
784
794
  return typeInfo.raw;
785
795
  }
786
796
  if (typeInfo.raw) {
@@ -813,7 +823,7 @@ function tsTypeStringToGo(ctx, tsType) {
813
823
  const arrayMatch = t.match(/^Array<(.+)>$/);
814
824
  if (arrayMatch)
815
825
  return `[]${tsTypeStringToGo(ctx, arrayMatch[1])}`;
816
- if (ctx.state.localTypeNames.has(t))
826
+ if (ctx.state.localStructFields.has(t) || ctx.state.localTypeAliases.has(t))
817
827
  return t;
818
828
  return "interface{}";
819
829
  }
@@ -835,6 +845,24 @@ function inferTypeFromValue(value) {
835
845
  }
836
846
 
837
847
  // src/adapter/value/parsed-literal-to-go.ts
848
+ function structPropertyType(ctx, structGoType, key) {
849
+ const td = ctx.state.currentTypeDefinitions.find((t) => t.name === structGoType);
850
+ return td?.properties?.find((p) => p.name === key)?.type;
851
+ }
852
+ function bakeInlineObjectAsGoMap(ctx, expr) {
853
+ if (expr.kind !== "object-literal")
854
+ return null;
855
+ const entries = [];
856
+ for (const prop of expr.properties) {
857
+ if (prop.shorthand)
858
+ return null;
859
+ const go = prop.value.kind === "object-literal" ? bakeInlineObjectAsGoMap(ctx, prop.value) : parsedLiteralToGo(ctx, prop.value);
860
+ if (go === null)
861
+ return null;
862
+ entries.push(`${JSON.stringify(goFieldNameForKey(prop.key))}: ${go}`);
863
+ }
864
+ return `map[string]interface{}{${entries.join(", ")}}`;
865
+ }
838
866
  function parsedLiteralToGo(ctx, expr, typeInfo) {
839
867
  if (expr.kind === "unary" && expr.op === "-" && expr.argument.kind === "literal" && expr.argument.literalType === "number") {
840
868
  return expr.argument.raw !== undefined ? `-${expr.argument.raw}` : null;
@@ -875,9 +903,16 @@ function parsedLiteralToGo(ctx, expr, typeInfo) {
875
903
  const goField = structFields.get(prop.key);
876
904
  if (!goField)
877
905
  return null;
878
- if (prop.value.kind === "object-literal" || prop.value.kind === "array-literal")
879
- return null;
880
- const go = parsedLiteralToGo(ctx, prop.value);
906
+ const propType = structPropertyType(ctx, goType, prop.key);
907
+ let go;
908
+ if (prop.value.kind === "array-literal") {
909
+ go = parsedLiteralToGo(ctx, prop.value, propType);
910
+ } else if (prop.value.kind === "object-literal") {
911
+ const nestedGoType = propType ? typeInfoToGo(ctx, propType) : undefined;
912
+ go = nestedGoType && ctx.state.localStructFields.has(nestedGoType) ? parsedLiteralToGo(ctx, prop.value, propType) : bakeInlineObjectAsGoMap(ctx, prop.value);
913
+ } else {
914
+ go = parsedLiteralToGo(ctx, prop.value);
915
+ }
881
916
  if (go === null)
882
917
  return null;
883
918
  entries.push(`${goField}: ${go}`);
@@ -2115,8 +2150,10 @@ class GoTemplateAdapter extends BaseAdapter {
2115
2150
  inLoop = false;
2116
2151
  loopParamStack = [];
2117
2152
  loopScalarItemStack = [];
2153
+ loopWrapperStack = [];
2118
2154
  loopVarRefCount = new Map;
2119
2155
  loopBindingStack = [];
2156
+ loopRestExcludeStack = [];
2120
2157
  childComponentShapes = new Map;
2121
2158
  childContextConsumers = new Map;
2122
2159
  constructor(options = {}) {
@@ -2309,11 +2346,22 @@ ${scriptRegistrations}${templateBody}
2309
2346
  contextFieldName(c) {
2310
2347
  return capitalizeFieldName(c.localName);
2311
2348
  }
2349
+ isMapRootedContextChain(node) {
2350
+ if (node.kind === "identifier") {
2351
+ return this.state.contextConsumers.some((c) => c.localName === node.name && c.defaultKind === "object");
2352
+ }
2353
+ if (node.kind === "member" && !node.computed) {
2354
+ return this.isMapRootedContextChain(node.object);
2355
+ }
2356
+ return false;
2357
+ }
2312
2358
  contextConsumerGoType(c) {
2313
2359
  if (typeof c.defaultValue === "number")
2314
2360
  return "int";
2315
2361
  if (typeof c.defaultValue === "boolean")
2316
2362
  return "bool";
2363
+ if (c.defaultKind === "object")
2364
+ return "map[string]interface{}";
2317
2365
  return "string";
2318
2366
  }
2319
2367
  contextConsumerGoDefault(c) {
@@ -2323,6 +2371,8 @@ ${scriptRegistrations}${templateBody}
2323
2371
  return String(c.defaultValue);
2324
2372
  if (typeof c.defaultValue === "string")
2325
2373
  return `"${escapeGoString(c.defaultValue)}"`;
2374
+ if (c.defaultKind === "object")
2375
+ return "map[string]interface{}{}";
2326
2376
  return '""';
2327
2377
  }
2328
2378
  nonCollidingContextConsumers(taken) {
@@ -2369,12 +2419,15 @@ ${goFields.join(`
2369
2419
  }
2370
2420
  structFieldsFor(td) {
2371
2421
  const fields = [];
2422
+ const seenGoNames = new Set;
2372
2423
  for (const prop of td.properties ?? []) {
2373
- if (!GO_IDENTIFIER.test(prop.name))
2424
+ const goName = goFieldNameForKey(prop.name);
2425
+ if (seenGoNames.has(goName))
2374
2426
  continue;
2427
+ seenGoNames.add(goName);
2375
2428
  fields.push({
2376
2429
  tsName: prop.name,
2377
- goName: capitalizeFieldName(prop.name),
2430
+ goName,
2378
2431
  goType: typeInfoToGo(this.emitCtx, prop.type)
2379
2432
  });
2380
2433
  }
@@ -2602,10 +2655,10 @@ ${goFields.join(`
2602
2655
  }
2603
2656
  return "interface{}";
2604
2657
  }
2605
- collectBodyChildInstances(bodyChildren) {
2658
+ collectBodyChildInstances(bodyChildren, propsParams = []) {
2606
2659
  const result = [];
2607
2660
  for (const child of bodyChildren) {
2608
- this.collectStaticChildInstancesRecursive(child, result, false, new Map);
2661
+ this.collectStaticChildInstancesRecursive(child, result, false, new Map, propsParams);
2609
2662
  }
2610
2663
  return result;
2611
2664
  }
@@ -2775,7 +2828,7 @@ ${goFields.join(`
2775
2828
  for (const c of this.nonCollidingContextConsumers(takenInit)) {
2776
2829
  const field = this.contextFieldName(c);
2777
2830
  const def = this.contextConsumerGoDefault(c);
2778
- const defaulted = c.defaultValue === null || def === '""' || def === "0" || def === "false" ? `in.${field}` : applyGoFallback(`in.${field}`, def);
2831
+ const defaulted = c.defaultValue === null || def === '""' || def === "0" || def === "false" || def === "map[string]interface{}{}" ? `in.${field}` : applyGoFallback(`in.${field}`, def);
2779
2832
  lines.push(` ${field}: ${defaulted},`);
2780
2833
  }
2781
2834
  this.emitStaticChildInstances(lines, ir);
@@ -2784,7 +2837,7 @@ ${goFields.join(`
2784
2837
  lines.push("}");
2785
2838
  }
2786
2839
  emitStaticChildInstances(lines, ir) {
2787
- const staticChildren = this.collectStaticChildInstances(ir.root);
2840
+ const staticChildren = this.collectStaticChildInstances(ir.root, ir.metadata.propsParams);
2788
2841
  for (const child of staticChildren) {
2789
2842
  lines.push(` ${child.fieldName}: New${child.name}Props(${child.name}Input{`);
2790
2843
  lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
@@ -2916,7 +2969,7 @@ ${goFields.join(`
2916
2969
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
2917
2970
  const wrapperType = this.loopBodyWrapperName(componentName, nested);
2918
2971
  const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
2919
- const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
2972
+ const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren, ir.metadata.propsParams);
2920
2973
  for (const child of bodyChildInstances) {
2921
2974
  const childVar = `child_${child.fieldName}`;
2922
2975
  lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
@@ -2993,7 +3046,7 @@ ${goFields.join(`
2993
3046
  const wrapperType = this.loopBodyWrapperName(componentName, nested);
2994
3047
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
2995
3048
  const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
2996
- const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
3049
+ const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren, ir.metadata.propsParams);
2997
3050
  for (const child of bodyChildInstances) {
2998
3051
  const childVar = `child_${child.fieldName}`;
2999
3052
  lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
@@ -3233,7 +3286,7 @@ ${goFields.join(`
3233
3286
  lines.push(` ${nested.name}s []${elemType} \`json:"${jsonTag}"\``);
3234
3287
  }
3235
3288
  }
3236
- const staticChildren = this.collectStaticChildInstances(ir.root);
3289
+ const staticChildren = this.collectStaticChildInstances(ir.root, ir.metadata.propsParams);
3237
3290
  for (const child of staticChildren) {
3238
3291
  lines.push(` ${child.fieldName} ${child.name}Props \`json:"-"\``);
3239
3292
  }
@@ -3245,9 +3298,9 @@ ${goFields.join(`
3245
3298
  toJsonTag(name) {
3246
3299
  return name.charAt(0).toLowerCase() + name.slice(1);
3247
3300
  }
3248
- collectStaticChildInstances(node) {
3301
+ collectStaticChildInstances(node, propsParams = []) {
3249
3302
  const result = [];
3250
- this.collectStaticChildInstancesRecursive(node, result, false, new Map);
3303
+ this.collectStaticChildInstancesRecursive(node, result, false, new Map, propsParams);
3251
3304
  return result;
3252
3305
  }
3253
3306
  extractTextChildren(children) {
@@ -3292,12 +3345,12 @@ ${goFields.join(`
3292
3345
  return null;
3293
3346
  return withSentinel.split(GoTemplateAdapter.SCOPE_SENTINEL).map((seg) => JSON.stringify(seg)).join(" + scopeID + ");
3294
3347
  }
3295
- collectStaticChildInstancesRecursive(node, result, inLoop, providerCtx) {
3348
+ collectStaticChildInstancesRecursive(node, result, inLoop, providerCtx, propsParams = []) {
3296
3349
  if (node.type === "component") {
3297
3350
  const comp = node;
3298
3351
  if (comp.dynamicTag) {
3299
3352
  for (const child of comp.children) {
3300
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3353
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3301
3354
  }
3302
3355
  return;
3303
3356
  }
@@ -3315,69 +3368,111 @@ ${goFields.join(`
3315
3368
  contextBindings: providerCtx.size > 0 ? providerCtx : undefined
3316
3369
  });
3317
3370
  for (const child of effectiveChildren) {
3318
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3371
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3319
3372
  }
3320
3373
  }
3321
3374
  if (comp.name === "Portal" && comp.children) {
3322
3375
  for (const child of comp.children) {
3323
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3376
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3324
3377
  }
3325
3378
  }
3326
3379
  } else if (node.type === "loop") {
3327
3380
  const loop = node;
3328
3381
  for (const child of loop.children) {
3329
- this.collectStaticChildInstancesRecursive(child, result, true, providerCtx);
3382
+ this.collectStaticChildInstancesRecursive(child, result, inLoop || !!loop.childComponent, providerCtx, propsParams);
3330
3383
  }
3331
3384
  } else if (node.type === "element") {
3332
3385
  const element = node;
3333
3386
  for (const child of element.children) {
3334
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3387
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3335
3388
  }
3336
3389
  } else if (node.type === "fragment") {
3337
3390
  const fragment = node;
3338
3391
  for (const child of fragment.children) {
3339
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3392
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3340
3393
  }
3341
3394
  } else if (node.type === "conditional") {
3342
3395
  const cond = node;
3343
- this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop, providerCtx);
3396
+ this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop, providerCtx, propsParams);
3344
3397
  if (cond.whenFalse) {
3345
- this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx);
3398
+ this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx, propsParams);
3346
3399
  }
3347
3400
  } else if (node.type === "if-statement") {
3348
3401
  const stmt = node;
3349
- this.collectStaticChildInstancesRecursive(stmt.consequent, result, inLoop, providerCtx);
3402
+ this.collectStaticChildInstancesRecursive(stmt.consequent, result, inLoop, providerCtx, propsParams);
3350
3403
  if (stmt.alternate) {
3351
- this.collectStaticChildInstancesRecursive(stmt.alternate, result, inLoop, providerCtx);
3404
+ this.collectStaticChildInstancesRecursive(stmt.alternate, result, inLoop, providerCtx, propsParams);
3352
3405
  }
3353
3406
  } else if (node.type === "provider") {
3354
3407
  const p = node;
3355
- const childCtx = this.extendProviderContext(providerCtx, p);
3408
+ const childCtx = this.extendProviderContext(providerCtx, p, propsParams);
3356
3409
  for (const child of p.children) {
3357
- this.collectStaticChildInstancesRecursive(child, result, inLoop, childCtx);
3410
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, childCtx, propsParams);
3358
3411
  }
3359
3412
  } else if (node.type === "async") {
3360
3413
  const a = node;
3361
- this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop, providerCtx);
3414
+ this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop, providerCtx, propsParams);
3362
3415
  for (const child of a.children) {
3363
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3416
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3364
3417
  }
3365
3418
  }
3366
3419
  }
3367
- extendProviderContext(current, p) {
3420
+ extendProviderContext(current, p, propsParams) {
3368
3421
  const v = p.valueProp?.value;
3369
- if (!v || v.kind !== "literal")
3370
- return current;
3371
- let goLit = null;
3372
- if (typeof v.value === "string")
3373
- goLit = `"${escapeGoString(v.value)}"`;
3374
- else if (typeof v.value === "number" || typeof v.value === "boolean")
3375
- goLit = String(v.value);
3376
- if (goLit === null)
3422
+ if (!v)
3377
3423
  return current;
3378
- const next = new Map(current);
3379
- next.set(p.contextName, goLit);
3380
- return next;
3424
+ if (v.kind === "literal") {
3425
+ let goLit = null;
3426
+ if (typeof v.value === "string")
3427
+ goLit = `"${escapeGoString(v.value)}"`;
3428
+ else if (typeof v.value === "number" || typeof v.value === "boolean")
3429
+ goLit = String(v.value);
3430
+ if (goLit === null)
3431
+ return current;
3432
+ const next = new Map(current);
3433
+ next.set(p.contextName, goLit);
3434
+ return next;
3435
+ }
3436
+ if (v.kind === "expression" && v.parsed) {
3437
+ const goMap = this.providerObjectValueToGoMap(v.parsed, propsParams);
3438
+ if (goMap !== null) {
3439
+ const next = new Map(current);
3440
+ next.set(p.contextName, goMap);
3441
+ return next;
3442
+ }
3443
+ }
3444
+ return current;
3445
+ }
3446
+ providerObjectValueToGoMap(parsed, propsParams) {
3447
+ if (parsed.kind !== "object-literal")
3448
+ return null;
3449
+ const entries = [];
3450
+ for (const prop of parsed.properties) {
3451
+ if (prop.shorthand)
3452
+ return null;
3453
+ const goVal = this.lowerProviderMapMemberValue(prop.value, propsParams);
3454
+ if (goVal === null)
3455
+ return null;
3456
+ entries.push(`${JSON.stringify(prop.key)}: ${goVal}`);
3457
+ }
3458
+ if (entries.length === 0)
3459
+ return null;
3460
+ return `map[string]interface{}{${entries.join(", ")}}`;
3461
+ }
3462
+ lowerProviderMapMemberValue(node, propsParams) {
3463
+ if (node.kind === "object-literal")
3464
+ return objectLiteralToGoMap(this.emitCtx, node);
3465
+ const literal = parsedLiteralToGo(this.emitCtx, node);
3466
+ if (literal !== null)
3467
+ return literal;
3468
+ 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) {
3469
+ const propName = node.left.property;
3470
+ if (propsParams.some((param) => param.name === propName)) {
3471
+ const fieldRef = `in.${capitalizeFieldName(propName)}`;
3472
+ return `func() map[string]interface{} { ` + `if m := bf.AsMap(${fieldRef}); m != nil { return m }; ` + `return map[string]interface{}{} }()`;
3473
+ }
3474
+ }
3475
+ return null;
3381
3476
  }
3382
3477
  templatePartsToGoCode(parts, propsParams) {
3383
3478
  const segments = [];
@@ -3847,15 +3942,19 @@ ${goFields.join(`
3847
3942
  if (objHO && (objHO.method === "find" || objHO.method === "findLast")) {
3848
3943
  const findResult = this.renderHigherOrderExpr(objHO, emit);
3849
3944
  if (findResult) {
3850
- return `{{with ${findResult}}}{{.${capitalizeFieldName(property)}}}{{end}}`;
3945
+ return `{{with ${findResult}}}{{.${goFieldNameForKey(property)}}}{{end}}`;
3851
3946
  }
3852
- const templateBlock = this.renderFindTemplateBlock(objHO, emit, capitalizeFieldName(property));
3947
+ const templateBlock = this.renderFindTemplateBlock(objHO, emit, goFieldNameForKey(property));
3853
3948
  if (templateBlock)
3854
3949
  return templateBlock;
3855
3950
  }
3856
3951
  if (object.kind === "identifier" && this.state.propsObjectName && object.name === this.state.propsObjectName) {
3857
3952
  return this.rootFieldRef(property);
3858
3953
  }
3954
+ if (this.isMapRootedContextChain(object)) {
3955
+ const objGo = emit(object);
3956
+ return `bf_get ${wrapIfMultiToken(objGo)} ${JSON.stringify(property)}`;
3957
+ }
3859
3958
  if (object.kind === "identifier") {
3860
3959
  const staticValue = this.resolveStaticRecordLiteralIndex(`${object.name}.${property}`);
3861
3960
  if (staticValue !== null)
@@ -3863,12 +3962,12 @@ ${goFields.join(`
3863
3962
  }
3864
3963
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
3865
3964
  if (object.kind === "identifier" && currentLoopParam && object.name === currentLoopParam) {
3866
- return `.${capitalizeFieldName(property)}`;
3965
+ return `.${goFieldNameForKey(property)}`;
3867
3966
  }
3868
3967
  const obj = emit(object);
3869
3968
  if (property === "length")
3870
3969
  return `len ${obj}`;
3871
- return `${obj}.${capitalizeFieldName(property)}`;
3970
+ return `${obj}.${goFieldNameForKey(property)}`;
3872
3971
  }
3873
3972
  indexAccess(object, index, emit) {
3874
3973
  return `index ${wrapIfMultiToken(emit(object))} ${wrapIfMultiToken(emit(index))}`;
@@ -3960,7 +4059,16 @@ ${goFields.join(`
3960
4059
  return `bf_arr ${parts.join(" ")}`;
3961
4060
  }
3962
4061
  objectLiteral(_properties, raw, _emit) {
3963
- return this.unsupported(raw, "object literal");
4062
+ this.state.errors.push({
4063
+ code: "BF101",
4064
+ severity: "error",
4065
+ message: `Expression not supported: ${raw}`,
4066
+ loc: this.makeLoc(),
4067
+ suggestion: {
4068
+ message: `Go templates have no object/map literal syntax, so the \`?? {}\` fallback can't render server-side. ${GO_REMEDIATION_OPTIONS}`
4069
+ }
4070
+ });
4071
+ return `""`;
3964
4072
  }
3965
4073
  static PREDICATE_METHODS = new Set([
3966
4074
  "filter",
@@ -4173,6 +4281,9 @@ ${goFields.join(`
4173
4281
  }
4174
4282
  }
4175
4283
  flatMethod(object, depth, emit) {
4284
+ if (typeof depth === "object") {
4285
+ return `bf_flat_dynamic ${wrapIfMultiToken(emit(object))} ${wrapIfMultiToken(emit(depth.expr))}`;
4286
+ }
4176
4287
  const d = depth === "infinity" ? -1 : depth;
4177
4288
  return `bf_flat ${wrapIfMultiToken(emit(object))} ${d}`;
4178
4289
  }
@@ -4871,23 +4982,44 @@ ${goFields.join(`
4871
4982
  return plain(expr.raw);
4872
4983
  }
4873
4984
  }
4985
+ buildSegmentAccessor(base, segments) {
4986
+ let acc = base;
4987
+ for (const seg of segments) {
4988
+ acc = seg.kind === "field" ? `${acc}.${goFieldNameForKey(seg.key)}` : `(index ${acc} ${seg.index})`;
4989
+ }
4990
+ return acc;
4991
+ }
4874
4992
  buildDestructureBindingMap(loop, rangeVar) {
4875
- const m = new Map;
4993
+ const bindings = new Map;
4994
+ const restExcludes = new Map;
4995
+ const base = `$${rangeVar}`;
4876
4996
  for (const b of loop.paramBindings ?? []) {
4877
- if (b.rest) {
4878
- m.set(b.name, `$${rangeVar}`);
4997
+ const parent = this.buildSegmentAccessor(base, b.segments ?? []);
4998
+ if (!b.rest) {
4999
+ bindings.set(b.name, parent);
5000
+ } else if (b.rest.kind === "array") {
5001
+ bindings.set(b.name, `(bf_slice ${parent} ${b.rest.from})`);
4879
5002
  } else {
4880
- m.set(b.name, `$${rangeVar}.${capitalizeFieldName(b.path.slice(1))}`);
5003
+ bindings.set(b.name, parent);
5004
+ restExcludes.set(b.name, { parent, excludeKeys: b.rest.exclude.map((k) => k.key) });
4881
5005
  }
4882
5006
  }
4883
- return m;
5007
+ return { bindings, restExcludes };
5008
+ }
5009
+ lookupRestExclude(name) {
5010
+ for (let i = this.loopRestExcludeStack.length - 1;i >= 0; i--) {
5011
+ const info = this.loopRestExcludeStack[i].get(name);
5012
+ if (info)
5013
+ return info;
5014
+ }
5015
+ return;
4884
5016
  }
4885
5017
  renderLoop(loop) {
4886
5018
  if (loop.clientOnly) {
4887
5019
  return `{{bfComment "loop:${loop.markerId}"}}{{bfComment "/loop:${loop.markerId}"}}`;
4888
5020
  }
4889
5021
  const destructure = !!(loop.paramBindings && loop.paramBindings.length > 0);
4890
- const supportableDestructure = destructure && isLowerableObjectRestDestructure(loop);
5022
+ const supportableDestructure = destructure && isLowerableLoopDestructure(loop);
4891
5023
  if (destructure && !supportableDestructure) {
4892
5024
  this.state.errors.push({
4893
5025
  code: "BF104",
@@ -4902,6 +5034,21 @@ ${goFields.join(`
4902
5034
  }
4903
5035
  });
4904
5036
  }
5037
+ const arrayName = loop.array.trim();
5038
+ if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
5039
+ const arrayConst = this.state.localConstants.find((c) => c.name === arrayName);
5040
+ if (arrayConst && !arrayConst.isModule && arrayConst.parsed && !this.isStringExpr(arrayConst.parsed, new Set)) {
5041
+ this.state.errors.push({
5042
+ code: "BF101",
5043
+ severity: "error",
5044
+ 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.`,
5045
+ loc: loop.loc ?? this.makeLoc(),
5046
+ suggestion: {
5047
+ 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."
5048
+ }
5049
+ });
5050
+ }
5051
+ }
4905
5052
  let goArray = this.convertExpressionToGo(loop.array);
4906
5053
  const param = loop.param;
4907
5054
  let index = loop.index || "_";
@@ -4911,15 +5058,16 @@ ${goFields.join(`
4911
5058
  rangeIndex = param;
4912
5059
  rangeValue = "_";
4913
5060
  }
4914
- const childComponent = this.findChildComponent(loop.children);
4915
- if (childComponent) {
4916
- goArray = `.${childComponent.name}s`;
5061
+ if (loop.childComponent) {
5062
+ goArray = `.${loop.childComponent.name}s`;
4917
5063
  }
4918
5064
  this.inLoop = true;
4919
5065
  const addedLoopVars = [];
4920
5066
  let pushedBindingMap = false;
4921
5067
  if (supportableDestructure) {
4922
- this.loopBindingStack.push(this.buildDestructureBindingMap(loop, rangeValue));
5068
+ const built = this.buildDestructureBindingMap(loop, rangeValue);
5069
+ this.loopBindingStack.push(built.bindings);
5070
+ this.loopRestExcludeStack.push(built.restExcludes);
4923
5071
  pushedBindingMap = true;
4924
5072
  this.loopParamStack.push("");
4925
5073
  if (rangeIndex !== "_") {
@@ -4938,7 +5086,9 @@ ${goFields.join(`
4938
5086
  }
4939
5087
  }
4940
5088
  this.loopScalarItemStack.push(this.scalarLiteralLoopGoType(loop.arrayParsed, loop.itemType) !== null);
5089
+ this.loopWrapperStack.push(!!loop.childComponent);
4941
5090
  const children = this.renderChildren(loop.children);
5091
+ this.loopWrapperStack.pop();
4942
5092
  this.loopScalarItemStack.pop();
4943
5093
  const itemMarker = this.loopItemMarker(loop);
4944
5094
  for (const v of addedLoopVars) {
@@ -4949,8 +5099,10 @@ ${goFields.join(`
4949
5099
  this.loopVarRefCount.set(v, rc);
4950
5100
  }
4951
5101
  this.loopParamStack.pop();
4952
- if (pushedBindingMap)
5102
+ if (pushedBindingMap) {
4953
5103
  this.loopBindingStack.pop();
5104
+ this.loopRestExcludeStack.pop();
5105
+ }
4954
5106
  this.inLoop = false;
4955
5107
  if (loop.sortComparator) {
4956
5108
  const sortEmit = (e) => this.renderParsedExpr(e);
@@ -4979,24 +5131,6 @@ ${goFields.join(`
4979
5131
  }
4980
5132
  return "";
4981
5133
  }
4982
- findChildComponent(nodes) {
4983
- for (const node of nodes) {
4984
- if (node.type === "component") {
4985
- return node;
4986
- }
4987
- if (node.type === "element" && node.children) {
4988
- const found = this.findChildComponent(node.children);
4989
- if (found)
4990
- return found;
4991
- }
4992
- if (node.type === "fragment" && node.children) {
4993
- const found = this.findChildComponent(node.children);
4994
- if (found)
4995
- return found;
4996
- }
4997
- }
4998
- return null;
4999
- }
5000
5134
  queueDynamicChildrenDefine(comp) {
5001
5135
  const effectiveChildren = comp.children.length > 0 ? comp.children : this.jsxChildrenPropNodes(comp.props);
5002
5136
  if (effectiveChildren.length === 0)
@@ -5044,7 +5178,7 @@ ${goFields.join(`
5044
5178
  return this.renderChildren(comp.children);
5045
5179
  }
5046
5180
  let templateCall;
5047
- if (this.inLoop) {
5181
+ if (this.inLoop && (this.loopWrapperStack[this.loopWrapperStack.length - 1] ?? false)) {
5048
5182
  const loopBodyDefine = this.queueLoopBodyChildrenDefine(comp);
5049
5183
  if (loopBodyDefine) {
5050
5184
  const bodyData = this.loopScalarItemStack[this.loopScalarItemStack.length - 1] ? ".BfLoopItem" : ".";
@@ -5052,6 +5186,12 @@ ${goFields.join(`
5052
5186
  } else {
5053
5187
  templateCall = `{{template "${comp.name}" .}}`;
5054
5188
  }
5189
+ } else if (this.inLoop && comp.slotId) {
5190
+ const suffix = slotIdToFieldSuffix(comp.slotId);
5191
+ const loopBodyDefine = this.queueLoopBodyChildrenDefine(comp);
5192
+ templateCall = loopBodyDefine ? `{{template "${comp.name}" (bf_with_children $.${comp.name}${suffix} (bf_tmpl "${loopBodyDefine}" .))}}` : `{{template "${comp.name}" $.${comp.name}${suffix}}}`;
5193
+ } else if (this.inLoop) {
5194
+ templateCall = `{{template "${comp.name}" .}}`;
5055
5195
  } else if (comp.slotId) {
5056
5196
  const suffix = slotIdToFieldSuffix(comp.slotId);
5057
5197
  const childrenDefine = this.queueDynamicChildrenDefine(comp);
@@ -5147,6 +5287,12 @@ ${children}`;
5147
5287
  if (currentLoopParam && trimmed === currentLoopParam) {
5148
5288
  return `{{bf_spread_attrs .}}`;
5149
5289
  }
5290
+ const restInfo = this.lookupRestExclude(trimmed);
5291
+ if (restInfo) {
5292
+ const excludeArgs = restInfo.excludeKeys.map((k) => JSON.stringify(k)).join(" ");
5293
+ const omitArgs = excludeArgs ? `${restInfo.parent} ${excludeArgs}` : restInfo.parent;
5294
+ return `{{bf_spread_attrs (bf_omit ${omitArgs})}}`;
5295
+ }
5150
5296
  const goExpr = this.convertExpressionToGo(value.expr);
5151
5297
  return `{{bf_spread_attrs ${goExpr}}}`;
5152
5298
  }
@@ -21,6 +21,29 @@ export declare const GO_KEYWORDS: Set<string>;
21
21
  export declare function capitalize(s: string): string;
22
22
  /** Capitalise a JSX prop / field name to its exported Go struct field name. */
23
23
  export declare function capitalizeFieldName(name: string): string;
24
+ /**
25
+ * Resolve ANY source property key — identifier or not (`data-priority`,
26
+ * `aria-label`, a numeric key) — to a valid Go struct field name. Splits on
27
+ * runs of characters that are invalid in a Go identifier (underscores are
28
+ * VALID and preserved, so a snake_case key round-trips to the exact same
29
+ * name `capitalizeFieldName` alone has always produced — `foo_bar` →
30
+ * `Foo_bar`, never `FooBar`; renaming it would break both existing member
31
+ * emission and consumers' hand-written constructors against generated
32
+ * types) and PascalCases each segment through `capitalizeFieldName`, so a
33
+ * hyphenated key gets a real field instead of being silently dropped
34
+ * (`data-priority` → `DataPriority`). A result that would start with a
35
+ * digit (numeric key `0`) is prefixed with `Field` (`Field0`), and a key
36
+ * with no usable characters at all falls back to `Field` — both keep the
37
+ * emitted struct compiling (not expected from real TS property names, but
38
+ * keeps the function total).
39
+ *
40
+ * Single source of truth for the source-key → Go-name mapping: used for
41
+ * struct-field generation (#2087 Phase B — `structFieldsFor`), inline-map
42
+ * baking (`bakeInlineObjectAsGoMap`), and the `member()` dot-access
43
+ * emitter, so the baked side and the accessor side can never disagree
44
+ * (PR #2089 review).
45
+ */
46
+ export declare function goFieldNameForKey(key: string): string;
24
47
  /**
25
48
  * Convert a slot ID (e.g., 's6') to a Go struct field suffix (e.g., 'Slot6').
26
49
  * Keeps field names human-readable regardless of the internal slot ID format.
@@ -1 +1 @@
1
- {"version":3,"file":"go-naming.d.ts","sourceRoot":"","sources":["../../../src/adapter/lib/go-naming.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,2DAA2D;AAC3D,eAAO,MAAM,aAAa,QAA6B,CAAA;AAEvD,iHAAiH;AACjH,eAAO,MAAM,cAAc,aAIzB,CAAA;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW,aAKtB,CAAA;AAEF;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAM5C;AAED,+EAA+E;AAC/E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOxD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAS1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAQtG"}
1
+ {"version":3,"file":"go-naming.d.ts","sourceRoot":"","sources":["../../../src/adapter/lib/go-naming.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,2DAA2D;AAC3D,eAAO,MAAM,aAAa,QAA6B,CAAA;AAEvD,iHAAiH;AACjH,eAAO,MAAM,cAAc,aAIzB,CAAA;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW,aAKtB,CAAA;AAEF;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAM5C;AAED,+EAA+E;AAC/E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOxD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKrD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAS1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAQtG"}
@@ -4,7 +4,11 @@
4
4
  * Free functions over a {@link GoEmitContext}. They resolve a prop/signal/const's
5
5
  * type (`TypeInfo`, a raw type string, or — as a last resort — an inferred shape
6
6
  * from a literal value) into the Go type used for its struct field. They read
7
- * only `state.localTypeNames`; `inferTypeFromValue` is fully pure.
7
+ * `state.localStructFields` / `state.localTypeAliases` (an ACTUAL Go-backed
8
+ * local type — a generated struct or a string-union alias) rather than the
9
+ * broader `state.localTypeNames` (every type definition, including a tuple
10
+ * alias no struct was ever emitted for — #2087); `inferTypeFromValue` is fully
11
+ * pure.
8
12
  */
9
13
  import type { TypeInfo } from '@barefootjs/jsx';
10
14
  import type { GoEmitContext } from '../emit-context.ts';