@barefootjs/go-template 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/go-template-adapter.d.ts +142 -0
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +402 -27
- package/dist/build.js +402 -27
- package/dist/index.js +402 -27
- package/dist/test-render.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/go-template-adapter.test.ts +189 -29
- package/src/adapter/go-template-adapter.ts +689 -25
- package/src/test-render.ts +189 -10
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
|
|
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
|
-
|
|
899
|
+
emitChildField(prop.name, this.goLiteral(prop.value.value));
|
|
806
900
|
break;
|
|
807
901
|
case "boolean-shorthand":
|
|
808
902
|
case "boolean-attr":
|
|
809
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1501
|
-
if (
|
|
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
|
|
1531
|
-
if (
|
|
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
|
-
|
|
3083
|
-
|
|
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,
|
|
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.
|
|
3
|
+
"version": "0.9.0",
|
|
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.
|
|
57
|
+
"@barefootjs/jsx": "0.9.0"
|
|
58
58
|
}
|
|
59
59
|
}
|