@barefootjs/go-template 0.15.2 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/adapter/analysis/component-tree.d.ts +23 -0
  2. package/dist/adapter/analysis/component-tree.d.ts.map +1 -0
  3. package/dist/adapter/emit-context.d.ts +53 -0
  4. package/dist/adapter/emit-context.d.ts.map +1 -0
  5. package/dist/adapter/expr/helper-inline.d.ts +31 -0
  6. package/dist/adapter/expr/helper-inline.d.ts.map +1 -0
  7. package/dist/adapter/expr/url-builder.d.ts +23 -0
  8. package/dist/adapter/expr/url-builder.d.ts.map +1 -0
  9. package/dist/adapter/go-template-adapter.d.ts +291 -1070
  10. package/dist/adapter/go-template-adapter.d.ts.map +1 -1
  11. package/dist/adapter/index.js +2859 -2618
  12. package/dist/adapter/lib/compile-state.d.ts +116 -0
  13. package/dist/adapter/lib/compile-state.d.ts.map +1 -0
  14. package/dist/adapter/lib/constants.d.ts +11 -0
  15. package/dist/adapter/lib/constants.d.ts.map +1 -0
  16. package/dist/adapter/lib/go-emit.d.ts +117 -0
  17. package/dist/adapter/lib/go-emit.d.ts.map +1 -0
  18. package/dist/adapter/lib/go-naming.d.ts +39 -0
  19. package/dist/adapter/lib/go-naming.d.ts.map +1 -0
  20. package/dist/adapter/lib/ir-scope.d.ts +13 -0
  21. package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
  22. package/dist/adapter/lib/types.d.ts +165 -0
  23. package/dist/adapter/lib/types.d.ts.map +1 -0
  24. package/dist/adapter/memo/ctor-lowering.d.ts +39 -0
  25. package/dist/adapter/memo/ctor-lowering.d.ts.map +1 -0
  26. package/dist/adapter/memo/memo-compute.d.ts +124 -0
  27. package/dist/adapter/memo/memo-compute.d.ts.map +1 -0
  28. package/dist/adapter/memo/memo-type.d.ts +38 -0
  29. package/dist/adapter/memo/memo-type.d.ts.map +1 -0
  30. package/dist/adapter/memo/memo-value.d.ts +64 -0
  31. package/dist/adapter/memo/memo-value.d.ts.map +1 -0
  32. package/dist/adapter/memo/template-interp.d.ts +43 -0
  33. package/dist/adapter/memo/template-interp.d.ts.map +1 -0
  34. package/dist/adapter/props/prop-types.d.ts +31 -0
  35. package/dist/adapter/props/prop-types.d.ts.map +1 -0
  36. package/dist/adapter/spread/spread-codegen.d.ts +40 -0
  37. package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
  38. package/dist/adapter/type/type-codegen.d.ts +25 -0
  39. package/dist/adapter/type/type-codegen.d.ts.map +1 -0
  40. package/dist/adapter/value/parsed-literal-to-go.d.ts +17 -0
  41. package/dist/adapter/value/parsed-literal-to-go.d.ts.map +1 -0
  42. package/dist/adapter/value/value-lowering.d.ts +46 -0
  43. package/dist/adapter/value/value-lowering.d.ts.map +1 -0
  44. package/dist/build.js +2857 -2616
  45. package/dist/index.js +2859 -2618
  46. package/dist/test-render.d.ts.map +1 -1
  47. package/package.json +3 -3
  48. package/src/__tests__/derived-state-memo.test.ts +155 -84
  49. package/src/__tests__/go-template-adapter.test.ts +211 -60
  50. package/src/__tests__/lowering-plugin.test.ts +109 -0
  51. package/src/__tests__/query-href.test.ts +179 -0
  52. package/src/adapter/analysis/component-tree.ts +174 -0
  53. package/src/adapter/emit-context.ts +59 -0
  54. package/src/adapter/expr/helper-inline.ts +274 -0
  55. package/src/adapter/expr/url-builder.ts +123 -0
  56. package/src/adapter/go-template-adapter.ts +2100 -5316
  57. package/src/adapter/lib/compile-state.ts +150 -0
  58. package/src/adapter/lib/constants.ts +24 -0
  59. package/src/adapter/lib/go-emit.ts +289 -0
  60. package/src/adapter/lib/go-naming.ts +84 -0
  61. package/src/adapter/lib/ir-scope.ts +31 -0
  62. package/src/adapter/lib/types.ts +182 -0
  63. package/src/adapter/memo/ctor-lowering.ts +267 -0
  64. package/src/adapter/memo/memo-compute.ts +451 -0
  65. package/src/adapter/memo/memo-type.ts +74 -0
  66. package/src/adapter/memo/memo-value.ts +197 -0
  67. package/src/adapter/memo/template-interp.ts +246 -0
  68. package/src/adapter/props/prop-types.ts +84 -0
  69. package/src/adapter/spread/spread-codegen.ts +458 -0
  70. package/src/adapter/type/type-codegen.ts +95 -0
  71. package/src/adapter/value/parsed-literal-to-go.ts +94 -0
  72. package/src/adapter/value/value-lowering.ts +162 -0
  73. package/src/test-render.ts +2 -14
@@ -1,32 +1,8 @@
1
- /**
2
- * BarefootJS Go html/template Adapter
3
- *
4
- * Generates Go html/template files from BarefootJS IR.
5
- */
6
- import type { ComponentIR, IRNode, IRElement, IRText, IRExpression, IRConditional, IRLoop, IRComponent, IRFragment, IRSlot, ParsedExpr, SortComparator, ReduceOp, FlatDepth, FlatMapOp, TemplatePart, IRIfStatement, IRProvider, IRAsync, TemplatePrimitiveRegistry } from '@barefootjs/jsx';
7
- import { BaseAdapter, type AdapterOutput, type AdapterGenerateOptions, type ParsedExprEmitter, type HigherOrderMethod, type ArrayMethod, type LiteralType, type IRNodeEmitter, type EmitIRNode } from '@barefootjs/jsx';
8
- /**
9
- * Go-template adapter's IRNode render context. Only `isRootOfClientComponent`
10
- * is consumed today (forwarded into `renderComponent` / `renderIfStatement`);
11
- * the type stays open so future render-position flags can be added without
12
- * widening the `IRNodeEmitter` contract.
13
- */
14
- type GoRenderCtx = {
15
- isRootOfClientComponent?: boolean;
16
- };
17
- export interface GoTemplateAdapterOptions {
18
- /** Go package name for generated types (default: 'components') */
19
- packageName?: string;
20
- /**
21
- * Base path for client JS files (e.g., '/static/client/').
22
- * Used to generate script registration paths.
23
- */
24
- clientJsBasePath?: string;
25
- /**
26
- * Path to barefoot.js runtime (e.g., '/static/client/barefoot.js').
27
- */
28
- barefootJsPath?: string;
29
- }
1
+ /** BarefootJS adapter: BarefootJS IR → Go `html/template` files. */
2
+ import type { ComponentIR, IRNode, IRElement, IRText, IRExpression, IRConditional, IRLoop, IRComponent, IRFragment, IRSlot, CompilerError, ParsedExpr, ObjectLiteralProperty, FlatDepth, TemplatePart, IRIfStatement, IRProvider, IRAsync, TemplatePrimitiveRegistry } from '@barefootjs/jsx';
3
+ import { BaseAdapter, type AdapterOutput, type AdapterGenerateOptions, type ParsedExprEmitter, type ArrayMethod, type LiteralType, type IRNodeEmitter, type EmitIRNode } from '@barefootjs/jsx';
4
+ import type { GoRenderCtx, GoTemplateAdapterOptions } from "./lib/types.ts";
5
+ export type { GoTemplateAdapterOptions } from "./lib/types.ts";
30
6
  export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter, IRNodeEmitter<GoRenderCtx> {
31
7
  name: string;
32
8
  extension: string;
@@ -35,247 +11,87 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
35
11
  private filterExprDepth;
36
12
  private filterExprUnsupported;
37
13
  /**
38
- * Identifier-path callees the Go runtime can render in template
39
- * scope (#1188). The relocate pass consults this map to mark
40
- * matching calls as template-safe so the surrounding expression
41
- * stays inlinable; the SSR template emitter (`renderParsedExpr`'s
42
- * `call` branch) uses the same map to substitute the JS call with
43
- * the registered Go template form.
44
- *
45
- * Keys are the textual callee path as written in the JSX
46
- * expression. Values are emit functions that receive the already-
47
- * Go-rendered argument expressions (e.g. `.Config`, `_p.Score`)
48
- * and return the substituted Go template body — without the
49
- * surrounding `{{ }}` action delimiters, so callers can wrap the
50
- * result in `{{...}}` or compose into larger expressions like
51
- * `{{if eq (bf_json .X) "..."}}`.
52
- *
53
- * V1 scope (#1187 R1): identifier-path callees only. Method calls
54
- * on values (`(arr).join(",")`) require analyzer-resolved receiver
55
- * type and are explicitly out of scope — users fall back to
56
- * `/* @client *\/` for those.
14
+ * Identifier-path callees the Go runtime can render in template scope, keyed
15
+ * by the textual callee path as written in JSX. Each value emits the
16
+ * substituted Go body from already-Go-rendered args, WITHOUT the `{{ }}`
17
+ * delimiters so callers compose it (`{{if eq (bf_json .X) "..."}}`).
57
18
  *
58
- * Public because the `TemplateAdapter` interface contract requires
59
- * the relocate pass to read this for boolean acceptance. The arity
60
- * map below is implementation detail (private) the asymmetry is
61
- * deliberate.
19
+ * Identifier-path callees only; method calls on values (`(arr).join(",")`)
20
+ * need an analyzer-resolved receiver type and are out of scope. Public
21
+ * because the relocate pass reads it for boolean acceptance.
62
22
  */
63
23
  templatePrimitives: TemplatePrimitiveRegistry;
64
24
  /**
65
- * Expected arg count per primitive. Consulted before invoking the
66
- * registered emit fn so a 0-arg `JSON.stringify()` or 2-arg
67
- * `JSON.stringify(x, replacer)` doesn't silently produce invalid
68
- * Go template syntax (the V1 emit fns blindly read `args[0]`).
69
- *
70
- * Derived from `GO_TEMPLATE_PRIMITIVES` so it can't drift from
71
- * `templatePrimitives` — a wrong-arity call falls back to the
72
- * standard BF101 unsupported-call diagnostic.
25
+ * Expected arg count per primitive, so a wrong-arity call (`JSON.stringify()`,
26
+ * `JSON.stringify(x, replacer)`) falls back to BF101 instead of emitting
27
+ * invalid Go from the emit fn's blind `args[0]` read. Derived from
28
+ * `GO_TEMPLATE_PRIMITIVES` so it can't drift from `templatePrimitives`.
73
29
  */
74
30
  private readonly templatePrimitiveArities;
75
- private componentName;
76
31
  private options;
77
- private inLoop;
32
+ /** Per-compile mutable state, reset at `generate()` / `generateTypes()` entry. See `CompileState` for the field-by-field docs. */
33
+ private readonly state;
78
34
  /**
79
- * Companion `{{define "<Component>__children_<slot>"}}` blocks queued
80
- * while rendering the template body (#1896): JSX children passed to an
81
- * imported child component that contain template actions (nested
82
- * components, dynamic text) can't be baked to a static `Children`
83
- * string — they render through a per-call-site define executed via
84
- * `bf_tmpl` with the PARENT's data, and the result is injected into
85
- * the child props with `bf_with_children`. Flushed after the main
86
- * define in `generate()`.
35
+ * The `GoEmitContext` handed to extracted emit modules — the seam that keeps
36
+ * `state` / `convert*` off the public adapter type. `state` is captured by
37
+ * reference (reset in place, never reassigned), so this single `emitCtx` stays
38
+ * valid across `generate()` calls.
87
39
  */
88
- private pendingChildrenDefines;
40
+ private readonly emitCtx;
41
+ /** Diagnostics from the current compile (backed by `CompileState`); `generate()` also merges these into `ir.errors`. */
42
+ get errors(): CompilerError[];
43
+ private inLoop;
89
44
  private loopParamStack;
45
+ /**
46
+ * Per-loop: true when the body renders the bare range value (scalar-item
47
+ * inline-literal loop), so the `bf_tmpl` companion is fed `.BfLoopItem` (the
48
+ * wrapper's synthetic scalar field) instead of `.`. Innermost last.
49
+ */
50
+ private loopScalarItemStack;
90
51
  private loopVarRefCount;
91
52
  /** Stack of destructure-param binding maps (binding name → Go accessor on the
92
- * range var, e.g. `id` → `$__bf_item0.Id`, `rest` → `$__bf_item0`). Innermost last.
93
- * Lets `.map(({ id, ...rest }) => …)` resolve `id` / `rest.flag` instead of
94
- * refusing with BF104. (#1310) */
53
+ * range var, e.g. `id` → `$__bf_item0.Id`, `rest` → `$__bf_item0`). Innermost
54
+ * last. Lets `.map(({ id, ...rest }) => …)` resolve instead of BF104. */
95
55
  private loopBindingStack;
