@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
package/dist/build.js CHANGED
@@ -371,17 +371,18 @@ import {
371
371
  isSupported,
372
372
  exprToString,
373
373
  identifierPath,
374
- asCallbackMethodCall,
374
+ asCallbackMethodCall as asCallbackMethodCall3,
375
375
  sortComparatorFromArrow,
376
376
  emitParsedExpr,
377
377
  emitIRNode,
378
378
  emitAttrValue,
379
379
  augmentInheritedPropAccesses,
380
380
  collectContextConsumers,
381
- isLowerableObjectRestDestructure,
381
+ isLowerableLoopDestructure,
382
382
  collectModuleStringConsts as collectModuleStringConstsShared,
383
- searchParamsLocalNames,
384
- prepareLoweringMatchers
383
+ prepareLoweringMatchers,
384
+ envSignalReaderFor,
385
+ computeSsrSeedPlan
385
386
  } from "@barefootjs/jsx";
386
387
  import { findInterpolationEnd } from "@barefootjs/jsx/scanner";
387
388
  import { BF_REGION } from "@barefootjs/shared";
@@ -462,6 +463,13 @@ function capitalizeFieldName(name) {
462
463
  }
463
464
  return name.charAt(0).toUpperCase() + name.slice(1);
464
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
+ }
465
473
  function slotIdToFieldSuffix(slotId) {
466
474
  const cleanId = slotId.startsWith("^") ? slotId.slice(1) : slotId;
467
475
  const match = cleanId.match(/^s(\d+)$/);
@@ -564,6 +572,13 @@ function emitFlatMapEval(recv, body, param, emit) {
564
572
  const env = emitEvalEnvArg(body, [param], emit);
565
573
  return `bf_flat_map_eval ${wrapIfMultiToken(recv)} "${escapeGoString(json)}" "${param}" ${env}`;
566
574
  }
575
+ function emitMapEval(recv, body, param, emit) {
576
+ const json = serializeParsedExpr(body);
577
+ if (json === null)
578
+ return null;
579
+ const env = emitEvalEnvArg(body, [param], emit);
580
+ return `bf_map_eval ${wrapIfMultiToken(recv)} "${escapeGoString(json)}" "${param}" ${env}`;
581
+ }
567
582
  function stringTolerantEqOperands(l, r) {
568
583
  const isStrLit = (x) => /^"(?:[^"\\]|\\.)*"$/.test(x);
569
584
  if (isStrLit(l) === isStrLit(r))
