@barefootjs/go-template 0.33.4 → 0.33.6

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/index.js CHANGED
@@ -28,7 +28,8 @@ import {
28
28
  dangerousInnerHtmlDiagnostic,
29
29
  collectLoopBoundNames as collectLoopBoundNames2,
30
30
  evaluateStaticLiteral as evaluateStaticLiteral3,
31
- BindingScope
31
+ BindingScope,
32
+ buildImportAliasMap
32
33
  } from "@barefootjs/jsx";
33
34
  import { findInterpolationEnd } from "@barefootjs/jsx/scanner";
34
35
  import { BF_REGION, escapeHtml, resolveJsxChildrenProp } from "@barefootjs/shared";
@@ -303,6 +304,7 @@ class CompileState {
303
304
  componentName = "";
304
305
  errors = [];
305
306
  referencedDerivedConsts = new Set;
307
+ templateReadRootFields = new Set;
306
308
  templateVarCounter = 0;
307
309
  pendingChildrenDefines = [];
308
310
  propsObjectName = null;
@@ -1170,6 +1172,15 @@ function convertInitialValue(ctx, value, _typeInfo, propsParams, preParsed) {
1170
1172
  if (param2) {
1171
1173
  return propRef(param2);
1172
1174
  }
1175
+ const inlinedStr = ctx.resolveModuleStringConst(value);
1176
+ if (inlinedStr !== null)
1177
+ return inlinedStr;
1178
+ const inlinedNum = ctx.resolveModuleNumericConst(value);
1179
+ if (inlinedNum !== null)
1180
+ return inlinedNum;
1181
+ const inlinedBool = ctx.resolveModuleBooleanConst(value);
1182
+ if (inlinedBool !== null)
1183
+ return inlinedBool;
1173
1184
  }
1174
1185
  const propName = ctx.extractPropNameFromInitialValue(value, preParsed);
1175
1186
  const param = propName ? propsParams?.find((p) => p.name === propName) : undefined;
@@ -2890,7 +2901,9 @@ class GoTemplateAdapter extends BaseAdapter {
2890
2901
  extractPropNameFromInitialValue: (initialValue, preParsed) => this.extractPropNameFromInitialValue(initialValue, preParsed),
2891
2902
  extractPropFallback: (initialValue, preParsed) => this.extractPropFallback(initialValue, preParsed),
2892
2903
  extractCollisionDerivation: (parsed) => this.extractCollisionDerivation(parsed),
2893
- resolveModuleStringConst: (name) => this.resolveModuleStringConst(name)
2904
+ resolveModuleStringConst: (name) => this.resolveModuleStringConst(name),
2905
+ resolveModuleNumericConst: (name) => this.resolveModuleNumericConst(name),
2906
+ resolveModuleBooleanConst: (name) => this.resolveModuleBooleanConst(name)
2894
2907
  };
2895
2908
  get errors() {
2896
2909
  return this.state.errors;
@@ -2907,6 +2920,10 @@ class GoTemplateAdapter extends BaseAdapter {
2907
2920
  staticLoopBakeFailed = false;
2908
2921
  childComponentShapes = new Map;
2909
2922
  childContextConsumers = new Map;
2923
+ importAliases = new Map;
2924
+ resolveChildName(name) {
2925
+ return this.importAliases.get(name) ?? name;
2926
+ }
2910
2927
  constructor(options = {}) {
2911
2928
  super();
2912
2929
  this.options = {
@@ -2918,6 +2935,7 @@ class GoTemplateAdapter extends BaseAdapter {
2918
2935
  primeCompileState(ir) {
2919
2936
  this.state.propsObjectName = ir.metadata.propsObjectName;
2920
2937
  this.state.restPropsName = ir.metadata.restPropsName ?? null;
2938
+ this.importAliases = buildImportAliasMap(ir.metadata.imports ?? []);
2921
2939
  this.state.objectTypedPropNames = new Set((ir.metadata.propsParams ?? []).filter((p) => p.type.kind === "object").map((p) => p.name));
2922
2940
  this.state.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
2923
2941
  this.state.localConstants = ir.metadata.localConstants ?? [];
@@ -2952,6 +2970,7 @@ class GoTemplateAdapter extends BaseAdapter {
2952
2970
  this.state.componentName = ir.metadata.componentName;
2953
2971
  this.state.errors = [];
2954
2972
  this.state.referencedDerivedConsts = new Set;
2973
+ this.state.templateReadRootFields = new Set;
2955
2974
  this.state.templateVarCounter = 0;
2956
2975
  this.state.pendingChildrenDefines = [];
2957
2976
  this.scope = BindingScope.EMPTY;
@@ -2980,7 +2999,7 @@ ${scriptRegistrations}${templateBody}
2980
2999
  template += `{{define "${d.name}"}}${d.content}{{end}}
2981
3000
  `;
2982
3001
  }
2983
- const types = this.generateTypes(ir);
3002
+ const types = this.generateTypes(ir, true);
2984
3003
  if (this.state.errors.length > 0) {
2985
3004
  ir.errors.push(...this.state.errors);
2986
3005
  }
@@ -3209,9 +3228,12 @@ ${scriptRegistrations}${templateBody}
3209
3228
  taken.add(desired);
3210
3229
  return desired;
3211
3230
  }
3212
- generateTypes(ir) {
3231
+ generateTypes(ir, preserveTemplateReadRootFields = false) {
3213
3232
  this.state.usesHtmlTemplate = false;
3214
3233
  this.state.usesFmt = false;
3234
+ if (!preserveTemplateReadRootFields) {
3235
+ this.state.templateReadRootFields = new Set;
3236
+ }
3215
3237
  this.primeCompileState(ir);
3216
3238
  const lines = [];
3217
3239
  const componentName = ir.metadata.componentName;
@@ -3355,10 +3377,17 @@ ${goFields.join(`
3355
3377
  const node = signal.parsed;
3356
3378
  if (!node || node.kind !== "array-literal" || node.elements.length === 0)
3357
3379
  return null;
3380
+ const name = `${componentName}${capitalizeFieldName(signal.getter)}Item`;
3381
+ return this.synthesizeStructsFromElements(node.elements, name);
3382
+ }
3383
+ synthesizeStructsFromElements(elements, name) {
3384
+ if (this.state.localTypeNames.has(name))
3385
+ return null;
3358
3386
  const order = [];
3359
- const goTypes = new Map;
3360
- for (let i = 0;i < node.elements.length; i++) {
3361
- const el = node.elements[i];
3387
+ const shapes = new Map;
3388
+ const nestedElements = new Map;
3389
+ for (let i = 0;i < elements.length; i++) {
3390
+ const el = elements[i];
3362
3391
  if (el.kind !== "object-literal")
3363
3392
  return null;
3364
3393
  const seen = new Set;
@@ -3370,37 +3399,93 @@ ${goFields.join(`
3370
3399
  const key = prop.key;
3371
3400
  if (!GO_IDENTIFIER.test(key))
3372
3401
  return null;
3402
+ seen.add(key);
3403
+ const isNestedArray = prop.value.kind === "array-literal" && prop.value.elements.every((e) => e.kind === "object-literal");
3404
+ if (isNestedArray) {
3405
+ const prevShape2 = shapes.get(key);
3406
+ if (prevShape2 === undefined) {
3407
+ if (i !== 0)
3408
+ return null;
3409
+ order.push(key);
3410
+ shapes.set(key, { kind: "nested-array" });
3411
+ nestedElements.set(key, []);
3412
+ } else if (prevShape2.kind !== "nested-array") {
3413
+ return null;
3414
+ }
3415
+ nestedElements.get(key).push(...prop.value.elements);
3416
+ continue;
3417
+ }
3373
3418
  const goType = this.scalarParsedGoType(prop.value);
3374
3419
  if (!goType)
3375
3420
  return null;
3376
- seen.add(key);
3377
- const prev = goTypes.get(key);
3378
- if (prev === undefined) {
3421
+ const prevShape = shapes.get(key);
3422
+ if (prevShape === undefined) {
3379
3423
  if (i !== 0)
3380
3424
  return null;
3381
3425
  order.push(key);
3382
- goTypes.set(key, goType);
3426
+ shapes.set(key, { kind: "scalar", goType });
3427
+ } else if (prevShape.kind !== "scalar") {
3428
+ return null;
3383
3429
  } else {
3384
- const merged = this.mergeScalarGoType(prev, goType);
3430
+ const merged = this.mergeScalarGoType(prevShape.goType, goType);
3385
3431
  if (!merged)
3386
3432
  return null;
3387
- goTypes.set(key, merged);
3433
+ shapes.set(key, { kind: "scalar", goType: merged });
3388
3434
  }
3389
3435
  }
3390
3436
  if (seen.size !== order.length)
3391
3437
  return null;
3392
3438
  }
3393
- const name = `${componentName}${capitalizeFieldName(signal.getter)}Item`;
3394
- if (this.state.localTypeNames.has(name))
3395
- return null;
3396
- return {
3439
+ const nestedStructs = [];
3440
+ const fields = [];
3441
+ const properties = [];
3442
+ for (const key of order) {
3443
+ const shape = shapes.get(key);
3444
+ if (shape.kind === "scalar") {
3445
+ fields.push({ tsName: key, goName: capitalizeFieldName(key), goType: shape.goType });
3446
+ properties.push({ name: key, type: this.scalarGoTypeToTypeInfo(shape.goType), optional: false, readonly: false });
3447
+ continue;
3448
+ }
3449
+ const nestedList = nestedElements.get(key);
3450
+ if (nestedList.length === 0)
3451
+ return null;
3452
+ const nestedName = `${name}${capitalizeFieldName(key)}Item`;
3453
+ const nested = this.synthesizeStructsFromElements(nestedList, nestedName);
3454
+ if (!nested)
3455
+ return null;
3456
+ nestedStructs.push(...nested);
3457
+ fields.push({ tsName: key, goName: capitalizeFieldName(key), goType: `[]${nestedName}` });
3458
+ properties.push({ name: key, type: this.synthSliceTypeInfo(nestedName), optional: false, readonly: false });
3459
+ }
3460
+ return [...nestedStructs, { name, fields, properties }];
3461
+ }
3462
+ scalarGoTypeToTypeInfo(goType) {
3463
+ if (goType === "string")
3464
+ return { kind: "primitive", raw: "string", primitive: "string" };
3465
+ if (goType === "bool")
3466
+ return { kind: "primitive", raw: "boolean", primitive: "boolean" };
3467
+ return { kind: "primitive", raw: "number", primitive: "number" };
3468
+ }
3469
+ synthSliceTypeInfo(name) {
3470
+ return { kind: "array", raw: `${name}[]`, elementType: { kind: "interface", raw: name } };
3471
+ }
3472
+ registerSynthStruct(lines, name, fields, properties, comment) {
3473
+ this.state.localTypeNames.add(name);
3474
+ this.state.localStructFields.set(name, new Map(fields.map((f) => [f.tsName, f.goName])));
3475
+ this.state.currentTypeDefinitions.push({
3476
+ kind: "type",
3397
3477
  name,
3398
- fields: order.map((key) => ({
3399
- tsName: key,
3400
- goName: capitalizeFieldName(key),
3401
- goType: goTypes.get(key)
3402
- }))
3403
- };
3478
+ definition: "",
3479
+ properties,
3480
+ loc: SYNTH_TYPE_LOC
3481
+ });
3482
+ const goFields = fields.map((f) => ` ${f.goName} ${f.goType} \`json:"${this.toJsonTag(f.tsName)}"\``);
3483
+ lines.push(comment);
3484
+ lines.push(`type ${name} struct {
3485
+ ${goFields.join(`
3486
+ `)}
3487
+ }`);
3488
+ lines.push("");
3404
3489
  }
3405
3490
  scalarParsedGoType(value) {
3406
3491
  if (value.kind === "unary" && value.op === "-" && value.argument.kind === "literal") {
@@ -3467,7 +3552,7 @@ ${goFields.join(`
3467
3552
  for (const nested of inputNested) {
3468
3553
  if (nested.loopMarkerId && this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey))
3469
3554
  continue;
3470
- lines.push(` ${nested.name}s []${nested.name}Input`);
3555
+ lines.push(` ${nested.name}s []${this.resolveChildName(nested.name)}Input`);
3471
3556
  }
3472
3557
  const takenInput = new Set(this.propParamFieldNamesUnion(ir.metadata.propsParams));
3473
3558
  for (const c of this.nonCollidingContextConsumers(takenInput)) {
@@ -3503,10 +3588,11 @@ ${goFields.join(`
3503
3588
  const wrapperName = this.loopBodyWrapperName(parentComponentName, nested);
3504
3589
  const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
3505
3590
  const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
3506
- lines.push(`// ${wrapperName} wraps ${nested.name}Props with per-row loop datum`);
3591
+ const declaredName = this.resolveChildName(nested.name);
3592
+ lines.push(`// ${wrapperName} wraps ${declaredName}Props with per-row loop datum`);
3507
3593
  lines.push(`// fields and child component slots for the loop body children. (#1897)`);
3508
3594
  lines.push(`type ${wrapperName} struct {`);
3509
- lines.push(` ${nested.name}Props`);
3595
+ lines.push(` ${declaredName}Props`);
3510
3596
  for (const f of datumFields) {
3511
3597
  lines.push(` ${f.goName} ${f.goType} \`json:"-"\``);
3512
3598
  }
@@ -3515,7 +3601,7 @@ ${goFields.join(`
3515
3601
  lines.push(` BfLoopItem ${scalarLoopType} \`json:"-"\``);
3516
3602
  }
3517
3603
  for (const child of bodyChildInstances) {
3518
- lines.push(` ${child.fieldName} ${child.name}Props \`json:"-"\``);
3604
+ lines.push(` ${child.fieldName} ${this.resolveChildName(child.name)}Props \`json:"-"\``);
3519
3605
  }
3520
3606
  lines.push("}");
3521
3607
  lines.push("");
@@ -3599,12 +3685,13 @@ ${goFields.join(`
3599
3685
  const staticWithoutBody = staticNested.filter((n) => !n.bodyChildren || n.bodyChildren.length === 0);
3600
3686
  for (const nested of staticWithoutBody) {
3601
3687
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
3688
+ const declaredName = this.resolveChildName(nested.name);
3602
3689
  const baked = nested.loopMarkerId ? this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey) : null;
3603
3690
  if (baked) {
3604
- lines.push(` ${varName} := make([]${nested.name}Props, ${baked.items.length})`);
3691
+ lines.push(` ${varName} := make([]${declaredName}Props, ${baked.items.length})`);
3605
3692
  baked.items.forEach((item, i) => {
3606
3693
  const fields = item.inputFields.map((f) => `${f.goField}: ${f.goValue}`).join(", ");
3607
- lines.push(` ${varName}[${i}] = New${nested.name}Props(${nested.name}Input{${fields}})`);
3694
+ lines.push(` ${varName}[${i}] = New${declaredName}Props(${declaredName}Input{${fields}})`);
3608
3695
  lines.push(` ${varName}[${i}].BfParent = scopeID`);
3609
3696
  lines.push(` ${varName}[${i}].BfMount = "${nested.slotId}"`);
3610
3697
  if (item.dataKey !== null) {
@@ -3614,9 +3701,9 @@ ${goFields.join(`
3614
3701
  lines.push("");
3615
3702
  continue;
3616
3703
  }
3617
- lines.push(` ${varName} := make([]${nested.name}Props, len(in.${nested.name}s))`);
3704
+ lines.push(` ${varName} := make([]${declaredName}Props, len(in.${nested.name}s))`);
3618
3705
  lines.push(` for i, item := range in.${nested.name}s {`);
3619
- lines.push(` ${varName}[i] = New${nested.name}Props(item)`);
3706
+ lines.push(` ${varName}[i] = New${declaredName}Props(item)`);
3620
3707
  lines.push(` ${varName}[i].BfParent = scopeID`);
3621
3708
  lines.push(` ${varName}[i].BfMount = "${nested.slotId}"`);
3622
3709
  const keyField = loopKeyToGoFieldPath(nested.loopKey, nested.loopParam);
@@ -3731,8 +3818,14 @@ ${goFields.join(`
3731
3818
  lines.push(` ${fieldName}: ${hoisted.varName},`);
3732
3819
  } else {
3733
3820
  const bakeType = this.state.synthStructTypes.get(signal.getter) ?? signal.type;
3821
+ const resolvedParsed = this.resolvedSignalParsed(signal);
3734
3822
  const initialValue = convertInitialValue(this.emitCtx, signal.initialValue, bakeType, ir.metadata.propsParams, signal.parsed);
3735
3823
  lines.push(` ${fieldName}: ${initialValue},`);
3824
+ if (resolvedParsed?.kind === "object-literal" && jsLiteralToGo(this.emitCtx, bakeType, resolvedParsed) === null) {
3825
+ const step = this.state.ssrSeedPlan.steps.find((s) => s.kind === "derived" && s.origin === "signal" && s.name === signal.getter);
3826
+ if (step?.kind === "derived")
3827
+ this.refuseUnbakeableDerivedObjectLiteral(signal.getter, signal.loc, step.frees);
3828
+ }
3736
3829
  }
3737
3830
  }
3738
3831
  for (const nested of staticWithoutBody) {
@@ -3832,19 +3925,20 @@ ${goFields.join(`
3832
3925
  emitStaticChildInstances(lines, ir) {
3833
3926
  const staticChildren = this.collectStaticChildInstances(ir.root, ir.metadata.propsParams);
3834
3927
  for (const child of staticChildren) {
3835
- lines.push(` ${child.fieldName}: New${child.name}Props(${child.name}Input{`);
3928
+ const declaredName = this.resolveChildName(child.name);
3929
+ lines.push(` ${child.fieldName}: New${declaredName}Props(${declaredName}Input{`);
3836
3930
  lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
3837
3931
  lines.push(` BfParent: scopeID,`);
3838
3932
  lines.push(` BfMount: "${child.slotId}",`);
3839
3933
  if (child.contextBindings) {
3840
- for (const consumer of this.childContextConsumers.get(child.name) ?? []) {
3934
+ for (const consumer of this.childContextConsumers.get(declaredName) ?? []) {
3841
3935
  const goVal = child.contextBindings.get(consumer.contextName);
3842
3936
  if (goVal !== undefined) {
3843
3937
  lines.push(` ${this.contextFieldName(consumer)}: ${goVal},`);
3844
3938
  }
3845
3939
  }
3846
3940
  }
3847
- const childShape = this.childComponentShapes.get(child.name);
3941
+ const childShape = this.childComponentShapes.get(declaredName);
3848
3942
  const restBagEntries = [];
3849
3943
  const emitChildField = (jsxName, goValue) => {
3850
3944
  if (childShape && childShape.restBagField && !childShape.paramNames.has(jsxName)) {
@@ -3941,6 +4035,7 @@ ${goFields.join(`
3941
4035
  lines.push(`// New${componentName}Props creates ${propsTypeName} from ${inputTypeName}.`);
3942
4036
  for (const nested of signalDynamicNested) {
3943
4037
  const arrayField = `${nested.name}s`;
4038
+ const declaredName = this.resolveChildName(nested.name);
3944
4039
  lines.push(`//`);
3945
4040
  lines.push(`// NOTE: \`${arrayField}\` is populated by the route handler, not by`);
3946
4041
  lines.push(`// New${componentName}Props — the SSR template iterates over it`);
@@ -3948,9 +4043,9 @@ ${goFields.join(`
3948
4043
  lines.push(`// assign it before passing the props to your renderer. Example:`);
3949
4044
  lines.push(`//`);
3950
4045
  lines.push(`// props := New${componentName}Props(${inputTypeName}{ /* ... */ })`);
3951
- lines.push(`// props.${arrayField} = make([]${nested.name}Props, len(items))`);
4046
+ lines.push(`// props.${arrayField} = make([]${declaredName}Props, len(items))`);
3952
4047
  lines.push(`// for i, item := range items {`);
3953
- lines.push(`// props.${arrayField}[i] = New${nested.name}Props(${nested.name}Input{ /* fields */ })`);
4048
+ lines.push(`// props.${arrayField}[i] = New${declaredName}Props(${declaredName}Input{ /* fields */ })`);
3954
4049
  lines.push(`// props.${arrayField}[i].BfParent = props.ScopeID`);
3955
4050
  lines.push(`// props.${arrayField}[i].BfMount = "${nested.slotId}"`);
3956
4051
  lines.push(`// }`);
@@ -3989,9 +4084,11 @@ ${goFields.join(`
3989
4084
  const wrapperType = this.loopBodyWrapperName(componentName, nested);
3990
4085
  const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
3991
4086
  const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren, ir.metadata.propsParams);
4087
+ const declaredName = this.resolveChildName(nested.name);
3992
4088
  for (const child of bodyChildInstances) {
3993
4089
  const childVar = `child_${child.fieldName}`;
3994
- lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
4090
+ const childDeclaredName = this.resolveChildName(child.name);
4091
+ lines.push(` ${childVar} := New${childDeclaredName}Props(${childDeclaredName}Input{`);
3995
4092
  lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
3996
4093
  lines.push(` BfParent: scopeID,`);
3997
4094
  lines.push(` BfMount: "${child.slotId}",`);
@@ -4011,7 +4108,7 @@ ${goFields.join(`
4011
4108
  lines.push(` ${varName} := make([]${wrapperType}, len(${dataVar}))`);
4012
4109
  lines.push(` for i, item := range ${dataVar} {`);
4013
4110
  lines.push(` ${varName}[i] = ${wrapperType}{`);
4014
- lines.push(` ${nested.name}Props: New${nested.name}Props(${nested.name}Input{`);
4111
+ lines.push(` ${declaredName}Props: New${declaredName}Props(${declaredName}Input{`);
4015
4112
  lines.push(` BfParent: scopeID,`);
4016
4113
  lines.push(` BfMount: "${nested.slotId}",`);
4017
4114
  for (const prop of nested.props ?? []) {
@@ -4066,9 +4163,11 @@ ${goFields.join(`
4066
4163
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
4067
4164
  const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
4068
4165
  const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren, ir.metadata.propsParams);
4166
+ const declaredName = this.resolveChildName(nested.name);
4069
4167
  for (const child of bodyChildInstances) {
4070
4168
  const childVar = `child_${child.fieldName}`;
4071
- lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
4169
+ const childDeclaredName = this.resolveChildName(child.name);
4170
+ lines.push(` ${childVar} := New${childDeclaredName}Props(${childDeclaredName}Input{`);
4072
4171
  lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
4073
4172
  lines.push(` BfParent: scopeID,`);
4074
4173
  lines.push(` BfMount: "${child.slotId}",`);
@@ -4087,7 +4186,7 @@ ${goFields.join(`
4087
4186
  lines.push(` ${varName} := make([]${wrapperType}, len(bakedData))`);
4088
4187
  lines.push(` for i, item := range bakedData {`);
4089
4188
  lines.push(` ${varName}[i] = ${wrapperType}{`);
4090
- lines.push(` ${nested.name}Props: New${nested.name}Props(${nested.name}Input{`);
4189
+ lines.push(` ${declaredName}Props: New${declaredName}Props(${declaredName}Input{`);
4091
4190
  lines.push(` BfParent: scopeID,`);
4092
4191
  lines.push(` BfMount: "${nested.slotId}",`);
4093
4192
  lines.push(` }),`);
@@ -4142,21 +4241,7 @@ ${goFields.join(`
4142
4241
  visit(prop.type, desiredName, prop.name);
4143
4242
  }
4144
4243
  const fields = this.structFieldsFor(typeInfo);
4145
- this.state.localStructFields.set(desiredName, new Map(fields.map((f) => [f.tsName, f.goName])));
4146
- this.state.currentTypeDefinitions.push({
4147
- kind: "type",
4148
- name: desiredName,
4149
- definition: "",
4150
- properties: typeInfo.properties ?? [],
4151
- loc: SYNTH_TYPE_LOC
4152
- });
4153
- const goFields = fields.map((f) => ` ${f.goName} ${f.goType} \`json:"${this.toJsonTag(f.tsName)}"\``);
4154
- lines.push(`// ${desiredName} is a synthesised type for an anonymous object type (#2674).`);
4155
- lines.push(`type ${desiredName} struct {
4156
- ${goFields.join(`
4157
- `)}
4158
- }`);
4159
- lines.push("");
4244
+ this.registerSynthStruct(lines, desiredName, fields, typeInfo.properties ?? [], `// ${desiredName} is a synthesised type for an anonymous object type (#2674).`);
4160
4245
  };
4161
4246
  const visitArrayElem = (elemType, parentName, propName) => {
4162
4247
  if (!elemType)
@@ -4208,20 +4293,11 @@ ${goFields.join(`
4208
4293
  const synth = this.synthesizeStructFromSignal(signal, componentName);
4209
4294
  if (!synth)
4210
4295
  continue;
4211
- this.state.localTypeNames.add(synth.name);
4212
- this.state.localStructFields.set(synth.name, new Map(synth.fields.map((f) => [f.tsName, f.goName])));
4213
- this.state.synthStructTypes.set(signal.getter, {
4214
- kind: "array",
4215
- raw: `${synth.name}[]`,
4216
- elementType: { kind: "interface", raw: synth.name }
4217
- });
4218
- const goFields = synth.fields.map((f) => ` ${f.goName} ${f.goType} \`json:"${this.toJsonTag(f.tsName)}"\``);
4219
- lines.push(`// ${synth.name} is a synthesised element type for the ${signal.getter} signal.`);
4220
- lines.push(`type ${synth.name} struct {
4221
- ${goFields.join(`
4222
- `)}
4223
- }`);
4224
- lines.push("");
4296
+ for (const s of synth) {
4297
+ this.registerSynthStruct(lines, s.name, s.fields, s.properties, `// ${s.name} is a synthesised element type for the ${signal.getter} signal.`);
4298
+ }
4299
+ const top = synth[synth.length - 1];
4300
+ this.state.synthStructTypes.set(signal.getter, this.synthSliceTypeInfo(top.name));
4225
4301
  }
4226
4302
  }
4227
4303
  resolveNestedLoopItemTypes(ir, nestedComponents) {
@@ -4364,7 +4440,7 @@ ${goFields.join(`
4364
4440
  for (const nested of nestedComponents) {
4365
4441
  if (this.isOrphanedClientOnlyNested(nested))
4366
4442
  continue;
4367
- const elemType = nested.bodyChildren?.length ? this.loopBodyWrapperName(componentName, nested) : `${nested.name}Props`;
4443
+ const elemType = nested.bodyChildren?.length ? this.loopBodyWrapperName(componentName, nested) : `${this.resolveChildName(nested.name)}Props`;
4368
4444
  if (nested.isDynamic && !nested.isPropDerived) {
4369
4445
  lines.push(` ${nested.name}s []${elemType} \`json:"-"\``);
4370
4446
  } else if (nested.isDynamic && nested.isPropDerived && !propDrivingFieldNames.has(`${nested.name}s`)) {
@@ -4376,7 +4452,7 @@ ${goFields.join(`
4376
4452
  }
4377
4453
  const staticChildren = this.collectStaticChildInstances(ir.root, ir.metadata.propsParams);
4378
4454
  for (const child of staticChildren) {
4379
- lines.push(` ${child.fieldName} ${child.name}Props \`json:"-"\``);
4455
+ lines.push(` ${child.fieldName} ${this.resolveChildName(child.name)}Props \`json:"-"\``);
4380
4456
  }
4381
4457
  for (const slot of spreadSlots) {
4382
4458
  const jsonTag = "-";
@@ -4755,6 +4831,22 @@ ${goFields.join(`
4755
4831
  resolvedSignalParsed(signal) {
4756
4832
  return resolveSignalParsedThroughSeedPlan(this.state, signal);
4757
4833
  }
4834
+ refuseUnbakeableDerivedObjectLiteral(name, loc, frees) {
4835
+ if (frees.length === 0)
4836
+ return;
4837
+ if (!this.state.templateReadRootFields.has(name))
4838
+ return;
4839
+ this.state.errors.push({
4840
+ code: "BF101",
4841
+ severity: "error",
4842
+ message: `Signal '${name}' is seeded from an object literal that references live value(s) (${frees.join(", ")}) — the Go template adapter bakes object-typed signal values into Go source at New${this.state.componentName}Props time, and that baker is static-only (identifier/member/call operands defer), so the SSR template's read of it would see the Go zero value instead of the derived object.`,
4843
+ loc,
4844
+ suggestion: {
4845
+ message: `Wrap each SSR read of '${name}()' in /* @client */ so it renders on the client instead, or pass the already-derived object in as a prop.`,
4846
+ escape: [{ kind: "client-directive" }]
4847
+ }
4848
+ });
4849
+ }
4758
4850
  extractPropFallback(initialValue, preParsed) {
4759
4851
  const structural = preParsed ? this.extractPropFallbackFromParsed(preParsed) : null;
4760
4852
  if (structural)
@@ -5083,6 +5175,7 @@ ${goFields.join(`
5083
5175
  return hit !== null && hit.depth > 0 && hit.binding.source === "item";
5084
5176
  }
5085
5177
  rootFieldRef(name) {
5178
+ this.state.templateReadRootFields.add(name);
5086
5179
  const prefix = this.inLoop ? "$." : ".";
5087
5180
  return `${prefix}${capitalizeFieldName(name)}`;
5088
5181
  }
@@ -5104,6 +5197,9 @@ ${goFields.join(`
5104
5197
  return null;
5105
5198
  return `"${escapeGoString(value)}"`;
5106
5199
  }
5200
+ findModuleConst(name) {
5201
+ return this.state.localConstants.find((k) => k.name === name && k.isModule && !k.containsArrow);
5202
+ }
5107
5203
  resolveModuleNumericConst(name) {
5108
5204
  if (this.isCurrentLoopItem(name))
5109
5205
  return null;
@@ -5111,12 +5207,25 @@ ${goFields.join(`
5111
5207
  return null;
5112
5208
  if (this.isOuterLoopParam(name))
5113
5209
  return null;
5114
- const c = this.state.localConstants.find((k) => k.name === name && k.isModule && !k.containsArrow);
5210
+ const c = this.findModuleConst(name);
5115
5211
  if (!c || c.value === undefined)
5116
5212
  return null;
5117
5213
  const v = c.value.trim().replace(/(?<=\d)_(?=\d)/g, "");
5118
5214
  return /^-?\d+(\.\d+)?$/.test(v) ? v : null;
5119
5215
  }
5216
+ resolveModuleBooleanConst(name) {
5217
+ if (this.isCurrentLoopItem(name))
5218
+ return null;
5219
+ if (this.loopVarRefCount.has(name))
5220
+ return null;
5221
+ if (this.isOuterLoopParam(name))
5222
+ return null;
5223
+ const c = this.findModuleConst(name);
5224
+ if (!c || c.value === undefined)
5225
+ return null;
5226
+ const v = c.value.trim();
5227
+ return v === "true" || v === "false" ? v : null;
5228
+ }
5120
5229
  literal(value, literalType) {
5121
5230
  if (literalType === "string")
5122
5231
  return `"${value}"`;
@@ -5754,8 +5863,10 @@ ${goFields.join(`
5754
5863
  }
5755
5864
  const signal = localVarMap.get(expr.name);
5756
5865
  if (signal) {
5866
+ this.rootFieldRef(signal);
5757
5867
  return `$.${capitalizeFieldName(signal)}`;
5758
5868
  }
5869
+ this.rootFieldRef(expr.name);
5759
5870
  return `.${capitalizeFieldName(expr.name)}`;
5760
5871
  }
5761
5872
  case "literal":
@@ -5788,6 +5899,7 @@ ${goFields.join(`
5788
5899
  return `${paramPrefix}.${capitalizeFieldName(expr.callee.property)}`;
5789
5900
  }
5790
5901
  if (expr.callee.kind === "identifier" && expr.args.length === 0) {
5902
+ this.rootFieldRef(expr.callee.name);
5791
5903
  return `$.${capitalizeFieldName(expr.callee.name)}`;
5792
5904
  }
5793
5905
  if (asCallbackMethodCall4(expr) !== null) {
@@ -5960,7 +6072,8 @@ ${goFields.join(`
5960
6072
  return this.scope.isBound(name) || this.loopVarRefCount.has(name);
5961
6073
  }
5962
6074
  loopRowChildPropOverrides(comp) {
5963
- const childShape = this.childComponentShapes.get(comp.name);
6075
+ const declaredName = this.resolveChildName(comp.name);
6076
+ const childShape = this.childComponentShapes.get(declaredName);
5964
6077
  const args = [];
5965
6078
  let needsRebuild = false;
5966
6079
  for (const prop of comp.props) {
@@ -5980,10 +6093,10 @@ ${goFields.join(`
5980
6093
  if (!free || ![...free].some((name) => this.isLoopShadowedName(name)))
5981
6094
  continue;
5982
6095
  {
5983
- const derived = this.childDerivedFieldDeps.get(comp.name);
6096
+ const derived = this.childDerivedFieldDeps.get(declaredName);
5984
6097
  const overriddenField = capitalizeFieldName(prop.name);
5985
6098
  const staleField = derived ? [...derived].find(([, deps]) => deps.has(overriddenField))?.[0] : undefined;
5986
- if (staleField && !this.childRepropsReady.has(comp.name)) {
6099
+ if (staleField && !this.childRepropsReady.has(declaredName)) {
5987
6100
  this.state.errors.push({
5988
6101
  code: "BF101",
5989
6102
  severity: "error",
@@ -5997,8 +6110,8 @@ ${goFields.join(`
5997
6110
  }
5998
6111
  if (staleField) {
5999
6112
  needsRebuild = true;
6000
- if (!this.repropsOwner.has(comp.name)) {
6001
- this.repropsOwner.set(comp.name, this.state.componentName);
6113
+ if (!this.repropsOwner.has(declaredName)) {
6114
+ this.repropsOwner.set(declaredName, this.state.componentName);
6002
6115
  }
6003
6116
  }
6004
6117
  }
@@ -6024,7 +6137,7 @@ ${goFields.join(`
6024
6137
  });
6025
6138
  continue;
6026
6139
  }
6027
- const fieldName = this.childPropFieldNames.get(comp.name)?.get(prop.name) ?? capitalizeFieldName(prop.name);
6140
+ const fieldName = this.childPropFieldNames.get(declaredName)?.get(prop.name) ?? capitalizeFieldName(prop.name);
6028
6141
  args.push(`${JSON.stringify(fieldName)} ${wrapIfMultiToken(go)}`);
6029
6142
  }
6030
6143
  if (args.length === 0)
@@ -6570,7 +6683,8 @@ ${goFields.join(`
6570
6683
  }
6571
6684
  queueDynamicPropDefine(comp) {
6572
6685
  const args = [];
6573
- const childShape = this.childComponentShapes.get(comp.name);
6686
+ const declaredName = this.resolveChildName(comp.name);
6687
+ const childShape = this.childComponentShapes.get(declaredName);
6574
6688
  for (const prop of comp.props) {
6575
6689
  if (prop.value.kind !== "jsx-children" || prop.name === "children")
6576
6690
  continue;
@@ -6597,7 +6711,7 @@ ${goFields.join(`
6597
6711
  content: this.renderChildren(children)
6598
6712
  });
6599
6713
  }
6600
- const fieldName = this.childPropFieldNames.get(comp.name)?.get(prop.name) ?? capitalizeFieldName(prop.name);
6714
+ const fieldName = this.childPropFieldNames.get(declaredName)?.get(prop.name) ?? capitalizeFieldName(prop.name);
6601
6715
  args.push(`${JSON.stringify(fieldName)} (bf_tmpl ${JSON.stringify(name)} .)`);
6602
6716
  }
6603
6717
  return args.length > 0 ? args.join(" ") : null;
@@ -6629,31 +6743,32 @@ ${goFields.join(`
6629
6743
  if (comp.dynamicTag) {
6630
6744
  return this.renderChildren(comp.children);
6631
6745
  }
6746
+ const declaredName = this.resolveChildName(comp.name);
6632
6747
  let templateCall;
6633
6748
  if (this.inLoop && (this.loopWrapperStack[this.loopWrapperStack.length - 1] ?? false)) {
6634
6749
  const loopBodyDefine = this.queueLoopBodyChildrenDefine(comp);
6635
6750
  if (loopBodyDefine) {
6636
6751
  const bodyData = this.loopScalarItemStack[this.loopScalarItemStack.length - 1] ? ".BfLoopItem" : ".";
6637
- templateCall = `{{template "${comp.name}" (bf_with_children . (bf_tmpl "${loopBodyDefine}" ${bodyData}))}}`;
6752
+ templateCall = `{{template "${declaredName}" (bf_with_children . (bf_tmpl "${loopBodyDefine}" ${bodyData}))}}`;
6638
6753
  } else {
6639
- templateCall = `{{template "${comp.name}" .}}`;
6754
+ templateCall = `{{template "${declaredName}" .}}`;
6640
6755
  }
6641
6756
  } else if (this.inLoop && comp.slotId) {
6642
6757
  const suffix = slotIdToFieldSuffix(comp.slotId);
6643
6758
  const overrides = this.loopRowChildPropOverrides(comp);
6644
6759
  const loopBodyDefine = this.queueLoopBodyChildrenDefine(comp);
6645
- const base = overrides ? overrides.helper === "bf_reprops" ? `(bf_reprops ${JSON.stringify(comp.name)} $.${comp.name}${suffix} ${overrides.args})` : `(bf_with_props $.${comp.name}${suffix} ${overrides.args})` : `$.${comp.name}${suffix}`;
6646
- templateCall = loopBodyDefine ? `{{template "${comp.name}" (bf_with_children ${base} (bf_tmpl "${loopBodyDefine}" .))}}` : `{{template "${comp.name}" ${base}}}`;
6760
+ const base = overrides ? overrides.helper === "bf_reprops" ? `(bf_reprops ${JSON.stringify(declaredName)} $.${comp.name}${suffix} ${overrides.args})` : `(bf_with_props $.${comp.name}${suffix} ${overrides.args})` : `$.${comp.name}${suffix}`;
6761
+ templateCall = loopBodyDefine ? `{{template "${declaredName}" (bf_with_children ${base} (bf_tmpl "${loopBodyDefine}" .))}}` : `{{template "${declaredName}" ${base}}}`;
6647
6762
  } else if (this.inLoop) {
6648
- templateCall = `{{template "${comp.name}" .}}`;
6763
+ templateCall = `{{template "${declaredName}" .}}`;
6649
6764
  } else if (comp.slotId) {
6650
6765
  const suffix = slotIdToFieldSuffix(comp.slotId);
6651
6766
  const childrenDefine = this.queueDynamicChildrenDefine(comp);
6652
6767
  const propArgs = this.queueDynamicPropDefine(comp);
6653
6768
  const base = propArgs ? `(bf_with_props .${comp.name}${suffix} ${propArgs})` : `.${comp.name}${suffix}`;
6654
- templateCall = childrenDefine ? `{{template "${comp.name}" (bf_with_children ${base} (bf_tmpl "${childrenDefine}" .))}}` : `{{template "${comp.name}" ${base}}}`;
6769
+ templateCall = childrenDefine ? `{{template "${declaredName}" (bf_with_children ${base} (bf_tmpl "${childrenDefine}" .))}}` : `{{template "${declaredName}" ${base}}}`;
6655
6770
  } else {
6656
- templateCall = `{{template "${comp.name}" .${comp.name}}}`;
6771
+ templateCall = `{{template "${declaredName}" .${comp.name}}}`;
6657
6772
  }
6658
6773
  if (ctx?.isRootOfClientComponent) {
6659
6774
  return `{{bfScopeComment .}}${templateCall}`;
@@ -6918,14 +7033,18 @@ var conformancePins = {
6918
7033
  severity: "error",
6919
7034
  issue: "https://github.com/piconic-ai/barefootjs/issues/2805",
6920
7035
  unescapable: { issue: "https://github.com/piconic-ai/barefootjs/issues/2805" }
6921
- }]
7036
+ }],
7037
+ "signal-object-spread-init": [{
7038
+ code: "BF101",
7039
+ severity: "error",
7040
+ issue: "https://github.com/piconic-ai/barefootjs/issues/2700"
7041
+ }],
7042
+ "namespace-import-primitive": [{ code: "BF013", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2771" }]
6922
7043
  };
6923
7044
  // src/render-divergences.ts
6924
7045
  var renderDivergences = {
6925
7046
  "children-passthrough-renamed": "A `children` prop destructured under a different name (`const { children: kids } = props`) does not reach the SSR template on Go, tracked as https://github.com/piconic-ai/barefootjs/issues/2788. The same fixture also fails on Mojolicious, where the mechanism IS isolated: the `.html.ep` interpolates the LOCAL alias (`$kids`) while the stash defines only the caller-facing `children`, so the Perl render dies inside `Mojo::Template::process`. Go's own failure mode has NOT been read — `go` is not reachable from the local test process (the conformance case prints \"go command not found\" and skips), so this entry is declared from the CI failure on #2787 alone, not from a local reproduction. Whoever graduates this should read Go's actual output first rather than assume it shares Mojo's mechanism. Same alias family as `aliased-destructured-prop` (`{ n: count }`), whose Go half graduated in #2525 — worth checking whether the reserved `children` slot bypasses that fix or never had it. `children-passthrough-renamed` asserts the CORRECT (Hono-generated) output, so deleting this entry is the graduation.",
6926
- "signal-object-spread-init": "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).",
6927
- "textarea-row-breakout": "A signal seeded from a bare identifier referencing a MODULE-LEVEL const (`const PAYLOAD = '...'; createSignal(PAYLOAD)`) bakes to `nil` in the generated `New<Component>Props` constructor instead of the const's literal value: `convertInitialValue` (`value-lowering.ts`) only resolves a direct prop reference or a literal expression for a bare identifier, and the analyzer types this signal `{ kind: 'unknown' }`, so every typed branch falls through to the final `nil` fallback. `resolveModuleStringConst` exists on the adapter for exactly this resolution (used by `template-interp.ts` for live template expressions) but isn't wired into this signal-baking path. Unrelated to what this fixture exists to cover (#2765's loop-row textarea-escaping fix, verified correct here) — every other adapter renders the fixture's controlled `<textarea>` correctly. Tracked at https://github.com/piconic-ai/barefootjs/issues/2794; graduate by wiring `resolveModuleStringConst` into `convertInitialValue`'s bare-identifier case.",
6928
- "nested-loop-ref-const": "A signal-backed object array whose elements have a NESTED array-of-objects field (`children: [{...}]`, producing this fixture's depth-2 `.map()`) bakes to `nil` in `New<Component>Props`, leaving the whole `{{range .Items}}` body empty on real Go — `synthesizeStructFromSignal` (`go-template-adapter.ts`) only synthesizes a struct when EVERY property value is a scalar literal (`scalarParsedGoType`), so a `children` array property aborts synthesis for the entire signal; `parsedLiteralToGo` then has no named struct to bake an object-literal element against and falls through to `nil`. Reproduced identically with #2750's fix reverted, confirming this predates and is independent of #2750 (which only touches client-JS reachability, never SSR baking) — every other adapter (confirmed: Hono) renders the nested loop correctly. Tracked at https://github.com/piconic-ai/barefootjs/issues/2800; graduate by teaching `synthesizeStructFromSignal` to recursively synthesize a nested struct for an array-valued property instead of bailing to `null`."
7047
+ "aliased-loop-source": "A `.map()` loop whose source is a local const alias of a signal getter (`const items__alias = items`) fails template execution on real Go (`can't evaluate field Items__alias in type main.AliasedLoopSourceProps`) the zero-arg-call-to-field lowering routes `items__alias()` to a `.Items__alias` struct field that was never seeded, since seeding only knows about `items`, the real signal name; the alias hop is never resolved. This is the SSR-side twin of #2778 (fixed for the CSR client-JS template in the same PR that added this fixture) that fix only touches client-JS emission, not Go's field-routing/seeding. Tracked at https://github.com/piconic-ai/barefootjs/issues/2813; graduate by resolving the alias hop at field-routing time using the same `resolveAliasOrigin`/`resolveGetterAliases` mechanism #2778 introduced, rather than a third alias-hop walker."
6929
7048
  };
6930
7049
  export {
6931
7050
  renderDivergences,