96
- private errors;
97
- /** The current IR's memos, stashed like `localConstants` so nested memo
98
- * resolution (a ternary memo whose condition is another memo, #1896)
99
- * can recurse without threading the list through every signature. */
100
- private currentMemos;
101
- private propsObjectName;
102
- /**
103
- * Component-scoped rest binding identifier (`function({ a, ...rest }: P)`
104
- * → `'rest'`). Stashed at `generate()` entry so per-attribute
105
- * emitter callbacks can classify a spread expression against it
106
- * without threading the IR through each recursion (#1407
107
- * follow-up).
108
- */
109
- private restPropsName;
110
- private templateVarCounter;
111
- /** Local type names resolved from typeDefinitions (populated during generateTypes) */
112
- private localTypeNames;
113
- /** Local type aliases mapping type name to base type (e.g., Filter → 'string') */
114
- private localTypeAliases;
115
56
  /**
116
- * Per-struct field map (type name source TS key → Go field name), populated
117
- * during generateTypes. The object-literal baker consults this so a baked
118
- * struct literal only names fields the generated struct actually declares.
119
- */
120
- private localStructFields;
121
- /**
122
- * Synthesised array types for untyped object-array signals (signal getter →
123
- * `[]SynthStruct` TypeInfo), populated during generateTypes (#1680). An
124
- * untyped `createSignal([{ id: "a" }])` has no element type to bake against;
125
- * we infer a struct from the literal's shape so the field can be typed and
126
- * the items baked. Consulted by both the signal field-type emitter and the
127
- * initial-value baker.
128
- */
129
- private synthStructTypes;
130
- /** Full type definitions from the current IR, stashed for loop-datum field resolution (#1897). */
131
- private currentTypeDefinitions;
132
- /** Set during type generation when any emit references
133
- * `template.HTML(...)`; toggles the `"html/template"` import. */
134
- private usesHtmlTemplate;
135
- /** Component root scope element(s) — each carries `data-key` for a keyed loop
136
- * item. A plain element root is one node; an `if-statement` (early-return)
137
- * root contributes the top element of every branch. (#1297) */
138
- private rootScopeNodes;
139
- /** Set during type generation when any emit references
140
- * `fmt.Sprint(...)` — e.g. a `Record<staticKeys, scalar>[propKey]`
141
- * indexed-map spread value (#checkbox); toggles the `"fmt"` import. */
142
- private usesFmt;
143
- /**
144
- * Cross-component child shapes (#checkbox), keyed by child component name.
145
- * Populated out-of-band via `registerChildComponentShape` before the parent
146
- * component's `generateTypes` runs, so the static-child-init codegen can
147
- * route an attribute that is NOT a declared param of the child
148
- * (`<CheckIcon data-slot=.../>`) into the child's rest bag
149
- * (`Capitalize(restPropsName)` map field) instead of emitting an invalid
150
- * hyphenated top-level field (`Data-slot:`). A child with no rest bag and
151
- * an unknown attr is left as-is so the existing field path / Go compile
152
- * error still surfaces.
57
+ * Cross-component child shapes, keyed by child component name. Populated via
58
+ * `registerChildComponentShape` before the parent's `generateTypes`, so the
59
+ * static-child-init codegen can route an attribute that is NOT a declared
60
+ * child param (`<CheckIcon data-slot=.../>`) into the child's rest bag instead
61
+ * of an invalid hyphenated field (`Data-slot:`). An unknown attr on a child
62
+ * with no rest bag is left as-is (existing field path / Go error surfaces).
153
63
  */
154
64
  private childComponentShapes;
155
- /**
156
- * Module-scope pure string-literal constants (`const X = 'literal'` at
157
- * file top-level), keyed by name → resolved literal value. Populated at
158
- * `generate()` entry from `ir.metadata.localConstants`. When an identifier
159
- * in an expression resolves to one of these, the adapter inlines the
160
- * literal value instead of emitting a struct-field reference
161
- * (`{{.X}}`) — the field never exists on the Props struct, so without
162
- * inlining Go's template engine fails with `can't evaluate field X`.
163
- * Hono inlines it for free (it evaluates real JS); this restores parity.
164
- * Only module-scope, pure string literals qualify — function-scope
165
- * locals legitimately become template vars/props, and `Record<T,string>`
166
- * indexed lookups / memos / signals are deliberately excluded.
167
- */
168
- private moduleStringConsts;
169
- /**
170
- * All local constants (module + function-scope) from the IR, retained for
171
- * the lifetime of `generate()` so the memo-computation path can resolve
172
- * `Record`-index lookups (`variantClasses[variant]`) without re-threading the
173
- * full `ir` through every helper. Reset at `generate()` entry.
174
- */
175
- private localConstants;
176
- /**
177
- * `useContext(...)` consumers in the component being generated. Each becomes
178
- * a struct field defaulted to the `createContext` default, which an enclosing
179
- * `<Ctx.Provider value>` overwrites for descendant child slots. Reset at
180
- * `generate()` / `generateTypes()` entry. (#1297)
181
- */
182
- private contextConsumers;
183
- /**
184
- * (#1922) Local binding names the request-scoped `searchParams()` env signal
185
- * is imported under (handles `import { searchParams as sp }`). A zero-arg call
186
- * on one of these names lowers to the canonical `.SearchParams` field
187
- * regardless of the JS alias. Set at `generate()` / `generateTypes()` entry.
188
- */
189
- private searchParamsLocals;
190
- /**
191
- * Names of component-scope arrow-const helpers (`const sortClass = …`),
192
- * eligible for call-site inlining (#1897). Precomputed per component so the
193
- * inliner can skip the AST parse for the common non-helper expression.
194
- */
195
- private localHelperNames;
196
- /** Set when a constructor-context lowering emits a `strings.` call (#1897), so
197
- * `strings` is added to the generated types file's import block. */
198
- private needsStringsImport;
199
- /** Component-scope derived consts referenced by the template during rendering
200
- * (e.g. `root` → `.Root`). `generateTypes` emits a computed field for each
201
- * that's resolvable and non-colliding (#1897). */
202
- private referencedDerivedConsts;
203
- /** Array-memo name → the handler-filled loop slice field its `.map()` feeds
204
- * (e.g. `visible` → `PostListItems`). Lets `<memo>().length` lower to
205
- * `len .<Slice>` instead of `len .<Memo>` (a nil/unset memo field) — the
206
- * slice IS the rendered (filtered) items, so its length is the count (#1897). */
207
- private memoBackedLoopSlice;
208
65
  /** Child component name → the contexts it consumes (cross-component, for provider wiring). */
209
66
  private childContextConsumers;
210
- /**
211
- * Set of prop NAMES whose resolved Go struct-field type is exactly
212
- * `interface{}` — i.e. nillable. Populated at `generate()` entry from
213
- * the SAME per-prop Go-type computation `generatePropsStruct` /
214
- * `generateInputStruct` use (`propTypeOverrides` + `typeInfoToGo`), so
215
- * it can't drift from the actual field types. Used by
216
- * `elementAttrEmitter.emitExpression` to omit a dynamic attribute whose
217
- * value is a bare reference to a nillable prop when that prop is nil
218
- * (Hono-style nullish-attribute omission: an `undefined`-valued
219
- * attribute is dropped rather than rendered as `attr=""`). Concrete
220
- * (`string`/`int`/`bool`) fields are never in this set and always emit
221
- * unconditionally, matching Hono's `value=""` / `data-count="0"`.
222
- */
223
- private nillablePropNames;
224
67
  constructor(options?: GoTemplateAdapterOptions);
225
68
  /**
226
- * Generate template output for a component.
227
- * @param ir - The component IR
228
- * @param options - Generation options
69
+ * Prime the per-compile state both `generate()` and `generateTypes()` derive
70
+ * from the IR. Both must prime identically or the emitted Input/Props structs
71
+ * drift (`generateTypes` runs on a separately round-tripped IR).
72
+ * `propsObjectName` is set first because the inherited-attr scan keys off it.
229
73
  */
74
+ private primeCompileState;
75
+ /** Generate template output for a component. */
230
76
  generate(ir: ComponentIR, options?: AdapterGenerateOptions): AdapterOutput;
231
77
  /**
232
- * Check if a component has client interactivity (needs client JS).
233
- * A component has client interactivity if it has:
234
- * - Signals (reactive state)
235
- * - Effects (side effects)
236
- * - Events on elements
237
- */
238
- private hasClientInteractivity;
239
- /**
240
- * Recursively check if any element in the tree has events.
241
- */
242
- private hasEventsInTree;
243
- /**
244
- * Find all child component names used in the IR tree.
245
- */
246
- private findChildComponentNames;
247
- private collectChildComponentNames;
248
- /**
249
- * Push a `BF103` diagnostic for every component reference inside a
250
- * loop body whose name is imported from a relative-path module
251
- * (i.e. a sibling .tsx file). The Go adapter renders these as
252
- * `{{template "X" .}}` calls, which Go's template engine resolves
253
- * only against templates registered on the same `*template.Template`
254
- * — so a user who factored a list item into `./list-item.tsx` and
255
- * mapped over it gets a working build and a `template: "X" is
256
- * undefined` at request time. Surfacing this at build time matches
257
- * the louder-over-silent contract (#1266).
258
- *
259
- * Scoped to loop bodies because that's the natural Hono-style
260
- * pattern the issue calls out; static (non-loop) usage of imported
261
- * components is left alone so existing static-layout patterns
262
- * keep working without noise.
78
+ * Push BF103 for every loop-body component reference whose name is imported
79
+ * from a relative-path (sibling) module. The Go adapter renders these as
80
+ * `{{template "X" .}}`, which resolves only against templates registered on
81
+ * the same `*template.Template` — otherwise a clean build fails at request
82
+ * time with `template: "X" is undefined`. Static (non-loop) usage is left
83
+ * alone so existing layouts keep working.
263
84
  */
264
85
  private checkImportedLoopChildComponents;
265
86
  /**
266
- * Generate script registration code for the template.
267
- * Scripts are registered at the beginning of the template.
268
- * Uses .Scripts which is available on all Props structs.
269
- * The same ScriptCollector should be shared across parent and child props.
270
- * Wrapped in {{if .Scripts}} to safely handle nil Scripts.
87
+ * Script registration code for the template start. Reads `.Scripts` (on every
88
+ * Props struct) and guards the registrations with `{{if .Scripts}}` for a nil
89
+ * collector.
271
90
  */
272
91
  private generateScriptRegistrations;
