@barefootjs/go-template 0.31.1 → 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.
- package/dist/adapter/go-template-adapter.d.ts +49 -41
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +167 -101
- package/dist/adapter/lib/types.d.ts +4 -1
- package/dist/adapter/lib/types.d.ts.map +1 -1
- package/dist/adapter/memo/memo-compute.d.ts +9 -0
- package/dist/adapter/memo/memo-compute.d.ts.map +1 -1
- package/dist/adapter/memo/memo-type.d.ts +2 -0
- package/dist/adapter/memo/memo-type.d.ts.map +1 -1
- package/dist/adapter/props/prop-types.d.ts +2 -1
- package/dist/adapter/props/prop-types.d.ts.map +1 -1
- package/dist/adapter/spread/spread-codegen.d.ts.map +1 -1
- package/dist/adapter/type/type-codegen.d.ts +22 -5
- package/dist/adapter/type/type-codegen.d.ts.map +1 -1
- package/dist/adapter/value/parsed-literal-to-go.d.ts +12 -0
- package/dist/adapter/value/parsed-literal-to-go.d.ts.map +1 -1
- package/dist/adapter/value/value-lowering.d.ts +3 -0
- package/dist/adapter/value/value-lowering.d.ts.map +1 -1
- package/dist/index.js +168 -104
- package/dist/render-divergences.d.ts +6 -0
- package/dist/render-divergences.d.ts.map +1 -1
- package/dist/vite.js +493 -192
- package/package.json +5 -5
- package/src/__tests__/go-template-adapter.test.ts +155 -10
- package/src/adapter/go-template-adapter.ts +213 -128
- package/src/adapter/lib/types.ts +4 -1
- package/src/adapter/memo/memo-compute.ts +21 -17
- package/src/adapter/memo/memo-type.ts +5 -5
- package/src/adapter/props/prop-types.ts +6 -5
- package/src/adapter/spread/spread-codegen.ts +12 -5
- package/src/adapter/type/type-codegen.ts +86 -25
- package/src/adapter/value/parsed-literal-to-go.ts +28 -10
- package/src/adapter/value/value-lowering.ts +65 -29
- package/src/render-divergences.ts +6 -15
- package/src/test-render.ts +6 -1
package/src/adapter/lib/types.ts
CHANGED
|
@@ -134,7 +134,10 @@ export interface SpreadSlotInfo {
|
|
|
134
134
|
export interface PropFallbackVar {
|
|
135
135
|
/** Local variable name (typically the lowercase prop identifier). */
|
|
136
136
|
varName: string
|
|
137
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* Capitalised Go field name on the `Input` struct — caller-facing
|
|
139
|
+
* (`sourceName ?? name`, #2525), since every reader does `in.${fieldName}`.
|
|
140
|
+
*/
|
|
138
141
|
fieldName: string
|
|
139
142
|
/** Go literal used when the input value equals its zero value. */
|
|
140
143
|
goFallback: string
|
|
@@ -109,7 +109,7 @@ export function matchFilterArmMemo(
|
|
|
109
109
|
ctx: GoEmitContext,
|
|
110
110
|
body: ParsedExpr,
|
|
111
111
|
signals: { getter: string; initialValue: string; type?: TypeInfo }[],
|
|
112
|
-
propsParams: { name: string; type?: TypeInfo; defaultValue?: string }[],
|
|
112
|
+
propsParams: { name: string; sourceName?: string; type?: TypeInfo; defaultValue?: string }[],
|
|
113
113
|
): { propName: string; predJSON: string; paramName: string; freeVars: string[] } | null {
|
|
114
114
|
const cb = asCallbackMethodCall(body)
|
|
115
115
|
if (!cb || cb.method !== 'filter') return null
|
|
@@ -146,7 +146,7 @@ export function filterArmEarlierSiblingRefs(
|
|
|
146
146
|
ctx: GoEmitContext,
|
|
147
147
|
memo: { name: string; parsed?: ParsedExpr },
|
|
148
148
|
signals: { getter: string; initialValue: string; type?: TypeInfo }[],
|
|
149
|
-
propsParams: { name: string; type?: TypeInfo; defaultValue?: string }[],
|
|
149
|
+
propsParams: { name: string; sourceName?: string; type?: TypeInfo; defaultValue?: string }[],
|
|
150
150
|
): string[] {
|
|
151
151
|
if (!memo.parsed) return []
|
|
152
152
|
const match = matchFilterArmMemo(ctx, memo.parsed, signals, propsParams)
|
|
@@ -179,7 +179,7 @@ export function computeMemoInitialValue(
|
|
|
179
179
|
ctx: GoEmitContext,
|
|
180
180
|
memo: { name: string; computation: string; deps: string[]; parsed?: ParsedExpr },
|
|
181
181
|
signals: { getter: string; initialValue: string; type?: TypeInfo }[],
|
|
182
|
-
propsParams: { name: string; type?: TypeInfo; defaultValue?: string }[],
|
|
182
|
+
propsParams: { name: string; sourceName?: string; type?: TypeInfo; defaultValue?: string }[],
|
|
183
183
|
propFallbackVars: ReadonlyMap<string, PropFallbackVar> = EMPTY_PROP_FALLBACK_VARS,
|
|
184
184
|
goType?: string,
|
|
185
185
|
): string {
|
|
@@ -221,7 +221,7 @@ export function memoInitialFromParsedBody(
|
|
|
221
221
|
ctx: GoEmitContext,
|
|
222
222
|
body: ParsedExpr,
|
|
223
223
|
signals: { getter: string; initialValue: string; type?: TypeInfo }[],
|
|
224
|
-
propsParams: { name: string; type?: TypeInfo; defaultValue?: string }[],
|
|
224
|
+
propsParams: { name: string; sourceName?: string; type?: TypeInfo; defaultValue?: string; parsed?: ParsedExpr }[],
|
|
225
225
|
propFallbackVars: ReadonlyMap<string, PropFallbackVar>,
|
|
226
226
|
currentMemoName: string,
|
|
227
227
|
resolving: ReadonlySet<string> = new Set(),
|
|
@@ -229,7 +229,8 @@ export function memoInitialFromParsedBody(
|
|
|
229
229
|
const propRef = (propName: string): string => {
|
|
230
230
|
const hoisted = propFallbackVars.get(propName)
|
|
231
231
|
if (hoisted) return hoisted.varName
|
|
232
|
-
|
|
232
|
+
const param = propsParams.find(p => p.name === propName)
|
|
233
|
+
return `in.${capitalizeFieldName(param?.sourceName ?? propName)}`
|
|
233
234
|
}
|
|
234
235
|
// A request-scoped env-signal read (`searchParams().get('k')`, any local
|
|
235
236
|
// alias, #1922) → the literal key and the reader's canonical field name,
|
|
@@ -393,7 +394,9 @@ export function memoInitialFromParsedBody(
|
|
|
393
394
|
// being concretely typed, wouldn't compile against `nil` either).
|
|
394
395
|
if (param && ctx.state.nillablePropNames.has(propName)) {
|
|
395
396
|
const isNe = body.op === '!==' || body.op === '!='
|
|
396
|
-
|
|
397
|
+
// `nillablePropNames` stays keyed by the LOCAL name (a source-level
|
|
398
|
+
// set); the emitted field is caller-facing (#2525).
|
|
399
|
+
return `in.${capitalizeFieldName(param.sourceName ?? propName)} ${isNe ? '!=' : '=='} nil`
|
|
397
400
|
}
|
|
398
401
|
}
|
|
399
402
|
}
|
|
@@ -532,7 +535,7 @@ export function memoInitialFromParsedBody(
|
|
|
532
535
|
if (hoisted) return `${hoisted.varName} ${operator} ${operand}`
|
|
533
536
|
const fieldName = capitalizeFieldName(propName)
|
|
534
537
|
if (param.type) {
|
|
535
|
-
const goType = typeInfoToGo(ctx, param.type, param.defaultValue)
|
|
538
|
+
const goType = typeInfoToGo(ctx, param.type, param.defaultValue, param.parsed)
|
|
536
539
|
if (goType === 'interface{}') return `in.${fieldName}.(int) ${operator} ${operand}`
|
|
537
540
|
}
|
|
538
541
|
return `in.${fieldName} ${operator} ${operand}`
|
|
@@ -544,9 +547,9 @@ export function memoInitialFromParsedBody(
|
|
|
544
547
|
const varName = body.left.name
|
|
545
548
|
const param = propsParams.find(p => p.name === varName)
|
|
546
549
|
if (param) {
|
|
547
|
-
const fieldName = capitalizeFieldName(varName)
|
|
550
|
+
const fieldName = capitalizeFieldName(param.sourceName ?? varName)
|
|
548
551
|
if (param.type) {
|
|
549
|
-
const goType = typeInfoToGo(ctx, param.type, param.defaultValue)
|
|
552
|
+
const goType = typeInfoToGo(ctx, param.type, param.defaultValue, param.parsed)
|
|
550
553
|
if (goType === 'interface{}') return `in.${fieldName}.(int) ${operator} ${operand}`
|
|
551
554
|
}
|
|
552
555
|
return `in.${fieldName} ${operator} ${operand}`
|
|
@@ -574,7 +577,7 @@ export function memoInitialFromParsedBody(
|
|
|
574
577
|
// () => var — destructured prop, return the input field directly.
|
|
575
578
|
if (body.kind === 'identifier') {
|
|
576
579
|
const param = propsParams.find(p => p.name === body.name)
|
|
577
|
-
if (param) return `in.${capitalizeFieldName(body.name)}`
|
|
580
|
+
if (param) return `in.${capitalizeFieldName(param.sourceName ?? body.name)}`
|
|
578
581
|
}
|
|
579
582
|
|
|
580
583
|
// () => <a> + <b> + … — a string-concatenation chain whose every leaf is a
|
|
@@ -607,7 +610,7 @@ function resolveStringConcatChainGo(
|
|
|
607
610
|
ctx: GoEmitContext,
|
|
608
611
|
expr: ParsedExpr,
|
|
609
612
|
signals: { getter: string; initialValue: string; type?: TypeInfo }[],
|
|
610
|
-
propsParams: { name: string; type?: TypeInfo; defaultValue?: string }[],
|
|
613
|
+
propsParams: { name: string; sourceName?: string; type?: TypeInfo; defaultValue?: string }[],
|
|
611
614
|
propFallbackVars: ReadonlyMap<string, PropFallbackVar>,
|
|
612
615
|
propRef: (propName: string) => string,
|
|
613
616
|
): string | null {
|
|
@@ -647,7 +650,7 @@ export function computeMemoInitialValueOrNull(
|
|
|
647
650
|
ctx: GoEmitContext,
|
|
648
651
|
memo: { name: string; computation: string; deps: string[]; parsed?: ParsedExpr; parsedBlock?: ParsedStatement[]; parsedBlockComplete?: boolean },
|
|
649
652
|
signals: { getter: string; initialValue: string; type?: TypeInfo }[],
|
|
650
|
-
propsParams: { name: string; type?: TypeInfo; defaultValue?: string }[],
|
|
653
|
+
propsParams: { name: string; sourceName?: string; type?: TypeInfo; defaultValue?: string }[],
|
|
651
654
|
propFallbackVars: ReadonlyMap<string, PropFallbackVar> = EMPTY_PROP_FALLBACK_VARS,
|
|
652
655
|
/**
|
|
653
656
|
* Memo names currently being resolved on this call stack — guards against
|
|
@@ -729,7 +732,7 @@ export function resolveGetterValueAsGo(
|
|
|
729
732
|
ctx: GoEmitContext,
|
|
730
733
|
name: string,
|
|
731
734
|
signals: { getter: string; initialValue: string; type?: TypeInfo }[],
|
|
732
|
-
propsParams: { name: string; type?: TypeInfo; defaultValue?: string }[],
|
|
735
|
+
propsParams: { name: string; sourceName?: string; type?: TypeInfo; defaultValue?: string }[],
|
|
733
736
|
propFallbackVars: ReadonlyMap<string, PropFallbackVar>,
|
|
734
737
|
resolving: ReadonlySet<string> = new Set(),
|
|
735
738
|
): string | null {
|
|
@@ -745,7 +748,8 @@ export function resolveGetterValueAsGo(
|
|
|
745
748
|
const stripped = memo.computation.replace(/^\(\)\s*=>\s*/, '')
|
|
746
749
|
const fb = ctx.extractPropFallback(stripped)
|
|
747
750
|
if (fb && capitalizeFieldName(fb.propName) === capitalizeFieldName(memo.name)) {
|
|
748
|
-
const
|
|
751
|
+
const fbParam = propsParams.find(p => p.name === fb.propName)
|
|
752
|
+
const field = `in.${capitalizeFieldName(fbParam?.sourceName ?? fb.propName)}`
|
|
749
753
|
return `func() interface{} { v := interface{}(${field}); if v == nil || v == "" { return ${fb.goFallback} }; return v }()`
|
|
750
754
|
}
|
|
751
755
|
return computeMemoInitialValueOrNull(
|
|
@@ -755,7 +759,7 @@ export function resolveGetterValueAsGo(
|
|
|
755
759
|
const param = propsParams.find(p => p.name === name)
|
|
756
760
|
if (param) {
|
|
757
761
|
const hoisted = propFallbackVars.get(name)
|
|
758
|
-
return hoisted ? hoisted.varName : `in.${capitalizeFieldName(name)}`
|
|
762
|
+
return hoisted ? hoisted.varName : `in.${capitalizeFieldName(param.sourceName ?? name)}`
|
|
759
763
|
}
|
|
760
764
|
return null
|
|
761
765
|
}
|
|
@@ -772,7 +776,7 @@ export function computeComparisonTernaryGo(
|
|
|
772
776
|
ctx: GoEmitContext,
|
|
773
777
|
parsed: ParsedExpr | undefined,
|
|
774
778
|
signals: { getter: string; initialValue: string; type?: TypeInfo }[],
|
|
775
|
-
propsParams: { name: string; type?: TypeInfo; defaultValue?: string }[],
|
|
779
|
+
propsParams: { name: string; sourceName?: string; type?: TypeInfo; defaultValue?: string }[],
|
|
776
780
|
propFallbackVars: ReadonlyMap<string, PropFallbackVar>,
|
|
777
781
|
resolving: ReadonlySet<string> = new Set(),
|
|
778
782
|
): string | null {
|
|
@@ -828,7 +832,7 @@ export function resolveComparisonOperandGo(
|
|
|
828
832
|
ctx: GoEmitContext,
|
|
829
833
|
node: ParsedExpr,
|
|
830
834
|
signals: { getter: string; initialValue: string; type?: TypeInfo }[],
|
|
831
|
-
propsParams: { name: string; type?: TypeInfo; defaultValue?: string }[],
|
|
835
|
+
propsParams: { name: string; sourceName?: string; type?: TypeInfo; defaultValue?: string }[],
|
|
832
836
|
propFallbackVars: ReadonlyMap<string, PropFallbackVar>,
|
|
833
837
|
resolving: ReadonlySet<string> = new Set(),
|
|
834
838
|
): string | null {
|
|
@@ -34,8 +34,8 @@ export function isListFilterMemo(memo: { parsed?: ParsedExpr }): boolean {
|
|
|
34
34
|
export function isBooleanMemo(
|
|
35
35
|
ctx: GoEmitContext,
|
|
36
36
|
memo: { computation: string; deps: string[]; parsed?: ParsedExpr },
|
|
37
|
-
signals: { getter: string; initialValue: string; type: TypeInfo }[],
|
|
38
|
-
propsParamMap: Map<string, { name: string; type: TypeInfo; defaultValue?: string }>,
|
|
37
|
+
signals: { getter: string; initialValue: string; type: TypeInfo; parsed?: ParsedExpr }[],
|
|
38
|
+
propsParamMap: Map<string, { name: string; type: TypeInfo; defaultValue?: string; parsed?: ParsedExpr }>,
|
|
39
39
|
): boolean {
|
|
40
40
|
const c = memo.computation
|
|
41
41
|
// A LIST-valued `.filter(arrow)` memo (#2075) is never boolean, even though
|
|
@@ -58,13 +58,13 @@ export function isBooleanMemo(
|
|
|
58
58
|
if (typeInfoToGo(ctx, sig.type) === 'bool') return true
|
|
59
59
|
// Signal initialised from `props.X ?? false` / a boolean prop.
|
|
60
60
|
if (/\?\?\s*(true|false)\b/.test(sig.initialValue)) return true
|
|
61
|
-
const propName = ctx.extractPropNameFromInitialValue(sig.initialValue) ?? sig.initialValue
|
|
61
|
+
const propName = ctx.extractPropNameFromInitialValue(sig.initialValue, sig.parsed) ?? sig.initialValue
|
|
62
62
|
const prop = propsParamMap.get(propName)
|
|
63
|
-
if (prop && typeInfoToGo(ctx, prop.type, prop.defaultValue) === 'bool') return true
|
|
63
|
+
if (prop && typeInfoToGo(ctx, prop.type, prop.defaultValue, prop.parsed) === 'bool') return true
|
|
64
64
|
return false
|
|
65
65
|
}
|
|
66
66
|
const prop = propsParamMap.get(name)
|
|
67
|
-
return !!prop && typeInfoToGo(ctx, prop.type, prop.defaultValue) === 'bool'
|
|
67
|
+
return !!prop && typeInfoToGo(ctx, prop.type, prop.defaultValue, prop.parsed) === 'bool'
|
|
68
68
|
}
|
|
69
69
|
const ternary = c.match(/=>\s*\w+\(\)\s*\?\s*(\w+)\(\)\s*:\s*(\w+)\(\)/)
|
|
70
70
|
if (ternary) {
|
|
@@ -27,9 +27,9 @@ export function buildPropTypeOverrides(ctx: GoEmitContext, ir: ComponentIR): Map
|
|
|
27
27
|
for (const propName of propNames) {
|
|
28
28
|
const param = ir.metadata.propsParams.find(p => p.name === propName)
|
|
29
29
|
if (!param) continue
|
|
30
|
-
const propGoType = typeInfoToGo(ctx, param.type, param.defaultValue)
|
|
30
|
+
const propGoType = typeInfoToGo(ctx, param.type, param.defaultValue, param.parsed)
|
|
31
31
|
if (propGoType.includes('interface{}')) {
|
|
32
|
-
const signalGoType = typeInfoToGo(ctx, signal.type, signal.initialValue)
|
|
32
|
+
const signalGoType = typeInfoToGo(ctx, signal.type, signal.initialValue, signal.parsed)
|
|
33
33
|
if (!signalGoType.includes('interface{}')) {
|
|
34
34
|
overrides.set(propName, signalGoType)
|
|
35
35
|
}
|
|
@@ -50,7 +50,7 @@ export function buildPropTypeOverrides(ctx: GoEmitContext, ir: ComponentIR): Map
|
|
|
50
50
|
for (const propName of collectToFixedPropNames(ir.root)) {
|
|
51
51
|
const param = ir.metadata.propsParams.find(p => p.name === propName)
|
|
52
52
|
if (!param) continue
|
|
53
|
-
const resolved = overrides.get(propName) ?? typeInfoToGo(ctx, param.type, param.defaultValue)
|
|
53
|
+
const resolved = overrides.get(propName) ?? typeInfoToGo(ctx, param.type, param.defaultValue, param.parsed)
|
|
54
54
|
if (resolved === 'int') {
|
|
55
55
|
overrides.set(propName, 'float64')
|
|
56
56
|
}
|
|
@@ -381,7 +381,8 @@ export function collectPresenceCheckedPropNames(ctx: GoEmitContext, ir: Componen
|
|
|
381
381
|
/**
|
|
382
382
|
* Resolve a prop param's Go struct-field type using the SAME logic
|
|
383
383
|
* `generatePropsStruct` / `generateInputStruct` use: a `propTypeOverrides` entry
|
|
384
|
-
* wins, otherwise `typeInfoToGo(param.type, param.defaultValue)`.
|
|
384
|
+
* wins, otherwise `typeInfoToGo(param.type, param.defaultValue, param.parsed)`.
|
|
385
|
+
* Factored out so
|
|
385
386
|
* the nillable-field set (`collectNillablePropNames`) can't drift from the
|
|
386
387
|
* emitted field types.
|
|
387
388
|
*/
|
|
@@ -390,7 +391,7 @@ export function resolvePropGoType(
|
|
|
390
391
|
param: IRMetadata['propsParams'][number],
|
|
391
392
|
propTypeOverrides: Map<string, string>,
|
|
392
393
|
): string {
|
|
393
|
-
const base = propTypeOverrides.get(param.name) ?? typeInfoToGo(ctx, param.type, param.defaultValue)
|
|
394
|
+
const base = propTypeOverrides.get(param.name) ?? typeInfoToGo(ctx, param.type, param.defaultValue, param.parsed)
|
|
394
395
|
// An OPTIONAL prop typed as a named struct (`opts?: EmblaOptionsType`) lowers
|
|
395
396
|
// to `map[string]interface{}`, not the value struct: a value struct is always
|
|
396
397
|
// truthy in Go templates (so a `{{if .Opts}}`-guarded attribute could never be
|
|
@@ -225,7 +225,7 @@ export function buildSpreadInitializer(
|
|
|
225
225
|
// `{...extras}` resolves to `in.Extras`.
|
|
226
226
|
const param = ir.metadata.propsParams.find(p => p.name === trimmed)
|
|
227
227
|
if (param) {
|
|
228
|
-
return `in.${capitalizeFieldName(param.name)}`
|
|
228
|
+
return `in.${capitalizeFieldName(param.sourceName ?? param.name)}`
|
|
229
229
|
}
|
|
230
230
|
// 2. SolidJS-style props object: `function(props: P)` → spread
|
|
231
231
|
// `{...props}` enumerates all propsParams into a `map[string]any`
|
|
@@ -235,8 +235,11 @@ export function buildSpreadInitializer(
|
|
|
235
235
|
// `applyRestAttrs` hydrate path still applies them — worse than full
|
|
236
236
|
// enumeration, better than BF101 blocking the build.
|
|
237
237
|
if (ir.metadata.propsObjectName === trimmed) {
|
|
238
|
+
// Bag key stays the caller-facing prop name (mirrors the spread bag's
|
|
239
|
+
// external shape); Input field read is caller-facing too (#2525) —
|
|
240
|
+
// identity here since the `props`-object pattern has no aliasing.
|
|
238
241
|
const entries = ir.metadata.propsParams.map(p =>
|
|
239
|
-
`${JSON.stringify(p.name)}: in.${capitalizeFieldName(p.name)}`,
|
|
242
|
+
`${JSON.stringify(p.sourceName ?? p.name)}: in.${capitalizeFieldName(p.sourceName ?? p.name)}`,
|
|
240
243
|
)
|
|
241
244
|
return `map[string]any{${entries.join(', ')}}`
|
|
242
245
|
}
|
|
@@ -344,7 +347,7 @@ function conditionToGoBool(
|
|
|
344
347
|
if (node.kind !== 'identifier') return null
|
|
345
348
|
const param = ir.metadata.propsParams.find(p => p.name === node.name)
|
|
346
349
|
if (!param) return null
|
|
347
|
-
const field = `in.${capitalizeFieldName(param.name)}`
|
|
350
|
+
const field = `in.${capitalizeFieldName(param.sourceName ?? param.name)}`
|
|
348
351
|
const prim = param.type.kind === 'primitive' ? param.type.primitive : undefined
|
|
349
352
|
let truthy: string
|
|
350
353
|
if (prim === 'boolean') {
|
|
@@ -396,7 +399,7 @@ function objectLiteralToGoSpreadMap(
|
|
|
396
399
|
} else if (val.kind === 'identifier') {
|
|
397
400
|
const param = ir.metadata.propsParams.find(p => p.name === val.name)
|
|
398
401
|
if (!param) return null
|
|
399
|
-
goVal = `in.${capitalizeFieldName(param.name)}`
|
|
402
|
+
goVal = `in.${capitalizeFieldName(param.sourceName ?? param.name)}`
|
|
400
403
|
} else {
|
|
401
404
|
const indexed = recordIndexAccessToGoMap(ctx, val, ir)
|
|
402
405
|
if (indexed === null) return null
|
|
@@ -453,6 +456,10 @@ function recordIndexAccessToGoMap(
|
|
|
453
456
|
return `${JSON.stringify(e.key)}: ${mapVal}`
|
|
454
457
|
})
|
|
455
458
|
ctx.state.usesFmt = true
|
|
456
|
-
|
|
459
|
+
// `parsed.indexPropName` is the LOCAL binding (`parseRecordIndexAccess` is
|
|
460
|
+
// shared with other adapters and stays name-agnostic); resolve the Input
|
|
461
|
+
// field caller-facing here (#2525) rather than in the shared parser.
|
|
462
|
+
const indexParam = ir.metadata.propsParams.find(p => p.name === parsed.indexPropName)
|
|
463
|
+
const field = `in.${capitalizeFieldName(indexParam?.sourceName ?? parsed.indexPropName)}`
|
|
457
464
|
return `map[string]any{${entries.join(', ')}}[fmt.Sprint(${field})]`
|
|
458
465
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* pure.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import type { TypeInfo } from '@barefootjs/jsx'
|
|
14
|
+
import type { ParsedExpr, TypeInfo } from '@barefootjs/jsx'
|
|
15
15
|
|
|
16
16
|
import type { GoEmitContext } from '../emit-context.ts'
|
|
17
17
|
|
|
@@ -57,6 +57,48 @@ export function collapseLiteralUnion(typeInfo: TypeInfo): TypeInfo {
|
|
|
57
57
|
return { kind: 'primitive', raw: typeInfo.raw, primitive: first }
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* A literal number's numeric value, unwrapping a leading unary minus (`-7.6`
|
|
62
|
+
* parses as `{kind:'unary', op:'-', argument:{literalType:'number', value:7.6}}`
|
|
63
|
+
* — the same shape `parsedLiteralToGo`'s own unary-minus arm unwraps,
|
|
64
|
+
* `value-lowering.ts`/`parsed-literal-to-go.ts`). Returns `null` for anything
|
|
65
|
+
* that isn't (possibly negated) a number literal.
|
|
66
|
+
*/
|
|
67
|
+
function literalNumberValue(expr: ParsedExpr): number | null {
|
|
68
|
+
if (expr.kind === 'literal' && expr.literalType === 'number' && typeof expr.value === 'number') {
|
|
69
|
+
return expr.value
|
|
70
|
+
}
|
|
71
|
+
if (
|
|
72
|
+
expr.kind === 'unary' &&
|
|
73
|
+
expr.op === '-' &&
|
|
74
|
+
expr.argument.kind === 'literal' &&
|
|
75
|
+
expr.argument.literalType === 'number' &&
|
|
76
|
+
typeof expr.argument.value === 'number'
|
|
77
|
+
) {
|
|
78
|
+
return -expr.argument.value
|
|
79
|
+
}
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Structural counterpart to {@link inferTypeFromValue}: classify a parsed
|
|
85
|
+
* literal's Go type from its `ParsedExpr` shape instead of its source text.
|
|
86
|
+
* Returns `null` for anything not covered here (identifier/call/member,
|
|
87
|
+
* object-literal — same scope `inferTypeFromValue`'s text scan covers, so a
|
|
88
|
+
* caller falling back to the text path for those sees identical results).
|
|
89
|
+
*/
|
|
90
|
+
function inferGoTypeFromParsed(expr: ParsedExpr): string | null {
|
|
91
|
+
const n = literalNumberValue(expr)
|
|
92
|
+
if (n !== null) return Number.isInteger(n) ? 'int' : 'float64'
|
|
93
|
+
if (expr.kind === 'literal') {
|
|
94
|
+
if (expr.literalType === 'boolean') return 'bool'
|
|
95
|
+
if (expr.literalType === 'string') return 'string'
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
if (expr.kind === 'array-literal') return '[]interface{}'
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
|
|
60
102
|
/**
|
|
61
103
|
* Convert a `TypeInfo` to a Go type string.
|
|
62
104
|
*
|
|
@@ -65,12 +107,18 @@ export function collapseLiteralUnion(typeInfo: TypeInfo): TypeInfo {
|
|
|
65
107
|
* `primitive`/`number` (#2168 math-methods/number-tofixed — a bare TS
|
|
66
108
|
* `number` blindly mapped to Go `int`, so a fractional signal initial
|
|
67
109
|
* value like `-7.6` silently truncated to the Go zero value)
|
|
110
|
+
* @param preParsed the SAME default/initial value as `defaultValue`, already
|
|
111
|
+
* parsed to structure (`SignalInfo.parsed` / `ParamInfo.parsed`) — preferred
|
|
112
|
+
* over regexing `defaultValue`'s text when present. Callers with no
|
|
113
|
+
* structural counterpart (a shape `tsNodeToParsedExpr` doesn't support) pass
|
|
114
|
+
* nothing and fall through to the text path.
|
|
68
115
|
* @returns the Go type, falling back to `interface{}` when unresolvable
|
|
69
116
|
*/
|
|
70
117
|
export function typeInfoToGo(
|
|
71
118
|
ctx: GoEmitContext,
|
|
72
119
|
_typeInfo: TypeInfo,
|
|
73
120
|
defaultValue?: string,
|
|
121
|
+
preParsed?: ParsedExpr,
|
|
74
122
|
): string {
|
|
75
123
|
const typeInfo = collapseLiteralUnion(_typeInfo)
|
|
76
124
|
switch (typeInfo.kind) {
|
|
@@ -78,8 +126,11 @@ export function typeInfoToGo(
|
|
|
78
126
|
switch (typeInfo.primitive) {
|
|
79
127
|
case 'string':
|
|
80
128
|
return 'string'
|
|
81
|
-
case 'number':
|
|
129
|
+
case 'number': {
|
|
130
|
+
const n = preParsed ? literalNumberValue(preParsed) : null
|
|
131
|
+
if (n !== null) return Number.isInteger(n) ? 'int' : 'float64'
|
|
82
132
|
return defaultValue !== undefined ? numberPrimitiveGoType(defaultValue) : 'int'
|
|
133
|
+
}
|
|
83
134
|
case 'boolean':
|
|
84
135
|
return 'bool'
|
|
85
136
|
default:
|
|
@@ -108,58 +159,68 @@ export function typeInfoToGo(
|
|
|
108
159
|
if (typeInfo.raw && (ctx.state.localStructFields.has(typeInfo.raw) || ctx.state.localTypeAliases.has(typeInfo.raw))) {
|
|
109
160
|
return typeInfo.raw
|
|
110
161
|
}
|
|
111
|
-
//
|
|
162
|
+
// A named type with no backing struct/alias — an external/unresolved
|
|
163
|
+
// reference. `typeNodeToTypeInfo` already normalises every ARRAY spelling
|
|
164
|
+
// (`T[]`, `Array<T>`, `ReadonlyArray<T>`) to `kind: 'array'` before a
|
|
165
|
+
// `TypeInfo` ever reaches here (#2480's structural literal-type pass did
|
|
166
|
+
// the same for literal unions), so `typeInfo.raw` at this point is never
|
|
167
|
+
// an array shape — `tsTypeStringToGo` is a plain lookup, not a parser.
|
|
112
168
|
if (typeInfo.raw) {
|
|
113
169
|
const resolved = tsTypeStringToGo(ctx, typeInfo.raw)
|
|
114
170
|
if (resolved !== 'interface{}') return resolved
|
|
115
171
|
}
|
|
116
172
|
return 'interface{}'
|
|
117
|
-
case 'unknown':
|
|
173
|
+
case 'unknown': {
|
|
174
|
+
const inferred = preParsed ? inferGoTypeFromParsed(preParsed) : null
|
|
175
|
+
if (inferred) return inferred
|
|
118
176
|
if (defaultValue !== undefined) {
|
|
119
177
|
return inferTypeFromValue(defaultValue)
|
|
120
178
|
}
|
|
121
179
|
return 'interface{}'
|
|
180
|
+
}
|
|
122
181
|
default:
|
|
123
182
|
return 'interface{}'
|
|
124
183
|
}
|
|
125
184
|
}
|
|
126
185
|
|
|
127
186
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
187
|
+
* Look up a raw TypeScript type-reference name against the component's own
|
|
188
|
+
* Go-backed local types. NOT a parser: by the time a `TypeInfo` reaches here
|
|
189
|
+
* (`typeInfoToGo`'s `'interface'` case, its one caller) `typeNodeToTypeInfo`
|
|
190
|
+
* has already normalised every array spelling to `kind: 'array'` and every
|
|
191
|
+
* primitive keyword to `kind: 'primitive'` — `tsType` is always a bare named
|
|
192
|
+
* reference (a struct, a string-union alias, or an unbacked/external name),
|
|
193
|
+
* never `T[]` / `Array<T>` / `'number'` text to re-parse. (#2484: this used to
|
|
194
|
+
* carry `t.endsWith('[]')` / `Array<(.+)>` regex branches as a fallback for
|
|
195
|
+
* that dead case — unreachable given the analyzer's normalisation, deleted.)
|
|
130
196
|
*/
|
|
131
197
|
export function tsTypeStringToGo(ctx: GoEmitContext, tsType: string): string {
|
|
132
198
|
const t = tsType.trim()
|
|
133
|
-
if (t === 'number') return 'int'
|
|
134
|
-
if (t === 'string') return 'string'
|
|
135
|
-
if (t === 'boolean' || t === 'bool') return 'bool'
|
|
136
|
-
if (t.endsWith('[]')) {
|
|
137
|
-
const elem = t.slice(0, -2)
|
|
138
|
-
return `[]${tsTypeStringToGo(ctx, elem)}`
|
|
139
|
-
}
|
|
140
|
-
const arrayMatch = t.match(/^Array<(.+)>$/)
|
|
141
|
-
if (arrayMatch) return `[]${tsTypeStringToGo(ctx, arrayMatch[1])}`
|
|
142
|
-
// Same backing gate as `typeInfoToGo`'s 'interface' case above — an
|
|
143
|
-
// unbacked local type name (a tuple alias with no struct fields) must not
|
|
144
|
-
// be returned bare, or the generated code references an undeclared type.
|
|
145
199
|
if (ctx.state.localStructFields.has(t) || ctx.state.localTypeAliases.has(t)) return t
|
|
146
200
|
return 'interface{}'
|
|
147
201
|
}
|
|
148
202
|
|
|
149
203
|
/**
|
|
150
204
|
* Distinguish Go `int` vs `float64` for a `number`-typed field from the
|
|
151
|
-
* literal source text of its default/initial value.
|
|
152
|
-
* when `
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
205
|
+
* literal source text of its default/initial value. TEXT FALLBACK: used only
|
|
206
|
+
* when `typeInfoToGo`'s caller has no structural `ParsedExpr` for the same
|
|
207
|
+
* value to pass as `preParsed` (see `inferGoTypeFromParsed`/`literalNumberValue`
|
|
208
|
+
* above, which this mirrors on parsed structure). Falls back to `int` when
|
|
209
|
+
* `value` isn't recognizably a bare numeric literal (e.g. a destructured
|
|
210
|
+
* default that's itself an expression, `props.initial ?? 0`) — `int` remains
|
|
211
|
+
* the blind fallback for `kind: 'primitive'`; only a literal fractional value
|
|
212
|
+
* (`-7.6`) is positive enough evidence to widen to `float64`.
|
|
157
213
|
*/
|
|
158
214
|
function numberPrimitiveGoType(value: string): string {
|
|
159
215
|
return /^-?\d+\.\d+$/.test(value) ? 'float64' : 'int'
|
|
160
216
|
}
|
|
161
217
|
|
|
162
|
-
/**
|
|
218
|
+
/**
|
|
219
|
+
* Infer a Go type from a JS value literal's source TEXT; `interface{}` when
|
|
220
|
+
* unrecognized. TEXT FALLBACK: mirrors `inferGoTypeFromParsed` above on raw
|
|
221
|
+
* text, used only when `typeInfoToGo`'s caller has no structural `ParsedExpr`
|
|
222
|
+
* for the same value to pass as `preParsed`.
|
|
223
|
+
*/
|
|
163
224
|
export function inferTypeFromValue(value: string): string {
|
|
164
225
|
if (value === 'true' || value === 'false') return 'bool'
|
|
165
226
|
if (/^-?\d+$/.test(value)) return 'int'
|
|
@@ -74,13 +74,18 @@ function bakeInlineObjectAsGoMap(ctx: GoEmitContext, expr: ParsedExpr): string |
|
|
|
74
74
|
return `map[string]interface{}{${entries.join(', ')}}`
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
/**
|
|
78
|
+
* A literal number's exact Go source text, unwrapping a leading unary minus
|
|
79
|
+
* (`-1` parses as `{kind:'unary', op:'-', argument:{literalType:'number'}}`).
|
|
80
|
+
* Needs the exact source token — re-stringifying the parsed numeric VALUE
|
|
81
|
+
* could change spelling / lose precision — so this defers (`null`) rather
|
|
82
|
+
* than reconstructing one, same as every other non-literal shape here.
|
|
83
|
+
*
|
|
84
|
+
* Shared by `parsedLiteralToGo`'s own number/unary-minus cases below and
|
|
85
|
+
* `convertInitialValue`'s primitive-number branch (`value-lowering.ts`) —
|
|
86
|
+
* the SAME literal needs the SAME exact-token handling wherever it's baked.
|
|
87
|
+
*/
|
|
88
|
+
export function numberLiteralRawGo(expr: ParsedExpr): string | null {
|
|
84
89
|
if (
|
|
85
90
|
expr.kind === 'unary' &&
|
|
86
91
|
expr.op === '-' &&
|
|
@@ -89,6 +94,19 @@ export function parsedLiteralToGo(
|
|
|
89
94
|
) {
|
|
90
95
|
return expr.argument.raw !== undefined ? `-${expr.argument.raw}` : null
|
|
91
96
|
}
|
|
97
|
+
if (expr.kind === 'literal' && expr.literalType === 'number') {
|
|
98
|
+
return expr.raw ?? null
|
|
99
|
+
}
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function parsedLiteralToGo(
|
|
104
|
+
ctx: GoEmitContext,
|
|
105
|
+
expr: ParsedExpr,
|
|
106
|
+
typeInfo?: TypeInfo,
|
|
107
|
+
): string | null {
|
|
108
|
+
const numberGo = numberLiteralRawGo(expr)
|
|
109
|
+
if (numberGo !== null) return numberGo
|
|
92
110
|
|
|
93
111
|
if (expr.kind === 'literal') {
|
|
94
112
|
switch (expr.literalType) {
|
|
@@ -96,9 +114,9 @@ export function parsedLiteralToGo(
|
|
|
96
114
|
// `value` is the unquoted text; `JSON.stringify` re-quotes it for Go.
|
|
97
115
|
return JSON.stringify(expr.value)
|
|
98
116
|
case 'number':
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
return
|
|
117
|
+
// A number literal with no `raw` token (`numberLiteralRawGo` already
|
|
118
|
+
// returned null above) — defer.
|
|
119
|
+
return null
|
|
102
120
|
case 'boolean':
|
|
103
121
|
return expr.value ? 'true' : 'false'
|
|
104
122
|
case 'null':
|