@barefootjs/go-template 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -109,6 +109,20 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
109
109
  /** Set during type generation when any emit references
110
110
  * `template.HTML(...)`; toggles the `"html/template"` import. */
111
111
  private usesHtmlTemplate;
112
+ /**
113
+ * Module-scope pure string-literal constants (`const X = 'literal'` at
114
+ * file top-level), keyed by name → resolved literal value. Populated at
115
+ * `generate()` entry from `ir.metadata.localConstants`. When an identifier
116
+ * in an expression resolves to one of these, the adapter inlines the
117
+ * literal value instead of emitting a struct-field reference
118
+ * (`{{.X}}`) — the field never exists on the Props struct, so without
119
+ * inlining Go's template engine fails with `can't evaluate field X`.
120
+ * Hono inlines it for free (it evaluates real JS); this restores parity.
121
+ * Only module-scope, pure string literals qualify — function-scope
122
+ * locals legitimately become template vars/props, and `Record<T,string>`
123
+ * indexed lookups / memos / signals are deliberately excluded.
124
+ */
125
+ private moduleStringConsts;
112
126
  constructor(options?: GoTemplateAdapterOptions);
113
127
  /**
114
128
  * Generate template output for a component.
@@ -554,6 +568,34 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
554
568
  * rebinds. Outside any loop the root *is* the dot, so we emit `.Field` (#1677).
555
569
  */
556
570
  private rootFieldRef;
571
+ /**
572
+ * Build the module pure-string-const map from the IR's localConstants.
573
+ * A const qualifies only when it is module-scope (`isModule`) and its
574
+ * initializer parses to a single string literal (`ts.StringLiteral` or
575
+ * `ts.NoSubstitutionTemplateLiteral` — a backtick string with no `${}`).
576
+ * Template literals *with* interpolations, numeric/object initializers,
577
+ * `Record<T,string>` maps, memos, and signals are all excluded: only a
578
+ * pure compile-time string can be safely inlined byte-for-byte.
579
+ */
580
+ private collectModuleStringConsts;
581
+ /**
582
+ * Parse a const initializer's source text. Returns the unescaped string
583
+ * value when the whole initializer is a single string literal (or a
584
+ * no-substitution template literal), else `null`. Uses the TS parser so
585
+ * escapes/quotes are resolved exactly as JS would, matching the value
586
+ * the Hono reference inlines at runtime.
587
+ */
588
+ private parsePureStringLiteral;
589
+ /**
590
+ * Resolve an identifier to its inlined Go string literal when it names a
591
+ * module pure-string const. Returns the Go template literal form
592
+ * (`"<escaped>"`) so callers can drop it straight into a `{{...}}` action,
593
+ * or `null` when the name is not such a const (the caller then falls back
594
+ * to its normal field-ref lowering). The value is escaped for a Go
595
+ * double-quoted string literal — Go's `html/template` then applies the
596
+ * same contextual auto-escaping it applies to any literal, matching Hono.
597
+ */
598
+ private resolveModuleStringConst;
557
599
  literal(value: string | number | boolean | null, literalType: LiteralType): string;
558
600
  call(callee: ParsedExpr, args: ParsedExpr[], emit: (e: ParsedExpr) => string): string;