273
92
  /**
274
- * Register a child component's shape (#checkbox) so a parent's
275
- * static-child-init codegen can route non-param attributes into the child's
276
- * rest bag rather than emitting an invalid hyphenated top-level field. Call
277
- * once per known child IR (siblings in the same source, auto-inferred
278
- * `../<name>` imports) before generating the parent's types. Idempotent.
93
+ * Register a child component's shape (see `childComponentShapes`). Call once
94
+ * per known child IR before the parent's `generateTypes`. Idempotent.
279
95
  */
280
96
  registerChildComponentShape(ir: ComponentIR): void;
281
97
  /** Go field name for a `useContext` consumer (the capitalized local binding). */
@@ -284,119 +100,55 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
284
100
  private contextConsumerGoType;
285
101
  /** Go literal for a context-consumer's default value (the `createContext` arg). */
286
102
  private contextConsumerGoDefault;
287
- /**
288
- * Context-consumer fields that don't collide with an already-emitted prop /
289
- * signal / memo field. The template reads them as `{{.Field}}` (the local
290
- * `useContext` binding lowered to a root field); the struct must carry them.
291
- */
103
+ /** Context-consumer fields not colliding with an already-emitted prop/signal/memo field (the struct must carry them; template reads `{{.Field}}`). */
292
104
  private nonCollidingContextConsumers;
293
105
  generateTypes(ir: ComponentIR): string | null;
294
- /**
295
- * Convert a TypeScript type definition to a Go type.
296
- * Handles object types → Go structs, and union string literals → string alias.
297
- */
106
+ /** Convert a TS type definition to Go: object types → structs, string-literal unions → a `string` alias. */
298
107
  private typeDefinitionToGo;
299
108
  /**
300
- * Derive a struct's Go fields from the analyzer-provided structured
301
- * properties no string parsing of the definition. This is the single
302
- * source of truth for which fields a generated struct has and each field's Go
303
- * name/type; both the struct emitter ({@link typeDefinitionToGo}) and the
304
- * object-literal baker ({@link tsLiteralToGo}) consume it, so a baked literal
305
- * can never name a field the struct doesn't declare.
306
- *
307
- * A property whose source key isn't a valid Go identifier (`"data-id"`, a
308
- * numeric key, …) can't become a struct field, so it's dropped here — and is
309
- * therefore absent from the baker's field map too, which bails to nil for any
310
- * literal that uses such a key.
109
+ * Single source of truth for a generated struct's Go fields, derived from the
110
+ * analyzer's structured properties (no definition-string parsing). Both the
111
+ * struct emitter and the object-literal baker consume it, so a baked literal
112
+ * can't name a field the struct lacks. A non-Go-identifier source key
113
+ * (`"data-id"`, a numeric key) is dropped here, so the baker bails to nil for
114
+ * any literal using such a key.
311
115
  */
312
116
  private structFieldsFor;
313
117
  /**
314
118
  * Synthesise a Go struct from an untyped object-array signal's inline initial
315
- * value (#1680). Returns the struct name + fields, or `null` when synthesis
316
- * isn't possible so the caller keeps the field `[]interface{}`/`nil`.
317
- *
318
- * Synthesis applies only when:
319
- * - the signal's type is an array with no usable element type (untyped),
320
- * - the initial value is a non-empty array literal of object literals,
321
- * - every element shares the same set of Go-identifier keys, and
322
- * - every value is a scalar literal whose Go type is consistent per key
323
- * (numeric keys widen int→float64 when mixed).
324
- *
325
- * Any deviation (heterogeneous shape, a nested object/array value, a
326
- * non-literal value, a non-identifier key, or a name collision with an
327
- * existing type) returns `null`.
119
+ * value, or `null` (caller keeps `[]interface{}`/`nil`). Requires: untyped
120
+ * array type; a non-empty array literal of object literals; every element
121
+ * sharing the same Go-identifier key set; every value a scalar literal with a
122
+ * per-key-consistent Go type (mixed int/float64 widens to float64). Any
123
+ * deviation, or a name collision with an existing type, returns `null`.
328
124
  */
329
125
  private synthesizeStructFromSignal;
330
126
  /**
331
- * The Go type for a scalar JS literal used as a synthesised struct field
332
- * value, or `null` for anything non-scalar (objects, arrays, identifiers,
333
- * calls, interpolated templates) so the caller bails out of synthesis.
127
+ * Go type for a scalar-literal `ParsedExpr` field value, else `null` (caller
128
+ * bails out of synthesis). string/no-substitution template `string`;
129
+ * numeric (optionally negated) int/float64; boolean `bool`.
334
130
  */
335
- private scalarLiteralGoType;
336
- /** `int` for an integer literal, `float64` when the literal has a fraction
337
- * or exponent. */
131
+ private scalarParsedGoType;
132
+ /** `float64` when the literal has a fraction or exponent, else `int`. */
338
133
  private numericLiteralGoType;
339
- /** Reconcile two inferred Go types for the same key across elements: equal
340
- * types stay; mixed numeric (int/float64) widens to float64; otherwise null
341
- * (incompatible → bail). */
134
+ /** Reconcile two per-key Go types: equal stays; mixed numeric widens to float64; else null. */
342
135
  private mergeScalarGoType;
343
136
  /**
344
- * Convert a raw TypeScript type string to a Go type string.
345
- * Handles primitives (number, string, boolean) and basic arrays.
346
- */
347
- private tsTypeStringToGo;
348
- /**
349
- * Build a map from prop name to a better Go type inferred from signals.
350
- * When a signal is initialized from a prop (e.g., createSignal(props.initial ?? 0)),
351
- * the signal's type annotation may be more specific than the prop's TypeInfo.
352
- */
353
- private buildPropTypeOverrides;
354
- /**
355
- * Resolve a prop param's Go struct-field type using the SAME logic
356
- * `generatePropsStruct` / `generateInputStruct` use for the field
357
- * declaration: a `propTypeOverrides` entry (signal-inferred override)
358
- * wins, otherwise `typeInfoToGo(param.type, param.defaultValue)`.
359
- * Factored out so the nillable-field set (`collectNillablePropNames`)
360
- * can't drift from the actual emitted field types.
361
- */
362
- private resolvePropGoType;
363
- /**
364
- * Build the set of prop NAMES whose resolved Go field type is exactly
365
- * `interface{}` (nillable). Uses the same `propTypeOverrides` +
366
- * `resolvePropGoType` pipeline as the struct generators, so a prop
367
- * that ends up `interface{}` on the Props struct — and only such a
368
- * prop — is treated as nillable for Hono-style attribute omission.
369
- * Concrete (`string`/`int`/`bool`/`[]T`/struct) types are excluded.
370
- */
371
- private collectNillablePropNames;
372
- /**
373
- * Whether the component reads the request-scoped `searchParams()`
374
- * environment signal (router v0.5, #1922). Detected from `searchParamsLocals`
375
- * — the binding names the shared `searchParamsLocalNames` helper found at
376
- * `generate()` / `generateTypes()` entry, covering any local name (including
377
- * an aliased `import { searchParams as sp }`). When non-empty the generated
378
- * structs carry a `SearchParams bf.SearchParams` binding the route handler
379
- * fills per request and the template reads via `.SearchParams.Get "key"`.
380
- *
381
- * Guarded against a name collision with a user prop / signal / memo also
382
- * called `searchParams`: that author owns the `SearchParams` field, so the
383
- * env-signal field is dropped (the reference would resolve to their value).
137
+ * Whether the component reads the request-scoped `searchParams()` env signal
138
+ * (from `searchParamsLocals`, covering any local incl. aliased imports). When
139
+ * true the structs carry a `SearchParams bf.SearchParams` binding the handler
140
+ * fills per request, read as `.SearchParams.Get "key"`. Returns false when a
141
+ * user prop/signal/memo named `searchParams` already owns the field (the
142
+ * binding would resolve to their value).
384
143
  */
385
144
  private usesSearchParams;
386
- /**
387
- * Generate Input struct for a component
388
- */
145
+ /** Generate the Input struct for a component. */
389
146
  private generateInputStruct;
390
- /**
391
- * Generate Props struct for a component
392
- */
393
147
  private generatePropsStruct;
394
148
  /**
395
- * (#1897) Generate a wrapper struct for loop body components with JSX children.
396
- * The wrapper embeds the child component's Props, adds datum fields from the
397
- * loop's item type, and adds static child instance fields for sub-components
398
- * within the loop body children (e.g. the `TableCell` instances inside
399
- * `<TableRow>…</TableRow>`).
149
+ * Wrapper struct for a loop-body component with JSX children: embeds the
150
+ * child's Props, datum fields from the loop's item type, and slot fields for
151
+ * sub-components in the body (`TableCell` inside `<TableRow>…</TableRow>`).
400
152
  */
401
153
  private generateLoopBodyWrapperStruct;
402
154
  /** Extract a memo name from a loop array expression like `sortedData()` → `sortedData`. */
@@ -405,31 +157,37 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
405
157
  private loopBodyWrapperName;
406
158
  /** Resolve a loop item's TypeInfo to Go struct fields for the wrapper. */
407
159
  private resolveLoopDatumFields;
408
- /** Collect static child instances from loop body children for the wrapper struct. */
409
- private collectBodyChildInstances;
410
160
  /**
411
- * Generate NewXxxProps function
161
+ * `'interface{}'` for a loop over an inline primitive-literal array whose body
162
+ * renders the bare item (`[1,2,3,4,5].map(n => …{n}…)`), else null. Such
163
+ * scalar-item loops have no datum fields, so the value is carried on the
164
+ * wrapper's synthetic `BfLoopItem`; object/field loops and non-literal sources
165
+ * keep the datum-field path.
412
166
  */
167
+ private scalarLiteralLoopGoType;
168
+ /** Collect static child instances from loop body children for the wrapper struct. */
169
+ private collectBodyChildInstances;
170
+ /** Generate the NewXxxProps function. */
413
171
  private generateNewPropsFunction;
414
- /**
415
- * Convert field name to JSON tag (camelCase)
416
- */
172
+ private emitStaticChildInstances;
173
+ private emitNewPropsDocComment;
174
+ private emitSpreadBagInits;
175
+ private emitStaticBodyWrappers;
176
+ private emitDynamicBodyWrappers;
177
+ private buildLocalTypeTables;
178
+ private emitLocalTypeStructs;
179
+ private emitSynthStructs;
180
+ private resolveNestedLoopItemTypes;
181
+ private composeFileHeader;
182
+ private emitPropsStructHeader;
183
+ private emitPropsDataFields;
184
+ private emitPropsAuxFields;
185
+ /** Convert a field name to its JSON tag (camelCase). */
417
186
  private toJsonTag;
418
187
  /**
419
- * Find all nested components (loops with childComponent).
420
- * Returns extended info that includes whether the component comes from a dynamic (signal) array loop.
421
- */
422
- private findNestedComponents;
423
- private collectNestedComponents;
424
- /**
425
- * Collect all static child component instances from the IR tree.
426
- * Excludes components inside loops (which are handled by nestedComponents).
427
- *
428
- * Each instance is identified by:
429
- * - name: Component name (e.g., "ReactiveChild")
430
- * - slotId: Unique slot ID (e.g., "slot_6")
431
- * - props: Component props
432
- * - fieldName: Go field name (e.g., "ReactiveChildSlot6")
188
+ * Collect all static child component instances from the IR tree. Excludes
189
+ * components inside loops (handled by nestedComponents). Each instance carries
190
+ * its component `name`, `slotId`, `props`, and Go `fieldName`.
433
191
  */
434
192
  private collectStaticChildInstances;
435
193
  /**
@@ -437,33 +195,20 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
437
195
  * every node is plain text; otherwise null.
438
196
  */
439
197
  private extractTextChildren;
