@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.
@@ -37,9 +37,12 @@ import {
37
37
  augmentInheritedPropAccesses,
38
38
  parseRecordIndexAccess,
39
39
  collectContextConsumers,
40
- isLowerableObjectRestDestructure
40
+ isLowerableObjectRestDestructure,
41
+ collectModuleStringConsts as collectModuleStringConstsShared,
42
+ searchParamsLocalNames
41
43
  } from "@barefootjs/jsx";
42
44
  import { findInterpolationEnd } from "@barefootjs/jsx/scanner";
45
+ import { BF_REGION } from "@barefootjs/shared";
43
46
  function wrapIfMultiToken(rendered) {
44
47
  if (rendered.startsWith("(") && rendered.endsWith(")"))
45
48
  return rendered;
@@ -127,13 +130,29 @@ function buildUnsupportedSuggestion(support) {
127
130
 
128
131
  ${GO_REMEDIATION_OPTIONS}`;
129
132
  }
133
+ function wrapGoArg(arg) {
134
+ if (!/\s/.test(arg))
135
+ return arg;
136
+ if (arg.startsWith("(") && arg.endsWith(")"))
137
+ return arg;
138
+ return `(${arg})`;
139
+ }
140
+ function stringTolerantEqOperands(l, r) {
141
+ const isStrLit = (x) => /^"(?:[^"\\]|\\.)*"$/.test(x);
142
+ if (isStrLit(l) === isStrLit(r))
143
+ return [l, r];
144
+ const wrap = (x) => isStrLit(x) ? x : `(bf_string ${wrapGoArg(x)})`;
145
+ return [wrap(l), wrap(r)];
146
+ }
130
147
  var GO_TEMPLATE_PRIMITIVES = {
131
- "JSON.stringify": { arity: 1, emit: (args) => `bf_json ${args[0]}` },
132
- String: { arity: 1, emit: (args) => `bf_string ${args[0]}` },
133
- Number: { arity: 1, emit: (args) => `bf_number ${args[0]}` },
134
- "Math.floor": { arity: 1, emit: (args) => `bf_floor ${args[0]}` },
135
- "Math.ceil": { arity: 1, emit: (args) => `bf_ceil ${args[0]}` },
136
- "Math.round": { arity: 1, emit: (args) => `bf_round ${args[0]}` }
148
+ "JSON.stringify": { arity: 1, emit: (args) => `bf_json ${wrapGoArg(args[0])}` },
149
+ String: { arity: 1, emit: (args) => `bf_string ${wrapGoArg(args[0])}` },
150
+ Number: { arity: 1, emit: (args) => `bf_number ${wrapGoArg(args[0])}` },
151
+ "Math.floor": { arity: 1, emit: (args) => `bf_floor ${wrapGoArg(args[0])}` },
152
+ "Math.ceil": { arity: 1, emit: (args) => `bf_ceil ${wrapGoArg(args[0])}` },
153
+ "Math.round": { arity: 1, emit: (args) => `bf_round ${wrapGoArg(args[0])}` },
154
+ "Math.min": { arity: 2, emit: (args) => `bf_min ${wrapGoArg(args[0])} ${wrapGoArg(args[1])}` },
155
+ "Math.max": { arity: 2, emit: (args) => `bf_max ${wrapGoArg(args[0])} ${wrapGoArg(args[1])}` }
137
156
  };
138
157
 
139
158
  class GoTemplateAdapter extends BaseAdapter {
@@ -148,10 +167,12 @@ class GoTemplateAdapter extends BaseAdapter {
148
167
  componentName = "";
149
168
  options;
150
169
  inLoop = false;
170
+ pendingChildrenDefines = [];
151
171
  loopParamStack = [];
152
172
  loopVarRefCount = new Map;
153
173
  loopBindingStack = [];
154
174
  errors = [];
175
+ currentMemos = [];
155
176
  propsObjectName = null;
156
177
  restPropsName = null;
157
178
  templateVarCounter = 0;
@@ -159,6 +180,7 @@ class GoTemplateAdapter extends BaseAdapter {
159
180
  localTypeAliases = new Map;
160
181
  localStructFields = new Map;
161
182
  synthStructTypes = new Map;
183
+ currentTypeDefinitions = [];
162
184
  usesHtmlTemplate = false;
163
185
  rootScopeNodes = new Set;
164
186
  usesFmt = false;
@@ -166,6 +188,11 @@ class GoTemplateAdapter extends BaseAdapter {
166
188
  moduleStringConsts = new Map;
167
189
  localConstants = [];
168
190
  contextConsumers = [];
191
+ searchParamsLocals = new Set;
192
+ localHelperNames = new Set;
193
+ needsStringsImport = false;
194
+ referencedDerivedConsts = new Set;
195
+ memoBackedLoopSlice = new Map;
169
196
  childContextConsumers = new Map;
170
197
  nillablePropNames = new Set;
171
198
  constructor(options = {}) {
@@ -179,12 +206,18 @@ class GoTemplateAdapter extends BaseAdapter {
179
206
  generate(ir, options) {
180
207
  this.componentName = ir.metadata.componentName;
181
208
  this.errors = [];
209
+ this.referencedDerivedConsts = new Set;
182
210
  this.templateVarCounter = 0;
211
+ this.pendingChildrenDefines = [];
183
212
  this.propsObjectName = ir.metadata.propsObjectName;
184
213
  this.restPropsName = ir.metadata.restPropsName ?? null;
185
214
  this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
186
215
  this.localConstants = ir.metadata.localConstants ?? [];
216
+ this.localHelperNames = new Set(this.localConstants.filter((c) => !c.isModule && c.containsArrow).map((c) => c.name));
217
+ this.currentMemos = ir.metadata.memos ?? [];
218
+ this.currentTypeDefinitions = ir.metadata.typeDefinitions ?? [];
187
219
  this.contextConsumers = collectContextConsumers(ir.metadata);
220
+ this.searchParamsLocals = searchParamsLocalNames(ir.metadata);
188
221
  augmentInheritedPropAccesses(ir);
189
222
  this.nillablePropNames = this.collectNillablePropNames(ir);
190
223
  if (!options?.siblingTemplatesRegistered) {
@@ -194,12 +227,22 @@ class GoTemplateAdapter extends BaseAdapter {
194
227
  const isRootComponent = ir.root.type === "component";
195
228
  const isIfStatement = ir.root.type === "if-statement";
196
229
  this.rootScopeNodes = collectRootScopeNodes(ir.root);
230
+ this.memoBackedLoopSlice = new Map;
231
+ for (const nested of this.findNestedComponents(ir.root)) {
232
+ const memoName = this.extractMemoNameFromLoopArray(nested.loopArray);
233
+ if (memoName)
234
+ this.memoBackedLoopSlice.set(memoName, `${nested.name}s`);
235
+ }
197
236
  const templateBody = isIfStatement ? this.renderIfStatement(ir.root, { isRootOfClientComponent: hasInteractivity }) : this.renderNode(ir.root, { isRootOfClientComponent: hasInteractivity && isRootComponent });
198
237
  const scriptRegistrations = options?.skipScriptRegistration ? "" : this.generateScriptRegistrations(ir, options?.scriptBaseName);
199
- const template = `{{define "${this.componentName}"}}
238
+ let template = `{{define "${this.componentName}"}}
200
239
  ${scriptRegistrations}${templateBody}
201
240
  {{end}}
202
241
  `;
242
+ for (const d of this.pendingChildrenDefines) {
243
+ template += `{{define "${d.name}"}}${d.content}{{end}}
244
+ `;
245
+ }
203
246
  const types = this.generateTypes(ir);
204
247
  if (this.errors.length > 0) {
205
248
  ir.errors.push(...this.errors);
@@ -436,10 +479,15 @@ ${scriptRegistrations}${templateBody}
436
479
  this.usesHtmlTemplate = false;
437
480
  this.usesFmt = false;
438
481
  this.propsObjectName = ir.metadata.propsObjectName;
482
+ this.restPropsName = ir.metadata.restPropsName ?? null;
439
483
  augmentInheritedPropAccesses(ir);
440
484
  this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
441
485
  this.localConstants = ir.metadata.localConstants ?? [];
486
+ this.localHelperNames = new Set(this.localConstants.filter((c) => !c.isModule && c.containsArrow).map((c) => c.name));
487
+ this.currentMemos = ir.metadata.memos ?? [];
488
+ this.currentTypeDefinitions = ir.metadata.typeDefinitions ?? [];
442
489
  this.contextConsumers = collectContextConsumers(ir.metadata);
490
+ this.searchParamsLocals = searchParamsLocalNames(ir.metadata);
443
491
  const lines = [];
444
492
  const componentName = ir.metadata.componentName;
445
493
  this.localTypeNames = new Set;
@@ -492,11 +540,39 @@ ${goFields.join(`
492
540
  lines.push("");
493
541
  }
494
542
  const nestedComponents = this.findNestedComponents(ir.root);
543
+ for (const nested of nestedComponents) {
544
+ if (nested.loopItemType || !nested.loopArray)
545
+ continue;
546
+ const memoName = this.extractMemoNameFromLoopArray(nested.loopArray);
547
+ if (memoName) {
548
+ const memo = ir.metadata.memos.find((m) => m.name === memoName);
549
+ if (memo) {
550
+ const blockReturn = this.resolveBlockBodyMemoModuleConst(memo.computation, ir.metadata.signals);
551
+ if (blockReturn) {
552
+ const constant = (ir.metadata.localConstants ?? []).find((c) => c.name === blockReturn.constName && c.origin?.scope === "module");
553
+ if (constant?.type?.elementType) {
554
+ nested.loopItemType = constant.type.elementType;
555
+ }
556
+ }
557
+ }
558
+ continue;
559
+ }
560
+ const directConst = (ir.metadata.localConstants ?? []).find((c) => c.name === nested.loopArray && c.origin?.scope === "module");
561
+ if (directConst?.type?.elementType) {
562
+ nested.loopItemType = directConst.type.elementType;
563
+ }
564
+ }
565
+ for (const nested of nestedComponents) {
566
+ if (!nested.bodyChildren || nested.bodyChildren.length === 0)
567
+ continue;
568
+ this.generateLoopBodyWrapperStruct(lines, componentName, nested);
569
+ }
495
570
  const propTypeOverrides = this.buildPropTypeOverrides(ir);
496
571
  const spreadSlots = this.collectSpreadSlots(ir.root);
497
572
  this.generateInputStruct(lines, ir, componentName, nestedComponents, propTypeOverrides, spreadSlots);
573
+ this.needsStringsImport = false;
498
574
  this.generatePropsStruct(lines, ir, componentName, nestedComponents, propTypeOverrides, spreadSlots);
499
- this.generateNewPropsFunction(lines, ir, componentName, nestedComponents, spreadSlots);
575
+ this.generateNewPropsFunction(lines, ir, componentName, nestedComponents, spreadSlots, propTypeOverrides);
500
576
  const header = [];
501
577
  header.push(`package ${this.options.packageName}`);
502
578
  header.push("");
@@ -506,6 +582,8 @@ ${goFields.join(`
506
582
  if (this.usesHtmlTemplate)
507
583
  header.push('\t"html/template"');
508
584
  header.push('\t"math/rand"');
585
+ if (this.needsStringsImport)
586
+ header.push('\t"strings"');
509
587
  header.push("");
510
588
  header.push('\tbf "github.com/barefootjs/runtime/bf"');
511
589
  header.push(")");
@@ -676,6 +754,20 @@ ${goFields.join(`
676
754
  }
677
755
  return nillable;
678
756
  }
757
+ usesSearchParams(ir) {
758
+ if (this.searchParamsLocals.size === 0)
759
+ return false;
760
+ const taken = new Set([
761
+ ...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
762
+ ...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
763
+ ...ir.metadata.memos.map((m) => this.capitalizeFieldName(m.name)),
764
+ ...this.contextConsumers.map((c) => this.contextFieldName(c))
765
+ ]);
766
+ if (ir.metadata.restPropsName) {
767
+ taken.add(this.capitalizeFieldName(ir.metadata.restPropsName));
768
+ }
769
+ return !taken.has("SearchParams");
770
+ }
679
771
  generateInputStruct(lines, ir, componentName, nestedComponents, propTypeOverrides, spreadSlots) {
680
772
  const inputTypeName = `${componentName}Input`;
681
773
  lines.push(`// ${inputTypeName} is the user-facing input type.`);
@@ -683,6 +775,9 @@ ${goFields.join(`
683
775
  lines.push("\tScopeID string // Optional: if empty, random ID is generated");
684
776
  lines.push("\tBfParent string // Optional: parent scope id");
685
777
  lines.push("\tBfMount string // Optional: slot id in parent");
778
+ if (this.usesSearchParams(ir)) {
779
+ lines.push("\tSearchParams bf.SearchParams // Optional: request query for searchParams()");
780
+ }
686
781
  const inputNested = nestedComponents.filter((n) => !n.isDynamic || n.isPropDerived);
687
782
  const nestedArrayFields = new Set(nestedComponents.map((n) => `${n.name}s`));
688
783
  for (const param of ir.metadata.propsParams) {
@@ -727,6 +822,9 @@ ${goFields.join(`
727
822
  lines.push('\tBfMount string `json:"-"`');
728
823
  lines.push('\tBfDataKey string `json:"-"`');
729
824
  lines.push('\tScripts *bf.ScriptCollector `json:"-"`');
825
+ if (this.usesSearchParams(ir)) {
826
+ lines.push('\tSearchParams bf.SearchParams `json:"-"`');
827
+ }
730
828
  const nestedArrayFields = new Set(nestedComponents.map((n) => `${n.name}s`));
731
829
  const propFieldNames = new Set;
732
830
  for (const param of ir.metadata.propsParams) {
@@ -773,10 +871,20 @@ ${goFields.join(`
773
871
  }
774
872
  for (const memo of ir.metadata.memos) {
775
873
  const fieldName = this.capitalizeFieldName(memo.name);
874
+ if (propFieldNames.has(fieldName))
875
+ continue;
776
876
  const jsonTag = this.toJsonTag(memo.name);
777
877
  const goType = this.inferMemoType(memo, ir.metadata.signals, propsParamMap);
778
878
  lines.push(` ${fieldName} ${goType} \`json:"${jsonTag}"\``);
779
879
  }
880
+ const takenForDerivedConsts = new Set([
881
+ ...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
882
+ ...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
883
+ ...ir.metadata.memos.map((m) => this.capitalizeFieldName(m.name))
884
+ ]);
885
+ for (const f of this.computeDerivedConstFields(takenForDerivedConsts)) {
886
+ lines.push(` ${f.name} string \`json:"-"\``);
887
+ }
780
888
  const takenProps = new Set([
781
889
  ...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
782
890
  ...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
@@ -787,11 +895,12 @@ ${goFields.join(`
787
895
  lines.push(` ${this.contextFieldName(c)} ${this.contextConsumerGoType(c)} \`json:"${jsonTag}"\``);
788
896
  }
789
897
  for (const nested of nestedComponents) {
898
+ const elemType = nested.bodyChildren?.length ? this.loopBodyWrapperName(componentName, nested) : `${nested.name}Props`;
790
899
  if (nested.isDynamic && !nested.isPropDerived) {
791
- lines.push(` ${nested.name}s []${nested.name}Props \`json:"-"\``);
900
+ lines.push(` ${nested.name}s []${elemType} \`json:"-"\``);
792
901
  } else {
793
902
  const jsonTag = this.toJsonTag(`${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`);
794
- lines.push(` ${nested.name}s []${nested.name}Props \`json:"${jsonTag}"\``);
903
+ lines.push(` ${nested.name}s []${elemType} \`json:"${jsonTag}"\``);
795
904
  }
796
905
  }
797
906
  const staticChildren = this.collectStaticChildInstances(ir.root);
@@ -805,10 +914,73 @@ ${goFields.join(`
805
914
  lines.push("}");
806
915
  lines.push("");
807
916
  }
808
- generateNewPropsFunction(lines, ir, componentName, nestedComponents, spreadSlots) {
917
+ generateLoopBodyWrapperStruct(lines, parentComponentName, nested) {
918
+ const wrapperName = this.loopBodyWrapperName(parentComponentName, nested);
919
+ const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
920
+ const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
921
+ lines.push(`// ${wrapperName} wraps ${nested.name}Props with per-row loop datum`);
922
+ lines.push(`// fields and child component slots for the loop body children. (#1897)`);
923
+ lines.push(`type ${wrapperName} struct {`);
924
+ lines.push(` ${nested.name}Props`);
925
+ for (const f of datumFields) {
926
+ lines.push(` ${f.goName} ${f.goType} \`json:"-"\``);
927
+ }
928
+ for (const child of bodyChildInstances) {
929
+ lines.push(` ${child.fieldName} ${child.name}Props \`json:"-"\``);
930
+ }
931
+ lines.push("}");
932
+ lines.push("");
933
+ }
934
+ extractMemoNameFromLoopArray(loopArray) {
935
+ if (!loopArray)
936
+ return null;
937
+ const match = loopArray.match(/^(\w+)\(\)$/);
938
+ return match ? match[1] : null;
939
+ }
940
+ loopBodyWrapperName(parentName, nested) {
941
+ return `${parentName}${nested.name}L${nested.loopMarkerId ?? "0"}Ctx`;
942
+ }
943
+ resolveLoopDatumFields(itemType) {
944
+ if (!itemType)
945
+ return [];
946
+ const typeName = itemType.raw?.replace(/\[\]$/, "") ?? itemType.raw;
947
+ if (!typeName)
948
+ return [];
949
+ for (const td of this.currentTypeDefinitions) {
950
+ if (td.name === typeName) {
951
+ const fields = [];
952
+ for (const prop of td.properties ?? []) {
953
+ if (!GoTemplateAdapter.GO_IDENTIFIER.test(prop.name))
954
+ continue;
955
+ fields.push({
956
+ tsName: prop.name,
957
+ goName: this.capitalizeFieldName(prop.name),
958
+ goType: this.typeInfoToGo(prop.type)
959
+ });
960
+ }
961
+ if (fields.length > 0)
962
+ return fields;
963
+ break;
964
+ }
965
+ }
966
+ const structFields = this.localStructFields.get(typeName);
967
+ if (structFields) {
968
+ return Array.from(structFields, ([tsName, goName]) => ({ tsName, goName, goType: "interface{}" }));
969
+ }
970
+ return [];
971
+ }
972
+ collectBodyChildInstances(bodyChildren) {
973
+ const result = [];
974
+ for (const child of bodyChildren) {
975
+ this.collectStaticChildInstancesRecursive(child, result, false, new Map);
976
+ }
977
+ return result;
978
+ }
979
+ generateNewPropsFunction(lines, ir, componentName, nestedComponents, spreadSlots, propTypeOverrides) {
809
980
  const inputTypeName = `${componentName}Input`;
810
981
  const propsTypeName = `${componentName}Props`;
811
- const signalDynamicNested = nestedComponents.filter((n) => n.isDynamic && !n.isPropDerived);
982
+ const dynamicWithBody = nestedComponents.filter((n) => n.isDynamic && !n.isPropDerived && n.bodyChildren && n.bodyChildren.length > 0);
983
+ const signalDynamicNested = nestedComponents.filter((n) => n.isDynamic && !n.isPropDerived && !(n.bodyChildren && n.bodyChildren.length > 0));
812
984
  lines.push(`// New${componentName}Props creates ${propsTypeName} from ${inputTypeName}.`);
813
985
  for (const nested of signalDynamicNested) {
814
986
  const arrayField = `${nested.name}s`;
@@ -833,22 +1005,77 @@ ${goFields.join(`
833
1005
  lines.push("\t}");
834
1006
  lines.push("");
835
1007
  const staticNested = nestedComponents.filter((n) => !n.isDynamic || n.isPropDerived);
836
- if (staticNested.length > 0) {
837
- for (const nested of staticNested) {
838
- const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
839
- lines.push(` ${varName} := make([]${nested.name}Props, len(in.${nested.name}s))`);
840
- lines.push(` for i, item := range in.${nested.name}s {`);
841
- lines.push(` ${varName}[i] = New${nested.name}Props(item)`);
842
- lines.push(` ${varName}[i].BfParent = scopeID`);
843
- lines.push(` ${varName}[i].BfMount = "${nested.slotId}"`);
844
- const keyField = loopKeyToGoFieldPath(nested.loopKey, nested.loopParam);
845
- if (keyField) {
846
- lines.push(` ${varName}[i].BfDataKey = fmt.Sprint(${keyField})`);
847
- this.usesFmt = true;
848
- }
849
- lines.push("\t}");
850
- lines.push("");
1008
+ const emittedWrapperVars = new Set;
1009
+ const staticWithBody = staticNested.filter((n) => n.bodyChildren && n.bodyChildren.length > 0);
1010
+ const staticWithoutBody = staticNested.filter((n) => !n.bodyChildren || n.bodyChildren.length === 0);
1011
+ for (const nested of staticWithoutBody) {
1012
+ const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
1013
+ lines.push(` ${varName} := make([]${nested.name}Props, len(in.${nested.name}s))`);
1014
+ lines.push(` for i, item := range in.${nested.name}s {`);
1015
+ lines.push(` ${varName}[i] = New${nested.name}Props(item)`);
1016
+ lines.push(` ${varName}[i].BfParent = scopeID`);
1017
+ lines.push(` ${varName}[i].BfMount = "${nested.slotId}"`);
1018
+ const keyField = loopKeyToGoFieldPath(nested.loopKey, nested.loopParam);
1019
+ if (keyField) {
1020
+ lines.push(` ${varName}[i].BfDataKey = fmt.Sprint(${keyField})`);
1021
+ this.usesFmt = true;
1022
+ }
1023
+ lines.push("\t}");
1024
+ lines.push("");
1025
+ }
1026
+ for (const nested of staticWithBody) {
1027
+ const loopArray = nested.loopArray;
1028
+ const moduleConst = loopArray ? (ir.metadata.localConstants ?? []).find((c) => c.name === loopArray && c.origin?.scope === "module" && c.value && c.type) : null;
1029
+ const bakedValue = moduleConst?.type ? this.convertInitialValue(moduleConst.value, moduleConst.type, ir.metadata.propsParams) : null;
1030
+ if (!bakedValue || bakedValue === "nil" || bakedValue === "0")
1031
+ continue;
1032
+ const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
1033
+ const wrapperType = this.loopBodyWrapperName(componentName, nested);
1034
+ const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
1035
+ const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
1036
+ for (const child of bodyChildInstances) {
1037
+ const childVar = `child_${child.fieldName}`;
1038
+ lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
1039
+ lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
1040
+ lines.push(` BfParent: scopeID,`);
1041
+ lines.push(` BfMount: "${child.slotId}",`);
1042
+ for (const prop of child.props) {
1043
+ if (prop.value.kind === "literal") {
1044
+ lines.push(` ${this.capitalizeFieldName(prop.name)}: ${this.goLiteral(prop.value.value)},`);
1045
+ } else if (prop.value.kind === "boolean-shorthand" || prop.value.kind === "boolean-attr") {
1046
+ lines.push(` ${this.capitalizeFieldName(prop.name)}: true,`);
1047
+ }
1048
+ }
1049
+ lines.push(` })`);
851
1050
  }
1051
+ if (bodyChildInstances.length > 0)
1052
+ lines.push("");
1053
+ const dataVar = `${varName}Data`;
1054
+ lines.push(` ${dataVar} := ${bakedValue}`);
1055
+ lines.push(` ${varName} := make([]${wrapperType}, len(${dataVar}))`);
1056
+ lines.push(` for i, item := range ${dataVar} {`);
1057
+ lines.push(` ${varName}[i] = ${wrapperType}{`);
1058
+ lines.push(` ${nested.name}Props: New${nested.name}Props(${nested.name}Input{`);
1059
+ lines.push(` BfParent: scopeID,`);
1060
+ lines.push(` BfMount: "${nested.slotId}",`);
1061
+ lines.push(` }),`);
1062
+ for (const f of datumFields) {
1063
+ lines.push(` ${f.goName}: item.${f.goName},`);
1064
+ }
1065
+ for (const child of bodyChildInstances) {
1066
+ lines.push(` ${child.fieldName}: child_${child.fieldName},`);
1067
+ }
1068
+ lines.push(` }`);
1069
+ lines.push(` ${varName}[i].BfParent = scopeID`);
1070
+ lines.push(` ${varName}[i].BfMount = "${nested.slotId}"`);
1071
+ const keyField = loopKeyToGoFieldPath(nested.loopKey, nested.loopParam);
1072
+ if (keyField) {
1073
+ lines.push(` ${varName}[i].BfDataKey = fmt.Sprint(${keyField})`);
1074
+ this.usesFmt = true;
1075
+ }
1076
+ lines.push("\t}");
1077
+ lines.push("");
1078
+ emittedWrapperVars.add(varName);
852
1079
  }
853
1080
  const propFallbackVars = this.collectPropFallbackVars(ir);
854
1081
  for (const [, info] of propFallbackVars) {
@@ -859,11 +1086,87 @@ ${goFields.join(`
859
1086
  }
860
1087
  if (propFallbackVars.size > 0)
861
1088
  lines.push("");
1089
+ const propsParamMap = new Map(ir.metadata.propsParams.map((p) => [p.name, p]));
1090
+ for (const nested of dynamicWithBody) {
1091
+ const memoName = this.extractMemoNameFromLoopArray(nested.loopArray);
1092
+ if (!memoName)
1093
+ continue;
1094
+ const memo = ir.metadata.memos.find((m) => m.name === memoName);
1095
+ if (!memo)
1096
+ continue;
1097
+ const goType = this.inferMemoType(memo, ir.metadata.signals, propsParamMap);
1098
+ const bakedValue = this.computeMemoInitialValue(memo, ir.metadata.signals, ir.metadata.propsParams, propFallbackVars, goType);
1099
+ if (bakedValue === "nil" || bakedValue === "0")
1100
+ continue;
1101
+ const wrapperType = this.loopBodyWrapperName(componentName, nested);
1102
+ const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
1103
+ const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
1104
+ const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
1105
+ for (const child of bodyChildInstances) {
1106
+ const childVar = `child_${child.fieldName}`;
1107
+ lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
1108
+ lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
1109
+ lines.push(` BfParent: scopeID,`);
1110
+ lines.push(` BfMount: "${child.slotId}",`);
1111
+ for (const prop of child.props) {
1112
+ if (prop.value.kind === "literal") {
1113
+ lines.push(` ${this.capitalizeFieldName(prop.name)}: ${this.goLiteral(prop.value.value)},`);
1114
+ } else if (prop.value.kind === "boolean-shorthand" || prop.value.kind === "boolean-attr") {
1115
+ lines.push(` ${this.capitalizeFieldName(prop.name)}: true,`);
1116
+ }
1117
+ }
1118
+ lines.push(` })`);
1119
+ }
1120
+ if (bodyChildInstances.length > 0)
1121
+ lines.push("");
1122
+ lines.push(` bakedData := ${bakedValue}`);
1123
+ lines.push(` ${varName} := make([]${wrapperType}, len(bakedData))`);
1124
+ lines.push(` for i, item := range bakedData {`);
1125
+ lines.push(` ${varName}[i] = ${wrapperType}{`);
1126
+ lines.push(` ${nested.name}Props: New${nested.name}Props(${nested.name}Input{`);
1127
+ lines.push(` BfParent: scopeID,`);
1128
+ lines.push(` BfMount: "${nested.slotId}",`);
1129
+ lines.push(` }),`);
1130
+ for (const f of datumFields) {
1131
+ lines.push(` ${f.goName}: item.${f.goName},`);
1132
+ }
1133
+ for (const child of bodyChildInstances) {
1134
+ lines.push(` ${child.fieldName}: child_${child.fieldName},`);
1135
+ }
1136
+ lines.push(` }`);
1137
+ const keyField = loopKeyToGoFieldPath(nested.loopKey, nested.loopParam);
1138
+ if (keyField) {
1139
+ lines.push(` ${varName}[i].BfDataKey = fmt.Sprint(${keyField})`);
1140
+ this.usesFmt = true;
1141
+ }
1142
+ lines.push(` }`);
1143
+ lines.push("");
1144
+ emittedWrapperVars.add(varName);
1145
+ }
862
1146
  lines.push(` return ${propsTypeName}{`);
863
1147
  lines.push("\t\tScopeID: scopeID,");
864
1148
  lines.push("\t\tBfParent: in.BfParent,");
865
1149
  lines.push("\t\tBfMount: in.BfMount,");
1150
+ if (this.usesSearchParams(ir)) {
1151
+ lines.push("\t\tSearchParams: in.SearchParams,");
1152
+ }
866
1153
  const nestedArrayFields = new Set(nestedComponents.map((n) => `${n.name}s`));
1154
+ const memoFallbacks = new Map;
1155
+ for (const memo of ir.metadata.memos) {
1156
+ const stripped = memo.computation.replace(/^\(\)\s*=>\s*/, "");
1157
+ const m = this.extractPropFallback(stripped);
1158
+ if (!m)
1159
+ continue;
1160
+ if (this.capitalizeFieldName(m.propName) !== this.capitalizeFieldName(memo.name))
1161
+ continue;
1162
+ const param = ir.metadata.propsParams.find((p) => this.capitalizeFieldName(p.name) === this.capitalizeFieldName(memo.name));
1163
+ if (!param)
1164
+ continue;
1165
+ const goType = this.resolvePropGoType(param, propTypeOverrides);
1166
+ if (goType !== "string" && goType !== "interface{}")
1167
+ continue;
1168
+ memoFallbacks.set(this.capitalizeFieldName(memo.name), { goFallback: m.goFallback, goType });
1169
+ }
867
1170
  const propFieldNames = new Set;
868
1171
  for (const param of ir.metadata.propsParams) {
869
1172
  const fieldName = this.capitalizeFieldName(param.name);
@@ -873,9 +1176,14 @@ ${goFields.join(`
873
1176
  if (hoisted) {
874
1177
  lines.push(` ${fieldName}: ${hoisted.varName},`);
875
1178
  } else {
876
- const fallback = this.goPropDefault(param.defaultValue);
877
- if (fallback !== null) {
878
- lines.push(` ${fieldName}: ${this.applyGoFallback(`in.${fieldName}`, fallback)},`);
1179
+ const paramDefault = this.goPropDefault(param.defaultValue);
1180
+ const memoFold = memoFallbacks.get(fieldName);
1181
+ if (paramDefault !== null) {
1182
+ lines.push(` ${fieldName}: ${this.applyGoFallback(`in.${fieldName}`, paramDefault)},`);
1183
+ } else if (memoFold !== undefined && memoFold.goType === "string") {
1184
+ lines.push(` ${fieldName}: ${this.applyGoFallback(`in.${fieldName}`, memoFold.goFallback)},`);
1185
+ } else if (memoFold !== undefined) {
1186
+ lines.push(` ${fieldName}: func() interface{} { v := interface{}(in.${fieldName}); if v == nil || v == "" { return ${memoFold.goFallback} }; return v }(),`);
879
1187
  } else {
880
1188
  lines.push(` ${fieldName}: in.${fieldName},`);
881
1189
  }
@@ -896,17 +1204,33 @@ ${goFields.join(`
896
1204
  lines.push(` ${fieldName}: ${initialValue},`);
897
1205
  }
898
1206
  }
899
- for (const nested of staticNested) {
1207
+ for (const nested of staticWithoutBody) {
1208
+ const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
1209
+ lines.push(` ${nested.name}s: ${varName},`);
1210
+ }
1211
+ for (const nested of [...staticWithBody, ...dynamicWithBody]) {
900
1212
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
1213
+ if (!emittedWrapperVars.has(varName))
1214
+ continue;
901
1215
  lines.push(` ${nested.name}s: ${varName},`);
902
1216
  }
903
1217
  const memoPropsParamMap = new Map(ir.metadata.propsParams.map((p) => [p.name, p]));
904
1218
  for (const memo of ir.metadata.memos) {
905
1219
  const fieldName = this.capitalizeFieldName(memo.name);
1220
+ if (propFieldNames.has(fieldName))
1221
+ continue;
906
1222
  const goType = this.inferMemoType(memo, ir.metadata.signals, memoPropsParamMap);
907
1223
  const memoValue = this.computeMemoInitialValue(memo, ir.metadata.signals, ir.metadata.propsParams, propFallbackVars, goType);
908
1224
  lines.push(` ${fieldName}: ${memoValue},`);
909
1225
  }
1226
+ const takenDerivedInit = new Set([
1227
+ ...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
1228
+ ...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
1229
+ ...ir.metadata.memos.map((m) => this.capitalizeFieldName(m.name))
1230
+ ]);
1231
+ for (const f of this.computeDerivedConstFields(takenDerivedInit)) {
1232
+ lines.push(` ${f.name}: ${f.init},`);
1233
+ }
910
1234
  const takenInit = new Set([
911
1235
  ...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
912
1236
  ...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
@@ -939,6 +1263,8 @@ ${goFields.join(`
939
1263
  restBagEntries.push(`${JSON.stringify(jsxName)}: ${goValue}`);
940
1264
  return;
941
1265
  }
1266
+ if (jsxName.includes("-"))
1267
+ return;
942
1268
  lines.push(` ${this.capitalizeFieldName(jsxName)}: ${goValue},`);
943
1269
  };
944
1270
  for (const prop of child.props) {
@@ -1020,12 +1346,17 @@ ${goFields.join(`
1020
1346
  const loop = node;
1021
1347
  if (loop.childComponent) {
1022
1348
  if (!result.some((c) => c.name === loop.childComponent.name)) {
1349
+ const hasBodyChildren = loop.childComponent.children.length > 0;
1023
1350
  result.push({
1024
1351
  ...loop.childComponent,
1025
1352
  isDynamic: !loop.isStaticArray,
1026
1353
  isPropDerived: !!loop.isPropDerivedArray,
1027
1354
  loopKey: loop.key ?? undefined,
1028
- loopParam: loop.param ?? undefined
1355
+ loopParam: loop.param ?? undefined,
1356
+ bodyChildren: hasBodyChildren ? loop.childComponent.children : undefined,
1357
+ loopArray: loop.array,
1358
+ loopMarkerId: loop.markerId,
1359
+ loopItemType: loop.itemType
1029
1360
  });
1030
1361
  }
1031
1362
  }
@@ -1048,6 +1379,17 @@ ${goFields.join(`
1048
1379
  if (cond.whenFalse) {
1049
1380
  this.collectNestedComponents(cond.whenFalse, result);
1050
1381
  }
1382
+ } else if (node.type === "if-statement") {
1383
+ const stmt = node;
1384
+ this.collectNestedComponents(stmt.consequent, result);
1385
+ if (stmt.alternate) {
1386
+ this.collectNestedComponents(stmt.alternate, result);
1387
+ }
1388
+ } else if (node.type === "component") {
1389
+ const comp = node;
1390
+ for (const child of comp.children) {
1391
+ this.collectNestedComponents(child, result);
1392
+ }
1051
1393
  }
1052
1394
  }
1053
1395
  collectStaticChildInstances(node) {
@@ -1119,6 +1461,9 @@ ${goFields.join(`
1119
1461
  childrenScopedHtmlExpr: this.extractScopedHtmlChildren(effectiveChildren),
1120
1462
  contextBindings: providerCtx.size > 0 ? providerCtx : undefined
1121
1463
  });
1464
+ for (const child of effectiveChildren) {
1465
+ this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
1466
+ }
1122
1467
  }
1123
1468
  if (comp.name === "Portal" && comp.children) {
1124
1469
  for (const child of comp.children) {
@@ -1146,6 +1491,12 @@ ${goFields.join(`
1146
1491
  if (cond.whenFalse) {
1147
1492
  this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx);
1148
1493
  }
1494
+ } else if (node.type === "if-statement") {
1495
+ const stmt = node;
1496
+ this.collectStaticChildInstancesRecursive(stmt.consequent, result, inLoop, providerCtx);
1497
+ if (stmt.alternate) {
1498
+ this.collectStaticChildInstancesRecursive(stmt.alternate, result, inLoop, providerCtx);
1499
+ }
1149
1500
  } else if (node.type === "provider") {
1150
1501
  const p = node;
1151
1502
  const childCtx = this.extendProviderContext(providerCtx, p);
@@ -1653,8 +2004,8 @@ ${goFields.join(`
1653
2004
  }
1654
2005
  const lines = [];
1655
2006
  lines.push("func() string {");
1656
- lines.push(` k, _ := in.${fieldName}.(string)`);
1657
- lines.push("\t\t\tswitch k {");
2007
+ this.usesFmt = true;
2008
+ lines.push(` switch fmt.Sprint(in.${fieldName}) {`);
1658
2009
  for (const [k, v] of caseEntries) {
1659
2010
  lines.push(` case ${JSON.stringify(k)}: return ${JSON.stringify(v)}`);
1660
2011
  }
@@ -1672,6 +2023,22 @@ ${goFields.join(`
1672
2023
  return segments.join(" + ");
1673
2024
  }
1674
2025
  resolveDynamicPropValue(expr, signals, memos, propsParams) {
2026
+ const cmpMatch = expr.match(/^(\w+)\(\)\s*([!=]==?)\s*(?:'([^']*)'|(-?\d+(?:\.\d+)?))\s*$/);
2027
+ if (cmpMatch) {
2028
+ const [, depName, op, strLit, numLit] = cmpMatch;
2029
+ const signal = signals.find((sg) => sg.getter === depName);
2030
+ const init = signal?.initialValue.trim();
2031
+ const initMatch = init !== undefined ? /^(?:'([^'\\]*)'|(-?\d+(?:\.\d+)?))$/.exec(init) : null;
2032
+ if (initMatch) {
2033
+ const initVal = initMatch[1] ?? initMatch[2];
2034
+ const litVal = strLit ?? numLit;
2035
+ const sameKind = initMatch[1] !== undefined === (strLit !== undefined);
2036
+ if (sameKind) {
2037
+ const equal = initVal === litVal;
2038
+ return String(op.startsWith("!") ? !equal : equal);
2039
+ }
2040
+ }
2041
+ }
1675
2042
  const getterMatch = expr.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\(\)$/);
1676
2043
  if (getterMatch) {
1677
2044
  const getterName = getterMatch[1];
@@ -1681,7 +2048,7 @@ ${goFields.join(`
1681
2048
  }
1682
2049
  const memo = memos.find((m) => m.name === getterName);
1683
2050
  if (memo) {
1684
- return this.computeMemoInitialValue(memo, signals, propsParams);
2051
+ return this.computeMemoInitialValueOrNull(memo, signals, propsParams);
1685
2052
  }
1686
2053
  }
1687
2054
  return null;
@@ -1822,6 +2189,19 @@ ${goFields.join(`
1822
2189
  return node.name.text;
1823
2190
  }
1824
2191
  computeMemoInitialValue(memo, signals, propsParams, propFallbackVars = GoTemplateAdapter.EMPTY_PROP_FALLBACK_VARS, goType) {
2192
+ const resolved = this.computeMemoInitialValueOrNull(memo, signals, propsParams, propFallbackVars);
2193
+ if (resolved !== null)
2194
+ return resolved;
2195
+ if (goType === "bool")
2196
+ return "false";
2197
+ if (goType === "string")
2198
+ return '""';
2199
+ if (goType !== undefined && (goType.startsWith("map[") || goType.startsWith("[]") || goType.startsWith("*") || goType.includes("interface{}") || goType === "any")) {
2200
+ return "nil";
2201
+ }
2202
+ return "0";
2203
+ }
2204
+ computeMemoInitialValueOrNull(memo, signals, propsParams, propFallbackVars = GoTemplateAdapter.EMPTY_PROP_FALLBACK_VARS) {
1825
2205
  const computation = memo.computation;
1826
2206
  const propRef = (propName) => {
1827
2207
  const hoisted = propFallbackVars.get(propName);
@@ -1832,6 +2212,52 @@ ${goFields.join(`
1832
2212
  const tmplMemo = this.computeTemplateLiteralMemoInitialValue(computation, propsParams);
1833
2213
  if (tmplMemo !== null)
1834
2214
  return tmplMemo;
2215
+ const eqMatch = computation.match(/^\(\)\s*=>\s*(\w+)\(\)\s*([!=]==?)\s*'([^']*)'\s*$/);
2216
+ if (eqMatch) {
2217
+ const [, depName, op, lit] = eqMatch;
2218
+ const signal = signals.find((sg) => sg.getter === depName);
2219
+ const initLit = signal ? /^'([^'\\]*)'$/.exec(signal.initialValue.trim()) : null;
2220
+ if (initLit) {
2221
+ const equal = initLit[1] === lit;
2222
+ return String(op.startsWith("!") ? !equal : equal);
2223
+ }
2224
+ }
2225
+ const boolPassthrough = computation.match(/^\(\)\s*=>\s*props\.(\w+)\s*\?\?\s*false\s*$/);
2226
+ if (boolPassthrough) {
2227
+ return propRef(boolPassthrough[1]);
2228
+ }
2229
+ const ternMatch = computation.match(/^\(\)\s*=>\s*(\w+)\(\)\s*\?\s*([\w$]+|'[^']*')\s*:\s*([\w$]+|'[^']*')\s*$/);
2230
+ if (ternMatch) {
2231
+ const [, condName, whenTrue, whenFalse] = ternMatch;
2232
+ const resolveBranch = (b) => {
2233
+ const lit = /^'([^']*)'$/.exec(b);
2234
+ if (lit)
2235
+ return JSON.stringify(lit[1]);
2236
+ const constVal = this.moduleStringConsts.get(b);
2237
+ return constVal !== undefined ? JSON.stringify(constVal) : null;
2238
+ };
2239
+ const t = resolveBranch(whenTrue);
2240
+ const f = resolveBranch(whenFalse);
2241
+ if (t !== null && f !== null) {
2242
+ let condGo = null;
2243
+ const condSignal = signals.find((sg) => sg.getter === condName);
2244
+ if (condSignal) {
2245
+ condGo = this.getSignalInitialValueAsGo(condSignal.initialValue, propsParams, propFallbackVars);
2246
+ } else {
2247
+ const condMemo = (this.currentMemos ?? []).find((m) => m.name === condName);
2248
+ if (condMemo) {
2249
+ condGo = this.computeMemoInitialValueOrNull(condMemo, signals, propsParams, propFallbackVars);
2250
+ }
2251
+ }
2252
+ if (condGo === "true")
2253
+ return t;
2254
+ if (condGo === "false")
2255
+ return f;
2256
+ if (condGo !== null) {
2257
+ return `func() string { if ${condGo} { return ${t} }; return ${f} }()`;
2258
+ }
2259
+ }
2260
+ }
1835
2261
  const arithmeticMatch = computation.match(/\(\)\s*=>\s*(\w+)\(\)\s*([*+\-/])\s*(\d+)/);
1836
2262
  if (arithmeticMatch) {
1837
2263
  const [, depName, operator, operand] = arithmeticMatch;
@@ -1851,8 +2277,8 @@ ${goFields.join(`
1851
2277
  return `${hoisted.varName} ${operator} ${operand}`;
1852
2278
  const fieldName = this.capitalizeFieldName(propName);
1853
2279
  if (param.type) {
1854
- const goType2 = this.typeInfoToGo(param.type, param.defaultValue);
1855
- if (goType2 === "interface{}")
2280
+ const goType = this.typeInfoToGo(param.type, param.defaultValue);
2281
+ if (goType === "interface{}")
1856
2282
  return `in.${fieldName}.(int) ${operator} ${operand}`;
1857
2283
  }
1858
2284
  return `in.${fieldName} ${operator} ${operand}`;
@@ -1881,8 +2307,8 @@ ${goFields.join(`
1881
2307
  if (param) {
1882
2308
  const fieldName = this.capitalizeFieldName(varName);
1883
2309
  if (param.type) {
1884
- const goType2 = this.typeInfoToGo(param.type, param.defaultValue);
1885
- if (goType2 === "interface{}")
2310
+ const goType = this.typeInfoToGo(param.type, param.defaultValue);
2311
+ if (goType === "interface{}")
1886
2312
  return `in.${fieldName}.(int) ${operator} ${operand}`;
1887
2313
  }
1888
2314
  return `in.${fieldName} ${operator} ${operand}`;
@@ -1896,11 +2322,283 @@ ${goFields.join(`
1896
2322
  return `in.${this.capitalizeFieldName(varName)}`;
1897
2323
  }
1898
2324
  }
1899
- if (goType === "bool")
2325
+ const blockReturn = this.resolveBlockBodyMemoModuleConst(computation, signals);
2326
+ if (blockReturn !== null && blockReturn.constValue && blockReturn.constType) {
2327
+ return this.convertInitialValue(blockReturn.constValue, blockReturn.constType, propsParams);
2328
+ }
2329
+ const objMemo = this.computeObjectMemoInitialValue(computation);
2330
+ if (objMemo !== null)
2331
+ return objMemo;
2332
+ return null;
2333
+ }
2334
+ resolveBlockBodyMemoModuleConst(computation, signals) {
2335
+ try {
2336
+ const sf = ts.createSourceFile("__memo.ts", `const __x = (${computation});`, ts.ScriptTarget.Latest, false);
2337
+ const stmt = sf.statements[0];
2338
+ if (!stmt || !ts.isVariableStatement(stmt))
2339
+ return null;
2340
+ let init = stmt.declarationList.declarations[0]?.initializer;
2341
+ while (init && ts.isParenthesizedExpression(init))
2342
+ init = init.expression;
2343
+ if (!init || !ts.isArrowFunction(init))
2344
+ return null;
2345
+ const body = init.body;
2346
+ if (!ts.isBlock(body))
2347
+ return null;
2348
+ const varToSignal = new Map;
2349
+ let guardSignalGetter = null;
2350
+ let returnedConst = null;
2351
+ for (const s of body.statements) {
2352
+ if (ts.isVariableStatement(s)) {
2353
+ for (const decl of s.declarationList.declarations) {
2354
+ if (ts.isIdentifier(decl.name) && decl.initializer && ts.isCallExpression(decl.initializer) && ts.isIdentifier(decl.initializer.expression)) {
2355
+ const callee = decl.initializer.expression.text;
2356
+ if (signals.some((sg) => sg.getter === callee)) {
2357
+ varToSignal.set(decl.name.text, callee);
2358
+ }
2359
+ }
2360
+ }
2361
+ }
2362
+ if (ts.isIfStatement(s) && ts.isPrefixUnaryExpression(s.expression) && s.expression.operator === ts.SyntaxKind.ExclamationToken && ts.isIdentifier(s.expression.operand)) {
2363
+ const guardVar = s.expression.operand.text;
2364
+ const signalGetter = varToSignal.get(guardVar);
2365
+ if (!signalGetter)
2366
+ continue;
2367
+ const thenBlock = ts.isBlock(s.thenStatement) ? s.thenStatement.statements : [s.thenStatement];
2368
+ for (const rs of thenBlock) {
2369
+ if (ts.isReturnStatement(rs) && rs.expression && ts.isIdentifier(rs.expression)) {
2370
+ guardSignalGetter = signalGetter;
2371
+ returnedConst = rs.expression.text;
2372
+ }
2373
+ }
2374
+ }
2375
+ if (guardSignalGetter && returnedConst)
2376
+ break;
2377
+ }
2378
+ if (!guardSignalGetter || !returnedConst)
2379
+ return null;
2380
+ const guardSignal = signals.find((sg) => sg.getter === guardSignalGetter);
2381
+ if (!guardSignal)
2382
+ return null;
2383
+ const iv = guardSignal.initialValue.trim();
2384
+ if (iv !== "null" && iv !== "''" && iv !== '""' && iv !== "0" && iv !== "false") {
2385
+ return null;
2386
+ }
2387
+ const constant = this.localConstants.find((c) => c.name === returnedConst && c.origin?.scope === "module");
2388
+ if (!constant)
2389
+ return null;
2390
+ return {
2391
+ constName: constant.name,
2392
+ constValue: constant.value,
2393
+ constType: constant.type ?? undefined
2394
+ };
2395
+ } catch {
2396
+ return null;
2397
+ }
2398
+ }
2399
+ computeObjectMemoInitialValue(computation) {
2400
+ const arrow = this.parseLiteralExpression(computation);
2401
+ if (!arrow || !ts.isArrowFunction(arrow) || !ts.isBlock(arrow.body))
2402
+ return null;
2403
+ const searchParamsVars = new Set;
2404
+ let retObj = null;
2405
+ const statements = arrow.body.statements;
2406
+ for (let i = 0;i < statements.length; i++) {
2407
+ const s = statements[i];
2408
+ if (ts.isVariableStatement(s)) {
2409
+ for (const d of s.declarationList.declarations) {
2410
+ 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)) {
2411
+ searchParamsVars.add(d.name.text);
2412
+ }
2413
+ }
2414
+ continue;
2415
+ }
2416
+ if (ts.isReturnStatement(s)) {
2417
+ if (i !== statements.length - 1 || !s.expression)
2418
+ return null;
2419
+ let e = s.expression;
2420
+ while (ts.isParenthesizedExpression(e))
2421
+ e = e.expression;
2422
+ if (!ts.isObjectLiteralExpression(e))
2423
+ return null;
2424
+ retObj = e;
2425
+ continue;
2426
+ }
2427
+ return null;
2428
+ }
2429
+ if (!retObj || retObj.properties.length === 0)
2430
+ return null;
2431
+ const env = { searchParamsVars, params: new Map };
2432
+ const entries = [];
2433
+ for (const prop of retObj.properties) {
2434
+ if (!ts.isPropertyAssignment(prop))
2435
+ return null;
2436
+ const key = ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name) ? prop.name.text : null;
2437
+ if (!key)
2438
+ return null;
2439
+ const go = this.lowerCtorExpr(prop.initializer, env);
2440
+ if (go === null)
2441
+ return null;
2442
+ entries.push(`"${this.capitalizeFieldName(key)}": ${go}`);
2443
+ }
2444
+ return `map[string]interface{}{
2445
+ ${entries.join(`,
2446
+ `)},
2447
+ }`;
2448
+ }
2449
+ lowerCtorExpr(node, env) {
2450
+ while (ts.isParenthesizedExpression(node))
2451
+ node = node.expression;
2452
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
2453
+ return JSON.stringify(node.text);
2454
+ }
2455
+ if (ts.isNumericLiteral(node))
2456
+ return node.text;
2457
+ if (ts.isIdentifier(node)) {
2458
+ const sub = env.params.get(node.text);
2459
+ if (sub !== undefined)
2460
+ return sub;
2461
+ const c = this.localConstants.find((lc) => lc.name === node.text);
2462
+ if (c?.value !== undefined) {
2463
+ if (c.isModule) {
2464
+ const lit = this.parseLiteralExpression(c.value);
2465
+ if (lit && (ts.isStringLiteral(lit) || ts.isNoSubstitutionTemplateLiteral(lit))) {
2466
+ return JSON.stringify(lit.text);
2467
+ }
2468
+ return null;
2469
+ }
2470
+ if (env.consts?.has(node.text))
2471
+ return null;
2472
+ const inner = this.parseLiteralExpression(c.value);
2473
+ if (!inner)
2474
+ return null;
2475
+ return this.lowerCtorExpr(inner, {
2476
+ ...env,
2477
+ consts: new Set([...env.consts ?? [], node.text])
2478
+ });
2479
+ }
2480
+ return null;
2481
+ }
2482
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === this.propsObjectName) {
2483
+ return `in.${this.capitalizeFieldName(node.name.text)}`;
2484
+ }
2485
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
2486
+ const method = node.expression.name.text;
2487
+ const recv = node.expression.expression;
2488
+ if (method === "get" && ts.isIdentifier(recv) && env.searchParamsVars.has(recv.text) && node.arguments.length === 1 && ts.isStringLiteral(node.arguments[0])) {
2489
+ return `in.SearchParams.Get(${JSON.stringify(node.arguments[0].text)})`;
2490
+ }
2491
+ if (method === "includes" && node.arguments.length === 1) {
2492
+ const arr = this.lowerCtorStringArray(recv);
2493
+ const elem = this.lowerCtorExpr(node.arguments[0], env);
2494
+ if (arr !== null && elem !== null)
2495
+ return `bf.Includes(${arr}, ${elem})`;
2496
+ return null;
2497
+ }
2498
+ 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 === "") {
2499
+ const recvGo = this.lowerCtorExpr(recv, env);
2500
+ if (recvGo === null)
2501
+ return null;
2502
+ this.needsStringsImport = true;
2503
+ return `strings.TrimRight(${recvGo}, "/")`;
2504
+ }
2505
+ return null;
2506
+ }
2507
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
2508
+ const fnConst = this.localConstants.find((lc) => lc.name === node.expression.text && lc.isModule);
2509
+ if (fnConst?.value) {
2510
+ const fn = this.parseLiteralExpression(fnConst.value);
2511
+ if (fn && ts.isArrowFunction(fn) && !ts.isBlock(fn.body) && fn.parameters.length === node.arguments.length) {
2512
+ const params = new Map(env.params);
2513
+ for (let i = 0;i < fn.parameters.length; i++) {
2514
+ const p = fn.parameters[i];
2515
+ if (!ts.isIdentifier(p.name))
2516
+ return null;
2517
+ const argGo = this.lowerCtorExpr(node.arguments[i], env);
2518
+ if (argGo === null)
2519
+ return null;
2520
+ params.set(p.name.text, argGo);
2521
+ }
2522
+ return this.lowerCtorExpr(fn.body, { searchParamsVars: env.searchParamsVars, params });
2523
+ }
2524
+ }
2525
+ return null;
2526
+ }
2527
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
2528
+ const left = this.lowerCtorExpr(node.left, env);
2529
+ if (left === null)
2530
+ return null;
2531
+ let r = node.right;
2532
+ while (ts.isParenthesizedExpression(r))
2533
+ r = r.expression;
2534
+ if ((ts.isStringLiteral(r) || ts.isNoSubstitutionTemplateLiteral(r)) && r.text === "") {
2535
+ return left;
2536
+ }
2537
+ const right = this.lowerCtorExpr(node.right, env);
2538
+ if (right === null)
2539
+ return null;
2540
+ return `func() string { v := ${left}; if v != "" { return v }; return ${right} }()`;
2541
+ }
2542
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
2543
+ const left = this.lowerCtorExpr(node.left, env);
2544
+ const right = this.lowerCtorExpr(node.right, env);
2545
+ if (left === null || right === null)
2546
+ return null;
2547
+ return `func() string { v := ${left}; if v != "" { return v }; return ${right} }()`;
2548
+ }
2549
+ if (ts.isConditionalExpression(node)) {
2550
+ const cond = this.lowerCtorCond(node.condition, env);
2551
+ const t = this.lowerCtorExpr(node.whenTrue, env);
2552
+ const f = this.lowerCtorExpr(node.whenFalse, env);
2553
+ if (cond !== null && t !== null && f !== null) {
2554
+ return `func() string { if ${cond} { return ${t} }; return ${f} }()`;
2555
+ }
2556
+ return null;
2557
+ }
2558
+ return null;
2559
+ }
2560
+ lowerCtorCond(node, env) {
2561
+ while (ts.isParenthesizedExpression(node))
2562
+ node = node.expression;
2563
+ if (node.kind === ts.SyntaxKind.TrueKeyword)
2564
+ return "true";
2565
+ if (node.kind === ts.SyntaxKind.FalseKeyword)
1900
2566
  return "false";
1901
- if (goType === "string")
1902
- return '""';
1903
- return "0";
2567
+ if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.ExclamationToken) {
2568
+ const inner = this.lowerCtorCond(node.operand, env);
2569
+ return inner === null ? null : `!(${inner})`;
2570
+ }
2571
+ if (ts.isBinaryExpression(node)) {
2572
+ const op = node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : node.operatorToken.kind === ts.SyntaxKind.BarBarToken ? "||" : null;
2573
+ if (op) {
2574
+ const l = this.lowerCtorCond(node.left, env);
2575
+ const r = this.lowerCtorCond(node.right, env);
2576
+ return l !== null && r !== null ? `(${l} ${op} ${r})` : null;
2577
+ }
2578
+ }
2579
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "includes") {
2580
+ return this.lowerCtorExpr(node, env);
2581
+ }
2582
+ return null;
2583
+ }
2584
+ lowerCtorStringArray(node) {
2585
+ let arr = node;
2586
+ if (ts.isIdentifier(node)) {
2587
+ const c = this.localConstants.find((lc) => lc.name === node.text && lc.isModule);
2588
+ if (!c?.value)
2589
+ return null;
2590
+ arr = this.parseLiteralExpression(c.value);
2591
+ }
2592
+ if (!arr || !ts.isArrayLiteralExpression(arr))
2593
+ return null;
2594
+ const elems = [];
2595
+ for (const el of arr.elements) {
2596
+ if (ts.isStringLiteral(el) || ts.isNoSubstitutionTemplateLiteral(el)) {
2597
+ elems.push(JSON.stringify(el.text));
2598
+ } else
2599
+ return null;
2600
+ }
2601
+ return `[]string{${elems.join(", ")}}`;
1904
2602
  }
1905
2603
  isTemplateLiteralMemo(computation) {
1906
2604
  const sf = ts.createSourceFile("__memo.ts", `const __x = (${computation});`, ts.ScriptTarget.Latest, false);
@@ -1956,6 +2654,10 @@ ${goFields.join(`
1956
2654
  if (this.typeInfoToGo(memo.type) === "interface{}" && this.isBooleanMemo(memo, signals, propsParamMap)) {
1957
2655
  return "bool";
1958
2656
  }
2657
+ const blockReturn = this.resolveBlockBodyMemoModuleConst(memo.computation, signals);
2658
+ if (blockReturn?.constType?.kind === "array") {
2659
+ return this.typeInfoToGo(blockReturn.constType);
2660
+ }
1959
2661
  return this.typeInfoToGo(memo.type);
1960
2662
  }
1961
2663
  isBooleanMemo(memo, signals, propsParamMap) {
@@ -2235,6 +2937,9 @@ ${goFields.join(`
2235
2937
  if (element.slotId) {
2236
2938
  hydrationAttrs += ` ${this.renderSlotMarker(element.slotId)}`;
2237
2939
  }
2940
+ if (element.regionId) {
2941
+ hydrationAttrs += ` ${BF_REGION}="${element.regionId}"`;
2942
+ }
2238
2943
  const voidElements = [
2239
2944
  "area",
2240
2945
  "base",
@@ -2263,8 +2968,9 @@ ${goFields.join(`
2263
2968
  }
2264
2969
  return "";
2265
2970
  }
2266
- const goExpr = this.convertExpressionToGo(expr.expr);
2267
- if (goExpr.startsWith("{{")) {
2971
+ const classify = {};
2972
+ const goExpr = this.convertExpressionToGo(expr.expr, classify);
2973
+ if (this.isTemplateFragment(goExpr, classify.parsed?.kind)) {
2268
2974
  if (expr.slotId) {
2269
2975
  return `{{bfTextStart "${expr.slotId}"}}${goExpr}{{bfTextEnd}}`;
2270
2976
  }
@@ -2275,6 +2981,9 @@ ${goFields.join(`
2275
2981
  }
2276
2982
  return `{{${goExpr}}}`;
2277
2983
  }
2984
+ isTemplateFragment(go, kind) {
2985
+ return go.startsWith("{{") || kind === "template-literal";
2986
+ }
2278
2987
  renderClientOnlyConditional(cond) {
2279
2988
  if (cond.slotId) {
2280
2989
  return `{{bfComment "cond-start:${cond.slotId}"}}{{bfComment "cond-end:${cond.slotId}"}}`;
@@ -2285,6 +2994,8 @@ ${goFields.join(`
2285
2994
  return emitParsedExpr(expr, this);
2286
2995
  }
2287
2996
  identifier(name) {
2997
+ if (name === "undefined" || name === "null")
2998
+ return '""';
2288
2999
  for (let i = this.loopBindingStack.length - 1;i >= 0; i--) {
2289
3000
  const acc = this.loopBindingStack[i].get(name);
2290
3001
  if (acc !== undefined)
@@ -2293,6 +3004,9 @@ ${goFields.join(`
2293
3004
  const inlined = this.resolveModuleStringConst(name);
2294
3005
  if (inlined !== null)
2295
3006
  return inlined;
3007
+ const inlinedNum = this.resolveModuleNumericConst(name);
3008
+ if (inlinedNum !== null)
3009
+ return inlinedNum;
2296
3010
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
2297
3011
  if (currentLoopParam && name === currentLoopParam)
2298
3012
  return ".";
@@ -2300,7 +3014,86 @@ ${goFields.join(`
2300
3014
  return `$${name}`;
2301
3015
  if (this.loopVarRefCount.has(name))
2302
3016
  return `$${name}`;
2303
- return this.rootFieldRef(name);
3017
+ if (this.localConstants.some((c) => c.name === name && !c.isModule && !c.containsArrow)) {
3018
+ this.referencedDerivedConsts.add(name);
3019
+ }
3020
+ return this.searchParamsFieldRef(name) ?? this.rootFieldRef(name);
3021
+ }
3022
+ computeDerivedConstFields(takenFieldNames) {
3023
+ const fields = [];
3024
+ for (const name of this.referencedDerivedConsts) {
3025
+ const fieldName = this.capitalizeFieldName(name);
3026
+ if (takenFieldNames.has(fieldName))
3027
+ continue;
3028
+ const c = this.localConstants.find((lc) => lc.name === name && !lc.isModule && lc.value);
3029
+ if (!c?.value)
3030
+ continue;
3031
+ const expr = this.parseLiteralExpression(c.value);
3032
+ if (!expr)
3033
+ continue;
3034
+ if (!this.isStringExpr(expr, new Set))
3035
+ continue;
3036
+ const init = this.lowerCtorExpr(expr, {
3037
+ searchParamsVars: new Set,
3038
+ params: new Map,
3039
+ consts: new Set([name])
3040
+ });
3041
+ if (init === null)
3042
+ continue;
3043
+ fields.push({ name: fieldName, init });
3044
+ }
3045
+ return fields;
3046
+ }
3047
+ isStringExpr(node, seen) {
3048
+ while (ts.isParenthesizedExpression(node))
3049
+ node = node.expression;
3050
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isTemplateExpression(node)) {
3051
+ return true;
3052
+ }
3053
+ if (ts.isBinaryExpression(node)) {
3054
+ const op = node.operatorToken.kind;
3055
+ if (op === ts.SyntaxKind.PlusToken) {
3056
+ return this.isStringExpr(node.left, seen) || this.isStringExpr(node.right, seen);
3057
+ }
3058
+ if (op === ts.SyntaxKind.BarBarToken || op === ts.SyntaxKind.QuestionQuestionToken) {
3059
+ return this.isStringExpr(node.left, seen) && this.isStringExpr(node.right, seen);
3060
+ }
3061
+ return false;
3062
+ }
3063
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
3064
+ const m = node.expression.name.text;
3065
+ const STRING_METHODS = new Set([
3066
+ "replace",
3067
+ "trim",
3068
+ "trimStart",
3069
+ "trimEnd",
3070
+ "toLowerCase",
3071
+ "toUpperCase",
3072
+ "slice",
3073
+ "substring",
3074
+ "substr",
3075
+ "padStart",
3076
+ "padEnd",
3077
+ "concat",
3078
+ "repeat",
3079
+ "get"
3080
+ ]);
3081
+ return STRING_METHODS.has(m);
3082
+ }
3083
+ if (ts.isConditionalExpression(node)) {
3084
+ return this.isStringExpr(node.whenTrue, seen) && this.isStringExpr(node.whenFalse, seen);
3085
+ }
3086
+ if (ts.isIdentifier(node)) {
3087
+ if (seen.has(node.text))
3088
+ return false;
3089
+ const c = this.localConstants.find((lc) => lc.name === node.text && !lc.isModule && lc.value);
3090
+ if (c?.value) {
3091
+ const inner = this.parseLiteralExpression(c.value);
3092
+ if (inner)
3093
+ return this.isStringExpr(inner, new Set([...seen, node.text]));
3094
+ }
3095
+ }
3096
+ return false;
2304
3097
  }
2305
3098
  isOuterLoopParam(name) {
2306
3099
  const top = this.loopParamStack.length - 1;
@@ -2314,70 +3107,26 @@ ${goFields.join(`
2314
3107
  const prefix = this.loopParamStack.length > 0 ? "$." : ".";
2315
3108
  return `${prefix}${this.capitalizeFieldName(name)}`;
2316
3109
  }
3110
+ searchParamsFieldRef(name) {
3111
+ return this.searchParamsLocals.has(name) ? this.rootFieldRef("searchParams") : null;
3112
+ }
2317
3113
  collectModuleStringConsts(constants) {
2318
- const map = new Map;
2319
- for (const c of constants ?? []) {
2320
- if (!c.isModule)
2321
- continue;
2322
- if (c.value === undefined)
2323
- continue;
2324
- const literal = this.parsePureStringLiteral(c.value);
2325
- if (literal !== null)
2326
- map.set(c.name, literal);
2327
- }
2328
- return map;
3114
+ return collectModuleStringConstsShared(constants);
2329
3115
  }
2330
- parsePureStringLiteral(source) {
2331
- const sf = ts.createSourceFile("__const.ts", `const __x = (${source});`, ts.ScriptTarget.Latest, false);
2332
- const stmt = sf.statements[0];
2333
- if (!stmt || !ts.isVariableStatement(stmt))
2334
- return null;
2335
- const decl = stmt.declarationList.declarations[0];
2336
- let init = decl?.initializer;
2337
- while (init && ts.isParenthesizedExpression(init))
2338
- init = init.expression;
2339
- if (!init)
3116
+ resolveModuleStringConst(name) {
3117
+ if (this.loopParamStack.length > 0 && this.loopParamStack[this.loopParamStack.length - 1] === name) {
2340
3118
  return null;
2341
- if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
2342
- return init.text;
2343
3119
  }
2344
- const joined = this.evalStringArrayJoin(init);
2345
- if (joined !== null)
2346
- return joined;
2347
- return null;
2348
- }
2349
- evalStringArrayJoin(node) {
2350
- if (!ts.isCallExpression(node))
2351
- return null;
2352
- const callee = node.expression;
2353
- if (!ts.isPropertyAccessExpression(callee))
3120
+ if (this.loopVarRefCount.has(name))
2354
3121
  return null;
2355
- if (callee.name.text !== "join")
3122
+ if (this.isOuterLoopParam(name))
2356
3123
  return null;
2357
- let recv = callee.expression;
2358
- while (ts.isParenthesizedExpression(recv))
2359
- recv = recv.expression;
2360
- if (!ts.isArrayLiteralExpression(recv))
3124
+ const value = this.moduleStringConsts.get(name);
3125
+ if (value === undefined)
2361
3126
  return null;
2362
- const parts = [];
2363
- for (const el of recv.elements) {
2364
- if (ts.isStringLiteral(el) || ts.isNoSubstitutionTemplateLiteral(el)) {
2365
- parts.push(el.text);
2366
- } else {
2367
- return null;
2368
- }
2369
- }
2370
- let sep = ",";
2371
- if (node.arguments.length >= 1) {
2372
- const arg = node.arguments[0];
2373
- if (ts.isStringLiteral(arg) || ts.isNoSubstitutionTemplateLiteral(arg))
2374
- sep = arg.text;
2375
- else
2376
- return null;
2377
- }
2378
- return parts.join(sep);
3127
+ return `"${this.escapeGoString(value)}"`;
2379
3128
  }
2380
- resolveModuleStringConst(name) {
3129
+ resolveModuleNumericConst(name) {
2381
3130
  if (this.loopParamStack.length > 0 && this.loopParamStack[this.loopParamStack.length - 1] === name) {
2382
3131
  return null;
2383
3132
  }
@@ -2385,10 +3134,11 @@ ${goFields.join(`
2385
3134
  return null;
2386
3135
  if (this.isOuterLoopParam(name))
2387
3136
  return null;
2388
- const value = this.moduleStringConsts.get(name);
2389
- if (value === undefined)
3137
+ const c = this.localConstants.find((k) => k.name === name && k.isModule && !k.containsArrow);
3138
+ if (!c || c.value === undefined)
2390
3139
  return null;
2391
- return `"${this.escapeGoString(value)}"`;
3140
+ const v = c.value.trim().replace(/(?<=\d)_(?=\d)/g, "");
3141
+ return /^-?\d+(\.\d+)?$/.test(v) ? v : null;
2392
3142
  }
2393
3143
  literal(value, literalType) {
2394
3144
  if (literalType === "string")
@@ -2399,7 +3149,7 @@ ${goFields.join(`
2399
3149
  }
2400
3150
  call(callee, args, emit) {
2401
3151
  if (callee.kind === "identifier" && args.length === 0) {
2402
- return this.rootFieldRef(callee.name);
3152
+ return this.searchParamsFieldRef(callee.name) ?? this.rootFieldRef(callee.name);
2403
3153
  }
2404
3154
  const path = identifierPath(callee);
2405
3155
  if (path && this.templatePrimitives[path]) {
@@ -2430,6 +3180,13 @@ ${goFields.join(`
2430
3180
  if (result)
2431
3181
  return result;
2432
3182
  }
3183
+ if (property === "length" && object.kind === "call" && object.callee.kind === "identifier" && object.args.length === 0) {
3184
+ const slice = this.memoBackedLoopSlice.get(object.callee.name);
3185
+ if (slice) {
3186
+ const prefix = this.loopParamStack.length > 0 ? "$." : ".";
3187
+ return `len ${prefix}${slice}`;
3188
+ }
3189
+ }
2433
3190
  if (object.kind === "higher-order" && (object.method === "find" || object.method === "findLast")) {
2434
3191
  const findResult = this.renderHigherOrderExpr(object, emit);
2435
3192
  if (findResult) {
@@ -2442,6 +3199,11 @@ ${goFields.join(`
2442
3199
  if (object.kind === "identifier" && this.propsObjectName && object.name === this.propsObjectName) {
2443
3200
  return this.rootFieldRef(property);
2444
3201
  }
3202
+ if (object.kind === "identifier") {
3203
+ const staticValue = this.resolveStaticRecordLiteralIndex(`${object.name}.${property}`);
3204
+ if (staticValue !== null)
3205
+ return staticValue;
3206
+ }
2445
3207
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
2446
3208
  if (object.kind === "identifier" && currentLoopParam && object.name === currentLoopParam) {
2447
3209
  return `.${this.capitalizeFieldName(property)}`;
@@ -2451,34 +3213,43 @@ ${goFields.join(`
2451
3213
  return `len ${obj}`;
2452
3214
  return `${obj}.${this.capitalizeFieldName(property)}`;
2453
3215
  }
3216
+ indexAccess(object, index, emit) {
3217
+ return `index ${wrapIfMultiToken(emit(object))} ${wrapIfMultiToken(emit(index))}`;
3218
+ }
2454
3219
  binary(op, left, right, emit) {
2455
3220
  const l = emit(left);
2456
3221
  const r = emit(right);
3222
+ const wl = wrapIfMultiToken(l);
3223
+ const wr = wrapIfMultiToken(r);
2457
3224
  switch (op) {
2458
3225
  case "===":
2459
- case "==":
2460
- return `eq ${l} ${r}`;
3226
+ case "==": {
3227
+ const [el, er] = stringTolerantEqOperands(l, r);
3228
+ return `eq ${wrapIfMultiToken(el)} ${wrapIfMultiToken(er)}`;
3229
+ }
2461
3230
  case "!==":
2462
- case "!=":
2463
- return `ne ${l} ${r}`;
3231
+ case "!=": {
3232
+ const [el, er] = stringTolerantEqOperands(l, r);
3233
+ return `ne ${wrapIfMultiToken(el)} ${wrapIfMultiToken(er)}`;
3234
+ }
2464
3235
  case ">":
2465
- return `gt ${l} ${r}`;
3236
+ return `gt ${wl} ${wr}`;
2466
3237
  case "<":
2467
- return `lt ${l} ${r}`;
3238
+ return `lt ${wl} ${wr}`;
2468
3239
  case ">=":
2469
- return `ge ${l} ${r}`;
3240
+ return `ge ${wl} ${wr}`;
2470
3241
  case "<=":
2471
- return `le ${l} ${r}`;
3242
+ return `le ${wl} ${wr}`;
2472
3243
  case "+":
2473
- return `bf_add ${l} ${r}`;
3244
+ return `bf_add ${wl} ${wr}`;
2474
3245
  case "-":
2475
- return `bf_sub ${l} ${r}`;
3246
+ return `bf_sub ${wl} ${wr}`;
2476
3247
  case "*":
2477
- return `bf_mul ${l} ${r}`;
3248
+ return `bf_mul ${wl} ${wr}`;
2478
3249
  case "/":
2479
- return `bf_div ${l} ${r}`;
3250
+ return `bf_div ${wl} ${wr}`;
2480
3251
  case "%":
2481
- return `bf_mod ${l} ${r}`;
3252
+ return `bf_mod ${wl} ${wr}`;
2482
3253
  default:
2483
3254
  return `${l} ${op} ${r}`;
2484
3255
  }
@@ -2492,10 +3263,8 @@ ${goFields.join(`
2492
3263
  return arg;
2493
3264
  }
2494
3265
  logical(op, left, right, emit) {
2495
- const l = emit(left);
2496
- const r = emit(right);
2497
- const wrapLeft = this.needsParens(left) ? `(${l})` : l;
2498
- const wrapRight = this.needsParens(right) ? `(${r})` : r;
3266
+ const wrapLeft = wrapIfMultiToken(emit(left));
3267
+ const wrapRight = wrapIfMultiToken(emit(right));
2499
3268
  if (op === "&&")
2500
3269
  return `and ${wrapLeft} ${wrapRight}`;
2501
3270
  return `or ${wrapLeft} ${wrapRight}`;
@@ -2512,7 +3281,8 @@ ${goFields.join(`
2512
3281
  if (part.type === "string") {
2513
3282
  result += part.value;
2514
3283
  } else {
2515
- result += `{{${emit(part.expr)}}}`;
3284
+ const e = emit(part.expr);
3285
+ result += this.isTemplateFragment(e, part.expr.kind) ? e : `{{${e}}}`;
2516
3286
  }
2517
3287
  }
2518
3288
  return result;
@@ -2613,6 +3383,11 @@ ${goFields.join(`
2613
3383
  const recv = emit(object);
2614
3384
  return `bf_trim ${wrapIfMultiToken(recv)}`;
2615
3385
  }
3386
+ case "toFixed": {
3387
+ const recv = emit(object);
3388
+ const digits = args.length >= 1 ? emit(args[0]) : "0";
3389
+ return `bf_to_fixed ${wrapIfMultiToken(recv)} ${wrapIfMultiToken(digits)}`;
3390
+ }
2616
3391
  case "split": {
2617
3392
  const recv = emit(object);
2618
3393
  if (args.length === 0) {
@@ -3098,11 +3873,33 @@ ${goFields.join(`
3098
3873
  return false;
3099
3874
  }
3100
3875
  }
3101
- convertExpressionToGo(jsExpr) {
3876
+ convertExpressionToGo(jsExpr, out) {
3102
3877
  const trimmed = jsExpr.trim();
3103
3878
  if (trimmed === "null" || trimmed === "undefined") {
3104
3879
  return '""';
3105
3880
  }
3881
+ const staticIndexed = this.resolveStaticRecordLiteralIndex(trimmed);
3882
+ if (staticIndexed !== null) {
3883
+ return staticIndexed;
3884
+ }
3885
+ if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
3886
+ const litConst = (this.localConstants ?? []).find((c) => c.name === trimmed);
3887
+ if (litConst?.value !== undefined) {
3888
+ const v = litConst.value.trim();
3889
+ if (/^-?\d+(\.\d+)?$/.test(v))
3890
+ return v;
3891
+ const strLit = /^'([^'\\]*)'$/.exec(v) ?? /^"([^"\\]*)"$/.exec(v);
3892
+ if (strLit)
3893
+ return JSON.stringify(strLit[1]);
3894
+ }
3895
+ }
3896
+ const urlBuilt = this.lowerUrlBuilderHelperCall(trimmed);
3897
+ if (urlBuilt !== null)
3898
+ return urlBuilt;
3899
+ const inlined = this.inlineLocalHelperCall(trimmed);
3900
+ if (inlined !== null) {
3901
+ return this.convertExpressionToGo(inlined, out);
3902
+ }
3106
3903
  const parsed = parseExpression(trimmed);
3107
3904
  const support = isSupported(parsed);
3108
3905
  if (!support.supported) {
@@ -3117,8 +3914,270 @@ ${goFields.join(`
3117
3914
  });
3118
3915
  return `""`;
3119
3916
  }
3917
+ if (out)
3918
+ out.parsed = parsed;
3120
3919
  return this.renderParsedExpr(parsed);
3121
3920
  }
3921
+ inlineLocalHelperCall(jsExpr) {
3922
+ if (this.localHelperNames.size === 0)
3923
+ return null;
3924
+ const head = /^\s*([A-Za-z_$][\w$]*)\s*\(/.exec(jsExpr);
3925
+ if (!head || !this.localHelperNames.has(head[1]))
3926
+ return null;
3927
+ const call = this.parseLiteralExpression(jsExpr);
3928
+ if (!call || !ts.isCallExpression(call) || !ts.isIdentifier(call.expression))
3929
+ return null;
3930
+ if (call.arguments.some((a) => ts.isSpreadElement(a)))
3931
+ return null;
3932
+ const fnConst = this.localConstants.find((c) => c.name === call.expression.text && !c.isModule && c.value);
3933
+ if (!fnConst?.value)
3934
+ return null;
3935
+ const fn = this.parseLiteralExpression(fnConst.value);
3936
+ if (!fn || !ts.isArrowFunction(fn) || ts.isBlock(fn.body))
3937
+ return null;
3938
+ if (fn.parameters.length !== call.arguments.length)
3939
+ return null;
3940
+ if (this.bodyCallsLocalHelper(fn.body))
3941
+ return null;
3942
+ const subs = new Map;
3943
+ for (let i = 0;i < fn.parameters.length; i++) {
3944
+ const p = fn.parameters[i];
3945
+ if (!ts.isIdentifier(p.name))
3946
+ return null;
3947
+ subs.set(p.name.text, `(${call.arguments[i].getText()})`);
3948
+ }
3949
+ if (!this.isSpliceSafeHelperBody(fn.body, new Set(subs.keys())))
3950
+ return null;
3951
+ return this.substituteHelperParams(fn.body, subs);
3952
+ }
3953
+ isSpliceSafeHelperBody(body, paramNames) {
3954
+ let safe = true;
3955
+ const visit = (n) => {
3956
+ if (!safe)
3957
+ return;
3958
+ if (ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n)) {
3959
+ safe = false;
3960
+ return;
3961
+ }
3962
+ if (ts.isShorthandPropertyAssignment(n) && paramNames.has(n.name.text)) {
3963
+ safe = false;
3964
+ return;
3965
+ }
3966
+ ts.forEachChild(n, visit);
3967
+ };
3968
+ visit(body);
3969
+ return safe;
3970
+ }
3971
+ bodyCallsLocalHelper(body) {
3972
+ let found = false;
3973
+ const visit = (n) => {
3974
+ if (found)
3975
+ return;
3976
+ if (ts.isCallExpression(n) && ts.isIdentifier(n.expression)) {
3977
+ const name = n.expression.text;
3978
+ if (this.localConstants.some((c) => c.name === name && !c.isModule && c.containsArrow)) {
3979
+ found = true;
3980
+ return;
3981
+ }
3982
+ }
3983
+ ts.forEachChild(n, visit);
3984
+ };
3985
+ visit(body);
3986
+ return found;
3987
+ }
3988
+ substituteHelperParams(body, subs) {
3989
+ const sf = body.getSourceFile();
3990
+ const base = body.getStart(sf);
3991
+ const repls = [];
3992
+ const visit = (node) => {
3993
+ if (ts.isPropertyAccessExpression(node)) {
3994
+ visit(node.expression);
3995
+ return;
3996
+ }
3997
+ if (ts.isPropertyAssignment(node)) {
3998
+ if (ts.isComputedPropertyName(node.name))
3999
+ visit(node.name);
4000
+ visit(node.initializer);
4001
+ return;
4002
+ }
4003
+ if (ts.isIdentifier(node)) {
4004
+ const sub = subs.get(node.text);
4005
+ if (sub !== undefined) {
4006
+ repls.push({ start: node.getStart(sf) - base, end: node.getEnd() - base, text: sub });
4007
+ return;
4008
+ }
4009
+ }
4010
+ ts.forEachChild(node, visit);
4011
+ };
4012
+ visit(body);
4013
+ let text = body.getText(sf);
4014
+ for (const r of repls.sort((a, b) => b.start - a.start)) {
4015
+ text = text.slice(0, r.start) + r.text + text.slice(r.end);
4016
+ }
4017
+ return text;
4018
+ }
4019
+ lowerUrlBuilderHelperCall(jsExpr) {
4020
+ if (this.localHelperNames.size === 0)
4021
+ return null;
4022
+ const head = /^\s*([A-Za-z_$][\w$]*)\s*\(/.exec(jsExpr);
4023
+ if (!head || !this.localHelperNames.has(head[1]))
4024
+ return null;
4025
+ const call = this.parseLiteralExpression(jsExpr);
4026
+ if (!call || !ts.isCallExpression(call) || !ts.isIdentifier(call.expression))
4027
+ return null;
4028
+ if (call.arguments.some((a) => ts.isSpreadElement(a)))
4029
+ return null;
4030
+ const fnConst = this.localConstants.find((c) => c.name === call.expression.text && !c.isModule && c.value);
4031
+ if (!fnConst?.value)
4032
+ return null;
4033
+ const fn = this.parseLiteralExpression(fnConst.value);
4034
+ if (!fn || !ts.isArrowFunction(fn) || fn.parameters.length !== call.arguments.length)
4035
+ return null;
4036
+ const subs = new Map;
4037
+ for (let i = 0;i < fn.parameters.length; i++) {
4038
+ const p = fn.parameters[i];
4039
+ if (!ts.isIdentifier(p.name))
4040
+ return null;
4041
+ subs.set(p.name.text, `(${call.arguments[i].getText()})`);
4042
+ }
4043
+ const shape = this.extractUrlBuilder(fn);
4044
+ if (shape)
4045
+ return this.emitUrlBuilder(shape, subs);
4046
+ if (!ts.isBlock(fn.body)) {
4047
+ let body = fn.body;
4048
+ while (ts.isParenthesizedExpression(body))
4049
+ body = body.expression;
4050
+ if (ts.isCallExpression(body) && ts.isIdentifier(body.expression) && this.localHelperNames.has(body.expression.text)) {
4051
+ return this.lowerUrlBuilderHelperCall(this.substituteHelperParams(body, subs));
4052
+ }
4053
+ }
4054
+ return null;
4055
+ }
4056
+ extractUrlBuilder(arrow) {
4057
+ if (!ts.isBlock(arrow.body))
4058
+ return null;
4059
+ let builderVar = null;
4060
+ let base = null;
4061
+ const sets = [];
4062
+ for (const s of arrow.body.statements) {
4063
+ if (ts.isVariableStatement(s)) {
4064
+ for (const d of s.declarationList.declarations) {
4065
+ 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) {
4066
+ builderVar = d.name.text;
4067
+ }
4068
+ }
4069
+ continue;
4070
+ }
4071
+ if (ts.isIfStatement(s) && !s.elseStatement && builderVar) {
4072
+ const set = this.matchUrlSet(s.thenStatement, builderVar);
4073
+ if (!set)
4074
+ return null;
4075
+ sets.push({ guard: s.expression, key: set.key, value: set.value });
4076
+ continue;
4077
+ }
4078
+ if (ts.isExpressionStatement(s) && builderVar) {
4079
+ const set = this.matchUrlSet(s, builderVar);
4080
+ if (set) {
4081
+ sets.push({ guard: null, key: set.key, value: set.value });
4082
+ continue;
4083
+ }
4084
+ return null;
4085
+ }
4086
+ if (ts.isReturnStatement(s) && s.expression) {
4087
+ let e = s.expression;
4088
+ while (ts.isParenthesizedExpression(e))
4089
+ e = e.expression;
4090
+ if (!ts.isConditionalExpression(e))
4091
+ return null;
4092
+ base = e.whenFalse;
4093
+ continue;
4094
+ }
4095
+ return null;
4096
+ }
4097
+ if (!builderVar || !base || sets.length === 0)
4098
+ return null;
4099
+ return { base, sets };
4100
+ }
4101
+ matchUrlSet(node, builderVar) {
4102
+ const stmt = ts.isBlock(node) ? node.statements.length === 1 ? node.statements[0] : null : node;
4103
+ if (!stmt || !ts.isExpressionStatement(stmt))
4104
+ return null;
4105
+ const call = stmt.expression;
4106
+ 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])) {
4107
+ return { key: call.arguments[0].text, value: call.arguments[1] };
4108
+ }
4109
+ return null;
4110
+ }
4111
+ emitUrlBuilder(shape, subs) {
4112
+ const lowerExpr = (n) => this.convertExpressionToGo(this.substituteHelperParams(n, subs));
4113
+ const baseGo = wrapIfMultiToken(lowerExpr(shape.base));
4114
+ const parts = [baseGo];
4115
+ for (const set of shape.sets) {
4116
+ const guardGo = set.guard ? this.lowerUrlGuard(set.guard, subs) : "true";
4117
+ parts.push(`(${guardGo})`);
4118
+ parts.push(JSON.stringify(set.key));
4119
+ parts.push(wrapIfMultiToken(lowerExpr(set.value)));
4120
+ }
4121
+ return `bf_query ${parts.join(" ")}`;
4122
+ }
4123
+ lowerUrlGuard(guard, subs) {
4124
+ let g = guard;
4125
+ while (ts.isParenthesizedExpression(g))
4126
+ g = g.expression;
4127
+ const isBoolShape = ts.isBinaryExpression(g) && [
4128
+ ts.SyntaxKind.EqualsEqualsToken,
4129
+ ts.SyntaxKind.EqualsEqualsEqualsToken,
4130
+ ts.SyntaxKind.ExclamationEqualsToken,
4131
+ ts.SyntaxKind.ExclamationEqualsEqualsToken,
4132
+ ts.SyntaxKind.LessThanToken,
4133
+ ts.SyntaxKind.GreaterThanToken,
4134
+ ts.SyntaxKind.LessThanEqualsToken,
4135
+ ts.SyntaxKind.GreaterThanEqualsToken
4136
+ ].includes(g.operatorToken.kind) || ts.isPrefixUnaryExpression(g) && g.operator === ts.SyntaxKind.ExclamationToken || g.kind === ts.SyntaxKind.TrueKeyword || g.kind === ts.SyntaxKind.FalseKeyword;
4137
+ if (isBoolShape) {
4138
+ return this.convertConditionToGo(this.substituteHelperParams(guard, subs)).condition;
4139
+ }
4140
+ const valueGo = wrapIfMultiToken(this.convertExpressionToGo(this.substituteHelperParams(guard, subs)));
4141
+ return `ne ${valueGo} ""`;
4142
+ }
4143
+ resolveStaticRecordLiteralIndex(jsExpr) {
4144
+ const m = /^([A-Za-z_$][\w$]*)\[\s*(?:'([^']*)'|"([^"]*)")\s*\]$/.exec(jsExpr) ?? /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/.exec(jsExpr);
4145
+ if (!m)
4146
+ return null;
4147
+ const key = m[2] ?? m[3];
4148
+ const constInfo = (this.localConstants ?? []).find((c) => c.name === m[1] && c.isModule);
4149
+ if (constInfo?.value === undefined)
4150
+ return null;
4151
+ const sf = ts.createSourceFile("__rec.ts", `(${constInfo.value})`, ts.ScriptTarget.Latest, true);
4152
+ if (sf.statements.length !== 1)
4153
+ return null;
4154
+ const stmt = sf.statements[0];
4155
+ if (!ts.isExpressionStatement(stmt))
4156
+ return null;
4157
+ let parsed = stmt.expression;
4158
+ while (ts.isParenthesizedExpression(parsed))
4159
+ parsed = parsed.expression;
4160
+ if (!ts.isObjectLiteralExpression(parsed))
4161
+ return null;
4162
+ for (const prop of parsed.properties) {
4163
+ if (!ts.isPropertyAssignment(prop))
4164
+ continue;
4165
+ const name = prop.name;
4166
+ const propKey = ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNoSubstitutionTemplateLiteral(name) ? name.text : null;
4167
+ if (propKey !== key)
4168
+ continue;
4169
+ let v = prop.initializer;
4170
+ while (ts.isParenthesizedExpression(v))
4171
+ v = v.expression;
4172
+ if (ts.isNumericLiteral(v))
4173
+ return v.text;
4174
+ if (ts.isStringLiteral(v) || ts.isNoSubstitutionTemplateLiteral(v)) {
4175
+ return JSON.stringify(v.text);
4176
+ }
4177
+ return null;
4178
+ }
4179
+ return null;
4180
+ }
3122
4181
  makeLoc() {
3123
4182
  return {
3124
4183
  file: this.componentName + ".tsx",
@@ -3292,6 +4351,14 @@ ${goFields.join(`
3292
4351
  }
3293
4352
  return { preamble: obj.preamble, expr: `${obj.expr}.${this.capitalizeFieldName(expr.property)}` };
3294
4353
  }
4354
+ case "index-access": {
4355
+ const obj = this.renderConditionExpr(expr.object);
4356
+ const idx = this.renderConditionExpr(expr.index);
4357
+ return {
4358
+ preamble: obj.preamble + idx.preamble,
4359
+ expr: `index ${wrapIfMultiToken(obj.expr)} ${wrapIfMultiToken(idx.expr)}`
4360
+ };
4361
+ }
3295
4362
  case "binary": {
3296
4363
  const leftNeedsParens = this.needsParensInGoTemplate(expr.left);
3297
4364
  const leftResult = this.renderConditionExpr(expr.left);
@@ -3303,13 +4370,17 @@ ${goFields.join(`
3303
4370
  let result;
3304
4371
  switch (expr.op) {
3305
4372
  case "===":
3306
- case "==":
3307
- result = `eq ${left} ${right}`;
4373
+ case "==": {
4374
+ const [el, er] = stringTolerantEqOperands(left, right);
4375
+ result = `eq ${el} ${er}`;
3308
4376
  break;
4377
+ }
3309
4378
  case "!==":
3310
- case "!=":
3311
- result = `ne ${left} ${right}`;
4379
+ case "!=": {
4380
+ const [el, er] = stringTolerantEqOperands(left, right);
4381
+ result = `ne ${el} ${er}`;
3312
4382
  break;
4383
+ }
3313
4384
  case ">":
3314
4385
  result = `gt ${left} ${right}`;
3315
4386
  break;
@@ -3500,6 +4571,45 @@ ${goFields.join(`
3500
4571
  }
3501
4572
  return null;
3502
4573
  }
4574
+ queueDynamicChildrenDefine(comp) {
4575
+ const effectiveChildren = comp.children.length > 0 ? comp.children : this.jsxChildrenPropNodes(comp.props);
4576
+ if (effectiveChildren.length === 0)
4577
+ return null;
4578
+ if (this.extractTextChildren(effectiveChildren) !== null)
4579
+ return null;
4580
+ if (this.extractHtmlChildren(effectiveChildren) !== null)
4581
+ return null;
4582
+ if (this.extractScopedHtmlChildren(effectiveChildren) !== null)
4583
+ return null;
4584
+ const name = `${this.componentName}__children_${comp.slotId}`;
4585
+ if (!this.pendingChildrenDefines.some((d) => d.name === name)) {
4586
+ this.pendingChildrenDefines.push({
4587
+ name,
4588
+ content: this.renderChildren(effectiveChildren)
4589
+ });
4590
+ }
4591
+ return name;
4592
+ }
4593
+ queueLoopBodyChildrenDefine(comp) {
4594
+ const effectiveChildren = comp.children.length > 0 ? comp.children : this.jsxChildrenPropNodes(comp.props);
4595
+ if (effectiveChildren.length === 0)
4596
+ return null;
4597
+ if (this.extractTextChildren(effectiveChildren) !== null)
4598
+ return null;
4599
+ if (this.extractHtmlChildren(effectiveChildren) !== null)
4600
+ return null;
4601
+ if (this.extractScopedHtmlChildren(effectiveChildren) !== null)
4602
+ return null;
4603
+ const name = `${this.componentName}__loop_children_${comp.slotId}`;
4604
+ if (!this.pendingChildrenDefines.some((d) => d.name === name)) {
4605
+ const wasInLoop = this.inLoop;
4606
+ this.inLoop = false;
4607
+ const content = this.renderChildren(effectiveChildren);
4608
+ this.inLoop = wasInLoop;
4609
+ this.pendingChildrenDefines.push({ name, content });
4610
+ }
4611
+ return name;
4612
+ }
3503
4613
  renderComponent(comp, ctx) {
3504
4614
  if (comp.name === "Portal") {
3505
4615
  return this.renderPortalComponent(comp);
@@ -3509,10 +4619,16 @@ ${goFields.join(`
3509
4619
  }
3510
4620
  let templateCall;
3511
4621
  if (this.inLoop) {
3512
- templateCall = `{{template "${comp.name}" .}}`;
4622
+ const loopBodyDefine = this.queueLoopBodyChildrenDefine(comp);
4623
+ if (loopBodyDefine) {
4624
+ templateCall = `{{template "${comp.name}" (bf_with_children . (bf_tmpl "${loopBodyDefine}" .))}}`;
4625
+ } else {
4626
+ templateCall = `{{template "${comp.name}" .}}`;
4627
+ }
3513
4628
  } else if (comp.slotId) {
3514
4629
  const suffix = slotIdToFieldSuffix(comp.slotId);
3515
- templateCall = `{{template "${comp.name}" .${comp.name}${suffix}}}`;
4630
+ const childrenDefine = this.queueDynamicChildrenDefine(comp);
4631
+ templateCall = childrenDefine ? `{{template "${comp.name}" (bf_with_children .${comp.name}${suffix} (bf_tmpl "${childrenDefine}" .))}}` : `{{template "${comp.name}" .${comp.name}${suffix}}}`;
3516
4632
  } else {
3517
4633
  templateCall = `{{template "${comp.name}" .${comp.name}}}`;
3518
4634
  }
@@ -3559,10 +4675,22 @@ ${children}`;
3559
4675
  }
3560
4676
  if (isBooleanAttr(name) || value.presenceOrUndefined) {
3561
4677
  const { condition: goCond, preamble } = this.convertConditionToGo(value.expr);
3562
- return `${preamble}{{if ${goCond}}}${name}{{end}}`;
4678
+ const body = name.startsWith("aria-") ? `${name}="true"` : name;
4679
+ return `${preamble}{{if ${goCond}}}${body}{{end}}`;
3563
4680
  }
3564
4681
  const parsed = parseExpression(value.expr.trim());
3565
- if (parsed.kind === "conditional" || parsed.kind === "template-literal") {
4682
+ if (parsed.kind === "conditional") {
4683
+ const undef = (e) => e.kind === "identifier" && (e.name === "undefined" || e.name === "null") || e.kind === "literal" && (e.value === null || e.value === undefined);
4684
+ const test = parsed.test;
4685
+ if (undef(parsed.alternate) && !undef(parsed.consequent)) {
4686
+ const { condition: goCond, preamble } = this.convertConditionToGo(this.isTemplateFragment(this.renderParsedExpr(test), test.kind) ? value.expr : value.expr.slice(0, value.expr.indexOf("?")).trim());
4687
+ const valueExpr = this.renderParsedExpr(parsed.consequent);
4688
+ const body = `${name}="{{${valueExpr}}}"`;
4689
+ return `${preamble}{{if ${goCond}}}${body}{{end}}`;
4690
+ }
4691
+ return `${name}="${this.renderParsedExpr(parsed)}"`;
4692
+ }
4693
+ if (parsed.kind === "template-literal") {
3566
4694
  return `${name}="${this.renderParsedExpr(parsed)}"`;
3567
4695
  }
3568
4696
  const bareId = value.expr.trim();
@@ -3571,7 +4699,9 @@ ${children}`;
3571
4699
  const field = `.${this.capitalizeFieldName(propName)}`;
3572
4700
  return `{{if ne ${field} nil}}${name}="{{${this.convertExpressionToGo(value.expr)}}}"{{end}}`;
3573
4701
  }
3574
- return `${name}="{{${this.convertExpressionToGo(value.expr)}}}"`;
4702
+ const exprOut = {};
4703
+ const go = this.convertExpressionToGo(value.expr, exprOut);
4704
+ return this.isTemplateFragment(go, exprOut.parsed?.kind) ? `${name}="${go}"` : `${name}="{{${go}}}"`;
3575
4705
  },
3576
4706
  emitBooleanAttr: (_value, name) => name,
3577
4707
  emitSpread: (value) => {
@@ -3645,7 +4775,21 @@ ${children}`;
3645
4775
  }
3646
4776
  const inner = s.slice(open + 2, close).trim();
3647
4777
  if (inner) {
3648
- out += `{{${this.convertExpressionToGo(inner)}}}`;
4778
+ const cls = {};
4779
+ const goExpr = this.convertExpressionToGo(inner, cls);
4780
+ const parsed = cls.parsed;
4781
+ if (parsed?.kind === "template-literal") {
4782
+ for (const part of parsed.parts) {
4783
+ if (part.type === "string") {
4784
+ out += this.escapeAttrText(part.value);
4785
+ } else {
4786
+ const e = this.renderParsedExpr(part.expr);
4787
+ out += this.isTemplateFragment(e, part.expr.kind) ? e : `{{${e}}}`;
4788
+ }
4789
+ }
4790
+ } else {
4791
+ out += this.isTemplateFragment(goExpr, parsed?.kind) ? goExpr : `{{${goExpr}}}`;
4792
+ }
3649
4793
  } else {
3650
4794
  out += s.slice(open, close + 1);
3651
4795
  }
@@ -3665,7 +4809,8 @@ ${children}`;
3665
4809
  const { condition: goCond, preamble } = this.convertConditionToGo(part.condition);
3666
4810
  output += `${preamble}{{if ${goCond}}}${part.whenTrue}{{else}}${part.whenFalse}{{end}}`;
3667
4811
  } else if (part.type === "lookup") {
3668
- const keyExpr = this.convertExpressionToGo(part.key);
4812
+ const rawKeyExpr = this.convertExpressionToGo(part.key);
4813
+ const keyExpr = /\s/.test(rawKeyExpr) ? `(${rawKeyExpr})` : rawKeyExpr;
3669
4814
  const caseEntries = Object.entries(part.cases);
3670
4815
  if (caseEntries.length === 0)
3671
4816
  continue;