@barefootjs/go-template 0.17.1 → 0.18.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/expr/url-builder.d.ts.map +1 -1
- package/dist/adapter/go-template-adapter.d.ts +134 -14
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +228 -82
- package/dist/adapter/lib/go-naming.d.ts +23 -0
- package/dist/adapter/lib/go-naming.d.ts.map +1 -1
- package/dist/adapter/type/type-codegen.d.ts +5 -1
- package/dist/adapter/type/type-codegen.d.ts.map +1 -1
- package/dist/adapter/value/parsed-literal-to-go.d.ts +13 -4
- package/dist/adapter/value/parsed-literal-to-go.d.ts.map +1 -1
- package/dist/build.js +228 -82
- package/dist/conformance-pins.d.ts +12 -0
- package/dist/conformance-pins.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +247 -82
- package/package.json +3 -3
- package/src/__tests__/go-template-adapter.test.ts +388 -145
- package/src/__tests__/lowering-plugin.test.ts +56 -0
- package/src/adapter/expr/url-builder.ts +14 -3
- package/src/adapter/go-template-adapter.ts +511 -109
- package/src/adapter/lib/go-naming.ts +29 -0
- package/src/adapter/type/type-codegen.ts +22 -3
- package/src/adapter/value/parsed-literal-to-go.ts +82 -9
- package/src/conformance-pins.ts +135 -0
- package/src/index.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type-codegen.d.ts","sourceRoot":"","sources":["../../../src/adapter/type/type-codegen.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"type-codegen.d.ts","sourceRoot":"","sources":["../../../src/adapter/type/type-codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAE/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEvD;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,aAAa,EAClB,QAAQ,EAAE,QAAQ,EAClB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAkDR;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAgB3E;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWxD"}
|
|
@@ -3,13 +3,22 @@
|
|
|
3
3
|
* for baking a signal's inline initial value into the SSR data context.
|
|
4
4
|
*
|
|
5
5
|
* Covers scalar literals, a unary-minus number, arrays of those, and object
|
|
6
|
-
* literals baked against a concrete local struct
|
|
6
|
+
* literals baked against a concrete local struct — including, per #2087, a
|
|
7
|
+
* struct property whose OWN value is a nested array or object literal
|
|
8
|
+
* (`{ id, cells: ['a', 'b'] }`, `{ id, user: { name: 'Ada' } }`): the struct's
|
|
9
|
+
* declared property TYPE (looked up from `ctx.state.currentTypeDefinitions`)
|
|
10
|
+
* threads through so a typed nested array bakes via the normal array branch
|
|
11
|
+
* below, and a nested INLINE object (one with no named Go struct —
|
|
12
|
+
* `typeInfoToGo`'s `'object'` case always falls back to
|
|
13
|
+
* `map[string]interface{}`) bakes as a capitalized-key Go map literal instead
|
|
14
|
+
* — the same convention `test-render.ts`'s harness-prop baking already uses
|
|
15
|
+
* for object elements, since `html/template`'s map field access is an exact
|
|
16
|
+
* case-sensitive `MapIndex`.
|
|
7
17
|
*
|
|
8
18
|
* Contract is **null-means-defer**: returns null for anything not reproduced
|
|
9
19
|
* exactly — an object whose target type isn't a known struct, a key the struct
|
|
10
|
-
* doesn't declare,
|
|
11
|
-
*
|
|
12
|
-
* then keeps `nil`.
|
|
20
|
+
* doesn't declare, an empty array, an identifier/call, or a numeric literal
|
|
21
|
+
* missing its `raw` token. The caller then keeps `nil`.
|
|
13
22
|
*/
|
|
14
23
|
import type { ParsedExpr, TypeInfo } from '@barefootjs/jsx';
|
|
15
24
|
import type { GoEmitContext } from '../emit-context.ts';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parsed-literal-to-go.d.ts","sourceRoot":"","sources":["../../../src/adapter/value/parsed-literal-to-go.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"parsed-literal-to-go.d.ts","sourceRoot":"","sources":["../../../src/adapter/value/parsed-literal-to-go.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAkB,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAE3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAmDvD,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,UAAU,EAChB,QAAQ,CAAC,EAAE,QAAQ,GAClB,MAAM,GAAG,IAAI,CAsFf"}
|
package/dist/build.js
CHANGED
|
@@ -378,7 +378,7 @@ import {
|
|
|
378
378
|
emitAttrValue,
|
|
379
379
|
augmentInheritedPropAccesses,
|
|
380
380
|
collectContextConsumers,
|
|
381
|
-
|
|
381
|
+
isLowerableLoopDestructure,
|
|
382
382
|
collectModuleStringConsts as collectModuleStringConstsShared,
|
|
383
383
|
prepareLoweringMatchers,
|
|
384
384
|
envSignalReaderFor,
|
|
@@ -463,6 +463,13 @@ function capitalizeFieldName(name) {
|
|
|
463
463
|
}
|
|
464
464
|
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
465
465
|
}
|
|
466
|
+
function goFieldNameForKey(key) {
|
|
467
|
+
const parts = key.split(/[^A-Za-z0-9_]+/).filter(Boolean);
|
|
468
|
+
if (parts.length === 0)
|
|
469
|
+
return "Field";
|
|
470
|
+
const name = parts.map(capitalizeFieldName).join("");
|
|
471
|
+
return /^[0-9]/.test(name) ? `Field${name}` : name;
|
|
472
|
+
}
|
|
466
473
|
function slotIdToFieldSuffix(slotId) {
|
|
467
474
|
const cleanId = slotId.startsWith("^") ? slotId.slice(1) : slotId;
|
|
468
475
|
const match = cleanId.match(/^s(\d+)$/);
|
|
@@ -1035,9 +1042,12 @@ function forEachValueChild(n, visit) {
|
|
|
1035
1042
|
// src/adapter/expr/url-builder.ts
|
|
1036
1043
|
import {
|
|
1037
1044
|
parseExpression,
|
|
1038
|
-
stringifyParsedExpr
|
|
1045
|
+
stringifyParsedExpr,
|
|
1046
|
+
isValidHelperId
|
|
1039
1047
|
} from "@barefootjs/jsx";
|
|
1040
|
-
|
|
1048
|
+
function goHelperName(helper) {
|
|
1049
|
+
return isValidHelperId(helper) ? `bf_${helper}` : null;
|
|
1050
|
+
}
|
|
1041
1051
|
var BOOL_COMPARISON_OPS = new Set([
|
|
1042
1052
|
"==",
|
|
1043
1053
|
"===",
|
|
@@ -1080,7 +1090,7 @@ function lowerRegisteredCall(ctx, jsExpr, preParsed) {
|
|
|
1080
1090
|
return null;
|
|
1081
1091
|
}
|
|
1082
1092
|
function renderLoweringNode(ctx, node) {
|
|
1083
|
-
const helper =
|
|
1093
|
+
const helper = goHelperName(node.helper);
|
|
1084
1094
|
if (!helper)
|
|
1085
1095
|
return null;
|
|
1086
1096
|
const lowerExpr = (n) => ctx.convertExpressionToGo(stringifyParsedExpr(n), undefined, n);
|
|
@@ -1120,7 +1130,7 @@ function typeInfoToGo(ctx, typeInfo, defaultValue) {
|
|
|
1120
1130
|
case "object":
|
|
1121
1131
|
return "map[string]interface{}";
|
|
1122
1132
|
case "interface":
|
|
1123
|
-
if (typeInfo.raw && ctx.state.
|
|
1133
|
+
if (typeInfo.raw && (ctx.state.localStructFields.has(typeInfo.raw) || ctx.state.localTypeAliases.has(typeInfo.raw))) {
|
|
1124
1134
|
return typeInfo.raw;
|
|
1125
1135
|
}
|
|
1126
1136
|
if (typeInfo.raw) {
|
|
@@ -1153,7 +1163,7 @@ function tsTypeStringToGo(ctx, tsType) {
|
|
|
1153
1163
|
const arrayMatch = t.match(/^Array<(.+)>$/);
|
|
1154
1164
|
if (arrayMatch)
|
|
1155
1165
|
return `[]${tsTypeStringToGo(ctx, arrayMatch[1])}`;
|
|
1156
|
-
if (ctx.state.
|
|
1166
|
+
if (ctx.state.localStructFields.has(t) || ctx.state.localTypeAliases.has(t))
|
|
1157
1167
|
return t;
|
|
1158
1168
|
return "interface{}";
|
|
1159
1169
|
}
|
|
@@ -1175,6 +1185,24 @@ function inferTypeFromValue(value) {
|
|
|
1175
1185
|
}
|
|
1176
1186
|
|
|
1177
1187
|
// src/adapter/value/parsed-literal-to-go.ts
|
|
1188
|
+
function structPropertyType(ctx, structGoType, key) {
|
|
1189
|
+
const td = ctx.state.currentTypeDefinitions.find((t) => t.name === structGoType);
|
|
1190
|
+
return td?.properties?.find((p) => p.name === key)?.type;
|
|
1191
|
+
}
|
|
1192
|
+
function bakeInlineObjectAsGoMap(ctx, expr) {
|
|
1193
|
+
if (expr.kind !== "object-literal")
|
|
1194
|
+
return null;
|
|
1195
|
+
const entries = [];
|
|
1196
|
+
for (const prop of expr.properties) {
|
|
1197
|
+
if (prop.shorthand)
|
|
1198
|
+
return null;
|
|
1199
|
+
const go = prop.value.kind === "object-literal" ? bakeInlineObjectAsGoMap(ctx, prop.value) : parsedLiteralToGo(ctx, prop.value);
|
|
1200
|
+
if (go === null)
|
|
1201
|
+
return null;
|
|
1202
|
+
entries.push(`${JSON.stringify(goFieldNameForKey(prop.key))}: ${go}`);
|
|
1203
|
+
}
|
|
1204
|
+
return `map[string]interface{}{${entries.join(", ")}}`;
|
|
1205
|
+
}
|
|
1178
1206
|
function parsedLiteralToGo(ctx, expr, typeInfo) {
|
|
1179
1207
|
if (expr.kind === "unary" && expr.op === "-" && expr.argument.kind === "literal" && expr.argument.literalType === "number") {
|
|
1180
1208
|
return expr.argument.raw !== undefined ? `-${expr.argument.raw}` : null;
|
|
@@ -1215,9 +1243,16 @@ function parsedLiteralToGo(ctx, expr, typeInfo) {
|
|
|
1215
1243
|
const goField = structFields.get(prop.key);
|
|
1216
1244
|
if (!goField)
|
|
1217
1245
|
return null;
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1246
|
+
const propType = structPropertyType(ctx, goType, prop.key);
|
|
1247
|
+
let go;
|
|
1248
|
+
if (prop.value.kind === "array-literal") {
|
|
1249
|
+
go = parsedLiteralToGo(ctx, prop.value, propType);
|
|
1250
|
+
} else if (prop.value.kind === "object-literal") {
|
|
1251
|
+
const nestedGoType = propType ? typeInfoToGo(ctx, propType) : undefined;
|
|
1252
|
+
go = nestedGoType && ctx.state.localStructFields.has(nestedGoType) ? parsedLiteralToGo(ctx, prop.value, propType) : bakeInlineObjectAsGoMap(ctx, prop.value);
|
|
1253
|
+
} else {
|
|
1254
|
+
go = parsedLiteralToGo(ctx, prop.value);
|
|
1255
|
+
}
|
|
1221
1256
|
if (go === null)
|
|
1222
1257
|
return null;
|
|
1223
1258
|
entries.push(`${goField}: ${go}`);
|
|
@@ -2455,8 +2490,10 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2455
2490
|
inLoop = false;
|
|
2456
2491
|
loopParamStack = [];
|
|
2457
2492
|
loopScalarItemStack = [];
|
|
2493
|
+
loopWrapperStack = [];
|
|
2458
2494
|
loopVarRefCount = new Map;
|
|
2459
2495
|
loopBindingStack = [];
|
|
2496
|
+
loopRestExcludeStack = [];
|
|
2460
2497
|
childComponentShapes = new Map;
|
|
2461
2498
|
childContextConsumers = new Map;
|
|
2462
2499
|
constructor(options = {}) {
|
|
@@ -2649,11 +2686,22 @@ ${scriptRegistrations}${templateBody}
|
|
|
2649
2686
|
contextFieldName(c) {
|
|
2650
2687
|
return capitalizeFieldName(c.localName);
|
|
2651
2688
|
}
|
|
2689
|
+
isMapRootedContextChain(node) {
|
|
2690
|
+
if (node.kind === "identifier") {
|
|
2691
|
+
return this.state.contextConsumers.some((c) => c.localName === node.name && c.defaultKind === "object");
|
|
2692
|
+
}
|
|
2693
|
+
if (node.kind === "member" && !node.computed) {
|
|
2694
|
+
return this.isMapRootedContextChain(node.object);
|
|
2695
|
+
}
|
|
2696
|
+
return false;
|
|
2697
|
+
}
|
|
2652
2698
|
contextConsumerGoType(c) {
|
|
2653
2699
|
if (typeof c.defaultValue === "number")
|
|
2654
2700
|
return "int";
|
|
2655
2701
|
if (typeof c.defaultValue === "boolean")
|
|
2656
2702
|
return "bool";
|
|
2703
|
+
if (c.defaultKind === "object")
|
|
2704
|
+
return "map[string]interface{}";
|
|
2657
2705
|
return "string";
|
|
2658
2706
|
}
|
|
2659
2707
|
contextConsumerGoDefault(c) {
|
|
@@ -2663,6 +2711,8 @@ ${scriptRegistrations}${templateBody}
|
|
|
2663
2711
|
return String(c.defaultValue);
|
|
2664
2712
|
if (typeof c.defaultValue === "string")
|
|
2665
2713
|
return `"${escapeGoString(c.defaultValue)}"`;
|
|
2714
|
+
if (c.defaultKind === "object")
|
|
2715
|
+
return "map[string]interface{}{}";
|
|
2666
2716
|
return '""';
|
|
2667
2717
|
}
|
|
2668
2718
|
nonCollidingContextConsumers(taken) {
|
|
@@ -2709,12 +2759,15 @@ ${goFields.join(`
|
|
|
2709
2759
|
}
|
|
2710
2760
|
structFieldsFor(td) {
|
|
2711
2761
|
const fields = [];
|
|
2762
|
+
const seenGoNames = new Set;
|
|
2712
2763
|
for (const prop of td.properties ?? []) {
|
|
2713
|
-
|
|
2764
|
+
const goName = goFieldNameForKey(prop.name);
|
|
2765
|
+
if (seenGoNames.has(goName))
|
|
2714
2766
|
continue;
|
|
2767
|
+
seenGoNames.add(goName);
|
|
2715
2768
|
fields.push({
|
|
2716
2769
|
tsName: prop.name,
|
|
2717
|
-
goName
|
|
2770
|
+
goName,
|
|
2718
2771
|
goType: typeInfoToGo(this.emitCtx, prop.type)
|
|
2719
2772
|
});
|
|
2720
2773
|
}
|
|
@@ -2942,10 +2995,10 @@ ${goFields.join(`
|
|
|
2942
2995
|
}
|
|
2943
2996
|
return "interface{}";
|
|
2944
2997
|
}
|
|
2945
|
-
collectBodyChildInstances(bodyChildren) {
|
|
2998
|
+
collectBodyChildInstances(bodyChildren, propsParams = []) {
|
|
2946
2999
|
const result = [];
|
|
2947
3000
|
for (const child of bodyChildren) {
|
|
2948
|
-
this.collectStaticChildInstancesRecursive(child, result, false, new Map);
|
|
3001
|
+
this.collectStaticChildInstancesRecursive(child, result, false, new Map, propsParams);
|
|
2949
3002
|
}
|
|
2950
3003
|
return result;
|
|
2951
3004
|
}
|
|
@@ -3115,7 +3168,7 @@ ${goFields.join(`
|
|
|
3115
3168
|
for (const c of this.nonCollidingContextConsumers(takenInit)) {
|
|
3116
3169
|
const field = this.contextFieldName(c);
|
|
3117
3170
|
const def = this.contextConsumerGoDefault(c);
|
|
3118
|
-
const defaulted = c.defaultValue === null || def === '""' || def === "0" || def === "false" ? `in.${field}` : applyGoFallback(`in.${field}`, def);
|
|
3171
|
+
const defaulted = c.defaultValue === null || def === '""' || def === "0" || def === "false" || def === "map[string]interface{}{}" ? `in.${field}` : applyGoFallback(`in.${field}`, def);
|
|
3119
3172
|
lines.push(` ${field}: ${defaulted},`);
|
|
3120
3173
|
}
|
|
3121
3174
|
this.emitStaticChildInstances(lines, ir);
|
|
@@ -3124,7 +3177,7 @@ ${goFields.join(`
|
|
|
3124
3177
|
lines.push("}");
|
|
3125
3178
|
}
|
|
3126
3179
|
emitStaticChildInstances(lines, ir) {
|
|
3127
|
-
const staticChildren = this.collectStaticChildInstances(ir.root);
|
|
3180
|
+
const staticChildren = this.collectStaticChildInstances(ir.root, ir.metadata.propsParams);
|
|
3128
3181
|
for (const child of staticChildren) {
|
|
3129
3182
|
lines.push(` ${child.fieldName}: New${child.name}Props(${child.name}Input{`);
|
|
3130
3183
|
lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
|
|
@@ -3256,7 +3309,7 @@ ${goFields.join(`
|
|
|
3256
3309
|
const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
|
|
3257
3310
|
const wrapperType = this.loopBodyWrapperName(componentName, nested);
|
|
3258
3311
|
const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
|
|
3259
|
-
const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
|
|
3312
|
+
const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren, ir.metadata.propsParams);
|
|
3260
3313
|
for (const child of bodyChildInstances) {
|
|
3261
3314
|
const childVar = `child_${child.fieldName}`;
|
|
3262
3315
|
lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
|
|
@@ -3333,7 +3386,7 @@ ${goFields.join(`
|
|
|
3333
3386
|
const wrapperType = this.loopBodyWrapperName(componentName, nested);
|
|
3334
3387
|
const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
|
|
3335
3388
|
const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
|
|
3336
|
-
const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
|
|
3389
|
+
const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren, ir.metadata.propsParams);
|
|
3337
3390
|
for (const child of bodyChildInstances) {
|
|
3338
3391
|
const childVar = `child_${child.fieldName}`;
|
|
3339
3392
|
lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
|
|
@@ -3573,7 +3626,7 @@ ${goFields.join(`
|
|
|
3573
3626
|
lines.push(` ${nested.name}s []${elemType} \`json:"${jsonTag}"\``);
|
|
3574
3627
|
}
|
|
3575
3628
|
}
|
|
3576
|
-
const staticChildren = this.collectStaticChildInstances(ir.root);
|
|
3629
|
+
const staticChildren = this.collectStaticChildInstances(ir.root, ir.metadata.propsParams);
|
|
3577
3630
|
for (const child of staticChildren) {
|
|
3578
3631
|
lines.push(` ${child.fieldName} ${child.name}Props \`json:"-"\``);
|
|
3579
3632
|
}
|
|
@@ -3585,9 +3638,9 @@ ${goFields.join(`
|
|
|
3585
3638
|
toJsonTag(name) {
|
|
3586
3639
|
return name.charAt(0).toLowerCase() + name.slice(1);
|
|
3587
3640
|
}
|
|
3588
|
-
collectStaticChildInstances(node) {
|
|
3641
|
+
collectStaticChildInstances(node, propsParams = []) {
|
|
3589
3642
|
const result = [];
|
|
3590
|
-
this.collectStaticChildInstancesRecursive(node, result, false, new Map);
|
|
3643
|
+
this.collectStaticChildInstancesRecursive(node, result, false, new Map, propsParams);
|
|
3591
3644
|
return result;
|
|
3592
3645
|
}
|
|
3593
3646
|
extractTextChildren(children) {
|
|
@@ -3632,12 +3685,12 @@ ${goFields.join(`
|
|
|
3632
3685
|
return null;
|
|
3633
3686
|
return withSentinel.split(GoTemplateAdapter.SCOPE_SENTINEL).map((seg) => JSON.stringify(seg)).join(" + scopeID + ");
|
|
3634
3687
|
}
|
|
3635
|
-
collectStaticChildInstancesRecursive(node, result, inLoop, providerCtx) {
|
|
3688
|
+
collectStaticChildInstancesRecursive(node, result, inLoop, providerCtx, propsParams = []) {
|
|
3636
3689
|
if (node.type === "component") {
|
|
3637
3690
|
const comp = node;
|
|
3638
3691
|
if (comp.dynamicTag) {
|
|
3639
3692
|
for (const child of comp.children) {
|
|
3640
|
-
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
|
|
3693
|
+
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
|
|
3641
3694
|
}
|
|
3642
3695
|
return;
|
|
3643
3696
|
}
|
|
@@ -3655,69 +3708,111 @@ ${goFields.join(`
|
|
|
3655
3708
|
contextBindings: providerCtx.size > 0 ? providerCtx : undefined
|
|
3656
3709
|
});
|
|
3657
3710
|
for (const child of effectiveChildren) {
|
|
3658
|
-
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
|
|
3711
|
+
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
|
|
3659
3712
|
}
|
|
3660
3713
|
}
|
|
3661
3714
|
if (comp.name === "Portal" && comp.children) {
|
|
3662
3715
|
for (const child of comp.children) {
|
|
3663
|
-
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
|
|
3716
|
+
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
|
|
3664
3717
|
}
|
|
3665
3718
|
}
|
|
3666
3719
|
} else if (node.type === "loop") {
|
|
3667
3720
|
const loop = node;
|
|
3668
3721
|
for (const child of loop.children) {
|
|
3669
|
-
this.collectStaticChildInstancesRecursive(child, result,
|
|
3722
|
+
this.collectStaticChildInstancesRecursive(child, result, inLoop || !!loop.childComponent, providerCtx, propsParams);
|
|
3670
3723
|
}
|
|
3671
3724
|
} else if (node.type === "element") {
|
|
3672
3725
|
const element = node;
|
|
3673
3726
|
for (const child of element.children) {
|
|
3674
|
-
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
|
|
3727
|
+
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
|
|
3675
3728
|
}
|
|
3676
3729
|
} else if (node.type === "fragment") {
|
|
3677
3730
|
const fragment = node;
|
|
3678
3731
|
for (const child of fragment.children) {
|
|
3679
|
-
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
|
|
3732
|
+
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
|
|
3680
3733
|
}
|
|
3681
3734
|
} else if (node.type === "conditional") {
|
|
3682
3735
|
const cond = node;
|
|
3683
|
-
this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop, providerCtx);
|
|
3736
|
+
this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop, providerCtx, propsParams);
|
|
3684
3737
|
if (cond.whenFalse) {
|
|
3685
|
-
this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx);
|
|
3738
|
+
this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx, propsParams);
|
|
3686
3739
|
}
|
|
3687
3740
|
} else if (node.type === "if-statement") {
|
|
3688
3741
|
const stmt = node;
|
|
3689
|
-
this.collectStaticChildInstancesRecursive(stmt.consequent, result, inLoop, providerCtx);
|
|
3742
|
+
this.collectStaticChildInstancesRecursive(stmt.consequent, result, inLoop, providerCtx, propsParams);
|
|
3690
3743
|
if (stmt.alternate) {
|
|
3691
|
-
this.collectStaticChildInstancesRecursive(stmt.alternate, result, inLoop, providerCtx);
|
|
3744
|
+
this.collectStaticChildInstancesRecursive(stmt.alternate, result, inLoop, providerCtx, propsParams);
|
|
3692
3745
|
}
|
|
3693
3746
|
} else if (node.type === "provider") {
|
|
3694
3747
|
const p = node;
|
|
3695
|
-
const childCtx = this.extendProviderContext(providerCtx, p);
|
|
3748
|
+
const childCtx = this.extendProviderContext(providerCtx, p, propsParams);
|
|
3696
3749
|
for (const child of p.children) {
|
|
3697
|
-
this.collectStaticChildInstancesRecursive(child, result, inLoop, childCtx);
|
|
3750
|
+
this.collectStaticChildInstancesRecursive(child, result, inLoop, childCtx, propsParams);
|
|
3698
3751
|
}
|
|
3699
3752
|
} else if (node.type === "async") {
|
|
3700
3753
|
const a = node;
|
|
3701
|
-
this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop, providerCtx);
|
|
3754
|
+
this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop, providerCtx, propsParams);
|
|
3702
3755
|
for (const child of a.children) {
|
|
3703
|
-
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
|
|
3756
|
+
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
|
|
3704
3757
|
}
|
|
3705
3758
|
}
|
|
3706
3759
|
}
|
|
3707
|
-
extendProviderContext(current, p) {
|
|
3760
|
+
extendProviderContext(current, p, propsParams) {
|
|
3708
3761
|
const v = p.valueProp?.value;
|
|
3709
|
-
if (!v
|
|
3710
|
-
return current;
|
|
3711
|
-
let goLit = null;
|
|
3712
|
-
if (typeof v.value === "string")
|
|
3713
|
-
goLit = `"${escapeGoString(v.value)}"`;
|
|
3714
|
-
else if (typeof v.value === "number" || typeof v.value === "boolean")
|
|
3715
|
-
goLit = String(v.value);
|
|
3716
|
-
if (goLit === null)
|
|
3762
|
+
if (!v)
|
|
3717
3763
|
return current;
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3764
|
+
if (v.kind === "literal") {
|
|
3765
|
+
let goLit = null;
|
|
3766
|
+
if (typeof v.value === "string")
|
|
3767
|
+
goLit = `"${escapeGoString(v.value)}"`;
|
|
3768
|
+
else if (typeof v.value === "number" || typeof v.value === "boolean")
|
|
3769
|
+
goLit = String(v.value);
|
|
3770
|
+
if (goLit === null)
|
|
3771
|
+
return current;
|
|
3772
|
+
const next = new Map(current);
|
|
3773
|
+
next.set(p.contextName, goLit);
|
|
3774
|
+
return next;
|
|
3775
|
+
}
|
|
3776
|
+
if (v.kind === "expression" && v.parsed) {
|
|
3777
|
+
const goMap = this.providerObjectValueToGoMap(v.parsed, propsParams);
|
|
3778
|
+
if (goMap !== null) {
|
|
3779
|
+
const next = new Map(current);
|
|
3780
|
+
next.set(p.contextName, goMap);
|
|
3781
|
+
return next;
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
return current;
|
|
3785
|
+
}
|
|
3786
|
+
providerObjectValueToGoMap(parsed, propsParams) {
|
|
3787
|
+
if (parsed.kind !== "object-literal")
|
|
3788
|
+
return null;
|
|
3789
|
+
const entries = [];
|
|
3790
|
+
for (const prop of parsed.properties) {
|
|
3791
|
+
if (prop.shorthand)
|
|
3792
|
+
return null;
|
|
3793
|
+
const goVal = this.lowerProviderMapMemberValue(prop.value, propsParams);
|
|
3794
|
+
if (goVal === null)
|
|
3795
|
+
return null;
|
|
3796
|
+
entries.push(`${JSON.stringify(prop.key)}: ${goVal}`);
|
|
3797
|
+
}
|
|
3798
|
+
if (entries.length === 0)
|
|
3799
|
+
return null;
|
|
3800
|
+
return `map[string]interface{}{${entries.join(", ")}}`;
|
|
3801
|
+
}
|
|
3802
|
+
lowerProviderMapMemberValue(node, propsParams) {
|
|
3803
|
+
if (node.kind === "object-literal")
|
|
3804
|
+
return objectLiteralToGoMap(this.emitCtx, node);
|
|
3805
|
+
const literal = parsedLiteralToGo(this.emitCtx, node);
|
|
3806
|
+
if (literal !== null)
|
|
3807
|
+
return literal;
|
|
3808
|
+
if (node.kind === "logical" && node.op === "??" && node.right.kind === "object-literal" && node.right.properties.length === 0 && node.left.kind === "member" && !node.left.computed && node.left.object.kind === "identifier" && node.left.object.name === this.state.propsObjectName) {
|
|
3809
|
+
const propName = node.left.property;
|
|
3810
|
+
if (propsParams.some((param) => param.name === propName)) {
|
|
3811
|
+
const fieldRef = `in.${capitalizeFieldName(propName)}`;
|
|
3812
|
+
return `func() map[string]interface{} { ` + `if m := bf.AsMap(${fieldRef}); m != nil { return m }; ` + `return map[string]interface{}{} }()`;
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
return null;
|
|
3721
3816
|
}
|
|
3722
3817
|
templatePartsToGoCode(parts, propsParams) {
|
|
3723
3818
|
const segments = [];
|
|
@@ -4187,15 +4282,19 @@ ${goFields.join(`
|
|
|
4187
4282
|
if (objHO && (objHO.method === "find" || objHO.method === "findLast")) {
|
|
4188
4283
|
const findResult = this.renderHigherOrderExpr(objHO, emit);
|
|
4189
4284
|
if (findResult) {
|
|
4190
|
-
return `{{with ${findResult}}}{{.${
|
|
4285
|
+
return `{{with ${findResult}}}{{.${goFieldNameForKey(property)}}}{{end}}`;
|
|
4191
4286
|
}
|
|
4192
|
-
const templateBlock = this.renderFindTemplateBlock(objHO, emit,
|
|
4287
|
+
const templateBlock = this.renderFindTemplateBlock(objHO, emit, goFieldNameForKey(property));
|
|
4193
4288
|
if (templateBlock)
|
|
4194
4289
|
return templateBlock;
|
|
4195
4290
|
}
|
|
4196
4291
|
if (object.kind === "identifier" && this.state.propsObjectName && object.name === this.state.propsObjectName) {
|
|
4197
4292
|
return this.rootFieldRef(property);
|
|
4198
4293
|
}
|
|
4294
|
+
if (this.isMapRootedContextChain(object)) {
|
|
4295
|
+
const objGo = emit(object);
|
|
4296
|
+
return `bf_get ${wrapIfMultiToken(objGo)} ${JSON.stringify(property)}`;
|
|
4297
|
+
}
|
|
4199
4298
|
if (object.kind === "identifier") {
|
|
4200
4299
|
const staticValue = this.resolveStaticRecordLiteralIndex(`${object.name}.${property}`);
|
|
4201
4300
|
if (staticValue !== null)
|
|
@@ -4203,12 +4302,12 @@ ${goFields.join(`
|
|
|
4203
4302
|
}
|
|
4204
4303
|
const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
|
|
4205
4304
|
if (object.kind === "identifier" && currentLoopParam && object.name === currentLoopParam) {
|
|
4206
|
-
return `.${
|
|
4305
|
+
return `.${goFieldNameForKey(property)}`;
|
|
4207
4306
|
}
|
|
4208
4307
|
const obj = emit(object);
|
|
4209
4308
|
if (property === "length")
|
|
4210
4309
|
return `len ${obj}`;
|
|
4211
|
-
return `${obj}.${
|
|
4310
|
+
return `${obj}.${goFieldNameForKey(property)}`;
|
|
4212
4311
|
}
|
|
4213
4312
|
indexAccess(object, index, emit) {
|
|
4214
4313
|
return `index ${wrapIfMultiToken(emit(object))} ${wrapIfMultiToken(emit(index))}`;
|
|
@@ -4300,7 +4399,16 @@ ${goFields.join(`
|
|
|
4300
4399
|
return `bf_arr ${parts.join(" ")}`;
|
|
4301
4400
|
}
|
|
4302
4401
|
objectLiteral(_properties, raw, _emit) {
|
|
4303
|
-
|
|
4402
|
+
this.state.errors.push({
|
|
4403
|
+
code: "BF101",
|
|
4404
|
+
severity: "error",
|
|
4405
|
+
message: `Expression not supported: ${raw}`,
|
|
4406
|
+
loc: this.makeLoc(),
|
|
4407
|
+
suggestion: {
|
|
4408
|
+
message: `Go templates have no object/map literal syntax, so the \`?? {}\` fallback can't render server-side. ${GO_REMEDIATION_OPTIONS}`
|
|
4409
|
+
}
|
|
4410
|
+
});
|
|
4411
|
+
return `""`;
|
|
4304
4412
|
}
|
|
4305
4413
|
static PREDICATE_METHODS = new Set([
|
|
4306
4414
|
"filter",
|
|
@@ -4513,6 +4621,9 @@ ${goFields.join(`
|
|
|
4513
4621
|
}
|
|
4514
4622
|
}
|
|
4515
4623
|
flatMethod(object, depth, emit) {
|
|
4624
|
+
if (typeof depth === "object") {
|
|
4625
|
+
return `bf_flat_dynamic ${wrapIfMultiToken(emit(object))} ${wrapIfMultiToken(emit(depth.expr))}`;
|
|
4626
|
+
}
|
|
4516
4627
|
const d = depth === "infinity" ? -1 : depth;
|
|
4517
4628
|
return `bf_flat ${wrapIfMultiToken(emit(object))} ${d}`;
|
|
4518
4629
|
}
|
|
@@ -5211,23 +5322,44 @@ ${goFields.join(`
|
|
|
5211
5322
|
return plain(expr.raw);
|
|
5212
5323
|
}
|
|
5213
5324
|
}
|
|
5325
|
+
buildSegmentAccessor(base, segments) {
|
|
5326
|
+
let acc = base;
|
|
5327
|
+
for (const seg of segments) {
|
|
5328
|
+
acc = seg.kind === "field" ? `${acc}.${goFieldNameForKey(seg.key)}` : `(index ${acc} ${seg.index})`;
|
|
5329
|
+
}
|
|
5330
|
+
return acc;
|
|
5331
|
+
}
|
|
5214
5332
|
buildDestructureBindingMap(loop, rangeVar) {
|
|
5215
|
-
const
|
|
5333
|
+
const bindings = new Map;
|
|
5334
|
+
const restExcludes = new Map;
|
|
5335
|
+
const base = `$${rangeVar}`;
|
|
5216
5336
|
for (const b of loop.paramBindings ?? []) {
|
|
5217
|
-
|
|
5218
|
-
|
|
5337
|
+
const parent = this.buildSegmentAccessor(base, b.segments ?? []);
|
|
5338
|
+
if (!b.rest) {
|
|
5339
|
+
bindings.set(b.name, parent);
|
|
5340
|
+
} else if (b.rest.kind === "array") {
|
|
5341
|
+
bindings.set(b.name, `(bf_slice ${parent} ${b.rest.from})`);
|
|
5219
5342
|
} else {
|
|
5220
|
-
|
|
5343
|
+
bindings.set(b.name, parent);
|
|
5344
|
+
restExcludes.set(b.name, { parent, excludeKeys: b.rest.exclude.map((k) => k.key) });
|
|
5221
5345
|
}
|
|
5222
5346
|
}
|
|
5223
|
-
return
|
|
5347
|
+
return { bindings, restExcludes };
|
|
5348
|
+
}
|
|
5349
|
+
lookupRestExclude(name) {
|
|
5350
|
+
for (let i = this.loopRestExcludeStack.length - 1;i >= 0; i--) {
|
|
5351
|
+
const info = this.loopRestExcludeStack[i].get(name);
|
|
5352
|
+
if (info)
|
|
5353
|
+
return info;
|
|
5354
|
+
}
|
|
5355
|
+
return;
|
|
5224
5356
|
}
|
|
5225
5357
|
renderLoop(loop) {
|
|
5226
5358
|
if (loop.clientOnly) {
|
|
5227
5359
|
return `{{bfComment "loop:${loop.markerId}"}}{{bfComment "/loop:${loop.markerId}"}}`;
|
|
5228
5360
|
}
|
|
5229
5361
|
const destructure = !!(loop.paramBindings && loop.paramBindings.length > 0);
|
|
5230
|
-
const supportableDestructure = destructure &&
|
|
5362
|
+
const supportableDestructure = destructure && isLowerableLoopDestructure(loop);
|
|
5231
5363
|
if (destructure && !supportableDestructure) {
|
|
5232
5364
|
this.state.errors.push({
|
|
5233
5365
|
code: "BF104",
|
|
@@ -5242,6 +5374,21 @@ ${goFields.join(`
|
|
|
5242
5374
|
}
|
|
5243
5375
|
});
|
|
5244
5376
|
}
|
|
5377
|
+
const arrayName = loop.array.trim();
|
|
5378
|
+
if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
|
|
5379
|
+
const arrayConst = this.state.localConstants.find((c) => c.name === arrayName);
|
|
5380
|
+
if (arrayConst && !arrayConst.isModule && arrayConst.parsed && !this.isStringExpr(arrayConst.parsed, new Set)) {
|
|
5381
|
+
this.state.errors.push({
|
|
5382
|
+
code: "BF101",
|
|
5383
|
+
severity: "error",
|
|
5384
|
+
message: `Loop array \`${arrayName}\` is a local computed value (\`${arrayConst.value}\`) that the Go template adapter cannot bind as a template variable — only a string-derived local resolves to a generated struct field.`,
|
|
5385
|
+
loc: loop.loc ?? this.makeLoc(),
|
|
5386
|
+
suggestion: {
|
|
5387
|
+
message: "Pre-compute the array server-side and pass it as a prop, or mark the loop position as @client-only so it runs in JS on the client."
|
|
5388
|
+
}
|
|
5389
|
+
});
|
|
5390
|
+
}
|
|
5391
|
+
}
|
|
5245
5392
|
let goArray = this.convertExpressionToGo(loop.array);
|
|
5246
5393
|
const param = loop.param;
|
|
5247
5394
|
let index = loop.index || "_";
|
|
@@ -5251,15 +5398,16 @@ ${goFields.join(`
|
|
|
5251
5398
|
rangeIndex = param;
|
|
5252
5399
|
rangeValue = "_";
|
|
5253
5400
|
}
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
goArray = `.${childComponent.name}s`;
|
|
5401
|
+
if (loop.childComponent) {
|
|
5402
|
+
goArray = `.${loop.childComponent.name}s`;
|
|
5257
5403
|
}
|
|
5258
5404
|
this.inLoop = true;
|
|
5259
5405
|
const addedLoopVars = [];
|
|
5260
5406
|
let pushedBindingMap = false;
|
|
5261
5407
|
if (supportableDestructure) {
|
|
5262
|
-
this.
|
|
5408
|
+
const built = this.buildDestructureBindingMap(loop, rangeValue);
|
|
5409
|
+
this.loopBindingStack.push(built.bindings);
|
|
5410
|
+
this.loopRestExcludeStack.push(built.restExcludes);
|
|
5263
5411
|
pushedBindingMap = true;
|
|
5264
5412
|
this.loopParamStack.push("");
|
|
5265
5413
|
if (rangeIndex !== "_") {
|
|
@@ -5278,7 +5426,9 @@ ${goFields.join(`
|
|
|
5278
5426
|
}
|
|
5279
5427
|
}
|
|
5280
5428
|
this.loopScalarItemStack.push(this.scalarLiteralLoopGoType(loop.arrayParsed, loop.itemType) !== null);
|
|
5429
|
+
this.loopWrapperStack.push(!!loop.childComponent);
|
|
5281
5430
|
const children = this.renderChildren(loop.children);
|
|
5431
|
+
this.loopWrapperStack.pop();
|
|
5282
5432
|
this.loopScalarItemStack.pop();
|
|
5283
5433
|
const itemMarker = this.loopItemMarker(loop);
|
|
5284
5434
|
for (const v of addedLoopVars) {
|
|
@@ -5289,8 +5439,10 @@ ${goFields.join(`
|
|
|
5289
5439
|
this.loopVarRefCount.set(v, rc);
|
|
5290
5440
|
}
|
|
5291
5441
|
this.loopParamStack.pop();
|
|
5292
|
-
if (pushedBindingMap)
|
|
5442
|
+
if (pushedBindingMap) {
|
|
5293
5443
|
this.loopBindingStack.pop();
|
|
5444
|
+
this.loopRestExcludeStack.pop();
|
|
5445
|
+
}
|
|
5294
5446
|
this.inLoop = false;
|
|
5295
5447
|
if (loop.sortComparator) {
|
|
5296
5448
|
const sortEmit = (e) => this.renderParsedExpr(e);
|
|
@@ -5319,24 +5471,6 @@ ${goFields.join(`
|
|
|
5319
5471
|
}
|
|
5320
5472
|
return "";
|
|
5321
5473
|
}
|
|
5322
|
-
findChildComponent(nodes) {
|
|
5323
|
-
for (const node of nodes) {
|
|
5324
|
-
if (node.type === "component") {
|
|
5325
|
-
return node;
|
|
5326
|
-
}
|
|
5327
|
-
if (node.type === "element" && node.children) {
|
|
5328
|
-
const found = this.findChildComponent(node.children);
|
|
5329
|
-
if (found)
|
|
5330
|
-
return found;
|
|
5331
|
-
}
|
|
5332
|
-
if (node.type === "fragment" && node.children) {
|
|
5333
|
-
const found = this.findChildComponent(node.children);
|
|
5334
|
-
if (found)
|
|
5335
|
-
return found;
|
|
5336
|
-
}
|
|
5337
|
-
}
|
|
5338
|
-
return null;
|
|
5339
|
-
}
|
|
5340
5474
|
queueDynamicChildrenDefine(comp) {
|
|
5341
5475
|
const effectiveChildren = comp.children.length > 0 ? comp.children : this.jsxChildrenPropNodes(comp.props);
|
|
5342
5476
|
if (effectiveChildren.length === 0)
|
|
@@ -5384,7 +5518,7 @@ ${goFields.join(`
|
|
|
5384
5518
|
return this.renderChildren(comp.children);
|
|
5385
5519
|
}
|
|
5386
5520
|
let templateCall;
|
|
5387
|
-
if (this.inLoop) {
|
|
5521
|
+
if (this.inLoop && (this.loopWrapperStack[this.loopWrapperStack.length - 1] ?? false)) {
|
|
5388
5522
|
const loopBodyDefine = this.queueLoopBodyChildrenDefine(comp);
|
|
5389
5523
|
if (loopBodyDefine) {
|
|
5390
5524
|
const bodyData = this.loopScalarItemStack[this.loopScalarItemStack.length - 1] ? ".BfLoopItem" : ".";
|
|
@@ -5392,6 +5526,12 @@ ${goFields.join(`
|
|
|
5392
5526
|
} else {
|
|
5393
5527
|
templateCall = `{{template "${comp.name}" .}}`;
|
|
5394
5528
|
}
|
|
5529
|
+
} else if (this.inLoop && comp.slotId) {
|
|
5530
|
+
const suffix = slotIdToFieldSuffix(comp.slotId);
|
|
5531
|
+
const loopBodyDefine = this.queueLoopBodyChildrenDefine(comp);
|
|
5532
|
+
templateCall = loopBodyDefine ? `{{template "${comp.name}" (bf_with_children $.${comp.name}${suffix} (bf_tmpl "${loopBodyDefine}" .))}}` : `{{template "${comp.name}" $.${comp.name}${suffix}}}`;
|
|
5533
|
+
} else if (this.inLoop) {
|
|
5534
|
+
templateCall = `{{template "${comp.name}" .}}`;
|
|
5395
5535
|
} else if (comp.slotId) {
|
|
5396
5536
|
const suffix = slotIdToFieldSuffix(comp.slotId);
|
|
5397
5537
|
const childrenDefine = this.queueDynamicChildrenDefine(comp);
|
|
@@ -5487,6 +5627,12 @@ ${children}`;
|
|
|
5487
5627
|
if (currentLoopParam && trimmed === currentLoopParam) {
|
|
5488
5628
|
return `{{bf_spread_attrs .}}`;
|
|
5489
5629
|
}
|
|
5630
|
+
const restInfo = this.lookupRestExclude(trimmed);
|
|
5631
|
+
if (restInfo) {
|
|
5632
|
+
const excludeArgs = restInfo.excludeKeys.map((k) => JSON.stringify(k)).join(" ");
|
|
5633
|
+
const omitArgs = excludeArgs ? `${restInfo.parent} ${excludeArgs}` : restInfo.parent;
|
|
5634
|
+
return `{{bf_spread_attrs (bf_omit ${omitArgs})}}`;
|
|
5635
|
+
}
|
|
5490
5636
|
const goExpr = this.convertExpressionToGo(value.expr);
|
|
5491
5637
|
return `{{bf_spread_attrs ${goExpr}}}`;
|
|
5492
5638
|
}
|