440
- /**
441
- * Render JSX children to a Go-template-ready HTML fragment when
442
- * children are non-text but produce purely-static HTML (no Go
443
- * template actions). Returns null when:
444
- * - children are absent or text-only (handled by extractTextChildren), or
445
- * - the rendered fragment contains any `{{...}}` action — passing
446
- * such a fragment through `template.HTML` and the parent's
447
- * `{{.Children}}` would output the actions verbatim instead of
448
- * evaluating them, which is worse than the existing
449
- * "drop children" fallback. Dynamic / component-bearing children
450
- * stay on the drop path until a re-evaluation hook lands.
451
- */
452
198
  /**
453
199
  * Pull the IR nodes out of a `children={<…/>}` attribute (a `jsx-children`
454
- * prop value). Empty when the component takes no such prop. (#1326 / #1335)
200
+ * prop value). Empty when the component takes no such prop.
455
201
  */
456
202
  private jsxChildrenPropNodes;
457
203
  private extractHtmlChildren;
458
204
  /**
459
205
  * Build a Go string-concat expression for hoisted-JSX children whose root
460
- * carries `needsScope` (`children={<span/>}` — #1326 / #1335). Such roots
461
- * render in the PARENT's scope, so their `bf-s` is the runtime parent
462
- * `scopeID`, not a bake-time constant. We render the fragment, swap the
463
- * parent-scope hydration marker for a sentinel, and splice `scopeID` back
464
- * in. Returns null when the plain static `childrenHtml` path already
465
- * applies, or when any other template action survives (genuinely dynamic
466
- * those stay on the drop path).
206
+ * carries `needsScope` (`children={<span/>}`). Such roots render in the
207
+ * PARENT's scope, so their `bf-s` is the runtime parent `scopeID`, not a
208
+ * bake-time constant. Render the fragment, swap the parent-scope hydration
209
+ * marker for a sentinel, and splice `scopeID` back in. Returns null when the
210
+ * plain static `childrenHtml` path already applies, or when any other template
211
+ * action survives (genuinely dynamic those stay on the drop path).
467
212
  */
468
213
  private extractScopedHtmlChildren;
469
214
  private collectStaticChildInstancesRecursive;
@@ -474,420 +219,42 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
474
219
  */
475
220
  private extendProviderContext;
476
221
  /**
477
- * Collect top-level (non-loop) JSX intrinsic-element spread slots
478
- * from the IR (#1407). Loop-internal spreads are skipped — they
479
- * emit the bag inline via the loop's iteration variable in
480
- * `elementAttrEmitter.emitSpread`, so they don't need a Props
481
- * struct field.
482
- *
483
- * Walks the IR tree, descending into elements, fragments,
484
- * conditionals, providers, async, and components, but stopping at
485
- * loop bodies. Each `IRElement.attrs[i].value` of kind `'spread'`
486
- * that has a `slotId` becomes one `SpreadSlotInfo` entry.
487
- */
488
- private collectSpreadSlots;
489
- /**
490
- * Decide how a spread bag should be plumbed onto the Input/Props
491
- * structs (#1407 follow-up). A bare-identifier spread that
492
- * matches the component's `restPropsName` is open-ended (Go's
493
- * static typing can't enumerate the keys), so the caller must
494
- * supply the bag via an Input-side `map[string]any` field. Every
495
- * other shape — signal getter, `propsObjectName`, plain
496
- * propsParam, object literal — can be constructed inline in
497
- * `NewXxxProps` from compile-time-known data.
498
- *
499
- * Reads `this.restPropsName` (stashed at `generate()` entry)
500
- * rather than receiving the IR per-call — matches the existing
501
- * `this.propsObjectName` / `this.componentName` storage pattern.
502
- */
503
- private classifySpreadBagSource;
504
- private collectSpreadSlotsRecursive;
505
- /**
506
- * Parse a JS object-literal source text (the raw string captured
507
- * for a signal's `initialValue` or a spread expression's argument)
508
- * into a Go `map[string]any{...}` literal source (#1407).
509
- *
510
- * Supports a deliberately conservative subset so the Go output is
511
- * a 1:1 translation of the JS source: string/number/boolean/null
512
- * values keyed by identifier or string-literal keys. Returns null
513
- * for unsupported shapes (nested objects, computed values,
514
- * function calls, spread elements) — callers fall back to BF101.
515
- */
516
- private parseJsObjectLiteralToGoMap;
517
- /**
518
- * Build a Go expression for a JSX spread bag's initial value, to
519
- * be placed inside `NewXxxProps`'s return literal (#1407).
520
- *
521
- * Supported shapes:
522
- * - Signal-getter call (e.g. `attrs()`): look up the signal,
523
- * parse its `initialValue` as a JS object literal, and emit a
524
- * Go `map[string]any{...}` literal.
525
- * - Bare identifier matching a destructured `propsParam` (e.g.
526
- * `function({ extras }: P) { <el {...extras}/> }`): emit
527
- * `in.<FieldName>` — works when the prop's Go type is a map
528
- * type the bag is assignable to.
529
- * - Bare identifier matching `propsObjectName` (SolidJS-style
530
- * `function(props: P) { <el {...props}/> }`): enumerate the
531
- * analyzer-extracted `propsParams` into an inline
532
- * `map[string]any{...}` literal so each typed Input field
533
- * surfaces as a bag key (#1407 follow-up).
534
- * - Bare identifier matching `restPropsName` (the destructured-
535
- * rest pattern `function({a, ...rest}: P) { <el {...rest}/> }`):
536
- * emit `in.<slotId>` against the `map[string]any` Input field
537
- * that `generateInputStruct` adds for `input-bag` slots. The
538
- * caller (parent component or test harness) populates the
539
- * bag with the open-ended rest values (#1407 follow-up).
540
- *
541
- * Returns null for unsupported shapes so the caller can raise a
542
- * narrowed BF101 with the offending expression.
543
- */
544
- private buildSpreadInitializer;
545
- /**
546
- * Lower a conditional inline-object spread bag value:
547
- * `(COND ? { 'aria-describedby': describedBy } : {})`
548
- * into an immediately-invoked Go func literal that conditionally
549
- * builds the map (so the falsy branch OMITS the key rather than
550
- * rendering it as an empty string, which `SpreadAttrs` does not
551
- * filter):
552
- *
553
- * func() map[string]any {
554
- * if in.DescribedBy != nil && in.DescribedBy != "" {
555
- * return map[string]any{"aria-describedby": in.DescribedBy}
556
- * }
557
- * return map[string]any{}
558
- * }()
559
- *
560
- * Returns:
561
- * - `undefined` when the expression is NOT a parenthesized ternary
562
- * of object literals — the caller falls through to other shapes.
563
- * - `null` when it IS that shape but a part can't be faithfully
564
- * converted (non-static key, unsupported condition, …) — the
565
- * caller raises BF101.
566
- * - the Go IIFE string when fully convertible.
567
- */
568
- private buildConditionalSpreadInitializer;
569
- /** Strip redundant parenthesised wrappers off a TS expression. */
570
- private unwrapParens;
571
- /**
572
- * Convert a conditional-spread condition expression to a Go bool in
573
- * the `in.` context. Supports a bare prop identifier (`describedBy`)
574
- * and its negation (`!describedBy`), type-aware on the prop:
575
- * string → `in.X != ""`
576
- * boolean → `in.X`
577
- * number → `in.X != 0`
578
- * unknown / interface{} → `in.X != nil && in.X != ""`
579
- * (faithful JS string-truthiness for an interface holding a
580
- * string — textarea's `describedBy` resolves to interface{}).
581
- * Returns null for any other shape (caller → BF101).
582
- */
583
- private conditionToGoBool;
584
- /**
585
- * Convert a static object literal (`{ 'aria-describedby': describedBy }`)
586
- * into a Go `map[string]any{...}` literal for a conditional spread.
587
- * Only static string/identifier keys are allowed; values resolve
588
- * prop-identifier references to `in.FieldName` and string literals to
589
- * Go string literals. Returns null for any computed/spread/dynamic
590
- * key or unsupported value (caller → BF101). Empty object → `map[string]any{}`.
591
- */
592
- private objectLiteralToGoSpreadMap;
593
- /**
594
- * Lower a spread-object VALUE of the form `IDENT[KEY]` where:
595
- * - `IDENT` resolves via `localConstants` to a MODULE-scope object
596
- * literal whose property values are all scalar (number/string)
597
- * literals under static (string-literal or identifier) keys
598
- * (a `Record<staticKeys, scalar>` map like `sizeMap`), AND
599
- * - `KEY` is a bare identifier that is a prop.
600
- * Emits an inline indexed Go map:
601
- * `map[string]any{"sm": 16, ...}[fmt.Sprint(in.Size)]`
602
- * (`fmt.Sprint` coerces the `interface{}`/typed prop to the map's
603
- * string key space — sets `usesFmt` so the `"fmt"` import is added).
604
- *
605
- * Returns the Go string when convertible, else `null` (caller → BF101)
606
- * for any non-scalar value, non-static key, or non-prop index so
607
- * unrelated shapes don't regress. (#checkbox / icon `sizeMap[size]`.)
608
- */
609
- private recordIndexAccessToGoMap;
610
- /**
611
- * Convert JavaScript initial value to Go value for NewXxxProps function.
612
- * References to props params are converted to in.FieldName format.
613
- */
614
- private convertInitialValue;
615
- /**
616
- * Convert a fully-literal JS expression string into an equivalent Go literal
617
- * whose Go type matches `typeInfo` (#1672), used to bake a signal's inline
618
- * initial value into the SSR data context:
619
- *
620
- * `["x", "y"]` (string[]) → `[]string{"x", "y"}`
621
- * `["x", "y"]` (unknown[]) → `[]interface{}{"x", "y"}`
622
- * `[{ id: "a" }]` (Item[]) → `[]Item{Item{ID: "a"}}`
623
- *
624
- * Returns `null` — so the caller keeps `nil` — when the expression (or any
625
- * nested element) is not a pure literal (a call, identifier, template with
626
- * interpolation, …) or cannot be expressed in the target Go type without a
627
- * render/compile mismatch (e.g. an object element in a `[]interface{}` field,
628
- * which the SSR template reaches via struct field access the map lacks).
629
- */
630
- private jsLiteralToGo;
631
- /**
632
- * Parse a JS expression string into its TS AST node (parentheses unwrapped),
633
- * or `null` when it isn't a single expression. Shared by the literal baker
634
- * and the struct-shape synthesiser.
635
- */
636
- private parseLiteralExpression;
637
- /**
638
- * Recursively convert a TS literal AST node to a Go literal typed as
639
- * `typeInfo`, or null when the node is not a pure literal / cannot be
640
- * represented in that Go type.
641
- */
642
- private tsLiteralToGo;
643
- /**
644
- * Convert TypeInfo to Go type string.
645
- * If type is unknown, tries to infer from defaultValue.
646
- */
647
- private typeInfoToGo;
648
- /**
649
- * Get signal's initial value as Go code.
650
- * Handles both literal values (0, true, "str") and props references (initial).
651
- *
652
- * (#1423) When the signal references a prop via `props.X ?? N` and
653
- * the caller hoisted a fallback variable for `X`, return the hoisted
654
- * variable's name so the memo inherits the signal-time fallback.
655
- */
656
- private getSignalInitialValueAsGo;
657
- /**
658
- * Resolve dynamic prop value (e.g., signal/memo getter calls) to Go initial value.
659
- * Handles expressions like `count()` → signal's initial value
660
- */
661
- /**
662
- * Convert a template literal's parsed parts into a Go expression of
663
- * type `string`, evaluated in `NewXxxProps` scope (where destructured
664
- * prop refs resolve via `in.FieldName`). Returns null when any part
665
- * is not representable in static Go code so the caller can fall back
666
- * to `resolveDynamicPropValue` (which handles the simpler shapes).
667
- *
668
- * Supported parts:
669
- * - `string`: emit as a Go string literal.
670
- * - `lookup`: `${MAP[KEY]}` against a `Record<T, string>` literal —
671
- * emit an IIFE that switches on the key prop and returns the
672
- * matching case (empty when no case matches). The key must be a
673
- * bare prop identifier today; other key shapes opt out.
674
- *
675
- * `ternary` is intentionally left unsupported — the existing
676
- * element-attribute path handles it via Go template `{{if}}` syntax,
677
- * and component-prop-via-ternary cases are rarer and can be added
678
- * incrementally.
222
+ * Convert a template literal's parsed parts into a `string`-typed Go
223
+ * expression in `NewXxxProps` scope (destructured prop refs `in.FieldName`),
224
+ * or null so the caller falls back to `resolveDynamicPropValue`. A `string`
225
+ * part emits a Go literal; a `lookup` (`${MAP[KEY]}` over a `Record<T,string>`,
226
+ * key must be a bare prop ident) emits a switch-on-key IIFE. `ternary` opts out
227
+ * — the element-attribute path handles it via `{{if}}`.
679
228
  */
