@barefootjs/go-template 0.34.0 → 0.35.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/expr/url-builder.d.ts +37 -1
- package/dist/adapter/expr/url-builder.d.ts.map +1 -1
- package/dist/adapter/go-template-adapter.d.ts +18 -1
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +59 -28
- package/dist/adapter/lib/compile-state.d.ts +20 -0
- package/dist/adapter/lib/compile-state.d.ts.map +1 -1
- package/dist/conformance-pins.d.ts.map +1 -1
- package/dist/index.js +63 -35
- package/dist/render-divergences.d.ts.map +1 -1
- package/dist/vite.js +147 -56
- package/package.json +5 -5
- package/src/__tests__/go-template-adapter.test.ts +167 -7
- package/src/adapter/expr/url-builder.ts +63 -28
- package/src/adapter/go-template-adapter.ts +105 -42
- package/src/adapter/lib/compile-state.ts +22 -0
- package/src/conformance-pins.ts +13 -7
- package/src/render-divergences.ts +1 -6
|
@@ -10,13 +10,14 @@
|
|
|
10
10
|
import {
|
|
11
11
|
type LoweringNode,
|
|
12
12
|
type ParsedExpr,
|
|
13
|
+
type TemplatePart,
|
|
13
14
|
parseExpression,
|
|
14
15
|
stringifyParsedExpr,
|
|
15
16
|
isValidHelperId,
|
|
16
17
|
} from '@barefootjs/jsx'
|
|
17
18
|
|
|
18
19
|
import type { GoEmitContext } from '../emit-context.ts'
|
|
19
|
-
import { wrapIfMultiToken } from '../lib/go-emit.ts'
|
|
20
|
+
import { escapeGoString, wrapIfMultiToken } from '../lib/go-emit.ts'
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Logical helper id → Go template helper name. `bf_<helper>` is a formula,
|
|
@@ -61,7 +62,7 @@ function lowerUrlGuard(ctx: GoEmitContext, g: ParsedExpr): string {
|
|
|
61
62
|
if (isBoolShape) {
|
|
62
63
|
return ctx.convertConditionToGo(stringifyParsedExpr(g), g).condition
|
|
63
64
|
}
|
|
64
|
-
const valueGo =
|
|
65
|
+
const valueGo = lowerValueOperand(ctx, g)
|
|
65
66
|
return `ne ${valueGo} ""`
|
|
66
67
|
}
|
|
67
68
|
|
|
@@ -91,21 +92,63 @@ export function lowerTernary(
|
|
|
91
92
|
alternate: ParsedExpr,
|
|
92
93
|
): string {
|
|
93
94
|
const t = lowerTernaryTest(ctx, test)
|
|
94
|
-
return `(bf_ternary ${t} ${
|
|
95
|
+
return `(bf_ternary ${t} ${lowerValueOperand(ctx, consequent)} ${lowerValueOperand(ctx, alternate)})`
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
/**
|
|
98
|
-
*
|
|
99
|
-
* `
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
99
|
+
* Lower a template literal to ONE Go pipeline VALUE (#2863): a left fold of
|
|
100
|
+
* `bf_concat_str` over the parts — static text as an escaped Go string
|
|
101
|
+
* literal, each interpolation as its own value operand. The TEXT-position
|
|
102
|
+
* form (`GoTemplateAdapter.templateLiteral()`: literal text interleaved with
|
|
103
|
+
* `{{…}}` actions) is only legal where the result is spliced directly into
|
|
104
|
+
* markup; inside another action's argument list (a `bf_ternary` branch, a
|
|
105
|
+
* helper-call arg, a `bf_query` guard value, …) Go rejects nested `{{`/`}}`
|
|
106
|
+
* delimiters ("unexpected \"{\" in operand"). This is the Go twin of the
|
|
107
|
+
* concatenation every other DSL adapter already emits for the same nested
|
|
108
|
+
* shape (e.g. Jinja/Twig `bf.string(a) ~ '–' ~ bf.string(b)`) — Go was the
|
|
109
|
+
* lone outlier because `text/template` has no expression-level string
|
|
110
|
+
* concatenation operator of its own, only this runtime helper (already used
|
|
111
|
+
* by `binary()`'s string-typed `+`, #2168).
|
|
112
|
+
*
|
|
113
|
+
* A single-part literal (`` `${x}` `` alone) reduces to that one value with
|
|
114
|
+
* no `bf_concat_str` wrapper — mirrors the sibling single-part unwrap at the
|
|
115
|
+
* child-prop call site. An all-static literal (already reduced to a plain
|
|
116
|
+
* `literal` ParsedExpr by the parser in practice, but handled here too)
|
|
117
|
+
* folds to one escaped string.
|
|
104
118
|
*/
|
|
105
|
-
function
|
|
119
|
+
export function lowerTemplateLiteralValue(ctx: GoEmitContext, parts: readonly TemplatePart[]): string {
|
|
120
|
+
const terms: string[] = []
|
|
121
|
+
for (const part of parts) {
|
|
122
|
+
if (part.type === 'string') {
|
|
123
|
+
if (part.value !== '') terms.push(`"${escapeGoString(part.value)}"`)
|
|
124
|
+
} else {
|
|
125
|
+
terms.push(lowerValueOperand(ctx, part.expr))
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (terms.length === 0) return '""'
|
|
129
|
+
return terms.slice(1).reduce((acc, t) => `(bf_concat_str ${acc} ${t})`, terms[0])
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The single door for "this ParsedExpr sits in Go pipeline ARGUMENT
|
|
134
|
+
* position" — a `bf_ternary` branch, a ternary/guard test's value form, a
|
|
135
|
+
* helper-call arg, a `bf_query` base/guard/value. A nested ternary recurses
|
|
136
|
+
* to another `bf_ternary`; a template literal folds through
|
|
137
|
+
* `lowerTemplateLiteralValue` (#2863) rather than the generic expression
|
|
138
|
+
* path, which would otherwise return the TEXT-position mixed
|
|
139
|
+
* literal-text-plus-`{{…}}`-actions form; anything else lowers to its Go
|
|
140
|
+
* value (parenthesised when multi-token so it stays one argument). String
|
|
141
|
+
* branches become quoted, html/template-escaped values — not the bare
|
|
142
|
+
* unquoted text the old `{{if}}` fragment path emitted — which is exactly
|
|
143
|
+
* right for the value positions this is reached for.
|
|
144
|
+
*/
|
|
145
|
+
export function lowerValueOperand(ctx: GoEmitContext, n: ParsedExpr): string {
|
|
106
146
|
if (n.kind === 'conditional') {
|
|
107
147
|
return lowerTernary(ctx, n.test, n.consequent, n.alternate)
|
|
108
148
|
}
|
|
149
|
+
if (n.kind === 'template-literal') {
|
|
150
|
+
return wrapIfMultiToken(lowerTemplateLiteralValue(ctx, n.parts))
|
|
151
|
+
}
|
|
109
152
|
return wrapIfMultiToken(ctx.convertExpressionToGo(stringifyParsedExpr(n), undefined, n))
|
|
110
153
|
}
|
|
111
154
|
|
|
@@ -120,7 +163,7 @@ function lowerTernaryOperand(ctx: GoEmitContext, n: ParsedExpr): string {
|
|
|
120
163
|
* counterpart of `lowerUrlGuard`'s string-only `ne <value> ""`.
|
|
121
164
|
*/
|
|
122
165
|
function lowerTernaryTest(ctx: GoEmitContext, test: ParsedExpr): string {
|
|
123
|
-
const go =
|
|
166
|
+
const go = lowerValueOperand(ctx, test)
|
|
124
167
|
const isBoolShape =
|
|
125
168
|
(test.kind === 'binary' && BOOL_COMPARISON_OPS.has(test.op)) ||
|
|
126
169
|
(test.kind === 'unary' && test.op === '!') ||
|
|
@@ -280,22 +323,14 @@ function ternaryHasQueryBranch(
|
|
|
280
323
|
function renderLoweringNode(ctx: GoEmitContext, node: LoweringNode): string | null {
|
|
281
324
|
const helper = goHelperName(node.helper)
|
|
282
325
|
if (!helper) return null
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
//
|
|
286
|
-
// `
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
// generic `lowerExpr`, whose `conditional()` dispatch now reaches the very
|
|
290
|
-
// same helper) keeps the arg lowering self-contained and its intent local;
|
|
291
|
-
// `lowerTernary` itself right-folds a nested chain (the #2324 union stage's
|
|
292
|
-
// locale→pattern table) into `(bf_ternary <cond> <a> (bf_ternary …))`.
|
|
293
|
-
const lowerArg = (n: ParsedExpr): string =>
|
|
294
|
-
n.kind === 'conditional'
|
|
295
|
-
? lowerTernary(ctx, n.test, n.consequent, n.alternate)
|
|
296
|
-
: wrapIfMultiToken(lowerExpr(n))
|
|
326
|
+
// Every argument here is Go pipeline ARGUMENT position — a `conditional`
|
|
327
|
+
// lowers to `(bf_ternary …)`, a `template-literal` folds through
|
|
328
|
+
// `bf_concat_str` (#2863) rather than leaking the TEXT-position
|
|
329
|
+
// `{{…}}`-actions form into this call's argument list, anything else takes
|
|
330
|
+
// the generic value path. `lowerValueOperand` is the one shared door every
|
|
331
|
+
// such position in this file now goes through.
|
|
297
332
|
if (node.kind === 'helper-call') {
|
|
298
|
-
const args = node.args.map(a =>
|
|
333
|
+
const args = node.args.map(a => lowerValueOperand(ctx, a))
|
|
299
334
|
return [helper, ...args].join(' ')
|
|
300
335
|
}
|
|
301
336
|
// guard-list — `queryHref`-shaped. Inclusion mirrors the client exactly, where
|
|
@@ -304,12 +339,12 @@ function renderLoweringNode(ctx: GoEmitContext, node: LoweringNode): string | nu
|
|
|
304
339
|
// - plain `key: v` (guard null) → `(true) "key" v`
|
|
305
340
|
// - conditional `key: cond ? a : <omit>` → `(<cond>) "key" a`, where the
|
|
306
341
|
// non-empty check is done by bf_query, not folded into the guard.
|
|
307
|
-
const parts: string[] = [
|
|
342
|
+
const parts: string[] = [lowerValueOperand(ctx, node.base)]
|
|
308
343
|
for (const t of node.triples) {
|
|
309
344
|
const includeGo = t.guard === null ? 'true' : lowerUrlGuard(ctx, t.guard)
|
|
310
345
|
parts.push(`(${includeGo})`)
|
|
311
346
|
parts.push(JSON.stringify(t.key))
|
|
312
|
-
parts.push(
|
|
347
|
+
parts.push(lowerValueOperand(ctx, t.value))
|
|
313
348
|
}
|
|
314
349
|
return `${helper} ${parts.join(' ')}`
|
|
315
350
|
}
|
|
@@ -80,6 +80,9 @@ import {
|
|
|
80
80
|
evaluateStaticLiteral,
|
|
81
81
|
BindingScope,
|
|
82
82
|
buildImportAliasMap,
|
|
83
|
+
resolveGetterAliases,
|
|
84
|
+
collectAliasableGetterNames,
|
|
85
|
+
resolveBodyDestructuredPropAliases,
|
|
83
86
|
} from '@barefootjs/jsx'
|
|
84
87
|
import { findInterpolationEnd } from '@barefootjs/jsx/scanner'
|
|
85
88
|
import { BF_REGION, escapeHtml, resolveJsxChildrenProp } from '@barefootjs/shared'
|
|
@@ -127,7 +130,7 @@ import { analyzeBakeableStaticChildLoop, scalarToGoLiteral, type BakedStaticChil
|
|
|
127
130
|
import { analyzeBakeableStaticElementLoop } from "./analysis/static-element-loop-bake.ts"
|
|
128
131
|
import type { GoEmitContext } from "./emit-context.ts"
|
|
129
132
|
import { inlineLocalHelperCall } from "./expr/helper-inline.ts"
|
|
130
|
-
import { lowerRegisteredAttrCall, lowerRegisteredCall, lowerRegisteredCallNode, lowerTernary } from "./expr/url-builder.ts"
|
|
133
|
+
import { lowerRegisteredAttrCall, lowerRegisteredCall, lowerRegisteredCallNode, lowerTemplateLiteralValue, lowerTernary, lowerValueOperand } from "./expr/url-builder.ts"
|
|
131
134
|
import {
|
|
132
135
|
convertInitialValue,
|
|
133
136
|
jsLiteralToGo,
|
|
@@ -465,6 +468,23 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
465
468
|
)
|
|
466
469
|
this.state.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants)
|
|
467
470
|
this.state.localConstants = ir.metadata.localConstants ?? []
|
|
471
|
+
// #2813: precompute which local consts are bare alias-hop chains onto a
|
|
472
|
+
// signal/memo getter, so `rootFieldRef` can route a call through the
|
|
473
|
+
// getter's OWN field instead of registering a separate, never-seeded
|
|
474
|
+
// field under the alias's name. Env-signal getters are excluded — they
|
|
475
|
+
// resolve through `searchParamsFieldRef`, not a seeded root field.
|
|
476
|
+
{
|
|
477
|
+
const getterNames = collectAliasableGetterNames(ir.metadata.signals ?? [], ir.metadata.memos ?? [])
|
|
478
|
+
this.state.getterAliases = resolveGetterAliases(ir.metadata.localConstants ?? [], (n) => getterNames.has(n))
|
|
479
|
+
}
|
|
480
|
+
// #2788: bare-props-form body destructure aliases (`const { children:
|
|
481
|
+
// kids } = props`) — same alias-resolution need as `getterAliases`
|
|
482
|
+
// above, different terminal set (a prop key here, a signal/memo
|
|
483
|
+
// getter there).
|
|
484
|
+
this.state.propDestructureAliases = resolveBodyDestructuredPropAliases(
|
|
485
|
+
ir.metadata.localConstants ?? [],
|
|
486
|
+
ir.metadata.propsObjectName,
|
|
487
|
+
)
|
|
468
488
|
// #2208 fable review: every name a `.map()`/`.filter()` loop callback
|
|
469
489
|
// binds as its item/index parameter anywhere in the component. Static
|
|
470
490
|
// loop-source resolution (`getBakedStaticChildLoop` /
|
|
@@ -4846,9 +4866,17 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
4846
4866
|
* rebinds. Outside any loop the root *is* the dot, so we emit `.Field`.
|
|
4847
4867
|
*/
|
|
4848
4868
|
private rootFieldRef(name: string): string {
|
|
4849
|
-
|
|
4869
|
+
// #2813 / #2788: `name` may be a bare local-const alias of a
|
|
4870
|
+
// signal/memo getter (`const items__alias = items`) or a bare-props-
|
|
4871
|
+
// form body destructure's renamed prop (`const { children: kids } =
|
|
4872
|
+
// props`) — resolve it to the ALIASED entity's own name FIRST, so the
|
|
4873
|
+
// emitted field matches what that entity itself seeds (`.Items`,
|
|
4874
|
+
// `.Children`) instead of registering a second, never-populated field
|
|
4875
|
+
// under the local alias (`.Items__alias`, `.Kids`).
|
|
4876
|
+
const resolved = this.state.getterAliases.get(name) ?? this.state.propDestructureAliases.get(name) ?? name
|
|
4877
|
+
this.state.templateReadRootFields.add(resolved)
|
|
4850
4878
|
const prefix = this.inLoop ? '$.' : '.'
|
|
4851
|
-
return `${prefix}${capitalizeFieldName(
|
|
4879
|
+
return `${prefix}${capitalizeFieldName(resolved)}`
|
|
4852
4880
|
}
|
|
4853
4881
|
|
|
4854
4882
|
/**
|
|
@@ -5152,12 +5180,35 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
5152
5180
|
// arithmetic index lowers correctly. A multi-token operand
|
|
5153
5181
|
// (`bf_add $i 1`) must be parenthesised or Go parses it as extra
|
|
5154
5182
|
// `bf_get` arguments.
|
|
5155
|
-
return `bf_get ${wrapIfMultiToken(
|
|
5183
|
+
return `bf_get ${wrapIfMultiToken(this.emitOperand(object, emit))} ${wrapIfMultiToken(this.emitOperand(index, emit))}`
|
|
5184
|
+
}
|
|
5185
|
+
|
|
5186
|
+
/**
|
|
5187
|
+
* A `binary`/`logical`/`unary` operand that feeds a prefix-call Go form
|
|
5188
|
+
* (`bf_mul a b`, `and a b`, `not a`, …), which cannot host a raw `{{…}}`
|
|
5189
|
+
* action the way the generic `emit` dispatcher's `templateLiteral()` would
|
|
5190
|
+
* produce for a template-literal operand (#2863). ONLY a template literal
|
|
5191
|
+
* is special-cased here, folded through `bf_concat_str` (the same door
|
|
5192
|
+
* `lowerValueOperand` uses for a ternary branch / helper-call arg /
|
|
5193
|
+
* query-guard value); every other kind still goes through the caller's own
|
|
5194
|
+
* `emit` closure unchanged. This matters: routing every operand through
|
|
5195
|
+
* `lowerValueOperand`'s `convertExpressionToGo` fallback (rather than only
|
|
5196
|
+
* the one broken kind) would ALSO pick up `convertExpressionToGo`'s own
|
|
5197
|
+
* string-keyed fast paths — e.g. inlining a bare identifier bound to a
|
|
5198
|
+
* local literal const — which `emit`'s AST-walk `identifier()` dispatch
|
|
5199
|
+
* does not do, a real behavioral divergence for that shape (caught by the
|
|
5200
|
+
* #2236 loop-param-shadowing regression pins).
|
|
5201
|
+
*/
|
|
5202
|
+
private emitOperand(n: ParsedExpr, emit: (e: ParsedExpr) => string): string {
|
|
5203
|
+
if (n.kind === 'template-literal') {
|
|
5204
|
+
return wrapIfMultiToken(lowerTemplateLiteralValue(this.emitCtx, n.parts))
|
|
5205
|
+
}
|
|
5206
|
+
return emit(n)
|
|
5156
5207
|
}
|
|
5157
5208
|
|
|
5158
5209
|
binary(op: string, left: ParsedExpr, right: ParsedExpr, emit: (e: ParsedExpr) => string): string {
|
|
5159
|
-
const l =
|
|
5160
|
-
const r =
|
|
5210
|
+
const l = this.emitOperand(left, emit)
|
|
5211
|
+
const r = this.emitOperand(right, emit)
|
|
5161
5212
|
// Every Go form below is a prefix function call (`bf_mul a b`, `gt a b`,
|
|
5162
5213
|
// `eq a b`), so a COMPOUND operand must be parenthesised or the template
|
|
5163
5214
|
// parser folds its tokens into the call's argument list — e.g.
|
|
@@ -5240,8 +5291,12 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
5240
5291
|
}
|
|
5241
5292
|
|
|
5242
5293
|
unary(op: string, argument: ParsedExpr, emit: (e: ParsedExpr) => string): string {
|
|
5243
|
-
const arg =
|
|
5244
|
-
|
|
5294
|
+
const arg = this.emitOperand(argument, emit)
|
|
5295
|
+
// `not` is a Go template prefix builtin like `and`/`or` (see `logical()`
|
|
5296
|
+
// below) — a multi-token argument (e.g. `or a b`) must be parenthesised
|
|
5297
|
+
// or it degrades into extra sibling args of `not` itself (#2758: `not
|
|
5298
|
+
// or a b` parses as `not` applied to 3 args, not 1).
|
|
5299
|
+
if (op === '!') return `not ${wrapIfMultiToken(arg)}`
|
|
5245
5300
|
if (op === '-') return `bf_neg ${arg}`
|
|
5246
5301
|
return arg
|
|
5247
5302
|
}
|
|
@@ -5259,8 +5314,8 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
5259
5314
|
// `and`/`or`. This makes `searchParams().get(k) ?? d` lower to
|
|
5260
5315
|
// `or (.SearchParams.Get "sort") "none"` instead of the broken
|
|
5261
5316
|
// `or .SearchParams.Get "sort" "none"`.
|
|
5262
|
-
const wrapLeft = wrapIfMultiToken(
|
|
5263
|
-
const wrapRight = wrapIfMultiToken(
|
|
5317
|
+
const wrapLeft = wrapIfMultiToken(this.emitOperand(left, emit))
|
|
5318
|
+
const wrapRight = wrapIfMultiToken(this.emitOperand(right, emit))
|
|
5264
5319
|
if (op === '&&') return `and ${wrapLeft} ${wrapRight}`
|
|
5265
5320
|
// `??` on a nillable prop needs true JS nullish semantics (#2248): Go's
|
|
5266
5321
|
// `or` is truthiness-based, so `{{or .Label "Default"}}` falls back on a
|
|
@@ -5568,8 +5623,16 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
5568
5623
|
method: ArrayMethod,
|
|
5569
5624
|
object: ParsedExpr,
|
|
5570
5625
|
args: ParsedExpr[],
|
|
5571
|
-
|
|
5626
|
+
rawEmit: (e: ParsedExpr) => string,
|
|
5572
5627
|
): string {
|
|
5628
|
+
// Every `emit(...)` call below lowers an ARGUMENT position (the
|
|
5629
|
+
// receiver or a `.method(...)` arg) that feeds a prefix-call Go form
|
|
5630
|
+
// (`bf_join (...) ...`, `bf_replace ... ... ...`, …) — shadow the
|
|
5631
|
+
// dispatcher's own `emit` with `emitOperand` (#2863) so a
|
|
5632
|
+
// template-literal receiver/argument (e.g. `items.join(\`${a}-${b}\`)`)
|
|
5633
|
+
// folds through `bf_concat_str` instead of leaking the TEXT-position
|
|
5634
|
+
// `templateLiteral()` form into another action's argument list.
|
|
5635
|
+
const emit = (e: ParsedExpr): string => this.emitOperand(e, rawEmit)
|
|
5573
5636
|
// `bf_join` etc. are registered in the runtime FuncMap. The exhaustive
|
|
5574
5637
|
// switch on `method` mirrors the IR-level discriminator — adding a new
|
|
5575
5638
|
// `ArrayMethod` variant becomes a TS compile error until every adapter
|
|
@@ -6650,34 +6713,23 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
6650
6713
|
}
|
|
6651
6714
|
continue
|
|
6652
6715
|
}
|
|
6653
|
-
// A ternary
|
|
6654
|
-
//
|
|
6655
|
-
//
|
|
6656
|
-
//
|
|
6657
|
-
//
|
|
6658
|
-
//
|
|
6659
|
-
//
|
|
6660
|
-
//
|
|
6661
|
-
//
|
|
6662
|
-
//
|
|
6663
|
-
|
|
6664
|
-
exprOut.parsed
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
go = go.slice(2, -2)
|
|
6671
|
-
}
|
|
6672
|
-
// Skip the fragment re-check for the just-unwrapped single-part case —
|
|
6673
|
-
// `kind` is still (accurately) `template-literal`, which would
|
|
6674
|
-
// otherwise re-trip `isTemplateFragment`'s `kind === 'template-literal'`
|
|
6675
|
-
// branch on the ALREADY-unwrapped bare value.
|
|
6676
|
-
if (!singlePartTemplateLiteral && this.isTemplateFragment(go, exprOut.parsed?.kind)) {
|
|
6677
|
-
// A genuine multi-part `{{if}}...{{end}}`-shaped fragment can't be a
|
|
6678
|
-
// bare pipeline argument — refuse loudly rather than silently
|
|
6679
|
-
// dropping the prop (the #2445 bug was exactly a silent drop one
|
|
6680
|
-
// level up).
|
|
6716
|
+
// A ternary or template literal (`row.on ? "yes" : "no"`, `` `#${row.id}
|
|
6717
|
+
// ${row.label}` ``) in this ARGUMENT position (a `bf_with_props`/
|
|
6718
|
+
// `bf_reprops` value, not a text position) must lower through the
|
|
6719
|
+
// shared value-operand door (#2863/#2335) — `convertExpressionToGo`'s
|
|
6720
|
+
// generic path returns `templateLiteral()`'s TEXT-position mixed
|
|
6721
|
+
// literal-text-plus-`{{…}}`-actions form for a template literal, which
|
|
6722
|
+
// is not a legal bare pipeline argument (multi-part) or needed an
|
|
6723
|
+
// ad-hoc single-part unwrap here (this replaces both). Recompute from
|
|
6724
|
+
// the already-validated `exprOut.parsed` tree rather than trying to
|
|
6725
|
+
// unwrap or refuse the text-position string after the fact.
|
|
6726
|
+
if (exprOut.parsed?.kind === 'template-literal' || exprOut.parsed?.kind === 'conditional') {
|
|
6727
|
+
go = lowerValueOperand(this.emitCtx, exprOut.parsed)
|
|
6728
|
+
} else if (this.isTemplateFragment(go, exprOut.parsed?.kind)) {
|
|
6729
|
+
// A genuine `{{if}}...{{end}}`-shaped (or other `{{`-leading action)
|
|
6730
|
+
// fragment can't be a bare pipeline argument — refuse loudly rather
|
|
6731
|
+
// than silently dropping the prop (the #2445 bug was exactly a
|
|
6732
|
+
// silent drop one level up).
|
|
6681
6733
|
this.state.errors.push({
|
|
6682
6734
|
code: 'BF101',
|
|
6683
6735
|
severity: 'error',
|
|
@@ -7083,7 +7135,11 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
7083
7135
|
|
|
7084
7136
|
case 'unary': {
|
|
7085
7137
|
const arg = this.renderConditionExpr(expr.argument)
|
|
7086
|
-
|
|
7138
|
+
// Same prefix-builtin wrapping rule as the `logical` case just below
|
|
7139
|
+
// (and the main `unary()` emitter above): `not` needs its multi-token
|
|
7140
|
+
// argument parenthesised, or e.g. `not or a b` parses as `not`
|
|
7141
|
+
// applied to 3 sibling args instead of 1 (#2758).
|
|
7142
|
+
if (expr.op === '!') return { preamble: arg.preamble, expr: `not ${wrapIfMultiToken(arg.expr)}` }
|
|
7087
7143
|
if (expr.op === '-') return { preamble: arg.preamble, expr: `bf_neg ${arg.expr}` }
|
|
7088
7144
|
return arg
|
|
7089
7145
|
}
|
|
@@ -7128,7 +7184,12 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
7128
7184
|
}
|
|
7129
7185
|
|
|
7130
7186
|
case 'template-literal':
|
|
7131
|
-
|
|
7187
|
+
// #2863 follow-up: fold to one Go value (`bf_concat_str` chain) via
|
|
7188
|
+
// the same door as the `conditional` case just above, instead of
|
|
7189
|
+
// `renderParsedExpr`'s TEXT-position `templateLiteral()` form —
|
|
7190
|
+
// which would leak raw `{{…}}` into THIS condition-expression's own
|
|
7191
|
+
// pipeline position (e.g. `(a === \`${x}-${y}\`) ? … : …`).
|
|
7192
|
+
return plain(lowerTemplateLiteralValue(this.emitCtx, expr.parts))
|
|
7132
7193
|
|
|
7133
7194
|
case 'arrow':
|
|
7134
7195
|
// A standalone arrow has no Go condition form (callbacks reach the
|
|
@@ -7696,7 +7757,9 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
7696
7757
|
// `bfComment` prepends `bf-`, so `printf "loop-i:%v"` yields
|
|
7697
7758
|
// `<!--bf-loop-i:KEY-->`. The key expression resolves against the current
|
|
7698
7759
|
// range item (`.` context), matching `data-key`'s emission.
|
|
7699
|
-
|
|
7760
|
+
// `bfEscapeCommentKey` (#2795 follow-up) neutralizes `-` so a key can't
|
|
7761
|
+
// spell `-->` and close the comment early — see its doc in `bf.go`.
|
|
7762
|
+
return `{{bfComment (printf "loop-i:%v" (bfEscapeCommentKey ${this.convertExpressionToGo(loop.key)}))}}`
|
|
7700
7763
|
}
|
|
7701
7764
|
return ''
|
|
7702
7765
|
}
|
|
@@ -88,6 +88,28 @@ export class CompileState {
|
|
|
88
88
|
*/
|
|
89
89
|
localConstants: IRMetadata['localConstants'] = []
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Bare local-const alias-hop chains that ultimately name a signal/memo
|
|
93
|
+
* getter (`const items__alias = items` → `items__alias` -> `items`,
|
|
94
|
+
* #2813) — the SSR-side twin of #2778's CSR-template fix. `rootFieldRef`
|
|
95
|
+
* resolves a name through this map before capitalizing it into a struct
|
|
96
|
+
* field, so `items__alias()` reads `.Items` (the field the getter
|
|
97
|
+
* actually seeds) instead of a phantom, never-seeded `.Items__alias`.
|
|
98
|
+
*/
|
|
99
|
+
getterAliases: Map<string, string> = new Map()
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Local names that are BODY-level destructured aliases of a prop key
|
|
103
|
+
* for a bare-props-form component (`const { children: kids } =
|
|
104
|
+
* props`), #2788 — the SSR-side twin of `aliased-destructured-prop`'s
|
|
105
|
+
* PARAMETER-destructuring case (`{ n: count }`, already handled via
|
|
106
|
+
* `ParamInfo.sourceName`/`capitalizeFieldName(p.name)`). `rootFieldRef`
|
|
107
|
+
* resolves a name through this map too, so `kids` reads the same
|
|
108
|
+
* `.Children` field the prop itself seeds instead of a phantom,
|
|
109
|
+
* never-populated `.Kids`.
|
|
110
|
+
*/
|
|
111
|
+
propDestructureAliases: Map<string, string> = new Map()
|
|
112
|
+
|
|
91
113
|
/**
|
|
92
114
|
* Every name a `.map()`/`.filter()` loop callback binds as its item/index
|
|
93
115
|
* parameter anywhere in the component (#2208 fable review). Consulted by
|
package/src/conformance-pins.ts
CHANGED
|
@@ -52,13 +52,16 @@ export const conformancePins: ConformancePins = {
|
|
|
52
52
|
'filter-nested-find-predicate': [{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2320' }],
|
|
53
53
|
'date-method-uncatalogued': [{ code: 'BF021', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2356' }],
|
|
54
54
|
'rich-prop-client-read': [{ code: 'BF049', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2648' }],
|
|
55
|
-
//
|
|
55
|
+
// A ternary or array literal LITERALLY WRAPPING JSX at a non-children prop
|
|
56
56
|
// position (e.g. `header={cond ? <a/> : <b/>}`) is refused ahead of
|
|
57
57
|
// `adapter.generate()` in the shared jsx-to-ir.ts phase, so it is pinned
|
|
58
58
|
// identically on every adapter (including Hono) — same reasoning as
|
|
59
|
-
// `rich-prop-client-read` above.
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
// `rich-prop-client-read` above. This is the permanent, intended behavior
|
|
60
|
+
// (formerly tracked as #2667, closed): the issue's own acceptance criteria
|
|
61
|
+
// treated a loud refusal as fully resolving the silent-divergence bug, so
|
|
62
|
+
// no open issue tracks further work here.
|
|
63
|
+
'jsx-element-prop-ternary': [{ code: 'BF021', severity: 'error' }],
|
|
64
|
+
'jsx-element-prop-array': [{ code: 'BF021', severity: 'error' }],
|
|
62
65
|
// `jsx-element-prop-fragment-conditional` (#2703) is NOT pinned here — it
|
|
63
66
|
// renders correctly since `queueDynamicPropDefine` (go-template-adapter.ts)
|
|
64
67
|
// extended the reserved `children` slot's `bf_with_children`/`bf_tmpl`
|
|
@@ -88,11 +91,14 @@ export const conformancePins: ConformancePins = {
|
|
|
88
91
|
severity: 'error',
|
|
89
92
|
issue: 'https://github.com/piconic-ai/barefootjs/issues/2700',
|
|
90
93
|
}],
|
|
91
|
-
//
|
|
94
|
+
// A reactive primitive invoked through a namespace import
|
|
92
95
|
// (`import * as bf from '@barefootjs/client'`, `bf.createSignal(...)`)
|
|
93
96
|
// that the analyzer's checker-less fast path cannot recognize refuses
|
|
94
97
|
// loudly (BF013) instead of silently dropping the declaration — fired
|
|
95
98
|
// in the shared analyzer pass ahead of any adapter's `adapter.generate()`,
|
|
96
|
-
// so all nine adapters (including Hono) pin this identically.
|
|
97
|
-
|
|
99
|
+
// so all nine adapters (including Hono) pin this identically. A compile
|
|
100
|
+
// that supplies a shared `ts.Program` (e.g. via `@barefootjs/vite`)
|
|
101
|
+
// resolves the primitive normally and never reaches this refusal (formerly
|
|
102
|
+
// tracked as #2771, closed) — no open issue tracks further work.
|
|
103
|
+
'namespace-import-primitive': [{ code: 'BF013', severity: 'error' }],
|
|
98
104
|
}
|
|
@@ -37,9 +37,4 @@
|
|
|
37
37
|
|
|
38
38
|
import type { RenderDivergences } from '@barefootjs/jsx'
|
|
39
39
|
|
|
40
|
-
export const renderDivergences: RenderDivergences = {
|
|
41
|
-
'children-passthrough-renamed':
|
|
42
|
-
'A `children` prop destructured under a different name (`const { children: kids } = props`) does not reach the SSR template on Go, tracked as https://github.com/piconic-ai/barefootjs/issues/2788. The same fixture also fails on Mojolicious, where the mechanism IS isolated: the `.html.ep` interpolates the LOCAL alias (`$kids`) while the stash defines only the caller-facing `children`, so the Perl render dies inside `Mojo::Template::process`. Go\'s own failure mode has NOT been read — `go` is not reachable from the local test process (the conformance case prints "go command not found" and skips), so this entry is declared from the CI failure on #2787 alone, not from a local reproduction. Whoever graduates this should read Go\'s actual output first rather than assume it shares Mojo\'s mechanism. Same alias family as `aliased-destructured-prop` (`{ n: count }`), whose Go half graduated in #2525 — worth checking whether the reserved `children` slot bypasses that fix or never had it. `children-passthrough-renamed` asserts the CORRECT (Hono-generated) output, so deleting this entry is the graduation.',
|
|
43
|
-
'aliased-loop-source':
|
|
44
|
-
'A `.map()` loop whose source is a local const alias of a signal getter (`const items__alias = items`) fails template execution on real Go (`can\'t evaluate field Items__alias in type main.AliasedLoopSourceProps`) — the zero-arg-call-to-field lowering routes `items__alias()` to a `.Items__alias` struct field that was never seeded, since seeding only knows about `items`, the real signal name; the alias hop is never resolved. This is the SSR-side twin of #2778 (fixed for the CSR client-JS template in the same PR that added this fixture) — that fix only touches client-JS emission, not Go\'s field-routing/seeding. Tracked at https://github.com/piconic-ai/barefootjs/issues/2813; graduate by resolving the alias hop at field-routing time using the same `resolveAliasOrigin`/`resolveGetterAliases` mechanism #2778 introduced, rather than a third alias-hop walker.',
|
|
45
|
-
}
|
|
40
|
+
export const renderDivergences: RenderDivergences = {}
|