@barefootjs/go-template 0.7.0 → 0.9.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.
package/dist/build.js CHANGED
@@ -372,7 +372,10 @@ import {
372
372
  identifierPath,
373
373
  emitParsedExpr,
374
374
  emitIRNode,
375
- emitAttrValue
375
+ emitAttrValue,
376
+ augmentInheritedPropAccesses,
377
+ parseRecordIndexAccess,
378
+ collectContextConsumers
376
379
  } from "@barefootjs/jsx";
377
380
  import { findInterpolationEnd } from "@barefootjs/jsx/scanner";
378
381
  function wrapIfMultiToken(rendered) {
@@ -457,7 +460,13 @@ class GoTemplateAdapter extends BaseAdapter {
457
460
  localStructFields = new Map;
458
461
  synthStructTypes = new Map;
459
462
  usesHtmlTemplate = false;
463
+ usesFmt = false;
464
+ childComponentShapes = new Map;
460
465
  moduleStringConsts = new Map;
466
+ localConstants = [];
467
+ contextConsumers = [];
468
+ childContextConsumers = new Map;
469
+ nillablePropNames = new Set;
461
470
  constructor(options = {}) {
462
471
  super();
463
472
  this.options = {
@@ -473,6 +482,10 @@ class GoTemplateAdapter extends BaseAdapter {
473
482
  this.propsObjectName = ir.metadata.propsObjectName;
474
483
  this.restPropsName = ir.metadata.restPropsName ?? null;
475
484
  this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
485
+ this.localConstants = ir.metadata.localConstants ?? [];
486
+ this.contextConsumers = collectContextConsumers(ir.metadata);
487
+ augmentInheritedPropAccesses(ir);
488
+ this.nillablePropNames = this.collectNillablePropNames(ir);
476
489
  if (!options?.siblingTemplatesRegistered) {
477
490
  this.checkImportedLoopChildComponents(ir);
478
491
  }
@@ -685,8 +698,46 @@ ${scriptRegistrations}${templateBody}
685
698
  return `{{if .Scripts}}${registrations.join("")}{{end}}
686
699
  `;
687
700
  }
701
+ registerChildComponentShape(ir) {
702
+ const name = ir.metadata.componentName;
703
+ if (!name)
704
+ return;
705
+ const paramNames = new Set((ir.metadata.propsParams ?? []).map((p) => p.name));
706
+ const restPropsName = ir.metadata.restPropsName ?? null;
707
+ const restBagField = restPropsName ? this.capitalizeFieldName(restPropsName) : null;
708
+ this.childComponentShapes.set(name, { paramNames, restBagField });
709
+ this.childContextConsumers.set(name, collectContextConsumers(ir.metadata));
710
+ }
711
+ contextFieldName(c) {
712
+ return this.capitalizeFieldName(c.localName);
713
+ }
714
+ contextConsumerGoType(c) {
715
+ if (typeof c.defaultValue === "number")
716
+ return "int";
717
+ if (typeof c.defaultValue === "boolean")
718
+ return "bool";
719
+ return "string";
720
+ }
721
+ contextConsumerGoDefault(c) {
722
+ if (typeof c.defaultValue === "number")
723
+ return String(c.defaultValue);
724
+ if (typeof c.defaultValue === "boolean")
725
+ return String(c.defaultValue);
726
+ if (typeof c.defaultValue === "string")
727
+ return `"${this.escapeGoString(c.defaultValue)}"`;
728
+ return '""';
729
+ }
730
+ nonCollidingContextConsumers(taken) {
731
+ return this.contextConsumers.filter((c) => !taken.has(this.contextFieldName(c)));
732
+ }
688
733
  generateTypes(ir) {
689
734
  this.usesHtmlTemplate = false;
735
+ this.usesFmt = false;
736
+ this.propsObjectName = ir.metadata.propsObjectName;
737
+ augmentInheritedPropAccesses(ir);
738
+ this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
739
+ this.localConstants = ir.metadata.localConstants ?? [];
740
+ this.contextConsumers = collectContextConsumers(ir.metadata);
690
741
  const lines = [];
691
742
  const componentName = ir.metadata.componentName;
692
743
  this.localTypeNames = new Set;
@@ -748,6 +799,8 @@ ${goFields.join(`
748
799
  header.push(`package ${this.options.packageName}`);
749
800
  header.push("");
750
801
  header.push("import (");
802
+ if (this.usesFmt)
803
+ header.push('\t"fmt"');
751
804
  if (this.usesHtmlTemplate)
752
805
  header.push('\t"html/template"');
753
806
  header.push('\t"math/rand"');
@@ -908,6 +961,19 @@ ${goFields.join(`
908
961
  }
909
962
  return overrides;
910
963
  }
964
+ resolvePropGoType(param, propTypeOverrides) {
965
+ return propTypeOverrides.get(param.name) ?? this.typeInfoToGo(param.type, param.defaultValue);
966
+ }
967
+ collectNillablePropNames(ir) {
968
+ const propTypeOverrides = this.buildPropTypeOverrides(ir);
969
+ const nillable = new Set;
970
+ for (const param of ir.metadata.propsParams) {
971
+ if (this.resolvePropGoType(param, propTypeOverrides) === "interface{}") {
972
+ nillable.add(param.name);
973
+ }
974
+ }
975
+ return nillable;
976
+ }
911
977
  generateInputStruct(lines, ir, componentName, nestedComponents, propTypeOverrides, spreadSlots) {
912
978
  const inputTypeName = `${componentName}Input`;
913
979
  lines.push(`// ${inputTypeName} is the user-facing input type.`);
@@ -921,12 +987,16 @@ ${goFields.join(`
921
987
  const fieldName = this.capitalizeFieldName(param.name);
922
988
  if (nestedArrayFields.has(fieldName))
923
989
  continue;
924
- const goType = propTypeOverrides.get(param.name) ?? this.typeInfoToGo(param.type, param.defaultValue);
990
+ const goType = this.resolvePropGoType(param, propTypeOverrides);
925
991
  lines.push(` ${fieldName} ${goType}`);
926
992
  }
927
993
  for (const nested of inputNested) {
928
994
  lines.push(` ${nested.name}s []${nested.name}Input`);
929
995
  }
996
+ const takenInput = new Set(ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)));
997
+ for (const c of this.nonCollidingContextConsumers(takenInput)) {
998
+ lines.push(` ${this.contextFieldName(c)} ${this.contextConsumerGoType(c)}`);
999
+ }
930
1000
  const restPropsName = ir.metadata.restPropsName;
931
1001
  if (restPropsName) {
932
1002
  const seen = new Set;
@@ -960,7 +1030,7 @@ ${goFields.join(`
960
1030
  const fieldName = this.capitalizeFieldName(param.name);
961
1031
  if (nestedArrayFields.has(fieldName))
962
1032
  continue;
963
- const goType = propTypeOverrides.get(param.name) ?? this.typeInfoToGo(param.type, param.defaultValue);
1033
+ const goType = this.resolvePropGoType(param, propTypeOverrides);
964
1034
  const jsonTag = this.toJsonTag(param.name);
965
1035
  lines.push(` ${fieldName} ${goType} \`json:"${jsonTag}"\``);
966
1036
  propFieldNames.add(fieldName);
@@ -1004,6 +1074,15 @@ ${goFields.join(`
1004
1074
  const goType = this.inferMemoType(memo, ir.metadata.signals, propsParamMap);
1005
1075
  lines.push(` ${fieldName} ${goType} \`json:"${jsonTag}"\``);
1006
1076
  }
1077
+ const takenProps = new Set([
1078
+ ...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
1079
+ ...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
1080
+ ...ir.metadata.memos.map((m) => this.capitalizeFieldName(m.name))
1081
+ ]);
1082
+ for (const c of this.nonCollidingContextConsumers(takenProps)) {
1083
+ const jsonTag = this.toJsonTag(c.localName);
1084
+ lines.push(` ${this.contextFieldName(c)} ${this.contextConsumerGoType(c)} \`json:"${jsonTag}"\``);
1085
+ }
1007
1086
  for (const nested of nestedComponents) {
1008
1087
  if (nested.isDynamic && !nested.isPropDerived) {
1009
1088
  lines.push(` ${nested.name}s []${nested.name}Props \`json:"-"\``);
@@ -1113,25 +1192,55 @@ ${goFields.join(`
1113
1192
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
1114
1193
  lines.push(` ${nested.name}s: ${varName},`);
1115
1194
  }
1195
+ const memoPropsParamMap = new Map(ir.metadata.propsParams.map((p) => [p.name, p]));
1116
1196
  for (const memo of ir.metadata.memos) {
1117
1197
  const fieldName = this.capitalizeFieldName(memo.name);
1118
- const memoValue = this.computeMemoInitialValue(memo, ir.metadata.signals, ir.metadata.propsParams, propFallbackVars);
1198
+ const goType = this.inferMemoType(memo, ir.metadata.signals, memoPropsParamMap);
1199
+ const memoValue = this.computeMemoInitialValue(memo, ir.metadata.signals, ir.metadata.propsParams, propFallbackVars, goType);
1119
1200
  lines.push(` ${fieldName}: ${memoValue},`);
1120
1201
  }
1202
+ const takenInit = new Set([
1203
+ ...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
1204
+ ...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
1205
+ ...ir.metadata.memos.map((m) => this.capitalizeFieldName(m.name))
1206
+ ]);
1207
+ for (const c of this.nonCollidingContextConsumers(takenInit)) {
1208
+ const field = this.contextFieldName(c);
1209
+ const def = this.contextConsumerGoDefault(c);
1210
+ const defaulted = c.defaultValue === null || def === '""' || def === "0" || def === "false" ? `in.${field}` : this.applyGoFallback(`in.${field}`, def);
1211
+ lines.push(` ${field}: ${defaulted},`);
1212
+ }
1121
1213
  const staticChildren = this.collectStaticChildInstances(ir.root);
1122
1214
  for (const child of staticChildren) {
1123
1215
  lines.push(` ${child.fieldName}: New${child.name}Props(${child.name}Input{`);
1124
1216
  lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
1125
1217
  lines.push(` BfParent: scopeID,`);
1126
1218
  lines.push(` BfMount: "${child.slotId}",`);
1219
+ if (child.contextBindings) {
1220
+ for (const consumer of this.childContextConsumers.get(child.name) ?? []) {
1221
+ const goVal = child.contextBindings.get(consumer.contextName);
1222
+ if (goVal !== undefined) {
1223
+ lines.push(` ${this.contextFieldName(consumer)}: ${goVal},`);
1224
+ }
1225
+ }
1226
+ }
1227
+ const childShape = this.childComponentShapes.get(child.name);
1228
+ const restBagEntries = [];
1229
+ const emitChildField = (jsxName, goValue) => {
1230
+ if (childShape && childShape.restBagField && !childShape.paramNames.has(jsxName)) {
1231
+ restBagEntries.push(`${JSON.stringify(jsxName)}: ${goValue}`);
1232
+ return;
1233
+ }
1234
+ lines.push(` ${this.capitalizeFieldName(jsxName)}: ${goValue},`);
1235
+ };
1127
1236
  for (const prop of child.props) {
1128
1237
  switch (prop.value.kind) {
1129
1238
  case "literal":
1130
- lines.push(` ${this.capitalizeFieldName(prop.name)}: ${this.goLiteral(prop.value.value)},`);
1239
+ emitChildField(prop.name, this.goLiteral(prop.value.value));
1131
1240
  break;
1132
1241
  case "boolean-shorthand":
1133
1242
  case "boolean-attr":
1134
- lines.push(` ${this.capitalizeFieldName(prop.name)}: true,`);
1243
+ emitChildField(prop.name, "true");
1135
1244
  break;
1136
1245
  case "expression":
1137
1246
  case "spread":
@@ -1140,7 +1249,7 @@ ${goFields.join(`
1140
1249
  if (parts) {
1141
1250
  const goExpr = this.templatePartsToGoCode(parts, ir.metadata.propsParams);
1142
1251
  if (goExpr !== null) {
1143
- lines.push(` ${this.capitalizeFieldName(prop.name)}: ${goExpr},`);
1252
+ emitChildField(prop.name, goExpr);
1144
1253
  break;
1145
1254
  }
1146
1255
  }
@@ -1149,7 +1258,7 @@ ${goFields.join(`
1149
1258
  break;
1150
1259
  const resolvedValue = this.resolveDynamicPropValue(exprText, ir.metadata.signals, ir.metadata.memos, ir.metadata.propsParams);
1151
1260
  if (resolvedValue !== null) {
1152
- lines.push(` ${this.capitalizeFieldName(prop.name)}: ${resolvedValue},`);
1261
+ emitChildField(prop.name, resolvedValue);
1153
1262
  }
1154
1263
  break;
1155
1264
  }
@@ -1157,6 +1266,9 @@ ${goFields.join(`
1157
1266
  break;
1158
1267
  }
1159
1268
  }
1269
+ if (childShape?.restBagField && restBagEntries.length > 0) {
1270
+ lines.push(` ${childShape.restBagField}: map[string]any{${restBagEntries.join(", ")}},`);
1271
+ }
1160
1272
  if (child.childrenText !== null) {
1161
1273
  lines.push(` Children: ${JSON.stringify(child.childrenText)},`);
1162
1274
  } else if (child.childrenHtml !== null) {
@@ -1227,7 +1339,7 @@ ${goFields.join(`
1227
1339
  }
1228
1340
  collectStaticChildInstances(node) {
1229
1341
  const result = [];
1230
- this.collectStaticChildInstancesRecursive(node, result, false);
1342
+ this.collectStaticChildInstancesRecursive(node, result, false, new Map);
1231
1343
  return result;
1232
1344
  }
1233
1345
  extractTextChildren(children) {
@@ -1251,12 +1363,12 @@ ${goFields.join(`
1251
1363
  return null;
1252
1364
  return html;
1253
1365
  }
1254
- collectStaticChildInstancesRecursive(node, result, inLoop) {
1366
+ collectStaticChildInstancesRecursive(node, result, inLoop, providerCtx) {
1255
1367
  if (node.type === "component") {
1256
1368
  const comp = node;
1257
1369
  if (comp.dynamicTag) {
1258
1370
  for (const child of comp.children) {
1259
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1371
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
1260
1372
  }
1261
1373
  return;
1262
1374
  }
@@ -1268,48 +1380,65 @@ ${goFields.join(`
1268
1380
  props: comp.props,
1269
1381
  fieldName: `${comp.name}${suffix}`,
1270
1382
  childrenText: this.extractTextChildren(comp.children),
1271
- childrenHtml: this.extractHtmlChildren(comp.children)
1383
+ childrenHtml: this.extractHtmlChildren(comp.children),
1384
+ contextBindings: providerCtx.size > 0 ? providerCtx : undefined
1272
1385
  });
1273
1386
  }
1274
1387
  if (comp.name === "Portal" && comp.children) {
1275
1388
  for (const child of comp.children) {
1276
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1389
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
1277
1390
  }
1278
1391
  }
1279
1392
  } else if (node.type === "loop") {
1280
1393
  const loop = node;
1281
1394
  for (const child of loop.children) {
1282
- this.collectStaticChildInstancesRecursive(child, result, true);
1395
+ this.collectStaticChildInstancesRecursive(child, result, true, providerCtx);
1283
1396
  }
1284
1397
  } else if (node.type === "element") {
1285
1398
  const element = node;
1286
1399
  for (const child of element.children) {
1287
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1400
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
1288
1401
  }
1289
1402
  } else if (node.type === "fragment") {
1290
1403
  const fragment = node;
1291
1404
  for (const child of fragment.children) {
1292
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1405
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
1293
1406
  }
1294
1407
  } else if (node.type === "conditional") {
1295
1408
  const cond = node;
1296
- this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop);
1409
+ this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop, providerCtx);
1297
1410
  if (cond.whenFalse) {
1298
- this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop);
1411
+ this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx);
1299
1412
  }
1300
1413
  } else if (node.type === "provider") {
1301
1414
  const p = node;
1415
+ const childCtx = this.extendProviderContext(providerCtx, p);
1302
1416
  for (const child of p.children) {
1303
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1417
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, childCtx);
1304
1418
  }
1305
1419
  } else if (node.type === "async") {
1306
1420
  const a = node;
1307
- this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop);
1421
+ this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop, providerCtx);
1308
1422
  for (const child of a.children) {
1309
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1423
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
1310
1424
  }
1311
1425
  }
1312
1426
  }
1427
+ extendProviderContext(current, p) {
1428
+ const v = p.valueProp?.value;
1429
+ if (!v || v.kind !== "literal")
1430
+ return current;
1431
+ let goLit = null;
1432
+ if (typeof v.value === "string")
1433
+ goLit = `"${this.escapeGoString(v.value)}"`;
1434
+ else if (typeof v.value === "number" || typeof v.value === "boolean")
1435
+ goLit = String(v.value);
1436
+ if (goLit === null)
1437
+ return current;
1438
+ const next = new Map(current);
1439
+ next.set(p.contextName, goLit);
1440
+ return next;
1441
+ }
1313
1442
  collectSpreadSlots(node) {
1314
1443
  const result = [];
1315
1444
  this.collectSpreadSlotsRecursive(node, result);
@@ -1434,6 +1563,9 @@ ${goFields.join(`
1434
1563
  }
1435
1564
  buildSpreadInitializer(spreadExpr, ir) {
1436
1565
  const trimmed = spreadExpr.trim();
1566
+ const conditional = this.buildConditionalSpreadInitializer(trimmed, ir);
1567
+ if (conditional !== undefined)
1568
+ return conditional;
1437
1569
  const callMatch = /^([a-zA-Z_][a-zA-Z0-9_]*)\s*\(\s*\)$/.exec(trimmed);
1438
1570
  if (callMatch) {
1439
1571
  const getterName = callMatch[1];
@@ -1457,9 +1589,127 @@ ${goFields.join(`
1457
1589
  if (ir.metadata.restPropsName === trimmed) {
1458
1590
  return `in.${this.capitalizeFieldName(trimmed)}`;
1459
1591
  }
1592
+ const localConst = (ir.metadata.localConstants ?? []).find((c) => c.name === trimmed && !c.isModule);
1593
+ if (localConst?.value !== undefined) {
1594
+ const initTrimmed = localConst.value.trim();
1595
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(initTrimmed)) {
1596
+ const resolved = this.buildConditionalSpreadInitializer(initTrimmed, ir);
1597
+ if (resolved)
1598
+ return resolved;
1599
+ if (resolved === null)
1600
+ return null;
1601
+ }
1602
+ }
1460
1603
  }
1461
1604
  return null;
1462
1605
  }
1606
+ buildConditionalSpreadInitializer(spreadExpr, ir) {
1607
+ const expr = this.parseLiteralExpression(spreadExpr);
1608
+ if (!expr || !ts.isConditionalExpression(expr))
1609
+ return;
1610
+ const whenTrue = this.unwrapParens(expr.whenTrue);
1611
+ const whenFalse = this.unwrapParens(expr.whenFalse);
1612
+ if (!ts.isObjectLiteralExpression(whenTrue) || !ts.isObjectLiteralExpression(whenFalse)) {
1613
+ return;
1614
+ }
1615
+ const goCond = this.conditionToGoBool(expr.condition, ir);
1616
+ if (goCond === null)
1617
+ return null;
1618
+ const trueMap = this.objectLiteralToGoSpreadMap(whenTrue, ir);
1619
+ const falseMap = this.objectLiteralToGoSpreadMap(whenFalse, ir);
1620
+ if (trueMap === null || falseMap === null)
1621
+ return null;
1622
+ return `func() map[string]any {
1623
+ ` + ` if ${goCond} {
1624
+ ` + ` return ${trueMap}
1625
+ ` + ` }
1626
+ ` + ` return ${falseMap}
1627
+ ` + ` }()`;
1628
+ }
1629
+ unwrapParens(node) {
1630
+ let e = node;
1631
+ while (ts.isParenthesizedExpression(e))
1632
+ e = e.expression;
1633
+ return e;
1634
+ }
1635
+ conditionToGoBool(condition, ir) {
1636
+ let node = this.unwrapParens(condition);
1637
+ let negate = false;
1638
+ if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.ExclamationToken) {
1639
+ negate = true;
1640
+ node = this.unwrapParens(node.operand);
1641
+ }
1642
+ if (!ts.isIdentifier(node))
1643
+ return null;
1644
+ const param = ir.metadata.propsParams.find((p) => p.name === node.text);
1645
+ if (!param)
1646
+ return null;
1647
+ const field = `in.${this.capitalizeFieldName(param.name)}`;
1648
+ const prim = param.type.kind === "primitive" ? param.type.primitive : undefined;
1649
+ let truthy;
1650
+ if (prim === "boolean") {
1651
+ truthy = field;
1652
+ } else if (prim === "number") {
1653
+ truthy = `${field} != 0`;
1654
+ } else if (prim === "string") {
1655
+ truthy = `${field} != ""`;
1656
+ } else {
1657
+ truthy = `bf.Truthy(${field})`;
1658
+ }
1659
+ if (!negate)
1660
+ return truthy;
1661
+ if (prim === "boolean")
1662
+ return `!${field}`;
1663
+ if (prim === "number")
1664
+ return `${field} == 0`;
1665
+ if (prim === "string")
1666
+ return `${field} == ""`;
1667
+ return `!bf.Truthy(${field})`;
1668
+ }
1669
+ objectLiteralToGoSpreadMap(obj, ir) {
1670
+ const entries = [];
1671
+ for (const prop of obj.properties) {
1672
+ if (!ts.isPropertyAssignment(prop))
1673
+ return null;
1674
+ let key;
1675
+ if (ts.isIdentifier(prop.name)) {
1676
+ key = prop.name.text;
1677
+ } else if (ts.isStringLiteral(prop.name) || ts.isNoSubstitutionTemplateLiteral(prop.name)) {
1678
+ key = prop.name.text;
1679
+ } else {
1680
+ return null;
1681
+ }
1682
+ const val = this.unwrapParens(prop.initializer);
1683
+ let goVal;
1684
+ if (ts.isStringLiteral(val) || ts.isNoSubstitutionTemplateLiteral(val)) {
1685
+ goVal = JSON.stringify(val.text);
1686
+ } else if (ts.isIdentifier(val)) {
1687
+ const param = ir.metadata.propsParams.find((p) => p.name === val.text);
1688
+ if (!param)
1689
+ return null;
1690
+ goVal = `in.${this.capitalizeFieldName(param.name)}`;
1691
+ } else {
1692
+ const indexed = this.recordIndexAccessToGoMap(val, ir);
1693
+ if (indexed === null)
1694
+ return null;
1695
+ goVal = indexed;
1696
+ }
1697
+ entries.push(`${JSON.stringify(key)}: ${goVal}`);
1698
+ }
1699
+ return `map[string]any{${entries.join(", ")}}`;
1700
+ }
1701
+ recordIndexAccessToGoMap(val, ir) {
1702
+ const parsed = parseRecordIndexAccess(val, ir.metadata.localConstants ?? [], ir.metadata.propsParams);
1703
+ if (!parsed)
1704
+ return null;
1705
+ const entries = parsed.entries.map((e) => {
1706
+ const mapVal = e.value.kind === "number" ? e.value.text : JSON.stringify(e.value.text);
1707
+ return `${JSON.stringify(e.key)}: ${mapVal}`;
1708
+ });
1709
+ this.usesFmt = true;
1710
+ const field = `in.${this.capitalizeFieldName(parsed.indexPropName)}`;
1711
+ return `map[string]any{${entries.join(", ")}}[fmt.Sprint(${field})]`;
1712
+ }
1463
1713
  convertInitialValue(value, typeInfo, propsParams) {
1464
1714
  if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {
1465
1715
  if (propsParams?.some((p) => p.name === value)) {
@@ -1700,7 +1950,142 @@ ${goFields.join(`
1700
1950
  }
1701
1951
  return null;
1702
1952
  }
1703
- computeMemoInitialValue(memo, signals, propsParams, propFallbackVars = GoTemplateAdapter.EMPTY_PROP_FALLBACK_VARS) {
1953
+ computeTemplateLiteralMemoInitialValue(computation, propsParams) {
1954
+ const sf = ts.createSourceFile("__memo.ts", `const __x = (${computation});`, ts.ScriptTarget.Latest, false);
1955
+ const stmt = sf.statements[0];
1956
+ if (!stmt || !ts.isVariableStatement(stmt))
1957
+ return null;
1958
+ let init = stmt.declarationList.declarations[0]?.initializer;
1959
+ while (init && ts.isParenthesizedExpression(init))
1960
+ init = init.expression;
1961
+ if (!init || !ts.isArrowFunction(init))
1962
+ return null;
1963
+ let body = init.body;
1964
+ while (ts.isParenthesizedExpression(body)) {
1965
+ body = body.expression;
1966
+ }
1967
+ const localKeyBindings = new Map;
1968
+ if (ts.isBlock(body)) {
1969
+ let returned = null;
1970
+ for (const s of body.statements) {
1971
+ if (ts.isVariableStatement(s)) {
1972
+ for (const d of s.declarationList.declarations) {
1973
+ if (!ts.isIdentifier(d.name) || !d.initializer)
1974
+ continue;
1975
+ const binding = this.parseLocalKeyBinding(d.initializer);
1976
+ if (binding)
1977
+ localKeyBindings.set(d.name.text, binding);
1978
+ }
1979
+ } else if (ts.isReturnStatement(s) && s.expression) {
1980
+ returned = s.expression;
1981
+ } else if (ts.isExpressionStatement(s) || ts.isEmptyStatement(s)) {} else {
1982
+ return null;
1983
+ }
1984
+ }
1985
+ if (!returned)
1986
+ return null;
1987
+ body = returned;
1988
+ while (ts.isParenthesizedExpression(body)) {
1989
+ body = body.expression;
1990
+ }
1991
+ }
1992
+ if (!ts.isTemplateExpression(body) && !ts.isNoSubstitutionTemplateLiteral(body)) {
1993
+ return null;
1994
+ }
1995
+ const propNames = new Set(propsParams.map((p) => p.name));
1996
+ const escGo = (s) => `"${this.escapeGoString(s)}"`;
1997
+ const segments = [];
1998
+ if (ts.isNoSubstitutionTemplateLiteral(body)) {
1999
+ return escGo(body.text);
2000
+ }
2001
+ if (body.head.text)
2002
+ segments.push(escGo(body.head.text));
2003
+ for (const span of body.templateSpans) {
2004
+ const resolved = this.resolveTemplateInterpolation(span.expression, propNames, localKeyBindings);
2005
+ if (resolved === null)
2006
+ return null;
2007
+ segments.push(resolved);
2008
+ if (span.literal.text)
2009
+ segments.push(escGo(span.literal.text));
2010
+ }
2011
+ if (segments.length === 0)
2012
+ return '""';
2013
+ return segments.join(" + ");
2014
+ }
2015
+ resolveTemplateInterpolation(expr, propNames, localKeyBindings = new Map) {
2016
+ let node = expr;
2017
+ while (ts.isParenthesizedExpression(node))
2018
+ node = node.expression;
2019
+ if (ts.isIdentifier(node)) {
2020
+ const inlined = this.resolveModuleStringConst(node.text);
2021
+ if (inlined !== null)
2022
+ return inlined;
2023
+ return null;
2024
+ }
2025
+ if (ts.isElementAccessExpression(node)) {
2026
+ const indexed = this.recordIndexInterpolationToGo(node, propNames, localKeyBindings);
2027
+ if (indexed !== null)
2028
+ return indexed;
2029
+ return null;
2030
+ }
2031
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
2032
+ const right = node.right;
2033
+ const isEmptyStr = (ts.isStringLiteral(right) || ts.isNoSubstitutionTemplateLiteral(right)) && right.text === "";
2034
+ const propName2 = this.propsAccessName(node.left);
2035
+ if (propName2 && propNames.has(propName2) && isEmptyStr) {
2036
+ return `in.${this.capitalizeFieldName(propName2)}`;
2037
+ }
2038
+ return null;
2039
+ }
2040
+ const propName = this.propsAccessName(node);
2041
+ if (propName && propNames.has(propName)) {
2042
+ return `in.${this.capitalizeFieldName(propName)}`;
2043
+ }
2044
+ return null;
2045
+ }
2046
+ parseLocalKeyBinding(init) {
2047
+ let node = init;
2048
+ while (ts.isParenthesizedExpression(node))
2049
+ node = node.expression;
2050
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
2051
+ const propName2 = this.propsAccessName(node.left);
2052
+ const right = node.right;
2053
+ if (propName2 && (ts.isStringLiteral(right) || ts.isNoSubstitutionTemplateLiteral(right))) {
2054
+ return { propName: propName2, defaultLiteral: right.text };
2055
+ }
2056
+ return null;
2057
+ }
2058
+ const propName = this.propsAccessName(node);
2059
+ if (propName)
2060
+ return { propName };
2061
+ return null;
2062
+ }
2063
+ recordIndexInterpolationToGo(node, propNames, localKeyBindings) {
2064
+ const parsed = parseRecordIndexAccess(node, this.localConstants ?? [], [...propNames].map((name) => ({ name })), (name) => localKeyBindings.get(name) ?? null);
2065
+ if (!parsed)
2066
+ return null;
2067
+ const goVal = (v) => v.kind === "number" ? v.text : JSON.stringify(v.text);
2068
+ const entries = parsed.entries.map((e) => `${JSON.stringify(e.key)}: ${goVal(e.value)}`);
2069
+ if (parsed.defaultKey !== undefined) {
2070
+ const def = parsed.entries.find((e) => e.key === parsed.defaultKey);
2071
+ if (def)
2072
+ entries.unshift(`"": ${goVal(def.value)}`);
2073
+ }
2074
+ const allString = parsed.entries.every((e) => e.value.kind === "string");
2075
+ const mapType = allString ? "map[string]string" : "map[string]any";
2076
+ this.usesFmt = true;
2077
+ return `${mapType}{${entries.join(", ")}}[fmt.Sprint(in.${this.capitalizeFieldName(parsed.indexPropName)})]`;
2078
+ }
2079
+ propsAccessName(node) {
2080
+ if (!ts.isPropertyAccessExpression(node))
2081
+ return null;
2082
+ if (!ts.isIdentifier(node.expression))
2083
+ return null;
2084
+ if (!this.propsObjectName || node.expression.text !== this.propsObjectName)
2085
+ return null;
2086
+ return node.name.text;
2087
+ }
2088
+ computeMemoInitialValue(memo, signals, propsParams, propFallbackVars = GoTemplateAdapter.EMPTY_PROP_FALLBACK_VARS, goType) {
1704
2089
  const computation = memo.computation;
1705
2090
  const propRef = (propName) => {
1706
2091
  const hoisted = propFallbackVars.get(propName);
@@ -1708,6 +2093,9 @@ ${goFields.join(`
1708
2093
  return hoisted.varName;
1709
2094
  return `in.${this.capitalizeFieldName(propName)}`;
1710
2095
  };
2096
+ const tmplMemo = this.computeTemplateLiteralMemoInitialValue(computation, propsParams);
2097
+ if (tmplMemo !== null)
2098
+ return tmplMemo;
1711
2099
  const arithmeticMatch = computation.match(/\(\)\s*=>\s*(\w+)\(\)\s*([*+\-/])\s*(\d+)/);
1712
2100
  if (arithmeticMatch) {
1713
2101
  const [, depName, operator, operand] = arithmeticMatch;
@@ -1727,8 +2115,8 @@ ${goFields.join(`
1727
2115
  return `${hoisted.varName} ${operator} ${operand}`;
1728
2116
  const fieldName = this.capitalizeFieldName(propName);
1729
2117
  if (param.type) {
1730
- const goType = this.typeInfoToGo(param.type, param.defaultValue);
1731
- if (goType === "interface{}")
2118
+ const goType2 = this.typeInfoToGo(param.type, param.defaultValue);
2119
+ if (goType2 === "interface{}")
1732
2120
  return `in.${fieldName}.(int) ${operator} ${operand}`;
1733
2121
  }
1734
2122
  return `in.${fieldName} ${operator} ${operand}`;
@@ -1757,8 +2145,8 @@ ${goFields.join(`
1757
2145
  if (param) {
1758
2146
  const fieldName = this.capitalizeFieldName(varName);
1759
2147
  if (param.type) {
1760
- const goType = this.typeInfoToGo(param.type, param.defaultValue);
1761
- if (goType === "interface{}")
2148
+ const goType2 = this.typeInfoToGo(param.type, param.defaultValue);
2149
+ if (goType2 === "interface{}")
1762
2150
  return `in.${fieldName}.(int) ${operator} ${operand}`;
1763
2151
  }
1764
2152
  return `in.${fieldName} ${operator} ${operand}`;
@@ -1772,9 +2160,40 @@ ${goFields.join(`
1772
2160
  return `in.${this.capitalizeFieldName(varName)}`;
1773
2161
  }
1774
2162
  }
2163
+ if (goType === "bool")
2164
+ return "false";
2165
+ if (goType === "string")
2166
+ return '""';
1775
2167
  return "0";
1776
2168
  }
2169
+ isTemplateLiteralMemo(computation) {
2170
+ const sf = ts.createSourceFile("__memo.ts", `const __x = (${computation});`, ts.ScriptTarget.Latest, false);
2171
+ const stmt = sf.statements[0];
2172
+ if (!stmt || !ts.isVariableStatement(stmt))
2173
+ return false;
2174
+ let init = stmt.declarationList.declarations[0]?.initializer;
2175
+ while (init && ts.isParenthesizedExpression(init))
2176
+ init = init.expression;
2177
+ if (!init || !ts.isArrowFunction(init))
2178
+ return false;
2179
+ let body = init.body;
2180
+ while (ts.isParenthesizedExpression(body)) {
2181
+ body = body.expression;
2182
+ }
2183
+ if (ts.isBlock(body)) {
2184
+ const ret = body.statements.find(ts.isReturnStatement);
2185
+ if (!ret || !ret.expression)
2186
+ return false;
2187
+ body = ret.expression;
2188
+ while (ts.isParenthesizedExpression(body)) {
2189
+ body = body.expression;
2190
+ }
2191
+ }
2192
+ return ts.isTemplateExpression(body) || ts.isNoSubstitutionTemplateLiteral(body);
2193
+ }
1777
2194
  inferMemoType(memo, signals, propsParamMap) {
2195
+ if (this.isTemplateLiteralMemo(memo.computation))
2196
+ return "string";
1778
2197
  if (memo.computation.includes("*") || memo.computation.includes("/") || memo.computation.includes("+") || memo.computation.includes("-")) {
1779
2198
  for (const dep of memo.deps) {
1780
2199
  const signal = signals.find((s) => s.getter === dep);
@@ -1798,8 +2217,39 @@ ${goFields.join(`
1798
2217
  }
1799
2218
  }
1800
2219
  }
2220
+ if (this.typeInfoToGo(memo.type) === "interface{}" && this.isBooleanMemo(memo, signals, propsParamMap)) {
2221
+ return "bool";
2222
+ }
1801
2223
  return this.typeInfoToGo(memo.type);
1802
2224
  }
2225
+ isBooleanMemo(memo, signals, propsParamMap) {
2226
+ const c = memo.computation;
2227
+ if (/(!==|===|!=(?!=)|==(?!=))/.test(c))
2228
+ return true;
2229
+ if (/=>\s*!/.test(c))
2230
+ return true;
2231
+ const isBoolGetter = (name) => {
2232
+ const sig = signals.find((s) => s.getter === name);
2233
+ if (sig) {
2234
+ if (this.typeInfoToGo(sig.type) === "bool")
2235
+ return true;
2236
+ if (/\?\?\s*(true|false)\b/.test(sig.initialValue))
2237
+ return true;
2238
+ const propName = this.extractPropNameFromInitialValue(sig.initialValue) ?? sig.initialValue;
2239
+ const prop2 = propsParamMap.get(propName);
2240
+ if (prop2 && this.typeInfoToGo(prop2.type, prop2.defaultValue) === "bool")
2241
+ return true;
2242
+ return false;
2243
+ }
2244
+ const prop = propsParamMap.get(name);
2245
+ return !!prop && this.typeInfoToGo(prop.type, prop.defaultValue) === "bool";
2246
+ };
2247
+ const ternary = c.match(/=>\s*\w+\(\)\s*\?\s*(\w+)\(\)\s*:\s*(\w+)\(\)/);
2248
+ if (ternary) {
2249
+ return isBoolGetter(ternary[1]) && isBoolGetter(ternary[2]);
2250
+ }
2251
+ return false;
2252
+ }
1803
2253
  inferTypeFromValue(value) {
1804
2254
  if (value === "true" || value === "false")
1805
2255
  return "bool";
@@ -2147,8 +2597,42 @@ ${goFields.join(`
2147
2597
  if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
2148
2598
  return init.text;
2149
2599
  }
2600
+ const joined = this.evalStringArrayJoin(init);
2601
+ if (joined !== null)
2602
+ return joined;
2150
2603
  return null;
2151
2604
  }
2605
+ evalStringArrayJoin(node) {
2606
+ if (!ts.isCallExpression(node))
2607
+ return null;
2608
+ const callee = node.expression;
2609
+ if (!ts.isPropertyAccessExpression(callee))
2610
+ return null;
2611
+ if (callee.name.text !== "join")
2612
+ return null;
2613
+ let recv = callee.expression;
2614
+ while (ts.isParenthesizedExpression(recv))
2615
+ recv = recv.expression;
2616
+ if (!ts.isArrayLiteralExpression(recv))
2617
+ return null;
2618
+ const parts = [];
2619
+ for (const el of recv.elements) {
2620
+ if (ts.isStringLiteral(el) || ts.isNoSubstitutionTemplateLiteral(el)) {
2621
+ parts.push(el.text);
2622
+ } else {
2623
+ return null;
2624
+ }
2625
+ }
2626
+ let sep = ",";
2627
+ if (node.arguments.length >= 1) {
2628
+ const arg = node.arguments[0];
2629
+ if (ts.isStringLiteral(arg) || ts.isNoSubstitutionTemplateLiteral(arg))
2630
+ sep = arg.text;
2631
+ else
2632
+ return null;
2633
+ }
2634
+ return parts.join(sep);
2635
+ }
2152
2636
  resolveModuleStringConst(name) {
2153
2637
  if (this.loopParamStack.length > 0 && this.loopParamStack[this.loopParamStack.length - 1] === name) {
2154
2638
  return null;
@@ -3308,6 +3792,12 @@ ${children}`;
3308
3792
  if (parsed.kind === "conditional" || parsed.kind === "template-literal") {
3309
3793
  return `${name}="${this.renderParsedExpr(parsed)}"`;
3310
3794
  }
3795
+ const bareId = value.expr.trim();
3796
+ const propName = this.propsObjectName && bareId.startsWith(`${this.propsObjectName}.`) ? bareId.slice(this.propsObjectName.length + 1) : bareId;
3797
+ if (/^[A-Za-z_$][\w$]*$/.test(propName) && this.nillablePropNames.has(propName)) {
3798
+ const field = `.${this.capitalizeFieldName(propName)}`;
3799
+ return `{{if ne ${field} nil}}${name}="{{${this.convertExpressionToGo(value.expr)}}}"{{end}}`;
3800
+ }
3311
3801
  return `${name}="{{${this.convertExpressionToGo(value.expr)}}}"`;
3312
3802
  },
3313
3803
  emitBooleanAttr: (_value, name) => name,