@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/index.js CHANGED
@@ -32,7 +32,10 @@ import {
32
32
  identifierPath,
33
33
  emitParsedExpr,
34
34
  emitIRNode,
35
- emitAttrValue
35
+ emitAttrValue,
36
+ augmentInheritedPropAccesses,
37
+ parseRecordIndexAccess,
38
+ collectContextConsumers
36
39
  } from "@barefootjs/jsx";
37
40
  import { findInterpolationEnd } from "@barefootjs/jsx/scanner";
38
41
  function wrapIfMultiToken(rendered) {
@@ -117,7 +120,12 @@ class GoTemplateAdapter extends BaseAdapter {
117
120
  localStructFields = new Map;
118
121
  synthStructTypes = new Map;
119
122
  usesHtmlTemplate = false;
123
+ usesFmt = false;
124
+ childComponentShapes = new Map;
120
125
  moduleStringConsts = new Map;
126
+ localConstants = [];
127
+ contextConsumers = [];
128
+ childContextConsumers = new Map;
121
129
  nillablePropNames = new Set;
122
130
  constructor(options = {}) {
123
131
  super();
@@ -134,6 +142,9 @@ class GoTemplateAdapter extends BaseAdapter {
134
142
  this.propsObjectName = ir.metadata.propsObjectName;
135
143
  this.restPropsName = ir.metadata.restPropsName ?? null;
136
144
  this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
145
+ this.localConstants = ir.metadata.localConstants ?? [];
146
+ this.contextConsumers = collectContextConsumers(ir.metadata);
147
+ augmentInheritedPropAccesses(ir);
137
148
  this.nillablePropNames = this.collectNillablePropNames(ir);
138
149
  if (!options?.siblingTemplatesRegistered) {
139
150
  this.checkImportedLoopChildComponents(ir);
@@ -347,8 +358,46 @@ ${scriptRegistrations}${templateBody}
347
358
  return `{{if .Scripts}}${registrations.join("")}{{end}}
348
359
  `;
349
360
  }
361
+ registerChildComponentShape(ir) {
362
+ const name = ir.metadata.componentName;
363
+ if (!name)
364
+ return;
365
+ const paramNames = new Set((ir.metadata.propsParams ?? []).map((p) => p.name));
366
+ const restPropsName = ir.metadata.restPropsName ?? null;
367
+ const restBagField = restPropsName ? this.capitalizeFieldName(restPropsName) : null;
368
+ this.childComponentShapes.set(name, { paramNames, restBagField });
369
+ this.childContextConsumers.set(name, collectContextConsumers(ir.metadata));
370
+ }
371
+ contextFieldName(c) {
372
+ return this.capitalizeFieldName(c.localName);
373
+ }
374
+ contextConsumerGoType(c) {
375
+ if (typeof c.defaultValue === "number")
376
+ return "int";
377
+ if (typeof c.defaultValue === "boolean")
378
+ return "bool";
379
+ return "string";
380
+ }
381
+ contextConsumerGoDefault(c) {
382
+ if (typeof c.defaultValue === "number")
383
+ return String(c.defaultValue);
384
+ if (typeof c.defaultValue === "boolean")
385
+ return String(c.defaultValue);
386
+ if (typeof c.defaultValue === "string")
387
+ return `"${this.escapeGoString(c.defaultValue)}"`;
388
+ return '""';
389
+ }
390
+ nonCollidingContextConsumers(taken) {
391
+ return this.contextConsumers.filter((c) => !taken.has(this.contextFieldName(c)));
392
+ }
350
393
  generateTypes(ir) {
351
394
  this.usesHtmlTemplate = false;
395
+ this.usesFmt = false;
396
+ this.propsObjectName = ir.metadata.propsObjectName;
397
+ augmentInheritedPropAccesses(ir);
398
+ this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
399
+ this.localConstants = ir.metadata.localConstants ?? [];
400
+ this.contextConsumers = collectContextConsumers(ir.metadata);
352
401
  const lines = [];
353
402
  const componentName = ir.metadata.componentName;
354
403
  this.localTypeNames = new Set;
@@ -410,6 +459,8 @@ ${goFields.join(`
410
459
  header.push(`package ${this.options.packageName}`);
411
460
  header.push("");
412
461
  header.push("import (");
462
+ if (this.usesFmt)
463
+ header.push('\t"fmt"');
413
464
  if (this.usesHtmlTemplate)
414
465
  header.push('\t"html/template"');
415
466
  header.push('\t"math/rand"');
@@ -602,6 +653,10 @@ ${goFields.join(`
602
653
  for (const nested of inputNested) {
603
654
  lines.push(` ${nested.name}s []${nested.name}Input`);
604
655
  }
656
+ const takenInput = new Set(ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)));
657
+ for (const c of this.nonCollidingContextConsumers(takenInput)) {
658
+ lines.push(` ${this.contextFieldName(c)} ${this.contextConsumerGoType(c)}`);
659
+ }
605
660
  const restPropsName = ir.metadata.restPropsName;
606
661
  if (restPropsName) {
607
662
  const seen = new Set;
@@ -679,6 +734,15 @@ ${goFields.join(`
679
734
  const goType = this.inferMemoType(memo, ir.metadata.signals, propsParamMap);
680
735
  lines.push(` ${fieldName} ${goType} \`json:"${jsonTag}"\``);
681
736
  }
737
+ const takenProps = new Set([
738
+ ...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
739
+ ...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
740
+ ...ir.metadata.memos.map((m) => this.capitalizeFieldName(m.name))
741
+ ]);
742
+ for (const c of this.nonCollidingContextConsumers(takenProps)) {
743
+ const jsonTag = this.toJsonTag(c.localName);
744
+ lines.push(` ${this.contextFieldName(c)} ${this.contextConsumerGoType(c)} \`json:"${jsonTag}"\``);
745
+ }
682
746
  for (const nested of nestedComponents) {
683
747
  if (nested.isDynamic && !nested.isPropDerived) {
684
748
  lines.push(` ${nested.name}s []${nested.name}Props \`json:"-"\``);
@@ -788,25 +852,55 @@ ${goFields.join(`
788
852
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
789
853
  lines.push(` ${nested.name}s: ${varName},`);
790
854
  }
855
+ const memoPropsParamMap = new Map(ir.metadata.propsParams.map((p) => [p.name, p]));
791
856
  for (const memo of ir.metadata.memos) {
792
857
  const fieldName = this.capitalizeFieldName(memo.name);
793
- const memoValue = this.computeMemoInitialValue(memo, ir.metadata.signals, ir.metadata.propsParams, propFallbackVars);
858
+ const goType = this.inferMemoType(memo, ir.metadata.signals, memoPropsParamMap);
859
+ const memoValue = this.computeMemoInitialValue(memo, ir.metadata.signals, ir.metadata.propsParams, propFallbackVars, goType);
794
860
  lines.push(` ${fieldName}: ${memoValue},`);
795
861
  }
862
+ const takenInit = new Set([
863
+ ...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
864
+ ...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
865
+ ...ir.metadata.memos.map((m) => this.capitalizeFieldName(m.name))
866
+ ]);
867
+ for (const c of this.nonCollidingContextConsumers(takenInit)) {
868
+ const field = this.contextFieldName(c);
869
+ const def = this.contextConsumerGoDefault(c);
870
+ const defaulted = c.defaultValue === null || def === '""' || def === "0" || def === "false" ? `in.${field}` : this.applyGoFallback(`in.${field}`, def);
871
+ lines.push(` ${field}: ${defaulted},`);
872
+ }
796
873
  const staticChildren = this.collectStaticChildInstances(ir.root);
797
874
  for (const child of staticChildren) {
798
875
  lines.push(` ${child.fieldName}: New${child.name}Props(${child.name}Input{`);
799
876
  lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
800
877
  lines.push(` BfParent: scopeID,`);
801
878
  lines.push(` BfMount: "${child.slotId}",`);
879
+ if (child.contextBindings) {
880
+ for (const consumer of this.childContextConsumers.get(child.name) ?? []) {
881
+ const goVal = child.contextBindings.get(consumer.contextName);
882
+ if (goVal !== undefined) {
883
+ lines.push(` ${this.contextFieldName(consumer)}: ${goVal},`);
884
+ }
885
+ }
886
+ }
887
+ const childShape = this.childComponentShapes.get(child.name);
888
+ const restBagEntries = [];
889
+ const emitChildField = (jsxName, goValue) => {
890
+ if (childShape && childShape.restBagField && !childShape.paramNames.has(jsxName)) {
891
+ restBagEntries.push(`${JSON.stringify(jsxName)}: ${goValue}`);
892
+ return;
893
+ }
894
+ lines.push(` ${this.capitalizeFieldName(jsxName)}: ${goValue},`);
895
+ };
802
896
  for (const prop of child.props) {
803
897
  switch (prop.value.kind) {
804
898
  case "literal":
805
- lines.push(` ${this.capitalizeFieldName(prop.name)}: ${this.goLiteral(prop.value.value)},`);
899
+ emitChildField(prop.name, this.goLiteral(prop.value.value));
806
900
  break;
807
901
  case "boolean-shorthand":
808
902
  case "boolean-attr":
809
- lines.push(` ${this.capitalizeFieldName(prop.name)}: true,`);
903
+ emitChildField(prop.name, "true");
810
904
  break;
811
905
  case "expression":
812
906
  case "spread":
@@ -815,7 +909,7 @@ ${goFields.join(`
815
909
  if (parts) {
816
910
  const goExpr = this.templatePartsToGoCode(parts, ir.metadata.propsParams);
817
911
  if (goExpr !== null) {
818
- lines.push(` ${this.capitalizeFieldName(prop.name)}: ${goExpr},`);
912
+ emitChildField(prop.name, goExpr);
819
913
  break;
820
914
  }
821
915
  }
@@ -824,7 +918,7 @@ ${goFields.join(`
824
918
  break;
825
919
  const resolvedValue = this.resolveDynamicPropValue(exprText, ir.metadata.signals, ir.metadata.memos, ir.metadata.propsParams);
826
920
  if (resolvedValue !== null) {
827
- lines.push(` ${this.capitalizeFieldName(prop.name)}: ${resolvedValue},`);
921
+ emitChildField(prop.name, resolvedValue);
828
922
  }
829
923
  break;
830
924
  }
@@ -832,6 +926,9 @@ ${goFields.join(`
832
926
  break;
833
927
  }
834
928
  }
929
+ if (childShape?.restBagField && restBagEntries.length > 0) {
930
+ lines.push(` ${childShape.restBagField}: map[string]any{${restBagEntries.join(", ")}},`);
931
+ }
835
932
  if (child.childrenText !== null) {
836
933
  lines.push(` Children: ${JSON.stringify(child.childrenText)},`);
837
934
  } else if (child.childrenHtml !== null) {
@@ -902,7 +999,7 @@ ${goFields.join(`
902
999
  }
903
1000
  collectStaticChildInstances(node) {
904
1001
  const result = [];
905
- this.collectStaticChildInstancesRecursive(node, result, false);
1002
+ this.collectStaticChildInstancesRecursive(node, result, false, new Map);
906
1003
  return result;
907
1004
  }
908
1005
  extractTextChildren(children) {
@@ -926,12 +1023,12 @@ ${goFields.join(`
926
1023
  return null;
927
1024
  return html;
928
1025
  }
929
- collectStaticChildInstancesRecursive(node, result, inLoop) {
1026
+ collectStaticChildInstancesRecursive(node, result, inLoop, providerCtx) {
930
1027
  if (node.type === "component") {
931
1028
  const comp = node;
932
1029
  if (comp.dynamicTag) {
933
1030
  for (const child of comp.children) {
934
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1031
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
935
1032
  }
936
1033
  return;
937
1034
  }
@@ -943,48 +1040,65 @@ ${goFields.join(`
943
1040
  props: comp.props,
944
1041
  fieldName: `${comp.name}${suffix}`,
945
1042
  childrenText: this.extractTextChildren(comp.children),
946
- childrenHtml: this.extractHtmlChildren(comp.children)
1043
+ childrenHtml: this.extractHtmlChildren(comp.children),
1044
+ contextBindings: providerCtx.size > 0 ? providerCtx : undefined
947
1045
  });
948
1046
  }
949
1047
  if (comp.name === "Portal" && comp.children) {
950
1048
  for (const child of comp.children) {
951
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1049
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
952
1050
  }
953
1051
  }
954
1052
  } else if (node.type === "loop") {
955
1053
  const loop = node;
956
1054
  for (const child of loop.children) {
957
- this.collectStaticChildInstancesRecursive(child, result, true);
1055
+ this.collectStaticChildInstancesRecursive(child, result, true, providerCtx);
958
1056
  }
959
1057
  } else if (node.type === "element") {
960
1058
  const element = node;
961
1059
  for (const child of element.children) {
962
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1060
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
963
1061
  }
964
1062
  } else if (node.type === "fragment") {
965
1063
  const fragment = node;
966
1064
  for (const child of fragment.children) {
967
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1065
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
968
1066
  }
969
1067
  } else if (node.type === "conditional") {
970
1068
  const cond = node;
971
- this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop);
1069
+ this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop, providerCtx);
972
1070
  if (cond.whenFalse) {
973
- this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop);
1071
+ this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx);
974
1072
  }
975
1073
  } else if (node.type === "provider") {
976
1074
  const p = node;
1075
+ const childCtx = this.extendProviderContext(providerCtx, p);
977
1076
  for (const child of p.children) {
978
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1077
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, childCtx);
979
1078
  }
980
1079
  } else if (node.type === "async") {
981
1080
  const a = node;
982
- this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop);
1081
+ this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop, providerCtx);
983
1082
  for (const child of a.children) {
984
- this.collectStaticChildInstancesRecursive(child, result, inLoop);
1083
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
985
1084
  }
986
1085
  }
987
1086
  }
1087
+ extendProviderContext(current, p) {
1088
+ const v = p.valueProp?.value;
1089
+ if (!v || v.kind !== "literal")
1090
+ return current;
1091
+ let goLit = null;
1092
+ if (typeof v.value === "string")
1093
+ goLit = `"${this.escapeGoString(v.value)}"`;
1094
+ else if (typeof v.value === "number" || typeof v.value === "boolean")
1095
+ goLit = String(v.value);
1096
+ if (goLit === null)
1097
+ return current;
1098
+ const next = new Map(current);
1099
+ next.set(p.contextName, goLit);
1100
+ return next;
1101
+ }
988
1102
  collectSpreadSlots(node) {
989
1103
  const result = [];
990
1104
  this.collectSpreadSlotsRecursive(node, result);
@@ -1135,6 +1249,17 @@ ${goFields.join(`
1135
1249
  if (ir.metadata.restPropsName === trimmed) {
1136
1250
  return `in.${this.capitalizeFieldName(trimmed)}`;
1137
1251
  }
1252
+ const localConst = (ir.metadata.localConstants ?? []).find((c) => c.name === trimmed && !c.isModule);
1253
+ if (localConst?.value !== undefined) {
1254
+ const initTrimmed = localConst.value.trim();
1255
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(initTrimmed)) {
1256
+ const resolved = this.buildConditionalSpreadInitializer(initTrimmed, ir);
1257
+ if (resolved)
1258
+ return resolved;
1259
+ if (resolved === null)
1260
+ return null;
1261
+ }
1262
+ }
1138
1263
  }
1139
1264
  return null;
1140
1265
  }
@@ -1224,12 +1349,27 @@ ${goFields.join(`
1224
1349
  return null;
1225
1350
  goVal = `in.${this.capitalizeFieldName(param.name)}`;
1226
1351
  } else {
1227
- return null;
1352
+ const indexed = this.recordIndexAccessToGoMap(val, ir);
1353
+ if (indexed === null)
1354
+ return null;
1355
+ goVal = indexed;
1228
1356
  }
1229
1357
  entries.push(`${JSON.stringify(key)}: ${goVal}`);
1230
1358
  }
1231
1359
  return `map[string]any{${entries.join(", ")}}`;
1232
1360
  }
1361
+ recordIndexAccessToGoMap(val, ir) {
1362
+ const parsed = parseRecordIndexAccess(val, ir.metadata.localConstants ?? [], ir.metadata.propsParams);
1363
+ if (!parsed)
1364
+ return null;
1365
+ const entries = parsed.entries.map((e) => {
1366
+ const mapVal = e.value.kind === "number" ? e.value.text : JSON.stringify(e.value.text);
1367
+ return `${JSON.stringify(e.key)}: ${mapVal}`;
1368
+ });
1369
+ this.usesFmt = true;
1370
+ const field = `in.${this.capitalizeFieldName(parsed.indexPropName)}`;
1371
+ return `map[string]any{${entries.join(", ")}}[fmt.Sprint(${field})]`;
1372
+ }
1233
1373
  convertInitialValue(value, typeInfo, propsParams) {
1234
1374
  if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {
1235
1375
  if (propsParams?.some((p) => p.name === value)) {
@@ -1470,7 +1610,142 @@ ${goFields.join(`
1470
1610
  }
1471
1611
  return null;
1472
1612
  }
1473
- computeMemoInitialValue(memo, signals, propsParams, propFallbackVars = GoTemplateAdapter.EMPTY_PROP_FALLBACK_VARS) {
1613
+ computeTemplateLiteralMemoInitialValue(computation, propsParams) {
1614
+ const sf = ts.createSourceFile("__memo.ts", `const __x = (${computation});`, ts.ScriptTarget.Latest, false);
1615
+ const stmt = sf.statements[0];
1616
+ if (!stmt || !ts.isVariableStatement(stmt))
1617
+ return null;
1618
+ let init = stmt.declarationList.declarations[0]?.initializer;
1619
+ while (init && ts.isParenthesizedExpression(init))
1620
+ init = init.expression;
1621
+ if (!init || !ts.isArrowFunction(init))
1622
+ return null;
1623
+ let body = init.body;
1624
+ while (ts.isParenthesizedExpression(body)) {
1625
+ body = body.expression;
1626
+ }
1627
+ const localKeyBindings = new Map;
1628
+ if (ts.isBlock(body)) {
1629
+ let returned = null;
1630
+ for (const s of body.statements) {
1631
+ if (ts.isVariableStatement(s)) {
1632
+ for (const d of s.declarationList.declarations) {
1633
+ if (!ts.isIdentifier(d.name) || !d.initializer)
1634
+ continue;
1635
+ const binding = this.parseLocalKeyBinding(d.initializer);
1636
+ if (binding)
1637
+ localKeyBindings.set(d.name.text, binding);
1638
+ }
1639
+ } else if (ts.isReturnStatement(s) && s.expression) {
1640
+ returned = s.expression;
1641
+ } else if (ts.isExpressionStatement(s) || ts.isEmptyStatement(s)) {} else {
1642
+ return null;
1643
+ }
1644
+ }
1645
+ if (!returned)
1646
+ return null;
1647
+ body = returned;
1648
+ while (ts.isParenthesizedExpression(body)) {
1649
+ body = body.expression;
1650
+ }
1651
+ }
1652
+ if (!ts.isTemplateExpression(body) && !ts.isNoSubstitutionTemplateLiteral(body)) {
1653
+ return null;
1654
+ }
1655
+ const propNames = new Set(propsParams.map((p) => p.name));
1656
+ const escGo = (s) => `"${this.escapeGoString(s)}"`;
1657
+ const segments = [];
1658
+ if (ts.isNoSubstitutionTemplateLiteral(body)) {
1659
+ return escGo(body.text);
1660
+ }
1661
+ if (body.head.text)
1662
+ segments.push(escGo(body.head.text));
1663
+ for (const span of body.templateSpans) {
1664
+ const resolved = this.resolveTemplateInterpolation(span.expression, propNames, localKeyBindings);
1665
+ if (resolved === null)
1666
+ return null;
1667
+ segments.push(resolved);
1668
+ if (span.literal.text)
1669
+ segments.push(escGo(span.literal.text));
1670
+ }
1671
+ if (segments.length === 0)
1672
+ return '""';
1673
+ return segments.join(" + ");
1674
+ }
1675
+ resolveTemplateInterpolation(expr, propNames, localKeyBindings = new Map) {
1676
+ let node = expr;
1677
+ while (ts.isParenthesizedExpression(node))
1678
+ node = node.expression;
1679
+ if (ts.isIdentifier(node)) {
1680
+ const inlined = this.resolveModuleStringConst(node.text);
1681
+ if (inlined !== null)
1682
+ return inlined;
1683
+ return null;
1684
+ }
1685
+ if (ts.isElementAccessExpression(node)) {
1686
+ const indexed = this.recordIndexInterpolationToGo(node, propNames, localKeyBindings);
1687
+ if (indexed !== null)
1688
+ return indexed;
1689
+ return null;
1690
+ }
1691
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
1692
+ const right = node.right;
1693
+ const isEmptyStr = (ts.isStringLiteral(right) || ts.isNoSubstitutionTemplateLiteral(right)) && right.text === "";
1694
+ const propName2 = this.propsAccessName(node.left);
1695
+ if (propName2 && propNames.has(propName2) && isEmptyStr) {
1696
+ return `in.${this.capitalizeFieldName(propName2)}`;
1697
+ }
1698
+ return null;
1699
+ }
1700
+ const propName = this.propsAccessName(node);
1701
+ if (propName && propNames.has(propName)) {
1702
+ return `in.${this.capitalizeFieldName(propName)}`;
1703
+ }
1704
+ return null;
1705
+ }
1706
+ parseLocalKeyBinding(init) {
1707
+ let node = init;
1708
+ while (ts.isParenthesizedExpression(node))
1709
+ node = node.expression;
1710
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
1711
+ const propName2 = this.propsAccessName(node.left);
1712
+ const right = node.right;
1713
+ if (propName2 && (ts.isStringLiteral(right) || ts.isNoSubstitutionTemplateLiteral(right))) {
1714
+ return { propName: propName2, defaultLiteral: right.text };
1715
+ }
1716
+ return null;
1717
+ }
1718
+ const propName = this.propsAccessName(node);
1719
+ if (propName)
1720
+ return { propName };
1721
+ return null;
1722
+ }
1723
+ recordIndexInterpolationToGo(node, propNames, localKeyBindings) {
1724
+ const parsed = parseRecordIndexAccess(node, this.localConstants ?? [], [...propNames].map((name) => ({ name })), (name) => localKeyBindings.get(name) ?? null);
1725
+ if (!parsed)
1726
+ return null;
1727
+ const goVal = (v) => v.kind === "number" ? v.text : JSON.stringify(v.text);
1728
+ const entries = parsed.entries.map((e) => `${JSON.stringify(e.key)}: ${goVal(e.value)}`);
1729
+ if (parsed.defaultKey !== undefined) {
1730
+ const def = parsed.entries.find((e) => e.key === parsed.defaultKey);
1731
+ if (def)
1732
+ entries.unshift(`"": ${goVal(def.value)}`);
1733
+ }
1734
+ const allString = parsed.entries.every((e) => e.value.kind === "string");
1735
+ const mapType = allString ? "map[string]string" : "map[string]any";
1736
+ this.usesFmt = true;
1737
+ return `${mapType}{${entries.join(", ")}}[fmt.Sprint(in.${this.capitalizeFieldName(parsed.indexPropName)})]`;
1738
+ }
1739
+ propsAccessName(node) {
1740
+ if (!ts.isPropertyAccessExpression(node))
1741
+ return null;
1742
+ if (!ts.isIdentifier(node.expression))
1743
+ return null;
1744
+ if (!this.propsObjectName || node.expression.text !== this.propsObjectName)
1745
+ return null;
1746
+ return node.name.text;
1747
+ }
1748
+ computeMemoInitialValue(memo, signals, propsParams, propFallbackVars = GoTemplateAdapter.EMPTY_PROP_FALLBACK_VARS, goType) {
1474
1749
  const computation = memo.computation;
1475
1750
  const propRef = (propName) => {
1476
1751
  const hoisted = propFallbackVars.get(propName);
@@ -1478,6 +1753,9 @@ ${goFields.join(`
1478
1753
  return hoisted.varName;
1479
1754
  return `in.${this.capitalizeFieldName(propName)}`;
1480
1755
  };
1756
+ const tmplMemo = this.computeTemplateLiteralMemoInitialValue(computation, propsParams);
1757
+ if (tmplMemo !== null)
1758
+ return tmplMemo;
1481
1759
  const arithmeticMatch = computation.match(/\(\)\s*=>\s*(\w+)\(\)\s*([*+\-/])\s*(\d+)/);
1482
1760
  if (arithmeticMatch) {
1483
1761
  const [, depName, operator, operand] = arithmeticMatch;
@@ -1497,8 +1775,8 @@ ${goFields.join(`
1497
1775
  return `${hoisted.varName} ${operator} ${operand}`;
1498
1776
  const fieldName = this.capitalizeFieldName(propName);
1499
1777
  if (param.type) {
1500
- const goType = this.typeInfoToGo(param.type, param.defaultValue);
1501
- if (goType === "interface{}")
1778
+ const goType2 = this.typeInfoToGo(param.type, param.defaultValue);
1779
+ if (goType2 === "interface{}")
1502
1780
  return `in.${fieldName}.(int) ${operator} ${operand}`;
1503
1781
  }
1504
1782
  return `in.${fieldName} ${operator} ${operand}`;
@@ -1527,8 +1805,8 @@ ${goFields.join(`
1527
1805
  if (param) {
1528
1806
  const fieldName = this.capitalizeFieldName(varName);
1529
1807
  if (param.type) {
1530
- const goType = this.typeInfoToGo(param.type, param.defaultValue);
1531
- if (goType === "interface{}")
1808
+ const goType2 = this.typeInfoToGo(param.type, param.defaultValue);
1809
+ if (goType2 === "interface{}")
1532
1810
  return `in.${fieldName}.(int) ${operator} ${operand}`;
1533
1811
  }
1534
1812
  return `in.${fieldName} ${operator} ${operand}`;
@@ -1542,9 +1820,40 @@ ${goFields.join(`
1542
1820
  return `in.${this.capitalizeFieldName(varName)}`;
1543
1821
  }
1544
1822
  }
1823
+ if (goType === "bool")
1824
+ return "false";
1825
+ if (goType === "string")
1826
+ return '""';
1545
1827
  return "0";
1546
1828
  }
1829
+ isTemplateLiteralMemo(computation) {
1830
+ const sf = ts.createSourceFile("__memo.ts", `const __x = (${computation});`, ts.ScriptTarget.Latest, false);
1831
+ const stmt = sf.statements[0];
1832
+ if (!stmt || !ts.isVariableStatement(stmt))
1833
+ return false;
1834
+ let init = stmt.declarationList.declarations[0]?.initializer;
1835
+ while (init && ts.isParenthesizedExpression(init))
1836
+ init = init.expression;
1837
+ if (!init || !ts.isArrowFunction(init))
1838
+ return false;
1839
+ let body = init.body;
1840
+ while (ts.isParenthesizedExpression(body)) {
1841
+ body = body.expression;
1842
+ }
1843
+ if (ts.isBlock(body)) {
1844
+ const ret = body.statements.find(ts.isReturnStatement);
1845
+ if (!ret || !ret.expression)
1846
+ return false;
1847
+ body = ret.expression;
1848
+ while (ts.isParenthesizedExpression(body)) {
1849
+ body = body.expression;
1850
+ }
1851
+ }
1852
+ return ts.isTemplateExpression(body) || ts.isNoSubstitutionTemplateLiteral(body);
1853
+ }
1547
1854
  inferMemoType(memo, signals, propsParamMap) {
1855
+ if (this.isTemplateLiteralMemo(memo.computation))
1856
+ return "string";
1548
1857
  if (memo.computation.includes("*") || memo.computation.includes("/") || memo.computation.includes("+") || memo.computation.includes("-")) {
1549
1858
  for (const dep of memo.deps) {
1550
1859
  const signal = signals.find((s) => s.getter === dep);
@@ -1568,8 +1877,39 @@ ${goFields.join(`
1568
1877
  }
1569
1878
  }
1570
1879
  }
1880
+ if (this.typeInfoToGo(memo.type) === "interface{}" && this.isBooleanMemo(memo, signals, propsParamMap)) {
1881
+ return "bool";
1882
+ }
1571
1883
  return this.typeInfoToGo(memo.type);
1572
1884
  }
1885
+ isBooleanMemo(memo, signals, propsParamMap) {
1886
+ const c = memo.computation;
1887
+ if (/(!==|===|!=(?!=)|==(?!=))/.test(c))
1888
+ return true;
1889
+ if (/=>\s*!/.test(c))
1890
+ return true;
1891
+ const isBoolGetter = (name) => {
1892
+ const sig = signals.find((s) => s.getter === name);
1893
+ if (sig) {
1894
+ if (this.typeInfoToGo(sig.type) === "bool")
1895
+ return true;
1896
+ if (/\?\?\s*(true|false)\b/.test(sig.initialValue))
1897
+ return true;
1898
+ const propName = this.extractPropNameFromInitialValue(sig.initialValue) ?? sig.initialValue;
1899
+ const prop2 = propsParamMap.get(propName);
1900
+ if (prop2 && this.typeInfoToGo(prop2.type, prop2.defaultValue) === "bool")
1901
+ return true;
1902
+ return false;
1903
+ }
1904
+ const prop = propsParamMap.get(name);
1905
+ return !!prop && this.typeInfoToGo(prop.type, prop.defaultValue) === "bool";
1906
+ };
1907
+ const ternary = c.match(/=>\s*\w+\(\)\s*\?\s*(\w+)\(\)\s*:\s*(\w+)\(\)/);
1908
+ if (ternary) {
1909
+ return isBoolGetter(ternary[1]) && isBoolGetter(ternary[2]);
1910
+ }
1911
+ return false;
1912
+ }
1573
1913
  inferTypeFromValue(value) {
1574
1914
  if (value === "true" || value === "false")
1575
1915
  return "bool";
@@ -1917,8 +2257,42 @@ ${goFields.join(`
1917
2257
  if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
1918
2258
  return init.text;
1919
2259
  }
2260
+ const joined = this.evalStringArrayJoin(init);
2261
+ if (joined !== null)
2262
+ return joined;
1920
2263
  return null;
1921
2264
  }
2265
+ evalStringArrayJoin(node) {
2266
+ if (!ts.isCallExpression(node))
2267
+ return null;
2268
+ const callee = node.expression;
2269
+ if (!ts.isPropertyAccessExpression(callee))
2270
+ return null;
2271
+ if (callee.name.text !== "join")
2272
+ return null;
2273
+ let recv = callee.expression;
2274
+ while (ts.isParenthesizedExpression(recv))
2275
+ recv = recv.expression;
2276
+ if (!ts.isArrayLiteralExpression(recv))
2277
+ return null;
2278
+ const parts = [];
2279
+ for (const el of recv.elements) {
2280
+ if (ts.isStringLiteral(el) || ts.isNoSubstitutionTemplateLiteral(el)) {
2281
+ parts.push(el.text);
2282
+ } else {
2283
+ return null;
2284
+ }
2285
+ }
2286
+ let sep = ",";
2287
+ if (node.arguments.length >= 1) {
2288
+ const arg = node.arguments[0];
2289
+ if (ts.isStringLiteral(arg) || ts.isNoSubstitutionTemplateLiteral(arg))
2290
+ sep = arg.text;
2291
+ else
2292
+ return null;
2293
+ }
2294
+ return parts.join(sep);
2295
+ }
1922
2296
  resolveModuleStringConst(name) {
1923
2297
  if (this.loopParamStack.length > 0 && this.loopParamStack[this.loopParamStack.length - 1] === name) {
1924
2298
  return null;
@@ -3079,8 +3453,9 @@ ${children}`;
3079
3453
  return `${name}="${this.renderParsedExpr(parsed)}"`;
3080
3454
  }
3081
3455
  const bareId = value.expr.trim();
3082
- if (this.nillablePropNames.has(bareId)) {
3083
- const field = `.${this.capitalizeFieldName(bareId)}`;
3456
+ const propName = this.propsObjectName && bareId.startsWith(`${this.propsObjectName}.`) ? bareId.slice(this.propsObjectName.length + 1) : bareId;
3457
+ if (/^[A-Za-z_$][\w$]*$/.test(propName) && this.nillablePropNames.has(propName)) {
3458
+ const field = `.${this.capitalizeFieldName(propName)}`;
3084
3459
  return `{{if ne ${field} nil}}${name}="{{${this.convertExpressionToGo(value.expr)}}}"{{end}}`;
3085
3460
  }
3086
3461
  return `${name}="{{${this.convertExpressionToGo(value.expr)}}}"`;
@@ -1 +1 @@
1
- {"version":3,"file":"test-render.d.ts","sourceRoot":"","sources":["../src/test-render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,iBAAiB,CAAA;AAOnE,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAgCD,MAAM,WAAW,aAAa;IAC5B,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,8BAA8B;IAC9B,OAAO,EAAE,eAAe,CAAA;IACxB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACpC;AAED,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAmNvF"}
1
+ {"version":3,"file":"test-render.d.ts","sourceRoot":"","sources":["../src/test-render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,iBAAiB,CAAA;AAOnE,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAgCD,MAAM,WAAW,aAAa;IAC5B,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,8BAA8B;IAC9B,OAAO,EAAE,eAAe,CAAA;IACxB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACpC;AAED,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAsRvF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/go-template",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "Go html/template adapter for BarefootJS - generates Go template files from IR",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -54,6 +54,6 @@
54
54
  },
55
55
  "devDependencies": {
56
56
  "@barefootjs/adapter-tests": "0.1.0",
57
- "@barefootjs/jsx": "0.8.0"
57
+ "@barefootjs/jsx": "0.9.1"
58
58
  }
59
59
  }