@barefootjs/go-template 0.5.1 → 0.5.3

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
@@ -430,6 +430,8 @@ class GoTemplateAdapter extends BaseAdapter {
430
430
  templateVarCounter = 0;
431
431
  localTypeNames = new Set;
432
432
  localTypeAliases = new Map;
433
+ localStructFields = new Map;
434
+ synthStructTypes = new Map;
433
435
  usesHtmlTemplate = false;
434
436
  constructor(options = {}) {
435
437
  super();
@@ -663,6 +665,7 @@ ${scriptRegistrations}${templateBody}
663
665
  const componentName = ir.metadata.componentName;
664
666
  this.localTypeNames = new Set;
665
667
  this.localTypeAliases = new Map;
668
+ this.localStructFields = new Map;
666
669
  for (const td of ir.metadata.typeDefinitions) {
667
670
  if (td.name === "Props" || td.name === `${componentName}Props`)
668
671
  continue;
@@ -671,6 +674,11 @@ ${scriptRegistrations}${templateBody}
671
674
  this.localTypeNames.add(td.name);
672
675
  if (td.definition.match(/^type \w+ = ('[^']*'(\s*\|\s*'[^']*')*)/)) {
673
676
  this.localTypeAliases.set(td.name, "string");
677
+ } else {
678
+ const fields = this.structFieldsFor(td);
679
+ if (fields.length > 0) {
680
+ this.localStructFields.set(td.name, new Map(fields.map((f) => [f.tsName, f.goName])));
681
+ }
674
682
  }
675
683
  }
676
684
  for (const td of ir.metadata.typeDefinitions) {
@@ -684,6 +692,26 @@ ${scriptRegistrations}${templateBody}
684
692
  lines.push("");
685
693
  }
686
694
  }
695
+ this.synthStructTypes = new Map;
696
+ for (const signal of ir.metadata.signals) {
697
+ const synth = this.synthesizeStructFromSignal(signal, componentName);
698
+ if (!synth)
699
+ continue;
700
+ this.localTypeNames.add(synth.name);
701
+ this.localStructFields.set(synth.name, new Map(synth.fields.map((f) => [f.tsName, f.goName])));
702
+ this.synthStructTypes.set(signal.getter, {
703
+ kind: "array",
704
+ raw: `${synth.name}[]`,
705
+ elementType: { kind: "interface", raw: synth.name }
706
+ });
707
+ const goFields = synth.fields.map((f) => ` ${f.goName} ${f.goType} \`json:"${this.toJsonTag(f.tsName)}"\``);
708
+ lines.push(`// ${synth.name} is a synthesised element type for the ${signal.getter} signal.`);
709
+ lines.push(`type ${synth.name} struct {
710
+ ${goFields.join(`
711
+ `)}
712
+ }`);
713
+ lines.push("");
714
+ }
687
715
  const nestedComponents = this.findNestedComponents(ir.root);
688
716
  const propTypeOverrides = this.buildPropTypeOverrides(ir);
689
717
  const spreadSlots = this.collectSpreadSlots(ir.root);
@@ -705,35 +733,114 @@ ${scriptRegistrations}${templateBody}
705
733
  `);
706
734
  }
707
735
  typeDefinitionToGo(td) {
708
- const def = td.definition;
709
- if (def.match(/^type \w+ = ('[^']*'(\s*\|\s*'[^']*')*)/)) {
736
+ if (td.definition.match(/^type \w+ = ('[^']*'(\s*\|\s*'[^']*')*)/)) {
710
737
  return `// ${td.name} is a string type.
711
738
  type ${td.name} = string`;
712
739
  }
713
- const bodyMatch = def.match(/(?:type \w+ = |interface \w+ )\{([\s\S]*)\}/);
714
- if (!bodyMatch)
715
- return null;
716
- const body = bodyMatch[1];
717
- const goFields = [];
718
- const fieldEntries = body.split(/[;\n]/).map((s) => s.trim()).filter(Boolean);
719
- for (const entry of fieldEntries) {
720
- const fieldMatch = entry.match(/^(\w+)\??\s*:\s*(.+)$/);
721
- if (!fieldMatch)
722
- continue;
723
- const [, fieldName, tsType] = fieldMatch;
724
- const goFieldName = this.capitalizeFieldName(fieldName);
725
- const goType = this.tsTypeStringToGo(tsType.trim());
726
- const jsonTag = this.toJsonTag(fieldName);
727
- goFields.push(` ${goFieldName} ${goType} \`json:"${jsonTag}"\``);
728
- }
729
- if (goFields.length === 0)
740
+ const fields = this.structFieldsFor(td);
741
+ if (fields.length === 0)
730
742
  return null;
