@barefootjs/go-template 0.17.0 → 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.
Files changed (39) hide show
  1. package/dist/adapter/expr/url-builder.d.ts.map +1 -1
  2. package/dist/adapter/go-template-adapter.d.ts +144 -14
  3. package/dist/adapter/go-template-adapter.d.ts.map +1 -1
  4. package/dist/adapter/index.js +449 -118
  5. package/dist/adapter/lib/compile-state.d.ts +27 -1
  6. package/dist/adapter/lib/compile-state.d.ts.map +1 -1
  7. package/dist/adapter/lib/go-emit.d.ts +9 -0
  8. package/dist/adapter/lib/go-emit.d.ts.map +1 -1
  9. package/dist/adapter/lib/go-naming.d.ts +23 -0
  10. package/dist/adapter/lib/go-naming.d.ts.map +1 -1
  11. package/dist/adapter/memo/memo-compute.d.ts +54 -5
  12. package/dist/adapter/memo/memo-compute.d.ts.map +1 -1
  13. package/dist/adapter/memo/memo-type.d.ts +10 -0
  14. package/dist/adapter/memo/memo-type.d.ts.map +1 -1
  15. package/dist/adapter/type/type-codegen.d.ts +5 -1
  16. package/dist/adapter/type/type-codegen.d.ts.map +1 -1
  17. package/dist/adapter/value/parsed-literal-to-go.d.ts +13 -4
  18. package/dist/adapter/value/parsed-literal-to-go.d.ts.map +1 -1
  19. package/dist/build.js +449 -118
  20. package/dist/conformance-pins.d.ts +12 -0
  21. package/dist/conformance-pins.d.ts.map +1 -0
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +468 -118
  25. package/package.json +3 -3
  26. package/src/__tests__/go-template-adapter.test.ts +652 -129
  27. package/src/__tests__/lowering-plugin.test.ts +56 -0
  28. package/src/adapter/expr/url-builder.ts +14 -3
  29. package/src/adapter/go-template-adapter.ts +637 -140
  30. package/src/adapter/lib/compile-state.ts +31 -0
  31. package/src/adapter/lib/go-emit.ts +20 -0
  32. package/src/adapter/lib/go-naming.ts +29 -0
  33. package/src/adapter/memo/memo-compute.ts +228 -18
  34. package/src/adapter/memo/memo-type.ts +19 -0
  35. package/src/adapter/type/type-codegen.ts +22 -3
  36. package/src/adapter/value/parsed-literal-to-go.ts +82 -9
  37. package/src/conformance-pins.ts +135 -0
  38. package/src/index.ts +1 -0
  39. package/src/test-render.ts +46 -8
@@ -31,17 +31,18 @@ import {
31
31
  isSupported,
32
32
  exprToString,
33
33
  identifierPath,
34
- asCallbackMethodCall,
34
+ asCallbackMethodCall as asCallbackMethodCall3,
35
35
  sortComparatorFromArrow,
36
36
  emitParsedExpr,
37
37
  emitIRNode,
38
38
  emitAttrValue,
39
39
  augmentInheritedPropAccesses,
40
40
  collectContextConsumers,
41
- isLowerableObjectRestDestructure,
41
+ isLowerableLoopDestructure,
42
42
  collectModuleStringConsts as collectModuleStringConstsShared,
43
- searchParamsLocalNames,
44
- prepareLoweringMatchers
43
+ prepareLoweringMatchers,
44
+ envSignalReaderFor,
45
+ computeSsrSeedPlan
45
46
  } from "@barefootjs/jsx";
46
47
  import { findInterpolationEnd } from "@barefootjs/jsx/scanner";
47
48
  import { BF_REGION } from "@barefootjs/shared";
@@ -122,6 +123,13 @@ function capitalizeFieldName(name) {
122
123
  }
123
124
  return name.charAt(0).toUpperCase() + name.slice(1);
124
125
  }
126
+ function goFieldNameForKey(key) {
127
+ const parts = key.split(/[^A-Za-z0-9_]+/).filter(Boolean);
128
+ if (parts.length === 0)
129
+ return "Field";
130
+ const name = parts.map(capitalizeFieldName).join("");
131
+ return /^[0-9]/.test(name) ? `Field${name}` : name;
132
+ }
125
133
  function slotIdToFieldSuffix(slotId) {
126
134
  const cleanId = slotId.startsWith("^") ? slotId.slice(1) : slotId;
127
135
  const match = cleanId.match(/^s(\d+)$/);
@@ -224,6 +232,13 @@ function emitFlatMapEval(recv, body, param, emit) {
224
232
  const env = emitEvalEnvArg(body, [param], emit);
225
233
  return `bf_flat_map_eval ${wrapIfMultiToken(recv)} "${escapeGoString(json)}" "${param}" ${env}`;
226
234
  }
235
+ function emitMapEval(recv, body, param, emit) {
236
+ const json = serializeParsedExpr(body);
237
+ if (json === null)
238
+ return null;
239
+ const env = emitEvalEnvArg(body, [param], emit);
240
+ return `bf_map_eval ${wrapIfMultiToken(recv)} "${escapeGoString(json)}" "${param}" ${env}`;
241
+ }
227
242
  function stringTolerantEqOperands(l, r) {
228
243
  const isStrLit = (x) => /^"(?:[^"\\]|\\.)*"$/.test(x);
229
244
  if (isStrLit(l) === isStrLit(r))
