@barefootjs/go-template 0.14.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/build.js CHANGED
@@ -377,9 +377,12 @@ import {
377
377
  augmentInheritedPropAccesses,
378
378
  parseRecordIndexAccess,
379
379
  collectContextConsumers,
380
- isLowerableObjectRestDestructure
380
+ isLowerableObjectRestDestructure,
381
+ collectModuleStringConsts as collectModuleStringConstsShared,
382
+ searchParamsLocalNames
381
383
  } from "@barefootjs/jsx";
382
384
  import { findInterpolationEnd } from "@barefootjs/jsx/scanner";
385
+ import { BF_REGION } from "@barefootjs/shared";
383
386
  function wrapIfMultiToken(rendered) {
384
387
  if (rendered.startsWith("(") && rendered.endsWith(")"))
385
388
  return rendered;
@@ -467,13 +470,29 @@ function buildUnsupportedSuggestion(support) {
467
470
 
468
471
  ${GO_REMEDIATION_OPTIONS}`;
469
472
  }
473
+ function wrapGoArg(arg) {
474
+ if (!/\s/.test(arg))
475
+ return arg;
476
+ if (arg.startsWith("(") && arg.endsWith(")"))
477
+ return arg;
478
+ return `(${arg})`;
479
+ }
480
+ function stringTolerantEqOperands(l, r) {
481
+ const isStrLit = (x) => /^"(?:[^"\\]|\\.)*"$/.test(x);
482
+ if (isStrLit(l) === isStrLit(r))
483
+ return [l, r];
484
+ const wrap = (x) => isStrLit(x) ? x : `(bf_string ${wrapGoArg(x)})`;
485
+ return [wrap(l), wrap(r)];
486
+ }
470
487
  var GO_TEMPLATE_PRIMITIVES = {
471
- "JSON.stringify": { arity: 1, emit: (args) => `bf_json ${args[0]}` },
472
- String: { arity: 1, emit: (args) => `bf_string ${args[0]}` },
473
- Number: { arity: 1, emit: (args) => `bf_number ${args[0]}` },
474
- "Math.floor": { arity: 1, emit: (args) => `bf_floor ${args[0]}` },
475
- "Math.ceil": { arity: 1, emit: (args) => `bf_ceil ${args[0]}` },
476
- "Math.round": { arity: 1, emit: (args) => `bf_round ${args[0]}` }
488
+ "JSON.stringify": { arity: 1, emit: (args) => `bf_json ${wrapGoArg(args[0])}` },
489
+ String: { arity: 1, emit: (args) => `bf_string ${wrapGoArg(args[0])}` },
490
+ Number: { arity: 1, emit: (args) => `bf_number ${wrapGoArg(args[0])}` },
491
+ "Math.floor": { arity: 1, emit: (args) => `bf_floor ${wrapGoArg(args[0])}` },
492
+ "Math.ceil": { arity: 1, emit: (args) => `bf_ceil ${wrapGoArg(args[0])}` },
493
+ "Math.round": { arity: 1, emit: (args) => `bf_round ${wrapGoArg(args[0])}` },
494
+ "Math.min": { arity: 2, emit: (args) => `bf_min ${wrapGoArg(args[0])} ${wrapGoArg(args[1])}` },
495
+ "Math.max": { arity: 2, emit: (args) => `bf_max ${wrapGoArg(args[0])} ${wrapGoArg(args[1])}` }
477
496
  };
478
497
 
479
498
  class GoTemplateAdapter extends BaseAdapter {
@@ -488,10 +507,12 @@ class GoTemplateAdapter extends BaseAdapter {
488
507
  componentName = "";
489
508
  options;
490
509
  inLoop = false;
510
+ pendingChildrenDefines = [];
491
511
  loopParamStack = [];
492
512
  loopVarRefCount = new Map;
493
513
  loopBindingStack = [];
494
514
  errors = [];
515
+ currentMemos = [];
495
516
  propsObjectName = null;
496
517
  restPropsName = null;
497
518
  templateVarCounter = 0;
@@ -499,6 +520,7 @@ class GoTemplateAdapter extends BaseAdapter {
499
520
  localTypeAliases = new Map;
500
521
  localStructFields = new Map;
501
522
  synthStructTypes = new Map;
523
+ currentTypeDefinitions = [];
502
524
  usesHtmlTemplate = false;
503
525
  rootScopeNodes = new Set;
504
526
  usesFmt = false;
@@ -506,6 +528,11 @@ class GoTemplateAdapter extends BaseAdapter {
506
528
  moduleStringConsts = new Map;
507
529
  localConstants = [];
508
530
  contextConsumers = [];
531
+ searchParamsLocals = new Set;
532
+ localHelperNames = new Set;
533
+ needsStringsImport = false;
534
+ referencedDerivedConsts = new Set;
535
+ memoBackedLoopSlice = new Map;
509
536
  childContextConsumers = new Map;
510
537
  nillablePropNames = new Set;
511
538
  constructor(options = {}) {
@@ -519,12 +546,18 @@ class GoTemplateAdapter extends BaseAdapter {
519
546
  generate(ir, options) {
520
547
  this.componentName = ir.metadata.componentName;
521
548
  this.errors = [];
549
+ this.referencedDerivedConsts = new Set;
522
550
  this.templateVarCounter = 0;
551
+ this.pendingChildrenDefines = [];
523
552
  this.propsObjectName = ir.metadata.propsObjectName;
524
553
  this.restPropsName = ir.metadata.restPropsName ?? null;
525
554
  this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
526
555
  this.localConstants = ir.metadata.localConstants ?? [];
556
+ this.localHelperNames = new Set(this.localConstants.filter((c) => !c.isModule && c.containsArrow).map((c) => c.name));
557
+ this.currentMemos = ir.metadata.memos ?? [];
558
+ this.currentTypeDefinitions = ir.metadata.typeDefinitions ?? [];
527
559
  this.contextConsumers = collectContextConsumers(ir.metadata);
560
+ this.searchParamsLocals = searchParamsLocalNames(ir.metadata);
528
561
  augmentInheritedPropAccesses(ir);
529
562
  this.nillablePropNames = this.collectNillablePropNames(ir);
530
563
  if (!options?.siblingTemplatesRegistered) {
@@ -534,12 +567,22 @@ class GoTemplateAdapter extends BaseAdapter {
534
567
  const isRootComponent = ir.root.type === "component";
535
568
  const isIfStatement = ir.root.type === "if-statement";
536
569
  this.rootScopeNodes = collectRootScopeNodes(ir.root);
570
+ this.memoBackedLoopSlice = new Map;
571
+ for (const nested of this.findNestedComponents(ir.root)) {
572
+ const memoName = this.extractMemoNameFromLoopArray(nested.loopArray);
573
+ if (memoName)
574
+ this.memoBackedLoopSlice.set(memoName, `${nested.name}s`);
575
+ }
537
576
  const templateBody = isIfStatement ? this.renderIfStatement(ir.root, { isRootOfClientComponent: hasInteractivity }) : this.renderNode(ir.root, { isRootOfClientComponent: hasInteractivity && isRootComponent });
538
577
  const scriptRegistrations = options?.skipScriptRegistration ? "" : this.generateScriptRegistrations(ir, options?.scriptBaseName);
539
- const template = `{{define "${this.componentName}"}}
578
+ let template = `{{define "${this.componentName}"}}
540
579
  ${scriptRegistrations}${templateBody}
541
580
  {{end}}
542
581
  `;
582
+ for (const d of this.pendingChildrenDefines) {
583
+ template += `{{define "${d.name}"}}${d.content}{{end}}
584
+ `;
585
+ }
543
586
  const types = this.generateTypes(ir);
544
587
  if (this.errors.length > 0) {
545
588
  ir.errors.push(...this.errors);
@@ -776,10 +819,15 @@ ${scriptRegistrations}${templateBody}
776
819
  this.usesHtmlTemplate = false;
777
820
  this.usesFmt = false;
778
821
  this.propsObjectName = ir.metadata.propsObjectName;
822
+ this.restPropsName = ir.metadata.restPropsName ?? null;
779
823
  augmentInheritedPropAccesses(ir);
780
824
  this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
781
825
  this.localConstants = ir.metadata.localConstants ?? [];
826
+ this.localHelperNames = new Set(this.localConstants.filter((c) => !c.isModule && c.containsArrow).map((c) => c.name));
827
+ this.currentMemos = ir.metadata.memos ?? [];
828
+ this.currentTypeDefinitions = ir.metadata.typeDefinitions ?? [];
782
829
  this.contextConsumers = collectContextConsumers(ir.metadata);
830
+ this.searchParamsLocals = searchParamsLocalNames(ir.metadata);
783
831
  const lines = [];
784
832
  const componentName = ir.metadata.componentName;
785
833
  this.localTypeNames = new Set;
@@ -832,11 +880,39 @@ ${goFields.join(`
832
880
  lines.push("");
833
881
  }
834
882
  const nestedComponents = this.findNestedComponents(ir.root);
883
+ for (const nested of nestedComponents) {
884
+ if (nested.loopItemType || !nested.loopArray)
885
+ continue;
886
+ const memoName = this.extractMemoNameFromLoopArray(nested.loopArray);
887
+ if (memoName) {
888
+ const memo = ir.metadata.memos.find((m) => m.name === memoName);
889
+ if (memo) {
890
+ const blockReturn = this.resolveBlockBodyMemoModuleConst(memo.computation, ir.metadata.signals);
891
+ if (blockReturn) {
892
+ const constant = (ir.metadata.localConstants ?? []).find((c) => c.name === blockReturn.constName && c.origin?.scope === "module");
893
+ if (constant?.type?.elementType) {
894
+ nested.loopItemType = constant.type.elementType;
895
+ }
896
+ }
897
+ }
898
+ continue;
899
+ }
900
+ const directConst = (ir.metadata.localConstants ?? []).find((c) => c.name === nested.loopArray && c.origin?.scope === "module");
901
+ if (directConst?.type?.elementType) {
902
+ nested.loopItemType = directConst.type.elementType;
903
+ }
904
+ }
905
+ for (const nested of nestedComponents) {
906
+ if (!nested.bodyChildren || nested.bodyChildren.length === 0)
907
+ continue;
908
+ this.generateLoopBodyWrapperStruct(lines, componentName, nested);
909
+ }
835
910
  const propTypeOverrides = this.buildPropTypeOverrides(ir);
836
911
  const spreadSlots = this.collectSpreadSlots(ir.root);
837
912
  this.generateInputStruct(lines, ir, componentName, nestedComponents, propTypeOverrides, spreadSlots);
913
+ this.needsStringsImport = false;
838
914
  this.generatePropsStruct(lines, ir, componentName, nestedComponents, propTypeOverrides, spreadSlots);
839
- this.generateNewPropsFunction(lines, ir, componentName, nestedComponents, spreadSlots);
915
+ this.generateNewPropsFunction(lines, ir, componentName, nestedComponents, spreadSlots, propTypeOverrides);
840
916
  const header = [];
841
917
  header.push(`package ${this.options.packageName}`);
842
918
  header.push("");
@@ -846,6 +922,8 @@ ${goFields.join(`
846
922
  if (this.usesHtmlTemplate)
847
923
  header.push('\t"html/template"');
848
924
  header.push('\t"math/rand"');
925
+ if (this.needsStringsImport)
926
+ header.push('\t"strings"');
849
927
  header.push("");
850
928
  header.push('\tbf "github.com/barefootjs/runtime/bf"');
851
929
  header.push(")");
@@ -1016,6 +1094,20 @@ ${goFields.join(`
1016
1094
  }
1017
1095
  return nillable;
1018
1096
  }
1097
+ usesSearchParams(ir) {
1098
+ if (this.searchParamsLocals.size === 0)
1099
+ return false;
1100
+ const taken = new Set([
1101
+ ...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
1102
+ ...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
1103
+ ...ir.metadata.memos.map((m) => this.capitalizeFieldName(m.name)),
1104
+ ...this.contextConsumers.map((c) => this.contextFieldName(c))
1105
+ ]);
1106
+ if (ir.metadata.restPropsName) {
1107
+ taken.add(this.capitalizeFieldName(ir.metadata.restPropsName));
1108
+ }
1109
+ return !taken.has("SearchParams");
1110
+ }
1019
1111
  generateInputStruct(lines, ir, componentName, nestedComponents, propTypeOverrides, spreadSlots) {
1020
1112
  const inputTypeName = `${componentName}Input`;
1021
1113
  lines.push(`// ${inputTypeName} is the user-facing input type.`);
@@ -1023,6 +1115,9 @@ ${goFields.join(`
1023
1115
  lines.push("\tScopeID string // Optional: if empty, random ID is generated");
1024
1116
  lines.push("\tBfParent string // Optional: parent scope id");
1025
1117
  lines.push("\tBfMount string // Optional: slot id in parent");
1118
+ if (this.usesSearchParams(ir)) {
1119
+ lines.push("\tSearchParams bf.SearchParams // Optional: request query for searchParams()");
1120
+ }
1026
1121
  const inputNested = nestedComponents.filter((n) => !n.isDynamic || n.isPropDerived);
1027
1122
  const nestedArrayFields = new Set(nestedComponents.map((n) => `${n.name}s`));
1028
1123
  for (const param of ir.metadata.propsParams) {
@@ -1067,6 +1162,9 @@ ${goFields.join(`
1067
1162
  lines.push('\tBfMount string `json:"-"`');
1068
1163
  lines.push('\tBfDataKey string `json:"-"`');
1069
1164
  lines.push('\tScripts *bf.ScriptCollector `json:"-"`');
1165
+ if (this.usesSearchParams(ir)) {
1166
+ lines.push('\tSearchParams bf.SearchParams `json:"-"`');
1167
+ }
1070
1168
  const nestedArrayFields = new Set(nestedComponents.map((n) => `${n.name}s`));
1071
1169
  const propFieldNames = new Set;
1072
1170
  for (const param of ir.metadata.propsParams) {
@@ -1113,10 +1211,20 @@ ${goFields.join(`
1113
1211
  }
1114
1212
  for (const memo of ir.metadata.memos) {
1115
1213
  const fieldName = this.capitalizeFieldName(memo.name);
1214
+ if (propFieldNames.has(fieldName))
1215
+ continue;
1116
1216
  const jsonTag = this.toJsonTag(memo.name);
1117
1217
  const goType = this.inferMemoType(memo, ir.metadata.signals, propsParamMap);
1118
1218
  lines.push(` ${fieldName} ${goType} \`json:"${jsonTag}"\``);
1119
1219
  }
1220
+ const takenForDerivedConsts = new Set([
1221
+ ...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
1222
+ ...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
1223
+ ...ir.metadata.memos.map((m) => this.capitalizeFieldName(m.name))
1224
+ ]);
1225
+ for (const f of this.computeDerivedConstFields(takenForDerivedConsts)) {
1226
+ lines.push(` ${f.name} string \`json:"-"\``);
1227
+ }
1120
1228
  const takenProps = new Set([
1121
1229
  ...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
1122
1230
  ...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
@@ -1127,11 +1235,12 @@ ${goFields.join(`
1127
1235
  lines.push(` ${this.contextFieldName(c)} ${this.contextConsumerGoType(c)} \`json:"${jsonTag}"\``);
1128
1236
  }
1129
1237
  for (const nested of nestedComponents) {
1238
+ const elemType = nested.bodyChildren?.length ? this.loopBodyWrapperName(componentName, nested) : `${nested.name}Props`;
1130
1239
  if (nested.isDynamic && !nested.isPropDerived) {
1131
- lines.push(` ${nested.name}s []${nested.name}Props \`json:"-"\``);
1240
+ lines.push(` ${nested.name}s []${elemType} \`json:"-"\``);
1132
1241
  } else {
1133
1242
  const jsonTag = this.toJsonTag(`${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`);
1134
- lines.push(` ${nested.name}s []${nested.name}Props \`json:"${jsonTag}"\``);
1243
+ lines.push(` ${nested.name}s []${elemType} \`json:"${jsonTag}"\``);
1135
1244
  }
1136
1245
  }
1137
1246
  const staticChildren = this.collectStaticChildInstances(ir.root);
@@ -1145,10 +1254,73 @@ ${goFields.join(`
1145
1254
  lines.push("}");
1146
1255
  lines.push("");
1147
1256
  }
1148
- generateNewPropsFunction(lines, ir, componentName, nestedComponents, spreadSlots) {
1257
+ generateLoopBodyWrapperStruct(lines, parentComponentName, nested) {
1258
+ const wrapperName = this.loopBodyWrapperName(parentComponentName, nested);
1259
+ const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
1260
+ const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
1261
+ lines.push(`// ${wrapperName} wraps ${nested.name}Props with per-row loop datum`);
1262
+ lines.push(`// fields and child component slots for the loop body children. (#1897)`);
1263
+ lines.push(`type ${wrapperName} struct {`);
1264
+ lines.push(` ${nested.name}Props`);
1265
+ for (const f of datumFields) {
1266
+ lines.push(` ${f.goName} ${f.goType} \`json:"-"\``);
1267
+ }
1268
+ for (const child of bodyChildInstances) {
1269
+ lines.push(` ${child.fieldName} ${child.name}Props \`json:"-"\``);
1270
+ }
1271
+ lines.push("}");
1272
+ lines.push("");
1273
+ }
1274
+ extractMemoNameFromLoopArray(loopArray) {
1275
+ if (!loopArray)
1276
+ return null;
1277
+ const match = loopArray.match(/^(\w+)\(\)$/);
1278
+ return match ? match[1] : null;
1279
+ }
1280
+ loopBodyWrapperName(parentName, nested) {
1281
+ return `${parentName}${nested.name}L${nested.loopMarkerId ?? "0"}Ctx`;
1282
+ }
1283
+ resolveLoopDatumFields(itemType) {
1284
+ if (!itemType)
1285
+ return [];
1286
+ const typeName = itemType.raw?.replace(/\[\]$/, "") ?? itemType.raw;
1287
+ if (!typeName)
1288
+ return [];
1289
+ for (const td of this.currentTypeDefinitions) {
1290
+ if (td.name === typeName) {
1291
+ const fields = [];
1292
+ for (const prop of td.properties ?? []) {
1293
+ if (!GoTemplateAdapter.GO_IDENTIFIER.test(prop.name))
1294
+ continue;
1295
+ fields.push({
1296
+ tsName: prop.name,
1297
+ goName: this.capitalizeFieldName(prop.name),
1298
+ goType: this.typeInfoToGo(prop.type)
1299
+ });
1300
+ }
1301
+ if (fields.length > 0)
1302
+ return fields;
1303
+ break;
1304
+ }
1305
+ }
1306
+ const structFields = this.localStructFields.get(typeName);
1307
+ if (structFields) {
1308
+ return Array.from(structFields, ([tsName, goName]) => ({ tsName, goName, goType: "interface{}" }));
1309
+ }
1310
+ return [];
1311
+ }
1312
+ collectBodyChildInstances(bodyChildren) {
1313
+ const result = [];
1314
+ for (const child of bodyChildren) {
1315
+ this.collectStaticChildInstancesRecursive(child, result, false, new Map);
1316
+ }
1317
+ return result;
1318
+ }
1319
+ generateNewPropsFunction(lines, ir, componentName, nestedComponents, spreadSlots, propTypeOverrides) {
1149
1320
  const inputTypeName = `${componentName}Input`;
1150
1321
  const propsTypeName = `${componentName}Props`;
1151
- const signalDynamicNested = nestedComponents.filter((n) => n.isDynamic && !n.isPropDerived);
1322
+ const dynamicWithBody = nestedComponents.filter((n) => n.isDynamic && !n.isPropDerived && n.bodyChildren && n.bodyChildren.length > 0);
1323
+ const signalDynamicNested = nestedComponents.filter((n) => n.isDynamic && !n.isPropDerived && !(n.bodyChildren && n.bodyChildren.length > 0));
1152
1324
  lines.push(`// New${componentName}Props creates ${propsTypeName} from ${inputTypeName}.`);
1153
1325
  for (const nested of signalDynamicNested) {
1154
1326
  const arrayField = `${nested.name}s`;
@@ -1173,22 +1345,77 @@ ${goFields.join(`
1173
1345
  lines.push("\t}");
1174
1346
  lines.push("");
1175
1347
  const staticNested = nestedComponents.filter((n) => !n.isDynamic || n.isPropDerived);
1176
- if (staticNested.length > 0) {
1177
- for (const nested of staticNested) {
1178
- const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
1179
- lines.push(` ${varName} := make([]${nested.name}Props, len(in.${nested.name}s))`);
1180
- lines.push(` for i, item := range in.${nested.name}s {`);
1181
- lines.push(` ${varName}[i] = New${nested.name}Props(item)`);
1182
- lines.push(` ${varName}[i].BfParent = scopeID`);
1183
- lines.push(` ${varName}[i].BfMount = "${nested.slotId}"`);
1184
- const keyField = loopKeyToGoFieldPath(nested.loopKey, nested.loopParam);
1185
- if (keyField) {
1186
- lines.push(` ${varName}[i].BfDataKey = fmt.Sprint(${keyField})`);
1187
- this.usesFmt = true;
1348
+ const emittedWrapperVars = new Set;
1349
+ const staticWithBody = staticNested.filter((n) => n.bodyChildren && n.bodyChildren.length > 0);
1350
+ const staticWithoutBody = staticNested.filter((n) => !n.bodyChildren || n.bodyChildren.length === 0);
1351
+ for (const nested of staticWithoutBody) {
1352
+ const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
1353
+ lines.push(` ${varName} := make([]${nested.name}Props, len(in.${nested.name}s))`);
1354
+ lines.push(` for i, item := range in.${nested.name}s {`);
1355
+ lines.push(` ${varName}[i] = New${nested.name}Props(item)`);
1356
+ lines.push(` ${varName}[i].BfParent = scopeID`);
1357
+ lines.push(` ${varName}[i].BfMount = "${nested.slotId}"`);
1358
+ const keyField = loopKeyToGoFieldPath(nested.loopKey, nested.loopParam);
1359
+ if (keyField) {
1360
+ lines.push(` ${varName}[i].BfDataKey = fmt.Sprint(${keyField})`);
1361
+ this.usesFmt = true;
1362
+ }
1363
+ lines.push("\t}");
1364
+ lines.push("");
1365
+ }
1366
+ for (const nested of staticWithBody) {
1367
+ const loopArray = nested.loopArray;
1368
+ const moduleConst = loopArray ? (ir.metadata.localConstants ?? []).find((c) => c.name === loopArray && c.origin?.scope === "module" && c.value && c.type) : null;
1369
+ const bakedValue = moduleConst?.type ? this.convertInitialValue(moduleConst.value, moduleConst.type, ir.metadata.propsParams) : null;
1370
+ if (!bakedValue || bakedValue === "nil" || bakedValue === "0")
1371
+ continue;
1372
+ const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
1373
+ const wrapperType = this.loopBodyWrapperName(componentName, nested);
1374
+ const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
1375
+ const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
1376
+ for (const child of bodyChildInstances) {
1377
+ const childVar = `child_${child.fieldName}`;
1378
+ lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
1379
+ lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
1380
+ lines.push(` BfParent: scopeID,`);
1381
+ lines.push(` BfMount: "${child.slotId}",`);
1382
+ for (const prop of child.props) {
1383
+ if (prop.value.kind === "literal") {
1384
+ lines.push(` ${this.capitalizeFieldName(prop.name)}: ${this.goLiteral(prop.value.value)},`);
1385
+ } else if (prop.value.kind === "boolean-shorthand" || prop.value.kind === "boolean-attr") {
1386
+ lines.push(` ${this.capitalizeFieldName(prop.name)}: true,`);
1387
+ }
1188
1388
  }
1189
- lines.push("\t}");
1190
- lines.push("");
1389
+ lines.push(` })`);
1191
1390
  }
1391
+ if (bodyChildInstances.length > 0)
1392
+ lines.push("");
1393
+ const dataVar = `${varName}Data`;
1394
+ lines.push(` ${dataVar} := ${bakedValue}`);
1395
+ lines.push(` ${varName} := make([]${wrapperType}, len(${dataVar}))`);
1396
+ lines.push(` for i, item := range ${dataVar} {`);
1397
+ lines.push(` ${varName}[i] = ${wrapperType}{`);
1398
+ lines.push(` ${nested.name}Props: New${nested.name}Props(${nested.name}Input{`);
1399
+ lines.push(` BfParent: scopeID,`);
1400
+ lines.push(` BfMount: "${nested.slotId}",`);
1401
+ lines.push(` }),`);
1402
+ for (const f of datumFields) {
1403
+ lines.push(` ${f.goName}: item.${f.goName},`);
1404
+ }
1405
+ for (const child of bodyChildInstances) {
1406
+ lines.push(` ${child.fieldName}: child_${child.fieldName},`);
1407
+ }
1408
+ lines.push(` }`);
1409
+ lines.push(` ${varName}[i].BfParent = scopeID`);
1410
+ lines.push(` ${varName}[i].BfMount = "${nested.slotId}"`);
1411
+ const keyField = loopKeyToGoFieldPath(nested.loopKey, nested.loopParam);
1412
+ if (keyField) {
1413
+ lines.push(` ${varName}[i].BfDataKey = fmt.Sprint(${keyField})`);
1414
+ this.usesFmt = true;
1415
+ }
1416
+ lines.push("\t}");
1417
+ lines.push("");
1418
+ emittedWrapperVars.add(varName);
1192
1419
  }
1193
1420
  const propFallbackVars = this.collectPropFallbackVars(ir);
1194
1421
  for (const [, info] of propFallbackVars) {
@@ -1199,11 +1426,87 @@ ${goFields.join(`
1199
1426
  }
1200
1427
  if (propFallbackVars.size > 0)
1201
1428
  lines.push("");
1429
+ const propsParamMap = new Map(ir.metadata.propsParams.map((p) => [p.name, p]));
1430
+ for (const nested of dynamicWithBody) {
1431
+ const memoName = this.extractMemoNameFromLoopArray(nested.loopArray);
1432
+ if (!memoName)
1433
+ continue;
1434
+ const memo = ir.metadata.memos.find((m) => m.name === memoName);
1435
+ if (!memo)
1436
+ continue;
1437
+ const goType = this.inferMemoType(memo, ir.metadata.signals, propsParamMap);
1438
+ const bakedValue = this.computeMemoInitialValue(memo, ir.metadata.signals, ir.metadata.propsParams, propFallbackVars, goType);
1439
+ if (bakedValue === "nil" || bakedValue === "0")
1440
+ continue;
1441
+ const wrapperType = this.loopBodyWrapperName(componentName, nested);
1442
+ const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
1443
+ const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
1444
+ const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
1445
+ for (const child of bodyChildInstances) {
1446
+ const childVar = `child_${child.fieldName}`;
1447
+ lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
1448
+ lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
1449
+ lines.push(` BfParent: scopeID,`);
1450
+ lines.push(` BfMount: "${child.slotId}",`);
1451
+ for (const prop of child.props) {
1452
+ if (prop.value.kind === "literal") {
1453
+ lines.push(` ${this.capitalizeFieldName(prop.name)}: ${this.goLiteral(prop.value.value)},`);
1454
+ } else if (prop.value.kind === "boolean-shorthand" || prop.value.kind === "boolean-attr") {
1455
+ lines.push(` ${this.capitalizeFieldName(prop.name)}: true,`);
1456
+ }
1457
+ }
1458
+ lines.push(` })`);
1459
+ }
1460
+ if (bodyChildInstances.length > 0)
1461
+ lines.push("");
1462
+ lines.push(` bakedData := ${bakedValue}`);
1463
+ lines.push(` ${varName} := make([]${wrapperType}, len(bakedData))`);
1464
+ lines.push(` for i, item := range bakedData {`);
1465
+ lines.push(` ${varName}[i] = ${wrapperType}{`);
1466
+ lines.push(` ${nested.name}Props: New${nested.name}Props(${nested.name}Input{`);
1467
+ lines.push(` BfParent: scopeID,`);
1468
+ lines.push(` BfMount: "${nested.slotId}",`);
1469
+ lines.push(` }),`);
1470
+ for (const f of datumFields) {
1471
+ lines.push(` ${f.goName}: item.${f.goName},`);
1472
+ }
1473
+ for (const child of bodyChildInstances) {
1474
+ lines.push(` ${child.fieldName}: child_${child.fieldName},`);
1475
+ }
1476
+ lines.push(` }`);
1477
+ const keyField = loopKeyToGoFieldPath(nested.loopKey, nested.loopParam);
1478
+ if (keyField) {
1479
+ lines.push(` ${varName}[i].BfDataKey = fmt.Sprint(${keyField})`);
1480
+ this.usesFmt = true;
1481
+ }
1482
+ lines.push(` }`);
1483
+ lines.push("");
1484
+ emittedWrapperVars.add(varName);
1485
+ }
1202
1486
  lines.push(` return ${propsTypeName}{`);
1203
1487
  lines.push("\t\tScopeID: scopeID,");
1204
1488
  lines.push("\t\tBfParent: in.BfParent,");
1205
1489
  lines.push("\t\tBfMount: in.BfMount,");
1490
+ if (this.usesSearchParams(ir)) {
1491
+ lines.push("\t\tSearchParams: in.SearchParams,");
1492
+ }
1206
1493
  const nestedArrayFields = new Set(nestedComponents.map((n) => `${n.name}s`));
1494
+ const memoFallbacks = new Map;
1495
+ for (const memo of ir.metadata.memos) {
1496
+ const stripped = memo.computation.replace(/^\(\)\s*=>\s*/, "");
1497
+ const m = this.extractPropFallback(stripped);
1498
+ if (!m)
1499
+ continue;
1500
+ if (this.capitalizeFieldName(m.propName) !== this.capitalizeFieldName(memo.name))
1501
+ continue;
1502
+ const param = ir.metadata.propsParams.find((p) => this.capitalizeFieldName(p.name) === this.capitalizeFieldName(memo.name));
1503
+ if (!param)
1504
+ continue;
1505
+ const goType = this.resolvePropGoType(param, propTypeOverrides);
1506
+ if (goType !== "string" && goType !== "interface{}")
1507
+ continue;
1508
+ memoFallbacks.set(this.capitalizeFieldName(memo.name), { goFallback: m.goFallback, goType });
1509
+ }
1207
1510
  const propFieldNames = new Set;
1208
1511
  for (const param of ir.metadata.propsParams) {
1209
1512
  const fieldName = this.capitalizeFieldName(param.name);
@@ -1213,9 +1516,14 @@ ${goFields.join(`
1213
1516
  if (hoisted) {
1214
1517
  lines.push(` ${fieldName}: ${hoisted.varName},`);
1215
1518
  } else {
1216
- const fallback = this.goPropDefault(param.defaultValue);
1217
- if (fallback !== null) {
1218
- lines.push(` ${fieldName}: ${this.applyGoFallback(`in.${fieldName}`, fallback)},`);
1519
+ const paramDefault = this.goPropDefault(param.defaultValue);
1520
+ const memoFold = memoFallbacks.get(fieldName);
1521
+ if (paramDefault !== null) {
1522
+ lines.push(` ${fieldName}: ${this.applyGoFallback(`in.${fieldName}`, paramDefault)},`);
1523
+ } else if (memoFold !== undefined && memoFold.goType === "string") {
1524
+ lines.push(` ${fieldName}: ${this.applyGoFallback(`in.${fieldName}`, memoFold.goFallback)},`);
1525
+ } else if (memoFold !== undefined) {
1526
+ lines.push(` ${fieldName}: func() interface{} { v := interface{}(in.${fieldName}); if v == nil || v == "" { return ${memoFold.goFallback} }; return v }(),`);
1219
1527
  } else {
1220
1528
  lines.push(` ${fieldName}: in.${fieldName},`);
1221
1529
  }
@@ -1236,17 +1544,33 @@ ${goFields.join(`
1236
1544
  lines.push(` ${fieldName}: ${initialValue},`);
1237
1545
  }
1238
1546
  }
1239
- for (const nested of staticNested) {
1547
+ for (const nested of staticWithoutBody) {
1548
+ const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
1549
+ lines.push(` ${nested.name}s: ${varName},`);
1550
+ }
1551
+ for (const nested of [...staticWithBody, ...dynamicWithBody]) {
1240
1552
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
1553
+ if (!emittedWrapperVars.has(varName))
1554
+ continue;
1241
1555
  lines.push(` ${nested.name}s: ${varName},`);
1242
1556
  }
1243
1557
  const memoPropsParamMap = new Map(ir.metadata.propsParams.map((p) => [p.name, p]));
1244
1558
  for (const memo of ir.metadata.memos) {
1245
1559
  const fieldName = this.capitalizeFieldName(memo.name);
1560
+ if (propFieldNames.has(fieldName))
1561
+ continue;
1246
1562
  const goType = this.inferMemoType(memo, ir.metadata.signals, memoPropsParamMap);
1247
1563
  const memoValue = this.computeMemoInitialValue(memo, ir.metadata.signals, ir.metadata.propsParams, propFallbackVars, goType);
1248
1564
  lines.push(` ${fieldName}: ${memoValue},`);
1249
1565
  }
1566
+ const takenDerivedInit = new Set([
1567
+ ...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
1568
+ ...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
1569
+ ...ir.metadata.memos.map((m) => this.capitalizeFieldName(m.name))
1570
+ ]);
1571
+ for (const f of this.computeDerivedConstFields(takenDerivedInit)) {
1572
+ lines.push(` ${f.name}: ${f.init},`);
1573
+ }
1250
1574
  const takenInit = new Set([
1251
1575
  ...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
1252
1576
  ...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
@@ -1279,6 +1603,8 @@ ${goFields.join(`
1279
1603
  restBagEntries.push(`${JSON.stringify(jsxName)}: ${goValue}`);
1280
1604
  return;
1281
1605
  }
1606
+ if (jsxName.includes("-"))
1607
+ return;
1282
1608
  lines.push(` ${this.capitalizeFieldName(jsxName)}: ${goValue},`);
1283
1609
  };
1284
1610
  for (const prop of child.props) {
@@ -1360,12 +1686,17 @@ ${goFields.join(`
1360
1686
  const loop = node;
1361
1687
  if (loop.childComponent) {
1362
1688
  if (!result.some((c) => c.name === loop.childComponent.name)) {
1689
+ const hasBodyChildren = loop.childComponent.children.length > 0;
1363
1690
  result.push({
1364
1691
  ...loop.childComponent,
1365
1692
  isDynamic: !loop.isStaticArray,
1366
1693
  isPropDerived: !!loop.isPropDerivedArray,
1367
1694
  loopKey: loop.key ?? undefined,
1368
- loopParam: loop.param ?? undefined
1695
+ loopParam: loop.param ?? undefined,
1696
+ bodyChildren: hasBodyChildren ? loop.childComponent.children : undefined,
1697
+ loopArray: loop.array,
1698
+ loopMarkerId: loop.markerId,
1699
+ loopItemType: loop.itemType
1369
1700
  });
1370
1701
  }
1371
1702
  }
@@ -1388,6 +1719,17 @@ ${goFields.join(`
1388
1719
  if (cond.whenFalse) {
1389
1720
  this.collectNestedComponents(cond.whenFalse, result);
1390
1721
  }
1722
+ } else if (node.type === "if-statement") {
1723
+ const stmt = node;
1724
+ this.collectNestedComponents(stmt.consequent, result);
1725
+ if (stmt.alternate) {
1726
+ this.collectNestedComponents(stmt.alternate, result);
1727
+ }
1728
+ } else if (node.type === "component") {
1729
+ const comp = node;
1730
+ for (const child of comp.children) {
1731
+ this.collectNestedComponents(child, result);
1732
+ }
1391
1733
  }
1392
1734
  }
1393
1735
  collectStaticChildInstances(node) {
@@ -1459,6 +1801,9 @@ ${goFields.join(`
1459
1801
  childrenScopedHtmlExpr: this.extractScopedHtmlChildren(effectiveChildren),
1460
1802
  contextBindings: providerCtx.size > 0 ? providerCtx : undefined
1461
1803
  });
1804
+ for (const child of effectiveChildren) {
1805
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
1806
+ }
1462
1807
  }
1463
1808
  if (comp.name === "Portal" && comp.children) {
1464
1809
  for (const child of comp.children) {
@@ -1486,6 +1831,12 @@ ${goFields.join(`
1486
1831
  if (cond.whenFalse) {
1487
1832
  this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx);
1488
1833
  }
1834
+ } else if (node.type === "if-statement") {
1835
+ const stmt = node;
1836
+ this.collectStaticChildInstancesRecursive(stmt.consequent, result, inLoop, providerCtx);
1837
+ if (stmt.alternate) {
1838
+ this.collectStaticChildInstancesRecursive(stmt.alternate, result, inLoop, providerCtx);
1839
+ }
1489
1840
  } else if (node.type === "provider") {
1490
1841
  const p = node;
1491
1842
  const childCtx = this.extendProviderContext(providerCtx, p);
@@ -1993,8 +2344,8 @@ ${goFields.join(`
1993
2344
  }
1994
2345
  const lines = [];
1995
2346
  lines.push("func() string {");
1996
- lines.push(` k, _ := in.${fieldName}.(string)`);
1997
- lines.push("\t\t\tswitch k {");
2347
+ this.usesFmt = true;
2348
+ lines.push(` switch fmt.Sprint(in.${fieldName}) {`);
1998
2349
  for (const [k, v] of caseEntries) {
1999
2350
  lines.push(` case ${JSON.stringify(k)}: return ${JSON.stringify(v)}`);
2000
2351
  }
@@ -2012,6 +2363,22 @@ ${goFields.join(`
2012
2363
  return segments.join(" + ");
2013
2364
  }
2014
2365
  resolveDynamicPropValue(expr, signals, memos, propsParams) {
2366
+ const cmpMatch = expr.match(/^(\w+)\(\)\s*([!=]==?)\s*(?:'([^']*)'|(-?\d+(?:\.\d+)?))\s*$/);
2367
+ if (cmpMatch) {
2368
+ const [, depName, op, strLit, numLit] = cmpMatch;
2369
+ const signal = signals.find((sg) => sg.getter === depName);
2370
+ const init = signal?.initialValue.trim();
2371
+ const initMatch = init !== undefined ? /^(?:'([^'\\]*)'|(-?\d+(?:\.\d+)?))$/.exec(init) : null;
2372
+ if (initMatch) {
2373
+ const initVal = initMatch[1] ?? initMatch[2];
2374
+ const litVal = strLit ?? numLit;
2375
+ const sameKind = initMatch[1] !== undefined === (strLit !== undefined);
2376
+ if (sameKind) {
2377
+ const equal = initVal === litVal;
2378
+ return String(op.startsWith("!") ? !equal : equal);
2379
+ }
2380
+ }
2381
+ }
2015
2382
  const getterMatch = expr.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\(\)$/);
2016
2383
  if (getterMatch) {
2017
2384
  const getterName = getterMatch[1];
@@ -2021,7 +2388,7 @@ ${goFields.join(`
2021
2388
  }
2022
2389
  const memo = memos.find((m) => m.name === getterName);
2023
2390
  if (memo) {
2024
- return this.computeMemoInitialValue(memo, signals, propsParams);
2391
+ return this.computeMemoInitialValueOrNull(memo, signals, propsParams);
2025
2392
  }
2026
2393
  }
2027
2394
  return null;
@@ -2162,6 +2529,19 @@ ${goFields.join(`
2162
2529
  return node.name.text;
2163
2530
  }
2164
2531
  computeMemoInitialValue(memo, signals, propsParams, propFallbackVars = GoTemplateAdapter.EMPTY_PROP_FALLBACK_VARS, goType) {
2532
+ const resolved = this.computeMemoInitialValueOrNull(memo, signals, propsParams, propFallbackVars);
2533
+ if (resolved !== null)
2534
+ return resolved;
2535
+ if (goType === "bool")
2536
+ return "false";
2537
+ if (goType === "string")
2538
+ return '""';
2539
+ if (goType !== undefined && (goType.startsWith("map[") || goType.startsWith("[]") || goType.startsWith("*") || goType.includes("interface{}") || goType === "any")) {
2540
+ return "nil";
2541
+ }
2542
+ return "0";
2543
+ }
2544
+ computeMemoInitialValueOrNull(memo, signals, propsParams, propFallbackVars = GoTemplateAdapter.EMPTY_PROP_FALLBACK_VARS) {
2165
2545
  const computation = memo.computation;
2166
2546
  const propRef = (propName) => {
2167
2547
  const hoisted = propFallbackVars.get(propName);
@@ -2172,6 +2552,52 @@ ${goFields.join(`
2172
2552
  const tmplMemo = this.computeTemplateLiteralMemoInitialValue(computation, propsParams);
2173
2553
  if (tmplMemo !== null)
2174
2554
  return tmplMemo;
2555
+ const eqMatch = computation.match(/^\(\)\s*=>\s*(\w+)\(\)\s*([!=]==?)\s*'([^']*)'\s*$/);
2556
+ if (eqMatch) {
2557
+ const [, depName, op, lit] = eqMatch;
2558
+ const signal = signals.find((sg) => sg.getter === depName);
2559
+ const initLit = signal ? /^'([^'\\]*)'$/.exec(signal.initialValue.trim()) : null;
2560
+ if (initLit) {
2561
+ const equal = initLit[1] === lit;
2562
+ return String(op.startsWith("!") ? !equal : equal);
2563
+ }
2564
+ }
2565
+ const boolPassthrough = computation.match(/^\(\)\s*=>\s*props\.(\w+)\s*\?\?\s*false\s*$/);
2566
+ if (boolPassthrough) {
2567
+ return propRef(boolPassthrough[1]);
2568
+ }
2569
+ const ternMatch = computation.match(/^\(\)\s*=>\s*(\w+)\(\)\s*\?\s*([\w$]+|'[^']*')\s*:\s*([\w$]+|'[^']*')\s*$/);
2570
+ if (ternMatch) {
2571
+ const [, condName, whenTrue, whenFalse] = ternMatch;
2572
+ const resolveBranch = (b) => {
2573
+ const lit = /^'([^']*)'$/.exec(b);
2574
+ if (lit)
2575
+ return JSON.stringify(lit[1]);
2576
+ const constVal = this.moduleStringConsts.get(b);
2577
+ return constVal !== undefined ? JSON.stringify(constVal) : null;
2578
+ };
2579
+ const t = resolveBranch(whenTrue);
2580
+ const f = resolveBranch(whenFalse);
2581
+ if (t !== null && f !== null) {
2582
+ let condGo = null;
2583
+ const condSignal = signals.find((sg) => sg.getter === condName);
2584
+ if (condSignal) {
2585
+ condGo = this.getSignalInitialValueAsGo(condSignal.initialValue, propsParams, propFallbackVars);
2586
+ } else {
2587
+ const condMemo = (this.currentMemos ?? []).find((m) => m.name === condName);
2588
+ if (condMemo) {
2589
+ condGo = this.computeMemoInitialValueOrNull(condMemo, signals, propsParams, propFallbackVars);
2590
+ }
2591
+ }
2592
+ if (condGo === "true")
2593
+ return t;
2594
+ if (condGo === "false")
2595
+ return f;
2596
+ if (condGo !== null) {
2597
+ return `func() string { if ${condGo} { return ${t} }; return ${f} }()`;
2598
+ }
2599
+ }
2600
+ }
2175
2601
  const arithmeticMatch = computation.match(/\(\)\s*=>\s*(\w+)\(\)\s*([*+\-/])\s*(\d+)/);
2176
2602
  if (arithmeticMatch) {
2177
2603
  const [, depName, operator, operand] = arithmeticMatch;
@@ -2191,8 +2617,8 @@ ${goFields.join(`
2191
2617
  return `${hoisted.varName} ${operator} ${operand}`;
2192
2618
  const fieldName = this.capitalizeFieldName(propName);
2193
2619
  if (param.type) {
2194
- const goType2 = this.typeInfoToGo(param.type, param.defaultValue);
2195
- if (goType2 === "interface{}")
2620
+ const goType = this.typeInfoToGo(param.type, param.defaultValue);
2621
+ if (goType === "interface{}")
2196
2622
  return `in.${fieldName}.(int) ${operator} ${operand}`;
2197
2623
  }
2198
2624
  return `in.${fieldName} ${operator} ${operand}`;
@@ -2221,8 +2647,8 @@ ${goFields.join(`
2221
2647
  if (param) {
2222
2648
  const fieldName = this.capitalizeFieldName(varName);
2223
2649
  if (param.type) {
2224
- const goType2 = this.typeInfoToGo(param.type, param.defaultValue);
2225
- if (goType2 === "interface{}")
2650
+ const goType = this.typeInfoToGo(param.type, param.defaultValue);
2651
+ if (goType === "interface{}")
2226
2652
  return `in.${fieldName}.(int) ${operator} ${operand}`;
2227
2653
  }
2228
2654
  return `in.${fieldName} ${operator} ${operand}`;
@@ -2236,11 +2662,283 @@ ${goFields.join(`
2236
2662
  return `in.${this.capitalizeFieldName(varName)}`;
2237
2663
  }
2238
2664
  }
2239
- if (goType === "bool")
2665
+ const blockReturn = this.resolveBlockBodyMemoModuleConst(computation, signals);
2666
+ if (blockReturn !== null && blockReturn.constValue && blockReturn.constType) {
2667
+ return this.convertInitialValue(blockReturn.constValue, blockReturn.constType, propsParams);
2668
+ }
2669
+ const objMemo = this.computeObjectMemoInitialValue(computation);
2670
+ if (objMemo !== null)
2671
+ return objMemo;
2672
+ return null;
2673
+ }
2674
+ resolveBlockBodyMemoModuleConst(computation, signals) {
2675
+ try {
2676
+ const sf = ts.createSourceFile("__memo.ts", `const __x = (${computation});`, ts.ScriptTarget.Latest, false);
2677
+ const stmt = sf.statements[0];
2678
+ if (!stmt || !ts.isVariableStatement(stmt))
2679
+ return null;
2680
+ let init = stmt.declarationList.declarations[0]?.initializer;
2681
+ while (init && ts.isParenthesizedExpression(init))
2682
+ init = init.expression;
2683
+ if (!init || !ts.isArrowFunction(init))
2684
+ return null;
2685
+ const body = init.body;
2686
+ if (!ts.isBlock(body))
2687
+ return null;
2688
+ const varToSignal = new Map;
2689
+ let guardSignalGetter = null;
2690
+ let returnedConst = null;
2691
+ for (const s of body.statements) {
2692
+ if (ts.isVariableStatement(s)) {
2693
+ for (const decl of s.declarationList.declarations) {
2694
+ if (ts.isIdentifier(decl.name) && decl.initializer && ts.isCallExpression(decl.initializer) && ts.isIdentifier(decl.initializer.expression)) {
2695
+ const callee = decl.initializer.expression.text;
2696
+ if (signals.some((sg) => sg.getter === callee)) {
2697
+ varToSignal.set(decl.name.text, callee);
2698
+ }
2699
+ }
2700
+ }
2701
+ }
2702
+ if (ts.isIfStatement(s) && ts.isPrefixUnaryExpression(s.expression) && s.expression.operator === ts.SyntaxKind.ExclamationToken && ts.isIdentifier(s.expression.operand)) {
2703
+ const guardVar = s.expression.operand.text;
2704
+ const signalGetter = varToSignal.get(guardVar);
2705
+ if (!signalGetter)
2706
+ continue;
2707
+ const thenBlock = ts.isBlock(s.thenStatement) ? s.thenStatement.statements : [s.thenStatement];
2708
+ for (const rs of thenBlock) {
2709
+ if (ts.isReturnStatement(rs) && rs.expression && ts.isIdentifier(rs.expression)) {
2710
+ guardSignalGetter = signalGetter;
2711
+ returnedConst = rs.expression.text;
2712
+ }
2713
+ }
2714
+ }
2715
+ if (guardSignalGetter && returnedConst)
2716
+ break;
2717
+ }
2718
+ if (!guardSignalGetter || !returnedConst)
2719
+ return null;
2720
+ const guardSignal = signals.find((sg) => sg.getter === guardSignalGetter);
2721
+ if (!guardSignal)
2722
+ return null;
2723
+ const iv = guardSignal.initialValue.trim();
2724
+ if (iv !== "null" && iv !== "''" && iv !== '""' && iv !== "0" && iv !== "false") {
2725
+ return null;
2726
+ }
2727
+ const constant = this.localConstants.find((c) => c.name === returnedConst && c.origin?.scope === "module");
2728
+ if (!constant)
2729
+ return null;
2730
+ return {
2731
+ constName: constant.name,
2732
+ constValue: constant.value,
2733
+ constType: constant.type ?? undefined
2734
+ };
2735
+ } catch {
2736
+ return null;
2737
+ }
2738
+ }
2739
+ computeObjectMemoInitialValue(computation) {
2740
+ const arrow = this.parseLiteralExpression(computation);
2741
+ if (!arrow || !ts.isArrowFunction(arrow) || !ts.isBlock(arrow.body))
2742
+ return null;
2743
+ const searchParamsVars = new Set;
2744
+ let retObj = null;
2745
+ const statements = arrow.body.statements;
2746
+ for (let i = 0;i < statements.length; i++) {
2747
+ const s = statements[i];
2748
+ if (ts.isVariableStatement(s)) {
2749
+ for (const d of s.declarationList.declarations) {
2750
+ if (ts.isIdentifier(d.name) && d.initializer && ts.isCallExpression(d.initializer) && ts.isIdentifier(d.initializer.expression) && d.initializer.arguments.length === 0 && this.searchParamsLocals.has(d.initializer.expression.text)) {
2751
+ searchParamsVars.add(d.name.text);
2752
+ }
2753
+ }
2754
+ continue;
2755
+ }
2756
+ if (ts.isReturnStatement(s)) {
2757
+ if (i !== statements.length - 1 || !s.expression)
2758
+ return null;
2759
+ let e = s.expression;
2760
+ while (ts.isParenthesizedExpression(e))
2761
+ e = e.expression;
2762
+ if (!ts.isObjectLiteralExpression(e))
2763
+ return null;
2764
+ retObj = e;
2765
+ continue;
2766
+ }
2767
+ return null;
2768
+ }
2769
+ if (!retObj || retObj.properties.length === 0)
2770
+ return null;
2771
+ const env = { searchParamsVars, params: new Map };
2772
+ const entries = [];
2773
+ for (const prop of retObj.properties) {
2774
+ if (!ts.isPropertyAssignment(prop))
2775
+ return null;
2776
+ const key = ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name) ? prop.name.text : null;
2777
+ if (!key)
2778
+ return null;
2779
+ const go = this.lowerCtorExpr(prop.initializer, env);
2780
+ if (go === null)
2781
+ return null;
2782
+ entries.push(`"${this.capitalizeFieldName(key)}": ${go}`);
2783
+ }
2784
+ return `map[string]interface{}{
2785
+ ${entries.join(`,
2786
+ `)},
2787
+ }`;
2788
+ }
2789
+ lowerCtorExpr(node, env) {
2790
+ while (ts.isParenthesizedExpression(node))
2791
+ node = node.expression;
2792
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
2793
+ return JSON.stringify(node.text);
2794
+ }
2795
+ if (ts.isNumericLiteral(node))
2796
+ return node.text;
2797
+ if (ts.isIdentifier(node)) {
2798
+ const sub = env.params.get(node.text);
2799
+ if (sub !== undefined)
2800
+ return sub;
2801
+ const c = this.localConstants.find((lc) => lc.name === node.text);
2802
+ if (c?.value !== undefined) {
2803
+ if (c.isModule) {
2804
+ const lit = this.parseLiteralExpression(c.value);
2805
+ if (lit && (ts.isStringLiteral(lit) || ts.isNoSubstitutionTemplateLiteral(lit))) {
2806
+ return JSON.stringify(lit.text);
2807
+ }
2808
+ return null;
2809
+ }
2810
+ if (env.consts?.has(node.text))
2811
+ return null;
2812
+ const inner = this.parseLiteralExpression(c.value);
2813
+ if (!inner)
2814
+ return null;
2815
+ return this.lowerCtorExpr(inner, {
2816
+ ...env,
2817
+ consts: new Set([...env.consts ?? [], node.text])
2818
+ });
2819
+ }
2820
+ return null;
2821
+ }
2822
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === this.propsObjectName) {
2823
+ return `in.${this.capitalizeFieldName(node.name.text)}`;
2824
+ }
2825
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
2826
+ const method = node.expression.name.text;
2827
+ const recv = node.expression.expression;
2828
+ if (method === "get" && ts.isIdentifier(recv) && env.searchParamsVars.has(recv.text) && node.arguments.length === 1 && ts.isStringLiteral(node.arguments[0])) {
2829
+ return `in.SearchParams.Get(${JSON.stringify(node.arguments[0].text)})`;
2830
+ }
2831
+ if (method === "includes" && node.arguments.length === 1) {
2832
+ const arr = this.lowerCtorStringArray(recv);
2833
+ const elem = this.lowerCtorExpr(node.arguments[0], env);
2834
+ if (arr !== null && elem !== null)
2835
+ return `bf.Includes(${arr}, ${elem})`;
2836
+ return null;
2837
+ }
2838
+ if (method === "replace" && node.arguments.length === 2 && node.arguments[0].kind === ts.SyntaxKind.RegularExpressionLiteral && node.arguments[0].getText() === "/\\/+$/" && (ts.isStringLiteral(node.arguments[1]) || ts.isNoSubstitutionTemplateLiteral(node.arguments[1])) && node.arguments[1].text === "") {
2839
+ const recvGo = this.lowerCtorExpr(recv, env);
2840
+ if (recvGo === null)
2841
+ return null;
2842
+ this.needsStringsImport = true;
2843
+ return `strings.TrimRight(${recvGo}, "/")`;
2844
+ }
2845
+ return null;
2846
+ }
2847
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
2848
+ const fnConst = this.localConstants.find((lc) => lc.name === node.expression.text && lc.isModule);
2849
+ if (fnConst?.value) {
2850
+ const fn = this.parseLiteralExpression(fnConst.value);
2851
+ if (fn && ts.isArrowFunction(fn) && !ts.isBlock(fn.body) && fn.parameters.length === node.arguments.length) {
2852
+ const params = new Map(env.params);
2853
+ for (let i = 0;i < fn.parameters.length; i++) {
2854
+ const p = fn.parameters[i];
2855
+ if (!ts.isIdentifier(p.name))
2856
+ return null;
2857
+ const argGo = this.lowerCtorExpr(node.arguments[i], env);
2858
+ if (argGo === null)
2859
+ return null;
2860
+ params.set(p.name.text, argGo);
2861
+ }
2862
+ return this.lowerCtorExpr(fn.body, { searchParamsVars: env.searchParamsVars, params });
2863
+ }
2864
+ }
2865
+ return null;
2866
+ }
2867
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
2868
+ const left = this.lowerCtorExpr(node.left, env);
2869
+ if (left === null)
2870
+ return null;
2871
+ let r = node.right;
2872
+ while (ts.isParenthesizedExpression(r))
2873
+ r = r.expression;
2874
+ if ((ts.isStringLiteral(r) || ts.isNoSubstitutionTemplateLiteral(r)) && r.text === "") {
2875
+ return left;
2876
+ }
2877
+ const right = this.lowerCtorExpr(node.right, env);
2878
+ if (right === null)
2879
+ return null;
2880
+ return `func() string { v := ${left}; if v != "" { return v }; return ${right} }()`;
2881
+ }
2882
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
2883
+ const left = this.lowerCtorExpr(node.left, env);
2884
+ const right = this.lowerCtorExpr(node.right, env);
2885
+ if (left === null || right === null)
2886
+ return null;
2887
+ return `func() string { v := ${left}; if v != "" { return v }; return ${right} }()`;
2888
+ }
2889
+ if (ts.isConditionalExpression(node)) {
2890
+ const cond = this.lowerCtorCond(node.condition, env);
2891
+ const t = this.lowerCtorExpr(node.whenTrue, env);
2892
+ const f = this.lowerCtorExpr(node.whenFalse, env);
2893
+ if (cond !== null && t !== null && f !== null) {
2894
+ return `func() string { if ${cond} { return ${t} }; return ${f} }()`;
2895
+ }
2896
+ return null;
2897
+ }
2898
+ return null;
2899
+ }
2900
+ lowerCtorCond(node, env) {
2901
+ while (ts.isParenthesizedExpression(node))
2902
+ node = node.expression;
2903
+ if (node.kind === ts.SyntaxKind.TrueKeyword)
2904
+ return "true";
2905
+ if (node.kind === ts.SyntaxKind.FalseKeyword)
2240
2906
  return "false";
2241
- if (goType === "string")
2242
- return '""';
2243
- return "0";
2907
+ if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.ExclamationToken) {
2908
+ const inner = this.lowerCtorCond(node.operand, env);
2909
+ return inner === null ? null : `!(${inner})`;
2910
+ }
2911
+ if (ts.isBinaryExpression(node)) {
2912
+ const op = node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : node.operatorToken.kind === ts.SyntaxKind.BarBarToken ? "||" : null;
2913
+ if (op) {
2914
+ const l = this.lowerCtorCond(node.left, env);
2915
+ const r = this.lowerCtorCond(node.right, env);
2916
+ return l !== null && r !== null ? `(${l} ${op} ${r})` : null;
2917
+ }
2918
+ }
2919
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "includes") {
2920
+ return this.lowerCtorExpr(node, env);
2921
+ }
2922
+ return null;
2923
+ }
2924
+ lowerCtorStringArray(node) {
2925
+ let arr = node;
2926
+ if (ts.isIdentifier(node)) {
2927
+ const c = this.localConstants.find((lc) => lc.name === node.text && lc.isModule);
2928
+ if (!c?.value)
2929
+ return null;
2930
+ arr = this.parseLiteralExpression(c.value);
2931
+ }
2932
+ if (!arr || !ts.isArrayLiteralExpression(arr))
2933
+ return null;
2934
+ const elems = [];
2935
+ for (const el of arr.elements) {
2936
+ if (ts.isStringLiteral(el) || ts.isNoSubstitutionTemplateLiteral(el)) {
2937
+ elems.push(JSON.stringify(el.text));
2938
+ } else
2939
+ return null;
2940
+ }
2941
+ return `[]string{${elems.join(", ")}}`;
2244
2942
  }
2245
2943
  isTemplateLiteralMemo(computation) {
2246
2944
  const sf = ts.createSourceFile("__memo.ts", `const __x = (${computation});`, ts.ScriptTarget.Latest, false);
@@ -2296,6 +2994,10 @@ ${goFields.join(`
2296
2994
  if (this.typeInfoToGo(memo.type) === "interface{}" && this.isBooleanMemo(memo, signals, propsParamMap)) {
2297
2995
  return "bool";
2298
2996
  }
2997
+ const blockReturn = this.resolveBlockBodyMemoModuleConst(memo.computation, signals);
2998
+ if (blockReturn?.constType?.kind === "array") {
2999
+ return this.typeInfoToGo(blockReturn.constType);
3000
+ }
2299
3001
  return this.typeInfoToGo(memo.type);
2300
3002
  }
2301
3003
  isBooleanMemo(memo, signals, propsParamMap) {
@@ -2575,6 +3277,9 @@ ${goFields.join(`
2575
3277
  if (element.slotId) {
2576
3278
  hydrationAttrs += ` ${this.renderSlotMarker(element.slotId)}`;
2577
3279
  }
3280
+ if (element.regionId) {
3281
+ hydrationAttrs += ` ${BF_REGION}="${element.regionId}"`;
3282
+ }
2578
3283
  const voidElements = [
2579
3284
  "area",
2580
3285
  "base",
@@ -2603,8 +3308,9 @@ ${goFields.join(`
2603
3308
  }
2604
3309
  return "";
2605
3310
  }
2606
- const goExpr = this.convertExpressionToGo(expr.expr);
2607
- if (goExpr.startsWith("{{")) {
3311
+ const classify = {};
3312
+ const goExpr = this.convertExpressionToGo(expr.expr, classify);
3313
+ if (this.isTemplateFragment(goExpr, classify.parsed?.kind)) {
2608
3314
  if (expr.slotId) {
2609
3315
  return `{{bfTextStart "${expr.slotId}"}}${goExpr}{{bfTextEnd}}`;
2610
3316
  }
@@ -2615,6 +3321,9 @@ ${goFields.join(`
2615
3321
  }
2616
3322
  return `{{${goExpr}}}`;
2617
3323
  }
3324
+ isTemplateFragment(go, kind) {
3325
+ return go.startsWith("{{") || kind === "template-literal";
3326
+ }
2618
3327
  renderClientOnlyConditional(cond) {
2619
3328
  if (cond.slotId) {
2620
3329
  return `{{bfComment "cond-start:${cond.slotId}"}}{{bfComment "cond-end:${cond.slotId}"}}`;
@@ -2625,6 +3334,8 @@ ${goFields.join(`
2625
3334
  return emitParsedExpr(expr, this);
2626
3335
  }
2627
3336
  identifier(name) {
3337
+ if (name === "undefined" || name === "null")
3338
+ return '""';
2628
3339
  for (let i = this.loopBindingStack.length - 1;i >= 0; i--) {
2629
3340
  const acc = this.loopBindingStack[i].get(name);
2630
3341
  if (acc !== undefined)
@@ -2633,6 +3344,9 @@ ${goFields.join(`
2633
3344
  const inlined = this.resolveModuleStringConst(name);
2634
3345
  if (inlined !== null)
2635
3346
  return inlined;
3347
+ const inlinedNum = this.resolveModuleNumericConst(name);
3348
+ if (inlinedNum !== null)
3349
+ return inlinedNum;
2636
3350
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
2637
3351
  if (currentLoopParam && name === currentLoopParam)
2638
3352
  return ".";
@@ -2640,7 +3354,86 @@ ${goFields.join(`
2640
3354
  return `$${name}`;
2641
3355
  if (this.loopVarRefCount.has(name))
2642
3356
  return `$${name}`;
2643
- return this.rootFieldRef(name);
3357
+ if (this.localConstants.some((c) => c.name === name && !c.isModule && !c.containsArrow)) {
3358
+ this.referencedDerivedConsts.add(name);
3359
+ }
3360
+ return this.searchParamsFieldRef(name) ?? this.rootFieldRef(name);
3361
+ }
3362
+ computeDerivedConstFields(takenFieldNames) {
3363
+ const fields = [];
3364
+ for (const name of this.referencedDerivedConsts) {
3365
+ const fieldName = this.capitalizeFieldName(name);
3366
+ if (takenFieldNames.has(fieldName))
3367
+ continue;
3368
+ const c = this.localConstants.find((lc) => lc.name === name && !lc.isModule && lc.value);
3369
+ if (!c?.value)
3370
+ continue;
3371
+ const expr = this.parseLiteralExpression(c.value);
3372
+ if (!expr)
3373
+ continue;
3374
+ if (!this.isStringExpr(expr, new Set))
3375
+ continue;
3376
+ const init = this.lowerCtorExpr(expr, {
3377
+ searchParamsVars: new Set,
3378
+ params: new Map,
3379
+ consts: new Set([name])
3380
+ });
3381
+ if (init === null)
3382
+ continue;
3383
+ fields.push({ name: fieldName, init });
3384
+ }
3385
+ return fields;
3386
+ }
3387
+ isStringExpr(node, seen) {
3388
+ while (ts.isParenthesizedExpression(node))
3389
+ node = node.expression;
3390
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isTemplateExpression(node)) {
3391
+ return true;
3392
+ }
3393
+ if (ts.isBinaryExpression(node)) {
3394
+ const op = node.operatorToken.kind;
3395
+ if (op === ts.SyntaxKind.PlusToken) {
3396
+ return this.isStringExpr(node.left, seen) || this.isStringExpr(node.right, seen);
3397
+ }
3398
+ if (op === ts.SyntaxKind.BarBarToken || op === ts.SyntaxKind.QuestionQuestionToken) {
3399
+ return this.isStringExpr(node.left, seen) && this.isStringExpr(node.right, seen);
3400
+ }
3401
+ return false;
3402
+ }
3403
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
3404
+ const m = node.expression.name.text;
3405
+ const STRING_METHODS = new Set([
3406
+ "replace",
3407
+ "trim",
3408
+ "trimStart",
3409
+ "trimEnd",
3410
+ "toLowerCase",
3411
+ "toUpperCase",
3412
+ "slice",
3413
+ "substring",
3414
+ "substr",
3415
+ "padStart",
3416
+ "padEnd",
3417
+ "concat",
3418
+ "repeat",
3419
+ "get"
3420
+ ]);
3421
+ return STRING_METHODS.has(m);
3422
+ }
3423
+ if (ts.isConditionalExpression(node)) {
3424
+ return this.isStringExpr(node.whenTrue, seen) && this.isStringExpr(node.whenFalse, seen);
3425
+ }
3426
+ if (ts.isIdentifier(node)) {
3427
+ if (seen.has(node.text))
3428
+ return false;
3429
+ const c = this.localConstants.find((lc) => lc.name === node.text && !lc.isModule && lc.value);
3430
+ if (c?.value) {
3431
+ const inner = this.parseLiteralExpression(c.value);
3432
+ if (inner)
3433
+ return this.isStringExpr(inner, new Set([...seen, node.text]));
3434
+ }
3435
+ }
3436
+ return false;
2644
3437
  }
2645
3438
  isOuterLoopParam(name) {
2646
3439
  const top = this.loopParamStack.length - 1;
@@ -2654,70 +3447,26 @@ ${goFields.join(`
2654
3447
  const prefix = this.loopParamStack.length > 0 ? "$." : ".";
2655
3448
  return `${prefix}${this.capitalizeFieldName(name)}`;
2656
3449
  }
3450
+ searchParamsFieldRef(name) {
3451
+ return this.searchParamsLocals.has(name) ? this.rootFieldRef("searchParams") : null;
3452
+ }
2657
3453
  collectModuleStringConsts(constants) {
2658
- const map = new Map;
2659
- for (const c of constants ?? []) {
2660
- if (!c.isModule)
2661
- continue;
2662
- if (c.value === undefined)
2663
- continue;
2664
- const literal = this.parsePureStringLiteral(c.value);
2665
- if (literal !== null)
2666
- map.set(c.name, literal);
2667
- }
2668
- return map;
3454
+ return collectModuleStringConstsShared(constants);
2669
3455
  }
2670
- parsePureStringLiteral(source) {
2671
- const sf = ts.createSourceFile("__const.ts", `const __x = (${source});`, ts.ScriptTarget.Latest, false);
2672
- const stmt = sf.statements[0];
2673
- if (!stmt || !ts.isVariableStatement(stmt))
2674
- return null;
2675
- const decl = stmt.declarationList.declarations[0];
2676
- let init = decl?.initializer;
2677
- while (init && ts.isParenthesizedExpression(init))
2678
- init = init.expression;
2679
- if (!init)
3456
+ resolveModuleStringConst(name) {
3457
+ if (this.loopParamStack.length > 0 && this.loopParamStack[this.loopParamStack.length - 1] === name) {
2680
3458
  return null;
2681
- if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
2682
- return init.text;
2683
3459
  }
2684
- const joined = this.evalStringArrayJoin(init);
2685
- if (joined !== null)
2686
- return joined;
2687
- return null;
2688
- }
2689
- evalStringArrayJoin(node) {
2690
- if (!ts.isCallExpression(node))
2691
- return null;
2692
- const callee = node.expression;
2693
- if (!ts.isPropertyAccessExpression(callee))
3460
+ if (this.loopVarRefCount.has(name))
2694
3461
  return null;
2695
- if (callee.name.text !== "join")
3462
+ if (this.isOuterLoopParam(name))
2696
3463
  return null;
2697
- let recv = callee.expression;
2698
- while (ts.isParenthesizedExpression(recv))
2699
- recv = recv.expression;
2700
- if (!ts.isArrayLiteralExpression(recv))
3464
+ const value = this.moduleStringConsts.get(name);
3465
+ if (value === undefined)
2701
3466
  return null;
2702
- const parts = [];
2703
- for (const el of recv.elements) {
2704
- if (ts.isStringLiteral(el) || ts.isNoSubstitutionTemplateLiteral(el)) {
2705
- parts.push(el.text);
2706
- } else {
2707
- return null;
2708
- }
2709
- }
2710
- let sep = ",";
2711
- if (node.arguments.length >= 1) {
2712
- const arg = node.arguments[0];
2713
- if (ts.isStringLiteral(arg) || ts.isNoSubstitutionTemplateLiteral(arg))
2714
- sep = arg.text;
2715
- else
2716
- return null;
2717
- }
2718
- return parts.join(sep);
3467
+ return `"${this.escapeGoString(value)}"`;
2719
3468
  }
2720
- resolveModuleStringConst(name) {
3469
+ resolveModuleNumericConst(name) {
2721
3470
  if (this.loopParamStack.length > 0 && this.loopParamStack[this.loopParamStack.length - 1] === name) {
2722
3471
  return null;
2723
3472
  }
@@ -2725,10 +3474,11 @@ ${goFields.join(`
2725
3474
  return null;
2726
3475
  if (this.isOuterLoopParam(name))
2727
3476
  return null;
2728
- const value = this.moduleStringConsts.get(name);
2729
- if (value === undefined)
3477
+ const c = this.localConstants.find((k) => k.name === name && k.isModule && !k.containsArrow);
3478
+ if (!c || c.value === undefined)
2730
3479
  return null;
2731
- return `"${this.escapeGoString(value)}"`;
3480
+ const v = c.value.trim().replace(/(?<=\d)_(?=\d)/g, "");
3481
+ return /^-?\d+(\.\d+)?$/.test(v) ? v : null;
2732
3482
  }
2733
3483
  literal(value, literalType) {
2734
3484
  if (literalType === "string")
@@ -2739,7 +3489,7 @@ ${goFields.join(`
2739
3489
  }
2740
3490
  call(callee, args, emit) {
2741
3491
  if (callee.kind === "identifier" && args.length === 0) {
2742
- return this.rootFieldRef(callee.name);
3492
+ return this.searchParamsFieldRef(callee.name) ?? this.rootFieldRef(callee.name);
2743
3493
  }
2744
3494
  const path = identifierPath(callee);
2745
3495
  if (path && this.templatePrimitives[path]) {
@@ -2770,6 +3520,13 @@ ${goFields.join(`
2770
3520
  if (result)
2771
3521
  return result;
2772
3522
  }
3523
+ if (property === "length" && object.kind === "call" && object.callee.kind === "identifier" && object.args.length === 0) {
3524
+ const slice = this.memoBackedLoopSlice.get(object.callee.name);
3525
+ if (slice) {
3526
+ const prefix = this.loopParamStack.length > 0 ? "$." : ".";
3527
+ return `len ${prefix}${slice}`;
3528
+ }
3529
+ }
2773
3530
  if (object.kind === "higher-order" && (object.method === "find" || object.method === "findLast")) {
2774
3531
  const findResult = this.renderHigherOrderExpr(object, emit);
2775
3532
  if (findResult) {
@@ -2782,6 +3539,11 @@ ${goFields.join(`
2782
3539
  if (object.kind === "identifier" && this.propsObjectName && object.name === this.propsObjectName) {
2783
3540
  return this.rootFieldRef(property);
2784
3541
  }
3542
+ if (object.kind === "identifier") {
3543
+ const staticValue = this.resolveStaticRecordLiteralIndex(`${object.name}.${property}`);
3544
+ if (staticValue !== null)
3545
+ return staticValue;
3546
+ }
2785
3547
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
2786
3548
  if (object.kind === "identifier" && currentLoopParam && object.name === currentLoopParam) {
2787
3549
  return `.${this.capitalizeFieldName(property)}`;
@@ -2791,34 +3553,43 @@ ${goFields.join(`
2791
3553
  return `len ${obj}`;
2792
3554
  return `${obj}.${this.capitalizeFieldName(property)}`;
2793
3555
  }
3556
+ indexAccess(object, index, emit) {
3557
+ return `index ${wrapIfMultiToken(emit(object))} ${wrapIfMultiToken(emit(index))}`;
3558
+ }
2794
3559
  binary(op, left, right, emit) {
2795
3560
  const l = emit(left);
2796
3561
  const r = emit(right);
3562
+ const wl = wrapIfMultiToken(l);
3563
+ const wr = wrapIfMultiToken(r);
2797
3564
  switch (op) {
2798
3565
  case "===":
2799
- case "==":
2800
- return `eq ${l} ${r}`;
3566
+ case "==": {
3567
+ const [el, er] = stringTolerantEqOperands(l, r);
3568
+ return `eq ${wrapIfMultiToken(el)} ${wrapIfMultiToken(er)}`;
3569
+ }
2801
3570
  case "!==":
2802
- case "!=":
2803
- return `ne ${l} ${r}`;
3571
+ case "!=": {
3572
+ const [el, er] = stringTolerantEqOperands(l, r);
3573
+ return `ne ${wrapIfMultiToken(el)} ${wrapIfMultiToken(er)}`;
3574
+ }
2804
3575
  case ">":
2805
- return `gt ${l} ${r}`;
3576
+ return `gt ${wl} ${wr}`;
2806
3577
  case "<":
2807
- return `lt ${l} ${r}`;
3578
+ return `lt ${wl} ${wr}`;
2808
3579
  case ">=":
2809
- return `ge ${l} ${r}`;
3580
+ return `ge ${wl} ${wr}`;
2810
3581
  case "<=":
2811
- return `le ${l} ${r}`;
3582
+ return `le ${wl} ${wr}`;
2812
3583
  case "+":
2813
- return `bf_add ${l} ${r}`;
3584
+ return `bf_add ${wl} ${wr}`;
2814
3585
  case "-":
2815
- return `bf_sub ${l} ${r}`;
3586
+ return `bf_sub ${wl} ${wr}`;
2816
3587
  case "*":
2817
- return `bf_mul ${l} ${r}`;
3588
+ return `bf_mul ${wl} ${wr}`;
2818
3589
  case "/":
2819
- return `bf_div ${l} ${r}`;
3590
+ return `bf_div ${wl} ${wr}`;
2820
3591
  case "%":
2821
- return `bf_mod ${l} ${r}`;
3592
+ return `bf_mod ${wl} ${wr}`;
2822
3593
  default:
2823
3594
  return `${l} ${op} ${r}`;
2824
3595
  }
@@ -2832,10 +3603,8 @@ ${goFields.join(`
2832
3603
  return arg;
2833
3604
  }
2834
3605
  logical(op, left, right, emit) {
2835
- const l = emit(left);
2836
- const r = emit(right);
2837
- const wrapLeft = this.needsParens(left) ? `(${l})` : l;
2838
- const wrapRight = this.needsParens(right) ? `(${r})` : r;
3606
+ const wrapLeft = wrapIfMultiToken(emit(left));
3607
+ const wrapRight = wrapIfMultiToken(emit(right));
2839
3608
  if (op === "&&")
2840
3609
  return `and ${wrapLeft} ${wrapRight}`;
2841
3610
  return `or ${wrapLeft} ${wrapRight}`;
@@ -2852,7 +3621,8 @@ ${goFields.join(`
2852
3621
  if (part.type === "string") {
2853
3622
  result += part.value;
2854
3623
  } else {
2855
- result += `{{${emit(part.expr)}}}`;
3624
+ const e = emit(part.expr);
3625
+ result += this.isTemplateFragment(e, part.expr.kind) ? e : `{{${e}}}`;
2856
3626
  }
2857
3627
  }
2858
3628
  return result;
@@ -2953,6 +3723,11 @@ ${goFields.join(`
2953
3723
  const recv = emit(object);
2954
3724
  return `bf_trim ${wrapIfMultiToken(recv)}`;
2955
3725
  }
3726
+ case "toFixed": {
3727
+ const recv = emit(object);
3728
+ const digits = args.length >= 1 ? emit(args[0]) : "0";
3729
+ return `bf_to_fixed ${wrapIfMultiToken(recv)} ${wrapIfMultiToken(digits)}`;
3730
+ }
2956
3731
  case "split": {
2957
3732
  const recv = emit(object);
2958
3733
  if (args.length === 0) {
@@ -3438,11 +4213,33 @@ ${goFields.join(`
3438
4213
  return false;
3439
4214
  }
3440
4215
  }
3441
- convertExpressionToGo(jsExpr) {
4216
+ convertExpressionToGo(jsExpr, out) {
3442
4217
  const trimmed = jsExpr.trim();
3443
4218
  if (trimmed === "null" || trimmed === "undefined") {
3444
4219
  return '""';
3445
4220
  }
4221
+ const staticIndexed = this.resolveStaticRecordLiteralIndex(trimmed);
4222
+ if (staticIndexed !== null) {
4223
+ return staticIndexed;
4224
+ }
4225
+ if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
4226
+ const litConst = (this.localConstants ?? []).find((c) => c.name === trimmed);
4227
+ if (litConst?.value !== undefined) {
4228
+ const v = litConst.value.trim();
4229
+ if (/^-?\d+(\.\d+)?$/.test(v))
4230
+ return v;
4231
+ const strLit = /^'([^'\\]*)'$/.exec(v) ?? /^"([^"\\]*)"$/.exec(v);
4232
+ if (strLit)
4233
+ return JSON.stringify(strLit[1]);
4234
+ }
4235
+ }
4236
+ const urlBuilt = this.lowerUrlBuilderHelperCall(trimmed);
4237
+ if (urlBuilt !== null)
4238
+ return urlBuilt;
4239
+ const inlined = this.inlineLocalHelperCall(trimmed);
4240
+ if (inlined !== null) {
4241
+ return this.convertExpressionToGo(inlined, out);
4242
+ }
3446
4243
  const parsed = parseExpression(trimmed);
3447
4244
  const support = isSupported(parsed);
3448
4245
  if (!support.supported) {
@@ -3457,8 +4254,270 @@ ${goFields.join(`
3457
4254
  });
3458
4255
  return `""`;
3459
4256
  }
4257
+ if (out)
4258
+ out.parsed = parsed;
3460
4259
  return this.renderParsedExpr(parsed);
3461
4260
  }
4261
+ inlineLocalHelperCall(jsExpr) {
4262
+ if (this.localHelperNames.size === 0)
4263
+ return null;
4264
+ const head = /^\s*([A-Za-z_$][\w$]*)\s*\(/.exec(jsExpr);
4265
+ if (!head || !this.localHelperNames.has(head[1]))
4266
+ return null;
4267
+ const call = this.parseLiteralExpression(jsExpr);
4268
+ if (!call || !ts.isCallExpression(call) || !ts.isIdentifier(call.expression))
4269
+ return null;
4270
+ if (call.arguments.some((a) => ts.isSpreadElement(a)))
4271
+ return null;
4272
+ const fnConst = this.localConstants.find((c) => c.name === call.expression.text && !c.isModule && c.value);
4273
+ if (!fnConst?.value)
4274
+ return null;
4275
+ const fn = this.parseLiteralExpression(fnConst.value);
4276
+ if (!fn || !ts.isArrowFunction(fn) || ts.isBlock(fn.body))
4277
+ return null;
4278
+ if (fn.parameters.length !== call.arguments.length)
4279
+ return null;
4280
+ if (this.bodyCallsLocalHelper(fn.body))
4281
+ return null;
4282
+ const subs = new Map;
4283
+ for (let i = 0;i < fn.parameters.length; i++) {
4284
+ const p = fn.parameters[i];
4285
+ if (!ts.isIdentifier(p.name))
4286
+ return null;
4287
+ subs.set(p.name.text, `(${call.arguments[i].getText()})`);
4288
+ }
4289
+ if (!this.isSpliceSafeHelperBody(fn.body, new Set(subs.keys())))
4290
+ return null;
4291
+ return this.substituteHelperParams(fn.body, subs);
4292
+ }
4293
+ isSpliceSafeHelperBody(body, paramNames) {
4294
+ let safe = true;
4295
+ const visit = (n) => {
4296
+ if (!safe)
4297
+ return;
4298
+ if (ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n)) {
4299
+ safe = false;
4300
+ return;
4301
+ }
4302
+ if (ts.isShorthandPropertyAssignment(n) && paramNames.has(n.name.text)) {
4303
+ safe = false;
4304
+ return;
4305
+ }
4306
+ ts.forEachChild(n, visit);
4307
+ };
4308
+ visit(body);
4309
+ return safe;
4310
+ }
4311
+ bodyCallsLocalHelper(body) {
4312
+ let found = false;
4313
+ const visit = (n) => {
4314
+ if (found)
4315
+ return;
4316
+ if (ts.isCallExpression(n) && ts.isIdentifier(n.expression)) {
4317
+ const name = n.expression.text;
4318
+ if (this.localConstants.some((c) => c.name === name && !c.isModule && c.containsArrow)) {
4319
+ found = true;
4320
+ return;
4321
+ }
4322
+ }
4323
+ ts.forEachChild(n, visit);
4324
+ };
4325
+ visit(body);
4326
+ return found;
4327
+ }
4328
+ substituteHelperParams(body, subs) {
4329
+ const sf = body.getSourceFile();
4330
+ const base = body.getStart(sf);
4331
+ const repls = [];
4332
+ const visit = (node) => {
4333
+ if (ts.isPropertyAccessExpression(node)) {
4334
+ visit(node.expression);
4335
+ return;
4336
+ }
4337
+ if (ts.isPropertyAssignment(node)) {
4338
+ if (ts.isComputedPropertyName(node.name))
4339
+ visit(node.name);
4340
+ visit(node.initializer);
4341
+ return;
4342
+ }
4343
+ if (ts.isIdentifier(node)) {
4344
+ const sub = subs.get(node.text);
4345
+ if (sub !== undefined) {
4346
+ repls.push({ start: node.getStart(sf) - base, end: node.getEnd() - base, text: sub });
4347
+ return;
4348
+ }
4349
+ }
4350
+ ts.forEachChild(node, visit);
4351
+ };
4352
+ visit(body);
4353
+ let text = body.getText(sf);
4354
+ for (const r of repls.sort((a, b) => b.start - a.start)) {
4355
+ text = text.slice(0, r.start) + r.text + text.slice(r.end);
4356
+ }
4357
+ return text;
4358
+ }
4359
+ lowerUrlBuilderHelperCall(jsExpr) {
4360
+ if (this.localHelperNames.size === 0)
4361
+ return null;
4362
+ const head = /^\s*([A-Za-z_$][\w$]*)\s*\(/.exec(jsExpr);
4363
+ if (!head || !this.localHelperNames.has(head[1]))
4364
+ return null;
4365
+ const call = this.parseLiteralExpression(jsExpr);
4366
+ if (!call || !ts.isCallExpression(call) || !ts.isIdentifier(call.expression))
4367
+ return null;
4368
+ if (call.arguments.some((a) => ts.isSpreadElement(a)))
4369
+ return null;
4370
+ const fnConst = this.localConstants.find((c) => c.name === call.expression.text && !c.isModule && c.value);
4371
+ if (!fnConst?.value)
4372
+ return null;
4373
+ const fn = this.parseLiteralExpression(fnConst.value);
4374
+ if (!fn || !ts.isArrowFunction(fn) || fn.parameters.length !== call.arguments.length)
4375
+ return null;
4376
+ const subs = new Map;
4377
+ for (let i = 0;i < fn.parameters.length; i++) {
4378
+ const p = fn.parameters[i];
4379
+ if (!ts.isIdentifier(p.name))
4380
+ return null;
4381
+ subs.set(p.name.text, `(${call.arguments[i].getText()})`);
4382
+ }
4383
+ const shape = this.extractUrlBuilder(fn);
4384
+ if (shape)
4385
+ return this.emitUrlBuilder(shape, subs);
4386
+ if (!ts.isBlock(fn.body)) {
4387
+ let body = fn.body;
4388
+ while (ts.isParenthesizedExpression(body))
4389
+ body = body.expression;
4390
+ if (ts.isCallExpression(body) && ts.isIdentifier(body.expression) && this.localHelperNames.has(body.expression.text)) {
4391
+ return this.lowerUrlBuilderHelperCall(this.substituteHelperParams(body, subs));
4392
+ }
4393
+ }
4394
+ return null;
4395
+ }
4396
+ extractUrlBuilder(arrow) {
4397
+ if (!ts.isBlock(arrow.body))
4398
+ return null;
4399
+ let builderVar = null;
4400
+ let base = null;
4401
+ const sets = [];
4402
+ for (const s of arrow.body.statements) {
4403
+ if (ts.isVariableStatement(s)) {
4404
+ for (const d of s.declarationList.declarations) {
4405
+ if (ts.isIdentifier(d.name) && d.initializer && ts.isNewExpression(d.initializer) && ts.isIdentifier(d.initializer.expression) && d.initializer.expression.text === "URLSearchParams" && (d.initializer.arguments?.length ?? 0) === 0) {
4406
+ builderVar = d.name.text;
4407
+ }
4408
+ }
4409
+ continue;
4410
+ }
4411
+ if (ts.isIfStatement(s) && !s.elseStatement && builderVar) {
4412
+ const set = this.matchUrlSet(s.thenStatement, builderVar);
4413
+ if (!set)
4414
+ return null;
4415
+ sets.push({ guard: s.expression, key: set.key, value: set.value });
4416
+ continue;
4417
+ }
4418
+ if (ts.isExpressionStatement(s) && builderVar) {
4419
+ const set = this.matchUrlSet(s, builderVar);
4420
+ if (set) {
4421
+ sets.push({ guard: null, key: set.key, value: set.value });
4422
+ continue;
4423
+ }
4424
+ return null;
4425
+ }
4426
+ if (ts.isReturnStatement(s) && s.expression) {
4427
+ let e = s.expression;
4428
+ while (ts.isParenthesizedExpression(e))
4429
+ e = e.expression;
4430
+ if (!ts.isConditionalExpression(e))
4431
+ return null;
4432
+ base = e.whenFalse;
4433
+ continue;
4434
+ }
4435
+ return null;
4436
+ }
4437
+ if (!builderVar || !base || sets.length === 0)
4438
+ return null;
4439
+ return { base, sets };
4440
+ }
4441
+ matchUrlSet(node, builderVar) {
4442
+ const stmt = ts.isBlock(node) ? node.statements.length === 1 ? node.statements[0] : null : node;
4443
+ if (!stmt || !ts.isExpressionStatement(stmt))
4444
+ return null;
4445
+ const call = stmt.expression;
4446
+ if (ts.isCallExpression(call) && ts.isPropertyAccessExpression(call.expression) && ts.isIdentifier(call.expression.expression) && call.expression.expression.text === builderVar && call.expression.name.text === "set" && call.arguments.length === 2 && ts.isStringLiteral(call.arguments[0])) {
4447
+ return { key: call.arguments[0].text, value: call.arguments[1] };
4448
+ }
4449
+ return null;
4450
+ }
4451
+ emitUrlBuilder(shape, subs) {
4452
+ const lowerExpr = (n) => this.convertExpressionToGo(this.substituteHelperParams(n, subs));
4453
+ const baseGo = wrapIfMultiToken(lowerExpr(shape.base));
4454
+ const parts = [baseGo];
4455
+ for (const set of shape.sets) {
4456
+ const guardGo = set.guard ? this.lowerUrlGuard(set.guard, subs) : "true";
4457
+ parts.push(`(${guardGo})`);
4458
+ parts.push(JSON.stringify(set.key));
4459
+ parts.push(wrapIfMultiToken(lowerExpr(set.value)));
4460
+ }
4461
+ return `bf_query ${parts.join(" ")}`;
4462
+ }
4463
+ lowerUrlGuard(guard, subs) {
4464
+ let g = guard;
4465
+ while (ts.isParenthesizedExpression(g))
4466
+ g = g.expression;
4467
+ const isBoolShape = ts.isBinaryExpression(g) && [
4468
+ ts.SyntaxKind.EqualsEqualsToken,
4469
+ ts.SyntaxKind.EqualsEqualsEqualsToken,
4470
+ ts.SyntaxKind.ExclamationEqualsToken,
4471
+ ts.SyntaxKind.ExclamationEqualsEqualsToken,
4472
+ ts.SyntaxKind.LessThanToken,
4473
+ ts.SyntaxKind.GreaterThanToken,
4474
+ ts.SyntaxKind.LessThanEqualsToken,
4475
+ ts.SyntaxKind.GreaterThanEqualsToken
4476
+ ].includes(g.operatorToken.kind) || ts.isPrefixUnaryExpression(g) && g.operator === ts.SyntaxKind.ExclamationToken || g.kind === ts.SyntaxKind.TrueKeyword || g.kind === ts.SyntaxKind.FalseKeyword;
4477
+ if (isBoolShape) {
4478
+ return this.convertConditionToGo(this.substituteHelperParams(guard, subs)).condition;
4479
+ }
4480
+ const valueGo = wrapIfMultiToken(this.convertExpressionToGo(this.substituteHelperParams(guard, subs)));
4481
+ return `ne ${valueGo} ""`;
4482
+ }
4483
+ resolveStaticRecordLiteralIndex(jsExpr) {
4484
+ const m = /^([A-Za-z_$][\w$]*)\[\s*(?:'([^']*)'|"([^"]*)")\s*\]$/.exec(jsExpr) ?? /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/.exec(jsExpr);
4485
+ if (!m)
4486
+ return null;
4487
+ const key = m[2] ?? m[3];
4488
+ const constInfo = (this.localConstants ?? []).find((c) => c.name === m[1] && c.isModule);
4489
+ if (constInfo?.value === undefined)
4490
+ return null;
4491
+ const sf = ts.createSourceFile("__rec.ts", `(${constInfo.value})`, ts.ScriptTarget.Latest, true);
4492
+ if (sf.statements.length !== 1)
4493
+ return null;
4494
+ const stmt = sf.statements[0];
4495
+ if (!ts.isExpressionStatement(stmt))
4496
+ return null;
4497
+ let parsed = stmt.expression;
4498
+ while (ts.isParenthesizedExpression(parsed))
4499
+ parsed = parsed.expression;
4500
+ if (!ts.isObjectLiteralExpression(parsed))
4501
+ return null;
4502
+ for (const prop of parsed.properties) {
4503
+ if (!ts.isPropertyAssignment(prop))
4504
+ continue;
4505
+ const name = prop.name;
4506
+ const propKey = ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNoSubstitutionTemplateLiteral(name) ? name.text : null;
4507
+ if (propKey !== key)
4508
+ continue;
4509
+ let v = prop.initializer;
4510
+ while (ts.isParenthesizedExpression(v))
4511
+ v = v.expression;
4512
+ if (ts.isNumericLiteral(v))
4513
+ return v.text;
4514
+ if (ts.isStringLiteral(v) || ts.isNoSubstitutionTemplateLiteral(v)) {
4515
+ return JSON.stringify(v.text);
4516
+ }
4517
+ return null;
4518
+ }
4519
+ return null;
4520
+ }
3462
4521
  makeLoc() {
3463
4522
  return {
3464
4523
  file: this.componentName + ".tsx",
@@ -3632,6 +4691,14 @@ ${goFields.join(`
3632
4691
  }
3633
4692
  return { preamble: obj.preamble, expr: `${obj.expr}.${this.capitalizeFieldName(expr.property)}` };
3634
4693
  }
4694
+ case "index-access": {
4695
+ const obj = this.renderConditionExpr(expr.object);
4696
+ const idx = this.renderConditionExpr(expr.index);
4697
+ return {
4698
+ preamble: obj.preamble + idx.preamble,
4699
+ expr: `index ${wrapIfMultiToken(obj.expr)} ${wrapIfMultiToken(idx.expr)}`
4700
+ };
4701
+ }
3635
4702
  case "binary": {
3636
4703
  const leftNeedsParens = this.needsParensInGoTemplate(expr.left);
3637
4704
  const leftResult = this.renderConditionExpr(expr.left);
@@ -3643,13 +4710,17 @@ ${goFields.join(`
3643
4710
  let result;
3644
4711
  switch (expr.op) {
3645
4712
  case "===":
3646
- case "==":
3647
- result = `eq ${left} ${right}`;
4713
+ case "==": {
4714
+ const [el, er] = stringTolerantEqOperands(left, right);
4715
+ result = `eq ${el} ${er}`;
3648
4716
  break;
4717
+ }
3649
4718
  case "!==":
3650
- case "!=":
3651
- result = `ne ${left} ${right}`;
4719
+ case "!=": {
4720
+ const [el, er] = stringTolerantEqOperands(left, right);
4721
+ result = `ne ${el} ${er}`;
3652
4722
  break;
4723
+ }
3653
4724
  case ">":
3654
4725
  result = `gt ${left} ${right}`;
3655
4726
  break;
@@ -3840,6 +4911,45 @@ ${goFields.join(`
3840
4911
  }
3841
4912
  return null;
3842
4913
  }
4914
+ queueDynamicChildrenDefine(comp) {
4915
+ const effectiveChildren = comp.children.length > 0 ? comp.children : this.jsxChildrenPropNodes(comp.props);
4916
+ if (effectiveChildren.length === 0)
4917
+ return null;
4918
+ if (this.extractTextChildren(effectiveChildren) !== null)
4919
+ return null;
4920
+ if (this.extractHtmlChildren(effectiveChildren) !== null)
4921
+ return null;
4922
+ if (this.extractScopedHtmlChildren(effectiveChildren) !== null)
4923
+ return null;
4924
+ const name = `${this.componentName}__children_${comp.slotId}`;
4925
+ if (!this.pendingChildrenDefines.some((d) => d.name === name)) {
4926
+ this.pendingChildrenDefines.push({
4927
+ name,
4928
+ content: this.renderChildren(effectiveChildren)
4929
+ });
4930
+ }
4931
+ return name;
4932
+ }
4933
+ queueLoopBodyChildrenDefine(comp) {
4934
+ const effectiveChildren = comp.children.length > 0 ? comp.children : this.jsxChildrenPropNodes(comp.props);
4935
+ if (effectiveChildren.length === 0)
4936
+ return null;
4937
+ if (this.extractTextChildren(effectiveChildren) !== null)
4938
+ return null;
4939
+ if (this.extractHtmlChildren(effectiveChildren) !== null)
4940
+ return null;
4941
+ if (this.extractScopedHtmlChildren(effectiveChildren) !== null)
4942
+ return null;
4943
+ const name = `${this.componentName}__loop_children_${comp.slotId}`;
4944
+ if (!this.pendingChildrenDefines.some((d) => d.name === name)) {
4945
+ const wasInLoop = this.inLoop;
4946
+ this.inLoop = false;
4947
+ const content = this.renderChildren(effectiveChildren);
4948
+ this.inLoop = wasInLoop;
4949
+ this.pendingChildrenDefines.push({ name, content });
4950
+ }
4951
+ return name;
4952
+ }
3843
4953
  renderComponent(comp, ctx) {
3844
4954
  if (comp.name === "Portal") {
3845
4955
  return this.renderPortalComponent(comp);
@@ -3849,10 +4959,16 @@ ${goFields.join(`
3849
4959
  }
3850
4960
  let templateCall;
3851
4961
  if (this.inLoop) {
3852
- templateCall = `{{template "${comp.name}" .}}`;
4962
+ const loopBodyDefine = this.queueLoopBodyChildrenDefine(comp);
4963
+ if (loopBodyDefine) {
4964
+ templateCall = `{{template "${comp.name}" (bf_with_children . (bf_tmpl "${loopBodyDefine}" .))}}`;
4965
+ } else {
4966
+ templateCall = `{{template "${comp.name}" .}}`;
4967
+ }
3853
4968
  } else if (comp.slotId) {
3854
4969
  const suffix = slotIdToFieldSuffix(comp.slotId);
3855
- templateCall = `{{template "${comp.name}" .${comp.name}${suffix}}}`;
4970
+ const childrenDefine = this.queueDynamicChildrenDefine(comp);
4971
+ templateCall = childrenDefine ? `{{template "${comp.name}" (bf_with_children .${comp.name}${suffix} (bf_tmpl "${childrenDefine}" .))}}` : `{{template "${comp.name}" .${comp.name}${suffix}}}`;
3856
4972
  } else {
3857
4973
  templateCall = `{{template "${comp.name}" .${comp.name}}}`;
3858
4974
  }
@@ -3899,10 +5015,22 @@ ${children}`;
3899
5015
  }
3900
5016
  if (isBooleanAttr(name) || value.presenceOrUndefined) {
3901
5017
  const { condition: goCond, preamble } = this.convertConditionToGo(value.expr);
3902
- return `${preamble}{{if ${goCond}}}${name}{{end}}`;
5018
+ const body = name.startsWith("aria-") ? `${name}="true"` : name;
5019
+ return `${preamble}{{if ${goCond}}}${body}{{end}}`;
3903
5020
  }
3904
5021
  const parsed = parseExpression(value.expr.trim());
3905
- if (parsed.kind === "conditional" || parsed.kind === "template-literal") {
5022
+ if (parsed.kind === "conditional") {
5023
+ const undef = (e) => e.kind === "identifier" && (e.name === "undefined" || e.name === "null") || e.kind === "literal" && (e.value === null || e.value === undefined);
5024
+ const test = parsed.test;
5025
+ if (undef(parsed.alternate) && !undef(parsed.consequent)) {
5026
+ const { condition: goCond, preamble } = this.convertConditionToGo(this.isTemplateFragment(this.renderParsedExpr(test), test.kind) ? value.expr : value.expr.slice(0, value.expr.indexOf("?")).trim());
5027
+ const valueExpr = this.renderParsedExpr(parsed.consequent);
5028
+ const body = `${name}="{{${valueExpr}}}"`;
5029
+ return `${preamble}{{if ${goCond}}}${body}{{end}}`;
5030
+ }
5031
+ return `${name}="${this.renderParsedExpr(parsed)}"`;
5032
+ }
5033
+ if (parsed.kind === "template-literal") {
3906
5034
  return `${name}="${this.renderParsedExpr(parsed)}"`;
3907
5035
  }
3908
5036
  const bareId = value.expr.trim();
@@ -3911,7 +5039,9 @@ ${children}`;
3911
5039
  const field = `.${this.capitalizeFieldName(propName)}`;
3912
5040
  return `{{if ne ${field} nil}}${name}="{{${this.convertExpressionToGo(value.expr)}}}"{{end}}`;
3913
5041
  }
3914
- return `${name}="{{${this.convertExpressionToGo(value.expr)}}}"`;
5042
+ const exprOut = {};
5043
+ const go = this.convertExpressionToGo(value.expr, exprOut);
5044
+ return this.isTemplateFragment(go, exprOut.parsed?.kind) ? `${name}="${go}"` : `${name}="{{${go}}}"`;
3915
5045
  },
3916
5046
  emitBooleanAttr: (_value, name) => name,
3917
5047
  emitSpread: (value) => {
@@ -3985,7 +5115,21 @@ ${children}`;
3985
5115
  }
3986
5116
  const inner = s.slice(open + 2, close).trim();
3987
5117
  if (inner) {
3988
- out += `{{${this.convertExpressionToGo(inner)}}}`;
5118
+ const cls = {};
5119
+ const goExpr = this.convertExpressionToGo(inner, cls);
5120
+ const parsed = cls.parsed;
5121
+ if (parsed?.kind === "template-literal") {
5122
+ for (const part of parsed.parts) {
5123
+ if (part.type === "string") {
5124
+ out += this.escapeAttrText(part.value);
5125
+ } else {
5126
+ const e = this.renderParsedExpr(part.expr);
5127
+ out += this.isTemplateFragment(e, part.expr.kind) ? e : `{{${e}}}`;
5128
+ }
5129
+ }
5130
+ } else {
5131
+ out += this.isTemplateFragment(goExpr, parsed?.kind) ? goExpr : `{{${goExpr}}}`;
5132
+ }
3989
5133
  } else {
3990
5134
  out += s.slice(open, close + 1);
3991
5135
  }
@@ -4005,7 +5149,8 @@ ${children}`;
4005
5149
  const { condition: goCond, preamble } = this.convertConditionToGo(part.condition);
4006
5150
  output += `${preamble}{{if ${goCond}}}${part.whenTrue}{{else}}${part.whenFalse}{{end}}`;
4007
5151
  } else if (part.type === "lookup") {
4008
- const keyExpr = this.convertExpressionToGo(part.key);
5152
+ const rawKeyExpr = this.convertExpressionToGo(part.key);
5153
+ const keyExpr = /\s/.test(rawKeyExpr) ? `(${rawKeyExpr})` : rawKeyExpr;
4009
5154
  const caseEntries = Object.entries(part.cases);
4010
5155
  if (caseEntries.length === 0)
4011
5156
  continue;
@@ -4154,6 +5299,8 @@ function combineGoTypes(options) {
4154
5299
  const stdlibImports = [` "math/rand"`];
4155
5300
  if (/\bfmt\./.test(usageScan))
4156
5301
  stdlibImports.push(` "fmt"`);
5302
+ if (/\bstrings\./.test(usageScan))
5303
+ stdlibImports.push(` "strings"`);
4157
5304
  if (/\btemplate\.HTML\b/.test(usageScan))
4158
5305
  stdlibImports.push(` "html/template"`);
4159
5306
  stdlibImports.sort();