@barefootjs/go-template 0.17.0 → 0.18.0

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 (39) hide show
  1. package/dist/adapter/expr/url-builder.d.ts.map +1 -1
  2. package/dist/adapter/go-template-adapter.d.ts +144 -14
  3. package/dist/adapter/go-template-adapter.d.ts.map +1 -1
  4. package/dist/adapter/index.js +449 -118
  5. package/dist/adapter/lib/compile-state.d.ts +27 -1
  6. package/dist/adapter/lib/compile-state.d.ts.map +1 -1
  7. package/dist/adapter/lib/go-emit.d.ts +9 -0
  8. package/dist/adapter/lib/go-emit.d.ts.map +1 -1
  9. package/dist/adapter/lib/go-naming.d.ts +23 -0
  10. package/dist/adapter/lib/go-naming.d.ts.map +1 -1
  11. package/dist/adapter/memo/memo-compute.d.ts +54 -5
  12. package/dist/adapter/memo/memo-compute.d.ts.map +1 -1
  13. package/dist/adapter/memo/memo-type.d.ts +10 -0
  14. package/dist/adapter/memo/memo-type.d.ts.map +1 -1
  15. package/dist/adapter/type/type-codegen.d.ts +5 -1
  16. package/dist/adapter/type/type-codegen.d.ts.map +1 -1
  17. package/dist/adapter/value/parsed-literal-to-go.d.ts +13 -4
  18. package/dist/adapter/value/parsed-literal-to-go.d.ts.map +1 -1
  19. package/dist/build.js +449 -118
  20. package/dist/conformance-pins.d.ts +12 -0
  21. package/dist/conformance-pins.d.ts.map +1 -0
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +468 -118
  25. package/package.json +3 -3
  26. package/src/__tests__/go-template-adapter.test.ts +652 -129
  27. package/src/__tests__/lowering-plugin.test.ts +56 -0
  28. package/src/adapter/expr/url-builder.ts +14 -3
  29. package/src/adapter/go-template-adapter.ts +637 -140
  30. package/src/adapter/lib/compile-state.ts +31 -0
  31. package/src/adapter/lib/go-emit.ts +20 -0
  32. package/src/adapter/lib/go-naming.ts +29 -0
  33. package/src/adapter/memo/memo-compute.ts +228 -18
  34. package/src/adapter/memo/memo-type.ts +19 -0
  35. package/src/adapter/type/type-codegen.ts +22 -3
  36. package/src/adapter/value/parsed-literal-to-go.ts +82 -9
  37. package/src/conformance-pins.ts +135 -0
  38. package/src/index.ts +1 -0
  39. package/src/test-render.ts +46 -8
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Per-fixture build-time contracts for shapes the Go template adapter
3
+ * intentionally refuses to lower. Lives here (not on the shared fixtures)
4
+ * so adding a new adapter doesn't require touching any cross-adapter
5
+ * file — every adapter declares its own refusal set against the
6
+ * canonical fixture corpus. Consumed by this package's own conformance
7
+ * test (as `expectedDiagnostics`) and by `bf compat` (issue-URL
8
+ * attribution).
9
+ */
10
+
11
+ import type { ConformancePins } from '@barefootjs/jsx'
12
+
13
+ export const conformancePins: ConformancePins = {
14
+ // `style-object-dynamic` / `style-3-signals` no longer pinned — a
15
+ // `style={{ … }}` object literal now lowers to a CSS string with dynamic
16
+ // values interpolated (`background-color:{{.Color}};padding:8px`) via
17
+ // `tryLowerStyleObject` (#1322).
18
+ // Sibling-imported child component inside a loop body: the adapter
19
+ // emits `{{template "X" .}}` which only resolves if the user has
20
+ // compiled the sibling file and registered the template on the
21
+ // same instance. BF103 makes that requirement loud. (The barefoot
22
+ // CLI passes `siblingTemplatesRegistered: true` so CLI builds
23
+ // suppress the diagnostic — see compileJSX `siblingTemplatesRegistered`.)
24
+ 'static-array-children': [{ code: 'BF103', severity: 'error' }],
25
+ // TodoApp / TodoAppSSR import `TodoItem` from a sibling file and
26
+ // call it inside a keyed `.map`. Same BF103 surface as
27
+ // `static-array-children` above — pinned at adapter level so the
28
+ // shared-component corpus stays adapter-neutral.
29
+ 'todo-app': [{ code: 'BF103', severity: 'error' }],
30
+ 'todo-app-ssr': [{ code: 'BF103', severity: 'error' }],
31
+ // `([emoji, users]) => ...` is an array-index tuple destructure — #2087
32
+ // Phase B's widened gate now admits this shape (`destructure-array-index-in-map`
33
+ // exercises the same `segments`-based lowering). The remaining refusal here
34
+ // is orthogonal: `entries` is a function-scope local const with a computed
35
+ // initializer (`Object.entries(props.reactions ?? {}).filter(...)`) that
36
+ // the Go adapter has no binding for (only a STRING-derived local resolves
37
+ // to a generated struct field, via `computeDerivedConstFields`/`isStringExpr`)
38
+ // — left unchecked this would silently execute-time-fail instead of
39
+ // building loud, so `renderLoop` raises BF101 for a bare-identifier loop
40
+ // array bound to such a const. See the `renderLoop` comment at the check
41
+ // site; Jinja / ERB apply the same narrow check for the same reason.
42
+ 'static-array-from-props': [{ code: 'BF101', severity: 'error' }],
43
+ // Same computed-const array as above, plus the pre-existing BF103 (a
44
+ // sibling-imported child component used inside the loop body) — the
45
+ // destructure param itself no longer contributes a diagnostic.
46
+ 'static-array-from-props-with-component': [
47
+ { code: 'BF103', severity: 'error' },
48
+ { code: 'BF101', severity: 'error' },
49
+ ],
50
+ // (`style-3-signals` graduated alongside `style-object-dynamic` — see note
51
+ // above; the `style={{ … }}` object now lowers to a CSS string.)
52
+ // (`tagged-template-classname` graduated by #2092 — the tag resolves
53
+ // through the interleave-tag catalogue and desugars to an untagged
54
+ // template literal, so it lowers like any other className template.)
55
+ // #2038: a filter predicate whose body contains a NESTED callback call
56
+ // (`t => !picked().some(p => …)` / `t => picked().find(p => …)`). The
57
+ // evaluator refuses nested arrows and `renderFilterExpr` has no faithful
58
+ // Go form for the inner call (its `call` arm used to silently drop the
59
+ // arrow argument and render only the callee) — the compiler is loud
60
+ // instead of lossy. The `/* @client */` twin
61
+ // (`filter-nested-callback-predicate-client`) has no pin here: it must
62
+ // render clean on every adapter, which asserts the suppression contract.
63
+ // https://github.com/piconic-ai/barefootjs/issues/2038
64
+ 'filter-nested-callback-predicate': [
65
+ { code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2038' },
66
+ ],
67
+ 'filter-nested-find-predicate': [
68
+ { code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2038' },
69
+ ],
70
+ // #1310 / #2087: rest destructure in .map() callback. `isLowerableLoopDestructure`
71
+ // now admits every shape this fixture family exercises — each fixed/rest
72
+ // binding resolves via `buildSegmentAccessor`/`buildDestructureBindingMap`
73
+ // against a synthetic `$__bf_item0` range var (the reserved `__bf_item`
74
+ // name, depth-suffixed): a plain field → `$__bf_item0.Id`, an array-index
75
+ // step → `(index $__bf_item0 0)`, an array-rest → `(bf_slice $__bf_item0
76
+ // 1)` (composes under `.length` via `member()`'s generic `len <obj>` arm),
77
+ // and an object-rest member read (`rest.flag`) → `$__bf_item0.Flag`. A
78
+ // `{...rest}` SPREAD (`rest-destructure-object-spread-in-map`) routes
79
+ // through the new `bf_omit` runtime helper instead, so the residual omits
80
+ // exactly the sibling keys the pattern destructured out. No fixture in
81
+ // this family is pinned anymore — all six render to real Go / byte-exact
82
+ // HTML (`rest-destructure-object-in-map`, `rest-destructure-object-spread-in-map`,
83
+ // `rest-destructure-array-in-map`, `rest-destructure-nested-in-map`,
84
+ // `destructure-array-index-in-map`, `destructure-nested-object-in-map`).
85
+ // #1443: `[a, b].filter(Boolean).join(' ')` (registry Slot) now
86
+ // lowers to `bf_join (bf_filter_truthy (bf_arr ...)) " "`. No
87
+ // BF101 expected — pinned positively by the
88
+ // `branch-local-filter-join-go` template-output test below.
89
+ //
90
+ // #1448 Tier A — JS Array / String methods that the Go template
91
+ // adapter hasn't lowered yet. Each row drops once the
92
+ // corresponding method PR lands. Hono / CSR pass these out of
93
+ // the box (they evaluate JS at runtime) so the pin only applies
94
+ // here.
95
+ //
96
+ // `array-includes` / `string-includes` no longer pinned — both
97
+ // shapes lower via the shared `array-method` IR + the polymorphic
98
+ // `bf_includes` runtime helper that dispatches on
99
+ // `reflect.Kind()` (slice/array → element search, string →
100
+ // substring search). The condition-position lowering picks up
101
+ // the same emit through the `array-method` arm of
102
+ // `renderConditionExpr` (#1448 Tier A first PR).
103
+ //
104
+ // Remaining fixtures land at expression position and surface BF101
105
+ // via `convertExpressionToGo`. Distinct codes for the two paths is
106
+ // pre-existing adapter behaviour, not something this catalog
107
+ // should paper over — pinned literally here.
108
+ // `array-indexOf` / `array-lastIndexOf` no longer pinned —
109
+ // value-equality `bf_index_of` / `bf_last_index_of` Go runtime
110
+ // helpers handle the shape (#1448 Tier A second PR).
111
+ // `array-at` no longer pinned — the pre-existing `bf_at` runtime
112
+ // helper now lowers `.at(i)` (#1448 Tier A third PR).
113
+ // `array-concat` no longer pinned — the new `bf_concat` runtime
114
+ // helper merges two arrays into a single `[]any` (#1448 Tier A
115
+ // fourth PR).
116
+ // `array-slice` no longer pinned — the new `bf_slice` runtime
117
+ // helper carves out a sub-range with JS-compat clamping
118
+ // (#1448 Tier A fifth PR).
119
+ // `array-reverse` / `array-toReversed` no longer pinned —
120
+ // both share the `bf_reverse` helper since SSR templates
121
+ // render a snapshot and the JS mutate-vs-new distinction has
122
+ // no template-level meaning (#1448 Tier A sixth PR).
123
+ // `string-toLowerCase` / `string-toUpperCase` no longer pinned —
124
+ // pre-existing `bf_lower` / `bf_upper` runtime helpers wire to
125
+ // the JS method names at the adapter layer (#1448 Tier A
126
+ // seventh + eighth PRs).
127
+ // `string-trim` no longer pinned — pre-existing `bf_trim`
128
+ // (wraps `strings.TrimSpace`) handles the strip (#1448 Tier A
129
+ // ninth PR, closing out Tier A).
130
+ // #2073 follow-up: a function-reference `.map(format)` callback has no
131
+ // arrow body to serialize — not a CALLBACK_METHODS shape — so the
132
+ // UNSUPPORTED_METHODS gate refuses it with BF101 rather than emitting
133
+ // a broken template.
134
+ 'array-map-function-reference': [{ code: 'BF101', severity: 'error' }],
135
+ }
package/src/index.ts CHANGED
@@ -6,3 +6,4 @@
6
6
 
7
7
  export { GoTemplateAdapter, goTemplateAdapter } from './adapter/index.ts'
8
8
  export type { GoTemplateAdapterOptions } from './adapter/index.ts'
9
+ export { conformancePins } from './conformance-pins.ts'
@@ -615,13 +615,23 @@ function buildGoPropsInit(
615
615
  // a bare `[]any{…}` would fail to compile against the typed field.
616
616
  // Fall back to `[]any` when the field type is `[]any` / unknown.
617
617
  const elemType = goSliceElemType(goTypes, componentName, goField)
618
- lines.push(
619
- `\t\t${goField}: ${
620
- elemType
621
- ? goTypedSliceLiteralFromArray(value, elemType)
622
- : goArrayLiteralFromArray(value)
623
- },`,
624
- )
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},`)
625
635
  } else if (value && typeof value === 'object') {
626
636
  // Plain object → Go `map[string]any` literal (#1407 follow-up).
627
637
  // Used by `jsx-spread-rest-prop` to populate the input-bag
@@ -658,7 +668,11 @@ function goSliceElemType(
658
668
  new RegExp(`type ${componentName}Input struct \\{([\\s\\S]*?)\\n\\}`),
659
669
  )
660
670
  if (!struct) return null
661
- 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.[\\]{}]+)`))
662
676
  if (!field) return null
663
677
  const elem = field[1]
664
678
  if (elem === 'any' || elem === 'interface{}') return null
@@ -683,6 +697,30 @@ function goTypedSliceLiteralFromArray(arr: unknown[], elemType: string): string
683
697
  return `[]${elemType}{${entries.join(', ')}}`
684
698
  }
685
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
+
686
724
  /**
687
725
  * Emit a keyed Go struct literal (`Elem{Field: val, …}`) with PascalCase field
688
726
  * names. Only the keys the caller supplied are set, so an omitted optional prop