@barefootjs/go-template 0.31.0 → 0.31.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/adapter/go-template-adapter.d.ts +49 -41
  2. package/dist/adapter/go-template-adapter.d.ts.map +1 -1
  3. package/dist/adapter/index.js +167 -101
  4. package/dist/adapter/lib/types.d.ts +4 -1
  5. package/dist/adapter/lib/types.d.ts.map +1 -1
  6. package/dist/adapter/memo/memo-compute.d.ts +9 -0
  7. package/dist/adapter/memo/memo-compute.d.ts.map +1 -1
  8. package/dist/adapter/memo/memo-type.d.ts +2 -0
  9. package/dist/adapter/memo/memo-type.d.ts.map +1 -1
  10. package/dist/adapter/props/prop-types.d.ts +2 -1
  11. package/dist/adapter/props/prop-types.d.ts.map +1 -1
  12. package/dist/adapter/spread/spread-codegen.d.ts.map +1 -1
  13. package/dist/adapter/type/type-codegen.d.ts +22 -5
  14. package/dist/adapter/type/type-codegen.d.ts.map +1 -1
  15. package/dist/adapter/value/parsed-literal-to-go.d.ts +12 -0
  16. package/dist/adapter/value/parsed-literal-to-go.d.ts.map +1 -1
  17. package/dist/adapter/value/value-lowering.d.ts +3 -0
  18. package/dist/adapter/value/value-lowering.d.ts.map +1 -1
  19. package/dist/index.js +168 -104
  20. package/dist/render-divergences.d.ts +6 -0
  21. package/dist/render-divergences.d.ts.map +1 -1
  22. package/dist/vite.js +493 -192
  23. package/package.json +5 -5
  24. package/src/__tests__/go-template-adapter.test.ts +155 -10
  25. package/src/adapter/go-template-adapter.ts +213 -128
  26. package/src/adapter/lib/types.ts +4 -1
  27. package/src/adapter/memo/memo-compute.ts +21 -17
  28. package/src/adapter/memo/memo-type.ts +5 -5
  29. package/src/adapter/props/prop-types.ts +6 -5
  30. package/src/adapter/spread/spread-codegen.ts +12 -5
  31. package/src/adapter/type/type-codegen.ts +86 -25
  32. package/src/adapter/value/parsed-literal-to-go.ts +28 -10
  33. package/src/adapter/value/value-lowering.ts +65 -29
  34. package/src/render-divergences.ts +6 -15
  35. package/src/test-render.ts +6 -1
@@ -17,7 +17,7 @@ import type { GoEmitContext } from '../emit-context.ts'
17
17
  import type { PropFallbackVar } from '../lib/types.ts'
18
18
  import { capitalizeFieldName } from '../lib/go-naming.ts'
19
19
  import { escapeGoString } from '../lib/go-emit.ts'
20
- import { parsedLiteralToGo } from './parsed-literal-to-go.ts'
20
+ import { numberLiteralRawGo, parsedLiteralToGo } from './parsed-literal-to-go.ts'
21
21
  import { collapseLiteralUnion } from '../type/type-codegen.ts'
22
22
 
23
23
  /** Default for `getSignalInitialValueAsGo`'s optional fallback-var map. */