@@ -677,6 +692,9 @@ class CompileState {
677
692
  currentTypeDefinitions = [];
678
693
  contextConsumers = [];
679
694
  searchParamsLocals = new Set;
695
+ envSignalReadersByLocal = new Map;
696
+ ssrSeedPlan = { baseScope: [], steps: [] };
697
+ hoistedMemoLocals = new Map;
680
698
  loweringMatchers = [];
681
699
  nillablePropNames = new Set;
682
700
  rootScopeNodes = new Set;
@@ -1024,9 +1042,12 @@ function forEachValueChild(n, visit) {
1024
1042
  // src/adapter/expr/url-builder.ts
1025
1043
  import {
1026
1044
  parseExpression,
1027
- stringifyParsedExpr
1045
+ stringifyParsedExpr,
1046
+ isValidHelperId
1028
1047
  } from "@barefootjs/jsx";
1029
- var GO_HELPER_NAMES = { query: "bf_query" };
1048
+ function goHelperName(helper) {
1049
+ return isValidHelperId(helper) ? `bf_${helper}` : null;
1050
+ }
1030
1051
  var BOOL_COMPARISON_OPS = new Set([
1031
1052
  "==",
1032
1053
  "===",
@@ -1069,7 +1090,7 @@ function lowerRegisteredCall(ctx, jsExpr, preParsed) {
1069
1090
  return null;
1070
1091
  }
1071
1092
  function renderLoweringNode(ctx, node) {
1072
- const helper = GO_HELPER_NAMES[node.helper];
1093
+ const helper = goHelperName(node.helper);
1073
1094
  if (!helper)
1074
1095
  return null;
1075
1096
  const lowerExpr = (n) => ctx.convertExpressionToGo(stringifyParsedExpr(n), undefined, n);
@@ -1109,7 +1130,7 @@ function typeInfoToGo(ctx, typeInfo, defaultValue) {
1109
1130
  case "object":
1110
1131
  return "map[string]interface{}";
1111
1132
  case "interface":
1112
- if (typeInfo.raw && ctx.state.localTypeNames.has(typeInfo.raw)) {
1133
+ if (typeInfo.raw && (ctx.state.localStructFields.has(typeInfo.raw) || ctx.state.localTypeAliases.has(typeInfo.raw))) {
1113
1134
  return typeInfo.raw;
1114
1135
  }
1115
1136
  if (typeInfo.raw) {
@@ -1142,7 +1163,7 @@ function tsTypeStringToGo(ctx, tsType) {
1142
1163
  const arrayMatch = t.match(/^Array<(.+)>$/);
1143
1164
  if (arrayMatch)
1144
1165
  return `[]${tsTypeStringToGo(ctx, arrayMatch[1])}`;
1145
- if (ctx.state.localTypeNames.has(t))
1166
+ if (ctx.state.localStructFields.has(t) || ctx.state.localTypeAliases.has(t))
1146
1167
  return t;
1147
1168
  return "interface{}";
1148
1169
  }
@@ -1164,6 +1185,24 @@ function inferTypeFromValue(value) {
1164
1185
  }
1165
1186
 
1166
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
+ }
1167
1206
  function parsedLiteralToGo(ctx, expr, typeInfo) {
1168
1207
  if (expr.kind === "unary" && expr.op === "-" && expr.argument.kind === "literal" && expr.argument.literalType === "number") {
1169
1208
  return expr.argument.raw !== undefined ? `-${expr.argument.raw}` : null;
@@ -1204,9 +1243,16 @@ function parsedLiteralToGo(ctx, expr, typeInfo) {
1204
1243
  const goField = structFields.get(prop.key);
1205
1244
  if (!goField)
1206
1245
  return null;
1207
- if (prop.value.kind === "object-literal" || prop.value.kind === "array-literal")
1208
- return null;
1209
- const go = parsedLiteralToGo(ctx, prop.value);
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
+ }
1210
1256
  if (go === null)
1211
1257
  return null;
1212
1258
  entries.push(`${goField}: ${go}`);
@@ -1317,8 +1363,17 @@ function getSignalInitialValueAsGo(ctx, initialValue, propsParams, propFallbackV
1317
1363
  }
1318
1364
 
1319
1365
  // src/adapter/memo/memo-type.ts
1366
+ import { asCallbackMethodCall } from "@barefootjs/jsx";
1367
+ function isListFilterMemo(memo) {
1368
+ if (!memo.parsed)
1369
+ return false;
1370
+ const cb = asCallbackMethodCall(memo.parsed);
1371
+ return cb !== null && cb.method === "filter";
1372
+ }
1320
1373
  function isBooleanMemo(ctx, memo, signals, propsParamMap) {
1321
1374
  const c = memo.computation;
1375
+ if (isListFilterMemo(memo))
1376
+ return false;
1322
1377
  if (isStringTernaryMemo(ctx, memo.parsed))
1323
1378
  return false;
1324
1379
  if (/(!==|===|!=(?!=)|==(?!=))/.test(c))
@@ -1609,6 +1664,14 @@ function computeObjectMemoInitialValue(ctx, memo) {
1609
1664
  }`;
1610
1665
  }
1611
1666
 
1667
+ // src/adapter/memo/memo-compute.ts
1668
+ import {
1669
+ asCallbackMethodCall as asCallbackMethodCall2,
1670
+ freeVarsInBody as freeVarsInBody2,
1671
+ materializeGetterCalls,
1672
+ serializeParsedExpr as serializeParsedExpr2
1673
+ } from "@barefootjs/jsx";
1674
+
1612
1675
  // src/adapter/memo/template-interp.ts
1613
1676
  import ts from "typescript";
1614
1677
  function computeTemplateLiteralMemoInitialValue(ctx, memo, propsParams) {
@@ -1749,8 +1812,49 @@ function propsAccessNameFromParsed(ctx, node) {
1749
1812
 
1750
1813
  // src/adapter/memo/memo-compute.ts
1751
1814
  var EMPTY_PROP_FALLBACK_VARS2 = new Map;
1815
+ function getterCallName(e) {
1816
+ return e.kind === "call" && e.callee.kind === "identifier" && e.args.length === 0 ? e.callee.name : null;
1817
+ }
1818
+ function propsMemberName(e) {
1819
+ return e.kind === "member" && !e.computed && e.object.kind === "identifier" && e.object.name === "props" ? e.property : null;
1820
+ }
1821
+ function matchFilterArmMemo(ctx, body, signals, propsParams) {
1822
+ const cb = asCallbackMethodCall2(body);
1823
+ if (!cb || cb.method !== "filter")
1824
+ return null;
1825
+ const propName = propsMemberName(cb.object);
1826
+ const param = propName ? propsParams.find((p) => p.name === propName) : undefined;
1827
+ if (!propName || !param)
1828
+ return null;
1829
+ const knownGetterNames = new Set(ctx.state.ssrSeedPlan.steps.filter((s) => s.kind !== "env-reader").map((s) => s.name));
1830
+ const materialized = materializeGetterCalls(cb.arrow.body, knownGetterNames);
1831
+ const predJSON = serializeParsedExpr2(materialized);
1832
+ if (predJSON === null)
1833
+ return null;
1834
+ const paramName = cb.arrow.params[0] ?? "_";
1835
+ const freeVars = freeVarsInBody2(materialized, new Set(cb.arrow.params));
1836
+ return { propName, predJSON, paramName, freeVars };
1837
+ }
1838
+ function filterArmEarlierSiblingRefs(ctx, memo, signals, propsParams) {
1839
+ if (!memo.parsed)
1840
+ return [];
1841
+ const match = matchFilterArmMemo(ctx, memo.parsed, signals, propsParams);
1842
+ if (!match)
1843
+ return [];
1844
+ const memos = ctx.state.currentMemos ?? [];
1845
+ const currentIndex = memos.findIndex((m) => m.name === memo.name);
1846
+ if (currentIndex < 0)
1847
+ return [];
1848
+ const refs = [];
1849
+ for (const name of match.freeVars) {
1850
+ const idx = memos.findIndex((m) => m.name === name);
1851
+ if (idx >= 0 && idx < currentIndex)
1852
+ refs.push(name);
1853
+ }
1854
+ return refs;
1855
+ }
1752
1856
  function computeMemoInitialValue(ctx, memo, signals, propsParams, propFallbackVars = EMPTY_PROP_FALLBACK_VARS2, goType) {
1753
- const resolved = computeMemoInitialValueOrNull(ctx, memo, signals, propsParams, propFallbackVars);
1857
+ const resolved = computeMemoInitialValueOrNull(ctx, memo, signals, propsParams, propFallbackVars, new Set([memo.name]));
1754
1858
  if (resolved !== null)
1755
1859
  return resolved;
1756
1860
  if (goType === "bool")
@@ -1762,15 +1866,79 @@ function computeMemoInitialValue(ctx, memo, signals, propsParams, propFallbackVa
1762
1866
  }
1763
1867
  return "0";
1764
1868
  }
1765
- function memoInitialFromParsedBody(ctx, body, signals, propsParams, propFallbackVars) {
1869
+ function memoInitialFromParsedBody(ctx, body, signals, propsParams, propFallbackVars, currentMemoName, resolving = new Set) {
1766
1870
  const propRef = (propName) => {
1767
1871
  const hoisted = propFallbackVars.get(propName);
1768
1872
  if (hoisted)
1769
1873
  return hoisted.varName;
1770
1874
  return `in.${capitalizeFieldName(propName)}`;
1771
1875
  };
1772
- const getterCallName = (e) => e.kind === "call" && e.callee.kind === "identifier" && e.args.length === 0 ? e.callee.name : null;
1773
- const propsMemberName = (e) => e.kind === "member" && !e.computed && e.object.kind === "identifier" && e.object.name === "props" ? e.property : null;
1876
+ const envGetKey = (e) => {
1877
+ if (e.kind !== "call" || e.callee.kind !== "member" || e.callee.computed)
1878
+ return null;
1879
+ const recvName = getterCallName(e.callee.object);
1880
+ if (recvName === null)
1881
+ return null;
1882
+ const reader = ctx.state.envSignalReadersByLocal.get(recvName);
1883
+ if (!reader || !reader.methods.has(e.callee.property))
1884
+ return null;
1885
+ const arg = e.args[0];
1886
+ if (!arg || arg.kind !== "literal" || arg.literalType !== "string")
1887
+ return null;
1888
+ return { key: String(arg.value), fieldName: capitalizeFieldName(reader.canonicalName) };
1889
+ };
1890
+ {
1891
+ const m = envGetKey(body);
1892
+ if (m !== null)
1893
+ return `in.${m.fieldName}.Get(${JSON.stringify(m.key)})`;
1894
+ }
1895
+ if (body.kind === "logical" && (body.op === "??" || body.op === "||") && body.right.kind === "literal" && body.right.literalType === "string") {
1896
+ const m = envGetKey(body.left);
1897
+ if (m !== null) {
1898
+ const def = JSON.stringify(String(body.right.value));
1899
+ return `func() string { if v := in.${m.fieldName}.Get(${JSON.stringify(m.key)}); v != "" { return v }; return ${def} }()`;
1900
+ }
1901
+ }
1902
+ {
1903
+ const match = matchFilterArmMemo(ctx, body, signals, propsParams);
1904
+ if (match) {
1905
+ const { propName, predJSON, paramName, freeVars } = match;
1906
+ const currentIndex = (ctx.state.currentMemos ?? []).findIndex((m) => m.name === currentMemoName);
1907
+ const envEntries = [];
1908
+ let unresolved = false;
1909
+ for (const name of freeVars) {
1910
+ let goExpr = null;
1911
+ const siblingIndex = (ctx.state.currentMemos ?? []).findIndex((m) => m.name === name);
1912
+ if (siblingIndex >= 0) {
1913
+ const siblingMemo = ctx.state.currentMemos[siblingIndex];
1914
+ const eligible = currentIndex >= 0 && siblingIndex < currentIndex && !resolving.has(siblingMemo.name);
1915
+ if (eligible) {
1916
+ const hoisted = ctx.state.hoistedMemoLocals.get(siblingMemo.name);
1917
+ goExpr = hoisted ?? computeMemoInitialValueOrNull(ctx, siblingMemo, signals, propsParams, propFallbackVars, new Set([...resolving, siblingMemo.name]));
1918
+ }
1919
+ } else {
1920
+ const sig = signals.find((s) => s.getter === name);
1921
+ if (sig) {
1922
+ goExpr = getSignalInitialValueAsGo(ctx, sig.initialValue, propsParams, propFallbackVars);
1923
+ } else {
1924
+ const p = propsParams.find((pp) => pp.name === name);
1925
+ if (p)
1926
+ goExpr = propRef(name);
1927
+ }
1928
+ }
1929
+ if (goExpr === null) {
1930
+ unresolved = true;
1931
+ break;
1932
+ }
1933
+ envEntries.push(`${JSON.stringify(name)}: ${goExpr}`);
1934
+ }
1935
+ if (!unresolved) {
1936
+ const itemsField = `in.${capitalizeFieldName(propName)}`;
1937
+ const envMap = `map[string]any{${envEntries.join(", ")}}`;
1938
+ return `bf.FilterEval(${itemsField}, "${escapeGoString(predJSON)}", ${JSON.stringify(paramName)}, ${envMap})`;
1939
+ }
1940
+ }
1941
+ }
1774
1942
  if (body.kind === "binary" && ["===", "!==", "==", "!="].includes(body.op) && body.right.kind === "literal" && body.right.literalType === "string") {
1775
1943
  const depName = getterCallName(body.left);
1776
1944
  if (depName) {
@@ -1809,8 +1977,8 @@ function memoInitialFromParsedBody(ctx, body, signals, propsParams, propFallback
1809
1977
  condGo = getSignalInitialValueAsGo(ctx, condSignal.initialValue, propsParams, propFallbackVars);
1810
1978
  } else {
1811
1979
  const condMemo = (ctx.state.currentMemos ?? []).find((m) => m.name === condName);
1812
- if (condMemo) {
1813
- condGo = computeMemoInitialValueOrNull(ctx, condMemo, signals, propsParams, propFallbackVars);
1980
+ if (condMemo && !resolving.has(condMemo.name)) {
1981
+ condGo = computeMemoInitialValueOrNull(ctx, condMemo, signals, propsParams, propFallbackVars, new Set([...resolving, condMemo.name]));
1814
1982
  }
1815
1983
  }
1816
1984
  if (condGo === "true")
@@ -1884,17 +2052,17 @@ function memoInitialFromParsedBody(ctx, body, signals, propsParams, propFallback
1884
2052
  }
1885
2053
  return null;
1886
2054
  }
1887
- function computeMemoInitialValueOrNull(ctx, memo, signals, propsParams, propFallbackVars = EMPTY_PROP_FALLBACK_VARS2) {
2055
+ function computeMemoInitialValueOrNull(ctx, memo, signals, propsParams, propFallbackVars = EMPTY_PROP_FALLBACK_VARS2, resolving = new Set) {
1888
2056
  const computation = memo.computation;
1889
2057
  const tmplMemo = computeTemplateLiteralMemoInitialValue(ctx, memo, propsParams);
1890
2058
  if (tmplMemo !== null)
1891
2059
  return tmplMemo;
1892
2060
  if (memo.parsed) {
1893
- const fromParsed = memoInitialFromParsedBody(ctx, memo.parsed, signals, propsParams, propFallbackVars);
2061
+ const fromParsed = memoInitialFromParsedBody(ctx, memo.parsed, signals, propsParams, propFallbackVars, memo.name, resolving);
1894
2062
  if (fromParsed !== null)
1895
2063
  return fromParsed;
1896
2064
  }
1897
- const cmpTernary = computeComparisonTernaryGo(ctx, memo.parsed, signals, propsParams, propFallbackVars);
2065
+ const cmpTernary = computeComparisonTernaryGo(ctx, memo.parsed, signals, propsParams, propFallbackVars, resolving);
1898
2066
  if (cmpTernary !== null)
1899
2067
  return cmpTernary;
1900
2068
  const blockReturn = resolveBlockBodyMemoModuleConst(ctx, memo, signals);
@@ -1906,20 +2074,22 @@ function computeMemoInitialValueOrNull(ctx, memo, signals, propsParams, propFall
1906
2074
  return objMemo;
1907
2075
  return null;
1908
2076
  }
1909
- function resolveGetterValueAsGo(ctx, name, signals, propsParams, propFallbackVars) {
2077
+ function resolveGetterValueAsGo(ctx, name, signals, propsParams, propFallbackVars, resolving = new Set) {
1910
2078
  const signal = signals.find((s) => s.getter === name);
1911
2079
  if (signal) {
1912
2080
  return getSignalInitialValueAsGo(ctx, signal.initialValue, propsParams, propFallbackVars);
1913
2081
  }
1914
2082
  const memo = (ctx.state.currentMemos ?? []).find((m) => m.name === name);
1915
2083
  if (memo) {
2084
+ if (resolving.has(memo.name))
2085
+ return null;
1916
2086
  const stripped = memo.computation.replace(/^\(\)\s*=>\s*/, "");
1917
2087
  const fb = ctx.extractPropFallback(stripped);
1918
2088
  if (fb && capitalizeFieldName(fb.propName) === capitalizeFieldName(memo.name)) {
1919
2089
  const field = `in.${capitalizeFieldName(fb.propName)}`;
1920
2090
  return `func() interface{} { v := interface{}(${field}); if v == nil || v == "" { return ${fb.goFallback} }; return v }()`;
1921
2091
  }
1922
- return computeMemoInitialValueOrNull(ctx, memo, signals, propsParams, propFallbackVars);
2092
+ return computeMemoInitialValueOrNull(ctx, memo, signals, propsParams, propFallbackVars, new Set([...resolving, memo.name]));
1923
2093
  }
1924
2094
  const param = propsParams.find((p) => p.name === name);
1925
2095
  if (param) {
@@ -1928,7 +2098,7 @@ function resolveGetterValueAsGo(ctx, name, signals, propsParams, propFallbackVar
1928
2098
  }
1929
2099
  return null;
1930
2100
  }
1931
- function computeComparisonTernaryGo(ctx, parsed, signals, propsParams, propFallbackVars) {
2101
+ function computeComparisonTernaryGo(ctx, parsed, signals, propsParams, propFallbackVars, resolving = new Set) {
1932
2102
  if (!parsed || parsed.kind !== "conditional")
1933
2103
  return null;
1934
2104
  const cond = parsed.test;
@@ -1953,16 +2123,16 @@ function computeComparisonTernaryGo(ctx, parsed, signals, propsParams, propFallb
1953
2123
  const f = branch(parsed.alternate);
1954
2124
  if (t === null || f === null)
1955
2125
  return null;
1956
- const condGo = resolveComparisonOperandGo(ctx, cond.left, signals, propsParams, propFallbackVars);
2126
+ const condGo = resolveComparisonOperandGo(ctx, cond.left, signals, propsParams, propFallbackVars, resolving);
1957
2127
  if (condGo === null)
1958
2128
  return null;
1959
2129
  const eqBranch = isEq ? t : f;
1960
2130
  const neBranch = isEq ? f : t;
1961
2131
  return `func() string { if ${condGo} == ${JSON.stringify(cond.right.value)} { return ${eqBranch} }; return ${neBranch} }()`;
1962
2132
  }
1963
- function resolveComparisonOperandGo(ctx, node, signals, propsParams, propFallbackVars) {
2133
+ function resolveComparisonOperandGo(ctx, node, signals, propsParams, propFallbackVars, resolving = new Set) {
1964
2134
  if (node.kind === "call" && node.callee.kind === "identifier" && node.args.length === 0) {
1965
- return resolveGetterValueAsGo(ctx, node.callee.name, signals, propsParams, propFallbackVars);
2135
+ return resolveGetterValueAsGo(ctx, node.callee.name, signals, propsParams, propFallbackVars, resolving);
1966
2136
  }
1967
2137
  if (node.kind === "logical" && node.op === "??" && node.right.kind === "literal" && node.right.literalType === "string") {
1968
2138
  const propName = propsAccessNameFromParsed2(ctx, node.left);
@@ -2320,8 +2490,10 @@ class GoTemplateAdapter extends BaseAdapter {
2320
2490
  inLoop = false;
2321
2491
  loopParamStack = [];
2322
2492
  loopScalarItemStack = [];
2493
+ loopWrapperStack = [];
2323
2494
  loopVarRefCount = new Map;
2324
2495
  loopBindingStack = [];
2496
+ loopRestExcludeStack = [];
2325
2497
  childComponentShapes = new Map;
2326
2498
  childContextConsumers = new Map;
2327
2499
  constructor(options = {}) {
@@ -2341,7 +2513,18 @@ class GoTemplateAdapter extends BaseAdapter {
2341
2513
  this.state.currentMemos = ir.metadata.memos ?? [];
2342
2514
  this.state.currentTypeDefinitions = ir.metadata.typeDefinitions ?? [];
2343
2515
  this.state.contextConsumers = collectContextConsumers(ir.metadata);
2344
- this.state.searchParamsLocals = searchParamsLocalNames(ir.metadata);
2516
+ this.state.ssrSeedPlan = ir.metadata.ssrSeedPlan ?? computeSsrSeedPlan(ir.metadata);
2517
+ this.state.envSignalReadersByLocal = new Map;
2518
+ this.state.searchParamsLocals = new Set;
2519
+ for (const step of this.state.ssrSeedPlan.steps) {
2520
+ if (step.kind !== "env-reader")
2521
+ continue;
2522
+ const reader = envSignalReaderFor(step.reader.key);
2523
+ if (reader)
2524
+ this.state.envSignalReadersByLocal.set(step.name, reader);
2525
+ if (step.reader.key === "search")
2526
+ this.state.searchParamsLocals.add(step.name);
2527
+ }
2345
2528
  this.state.loweringMatchers = prepareLoweringMatchers(ir.metadata);
2346
2529
  augmentInheritedPropAccesses(ir);
2347
2530
  }
@@ -2503,11 +2686,22 @@ ${scriptRegistrations}${templateBody}
2503
2686
  contextFieldName(c) {
2504
2687
  return capitalizeFieldName(c.localName);
2505
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
+ }
2506
2698
  contextConsumerGoType(c) {
2507
2699
  if (typeof c.defaultValue === "number")
2508
2700
  return "int";
2509
2701
  if (typeof c.defaultValue === "boolean")
2510
2702
  return "bool";
2703
+ if (c.defaultKind === "object")
2704
+ return "map[string]interface{}";
2511
2705
  return "string";
2512
2706
  }
2513
2707
  contextConsumerGoDefault(c) {
@@ -2517,6 +2711,8 @@ ${scriptRegistrations}${templateBody}
2517
2711
  return String(c.defaultValue);
2518
2712
  if (typeof c.defaultValue === "string")
2519
2713
  return `"${escapeGoString(c.defaultValue)}"`;
2714
+ if (c.defaultKind === "object")
2715
+ return "map[string]interface{}{}";
2520
2716
  return '""';
2521
2717
  }
2522
2718
  nonCollidingContextConsumers(taken) {
@@ -2563,12 +2759,15 @@ ${goFields.join(`
2563
2759
  }
2564
2760
  structFieldsFor(td) {
2565
2761
  const fields = [];
2762
+ const seenGoNames = new Set;
2566
2763
  for (const prop of td.properties ?? []) {
2567
- if (!GO_IDENTIFIER.test(prop.name))
2764
+ const goName = goFieldNameForKey(prop.name);
2765
+ if (seenGoNames.has(goName))
2568
2766
  continue;
2767
+ seenGoNames.add(goName);
2569
2768
  fields.push({
2570
2769
  tsName: prop.name,
2571
- goName: capitalizeFieldName(prop.name),
2770
+ goName,
2572
2771
  goType: typeInfoToGo(this.emitCtx, prop.type)
2573
2772
  });
2574
2773
  }
@@ -2796,10 +2995,10 @@ ${goFields.join(`
2796
2995
  }
2797
2996
  return "interface{}";
2798
2997
  }
2799
- collectBodyChildInstances(bodyChildren) {
2998
+ collectBodyChildInstances(bodyChildren, propsParams = []) {
2800
2999
  const result = [];
2801
3000
  for (const child of bodyChildren) {
2802
- this.collectStaticChildInstancesRecursive(child, result, false, new Map);
3001
+ this.collectStaticChildInstancesRecursive(child, result, false, new Map, propsParams);
2803
3002
  }
2804
3003
  return result;
2805
3004
  }
@@ -2845,6 +3044,28 @@ ${goFields.join(`
2845
3044
  if (propFallbackVars.size > 0)
2846
3045
  lines.push("");
2847
3046
  this.emitDynamicBodyWrappers(lines, ir, componentName, dynamicWithBody, propFallbackVars, emittedWrapperVars);
3047
+ const memoPropsParamMap = new Map(ir.metadata.propsParams.map((p) => [p.name, p]));
3048
+ this.state.hoistedMemoLocals = new Map;
3049
+ const hoistNames = new Set;
3050
+ for (const memo of ir.metadata.memos) {
3051
+ for (const name of filterArmEarlierSiblingRefs(this.emitCtx, memo, ir.metadata.signals, ir.metadata.propsParams)) {
3052
+ hoistNames.add(name);
3053
+ }
3054
+ }
3055
+ if (hoistNames.size > 0) {
3056
+ for (const memo of ir.metadata.memos) {
3057
+ if (!hoistNames.has(memo.name))
3058
+ continue;
3059
+ const goType = this.inferMemoType(memo, ir.metadata.signals, memoPropsParamMap);
3060
+ const value = computeMemoInitialValue(this.emitCtx, memo, ir.metadata.signals, ir.metadata.propsParams, propFallbackVars, goType);
3061
+ let localName = `memo${capitalizeFieldName(memo.name)}`;
3062
+ while (GO_KEYWORDS.has(localName))
3063
+ localName += "_";
3064
+ lines.push(` var ${localName} ${goType} = ${value}`);
3065
+ this.state.hoistedMemoLocals.set(memo.name, localName);
3066
+ }
3067
+ lines.push("");
3068
+ }
2848
3069
  lines.push(` return ${propsTypeName}{`);
2849
3070
  lines.push("\t\tScopeID: scopeID,");
2850
3071
  lines.push("\t\tBfParent: in.BfParent,");
@@ -2918,11 +3139,15 @@ ${goFields.join(`
2918
3139
  continue;
2919
3140
  lines.push(` ${nested.name}s: ${varName},`);
2920
3141
  }
2921
- const memoPropsParamMap = new Map(ir.metadata.propsParams.map((p) => [p.name, p]));
2922
3142
  for (const memo of ir.metadata.memos) {
2923
3143
  const fieldName = capitalizeFieldName(memo.name);
2924
3144
  if (propFieldNames.has(fieldName))
2925
3145
  continue;
3146
+ const hoistedLocal = this.state.hoistedMemoLocals.get(memo.name);
3147
+ if (hoistedLocal) {
3148
+ lines.push(` ${fieldName}: ${hoistedLocal},`);
3149
+ continue;
3150
+ }
2926
3151
  const goType = this.inferMemoType(memo, ir.metadata.signals, memoPropsParamMap);
2927
3152
  const memoValue = computeMemoInitialValue(this.emitCtx, memo, ir.metadata.signals, ir.metadata.propsParams, propFallbackVars, goType);
2928
3153
  lines.push(` ${fieldName}: ${memoValue},`);
@@ -2943,7 +3168,7 @@ ${goFields.join(`
2943
3168
  for (const c of this.nonCollidingContextConsumers(takenInit)) {
2944
3169
  const field = this.contextFieldName(c);
2945
3170
  const def = this.contextConsumerGoDefault(c);
2946
- 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);
2947
3172
  lines.push(` ${field}: ${defaulted},`);
2948
3173
  }
2949
3174
  this.emitStaticChildInstances(lines, ir);
@@ -2952,7 +3177,7 @@ ${goFields.join(`
2952
3177
  lines.push("}");
2953
3178
  }
2954
3179
  emitStaticChildInstances(lines, ir) {
2955
- const staticChildren = this.collectStaticChildInstances(ir.root);
3180
+ const staticChildren = this.collectStaticChildInstances(ir.root, ir.metadata.propsParams);
2956
3181
  for (const child of staticChildren) {
2957
3182
  lines.push(` ${child.fieldName}: New${child.name}Props(${child.name}Input{`);
2958
3183
  lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
@@ -3084,7 +3309,7 @@ ${goFields.join(`
3084
3309
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
3085
3310
  const wrapperType = this.loopBodyWrapperName(componentName, nested);
3086
3311
  const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
3087
- const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
3312
+ const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren, ir.metadata.propsParams);
3088
3313
  for (const child of bodyChildInstances) {
3089
3314
  const childVar = `child_${child.fieldName}`;
3090
3315
  lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
@@ -3161,7 +3386,7 @@ ${goFields.join(`
3161
3386
  const wrapperType = this.loopBodyWrapperName(componentName, nested);
3162
3387
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
3163
3388
  const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
3164
- const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
3389
+ const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren, ir.metadata.propsParams);
3165
3390
  for (const child of bodyChildInstances) {
3166
3391
  const childVar = `child_${child.fieldName}`;
3167
3392
  lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
@@ -3401,7 +3626,7 @@ ${goFields.join(`
3401
3626
  lines.push(` ${nested.name}s []${elemType} \`json:"${jsonTag}"\``);
3402
3627
  }
3403
3628
  }
3404
- const staticChildren = this.collectStaticChildInstances(ir.root);
3629
+ const staticChildren = this.collectStaticChildInstances(ir.root, ir.metadata.propsParams);
3405
3630
  for (const child of staticChildren) {
3406
3631
  lines.push(` ${child.fieldName} ${child.name}Props \`json:"-"\``);
3407
3632
  }
@@ -3413,9 +3638,9 @@ ${goFields.join(`
3413
3638
  toJsonTag(name) {
3414
3639
  return name.charAt(0).toLowerCase() + name.slice(1);
3415
3640
  }
3416
- collectStaticChildInstances(node) {
3641
+ collectStaticChildInstances(node, propsParams = []) {
3417
3642
  const result = [];
3418
- this.collectStaticChildInstancesRecursive(node, result, false, new Map);
3643
+ this.collectStaticChildInstancesRecursive(node, result, false, new Map, propsParams);
3419
3644
  return result;
3420
3645
  }
3421
3646
  extractTextChildren(children) {
@@ -3460,12 +3685,12 @@ ${goFields.join(`
3460
3685
  return null;
3461
3686
  return withSentinel.split(GoTemplateAdapter.SCOPE_SENTINEL).map((seg) => JSON.stringify(seg)).join(" + scopeID + ");
3462
3687
  }
3463
- collectStaticChildInstancesRecursive(node, result, inLoop, providerCtx) {
3688
+ collectStaticChildInstancesRecursive(node, result, inLoop, providerCtx, propsParams = []) {
3464
3689
  if (node.type === "component") {
3465
3690
  const comp = node;
3466
3691
  if (comp.dynamicTag) {
3467
3692
  for (const child of comp.children) {
3468
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3693
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3469
3694
  }
3470
3695
  return;
3471
3696
  }
@@ -3483,69 +3708,111 @@ ${goFields.join(`
3483
3708
  contextBindings: providerCtx.size > 0 ? providerCtx : undefined
3484
3709
  });
3485
3710
  for (const child of effectiveChildren) {
3486
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3711
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3487
3712
  }
3488
3713
  }
3489
3714
  if (comp.name === "Portal" && comp.children) {
3490
3715
  for (const child of comp.children) {
3491
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3716
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3492
3717
  }
3493
3718
  }
3494
3719
  } else if (node.type === "loop") {
3495
3720
  const loop = node;
3496
3721
  for (const child of loop.children) {
3497
- this.collectStaticChildInstancesRecursive(child, result, true, providerCtx);
3722
+ this.collectStaticChildInstancesRecursive(child, result, inLoop || !!loop.childComponent, providerCtx, propsParams);
3498
3723
  }
3499
3724
  } else if (node.type === "element") {
3500
3725
  const element = node;
3501
3726
  for (const child of element.children) {
3502
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3727
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3503
3728
  }
3504
3729
  } else if (node.type === "fragment") {
3505
3730
  const fragment = node;
3506
3731
  for (const child of fragment.children) {
3507
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3732
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3508
3733
  }
3509
3734
  } else if (node.type === "conditional") {
3510
3735
  const cond = node;
3511
- this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop, providerCtx);
3736
+ this.collectStaticChildInstancesRecursive(cond.whenTrue, result, inLoop, providerCtx, propsParams);
3512
3737
  if (cond.whenFalse) {
3513
- this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx);
3738
+ this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx, propsParams);
3514
3739
  }
3515
3740
  } else if (node.type === "if-statement") {
3516
3741
  const stmt = node;
3517
- this.collectStaticChildInstancesRecursive(stmt.consequent, result, inLoop, providerCtx);
3742
+ this.collectStaticChildInstancesRecursive(stmt.consequent, result, inLoop, providerCtx, propsParams);
3518
3743
  if (stmt.alternate) {
3519
- this.collectStaticChildInstancesRecursive(stmt.alternate, result, inLoop, providerCtx);
3744
+ this.collectStaticChildInstancesRecursive(stmt.alternate, result, inLoop, providerCtx, propsParams);
3520
3745
  }
3521
3746
  } else if (node.type === "provider") {
3522
3747
  const p = node;
3523
- const childCtx = this.extendProviderContext(providerCtx, p);
3748
+ const childCtx = this.extendProviderContext(providerCtx, p, propsParams);
3524
3749
  for (const child of p.children) {
3525
- this.collectStaticChildInstancesRecursive(child, result, inLoop, childCtx);
3750
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, childCtx, propsParams);
3526
3751
  }
3527
3752
  } else if (node.type === "async") {
3528
3753
  const a = node;
3529
- this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop, providerCtx);
3754
+ this.collectStaticChildInstancesRecursive(a.fallback, result, inLoop, providerCtx, propsParams);
3530
3755
  for (const child of a.children) {
3531
- this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
3756
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx, propsParams);
3532
3757
  }
3533
3758
  }
3534
3759
  }
3535
- extendProviderContext(current, p) {
3760
+ extendProviderContext(current, p, propsParams) {
3536
3761
  const v = p.valueProp?.value;
3537
- if (!v || v.kind !== "literal")
3762
+ if (!v)
3538
3763
  return current;
3539
- let goLit = null;
3540
- if (typeof v.value === "string")
3541
- goLit = `"${escapeGoString(v.value)}"`;
3542
- else if (typeof v.value === "number" || typeof v.value === "boolean")
3543
- goLit = String(v.value);
3544
- if (goLit === null)
3545
- return current;
3546
- const next = new Map(current);
3547
- next.set(p.contextName, goLit);
3548
- return next;
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;
3549
3816
  }
3550
3817
  templatePartsToGoCode(parts, propsParams) {
3551
3818
  const segments = [];
@@ -3611,12 +3878,14 @@ ${goFields.join(`
3611
3878
  }
3612
3879
  const memo = memos.find((m) => m.name === getterName);
3613
3880
  if (memo) {
3614
- return computeMemoInitialValueOrNull(this.emitCtx, memo, signals, propsParams);
3881
+ return computeMemoInitialValueOrNull(this.emitCtx, memo, signals, propsParams, undefined, new Set([memo.name]));
3615
3882
  }
3616
3883
  }
3617
3884
  return null;
3618
3885
  }
3619
3886
  inferMemoType(memo, signals, propsParamMap) {
3887
+ if (isListFilterMemo(memo))
3888
+ return "[]any";
3620
3889
  if (memo.bodyIsTemplateLiteral)
3621
3890
  return "string";
3622
3891
  if (memo.computation.includes("*") || memo.computation.includes("/") || memo.computation.includes("+") || memo.computation.includes("-")) {
@@ -4013,15 +4282,19 @@ ${goFields.join(`
4013
4282
  if (objHO && (objHO.method === "find" || objHO.method === "findLast")) {
4014
4283
  const findResult = this.renderHigherOrderExpr(objHO, emit);
4015
4284
  if (findResult) {
4016
- return `{{with ${findResult}}}{{.${capitalizeFieldName(property)}}}{{end}}`;
4285
+ return `{{with ${findResult}}}{{.${goFieldNameForKey(property)}}}{{end}}`;
4017
4286
  }
4018
- const templateBlock = this.renderFindTemplateBlock(objHO, emit, capitalizeFieldName(property));
4287
+ const templateBlock = this.renderFindTemplateBlock(objHO, emit, goFieldNameForKey(property));
4019
4288
  if (templateBlock)
4020
4289
  return templateBlock;
4021
4290
  }
4022
4291
  if (object.kind === "identifier" && this.state.propsObjectName && object.name === this.state.propsObjectName) {
4023
4292
  return this.rootFieldRef(property);
4024
4293
  }
4294
+ if (this.isMapRootedContextChain(object)) {
4295
+ const objGo = emit(object);
4296
+ return `bf_get ${wrapIfMultiToken(objGo)} ${JSON.stringify(property)}`;
4297
+ }
4025
4298
  if (object.kind === "identifier") {
4026
4299
  const staticValue = this.resolveStaticRecordLiteralIndex(`${object.name}.${property}`);
4027
4300
  if (staticValue !== null)
@@ -4029,12 +4302,12 @@ ${goFields.join(`
4029
4302
  }
4030
4303
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
4031
4304
  if (object.kind === "identifier" && currentLoopParam && object.name === currentLoopParam) {
4032
- return `.${capitalizeFieldName(property)}`;
4305
+ return `.${goFieldNameForKey(property)}`;
4033
4306
  }
4034
4307
  const obj = emit(object);
4035
4308
  if (property === "length")
4036
4309
  return `len ${obj}`;
4037
- return `${obj}.${capitalizeFieldName(property)}`;
4310
+ return `${obj}.${goFieldNameForKey(property)}`;
4038
4311
  }
4039
4312
  indexAccess(object, index, emit) {
4040
4313
  return `index ${wrapIfMultiToken(emit(object))} ${wrapIfMultiToken(emit(index))}`;
@@ -4126,7 +4399,16 @@ ${goFields.join(`
4126
4399
  return `bf_arr ${parts.join(" ")}`;
4127
4400
  }
4128
4401
  objectLiteral(_properties, raw, _emit) {
4129
- return this.unsupported(raw, "object literal");
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 `""`;
4130
4412
  }
4131
4413
  static PREDICATE_METHODS = new Set([
4132
4414
  "filter",
@@ -4138,7 +4420,7 @@ ${goFields.join(`
4138
4420
  "some"
4139
4421
  ]);
4140
4422
  higherOrderShapeOf(node) {
4141
- const cb = asCallbackMethodCall(node);
4423
+ const cb = asCallbackMethodCall3(node);
4142
4424
  if (!cb)
4143
4425
  return null;
4144
4426
  if (!GoTemplateAdapter.PREDICATE_METHODS.has(cb.method))
@@ -4213,6 +4495,12 @@ ${goFields.join(`
4213
4495
  return evalForm;
4214
4496
  return this.pushCallbackBF101(method, true);
4215
4497
  }
4498
+ if (method === "map") {
4499
+ const evalForm = emitMapEval(recv, body, params[0] ?? "_", emit);
4500
+ if (evalForm !== null)
4501
+ return evalForm;
4502
+ return this.pushCallbackBF101(method, true);
4503
+ }
4216
4504
  return this.pushCallbackBF101(method, true);
4217
4505
  }
4218
4506
  arrayMethod(method, object, args, emit) {
@@ -4333,6 +4621,9 @@ ${goFields.join(`
4333
4621
  }
4334
4622
  }
4335
4623
  flatMethod(object, depth, emit) {
4624
+ if (typeof depth === "object") {
4625
+ return `bf_flat_dynamic ${wrapIfMultiToken(emit(object))} ${wrapIfMultiToken(emit(depth.expr))}`;
4626
+ }
4336
4627
  const d = depth === "infinity" ? -1 : depth;
4337
4628
  return `bf_flat ${wrapIfMultiToken(emit(object))} ${d}`;
4338
4629
  }
@@ -4566,6 +4857,9 @@ ${goFields.join(`
4566
4857
  if (expr.callee.kind === "identifier" && expr.args.length === 0) {
4567
4858
  return `$.${capitalizeFieldName(expr.callee.name)}`;
4568
4859
  }
4860
+ if (asCallbackMethodCall3(expr) !== null) {
4861
+ return this.refuseFilterExprNode(expr);
4862
+ }
4569
4863
  const result = this.renderFilterExpr(expr.callee, param, localVarMap);
4570
4864
  if (this.filterExprUnsupported)
4571
4865
  return "false";
@@ -4630,21 +4924,23 @@ ${goFields.join(`
4630
4924
  }
4631
4925
  return `or (${left}) (${right})`;
4632
4926
  }
4633
- default: {
4634
- this.filterExprUnsupported = true;
4635
- this.state.errors.push({
4636
- code: "BF101",
4637
- severity: "error",
4638
- message: `Filter predicate contains an expression that cannot be lowered to a Go template action: ${exprToString(expr)}`,
4639
- loc: this.makeLoc(),
4640
- suggestion: {
4641
- 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)"
4642
- }
4643
- });
4644
- return "false";
4645
- }
4927
+ default:
4928
+ return this.refuseFilterExprNode(expr);
4646
4929
  }
4647
4930
  }
4931
+ refuseFilterExprNode(expr) {
4932
+ this.filterExprUnsupported = true;
4933
+ this.state.errors.push({
4934
+ code: "BF101",
4935
+ severity: "error",
4936
+ message: `Filter predicate contains an expression that cannot be lowered to a Go template action: ${exprToString(expr)}`,
4937
+ loc: this.makeLoc(),
4938
+ suggestion: {
4939
+ 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)"
4940
+ }
4941
+ });
4942
+ return "false";
4943
+ }
4648
4944
  isGoFunctionCall(expr) {
4649
4945
  switch (expr.kind) {
4650
4946
  case "binary":
@@ -4898,7 +5194,7 @@ ${goFields.join(`
4898
5194
  });
4899
5195
  return plain("false");
4900
5196
  }
4901
- if (asCallbackMethodCall(expr)) {
5197
+ if (asCallbackMethodCall3(expr)) {
4902
5198
  const rendered = this.renderParsedExpr(expr);
4903
5199
  const split = this.splitPreamble(rendered);
4904
5200
  if (split)
@@ -5026,23 +5322,44 @@ ${goFields.join(`
5026
5322
  return plain(expr.raw);
5027
5323
  }
5028
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
+ }
5029
5332
  buildDestructureBindingMap(loop, rangeVar) {
5030
- const m = new Map;
5333
+ const bindings = new Map;
5334
+ const restExcludes = new Map;
5335
+ const base = `$${rangeVar}`;
5031
5336
  for (const b of loop.paramBindings ?? []) {
5032
- if (b.rest) {
5033
- m.set(b.name, `$${rangeVar}`);
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})`);
5034
5342
  } else {
5035
- m.set(b.name, `$${rangeVar}.${capitalizeFieldName(b.path.slice(1))}`);
5343
+ bindings.set(b.name, parent);
5344
+ restExcludes.set(b.name, { parent, excludeKeys: b.rest.exclude.map((k) => k.key) });
5036
5345
  }
5037
5346
  }
5038
- return m;
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;
5039
5356
  }
5040
5357
  renderLoop(loop) {
5041
5358
  if (loop.clientOnly) {
5042
5359
  return `{{bfComment "loop:${loop.markerId}"}}{{bfComment "/loop:${loop.markerId}"}}`;
5043
5360
  }
5044
5361
  const destructure = !!(loop.paramBindings && loop.paramBindings.length > 0);
5045
- const supportableDestructure = destructure && isLowerableObjectRestDestructure(loop);
5362
+ const supportableDestructure = destructure && isLowerableLoopDestructure(loop);
5046
5363
  if (destructure && !supportableDestructure) {
5047
5364
  this.state.errors.push({
5048
5365
  code: "BF104",
@@ -5057,6 +5374,21 @@ ${goFields.join(`
5057
5374
  }
5058
5375
  });
5059
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
+ }
5060
5392
  let goArray = this.convertExpressionToGo(loop.array);
5061
5393
  const param = loop.param;
5062
5394
  let index = loop.index || "_";
@@ -5066,15 +5398,16 @@ ${goFields.join(`
5066
5398
  rangeIndex = param;
5067
5399
  rangeValue = "_";
5068
5400
  }
5069
- const childComponent = this.findChildComponent(loop.children);
5070
- if (childComponent) {
5071
- goArray = `.${childComponent.name}s`;
5401
+ if (loop.childComponent) {
5402
+ goArray = `.${loop.childComponent.name}s`;
5072
5403
  }
5073
5404
  this.inLoop = true;
5074
5405
  const addedLoopVars = [];
5075
5406
  let pushedBindingMap = false;
5076
5407
  if (supportableDestructure) {
5077
- this.loopBindingStack.push(this.buildDestructureBindingMap(loop, rangeValue));
5408
+ const built = this.buildDestructureBindingMap(loop, rangeValue);
5409
+ this.loopBindingStack.push(built.bindings);
5410
+ this.loopRestExcludeStack.push(built.restExcludes);
5078
5411
  pushedBindingMap = true;
5079
5412
  this.loopParamStack.push("");
5080
5413
  if (rangeIndex !== "_") {
@@ -5093,7 +5426,9 @@ ${goFields.join(`
5093
5426
  }
5094
5427
  }
5095
5428
  this.loopScalarItemStack.push(this.scalarLiteralLoopGoType(loop.arrayParsed, loop.itemType) !== null);
5429
+ this.loopWrapperStack.push(!!loop.childComponent);
5096
5430
  const children = this.renderChildren(loop.children);
5431
+ this.loopWrapperStack.pop();
5097
5432
  this.loopScalarItemStack.pop();
5098
5433
  const itemMarker = this.loopItemMarker(loop);
5099
5434
  for (const v of addedLoopVars) {
@@ -5104,8 +5439,10 @@ ${goFields.join(`
5104
5439
  this.loopVarRefCount.set(v, rc);
5105
5440
  }
5106
5441
  this.loopParamStack.pop();
5107
- if (pushedBindingMap)
5442
+ if (pushedBindingMap) {
5108
5443
  this.loopBindingStack.pop();
5444
+ this.loopRestExcludeStack.pop();
5445
+ }
5109
5446
  this.inLoop = false;
5110
5447
  if (loop.sortComparator) {
5111
5448
  const sortEmit = (e) => this.renderParsedExpr(e);
@@ -5134,24 +5471,6 @@ ${goFields.join(`
5134
5471
  }
5135
5472
  return "";
5136
5473
  }
5137
- findChildComponent(nodes) {
5138
- for (const node of nodes) {
5139
- if (node.type === "component") {
5140
- return node;
5141
- }
5142
- if (node.type === "element" && node.children) {
5143
- const found = this.findChildComponent(node.children);
5144
- if (found)
5145
- return found;
5146
- }
5147
- if (node.type === "fragment" && node.children) {
5148
- const found = this.findChildComponent(node.children);
5149
- if (found)
5150
- return found;
5151
- }
5152
- }
5153
- return null;
5154
- }
5155
5474
  queueDynamicChildrenDefine(comp) {
5156
5475
  const effectiveChildren = comp.children.length > 0 ? comp.children : this.jsxChildrenPropNodes(comp.props);
5157
5476
  if (effectiveChildren.length === 0)
@@ -5199,7 +5518,7 @@ ${goFields.join(`
5199
5518
  return this.renderChildren(comp.children);
5200
5519
  }
5201
5520
  let templateCall;
5202
- if (this.inLoop) {
5521
+ if (this.inLoop && (this.loopWrapperStack[this.loopWrapperStack.length - 1] ?? false)) {
5203
5522
  const loopBodyDefine = this.queueLoopBodyChildrenDefine(comp);
5204
5523
  if (loopBodyDefine) {
5205
5524
  const bodyData = this.loopScalarItemStack[this.loopScalarItemStack.length - 1] ? ".BfLoopItem" : ".";
@@ -5207,6 +5526,12 @@ ${goFields.join(`
5207
5526
  } else {
5208
5527
  templateCall = `{{template "${comp.name}" .}}`;
5209
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}" .}}`;
5210
5535
  } else if (comp.slotId) {
5211
5536
  const suffix = slotIdToFieldSuffix(comp.slotId);
5212
5537
  const childrenDefine = this.queueDynamicChildrenDefine(comp);
@@ -5302,6 +5627,12 @@ ${children}`;
5302
5627
  if (currentLoopParam && trimmed === currentLoopParam) {
5303
5628
  return `{{bf_spread_attrs .}}`;
5304
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
+ }
5305
5636
  const goExpr = this.convertExpressionToGo(value.expr);
5306
5637
  return `{{bf_spread_attrs ${goExpr}}}`;
5307
5638
  }