680
229
  private templatePartsToGoCode;
681
230
  private resolveDynamicPropValue;
682
- /**
683
- * Compute the initial value for a memo based on its computation and signal initial values.
684
- * Handles simple cases like `() => count() * 2` → `in.Initial * 2`
685
- * Also handles props.xxx patterns like `() => props.value * 10` → `in.Value * 10`
686
- *
687
- * (#1423) When `propFallbackVars` carries a hoisted variable for the
688
- * referenced prop, substitute it for `in.FieldName` so the memo
689
- * inherits the signal-time `??` fallback.
690
- */
691
- /**
692
- * (#checkbox) Compute the SSR initial value of a template-literal memo as a
693
- * Go `string` expression. The memo computation looks like
694
- * `() => `${a} ${b} ${props.className ?? ''} grid place-content-center``.
695
- *
696
- * Each quasi (literal text span) becomes a Go string literal; each
697
- * interpolation is resolved:
698
- * - an identifier naming a module string const → its inlined literal
699
- * (covers both pure-string and `[...].join(' ')` consts);
700
- * - `props.<name> ?? '<fallback>'` or bare `props.<name>` → `in.<Field>`
701
- * when `<name>` is a known prop param (typed `string`); the `?? ''`
702
- * fallback maps to Go's zero value for an unset string field, matching
703
- * the Hono reference's empty-string result.
704
- *
705
- * Returns the `"a" + in.Field + " grid..."` concatenation, or null when the
706
- * computation isn't a single template literal or any interpolation isn't
707
- * representable (so the caller keeps its existing pattern matching).
708
- */
709
- private computeTemplateLiteralMemoInitialValue;
710
- /**
711
- * (#checkbox) Resolve one `${expr}` interpolation of a template-literal memo
712
- * to a Go string expression, or null when unsupported. See
713
- * `computeTemplateLiteralMemoInitialValue` for the supported shapes.
714
- */
715
- private resolveTemplateInterpolation;
716
- /**
717
- * Parse a memo-local `const X = …` initializer into a `Record`-index key
718
- * binding: `props.Y ?? 'lit'` → `{ propName: 'Y', defaultLiteral: 'lit' }`,
719
- * or bare `props.Y` → `{ propName: 'Y' }`. Returns null for any other shape
720
- * (a literal const, a call, etc.) so it simply isn't registered as a key.
721
- */
722
- private parseLocalKeyBinding;
723
- /**
724
- * Lower a `recordConst[key]` interpolation to an inline indexed Go map,
725
- * emitting `map[string]string{…}[fmt.Sprint(in.Field)]` (or `map[string]any`
726
- * for mixed values). `key` is a bare prop or a memo-local const bound to
727
- * `props.X ?? 'default'` (resolved via `localKeyBindings`); a `'default'`
728
- * fallback also maps `""` to that entry, so an unset prop (Go zero value `""`)
729
- * renders the default — matching Hono's `props.X ?? 'default'`. Returns null
730
- * for any non-record / non-resolvable key so the caller falls through.
731
- */
732
- private recordIndexInterpolationToGo;
733
- /**
734
- * If `node` is a `<propsObjectName>.<name>` access, return `<name>`, else
735
- * null. Used to recognize props-object reads inside memo interpolations.
736
- */
737
- private propsAccessName;
738
- private computeMemoInitialValue;
739
- /**
740
- * Pattern-matching core of `computeMemoInitialValue`: returns the
741
- * memo's SSR initial value as a Go expression, or `null` when no
742
- * pattern applies. Callers that have a typed field to fill use the
743
- * zero-value-defaulting wrapper above; callers that can simply OMIT
744
- * the field (a child-instance prop init — Go's zero values then apply
745
- * with the right type for free) use this directly (#1896,
746
- * data-table's `Checked: 0` into a bool field).
747
- */
748
- private computeMemoInitialValueOrNull;
749
- /**
750
- * (#1897) Recognises a block-body memo whose SSR path returns a module-const
751
- * array when the guard signal starts falsy:
752
- * `() => { const k = getter(); if (!k) return MODULE_CONST; … }`
753
- * Returns the constant's name and inferred type, or null.
754
- */
755
- private resolveBlockBodyMemoModuleConst;
756
- /**
757
- * (#1897 PostList) Compute the SSR value of an object-returning block-body
758
- * memo derived from `searchParams()`:
759
- * () => { const sp = searchParams(); return { sort: asSortKey(sp.get('sort')),
760
- * tag: sp.get('tag') ?? '' } }
761
- * Emits a Go `map[string]interface{}{ "Sort": …, "Tag": … }` whose values are
762
- * lowered from the request query (see `lowerCtorExpr`). Keys are capitalized to
763
- * match the template's `.Params.<Field>` map access. Returns null for any shape
764
- * the lowerer can't represent, so the caller falls back to a nil map.
765
- */
766
- private computeObjectMemoInitialValue;
767
- /**
768
- * Lower a JS expression to a Go expression in the `NewXxxProps` constructor
769
- * context. This is Go *code*, not template syntax — so a search-param read
770
- * becomes `in.SearchParams.Get("k")` (method call), not the template's
771
- * `.SearchParams.Get "k"`. Supports the narrow surface derived-state memos
772
- * need: string/number literals, `<sp>.get('k')`, `<arr>.includes(<x>)`,
773
- * module arrow-helper inlining, `<expr> ?? <fallback>`, and string ternaries.
774
- * Returns null for anything else so the caller can fall back safely.
775
- */
776
- private lowerCtorExpr;
777
- /**
778
- * Lower a JS expression used as a *boolean* condition to a Go bool expression,
779
- * or null when it is not provably boolean. Distinct from `lowerCtorExpr`,
780
- * which lowers value expressions: a string-valued condition (`sp.get('tag')`)
781
- * is truthy in JS but `if "<string>"` does not compile in Go, so anything not
782
- * known to yield a Go bool must fall back to null (#1941 review).
783
- */
784
- private lowerCtorCond;
785
- /**
786
- * Resolve a string-array expression (a `['a','b']` literal, or a module const
787
- * bound to one) to a Go `[]string{…}` literal, or null when it isn't a pure
788
- * string-array. Used by `lowerCtorExpr` for `<arr>.includes(<x>)`.
789
- */
790
- private lowerCtorStringArray;
791
- /**
792
- * Whether a memo is an arrow whose result is a template literal — either a
793
- * concise body (`() => \`…\``) or a block body whose `return` is one.
794
- */
795
- private isTemplateLiteralMemo;
796
- /**
797
- * Infer the Go type for a memo based on its computation and dependencies.
798
- */
231
+ /** Infer the Go type for a memo from its computation and dependencies. */
799
232
  private inferMemoType;
800
233
  /**
801
- * (#checkbox) Heuristic: does this memo evaluate to a boolean? True when its
802
- * computation is a comparison (`!==`/`===`/`!=`/`==`), a negation (`!x`), or
803
- * a ternary whose branches are all boolean signals/props. Used to pick `bool`
804
- * (zero value `false`) over the int `0` default for the SSR initial value.
805
- */
806
- private isBooleanMemo;
807
- /**
808
- * Infer Go type from a JavaScript value literal.
809
- */
810
- private inferTypeFromValue;
811
- /**
812
- * (#1423) Hoisted-variable record for a prop with a signal-time
813
- * `??` fallback. The same record is referenced from the prop field
814
- * loop, the signal field loop, and the memo computation path.
815
- */
816
- private static EMPTY_PROP_FALLBACK_VARS;
817
- /**
818
- * (#1423) Walk signals to collect prop fallbacks. Skips props that
819
- * already have a destructure-side default (`{ X = N }`) or signals
820
- * whose fallback resolves to the type's Go zero value (no-op).
234
+ * Walk signals to collect prop fallbacks. Skips props that already have a
235
+ * destructure-side default (`{ X = N }`) or signals whose fallback resolves to
236
+ * the type's Go zero value (no-op).
821
237
  */
822
238
  private collectPropFallbackVars;
823
239
  /**
824
- * (#1423) Parse a signal-time initial value of the form
825
- * `props.X ?? <literal>` into the source prop name and the Go-formatted
826
- * fallback. Returns null when:
827
- * - the expression isn't a `??` against a property access on
828
- * `propsObjectName`
829
- * - the fallback isn't a simple literal `goPropDefault` can translate
240
+ * Parse a signal-time initial value of the form `props.X ?? <literal>` into
241
+ * the source prop name and the Go-formatted fallback. Returns null when the
242
+ * expression isn't a `??` against a property access on `propsObjectName`, or
243
+ * the fallback isn't a simple literal `goPropDefault` can translate.
830
244
  *
831
- * The Go-adapter equivalent of the same parse already done by the
832
- * static evaluator in `ssr-defaults.ts` duplicated here because we
833
- * need the original prop reference (not just the resolved value)
834
- * to honour caller-supplied non-zero inputs.
245
+ * Keeps the original prop reference (not just the resolved value) so
246
+ * caller-supplied non-zero inputs are honoured.
835
247
  */
836
248
  private extractPropFallback;
837
249
  /**
838
- * Extract prop name from a signal's initialValue that uses props.xxx pattern.
839
- * e.g., "props.initial ?? 0" → "initial", "props.checked" → "checked"
250
+ * Extract the prop name from a signal's `props.xxx`-pattern initialValue,
251
+ * e.g. `"props.initial ?? 0"``"initial"`, `"props.checked"``"checked"`.
840
252
  */
841
253
  private extractPropNameFromInitialValue;
842
- /**
843
- * A source key that can become a Go struct field — i.e. a valid Go
844
- * identifier. TS object keys that aren't (e.g. `"data-id"`, numeric keys)
845
- * can't be exported struct fields, so they're excluded from struct generation
846
- * and from the baker's field map.
847
- */
848
- private static GO_IDENTIFIER;
849
- /** Go common initialisms that should be fully uppercased (https://go.dev/wiki/CodeReviewComments#initialisms) */
850
- static GO_INITIALISMS: Set<string>;
851
- /**
852
- * (#1423) Go reserved keywords. When we hoist a local var named after
853
- * a JSX prop, the prop name could collide with one of these — append
854
- * `_` until the name is free.
855
- */
856
- private static GO_KEYWORDS;
857
- private capitalizeFieldName;
858
- /**
859
- * Convert a JavaScript literal value to Go literal syntax.
860
- */
861
- /**
862
- * Translate a JSX param default (e.g. `'default'`, `0`, `false`) into
863
- * the corresponding Go literal. Returns null when the default is
864
- * absent or non-trivial (objects, arrow functions, etc.) — those
865
- * fall back to letting Go's zero value win.
866
- */
867
- private goPropDefault;
868
- /**
869
- * Wrap an `in.X` reference in a Go expression that substitutes
870
- * `fallback` when the input is the zero value for its type. We pick
871
- * the comparison based on the fallback literal's shape.
872
- *
873
- * Asymmetry on bool defaults is intentional and worth flagging:
874
- * - For a `true` default, the generated expression is
875
- * `(in.X || true)` — which is **always `true`**. Go has no
876
- * unset-vs-explicit-false distinction at the struct-field level,
877
- * so any caller wanting to thread `false` through has to set it
878
- * after `NewXxxProps` rather than via the input struct.
879
- * - For a `false` default, the Go zero value already matches, so
880
- * the helper is a no-op (returns `ref` unchanged).
881
- * Numeric `0` defaults are similarly indistinguishable from "unset"
882
- * and pass through as the zero value; non-zero numeric defaults
883
- * substitute, matching the JSX behavior of `(initial = 5) => ...`.
884
- */
885
- private applyGoFallback;
886
- private goLiteral;
887
254
  /**
888
255
  * Public entry point for node rendering. Delegates to the shared
889
- * `IRNodeEmitter` dispatcher (#1290 step 1); per-kind logic lives in
890
- * the `IRNodeEmitter` methods below.
256
+ * `IRNodeEmitter` dispatcher; per-kind logic lives in the `IRNodeEmitter`
257
+ * methods below.
891
258
  */
