@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.
Files changed (73) hide show
  1. package/dist/adapter/analysis/component-tree.d.ts +23 -0
  2. package/dist/adapter/analysis/component-tree.d.ts.map +1 -0
  3. package/dist/adapter/emit-context.d.ts +53 -0
  4. package/dist/adapter/emit-context.d.ts.map +1 -0
  5. package/dist/adapter/expr/helper-inline.d.ts +31 -0
  6. package/dist/adapter/expr/helper-inline.d.ts.map +1 -0
  7. package/dist/adapter/expr/url-builder.d.ts +23 -0
  8. package/dist/adapter/expr/url-builder.d.ts.map +1 -0
  9. package/dist/adapter/go-template-adapter.d.ts +302 -1071
  10. package/dist/adapter/go-template-adapter.d.ts.map +1 -1
  11. package/dist/adapter/index.js +3116 -2692
  12. package/dist/adapter/lib/compile-state.d.ts +142 -0
  13. package/dist/adapter/lib/compile-state.d.ts.map +1 -0
  14. package/dist/adapter/lib/constants.d.ts +11 -0
  15. package/dist/adapter/lib/constants.d.ts.map +1 -0
  16. package/dist/adapter/lib/go-emit.d.ts +126 -0
  17. package/dist/adapter/lib/go-emit.d.ts.map +1 -0
  18. package/dist/adapter/lib/go-naming.d.ts +39 -0
  19. package/dist/adapter/lib/go-naming.d.ts.map +1 -0
  20. package/dist/adapter/lib/ir-scope.d.ts +13 -0
  21. package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
  22. package/dist/adapter/lib/types.d.ts +165 -0
  23. package/dist/adapter/lib/types.d.ts.map +1 -0
  24. package/dist/adapter/memo/ctor-lowering.d.ts +39 -0
  25. package/dist/adapter/memo/ctor-lowering.d.ts.map +1 -0
  26. package/dist/adapter/memo/memo-compute.d.ts +173 -0
  27. package/dist/adapter/memo/memo-compute.d.ts.map +1 -0
  28. package/dist/adapter/memo/memo-type.d.ts +48 -0
  29. package/dist/adapter/memo/memo-type.d.ts.map +1 -0
  30. package/dist/adapter/memo/memo-value.d.ts +64 -0
  31. package/dist/adapter/memo/memo-value.d.ts.map +1 -0
  32. package/dist/adapter/memo/template-interp.d.ts +43 -0
  33. package/dist/adapter/memo/template-interp.d.ts.map +1 -0
  34. package/dist/adapter/props/prop-types.d.ts +31 -0
  35. package/dist/adapter/props/prop-types.d.ts.map +1 -0
  36. package/dist/adapter/spread/spread-codegen.d.ts +40 -0
  37. package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
  38. package/dist/adapter/type/type-codegen.d.ts +25 -0
  39. package/dist/adapter/type/type-codegen.d.ts.map +1 -0
  40. package/dist/adapter/value/parsed-literal-to-go.d.ts +17 -0
  41. package/dist/adapter/value/parsed-literal-to-go.d.ts.map +1 -0
  42. package/dist/adapter/value/value-lowering.d.ts +46 -0
  43. package/dist/adapter/value/value-lowering.d.ts.map +1 -0
  44. package/dist/build.js +3114 -2690
  45. package/dist/index.js +3116 -2692
  46. package/dist/test-render.d.ts.map +1 -1
  47. package/package.json +3 -3
  48. package/src/__tests__/derived-state-memo.test.ts +155 -84
  49. package/src/__tests__/go-template-adapter.test.ts +398 -54
  50. package/src/__tests__/lowering-plugin.test.ts +109 -0
  51. package/src/__tests__/query-href.test.ts +179 -0
  52. package/src/adapter/analysis/component-tree.ts +174 -0
  53. package/src/adapter/emit-context.ts +59 -0
  54. package/src/adapter/expr/helper-inline.ts +274 -0
  55. package/src/adapter/expr/url-builder.ts +123 -0
  56. package/src/adapter/go-template-adapter.ts +2189 -5320
  57. package/src/adapter/lib/compile-state.ts +181 -0
  58. package/src/adapter/lib/constants.ts +24 -0
  59. package/src/adapter/lib/go-emit.ts +309 -0
  60. package/src/adapter/lib/go-naming.ts +84 -0
  61. package/src/adapter/lib/ir-scope.ts +31 -0
  62. package/src/adapter/lib/types.ts +182 -0
  63. package/src/adapter/memo/ctor-lowering.ts +267 -0
  64. package/src/adapter/memo/memo-compute.ts +661 -0
  65. package/src/adapter/memo/memo-type.ts +93 -0
  66. package/src/adapter/memo/memo-value.ts +197 -0
  67. package/src/adapter/memo/template-interp.ts +246 -0
  68. package/src/adapter/props/prop-types.ts +84 -0
  69. package/src/adapter/spread/spread-codegen.ts +458 -0
  70. package/src/adapter/type/type-codegen.ts +95 -0
  71. package/src/adapter/value/parsed-literal-to-go.ts +94 -0
  72. package/src/adapter/value/value-lowering.ts +162 -0
  73. 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
+ }
@@ -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
- lines.push(
631
- `\t\t${goField}: ${
632
- elemType
633
- ? goTypedSliceLiteralFromArray(value, elemType)
634
- : goArrayLiteralFromArray(value)
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
- const field = struct[1].match(new RegExp(`\\n\\s*${goField}\\s+\\[\\]([\\w.]+)`))
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 = capitalizeGoField(k)
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`)