@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
|
@@ -158,17 +158,12 @@ type HigherOrderShape = {
|
|
|
158
158
|
/**
|
|
159
159
|
* Everything needed to emit a component's #2448 props rebuilder, captured when
|
|
160
160
|
* its own types are generated so a PARENT can emit the registration into its
|
|
161
|
-
* own type block (`emitOwnedReprops`). `
|
|
162
|
-
*
|
|
163
|
-
* `
|
|
164
|
-
* child's own field name before emitting the call, so the switch this spec
|
|
165
|
-
* drives is keyed by that one name on both sides; there is no longer a
|
|
166
|
-
* separate "wire" name to carry (an aliased destructure used to make the
|
|
167
|
-
* parent's JSX-attribute name and the child's field name differ — `"N"` vs
|
|
168
|
-
* `Count` — and this spec used to carry both).
|
|
161
|
+
* own type block (`emitOwnedReprops`). `propsField` (LOCAL) and `inputField`
|
|
162
|
+
* (caller-facing) diverge only for an aliased destructured prop — one name is
|
|
163
|
+
* not enough (see `emitRepropsRegistration`).
|
|
169
164
|
*/
|
|
170
165
|
type RepropsSpec = {
|
|
171
|
-
params: string[]
|
|
166
|
+
params: { propsField: string; inputField: string }[]
|
|
172
167
|
usesSearchParams: boolean
|
|
173
168
|
}
|
|
174
169
|
|
|
@@ -688,35 +683,18 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
688
683
|
private childDerivedFieldDeps: Map<string, Map<string, ReadonlySet<string>>> = new Map()
|
|
689
684
|
|
|
690
685
|
/**
|
|
691
|
-
* component name -> (
|
|
692
|
-
*
|
|
686
|
+
* component name -> (JSX-attribute name -> that component's own Props
|
|
687
|
+
* field, LOCAL binding). `bf.WithProps` treats an unknown field as a
|
|
688
|
+
* silent passthrough, so `bf_with_props`/`bf_reprops` call sites cannot
|
|
689
|
+
* just capitalize the JSX attribute: for `{ n: count }` that names a
|
|
690
|
+
* field (`N`) the Props struct doesn't have, dropping the override with
|
|
691
|
+
* no diagnostic. Input composite literals need no such lookup — Input
|
|
692
|
+
* fields are caller-facing, so `capitalizeFieldName(jsxName)` already
|
|
693
|
+
* names them (see `emitChildField`).
|
|
693
694
|
*
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
* (`loopRowChildPropOverrides`) only knows the JSX attribute it wrote. For
|
|
698
|
-
* an un-aliased prop those are the same string, so this map is an identity
|
|
699
|
-
* for every existing shape and every currently-passing fixture stays
|
|
700
|
-
* byte-identical. For an ALIASED destructure (`{ n: count }`) they differ
|
|
701
|
-
* (`"N"` vs `Count`) — `bf.WithProps` documents unknown-field pairs as a
|
|
702
|
-
* silent passthrough, so emitting the JSX name against a struct that has no
|
|
703
|
-
* such field drops the override with no diagnostic. Resolving through this
|
|
704
|
-
* map once, at the parent's emission site (the only place that has both
|
|
705
|
-
* the JSX name and the child's shape), replaces that silent drop with the
|
|
706
|
-
* correct field.
|
|
707
|
-
*
|
|
708
|
-
* Key = `p.sourceName ?? p.name` (identity for an un-aliased param, the JSX
|
|
709
|
-
* attribute name for an aliased one). Value = `capitalizeFieldName(p.name)`
|
|
710
|
-
* — the child's own field, from its LOCAL binding, same as
|
|
711
|
-
* `generateInputStruct` uses.
|
|
712
|
-
*
|
|
713
|
-
* Populated from the same two doors as `childDerivedFieldDeps` — inside
|
|
714
|
-
* `recordDerivedFieldDeps`, unconditionally (before that method's own
|
|
715
|
-
* `deps.size > 0` gate), so every component gets an entry here regardless
|
|
716
|
-
* of whether it has a derived field. `recordRepropsSpec` (the other nearby
|
|
717
|
-
* populator) returns early for components with no derived field or an
|
|
718
|
-
* ineligible shape — this map must NOT inherit either gate, since an
|
|
719
|
-
* aliased child with no derived field at all is exactly the #2457 shape.
|
|
695
|
+
* Populated inside `recordDerivedFieldDeps` BEFORE its `deps.size > 0`
|
|
696
|
+
* gate: an aliased child with no derived field at all is exactly the
|
|
697
|
+
* #2457 shape, so this map must not inherit `recordRepropsSpec`'s gates.
|
|
720
698
|
*/
|
|
721
699
|
private childPropFieldNames: Map<string, Map<string, string>> = new Map()
|
|
722
700
|
|
|
@@ -798,9 +776,8 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
798
776
|
)
|
|
799
777
|
const canonical = (local: string): string => capitalizeFieldName(sourceOf.get(local) ?? local)
|
|
800
778
|
const propFieldNames = new Set([...paramNames].map(canonical))
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
// docstring for why this can't share `recordRepropsSpec`'s gates.
|
|
779
|
+
// Recorded for EVERY component — this must not share `recordRepropsSpec`'s
|
|
780
|
+
// gates (see `childPropFieldNames`).
|
|
804
781
|
const fieldNames = new Map<string, string>()
|
|
805
782
|
for (const p of ir.metadata.propsParams ?? []) {
|
|
806
783
|
fieldNames.set(p.sourceName ?? p.name, capitalizeFieldName(p.name))
|
|
@@ -944,6 +921,53 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
944
921
|
return this.state.contextConsumers.filter(c => !taken.has(this.contextFieldName(c)))
|
|
945
922
|
}
|
|
946
923
|
|
|
924
|
+
/**
|
|
925
|
+
* True when `param`'s prop is subsumed by a same-name nested-component
|
|
926
|
+
* array field (`<Row>` children collected into `Rows []RowProps`). Must
|
|
927
|
+
* check the LOCAL and caller-facing name together — Input keys its fields
|
|
928
|
+
* caller-facing while Props/NewProps key local, so a one-sided check lets
|
|
929
|
+
* the structs disagree on whether the field exists (a duplicate json tag,
|
|
930
|
+
* or a dead Input field whose caller writes are silently ignored).
|
|
931
|
+
*/
|
|
932
|
+
private isNestedArrayShadowed(
|
|
933
|
+
param: { name: string; sourceName?: string },
|
|
934
|
+
nestedArrayFields: ReadonlySet<string>,
|
|
935
|
+
): boolean {
|
|
936
|
+
return (
|
|
937
|
+
nestedArrayFields.has(capitalizeFieldName(param.name)) ||
|
|
938
|
+
nestedArrayFields.has(capitalizeFieldName(param.sourceName ?? param.name))
|
|
939
|
+
)
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Every Go field name these props params could claim — LOCAL and
|
|
944
|
+
* caller-facing. A context-consumer field must exist in ALL of
|
|
945
|
+
* Input/Props/NewProps or none, and the three key prop fields under
|
|
946
|
+
* different namings, so their shared collision gate cannot check just one
|
|
947
|
+
* (`{ n: searchParams }` collides in Props but not Input).
|
|
948
|
+
*/
|
|
949
|
+
private propParamFieldNamesUnion(params: ReadonlyArray<{ name: string; sourceName?: string }>): string[] {
|
|
950
|
+
return params.flatMap(p => [capitalizeFieldName(p.name), capitalizeFieldName(p.sourceName ?? p.name)])
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Claim `desired` in the struct-wide set of emitted json tags, or fall
|
|
955
|
+
* back to `json:"-"` on collision. The identifier de-dup sets cannot cover
|
|
956
|
+
* this: an aliased prop's tag is caller-facing while other fields' tags
|
|
957
|
+
* stay local, so two DIFFERENT Go identifiers can want the same tag — and
|
|
958
|
+
* `encoding/json` silently drops every field sharing an ambiguous tag,
|
|
959
|
+
* losing the key from the bf-p payload entirely. Callers thread one set
|
|
960
|
+
* through the field-emitting loops in declaration order, so props win;
|
|
961
|
+
* that matches hono, whose hydration blob is built only from props params
|
|
962
|
+
* (a signal seeds by reading the prop's key, never a key of its own, so
|
|
963
|
+
* the dropped tag was never a client read path).
|
|
964
|
+
*/
|
|
965
|
+
private claimJsonTag(desired: string, taken: Set<string>): string {
|
|
966
|
+
if (taken.has(desired)) return '-'
|
|
967
|
+
taken.add(desired)
|
|
968
|
+
return desired
|
|
969
|
+
}
|
|
970
|
+
|
|
947
971
|
generateTypes(ir: ComponentIR): string | null {
|
|
948
972
|
this.state.usesHtmlTemplate = false
|
|
949
973
|
this.state.usesFmt = false
|
|
@@ -1004,8 +1028,12 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
1004
1028
|
* Two invariants the emitted code depends on:
|
|
1005
1029
|
*
|
|
1006
1030
|
* - **Input is recoverable from Props.** `emitPropsDataFields` and
|
|
1007
|
-
* `generateInputStruct` emit each props param with the SAME
|
|
1008
|
-
*
|
|
1031
|
+
* `generateInputStruct` emit each props param with the SAME
|
|
1032
|
+
* `resolvePropGoType`, and the same field name EXCEPT for an aliased
|
|
1033
|
+
* destructured prop, where Props keeps the LOCAL field and Input keys by
|
|
1034
|
+
* the CALLER-facing name (a caller-side `BadgeInput{N: 5}` literal must
|
|
1035
|
+
* type-check). `in.<inputField> = b.<propsField>` bridges the pair
|
|
1036
|
+
* (identity when un-aliased). A prop read
|
|
1009
1037
|
* only by a memo still gets its passthrough field, so nothing is lost.
|
|
1010
1038
|
* Shapes that add Input fields with no Props counterpart (a rest bag, a
|
|
1011
1039
|
* spread slot, context consumers, nested child Inputs) are declined —
|
|
@@ -1016,17 +1044,8 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
1016
1044
|
* BfMount come from the base, and the Props-only fields (Scripts,
|
|
1017
1045
|
* BfIsRoot/BfIsChild/BfDataKey) are reapplied after the constructor.
|
|
1018
1046
|
*
|
|
1019
|
-
* The override switch
|
|
1020
|
-
*
|
|
1021
|
-
* used to differ under an aliased destructure — the case label carried the
|
|
1022
|
-
* name the PARENT wrote (the JSX attribute, `ParamInfo.sourceName ?? name`)
|
|
1023
|
-
* while the assignment targeted the child's own field, so this switch was
|
|
1024
|
-
* where those two sides were reconciled. #2457 moved that reconciliation to
|
|
1025
|
-
* the PARENT (`loopRowChildPropOverrides`, via `childPropFieldNames`): the
|
|
1026
|
-
* parent now emits the child's own field name at the call site, so by the
|
|
1027
|
-
* time a `bf_reprops` pipeline reaches this switch both sides already agree
|
|
1028
|
-
* and there's exactly one place — the parent's emission — where the naming
|
|
1029
|
-
* gets reconciled, instead of two.
|
|
1047
|
+
* The override switch built from `params` is emitted by
|
|
1048
|
+
* `emitRepropsRegistration`.
|
|
1030
1049
|
*/
|
|
1031
1050
|
private recordRepropsSpec(
|
|
1032
1051
|
ir: ComponentIR,
|
|
@@ -1036,11 +1055,14 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
1036
1055
|
): void {
|
|
1037
1056
|
if (!this.childDerivedFieldDeps.has(componentName)) return
|
|
1038
1057
|
|
|
1058
|
+
// Must mirror `generateInputStruct`'s exclusion/collision checks exactly —
|
|
1059
|
+
// both walk the Input struct's actual (caller-keyed) field names, or the
|
|
1060
|
+
// reprops switch references fields the struct doesn't have.
|
|
1039
1061
|
const nestedArrayFields = new Set(nestedComponents.map(n => `${n.name}s`))
|
|
1040
1062
|
const params = (ir.metadata.propsParams ?? []).filter(
|
|
1041
|
-
p => !
|
|
1063
|
+
p => !this.isNestedArrayShadowed(p, nestedArrayFields),
|
|
1042
1064
|
)
|
|
1043
|
-
const takenInput = new Set((ir.metadata.propsParams ?? [])
|
|
1065
|
+
const takenInput = new Set(this.propParamFieldNamesUnion(ir.metadata.propsParams ?? []))
|
|
1044
1066
|
const eligible =
|
|
1045
1067
|
nestedComponents.every(n => n.isDynamic && !n.isPropDerived) &&
|
|
1046
1068
|
spreadSlots.length === 0 &&
|
|
@@ -1049,7 +1071,10 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
1049
1071
|
if (!eligible) return
|
|
1050
1072
|
|
|
1051
1073
|
this.childRepropsReady.set(componentName, {
|
|
1052
|
-
params: params.map(p =>
|
|
1074
|
+
params: params.map(p => ({
|
|
1075
|
+
propsField: capitalizeFieldName(p.name),
|
|
1076
|
+
inputField: capitalizeFieldName(p.sourceName ?? p.name),
|
|
1077
|
+
})),
|
|
1053
1078
|
usesSearchParams: this.usesSearchParams(ir),
|
|
1054
1079
|
})
|
|
1055
1080
|
}
|
|
@@ -1108,22 +1133,22 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
1108
1133
|
lines.push('\t\t\tBfParent: b.BfParent,')
|
|
1109
1134
|
lines.push('\t\t\tBfMount: b.BfMount,')
|
|
1110
1135
|
if (usesSearchParams) lines.push('\t\t\tSearchParams: b.SearchParams,')
|
|
1111
|
-
for (const
|
|
1112
|
-
lines.push(`\t\t\t${
|
|
1136
|
+
for (const { propsField, inputField } of params) {
|
|
1137
|
+
lines.push(`\t\t\t${inputField}: b.${propsField},`)
|
|
1113
1138
|
}
|
|
1114
1139
|
lines.push('\t\t}')
|
|
1115
1140
|
lines.push('\t\tfor i := 0; i < len(kv); i += 2 {')
|
|
1116
1141
|
lines.push('\t\t\tname, _ := kv[i].(string)')
|
|
1117
1142
|
lines.push('\t\t\tvar err error')
|
|
1118
1143
|
lines.push('\t\t\tswitch name {')
|
|
1119
|
-
for (const
|
|
1120
|
-
// Case label
|
|
1121
|
-
//
|
|
1122
|
-
//
|
|
1123
|
-
//
|
|
1124
|
-
//
|
|
1125
|
-
lines.push(`\t\t\tcase ${JSON.stringify(
|
|
1126
|
-
lines.push(`\t\t\t\terr = bf.RepropsAssign(${q}, ${JSON.stringify(
|
|
1144
|
+
for (const { propsField, inputField } of params) {
|
|
1145
|
+
// Case label = the child's own PROPS field (what the parent's
|
|
1146
|
+
// `bf_with_props`/`bf_reprops` call is keyed by); target = the Input
|
|
1147
|
+
// field. One name is not enough: an aliased destructure gives
|
|
1148
|
+
// `{ n: count }` → Props `Count`, Input `N` (437f822 had unified the
|
|
1149
|
+
// pair while the two could never diverge).
|
|
1150
|
+
lines.push(`\t\t\tcase ${JSON.stringify(propsField)}:`)
|
|
1151
|
+
lines.push(`\t\t\t\terr = bf.RepropsAssign(${q}, ${JSON.stringify(propsField)}, &in.${inputField}, kv[i+1])`)
|
|
1127
1152
|
}
|
|
1128
1153
|
// No silent drop. `bf_with_props` passes an unknown field through because
|
|
1129
1154
|
// a rest-bag prop legitimately has no named field — but a rebuilder is
|
|
@@ -1301,10 +1326,14 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
1301
1326
|
*/
|
|
1302
1327
|
private usesSearchParams(ir: ComponentIR): boolean {
|
|
1303
1328
|
if (this.state.searchParamsLocals.size === 0) return false
|
|
1304
|
-
//
|
|
1305
|
-
//
|
|
1329
|
+
// A `SearchParams` collision would redeclare the field and break the Go
|
|
1330
|
+
// compile. This ONE boolean gates the field in Input (caller-facing
|
|
1331
|
+
// names), Props (LOCAL names) and NewProps at once, so a prop cannot be
|
|
1332
|
+
// checked under just one naming: `{ x: searchParams }` collides only in
|
|
1333
|
+
// Props, `{ searchParams: x }` only in Input.
|
|
1306
1334
|
const taken = new Set<string>([
|
|
1307
1335
|
...ir.metadata.propsParams.map(p => capitalizeFieldName(p.name)),
|
|
1336
|
+
...ir.metadata.propsParams.map(p => capitalizeFieldName(p.sourceName ?? p.name)),
|
|
1308
1337
|
// Env signals (`createSearchParams()`) don't produce a normal value field —
|
|
1309
1338
|
// they ARE the `SearchParams` binding — so they must not poison this
|
|
1310
1339
|
// collision set (a getter named `searchParams` would otherwise capitalise
|
|
@@ -1351,8 +1380,14 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
1351
1380
|
const nestedArrayFields = new Set(nestedComponents.map(n => `${n.name}s`))
|
|
1352
1381
|
|
|
1353
1382
|
for (const param of ir.metadata.propsParams) {
|
|
1354
|
-
|
|
1355
|
-
|
|
1383
|
+
// #2525: caller-facing name, not the local destructure binding — a
|
|
1384
|
+
// caller-side composite `BadgeInput{N: 5}` literal is written against
|
|
1385
|
+
// the name the callee's `{ n: count }` destructure was GIVEN, not the
|
|
1386
|
+
// name it renamed to internally. `generatePropsStruct`'s field (what
|
|
1387
|
+
// `{{.X}}` executes against) stays keyed by the local binding — see
|
|
1388
|
+
// `emitPropsDataFields`.
|
|
1389
|
+
const fieldName = capitalizeFieldName(param.sourceName ?? param.name)
|
|
1390
|
+
if (this.isNestedArrayShadowed(param, nestedArrayFields)) continue
|
|
1356
1391
|
const goType = resolvePropGoType(this.emitCtx, param, propTypeOverrides)
|
|
1357
1392
|
lines.push(`\t${fieldName} ${goType}`)
|
|
1358
1393
|
}
|
|
@@ -1374,8 +1409,10 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
1374
1409
|
}
|
|
1375
1410
|
|
|
1376
1411
|
// `useContext` consumer fields — settable by an enclosing provider; default
|
|
1377
|
-
// applied in NewXxxProps.
|
|
1378
|
-
|
|
1412
|
+
// applied in NewXxxProps. `taken` must union the local and caller-facing
|
|
1413
|
+
// prop names (`propParamFieldNamesUnion`) or Input disagrees with
|
|
1414
|
+
// Props/NewProps on whether the consumer field exists.
|
|
1415
|
+
const takenInput = new Set(this.propParamFieldNamesUnion(ir.metadata.propsParams))
|
|
1379
1416
|
for (const c of this.nonCollidingContextConsumers(takenInput)) {
|
|
1380
1417
|
lines.push(`\t${this.contextFieldName(c)} ${this.contextConsumerGoType(c)}`)
|
|
1381
1418
|
}
|
|
@@ -1413,9 +1450,14 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
1413
1450
|
const propsTypeName = `${componentName}Props`
|
|
1414
1451
|
this.emitPropsStructHeader(lines, ir, propsTypeName, componentName)
|
|
1415
1452
|
|
|
1416
|
-
|
|
1453
|
+
// ONE taken-tags set spans every field-emitting loop below — per-loop
|
|
1454
|
+
// sets would let two loops emit the same tag, and `encoding/json` drops
|
|
1455
|
+
// both such fields silently (see `claimJsonTag`).
|
|
1456
|
+
const takenJsonTags = new Set<string>()
|
|
1417
1457
|
|
|
1418
|
-
this.
|
|
1458
|
+
this.emitPropsDataFields(lines, ir, nestedComponents, propTypeOverrides, takenJsonTags)
|
|
1459
|
+
|
|
1460
|
+
this.emitPropsAuxFields(lines, ir, componentName, nestedComponents, spreadSlots, takenJsonTags)
|
|
1419
1461
|
|
|
1420
1462
|
lines.push('}')
|
|
1421
1463
|
lines.push('')
|
|
@@ -1472,7 +1514,12 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
1472
1514
|
itemType: TypeInfo | null | undefined,
|
|
1473
1515
|
): Array<{ tsName: string; goName: string; goType: string }> {
|
|
1474
1516
|
if (!itemType) return []
|
|
1475
|
-
|
|
1517
|
+
// `itemType` is documented as the loop ITEM's type, but historically
|
|
1518
|
+
// could arrive already ARRAY-shaped (`Todo[]`) from a caller that hadn't
|
|
1519
|
+
// unwrapped it — `typeNodeToTypeInfo` normalises every array spelling to
|
|
1520
|
+
// `kind: 'array'` + `elementType`, so read the element structurally
|
|
1521
|
+
// (#2484) instead of regexing a trailing `[]` off `raw`.
|
|
1522
|
+
const typeName = itemType.kind === 'array' ? (itemType.elementType?.raw ?? itemType.raw) : itemType.raw
|
|
1476
1523
|
if (!typeName) return []
|
|
1477
1524
|
for (const td of this.state.currentTypeDefinitions) {
|
|
1478
1525
|
if (td.name === typeName) {
|
|
@@ -1734,8 +1781,12 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
1734
1781
|
|
|
1735
1782
|
const propFieldNames = new Set<string>()
|
|
1736
1783
|
for (const param of ir.metadata.propsParams) {
|
|
1784
|
+
// Props field: LOCAL binding, what the template reads (`{{.X}}`).
|
|
1785
|
+
// Everything on the right of `:` below reads `in.<inputField>`
|
|
1786
|
+
// (caller-facing), never `in.<fieldName>` (local).
|
|
1737
1787
|
const fieldName = capitalizeFieldName(param.name)
|
|
1738
|
-
|
|
1788
|
+
const inputField = capitalizeFieldName(param.sourceName ?? param.name)
|
|
1789
|
+
if (this.isNestedArrayShadowed(param, nestedArrayFields)) continue
|
|
1739
1790
|
const hoisted = propFallbackVars.get(param.name)
|
|
1740
1791
|
if (hoisted) {
|
|
1741
1792
|
lines.push(`\t\t${fieldName}: ${hoisted.varName},`)
|
|
@@ -1743,17 +1794,17 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
1743
1794
|
const paramDefault = goPropDefault(param.defaultValue)
|
|
1744
1795
|
const memoFold = memoFallbacks.get(fieldName)
|
|
1745
1796
|
if (paramDefault !== null) {
|
|
1746
|
-
lines.push(`\t\t${fieldName}: ${applyGoFallback(`in.${
|
|
1797
|
+
lines.push(`\t\t${fieldName}: ${applyGoFallback(`in.${inputField}`, paramDefault)},`)
|
|
1747
1798
|
} else if (memoFold !== undefined && memoFold.goType === 'string') {
|
|
1748
|
-
lines.push(`\t\t${fieldName}: ${applyGoFallback(`in.${
|
|
1799
|
+
lines.push(`\t\t${fieldName}: ${applyGoFallback(`in.${inputField}`, memoFold.goFallback)},`)
|
|
1749
1800
|
} else if (memoFold !== undefined) {
|
|
1750
1801
|
// interface{} field (`size ?? 'icon'`): the string zero-check doesn't
|
|
1751
1802
|
// compile, so wrap in a nil/empty-tolerant IIFE.
|
|
1752
1803
|
lines.push(
|
|
1753
|
-
`\t\t${fieldName}: func() interface{} { v := interface{}(in.${
|
|
1804
|
+
`\t\t${fieldName}: func() interface{} { v := interface{}(in.${inputField}); if v == nil || v == "" { return ${memoFold.goFallback} }; return v }(),`,
|
|
1754
1805
|
)
|
|
1755
1806
|
} else {
|
|
1756
|
-
lines.push(`\t\t${fieldName}: in.${
|
|
1807
|
+
lines.push(`\t\t${fieldName}: in.${inputField},`)
|
|
1757
1808
|
}
|
|
1758
1809
|
}
|
|
1759
1810
|
propFieldNames.add(fieldName)
|
|
@@ -1823,9 +1874,11 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
1823
1874
|
}
|
|
1824
1875
|
|
|
1825
1876
|
// `useContext` consumer fields default to the `createContext` default when
|
|
1826
|
-
// the provider didn't set them.
|
|
1877
|
+
// the provider didn't set them. Props params must contribute BOTH namings
|
|
1878
|
+
// (`propParamFieldNamesUnion`) or this disagrees with Input/Props on
|
|
1879
|
+
// whether the consumer field exists.
|
|
1827
1880
|
const takenInit = new Set<string>([
|
|
1828
|
-
...ir.metadata.propsParams
|
|
1881
|
+
...this.propParamFieldNamesUnion(ir.metadata.propsParams),
|
|
1829
1882
|
...ir.metadata.signals.map(s => capitalizeFieldName(s.getter)),
|
|
1830
1883
|
...ir.metadata.memos.map(m => capitalizeFieldName(m.name)),
|
|
1831
1884
|
])
|
|
@@ -1891,14 +1944,12 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
1891
1944
|
// A hyphenated attr (`aria-label`) can't be a Go field and, with no rest
|
|
1892
1945
|
// bag to route it into, has nowhere to go — skip over emitting invalid Go.
|
|
1893
1946
|
if (jsxName.includes('-')) return
|
|
1894
|
-
//
|
|
1895
|
-
//
|
|
1896
|
-
//
|
|
1897
|
-
//
|
|
1898
|
-
//
|
|
1899
|
-
|
|
1900
|
-
const fieldName =
|
|
1901
|
-
this.childPropFieldNames.get(child.name)?.get(jsxName) ?? capitalizeFieldName(jsxName)
|
|
1947
|
+
// `<Child>Input{...}` composite literal: Input fields are
|
|
1948
|
+
// caller-facing and `jsxName` IS the caller-facing name, so no
|
|
1949
|
+
// `childPropFieldNames` lookup — that map resolves the child's
|
|
1950
|
+
// LOCAL/Props field for `loopRowChildPropOverrides`'s different
|
|
1951
|
+
// struct.
|
|
1952
|
+
const fieldName = capitalizeFieldName(jsxName)
|
|
1902
1953
|
lines.push(`\t\t\t${fieldName}: ${goValue},`)
|
|
1903
1954
|
}
|
|
1904
1955
|
for (const prop of child.props) {
|
|
@@ -2425,6 +2476,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
2425
2476
|
ir: ComponentIR,
|
|
2426
2477
|
nestedComponents: NestedComponentInfo[],
|
|
2427
2478
|
propTypeOverrides: Map<string, string>,
|
|
2479
|
+
takenJsonTags: Set<string>,
|
|
2428
2480
|
): void {
|
|
2429
2481
|
// Nested-component array fields are emitted as typed arrays below, not as
|
|
2430
2482
|
// their raw prop; track them (and emitted names) to skip duplicates.
|
|
@@ -2433,12 +2485,21 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
2433
2485
|
|
|
2434
2486
|
for (const param of ir.metadata.propsParams) {
|
|
2435
2487
|
const fieldName = capitalizeFieldName(param.name)
|
|
2436
|
-
if (
|
|
2488
|
+
if (this.isNestedArrayShadowed(param, nestedArrayFields)) continue
|
|
2437
2489
|
const goType = resolvePropGoType(this.emitCtx, param, propTypeOverrides)
|
|
2438
2490
|
// Children are already rendered in the DOM; serialising them into bf-p
|
|
2439
2491
|
// leaks nested scope ids and bloats the attribute. Exclude from JSON so
|
|
2440
2492
|
// BfPropsAttr never marshals them.
|
|
2441
|
-
|
|
2493
|
+
//
|
|
2494
|
+
// The tag is CALLER-facing even though `fieldName` stays LOCAL: the
|
|
2495
|
+
// tag is the hydration wire format and the client JS reads
|
|
2496
|
+
// `_p.<callerKey>` (#2524) — a local tag would make hydration read the
|
|
2497
|
+
// wrong key. Props are emitted FIRST into `takenJsonTags`, so a prop
|
|
2498
|
+
// always wins a tag collision against a later signal/memo/etc field.
|
|
2499
|
+
const jsonTag =
|
|
2500
|
+
param.name === 'children'
|
|
2501
|
+
? '-'
|
|
2502
|
+
: this.claimJsonTag(this.toJsonTag(param.sourceName ?? param.name), takenJsonTags)
|
|
2442
2503
|
lines.push(`\t${fieldName} ${goType} \`json:"${jsonTag}"\``)
|
|
2443
2504
|
propFieldNames.add(fieldName)
|
|
2444
2505
|
}
|
|
@@ -2451,7 +2512,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
2451
2512
|
if (signal.envReader) continue
|
|
2452
2513
|
const fieldName = capitalizeFieldName(signal.getter)
|
|
2453
2514
|
if (propFieldNames.has(fieldName)) continue
|
|
2454
|
-
const jsonTag = this.toJsonTag(signal.getter)
|
|
2515
|
+
const jsonTag = this.claimJsonTag(this.toJsonTag(signal.getter), takenJsonTags)
|
|
2455
2516
|
// A synthesised struct type wins outright — the signal is an untyped
|
|
2456
2517
|
// object array we gave a concrete element type.
|
|
2457
2518
|
const synthType = this.state.synthStructTypes.get(signal.getter)
|
|
@@ -2462,12 +2523,12 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
2462
2523
|
let goType: string
|
|
2463
2524
|
let referencedProp = propsParamMap.get(signal.initialValue)
|
|
2464
2525
|
if (!referencedProp) {
|
|
2465
|
-
const propName = this.extractPropNameFromInitialValue(signal.initialValue)
|
|
2526
|
+
const propName = this.extractPropNameFromInitialValue(signal.initialValue, signal.parsed)
|
|
2466
2527
|
if (propName) referencedProp = propsParamMap.get(propName)
|
|
2467
2528
|
}
|
|
2468
2529
|
if (referencedProp) {
|
|
2469
|
-
const propGoType = typeInfoToGo(this.emitCtx, referencedProp.type, referencedProp.defaultValue)
|
|
2470
|
-
const signalGoType = typeInfoToGo(this.emitCtx, signal.type, signal.initialValue)
|
|
2530
|
+
const propGoType = typeInfoToGo(this.emitCtx, referencedProp.type, referencedProp.defaultValue, referencedProp.parsed)
|
|
2531
|
+
const signalGoType = typeInfoToGo(this.emitCtx, signal.type, signal.initialValue, signal.parsed)
|
|
2471
2532
|
// The "prop type wins" heuristic helps when the signal infer is less
|
|
2472
2533
|
// specific than the prop (e.g. `createSignal(props.todos)` wants
|
|
2473
2534
|
// `[]Todo`, not `interface{}`). It HURTS when the initial expression
|
|
@@ -2489,7 +2550,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
2489
2550
|
goType = propGoType
|
|
2490
2551
|
}
|
|
2491
2552
|
} else {
|
|
2492
|
-
goType = typeInfoToGo(this.emitCtx, signal.type, signal.initialValue)
|
|
2553
|
+
goType = typeInfoToGo(this.emitCtx, signal.type, signal.initialValue, signal.parsed)
|
|
2493
2554
|
}
|
|
2494
2555
|
lines.push(`\t${fieldName} ${goType} \`json:"${jsonTag}"\``)
|
|
2495
2556
|
}
|
|
@@ -2501,7 +2562,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
2501
2562
|
for (const memo of ir.metadata.memos) {
|
|
2502
2563
|
const fieldName = capitalizeFieldName(memo.name)
|
|
2503
2564
|
if (propFieldNames.has(fieldName)) continue
|
|
2504
|
-
const jsonTag = this.toJsonTag(memo.name)
|
|
2565
|
+
const jsonTag = this.claimJsonTag(this.toJsonTag(memo.name), takenJsonTags)
|
|
2505
2566
|
const goType = this.inferMemoType(memo, ir.metadata.signals, propsParamMap)
|
|
2506
2567
|
lines.push(`\t${fieldName} ${goType} \`json:"${jsonTag}"\``)
|
|
2507
2568
|
}
|
|
@@ -2513,6 +2574,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
2513
2574
|
componentName: string,
|
|
2514
2575
|
nestedComponents: NestedComponentInfo[],
|
|
2515
2576
|
spreadSlots: SpreadSlotInfo[],
|
|
2577
|
+
takenJsonTags: Set<string>,
|
|
2516
2578
|
): void {
|
|
2517
2579
|
// Computed fields for component-scope derived string consts the template
|
|
2518
2580
|
// references (e.g. `root = base || '/'`). Not serialised — the route handler
|
|
@@ -2527,14 +2589,16 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
2527
2589
|
}
|
|
2528
2590
|
|
|
2529
2591
|
// `useContext` consumer fields (skip names already taken by a prop /
|
|
2530
|
-
// signal / memo field).
|
|
2592
|
+
// signal / memo field). Props params must contribute BOTH namings
|
|
2593
|
+
// (`propParamFieldNamesUnion`) or this disagrees with Input on whether
|
|
2594
|
+
// the consumer field exists.
|
|
2531
2595
|
const takenProps = new Set<string>([
|
|
2532
|
-
...ir.metadata.propsParams
|
|
2596
|
+
...this.propParamFieldNamesUnion(ir.metadata.propsParams),
|
|
2533
2597
|
...ir.metadata.signals.map(s => capitalizeFieldName(s.getter)),
|
|
2534
2598
|
...ir.metadata.memos.map(m => capitalizeFieldName(m.name)),
|
|
2535
2599
|
])
|
|
2536
2600
|
for (const c of this.nonCollidingContextConsumers(takenProps)) {
|
|
2537
|
-
const jsonTag = this.toJsonTag(c.localName)
|
|
2601
|
+
const jsonTag = this.claimJsonTag(this.toJsonTag(c.localName), takenJsonTags)
|
|
2538
2602
|
lines.push(`\t${this.contextFieldName(c)} ${this.contextConsumerGoType(c)} \`json:"${jsonTag}"\``)
|
|
2539
2603
|
}
|
|
2540
2604
|
|
|
@@ -2548,7 +2612,10 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
2548
2612
|
lines.push(`\t${nested.name}s []${elemType} \`json:"-"\``)
|
|
2549
2613
|
} else {
|
|
2550
2614
|
// Static + prop-derived arrays go in JSON so the client can hydrate.
|
|
2551
|
-
const jsonTag = this.
|
|
2615
|
+
const jsonTag = this.claimJsonTag(
|
|
2616
|
+
this.toJsonTag(`${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`),
|
|
2617
|
+
takenJsonTags,
|
|
2618
|
+
)
|
|
2552
2619
|
lines.push(`\t${nested.name}s []${elemType} \`json:"${jsonTag}"\``)
|
|
2553
2620
|
}
|
|
2554
2621
|
}
|
|
@@ -2562,7 +2629,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
2562
2629
|
// map[string]any` field the template reads via `{{bf_spread_attrs}}`.
|
|
2563
2630
|
// Loop-internal spreads emit inline and don't appear here.
|
|
2564
2631
|
for (const slot of spreadSlots) {
|
|
2565
|
-
const jsonTag = this.toJsonTag(slot.slotId)
|
|
2632
|
+
const jsonTag = this.claimJsonTag(this.toJsonTag(slot.slotId), takenJsonTags)
|
|
2566
2633
|
lines.push(`\t${slot.slotId} map[string]any \`json:"${jsonTag}"\``)
|
|
2567
2634
|
}
|
|
2568
2635
|
}
|
|
@@ -2870,7 +2937,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
2870
2937
|
*/
|
|
2871
2938
|
private lowerProviderMapMemberValue(
|
|
2872
2939
|
node: ParsedExpr,
|
|
2873
|
-
propsParams: ReadonlyArray<{ name: string }>,
|
|
2940
|
+
propsParams: ReadonlyArray<{ name: string; sourceName?: string }>,
|
|
2874
2941
|
): string | null {
|
|
2875
2942
|
if (node.kind === 'object-literal') return objectLiteralToGoMap(this.emitCtx, node)
|
|
2876
2943
|
const literal = parsedLiteralToGo(this.emitCtx, node)
|
|
@@ -2889,8 +2956,15 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
2889
2956
|
// closure below — TS drops property-path narrowing (`node.left.kind
|
|
2890
2957
|
// === 'member'`) across a nested arrow function boundary.
|
|
2891
2958
|
const propName = node.left.property
|
|
2892
|
-
|
|
2893
|
-
|
|
2959
|
+
const matchedParam = propsParams.find(param => param.name === propName)
|
|
2960
|
+
if (matchedParam) {
|
|
2961
|
+
// `sourceName ?? name`: the Input struct's field is caller-facing
|
|
2962
|
+
// (#2525). A `props.<key>` member read (this function's shape) has
|
|
2963
|
+
// no destructure aliasing to begin with — `key` IS the caller-facing
|
|
2964
|
+
// name — but resolving through the matched param keeps this in step
|
|
2965
|
+
// with every other `in.<field>` read in the file rather than
|
|
2966
|
+
// re-deriving the assumption locally.
|
|
2967
|
+
const fieldRef = `in.${capitalizeFieldName(matchedParam.sourceName ?? matchedParam.name)}`
|
|
2894
2968
|
return (
|
|
2895
2969
|
`func() map[string]interface{} { ` +
|
|
2896
2970
|
`if m := bf.AsMap(${fieldRef}); m != nil { return m }; ` +
|
|
@@ -2911,7 +2985,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
2911
2985
|
*/
|
|
2912
2986
|
private templatePartsToGoCode(
|
|
2913
2987
|
parts: IRTemplatePart[],
|
|
2914
|
-
propsParams: { name: string }[]
|
|
2988
|
+
propsParams: { name: string; sourceName?: string }[]
|
|
2915
2989
|
): string | null {
|
|
2916
2990
|
const segments: string[] = []
|
|
2917
2991
|
for (const part of parts) {
|
|
@@ -2929,7 +3003,9 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
2929
3003
|
const keyExpr = part.key.trim()
|
|
2930
3004
|
const param = propsParams.find(p => p.name === keyExpr)
|
|
2931
3005
|
if (!param) return null
|
|
2932
|
-
|
|
3006
|
+
// Input field is caller-facing (#2525); `keyExpr` is the LOCAL prop
|
|
3007
|
+
// binding used in the source expression.
|
|
3008
|
+
const fieldName = capitalizeFieldName(param.sourceName ?? keyExpr)
|
|
2933
3009
|
const caseEntries = Object.entries(part.cases)
|
|
2934
3010
|
if (caseEntries.length === 0) {
|
|
2935
3011
|
segments.push('""')
|
|
@@ -2964,7 +3040,7 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
2964
3040
|
expr: string,
|
|
2965
3041
|
signals: { getter: string; setter: string | null; initialValue: string; type: TypeInfo; parsed?: ParsedExpr }[],
|
|
2966
3042
|
memos: { name: string; computation: string; deps: string[] }[],
|
|
2967
|
-
propsParams: { name: string }[]
|
|
3043
|
+
propsParams: { name: string; sourceName?: string }[]
|
|
2968
3044
|
): string | null {
|
|
2969
3045
|
// `getter() === 'lit'` / `!==` as a child-instance prop value
|
|
2970
3046
|
// (`open={openItem() === 'item-1'}`): resolves to a Go bool when the
|
|
@@ -3069,8 +3145,16 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
3069
3145
|
passthroughName !== null &&
|
|
3070
3146
|
((localConst !== undefined && !isPropsDestructureAlias) ||
|
|
3071
3147
|
this.state.localHelperNames.has(passthroughName))
|
|
3072
|
-
|
|
3073
|
-
|
|
3148
|
+
const passthroughParam =
|
|
3149
|
+
passthroughName && !shadowedByLocal
|
|
3150
|
+
? propsParams.find(p => p.name === passthroughName)
|
|
3151
|
+
: undefined
|
|
3152
|
+
if (passthroughParam) {
|
|
3153
|
+
// Input field is caller-facing (#2525); `passthroughName` is the LOCAL
|
|
3154
|
+
// binding (`bareIdentifier`) or the `props.<key>` source key
|
|
3155
|
+
// (`barePropAccess`, never aliased) — resolve through the matched
|
|
3156
|
+
// param rather than assuming the two coincide.
|
|
3157
|
+
return `in.${capitalizeFieldName(passthroughParam.sourceName ?? passthroughParam.name)}`
|
|
3074
3158
|
}
|
|
3075
3159
|
|
|
3076
3160
|
return null
|
|
@@ -3079,8 +3163,8 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
3079
3163
|
/** Infer the Go type for a memo from its computation and dependencies. */
|
|
3080
3164
|
private inferMemoType(
|
|
3081
3165
|
memo: { name: string; computation: string; type: TypeInfo; deps: string[]; bodyIsTemplateLiteral?: boolean; parsed?: ParsedExpr; parsedBlock?: ParsedStatement[] },
|
|
3082
|
-
signals: { getter: string; initialValue: string; type: TypeInfo }[],
|
|
3083
|
-
propsParamMap: Map<string, { name: string; type: TypeInfo; defaultValue?: string }>
|
|
3166
|
+
signals: { getter: string; initialValue: string; type: TypeInfo; parsed?: ParsedExpr }[],
|
|
3167
|
+
propsParamMap: Map<string, { name: string; type: TypeInfo; defaultValue?: string; parsed?: ParsedExpr }>
|
|
3084
3168
|
): string {
|
|
3085
3169
|
// A LIST-valued `.filter(arrow)` memo (#2075 — the blog PostList `visible`
|
|
3086
3170
|
// shape) is a slice of the receiver's boxed elements, not a scalar.
|
|
@@ -3110,16 +3194,16 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
3110
3194
|
if (signal) {
|
|
3111
3195
|
let referencedProp = propsParamMap.get(signal.initialValue)
|
|
3112
3196
|
if (!referencedProp) {
|
|
3113
|
-
const propName = this.extractPropNameFromInitialValue(signal.initialValue)
|
|
3197
|
+
const propName = this.extractPropNameFromInitialValue(signal.initialValue, signal.parsed)
|
|
3114
3198
|
if (propName) referencedProp = propsParamMap.get(propName)
|
|
3115
3199
|
}
|
|
3116
3200
|
if (referencedProp) {
|
|
3117
|
-
const propType = typeInfoToGo(this.emitCtx, referencedProp.type, referencedProp.defaultValue)
|
|
3201
|
+
const propType = typeInfoToGo(this.emitCtx, referencedProp.type, referencedProp.defaultValue, referencedProp.parsed)
|
|
3118
3202
|
if (propType === 'int' || propType === 'float64') {
|
|
3119
3203
|
return 'int'
|
|
3120
3204
|
}
|
|
3121
3205
|
}
|
|
3122
|
-
const signalType = typeInfoToGo(this.emitCtx, signal.type, signal.initialValue)
|
|
3206
|
+
const signalType = typeInfoToGo(this.emitCtx, signal.type, signal.initialValue, signal.parsed)
|
|
3123
3207
|
if (signalType === 'int' || signalType === 'float64') {
|
|
3124
3208
|
return 'int'
|
|
3125
3209
|
}
|
|
@@ -3175,12 +3259,15 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
3175
3259
|
if (!param) continue
|
|
3176
3260
|
// A destructure default already wins via applyGoFallback below.
|
|
3177
3261
|
if (goPropDefault(param.defaultValue) !== null) continue
|
|
3178
|
-
|
|
3262
|
+
// `PropFallbackVar.fieldName` is read off the INPUT struct (every
|
|
3263
|
+
// consumer does `in.${fieldName}`, #2525) — caller-facing, not
|
|
3264
|
+
// `match.propName`'s local binding.
|
|
3265
|
+
const fieldName = capitalizeFieldName(param.sourceName ?? match.propName)
|
|
3179
3266
|
// A `??`-consumed optional scalar lowered to `interface{}` (#2248) —
|
|
3180
3267
|
// detected off the SAME `resolvePropGoType` pipeline the struct
|
|
3181
3268
|
// generators use, so this can't drift from the emitted field type. The
|
|
3182
3269
|
// concrete pre-flip type is what the hoisted local materializes as.
|
|
3183
|
-
const concreteType = propTypeOverrides.get(param.name) ?? typeInfoToGo(this.emitCtx, param.type, param.defaultValue)
|
|
3270
|
+
const concreteType = propTypeOverrides.get(param.name) ?? typeInfoToGo(this.emitCtx, param.type, param.defaultValue, param.parsed)
|
|
3184
3271
|
const nullishLowered =
|
|
3185
3272
|
NULLISH_SCALAR_GO_TYPES.has(concreteType) &&
|
|
3186
3273
|
resolvePropGoType(this.emitCtx, param, propTypeOverrides) === 'interface{}'
|
|
@@ -5561,16 +5648,14 @@ export class GoTemplateAdapter extends BaseAdapter implements ParsedExprEmitter,
|
|
|
5561
5648
|
})
|
|
5562
5649
|
continue
|
|
5563
5650
|
}
|
|
5564
|
-
//
|
|
5565
|
-
//
|
|
5566
|
-
//
|
|
5567
|
-
//
|
|
5568
|
-
//
|
|
5569
|
-
//
|
|
5570
|
-
//
|
|
5571
|
-
|
|
5572
|
-
const fieldName =
|
|
5573
|
-
this.childPropFieldNames.get(comp.name)?.get(prop.name) ?? capitalizeFieldName(prop.name)
|
|
5651
|
+
// Emit the CHILD's own PROPS field name, not the JSX attribute name —
|
|
5652
|
+
// `bf.WithProps`/`bf.RepropsAssign` patch the constructed PROPS
|
|
5653
|
+
// instance, and passing a name the struct doesn't have (an aliased
|
|
5654
|
+
// `{ n: count }` attribute) is a silent passthrough, not an error
|
|
5655
|
+
// (#2457). Stays LOCAL even though the child's Input field is
|
|
5656
|
+
// caller-facing. Falls back to the capitalized attribute for a
|
|
5657
|
+
// cross-file child this run's pre-pass never registered.
|
|
5658
|
+
const fieldName = this.childPropFieldNames.get(comp.name)?.get(prop.name) ?? capitalizeFieldName(prop.name)
|
|
5574
5659
|
args.push(`${JSON.stringify(fieldName)} ${wrapIfMultiToken(go)}`)
|
|
5575
5660
|
}
|
|
5576
5661
|
if (args.length === 0) return null
|