743
+ const goFields = fields.map((f) => ` ${f.goName} ${f.goType} \`json:"${this.toJsonTag(f.tsName)}"\``);
731
744
  return `// ${td.name} represents a ${td.name.toLowerCase()}.
732
745
  type ${td.name} struct {
733
746
  ${goFields.join(`
734
747
  `)}
735
748
  }`;
736
749
  }
750
+ structFieldsFor(td) {
751
+ const fields = [];
752
+ for (const prop of td.properties ?? []) {
753
+ if (!GoTemplateAdapter.GO_IDENTIFIER.test(prop.name))
754
+ continue;
755
+ fields.push({
756
+ tsName: prop.name,
757
+ goName: this.capitalizeFieldName(prop.name),
758
+ goType: this.typeInfoToGo(prop.type)
759
+ });
760
+ }
761
+ return fields;
762
+ }
763
+ synthesizeStructFromSignal(signal, componentName) {
764
+ if (signal.type.kind !== "array")
765
+ return null;
766
+ const elem = signal.type.elementType;
767
+ if (elem && elem.kind !== "unknown")
768
+ return null;
769
+ const node = this.parseLiteralExpression(signal.initialValue);
770
+ if (!node || !ts.isArrayLiteralExpression(node) || node.elements.length === 0)
771
+ return null;
772
+ const order = [];
773
+ const goTypes = new Map;
774
+ for (let i = 0;i < node.elements.length; i++) {
775
+ const el = node.elements[i];
776
+ if (!ts.isObjectLiteralExpression(el))
777
+ return null;
778
+ const seen = new Set;
779
+ for (const prop of el.properties) {
780
+ if (!ts.isPropertyAssignment(prop))
781
+ return null;
782
+ if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name) && !ts.isNumericLiteral(prop.name)) {
783
+ return null;
784
+ }
785
+ const key = prop.name.text;
786
+ if (!GoTemplateAdapter.GO_IDENTIFIER.test(key))
787
+ return null;
788
+ const goType = this.scalarLiteralGoType(prop.initializer);
789
+ if (!goType)
790
+ return null;
791
+ seen.add(key);
792
+ const prev = goTypes.get(key);
793
+ if (prev === undefined) {
794
+ if (i !== 0)
795
+ return null;
796
+ order.push(key);
797
+ goTypes.set(key, goType);
798
+ } else {
799
+ const merged = this.mergeScalarGoType(prev, goType);
800
+ if (!merged)
801
+ return null;
802
+ goTypes.set(key, merged);
803
+ }
804
+ }
805
+ if (seen.size !== order.length)
806
+ return null;
807
+ }
808
+ const name = `${componentName}${this.capitalizeFieldName(signal.getter)}Item`;
809
+ if (this.localTypeNames.has(name))
810
+ return null;
811
+ return {
812
+ name,
813
+ fields: order.map((key) => ({
814
+ tsName: key,
815
+ goName: this.capitalizeFieldName(key),
816
+ goType: goTypes.get(key)
817
+ }))
818
+ };
819
+ }
820
+ scalarLiteralGoType(node) {
821
+ if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(node.operand)) {
822
+ return this.numericLiteralGoType(node.operand.text);
823
+ }
824
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
825
+ return "string";
826
+ if (ts.isNumericLiteral(node))
827
+ return this.numericLiteralGoType(node.text);
828
+ if (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) {
829
+ return "bool";
830
+ }
831
+ return null;
832
+ }
833
+ numericLiteralGoType(text) {
834
+ return /[.eE]/.test(text) && !text.startsWith("0x") ? "float64" : "int";
835
+ }
836
+ mergeScalarGoType(a, b) {
837
+ if (a === b)
838
+ return a;
839
+ const numeric = new Set(["int", "float64"]);
840
+ if (numeric.has(a) && numeric.has(b))
841
+ return "float64";
842
+ return null;
843
+ }
737
844
  tsTypeStringToGo(tsType) {
738
845
  const t = tsType.trim();
739
846
  if (t === "number")
@@ -838,6 +945,11 @@ ${goFields.join(`
838
945
  if (propFieldNames.has(fieldName))
839
946
  continue;
840
947
  const jsonTag = this.toJsonTag(signal.getter);
948
+ const synthType = this.synthStructTypes.get(signal.getter);
949
+ if (synthType) {
950
+ lines.push(` ${fieldName} ${this.typeInfoToGo(synthType)} \`json:"${jsonTag}"\``);
951
+ continue;
952
+ }
841
953
  let goType;
842
954
  let referencedProp = propsParamMap.get(signal.initialValue);
843
955
  if (!referencedProp) {
@@ -966,7 +1078,8 @@ ${goFields.join(`
966
1078
  if (hoisted) {
967
1079
  lines.push(` ${fieldName}: ${hoisted.varName},`);
968
1080
  } else {
969
- const initialValue = this.convertInitialValue(signal.initialValue, signal.type, ir.metadata.propsParams);
1081
+ const bakeType = this.synthStructTypes.get(signal.getter) ?? signal.type;
1082
+ const initialValue = this.convertInitialValue(signal.initialValue, bakeType, ir.metadata.propsParams);
970
1083
  lines.push(` ${fieldName}: ${initialValue},`);
971
1084
  }
972
1085
  }
@@ -1347,10 +1460,7 @@ ${goFields.join(`
1347
1460
  }
1348
1461
  }
1349
1462
  if (typeInfo.kind === "array") {
1350
- if (value === "[]" || value === "null" || value === "undefined") {
1351
- return "nil";
1352
- }
1353
- return "nil";
1463
+ return this.jsLiteralToGo(value, typeInfo) ?? "nil";
1354
1464
  }
1355
1465
  if (typeInfo.kind === "interface" && typeInfo.raw) {
1356
1466
  const aliasBase = this.localTypeAliases.get(typeInfo.raw);
@@ -1363,6 +1473,81 @@ ${goFields.join(`
1363
1473
  }
1364
1474
  return "nil";
1365
1475
  }
1476
+ jsLiteralToGo(value, typeInfo) {
1477
+ const expr = this.parseLiteralExpression(value);
1478
+ if (!expr)
1479
+ return null;
1480
+ return this.tsLiteralToGo(expr, typeInfo);
1481
+ }
1482
+ parseLiteralExpression(value) {
1483
+ const sf = ts.createSourceFile("__lit.ts", `(${value})`, ts.ScriptTarget.Latest, true);
1484
+ if (sf.statements.length !== 1)
1485
+ return null;
1486
+ const stmt = sf.statements[0];
1487
+ if (!ts.isExpressionStatement(stmt))
1488
+ return null;
1489
+ let expr = stmt.expression;
1490
+ while (ts.isParenthesizedExpression(expr))
1491
+ expr = expr.expression;
1492
+ return expr;
1493
+ }
1494
+ tsLiteralToGo(node, typeInfo) {
1495
+ if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(node.operand)) {
1496
+ return `-${node.operand.text}`;
1497
+ }
1498
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
1499
+ return JSON.stringify(node.text);
1500
+ }
1501
+ if (ts.isNumericLiteral(node))
1502
+ return node.text;
1503
+ if (node.kind === ts.SyntaxKind.TrueKeyword)
1504
+ return "true";
1505
+ if (node.kind === ts.SyntaxKind.FalseKeyword)
1506
+ return "false";
1507
+ if (node.kind === ts.SyntaxKind.NullKeyword)
1508
+ return "nil";
1509
+ if (ts.isArrayLiteralExpression(node)) {
1510
+ if (node.elements.length === 0)
1511
+ return null;
1512
+ const elemType = typeInfo?.kind === "array" ? typeInfo.elementType : undefined;
1513
+ const sliceHeader = typeInfo?.kind === "array" ? this.typeInfoToGo(typeInfo) : "[]interface{}";
1514
+ const elems = [];
1515
+ for (const el of node.elements) {
1516
+ const go = this.tsLiteralToGo(el, elemType);
1517
+ if (go === null)
1518
+ return null;
1519
+ elems.push(go);
1520
+ }
1521
+ return `${sliceHeader}{${elems.join(", ")}}`;
1522
+ }
1523
+ if (ts.isObjectLiteralExpression(node)) {
1524
+ const goType = typeInfo ? this.typeInfoToGo(typeInfo) : "interface{}";
1525
+ const structFields = this.localStructFields.get(goType);
1526
+ if (!structFields)
1527
+ return null;
1528
+ const entries = [];
1529
+ for (const prop of node.properties) {
1530
+ if (!ts.isPropertyAssignment(prop))
1531
+ return null;
1532
+ if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name) && !ts.isNumericLiteral(prop.name)) {
1533
+ return null;
1534
+ }
1535
+ const goField = structFields.get(prop.name.text);
1536
+ if (!goField)
1537
+ return null;
1538
+ const init = prop.initializer;
1539
+ if (ts.isObjectLiteralExpression(init) || ts.isArrayLiteralExpression(init)) {
1540
+ return null;
1541
+ }
1542
+ const go = this.tsLiteralToGo(init);
1543
+ if (go === null)
1544
+ return null;
1545
+ entries.push(`${goField}: ${go}`);
1546
+ }
1547
+ return `${goType}{${entries.join(", ")}}`;
1548
+ }
1549
+ return null;
1550
+ }
1366
1551
  typeInfoToGo(typeInfo, defaultValue) {
1367
1552
  switch (typeInfo.kind) {
1368
1553
  case "primitive":
@@ -1675,6 +1860,7 @@ ${goFields.join(`
1675
1860
  }
1676
1861
  return null;
1677
1862
  }
1863
+ static GO_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
1678
1864
  static GO_INITIALISMS = new Set([
1679
1865
  "id",
1680
1866
  "url",
@@ -1881,9 +2067,23 @@ ${goFields.join(`
1881
2067
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
1882
2068
  if (currentLoopParam && name === currentLoopParam)
1883
2069
  return ".";
2070
+ if (this.isOuterLoopParam(name))
2071
+ return `$${name}`;
1884
2072
  if (this.loopVarRefCount.has(name))
1885
2073
  return `$${name}`;
1886
- return `.${this.capitalizeFieldName(name)}`;
2074
+ return this.rootFieldRef(name);
2075
+ }
2076
+ isOuterLoopParam(name) {
2077
+ const top = this.loopParamStack.length - 1;
2078
+ for (let i = 0;i < top; i++) {
2079
+ if (this.loopParamStack[i] === name)
2080
+ return true;
2081
+ }
2082
+ return false;
2083
+ }
2084
+ rootFieldRef(name) {
2085
+ const prefix = this.loopParamStack.length > 0 ? "$." : ".";
2086
+ return `${prefix}${this.capitalizeFieldName(name)}`;
1887
2087
  }
1888
2088
  literal(value, literalType) {
1889
2089
  if (literalType === "string")
@@ -1894,7 +2094,7 @@ ${goFields.join(`
1894
2094
  }
1895
2095
  call(callee, args, emit) {
1896
2096
  if (callee.kind === "identifier" && args.length === 0) {
1897
- return `.${this.capitalizeFieldName(callee.name)}`;
2097
+ return this.rootFieldRef(callee.name);
1898
2098
  }
1899
2099
  const path = identifierPath(callee);
1900
2100
  if (path && this.templatePrimitives[path]) {
@@ -1935,7 +2135,7 @@ ${goFields.join(`
1935
2135
  return templateBlock;
1936
2136
  }
1937
2137
  if (object.kind === "identifier" && this.propsObjectName && object.name === this.propsObjectName) {
1938
- return `.${this.capitalizeFieldName(property)}`;
2138
+ return this.rootFieldRef(property);
1939
2139
  }
1940
2140
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
1941
2141
  if (object.kind === "identifier" && currentLoopParam && object.name === currentLoopParam) {
@@ -2672,11 +2872,14 @@ Options:
2672
2872
  if (currentLoopParam && expr.name === currentLoopParam) {
2673
2873
  return plain(".");
2674
2874
  }
2875
+ if (this.isOuterLoopParam(expr.name)) {
2876
+ return plain(`$${expr.name}`);
2877
+ }
2675
2878
  if (this.loopVarRefCount.has(expr.name)) {
2676
2879
  return plain(`$${expr.name}`);
2677
2880
  }
2678
2881
  }
2679
- return plain(`.${this.capitalizeFieldName(expr.name)}`);
2882
+ return plain(this.rootFieldRef(expr.name));
2680
2883
  case "literal":
2681
2884
  if (expr.literalType === "string")
2682
2885
  return plain(`"${expr.value}"`);
@@ -2685,7 +2888,7 @@ Options:
2685
2888
  return plain(String(expr.value));
2686
2889
  case "call": {
2687
2890
  if (expr.callee.kind === "identifier" && expr.args.length === 0) {
2688
- return plain(`.${this.capitalizeFieldName(expr.callee.name)}`);
2891
+ return plain(this.rootFieldRef(expr.callee.name));
2689
2892
  }
2690
2893
  return plain(this.renderParsedExpr(expr));
2691
2894
  }
@@ -2696,7 +2899,7 @@ Options:
2696
2899
  return plain(result);
2697
2900
  }
2698
2901
  if (expr.object.kind === "identifier" && this.propsObjectName && expr.object.name === this.propsObjectName) {
2699
- return plain(`.${this.capitalizeFieldName(expr.property)}`);
2902
+ return plain(this.rootFieldRef(expr.property));
2700
2903
  }
2701
2904
  {
2702
2905
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];