@barefootjs/go-template 0.18.4 → 0.18.7
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/static-child-loop-bake.d.ts +61 -0
- package/dist/adapter/analysis/static-child-loop-bake.d.ts.map +1 -0
- package/dist/adapter/analysis/static-element-loop-bake.d.ts +83 -0
- package/dist/adapter/analysis/static-element-loop-bake.d.ts.map +1 -0
- package/dist/adapter/go-template-adapter.d.ts +151 -3
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +500 -52
- package/dist/adapter/lib/compile-state.d.ts +15 -0
- package/dist/adapter/lib/compile-state.d.ts.map +1 -1
- package/dist/adapter/lib/constants.d.ts.map +1 -1
- package/dist/adapter/memo/memo-compute.d.ts.map +1 -1
- package/dist/adapter/props/prop-classes.d.ts +40 -0
- package/dist/adapter/props/prop-classes.d.ts.map +1 -0
- package/dist/adapter/props/prop-types.d.ts.map +1 -1
- package/dist/adapter/type/type-codegen.d.ts +5 -1
- package/dist/adapter/type/type-codegen.d.ts.map +1 -1
- package/dist/adapter/value/value-lowering.d.ts.map +1 -1
- package/dist/build.js +500 -52
- package/dist/conformance-pins.d.ts.map +1 -1
- package/dist/index.js +503 -79
- package/dist/render-divergences.d.ts.map +1 -1
- package/dist/test-render.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/go-template-adapter.test.ts +708 -4
- package/src/adapter/analysis/static-child-loop-bake.ts +119 -0
- package/src/adapter/analysis/static-element-loop-bake.ts +211 -0
- package/src/adapter/go-template-adapter.ts +661 -34
- package/src/adapter/lib/compile-state.ts +17 -0
- package/src/adapter/lib/constants.ts +1 -0
- package/src/adapter/memo/memo-compute.ts +37 -9
- package/src/adapter/props/prop-classes.ts +70 -0
- package/src/adapter/props/prop-types.ts +69 -1
- package/src/adapter/type/type-codegen.ts +19 -2
- package/src/adapter/value/value-lowering.ts +27 -2
- package/src/conformance-pins.ts +30 -36
- package/src/render-divergences.ts +12 -30
- package/src/test-render.ts +131 -13
|
@@ -66,6 +66,15 @@ export class CompileState {
|
|
|
66
66
|
*/
|
|
67
67
|
localConstants: IRMetadata['localConstants'] = []
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Every name a `.map()`/`.filter()` loop callback binds as its item/index
|
|
71
|
+
* parameter anywhere in the component (#2208 fable review). Consulted by
|
|
72
|
+
* static loop-source resolution (`getBakedStaticChildLoop`) so a const
|
|
73
|
+
* whose name a DIFFERENT, enclosing loop's own callback param shadows is
|
|
74
|
+
* never resolved as that const's static value.
|
|
75
|
+
*/
|
|
76
|
+
staticLoopSourceBoundNames: Set<string> = new Set()
|
|
77
|
+
|
|
69
78
|
/**
|
|
70
79
|
* Names of component-scope arrow-const helpers (`const sortClass = …`),
|
|
71
80
|
* eligible for call-site inlining.
|
|
@@ -137,6 +146,14 @@ export class CompileState {
|
|
|
137
146
|
*/
|
|
138
147
|
nillablePropNames: Set<string> = new Set()
|
|
139
148
|
|
|
149
|
+
/**
|
|
150
|
+
* String-typed signal getter / prop names (#2168 string-concat-plus).
|
|
151
|
+
* Feeds `isStringName` for `isStringConcatBinary`, which decides whether a
|
|
152
|
+
* JS `+` operand chain is string concatenation rather than numeric
|
|
153
|
+
* addition — see `collectStringValueNames` (`props/prop-classes.ts`).
|
|
154
|
+
*/
|
|
155
|
+
stringValueNames: Set<string> = new Set()
|
|
156
|
+
|
|
140
157
|
/** Component root scope element(s) — each carries `data-key` for a keyed loop
|
|
141
158
|
* item. */
|
|
142
159
|
rootScopeNodes: Set<IRNode> = new Set()
|
|
@@ -17,6 +17,7 @@ export const GO_TEMPLATE_PRIMITIVES: Record<string, PrimitiveSpec> = {
|
|
|
17
17
|
'Math.floor': { arity: 1, emit: (args) => `bf_floor ${wrapGoArg(args[0])}` },
|
|
18
18
|
'Math.ceil': { arity: 1, emit: (args) => `bf_ceil ${wrapGoArg(args[0])}` },
|
|
19
19
|
'Math.round': { arity: 1, emit: (args) => `bf_round ${wrapGoArg(args[0])}` },
|
|
20
|
+
'Math.abs': { arity: 1, emit: (args) => `bf_abs ${wrapGoArg(args[0])}` },
|
|
20
21
|
// Two-arg forms only; an N-arg `Math.min(a, b, c)` falls through to the
|
|
21
22
|
// standard BF101 unsupported-call diagnostic via the arity gate.
|
|
22
23
|
'Math.min': { arity: 2, emit: (args) => `bf_min ${wrapGoArg(args[0])} ${wrapGoArg(args[1])}` },
|
|
@@ -366,13 +366,40 @@ export function memoInitialFromParsedBody(
|
|
|
366
366
|
const operator = body.op
|
|
367
367
|
const operand = String(body.right.value)
|
|
368
368
|
|
|
369
|
-
// getter() * N — return the signal's
|
|
369
|
+
// getter() * N — return the signal's (or, #2168 memo-chain, another
|
|
370
|
+
// memo's) Go initial value times N. `resolveGetterValueAsGo` checks
|
|
371
|
+
// `signals` first (unchanged behavior for a signal-derived memo like
|
|
372
|
+
// `doubled = createMemo(() => count() * 2)`), then falls back to
|
|
373
|
+
// `ctx.state.currentMemos` and recurses — needed for a memo derived from
|
|
374
|
+
// ANOTHER memo (`label = createMemo(() => doubled() + 1)`), which this
|
|
375
|
+
// branch previously couldn't recognize at all (a signals-only lookup),
|
|
376
|
+
// silently folding to the Go zero value instead of "7".
|
|
370
377
|
const depName = getterCallName(body.left)
|
|
371
378
|
if (depName) {
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
379
|
+
const depInitial = resolveGetterValueAsGo(ctx, depName, signals, propsParams, propFallbackVars, resolving)
|
|
380
|
+
// `resolveGetterValueAsGo` can return an IIFE (`func() string { ... }()`
|
|
381
|
+
// / `func() interface{} { ... }()`, e.g. a memo that shadows `props.X
|
|
382
|
+
// ?? <lit>`, #2075) rather than a plain atom — none of those return
|
|
383
|
+
// types support Go's `<op>` arithmetic operators, so splicing one in
|
|
384
|
+
// bare would emit invalid Go (Copilot review, #2200: e.g. `operator
|
|
385
|
+
// is not defined on interface{}`/`string`). Bail (fall through to the
|
|
386
|
+
// caller's zero-value default) rather than emit broken arithmetic.
|
|
387
|
+
const isArithmeticSafe = depInitial !== null && !depInitial.startsWith('func(')
|
|
388
|
+
if (isArithmeticSafe) {
|
|
389
|
+
// A signal's own initial value is always a simple atom (a literal,
|
|
390
|
+
// `in.Field`, a hoisted var) — never needs grouping. A MEMO's initial
|
|
391
|
+
// value can itself be a compound expression from this exact branch
|
|
392
|
+
// one level up (`"3 * 2"`), and splicing that in bare under a
|
|
393
|
+
// DIFFERENT outer operator can silently invert precedence (a memo
|
|
394
|
+
// chain shaped `inner = () => count() + 1` then `outer = () =>
|
|
395
|
+
// inner() * 2` would fold to `3 + 1 * 2` = 5 in Go, vs JS's `(3+1)*2`
|
|
396
|
+
// = 8). Parenthesize whenever `depInitial` is compound (contains
|
|
397
|
+
// whitespace — a simple atom never does) so precedence is
|
|
398
|
+
// preserved regardless of which operator combination a memo chain
|
|
399
|
+
// uses. A simple atom is left bare so existing exact-text
|
|
400
|
+
// expectations (`Doubled: 3 * 2,`) don't gain a no-op paren.
|
|
401
|
+
const wrapped = /\s/.test(depInitial) ? `(${depInitial})` : depInitial
|
|
402
|
+
return `${wrapped} ${operator} ${operand}`
|
|
376
403
|
}
|
|
377
404
|
}
|
|
378
405
|
|
|
@@ -408,12 +435,13 @@ export function memoInitialFromParsedBody(
|
|
|
408
435
|
}
|
|
409
436
|
}
|
|
410
437
|
|
|
411
|
-
// () => getter() — just return the signal's
|
|
438
|
+
// () => getter() — just return the signal's (or another memo's, #2168
|
|
439
|
+
// memo-chain) Go initial value.
|
|
412
440
|
const simpleDep = getterCallName(body)
|
|
413
441
|
if (simpleDep) {
|
|
414
|
-
const
|
|
415
|
-
if (
|
|
416
|
-
return
|
|
442
|
+
const depInitial = resolveGetterValueAsGo(ctx, simpleDep, signals, propsParams, propFallbackVars, resolving)
|
|
443
|
+
if (depInitial !== null) {
|
|
444
|
+
return depInitial
|
|
417
445
|
}
|
|
418
446
|
}
|
|
419
447
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prop classification for the Go template adapter.
|
|
3
|
+
*
|
|
4
|
+
* Ported from `packages/adapter-blade/src/adapter/props/prop-classes.ts` (itself
|
|
5
|
+
* ported from Jinja) — ONE function: string-typed signal/prop names, needed to
|
|
6
|
+
* decide `+` string-concat vs numeric addition (#2168 string-concat-plus). Pure
|
|
7
|
+
* function over `ir.metadata`; no adapter instance state.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { collectLoopBoundNames, type ComponentIR, type TypeInfo } from '@barefootjs/jsx'
|
|
11
|
+
|
|
12
|
+
/** True when `type` is the `string` primitive. */
|
|
13
|
+
function isStringTypeInfo(type: TypeInfo): boolean {
|
|
14
|
+
return type.kind === 'primitive' && type.primitive === 'string'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** True when `initialValue` is a bare string-literal expression. */
|
|
18
|
+
function isBareStringLiteral(initialValue: string | undefined): boolean {
|
|
19
|
+
if (!initialValue) return false
|
|
20
|
+
const v = initialValue.trim()
|
|
21
|
+
return (v.startsWith("'") && v.endsWith("'")) || (v.startsWith('"') && v.endsWith('"'))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* String-typed signals, props, and same-file local consts (#2212, ported
|
|
26
|
+
* here for #2236). A signal is string-typed when its inferred type is
|
|
27
|
+
* `string` (or, defensively, when its initial value is a bare string
|
|
28
|
+
* literal); a prop when its annotated type is `string`; a local const the
|
|
29
|
+
* same way. Drives `isStringName` for `isStringConcatBinary` — the shared
|
|
30
|
+
* helper (`@barefootjs/jsx`) that decides whether a JS `+` is string
|
|
31
|
+
* concatenation rather than numeric addition (Go's `html/template` has no
|
|
32
|
+
* native `+` at all; `binary()` always emits a runtime call, `bf_add` for
|
|
33
|
+
* addition or `bf_concat_str` for concatenation — see
|
|
34
|
+
* `go-template-adapter.ts`'s `binary()`). Local consts matter for exactly
|
|
35
|
+
* the shadowing shape this exclusion exists for: with a loop-bound `label`
|
|
36
|
+
* subtracted, an outer `{label + suffix}` (where `suffix = '!'`) must
|
|
37
|
+
* still classify as string concat via its OTHER operand, or it would fall
|
|
38
|
+
* back to `bf_add` and render `0`.
|
|
39
|
+
*
|
|
40
|
+
* Excludes any name bound as a `.map()`/`.filter()` loop callback's item or
|
|
41
|
+
* index parameter ANYWHERE in the component (#2212, ported here for #2236):
|
|
42
|
+
* this lookup is a flat, scope-blind `Set<string>` with no notion of a loop
|
|
43
|
+
* param shadowing an outer string-typed binding of the same name
|
|
44
|
+
* (`values.map((label) => 1 + label)` inside a component that also has a
|
|
45
|
+
* string `label` prop) — left unguarded, that shadowed `label` would be
|
|
46
|
+
* misdetected as string-typed and `1 + label` would silently lower to
|
|
47
|
+
* `bf_concat_str` instead of staying numeric `bf_add`. Subtracting loop-bound
|
|
48
|
+
* names is coarse (it also suppresses a genuinely non-shadowed same-named
|
|
49
|
+
* string elsewhere in the component) but safe: the suppressed case just
|
|
50
|
+
* falls back to today's numeric `bf_add` — the same, already-accepted
|
|
51
|
+
* residual as an unresolvable operand — never silently-wrong output.
|
|
52
|
+
*/
|
|
53
|
+
export function collectStringValueNames(ir: ComponentIR): Set<string> {
|
|
54
|
+
const names = new Set<string>()
|
|
55
|
+
for (const s of ir.metadata.signals) {
|
|
56
|
+
if (isStringTypeInfo(s.type) || isBareStringLiteral(s.initialValue)) {
|
|
57
|
+
names.add(s.getter)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const p of ir.metadata.propsParams) {
|
|
61
|
+
if (isStringTypeInfo(p.type)) names.add(p.name)
|
|
62
|
+
}
|
|
63
|
+
for (const c of ir.metadata.localConstants) {
|
|
64
|
+
if ((c.type !== null && isStringTypeInfo(c.type)) || isBareStringLiteral(c.value)) {
|
|
65
|
+
names.add(c.name)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for (const bound of collectLoopBoundNames(ir)) names.delete(bound)
|
|
69
|
+
return names
|
|
70
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* generators and the nillable-field set so they can't drift.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ComponentIR, IRMetadata } from '@barefootjs/jsx'
|
|
8
|
+
import type { ComponentIR, IRMetadata, IRNode, ParsedExpr } from '@barefootjs/jsx'
|
|
9
9
|
|
|
10
10
|
import type { GoEmitContext } from '../emit-context.ts'
|
|
11
11
|
import { typeInfoToGo } from '../type/type-codegen.ts'
|
|
@@ -35,9 +35,77 @@ export function buildPropTypeOverrides(ctx: GoEmitContext, ir: ComponentIR): Map
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
// A bare `number`-typed prop with no other evidence resolves to Go `int`
|
|
40
|
+
// (`typeInfoToGo`'s blind default) — but `.toFixed()` can only be called on
|
|
41
|
+
// a real JS number, and the runtime value it formats (e.g. a price, 19.5)
|
|
42
|
+
// may be fractional, which a Go `int` struct field can't hold (assigning a
|
|
43
|
+
// fractional untyped constant to it is a compile error: #2168
|
|
44
|
+
// number-tofixed). Unlike a signal's fractional LITERAL initial value
|
|
45
|
+
// (rescued by `typeInfoToGo`'s own `defaultValue` consultation — the
|
|
46
|
+
// math-methods half of the same divergence), a prop with no default has no
|
|
47
|
+
// literal to read the fraction off of; the usage of `.toFixed()` itself is
|
|
48
|
+
// the only available evidence, so it's collected by walking the JSX tree.
|
|
49
|
+
for (const propName of collectToFixedPropNames(ir.root)) {
|
|
50
|
+
const param = ir.metadata.propsParams.find(p => p.name === propName)
|
|
51
|
+
if (!param) continue
|
|
52
|
+
const resolved = overrides.get(propName) ?? typeInfoToGo(ctx, param.type, param.defaultValue)
|
|
53
|
+
if (resolved === 'int') {
|
|
54
|
+
overrides.set(propName, 'float64')
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
38
58
|
return overrides
|
|
39
59
|
}
|
|
40
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Names of identifiers used as the receiver of `.toFixed(...)` anywhere in
|
|
63
|
+
* the component's JSX tree (text expressions, conditions, and attribute
|
|
64
|
+
* values). Deliberately narrow — `.toFixed()` is the one number-shape usage
|
|
65
|
+
* that needs this rescue (see `buildPropTypeOverrides` above); it isn't a
|
|
66
|
+
* general "infer number-ness from usage" walker.
|
|
67
|
+
*
|
|
68
|
+
* KNOWN LIMITATION: only a DIRECT identifier receiver in the JSX tree is
|
|
69
|
+
* caught (`{price.toFixed(2)}`). A bare `number` prop with no default is
|
|
70
|
+
* still silently typed `int` — and hits the exact same `go run` compile
|
|
71
|
+
* failure (#2168 number-tofixed) on a fractional runtime value — if the
|
|
72
|
+
* fraction only surfaces indirectly (`.toFixed()` inside a signal's
|
|
73
|
+
* initial value or a memo's computation, reached via `ir.metadata` rather
|
|
74
|
+
* than `ir.root`) or via any OTHER fraction-producing operation on the
|
|
75
|
+
* same bare prop (division, `Math.round`/`Math.floor`, etc. — none of
|
|
76
|
+
* which carry the same unambiguous "this must be a real JS number" signal
|
|
77
|
+
* `.toFixed()` does). Widening this walker to those cases is a real,
|
|
78
|
+
* currently-unaddressed gap, not a hypothetical one — flag it rather than
|
|
79
|
+
* treating a future occurrence as a fresh regression.
|
|
80
|
+
*/
|
|
81
|
+
function collectToFixedPropNames(root: IRNode): Set<string> {
|
|
82
|
+
const names = new Set<string>()
|
|
83
|
+
const checkExpr = (expr: ParsedExpr | undefined) => {
|
|
84
|
+
if (expr?.kind === 'array-method' && expr.method === 'toFixed' && expr.object.kind === 'identifier') {
|
|
85
|
+
names.add(expr.object.name)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const walk = (node: IRNode | null | undefined) => {
|
|
89
|
+
if (!node) return
|
|
90
|
+
if (node.type === 'expression') checkExpr(node.parsed)
|
|
91
|
+
if (node.type === 'conditional') {
|
|
92
|
+
checkExpr(node.parsedCondition)
|
|
93
|
+
walk(node.whenTrue)
|
|
94
|
+
walk(node.whenFalse)
|
|
95
|
+
}
|
|
96
|
+
if (node.type === 'element') {
|
|
97
|
+
for (const attr of node.attrs) {
|
|
98
|
+
if (attr.value.kind === 'expression') checkExpr(attr.value.parsed)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
102
|
+
node.children.forEach(walk)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
walk(root)
|
|
106
|
+
return names
|
|
107
|
+
}
|
|
108
|
+
|
|
41
109
|
/**
|
|
42
110
|
* Resolve a prop param's Go struct-field type using the SAME logic
|
|
43
111
|
* `generatePropsStruct` / `generateInputStruct` use: a `propTypeOverrides` entry
|
|
@@ -18,7 +18,11 @@ import type { GoEmitContext } from '../emit-context.ts'
|
|
|
18
18
|
/**
|
|
19
19
|
* Convert a `TypeInfo` to a Go type string.
|
|
20
20
|
*
|
|
21
|
-
* @param defaultValue used to infer the type when `typeInfo.kind` is
|
|
21
|
+
* @param defaultValue used to infer the type when `typeInfo.kind` is
|
|
22
|
+
* `unknown`, and to distinguish `int` vs `float64` when `kind` is
|
|
23
|
+
* `primitive`/`number` (#2168 math-methods/number-tofixed — a bare TS
|
|
24
|
+
* `number` blindly mapped to Go `int`, so a fractional signal initial
|
|
25
|
+
* value like `-7.6` silently truncated to the Go zero value)
|
|
22
26
|
* @returns the Go type, falling back to `interface{}` when unresolvable
|
|
23
27
|
*/
|
|
24
28
|
export function typeInfoToGo(
|
|
@@ -32,7 +36,7 @@ export function typeInfoToGo(
|
|
|
32
36
|
case 'string':
|
|
33
37
|
return 'string'
|
|
34
38
|
case 'number':
|
|
35
|
-
return 'int'
|
|
39
|
+
return defaultValue !== undefined ? numberPrimitiveGoType(defaultValue) : 'int'
|
|
36
40
|
case 'boolean':
|
|
37
41
|
return 'bool'
|
|
38
42
|
default:
|
|
@@ -99,6 +103,19 @@ export function tsTypeStringToGo(ctx: GoEmitContext, tsType: string): string {
|
|
|
99
103
|
return 'interface{}'
|
|
100
104
|
}
|
|
101
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Distinguish Go `int` vs `float64` for a `number`-typed field from the
|
|
108
|
+
* literal source text of its default/initial value. Falls back to `int`
|
|
109
|
+
* when `value` isn't recognizably a bare numeric literal (e.g. a
|
|
110
|
+
* destructured default that's itself an expression, `props.initial ?? 0`)
|
|
111
|
+
* — `int` remains the blind fallback for `kind: 'primitive'`; only a
|
|
112
|
+
* literal fractional value (`-7.6`) is positive enough evidence to widen
|
|
113
|
+
* to `float64`.
|
|
114
|
+
*/
|
|
115
|
+
function numberPrimitiveGoType(value: string): string {
|
|
116
|
+
return /^-?\d+\.\d+$/.test(value) ? 'float64' : 'int'
|
|
117
|
+
}
|
|
118
|
+
|
|
102
119
|
/** Infer a Go type from a JS value literal; `interface{}` when unrecognized. */
|
|
103
120
|
export function inferTypeFromValue(value: string): string {
|
|
104
121
|
if (value === 'true' || value === 'false') return 'bool'
|
|
@@ -42,8 +42,12 @@ export function convertInitialValue(
|
|
|
42
42
|
return value === 'true' ? 'true' : 'false'
|
|
43
43
|
}
|
|
44
44
|
if (typeInfo.primitive === 'number') {
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
// Leading `-` (#2168 math-methods: `createSignal(-7.6)`) — without it,
|
|
46
|
+
// a negative initial value never matches either literal shape below
|
|
47
|
+
// and silently falls to the `0` zero-value fallback, regardless of the
|
|
48
|
+
// field's Go type.
|
|
49
|
+
if (/^-?\d+$/.test(value)) return value
|
|
50
|
+
if (/^-?\d+\.\d+$/.test(value)) return value
|
|
47
51
|
return '0'
|
|
48
52
|
}
|
|
49
53
|
if (typeInfo.primitive === 'string') {
|
|
@@ -70,6 +74,27 @@ export function convertInitialValue(
|
|
|
70
74
|
}
|
|
71
75
|
return '""'
|
|
72
76
|
}
|
|
77
|
+
// A struct-backed `interface` kind (an explicitly-typed object signal,
|
|
78
|
+
// `createSignal<User>({...})`) — #2168 signal-object-field. Mirrors the
|
|
79
|
+
// `array` branch above: `jsLiteralToGo` → `parsedLiteralToGo`'s
|
|
80
|
+
// object-literal case already bakes an object literal against a named
|
|
81
|
+
// local struct correctly (proven by the existing typed-array-of-objects
|
|
82
|
+
// test); it just wasn't reachable from a SCALAR struct signal, which
|
|
83
|
+
// fell straight through to `nil` — a compile error for a non-pointer
|
|
84
|
+
// struct field (`cannot use nil as User value in struct literal`), not
|
|
85
|
+
// merely a silently-dropped initial value.
|
|
86
|
+
if (ctx.state.localStructFields.has(typeInfo.raw)) {
|
|
87
|
+
const baked = jsLiteralToGo(ctx, typeInfo, preParsed)
|
|
88
|
+
if (baked !== null) return baked
|
|
89
|
+
// Baking failed (a non-literal initial value, or no `preParsed` tree)
|
|
90
|
+
// — `nil` is STILL invalid Go for this non-pointer struct field, so
|
|
91
|
+
// the same compile error would resurface for any such case (Copilot
|
|
92
|
+
// review, #2201). The struct's own zero value (`User{}`) is the
|
|
93
|
+
// correct fallback here — mirrors this function's own docstring
|
|
94
|
+
// ("falls back to the type's zero value") for every other typed
|
|
95
|
+
// branch above.
|
|
96
|
+
return `${typeInfo.raw}{}`
|
|
97
|
+
}
|
|
73
98
|
}
|
|
74
99
|
|
|
75
100
|
return 'nil'
|
package/src/conformance-pins.ts
CHANGED
|
@@ -15,19 +15,21 @@ export const conformancePins: ConformancePins = {
|
|
|
15
15
|
// `style={{ … }}` object literal now lowers to a CSS string with dynamic
|
|
16
16
|
// values interpolated (`background-color:{{.Color}};padding:8px`) via
|
|
17
17
|
// `tryLowerStyleObject` (#1322).
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
// `todo-app` / `todo-app-ssr` no longer pinned (#2205) — the conformance
|
|
19
|
+
// harness now passes `siblingTemplatesRegistered: true` for fixtures with
|
|
20
|
+
// sibling `components`, matching `bf build`'s real semantics, so the
|
|
21
|
+
// BF103 loop-body cross-template check no longer fires spuriously.
|
|
22
|
+
// (`todo-app-ssr` is still skipped on this adapter via
|
|
23
|
+
// `render-divergences.ts` — #2209 — for an unrelated signal-seeding gap;
|
|
24
|
+
// `todo-app`'s pre-hydration empty render is unaffected.)
|
|
25
|
+
// `static-array-children` no longer pinned (#2208) — `items`'s
|
|
26
|
+
// array-literal initializer is now recognized as fully-static and its
|
|
27
|
+
// per-item ListItem props/data-key are baked directly into
|
|
28
|
+
// `NewStaticListProps`'s constructor (`analyzeBakeableStaticChildLoop`),
|
|
29
|
+
// since the loop body is a single child component with a plain-value
|
|
30
|
+
// prop set. See #2224 for the narrower remaining gap (a plain-ELEMENT
|
|
31
|
+
// loop body over a static array, or an inline/unnamed array literal —
|
|
32
|
+
// still refused).
|
|
31
33
|
// `([emoji, users]) => ...` is an array-index tuple destructure — #2087
|
|
32
34
|
// Phase B's widened gate now admits this shape (`destructure-array-index-in-map`
|
|
33
35
|
// exercises the same `segments`-based lowering). The remaining refusal here
|
|
@@ -40,13 +42,11 @@ export const conformancePins: ConformancePins = {
|
|
|
40
42
|
// array bound to such a const. See the `renderLoop` comment at the check
|
|
41
43
|
// site; Jinja / ERB apply the same narrow check for the same reason.
|
|
42
44
|
'static-array-from-props': [{ code: 'BF101', severity: 'error' }],
|
|
43
|
-
// Same computed-const array as above
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
{ code: 'BF101', severity: 'error' },
|
|
49
|
-
],
|
|
45
|
+
// Same computed-const array as above — the destructure param itself no
|
|
46
|
+
// longer contributes a diagnostic, and BF103 (sibling-imported child
|
|
47
|
+
// component in the loop body) no longer fires either now that the
|
|
48
|
+
// conformance harness passes `siblingTemplatesRegistered: true` (#2205).
|
|
49
|
+
'static-array-from-props-with-component': [{ code: 'BF101', severity: 'error' }],
|
|
50
50
|
// (`style-3-signals` graduated alongside `style-object-dynamic` — see note
|
|
51
51
|
// above; the `style={{ … }}` object now lowers to a CSS string.)
|
|
52
52
|
// (`tagged-template-classname` graduated by #2092 — the tag resolves
|
|
@@ -127,20 +127,14 @@ export const conformancePins: ConformancePins = {
|
|
|
127
127
|
// `string-trim` no longer pinned — pre-existing `bf_trim`
|
|
128
128
|
// (wraps `strings.TrimSpace`) handles the strip (#1448 Tier A
|
|
129
129
|
// ninth PR, closing out Tier A).
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
'dangerous-inner-html': [{ code: 'BF101', severity: 'error' }],
|
|
141
|
-
// Edge-case sweep (Priority 12): `.replaceAll` has no lowering yet —
|
|
142
|
-
// only first-occurrence `.replace` is wired to the runtime helpers.
|
|
143
|
-
// Refused with BF101 rather than reusing the first-only lowering,
|
|
144
|
-
// which would silently change semantics.
|
|
145
|
-
'string-replaceall': [{ code: 'BF101', severity: 'error' }],
|
|
130
|
+
// `array-map-function-reference` no longer pinned — a bare-identifier
|
|
131
|
+
// `.map(format)` callback now resolves one hop to its declaration
|
|
132
|
+
// (`resolveCallbackMethodFunctionReferences`, #2206), the same mechanism
|
|
133
|
+
// #2090 established for `.sort(fnref)`.
|
|
134
|
+
// `dangerous-inner-html` no longer pinned — a compile-time string-literal
|
|
135
|
+
// `dangerouslySetInnerHTML={{ __html: '...' }}` is spliced directly into
|
|
136
|
+
// the template as trusted raw text (`resolveDangerousInnerHtml`, #2207).
|
|
137
|
+
// A dynamic/signal-derived value still refuses with BF101 — see the
|
|
138
|
+
// `dangerous-inner-html-dynamic` fixture/pin below (tracked: #2215).
|
|
139
|
+
'dangerous-inner-html-dynamic': [{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2215' }],
|
|
146
140
|
}
|
|
@@ -17,34 +17,16 @@
|
|
|
17
17
|
import type { RenderDivergences } from '@barefootjs/jsx'
|
|
18
18
|
|
|
19
19
|
export const renderDivergences: RenderDivergences = {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
'object-entries-map':
|
|
33
|
-
'`Object.entries(prop).map(([k, v]) => …)`: generated Go fails to run (exit 1) — no object-iteration loop lowering',
|
|
34
|
-
'nested-loop-outer-binding':
|
|
35
|
-
'nested-loop inner items carry `data-key` where the reference emits the depth-suffixed `data-key-1`',
|
|
36
|
-
'jsx-element-prop':
|
|
37
|
-
'a JSX element passed as a NON-children prop renders an empty slot — the element value is silently dropped',
|
|
38
|
-
'grandchild-composition':
|
|
39
|
-
"three-level composition: the grandchild's threaded prop renders EMPTY — prop forwarding through two template-render layers loses the value",
|
|
40
|
-
'child-primitive-props':
|
|
41
|
-
'numeric/boolean LITERAL props on a child (`count={5}` `active={true}`) render as Go zero values (0 / false)',
|
|
42
|
-
'memo-chain':
|
|
43
|
-
'a memo derived from another memo renders EMPTY for the second layer — the constructor folds only one derivation level',
|
|
44
|
-
'signal-object-field':
|
|
45
|
-
'object-valued signal (`user().name`): generated Go fails to run (exit 1) — no struct synthesis outside loops',
|
|
46
|
-
'string-slice':
|
|
47
|
-
'`.slice()` on a STRING routes through the array `bf_slice` helper and renders "[]" instead of the substring',
|
|
48
|
-
'string-trim-sided':
|
|
49
|
-
'`.trimStart()` / `.trimEnd()`: generated Go fails to run (exit 1) — only both-sides `bf_trim` exists',
|
|
20
|
+
// `todo-app-ssr` no longer diverges (#2209). Two parts: (1) `.Todos`
|
|
21
|
+
// (the loop's DATUM slice) is already seeded straight from the caller's
|
|
22
|
+
// Input — the constructor derives it from `initialTodos`, and `[]Todo`
|
|
23
|
+
// zero-fills `Editing: false`, so the `.map(t => ({ ...t, editing:
|
|
24
|
+
// false }))` transform in the signal initializer was never actually the
|
|
25
|
+
// gap on Go, unlike the 7 template-string adapters. (2) The real gap was
|
|
26
|
+
// `.TodoItems []TodoItemProps` — the loop-body CHILD COMPONENT slice the
|
|
27
|
+
// template actually ranges over — which has no server-side population
|
|
28
|
+
// path in this harness (documented as route-handler-populated in
|
|
29
|
+
// production). `buildDynamicChildLoopSeeding` (this package's
|
|
30
|
+
// `test-render.ts`) now replicates that documented contract for a
|
|
31
|
+
// signal-backed dynamic child-component loop.
|
|
50
32
|
}
|