892
259
  renderNode(node: IRNode, ctx?: GoRenderCtx): string;
893
260
  emitElement(node: IRElement, _ctx: GoRenderCtx, _emit: EmitIRNode<GoRenderCtx>): string;
@@ -904,57 +271,38 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
904
271
  renderElement(element: IRElement): string;
905
272
  renderExpression(expr: IRExpression): string;
906
273
  /**
907
- * Decide whether a lowered Go string is ALREADY a self-contained template
908
- * fragment i.e. it carries its own `{{...}}` actions and so must NOT be
909
- * re-wrapped in another `{{...}}` (doing so yields `{{ {{...}} }}`, which
910
- * `html/template` rejects at parse time). Single source of truth for the
911
- * wrap-or-not decision across `renderExpression`, `templateLiteral`, and the
912
- * static-string / attribute interpolation paths.
274
+ * Whether a lowered Go string already carries its own `{{...}}` actions and so
275
+ * must NOT be re-wrapped (`{{ {{...}} }}` is a parse error). Single source of
276
+ * the wrap-or-not decision across the expression / template-literal / attr
277
+ * paths.
913
278
  *
914
- * The decision is STRUCTURAL, deliberately NOT a `{{` substring scan: literal
915
- * text and Go string literals are ambiguous to scan `5" ${x}` lowers to
916
- * `5" {{.X}}` while JS `{"{{"}` lowers to the Go literal `"{{"`; both mix
917
- * quotes and braces, so no scan can tell them apart. Two — and only two —
918
- * structural shapes are fragments:
919
- *
920
- * 1. A pure action block (`{{if}}` / `{{with}}` / `{{range}}` from a ternary,
921
- * a `find().prop`, a `filter().length`, …). The emitter prepends NO
922
- * literal text to these, so they ALWAYS start with `{{`; a leading `{{`
923
- * is therefore an unambiguous structural marker for this whole class.
924
- * 2. A template literal — the ONLY source form that interleaves author
925
- * literal text with `{{...}}` actions (` · #${tag}` → ` · #{{.Tag}}`), so
926
- * it may begin with literal text and is detected by its parsed `kind`.
927
- *
928
- * Everything else is a bare pipeline (`.Foo`, `len .X`, `bf_arr …`) — even one
929
- * whose value contains `{{` inside a Go string literal — and MUST be wrapped.
930
- *
931
- * Invariant (enforced by the `template-fragment invariant` tests): no
932
- * non-template-literal fragment ever begins with literal text, so case 1's
933
- * `startsWith('{{')` is complete. If a future emitter prepends literal text to
934
- * an action block those tests fail — fix it by giving that shape a parsed kind
935
- * this helper can key off, exactly as template literals are handled here.
279
+ * Structural, deliberately NOT a `{{` substring scan (a Go string literal may
280
+ * contain `{{`). Exactly two shapes are fragments: an action block, which the
281
+ * emitter never prefixes with literal text so it always starts with `{{`; and
282
+ * a template literal, the only form interleaving author text with actions,
283
+ * keyed by its parsed `kind`. The "no non-template-literal fragment starts
284
+ * with literal text" invariant is enforced by the template-fragment tests.
936
285
  */
937
286
  private isTemplateFragment;
938
287
  /**
939
- * Render a client-only conditional as comment markers.
940
- * Used when @client directive is applied to an unsupported conditional.
941
- * The condition is evaluated on the client side via insert().
288
+ * Render a client-only conditional as comment markers (used when @client is
289
+ * applied to an unsupported conditional). The client evaluates the condition
290
+ * via insert().
942
291
  */
943
292
  private renderClientOnlyConditional;
944
293
  /**
945
- * Render a ParsedExpr to Go template syntax via the shared
946
- * dispatcher (#1250 phase 1). The per-kind logic lives in the
947
- * `ParsedExprEmitter` methods below; this method is a thin wrapper
948
- * so existing call sites keep working.
294
+ * Render a ParsedExpr to Go template syntax via the shared dispatcher. The
295
+ * per-kind logic lives in the `ParsedExprEmitter` methods below; this is a
296
+ * thin wrapper so existing call sites keep working.
949
297
  */
950
298
  private renderParsedExpr;
951
299
  identifier(name: string): string;
952
300
  /**
953
- * (#1897 PostList) Compute the Go struct fields for component-scope derived
954
- * string consts referenced by the template (e.g. `root = base || '/'`). Each
955
- * is lowered to a constructor-context Go expression via `lowerCtorExpr`, with
956
- * its dependency consts inlined. Skips names that collide with an existing
957
- * field (`takenFieldNames`) or that the lowerer can't represent.
301
+ * Compute the Go struct fields for component-scope derived string consts
302
+ * referenced by the template (e.g. `root = base || '/'`). Each is lowered to a
303
+ * constructor-context Go expression via `lowerCtorExpr`, with its dependency
304
+ * consts inlined. Skips names that collide with an existing field
305
+ * (`takenFieldNames`) or that the lowerer can't represent.
958
306
  */
959
307
  private computeDerivedConstFields;
960
308
  /**
@@ -978,15 +326,15 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
978
326
  * that lives on the component's top-level data struct. Inside a `{{range}}`
979
327
  * the dot is rebound to the iteration element, so root data must be reached
980
328
  * through Go template's `$` (the top-level argument to Execute), which never
981
- * rebinds. Outside any loop the root *is* the dot, so we emit `.Field` (#1677).
329
+ * rebinds. Outside any loop the root *is* the dot, so we emit `.Field`.
982
330
  */
983
331
  private rootFieldRef;
984
332
  /**
985
- * (#1922) When `name` is a local binding of the `searchParams()` env signal,
986
- * resolve it to the canonical `.SearchParams` field — not `.<Capitalized
987
- * name>` — so an aliased `import { searchParams as sp }` (`sp()`) reaches the
988
- * same struct field the generator emits. Returns null for any other name so
989
- * callers fall back to their normal field-ref lowering.
333
+ * When `name` is a local binding of the `searchParams()` env signal, resolve
334
+ * it to the canonical `.SearchParams` field — not `.<Capitalized name>` — so
335
+ * an aliased `import { searchParams as sp }` (`sp()`) reaches the same struct
336
+ * field the generator emits. Returns null for any other name so callers fall
337
+ * back to their normal field-ref lowering.
990
338
  */
991
339
  private searchParamsFieldRef;
992
340
  /**
@@ -999,21 +347,14 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
999
347
  * pure compile-time string can be safely inlined byte-for-byte.
1000
348
  */
1001
349
  private collectModuleStringConsts;
1002
- /**
1003
- * (#checkbox) Statically evaluate `[<string literals>].join(<sep?>)`.
1004
- * Returns the joined string, or null when the shape doesn't match (non-call,
1005
- * non-`.join`, non-array receiver, any non-string-literal element, or a
1006
- * non-string-literal separator). Comments/whitespace between elements are
1007
- * irrelevant — the TS parser already discarded them.
1008
- */
1009
350
  /**
1010
351
  * Resolve an identifier to its inlined Go string literal when it names a
1011
352
  * module pure-string const. Returns the Go template literal form
1012
- * (`"<escaped>"`) so callers can drop it straight into a `{{...}}` action,
1013
- * or `null` when the name is not such a const (the caller then falls back
1014
- * to its normal field-ref lowering). The value is escaped for a Go
1015
- * double-quoted string literal — Go's `html/template` then applies the
1016
- * same contextual auto-escaping it applies to any literal, matching Hono.
353
+ * (`"<escaped>"`) so callers can drop it straight into a `{{...}}` action, or
354
+ * `null` when the name is not such a const (the caller then falls back to its
355
+ * normal field-ref lowering). The value is escaped for a Go double-quoted
356
+ * string literal; `html/template` then applies its usual contextual
357
+ * auto-escaping.
1017
358
  */
1018
359
  private resolveModuleStringConst;
1019
360
  /**
@@ -1033,37 +374,66 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
1033
374
  logical(op: '&&' | '||' | '??', left: ParsedExpr, right: ParsedExpr, emit: (e: ParsedExpr) => string): string;
1034
375
  conditional(test: ParsedExpr, consequent: ParsedExpr, alternate: ParsedExpr, emit: (e: ParsedExpr) => string): string;
1035
376
  templateLiteral(parts: TemplatePart[], emit: (e: ParsedExpr) => string): string;
1036
- arrowFn(param: string, _body: ParsedExpr, _emit: (e: ParsedExpr) => string): string;
377
+ arrow(params: string[], _body: ParsedExpr, _emit: (e: ParsedExpr) => string): string;
378
+ regex(raw: string): string;
1037
379
  arrayLiteral(elements: ParsedExpr[], emit: (e: ParsedExpr) => string): string;
1038
- higherOrder(method: HigherOrderMethod, object: ParsedExpr, param: string, predicate: ParsedExpr, emit: (e: ParsedExpr) => string): string;
380
+ objectLiteral(_properties: ObjectLiteralProperty[], raw: string, _emit: (e: ParsedExpr) => string): string;
381
+ /** Set of predicate (boolean-callback) higher-order methods. */
382
+ private static readonly PREDICATE_METHODS;
383
+ /**
384
+ * Recover the (removed) `higher-order` destructured shape from a generic
385
+ * `call` that is a recognised callback method whose method is a *predicate*
386
+ * one. Returns null for sort/reduce/flatMap or any non-callback call. Used by
387
+ * the `member`-arm short-circuits (`obj.filter(...).length`,
388
+ * `obj.find(...).prop`) that need the structured fallbacks.
389
+ */
390
+ private higherOrderShapeOf;
391
+ /**
392
+ * Push BF101 for a callback method whose shape has no Go template form.
393
+ *
394
+ * `selfContained` callbacks (`reduce` / `flatMap` off the eval-lowerable
395
+ * catalogue — a `.reduce` with no init, a tuple `.flatMap` with a non-leaf
396
+ * element) carry the self-contained "no SSR" remedy and must NOT get the
397
+ * generic Options block appended (mirrors `isSupported`'s `selfContained`
398
+ * flag for `UNSUPPORTED_METHODS`). Predicate callbacks that exhaust their
399
+ * template-block fallbacks keep the generic Options remediation.
400
+ */
401
+ private pushCallbackBF101;
402
+ callbackMethod(method: string, object: ParsedExpr, arrow: Extract<ParsedExpr, {
403
+ kind: 'arrow';
404
+ }>, restArgs: ParsedExpr[], emit: (e: ParsedExpr) => string): string;
1039
405
  arrayMethod(method: ArrayMethod, object: ParsedExpr, args: ParsedExpr[], emit: (e: ParsedExpr) => string): string;
1040
- sortMethod(method: 'sort' | 'toSorted', object: ParsedExpr, comparator: SortComparator, emit: (e: ParsedExpr) => string): string;
1041
- reduceMethod(method: 'reduce' | 'reduceRight', object: ParsedExpr, reduceOp: ReduceOp, emit: (e: ParsedExpr) => string): string;
1042
406
  flatMethod(object: ParsedExpr, depth: FlatDepth, emit: (e: ParsedExpr) => string): string;
