@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/build.js
CHANGED
|
@@ -372,7 +372,10 @@ import {
|
|
|
372
372
|
identifierPath,
|
|
373
373
|
emitParsedExpr,
|
|
374
374
|
emitIRNode,
|
|
375
|
-
emitAttrValue
|
|
375
|
+
emitAttrValue,
|
|
376
|
+
augmentInheritedPropAccesses,
|
|
377
|
+
parseRecordIndexAccess,
|
|
378
|
+
collectContextConsumers
|
|
376
379
|
} from "@barefootjs/jsx";
|
|
377
380
|
import { findInterpolationEnd } from "@barefootjs/jsx/scanner";
|
|
378
381
|
function wrapIfMultiToken(rendered) {
|
|
@@ -457,7 +460,12 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
457
460
|
localStructFields = new Map;
|
|
458
461
|
synthStructTypes = new Map;
|
|
459
462
|
usesHtmlTemplate = false;
|
|
463
|
+
usesFmt = false;
|
|
464
|
+
childComponentShapes = new Map;
|
|
460
465
|
moduleStringConsts = new Map;
|
|
466
|
+
localConstants = [];
|
|
467
|
+
contextConsumers = [];
|
|
468
|
+
childContextConsumers = new Map;
|
|
461
469
|
nillablePropNames = new Set;
|
|
462
470
|
constructor(options = {}) {
|
|
463
471
|
super();
|
|
@@ -474,6 +482,9 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
474
482
|
this.propsObjectName = ir.metadata.propsObjectName;
|
|
475
483
|
this.restPropsName = ir.metadata.restPropsName ?? null;
|
|
476
484
|
this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
|
|
485
|
+
this.localConstants = ir.metadata.localConstants ?? [];
|
|
486
|
+
this.contextConsumers = collectContextConsumers(ir.metadata);
|
|
487
|
+
augmentInheritedPropAccesses(ir);
|
|
477
488
|
this.nillablePropNames = this.collectNillablePropNames(ir);
|
|
478
489
|
if (!options?.siblingTemplatesRegistered) {
|
|
479
490
|
this.checkImportedLoopChildComponents(ir);
|
|
@@ -687,8 +698,46 @@ ${scriptRegistrations}${templateBody}
|
|
|
687
698
|
return `{{if .Scripts}}${registrations.join("")}{{end}}
|
|
688
699
|
`;
|
|
689
700
|
}
|
|
701
|
+
registerChildComponentShape(ir) {
|
|
702
|
+
const name = ir.metadata.componentName;
|
|
703
|
+
if (!name)
|
|
704
|
+
return;
|
|
705
|
+
const paramNames = new Set((ir.metadata.propsParams ?? []).map((p) => p.name));
|
|
706
|
+
const restPropsName = ir.metadata.restPropsName ?? null;
|
|
707
|
+
const restBagField = restPropsName ? this.capitalizeFieldName(restPropsName) : null;
|
|
708
|
+
this.childComponentShapes.set(name, { paramNames, restBagField });
|
|
709
|
+
this.childContextConsumers.set(name, collectContextConsumers(ir.metadata));
|
|
710
|
+
}
|
|
711
|
+
contextFieldName(c) {
|
|
712
|
+
return this.capitalizeFieldName(c.localName);
|
|
713
|
+
}
|
|
714
|
+
contextConsumerGoType(c) {
|
|
715
|
+
if (typeof c.defaultValue === "number")
|
|
716
|
+
return "int";
|
|
717
|
+
if (typeof c.defaultValue === "boolean")
|
|
718
|
+
return "bool";
|
|
719
|
+
return "string";
|
|
720
|
+
}
|
|
721
|
+
contextConsumerGoDefault(c) {
|
|
722
|
+
if (typeof c.defaultValue === "number")
|
|
723
|
+
return String(c.defaultValue);
|
|
724
|
+
if (typeof c.defaultValue === "boolean")
|
|
725
|
+
return String(c.defaultValue);
|
|
726
|
+
if (typeof c.defaultValue === "string")
|
|
727
|
+
return `"${this.escapeGoString(c.defaultValue)}"`;
|
|
728
|
+
return '""';
|
|
729
|
+
}
|
|
730
|
+
nonCollidingContextConsumers(taken) {
|
|
731
|
+
return this.contextConsumers.filter((c) => !taken.has(this.contextFieldName(c)));
|
|
732
|
+
}
|
|
690
733
|
generateTypes(ir) {
|
|
691
734
|
this.usesHtmlTemplate = false;
|
|
735
|
+
this.usesFmt = false;
|
|
736
|
+
this.propsObjectName = ir.metadata.propsObjectName;
|
|
737
|
+
augmentInheritedPropAccesses(ir);
|
|
738
|
+
this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
|
|
739
|
+
this.localConstants = ir.metadata.localConstants ?? [];
|
|
740
|
+
this.contextConsumers = collectContextConsumers(ir.metadata);
|
|
692
741
|
const lines = [];
|
|
693
742
|
const componentName = ir.metadata.componentName;
|
|
694
743
|
this.localTypeNames = new Set;
|
|
@@ -750,6 +799,8 @@ ${goFields.join(`
|
|
|
750
799
|
header.push(`package ${this.options.packageName}`);
|
|
751
800
|
header.push("");
|
|
752
801
|
header.push("import (");
|
|
802
|
+
if (this.usesFmt)
|
|
803
|
+
header.push('\t"fmt"');
|
|
753
804
|
if (this.usesHtmlTemplate)
|
|
754
805
|
header.push('\t"html/template"');
|
|
755
806
|
header.push('\t"math/rand"');
|
|
@@ -942,6 +993,10 @@ ${goFields.join(`
|
|
|
942
993
|
for (const nested of inputNested) {
|
|
943
994
|
lines.push(` ${nested.name}s []${nested.name}Input`);
|
|
944
995
|
}
|
|
996
|
+
const takenInput = new Set(ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)));
|
|
997
|
+
for (const c of this.nonCollidingContextConsumers(takenInput)) {
|
|
998
|
+
lines.push(` ${this.contextFieldName(c)} ${this.contextConsumerGoType(c)}`);
|
|
999
|
+
}
|
|
945
1000
|
const restPropsName = ir.metadata.restPropsName;
|
|
946
1001
|
if (restPropsName) {
|
|
947
1002
|
const seen = new Set;
|
|
@@ -1019,6 +1074,15 @@ ${goFields.join(`
|
|
|
1019
1074
|
const goType = this.inferMemoType(memo, ir.metadata.signals, propsParamMap);
|
|
1020
1075
|
lines.push(` ${fieldName} ${goType} \`json:"${jsonTag}"\``);
|
|
1021
1076
|
}
|
|
1077
|
+
const takenProps = new Set([
|
|
1078
|
+
...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
|
|
1079
|
+
...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
|
|
1080
|
+
...ir.metadata.memos.map((m) => this.capitalizeFieldName(m.name))
|
|
1081
|
+
]);
|
|
1082
|
+
for (const c of this.nonCollidingContextConsumers(takenProps)) {
|
|
1083
|
+
const jsonTag = this.toJsonTag(c.localName);
|
|
1084
|
+
lines.push(` ${this.contextFieldName(c)} ${this.contextConsumerGoType(c)} \`json:"${jsonTag}"\``);
|
|
1085
|
+
}
|
|
1022
1086
|
for (const nested of nestedComponents) {
|
|
1023
1087
|
if (nested.isDynamic && !nested.isPropDerived) {
|
|
1024
1088
|
lines.push(` ${nested.name}s []${nested.name}Props \`json:"-"\``);
|
|
@@ -1128,25 +1192,55 @@ ${goFields.join(`
|
|
|
1128
1192
|
const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
|
|
1129
1193
|
lines.push(` ${nested.name}s: ${varName},`);
|
|
1130
1194
|
}
|
|
1195
|
+
const memoPropsParamMap = new Map(ir.metadata.propsParams.map((p) => [p.name, p]));
|
|
1131
1196
|
for (const memo of ir.metadata.memos) {
|
|
1132
1197
|
const fieldName = this.capitalizeFieldName(memo.name);
|
|
1133
|
-
const
|
|
1198
|
+
const goType = this.inferMemoType(memo, ir.metadata.signals, memoPropsParamMap);
|
|
1199
|
+
const memoValue = this.computeMemoInitialValue(memo, ir.metadata.signals, ir.metadata.propsParams, propFallbackVars, goType);
|
|
1134
1200
|
lines.push(` ${fieldName}: ${memoValue},`);
|
|
1135
1201
|
}
|
|
1202
|
+
const takenInit = new Set([
|
|
1203
|
+
...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
|
|
1204
|
+
...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
|
|
1205
|
+
...ir.metadata.memos.map((m) => this.capitalizeFieldName(m.name))
|
|
1206
|
+
]);
|
|
1207
|
+
for (const c of this.nonCollidingContextConsumers(takenInit)) {
|
|
1208
|
+
const field = this.contextFieldName(c);
|
|
1209
|
+
const def = this.contextConsumerGoDefault(c);
|
|
1210
|
+
const defaulted = c.defaultValue === null || def === '""' || def === "0" || def === "false" ? `in.${field}` : this.applyGoFallback(`in.${field}`, def);
|
|
1211
|
+
lines.push(` ${field}: ${defaulted},`);
|
|
1212
|
+
}
|
|
1136
1213
|
const staticChildren = this.collectStaticChildInstances(ir.root);
|
|
1137
1214
|
for (const child of staticChildren) {
|
|
1138
1215
|
lines.push(` ${child.fieldName}: New${child.name}Props(${child.name}Input{`);
|
|
1139
1216
|
lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
|
|
1140
1217
|
lines.push(` BfParent: scopeID,`);
|
|
1141
1218
|
lines.push(` BfMount: "${child.slotId}",`);
|
|
1219
|
+
if (child.contextBindings) {
|
|
1220
|
+
for (const consumer of this.childContextConsumers.get(child.name) ?? []) {
|
|
1221
|
+
const goVal = child.contextBindings.get(consumer.contextName);
|
|
1222
|
+
if (goVal !== undefined) {
|
|
1223
|
+
lines.push(` ${this.contextFieldName(consumer)}: ${goVal},`);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
const childShape = this.childComponentShapes.get(child.name);
|
|
1228
|
+
const restBagEntries = [];
|
|
1229
|
+
const emitChildField = (jsxName, goValue) => {
|
|
1230
|
+
if (childShape && childShape.restBagField && !childShape.paramNames.has(jsxName)) {
|
|
1231
|
+
restBagEntries.push(`${JSON.stringify(jsxName)}: ${goValue}`);
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
lines.push(` ${this.capitalizeFieldName(jsxName)}: ${goValue},`);
|
|
1235
|
+
};
|
|
1142
1236
|
for (const prop of child.props) {
|
|
1143
1237
|
switch (prop.value.kind) {
|
|
1144
1238
|
case "literal":
|
|
1145
|
-
|
|
1239
|
+
emitChildField(prop.name, this.goLiteral(prop.value.value));
|
|
1146
1240
|
break;
|
|
1147
1241
|
case "boolean-shorthand":
|
|
1148
1242
|
case "boolean-attr":
|
|
1149
|
-
|
|
1243
|
+
emitChildField(prop.name, "true");
|
|
1150
1244
|
break;
|
|
1151
1245
|
case "expression":
|
|
1152
1246
|
case "spread":
|
|
@@ -1155,7 +1249,7 @@ ${goFields.join(`
|
|
|
1155
1249
|
if (parts) {
|
|
1156
1250
|
const goExpr = this.templatePartsToGoCode(parts, ir.metadata.propsParams);
|
|
1157
1251
|
if (goExpr !== null) {
|
|
1158
|
-
|
|
1252
|
+
emitChildField(prop.name, goExpr);
|
|
1159
1253
|
break;
|
|
1160
1254
|
}
|
|
1161
1255
|
}
|
|
@@ -1164,7 +1258,7 @@ ${goFields.join(`
|
|
|
1164
1258
|
break;
|
|
1165
1259
|
const resolvedValue = this.resolveDynamicPropValue(exprText, ir.metadata.signals, ir.metadata.memos, ir.metadata.propsParams);
|
|
1166
1260
|
if (resolvedValue !== null) {
|
|
1167
|
-
|
|
1261
|
+
emitChildField(prop.name, resolvedValue);
|
|
1168
1262
|
}
|
|
1169
1263
|
break;
|
|
1170
1264
|
}
|
|
@@ -1172,6 +1266,9 @@ ${goFields.join(`
|
|
|
1172
1266
|
break;
|
|
1173
1267
|
}
|
|
1174
1268
|
}
|
|
1269
|
+
if (childShape?.restBagField && restBagEntries.length > 0) {
|
|
1270
|
+
lines.push(` ${childShape.restBagField}: map[string]any{${restBagEntries.join(", ")}},`);
|
|
1271
|
+
}
|
|
1175
1272
|
if (child.childrenText !== null) {
|
|
1176
1273
|
lines.push(` Children: ${JSON.stringify(child.childrenText)},`);
|
|
1177
1274
|
} else if (child.childrenHtml !== null) {
|
|
@@ -1242,7 +1339,7 @@ ${goFields.join(`
|
|
|
1242
1339
|
}
|
|
1243
1340
|
collectStaticChildInstances(node) {
|
|
1244
1341
|
const result = [];
|
|
1245
|
-
this.collectStaticChildInstancesRecursive(node, result, false);
|
|
1342
|
+
this.collectStaticChildInstancesRecursive(node, result, false, new Map);
|
|
1246
1343
|
return result;
|
|
1247
1344
|
}
|
|
1248
1345
|
extractTextChildren(children) {
|
|
@@ -1266,12 +1363,12 @@ ${goFields.join(`
|
|
|
1266
1363
|
return null;
|
|
1267
1364
|
return html;
|
|
1268
1365
|
}
|
|
1269
|
-
collectStaticChildInstancesRecursive(node, result, inLoop) {
|
|
1366
|
+
collectStaticChildInstancesRecursive(node, result, inLoop, providerCtx) {
|
|
1270
1367
|
if (node.type === "component") {
|
|
1271
1368
|
const comp = node;
|
|
1272
1369
|
if (comp.dynamicTag) {
|
|
1273
1370
|
for (const child of comp.children) {
|
|
1274
|
-
this.collectStaticChildInstancesRecursive(child, result, inLoop);
|
|
1371
|
+
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
|
|
1275
1372
|
}
|
|
1276
1373
|
return;
|
|
1277
1374
|
}
|
|
@@ -1283,48 +1380,65 @@ ${goFields.join(`
|
|
|
1283
1380
|
props: comp.props,
|
|
1284
1381
|
fieldName: `${comp.name}${suffix}`,
|
|
1285
1382
|
childrenText: this.extractTextChildren(comp.children),
|
|
1286
|
-
childrenHtml: this.extractHtmlChildren(comp.children)
|
|
1383
|
+
childrenHtml: this.extractHtmlChildren(comp.children),
|
|
1384
|
+
contextBindings: providerCtx.size > 0 ? providerCtx : undefined
|
|
1287
1385
|
});
|
|
1288
1386
|
}
|
|
1289
1387
|
if (comp.name === "Portal" && comp.children) {
|
|
1290
1388
|
for (const child of comp.children) {
|
|
1291
|
-
this.collectStaticChildInstancesRecursive(child, result, inLoop);
|
|
1389
|
+
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
|
|
1292
1390
|
}
|
|
1293
1391
|
}
|
|
1294
1392
|
} else if (node.type === "loop") {
|
|
1295
1393
|
const loop = node;
|
|
1296
1394
|
for (const child of loop.children) {
|
|
1297
|
-
this.collectStaticChildInstancesRecursive(child, result, true);
|
|
1395
|
+
this.collectStaticChildInstancesRecursive(child, result, true, providerCtx);
|
|
1298
1396
|
}
|
|
1299
1397
|
} else if (node.type === "element") {
|
|
1300
1398
|
const element = node;
|
|
1301
1399
|
for (const child of element.children) {
|
|
1302
|
-
this.collectStaticChildInstancesRecursive(child, result, inLoop);
|
|
1400
|
+
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
|
|
1303
1401
|
}
|
|
1304
1402
|
} else if (node.type === "fragment") {
|
|
1305
1403
|
const fragment = node;
|
|
1306
1404
|
for (const child of fragment.children) {
|
|
1307
|
-
this.collectStaticChildInstancesRecursive(child, result, inLoop);
|
|
1405
|
+
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
|
|
1308
1406
|
}
|
|
1309
1407
|
} else if (node.type === "conditional") {
|
|
1310
1408
|
const cond = node;
|
|
1311
|
-
this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop);
|
|
1409
|
+
this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop, providerCtx);
|
|
1312
1410
|
if (cond.whenFalse) {
|
|
1313
|
-
this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop);
|
|
1411
|
+
this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx);
|
|
1314
1412
|
}
|
|
1315
1413
|
} else if (node.type === "provider") {
|
|
1316
1414
|
const p = node;
|
|
1415
|
+
const childCtx = this.extendProviderContext(providerCtx, p);
|
|
1317
1416
|
for (const child of p.children) {
|
|
1318
|
-
this.collectStaticChildInstancesRecursive(child, result, inLoop);
|
|
1417
|
+
this.collectStaticChildInstancesRecursive(child, result, inLoop, childCtx);
|
|
1319
1418
|
}
|
|
1320
1419
|
} else if (node.type === "async") {
|
|
1321
1420
|
const a = node;
|
|
1322
|
-
this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop);
|
|
1421
|
+
this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop, providerCtx);
|
|
1323
1422
|
for (const child of a.children) {
|
|
1324
|
-
this.collectStaticChildInstancesRecursive(child, result, inLoop);
|
|
1423
|
+
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
|
|
1325
1424
|
}
|
|
1326
1425
|
}
|
|
1327
1426
|
}
|
|
1427
|
+
extendProviderContext(current, p) {
|
|
1428
|
+
const v = p.valueProp?.value;
|
|
1429
|
+
if (!v || v.kind !== "literal")
|
|
1430
|
+
return current;
|
|
1431
|
+
let goLit = null;
|
|
1432
|
+
if (typeof v.value === "string")
|
|
1433
|
+
goLit = `"${this.escapeGoString(v.value)}"`;
|
|
1434
|
+
else if (typeof v.value === "number" || typeof v.value === "boolean")
|
|
1435
|
+
goLit = String(v.value);
|
|
1436
|
+
if (goLit === null)
|
|
1437
|
+
return current;
|
|
1438
|
+
const next = new Map(current);
|
|
1439
|
+
next.set(p.contextName, goLit);
|
|
1440
|
+
return next;
|
|
1441
|
+
}
|
|
1328
1442
|
collectSpreadSlots(node) {
|
|
1329
1443
|
const result = [];
|
|
1330
1444
|
this.collectSpreadSlotsRecursive(node, result);
|
|
@@ -1475,6 +1589,17 @@ ${goFields.join(`
|
|
|
1475
1589
|
if (ir.metadata.restPropsName === trimmed) {
|
|
1476
1590
|
return `in.${this.capitalizeFieldName(trimmed)}`;
|
|
1477
1591
|
}
|
|
1592
|
+
const localConst = (ir.metadata.localConstants ?? []).find((c) => c.name === trimmed && !c.isModule);
|
|
1593
|
+
if (localConst?.value !== undefined) {
|
|
1594
|
+
const initTrimmed = localConst.value.trim();
|
|
1595
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(initTrimmed)) {
|
|
1596
|
+
const resolved = this.buildConditionalSpreadInitializer(initTrimmed, ir);
|
|
1597
|
+
if (resolved)
|
|
1598
|
+
return resolved;
|
|
1599
|
+
if (resolved === null)
|
|
1600
|
+
return null;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1478
1603
|
}
|
|
1479
1604
|
return null;
|
|
1480
1605
|
}
|
|
@@ -1564,12 +1689,27 @@ ${goFields.join(`
|
|
|
1564
1689
|
return null;
|
|
1565
1690
|
goVal = `in.${this.capitalizeFieldName(param.name)}`;
|
|
1566
1691
|
} else {
|
|
1567
|
-
|
|
1692
|
+
const indexed = this.recordIndexAccessToGoMap(val, ir);
|
|
1693
|
+
if (indexed === null)
|
|
1694
|
+
return null;
|
|
1695
|
+
goVal = indexed;
|
|
1568
1696
|
}
|
|
1569
1697
|
entries.push(`${JSON.stringify(key)}: ${goVal}`);
|
|
1570
1698
|
}
|
|
1571
1699
|
return `map[string]any{${entries.join(", ")}}`;
|
|
1572
1700
|
}
|
|
1701
|
+
recordIndexAccessToGoMap(val, ir) {
|
|
1702
|
+
const parsed = parseRecordIndexAccess(val, ir.metadata.localConstants ?? [], ir.metadata.propsParams);
|
|
1703
|
+
if (!parsed)
|
|
1704
|
+
return null;
|
|
1705
|
+
const entries = parsed.entries.map((e) => {
|
|
1706
|
+
const mapVal = e.value.kind === "number" ? e.value.text : JSON.stringify(e.value.text);
|
|
1707
|
+
return `${JSON.stringify(e.key)}: ${mapVal}`;
|
|
1708
|
+
});
|
|
1709
|
+
this.usesFmt = true;
|
|
1710
|
+
const field = `in.${this.capitalizeFieldName(parsed.indexPropName)}`;
|
|
1711
|
+
return `map[string]any{${entries.join(", ")}}[fmt.Sprint(${field})]`;
|
|
1712
|
+
}
|
|
1573
1713
|
convertInitialValue(value, typeInfo, propsParams) {
|
|
1574
1714
|
if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {
|
|
1575
1715
|
if (propsParams?.some((p) => p.name === value)) {
|
|
@@ -1810,7 +1950,142 @@ ${goFields.join(`
|
|
|
1810
1950
|
}
|
|
1811
1951
|
return null;
|
|
1812
1952
|
}
|
|
1813
|
-
|
|
1953
|
+
computeTemplateLiteralMemoInitialValue(computation, propsParams) {
|
|
1954
|
+
const sf = ts.createSourceFile("__memo.ts", `const __x = (${computation});`, ts.ScriptTarget.Latest, false);
|
|
1955
|
+
const stmt = sf.statements[0];
|
|
1956
|
+
if (!stmt || !ts.isVariableStatement(stmt))
|
|
1957
|
+
return null;
|
|
1958
|
+
let init = stmt.declarationList.declarations[0]?.initializer;
|
|
1959
|
+
while (init && ts.isParenthesizedExpression(init))
|
|
1960
|
+
init = init.expression;
|
|
1961
|
+
if (!init || !ts.isArrowFunction(init))
|
|
1962
|
+
return null;
|
|
1963
|
+
let body = init.body;
|
|
1964
|
+
while (ts.isParenthesizedExpression(body)) {
|
|
1965
|
+
body = body.expression;
|
|
1966
|
+
}
|
|
1967
|
+
const localKeyBindings = new Map;
|
|
1968
|
+
if (ts.isBlock(body)) {
|
|
1969
|
+
let returned = null;
|
|
1970
|
+
for (const s of body.statements) {
|
|
1971
|
+
if (ts.isVariableStatement(s)) {
|
|
1972
|
+
for (const d of s.declarationList.declarations) {
|
|
1973
|
+
if (!ts.isIdentifier(d.name) || !d.initializer)
|
|
1974
|
+
continue;
|
|
1975
|
+
const binding = this.parseLocalKeyBinding(d.initializer);
|
|
1976
|
+
if (binding)
|
|
1977
|
+
localKeyBindings.set(d.name.text, binding);
|
|
1978
|
+
}
|
|
1979
|
+
} else if (ts.isReturnStatement(s) && s.expression) {
|
|
1980
|
+
returned = s.expression;
|
|
1981
|
+
} else if (ts.isExpressionStatement(s) || ts.isEmptyStatement(s)) {} else {
|
|
1982
|
+
return null;
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
if (!returned)
|
|
1986
|
+
return null;
|
|
1987
|
+
body = returned;
|
|
1988
|
+
while (ts.isParenthesizedExpression(body)) {
|
|
1989
|
+
body = body.expression;
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
if (!ts.isTemplateExpression(body) && !ts.isNoSubstitutionTemplateLiteral(body)) {
|
|
1993
|
+
return null;
|
|
1994
|
+
}
|
|
1995
|
+
const propNames = new Set(propsParams.map((p) => p.name));
|
|
1996
|
+
const escGo = (s) => `"${this.escapeGoString(s)}"`;
|
|
1997
|
+
const segments = [];
|
|
1998
|
+
if (ts.isNoSubstitutionTemplateLiteral(body)) {
|
|
1999
|
+
return escGo(body.text);
|
|
2000
|
+
}
|
|
2001
|
+
if (body.head.text)
|
|
2002
|
+
segments.push(escGo(body.head.text));
|
|
2003
|
+
for (const span of body.templateSpans) {
|
|
2004
|
+
const resolved = this.resolveTemplateInterpolation(span.expression, propNames, localKeyBindings);
|
|
2005
|
+
if (resolved === null)
|
|
2006
|
+
return null;
|
|
2007
|
+
segments.push(resolved);
|
|
2008
|
+
if (span.literal.text)
|
|
2009
|
+
segments.push(escGo(span.literal.text));
|
|
2010
|
+
}
|
|
2011
|
+
if (segments.length === 0)
|
|
2012
|
+
return '""';
|
|
2013
|
+
return segments.join(" + ");
|
|
2014
|
+
}
|
|
2015
|
+
resolveTemplateInterpolation(expr, propNames, localKeyBindings = new Map) {
|
|
2016
|
+
let node = expr;
|
|
2017
|
+
while (ts.isParenthesizedExpression(node))
|
|
2018
|
+
node = node.expression;
|
|
2019
|
+
if (ts.isIdentifier(node)) {
|
|
2020
|
+
const inlined = this.resolveModuleStringConst(node.text);
|
|
2021
|
+
if (inlined !== null)
|
|
2022
|
+
return inlined;
|
|
2023
|
+
return null;
|
|
2024
|
+
}
|
|
2025
|
+
if (ts.isElementAccessExpression(node)) {
|
|
2026
|
+
const indexed = this.recordIndexInterpolationToGo(node, propNames, localKeyBindings);
|
|
2027
|
+
if (indexed !== null)
|
|
2028
|
+
return indexed;
|
|
2029
|
+
return null;
|
|
2030
|
+
}
|
|
2031
|
+
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
2032
|
+
const right = node.right;
|
|
2033
|
+
const isEmptyStr = (ts.isStringLiteral(right) || ts.isNoSubstitutionTemplateLiteral(right)) && right.text === "";
|
|
2034
|
+
const propName2 = this.propsAccessName(node.left);
|
|
2035
|
+
if (propName2 && propNames.has(propName2) && isEmptyStr) {
|
|
2036
|
+
return `in.${this.capitalizeFieldName(propName2)}`;
|
|
2037
|
+
}
|
|
2038
|
+
return null;
|
|
2039
|
+
}
|
|
2040
|
+
const propName = this.propsAccessName(node);
|
|
2041
|
+
if (propName && propNames.has(propName)) {
|
|
2042
|
+
return `in.${this.capitalizeFieldName(propName)}`;
|
|
2043
|
+
}
|
|
2044
|
+
return null;
|
|
2045
|
+
}
|
|
2046
|
+
parseLocalKeyBinding(init) {
|
|
2047
|
+
let node = init;
|
|
2048
|
+
while (ts.isParenthesizedExpression(node))
|
|
2049
|
+
node = node.expression;
|
|
2050
|
+
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
2051
|
+
const propName2 = this.propsAccessName(node.left);
|
|
2052
|
+
const right = node.right;
|
|
2053
|
+
if (propName2 && (ts.isStringLiteral(right) || ts.isNoSubstitutionTemplateLiteral(right))) {
|
|
2054
|
+
return { propName: propName2, defaultLiteral: right.text };
|
|
2055
|
+
}
|
|
2056
|
+
return null;
|
|
2057
|
+
}
|
|
2058
|
+
const propName = this.propsAccessName(node);
|
|
2059
|
+
if (propName)
|
|
2060
|
+
return { propName };
|
|
2061
|
+
return null;
|
|
2062
|
+
}
|
|
2063
|
+
recordIndexInterpolationToGo(node, propNames, localKeyBindings) {
|
|
2064
|
+
const parsed = parseRecordIndexAccess(node, this.localConstants ?? [], [...propNames].map((name) => ({ name })), (name) => localKeyBindings.get(name) ?? null);
|
|
2065
|
+
if (!parsed)
|
|
2066
|
+
return null;
|
|
2067
|
+
const goVal = (v) => v.kind === "number" ? v.text : JSON.stringify(v.text);
|
|
2068
|
+
const entries = parsed.entries.map((e) => `${JSON.stringify(e.key)}: ${goVal(e.value)}`);
|
|
2069
|
+
if (parsed.defaultKey !== undefined) {
|
|
2070
|
+
const def = parsed.entries.find((e) => e.key === parsed.defaultKey);
|
|
2071
|
+
if (def)
|
|
2072
|
+
entries.unshift(`"": ${goVal(def.value)}`);
|
|
2073
|
+
}
|
|
2074
|
+
const allString = parsed.entries.every((e) => e.value.kind === "string");
|
|
2075
|
+
const mapType = allString ? "map[string]string" : "map[string]any";
|
|
2076
|
+
this.usesFmt = true;
|
|
2077
|
+
return `${mapType}{${entries.join(", ")}}[fmt.Sprint(in.${this.capitalizeFieldName(parsed.indexPropName)})]`;
|
|
2078
|
+
}
|
|
2079
|
+
propsAccessName(node) {
|
|
2080
|
+
if (!ts.isPropertyAccessExpression(node))
|
|
2081
|
+
return null;
|
|
2082
|
+
if (!ts.isIdentifier(node.expression))
|
|
2083
|
+
return null;
|
|
2084
|
+
if (!this.propsObjectName || node.expression.text !== this.propsObjectName)
|
|
2085
|
+
return null;
|
|
2086
|
+
return node.name.text;
|
|
2087
|
+
}
|
|
2088
|
+
computeMemoInitialValue(memo, signals, propsParams, propFallbackVars = GoTemplateAdapter.EMPTY_PROP_FALLBACK_VARS, goType) {
|
|
1814
2089
|
const computation = memo.computation;
|
|
1815
2090
|
const propRef = (propName) => {
|
|
1816
2091
|
const hoisted = propFallbackVars.get(propName);
|
|
@@ -1818,6 +2093,9 @@ ${goFields.join(`
|
|
|
1818
2093
|
return hoisted.varName;
|
|
1819
2094
|
return `in.${this.capitalizeFieldName(propName)}`;
|
|
1820
2095
|
};
|
|
2096
|
+
const tmplMemo = this.computeTemplateLiteralMemoInitialValue(computation, propsParams);
|
|
2097
|
+
if (tmplMemo !== null)
|
|
2098
|
+
return tmplMemo;
|
|
1821
2099
|
const arithmeticMatch = computation.match(/\(\)\s*=>\s*(\w+)\(\)\s*([*+\-/])\s*(\d+)/);
|
|
1822
2100
|
if (arithmeticMatch) {
|
|
1823
2101
|
const [, depName, operator, operand] = arithmeticMatch;
|
|
@@ -1837,8 +2115,8 @@ ${goFields.join(`
|
|
|
1837
2115
|
return `${hoisted.varName} ${operator} ${operand}`;
|
|
1838
2116
|
const fieldName = this.capitalizeFieldName(propName);
|
|
1839
2117
|
if (param.type) {
|
|
1840
|
-
const
|
|
1841
|
-
if (
|
|
2118
|
+
const goType2 = this.typeInfoToGo(param.type, param.defaultValue);
|
|
2119
|
+
if (goType2 === "interface{}")
|
|
1842
2120
|
return `in.${fieldName}.(int) ${operator} ${operand}`;
|
|
1843
2121
|
}
|
|
1844
2122
|
return `in.${fieldName} ${operator} ${operand}`;
|
|
@@ -1867,8 +2145,8 @@ ${goFields.join(`
|
|
|
1867
2145
|
if (param) {
|
|
1868
2146
|
const fieldName = this.capitalizeFieldName(varName);
|
|
1869
2147
|
if (param.type) {
|
|
1870
|
-
const
|
|
1871
|
-
if (
|
|
2148
|
+
const goType2 = this.typeInfoToGo(param.type, param.defaultValue);
|
|
2149
|
+
if (goType2 === "interface{}")
|
|
1872
2150
|
return `in.${fieldName}.(int) ${operator} ${operand}`;
|
|
1873
2151
|
}
|
|
1874
2152
|
return `in.${fieldName} ${operator} ${operand}`;
|
|
@@ -1882,9 +2160,40 @@ ${goFields.join(`
|
|
|
1882
2160
|
return `in.${this.capitalizeFieldName(varName)}`;
|
|
1883
2161
|
}
|
|
1884
2162
|
}
|
|
2163
|
+
if (goType === "bool")
|
|
2164
|
+
return "false";
|
|
2165
|
+
if (goType === "string")
|
|
2166
|
+
return '""';
|
|
1885
2167
|
return "0";
|
|
1886
2168
|
}
|
|
2169
|
+
isTemplateLiteralMemo(computation) {
|
|
2170
|
+
const sf = ts.createSourceFile("__memo.ts", `const __x = (${computation});`, ts.ScriptTarget.Latest, false);
|
|
2171
|
+
const stmt = sf.statements[0];
|
|
2172
|
+
if (!stmt || !ts.isVariableStatement(stmt))
|
|
2173
|
+
return false;
|
|
2174
|
+
let init = stmt.declarationList.declarations[0]?.initializer;
|
|
2175
|
+
while (init && ts.isParenthesizedExpression(init))
|
|
2176
|
+
init = init.expression;
|
|
2177
|
+
if (!init || !ts.isArrowFunction(init))
|
|
2178
|
+
return false;
|
|
2179
|
+
let body = init.body;
|
|
2180
|
+
while (ts.isParenthesizedExpression(body)) {
|
|
2181
|
+
body = body.expression;
|
|
2182
|
+
}
|
|
2183
|
+
if (ts.isBlock(body)) {
|
|
2184
|
+
const ret = body.statements.find(ts.isReturnStatement);
|
|
2185
|
+
if (!ret || !ret.expression)
|
|
2186
|
+
return false;
|
|
2187
|
+
body = ret.expression;
|
|
2188
|
+
while (ts.isParenthesizedExpression(body)) {
|
|
2189
|
+
body = body.expression;
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
return ts.isTemplateExpression(body) || ts.isNoSubstitutionTemplateLiteral(body);
|
|
2193
|
+
}
|
|
1887
2194
|
inferMemoType(memo, signals, propsParamMap) {
|
|
2195
|
+
if (this.isTemplateLiteralMemo(memo.computation))
|
|
2196
|
+
return "string";
|
|
1888
2197
|
if (memo.computation.includes("*") || memo.computation.includes("/") || memo.computation.includes("+") || memo.computation.includes("-")) {
|
|
1889
2198
|
for (const dep of memo.deps) {
|
|
1890
2199
|
const signal = signals.find((s) => s.getter === dep);
|
|
@@ -1908,8 +2217,39 @@ ${goFields.join(`
|
|
|
1908
2217
|
}
|
|
1909
2218
|
}
|
|
1910
2219
|
}
|
|
2220
|
+
if (this.typeInfoToGo(memo.type) === "interface{}" && this.isBooleanMemo(memo, signals, propsParamMap)) {
|
|
2221
|
+
return "bool";
|
|
2222
|
+
}
|
|
1911
2223
|
return this.typeInfoToGo(memo.type);
|
|
1912
2224
|
}
|
|
2225
|
+
isBooleanMemo(memo, signals, propsParamMap) {
|
|
2226
|
+
const c = memo.computation;
|
|
2227
|
+
if (/(!==|===|!=(?!=)|==(?!=))/.test(c))
|
|
2228
|
+
return true;
|
|
2229
|
+
if (/=>\s*!/.test(c))
|
|
2230
|
+
return true;
|
|
2231
|
+
const isBoolGetter = (name) => {
|
|
2232
|
+
const sig = signals.find((s) => s.getter === name);
|
|
2233
|
+
if (sig) {
|
|
2234
|
+
if (this.typeInfoToGo(sig.type) === "bool")
|
|
2235
|
+
return true;
|
|
2236
|
+
if (/\?\?\s*(true|false)\b/.test(sig.initialValue))
|
|
2237
|
+
return true;
|
|
2238
|
+
const propName = this.extractPropNameFromInitialValue(sig.initialValue) ?? sig.initialValue;
|
|
2239
|
+
const prop2 = propsParamMap.get(propName);
|
|
2240
|
+
if (prop2 && this.typeInfoToGo(prop2.type, prop2.defaultValue) === "bool")
|
|
2241
|
+
return true;
|
|
2242
|
+
return false;
|
|
2243
|
+
}
|
|
2244
|
+
const prop = propsParamMap.get(name);
|
|
2245
|
+
return !!prop && this.typeInfoToGo(prop.type, prop.defaultValue) === "bool";
|
|
2246
|
+
};
|
|
2247
|
+
const ternary = c.match(/=>\s*\w+\(\)\s*\?\s*(\w+)\(\)\s*:\s*(\w+)\(\)/);
|
|
2248
|
+
if (ternary) {
|
|
2249
|
+
return isBoolGetter(ternary[1]) && isBoolGetter(ternary[2]);
|
|
2250
|
+
}
|
|
2251
|
+
return false;
|
|
2252
|
+
}
|
|
1913
2253
|
inferTypeFromValue(value) {
|
|
1914
2254
|
if (value === "true" || value === "false")
|
|
1915
2255
|
return "bool";
|
|
@@ -2257,8 +2597,42 @@ ${goFields.join(`
|
|
|
2257
2597
|
if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
|
|
2258
2598
|
return init.text;
|
|
2259
2599
|
}
|
|
2600
|
+
const joined = this.evalStringArrayJoin(init);
|
|
2601
|
+
if (joined !== null)
|
|
2602
|
+
return joined;
|
|
2260
2603
|
return null;
|
|
2261
2604
|
}
|
|
2605
|
+
evalStringArrayJoin(node) {
|
|
2606
|
+
if (!ts.isCallExpression(node))
|
|
2607
|
+
return null;
|
|
2608
|
+
const callee = node.expression;
|
|
2609
|
+
if (!ts.isPropertyAccessExpression(callee))
|
|
2610
|
+
return null;
|
|
2611
|
+
if (callee.name.text !== "join")
|
|
2612
|
+
return null;
|
|
2613
|
+
let recv = callee.expression;
|
|
2614
|
+
while (ts.isParenthesizedExpression(recv))
|
|
2615
|
+
recv = recv.expression;
|
|
2616
|
+
if (!ts.isArrayLiteralExpression(recv))
|
|
2617
|
+
return null;
|
|
2618
|
+
const parts = [];
|
|
2619
|
+
for (const el of recv.elements) {
|
|
2620
|
+
if (ts.isStringLiteral(el) || ts.isNoSubstitutionTemplateLiteral(el)) {
|
|
2621
|
+
parts.push(el.text);
|
|
2622
|
+
} else {
|
|
2623
|
+
return null;
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
let sep = ",";
|
|
2627
|
+
if (node.arguments.length >= 1) {
|
|
2628
|
+
const arg = node.arguments[0];
|
|
2629
|
+
if (ts.isStringLiteral(arg) || ts.isNoSubstitutionTemplateLiteral(arg))
|
|
2630
|
+
sep = arg.text;
|
|
2631
|
+
else
|
|
2632
|
+
return null;
|
|
2633
|
+
}
|
|
2634
|
+
return parts.join(sep);
|
|
2635
|
+
}
|
|
2262
2636
|
resolveModuleStringConst(name) {
|
|
2263
2637
|
if (this.loopParamStack.length > 0 && this.loopParamStack[this.loopParamStack.length - 1] === name) {
|
|
2264
2638
|
return null;
|
|
@@ -3419,8 +3793,9 @@ ${children}`;
|
|
|
3419
3793
|
return `${name}="${this.renderParsedExpr(parsed)}"`;
|
|
3420
3794
|
}
|
|
3421
3795
|
const bareId = value.expr.trim();
|
|
3422
|
-
|
|
3423
|
-
|
|
3796
|
+
const propName = this.propsObjectName && bareId.startsWith(`${this.propsObjectName}.`) ? bareId.slice(this.propsObjectName.length + 1) : bareId;
|
|
3797
|
+
if (/^[A-Za-z_$][\w$]*$/.test(propName) && this.nillablePropNames.has(propName)) {
|
|
3798
|
+
const field = `.${this.capitalizeFieldName(propName)}`;
|
|
3424
3799
|
return `{{if ne ${field} nil}}${name}="{{${this.convertExpressionToGo(value.expr)}}}"{{end}}`;
|
|
3425
3800
|
}
|
|
3426
3801
|
return `${name}="{{${this.convertExpressionToGo(value.expr)}}}"`;
|