@barefootjs/go-template 0.33.4 → 0.34.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.
@@ -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;
@@ -887,9 +889,22 @@ function lowerTernaryTest(ctx, test) {
887
889
  const isBoolShape = test.kind === "binary" && BOOL_COMPARISON_OPS.has(test.op) || test.kind === "unary" && test.op === "!" || test.kind === "literal" && test.literalType === "boolean";
888
890
  return isBoolShape ? go : `(bf_truthy ${go})`;
889
891
  }
892
+ function matchRegisteredCall(ctx, callee, args) {
893
+ for (const matcher of ctx.state.loweringMatchers) {
894
+ const node = matcher(callee, args);
895
+ if (node)
896
+ return node;
897
+ }
898
+ return null;
899
+ }
900
+ function lowerRegisteredCallNode(ctx, callee, args) {
901
+ if (ctx.state.loweringMatchers.length === 0)
902
+ return null;
903
+ const node = matchRegisteredCall(ctx, callee, args);
904
+ return node ? renderLoweringNode(ctx, node) : null;
905
+ }
890
906
  function lowerRegisteredCall(ctx, jsExpr, preParsed) {
891
- const matchers = ctx.state.loweringMatchers;
892
- if (matchers.length === 0)
907
+ if (ctx.state.loweringMatchers.length === 0)
893
908
  return null;
894
909
  let call = preParsed?.kind === "call" ? preParsed : undefined;
895
910
  if (!call) {
@@ -900,15 +915,32 @@ function lowerRegisteredCall(ctx, jsExpr, preParsed) {
900
915
  return null;
901
916
  call = parsed;
902
917
  }
903
- for (const matcher of matchers) {
904
- const node = matcher(call.callee, call.args);
905
- if (!node)
906
- continue;
907
- const rendered = renderLoweringNode(ctx, node);
908
- if (rendered !== null)
909
- return rendered;
918
+ return lowerRegisteredCallNode(ctx, call.callee, call.args);
919
+ }
920
+ function lowerRegisteredAttrCall(ctx, attrName, parsed) {
921
+ if (parsed.kind === "conditional") {
922
+ if (!ternaryHasQueryBranch(ctx, parsed.consequent, parsed.alternate))
923
+ return null;
924
+ const rendered2 = lowerTernary(ctx, parsed.test, parsed.consequent, parsed.alternate);
925
+ return `{{bf_attr ${JSON.stringify(attrName)} ${rendered2}}}`;
910
926
  }
911
- return null;
927
+ if (parsed.kind !== "call")
928
+ return null;
929
+ const node = matchRegisteredCall(ctx, parsed.callee, parsed.args);
930
+ if (!node || node.kind !== "guard-list" || node.helper !== "query")
931
+ return null;
932
+ const rendered = renderLoweringNode(ctx, node);
933
+ return rendered === null ? null : `{{bf_attr ${JSON.stringify(attrName)} (${rendered})}}`;
934
+ }
935
+ function isQueryGuardListCall(ctx, node) {
936
+ if (node.kind !== "call")
937
+ return false;
938
+ const lowered = matchRegisteredCall(ctx, node.callee, node.args);
939
+ return lowered?.kind === "guard-list" && lowered.helper === "query";
940
+ }
941
+ function ternaryHasQueryBranch(ctx, consequent, alternate) {
942
+ const branchHasQuery = (n) => n.kind === "conditional" ? ternaryHasQueryBranch(ctx, n.consequent, n.alternate) : isQueryGuardListCall(ctx, n);
943
+ return branchHasQuery(consequent) || branchHasQuery(alternate);
912
944
  }
913
945
  function renderLoweringNode(ctx, node) {
914
946
  const helper = goHelperName(node.helper);
@@ -1170,6 +1202,15 @@ function convertInitialValue(ctx, value, _typeInfo, propsParams, preParsed) {
1170
1202
  if (param2) {
1171
1203
  return propRef(param2);
1172
1204
  }
1205
+ const inlinedStr = ctx.resolveModuleStringConst(value);
1206
+ if (inlinedStr !== null)
1207
+ return inlinedStr;
1208
+ const inlinedNum = ctx.resolveModuleNumericConst(value);
1209
+ if (inlinedNum !== null)
1210
+ return inlinedNum;
1211
+ const inlinedBool = ctx.resolveModuleBooleanConst(value);
1212
+ if (inlinedBool !== null)
1213
+ return inlinedBool;
1173
1214
  }
1174
1215
  const propName = ctx.extractPropNameFromInitialValue(value, preParsed);
1175
1216
  const param = propName ? propsParams?.find((p) => p.name === propName) : undefined;
@@ -2890,7 +2931,9 @@ class GoTemplateAdapter extends BaseAdapter {
2890
2931
  extractPropNameFromInitialValue: (initialValue, preParsed) => this.extractPropNameFromInitialValue(initialValue, preParsed),
2891
2932
  extractPropFallback: (initialValue, preParsed) => this.extractPropFallback(initialValue, preParsed),
2892
2933
  extractCollisionDerivation: (parsed) => this.extractCollisionDerivation(parsed),
2893
- resolveModuleStringConst: (name) => this.resolveModuleStringConst(name)
2934
+ resolveModuleStringConst: (name) => this.resolveModuleStringConst(name),
2935
+ resolveModuleNumericConst: (name) => this.resolveModuleNumericConst(name),
2936
+ resolveModuleBooleanConst: (name) => this.resolveModuleBooleanConst(name)
2894
2937
  };
2895
2938
  get errors() {
2896
2939
  return this.state.errors;
@@ -2907,6 +2950,10 @@ class GoTemplateAdapter extends BaseAdapter {
2907
2950
  staticLoopBakeFailed = false;
2908
2951
  childComponentShapes = new Map;
2909
2952
  childContextConsumers = new Map;
2953
+ importAliases = new Map;
2954
+ resolveChildName(name) {
2955
+ return this.importAliases.get(name) ?? name;
2956
+ }
2910
2957
  constructor(options = {}) {
2911
2958
  super();
2912
2959
  this.options = {
@@ -2918,6 +2965,7 @@ class GoTemplateAdapter extends BaseAdapter {
2918
2965
  primeCompileState(ir) {
2919
2966
  this.state.propsObjectName = ir.metadata.propsObjectName;
2920
2967
  this.state.restPropsName = ir.metadata.restPropsName ?? null;
2968
+ this.importAliases = buildImportAliasMap(ir.metadata.imports ?? []);
2921
2969
  this.state.objectTypedPropNames = new Set((ir.metadata.propsParams ?? []).filter((p) => p.type.kind === "object").map((p) => p.name));
2922
2970
  this.state.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
2923
2971
  this.state.localConstants = ir.metadata.localConstants ?? [];
@@ -2952,6 +3000,7 @@ class GoTemplateAdapter extends BaseAdapter {
2952
3000
  this.state.componentName = ir.metadata.componentName;
2953
3001
  this.state.errors = [];
2954
3002
  this.state.referencedDerivedConsts = new Set;
3003
+ this.state.templateReadRootFields = new Set;
2955
3004
  this.state.templateVarCounter = 0;
2956
3005
  this.state.pendingChildrenDefines = [];
2957
3006
  this.scope = BindingScope.EMPTY;
@@ -2980,7 +3029,7 @@ ${scriptRegistrations}${templateBody}
2980
3029
  template += `{{define "${d.name}"}}${d.content}{{end}}
2981
3030
  `;
2982
3031
  }
2983
- const types = this.generateTypes(ir);
3032
+ const types = this.generateTypes(ir, true);
2984
3033
  if (this.state.errors.length > 0) {
2985
3034
  ir.errors.push(...this.state.errors);
2986
3035
  }
@@ -3209,9 +3258,12 @@ ${scriptRegistrations}${templateBody}
3209
3258
  taken.add(desired);
3210
3259
  return desired;
3211
3260
  }
3212
- generateTypes(ir) {
3261
+ generateTypes(ir, preserveTemplateReadRootFields = false) {
3213
3262
  this.state.usesHtmlTemplate = false;
3214
3263
  this.state.usesFmt = false;
3264
+ if (!preserveTemplateReadRootFields) {
3265
+ this.state.templateReadRootFields = new Set;
3266
+ }
3215
3267
  this.primeCompileState(ir);
3216
3268
  const lines = [];
3217
3269
  const componentName = ir.metadata.componentName;
@@ -3355,10 +3407,17 @@ ${goFields.join(`
3355
3407
  const node = signal.parsed;
3356
3408
  if (!node || node.kind !== "array-literal" || node.elements.length === 0)
3357
3409
  return null;
3410
+ const name = `${componentName}${capitalizeFieldName(signal.getter)}Item`;
3411
+ return this.synthesizeStructsFromElements(node.elements, name);
3412
+ }
3413
+ synthesizeStructsFromElements(elements, name) {
3414
+ if (this.state.localTypeNames.has(name))
3415
+ return null;
3358
3416
  const order = [];
3359
- const goTypes = new Map;
3360
- for (let i = 0;i < node.elements.length; i++) {
3361
- const el = node.elements[i];
3417
+ const shapes = new Map;
3418
+ const nestedElements = new Map;
3419
+ for (let i = 0;i < elements.length; i++) {
3420
+ const el = elements[i];
3362
3421
  if (el.kind !== "object-literal")
3363
3422
  return null;
3364
3423
  const seen = new Set;
@@ -3370,37 +3429,93 @@ ${goFields.join(`
3370
3429
  const key = prop.key;
3371
3430
  if (!GO_IDENTIFIER.test(key))
3372
3431
  return null;
3432
+ seen.add(key);
3433
+ const isNestedArray = prop.value.kind === "array-literal" && prop.value.elements.every((e) => e.kind === "object-literal");
3434
+ if (isNestedArray) {
3435
+ const prevShape2 = shapes.get(key);
3436
+ if (prevShape2 === undefined) {
3437
+ if (i !== 0)
3438
+ return null;
3439
+ order.push(key);
3440
+ shapes.set(key, { kind: "nested-array" });
3441
+ nestedElements.set(key, []);
3442
+ } else if (prevShape2.kind !== "nested-array") {
3443
+ return null;
3444
+ }
3445
+ nestedElements.get(key).push(...prop.value.elements);
3446
+ continue;
3447
+ }
3373
3448
  const goType = this.scalarParsedGoType(prop.value);
3374
3449
  if (!goType)
3375
3450
  return null;
3376
- seen.add(key);
3377
- const prev = goTypes.get(key);
3378
- if (prev === undefined) {
3451
+ const prevShape = shapes.get(key);
3452
+ if (prevShape === undefined) {
3379
3453
  if (i !== 0)
3380
3454
  return null;
3381
3455
  order.push(key);
3382
- goTypes.set(key, goType);
3456
+ shapes.set(key, { kind: "scalar", goType });
3457
+ } else if (prevShape.kind !== "scalar") {
3458
+ return null;
3383
3459
  } else {
3384
- const merged = this.mergeScalarGoType(prev, goType);
3460
+ const merged = this.mergeScalarGoType(prevShape.goType, goType);
3385
3461
  if (!merged)
3386
3462
  return null;
3387
- goTypes.set(key, merged);
3463
+ shapes.set(key, { kind: "scalar", goType: merged });
3388
3464
  }
3389
3465
  }
3390
3466
  if (seen.size !== order.length)
3391
3467
  return null;
3392
3468
  }
3393
- const name = `${componentName}${capitalizeFieldName(signal.getter)}Item`;
3394
- if (this.state.localTypeNames.has(name))
3395
- return null;
3396
- return {
3469
+ const nestedStructs = [];
3470
+ const fields = [];
3471
+ const properties = [];
3472
+ for (const key of order) {
3473
+ const shape = shapes.get(key);
3474
+ if (shape.kind === "scalar") {
3475
+ fields.push({ tsName: key, goName: capitalizeFieldName(key), goType: shape.goType });
3476
+ properties.push({ name: key, type: this.scalarGoTypeToTypeInfo(shape.goType), optional: false, readonly: false });
3477
+ continue;
3478
+ }
3479
+ const nestedList = nestedElements.get(key);
3480
+ if (nestedList.length === 0)
3481
+ return null;
3482
+ const nestedName = `${name}${capitalizeFieldName(key)}Item`;
3483
+ const nested = this.synthesizeStructsFromElements(nestedList, nestedName);
3484
+ if (!nested)
3485
+ return null;
3486
+ nestedStructs.push(...nested);
3487
+ fields.push({ tsName: key, goName: capitalizeFieldName(key), goType: `[]${nestedName}` });
3488
+ properties.push({ name: key, type: this.synthSliceTypeInfo(nestedName), optional: false, readonly: false });
3489
+ }
3490
+ return [...nestedStructs, { name, fields, properties }];
3491
+ }
3492
+ scalarGoTypeToTypeInfo(goType) {
3493
+ if (goType === "string")
3494
+ return { kind: "primitive", raw: "string", primitive: "string" };
3495
+ if (goType === "bool")
3496
+ return { kind: "primitive", raw: "boolean", primitive: "boolean" };
3497
+ return { kind: "primitive", raw: "number", primitive: "number" };
3498
+ }
3499
+ synthSliceTypeInfo(name) {
3500
+ return { kind: "array", raw: `${name}[]`, elementType: { kind: "interface", raw: name } };
3501
+ }
3502
+ registerSynthStruct(lines, name, fields, properties, comment) {
3503
+ this.state.localTypeNames.add(name);
3504
+ this.state.localStructFields.set(name, new Map(fields.map((f) => [f.tsName, f.goName])));
3505
+ this.state.currentTypeDefinitions.push({
3506
+ kind: "type",
3397
3507
  name,
3398
- fields: order.map((key) => ({
3399
- tsName: key,
3400
- goName: capitalizeFieldName(key),
3401
- goType: goTypes.get(key)
3402
- }))
3403
- };
3508
+ definition: "",
3509
+ properties,
3510
+ loc: SYNTH_TYPE_LOC
3511
+ });
3512
+ const goFields = fields.map((f) => ` ${f.goName} ${f.goType} \`json:"${this.toJsonTag(f.tsName)}"\``);
3513
+ lines.push(comment);
3514
+ lines.push(`type ${name} struct {
3515
+ ${goFields.join(`
3516
+ `)}
3517
+ }`);
3518
+ lines.push("");
3404
3519
  }
3405
3520
  scalarParsedGoType(value) {
3406
3521
  if (value.kind === "unary" && value.op === "-" && value.argument.kind === "literal") {
@@ -3467,7 +3582,7 @@ ${goFields.join(`
3467
3582
  for (const nested of inputNested) {
3468
3583
  if (nested.loopMarkerId && this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey))
3469
3584
  continue;
3470
- lines.push(` ${nested.name}s []${nested.name}Input`);
3585
+ lines.push(` ${nested.name}s []${this.resolveChildName(nested.name)}Input`);
3471
3586
  }
3472
3587
  const takenInput = new Set(this.propParamFieldNamesUnion(ir.metadata.propsParams));
3473
3588
  for (const c of this.nonCollidingContextConsumers(takenInput)) {
@@ -3503,10 +3618,11 @@ ${goFields.join(`
3503
3618
  const wrapperName = this.loopBodyWrapperName(parentComponentName, nested);
3504
3619
  const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
3505
3620
  const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
3506
- lines.push(`// ${wrapperName} wraps ${nested.name}Props with per-row loop datum`);
3621
+ const declaredName = this.resolveChildName(nested.name);
3622
+ lines.push(`// ${wrapperName} wraps ${declaredName}Props with per-row loop datum`);
3507
3623
  lines.push(`// fields and child component slots for the loop body children. (#1897)`);
3508
3624
  lines.push(`type ${wrapperName} struct {`);
3509
- lines.push(` ${nested.name}Props`);
3625
+ lines.push(` ${declaredName}Props`);
3510
3626
  for (const f of datumFields) {
3511
3627
  lines.push(` ${f.goName} ${f.goType} \`json:"-"\``);
3512
3628
  }
@@ -3515,7 +3631,7 @@ ${goFields.join(`
3515
3631
  lines.push(` BfLoopItem ${scalarLoopType} \`json:"-"\``);
3516
3632
  }
3517
3633
  for (const child of bodyChildInstances) {
3518
- lines.push(` ${child.fieldName} ${child.name}Props \`json:"-"\``);
3634
+ lines.push(` ${child.fieldName} ${this.resolveChildName(child.name)}Props \`json:"-"\``);
3519
3635
  }
3520
3636
  lines.push("}");
3521
3637
  lines.push("");
@@ -3599,12 +3715,13 @@ ${goFields.join(`
3599
3715
  const staticWithoutBody = staticNested.filter((n) => !n.bodyChildren || n.bodyChildren.length === 0);
3600
3716
  for (const nested of staticWithoutBody) {
3601
3717
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
3718
+ const declaredName = this.resolveChildName(nested.name);
3602
3719
  const baked = nested.loopMarkerId ? this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey) : null;
3603
3720
  if (baked) {
3604
- lines.push(` ${varName} := make([]${nested.name}Props, ${baked.items.length})`);
3721
+ lines.push(` ${varName} := make([]${declaredName}Props, ${baked.items.length})`);
3605
3722
  baked.items.forEach((item, i) => {
3606
3723
  const fields = item.inputFields.map((f) => `${f.goField}: ${f.goValue}`).join(", ");
3607
- lines.push(` ${varName}[${i}] = New${nested.name}Props(${nested.name}Input{${fields}})`);
3724
+ lines.push(` ${varName}[${i}] = New${declaredName}Props(${declaredName}Input{${fields}})`);
3608
3725
  lines.push(` ${varName}[${i}].BfParent = scopeID`);
3609
3726
  lines.push(` ${varName}[${i}].BfMount = "${nested.slotId}"`);
3610
3727
  if (item.dataKey !== null) {
@@ -3614,9 +3731,9 @@ ${goFields.join(`
3614
3731
  lines.push("");
3615
3732
  continue;
3616
3733
  }
3617
- lines.push(` ${varName} := make([]${nested.name}Props, len(in.${nested.name}s))`);
3734
+ lines.push(` ${varName} := make([]${declaredName}Props, len(in.${nested.name}s))`);
3618
3735
  lines.push(` for i, item := range in.${nested.name}s {`);
3619
- lines.push(` ${varName}[i] = New${nested.name}Props(item)`);
3736
+ lines.push(` ${varName}[i] = New${declaredName}Props(item)`);
3620
3737
  lines.push(` ${varName}[i].BfParent = scopeID`);
3621
3738
  lines.push(` ${varName}[i].BfMount = "${nested.slotId}"`);
3622
3739
  const keyField = loopKeyToGoFieldPath(nested.loopKey, nested.loopParam);
@@ -3731,8 +3848,14 @@ ${goFields.join(`
3731
3848
  lines.push(` ${fieldName}: ${hoisted.varName},`);
3732
3849
  } else {
3733
3850
  const bakeType = this.state.synthStructTypes.get(signal.getter) ?? signal.type;
3851
+ const resolvedParsed = this.resolvedSignalParsed(signal);
3734
3852
  const initialValue = convertInitialValue(this.emitCtx, signal.initialValue, bakeType, ir.metadata.propsParams, signal.parsed);
3735
3853
  lines.push(` ${fieldName}: ${initialValue},`);
3854
+ if (resolvedParsed?.kind === "object-literal" && jsLiteralToGo(this.emitCtx, bakeType, resolvedParsed) === null) {
3855
+ const step = this.state.ssrSeedPlan.steps.find((s) => s.kind === "derived" && s.origin === "signal" && s.name === signal.getter);
3856
+ if (step?.kind === "derived")
3857
+ this.refuseUnbakeableDerivedObjectLiteral(signal.getter, signal.loc, step.frees);
3858
+ }
3736
3859
  }
3737
3860
  }
3738
3861
  for (const nested of staticWithoutBody) {
@@ -3832,19 +3955,20 @@ ${goFields.join(`
3832
3955
  emitStaticChildInstances(lines, ir) {
3833
3956
  const staticChildren = this.collectStaticChildInstances(ir.root, ir.metadata.propsParams);
3834
3957
  for (const child of staticChildren) {
3835
- lines.push(` ${child.fieldName}: New${child.name}Props(${child.name}Input{`);
3958
+ const declaredName = this.resolveChildName(child.name);
3959
+ lines.push(` ${child.fieldName}: New${declaredName}Props(${declaredName}Input{`);
3836
3960
  lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
3837
3961
  lines.push(` BfParent: scopeID,`);
3838
3962
  lines.push(` BfMount: "${child.slotId}",`);
3839
3963
  if (child.contextBindings) {
3840
- for (const consumer of this.childContextConsumers.get(child.name) ?? []) {
3964
+ for (const consumer of this.childContextConsumers.get(declaredName) ?? []) {
3841
3965
  const goVal = child.contextBindings.get(consumer.contextName);
3842
3966
  if (goVal !== undefined) {
3843
3967
  lines.push(` ${this.contextFieldName(consumer)}: ${goVal},`);
3844
3968
  }
3845
3969
  }
3846
3970
  }
3847
- const childShape = this.childComponentShapes.get(child.name);
3971
+ const childShape = this.childComponentShapes.get(declaredName);
3848
3972
  const restBagEntries = [];
3849
3973
  const emitChildField = (jsxName, goValue) => {
3850
3974
  if (childShape && childShape.restBagField && !childShape.paramNames.has(jsxName)) {
@@ -3941,6 +4065,7 @@ ${goFields.join(`
3941
4065
  lines.push(`// New${componentName}Props creates ${propsTypeName} from ${inputTypeName}.`);
3942
4066
  for (const nested of signalDynamicNested) {
3943
4067
  const arrayField = `${nested.name}s`;
4068
+ const declaredName = this.resolveChildName(nested.name);
3944
4069
  lines.push(`//`);
3945
4070
  lines.push(`// NOTE: \`${arrayField}\` is populated by the route handler, not by`);
3946
4071
  lines.push(`// New${componentName}Props — the SSR template iterates over it`);
@@ -3948,9 +4073,9 @@ ${goFields.join(`
3948
4073
  lines.push(`// assign it before passing the props to your renderer. Example:`);
3949
4074
  lines.push(`//`);
3950
4075
  lines.push(`// props := New${componentName}Props(${inputTypeName}{ /* ... */ })`);
3951
- lines.push(`// props.${arrayField} = make([]${nested.name}Props, len(items))`);
4076
+ lines.push(`// props.${arrayField} = make([]${declaredName}Props, len(items))`);
3952
4077
  lines.push(`// for i, item := range items {`);
3953
- lines.push(`// props.${arrayField}[i] = New${nested.name}Props(${nested.name}Input{ /* fields */ })`);
4078
+ lines.push(`// props.${arrayField}[i] = New${declaredName}Props(${declaredName}Input{ /* fields */ })`);
3954
4079
  lines.push(`// props.${arrayField}[i].BfParent = props.ScopeID`);
3955
4080
  lines.push(`// props.${arrayField}[i].BfMount = "${nested.slotId}"`);
3956
4081
  lines.push(`// }`);
@@ -3989,9 +4114,11 @@ ${goFields.join(`
3989
4114
  const wrapperType = this.loopBodyWrapperName(componentName, nested);
3990
4115
  const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
3991
4116
  const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren, ir.metadata.propsParams);
4117
+ const declaredName = this.resolveChildName(nested.name);
3992
4118
  for (const child of bodyChildInstances) {
3993
4119
  const childVar = `child_${child.fieldName}`;
3994
- lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
4120
+ const childDeclaredName = this.resolveChildName(child.name);
4121
+ lines.push(` ${childVar} := New${childDeclaredName}Props(${childDeclaredName}Input{`);
3995
4122
  lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
3996
4123
  lines.push(` BfParent: scopeID,`);
3997
4124
  lines.push(` BfMount: "${child.slotId}",`);
@@ -4011,7 +4138,7 @@ ${goFields.join(`
4011
4138
  lines.push(` ${varName} := make([]${wrapperType}, len(${dataVar}))`);
4012
4139
  lines.push(` for i, item := range ${dataVar} {`);
4013
4140
  lines.push(` ${varName}[i] = ${wrapperType}{`);
4014
- lines.push(` ${nested.name}Props: New${nested.name}Props(${nested.name}Input{`);
4141
+ lines.push(` ${declaredName}Props: New${declaredName}Props(${declaredName}Input{`);
4015
4142
  lines.push(` BfParent: scopeID,`);
4016
4143
  lines.push(` BfMount: "${nested.slotId}",`);
4017
4144
  for (const prop of nested.props ?? []) {
@@ -4066,9 +4193,11 @@ ${goFields.join(`
4066
4193
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
4067
4194
  const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
4068
4195
  const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren, ir.metadata.propsParams);
4196
+ const declaredName = this.resolveChildName(nested.name);
4069
4197
  for (const child of bodyChildInstances) {
4070
4198
  const childVar = `child_${child.fieldName}`;
4071
- lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
4199
+ const childDeclaredName = this.resolveChildName(child.name);
4200
+ lines.push(` ${childVar} := New${childDeclaredName}Props(${childDeclaredName}Input{`);
4072
4201
  lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
4073
4202
  lines.push(` BfParent: scopeID,`);
4074
4203
  lines.push(` BfMount: "${child.slotId}",`);
@@ -4087,7 +4216,7 @@ ${goFields.join(`
4087
4216
  lines.push(` ${varName} := make([]${wrapperType}, len(bakedData))`);
4088
4217
  lines.push(` for i, item := range bakedData {`);
4089
4218
  lines.push(` ${varName}[i] = ${wrapperType}{`);
4090
- lines.push(` ${nested.name}Props: New${nested.name}Props(${nested.name}Input{`);
4219
+ lines.push(` ${declaredName}Props: New${declaredName}Props(${declaredName}Input{`);
4091
4220
  lines.push(` BfParent: scopeID,`);
4092
4221
  lines.push(` BfMount: "${nested.slotId}",`);
4093
4222
  lines.push(` }),`);
@@ -4142,21 +4271,7 @@ ${goFields.join(`
4142
4271
  visit(prop.type, desiredName, prop.name);
4143
4272
  }
4144
4273
  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("");
4274
+ this.registerSynthStruct(lines, desiredName, fields, typeInfo.properties ?? [], `// ${desiredName} is a synthesised type for an anonymous object type (#2674).`);
4160
4275
  };
4161
4276
  const visitArrayElem = (elemType, parentName, propName) => {
4162
4277
  if (!elemType)
@@ -4208,20 +4323,11 @@ ${goFields.join(`
4208
4323
  const synth = this.synthesizeStructFromSignal(signal, componentName);
4209
4324
  if (!synth)
4210
4325
  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("");
4326
+ for (const s of synth) {
4327
+ this.registerSynthStruct(lines, s.name, s.fields, s.properties, `// ${s.name} is a synthesised element type for the ${signal.getter} signal.`);
4328
+ }
4329
+ const top = synth[synth.length - 1];
4330
+ this.state.synthStructTypes.set(signal.getter, this.synthSliceTypeInfo(top.name));
4225
4331
  }
4226
4332
  }
4227
4333
  resolveNestedLoopItemTypes(ir, nestedComponents) {
@@ -4364,7 +4470,7 @@ ${goFields.join(`
4364
4470
  for (const nested of nestedComponents) {
4365
4471
  if (this.isOrphanedClientOnlyNested(nested))
4366
4472
  continue;
4367
- const elemType = nested.bodyChildren?.length ? this.loopBodyWrapperName(componentName, nested) : `${nested.name}Props`;
4473
+ const elemType = nested.bodyChildren?.length ? this.loopBodyWrapperName(componentName, nested) : `${this.resolveChildName(nested.name)}Props`;
4368
4474
  if (nested.isDynamic && !nested.isPropDerived) {
4369
4475
  lines.push(` ${nested.name}s []${elemType} \`json:"-"\``);
4370
4476
  } else if (nested.isDynamic && nested.isPropDerived && !propDrivingFieldNames.has(`${nested.name}s`)) {
@@ -4376,7 +4482,7 @@ ${goFields.join(`
4376
4482
  }
4377
4483
  const staticChildren = this.collectStaticChildInstances(ir.root, ir.metadata.propsParams);
4378
4484
  for (const child of staticChildren) {
4379
- lines.push(` ${child.fieldName} ${child.name}Props \`json:"-"\``);
4485
+ lines.push(` ${child.fieldName} ${this.resolveChildName(child.name)}Props \`json:"-"\``);
4380
4486
  }
4381
4487
  for (const slot of spreadSlots) {
4382
4488
  const jsonTag = "-";
@@ -4755,6 +4861,22 @@ ${goFields.join(`
4755
4861
  resolvedSignalParsed(signal) {
4756
4862
  return resolveSignalParsedThroughSeedPlan(this.state, signal);
4757
4863
  }
4864
+ refuseUnbakeableDerivedObjectLiteral(name, loc, frees) {
4865
+ if (frees.length === 0)
4866
+ return;
4867
+ if (!this.state.templateReadRootFields.has(name))
4868
+ return;
4869
+ this.state.errors.push({
4870
+ code: "BF101",
4871
+ severity: "error",
4872
+ 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.`,
4873
+ loc,
4874
+ suggestion: {
4875
+ 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.`,
4876
+ escape: [{ kind: "client-directive" }]
4877
+ }
4878
+ });
4879
+ }
4758
4880
  extractPropFallback(initialValue, preParsed) {
4759
4881
  const structural = preParsed ? this.extractPropFallbackFromParsed(preParsed) : null;
4760
4882
  if (structural)
@@ -5083,6 +5205,7 @@ ${goFields.join(`
5083
5205
  return hit !== null && hit.depth > 0 && hit.binding.source === "item";
5084
5206
  }
5085
5207
  rootFieldRef(name) {
5208
+ this.state.templateReadRootFields.add(name);
5086
5209
  const prefix = this.inLoop ? "$." : ".";
5087
5210
  return `${prefix}${capitalizeFieldName(name)}`;
5088
5211
  }
@@ -5104,6 +5227,9 @@ ${goFields.join(`
5104
5227
  return null;
5105
5228
  return `"${escapeGoString(value)}"`;
5106
5229
  }
5230
+ findModuleConst(name) {
5231
+ return this.state.localConstants.find((k) => k.name === name && k.isModule && !k.containsArrow);
5232
+ }
5107
5233
  resolveModuleNumericConst(name) {
5108
5234
  if (this.isCurrentLoopItem(name))
5109
5235
  return null;
@@ -5111,12 +5237,25 @@ ${goFields.join(`
5111
5237
  return null;
5112
5238
  if (this.isOuterLoopParam(name))
5113
5239
  return null;
5114
- const c = this.state.localConstants.find((k) => k.name === name && k.isModule && !k.containsArrow);
5240
+ const c = this.findModuleConst(name);
5115
5241
  if (!c || c.value === undefined)
5116
5242
  return null;
5117
5243
  const v = c.value.trim().replace(/(?<=\d)_(?=\d)/g, "");
5118
5244
  return /^-?\d+(\.\d+)?$/.test(v) ? v : null;
5119
5245
  }
5246
+ resolveModuleBooleanConst(name) {
5247
+ if (this.isCurrentLoopItem(name))
5248
+ return null;
5249
+ if (this.loopVarRefCount.has(name))
5250
+ return null;
5251
+ if (this.isOuterLoopParam(name))
5252
+ return null;
5253
+ const c = this.findModuleConst(name);
5254
+ if (!c || c.value === undefined)
5255
+ return null;
5256
+ const v = c.value.trim();
5257
+ return v === "true" || v === "false" ? v : null;
5258
+ }
5120
5259
  literal(value, literalType) {
5121
5260
  if (literalType === "string")
5122
5261
  return `"${value}"`;
@@ -5125,6 +5264,9 @@ ${goFields.join(`
5125
5264
  return String(value);
5126
5265
  }
5127
5266
  call(callee, args, emit) {
5267
+ const lowered = lowerRegisteredCallNode(this.emitCtx, callee, args);
5268
+ if (lowered !== null)
5269
+ return lowered;
5128
5270
  if (callee.kind === "identifier" && args.length === 0) {
5129
5271
  return this.searchParamsFieldRef(callee.name) ?? this.rootFieldRef(callee.name);
5130
5272
  }
@@ -5754,8 +5896,10 @@ ${goFields.join(`
5754
5896
  }
5755
5897
  const signal = localVarMap.get(expr.name);
5756
5898
  if (signal) {
5899
+ this.rootFieldRef(signal);
5757
5900
  return `$.${capitalizeFieldName(signal)}`;
5758
5901
  }
5902
+ this.rootFieldRef(expr.name);
5759
5903
  return `.${capitalizeFieldName(expr.name)}`;
5760
5904
  }
5761
5905
  case "literal":
@@ -5788,6 +5932,7 @@ ${goFields.join(`
5788
5932
  return `${paramPrefix}.${capitalizeFieldName(expr.callee.property)}`;
5789
5933
  }
5790
5934
  if (expr.callee.kind === "identifier" && expr.args.length === 0) {
5935
+ this.rootFieldRef(expr.callee.name);
5791
5936
  return `$.${capitalizeFieldName(expr.callee.name)}`;
5792
5937
  }
5793
5938
  if (asCallbackMethodCall4(expr) !== null) {
@@ -5960,7 +6105,8 @@ ${goFields.join(`
5960
6105
  return this.scope.isBound(name) || this.loopVarRefCount.has(name);
5961
6106
  }
5962
6107
  loopRowChildPropOverrides(comp) {
5963
- const childShape = this.childComponentShapes.get(comp.name);
6108
+ const declaredName = this.resolveChildName(comp.name);
6109
+ const childShape = this.childComponentShapes.get(declaredName);
5964
6110
  const args = [];
5965
6111
  let needsRebuild = false;
5966
6112
  for (const prop of comp.props) {
@@ -5980,10 +6126,10 @@ ${goFields.join(`
5980
6126
  if (!free || ![...free].some((name) => this.isLoopShadowedName(name)))
5981
6127
  continue;
5982
6128
  {
5983
- const derived = this.childDerivedFieldDeps.get(comp.name);
6129
+ const derived = this.childDerivedFieldDeps.get(declaredName);
5984
6130
  const overriddenField = capitalizeFieldName(prop.name);
5985
6131
  const staleField = derived ? [...derived].find(([, deps]) => deps.has(overriddenField))?.[0] : undefined;
5986
- if (staleField && !this.childRepropsReady.has(comp.name)) {
6132
+ if (staleField && !this.childRepropsReady.has(declaredName)) {
5987
6133
  this.state.errors.push({
5988
6134
  code: "BF101",
5989
6135
  severity: "error",
@@ -5997,8 +6143,8 @@ ${goFields.join(`
5997
6143
  }
5998
6144
  if (staleField) {
5999
6145
  needsRebuild = true;
6000
- if (!this.repropsOwner.has(comp.name)) {
6001
- this.repropsOwner.set(comp.name, this.state.componentName);
6146
+ if (!this.repropsOwner.has(declaredName)) {
6147
+ this.repropsOwner.set(declaredName, this.state.componentName);
6002
6148
  }
6003
6149
  }
6004
6150
  }
@@ -6024,7 +6170,7 @@ ${goFields.join(`
6024
6170
  });
6025
6171
  continue;
6026
6172
  }
6027
- const fieldName = this.childPropFieldNames.get(comp.name)?.get(prop.name) ?? capitalizeFieldName(prop.name);
6173
+ const fieldName = this.childPropFieldNames.get(declaredName)?.get(prop.name) ?? capitalizeFieldName(prop.name);
6028
6174
  args.push(`${JSON.stringify(fieldName)} ${wrapIfMultiToken(go)}`);
6029
6175
  }
6030
6176
  if (args.length === 0)
@@ -6570,7 +6716,8 @@ ${goFields.join(`
6570
6716
  }
6571
6717
  queueDynamicPropDefine(comp) {
6572
6718
  const args = [];
6573
- const childShape = this.childComponentShapes.get(comp.name);
6719
+ const declaredName = this.resolveChildName(comp.name);
6720
+ const childShape = this.childComponentShapes.get(declaredName);
6574
6721
  for (const prop of comp.props) {
6575
6722
  if (prop.value.kind !== "jsx-children" || prop.name === "children")
6576
6723
  continue;
@@ -6597,7 +6744,7 @@ ${goFields.join(`
6597
6744
  content: this.renderChildren(children)
6598
6745
  });
6599
6746
  }
6600
- const fieldName = this.childPropFieldNames.get(comp.name)?.get(prop.name) ?? capitalizeFieldName(prop.name);
6747
+ const fieldName = this.childPropFieldNames.get(declaredName)?.get(prop.name) ?? capitalizeFieldName(prop.name);
6601
6748
  args.push(`${JSON.stringify(fieldName)} (bf_tmpl ${JSON.stringify(name)} .)`);
6602
6749
  }
6603
6750
  return args.length > 0 ? args.join(" ") : null;
@@ -6629,31 +6776,32 @@ ${goFields.join(`
6629
6776
  if (comp.dynamicTag) {
6630
6777
  return this.renderChildren(comp.children);
6631
6778
  }
6779
+ const declaredName = this.resolveChildName(comp.name);
6632
6780
  let templateCall;
6633
6781
  if (this.inLoop && (this.loopWrapperStack[this.loopWrapperStack.length - 1] ?? false)) {
6634
6782
  const loopBodyDefine = this.queueLoopBodyChildrenDefine(comp);
6635
6783
  if (loopBodyDefine) {
6636
6784
  const bodyData = this.loopScalarItemStack[this.loopScalarItemStack.length - 1] ? ".BfLoopItem" : ".";
6637
- templateCall = `{{template "${comp.name}" (bf_with_children . (bf_tmpl "${loopBodyDefine}" ${bodyData}))}}`;
6785
+ templateCall = `{{template "${declaredName}" (bf_with_children . (bf_tmpl "${loopBodyDefine}" ${bodyData}))}}`;
6638
6786
  } else {
6639
- templateCall = `{{template "${comp.name}" .}}`;
6787
+ templateCall = `{{template "${declaredName}" .}}`;
6640
6788
  }
6641
6789
  } else if (this.inLoop && comp.slotId) {
6642
6790
  const suffix = slotIdToFieldSuffix(comp.slotId);
6643
6791
  const overrides = this.loopRowChildPropOverrides(comp);
6644
6792
  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}}}`;
6793
+ 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}`;
6794
+ templateCall = loopBodyDefine ? `{{template "${declaredName}" (bf_with_children ${base} (bf_tmpl "${loopBodyDefine}" .))}}` : `{{template "${declaredName}" ${base}}}`;
6647
6795
  } else if (this.inLoop) {
6648
- templateCall = `{{template "${comp.name}" .}}`;
6796
+ templateCall = `{{template "${declaredName}" .}}`;
6649
6797
  } else if (comp.slotId) {
6650
6798
  const suffix = slotIdToFieldSuffix(comp.slotId);
6651
6799
  const childrenDefine = this.queueDynamicChildrenDefine(comp);
6652
6800
  const propArgs = this.queueDynamicPropDefine(comp);
6653
6801
  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}}}`;
6802
+ templateCall = childrenDefine ? `{{template "${declaredName}" (bf_with_children ${base} (bf_tmpl "${childrenDefine}" .))}}` : `{{template "${declaredName}" ${base}}}`;
6655
6803
  } else {
6656
- templateCall = `{{template "${comp.name}" .${comp.name}}}`;
6804
+ templateCall = `{{template "${declaredName}" .${comp.name}}}`;
6657
6805
  }
6658
6806
  if (ctx?.isRootOfClientComponent) {
6659
6807
  return `{{bfScopeComment .}}${templateCall}`;
@@ -6704,15 +6852,21 @@ ${children}`;
6704
6852
  const test = parsed.test;
6705
6853
  if (undef(parsed.alternate) && !undef(parsed.consequent)) {
6706
6854
  const { condition: goCond, preamble } = this.convertConditionToGo(this.isTemplateFragment(this.renderParsedExpr(test), test.kind) ? value.expr : value.expr.slice(0, value.expr.indexOf("?")).trim());
6707
- const valueExpr = this.renderParsedExpr(parsed.consequent);
6708
- const body = `${name}="{{${valueExpr}}}"`;
6855
+ const attrConsequent = lowerRegisteredAttrCall(this.emitCtx, name, parsed.consequent);
6856
+ const body = attrConsequent !== null ? attrConsequent : `${name}="{{${this.renderParsedExpr(parsed.consequent)}}}"`;
6709
6857
  return `${preamble}{{if ${goCond}}}${body}{{end}}`;
6710
6858
  }
6859
+ const attrTernary = lowerRegisteredAttrCall(this.emitCtx, name, parsed);
6860
+ if (attrTernary !== null)
6861
+ return attrTernary;
6711
6862
  return `${name}="{{${this.renderParsedExpr(parsed)}}}"`;
6712
6863
  }
6713
6864
  if (parsed.kind === "template-literal") {
6714
6865
  return `${name}="${this.renderParsedExpr(parsed)}"`;
6715
6866
  }
6867
+ const attrAction = lowerRegisteredAttrCall(this.emitCtx, name, parsed);
6868
+ if (attrAction !== null)
6869
+ return attrAction;
6716
6870
  const bareId = value.expr.trim();
6717
6871
  const propName = this.state.propsObjectName && bareId.startsWith(`${this.state.propsObjectName}.`) ? bareId.slice(this.state.propsObjectName.length + 1) : bareId;
6718
6872
  if (/^[A-Za-z_$][\w$]*$/.test(propName) && this.state.nillablePropNames.has(propName)) {