@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/adapter/go-template-adapter.d.ts +109 -0
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +233 -30
- package/dist/build.js +233 -30
- package/dist/index.js +233 -30
- package/package.json +2 -2
- package/src/__tests__/go-template-adapter.test.ts +428 -77
- package/src/adapter/go-template-adapter.ts +405 -38
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
|
-
|
|
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
|
|
714
|
-
if (
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
2902
|
+
return plain(this.rootFieldRef(expr.property));
|
|
2700
2903
|
}
|
|
2701
2904
|
{
|
|
2702
2905
|
const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
|