@barefootjs/jsx 0.15.2 → 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.
- package/dist/adapters/env-signal.d.ts +38 -15
- package/dist/adapters/env-signal.d.ts.map +1 -1
- package/dist/adapters/jsx-adapter.d.ts.map +1 -1
- package/dist/adapters/parsed-expr-emitter.d.ts +7 -6
- package/dist/adapters/parsed-expr-emitter.d.ts.map +1 -1
- package/dist/analyzer-context.d.ts +29 -1
- package/dist/analyzer-context.d.ts.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/builtin-lowering-plugins.d.ts +34 -0
- package/dist/builtin-lowering-plugins.d.ts.map +1 -0
- package/dist/expression-parser.d.ts +219 -163
- package/dist/expression-parser.d.ts.map +1 -1
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6892 -6118
- package/dist/ir-to-client-js/csr-substitute.d.ts.map +1 -1
- package/dist/ir-to-client-js/plan/build-declaration-emit.d.ts.map +1 -1
- package/dist/ir-to-client-js/plan/declaration-emit.d.ts +9 -0
- package/dist/ir-to-client-js/plan/declaration-emit.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/lowering-registry.d.ts +122 -0
- package/dist/lowering-registry.d.ts.map +1 -0
- package/dist/profiler.d.ts +115 -0
- package/dist/profiler.d.ts.map +1 -1
- package/dist/query-href-lowering.d.ts +63 -0
- package/dist/query-href-lowering.d.ts.map +1 -0
- package/dist/ssr-defaults.d.ts.map +1 -1
- package/dist/types.d.ts +169 -11
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +68 -3
- package/src/__tests__/analyzer.test.ts +53 -0
- package/src/__tests__/expression-parser.test.ts +703 -391
- package/src/__tests__/ir-reduce-op.test.ts +18 -21
- package/src/__tests__/ir-sort-comparator.test.ts +19 -20
- package/src/__tests__/lowering-registry.test.ts +141 -0
- package/src/__tests__/primitive-resolver-alias.test.ts +23 -0
- package/src/__tests__/profiler.test.ts +149 -0
- package/src/__tests__/query-href-recognition.test.ts +58 -0
- package/src/__tests__/serialize-parsed-expr.test.ts +204 -0
- package/src/__tests__/unsupported-expression.test.ts +98 -4
- package/src/adapters/env-signal.ts +60 -21
- package/src/adapters/jsx-adapter.ts +17 -0
- package/src/adapters/parsed-expr-emitter.ts +39 -41
- package/src/analyzer-context.ts +72 -27
- package/src/analyzer.ts +226 -9
- package/src/builtin-lowering-plugins.ts +54 -0
- package/src/expression-parser.ts +1183 -927
- package/src/index.ts +35 -3
- package/src/ir-to-client-js/csr-substitute.ts +5 -0
- package/src/ir-to-client-js/plan/build-declaration-emit.ts +16 -0
- package/src/ir-to-client-js/plan/declaration-emit.ts +9 -0
- package/src/ir-to-client-js/stringify/declaration-emit.ts +11 -0
- package/src/jsx-to-ir.ts +182 -43
- package/src/lowering-registry.ts +160 -0
- package/src/profiler.ts +328 -0
- package/src/query-href-lowering.ts +147 -0
- package/src/ssr-defaults.ts +5 -1
- package/src/types.ts +171 -12
- package/src/__tests__/flatmap-support.test.ts +0 -218
- 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
|
-
})
|