@barefootjs/go-template 0.8.0 → 0.9.1

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,12 @@ 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;
461
469
  nillablePropNames = new Set;
462
470
  constructor(options = {}) {
463
471
  super();
@@ -474,6 +482,9 @@ class GoTemplateAdapter extends BaseAdapter {
474
482
  this.propsObjectName = ir.metadata.propsObjectName;
475
483
  this.restPropsName = ir.metadata.restPropsName ?? null;
476
484
  this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
485
+ this.localConstants = ir.metadata.localConstants ?? [];
486
+ this.contextConsumers = collectContextConsumers(ir.metadata);
487
+ augmentInheritedPropAccesses(ir);
477
488
  this.nillablePropNames = this.collectNillablePropNames(ir);
478
489
  if (!options?.siblingTemplatesRegistered) {
479
490
  this.checkImportedLoopChildComponents(ir);
@@ -687,8 +698,46 @@ ${scriptRegistrations}${templateBody}
687
698
  return `{{if .Scripts}}${registrations.join("")}{{end}}
688
699
  `;
689
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
+ }
690
733
  generateTypes(ir) {
691
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);
692
741
  const lines = [];
693
742
  const componentName = ir.metadata.componentName;
694
743
  this.localTypeNames = new Set;
@@ -750,6 +799,8 @@ ${goFields.join(`
750
799
  header.push(`package ${this.options.packageName}`);
751
800
  header.push("");
752
801
  header.push("import (");
802
+ if (this.usesFmt)
803
+ header.push('\t"fmt"');
753
804
  if (this.usesHtmlTemplate)
754
805
  header.push('\t"html/template"');
755
806
  header.push('\t"math/rand"');
@@ -942,6 +993,10 @@ ${goFields.join(`
942
993
  for (const nested of inputNested) {
943
994
  lines.push(` ${nested.name}s []${nested.name}Input`);
944
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
+ }
945
1000
  const restPropsName = ir.metadata.restPropsName;
946
1001
  if (restPropsName) {
947
1002
  const seen = new Set;
@@ -1019,6 +1074,15 @@ ${goFields.join(`
1019
1074
  const goType = this.inferMemoType(memo, ir.metadata.signals, propsParamMap);
1020
1075
  lines.push(` ${fieldName} ${goType} \`json:"${jsonTag}"\``);
1021
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
+ }
1022
1086
  for (const nested of nestedComponents) {
1023
1087
  if (nested.isDynamic && !nested.isPropDerived) {
1024
1088
  lines.push(` ${nested.name}s []${nested.name}Props \`json:"-"\``);
@@ -1128,25 +1192,55 @@ ${goFields.join(`
1128
1192
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
1129
1193
  lines.push(` ${nested.name}s: ${varName},`);
1130
1194
  }
1195
+ const memoPropsParamMap = new Map(ir.metadata.propsParams.map((p) => [p.name, p]));
1131
1196
  for (const memo of ir.metadata.memos) {
1132
1197
  const fieldName = this.capitalizeFieldName(memo.name);
1133
- 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);
1134
1200
  lines.push(` ${fieldName}: ${memoValue},`);
1135
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
+ }
1136
1213
  const staticChildren = this.collectStaticChildInstances(ir.root);
1137
1214
  for (const child of staticChildren) {
1138
1215
  lines.push(` ${child.fieldName}: New${child.name}Props(${child.name}Input{`);
1139
1216
  lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
1140
1217
  lines.push(` BfParent: scopeID,`);
1141
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
+ };
1142
1236
  for (const prop of child.props) {
1143
1237
  switch (prop.value.kind) {
1144
1238
  case "literal":
1145
- lines.push(` ${this.capitalizeFieldName(prop.name)}: ${this.goLiteral(prop.value.value)},`);
1239
+ emitChildField(prop.name, this.goLiteral(prop.value.value));
1146
1240
  break;
1147
1241
  case "boolean-shorthand":
1148
1242
  case "boolean-attr":
1149
- lines.push(` ${this.capitalizeFieldName(prop.name)}: true,`);
1243
+ emitChildField(prop.name, "true");
1150
1244
  break;
1151
1245
  case "expression":
1152
1246
  case "spread":
@@ -1155,7 +1249,7 @@ ${goFields.join(`
1155
1249
  if (parts) {
1156
1250
  const goExpr = this.templatePartsToGoCode(parts, ir.metadata.propsParams);
1157
1251
  if (goExpr !== null) {
1158
- lines.push(` ${this.capitalizeFieldName(prop.name)}: ${goExpr},`);
1252
+ emitChildField(prop.name, goExpr);
1159
1253
  break;
1160
1254
  }
1161
1255
  }
@@ -1164,7 +1258,7 @@ ${goFields.join(`
1164
1258
  break;
1165
1259
  const resolvedValue = this.resolveDynamicPropValue(exprText, ir.metadata.signals, ir.metadata.memos, ir.metadata.propsParams);
1166
1260
  if (resolvedValue !== null) {
1167
- lines.push(` ${this.capitalizeFieldName(prop.name)}: ${resolvedValue},`);
1261
+ emitChildField(prop.name, resolvedValue);
1168
1262
  }
1169
1263
  break;
1170
1264
  }
@@ -1172,6 +1266,9 @@ ${goFields.join(`
1172
1266
  break;
1173
1267
  }
1174
1268
  }
1269
+ if (childShape?.restBagField && restBagEntries.length > 0) {
1270
+ lines.push(` ${childShape.restBagField}: map[string]any{${restBagEntries.join(", ")}},`);
1271
+ }
1175
1272
  if (child.childrenText !== null) {
1176
1273
  lines.push(` Children: ${JSON.stringify(child.childrenText)},`);
1177
1274
  } else if (child.childrenHtml !== null) {
@@ -1242,7 +1339,7 @@ ${goFields.join(`
1242
1339
  }
1243
1340
  collectStaticChildInstances(node) {
1244
1341
  const result = [];
1245
- this.collectStaticChildInstancesRecursive(node, result, false);
1342
+ this.collectStaticChildInstancesRecursive(node, result, false, new Map);
1246
1343
  return result;
1247
1344
  }
1248
1345
  extractTextChildren(children) {
@@ -1266,12 +1363,12 @@ ${goFields.join(`
1266
1363
  return null;
1267
1364
  return html;
1268
1365
  }
1269
- collectStaticChildInstancesRecursive(node, result, inLoop) {
1366
+ collectStaticChildInstancesRecursive(node, result, inLoop, providerCtx) {
1270
1367
  if (node.type === "component") {
1271
1368
  const comp = node;
1272
1369
  if (comp.dynamicTag) {
1273
1370
  for (const child of comp.children) {
1274
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1371
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
1275
1372
  }
1276
1373
  return;
1277
1374
  }
@@ -1283,48 +1380,65 @@ ${goFields.join(`
1283
1380
  props: comp.props,
1284
1381
  fieldName: `${comp.name}${suffix}`,
1285
1382
  childrenText: this.extractTextChildren(comp.children),
1286
- childrenHtml: this.extractHtmlChildren(comp.children)
1383
+ childrenHtml: this.extractHtmlChildren(comp.children),
1384
+ contextBindings: providerCtx.size > 0 ? providerCtx : undefined
1287
1385
  });
1288
1386
  }
1289
1387
  if (comp.name === "Portal" && comp.children) {
1290
1388
  for (const child of comp.children) {
1291
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1389
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
1292
1390
  }
1293
1391
  }
1294
1392
  } else if (node.type === "loop") {
1295
1393
  const loop = node;
1296
1394
  for (const child of loop.children) {
1297
- this.collectStaticChildInstancesRecursive(child, result, true);
1395
+ this.collectStaticChildInstancesRecursive(child, result, true, providerCtx);
1298
1396
  }
1299
1397
  } else if (node.type === "element") {
1300
1398
  const element = node;
1301
1399
  for (const child of element.children) {
1302
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1400
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
1303
1401
  }
1304
1402
  } else if (node.type === "fragment") {
1305
1403
  const fragment = node;
1306
1404
  for (const child of fragment.children) {
1307
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1405
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
1308
1406
  }
1309
1407
  } else if (node.type === "conditional") {
1310
1408
  const cond = node;
1311
- this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop);
1409
+ this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop, providerCtx);
1312
1410
  if (cond.whenFalse) {
1313
- this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop);
1411
+ this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx);
1314
1412
  }
1315
1413
  } else if (node.type === "provider") {
1316
1414
  const p = node;
1415
+ const childCtx = this.extendProviderContext(providerCtx, p);
1317
1416
  for (const child of p.children) {
1318
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1417
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, childCtx);
1319
1418
  }
1320
1419
  } else if (node.type === "async") {
1321
1420
  const a = node;
1322
- this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop);
1421
+ this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop, providerCtx);
1323
1422
  for (const child of a.children) {
1324
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1423
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
1325
1424
  }
1326
1425
  }
1327
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
+ }
1328
1442
  collectSpreadSlots(node) {
1329
1443
  const result = [];
1330
1444
  this.collectSpreadSlotsRecursive(node, result);
@@ -1475,6 +1589,17 @@ ${goFields.join(`
1475
1589
  if (ir.metadata.restPropsName === trimmed) {
1476
1590
  return `in.${this.capitalizeFieldName(trimmed)}`;
1477
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
+ }
1478
1603
  }
1479
1604
  return null;
1480
1605
  }
@@ -1564,12 +1689,27 @@ ${goFields.join(`
1564
1689
  return null;
1565
1690
  goVal = `in.${this.capitalizeFieldName(param.name)}`;
1566
1691
  } else {
1567
- return null;
1692
+ const indexed = this.recordIndexAccessToGoMap(val, ir);
1693
+ if (indexed === null)
1694
+ return null;
1695
+ goVal = indexed;
1568
1696
  }
1569
1697
  entries.push(`${JSON.stringify(key)}: ${goVal}`);
1570
1698
  }
1571
1699
  return `map[string]any{${entries.join(", ")}}`;
1572
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
+ }
1573
1713
  convertInitialValue(value, typeInfo, propsParams) {
1574
1714
  if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {
1575
1715
  if (propsParams?.some((p) => p.name === value)) {
@@ -1810,7 +1950,142 @@ ${goFields.join(`
1810
1950
  }
1811
1951
  return null;
1812
1952
  }
1813
- 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) {
1814
2089
  const computation = memo.computation;
1815
2090
  const propRef = (propName) => {
1816
2091
  const hoisted = propFallbackVars.get(propName);
@@ -1818,6 +2093,9 @@ ${goFields.join(`
1818
2093
  return hoisted.varName;
1819
2094
  return `in.${this.capitalizeFieldName(propName)}`;
1820
2095
  };
2096
+ const tmplMemo = this.computeTemplateLiteralMemoInitialValue(computation, propsParams);
2097
+ if (tmplMemo !== null)
2098
+ return tmplMemo;
1821
2099
  const arithmeticMatch = computation.match(/\(\)\s*=>\s*(\w+)\(\)\s*([*+\-/])\s*(\d+)/);
1822
2100
  if (arithmeticMatch) {
1823
2101
  const [, depName, operator, operand] = arithmeticMatch;
@@ -1837,8 +2115,8 @@ ${goFields.join(`
1837
2115
  return `${hoisted.varName} ${operator} ${operand}`;
1838
2116
  const fieldName = this.capitalizeFieldName(propName);
1839
2117
  if (param.type) {
1840
- const goType = this.typeInfoToGo(param.type, param.defaultValue);
1841
- if (goType === "interface{}")
2118
+ const goType2 = this.typeInfoToGo(param.type, param.defaultValue);
2119
+ if (goType2 === "interface{}")
1842
2120
  return `in.${fieldName}.(int) ${operator} ${operand}`;
1843
2121
  }
1844
2122
  return `in.${fieldName} ${operator} ${operand}`;
@@ -1867,8 +2145,8 @@ ${goFields.join(`
1867
2145
  if (param) {
1868
2146
  const fieldName = this.capitalizeFieldName(varName);
1869
2147
  if (param.type) {
1870
- const goType = this.typeInfoToGo(param.type, param.defaultValue);
1871
- if (goType === "interface{}")
2148
+ const goType2 = this.typeInfoToGo(param.type, param.defaultValue);
2149
+ if (goType2 === "interface{}")
1872
2150
  return `in.${fieldName}.(int) ${operator} ${operand}`;
1873
2151
  }
1874
2152
  return `in.${fieldName} ${operator} ${operand}`;
@@ -1882,9 +2160,40 @@ ${goFields.join(`
1882
2160
  return `in.${this.capitalizeFieldName(varName)}`;
1883
2161
  }
1884
2162
  }
2163
+ if (goType === "bool")
2164
+ return "false";
2165
+ if (goType === "string")
2166
+ return '""';
1885
2167
  return "0";
1886
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
+ }
1887
2194
  inferMemoType(memo, signals, propsParamMap) {
2195
+ if (this.isTemplateLiteralMemo(memo.computation))
2196
+ return "string";
1888
2197
  if (memo.computation.includes("*") || memo.computation.includes("/") || memo.computation.includes("+") || memo.computation.includes("-")) {
1889
2198
  for (const dep of memo.deps) {
1890
2199
  const signal = signals.find((s) => s.getter === dep);
@@ -1908,8 +2217,39 @@ ${goFields.join(`
1908
2217
  }
1909
2218
  }
1910
2219
  }
2220
+ if (this.typeInfoToGo(memo.type) === "interface{}" && this.isBooleanMemo(memo, signals, propsParamMap)) {
2221
+ return "bool";
2222
+ }
1911
2223
  return this.typeInfoToGo(memo.type);
1912
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
+ }
1913
2253
  inferTypeFromValue(value) {
1914
2254
  if (value === "true" || value === "false")
1915
2255
  return "bool";
@@ -2257,8 +2597,42 @@ ${goFields.join(`
2257
2597
  if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
2258
2598
  return init.text;
2259
2599
  }
2600
+ const joined = this.evalStringArrayJoin(init);
2601
+ if (joined !== null)
2602
+ return joined;
2260
2603
  return null;
2261
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
+ }
2262
2636
  resolveModuleStringConst(name) {
2263
2637
  if (this.loopParamStack.length > 0 && this.loopParamStack[this.loopParamStack.length - 1] === name) {
2264
2638
  return null;
@@ -3419,8 +3793,9 @@ ${children}`;
3419
3793
  return `${name}="${this.renderParsedExpr(parsed)}"`;
3420
3794
  }
3421
3795
  const bareId = value.expr.trim();
3422
- if (this.nillablePropNames.has(bareId)) {
3423
- const field = `.${this.capitalizeFieldName(bareId)}`;
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)}`;
3424
3799
  return `{{if ne ${field} nil}}${name}="{{${this.convertExpressionToGo(value.expr)}}}"{{end}}`;
3425
3800
  }
3426
3801
  return `${name}="{{${this.convertExpressionToGo(value.expr)}}}"`;