1043
- flatMapMethod(object: ParsedExpr, op: FlatMapOp, emit: (e: ParsedExpr) => string): string;
1044
407
  unsupported(raw: string, _reason: string): string;
1045
408
  /**
1046
409
  * Extract field name and negation from a simple predicate.
1047
- * t => t.done → { field: "Done", negated: false }
1048
- * t => !t.done → { field: "Done", negated: true }
410
+ * `t => t.done` → { field: "Done", negated: false }
411
+ * `t => !t.done` → { field: "Done", negated: true }
1049
412
  */
1050
413
  private extractFieldPredicate;
1051
414
  /**
1052
- * Extract field name and value from an equality predicate.
1053
- * Extends extractFieldPredicate to also handle equality comparisons.
1054
- *
1055
- * t.done → { field: "Done", value: "true" }
1056
- * !t.done → { field: "Done", value: "false" }
1057
- * u.id === selectedId() { field: "Id", value: <rendered expr> }
1058
- * selectedId() === u.id → same (supports both operand orders)
415
+ * Extract field name and value from an equality predicate (extends
416
+ * extractFieldPredicate to also handle equality comparisons).
417
+ * `t.done` → { field: "Done", value: "true" }
418
+ * `!t.done` → { field: "Done", value: "false" }
419
+ * `u.id === selectedId()` → { field: "Id", value: <rendered expr> }
420
+ * `selectedId() === u.id`same (both operand orders supported)
1059
421
  */
1060
422
  private extractEqualityPredicate;
1061
423
  /**
1062
- * Render a higher-order expression (filter, every, some, find, findIndex) to Go template.
1063
- * Returns null if the expression is not supported.
1064
- *
1065
- * @param expr - The higher-order expression
1066
- * @param renderArray - Function to render the array expression (allows recursion via different methods)
424
+ * Emit a higher-order method through the runtime evaluator (#2018 P2):
425
+ * `bf_filter_eval` / `bf_every_eval` / `bf_some_eval` / `bf_find_eval` /
426
+ * `bf_find_index_eval`, carrying the serialized predicate body + captured
427
+ * env. Returns null when the predicate is outside the evaluator surface
428
+ * (caller falls back to the structured helper / template block). The find /
429
+ * findLast (and findIndex / findLastIndex) pair share a helper, differing
430
+ * only by the `forward` bool argument.
431
+ */
432
+ private renderHigherOrderEval;
433
+ /**
434
+ * Render a higher-order expression (filter, every, some, find, findIndex) to
435
+ * Go template, or null when the shape isn't supported. `renderArray` is passed
436
+ * in so the array can recurse through different lowering methods.
1067
437
  */
1068
438
  private renderHigherOrderExpr;
1069
439
  /**
@@ -1077,39 +447,30 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
1077
447
  */
1078
448
  private renderFindTemplateBlock;
1079
449
  /**
1080
- * Render every()/some() with complex predicates using {{range}}{{if}} with variable reassignment.
1081
- * Falls back from bf_every/bf_some when extractFieldPredicate returns null.
1082
- * Reuses renderFilterExpr for condition rendering.
450
+ * Render every()/some() with complex predicates as a `{{range}}{{if}}` with
451
+ * variable reassignment — the fallback from bf_every/bf_some when
452
+ * extractFieldPredicate returns null.
1083
453
  *
1084
- * every: start true, set false on first failure, break early
1085
- * some: start false, set true on first match, break early
1086
- *
1087
- * @param expr - The higher-order every/some expression
1088
- * @param renderArray - Function to render the array expression
454
+ * every: start true, set false on first failure, break early
455
+ * some: start false, set true on first match, break early
1089
456
  */
1090
457
  private renderEverySomeTemplateBlock;
1091
458
  /**
1092
- * Negate a Go template condition.
1093
- * Wraps in `not (...)` when the condition is a Go function call (eq, ne, gt, etc.),
1094
- * otherwise uses `not condition`.
459
+ * Negate a Go template condition: `not (...)` when it's a Go function call
460
+ * (eq, ne, gt, ), else `not condition`.
1095
461
  */
1096
462
  private negateGoCondition;
1097
463
  /**
1098
- * Render .length on a filter higher-order expression.
1099
- * e.g., todos().filter(t => !t.done).length → len (bf_filter .Todos "Done" false)
1100
- *
1101
- * @param filterExpr - The filter higher-order expression
1102
- * @param renderArray - Function to render the array expression
464
+ * Render `.length` on a filter higher-order expression, e.g.
465
+ * `todos().filter(t => !t.done).length``len (bf_filter .Todos "Done" false)`.
1103
466
  */
1104
467
  private renderFilterLengthExpr;
1105
468
  /**
1106
- * Render a predicate expression for use in Go template {{if}} conditions.
1107
- * Substitutes the loop parameter (e.g., 't' in 't.done') with dot notation.
469
+ * Render a predicate for use in Go template `{{if}}` conditions, substituting
470
+ * the loop parameter (e.g. `t` in `t.done`) with dot notation.
1108
471
  */
1109
472
  private renderPredicateCondition;
1110
- /**
1111
- * Check if expression needs parentheses when used in and/or.
1112
- */
473
+ /** Whether an expression needs parentheses when used in and/or. */
1113
474
  private needsParens;
1114
475
  /**
1115
476
  * Split a rendered template block into preamble + final expression.
@@ -1120,169 +481,50 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
1120
481
  */
1121
482
  private splitPreamble;
1122
483
  /**
1123
- * Render block body filter into a single Go template condition.
1124
- *
1125
- * Example block body:
1126
- * ```
1127
- * filter(t => {
1128
- * const f = filter()
1129
- * if (f === 'active') return !t.done
1130
- * if (f === 'completed') return t.done
1131
- * return true
1132
- * })
1133
- * ```
1134
- *
1135
- * Becomes:
1136
- * ```
1137
- * or (and (eq $.Filter "active") (not .Done))
1138
- * (and (eq $.Filter "completed") .Done)
1139
- * (and (ne $.Filter "active") (ne $.Filter "completed"))
1140
- * ```
1141
- */
1142
- private renderBlockBodyCondition;
1143
- /**
1144
- * Recursively collect all return paths through the statements.
1145
- * Returns an array of ReturnPath objects.
1146
- */
1147
- private collectReturnPaths;
1148
- /**
1149
- * Build a condition for a single return path.
1150
- */
1151
- private buildSinglePathCondition;
1152
- /**
1153
- * Build an OR condition from multiple return paths.
1154
- */
1155
- private buildOrCondition;
1156
- /**
1157
- * Render multiple conditions combined with AND.
1158
- */
1159
- private renderConditionsAnd;
1160
- /**
1161
- * Unified method for rendering filter predicate expressions.
1162
- * Used for both expression body (t => !t.done) and block body filters.
1163
- *
1164
- * @param expr - The parsed expression to render
1165
- * @param param - The loop parameter name (e.g., 't' in filter(t => ...))
1166
- * @param localVarMap - Optional map of local variables to signal names (for block body)
484
+ * Render a filter predicate expression (`t => !t.done`, or a block body
485
+ * normalized to one — #2040). `localVarMap` is a vestigial empty default kept
486
+ * on the recursion; block-body locals are now inlined upstream, so no caller
487
+ * populates it.
1167
488
  */
1168
489
  private renderFilterExpr;
1169
490
  private renderFilterExprNode;
1170
491
  /**
1171
- * Check if a ParsedExpr will render as a Go template function call.
1172
- * Used to determine if parentheses are needed around the expression.
492
+ * Whether a ParsedExpr renders as a Go template function call (so it needs
493
+ * parentheses around it).
1173
494
  */
1174
495
  private isGoFunctionCall;
1175
496
  /**
1176
- * Render a branch of a conditional expression.
1177
- * String literals render as bare text (no quotes).
1178
- * Nested conditionals render as complete {{if}}...{{end}} blocks.
497
+ * Render a branch of a conditional expression. String literals render as bare
498
+ * text (no quotes); nested conditionals render as complete `{{if}}…{{end}}`
499
+ * blocks; everything else is wrapped in `{{...}}`.
1179
500
  */
1180
501
  private renderConditionalBranch;
1181
502
  /**
1182
- * Check if a ParsedExpr renders to a Go template function call that needs parentheses.
1183
- * In Go templates, function calls like `len .X` or `bf_add .A .B` need parentheses
1184
- * when used as arguments to comparison operators (eq, gt, lt, etc.).
503
+ * Whether a ParsedExpr renders to a Go template function call (`len .X`,
504
+ * `bf_add .A .B`) that needs parentheses when used as an argument to a
505
+ * comparison operator (eq, gt, lt, ).
1185
506
  */
1186
507
  private needsParensInGoTemplate;
1187
- /**
1188
- * Convert a JS expression to Go template syntax.
1189
- */
508
+ /** Convert a JS expression to Go template syntax. */
1190
509
  private convertExpressionToGo;
1191
510
  /**
1192
- * (#1897 PostList) If `jsExpr` is a call to a local, expression-bodied helper
1193
- * arrow const (`sortClass(k)` / `tagClass(t)`), return its body with the call
1194
- * args substituted for the params (re-emitted to source), so the caller can
1195
- * lower the inlined expression. Returns null when `jsExpr` is not such a call,
1196
- * or when the helper delegates to another local helper (e.g. `sortHref`
1197
- * `hrefFor`) those need their own lowering capability and must not be
1198
- * half-inlined here. Substitution is AST-based (no string matching) so it is
1199
- * shadowing- and member-name-safe.
1200
- */
1201
- private inlineLocalHelperCall;
1202
- /**
1203
- * Guard for `substituteHelperParams`: the span splicer replaces identifiers by
1204
- * name without scope tracking, so it is only safe on bodies free of
1205
- * constructs where a param name could appear in a position the splice can't
1206
- * handle — a nested function scope (shadowing or its own parameters), or an
1207
- * object shorthand key (`{ k }`, which can't be rewritten to `{ (arg) }`).
1208
- */
1209
- private isSpliceSafeHelperBody;
1210
- /**
1211
- * True when `body` contains a call to a *local* (component-scope) arrow helper
1212
- * const — the signal that inlining `body` here would only push the problem to
1213
- * another un-lowered helper (e.g. `sortHref`'s body calls `hrefFor`).
1214
- */
1215
- private bodyCallsLocalHelper;
1216
- /**
1217
- * Re-emit `body` to source with each identifier named in `subs` replaced by
1218
- * the substitution's source text. Implemented as span splicing over `body`'s
1219
- * own source (single source file — args are passed as text, not cross-file
1220
- * AST nodes, which would corrupt a printer keyed to `body`'s source). The walk
1221
- * skips non-value identifier positions — the property NAME in `a.b` and a
1222
- * plain object-literal key in `{ k: … }` — so a param sharing a name with a
1223
- * member or key is left untouched. (`isSpliceSafeHelperBody` has already
1224
- * rejected nested functions and `{ k }` shorthand keys.)
1225
- */
1226
- private substituteHelperParams;
1227
- /**
1228
- * (#1897 PostList) Lower a call to a local URL-builder helper to a `bf_query`
1229
- * template expression. Handles two shapes:
1230
- * - the builder itself — `(sort, tag) => { const u = new URLSearchParams();
1231
- * if (sort !== 'date') u.set('sort', sort); if (tag) u.set('tag', tag);
1232
- * return u.toString() ? \`${root}?${u}\` : root }` — substitute the call
1233
- * args for params and emit `bf_query <base> (<guard>) "key" <value> …`;
1234
- * - a pass-through delegate — `(k) => hrefFor(k, params().tag)` — substitute
1235
- * and recurse on the delegated call.
1236
- * Returns null for anything else (→ existing lowering / method-call fallback).
1237
- */
1238
- private lowerUrlBuilderHelperCall;
1239
- /**
1240
- * Extract the `bf_query` shape from a `(…) => { const u = new URLSearchParams();
1241
- * [if (G)] u.set(K, V); …; return <s> ? … : <base> }` helper, or null when the
1242
- * block doesn't match that exact builder idiom.
1243
- */
1244
- private extractUrlBuilder;
1245
- /**
1246
- * Match `<builderVar>.set('literalKey', <value>)` — as a statement or an
1247
- * if-then — returning the key text and value node, else null.
1248
- */
1249
- private matchUrlSet;
1250
- /**
1251
- * Emit `bf_query <base> (<guard>) "key" <value> …` from an extracted builder
1252
- * shape, lowering each part (with the call args substituted for params) via
1253
- * the normal expression / condition lowering. Unguarded sets use `true`.
1254
- */
1255
- private emitUrlBuilder;
1256
- /**
1257
- * Lower a `u.set()` guard to a Go *bool* for `bf_query`'s `include` argument.
1258
- * A comparison / logical / negation / bool-literal already yields a bool
1259
- * (`convertConditionToGo`); a bare value (`if (tag)`) is JS string-truthiness,
1260
- * lowered to `ne <value> ""`. The arg must be a real bool — `bf_query` type-
1261
- * asserts it, so Go-template truthiness (`{{if x}}`) is not enough.
1262
- */
1263
- private lowerUrlGuard;
1264
- /**
1265
- * Resolve `IDENT['key']` / `IDENT["key"]` where `IDENT` is a
1266
- * module-scope object-literal const and the key is a string literal —
1267
- * a compile-time-static lookup (the icon registry's
1268
- * `strokePaths['chevron-down']`, #1896). Returns the looked-up value
1269
- * as a Go literal (quoted string / bare number) usable inside a
1270
- * template action, or `null` for any other shape so the caller falls
1271
- * through to the generic lowering. The prop-keyed variant of the same
1272
- * pattern lives in `parseRecordIndexAccess` (shared with Mojo); this
1273
- * helper covers the literal-key case that parse rejects.
511
+ * Resolve `IDENT['key']` / `IDENT["key"]` where `IDENT` is a module-scope
512
+ * object-literal const and the key is a string literal a compile-time-static
513
+ * lookup (the icon registry's `strokePaths['chevron-down']`). Returns the
514
+ * looked-up value as a Go literal (quoted string / bare number) usable inside a
515
+ * template action, or `null` for any other shape so the caller falls through to
516
+ * the generic lowering. The prop-keyed variant lives in `parseRecordIndexAccess`
517
+ * (shared with Mojo); this helper covers the literal-key case parse rejects.
1274
518
  */
