@barefootjs/go-template 0.1.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.
@@ -0,0 +1,683 @@
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, 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
+ }
30
+ export declare class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter, IRNodeEmitter<GoRenderCtx> {
31
+ name: string;
32
+ extension: string;
33
+ private filterExprDepth;
34
+ private filterExprUnsupported;
35
+ /**
36
+ * Identifier-path callees the Go runtime can render in template
37
+ * scope (#1188). The relocate pass consults this map to mark
38
+ * matching calls as template-safe so the surrounding expression
39
+ * stays inlinable; the SSR template emitter (`renderParsedExpr`'s
40
+ * `call` branch) uses the same map to substitute the JS call with
41
+ * the registered Go template form.
42
+ *
43
+ * Keys are the textual callee path as written in the JSX
44
+ * expression. Values are emit functions that receive the already-
45
+ * Go-rendered argument expressions (e.g. `.Config`, `_p.Score`)
46
+ * and return the substituted Go template body — without the
47
+ * surrounding `{{ }}` action delimiters, so callers can wrap the
48
+ * result in `{{...}}` or compose into larger expressions like
49
+ * `{{if eq (bf_json .X) "..."}}`.
50
+ *
51
+ * V1 scope (#1187 R1): identifier-path callees only. Method calls
52
+ * on values (`(arr).join(",")`) require analyzer-resolved receiver
53
+ * type and are explicitly out of scope — users fall back to
54
+ * `/* @client *\/` for those.
55
+ *
56
+ * Public because the `TemplateAdapter` interface contract requires
57
+ * the relocate pass to read this for boolean acceptance. The arity
58
+ * map below is implementation detail (private) — the asymmetry is
59
+ * deliberate.
60
+ */
61
+ templatePrimitives: TemplatePrimitiveRegistry;
62
+ /**
63
+ * Expected arg count per primitive. Consulted before invoking the
64
+ * registered emit fn so a 0-arg `JSON.stringify()` or 2-arg
65
+ * `JSON.stringify(x, replacer)` doesn't silently produce invalid
66
+ * Go template syntax (the V1 emit fns blindly read `args[0]`).
67
+ *
68
+ * Derived from `GO_TEMPLATE_PRIMITIVES` so it can't drift from
69
+ * `templatePrimitives` — a wrong-arity call falls back to the
70
+ * standard BF101 unsupported-call diagnostic.
71
+ */
72
+ private readonly templatePrimitiveArities;
73
+ private componentName;
74
+ private options;
75
+ private inLoop;
76
+ private loopParamStack;
77
+ private errors;
78
+ private propsObjectName;
79
+ /**
80
+ * Component-scoped rest binding identifier (`function({ a, ...rest }: P)`
81
+ * → `'rest'`). Stashed at `generate()` entry so per-attribute
82
+ * emitter callbacks can classify a spread expression against it
83
+ * without threading the IR through each recursion (#1407
84
+ * follow-up).
85
+ */
86
+ private restPropsName;
87
+ /** Local type names resolved from typeDefinitions (populated during generateTypes) */
88
+ private localTypeNames;
89
+ /** Local type aliases mapping type name to base type (e.g., Filter → 'string') */
90
+ private localTypeAliases;
91
+ /** Set during type generation when any emit references
92
+ * `template.HTML(...)`; toggles the `"html/template"` import. */
93
+ private usesHtmlTemplate;
94
+ constructor(options?: GoTemplateAdapterOptions);
95
+ /**
96
+ * Generate template output for a component.
97
+ * @param ir - The component IR
98
+ * @param options - Generation options
99
+ */
100
+ generate(ir: ComponentIR, options?: AdapterGenerateOptions): AdapterOutput;
101
+ /**
102
+ * Check if a component has client interactivity (needs client JS).
103
+ * A component has client interactivity if it has:
104
+ * - Signals (reactive state)
105
+ * - Effects (side effects)
106
+ * - Events on elements
107
+ */
108
+ private hasClientInteractivity;
109
+ /**
110
+ * Recursively check if any element in the tree has events.
111
+ */
112
+ private hasEventsInTree;
113
+ /**
114
+ * Find all child component names used in the IR tree.
115
+ */
116
+ private findChildComponentNames;
117
+ private collectChildComponentNames;
118
+ /**
119
+ * Push a `BF103` diagnostic for every component reference inside a
120
+ * loop body whose name is imported from a relative-path module
121
+ * (i.e. a sibling .tsx file). The Go adapter renders these as
122
+ * `{{template "X" .}}` calls, which Go's template engine resolves
123
+ * only against templates registered on the same `*template.Template`
124
+ * — so a user who factored a list item into `./list-item.tsx` and
125
+ * mapped over it gets a working build and a `template: "X" is
126
+ * undefined` at request time. Surfacing this at build time matches
127
+ * the louder-over-silent contract (#1266).
128
+ *
129
+ * Scoped to loop bodies because that's the natural Hono-style
130
+ * pattern the issue calls out; static (non-loop) usage of imported
131
+ * components is left alone so existing static-layout patterns
132
+ * keep working without noise.
133
+ */
134
+ private checkImportedLoopChildComponents;
135
+ /**
136
+ * Generate script registration code for the template.
137
+ * Scripts are registered at the beginning of the template.
138
+ * Uses .Scripts which is available on all Props structs.
139
+ * The same ScriptCollector should be shared across parent and child props.
140
+ * Wrapped in {{if .Scripts}} to safely handle nil Scripts.
141
+ */
142
+ private generateScriptRegistrations;
143
+ generateTypes(ir: ComponentIR): string | null;
144
+ /**
145
+ * Convert a TypeScript type definition to a Go type.
146
+ * Handles object types → Go structs, and union string literals → string alias.
147
+ */
148
+ private typeDefinitionToGo;
149
+ /**
150
+ * Convert a raw TypeScript type string to a Go type string.
151
+ * Handles primitives (number, string, boolean) and basic arrays.
152
+ */
153
+ private tsTypeStringToGo;
154
+ /**
155
+ * Build a map from prop name to a better Go type inferred from signals.
156
+ * When a signal is initialized from a prop (e.g., createSignal(props.initial ?? 0)),
157
+ * the signal's type annotation may be more specific than the prop's TypeInfo.
158
+ */
159
+ private buildPropTypeOverrides;
160
+ /**
161
+ * Generate Input struct for a component
162
+ */
163
+ private generateInputStruct;
164
+ /**
165
+ * Generate Props struct for a component
166
+ */
167
+ private generatePropsStruct;
168
+ /**
169
+ * Generate NewXxxProps function
170
+ */
171
+ private generateNewPropsFunction;
172
+ /**
173
+ * Convert field name to JSON tag (camelCase)
174
+ */
175
+ private toJsonTag;
176
+ /**
177
+ * Find all nested components (loops with childComponent).
178
+ * Returns extended info that includes whether the component comes from a dynamic (signal) array loop.
179
+ */
180
+ private findNestedComponents;
181
+ private collectNestedComponents;
182
+ /**
183
+ * Collect all static child component instances from the IR tree.
184
+ * Excludes components inside loops (which are handled by nestedComponents).
185
+ *
186
+ * Each instance is identified by:
187
+ * - name: Component name (e.g., "ReactiveChild")
188
+ * - slotId: Unique slot ID (e.g., "slot_6")
189
+ * - props: Component props
190
+ * - fieldName: Go field name (e.g., "ReactiveChildSlot6")
191
+ */
192
+ private collectStaticChildInstances;
193
+ /**
194
+ * Return the concatenated text content of a list of IR nodes when
195
+ * every node is plain text; otherwise null.
196
+ */
197
+ private extractTextChildren;
198
+ /**
199
+ * Render JSX children to a Go-template-ready HTML fragment when
200
+ * children are non-text but produce purely-static HTML (no Go
201
+ * template actions). Returns null when:
202
+ * - children are absent or text-only (handled by extractTextChildren), or
203
+ * - the rendered fragment contains any `{{...}}` action — passing
204
+ * such a fragment through `template.HTML` and the parent's
205
+ * `{{.Children}}` would output the actions verbatim instead of
206
+ * evaluating them, which is worse than the existing
207
+ * "drop children" fallback. Dynamic / component-bearing children
208
+ * stay on the drop path until a re-evaluation hook lands.
209
+ */
210
+ private extractHtmlChildren;
211
+ private collectStaticChildInstancesRecursive;
212
+ /**
213
+ * Collect top-level (non-loop) JSX intrinsic-element spread slots
214
+ * from the IR (#1407). Loop-internal spreads are skipped — they
215
+ * emit the bag inline via the loop's iteration variable in
216
+ * `elementAttrEmitter.emitSpread`, so they don't need a Props
217
+ * struct field.
218
+ *
219
+ * Walks the IR tree, descending into elements, fragments,
220
+ * conditionals, providers, async, and components, but stopping at
221
+ * loop bodies. Each `IRElement.attrs[i].value` of kind `'spread'`
222
+ * that has a `slotId` becomes one `SpreadSlotInfo` entry.
223
+ */
224
+ private collectSpreadSlots;
225
+ /**
226
+ * Decide how a spread bag should be plumbed onto the Input/Props
227
+ * structs (#1407 follow-up). A bare-identifier spread that
228
+ * matches the component's `restPropsName` is open-ended (Go's
229
+ * static typing can't enumerate the keys), so the caller must
230
+ * supply the bag via an Input-side `map[string]any` field. Every
231
+ * other shape — signal getter, `propsObjectName`, plain
232
+ * propsParam, object literal — can be constructed inline in
233
+ * `NewXxxProps` from compile-time-known data.
234
+ *
235
+ * Reads `this.restPropsName` (stashed at `generate()` entry)
236
+ * rather than receiving the IR per-call — matches the existing
237
+ * `this.propsObjectName` / `this.componentName` storage pattern.
238
+ */
239
+ private classifySpreadBagSource;
240
+ private collectSpreadSlotsRecursive;
241
+ /**
242
+ * Parse a JS object-literal source text (the raw string captured
243
+ * for a signal's `initialValue` or a spread expression's argument)
244
+ * into a Go `map[string]any{...}` literal source (#1407).
245
+ *
246
+ * Supports a deliberately conservative subset so the Go output is
247
+ * a 1:1 translation of the JS source: string/number/boolean/null
248
+ * values keyed by identifier or string-literal keys. Returns null
249
+ * for unsupported shapes (nested objects, computed values,
250
+ * function calls, spread elements) — callers fall back to BF101.
251
+ */
252
+ private parseJsObjectLiteralToGoMap;
253
+ /**
254
+ * Build a Go expression for a JSX spread bag's initial value, to
255
+ * be placed inside `NewXxxProps`'s return literal (#1407).
256
+ *
257
+ * Supported shapes:
258
+ * - Signal-getter call (e.g. `attrs()`): look up the signal,
259
+ * parse its `initialValue` as a JS object literal, and emit a
260
+ * Go `map[string]any{...}` literal.
261
+ * - Bare identifier matching a destructured `propsParam` (e.g.
262
+ * `function({ extras }: P) { <el {...extras}/> }`): emit
263
+ * `in.<FieldName>` — works when the prop's Go type is a map
264
+ * type the bag is assignable to.
265
+ * - Bare identifier matching `propsObjectName` (SolidJS-style
266
+ * `function(props: P) { <el {...props}/> }`): enumerate the
267
+ * analyzer-extracted `propsParams` into an inline
268
+ * `map[string]any{...}` literal so each typed Input field
269
+ * surfaces as a bag key (#1407 follow-up).
270
+ * - Bare identifier matching `restPropsName` (the destructured-
271
+ * rest pattern `function({a, ...rest}: P) { <el {...rest}/> }`):
272
+ * emit `in.<slotId>` against the `map[string]any` Input field
273
+ * that `generateInputStruct` adds for `input-bag` slots. The
274
+ * caller (parent component or test harness) populates the
275
+ * bag with the open-ended rest values (#1407 follow-up).
276
+ *
277
+ * Returns null for unsupported shapes so the caller can raise a
278
+ * narrowed BF101 with the offending expression.
279
+ */
280
+ private buildSpreadInitializer;
281
+ /**
282
+ * Convert JavaScript initial value to Go value for NewXxxProps function.
283
+ * References to props params are converted to in.FieldName format.
284
+ */
285
+ private convertInitialValue;
286
+ /**
287
+ * Convert TypeInfo to Go type string.
288
+ * If type is unknown, tries to infer from defaultValue.
289
+ */
290
+ private typeInfoToGo;
291
+ /**
292
+ * Get signal's initial value as Go code.
293
+ * Handles both literal values (0, true, "str") and props references (initial).
294
+ *
295
+ * (#1423) When the signal references a prop via `props.X ?? N` and
296
+ * the caller hoisted a fallback variable for `X`, return the hoisted
297
+ * variable's name so the memo inherits the signal-time fallback.
298
+ */
299
+ private getSignalInitialValueAsGo;
300
+ /**
301
+ * Resolve dynamic prop value (e.g., signal/memo getter calls) to Go initial value.
302
+ * Handles expressions like `count()` → signal's initial value
303
+ */
304
+ /**
305
+ * Convert a template literal's parsed parts into a Go expression of
306
+ * type `string`, evaluated in `NewXxxProps` scope (where destructured
307
+ * prop refs resolve via `in.FieldName`). Returns null when any part
308
+ * is not representable in static Go code so the caller can fall back
309
+ * to `resolveDynamicPropValue` (which handles the simpler shapes).
310
+ *
311
+ * Supported parts:
312
+ * - `string`: emit as a Go string literal.
313
+ * - `lookup`: `${MAP[KEY]}` against a `Record<T, string>` literal —
314
+ * emit an IIFE that switches on the key prop and returns the
315
+ * matching case (empty when no case matches). The key must be a
316
+ * bare prop identifier today; other key shapes opt out.
317
+ *
318
+ * `ternary` is intentionally left unsupported — the existing
319
+ * element-attribute path handles it via Go template `{{if}}` syntax,
320
+ * and component-prop-via-ternary cases are rarer and can be added
321
+ * incrementally.
322
+ */
323
+ private templatePartsToGoCode;
324
+ private resolveDynamicPropValue;
325
+ /**
326
+ * Compute the initial value for a memo based on its computation and signal initial values.
327
+ * Handles simple cases like `() => count() * 2` → `in.Initial * 2`
328
+ * Also handles props.xxx patterns like `() => props.value * 10` → `in.Value * 10`
329
+ *
330
+ * (#1423) When `propFallbackVars` carries a hoisted variable for the
331
+ * referenced prop, substitute it for `in.FieldName` so the memo
332
+ * inherits the signal-time `??` fallback.
333
+ */
334
+ private computeMemoInitialValue;
335
+ /**
336
+ * Infer the Go type for a memo based on its computation and dependencies.
337
+ */
338
+ private inferMemoType;
339
+ /**
340
+ * Infer Go type from a JavaScript value literal.
341
+ */
342
+ private inferTypeFromValue;
343
+ /**
344
+ * (#1423) Hoisted-variable record for a prop with a signal-time
345
+ * `??` fallback. The same record is referenced from the prop field
346
+ * loop, the signal field loop, and the memo computation path.
347
+ */
348
+ private static EMPTY_PROP_FALLBACK_VARS;
349
+ /**
350
+ * (#1423) Walk signals to collect prop fallbacks. Skips props that
351
+ * already have a destructure-side default (`{ X = N }`) or signals
352
+ * whose fallback resolves to the type's Go zero value (no-op).
353
+ */
354
+ private collectPropFallbackVars;
355
+ /**
356
+ * (#1423) Parse a signal-time initial value of the form
357
+ * `props.X ?? <literal>` into the source prop name and the Go-formatted
358
+ * fallback. Returns null when:
359
+ * - the expression isn't a `??` against a property access on
360
+ * `propsObjectName`
361
+ * - the fallback isn't a simple literal `goPropDefault` can translate
362
+ *
363
+ * The Go-adapter equivalent of the same parse already done by the
364
+ * static evaluator in `ssr-defaults.ts` — duplicated here because we
365
+ * need the original prop reference (not just the resolved value)
366
+ * to honour caller-supplied non-zero inputs.
367
+ */
368
+ private extractPropFallback;
369
+ /**
370
+ * Extract prop name from a signal's initialValue that uses props.xxx pattern.
371
+ * e.g., "props.initial ?? 0" → "initial", "props.checked" → "checked"
372
+ */
373
+ private extractPropNameFromInitialValue;
374
+ /** Go common initialisms that should be fully uppercased (https://go.dev/wiki/CodeReviewComments#initialisms) */
375
+ private static GO_INITIALISMS;
376
+ /**
377
+ * (#1423) Go reserved keywords. When we hoist a local var named after
378
+ * a JSX prop, the prop name could collide with one of these — append
379
+ * `_` until the name is free.
380
+ */
381
+ private static GO_KEYWORDS;
382
+ private capitalizeFieldName;
383
+ /**
384
+ * Convert a JavaScript literal value to Go literal syntax.
385
+ */
386
+ /**
387
+ * Translate a JSX param default (e.g. `'default'`, `0`, `false`) into
388
+ * the corresponding Go literal. Returns null when the default is
389
+ * absent or non-trivial (objects, arrow functions, etc.) — those
390
+ * fall back to letting Go's zero value win.
391
+ */
392
+ private goPropDefault;
393
+ /**
394
+ * Wrap an `in.X` reference in a Go expression that substitutes
395
+ * `fallback` when the input is the zero value for its type. We pick
396
+ * the comparison based on the fallback literal's shape.
397
+ *
398
+ * Asymmetry on bool defaults is intentional and worth flagging:
399
+ * - For a `true` default, the generated expression is
400
+ * `(in.X || true)` — which is **always `true`**. Go has no
401
+ * unset-vs-explicit-false distinction at the struct-field level,
402
+ * so any caller wanting to thread `false` through has to set it
403
+ * after `NewXxxProps` rather than via the input struct.
404
+ * - For a `false` default, the Go zero value already matches, so
405
+ * the helper is a no-op (returns `ref` unchanged).
406
+ * Numeric `0` defaults are similarly indistinguishable from "unset"
407
+ * and pass through as the zero value; non-zero numeric defaults
408
+ * substitute, matching the JSX behavior of `(initial = 5) => ...`.
409
+ */
410
+ private applyGoFallback;
411
+ private goLiteral;
412
+ /**
413
+ * Public entry point for node rendering. Delegates to the shared
414
+ * `IRNodeEmitter` dispatcher (#1290 step 1); per-kind logic lives in
415
+ * the `IRNodeEmitter` methods below.
416
+ */
417
+ renderNode(node: IRNode, ctx?: GoRenderCtx): string;
418
+ emitElement(node: IRElement, _ctx: GoRenderCtx, _emit: EmitIRNode<GoRenderCtx>): string;
419
+ emitText(node: IRText): string;
420
+ emitExpression(node: IRExpression): string;
421
+ emitConditional(node: IRConditional, _ctx: GoRenderCtx, _emit: EmitIRNode<GoRenderCtx>): string;
422
+ emitLoop(node: IRLoop, _ctx: GoRenderCtx, _emit: EmitIRNode<GoRenderCtx>): string;
423
+ emitComponent(node: IRComponent, ctx: GoRenderCtx, _emit: EmitIRNode<GoRenderCtx>): string;
424
+ emitFragment(node: IRFragment, _ctx: GoRenderCtx, _emit: EmitIRNode<GoRenderCtx>): string;
425
+ emitSlot(node: IRSlot): string;
426
+ emitIfStatement(node: IRIfStatement, ctx: GoRenderCtx, _emit: EmitIRNode<GoRenderCtx>): string;
427
+ emitProvider(node: IRProvider, _ctx: GoRenderCtx, _emit: EmitIRNode<GoRenderCtx>): string;
428
+ emitAsync(node: IRAsync, _ctx: GoRenderCtx, _emit: EmitIRNode<GoRenderCtx>): string;
429
+ renderElement(element: IRElement): string;
430
+ renderExpression(expr: IRExpression): string;
431
+ /**
432
+ * Render a client-only conditional as comment markers.
433
+ * Used when @client directive is applied to an unsupported conditional.
434
+ * The condition is evaluated on the client side via insert().
435
+ */
436
+ private renderClientOnlyConditional;
437
+ /**
438
+ * Render a ParsedExpr to Go template syntax via the shared
439
+ * dispatcher (#1250 phase 1). The per-kind logic lives in the
440
+ * `ParsedExprEmitter` methods below; this method is a thin wrapper
441
+ * so existing call sites keep working.
442
+ */
443
+ private renderParsedExpr;
444
+ identifier(name: string): string;
445
+ literal(value: string | number | boolean | null, literalType: LiteralType): string;
446
+ call(callee: ParsedExpr, args: ParsedExpr[], emit: (e: ParsedExpr) => string): string;
447
+ member(object: ParsedExpr, property: string, _computed: boolean, emit: (e: ParsedExpr) => string): string;
448
+ binary(op: string, left: ParsedExpr, right: ParsedExpr, emit: (e: ParsedExpr) => string): string;
449
+ unary(op: string, argument: ParsedExpr, emit: (e: ParsedExpr) => string): string;
450
+ logical(op: '&&' | '||' | '??', left: ParsedExpr, right: ParsedExpr, emit: (e: ParsedExpr) => string): string;
451
+ conditional(test: ParsedExpr, consequent: ParsedExpr, alternate: ParsedExpr, emit: (e: ParsedExpr) => string): string;
452
+ templateLiteral(parts: TemplatePart[], emit: (e: ParsedExpr) => string): string;
453
+ arrowFn(param: string, _body: ParsedExpr, _emit: (e: ParsedExpr) => string): string;
454
+ arrayLiteral(elements: ParsedExpr[], emit: (e: ParsedExpr) => string): string;
455
+ higherOrder(method: HigherOrderMethod, object: ParsedExpr, param: string, predicate: ParsedExpr, emit: (e: ParsedExpr) => string): string;
456
+ arrayMethod(method: ArrayMethod, object: ParsedExpr, args: ParsedExpr[], emit: (e: ParsedExpr) => string): string;
457
+ sortMethod(method: 'sort' | 'toSorted', object: ParsedExpr, comparator: SortComparator, emit: (e: ParsedExpr) => string): string;
458
+ unsupported(raw: string, _reason: string): string;
459
+ /**
460
+ * Extract field name and negation from a simple predicate.
461
+ * t => t.done → { field: "Done", negated: false }
462
+ * t => !t.done → { field: "Done", negated: true }
463
+ */
464
+ private extractFieldPredicate;
465
+ /**
466
+ * Extract field name and value from an equality predicate.
467
+ * Extends extractFieldPredicate to also handle equality comparisons.
468
+ *
469
+ * t.done → { field: "Done", value: "true" }
470
+ * !t.done → { field: "Done", value: "false" }
471
+ * u.id === selectedId() → { field: "Id", value: <rendered expr> }
472
+ * selectedId() === u.id → same (supports both operand orders)
473
+ */
474
+ private extractEqualityPredicate;
475
+ /**
476
+ * Render a higher-order expression (filter, every, some, find, findIndex) to Go template.
477
+ * Returns null if the expression is not supported.
478
+ *
479
+ * @param expr - The higher-order expression
480
+ * @param renderArray - Function to render the array expression (allows recursion via different methods)
481
+ */
482
+ private renderHigherOrderExpr;
483
+ /**
484
+ * Render find()/findIndex() with complex predicates using {{range}}{{if}}...{{break}} blocks.
485
+ * Falls back from bf_find/bf_find_index when extractEqualityPredicate returns null.
486
+ * Reuses renderFilterExpr for condition rendering.
487
+ *
488
+ * @param expr - The higher-order find/findIndex expression
489
+ * @param renderArray - Function to render the array expression
490
+ * @param propertyAccess - Optional property to access on the found element (for find().property)
491
+ */
492
+ private renderFindTemplateBlock;
493
+ /**
494
+ * Render every()/some() with complex predicates using {{range}}{{if}} with variable reassignment.
495
+ * Falls back from bf_every/bf_some when extractFieldPredicate returns null.
496
+ * Reuses renderFilterExpr for condition rendering.
497
+ *
498
+ * every: start true, set false on first failure, break early
499
+ * some: start false, set true on first match, break early
500
+ *
501
+ * @param expr - The higher-order every/some expression
502
+ * @param renderArray - Function to render the array expression
503
+ */
504
+ private renderEverySomeTemplateBlock;
505
+ /**
506
+ * Negate a Go template condition.
507
+ * Wraps in `not (...)` when the condition is a Go function call (eq, ne, gt, etc.),
508
+ * otherwise uses `not condition`.
509
+ */
510
+ private negateGoCondition;
511
+ /**
512
+ * Render .length on a filter higher-order expression.
513
+ * e.g., todos().filter(t => !t.done).length → len (bf_filter .Todos "Done" false)
514
+ *
515
+ * @param filterExpr - The filter higher-order expression
516
+ * @param renderArray - Function to render the array expression
517
+ */
518
+ private renderFilterLengthExpr;
519
+ /**
520
+ * Render a predicate expression for use in Go template {{if}} conditions.
521
+ * Substitutes the loop parameter (e.g., 't' in 't.done') with dot notation.
522
+ */
523
+ private renderPredicateCondition;
524
+ /**
525
+ * Check if expression needs parentheses when used in and/or.
526
+ */
527
+ private needsParens;
528
+ /**
529
+ * Render block body filter into a single Go template condition.
530
+ *
531
+ * Example block body:
532
+ * ```
533
+ * filter(t => {
534
+ * const f = filter()
535
+ * if (f === 'active') return !t.done
536
+ * if (f === 'completed') return t.done
537
+ * return true
538
+ * })
539
+ * ```
540
+ *
541
+ * Becomes:
542
+ * ```
543
+ * or (and (eq $.Filter "active") (not .Done))
544
+ * (and (eq $.Filter "completed") .Done)
545
+ * (and (ne $.Filter "active") (ne $.Filter "completed"))
546
+ * ```
547
+ */
548
+ private renderBlockBodyCondition;
549
+ /**
550
+ * Recursively collect all return paths through the statements.
551
+ * Returns an array of ReturnPath objects.
552
+ */
553
+ private collectReturnPaths;
554
+ /**
555
+ * Build a condition for a single return path.
556
+ */
557
+ private buildSinglePathCondition;
558
+ /**
559
+ * Build an OR condition from multiple return paths.
560
+ */
561
+ private buildOrCondition;
562
+ /**
563
+ * Render multiple conditions combined with AND.
564
+ */
565
+ private renderConditionsAnd;
566
+ /**
567
+ * Unified method for rendering filter predicate expressions.
568
+ * Used for both expression body (t => !t.done) and block body filters.
569
+ *
570
+ * @param expr - The parsed expression to render
571
+ * @param param - The loop parameter name (e.g., 't' in filter(t => ...))
572
+ * @param localVarMap - Optional map of local variables to signal names (for block body)
573
+ */
574
+ private renderFilterExpr;
575
+ private renderFilterExprNode;
576
+ /**
577
+ * Check if a ParsedExpr will render as a Go template function call.
578
+ * Used to determine if parentheses are needed around the expression.
579
+ */
580
+ private isGoFunctionCall;
581
+ /**
582
+ * Render a branch of a conditional expression.
583
+ * String literals render as bare text (no quotes).
584
+ * Nested conditionals render as complete {{if}}...{{end}} blocks.
585
+ */
586
+ private renderConditionalBranch;
587
+ /**
588
+ * Check if a ParsedExpr renders to a Go template function call that needs parentheses.
589
+ * In Go templates, function calls like `len .X` or `bf_add .A .B` need parentheses
590
+ * when used as arguments to comparison operators (eq, gt, lt, etc.).
591
+ */
592
+ private needsParensInGoTemplate;
593
+ /**
594
+ * Convert a JS expression to Go template syntax.
595
+ */
596
+ private convertExpressionToGo;
597
+ /**
598
+ * Create a source location for error reporting.
599
+ */
600
+ private makeLoc;
601
+ private renderIfStatement;
602
+ renderConditional(cond: IRConditional): string;
603
+ /**
604
+ * Convert a JS condition to Go template condition syntax.
605
+ * Returns { condition, preamble } where preamble contains template blocks
606
+ * that must be emitted before the {{if}} (e.g., every/some range blocks).
607
+ */
608
+ private convertConditionToGo;
609
+ /**
610
+ * Render a ParsedExpr as a Go template condition.
611
+ */
612
+ private renderConditionExpr;
613
+ renderLoop(loop: IRLoop): string;
614
+ /**
615
+ * Find the first component child in a list of nodes
616
+ */
617
+ private findChildComponent;
618
+ renderComponent(comp: IRComponent, ctx?: {
619
+ isRootOfClientComponent?: boolean;
620
+ }): string;
621
+ /**
622
+ * Render a Portal component by adding its children to PortalCollector.
623
+ * Portal content is rendered at </body> instead of inline.
624
+ *
625
+ * For static content: uses simple string literal with Add()
626
+ * For dynamic content: uses bfPortalHTML() to parse and execute template string
627
+ */
628
+ private renderPortalComponent;
629
+ private renderFragment;
630
+ private renderSlot;
631
+ renderAsync(node: IRAsync): string;
632
+ private escapeGoString;
633
+ /**
634
+ * AttrValue lowering for intrinsic-element attributes (Go templates).
635
+ * Per-kind logic that used to live in a `switch (v.kind)` inside
636
+ * `renderAttributes`; routed through the shared dispatcher so a new
637
+ * AttrValue kind becomes a TS compile error here (#1290 step 2).
638
+ *
639
+ * Components have no equivalent AttrValueEmitter on this adapter:
640
+ * Go templates pass component-instance props as Go struct fields
641
+ * (`collectStaticChildInstances` builds them), not as string-emitted
642
+ * markup, so that path does not share a contract with the
643
+ * intrinsic-attribute one.
644
+ */
645
+ private readonly elementAttrEmitter;
646
+ private renderAttributes;
647
+ /**
648
+ * Replace `${EXPR}` JS-template-literal interpolations in a static
649
+ * string part with Go template actions (`{{<expr-as-go>}}`), and
650
+ * HTML-escape the surrounding literal text so embedded characters
651
+ * don't break the attribute quoting we render into.
652
+ *
653
+ * UnoCSS arbitrary-value classes like `[class*="size-"]:size-4`
654
+ * legitimately contain `"`, which would otherwise terminate the
655
+ * `class="..."` attribute early and produce invalid HTML / a
656
+ * `html/template` error at execution time.
657
+ *
658
+ * The interpolation parser is brace-depth aware: nested `{...}`
659
+ * inside an expression (object literals, nested template literals,
660
+ * etc.) are skipped past correctly so the closing brace of the
661
+ * outer `${...}` is found. An unterminated `${` falls back to
662
+ * literal text — better to output something than swallow it.
663
+ */
664
+ private substituteJsInterpolations;
665
+ /**
666
+ * HTML-attribute-safe escaping for double-quoted attribute values.
667
+ * `&`/`"`/`<` are non-negotiable — without them the surrounding
668
+ * `class="..."` quoting breaks (a real bug we hit with UnoCSS's
669
+ * `[class*="size-"]`). `>`/`'` are belt-and-suspenders: HTML5
670
+ * permits both inside double-quoted attrs, but Go's `html/template`
671
+ * lexer is contextual and we'd rather not bet on its edge cases
672
+ * matching ours forever.
673
+ */
674
+ private escapeAttrText;
675
+ private renderTemplateLiteralParts;
676
+ renderScopeMarker(_instanceIdExpr: string): string;
677
+ renderSlotMarker(slotId: string): string;
678
+ renderCondMarker(condId: string): string;
679
+ private wrapWithCondMarker;
680
+ }
681
+ export declare const goTemplateAdapter: GoTemplateAdapter;
682
+ export {};
683
+ //# sourceMappingURL=go-template-adapter.d.ts.map