@barefootjs/go-template 0.16.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/analysis/component-tree.d.ts +23 -0
- package/dist/adapter/analysis/component-tree.d.ts.map +1 -0
- package/dist/adapter/emit-context.d.ts +53 -0
- package/dist/adapter/emit-context.d.ts.map +1 -0
- package/dist/adapter/expr/helper-inline.d.ts +31 -0
- package/dist/adapter/expr/helper-inline.d.ts.map +1 -0
- package/dist/adapter/expr/url-builder.d.ts +23 -0
- package/dist/adapter/expr/url-builder.d.ts.map +1 -0
- package/dist/adapter/go-template-adapter.d.ts +302 -1071
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +3116 -2692
- package/dist/adapter/lib/compile-state.d.ts +142 -0
- package/dist/adapter/lib/compile-state.d.ts.map +1 -0
- package/dist/adapter/lib/constants.d.ts +11 -0
- package/dist/adapter/lib/constants.d.ts.map +1 -0
- package/dist/adapter/lib/go-emit.d.ts +126 -0
- package/dist/adapter/lib/go-emit.d.ts.map +1 -0
- package/dist/adapter/lib/go-naming.d.ts +39 -0
- package/dist/adapter/lib/go-naming.d.ts.map +1 -0
- package/dist/adapter/lib/ir-scope.d.ts +13 -0
- package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
- package/dist/adapter/lib/types.d.ts +165 -0
- package/dist/adapter/lib/types.d.ts.map +1 -0
- package/dist/adapter/memo/ctor-lowering.d.ts +39 -0
- package/dist/adapter/memo/ctor-lowering.d.ts.map +1 -0
- package/dist/adapter/memo/memo-compute.d.ts +173 -0
- package/dist/adapter/memo/memo-compute.d.ts.map +1 -0
- package/dist/adapter/memo/memo-type.d.ts +48 -0
- package/dist/adapter/memo/memo-type.d.ts.map +1 -0
- package/dist/adapter/memo/memo-value.d.ts +64 -0
- package/dist/adapter/memo/memo-value.d.ts.map +1 -0
- package/dist/adapter/memo/template-interp.d.ts +43 -0
- package/dist/adapter/memo/template-interp.d.ts.map +1 -0
- package/dist/adapter/props/prop-types.d.ts +31 -0
- package/dist/adapter/props/prop-types.d.ts.map +1 -0
- package/dist/adapter/spread/spread-codegen.d.ts +40 -0
- package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
- package/dist/adapter/type/type-codegen.d.ts +25 -0
- package/dist/adapter/type/type-codegen.d.ts.map +1 -0
- package/dist/adapter/value/parsed-literal-to-go.d.ts +17 -0
- package/dist/adapter/value/parsed-literal-to-go.d.ts.map +1 -0
- package/dist/adapter/value/value-lowering.d.ts +46 -0
- package/dist/adapter/value/value-lowering.d.ts.map +1 -0
- package/dist/build.js +3114 -2690
- package/dist/index.js +3116 -2692
- package/dist/test-render.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/derived-state-memo.test.ts +155 -84
- package/src/__tests__/go-template-adapter.test.ts +398 -54
- package/src/__tests__/lowering-plugin.test.ts +109 -0
- package/src/__tests__/query-href.test.ts +179 -0
- package/src/adapter/analysis/component-tree.ts +174 -0
- package/src/adapter/emit-context.ts +59 -0
- package/src/adapter/expr/helper-inline.ts +274 -0
- package/src/adapter/expr/url-builder.ts +123 -0
- package/src/adapter/go-template-adapter.ts +2189 -5320
- package/src/adapter/lib/compile-state.ts +181 -0
- package/src/adapter/lib/constants.ts +24 -0
- package/src/adapter/lib/go-emit.ts +309 -0
- package/src/adapter/lib/go-naming.ts +84 -0
- package/src/adapter/lib/ir-scope.ts +31 -0
- package/src/adapter/lib/types.ts +182 -0
- package/src/adapter/memo/ctor-lowering.ts +267 -0
- package/src/adapter/memo/memo-compute.ts +661 -0
- package/src/adapter/memo/memo-type.ts +93 -0
- package/src/adapter/memo/memo-value.ts +197 -0
- package/src/adapter/memo/template-interp.ts +246 -0
- package/src/adapter/props/prop-types.ts +84 -0
- package/src/adapter/spread/spread-codegen.ts +458 -0
- package/src/adapter/type/type-codegen.ts +95 -0
- package/src/adapter/value/parsed-literal-to-go.ts +94 -0
- package/src/adapter/value/value-lowering.ts +162 -0
- package/src/test-render.ts +48 -22
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Value lowering: convert a JS signal/const initial value into a Go literal for
|
|
3
|
+
* the SSR data context — scalars, prop references, and fully-literal
|
|
4
|
+
* arrays/objects — falling back to `nil`/`0` for anything not reducible to a
|
|
5
|
+
* literal. Pure free functions over a {@link GoEmitContext}.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ParsedExpr, TypeInfo } from '@barefootjs/jsx'
|
|
9
|
+
|
|
10
|
+
import type { GoEmitContext } from '../emit-context.ts'
|
|
11
|
+
import type { PropFallbackVar } from '../lib/types.ts'
|
|
12
|
+
import { capitalizeFieldName } from '../lib/go-naming.ts'
|
|
13
|
+
import { parsedLiteralToGo } from './parsed-literal-to-go.ts'
|
|
14
|
+
|
|
15
|
+
/** Default for `getSignalInitialValueAsGo`'s optional fallback-var map. */
|
|
16
|
+
const EMPTY_PROP_FALLBACK_VARS: ReadonlyMap<string, PropFallbackVar> = new Map()
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Lower a signal/const initial value to its Go SSR literal: a prop reference
|
|
20
|
+
* becomes `in.<Field>`, a non-literal falls back to the type's zero value.
|
|
21
|
+
*/
|
|
22
|
+
export function convertInitialValue(
|
|
23
|
+
ctx: GoEmitContext,
|
|
24
|
+
value: string,
|
|
25
|
+
typeInfo: TypeInfo,
|
|
26
|
+
propsParams?: { name: string }[],
|
|
27
|
+
preParsed?: ParsedExpr,
|
|
28
|
+
): string {
|
|
29
|
+
if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {
|
|
30
|
+
if (propsParams?.some(p => p.name === value)) {
|
|
31
|
+
return `in.${capitalizeFieldName(value)}`
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const propName = ctx.extractPropNameFromInitialValue(value)
|
|
36
|
+
if (propName && propsParams?.some(p => p.name === propName)) {
|
|
37
|
+
return `in.${capitalizeFieldName(propName)}`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (typeInfo.kind === 'primitive') {
|
|
41
|
+
if (typeInfo.primitive === 'boolean') {
|
|
42
|
+
return value === 'true' ? 'true' : 'false'
|
|
43
|
+
}
|
|
44
|
+
if (typeInfo.primitive === 'number') {
|
|
45
|
+
if (/^\d+$/.test(value)) return value
|
|
46
|
+
if (/^\d+\.\d+$/.test(value)) return value
|
|
47
|
+
return '0'
|
|
48
|
+
}
|
|
49
|
+
if (typeInfo.primitive === 'string') {
|
|
50
|
+
if (value.startsWith("'") || value.endsWith("'")) {
|
|
51
|
+
return value.replace(/'/g, '"')
|
|
52
|
+
}
|
|
53
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
54
|
+
return value
|
|
55
|
+
}
|
|
56
|
+
return '""'
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeInfo.kind === 'array') {
|
|
61
|
+
return jsLiteralToGo(ctx, typeInfo, preParsed) ?? 'nil'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// A string type-alias keeps its string value instead of falling to nil.
|
|
65
|
+
if (typeInfo.kind === 'interface' && typeInfo.raw) {
|
|
66
|
+
const aliasBase = ctx.state.localTypeAliases.get(typeInfo.raw)
|
|
67
|
+
if (aliasBase === 'string') {
|
|
68
|
+
if (value.startsWith("'") || value.startsWith('"')) {
|
|
69
|
+
return value.replace(/'/g, '"')
|
|
70
|
+
}
|
|
71
|
+
return '""'
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return 'nil'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Lower a fully-literal value — from the analyzer's carried `ParsedExpr` tree —
|
|
80
|
+
* to a Go literal typed as `typeInfo`:
|
|
81
|
+
*
|
|
82
|
+
* `["x", "y"]` (string[]) → `[]string{"x", "y"}`
|
|
83
|
+
* `["x", "y"]` (unknown[]) → `[]interface{}{"x", "y"}`
|
|
84
|
+
* `[{ id: "a" }]` (Item[]) → `[]Item{Item{ID: "a"}}`
|
|
85
|
+
*
|
|
86
|
+
* Returns null (caller keeps `nil`) for a non-literal, or a shape that can't be
|
|
87
|
+
* expressed in the target type (e.g. an object in a `[]interface{}`, unreachable
|
|
88
|
+
* via the template's struct-field access).
|
|
89
|
+
*/
|
|
90
|
+
export function jsLiteralToGo(
|
|
91
|
+
ctx: GoEmitContext,
|
|
92
|
+
typeInfo: TypeInfo,
|
|
93
|
+
preParsed?: ParsedExpr,
|
|
94
|
+
): string | null {
|
|
95
|
+
if (preParsed) {
|
|
96
|
+
const structured = parsedLiteralToGo(ctx, preParsed, typeInfo)
|
|
97
|
+
if (structured !== null) return structured
|
|
98
|
+
}
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Bake a flat object literal (`{ align: 'start' }`) into a Go
|
|
104
|
+
* `map[string]interface{}` keyed by SOURCE property names, so it round-trips
|
|
105
|
+
* through `bf_json` like `JSON.stringify` (only the supplied keys, no zero-filled
|
|
106
|
+
* struct fields). Used for an inline object passed to a child's optional object
|
|
107
|
+
* prop. Returns null for a non-object / shorthand / nested / empty object.
|
|
108
|
+
*/
|
|
109
|
+
export function objectLiteralToGoMap(ctx: GoEmitContext, expr: ParsedExpr): string | null {
|
|
110
|
+
if (expr.kind !== 'object-literal') return null
|
|
111
|
+
const entries: string[] = []
|
|
112
|
+
for (const prop of expr.properties) {
|
|
113
|
+
if (prop.shorthand) return null
|
|
114
|
+
const val = parsedLiteralToGo(ctx, prop.value)
|
|
115
|
+
if (val === null) return null
|
|
116
|
+
entries.push(`${JSON.stringify(prop.key)}: ${val}`)
|
|
117
|
+
}
|
|
118
|
+
if (entries.length === 0) return null
|
|
119
|
+
return `map[string]interface{}{${entries.join(', ')}}`
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get a signal's initial value as Go code — a literal, or a props reference
|
|
124
|
+
* (`in.<Field>`, or the hoisted fallback var when `props.X ?? N` has one).
|
|
125
|
+
* Unrecognized values default to `0`.
|
|
126
|
+
*/
|
|
127
|
+
export function getSignalInitialValueAsGo(
|
|
128
|
+
ctx: GoEmitContext,
|
|
129
|
+
initialValue: string,
|
|
130
|
+
propsParams: { name: string }[],
|
|
131
|
+
propFallbackVars: ReadonlyMap<string, PropFallbackVar> = EMPTY_PROP_FALLBACK_VARS,
|
|
132
|
+
): string {
|
|
133
|
+
if (propsParams.some(p => p.name === initialValue)) {
|
|
134
|
+
const hoisted = propFallbackVars.get(initialValue)
|
|
135
|
+
if (hoisted) return hoisted.varName
|
|
136
|
+
return `in.${capitalizeFieldName(initialValue)}`
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const propName = ctx.extractPropNameFromInitialValue(initialValue)
|
|
140
|
+
if (propName && propsParams.some(p => p.name === propName)) {
|
|
141
|
+
const hoisted = propFallbackVars.get(propName)
|
|
142
|
+
if (hoisted) return hoisted.varName
|
|
143
|
+
return `in.${capitalizeFieldName(propName)}`
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// single quotes are normalized to Go double quotes
|
|
147
|
+
if (/^-?\d+$/.test(initialValue)) {
|
|
148
|
+
return initialValue
|
|
149
|
+
}
|
|
150
|
+
if (/^-?\d+\.\d+$/.test(initialValue)) {
|
|
151
|
+
return initialValue
|
|
152
|
+
}
|
|
153
|
+
if (initialValue === 'true' || initialValue === 'false') {
|
|
154
|
+
return initialValue
|
|
155
|
+
}
|
|
156
|
+
if ((initialValue.startsWith("'") && initialValue.endsWith("'")) ||
|
|
157
|
+
(initialValue.startsWith('"') && initialValue.endsWith('"'))) {
|
|
158
|
+
return initialValue.replace(/'/g, '"')
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return '0'
|
|
162
|
+
}
|
package/src/test-render.ts
CHANGED
|
@@ -9,19 +9,7 @@ import { compileJSX } from '@barefootjs/jsx'
|
|
|
9
9
|
import type { TemplateAdapter, ComponentIR } from '@barefootjs/jsx'
|
|
10
10
|
import { GoTemplateAdapter } from './adapter/go-template-adapter.ts'
|
|
11
11
|
import { deduplicateGoTypes } from './build.ts'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Capitalize a JSON key to its Go struct field name using the same
|
|
15
|
-
* initialism rules as the adapter (`capitalizeFieldName`): a whole-word Go
|
|
16
|
-
* initialism uppercases entirely (`id` → `ID`), otherwise just the first
|
|
17
|
-
* letter. Keeps harness-emitted struct literals (`ItemInput{ID: …}`) matching
|
|
18
|
-
* the generated exported field names. (#1297)
|
|
19
|
-
*/
|
|
20
|
-
function capitalizeGoField(name: string): string {
|
|
21
|
-
if (!name) return name
|
|
22
|
-
if (GoTemplateAdapter.GO_INITIALISMS.has(name.toLowerCase())) return name.toUpperCase()
|
|
23
|
-
return name.charAt(0).toUpperCase() + name.slice(1)
|
|
24
|
-
}
|
|
12
|
+
import { capitalizeFieldName } from './adapter/lib/go-naming.ts'
|
|
25
13
|
import { mkdir, rm } from 'node:fs/promises'
|
|
26
14
|
import { resolve } from 'node:path'
|
|
27
15
|
|
|
@@ -627,13 +615,23 @@ function buildGoPropsInit(
|
|
|
627
615
|
// a bare `[]any{…}` would fail to compile against the typed field.
|
|
628
616
|
// Fall back to `[]any` when the field type is `[]any` / unknown.
|
|
629
617
|
const elemType = goSliceElemType(goTypes, componentName, goField)
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
}
|
|
636
|
-
|
|
618
|
+
let sliceLiteral: string
|
|
619
|
+
if (elemType && elemType.startsWith('map[')) {
|
|
620
|
+
// An untyped object-array Input field (an inline prop object type
|
|
621
|
+
// that didn't synthesize a named struct, e.g. `items: { title:
|
|
622
|
+
// string; tags: string[] }[]` — `typeInfoToGo`'s 'object' case,
|
|
623
|
+
// type-codegen.ts) resolves to `[]map[string]interface{}`, not a
|
|
624
|
+
// named struct. `goTypedSliceLiteralFromArray`'s `goStructLiteral`
|
|
625
|
+
// emits bare `Field: value` entries, which is struct-literal syntax
|
|
626
|
+
// and doesn't compile as a map literal's keys — route these through
|
|
627
|
+
// the map-literal builder instead (#2075, search-params-derived-filter).
|
|
628
|
+
sliceLiteral = goTypedMapSliceLiteralFromArray(value, elemType)
|
|
629
|
+
} else if (elemType) {
|
|
630
|
+
sliceLiteral = goTypedSliceLiteralFromArray(value, elemType)
|
|
631
|
+
} else {
|
|
632
|
+
sliceLiteral = goArrayLiteralFromArray(value)
|
|
633
|
+
}
|
|
634
|
+
lines.push(`\t\t${goField}: ${sliceLiteral},`)
|
|
637
635
|
} else if (value && typeof value === 'object') {
|
|
638
636
|
// Plain object → Go `map[string]any` literal (#1407 follow-up).
|
|
639
637
|
// Used by `jsx-spread-rest-prop` to populate the input-bag
|
|
@@ -670,7 +668,11 @@ function goSliceElemType(
|
|
|
670
668
|
new RegExp(`type ${componentName}Input struct \\{([\\s\\S]*?)\\n\\}`),
|
|
671
669
|
)
|
|
672
670
|
if (!struct) return null
|
|
673
|
-
|
|
671
|
+
// The element-type token can carry brackets/braces of its own (a map type
|
|
672
|
+
// like `map[string]interface{}`), not just word chars — broadened so the
|
|
673
|
+
// capture doesn't truncate at the first `[`. The token has no internal
|
|
674
|
+
// whitespace, so it still stops cleanly before a trailing `// comment`.
|
|
675
|
+
const field = struct[1].match(new RegExp(`\\n\\s*${goField}\\s+\\[\\]([\\w.[\\]{}]+)`))
|
|
674
676
|
if (!field) return null
|
|
675
677
|
const elem = field[1]
|
|
676
678
|
if (elem === 'any' || elem === 'interface{}') return null
|
|
@@ -695,6 +697,30 @@ function goTypedSliceLiteralFromArray(arr: unknown[], elemType: string): string
|
|
|
695
697
|
return `[]${elemType}{${entries.join(', ')}}`
|
|
696
698
|
}
|
|
697
699
|
|
|
700
|
+
/**
|
|
701
|
+
* Emit a typed Go slice-of-map literal (`[]map[string]interface{}{map[string]any{…}, …}`)
|
|
702
|
+
* for an untyped object-array Input field. Object elements become
|
|
703
|
+
* `map[string]any{"Field": val, …}` literals with PascalCase keys — Go
|
|
704
|
+
* template map lookup is case-sensitive, so `{{.Title}}` needs a capitalized
|
|
705
|
+
* key, same as the untyped `goArrayLiteralFromArray` fallback's object
|
|
706
|
+
* entries. `map[string]any` and `map[string]interface{}` are the identical
|
|
707
|
+
* type (`any` is the builtin alias for `interface{}`), so the emitted
|
|
708
|
+
* literal is assignable to `elemType` regardless of which spelling the
|
|
709
|
+
* Input struct field carries. (#2075, search-params-derived-filter)
|
|
710
|
+
*/
|
|
711
|
+
function goTypedMapSliceLiteralFromArray(arr: unknown[], elemType: string): string {
|
|
712
|
+
const entries = arr.map(v => {
|
|
713
|
+
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
714
|
+
return goMapLiteralFromObject(v as Record<string, unknown>, true)
|
|
715
|
+
}
|
|
716
|
+
if (typeof v === 'string') return `"${v.replace(/"/g, '\\"')}"`
|
|
717
|
+
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
|
718
|
+
if (v === null) return 'nil'
|
|
719
|
+
return goArrayLiteralFromArray(v as unknown[])
|
|
720
|
+
})
|
|
721
|
+
return `[]${elemType}{${entries.join(', ')}}`
|
|
722
|
+
}
|
|
723
|
+
|
|
698
724
|
/**
|
|
699
725
|
* Emit a keyed Go struct literal (`Elem{Field: val, …}`) with PascalCase field
|
|
700
726
|
* names. Only the keys the caller supplied are set, so an omitted optional prop
|
|
@@ -703,7 +729,7 @@ function goTypedSliceLiteralFromArray(arr: unknown[], elemType: string): string
|
|
|
703
729
|
function goStructLiteral(obj: Record<string, unknown>, typeName: string): string {
|
|
704
730
|
const fields: string[] = []
|
|
705
731
|
for (const [k, v] of Object.entries(obj)) {
|
|
706
|
-
const goField =
|
|
732
|
+
const goField = capitalizeFieldName(k)
|
|
707
733
|
if (typeof v === 'string') fields.push(`${goField}: "${v.replace(/"/g, '\\"')}"`)
|
|
708
734
|
else if (typeof v === 'number' || typeof v === 'boolean') fields.push(`${goField}: ${v}`)
|
|
709
735
|
else if (v === null) fields.push(`${goField}: nil`)
|