1275
519
  private resolveStaticRecordLiteralIndex;
1276
- /**
1277
- * Create a source location for error reporting.
1278
- */
520
+ /** Create a source location for error reporting. */
1279
521
  private makeLoc;
1280
522
  private renderIfStatement;
1281
523
  renderConditional(cond: IRConditional): string;
1282
524
  /**
1283
- * Convert a JS condition to Go template condition syntax.
1284
- * Returns { condition, preamble } where preamble contains template blocks
1285
- * that must be emitted before the {{if}} (e.g., every/some range blocks).
525
+ * Convert a JS condition to Go template condition syntax. `preamble` holds
526
+ * template blocks that must be emitted before the `{{if}}` (e.g. every/some
527
+ * range blocks).
1286
528
  */
1287
529
  private convertConditionToGo;
1288
530
  private renderConditionExpr;
@@ -1290,36 +532,33 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
1290
532
  * Map each destructure binding to its Go accessor on the range var: a named
1291
533
  * binding → `$<rangeVar>.<Field>`, an object-rest binding → the bare
1292
534
  * `$<rangeVar>` so the member emitter renders `rest.flag` → `$<rangeVar>.Flag`.
1293
- * (#1310)
1294
535
  */
1295
536
  private buildDestructureBindingMap;
1296
537
  renderLoop(loop: IRLoop): string;
1297
538
  /**
1298
539
  * Per-item `<!--bf-loop-i-->` / `<!--bf-loop-i:KEY-->` start marker emitted
1299
- * inside a `{{range}}` body. Multi-root Fragment items (#1212) get the bare
1300
- * anchor; whole-item conditional items (#1665) get the key-bearing anchor so
1301
- * the client's `mapArrayAnchored` can hydrate items that render no element.
540
+ * inside a `{{range}}` body. Multi-root Fragment items get the bare anchor;
541
+ * whole-item conditional items get the key-bearing anchor so the client's
542
+ * `mapArrayAnchored` can hydrate items that render no element.
1302
543
  */
1303
544
  private loopItemMarker;
1304
- /**
1305
- * Find the first component child in a list of nodes
1306
- */
545
+ /** Find the first component child in a list of nodes. */
1307
546
  private findChildComponent;
1308
547
  /**
1309
- * When `comp`'s JSX children contain template actions (nested
1310
- * components, dynamic text) — i.e. none of the static bake paths in
1311
- * `collectStaticChildInstances` apply — render them into a companion
1312
- * define and return its name; `renderComponent` then routes the child
1313
- * call through `bf_with_children` + `bf_tmpl` (#1896). Returns null
1314
- * for childless or statically-bakeable children, which keep the
1315
- * constructor-baked `Children` value.
548
+ * When `comp`'s JSX children contain template actions (nested components,
549
+ * dynamic text) — i.e. none of the static bake paths in
550
+ * `collectStaticChildInstances` apply — render them into a companion define
551
+ * and return its name; `renderComponent` then routes the child call through
552
+ * `bf_with_children` + `bf_tmpl`. Returns null for childless or
553
+ * statically-bakeable children, which keep the constructor-baked `Children`
554
+ * value.
1316
555
  */
1317
556
  private queueDynamicChildrenDefine;
1318
557
  /**
1319
- * (#1897) Queue a companion define for a loop body component's JSX children.
1320
- * Like `queueDynamicChildrenDefine` but temporarily exits the `inLoop`
1321
- * context so nested component calls render with the normal `.NameSlotN`
1322
- * field-access pattern (the fields live on the wrapper struct that the
558
+ * Queue a companion define for a loop body component's JSX children. Like
559
+ * `queueDynamicChildrenDefine` but temporarily exits the `inLoop` context so
560
+ * nested component calls render with the normal `.NameSlotN` field-access
561
+ * pattern (the fields live on the wrapper struct that the
1323
562
  * companion define receives as its data context). The loop param stack
1324
563
  * stays intact so datum-field references (`payment.id` → `.Id`) still
1325
564
  * resolve.
@@ -1329,28 +568,23 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
1329
568
  isRootOfClientComponent?: boolean;
1330
569
  }): string;
1331
570
  /**
1332
- * Render a Portal component by adding its children to PortalCollector.
1333
- * Portal content is rendered at </body> instead of inline.
1334
- *
1335
- * For static content: uses simple string literal with Add()
1336
- * For dynamic content: uses bfPortalHTML() to parse and execute template string
571
+ * Render a Portal component by adding its children to PortalCollector. Portal
572
+ * content is rendered at </body> instead of inline: static content via a plain
573
+ * string literal, dynamic content via `bfPortalHTML()` (parses and executes
574
+ * the template string with the provided data).
1337
575
  */
1338
576
  private renderPortalComponent;
1339
577
  private renderFragment;
1340
578
  private renderSlot;
1341
579
  renderAsync(node: IRAsync): string;
1342
- private escapeGoString;
1343
580
  /**
1344
- * AttrValue lowering for intrinsic-element attributes (Go templates).
1345
- * Per-kind logic that used to live in a `switch (v.kind)` inside
1346
- * `renderAttributes`; routed through the shared dispatcher so a new
1347
- * AttrValue kind becomes a TS compile error here (#1290 step 2).
581
+ * AttrValue lowering for intrinsic-element attributes. Routed through the
582
+ * shared dispatcher so a new AttrValue kind becomes a TS compile error here.
1348
583
  *
1349
- * Components have no equivalent AttrValueEmitter on this adapter:
1350
- * Go templates pass component-instance props as Go struct fields
1351
- * (`collectStaticChildInstances` builds them), not as string-emitted
1352
- * markup, so that path does not share a contract with the
1353
- * intrinsic-attribute one.
584
+ * Components have no equivalent AttrValueEmitter: Go templates pass
585
+ * component-instance props as Go struct fields (built by
586
+ * `collectStaticChildInstances`), not as string-emitted markup, so that path
587
+ * doesn't share a contract with the intrinsic-attribute one.
1354
588
  */
1355
589
  private readonly elementAttrEmitter;
1356
590
  /**
@@ -1359,36 +593,24 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
1359
593
  * `{ backgroundColor: color, padding: '8px' }` →
1360
594
  * `background-color:{{.Color}};padding:8px`. Returns null when the object
1361
595
  * shape is unsupported or any value expression can't be lowered (the caller
1362
- * then falls through to the generic BF101 path). (#1322)
596
+ * then falls through to the generic BF101 path).
1363
597
  */
1364
598
  private tryLowerStyleObject;
1365
599
  private renderAttributes;
1366
600
  /**
1367
- * Replace `${EXPR}` JS-template-literal interpolations in a static
1368
- * string part with Go template actions (`{{<expr-as-go>}}`), and
1369
- * HTML-escape the surrounding literal text so embedded characters
1370
- * don't break the attribute quoting we render into.
1371
- *
1372
- * UnoCSS arbitrary-value classes like `[class*="size-"]:size-4`
1373
- * legitimately contain `"`, which would otherwise terminate the
1374
- * `class="..."` attribute early and produce invalid HTML / a
1375
- * `html/template` error at execution time.
1376
- *
1377
- * The interpolation parser is brace-depth aware: nested `{...}`
1378
- * inside an expression (object literals, nested template literals,
1379
- * etc.) are skipped past correctly so the closing brace of the
1380
- * outer `${...}` is found. An unterminated `${` falls back to
1381
- * literal text — better to output something than swallow it.
601
+ * Replace `${EXPR}` interpolations with Go actions (`{{<expr-as-go>}}`) and
602
+ * HTML-escape the surrounding literal text UnoCSS arbitrary values
603
+ * (`[class*="size-"]:size-4`) contain `"` that would otherwise close the
604
+ * `class="..."` attribute. Brace-depth aware (nested `{...}` skipped to find
605
+ * the outer `}`); an unterminated `${` falls back to literal text.
1382
606
  */
1383
607
  private substituteJsInterpolations;
1384
608
  /**
1385
- * HTML-attribute-safe escaping for double-quoted attribute values.
1386
- * `&`/`"`/`<` are non-negotiable — without them the surrounding
1387
- * `class="..."` quoting breaks (a real bug we hit with UnoCSS's
1388
- * `[class*="size-"]`). `>`/`'` are belt-and-suspenders: HTML5
1389
- * permits both inside double-quoted attrs, but Go's `html/template`
1390
- * lexer is contextual and we'd rather not bet on its edge cases
1391
- * matching ours forever.
609
+ * HTML-attribute-safe escaping for double-quoted attribute values. `&`/`"`/`<`
610
+ * are non-negotiable — without them the surrounding `class="..."` quoting
611
+ * breaks (e.g. UnoCSS's `[class*="size-"]`). `>`/`'` are belt-and-suspenders:
612
+ * HTML5 permits both inside double-quoted attrs, but Go's `html/template`
613
+ * lexer is contextual, so escape them too.
1392
614
  */
1393
615
  private escapeAttrText;
1394
616
  private renderTemplateLiteralParts;
@@ -1398,5 +620,4 @@ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExpr
1398
620
  private wrapWithCondMarker;
1399
621
  }
1400
622
  export declare const goTemplateAdapter: GoTemplateAdapter;
1401
- export {};
1402
623
  //# sourceMappingURL=go-template-adapter.d.ts.map