@@ -337,6 +352,9 @@ class CompileState {
337
352
  currentTypeDefinitions = [];
338
353
  contextConsumers = [];
339
354
  searchParamsLocals = new Set;
355
+ envSignalReadersByLocal = new Map;
356
+ ssrSeedPlan = { baseScope: [], steps: [] };
357
+ hoistedMemoLocals = new Map;
340
358
  loweringMatchers = [];
341
359
  nillablePropNames = new Set;
342
360
  rootScopeNodes = new Set;
@@ -684,9 +702,12 @@ function forEachValueChild(n, visit) {
684
702
  // src/adapter/expr/url-builder.ts
685
703
  import {
686
704
  parseExpression,
687
- stringifyParsedExpr
705
+ stringifyParsedExpr,
706
+ isValidHelperId
688
707
  } from "@barefootjs/jsx";
689
- var GO_HELPER_NAMES = { query: "bf_query" };
708
+ function goHelperName(helper) {
709
+ return isValidHelperId(helper) ? `bf_${helper}` : null;
710
+ }
690
711
  var BOOL_COMPARISON_OPS = new Set([
691
712
  "==",
692
713
  "===",
@@ -729,7 +750,7 @@ function lowerRegisteredCall(ctx, jsExpr, preParsed) {
729
750
  return null;
730
751
  }
731
752
  function renderLoweringNode(ctx, node) {
732
- const helper = GO_HELPER_NAMES[node.helper];
753
+ const helper = goHelperName(node.helper);
733
754
  if (!helper)
734
755
  return null;
735
756
  const lowerExpr = (n) => ctx.convertExpressionToGo(stringifyParsedExpr(n), undefined, n);
@@ -769,7 +790,7 @@ function typeInfoToGo(ctx, typeInfo, defaultValue) {
769
790
  case "object":
770
791
  return "map[string]interface{}";
771
792
  case "interface":
772
- if (typeInfo.raw && ctx.state.localTypeNames.has(typeInfo.raw)) {
793
+ if (typeInfo.raw && (ctx.state.localStructFields.has(typeInfo.raw) || ctx.state.localTypeAliases.has(typeInfo.raw))) {
773
794
  return typeInfo.raw;
774
795
  }
775
796
  if (typeInfo.raw) {
@@ -802,7 +823,7 @@ function tsTypeStringToGo(ctx, tsType) {
802
823
  const arrayMatch = t.match(/^Array<(.+)>$/);
803
824
  if (arrayMatch)
804
825
  return `[]${tsTypeStringToGo(ctx, arrayMatch[1])}`;
805
- if (ctx.state.localTypeNames.has(t))
826
+ if (ctx.state.localStructFields.has(t) || ctx.state.localTypeAliases.has(t))
806
827
  return t;
807
828
  return "interface{}";
808
829
  }
@@ -824,6 +845,24 @@ function inferTypeFromValue(value) {
824
845
  }
825
846
 
826
847
  // src/adapter/value/parsed-literal-to-go.ts
848
+ function structPropertyType(ctx, structGoType, key) {
849
+ const td = ctx.state.currentTypeDefinitions.find((t) => t.name === structGoType);
850
+ return td?.properties?.find((p) => p.name === key)?.type;
851
+ }
852
+ function bakeInlineObjectAsGoMap(ctx, expr) {
853
+ if (expr.kind !== "object-literal")
854
+ return null;
855
+ const entries = [];
856
+ for (const prop of expr.properties) {
857
+ if (prop.shorthand)
858
+ return null;
859
+ const go = prop.value.kind === "object-literal" ? bakeInlineObjectAsGoMap(ctx, prop.value) : parsedLiteralToGo(ctx, prop.value);
860
+ if (go === null)
861
+ return null;
862
+ entries.push(`${JSON.stringify(goFieldNameForKey(prop.key))}: ${go}`);
863
+ }
864
+ return `map[string]interface{}{${entries.join(", ")}}`;
865
+ }
827
866
  function parsedLiteralToGo(ctx, expr, typeInfo) {
828
867
  if (expr.kind === "unary" && expr.op === "-" && expr.argument.kind === "literal" && expr.argument.literalType === "number") {
829
868
  return expr.argument.raw !== undefined ? `-${expr.argument.raw}` : null;
@@ -864,9 +903,16 @@ function parsedLiteralToGo(ctx, expr, typeInfo) {
864
903
  const goField = structFields.get(prop.key);
865
904
  if (!goField)
866
905
  return null;
867
- if (prop.value.kind === "object-literal" || prop.value.kind === "array-literal")
868
- return null;
869
- const go = parsedLiteralToGo(ctx, prop.value);
906
+ const propType = structPropertyType(ctx, goType, prop.key);
907
+ let go;
908
+ if (prop.value.kind === "array-literal") {
909
+ go = parsedLiteralToGo(ctx, prop.value, propType);
910
+ } else if (prop.value.kind === "object-literal") {
911
+ const nestedGoType = propType ? typeInfoToGo(ctx, propType) : undefined;
912
+ go = nestedGoType && ctx.state.localStructFields.has(nestedGoType) ? parsedLiteralToGo(ctx, prop.value, propType) : bakeInlineObjectAsGoMap(ctx, prop.value);
913
+ } else {
914
+ go = parsedLiteralToGo(ctx, prop.value);
915
+ }
870
916
  if (go === null)
871
917
  return null;
872
918
  entries.push(`${goField}: ${go}`);
@@ -977,8 +1023,17 @@ function getSignalInitialValueAsGo(ctx, initialValue, propsParams, propFallbackV
977
1023
  }
978
1024
 
979
1025
  // src/adapter/memo/memo-type.ts
1026
+ import { asCallbackMethodCall } from "@barefootjs/jsx";
1027
+ function isListFilterMemo(memo) {
1028
+ if (!memo.parsed)
1029
+ return false;
1030
+ const cb = asCallbackMethodCall(memo.parsed);
1031
+ return cb !== null && cb.method === "filter";
1032
+ }
980
1033
  function isBooleanMemo(ctx, memo, signals, propsParamMap) {
981
1034
  const c = memo.computation;
1035
+ if (isListFilterMemo(memo))
1036
+ return false;
982
1037
  if (isStringTernaryMemo(ctx, memo.parsed))
983
1038
  return false;
984
1039
  if (/(!==|===|!=(?!=)|==(?!=))/.test(c))
@@ -1269,6 +1324,14 @@ function computeObjectMemoInitialValue(ctx, memo) {
1269
1324
  }`;
1270
1325
  }
1271
1326
 
1327
+ // src/adapter/memo/memo-compute.ts
1328
+ import {
1329
+ asCallbackMethodCall as asCallbackMethodCall2,
1330
+ freeVarsInBody as freeVarsInBody2,
1331
+ materializeGetterCalls,
1332
+ serializeParsedExpr as serializeParsedExpr2
1333
+ } from "@barefootjs/jsx";
1334
+
1272
1335
  // src/adapter/memo/template-interp.ts
1273
1336
  import ts from "typescript";
1274
1337
  function computeTemplateLiteralMemoInitialValue(ctx, memo, propsParams) {
@@ -1409,8 +1472,49 @@ function propsAccessNameFromParsed(ctx, node) {
1409
1472
 
1410
1473
  // src/adapter/memo/memo-compute.ts
1411
1474
  var EMPTY_PROP_FALLBACK_VARS2 = new Map;
1475
+ function getterCallName(e) {
1476
+ return e.kind === "call" && e.callee.kind === "identifier" && e.args.length === 0 ? e.callee.name : null;
1477
+ }
1478
+ function propsMemberName(e) {
1479
+ return e.kind === "member" && !e.computed && e.object.kind === "identifier" && e.object.name === "props" ? e.property : null;
1480
+ }
1481
+ function matchFilterArmMemo(ctx, body, signals, propsParams) {
1482
+ const cb = asCallbackMethodCall2(body);
1483
+ if (!cb || cb.method !== "filter")
1484
+ return null;
1485
+ const propName = propsMemberName(cb.object);
1486
+ const param = propName ? propsParams.find((p) => p.name === propName) : undefined;
1487
+ if (!propName || !param)
1488
+ return null;
1489
+ const knownGetterNames = new Set(ctx.state.ssrSeedPlan.steps.filter((s) => s.kind !== "env-reader").map((s) => s.name));
1490
+ const materialized = materializeGetterCalls(cb.arrow.body, knownGetterNames);
1491
+ const predJSON = serializeParsedExpr2(materialized);
1492
+ if (predJSON === null)
1493
+ return null;
1494
+ const paramName = cb.arrow.params[0] ?? "_";
1495
+ const freeVars = freeVarsInBody2(materialized, new Set(cb.arrow.params));
1496
+ return { propName, predJSON, paramName, freeVars };
1497
+ }
1498
+ function filterArmEarlierSiblingRefs(ctx, memo, signals, propsParams) {
1499
+ if (!memo.parsed)
1500
+ return [];
1501
+ const match = matchFilterArmMemo(ctx, memo.parsed, signals, propsParams);
1502
+ if (!match)
1503
+ return [];
1504
+ const memos = ctx.state.currentMemos ?? [];
1505
+ const currentIndex = memos.findIndex((m) => m.name === memo.name);
1506
+ if (currentIndex < 0)
1507
+ return [];
1508
+ const refs = [];
1509
+ for (const name of match.freeVars) {
1510
+ const idx = memos.findIndex((m) => m.name === name);
1511
+ if (idx >= 0 && idx < currentIndex)
1512
+ refs.push(name);
1513
+ }
1514
+ return refs;
1515
+ }
1412
1516
  function computeMemoInitialValue(ctx, memo, signals, propsParams, propFallbackVars = EMPTY_PROP_FALLBACK_VARS2, goType) {
1413
- const resolved = computeMemoInitialValueOrNull(ctx, memo, signals, propsParams, propFallbackVars);
1517
+ const resolved = computeMemoInitialValueOrNull(ctx, memo, signals, propsParams, propFallbackVars, new Set([memo.name]));
1414
1518
  if (resolved !== null)
1415
1519
  return resolved;
1416
1520
  if (goType === "bool")
@@ -1422,15 +1526,79 @@ function computeMemoInitialValue(ctx, memo, signals, propsParams, propFallbackVa
1422
1526
  }
1423
1527
  return "0";
1424
1528
  }
1425
- function memoInitialFromParsedBody(ctx, body, signals, propsParams, propFallbackVars) {
1529
+ function memoInitialFromParsedBody(ctx, body, signals, propsParams, propFallbackVars, currentMemoName, resolving = new Set) {
1426
1530
  const propRef = (propName) => {
1427
1531
  const hoisted = propFallbackVars.get(propName);
1428
1532
  if (hoisted)
1429
1533
  return hoisted.varName;
1430
1534
  return `in.${capitalizeFieldName(propName)}`;
1431
1535
  };
1432
- const getterCallName = (e) => e.kind === "call" && e.callee.kind === "identifier" && e.args.length === 0 ? e.callee.name : null;
1433
- const propsMemberName = (e) => e.kind === "member" && !e.computed && e.object.kind === "identifier" && e.object.name === "props" ? e.property : null;
1536
+ const envGetKey = (e) => {
1537
+ if (e.kind !== "call" || e.callee.kind !== "member" || e.callee.computed)
1538
+ return null;
1539
+ const recvName = getterCallName(e.callee.object);
1540
+ if (recvName === null)
1541
+ return null;
1542
+ const reader = ctx.state.envSignalReadersByLocal.get(recvName);
1543
+ if (!reader || !reader.methods.has(e.callee.property))
1544
+ return null;
1545
+ const arg = e.args[0];
1546
+ if (!arg || arg.kind !== "literal" || arg.literalType !== "string")
1547
+ return null;
1548
+ return { key: String(arg.value), fieldName: capitalizeFieldName(reader.canonicalName) };
1549
+ };
1550
+ {
1551
+ const m = envGetKey(body);
1552
+ if (m !== null)
1553
+ return `in.${m.fieldName}.Get(${JSON.stringify(m.key)})`;
1554
+ }
1555
+ if (body.kind === "logical" && (body.op === "??" || body.op === "||") && body.right.kind === "literal" && body.right.literalType === "string") {
1556
+ const m = envGetKey(body.left);
1557
+ if (m !== null) {
1558
+ const def = JSON.stringify(String(body.right.value));
1559
+ return `func() string { if v := in.${m.fieldName}.Get(${JSON.stringify(m.key)}); v != "" { return v }; return ${def} }()`;
1560
+ }
1561
+ }
1562
+ {
1563
+ const match = matchFilterArmMemo(ctx, body, signals, propsParams);
1564
+ if (match) {
1565
+ const { propName, predJSON, paramName, freeVars } = match;
1566
+ const currentIndex = (ctx.state.currentMemos ?? []).findIndex((m) => m.name === currentMemoName);
1567
+ const envEntries = [];
1568
+ let unresolved = false;
1569
+ for (const name of freeVars) {
1570
+ let goExpr = null;
1571
+ const siblingIndex = (ctx.state.currentMemos ?? []).findIndex((m) => m.name === name);
1572
+ if (siblingIndex >= 0) {
1573
+ const siblingMemo = ctx.state.currentMemos[siblingIndex];
1574
+ const eligible = currentIndex >= 0 && siblingIndex < currentIndex && !resolving.has(siblingMemo.name);
1575
+ if (eligible) {
1576
+ const hoisted = ctx.state.hoistedMemoLocals.get(siblingMemo.name);
1577
+ goExpr = hoisted ?? computeMemoInitialValueOrNull(ctx, siblingMemo, signals, propsParams, propFallbackVars, new Set([...resolving, siblingMemo.name]));
1578
+ }
1579
+ } else {
1580
+ const sig = signals.find((s) => s.getter === name);
1581
+ if (sig) {
1582
+ goExpr = getSignalInitialValueAsGo(ctx, sig.initialValue, propsParams, propFallbackVars);
1583
+ } else {
1584
+ const p = propsParams.find((pp) => pp.name === name);
1585
+ if (p)
1586
+ goExpr = propRef(name);
1587
+ }
1588
+ }
1589
+ if (goExpr === null) {
1590
+ unresolved = true;
1591
+ break;
1592
+ }
1593
+ envEntries.push(`${JSON.stringify(name)}: ${goExpr}`);
1594
+ }
1595
+ if (!unresolved) {
1596
+ const itemsField = `in.${capitalizeFieldName(propName)}`;
1597
+ const envMap = `map[string]any{${envEntries.join(", ")}}`;
1598
+ return `bf.FilterEval(${itemsField}, "${escapeGoString(predJSON)}", ${JSON.stringify(paramName)}, ${envMap})`;
1599
+ }
1600
+ }
1601
+ }
1434
1602
  if (body.kind === "binary" && ["===", "!==", "==", "!="].includes(body.op) && body.right.kind === "literal" && body.right.literalType === "string") {
1435
1603
  const depName = getterCallName(body.left);
1436
1604
  if (depName) {
@@ -1469,8 +1637,8 @@ function memoInitialFromParsedBody(ctx, body, signals, propsParams, propFallback
1469
1637
  condGo = getSignalInitialValueAsGo(ctx, condSignal.initialValue, propsParams, propFallbackVars);
1470
1638
  } else {
1471
1639
  const condMemo = (ctx.state.currentMemos ?? []).find((m) => m.name === condName);
1472
- if (condMemo) {
1473
- condGo = computeMemoInitialValueOrNull(ctx, condMemo, signals, propsParams, propFallbackVars);
1640
+ if (condMemo && !resolving.has(condMemo.name)) {
1641
+ condGo = computeMemoInitialValueOrNull(ctx, condMemo, signals, propsParams, propFallbackVars, new Set([...resolving, condMemo.name]));
1474
1642
  }
1475
1643
  }
1476
1644
  if (condGo === "true")
@@ -1544,17 +1712,17 @@ function memoInitialFromParsedBody(ctx, body, signals, propsParams, propFallback
1544
1712
  }
1545
1713
  return null;
1546
1714
  }
1547
- function computeMemoInitialValueOrNull(ctx, memo, signals, propsParams, propFallbackVars = EMPTY_PROP_FALLBACK_VARS2) {
1715
+ function computeMemoInitialValueOrNull(ctx, memo, signals, propsParams, propFallbackVars = EMPTY_PROP_FALLBACK_VARS2, resolving = new Set) {
1548
1716
  const computation = memo.computation;
1549
1717
  const tmplMemo = computeTemplateLiteralMemoInitialValue(ctx, memo, propsParams);
1550
1718
  if (tmplMemo !== null)
1551
1719
  return tmplMemo;
1552
1720
  if (memo.parsed) {
1553
- const fromParsed = memoInitialFromParsedBody(ctx, memo.parsed, signals, propsParams, propFallbackVars);
1721
+ const fromParsed = memoInitialFromParsedBody(ctx, memo.parsed, signals, propsParams, propFallbackVars, memo.name, resolving);
1554
1722
  if (fromParsed !== null)
1555
1723
  return fromParsed;
1556
1724
  }
1557
- const cmpTernary = computeComparisonTernaryGo(ctx, memo.parsed, signals, propsParams, propFallbackVars);
1725
+ const cmpTernary = computeComparisonTernaryGo(ctx, memo.parsed, signals, propsParams, propFallbackVars, resolving);
1558
1726
  if (cmpTernary !== null)
1559
1727
  return cmpTernary;
1560
1728
  const blockReturn = resolveBlockBodyMemoModuleConst(ctx, memo, signals);
@@ -1566,20 +1734,22 @@ function computeMemoInitialValueOrNull(ctx, memo, signals, propsParams, propFall
1566
1734
  return objMemo;
1567
1735
  return null;
1568
1736
  }
1569
- function resolveGetterValueAsGo(ctx, name, signals, propsParams, propFallbackVars) {
1737
+ function resolveGetterValueAsGo(ctx, name, signals, propsParams, propFallbackVars, resolving = new Set) {
1570
1738
  const signal = signals.find((s) => s.getter === name);
1571
1739
  if (signal) {
1572
1740
  return getSignalInitialValueAsGo(ctx, signal.initialValue, propsParams, propFallbackVars);
1573
1741
  }
1574
1742
  const memo = (ctx.state.currentMemos ?? []).find((m) => m.name === name);
1575
1743
  if (memo) {
1744
+ if (resolving.has(memo.name))
1745
+ return null;
1576
1746
  const stripped = memo.computation.replace(/^\(\)\s*=>\s*/, "");
1577
1747
  const fb = ctx.extractPropFallback(stripped);
1578
1748
  if (fb && capitalizeFieldName(fb.propName) === capitalizeFieldName(memo.name)) {
1579
1749
  const field = `in.${capitalizeFieldName(fb.propName)}`;
1580
1750
  return `func() interface{} { v := interface{}(${field}); if v == nil || v == "" { return ${fb.goFallback} }; return v }()`;
1581
1751
  }
1582
- return computeMemoInitialValueOrNull(ctx, memo, signals, propsParams, propFallbackVars);
1752
+ return computeMemoInitialValueOrNull(ctx, memo, signals, propsParams, propFallbackVars, new Set([...resolving, memo.name]));
1583
1753
  }
1584
1754
  const param = propsParams.find((p) => p.name === name);
1585
1755
  if (param) {
@@ -1588,7 +1758,7 @@ function resolveGetterValueAsGo(ctx, name, signals, propsParams, propFallbackVar
1588
1758
  }
1589
1759
  return null;
1590
1760
  }
1591
- function computeComparisonTernaryGo(ctx, parsed, signals, propsParams, propFallbackVars) {
1761
+ function computeComparisonTernaryGo(ctx, parsed, signals, propsParams, propFallbackVars, resolving = new Set) {
1592
1762
  if (!parsed || parsed.kind !== "conditional")
1593
1763
  return null;
1594
1764
  const cond = parsed.test;
@@ -1613,16 +1783,16 @@ function computeComparisonTernaryGo(ctx, parsed, signals, propsParams, propFallb
1613
1783
  const f = branch(parsed.alternate);
1614
1784
  if (t === null || f === null)
1615
1785
  return null;
1616
- const condGo = resolveComparisonOperandGo(ctx, cond.left, signals, propsParams, propFallbackVars);
1786
+ const condGo = resolveComparisonOperandGo(ctx, cond.left, signals, propsParams, propFallbackVars, resolving);
1617
1787
  if (condGo === null)
1618
1788
  return null;
1619
1789
  const eqBranch = isEq ? t : f;
1620
1790
  const neBranch = isEq ? f : t;
1621
1791
  return `func() string { if ${condGo} == ${JSON.stringify(cond.right.value)} { return ${eqBranch} }; return ${neBranch} }()`;
1622
1792
  }
1623
- function resolveComparisonOperandGo(ctx, node, signals, propsParams, propFallbackVars) {
1793
+ function resolveComparisonOperandGo(ctx, node, signals, propsParams, propFallbackVars, resolving = new Set) {
1624
1794
  if (node.kind === "call" && node.callee.kind === "identifier" && node.args.length === 0) {
1625
- return resolveGetterValueAsGo(ctx, node.callee.name, signals, propsParams, propFallbackVars);
1795
+ return resolveGetterValueAsGo(ctx, node.callee.name, signals, propsParams, propFallbackVars, resolving);
1626
1796
  }
1627
1797
  if (node.kind === "logical" && node.op === "??" && node.right.kind === "literal" && node.right.literalType === "string") {
1628
1798
  const propName = propsAccessNameFromParsed2(ctx, node.left);
@@ -1980,8 +2150,10 @@ class GoTemplateAdapter extends BaseAdapter {
1980
2150
  inLoop = false;
1981
2151
  loopParamStack = [];
1982
2152
  loopScalarItemStack = [];
2153
+ loopWrapperStack = [];
1983
2154
  loopVarRefCount = new Map;
1984
2155
  loopBindingStack = [];
2156
+ loopRestExcludeStack = [];
1985
2157
  childComponentShapes = new Map;
1986
2158
  childContextConsumers = new Map;
1987
2159
  constructor(options = {}) {
@@ -2001,7 +2173,18 @@ class GoTemplateAdapter extends BaseAdapter {
2001
2173
  this.state.currentMemos = ir.metadata.memos ?? [];
2002
2174
  this.state.currentTypeDefinitions = ir.metadata.typeDefinitions ?? [];
2003
2175
  this.state.contextConsumers = collectContextConsumers(ir.metadata);
2004
- this.state.searchParamsLocals = searchParamsLocalNames(ir.metadata);
2176
+ this.state.ssrSeedPlan = ir.metadata.ssrSeedPlan ?? computeSsrSeedPlan(ir.metadata);
2177
+ this.state.envSignalReadersByLocal = new Map;
2178
+ this.state.searchParamsLocals = new Set;
2179
+ for (const step of this.state.ssrSeedPlan.steps) {
2180
+ if (step.kind !== "env-reader")
2181
+ continue;
2182
+ const reader = envSignalReaderFor(step.reader.key);
2183
+ if (reader)
2184
+ this.state.envSignalReadersByLocal.set(step.name, reader);
2185
+ if (step.reader.key === "search")
2186
+ this.state.searchParamsLocals.add(step.name);
2187
+ }
2005
2188
  this.state.loweringMatchers = prepareLoweringMatchers(ir.metadata);
2006
2189
  augmentInheritedPropAccesses(ir);
2007
2190
  }
@@ -2163,11 +2346,22 @@ ${scriptRegistrations}${templateBody}
2163
2346
  contextFieldName(c) {
2164
2347
  return capitalizeFieldName(c.localName);
2165
2348
  }
2349
+ isMapRootedContextChain(node) {
2350
+ if (node.kind === "identifier") {
2351
+ return this.state.contextConsumers.some((c) => c.localName === node.name && c.defaultKind === "object");
2352
+ }
2353
+ if (node.kind === "member" && !node.computed) {
2354
+ return this.isMapRootedContextChain(node.object);
2355
+ }
2356
+ return false;
2357
+ }
2166
2358
  contextConsumerGoType(c) {
2167
2359
  if (typeof c.defaultValue === "number")
2168
2360
  return "int";
2169
2361
  if (typeof c.defaultValue === "boolean")
2170
2362
  return "bool";
2363
+ if (c.defaultKind === "object")
2364
+ return "map[string]interface{}";
2171
2365
  return "string";
2172
2366
  }
2173
2367
  contextConsumerGoDefault(c) {
@@ -2177,6 +2371,8 @@ ${scriptRegistrations}${templateBody}
2177
2371
  return String(c.defaultValue);
2178
2372
  if (typeof c.defaultValue === "string")
2179
2373
  return `"${escapeGoString(c.defaultValue)}"`;
2374
+ if (c.defaultKind === "object")
2375
+ return "map[string]interface{}{}";
2180
2376
  return '""';
2181
2377
  }
2182
2378
  nonCollidingContextConsumers(taken) {
@@ -2223,12 +2419,15 @@ ${goFields.join(`
2223
2419
  }
2224
2420
  structFieldsFor(td) {
2225
2421
  const fields = [];
2422
+ const seenGoNames = new Set;
2226
2423
  for (const prop of td.properties ?? []) {
2227
- if (!GO_IDENTIFIER.test(prop.name))
2424
+ const goName = goFieldNameForKey(prop.name);
2425
+ if (seenGoNames.has(goName))
2228
2426
  continue;
2427
+ seenGoNames.add(goName);
2229
2428
  fields.push({
2230
2429
  tsName: prop.name,
2231
- goName: capitalizeFieldName(prop.name),
2430
+ goName,
2232
2431
  goType: typeInfoToGo(this.emitCtx, prop.type)
2233
2432
  });
2234
2433
  }
@@ -2456,10 +2655,10 @@ ${goFields.join(`
2456
2655
  }
2457
2656
  return "interface{}";
2458
2657
  }
2459
- collectBodyChildInstances(bodyChildren) {
2658
+ collectBodyChildInstances(bodyChildren, propsParams = []) {
2460
2659
  const result = [];
2461
2660
  for (const child of bodyChildren) {
2462
- this.collectStaticChildInstancesRecursive(child, result, false, new Map);
2661
+ this.collectStaticChildInstancesRecursive(child, result, false, new Map, propsParams);
2463
2662
  }
2464
2663
  return result;
2465
2664
  }
@@ -2505,6 +2704,28 @@ ${goFields.join(`
2505
2704
  if (propFallbackVars.size > 0)
2506
2705
  lines.push("");
2507
2706
  this.emitDynamicBodyWrappers(lines, ir, componentName, dynamicWithBody, propFallbackVars, emittedWrapperVars);
2707
+ const memoPropsParamMap = new Map(ir.metadata.propsParams.map((p) => [p.name, p]));
2708
+ this.state.hoistedMemoLocals = new Map;
2709
+ const hoistNames = new Set;
2710
+ for (const memo of ir.metadata.memos) {
2711
+ for (const name of filterArmEarlierSiblingRefs(this.emitCtx, memo, ir.metadata.signals, ir.metadata.propsParams)) {
2712
+ hoistNames.add(name);
2713
+ }
2714
+ }
2715
+ if (hoistNames.size > 0) {
2716
+ for (const memo of ir.metadata.memos) {
2717
+ if (!hoistNames.has(memo.name))
2718
+ continue;
2719
+ const goType = this.inferMemoType(memo, ir.metadata.signals, memoPropsParamMap);
2720
+ const value = computeMemoInitialValue(this.emitCtx, memo, ir.metadata.signals, ir.metadata.propsParams, propFallbackVars, goType);
2721
+ let localName = `memo${capitalizeFieldName(memo.name)}`;
2722
+ while (GO_KEYWORDS.has(localName))
2723
+ localName += "_";
2724
+ lines.push(` var ${localName} ${goType} = ${value}`);
2725
+ this.state.hoistedMemoLocals.set(memo.name, localName);
2726
+ }
2727
+ lines.push("");
2728
+ }
2508
2729
  lines.push(` return ${propsTypeName}{`);
2509
2730
  lines.push("\t\tScopeID: scopeID,");
2510
2731
  lines.push("\t\tBfParent: in.BfParent,");
@@ -2578,11 +2799,15 @@ ${goFields.join(`
2578
2799
  continue;
2579
2800
  lines.push(` ${nested.name}s: ${varName},`);
2580
2801
  }
2581
- const memoPropsParamMap = new Map(ir.metadata.propsParams.map((p) => [p.name, p]));
2582
2802
  for (const memo of ir.metadata.memos) {
2583
2803
  const fieldName = capitalizeFieldName(memo.name);
2584
2804
  if (propFieldNames.has(fieldName))
2585
2805
  continue;
2806
+ const hoistedLocal = this.state.hoistedMemoLocals.get(memo.name);
2807
+ if (hoistedLocal) {
2808
+ lines.push(` ${fieldName}: ${hoistedLocal},`);
2809
+ continue;
2810
+ }
2586
2811
  const goType = this.inferMemoType(memo, ir.metadata.signals, memoPropsParamMap);
2587
2812
  const memoValue = computeMemoInitialValue(this.emitCtx, memo, ir.metadata.signals, ir.metadata.propsParams, propFallbackVars, goType);
2588
2813
  lines.push(` ${fieldName}: ${memoValue},`);
@@ -2603,7 +2828,7 @@ ${goFields.join(`
2603
2828
  for (const c of this.nonCollidingContextConsumers(takenInit)) {
2604
2829
  const field = this.contextFieldName(c);
2605
2830
  const def = this.contextConsumerGoDefault(c);
2606
- const defaulted = c.defaultValue === null || def === '""' || def === "0" || def === "false" ? `in.${field}` : applyGoFallback(`in.${field}`, def);
2831
+ const defaulted = c.defaultValue === null || def === '""' || def === "0" || def === "false" || def === "map[string]interface{}{}" ? `in.${field}` : applyGoFallback(`in.${field}`, def);
2607
2832
  lines.push(` ${field}: ${defaulted},`);
2608
2833
  }
2609
2834
  this.emitStaticChildInstances(lines, ir);
@@ -2612,7 +2837,7 @@ ${goFields.join(`
2612
2837
  lines.push("}");
2613
2838
  }
2614
2839
  emitStaticChildInstances(lines, ir) {
2615
- const staticChildren = this.collectStaticChildInstances(ir.root);
2840
+ const staticChildren = this.collectStaticChildInstances(ir.root, ir.metadata.propsParams);
2616
2841
  for (const child of staticChildren) {
2617
2842
  lines.push(` ${child.fieldName}: New${child.name}Props(${child.name}Input{`);
2618
2843
  lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
@@ -2744,7 +2969,7 @@ ${goFields.join(`
2744
2969
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
2745
2970
  const wrapperType = this.loopBodyWrapperName(componentName, nested);
2746
2971
  const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
2747
- const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
2972
+ const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren, ir.metadata.propsParams);
2748
2973
  for (const child of bodyChildInstances) {
2749
2974
  const childVar = `child_${child.fieldName}`;
2750
2975
  lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
@@ -2821,7 +3046,7 @@ ${goFields.join(`
2821
3046
  const wrapperType = this.loopBodyWrapperName(componentName, nested);
2822
3047
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
2823
3048
  const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
2824
- const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
3049
+ const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren, ir.metadata.propsParams);
2825
3050
  for (const child of bodyChildInstances) {
2826
3051
  const childVar = `child_${child.fieldName}`;
2827
3052
  lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
@@ -3061,7 +3286,7 @@ ${goFields.join(`
3061
3286
  lines.push(` ${nested.name}s []${elemType} \`json:"${jsonTag}"\``);
3062
3287
  }
3063
3288
  }
3064
- const staticChildren = this.collectStaticChildInstances(ir.root);
3289
+ const staticChildren = this.collectStaticChildInstances(ir.root, ir.metadata.propsParams);
3065
3290
  for (const child of staticChildren) {
3066
3291
  lines.push(` ${child.fieldName} ${child.name}Props \`json:"-"\``);
3067
3292
  }
@@ -3073,9 +3298,9 @@ ${goFields.join(`
3073
3298
  toJsonTag(name) {
3074
3299
  return name.charAt(0).toLowerCase() + name.slice(1);
3075
3300
  }
3076
- collectStaticChildInstances(node) {
3301
+ collectStaticChildInstances(node, propsParams = []) {
3077
3302
  const result = [];
3078
- this.collectStaticChildInstancesRecursive(node, result, false, new Map);
3303
+ this.collectStaticChildInstancesRecursive(node, result, false, new Map, propsParams);
3079
3304
  return result;
3080
3305
  }
3081
3306
  extractTextChildren(children) {
@@ -3120,12 +3345,12 @@ ${goFields.join(`
3120
3345
  return null;
3121
3346
  return withSentinel.split(GoTemplateAdapter.SCOPE_SENTINEL).map((seg) => JSON.stringify(seg)).join(" + scopeID + ");
3122
3347
  }
3123
- collectStaticChildInstancesRecursive(node, result, inLoop, providerCtx) {
3348
+ collectStaticChildInstancesRecursive(node, result, inLoop, providerCtx, propsParams = []) {
3124
3349
  if (node.type === "component") {
3125
3350
  const comp = node;
3126
3351
  if (comp.dynamicTag) {
3127
3352
  for (const child of comp.children) {
3128
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3353
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3129
3354
  }
3130
3355
  return;
3131
3356
  }
@@ -3143,69 +3368,111 @@ ${goFields.join(`
3143
3368
  contextBindings: providerCtx.size > 0 ? providerCtx : undefined
3144
3369
  });
3145
3370
  for (const child of effectiveChildren) {
3146
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3371
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3147
3372
  }
3148
3373
  }
3149
3374
  if (comp.name === "Portal" && comp.children) {
3150
3375
  for (const child of comp.children) {
3151
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3376
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3152
3377
  }
3153
3378
  }
3154
3379
  } else if (node.type === "loop") {
3155
3380
  const loop = node;
3156
3381
  for (const child of loop.children) {
3157
- this.collectStaticChildInstancesRecursive(child, result, true, providerCtx);
3382
+ this.collectStaticChildInstancesRecursive(child, result, inLoop || !!loop.childComponent, providerCtx, propsParams);
3158
3383
  }
3159
3384
  } else if (node.type === "element") {
3160
3385
  const element = node;
3161
3386
  for (const child of element.children) {
3162
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3387
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3163
3388
  }
3164
3389
  } else if (node.type === "fragment") {
3165
3390
  const fragment = node;
3166
3391
  for (const child of fragment.children) {
3167
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3392
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3168
3393
  }
3169
3394
  } else if (node.type === "conditional") {
3170
3395
  const cond = node;
3171
- this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop, providerCtx);
3396
+ this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop, providerCtx, propsParams);
3172
3397
  if (cond.whenFalse) {
3173
- this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx);
3398
+ this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx, propsParams);
3174
3399
  }
3175
3400
  } else if (node.type === "if-statement") {
3176
3401
  const stmt = node;
3177
- this.collectStaticChildInstancesRecursive(stmt.consequent, result, inLoop, providerCtx);
3402
+ this.collectStaticChildInstancesRecursive(stmt.consequent, result, inLoop, providerCtx, propsParams);
3178
3403
  if (stmt.alternate) {
3179
- this.collectStaticChildInstancesRecursive(stmt.alternate, result, inLoop, providerCtx);
3404
+ this.collectStaticChildInstancesRecursive(stmt.alternate, result, inLoop, providerCtx, propsParams);
3180
3405
  }
3181
3406
  } else if (node.type === "provider") {
3182
3407
  const p = node;
3183
- const childCtx = this.extendProviderContext(providerCtx, p);
3408
+ const childCtx = this.extendProviderContext(providerCtx, p, propsParams);
3184
3409
  for (const child of p.children) {
3185
- this.collectStaticChildInstancesRecursive(child, result, inLoop, childCtx);
3410
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, childCtx, propsParams);
3186
3411
  }
3187
3412
  } else if (node.type === "async") {
3188
3413
  const a = node;
3189
- this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop, providerCtx);
3414
+ this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop, providerCtx, propsParams);
3190
3415
  for (const child of a.children) {
3191
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3416
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3192
3417
  }
3193
3418
  }
3194
3419
  }
3195
- extendProviderContext(current, p) {
3420
+ extendProviderContext(current, p, propsParams) {
3196
3421
  const v = p.valueProp?.value;
3197
- if (!v || v.kind !== "literal")
3422
+ if (!v)
3198
3423
  return current;
3199
- let goLit = null;
3200
- if (typeof v.value === "string")
3201
- goLit = `"${escapeGoString(v.value)}"`;
3202
- else if (typeof v.value === "number" || typeof v.value === "boolean")
3203
- goLit = String(v.value);
3204
- if (goLit === null)
3205
- return current;
3206
- const next = new Map(current);
3207
- next.set(p.contextName, goLit);
3208
- return next;
3424
+ if (v.kind === "literal") {
3425
+ let goLit = null;
3426
+ if (typeof v.value === "string")
3427
+ goLit = `"${escapeGoString(v.value)}"`;
3428
+ else if (typeof v.value === "number" || typeof v.value === "boolean")
3429
+ goLit = String(v.value);
3430
+ if (goLit === null)
3431
+ return current;
3432
+ const next = new Map(current);
3433
+ next.set(p.contextName, goLit);
3434
+ return next;
3435
+ }
3436
+ if (v.kind === "expression" && v.parsed) {
3437
+ const goMap = this.providerObjectValueToGoMap(v.parsed, propsParams);
3438
+ if (goMap !== null) {
3439
+ const next = new Map(current);
3440
+ next.set(p.contextName, goMap);
3441
+ return next;
3442
+ }
3443
+ }
3444
+ return current;
3445
+ }
3446
+ providerObjectValueToGoMap(parsed, propsParams) {
3447
+ if (parsed.kind !== "object-literal")
3448
+ return null;
3449
+ const entries = [];
3450
+ for (const prop of parsed.properties) {
3451
+ if (prop.shorthand)
3452
+ return null;
3453
+ const goVal = this.lowerProviderMapMemberValue(prop.value, propsParams);
3454
+ if (goVal === null)
3455
+ return null;
3456
+ entries.push(`${JSON.stringify(prop.key)}: ${goVal}`);
3457
+ }
3458
+ if (entries.length === 0)
3459
+ return null;
3460
+ return `map[string]interface{}{${entries.join(", ")}}`;
3461
+ }
3462
+ lowerProviderMapMemberValue(node, propsParams) {
3463
+ if (node.kind === "object-literal")
3464
+ return objectLiteralToGoMap(this.emitCtx, node);
3465
+ const literal = parsedLiteralToGo(this.emitCtx, node);
3466
+ if (literal !== null)
3467
+ return literal;
3468
+ 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) {
3469
+ const propName = node.left.property;
3470
+ if (propsParams.some((param) => param.name === propName)) {
3471
+ const fieldRef = `in.${capitalizeFieldName(propName)}`;
3472
+ return `func() map[string]interface{} { ` + `if m := bf.AsMap(${fieldRef}); m != nil { return m }; ` + `return map[string]interface{}{} }()`;
3473
+ }
3474
+ }
3475
+ return null;
3209
3476
  }
3210
3477
  templatePartsToGoCode(parts, propsParams) {
3211
3478
  const segments = [];
@@ -3271,12 +3538,14 @@ ${goFields.join(`
3271
3538
  }
3272
3539
  const memo = memos.find((m) => m.name === getterName);
3273
3540
  if (memo) {
3274
- return computeMemoInitialValueOrNull(this.emitCtx, memo, signals, propsParams);
3541
+ return computeMemoInitialValueOrNull(this.emitCtx, memo, signals, propsParams, undefined, new Set([memo.name]));
3275
3542
  }
3276
3543
  }
3277
3544
  return null;
3278
3545
  }
3279
3546
  inferMemoType(memo, signals, propsParamMap) {
3547
+ if (isListFilterMemo(memo))
3548
+ return "[]any";
3280
3549
  if (memo.bodyIsTemplateLiteral)
3281
3550
  return "string";
3282
3551
  if (memo.computation.includes("*") || memo.computation.includes("/") || memo.computation.includes("+") || memo.computation.includes("-")) {
@@ -3673,15 +3942,19 @@ ${goFields.join(`
3673
3942
  if (objHO && (objHO.method === "find" || objHO.method === "findLast")) {
3674
3943
  const findResult = this.renderHigherOrderExpr(objHO, emit);
3675
3944
  if (findResult) {
3676
- return `{{with ${findResult}}}{{.${capitalizeFieldName(property)}}}{{end}}`;
3945
+ return `{{with ${findResult}}}{{.${goFieldNameForKey(property)}}}{{end}}`;
3677
3946
  }
3678
- const templateBlock = this.renderFindTemplateBlock(objHO, emit, capitalizeFieldName(property));
3947
+ const templateBlock = this.renderFindTemplateBlock(objHO, emit, goFieldNameForKey(property));
3679
3948
  if (templateBlock)
3680
3949
  return templateBlock;
3681
3950
  }
3682
3951
  if (object.kind === "identifier" && this.state.propsObjectName && object.name === this.state.propsObjectName) {
3683
3952
  return this.rootFieldRef(property);
3684
3953
  }
3954
+ if (this.isMapRootedContextChain(object)) {
3955
+ const objGo = emit(object);
3956
+ return `bf_get ${wrapIfMultiToken(objGo)} ${JSON.stringify(property)}`;
3957
+ }
3685
3958
  if (object.kind === "identifier") {
3686
3959
  const staticValue = this.resolveStaticRecordLiteralIndex(`${object.name}.${property}`);
3687
3960
  if (staticValue !== null)
@@ -3689,12 +3962,12 @@ ${goFields.join(`
3689
3962
  }
3690
3963
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
3691
3964
  if (object.kind === "identifier" && currentLoopParam && object.name === currentLoopParam) {
3692
- return `.${capitalizeFieldName(property)}`;
3965
+ return `.${goFieldNameForKey(property)}`;
3693
3966
  }
3694
3967
  const obj = emit(object);
3695
3968
  if (property === "length")
3696
3969
  return `len ${obj}`;
3697
- return `${obj}.${capitalizeFieldName(property)}`;
3970
+ return `${obj}.${goFieldNameForKey(property)}`;
3698
3971
  }
3699
3972
  indexAccess(object, index, emit) {
3700
3973
  return `index ${wrapIfMultiToken(emit(object))} ${wrapIfMultiToken(emit(index))}`;
@@ -3786,7 +4059,16 @@ ${goFields.join(`
3786
4059
  return `bf_arr ${parts.join(" ")}`;
3787
4060
  }
3788
4061
  objectLiteral(_properties, raw, _emit) {
3789
- return this.unsupported(raw, "object literal");
4062
+ this.state.errors.push({
4063
+ code: "BF101",
4064
+ severity: "error",
4065
+ message: `Expression not supported: ${raw}`,
4066
+ loc: this.makeLoc(),
4067
+ suggestion: {
4068
+ message: `Go templates have no object/map literal syntax, so the \`?? {}\` fallback can't render server-side. ${GO_REMEDIATION_OPTIONS}`
4069
+ }
4070
+ });
4071
+ return `""`;
3790
4072
  }
3791
4073
  static PREDICATE_METHODS = new Set([
3792
4074
  "filter",
@@ -3798,7 +4080,7 @@ ${goFields.join(`
3798
4080
  "some"
3799
4081
  ]);
3800
4082
  higherOrderShapeOf(node) {
3801
- const cb = asCallbackMethodCall(node);
4083
+ const cb = asCallbackMethodCall3(node);
3802
4084
  if (!cb)
3803
4085
  return null;
3804
4086
  if (!GoTemplateAdapter.PREDICATE_METHODS.has(cb.method))
@@ -3873,6 +4155,12 @@ ${goFields.join(`
3873
4155
  return evalForm;
3874
4156
  return this.pushCallbackBF101(method, true);
3875
4157
  }
4158
+ if (method === "map") {
4159
+ const evalForm = emitMapEval(recv, body, params[0] ?? "_", emit);
4160
+ if (evalForm !== null)
4161
+ return evalForm;
4162
+ return this.pushCallbackBF101(method, true);
4163
+ }
3876
4164
  return this.pushCallbackBF101(method, true);
3877
4165
  }
3878
4166
  arrayMethod(method, object, args, emit) {
@@ -3993,6 +4281,9 @@ ${goFields.join(`
3993
4281
  }
3994
4282
  }
3995
4283
  flatMethod(object, depth, emit) {
4284
+ if (typeof depth === "object") {
4285
+ return `bf_flat_dynamic ${wrapIfMultiToken(emit(object))} ${wrapIfMultiToken(emit(depth.expr))}`;
4286
+ }
3996
4287
  const d = depth === "infinity" ? -1 : depth;
3997
4288
  return `bf_flat ${wrapIfMultiToken(emit(object))} ${d}`;
3998
4289
  }
@@ -4226,6 +4517,9 @@ ${goFields.join(`
4226
4517
  if (expr.callee.kind === "identifier" && expr.args.length === 0) {
4227
4518
  return `$.${capitalizeFieldName(expr.callee.name)}`;
4228
4519
  }
4520
+ if (asCallbackMethodCall3(expr) !== null) {
4521
+ return this.refuseFilterExprNode(expr);
4522
+ }
4229
4523
  const result = this.renderFilterExpr(expr.callee, param, localVarMap);
4230
4524
  if (this.filterExprUnsupported)
4231
4525
  return "false";
@@ -4290,21 +4584,23 @@ ${goFields.join(`
4290
4584
  }
4291
4585
  return `or (${left}) (${right})`;
4292
4586
  }
4293
- default: {
4294
- this.filterExprUnsupported = true;
4295
- this.state.errors.push({
4296
- code: "BF101",
4297
- severity: "error",
4298
- message: `Filter predicate contains an expression that cannot be lowered to a Go template action: ${exprToString(expr)}`,
4299
- loc: this.makeLoc(),
4300
- suggestion: {
4301
- message: "Options:\n1. Use /* @client */ for client-side evaluation\n2. Rewrite the predicate to avoid nested higher-order methods (`.filter()` / `.map()` / etc. inside the predicate body)"
4302
- }
4303
- });
4304
- return "false";
4305
- }
4587
+ default:
4588
+ return this.refuseFilterExprNode(expr);
4306
4589
  }
4307
4590
  }
4591
+ refuseFilterExprNode(expr) {
4592
+ this.filterExprUnsupported = true;
4593
+ this.state.errors.push({
4594
+ code: "BF101",
4595
+ severity: "error",
4596
+ message: `Filter predicate contains an expression that cannot be lowered to a Go template action: ${exprToString(expr)}`,
4597
+ loc: this.makeLoc(),
4598
+ suggestion: {
4599
+ message: "Options:\n1. Use /* @client */ for client-side evaluation\n2. Rewrite the predicate to avoid nested higher-order methods (`.filter()` / `.map()` / etc. inside the predicate body)"
4600
+ }
4601
+ });
4602
+ return "false";
4603
+ }
4308
4604
  isGoFunctionCall(expr) {
4309
4605
  switch (expr.kind) {
4310
4606
  case "binary":
@@ -4558,7 +4854,7 @@ ${goFields.join(`
4558
4854
  });
4559
4855
  return plain("false");
4560
4856
  }
4561
- if (asCallbackMethodCall(expr)) {
4857
+ if (asCallbackMethodCall3(expr)) {
4562
4858
  const rendered = this.renderParsedExpr(expr);
4563
4859
  const split = this.splitPreamble(rendered);
4564
4860
  if (split)
@@ -4686,23 +4982,44 @@ ${goFields.join(`
4686
4982
  return plain(expr.raw);
4687
4983
  }
4688
4984
  }
4985
+ buildSegmentAccessor(base, segments) {
4986
+ let acc = base;
4987
+ for (const seg of segments) {
4988
+ acc = seg.kind === "field" ? `${acc}.${goFieldNameForKey(seg.key)}` : `(index ${acc} ${seg.index})`;
4989
+ }
4990
+ return acc;
4991
+ }
4689
4992
  buildDestructureBindingMap(loop, rangeVar) {
4690
- const m = new Map;
4993
+ const bindings = new Map;
4994
+ const restExcludes = new Map;
4995
+ const base = `$${rangeVar}`;
4691
4996
  for (const b of loop.paramBindings ?? []) {
4692
- if (b.rest) {
4693
- m.set(b.name, `$${rangeVar}`);
4997
+ const parent = this.buildSegmentAccessor(base, b.segments ?? []);
4998
+ if (!b.rest) {
4999
+ bindings.set(b.name, parent);
5000
+ } else if (b.rest.kind === "array") {
5001
+ bindings.set(b.name, `(bf_slice ${parent} ${b.rest.from})`);
4694
5002
  } else {
4695
- m.set(b.name, `$${rangeVar}.${capitalizeFieldName(b.path.slice(1))}`);
5003
+ bindings.set(b.name, parent);
5004
+ restExcludes.set(b.name, { parent, excludeKeys: b.rest.exclude.map((k) => k.key) });
4696
5005
  }
4697
5006
  }
4698
- return m;
5007
+ return { bindings, restExcludes };
5008
+ }
5009
+ lookupRestExclude(name) {
5010
+ for (let i = this.loopRestExcludeStack.length - 1;i >= 0; i--) {
5011
+ const info = this.loopRestExcludeStack[i].get(name);
5012
+ if (info)
5013
+ return info;
5014
+ }
5015
+ return;
4699
5016
  }
4700
5017
  renderLoop(loop) {
4701
5018
  if (loop.clientOnly) {
4702
5019
  return `{{bfComment "loop:${loop.markerId}"}}{{bfComment "/loop:${loop.markerId}"}}`;
4703
5020
  }
4704
5021
  const destructure = !!(loop.paramBindings && loop.paramBindings.length > 0);
4705
- const supportableDestructure = destructure && isLowerableObjectRestDestructure(loop);
5022
+ const supportableDestructure = destructure && isLowerableLoopDestructure(loop);
4706
5023
  if (destructure && !supportableDestructure) {
4707
5024
  this.state.errors.push({
4708
5025
  code: "BF104",
@@ -4717,6 +5034,21 @@ ${goFields.join(`
4717
5034
  }
4718
5035
  });
4719
5036
  }
5037
+ const arrayName = loop.array.trim();
5038
+ if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
5039
+ const arrayConst = this.state.localConstants.find((c) => c.name === arrayName);
5040
+ if (arrayConst && !arrayConst.isModule && arrayConst.parsed && !this.isStringExpr(arrayConst.parsed, new Set)) {
5041
+ this.state.errors.push({
5042
+ code: "BF101",
5043
+ severity: "error",
5044
+ 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.`,
5045
+ loc: loop.loc ?? this.makeLoc(),
5046
+ suggestion: {
5047
+ 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."
5048
+ }
5049
+ });
5050
+ }
5051
+ }
4720
5052
  let goArray = this.convertExpressionToGo(loop.array);
4721
5053
  const param = loop.param;
4722
5054
  let index = loop.index || "_";
@@ -4726,15 +5058,16 @@ ${goFields.join(`
4726
5058
  rangeIndex = param;
4727
5059
  rangeValue = "_";
4728
5060
  }
4729
- const childComponent = this.findChildComponent(loop.children);
4730
- if (childComponent) {
4731
- goArray = `.${childComponent.name}s`;
5061
+ if (loop.childComponent) {
5062
+ goArray = `.${loop.childComponent.name}s`;
4732
5063
  }
4733
5064
  this.inLoop = true;
4734
5065
  const addedLoopVars = [];
4735
5066
  let pushedBindingMap = false;
4736
5067
  if (supportableDestructure) {
4737
- this.loopBindingStack.push(this.buildDestructureBindingMap(loop, rangeValue));
5068
+ const built = this.buildDestructureBindingMap(loop, rangeValue);
5069
+ this.loopBindingStack.push(built.bindings);
5070
+ this.loopRestExcludeStack.push(built.restExcludes);
4738
5071
  pushedBindingMap = true;
4739
5072
  this.loopParamStack.push("");
4740
5073
  if (rangeIndex !== "_") {
@@ -4753,7 +5086,9 @@ ${goFields.join(`
4753
5086
  }
4754
5087
  }
4755
5088
  this.loopScalarItemStack.push(this.scalarLiteralLoopGoType(loop.arrayParsed, loop.itemType) !== null);
5089
+ this.loopWrapperStack.push(!!loop.childComponent);
4756
5090
  const children = this.renderChildren(loop.children);
5091
+ this.loopWrapperStack.pop();
4757
5092
  this.loopScalarItemStack.pop();
4758
5093
  const itemMarker = this.loopItemMarker(loop);
4759
5094
  for (const v of addedLoopVars) {
@@ -4764,8 +5099,10 @@ ${goFields.join(`
4764
5099
  this.loopVarRefCount.set(v, rc);
4765
5100
  }
4766
5101
  this.loopParamStack.pop();
4767
- if (pushedBindingMap)
5102
+ if (pushedBindingMap) {
4768
5103
  this.loopBindingStack.pop();
5104
+ this.loopRestExcludeStack.pop();
5105
+ }
4769
5106
  this.inLoop = false;
4770
5107
  if (loop.sortComparator) {
4771
5108
  const sortEmit = (e) => this.renderParsedExpr(e);
@@ -4794,24 +5131,6 @@ ${goFields.join(`
4794
5131
  }
4795
5132
  return "";
4796
5133
  }
4797
- findChildComponent(nodes) {
4798
- for (const node of nodes) {
4799
- if (node.type === "component") {
4800
- return node;
4801
- }
4802
- if (node.type === "element" && node.children) {
4803
- const found = this.findChildComponent(node.children);
4804
- if (found)
4805
- return found;
4806
- }
4807
- if (node.type === "fragment" && node.children) {
4808
- const found = this.findChildComponent(node.children);
4809
- if (found)
4810
- return found;
4811
- }
4812
- }
4813
- return null;
4814
- }
4815
5134
  queueDynamicChildrenDefine(comp) {
4816
5135
  const effectiveChildren = comp.children.length > 0 ? comp.children : this.jsxChildrenPropNodes(comp.props);
4817
5136
  if (effectiveChildren.length === 0)
@@ -4859,7 +5178,7 @@ ${goFields.join(`
4859
5178
  return this.renderChildren(comp.children);
4860
5179
  }
4861
5180
  let templateCall;
4862
- if (this.inLoop) {
5181
+ if (this.inLoop && (this.loopWrapperStack[this.loopWrapperStack.length - 1] ?? false)) {
4863
5182
  const loopBodyDefine = this.queueLoopBodyChildrenDefine(comp);
4864
5183
  if (loopBodyDefine) {
4865
5184
  const bodyData = this.loopScalarItemStack[this.loopScalarItemStack.length - 1] ? ".BfLoopItem" : ".";
@@ -4867,6 +5186,12 @@ ${goFields.join(`
4867
5186
  } else {
4868
5187
  templateCall = `{{template "${comp.name}" .}}`;
4869
5188
  }
5189
+ } else if (this.inLoop && comp.slotId) {
5190
+ const suffix = slotIdToFieldSuffix(comp.slotId);
5191
+ const loopBodyDefine = this.queueLoopBodyChildrenDefine(comp);
5192
+ templateCall = loopBodyDefine ? `{{template "${comp.name}" (bf_with_children $.${comp.name}${suffix} (bf_tmpl "${loopBodyDefine}" .))}}` : `{{template "${comp.name}" $.${comp.name}${suffix}}}`;
5193
+ } else if (this.inLoop) {
5194
+ templateCall = `{{template "${comp.name}" .}}`;
4870
5195
  } else if (comp.slotId) {
4871
5196
  const suffix = slotIdToFieldSuffix(comp.slotId);
4872
5197
  const childrenDefine = this.queueDynamicChildrenDefine(comp);
@@ -4962,6 +5287,12 @@ ${children}`;
4962
5287
  if (currentLoopParam && trimmed === currentLoopParam) {
4963
5288
  return `{{bf_spread_attrs .}}`;
4964
5289
  }
5290
+ const restInfo = this.lookupRestExclude(trimmed);
5291
+ if (restInfo) {
5292
+ const excludeArgs = restInfo.excludeKeys.map((k) => JSON.stringify(k)).join(" ");
5293
+ const omitArgs = excludeArgs ? `${restInfo.parent} ${excludeArgs}` : restInfo.parent;
5294
+ return `{{bf_spread_attrs (bf_omit ${omitArgs})}}`;
5295
+ }
4965
5296
  const goExpr = this.convertExpressionToGo(value.expr);
4966
5297
  return `{{bf_spread_attrs ${goExpr}}}`;
4967
5298
  }