559
601
  member(object: ParsedExpr, property: string, _computed: boolean, emit: (e: ParsedExpr) => string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"go-template-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/go-template-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"go-template-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/go-template-adapter.ts"],"names":[],"mappings":"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"}
@@ -117,6 +117,7 @@ class GoTemplateAdapter extends BaseAdapter {
117
117
  localStructFields = new Map;
118
118
  synthStructTypes = new Map;
119
119
  usesHtmlTemplate = false;
120
+ moduleStringConsts = new Map;
120
121
  constructor(options = {}) {
121
122
  super();
122
123
  this.options = {
@@ -131,6 +132,7 @@ class GoTemplateAdapter extends BaseAdapter {
131
132
  this.templateVarCounter = 0;
132
133
  this.propsObjectName = ir.metadata.propsObjectName;
133
134
  this.restPropsName = ir.metadata.restPropsName ?? null;
135
+ this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
134
136
  if (!options?.siblingTemplatesRegistered) {
135
137
  this.checkImportedLoopChildComponents(ir);
136
138
  }
@@ -912,6 +914,12 @@ ${goFields.join(`
912
914
  collectStaticChildInstancesRecursive(node, result, inLoop) {
913
915
  if (node.type === "component") {
914
916
  const comp = node;
917
+ if (comp.dynamicTag) {
918
+ for (const child of comp.children) {
919
+ this.collectStaticChildInstancesRecursive(child, result, inLoop);
920
+ }
921
+ return;
922
+ }
915
923
  if (comp.name !== "Portal" && !inLoop && comp.slotId) {
916
924
  const suffix = slotIdToFieldSuffix(comp.slotId);
917
925
  result.push({
@@ -1748,6 +1756,9 @@ ${goFields.join(`
1748
1756
  return emitParsedExpr(expr, this);
1749
1757
  }
1750
1758
  identifier(name) {
1759
+ const inlined = this.resolveModuleStringConst(name);
1760
+ if (inlined !== null)
1761
+ return inlined;
1751
1762
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
1752
1763
  if (currentLoopParam && name === currentLoopParam)
1753
1764
  return ".";
@@ -1769,6 +1780,48 @@ ${goFields.join(`
1769
1780
  const prefix = this.loopParamStack.length > 0 ? "$." : ".";
1770
1781
  return `${prefix}${this.capitalizeFieldName(name)}`;
1771
1782
  }
1783
+ collectModuleStringConsts(constants) {
1784
+ const map = new Map;
1785
+ for (const c of constants ?? []) {
1786
+ if (!c.isModule)
1787
+ continue;
1788
+ if (c.value === undefined)
1789
+ continue;
1790
+ const literal = this.parsePureStringLiteral(c.value);
1791
+ if (literal !== null)
1792
+ map.set(c.name, literal);
1793
+ }
1794
+ return map;
1795
+ }
1796
+ parsePureStringLiteral(source) {
1797
+ const sf = ts.createSourceFile("__const.ts", `const __x = (${source});`, ts.ScriptTarget.Latest, false);
1798
+ const stmt = sf.statements[0];
1799
+ if (!stmt || !ts.isVariableStatement(stmt))
1800
+ return null;
1801
+ const decl = stmt.declarationList.declarations[0];
1802
+ let init = decl?.initializer;
1803
+ while (init && ts.isParenthesizedExpression(init))
1804
+ init = init.expression;
1805
+ if (!init)
1806
+ return null;
1807
+ if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
1808
+ return init.text;
1809
+ }
1810
+ return null;
1811
+ }
1812
+ resolveModuleStringConst(name) {
1813
+ if (this.loopParamStack.length > 0 && this.loopParamStack[this.loopParamStack.length - 1] === name) {
1814
+ return null;
1815
+ }
1816
+ if (this.loopVarRefCount.has(name))
1817
+ return null;
1818
+ if (this.isOuterLoopParam(name))
1819
+ return null;
1820
+ const value = this.moduleStringConsts.get(name);
1821
+ if (value === undefined)
1822
+ return null;
1823
+ return `"${this.escapeGoString(value)}"`;
1824
+ }
1772
1825
  literal(value, literalType) {
1773
1826
  if (literalType === "string")
1774
1827
  return `"${value}"`;
@@ -2609,6 +2662,9 @@ ${goFields.join(`
2609
2662
  switch (expr.kind) {
2610
2663
  case "identifier":
2611
2664
  {
2665
+ const inlined = this.resolveModuleStringConst(expr.name);
2666
+ if (inlined !== null)
2667
+ return plain(inlined);
2612
2668
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
2613
2669
  if (currentLoopParam && expr.name === currentLoopParam) {
2614
2670
  return plain(".");
@@ -2631,6 +2687,20 @@ ${goFields.join(`
2631
2687
  if (expr.callee.kind === "identifier" && expr.args.length === 0) {
2632
2688
  return plain(this.rootFieldRef(expr.callee.name));
2633
2689
  }
2690
+ if (expr.callee.kind === "identifier" && (identifierPath(expr.callee) ?? expr.callee.name) === "isValidElement" && expr.args.length === 1) {
2691
+ return this.renderConditionExpr(expr.args[0]);
2692
+ }
2693
+ if (expr.callee.kind === "identifier" && !this.templatePrimitives[identifierPath(expr.callee) ?? ""]) {
2694
+ const path = identifierPath(expr.callee) ?? expr.callee.name;
2695
+ this.errors.push({
2696
+ code: "BF102",
2697
+ severity: "error",
2698
+ message: `Predicate '${path}(...)' cannot be evaluated in a Go template. ` + `A server-side template cannot call user-defined JavaScript predicates.`,
2699
+ loc: this.makeLoc(),
2700
+ suggestion: { message: GO_REMEDIATION_OPTIONS }
2701
+ });
2702
+ return plain("false");
2703
+ }
2634
2704
  return plain(this.renderParsedExpr(expr));
2635
2705
  }
2636
2706
  case "member": {
@@ -2842,6 +2912,9 @@ ${goFields.join(`
2842
2912
  if (comp.name === "Portal") {
2843
2913
  return this.renderPortalComponent(comp);
2844
2914
  }
2915
+ if (comp.dynamicTag) {
2916
+ return this.renderChildren(comp.children);
2917
+ }
2845
2918
  let templateCall;
2846
2919
  if (this.inLoop) {
2847
2920
  templateCall = `{{template "${comp.name}" .}}`;
@@ -2985,7 +3058,7 @@ ${children}`;
2985
3058
  continue;
2986
3059
  const branches = caseEntries.map(([k, v], i) => {
2987
3060
  const head = i === 0 ? "{{if" : "{{else if";
2988
- return `${head} eq ${keyExpr} ${JSON.stringify(k)}}}${v}`;
3061
+ return `${head} eq ${keyExpr} ${JSON.stringify(k)}}}${this.escapeAttrText(v)}`;
2989
3062
  });
2990
3063
  output += branches.join("") + "{{end}}";
2991
3064
  }
package/dist/build.js CHANGED
@@ -457,6 +457,7 @@ class GoTemplateAdapter extends BaseAdapter {
457
457
  localStructFields = new Map;
458
458
  synthStructTypes = new Map;
459
459
  usesHtmlTemplate = false;
460
+ moduleStringConsts = new Map;
460
461
  constructor(options = {}) {
461
462
  super();
462
463
  this.options = {
@@ -471,6 +472,7 @@ class GoTemplateAdapter extends BaseAdapter {
471
472
  this.templateVarCounter = 0;
472
473
  this.propsObjectName = ir.metadata.propsObjectName;
473
474
  this.restPropsName = ir.metadata.restPropsName ?? null;
475
+ this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
474
476
  if (!options?.siblingTemplatesRegistered) {
475
477
  this.checkImportedLoopChildComponents(ir);
476
478
  }
@@ -1252,6 +1254,12 @@ ${goFields.join(`
1252
1254
  collectStaticChildInstancesRecursive(node, result, inLoop) {
1253
1255
  if (node.type === "component") {
1254
1256
  const comp = node;
1257
+ if (comp.dynamicTag) {
1258
+ for (const child of comp.children) {
1259
+ this.collectStaticChildInstancesRecursive(child, result, inLoop);
1260
+ }
1261
+ return;
1262
+ }
1255
1263
  if (comp.name !== "Portal" && !inLoop && comp.slotId) {
1256
1264
  const suffix = slotIdToFieldSuffix(comp.slotId);
1257
1265
  result.push({
@@ -2088,6 +2096,9 @@ ${goFields.join(`
2088
2096
  return emitParsedExpr(expr, this);
2089
2097
  }
2090
2098
  identifier(name) {
2099
+ const inlined = this.resolveModuleStringConst(name);
2100
+ if (inlined !== null)
2101
+ return inlined;
2091
2102
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
2092
2103
  if (currentLoopParam && name === currentLoopParam)
2093
2104
  return ".";
@@ -2109,6 +2120,48 @@ ${goFields.join(`
2109
2120
  const prefix = this.loopParamStack.length > 0 ? "$." : ".";
2110
2121
  return `${prefix}${this.capitalizeFieldName(name)}`;
2111
2122
  }
2123
+ collectModuleStringConsts(constants) {
2124
+ const map = new Map;
2125
+ for (const c of constants ?? []) {
2126
+ if (!c.isModule)
2127
+ continue;
2128
+ if (c.value === undefined)
2129
+ continue;
2130
+ const literal = this.parsePureStringLiteral(c.value);
2131
+ if (literal !== null)
2132
+ map.set(c.name, literal);
2133
+ }
2134
+ return map;
2135
+ }
2136
+ parsePureStringLiteral(source) {
2137
+ const sf = ts.createSourceFile("__const.ts", `const __x = (${source});`, ts.ScriptTarget.Latest, false);
2138
+ const stmt = sf.statements[0];
2139
+ if (!stmt || !ts.isVariableStatement(stmt))
2140
+ return null;
2141
+ const decl = stmt.declarationList.declarations[0];
2142
+ let init = decl?.initializer;
2143
+ while (init && ts.isParenthesizedExpression(init))
2144
+ init = init.expression;
2145
+ if (!init)
2146
+ return null;
2147
+ if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
2148
+ return init.text;
2149
+ }
2150
+ return null;
2151
+ }
2152
+ resolveModuleStringConst(name) {
2153
+ if (this.loopParamStack.length > 0 && this.loopParamStack[this.loopParamStack.length - 1] === name) {
2154
+ return null;
2155
+ }
2156
+ if (this.loopVarRefCount.has(name))
2157
+ return null;
2158
+ if (this.isOuterLoopParam(name))
2159
+ return null;
2160
+ const value = this.moduleStringConsts.get(name);
2161
+ if (value === undefined)
2162
+ return null;
2163
+ return `"${this.escapeGoString(value)}"`;
2164
+ }
2112
2165
  literal(value, literalType) {
2113
2166
  if (literalType === "string")
2114
2167
  return `"${value}"`;
@@ -2949,6 +3002,9 @@ ${goFields.join(`
2949
3002
  switch (expr.kind) {
2950
3003
  case "identifier":
2951
3004
  {
3005
+ const inlined = this.resolveModuleStringConst(expr.name);
3006
+ if (inlined !== null)
3007
+ return plain(inlined);
2952
3008
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
2953
3009
  if (currentLoopParam && expr.name === currentLoopParam) {
2954
3010
  return plain(".");
@@ -2971,6 +3027,20 @@ ${goFields.join(`
2971
3027
  if (expr.callee.kind === "identifier" && expr.args.length === 0) {
2972
3028
  return plain(this.rootFieldRef(expr.callee.name));
2973
3029
  }
3030
+ if (expr.callee.kind === "identifier" && (identifierPath(expr.callee) ?? expr.callee.name) === "isValidElement" && expr.args.length === 1) {
3031
+ return this.renderConditionExpr(expr.args[0]);
3032
+ }
3033
+ if (expr.callee.kind === "identifier" && !this.templatePrimitives[identifierPath(expr.callee) ?? ""]) {
3034
+ const path = identifierPath(expr.callee) ?? expr.callee.name;
3035
+ this.errors.push({
3036
+ code: "BF102",
3037
+ severity: "error",
3038
+ message: `Predicate '${path}(...)' cannot be evaluated in a Go template. ` + `A server-side template cannot call user-defined JavaScript predicates.`,
3039
+ loc: this.makeLoc(),
3040
+ suggestion: { message: GO_REMEDIATION_OPTIONS }
3041
+ });
3042
+ return plain("false");
3043
+ }
2974
3044
  return plain(this.renderParsedExpr(expr));
2975
3045
  }
2976
3046
  case "member": {
@@ -3182,6 +3252,9 @@ ${goFields.join(`
3182
3252
  if (comp.name === "Portal") {
3183
3253
  return this.renderPortalComponent(comp);
3184
3254
  }
3255
+ if (comp.dynamicTag) {
3256
+ return this.renderChildren(comp.children);
3257
+ }
3185
3258
  let templateCall;
3186
3259
  if (this.inLoop) {
3187
3260
  templateCall = `{{template "${comp.name}" .}}`;
@@ -3325,7 +3398,7 @@ ${children}`;
3325
3398
  continue;
3326
3399
  const branches = caseEntries.map(([k, v], i) => {
3327
3400
  const head = i === 0 ? "{{if" : "{{else if";
3328
- return `${head} eq ${keyExpr} ${JSON.stringify(k)}}}${v}`;
3401
+ return `${head} eq ${keyExpr} ${JSON.stringify(k)}}}${this.escapeAttrText(v)}`;
3329
3402
  });
3330
3403
  output += branches.join("") + "{{end}}";
3331
3404
  }
package/dist/index.js CHANGED
@@ -117,6 +117,7 @@ class GoTemplateAdapter extends BaseAdapter {
117
117
  localStructFields = new Map;
118
118
  synthStructTypes = new Map;
119
119
  usesHtmlTemplate = false;
120
+ moduleStringConsts = new Map;
120
121
  constructor(options = {}) {
121
122
  super();
122
123
  this.options = {
@@ -131,6 +132,7 @@ class GoTemplateAdapter extends BaseAdapter {
131
132
  this.templateVarCounter = 0;
132
133
  this.propsObjectName = ir.metadata.propsObjectName;
133
134
  this.restPropsName = ir.metadata.restPropsName ?? null;
135
+ this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
134
136
  if (!options?.siblingTemplatesRegistered) {
135
137
  this.checkImportedLoopChildComponents(ir);
136
138
  }
@@ -912,6 +914,12 @@ ${goFields.join(`
912
914
  collectStaticChildInstancesRecursive(node, result, inLoop) {
913
915
  if (node.type === "component") {
914
916
  const comp = node;
917
+ if (comp.dynamicTag) {
918
+ for (const child of comp.children) {
919
+ this.collectStaticChildInstancesRecursive(child, result, inLoop);
920
+ }
921
+ return;
922
+ }
915
923
  if (comp.name !== "Portal" && !inLoop && comp.slotId) {
916
924
  const suffix = slotIdToFieldSuffix(comp.slotId);
917
925
  result.push({
@@ -1748,6 +1756,9 @@ ${goFields.join(`
1748
1756
  return emitParsedExpr(expr, this);
1749
1757
  }
1750
1758
  identifier(name) {
1759
+ const inlined = this.resolveModuleStringConst(name);
1760
+ if (inlined !== null)
1761
+ return inlined;
1751
1762
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
1752
1763
  if (currentLoopParam && name === currentLoopParam)
1753
1764
  return ".";
@@ -1769,6 +1780,48 @@ ${goFields.join(`
1769
1780
  const prefix = this.loopParamStack.length > 0 ? "$." : ".";
1770
1781
  return `${prefix}${this.capitalizeFieldName(name)}`;
1771
1782
  }
1783
+ collectModuleStringConsts(constants) {
1784
+ const map = new Map;
1785
+ for (const c of constants ?? []) {
1786
+ if (!c.isModule)
1787
+ continue;
1788
+ if (c.value === undefined)
1789
+ continue;
1790
+ const literal = this.parsePureStringLiteral(c.value);
1791
+ if (literal !== null)
1792
+ map.set(c.name, literal);
1793
+ }
1794
+ return map;
1795
+ }
1796
+ parsePureStringLiteral(source) {
1797
+ const sf = ts.createSourceFile("__const.ts", `const __x = (${source});`, ts.ScriptTarget.Latest, false);
1798
+ const stmt = sf.statements[0];
1799
+ if (!stmt || !ts.isVariableStatement(stmt))
1800
+ return null;
1801
+ const decl = stmt.declarationList.declarations[0];
1802
+ let init = decl?.initializer;
1803
+ while (init && ts.isParenthesizedExpression(init))
1804
+ init = init.expression;
1805
+ if (!init)
1806
+ return null;
1807
+ if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
1808
+ return init.text;
1809
+ }
1810
+ return null;
1811
+ }
1812
+ resolveModuleStringConst(name) {
1813
+ if (this.loopParamStack.length > 0 && this.loopParamStack[this.loopParamStack.length - 1] === name) {
1814
+ return null;
1815
+ }
1816
+ if (this.loopVarRefCount.has(name))
1817
+ return null;
1818
+ if (this.isOuterLoopParam(name))
1819
+ return null;
1820
+ const value = this.moduleStringConsts.get(name);
1821
+ if (value === undefined)
1822
+ return null;
1823
+ return `"${this.escapeGoString(value)}"`;
1824
+ }
1772
1825
  literal(value, literalType) {
1773
1826
  if (literalType === "string")
1774
1827
  return `"${value}"`;
@@ -2609,6 +2662,9 @@ ${goFields.join(`
2609
2662
  switch (expr.kind) {
2610
2663
  case "identifier":
2611
2664
  {
2665
+ const inlined = this.resolveModuleStringConst(expr.name);
2666
+ if (inlined !== null)
2667
+ return plain(inlined);
2612
2668
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
2613
2669
  if (currentLoopParam && expr.name === currentLoopParam) {
2614
2670
  return plain(".");
@@ -2631,6 +2687,20 @@ ${goFields.join(`
2631
2687
  if (expr.callee.kind === "identifier" && expr.args.length === 0) {
2632
2688
  return plain(this.rootFieldRef(expr.callee.name));
2633
2689
  }
2690
+ if (expr.callee.kind === "identifier" && (identifierPath(expr.callee) ?? expr.callee.name) === "isValidElement" && expr.args.length === 1) {
2691
+ return this.renderConditionExpr(expr.args[0]);
2692
+ }
2693
+ if (expr.callee.kind === "identifier" && !this.templatePrimitives[identifierPath(expr.callee) ?? ""]) {
2694
+ const path = identifierPath(expr.callee) ?? expr.callee.name;
2695
+ this.errors.push({
2696
+ code: "BF102",
2697
+ severity: "error",
2698
+ message: `Predicate '${path}(...)' cannot be evaluated in a Go template. ` + `A server-side template cannot call user-defined JavaScript predicates.`,
2699
+ loc: this.makeLoc(),
2700
+ suggestion: { message: GO_REMEDIATION_OPTIONS }
2701
+ });
2702
+ return plain("false");
2703
+ }
2634
2704
  return plain(this.renderParsedExpr(expr));
2635
2705
  }
2636
2706
  case "member": {
@@ -2842,6 +2912,9 @@ ${goFields.join(`
2842
2912
  if (comp.name === "Portal") {
2843
2913
  return this.renderPortalComponent(comp);
2844
2914
  }
2915
+ if (comp.dynamicTag) {
2916
+ return this.renderChildren(comp.children);
2917
+ }
2845
2918
  let templateCall;
2846
2919
  if (this.inLoop) {
2847
2920
  templateCall = `{{template "${comp.name}" .}}`;
@@ -2985,7 +3058,7 @@ ${children}`;
2985
3058
  continue;
2986
3059
  const branches = caseEntries.map(([k, v], i) => {
2987
3060
  const head = i === 0 ? "{{if" : "{{else if";
2988
- return `${head} eq ${keyExpr} ${JSON.stringify(k)}}}${v}`;
3061
+ return `${head} eq ${keyExpr} ${JSON.stringify(k)}}}${this.escapeAttrText(v)}`;
2989
3062
  });
2990
3063
  output += branches.join("") + "{{end}}";
2991
3064
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/go-template",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Go html/template adapter for BarefootJS - generates Go template files from IR",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -54,6 +54,6 @@
54
54
  },
55
55
  "devDependencies": {
56
56
  "@barefootjs/adapter-tests": "0.1.0",
57
- "@barefootjs/jsx": "0.6.1"
57
+ "@barefootjs/jsx": "0.7.0"
58
58
  }
59
59
  }
@@ -76,16 +76,17 @@ runAdapterConformanceTests({
76
76
  // option fixed on the Hono side. Separate follow-up.
77
77
  'toggle-shared',
78
78
  'props-reactivity-comparison',
79
- // #1467 Phase 2a: first `site/ui` source-root fixture. Button
80
- // compiles cleanly on the Go adapter (no diagnostic), but its
81
- // variant/size class composition (`Record<…>[key]` indexed object
82
- // literals) and `applyRestAttrs` spread haven't been validated for
83
- // byte-parity against the Hono reference under Go's template
84
- // semantics. Cross-adapter parity for the `site/ui` corpus is
85
- // explicitly Phase 3 ("cross-adapter parity (Mojo/Go templates)"),
86
- // so Button participates only in Hono SSR conformance + the
87
- // fixture-hydrate runtime layer for now.
88
- 'button',
79
+ // #1467 Phase 2b: basic interactive `site/ui` primitives. Cross-adapter
80
+ // parity for the `site/ui` corpus is Phase 3, so these participate only
81
+ // in Hono SSR conformance + the fixture-hydrate runtime layer for now.
82
+ // (`label` and `kbd` are static helpers; `toggle` / `switch` /
83
+ // `checkbox` carry uncontrolled state; `input` / `textarea` are
84
+ // pass-through native controls.)
85
+ 'toggle',
86
+ 'switch',
87
+ 'checkbox',
88
+ 'textarea',
89
+ 'kbd',
89
90
  ],
90
91
  // Per-fixture build-time contracts for shapes the Go template
91
92
  // adapter intentionally refuses to lower. Lives here (not on the
@@ -298,6 +299,28 @@ export function Counter(props: { initial?: number }) {
298
299
  expect(result.types).toContain('Count int')
299
300
  })
300
301
 
302
+ test('module pure-string const referenced in className inlines the literal (#1467 Phase 2b)', () => {
303
+ // A module-scope `const X = 'literal'` used inside a className
304
+ // template literal must inline its value, NOT emit `{{.X}}` against a
305
+ // Props field that never exists (Go fails `can't evaluate field X`).
306
+ // Hono inlines it at runtime; this restores byte-parity.
307
+ const source = `
308
+ "use client"
309
+ const labelClasses = 'flex items-center group-data-[disabled=true]:opacity-50'
310
+ export function Label({ className = '' }: { className?: string }) {
311
+ return <label className={\`\${labelClasses} \${className}\`} />
312
+ }
313
+ `
314
+ const { template, types } = compileAndGenerate(source)
315
+ // The literal is inlined as a Go string literal, escaped tokens intact.
316
+ expect(template).toContain(
317
+ '{{"flex items-center group-data-[disabled=true]:opacity-50"}}',
318
+ )
319
+ // No struct-field reference to the const, and no Props field for it.
320
+ expect(template).not.toContain('.LabelClasses')
321
+ expect(types).not.toContain('LabelClasses')
322
+ })
323
+
301
324
  test('dynamic loop with child component → NewXxxProps carries a populate-this-slice doc comment (#1442 echo TodoApp repro)', () => {
302
325
  // Regression: a `todos().map(t => <TodoItem todo={t} />)` loop with a
303
326
  // dynamic array (signal getter, not a static prop) declares
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Pins the Go template lowering of the `Slot` component's dynamic tag.
3
+ *
4
+ * `Slot` does `const Tag = children.tag; return <Tag …>{…}</Tag>` inside
5
+ * an `if (isValidElement(children)) { … }` block. A naive lowering would:
6
+ * 1. emit `{{template "Tag" .TagSlot0}}` — a template that can never be
7
+ * registered. Go's html/template escape-walks ALL registered
8
+ * templates (even dead branches), so the whole render fails with
9
+ * `no such template "Tag"`.
10
+ * 2. lower `isValidElement(children)` to a bogus `.IsValidElement`
11
+ * struct-field access (a user type-guard predicate Go can't evaluate).
12
+ *
13
+ * The `dynamicTag` IR flag (jsx-to-ir) plus the call-condition fallback
14
+ * (go-template-adapter) defuse both. This test asserts the *emitted Go
15
+ * template string* carries neither pathology, complementing the full
16
+ * conformance render in `go-template-adapter.test.ts`.
17
+ */
18
+
19
+ import { describe, test, expect } from 'bun:test'
20
+ import { GoTemplateAdapter } from '../adapter/go-template-adapter'
21
+ import { compileJSX } from '@barefootjs/jsx'
22
+
23
+ // The committed SSR-precompiled Slot module the conformance harness feeds
24
+ // to Go (`children.tag` dynamic tag inside an isValidElement guard).
25
+ const SLOT_SSR = `/** @jsxImportSource hono/jsx */
26
+ interface SlotProps { children?: unknown; className?: string; [key: string]: unknown }
27
+ function isValidElement(element: unknown): element is { tag: unknown; props: Record<string, unknown> } {
28
+ return !!(element && typeof element === 'object' && 'tag' in element && 'props' in element)
29
+ }
30
+ export function Slot({ children, className, ...props }: SlotProps) {
31
+ if (children && isValidElement(children)) {
32
+ const Tag = children.tag as any
33
+ const childProps = children.props
34
+ const childClass = (childProps.className as string) || ''
35
+ const childChildren = childProps.children
36
+ const mergedClass = [className, childClass].filter(Boolean).join(' ')
37
+ return <Tag {...childProps} {...props} className={mergedClass || undefined}>{childChildren}</Tag>
38
+ }
39
+ return <>{children}</>
40
+ }
41
+ `
42
+
43
+ describe('Slot dynamic-tag Go lowering', () => {
44
+ test('emitted Go template has no `{{template "Tag"` call and no `.IsValidElement` field', () => {
45
+ const adapter = new GoTemplateAdapter()
46
+ const result = compileJSX(SLOT_SSR, 'slot.tsx', { adapter, componentName: 'Slot' })
47
+ expect(result.errors.filter(e => e.severity === 'error')).toEqual([])
48
+ const template = result.files.find(f => f.type === 'markedTemplate')!
49
+ expect(template).toBeDefined()
50
+ expect(template.content).not.toContain('{{template "Tag"')
51
+ expect(template.content).not.toContain('.IsValidElement')
52
+ })
53
+
54
+ test('`isValidElement(children)` lowers to a real truthiness check — no diagnostic, no forced literal', () => {
55
+ const adapter = new GoTemplateAdapter()
56
+ const result = compileJSX(SLOT_SSR, 'slot.tsx', { adapter, componentName: 'Slot' })
57
+ // The guard evaluates faithfully on Go (element ⟺ has children to render),
58
+ // so there is neither an error nor an ignorable warning, and the condition
59
+ // is a real `.Children` truthiness check rather than a fudged literal.
60
+ expect(result.errors).toEqual([])
61
+ const template = result.files.find(f => f.type === 'markedTemplate')!
62
+ expect(template.content).toContain('.Children')
63
+ })
64
+
65
+ test('a non-isValidElement user predicate (e.g. `isAdmin`) is a hard BF102 error, not a silent literal', () => {
66
+ const SRC = `/** @jsxImportSource hono/jsx */
67
+ declare function isAdmin(u: unknown): boolean
68
+ export function Gate({ user }: { user?: unknown }) {
69
+ return <div>{isAdmin(user) ? <span>secret</span> : null}</div>
70
+ }
71
+ `
72
+ const adapter = new GoTemplateAdapter()
73
+ const result = compileJSX(SRC, 'gate.tsx', { adapter, componentName: 'Gate' })
74
+ const errors = result.errors.filter(e => e.severity === 'error')
75
+ expect(
76
+ errors.some(e => e.code === 'BF102' && /cannot be evaluated/i.test(e.message))
77
+ ).toBe(true)
78
+ })
79
+ })
@@ -34,6 +34,7 @@ import type {
34
34
  IRIfStatement,
35
35
  IRProvider,
36
36
  IRAsync,
37
+ IRMetadata,
37
38
  TemplatePrimitiveRegistry,
38
39
  } from '@barefootjs/jsx'
39
40
  import {
@@ -418,6 +419,21 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
418
419
  * `template.HTML(...)`; toggles the `"html/template"` import. */
419
420
  private usesHtmlTemplate: boolean = false
420
421
 
422
+ /**
423
+ * Module-scope pure string-literal constants (`const X = 'literal'` at
424
+ * file top-level), keyed by name → resolved literal value. Populated at
425
+ * `generate()` entry from `ir.metadata.localConstants`. When an identifier
426
+ * in an expression resolves to one of these, the adapter inlines the
427
+ * literal value instead of emitting a struct-field reference
428
+ * (`{{.X}}`) — the field never exists on the Props struct, so without
429
+ * inlining Go's template engine fails with `can't evaluate field X`.
430
+ * Hono inlines it for free (it evaluates real JS); this restores parity.
431
+ * Only module-scope, pure string literals qualify — function-scope
432
+ * locals legitimately become template vars/props, and `Record<T,string>`
433
+ * indexed lookups / memos / signals are deliberately excluded.
434
+ */
435
+ private moduleStringConsts: Map<string, string> = new Map()
436
+
421
437
  constructor(options: GoTemplateAdapterOptions = {}) {
422
438
  super()
423
439
  this.options = {
@@ -438,6 +454,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
438
454
  this.templateVarCounter = 0
439
455
  this.propsObjectName = ir.metadata.propsObjectName
440
456
  this.restPropsName = ir.metadata.restPropsName ?? null
457
+ this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants)
441
458
 
442
459
  // Surface loop-body usages of components imported from sibling
443
460
  // .tsx files. The adapter emits `{{template "X" .}}` for these,
@@ -1624,6 +1641,16 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
1624
1641
  ): void {
1625
1642
  if (node.type === 'component') {
1626
1643
  const comp = node as IRComponent
1644
+ // Dynamic-tag locals (`const Tag = children.tag`) have no registrable
1645
+ // template, so they get no `.<Name>SlotN` struct field. Recurse into
1646
+ // their children (which lower as a passthrough) so any real static
1647
+ // child components nested inside still get their slot fields.
1648
+ if (comp.dynamicTag) {
1649
+ for (const child of comp.children) {
1650
+ this.collectStaticChildInstancesRecursive(child, result, inLoop)
1651
+ }
1652
+ return
1653
+ }
1627
1654
  // Skip Portal components (handled separately via PortalCollector)
1628
1655
  // Skip components inside loops (handled by nestedComponents)
1629
1656
  if (comp.name !== 'Portal' && !inLoop && comp.slotId) {
@@ -2891,6 +2918,11 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
2891
2918
  // ===========================================================================
2892
2919
 
2893
2920
  identifier(name: string): string {
2921
+ // Module pure-string const (e.g. `const baseClasses = '...'` used in a
2922
+ // className template literal): inline the literal value rather than
2923
+ // emit `{{.BaseClasses}}` against a Props field that never exists.
2924
+ const inlined = this.resolveModuleStringConst(name)
2925
+ if (inlined !== null) return inlined
2894
2926
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1]
2895
2927
  if (currentLoopParam && name === currentLoopParam) return '.'
2896
2928
  // An *outer* loop's value variable (we're in a nested loop) is in scope as
@@ -2927,6 +2959,72 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
2927
2959
  return `${prefix}${this.capitalizeFieldName(name)}`
2928
2960
  }
2929
2961
 
2962
+ /**
2963
+ * Build the module pure-string-const map from the IR's localConstants.
2964
+ * A const qualifies only when it is module-scope (`isModule`) and its
2965
+ * initializer parses to a single string literal (`ts.StringLiteral` or
2966
+ * `ts.NoSubstitutionTemplateLiteral` — a backtick string with no `${}`).
2967
+ * Template literals *with* interpolations, numeric/object initializers,
2968
+ * `Record<T,string>` maps, memos, and signals are all excluded: only a
2969
+ * pure compile-time string can be safely inlined byte-for-byte.
2970
+ */
2971
+ private collectModuleStringConsts(constants: IRMetadata['localConstants']): Map<string, string> {
2972
+ const map = new Map<string, string>()
2973
+ for (const c of constants ?? []) {
2974
+ if (!c.isModule) continue
2975
+ if (c.value === undefined) continue
2976
+ const literal = this.parsePureStringLiteral(c.value)
2977
+ if (literal !== null) map.set(c.name, literal)
2978
+ }
2979
+ return map
2980
+ }
2981
+
2982
+ /**
2983
+ * Parse a const initializer's source text. Returns the unescaped string
2984
+ * value when the whole initializer is a single string literal (or a
2985
+ * no-substitution template literal), else `null`. Uses the TS parser so
2986
+ * escapes/quotes are resolved exactly as JS would, matching the value
2987
+ * the Hono reference inlines at runtime.
2988
+ */
2989
+ private parsePureStringLiteral(source: string): string | null {
2990
+ const sf = ts.createSourceFile(
2991
+ '__const.ts',
2992
+ `const __x = (${source});`,
2993
+ ts.ScriptTarget.Latest,
2994
+ /*setParentNodes*/ false,
2995
+ )
2996
+ const stmt = sf.statements[0]
2997
+ if (!stmt || !ts.isVariableStatement(stmt)) return null
2998
+ const decl = stmt.declarationList.declarations[0]
2999
+ let init = decl?.initializer
3000
+ while (init && ts.isParenthesizedExpression(init)) init = init.expression
3001
+ if (!init) return null
3002
+ if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
3003
+ return init.text
3004
+ }
3005
+ return null
3006
+ }
3007
+
3008
+ /**
3009
+ * Resolve an identifier to its inlined Go string literal when it names a
3010
+ * module pure-string const. Returns the Go template literal form
3011
+ * (`"<escaped>"`) so callers can drop it straight into a `{{...}}` action,
3012
+ * or `null` when the name is not such a const (the caller then falls back
3013
+ * to its normal field-ref lowering). The value is escaped for a Go
3014
+ * double-quoted string literal — Go's `html/template` then applies the
3015
+ * same contextual auto-escaping it applies to any literal, matching Hono.
3016
+ */
3017
+ private resolveModuleStringConst(name: string): string | null {
3018
+ if (this.loopParamStack.length > 0 && this.loopParamStack[this.loopParamStack.length - 1] === name) {
3019
+ return null
3020
+ }
3021
+ if (this.loopVarRefCount.has(name)) return null
3022
+ if (this.isOuterLoopParam(name)) return null
3023
+ const value = this.moduleStringConsts.get(name)
3024
+ if (value === undefined) return null
3025
+ return `"${this.escapeGoString(value)}"`
3026
+ }
3027
+
2930
3028
  literal(value: string | number | boolean | null, literalType: LiteralType): string {
2931
3029
  if (literalType === 'string') return `"${value}"`
2932
3030
  if (literalType === 'null') return 'nil'
@@ -4313,6 +4411,8 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
4313
4411
  switch (expr.kind) {
4314
4412
  case 'identifier':
4315
4413
  {
4414
+ const inlined = this.resolveModuleStringConst(expr.name)
4415
+ if (inlined !== null) return plain(inlined)
4316
4416
  const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1]
4317
4417
  if (currentLoopParam && expr.name === currentLoopParam) {
4318
4418
  return plain('.')
@@ -4336,6 +4436,46 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
4336
4436
  if (expr.callee.kind === 'identifier' && expr.args.length === 0) {
4337
4437
  return plain(this.rootFieldRef(expr.callee.name))
4338
4438
  }
4439
+ // `isValidElement(x)` — the framework "is this a renderable element?"
4440
+ // predicate. In the Go SSR children model an element is represented by
4441
+ // its already-rendered markup, so this evaluates faithfully as a
4442
+ // truthiness check on the argument (an element is "valid" when there is
4443
+ // something to render). Lowering it as a real, evaluatable expression —
4444
+ // rather than a fabricated `.IsValidElement` field access — is what lets
4445
+ // the `Slot` dynamic-tag guard register and run cleanly on Go.
4446
+ if (
4447
+ expr.callee.kind === 'identifier' &&
4448
+ (identifierPath(expr.callee) ?? expr.callee.name) === 'isValidElement' &&
4449
+ expr.args.length === 1
4450
+ ) {
4451
+ return this.renderConditionExpr(expr.args[0])
4452
+ }
4453
+ // Any other user-defined predicate call with arguments (e.g.
4454
+ // `isAdmin(user)`) has no server-side evaluator and is not a registered
4455
+ // template primitive. There is no honest way to evaluate it at SSR time,
4456
+ // and silently forcing it (to true OR false) is a correctness hazard —
4457
+ // a forced-true could expose auth-gated content, a forced-false could
4458
+ // hide required content, and a warning is too easily ignored. Refuse
4459
+ // with a hard BF102 error so the author must move the predicate to a
4460
+ // supported primitive or defer it with `/* @client */`.
4461
+ if (
4462
+ expr.callee.kind === 'identifier' &&
4463
+ !this.templatePrimitives[identifierPath(expr.callee) ?? '']
4464
+ ) {
4465
+ const path = identifierPath(expr.callee) ?? expr.callee.name
4466
+ this.errors.push({
4467
+ code: 'BF102',
4468
+ severity: 'error',
4469
+ message:
4470
+ `Predicate '${path}(...)' cannot be evaluated in a Go template. ` +
4471
+ `A server-side template cannot call user-defined JavaScript predicates.`,
4472
+ loc: this.makeLoc(),
4473
+ suggestion: { message: GO_REMEDIATION_OPTIONS },
4474
+ })
4475
+ // Go template actions must be self-contained; emit a literal so the
4476
+ // partial still parses while the build fails on the BF102 error.
4477
+ return plain('false')
4478
+ }
4339
4479
  return plain(this.renderParsedExpr(expr))
4340
4480
  }
4341
4481
 
@@ -4634,6 +4774,18 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
4634
4774
  return this.renderPortalComponent(comp)
4635
4775
  }
4636
4776
 
4777
+ // Dynamic-tag local (`const Tag = children.tag`): there is no template
4778
+ // named `<Tag>` to call — emitting `{{template "Tag" .TagSlot0}}` would
4779
+ // reference a template that can never be registered, and Go's
4780
+ // html/template escape-walks ALL registered templates (even dead
4781
+ // branches), so the whole render fails with `no such template "Tag"`.
4782
+ // Lower it to its children passthrough instead, so the dead branch
4783
+ // renders harmlessly and the Slot template registers cleanly. (Real
4784
+ // server-side asChild prop-merge on Go is a separate, deferred concern.)
4785
+ if (comp.dynamicTag) {
4786
+ return this.renderChildren(comp.children)
4787
+ }
4788
+
4637
4789
  // In Go templates, components are rendered using {{template "name" data}}
4638
4790
  let templateCall: string
4639
4791
  if (this.inLoop) {
@@ -4907,7 +5059,13 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
4907
5059
  if (caseEntries.length === 0) continue
4908
5060
  const branches = caseEntries.map(([k, v], i) => {
4909
5061
  const head = i === 0 ? '{{if' : '{{else if'
4910
- return `${head} eq ${keyExpr} ${JSON.stringify(k)}}}${v}`
5062
+ // The case value is a static Record<T,string> literal emitted
5063
+ // straight into attribute-value text, so HTML-escape it the same
5064
+ // way `string` parts are (via substituteJsInterpolations →
5065
+ // escapeAttrText). Without this, UnoCSS tokens like
5066
+ // `has-[>svg]:px-2.5` would leak a raw `>` and diverge from the
5067
+ // Hono reference, which escapes it to `&gt;`.
5068
+ return `${head} eq ${keyExpr} ${JSON.stringify(k)}}}${this.escapeAttrText(v)}`
4911
5069
  })
4912
5070
  output += branches.join('') + '{{end}}'
4913
5071
  }
@@ -180,7 +180,7 @@ export async function renderGoTemplateComponent(options: RenderOptions): Promise
180
180
  const escapedTemplate = template.replace(/`/g, '` + "`" + `')
181
181
 
182
182
  // Build props initialization
183
- const propsInit = buildGoPropsInit(componentName, props)
183
+ const propsInit = buildGoPropsInit(componentName, props, ir)
184
184
 
185
185
  // Honour `__instanceId` from props for the root scope id so
186
186
  // shared-component fixtures (which pin `<ComponentName>_test`) match
@@ -280,10 +280,26 @@ ${propsInit}
280
280
  function buildGoPropsInit(
281
281
  _componentName: string,
282
282
  props?: Record<string, unknown>,
283
+ ir?: ComponentIR,
283
284
  ): string {
284
285
  if (!props) return ''
285
286
 
287
+ // A `{...props}` rest spread on a component means props that are NOT
288
+ // declared as named params don't get their own top-level Input struct
289
+ // field — they flow through the open-ended rest bag (`Props map[string]any`,
290
+ // the `Capitalize(restPropsName)` field). Without routing them there, a
291
+ // fixture passing e.g. `placeholder` to the `Input` component (whose only
292
+ // declared params are `className` / `type`) emits a top-level
293
+ // `Placeholder:` initializer and Go fails with `unknown field Placeholder
294
+ // in struct literal of type InputInput`. (#1467 Phase 2b)
295
+ const declaredParams = new Set((ir?.metadata.propsParams ?? []).map(p => p.name))
296
+ const restPropsName = ir?.metadata.restPropsName ?? null
297
+ const restBagField = restPropsName
298
+ ? restPropsName.charAt(0).toUpperCase() + restPropsName.slice(1)
299
+ : null
300
+
286
301
  const lines: string[] = []
302
+ const restBagEntries: Array<[string, unknown]> = []
287
303
  for (const [key, value] of Object.entries(props)) {
288
304
  // Skip internal hydration markers — `__instanceId` / `__bfScope`
289
305
  // / `__bfChild` are routed by the framework (consumed via the
@@ -291,6 +307,15 @@ function buildGoPropsInit(
291
307
  // appear on the user-facing input struct). Including them produces
292
308
  // `unknown field __instanceId in struct literal of type XxxInput`.
293
309
  if (key.startsWith('__')) continue
310
+ // A prop that isn't a declared named param on a rest-spread component
311
+ // belongs in the rest bag, not a top-level field. A key that literally
312
+ // matches `restPropsName` already carries a pre-formed bag object and
313
+ // maps straight onto the `Capitalize(restPropsName)` field, so it falls
314
+ // through to the normal (object → map literal) emit below.
315
+ if (restBagField && key !== restPropsName && !declaredParams.has(key)) {
316
+ restBagEntries.push([key, value])
317
+ continue
318
+ }
294
319
  // Capitalize first letter for Go field name
295
320
  const goField = key.charAt(0).toUpperCase() + key.slice(1)
296
321
  if (typeof value === 'string') {
@@ -319,6 +344,13 @@ function buildGoPropsInit(
319
344
  lines.push(`\t\t${goField}: ${goMapLiteralFromObject(value as Record<string, unknown>)},`)
320
345
  }
321
346
  }
347
+ // Emit the collected rest-bag entries as the open-ended bag field. Skip
348
+ // when a direct `restPropsName`-keyed prop already populated it above
349
+ // (merging two sources isn't a shape any fixture needs).
350
+ if (restBagField && restBagEntries.length > 0 && !(restPropsName! in props)) {
351
+ const bag = Object.fromEntries(restBagEntries)
352
+ lines.push(`\t\t${restBagField}: ${goMapLiteralFromObject(bag)},`)
353
+ }
322
354
  return lines.join('\n')
323
355
  }
324
356