@barefootjs/jsx 0.16.0 → 0.17.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 (57) hide show
  1. package/dist/adapters/env-signal.d.ts +38 -15
  2. package/dist/adapters/env-signal.d.ts.map +1 -1
  3. package/dist/adapters/jsx-adapter.d.ts.map +1 -1
  4. package/dist/adapters/parsed-expr-emitter.d.ts +7 -6
  5. package/dist/adapters/parsed-expr-emitter.d.ts.map +1 -1
  6. package/dist/analyzer-context.d.ts +29 -1
  7. package/dist/analyzer-context.d.ts.map +1 -1
  8. package/dist/analyzer.d.ts.map +1 -1
  9. package/dist/builtin-lowering-plugins.d.ts +34 -0
  10. package/dist/builtin-lowering-plugins.d.ts.map +1 -0
  11. package/dist/expression-parser.d.ts +219 -163
  12. package/dist/expression-parser.d.ts.map +1 -1
  13. package/dist/index.d.ts +7 -4
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +6754 -6129
  16. package/dist/ir-to-client-js/csr-substitute.d.ts.map +1 -1
  17. package/dist/ir-to-client-js/plan/build-declaration-emit.d.ts.map +1 -1
  18. package/dist/ir-to-client-js/plan/declaration-emit.d.ts +9 -0
  19. package/dist/ir-to-client-js/plan/declaration-emit.d.ts.map +1 -1
  20. package/dist/jsx-to-ir.d.ts.map +1 -1
  21. package/dist/lowering-registry.d.ts +122 -0
  22. package/dist/lowering-registry.d.ts.map +1 -0
  23. package/dist/query-href-lowering.d.ts +63 -0
  24. package/dist/query-href-lowering.d.ts.map +1 -0
  25. package/dist/ssr-defaults.d.ts.map +1 -1
  26. package/dist/types.d.ts +169 -11
  27. package/dist/types.d.ts.map +1 -1
  28. package/package.json +2 -2
  29. package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +68 -3
  30. package/src/__tests__/analyzer.test.ts +53 -0
  31. package/src/__tests__/expression-parser.test.ts +703 -391
  32. package/src/__tests__/ir-reduce-op.test.ts +18 -21
  33. package/src/__tests__/ir-sort-comparator.test.ts +19 -20
  34. package/src/__tests__/lowering-registry.test.ts +141 -0
  35. package/src/__tests__/primitive-resolver-alias.test.ts +23 -0
  36. package/src/__tests__/query-href-recognition.test.ts +58 -0
  37. package/src/__tests__/serialize-parsed-expr.test.ts +204 -0
  38. package/src/__tests__/unsupported-expression.test.ts +98 -4
  39. package/src/adapters/env-signal.ts +60 -21
  40. package/src/adapters/jsx-adapter.ts +17 -0
  41. package/src/adapters/parsed-expr-emitter.ts +39 -41
  42. package/src/analyzer-context.ts +72 -27
  43. package/src/analyzer.ts +226 -9
  44. package/src/builtin-lowering-plugins.ts +54 -0
  45. package/src/expression-parser.ts +1183 -927
  46. package/src/index.ts +26 -3
  47. package/src/ir-to-client-js/csr-substitute.ts +5 -0
  48. package/src/ir-to-client-js/plan/build-declaration-emit.ts +16 -0
  49. package/src/ir-to-client-js/plan/declaration-emit.ts +9 -0
  50. package/src/ir-to-client-js/stringify/declaration-emit.ts +11 -0
  51. package/src/jsx-to-ir.ts +182 -43
  52. package/src/lowering-registry.ts +160 -0
  53. package/src/query-href-lowering.ts +147 -0
  54. package/src/ssr-defaults.ts +5 -1
  55. package/src/types.ts +171 -12
  56. package/src/__tests__/flatmap-support.test.ts +0 -218
  57. package/src/__tests__/reduce-op.test.ts +0 -201
