@barefootjs/go-template 0.16.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/analysis/component-tree.d.ts +23 -0
- package/dist/adapter/analysis/component-tree.d.ts.map +1 -0
- package/dist/adapter/emit-context.d.ts +53 -0
- package/dist/adapter/emit-context.d.ts.map +1 -0
- package/dist/adapter/expr/helper-inline.d.ts +31 -0
- package/dist/adapter/expr/helper-inline.d.ts.map +1 -0
- package/dist/adapter/expr/url-builder.d.ts +23 -0
- package/dist/adapter/expr/url-builder.d.ts.map +1 -0
- package/dist/adapter/go-template-adapter.d.ts +302 -1071
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +3116 -2692
- package/dist/adapter/lib/compile-state.d.ts +142 -0
- package/dist/adapter/lib/compile-state.d.ts.map +1 -0
- package/dist/adapter/lib/constants.d.ts +11 -0
- package/dist/adapter/lib/constants.d.ts.map +1 -0
- package/dist/adapter/lib/go-emit.d.ts +126 -0
- package/dist/adapter/lib/go-emit.d.ts.map +1 -0
- package/dist/adapter/lib/go-naming.d.ts +39 -0
- package/dist/adapter/lib/go-naming.d.ts.map +1 -0
- package/dist/adapter/lib/ir-scope.d.ts +13 -0
- package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
- package/dist/adapter/lib/types.d.ts +165 -0
- package/dist/adapter/lib/types.d.ts.map +1 -0
- package/dist/adapter/memo/ctor-lowering.d.ts +39 -0
- package/dist/adapter/memo/ctor-lowering.d.ts.map +1 -0
- package/dist/adapter/memo/memo-compute.d.ts +173 -0
- package/dist/adapter/memo/memo-compute.d.ts.map +1 -0
- package/dist/adapter/memo/memo-type.d.ts +48 -0
- package/dist/adapter/memo/memo-type.d.ts.map +1 -0
- package/dist/adapter/memo/memo-value.d.ts +64 -0
- package/dist/adapter/memo/memo-value.d.ts.map +1 -0
- package/dist/adapter/memo/template-interp.d.ts +43 -0
- package/dist/adapter/memo/template-interp.d.ts.map +1 -0
- package/dist/adapter/props/prop-types.d.ts +31 -0
- package/dist/adapter/props/prop-types.d.ts.map +1 -0
- package/dist/adapter/spread/spread-codegen.d.ts +40 -0
- package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
- package/dist/adapter/type/type-codegen.d.ts +25 -0
- package/dist/adapter/type/type-codegen.d.ts.map +1 -0
- package/dist/adapter/value/parsed-literal-to-go.d.ts +17 -0
- package/dist/adapter/value/parsed-literal-to-go.d.ts.map +1 -0
- package/dist/adapter/value/value-lowering.d.ts +46 -0
- package/dist/adapter/value/value-lowering.d.ts.map +1 -0
- package/dist/build.js +3114 -2690
- package/dist/index.js +3116 -2692
- package/dist/test-render.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/derived-state-memo.test.ts +155 -84
- package/src/__tests__/go-template-adapter.test.ts +398 -54
- package/src/__tests__/lowering-plugin.test.ts +109 -0
- package/src/__tests__/query-href.test.ts +179 -0
- package/src/adapter/analysis/component-tree.ts +174 -0
- package/src/adapter/emit-context.ts +59 -0
- package/src/adapter/expr/helper-inline.ts +274 -0
- package/src/adapter/expr/url-builder.ts +123 -0
- package/src/adapter/go-template-adapter.ts +2189 -5320
- package/src/adapter/lib/compile-state.ts +181 -0
- package/src/adapter/lib/constants.ts +24 -0
- package/src/adapter/lib/go-emit.ts +309 -0
- package/src/adapter/lib/go-naming.ts +84 -0
- package/src/adapter/lib/ir-scope.ts +31 -0
- package/src/adapter/lib/types.ts +182 -0
- package/src/adapter/memo/ctor-lowering.ts +267 -0
- package/src/adapter/memo/memo-compute.ts +661 -0
- package/src/adapter/memo/memo-type.ts +93 -0
- package/src/adapter/memo/memo-value.ts +197 -0
- package/src/adapter/memo/template-interp.ts +246 -0
- package/src/adapter/props/prop-types.ts +84 -0
- package/src/adapter/spread/spread-codegen.ts +458 -0
- package/src/adapter/type/type-codegen.ts +95 -0
- package/src/adapter/value/parsed-literal-to-go.ts +94 -0
- package/src/adapter/value/value-lowering.ts +162 -0
- package/src/test-render.ts +48 -22
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inlining of component-scope arrow-const helpers at a call site.
|
|
3
|
+
*
|
|
4
|
+
* When a JSX expression calls a local `const f = (a) => …` helper, the Go
|
|
5
|
+
* adapter has no function to emit — it inlines the helper body with the call
|
|
6
|
+
* args substituted for the params, so the result lowers like any inline
|
|
7
|
+
* expression. The substitution is scope-blind, so the guards here reject bodies
|
|
8
|
+
* where a param replacement could be wrong.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { type ParsedExpr } from '@barefootjs/jsx'
|
|
12
|
+
|
|
13
|
+
import type { GoEmitContext } from '../emit-context.ts'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Inline a call to a local arrow-const helper.
|
|
17
|
+
*
|
|
18
|
+
* @param callParsed the call's already-built `ParsedExpr`. Must be a `call`
|
|
19
|
+
* node; inlining is declined without it.
|
|
20
|
+
* @returns the substituted body tree, or null when the expression isn't such a
|
|
21
|
+
* call or the body isn't substitution-safe (nested functions, helper-calling
|
|
22
|
+
* helpers, spread args, …)
|
|
23
|
+
*/
|
|
24
|
+
export function inlineLocalHelperCall(
|
|
25
|
+
ctx: GoEmitContext,
|
|
26
|
+
jsExpr: string,
|
|
27
|
+
callParsed?: ParsedExpr,
|
|
28
|
+
): ParsedExpr | null {
|
|
29
|
+
if (ctx.state.localHelperNames.size === 0) return null
|
|
30
|
+
// Fast path: skip unless the expression begins with a known helper name
|
|
31
|
+
// applied as a call (`<helper>(`); the real shape check is the tree
|
|
32
|
+
// inspection below.
|
|
33
|
+
const head = /^\s*([A-Za-z_$][\w$]*)\s*\(/.exec(jsExpr)
|
|
34
|
+
if (!head || !ctx.state.localHelperNames.has(head[1])) return null
|
|
35
|
+
|
|
36
|
+
if (!callParsed || callParsed.kind !== 'call' || callParsed.callee.kind !== 'identifier') {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
const calleeName = callParsed.callee.name
|
|
40
|
+
if (!ctx.state.localHelperNames.has(calleeName)) return null
|
|
41
|
+
|
|
42
|
+
const fnConst = ctx.state.localConstants.find(
|
|
43
|
+
c => c.name === calleeName && !c.isModule && c.parsed,
|
|
44
|
+
)
|
|
45
|
+
const arrow = fnConst?.parsed
|
|
46
|
+
if (!arrow || arrow.kind !== 'arrow') return null
|
|
47
|
+
if (arrow.params.length !== callParsed.args.length) return null
|
|
48
|
+
|
|
49
|
+
// The body must be a lowerable `ParsedExpr` — an `unsupported` body (e.g. a
|
|
50
|
+
// nested function / regex outside the narrow surface) refuses here.
|
|
51
|
+
const body = arrow.body
|
|
52
|
+
if (!body || body.kind === 'unsupported') return null
|
|
53
|
+
|
|
54
|
+
// Don't half-inline a helper that calls another local helper.
|
|
55
|
+
if (bodyCallsLocalHelper(ctx, body)) return null
|
|
56
|
+
|
|
57
|
+
// Refuse a body containing a method call (`x.foo(…)`). The structural body
|
|
58
|
+
// tree models it as a generic `call`, but `parseExpression` (which the rest
|
|
59
|
+
// of the lowering is keyed to) folds the string / array method family
|
|
60
|
+
// (`.replace`, `.includes`, `.filter`, …) into specialised `array-method` /
|
|
61
|
+
// `higher-order` nodes — so lowering this tree directly would diverge.
|
|
62
|
+
// (`x()` with an identifier callee — `params()` — is fine.)
|
|
63
|
+
if (bodyHasMethodCall(body)) return null
|
|
64
|
+
|
|
65
|
+
const subs = new Map<string, ParsedExpr>()
|
|
66
|
+
for (let i = 0; i < arrow.params.length; i++) {
|
|
67
|
+
subs.set(arrow.params[i], callParsed.args[i])
|
|
68
|
+
}
|
|
69
|
+
// Reject bodies where a param name appears as an object shorthand key
|
|
70
|
+
// (`{ k }`, which can't be rewritten to `{ (arg) }`).
|
|
71
|
+
if (!isSubstituteSafeBody(body, new Set(subs.keys()))) return null
|
|
72
|
+
return substituteHelperParams(body, subs)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Guard for `substituteHelperParams`: an `object-literal` lowers opaquely from
|
|
77
|
+
* its `raw` source string, so a param referenced ANYWHERE inside it survives
|
|
78
|
+
* un-substituted — a shorthand key (`{ p }`) or a value position (`{ x: p }`,
|
|
79
|
+
* `{ x: f(p) }`) — and would emit the param's original name instead of the call
|
|
80
|
+
* arg. A body is substitute-safe only when no object literal it contains
|
|
81
|
+
* references any param.
|
|
82
|
+
*/
|
|
83
|
+
function isSubstituteSafeBody(body: ParsedExpr, paramNames: ReadonlySet<string>): boolean {
|
|
84
|
+
// True when `n`'s subtree references any param (handling object-literal
|
|
85
|
+
// shorthand keys, which carry the param name on the key rather than a value).
|
|
86
|
+
const referencesParam = (n: ParsedExpr): boolean => {
|
|
87
|
+
if (n.kind === 'identifier') return paramNames.has(n.name)
|
|
88
|
+
if (n.kind === 'object-literal') {
|
|
89
|
+
return n.properties.some(
|
|
90
|
+
p => (p.shorthand && paramNames.has(p.key)) || referencesParam(p.value),
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
let hit = false
|
|
94
|
+
forEachValueChild(n, c => {
|
|
95
|
+
if (referencesParam(c)) hit = true
|
|
96
|
+
})
|
|
97
|
+
return hit
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let safe = true
|
|
101
|
+
const visit = (n: ParsedExpr): void => {
|
|
102
|
+
if (!safe) return
|
|
103
|
+
if (n.kind === 'object-literal') {
|
|
104
|
+
// The whole object literal lowers from `raw`; a param mentioned anywhere
|
|
105
|
+
// inside can't be substituted — decline the body.
|
|
106
|
+
if (referencesParam(n)) safe = false
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
forEachValueChild(n, visit)
|
|
110
|
+
}
|
|
111
|
+
visit(body)
|
|
112
|
+
return safe
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* True when `body` contains a call to a *local* (component-scope) arrow helper
|
|
117
|
+
* const — the signal that inlining `body` here would only push the problem to
|
|
118
|
+
* another un-lowered helper (e.g. `sortHref`'s body calls `hrefFor`).
|
|
119
|
+
*/
|
|
120
|
+
function bodyCallsLocalHelper(ctx: GoEmitContext, body: ParsedExpr): boolean {
|
|
121
|
+
let found = false
|
|
122
|
+
const visit = (n: ParsedExpr): void => {
|
|
123
|
+
if (found) return
|
|
124
|
+
if (n.kind === 'call' && n.callee.kind === 'identifier') {
|
|
125
|
+
const name = n.callee.name
|
|
126
|
+
if (ctx.state.localConstants.some(c => c.name === name && !c.isModule && c.containsArrow)) {
|
|
127
|
+
found = true
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
forEachValueChild(n, visit)
|
|
132
|
+
}
|
|
133
|
+
visit(body)
|
|
134
|
+
return found
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* True when `body` contains a method call (`x.foo(…)` — a `call` whose callee
|
|
139
|
+
* is a `member`); such a body must not be lowered from this tree. See the guard
|
|
140
|
+
* in `inlineLocalHelperCall` for why.
|
|
141
|
+
*/
|
|
142
|
+
function bodyHasMethodCall(body: ParsedExpr): boolean {
|
|
143
|
+
let found = false
|
|
144
|
+
const visit = (n: ParsedExpr): void => {
|
|
145
|
+
if (found) return
|
|
146
|
+
if (n.kind === 'call' && n.callee.kind === 'member') {
|
|
147
|
+
found = true
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
forEachValueChild(n, visit)
|
|
151
|
+
}
|
|
152
|
+
visit(body)
|
|
153
|
+
return found
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Re-build `body` with each identifier named in `subs` replaced by the
|
|
158
|
+
* substitution's `ParsedExpr` subtree. The substitution is the arg subtree
|
|
159
|
+
* itself, so operator precedence survives without parenthesisation (the tree
|
|
160
|
+
* *is* the precedence). Non-value identifier positions — the property NAME in
|
|
161
|
+
* `a.b`, a plain `{ k: … }` key — are not visited, so a param sharing a name
|
|
162
|
+
* with a member or key is left untouched.
|
|
163
|
+
*/
|
|
164
|
+
export function substituteHelperParams(
|
|
165
|
+
body: ParsedExpr,
|
|
166
|
+
subs: ReadonlyMap<string, ParsedExpr>,
|
|
167
|
+
): ParsedExpr {
|
|
168
|
+
const go = (n: ParsedExpr): ParsedExpr => {
|
|
169
|
+
switch (n.kind) {
|
|
170
|
+
case 'identifier': {
|
|
171
|
+
const sub = subs.get(n.name)
|
|
172
|
+
return sub !== undefined ? sub : n
|
|
173
|
+
}
|
|
174
|
+
// `object-literal` / `unsupported` lower from their `raw` string, and a
|
|
175
|
+
// `regex` carries its exact source text — re-lowering structured children
|
|
176
|
+
// has no effect, so leave as-is (a param inside an object literal is
|
|
177
|
+
// rejected by the shorthand guard upstream).
|
|
178
|
+
case 'literal':
|
|
179
|
+
case 'object-literal':
|
|
180
|
+
case 'unsupported':
|
|
181
|
+
case 'regex':
|
|
182
|
+
return n
|
|
183
|
+
case 'member':
|
|
184
|
+
return { ...n, object: go(n.object) }
|
|
185
|
+
case 'index-access':
|
|
186
|
+
return { ...n, object: go(n.object), index: go(n.index) }
|
|
187
|
+
case 'call':
|
|
188
|
+
return { ...n, callee: go(n.callee), args: n.args.map(go) }
|
|
189
|
+
case 'binary':
|
|
190
|
+
return { ...n, left: go(n.left), right: go(n.right) }
|
|
191
|
+
case 'logical':
|
|
192
|
+
return { ...n, left: go(n.left), right: go(n.right) }
|
|
193
|
+
case 'unary':
|
|
194
|
+
return { ...n, argument: go(n.argument) }
|
|
195
|
+
case 'conditional':
|
|
196
|
+
return { ...n, test: go(n.test), consequent: go(n.consequent), alternate: go(n.alternate) }
|
|
197
|
+
case 'template-literal':
|
|
198
|
+
return {
|
|
199
|
+
...n,
|
|
200
|
+
parts: n.parts.map(p =>
|
|
201
|
+
p.type === 'expression' ? { type: 'expression', expr: go(p.expr) } : p,
|
|
202
|
+
),
|
|
203
|
+
}
|
|
204
|
+
case 'array-literal':
|
|
205
|
+
return { ...n, elements: n.elements.map(go) }
|
|
206
|
+
case 'arrow':
|
|
207
|
+
// A nested arrow's params shadow any same-named outer helper param, but
|
|
208
|
+
// the inline body-method guard (`bodyHasMethodCall`) already declined
|
|
209
|
+
// bodies whose substitution could be wrong; re-lower the body verbatim.
|
|
210
|
+
return { ...n, body: go(n.body) }
|
|
211
|
+
case 'array-method':
|
|
212
|
+
// `.flat` carries no `args`; the generic variant does. Re-lower both
|
|
213
|
+
// `object` and any `args`.
|
|
214
|
+
return n.method === 'flat'
|
|
215
|
+
? { ...n, object: go(n.object) }
|
|
216
|
+
: { ...n, object: go(n.object), args: n.args.map(go) }
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return go(body)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Visit the value-position `ParsedExpr` children of `n` (the property name in
|
|
224
|
+
* `a.b` and a plain `{ k: v }` key are skipped — they aren't value positions;
|
|
225
|
+
* `object-literal` is handled by the caller because its shorthand keys carry a
|
|
226
|
+
* guard).
|
|
227
|
+
*/
|
|
228
|
+
function forEachValueChild(n: ParsedExpr, visit: (c: ParsedExpr) => void): void {
|
|
229
|
+
switch (n.kind) {
|
|
230
|
+
case 'identifier':
|
|
231
|
+
case 'literal':
|
|
232
|
+
case 'unsupported':
|
|
233
|
+
case 'object-literal':
|
|
234
|
+
case 'regex':
|
|
235
|
+
return
|
|
236
|
+
case 'member':
|
|
237
|
+
visit(n.object)
|
|
238
|
+
return
|
|
239
|
+
case 'index-access':
|
|
240
|
+
visit(n.object)
|
|
241
|
+
visit(n.index)
|
|
242
|
+
return
|
|
243
|
+
case 'call':
|
|
244
|
+
visit(n.callee)
|
|
245
|
+
n.args.forEach(visit)
|
|
246
|
+
return
|
|
247
|
+
case 'binary':
|
|
248
|
+
case 'logical':
|
|
249
|
+
visit(n.left)
|
|
250
|
+
visit(n.right)
|
|
251
|
+
return
|
|
252
|
+
case 'unary':
|
|
253
|
+
visit(n.argument)
|
|
254
|
+
return
|
|
255
|
+
case 'conditional':
|
|
256
|
+
visit(n.test)
|
|
257
|
+
visit(n.consequent)
|
|
258
|
+
visit(n.alternate)
|
|
259
|
+
return
|
|
260
|
+
case 'template-literal':
|
|
261
|
+
for (const p of n.parts) if (p.type === 'expression') visit(p.expr)
|
|
262
|
+
return
|
|
263
|
+
case 'array-literal':
|
|
264
|
+
n.elements.forEach(visit)
|
|
265
|
+
return
|
|
266
|
+
case 'arrow':
|
|
267
|
+
visit(n.body)
|
|
268
|
+
return
|
|
269
|
+
case 'array-method':
|
|
270
|
+
visit(n.object)
|
|
271
|
+
if (n.method !== 'flat') n.args.forEach(visit)
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Go lowering of helper calls to template expressions. Renders backend-neutral
|
|
3
|
+
* `LoweringNode`s produced by registered lowering plugins (#2057) — one uniform
|
|
4
|
+
* path for both userland plugins and built-ins like `queryHref` (a default plugin
|
|
5
|
+
* that lowers to `bf_query`, #2042). Every node funnels through
|
|
6
|
+
* `renderLoweringNode`, which maps a logical helper id to its Go helper; the
|
|
7
|
+
* adapter carries no per-API recognition branch of its own.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
type LoweringNode,
|
|
12
|
+
type ParsedExpr,
|
|
13
|
+
parseExpression,
|
|
14
|
+
stringifyParsedExpr,
|
|
15
|
+
} from '@barefootjs/jsx'
|
|
16
|
+
|
|
17
|
+
import type { GoEmitContext } from '../emit-context.ts'
|
|
18
|
+
import { wrapIfMultiToken } from '../lib/go-emit.ts'
|
|
19
|
+
|
|
20
|
+
/** Logical helper id → Go template helper name. */
|
|
21
|
+
const GO_HELPER_NAMES: Record<string, string> = { query: 'bf_query' }
|
|
22
|
+
|
|
23
|
+
const BOOL_COMPARISON_OPS: ReadonlySet<string> = new Set([
|
|
24
|
+
'==', '===', '!=', '!==', '<', '>', '<=', '>=',
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Lower an expression to a Go *bool* for `bf_query`'s `include` argument. A
|
|
29
|
+
* comparison / negation / bool-literal already yields a bool
|
|
30
|
+
* (`convertConditionToGo`); anything else is JS string-truthiness, lowered to
|
|
31
|
+
* `ne <value> ""`. The arg must be a real bool — `bf_query` type-asserts it, so
|
|
32
|
+
* Go-template truthiness (`{{if x}}`) is not enough.
|
|
33
|
+
*
|
|
34
|
+
* The `ne <value> ""` path models *string* truthiness, which is the query
|
|
35
|
+
* domain: `queryHref` values are strings (`QueryParamValue`). A guard on a
|
|
36
|
+
* non-string value is outside the supported surface; the type-assert on
|
|
37
|
+
* `bf_query`'s include arg surfaces it as a build error rather than silently
|
|
38
|
+
* mis-including. (#2041 review.)
|
|
39
|
+
*/
|
|
40
|
+
function lowerUrlGuard(ctx: GoEmitContext, g: ParsedExpr): string {
|
|
41
|
+
// Comparisons, `!x`, and bool literals lower to a Go bool via the condition
|
|
42
|
+
// lowering. `&&` / `||` do NOT qualify: Go's `and`/`or` return one of their
|
|
43
|
+
// operands (a string for a truthiness guard like `tag && other`), not a
|
|
44
|
+
// bool — so they take the truthiness-wrap path below, yielding
|
|
45
|
+
// `ne (and …) ""`, an actual bool.
|
|
46
|
+
const isBoolShape =
|
|
47
|
+
(g.kind === 'binary' && BOOL_COMPARISON_OPS.has(g.op)) ||
|
|
48
|
+
(g.kind === 'unary' && g.op === '!') ||
|
|
49
|
+
(g.kind === 'literal' && g.literalType === 'boolean')
|
|
50
|
+
if (isBoolShape) {
|
|
51
|
+
return ctx.convertConditionToGo(stringifyParsedExpr(g), g).condition
|
|
52
|
+
}
|
|
53
|
+
const valueGo = wrapIfMultiToken(ctx.convertExpressionToGo(stringifyParsedExpr(g), undefined, g))
|
|
54
|
+
return `ne ${valueGo} ""`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Lower a helper call to a Go template expression, or null when no registered
|
|
59
|
+
* plugin recognises it (→ the generic lowering). Matchers — including the
|
|
60
|
+
* built-in `queryHref` plugin (#2042) — are bound to the component metadata at
|
|
61
|
+
* init (`ctx.state.loweringMatchers`), so this is one uniform loop with no
|
|
62
|
+
* per-API branch.
|
|
63
|
+
*
|
|
64
|
+
* Cheap-out: no active matchers → null; else reuse `preParsed` when it's already
|
|
65
|
+
* a call, otherwise a regex pre-filter gates the parse so non-call expressions
|
|
66
|
+
* never pay for `parseExpression`.
|
|
67
|
+
*/
|
|
68
|
+
export function lowerRegisteredCall(
|
|
69
|
+
ctx: GoEmitContext,
|
|
70
|
+
jsExpr: string,
|
|
71
|
+
preParsed?: ParsedExpr,
|
|
72
|
+
): string | null {
|
|
73
|
+
const matchers = ctx.state.loweringMatchers
|
|
74
|
+
if (matchers.length === 0) return null
|
|
75
|
+
|
|
76
|
+
let call: ParsedExpr | undefined = preParsed?.kind === 'call' ? preParsed : undefined
|
|
77
|
+
if (!call) {
|
|
78
|
+
if (!/^\s*[A-Za-z_$][\w$]*\s*\(/.test(jsExpr)) return null
|
|
79
|
+
const parsed = parseExpression(jsExpr)
|
|
80
|
+
if (parsed.kind !== 'call') return null
|
|
81
|
+
call = parsed
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const matcher of matchers) {
|
|
85
|
+
const node = matcher(call.callee, call.args)
|
|
86
|
+
if (!node) continue
|
|
87
|
+
const rendered = renderLoweringNode(ctx, node)
|
|
88
|
+
if (rendered !== null) return rendered
|
|
89
|
+
}
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Render a backend-neutral lowering node to a Go template expression, or null
|
|
95
|
+
* when the node's `helper` has no Go mapping — the caller then declines
|
|
96
|
+
* (generic lowering → BF101), rather than emitting the raw helper id, which
|
|
97
|
+
* would be invalid Go. Adapters must switch on `helper`; an unknown one is not
|
|
98
|
+
* rendered.
|
|
99
|
+
*/
|
|
100
|
+
function renderLoweringNode(ctx: GoEmitContext, node: LoweringNode): string | null {
|
|
101
|
+
const helper = GO_HELPER_NAMES[node.helper]
|
|
102
|
+
if (!helper) return null
|
|
103
|
+
const lowerExpr = (n: ParsedExpr): string =>
|
|
104
|
+
ctx.convertExpressionToGo(stringifyParsedExpr(n), undefined, n)
|
|
105
|
+
if (node.kind === 'helper-call') {
|
|
106
|
+
const args = node.args.map(a => wrapIfMultiToken(lowerExpr(a)))
|
|
107
|
+
return [helper, ...args].join(' ')
|
|
108
|
+
}
|
|
109
|
+
// guard-list — `queryHref`-shaped. Inclusion mirrors the client exactly, where
|
|
110
|
+
// every param value is a STRING (`QueryParamValue`): bf_query drops an
|
|
111
|
+
// included-but-empty value and appends array members.
|
|
112
|
+
// - plain `key: v` (guard null) → `(true) "key" v`
|
|
113
|
+
// - conditional `key: cond ? a : <omit>` → `(<cond>) "key" a`, where the
|
|
114
|
+
// non-empty check is done by bf_query, not folded into the guard.
|
|
115
|
+
const parts: string[] = [wrapIfMultiToken(lowerExpr(node.base))]
|
|
116
|
+
for (const t of node.triples) {
|
|
117
|
+
const includeGo = t.guard === null ? 'true' : lowerUrlGuard(ctx, t.guard)
|
|
118
|
+
parts.push(`(${includeGo})`)
|
|
119
|
+
parts.push(JSON.stringify(t.key))
|
|
120
|
+
parts.push(wrapIfMultiToken(lowerExpr(t.value)))
|
|
121
|
+
}
|
|
122
|
+
return `${helper} ${parts.join(' ')}`
|
|
123
|
+
}
|