@@ -40,16 +40,25 @@ const EMPTY_PROP_FALLBACK_VARS: ReadonlyMap<string, PropFallbackVar> = new Map()
40
40
  * undefined` half is source-level documentation of nullability, not a
41
41
  * Go-representable branch, so it's unwrapped to its single non-
42
42
  * undefined/null primitive branch.
43
+ *
44
+ * `param.name` is the LOCAL binding — `nillablePropNames` (a source-level
45
+ * analysis set, `collectNillablePropNames`) stays keyed by it — while the
46
+ * emitted `in.<Field>` reference is caller-facing (`sourceName ?? name`,
47
+ * #2525), so the two must resolve separately rather than off one name.
43
48
  */
44
- function nillableAwarePropRef(ctx: GoEmitContext, propName: string, expectedType: TypeInfo): string {
45
- const fieldRef = `in.${capitalizeFieldName(propName)}`
49
+ function nillableAwarePropRef(
50
+ ctx: GoEmitContext,
51
+ param: { name: string; sourceName?: string },
52
+ expectedType: TypeInfo,
53
+ ): string {
54
+ const fieldRef = `in.${capitalizeFieldName(param.sourceName ?? param.name)}`
46
55
  const scalar =
47
56
  expectedType.kind === 'primitive'
48
57
  ? expectedType
49
58
  : expectedType.kind === 'union' && expectedType.unionTypes?.length === 2
50
59
  ? expectedType.unionTypes.find(t => t.primitive !== 'undefined' && t.primitive !== 'null')
51
60
  : undefined
52
- if (ctx.state.nillablePropNames.has(propName) && scalar?.kind === 'primitive') {
61
+ if (ctx.state.nillablePropNames.has(param.name) && scalar?.kind === 'primitive') {
53
62
  const goType =
54
63
  scalar.primitive === 'boolean' ? 'bool' :
55
64
  scalar.primitive === 'number' ? 'float64' :
@@ -70,41 +79,62 @@ export function convertInitialValue(
70
79
  ctx: GoEmitContext,
71
80
  value: string,
72
81
  _typeInfo: TypeInfo,
73
- propsParams?: { name: string }[],
82
+ propsParams?: { name: string; sourceName?: string }[],
74
83
  preParsed?: ParsedExpr,
75
84
  ): string {
76
85
  // Literal unions collapse to their backing primitive the same way
77
86
  // `typeInfoToGo` collapses the field's type — the two MUST agree, or a
78
87
  // `string` field gets a `nil` seed (#2477's `go run` failure).
79
88
  const typeInfo = collapseLiteralUnion(_typeInfo)
80
- const propRef = (propName: string): string => nillableAwarePropRef(ctx, propName, typeInfo)
89
+ const propRef = (param: { name: string; sourceName?: string }): string =>
90
+ nillableAwarePropRef(ctx, param, typeInfo)
81
91
 
82
92
  if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {
83
- if (propsParams?.some(p => p.name === value)) {
84
- return propRef(value)
93
+ const param = propsParams?.find(p => p.name === value)
94
+ if (param) {
95
+ return propRef(param)
85
96
  }
86
97
  }
87
98
 
88
99
  const propName = ctx.extractPropNameFromInitialValue(value, preParsed)
89
- if (propName && propsParams?.some(p => p.name === propName)) {
90
- return propRef(propName)
100
+ const param = propName ? propsParams?.find(p => p.name === propName) : undefined
101
+ if (param) {
102
+ return propRef(param)
91
103
  }
92
104
 
93
105
  if (typeInfo.kind === 'primitive') {
94
106
  if (typeInfo.primitive === 'boolean') {
107
+ // Structural first: the SAME initial value, already parsed
108
+ // (`SignalInfo.parsed`/module-const `parsed`) — text-matching
109
+ // `value === 'true'` is the fallback for a caller with no `preParsed`
110
+ // (an unsupported shape `tsNodeToParsedExpr` couldn't represent).
111
+ if (preParsed?.kind === 'literal' && preParsed.literalType === 'boolean' && typeof preParsed.value === 'boolean') {
112
+ return preParsed.value ? 'true' : 'false'
113
+ }
95
114
  return value === 'true' ? 'true' : 'false'
96
115
  }
97
116
  if (typeInfo.primitive === 'number') {
98
- // Leading `-` (#2168 math-methods: `createSignal(-7.6)`) without it,
99
- // a negative initial value never matches either literal shape below
100
- // and silently falls to the `0` zero-value fallback, regardless of the
101
- // field's Go type.
117
+ // Structural first `numberLiteralRawGo` unwraps a leading unary minus
118
+ // (#2168 math-methods: `createSignal(-7.6)`) off the literal's OWN
119
+ // `raw` token (exact source spelling, never a re-stringified value).
120
+ const numGo = preParsed ? numberLiteralRawGo(preParsed) : null
121
+ if (numGo !== null) return numGo
122
+ // Text fallback for a caller with no `preParsed`. Leading `-` handled
123
+ // the same way (without it, a negative initial value never matches
124
+ // either literal shape below and silently falls to the `0`
125
+ // zero-value fallback, regardless of the field's Go type).
102
126
  if (/^-?\d+$/.test(value)) return value
103
127
  if (/^-?\d+\.\d+$/.test(value)) return value
104
128
  return '0'
105
129
  }
106
130
  if (typeInfo.primitive === 'string') {
107
- if (value.startsWith("'") || value.endsWith("'")) {
131
+ // Structural first: `JSON.stringify` re-quotes/escapes the literal's
132
+ // unquoted `value` for Go — correct for any embedded quote/backslash,
133
+ // unlike the text fallback's blind `'` → `"` swap below.
134
+ if (preParsed?.kind === 'literal' && preParsed.literalType === 'string' && typeof preParsed.value === 'string') {
135
+ return JSON.stringify(preParsed.value)
136
+ }
137
+ if (value.startsWith("'") && value.endsWith("'")) {
108
138
  return value.replace(/'/g, '"')
109
139
  }
110
140
  if (value.startsWith('"') && value.endsWith('"')) {
@@ -227,24 +257,28 @@ export function objectLiteralToGoMap(ctx: GoEmitContext, expr: ParsedExpr): stri
227
257
  export function getSignalInitialValueAsGo(
228
258
  ctx: GoEmitContext,
229
259
  initialValue: string,
230
- propsParams: { name: string }[],
260
+ propsParams: { name: string; sourceName?: string }[],
231
261
  propFallbackVars: ReadonlyMap<string, PropFallbackVar> = EMPTY_PROP_FALLBACK_VARS,
232
262
  signalType?: TypeInfo,
233
263
  ): string {
234
- const propRef = (propName: string): string =>
235
- signalType ? nillableAwarePropRef(ctx, propName, signalType) : `in.${capitalizeFieldName(propName)}`
264
+ const propRef = (param: { name: string; sourceName?: string }): string =>
265
+ signalType
266
+ ? nillableAwarePropRef(ctx, param, signalType)
267
+ : `in.${capitalizeFieldName(param.sourceName ?? param.name)}`
236
268
 
237
- if (propsParams.some(p => p.name === initialValue)) {
269
+ const directParam = propsParams.find(p => p.name === initialValue)
270
+ if (directParam) {
238
271
  const hoisted = propFallbackVars.get(initialValue)
239
272
  if (hoisted) return hoisted.varName
240
- return propRef(initialValue)
273
+ return propRef(directParam)
241
274
  }
242
275
 
243
276
  const propName = ctx.extractPropNameFromInitialValue(initialValue)
244
- if (propName && propsParams.some(p => p.name === propName)) {
245
- const hoisted = propFallbackVars.get(propName)
277
+ const param = propName ? propsParams.find(p => p.name === propName) : undefined
278
+ if (param) {
279
+ const hoisted = propFallbackVars.get(propName!)
246
280
  if (hoisted) return hoisted.varName
247
- return propRef(propName)
281
+ return propRef(param)
248
282
  }
249
283
 
250
284
  // single quotes are normalized to Go double quotes
@@ -320,7 +354,7 @@ function resolveMapJoinBaseAsGo(
320
354
  ctx: GoEmitContext,
321
355
  object: ParsedExpr,
322
356
  signals: { getter: string; initialValue: string; type?: TypeInfo; parsed?: ParsedExpr }[],
323
- propsParams: { name: string }[],
357
+ propsParams: { name: string; sourceName?: string }[],
324
358
  ): string | null {
325
359
  if (object.kind === 'call' && object.callee.kind === 'identifier' && object.args.length === 0) {
326
360
  const calleeName = object.callee.name
@@ -337,8 +371,9 @@ function resolveMapJoinBaseAsGo(
337
371
  : object.kind === 'identifier'
338
372
  ? object.name
339
373
  : null
340
- if (propName && propsParams.some(p => p.name === propName)) {
341
- return `in.${capitalizeFieldName(propName)}`
374
+ const param = propName ? propsParams.find(p => p.name === propName) : undefined
375
+ if (param) {
376
+ return `in.${capitalizeFieldName(param.sourceName ?? param.name)}`
342
377
  }
343
378
 
344
379
  if (object.kind === 'array-literal') {
@@ -376,7 +411,7 @@ export function mapJoinChainToGo(
376
411
  ctx: GoEmitContext,
377
412
  chain: { object: ParsedExpr; arrow: Extract<ParsedExpr, { kind: 'arrow' }>; sepArg?: ParsedExpr },
378
413
  signals: { getter: string; initialValue: string; type?: TypeInfo; parsed?: ParsedExpr }[],
379
- propsParams: { name: string }[],
414
+ propsParams: { name: string; sourceName?: string }[],
380
415
  propFallbackVars: ReadonlyMap<string, PropFallbackVar>,
381
416
  ): string | null {
382
417
  const itemsGo = resolveMapJoinBaseAsGo(ctx, chain.object, signals, propsParams)
@@ -393,11 +428,12 @@ export function mapJoinChainToGo(
393
428
  for (const name of freeVars) {
394
429
  const sig = signals.find(s => s.getter === name)
395
430
  let goExpr: string | null = null
431
+ const freeVarParam = propsParams.find(p => p.name === name)
396
432
  if (sig) {
397
433
  goExpr = getSignalInitialValueAsGo(ctx, sig.initialValue, propsParams, propFallbackVars, sig.type)
398
- } else if (propsParams.some(p => p.name === name)) {
434
+ } else if (freeVarParam) {
399
435
  const hoisted = propFallbackVars.get(name)
400
- goExpr = hoisted ? hoisted.varName : `in.${capitalizeFieldName(name)}`
436
+ goExpr = hoisted ? hoisted.varName : `in.${capitalizeFieldName(freeVarParam.sourceName ?? name)}`
401
437
  }
402
438
  if (goExpr === null) return null
403
439
  envEntries.push(`${JSON.stringify(name)}: ${goExpr}`)
@@ -12,6 +12,12 @@
12
12
  * fixture-divergences section of `ui/compat.lock.json` — surfaced on
13
13
  * the docs compatibility-matrix page. Graduating an entry means fixing
14
14
  * the adapter (or the shared compiler layer) and deleting the line.
15
+ *
16
+ * Empty — the file's last live entry (`aliased-destructured-prop`, #2525)
17
+ * graduated: the Input struct field is now keyed by `sourceName ?? name`
18
+ * (caller-facing), so the caller-side composite literal `go run`s clean.
19
+ * Keep the file (and this header) when the set is empty — the next
20
+ * divergence lands here, not in a re-created file.
15
21
  */
16
22
 
17
23
  import type { RenderDivergences } from '@barefootjs/jsx'
@@ -29,19 +35,4 @@ export const renderDivergences: RenderDivergences = {
29
35
  // production). `buildDynamicChildLoopSeeding` (this package's
30
36
  // `test-render.ts`) now replicates that documented contract for a
31
37
  // signal-backed dynamic child-component loop.
32
-
33
- // Onboarding TSX-fidelity fixtures (PR #2461): `expectedHtml` was
34
- // hand-authored to the CORRECT output while the emission bug lived in
35
- // the shared compiler layer — every adapter, including the Hono
36
- // reference, used to emit the broken form. That shared-layer defect
37
- // (#2460) is now FIXED (b4f5075): `expectedHtml` is generated from the
38
- // Hono reference like any other fixture. The remaining gap is
39
- // Go-specific — the Input struct field stays keyed by the local
40
- // binding instead of `sourceName ?? name`, so the caller-side struct
41
- // literal fails `go run` outright — tracked by #2525 (go-template's
42
- // `go run` exit-1 failure). Graduate by keying the Input struct field
43
- // by `sourceName ?? name` and deleting this line (and the matching
44
- // hono `skipJsx` entry, already gone).
45
- 'aliased-destructured-prop':
46
- 'aliased destructured prop `{ n: count }` loses its rename — the Input struct field is Count `json:"count"`, so the caller-side struct literal keyed by the real prop name fails `go run` outright (unknown field N, exit 1) (https://github.com/piconic-ai/barefootjs/issues/2525)',
47
38
  }
@@ -673,7 +673,12 @@ function buildGoPropsInit(
673
673
  // declared params are `className` / `type`) emits a top-level
674
674
  // `Placeholder:` initializer and Go fails with `unknown field Placeholder
675
675
  // in struct literal of type InputInput`. (#1467 Phase 2b)
676
- const declaredParams = new Set((ir?.metadata.propsParams ?? []).map(p => p.name))
676
+ // `key` is caller-facing (what the fixture's caller supplied), so this
677
+ // set must be built from `sourceName ?? name` — a local-only set routes
678
+ // an aliased param into the rest bag.
679
+ const declaredParams = new Set(
680
+ (ir?.metadata.propsParams ?? []).map(p => p.sourceName ?? p.name),
681
+ )
677
682
  const restPropsName = ir?.metadata.restPropsName ?? null
678
683
  const restBagField = restPropsName ? capitalizeFieldName(restPropsName) : null
679
684