@@ -1,201 +0,0 @@
1
- import { describe, test, expect } from 'bun:test'
2
- import { parseExpression, isSupported, stringifyParsedExpr } from '../expression-parser'
3
-
4
- /**
5
- * `.reduce(fn, init)` arithmetic-fold catalogue (#1448 Tier C). The
6
- * parser intercepts the accepted shapes into a structured `ReduceOp`
7
- * (mirroring the `.sort` `SortComparator` precedent) and refuses
8
- * everything else to `unsupported` so the template adapters surface
9
- * BF101 with the `@client` escape hatch.
10
- */
11
- describe('reduce() arithmetic-fold catalogue', () => {
12
- test('numeric sum over a field → field/numeric', () => {
13
- const r = parseExpression('items.reduce((sum, t) => sum + t.duration, 0)')
14
- expect(r.kind).toBe('array-method')
15
- if (r.kind === 'array-method' && r.method === 'reduce') {
16
- expect(r.reduceOp.op).toBe('+')
17
- expect(r.reduceOp.key).toEqual({ kind: 'field', field: 'duration' })
18
- expect(r.reduceOp.type).toBe('numeric')
19
- expect(r.reduceOp.init).toBe('0')
20
- expect(r.reduceOp.paramAcc).toBe('sum')
21
- expect(r.reduceOp.paramItem).toBe('t')
22
- }
23
- expect(isSupported(r).supported).toBe(true)
24
- })
25
-
26
- test('numeric sum over self (primitive array) → self/numeric', () => {
27
- const r = parseExpression('nums.reduce((a, b) => a + b, 0)')
28
- expect(r.kind).toBe('array-method')
29
- if (r.kind === 'array-method' && r.method === 'reduce') {
30
- expect(r.reduceOp.op).toBe('+')
31
- expect(r.reduceOp.key).toEqual({ kind: 'self' })
32
- expect(r.reduceOp.type).toBe('numeric')
33
- }
34
- })
35
-
36
- test('reduceRight shares the catalogue and preserves the method name', () => {
37
- const r = parseExpression("items.reduceRight((acc, x) => acc + x.label, '')")
38
- expect(r.kind).toBe('array-method')
39
- if (r.kind === 'array-method') {
40
- expect(r.method).toBe('reduceRight')
41
- if (r.method === 'reduceRight') {
42
- expect(r.reduceOp.op).toBe('+')
43
- expect(r.reduceOp.key).toEqual({ kind: 'field', field: 'label' })
44
- expect(r.reduceOp.type).toBe('string')
45
- }
46
- }
47
- })
48
-
49
- test('reduceRight round-trips with its method name preserved', () => {
50
- const r = parseExpression('nums.reduceRight((a, b) => a + b, 0)')
51
- expect(stringifyParsedExpr(r)).toBe('nums.reduceRight((a,b) => a + b, 0)')
52
- })
53
-
54
- test('product over a field with init 1 → field/numeric/*', () => {
55
- const r = parseExpression('items.reduce((acc, x) => acc * x.qty, 1)')
56
- expect(r.kind).toBe('array-method')
57
- if (r.kind === 'array-method' && r.method === 'reduce') {
58
- expect(r.reduceOp.op).toBe('*')
59
- expect(r.reduceOp.key).toEqual({ kind: 'field', field: 'qty' })
60
- expect(r.reduceOp.type).toBe('numeric')
61
- expect(r.reduceOp.init).toBe('1')
62
- }
63
- })
64
-
65
- test('string init makes + a concatenation fold (init is the decoded value)', () => {
66
- const r = parseExpression("items.reduce((acc, x) => acc + x.label, '')")
67
- expect(r.kind).toBe('array-method')
68
- if (r.kind === 'array-method' && r.method === 'reduce') {
69
- expect(r.reduceOp.op).toBe('+')
70
- expect(r.reduceOp.type).toBe('string')
71
- expect(r.reduceOp.init).toBe('') // decoded contents, not the `''` source
72
- }
73
- })
74
-
75
- test('non-empty string seed decodes to its contents', () => {
76
- const r = parseExpression("items.reduce((acc, x) => acc + x.label, ', ')")
77
- expect(r.kind).toBe('array-method')
78
- if (r.kind === 'array-method' && r.method === 'reduce') {
79
- expect(r.reduceOp.init).toBe(', ')
80
- }
81
- })
82
-
83
- test('numeric init normalises separators / radix to canonical decimal', () => {
84
- // `.text` gives TS's canonical decimal so Go ParseFloat + Perl agree
85
- // (#1728 review: raw `1_000` / `0x10` would mis-parse on Go).
86
- for (const [src, expected] of [
87
- ['1_000', '1000'],
88
- ['0x10', '16'],
89
- ['1e3', '1000'],
90
- ] as const) {
91
- const r = parseExpression(`nums.reduce((a, b) => a + b, ${src})`)
92
- expect(r.kind).toBe('array-method')
93
- if (r.kind === 'array-method' && r.method === 'reduce') {
94
- expect(r.reduceOp.init).toBe(expected)
95
- }
96
- }
97
- })
98
-
99
- test('parenthesized numeric init is unwrapped and accepted', () => {
100
- for (const [src, expected] of [
101
- ['(0)', '0'],
102
- ['(-1)', '-1'],
103
- ] as const) {
104
- const r = parseExpression(`nums.reduce((a, b) => a + b, ${src})`)
105
- expect(r.kind).toBe('array-method')
106
- if (r.kind === 'array-method' && r.method === 'reduce') {
107
- expect(r.reduceOp.init).toBe(expected)
108
- }
109
- }
110
- })
111
-
112
- test('an escape-free seed containing an apostrophe is accepted (decoded contents kept)', () => {
113
- // `"a'b"` is escape-free (decoded === raw inner), so it's accepted;
114
- // the decoded value carries the apostrophe, which the Mojo emit
115
- // single-quote-escapes.
116
- const r = parseExpression(`items.reduce((acc, x) => acc + x.l, "a'b")`)
117
- expect(r.kind).toBe('array-method')
118
- if (r.kind === 'array-method' && r.method === 'reduce') {
119
- expect(r.reduceOp.init).toBe("a'b")
120
- }
121
- })
122
-
123
- test('single-return block body unwraps to the fold expression', () => {
124
- const r = parseExpression('items.reduce((sum, t) => { return sum + t.n }, 0)')
125
- expect(r.kind).toBe('array-method')
126
- if (r.kind === 'array-method' && r.method === 'reduce') {
127
- expect(r.reduceOp.key).toEqual({ kind: 'field', field: 'n' })
128
- }
129
- })
130
-
131
- test('negative numeric init is accepted', () => {
132
- const r = parseExpression('nums.reduce((a, b) => a + b, -1)')
133
- expect(r.kind).toBe('array-method')
134
- if (r.kind === 'array-method' && r.method === 'reduce') {
135
- expect(r.reduceOp.init).toBe('-1')
136
- }
137
- })
138
-
139
- test('round-trips a numeric fold back to valid JS via stringifyParsedExpr', () => {
140
- const r = parseExpression('items.reduce((sum, t) => sum + t.duration, 0)')
141
- expect(stringifyParsedExpr(r)).toBe('items.reduce((sum,t) => sum + t.duration, 0)')
142
- })
143
-
144
- test('round-trips a string fold by re-quoting the decoded seed', () => {
145
- const r = parseExpression("items.reduce((a, x) => a + x.l, ', ')")
146
- // Decoded seed `, ` re-quoted via JSON.stringify → a valid JS string.
147
- expect(stringifyParsedExpr(r)).toBe('items.reduce((a,x) => a + x.l, ", ")')
148
- })
149
-
150
- describe('refused shapes → unsupported (BF101)', () => {
151
- test('missing initial value', () => {
152
- // A no-init reduce isn't intercepted (it needs 2 args); it falls
153
- // through to a generic `call` that the UNSUPPORTED_METHODS gate
154
- // refuses — JS throws on an empty array here, so a template can't
155
- // mirror it cleanly.
156
- const r = parseExpression('items.reduce((sum, t) => sum + t.duration)')
157
- expect(isSupported(r).supported).toBe(false)
158
- })
159
-
160
- test('string concat with * is rejected', () => {
161
- const r = parseExpression("items.reduce((acc, x) => acc * x.label, '')")
162
- expect(r.kind).toBe('unsupported')
163
- })
164
-
165
- test('accumulator on the right operand is rejected', () => {
166
- const r = parseExpression('items.reduce((sum, t) => t.duration + sum, 0)')
167
- expect(r.kind).toBe('unsupported')
168
- })
169
-
170
- test('non-literal init is rejected', () => {
171
- const r = parseExpression('items.reduce((sum, t) => sum + t.n, start)')
172
- expect(r.kind).toBe('unsupported')
173
- })
174
-
175
- test('object-building reducer is rejected', () => {
176
- const r = parseExpression('items.reduce((acc, x) => ({ ...acc, [x.id]: x }), {})')
177
- expect(r.kind).toBe('unsupported')
178
- })
179
-
180
- test('deep field access is rejected', () => {
181
- const r = parseExpression('items.reduce((sum, t) => sum + t.a.b, 0)')
182
- expect(r.kind).toBe('unsupported')
183
- })
184
-
185
- test('string seed carrying an escape is rejected (cross-adapter safety)', () => {
186
- // A seed with an escape can't be guaranteed byte-equal across the
187
- // Go-template / Perl string embeddings without per-target decoding,
188
- // so it refuses to `unsupported` rather than risk divergence.
189
- const r = parseExpression("items.reduce((acc, x) => acc + x.l, '\\n')")
190
- expect(r.kind).toBe('unsupported')
191
- })
192
-
193
- test('reduceRight without an init is refused (like reduce)', () => {
194
- // The matching 2-arg form is intercepted; a no-init reduceRight
195
- // falls through to a generic call the UNSUPPORTED_METHODS gate
196
- // refuses (JS throws on an empty array there).
197
- const rr = parseExpression('items.reduceRight((sum, t) => sum + t.n)')
198
- expect(isSupported(rr).supported).toBe(false)
199
- })
200
- })
201
- })