@barefootjs/jsx 0.31.2 → 0.31.4
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/jsx-adapter.d.ts.map +1 -1
- package/dist/adapters/template-imports.d.ts +27 -15
- package/dist/adapters/template-imports.d.ts.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/debug.d.ts.map +1 -1
- package/dist/identifier-pattern.d.ts +62 -0
- package/dist/identifier-pattern.d.ts.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +280 -119
- package/dist/ir-to-client-js/collect-elements.d.ts +23 -2
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-event-delegation.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/event-delegation.d.ts.map +1 -1
- package/dist/ir-to-client-js/csr-substitute.d.ts +12 -1
- package/dist/ir-to-client-js/csr-substitute.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts +20 -12
- package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
- package/dist/ir-to-client-js/imports.d.ts.map +1 -1
- package/dist/ir-to-client-js/prop-handling.d.ts +25 -2
- package/dist/ir-to-client-js/prop-handling.d.ts.map +1 -1
- package/dist/ir-to-client-js/reactivity.d.ts +30 -2
- package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
- package/dist/ir-to-client-js/rewrite-props-object.d.ts.map +1 -1
- package/dist/ir-to-client-js/utils.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/module-exports.d.ts.map +1 -1
- package/dist/prop-rewrite.d.ts +1 -1
- package/dist/relocate.d.ts.map +1 -1
- package/dist/scope/binding-scope.d.ts +179 -0
- package/dist/scope/binding-scope.d.ts.map +1 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/binding-scope-preamble-shadowing.test.ts +115 -0
- package/src/__tests__/binding-scope-ratchet.test.ts +194 -0
- package/src/__tests__/binding-scope.test.ts +200 -0
- package/src/__tests__/csr-materialize-loop-preamble-shadow.test.ts +76 -0
- package/src/__tests__/csr-substitute-enclosing-scope.test.ts +77 -0
- package/src/__tests__/identifier-pattern.test.ts +170 -0
- package/src/__tests__/let-type-annotation.test.ts +208 -0
- package/src/__tests__/loop-child-reactive-attr-const-shadow.test.ts +107 -0
- package/src/__tests__/rewrite-dynamic-imports.test.ts +98 -0
- package/src/adapters/jsx-adapter.ts +34 -5
- package/src/adapters/template-imports.ts +93 -0
- package/src/analyzer.ts +20 -3
- package/src/debug.ts +4 -3
- package/src/identifier-pattern.ts +79 -0
- package/src/index.ts +5 -1
- package/src/ir-to-client-js/collect-elements.ts +44 -15
- package/src/ir-to-client-js/control-flow/plan/build-event-delegation.ts +2 -1
- package/src/ir-to-client-js/control-flow/stringify/event-delegation.ts +2 -1
- package/src/ir-to-client-js/csr-substitute.ts +19 -2
- package/src/ir-to-client-js/html-template.ts +37 -31
- package/src/ir-to-client-js/imports.ts +2 -1
- package/src/ir-to-client-js/prop-handling.ts +28 -1
- package/src/ir-to-client-js/reactivity.ts +54 -4
- package/src/ir-to-client-js/rewrite-props-object.ts +2 -1
- package/src/ir-to-client-js/utils.ts +9 -8
- package/src/jsx-to-ir.ts +187 -73
- package/src/module-exports.ts +3 -2
- package/src/prop-rewrite.ts +1 -1
- package/src/relocate.ts +2 -1
- package/src/scope/binding-scope.ts +238 -0
- package/src/types.ts +8 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `BindingScope`: the one shared, immutable, stack-shaped model of "names
|
|
3
|
+
* bound by a loop callback" (#2482 Stage 0). Six independent ad-hoc
|
|
4
|
+
* mechanisms across the compiler currently answer this same question —
|
|
5
|
+
* `ctx.loopParams` (a mutated `Set<string>` in `jsx-to-ir.ts`),
|
|
6
|
+
* `collectLoopBoundNames` (`adapters/loop-bound-names.ts`),
|
|
7
|
+
* `resolveStaticLoopSource`'s `isNameShadowed` callback
|
|
8
|
+
* (`static-literal.ts`), and others — each reimplementing the same
|
|
9
|
+
* item/index/destructure/preamble-local bookkeeping with its own bugs and
|
|
10
|
+
* its own blind spots. This module is the single door those mechanisms
|
|
11
|
+
* migrate onto in later stages (Stage 0 only ships the service + tests;
|
|
12
|
+
* NO call site is migrated yet).
|
|
13
|
+
*
|
|
14
|
+
* Immutability is the point, not an incidental style choice:
|
|
15
|
+
* `ctx.loopParams` is `.add`/`.delete`-mutated as `jsx-to-ir.ts` walks in
|
|
16
|
+
* and back out of nested loops, so a caller that forgets (or races) a
|
|
17
|
+
* `.delete()` — or that holds a reference to the "current" set across a
|
|
18
|
+
* push/pop it didn't expect — silently observes the WRONG scope. A
|
|
19
|
+
* restore-bug of that shape is impossible by construction here:
|
|
20
|
+
* `enterLoopRow`/`enterCallback` never mutate `this`, they return a NEW
|
|
21
|
+
* `BindingScope` whose parent is untouched, so holding an old reference
|
|
22
|
+
* always sees the scope as it was, and there is no delete step to forget.
|
|
23
|
+
*
|
|
24
|
+
* Filter/sort callback params (`.filter(x => ...)`, `.sort((a, b) => ...)`,
|
|
25
|
+
* a nested arrow) are bound as `'callback'` frames via `enterCallback` —
|
|
26
|
+
* never folded into a `'loop-row'` frame's bindings. They are a distinct
|
|
27
|
+
* scope-introduction shape (an inner function's own parameter list, not a
|
|
28
|
+
* row's item/index/destructure/preamble names) even though both end up
|
|
29
|
+
* "just names you can't resolve against component-level state."
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* How a name inside a `ScopeFrame` came to be bound — the row shapes
|
|
34
|
+
* (`'item'`/`'index'`/`'destructure'`/`'preamble'`) for `'loop-row'` frames,
|
|
35
|
+
* `'param'` for `'callback'` frames. One shared type because both frame
|
|
36
|
+
* kinds carry the same binding metadata.
|
|
37
|
+
*/
|
|
38
|
+
export type ScopeBindingSource = 'item' | 'index' | 'destructure' | 'preamble' | 'param'
|
|
39
|
+
|
|
40
|
+
export interface ScopeBinding {
|
|
41
|
+
readonly source: ScopeBindingSource
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ScopeFrame {
|
|
45
|
+
readonly kind: 'loop-row' | 'callback'
|
|
46
|
+
readonly bindings: ReadonlyMap<string, ScopeBinding>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Structural pick satisfied by BOTH `IRLoop` (`packages/jsx/src/types.ts`)
|
|
51
|
+
* and the client-JS `LoopCore` family (`packages/jsx/src/ir-to-client-js/types.ts`)
|
|
52
|
+
* without importing either — this module stays dependency-free and cannot
|
|
53
|
+
* form an import cycle with `types.ts` / `ir-to-client-js`.
|
|
54
|
+
*/
|
|
55
|
+
export interface LoopBindingSource {
|
|
56
|
+
readonly param: string
|
|
57
|
+
readonly index?: string | null
|
|
58
|
+
readonly paramBindings?: readonly { readonly name: string }[]
|
|
59
|
+
readonly preamble?: { readonly declaredNames: readonly string[] } | null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A stack of `ScopeFrame`s, innermost frame at index 0 of the internal
|
|
64
|
+
* array (i.e. `frames[0]` is what `enterLoopRow`/`enterCallback` most
|
|
65
|
+
* recently pushed). `lookup`'s `depth` counts from `frames[0]`, so depth 0
|
|
66
|
+
* always means "the innermost frame," independent of how many ancestor
|
|
67
|
+
* frames exist.
|
|
68
|
+
*/
|
|
69
|
+
export class BindingScope {
|
|
70
|
+
static readonly EMPTY: BindingScope = new BindingScope([])
|
|
71
|
+
|
|
72
|
+
private constructor(private readonly frames: readonly ScopeFrame[]) {}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Child scope with a new `'loop-row'` frame for one loop's per-item
|
|
76
|
+
* bindings. Parent (`this`) is not mutated; the returned scope is a
|
|
77
|
+
* NEW object with `frames = [newFrame, ...this.frames]`.
|
|
78
|
+
*
|
|
79
|
+
* Binding semantics mirror `jsx-to-ir.ts`'s `ctx.loopParams` add site
|
|
80
|
+
* EXACTLY (verified against lines ~4320-4345 and the matching delete
|
|
81
|
+
* site ~4695-4710 of `packages/jsx/src/jsx-to-ir.ts`):
|
|
82
|
+
*
|
|
83
|
+
* - When `loop.paramBindings` is non-empty (a destructured callback
|
|
84
|
+
* param, e.g. `.map(({ id, name }) => ...)`), each `paramBindings[i].name`
|
|
85
|
+
* is bound with source `'destructure'` and the raw `param` text
|
|
86
|
+
* (which for a destructured callback holds the ORIGINAL pattern
|
|
87
|
+
* source, e.g. `"{ id, name }"`, not a usable identifier) is NOT
|
|
88
|
+
* bound. This matches `jsx-to-ir.ts`:
|
|
89
|
+
* `if (paramBindings) { for (const b of paramBindings) ctx.loopParams.add(b.name) }`
|
|
90
|
+
* — the `else` branch (`ctx.loopParams.add(param)`) is skipped
|
|
91
|
+
* entirely when `paramBindings` is present.
|
|
92
|
+
* - Otherwise (a plain identifier param, e.g. `.map(item => ...)`),
|
|
93
|
+
* `param` itself is bound with source `'item'`.
|
|
94
|
+
* - `index` (the second callback param, e.g. `.map((item, i) => ...)`)
|
|
95
|
+
* is bound with source `'index'` when non-null/non-undefined.
|
|
96
|
+
* - Every name in `preamble.declaredNames` (a `.map()` callback's
|
|
97
|
+
* pre-return `const`/`let`/`function` locals, #2447) is bound with
|
|
98
|
+
* source `'preamble'`.
|
|
99
|
+
*
|
|
100
|
+
* NOTE on a sibling mechanism this method does NOT mirror:
|
|
101
|
+
* `adapters/loop-bound-names.ts`'s `collectLoopBoundNames` adds BOTH
|
|
102
|
+
* `node.param` AND every `paramBindings[i].name` unconditionally
|
|
103
|
+
* (never skipping `param` in the destructured case) — a deliberately
|
|
104
|
+
* coarser, over-inclusive collection used only to subtract names from
|
|
105
|
+
* a flat string-typing Set (safe to over-exclude there). This method
|
|
106
|
+
* follows the precise `jsx-to-ir.ts` `ctx.loopParams` semantics, since
|
|
107
|
+
* that is the mechanism actually doing scope-shadowed name RESOLUTION
|
|
108
|
+
* (the behavior `BindingScope` replaces), not coarse exclusion.
|
|
109
|
+
*/
|
|
110
|
+
enterLoopRow(loop: LoopBindingSource): BindingScope {
|
|
111
|
+
const bindings = new Map<string, ScopeBinding>()
|
|
112
|
+
if (loop.paramBindings && loop.paramBindings.length > 0) {
|
|
113
|
+
for (const b of loop.paramBindings) bindings.set(b.name, { source: 'destructure' })
|
|
114
|
+
} else {
|
|
115
|
+
bindings.set(loop.param, { source: 'item' })
|
|
116
|
+
}
|
|
117
|
+
if (loop.index != null) bindings.set(loop.index, { source: 'index' })
|
|
118
|
+
for (const name of loop.preamble?.declaredNames ?? []) bindings.set(name, { source: 'preamble' })
|
|
119
|
+
|
|
120
|
+
const frame: ScopeFrame = { kind: 'loop-row', bindings }
|
|
121
|
+
return new BindingScope([frame, ...this.frames])
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Child scope with a new `'callback'` frame binding `params` (a filter
|
|
126
|
+
* predicate's `x`, a sort comparator's `(a, b)`, or a nested arrow's
|
|
127
|
+
* parameter list) with source `'param'`. Parent is not mutated.
|
|
128
|
+
*/
|
|
129
|
+
enterCallback(params: readonly string[]): BindingScope {
|
|
130
|
+
const bindings = new Map<string, ScopeBinding>()
|
|
131
|
+
for (const name of params) bindings.set(name, { source: 'param' })
|
|
132
|
+
const frame: ScopeFrame = { kind: 'callback', bindings }
|
|
133
|
+
return new BindingScope([frame, ...this.frames])
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Innermost-first membership check across every frame in the stack. */
|
|
137
|
+
isBound(name: string): boolean {
|
|
138
|
+
for (const frame of this.frames) {
|
|
139
|
+
if (frame.bindings.has(name)) return true
|
|
140
|
+
}
|
|
141
|
+
return false
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Resolves `name` against the frame stack innermost-first. `depth 0`
|
|
146
|
+
* means the innermost (most recently entered) frame; `null` when `name`
|
|
147
|
+
* is not bound in any frame.
|
|
148
|
+
*/
|
|
149
|
+
lookup(name: string): { readonly depth: number; readonly frame: ScopeFrame; readonly binding: ScopeBinding } | null {
|
|
150
|
+
for (let depth = 0; depth < this.frames.length; depth++) {
|
|
151
|
+
const frame = this.frames[depth]
|
|
152
|
+
const binding = frame.bindings.get(name)
|
|
153
|
+
if (binding) return { depth, frame, binding }
|
|
154
|
+
}
|
|
155
|
+
return null
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Union of every frame's bound names (every `ScopeBindingSource`), for
|
|
160
|
+
* migration interop with legacy `Set<string>`-shaped consumers (e.g.
|
|
161
|
+
* `collectLoopBoundNames`'s return type) as later stages migrate them
|
|
162
|
+
* onto `BindingScope`.
|
|
163
|
+
*
|
|
164
|
+
* This is the SHADOW-GUARD query — see {@link valueBoundNames} for the
|
|
165
|
+
* other consumer class and why the two must not be conflated.
|
|
166
|
+
*/
|
|
167
|
+
boundNames(): ReadonlySet<string> {
|
|
168
|
+
if (this.boundNamesCache) return this.boundNamesCache
|
|
169
|
+
const names = new Set<string>()
|
|
170
|
+
for (const frame of this.frames) {
|
|
171
|
+
for (const name of frame.bindings.keys()) names.add(name)
|
|
172
|
+
}
|
|
173
|
+
this.boundNamesCache = names
|
|
174
|
+
return names
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Both name queries are hot (shadow guards, slot/reactivity classifiers,
|
|
178
|
+
// binding-env memo keying) and the scope is immutable, so each computes
|
|
179
|
+
// once per instance. Callers receive the cached set as ReadonlySet —
|
|
180
|
+
// never mutate it.
|
|
181
|
+
private boundNamesCache: ReadonlySet<string> | undefined
|
|
182
|
+
private valueBoundNamesCache: ReadonlySet<string> | undefined
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Union of names bound via `'item'`/`'index'`/`'destructure'` sources
|
|
186
|
+
* only — the loop row's own per-item identity — excluding `'preamble'`
|
|
187
|
+
* (a `.map()` callback's pre-return `const`/`let`/`function` locals,
|
|
188
|
+
* #2447) and `'param'` (an `enterCallback` frame's filter/sort/nested-
|
|
189
|
+
* arrow parameters).
|
|
190
|
+
*
|
|
191
|
+
* `BindingScope` has exactly two consumer classes, and conflating them
|
|
192
|
+
* is the #2482 Stage 1a Commit 2 regression this split exists to
|
|
193
|
+
* prevent (a `ctx.scope`-wide preamble merge flipped `tag-cloud` and
|
|
194
|
+
* `preamble-cells` conformance fixtures before this method existed):
|
|
195
|
+
*
|
|
196
|
+
* - SHADOW GUARDS (`tryResolveTemplateSpanFromConst`,
|
|
197
|
+
* `tryResolveIdentifierAsTemplateLiteral`, `rewriteBarePropRefs`
|
|
198
|
+
* in `jsx-to-ir.ts`) ask "is this name resolved to SOMETHING in
|
|
199
|
+
* this scope, so an outer const/prop of the same name must not be
|
|
200
|
+
* substituted here at this transform position" — every source
|
|
201
|
+
* qualifies, including a preamble local shadowing a module const.
|
|
202
|
+
* These call `isBound` / `boundNames()`.
|
|
203
|
+
* - REACTIVITY / SLOT-ID CLASSIFIERS (`referencesLoopParam`,
|
|
204
|
+
* `hasReactiveAttributes`, and the `BindingEnvironment.loopParams`
|
|
205
|
+
* feed built from `makeBindingEnv`, all in `jsx-to-ir.ts`) ask
|
|
206
|
+
* "does this expression read a value that changes per row and so
|
|
207
|
+
* needs its own patchable slot" — a preamble local already gets
|
|
208
|
+
* ITS OWN dedicated slot/region-patch machinery
|
|
209
|
+
* (`preambleRegions` / `markPreambleAttrSlots`, #2447), so folding
|
|
210
|
+
* it into this classification double-counts it. Worse: widening a
|
|
211
|
+
* text child's `reactive` flag this way is read by
|
|
212
|
+
* `hasDynamicContent` to decide whether the loop ROW's own root
|
|
213
|
+
* element needs a slot — an unrelated, narrower decision that must
|
|
214
|
+
* not move just because a preamble local is now scope-visible.
|
|
215
|
+
* These call `valueBoundNames()`.
|
|
216
|
+
*/
|
|
217
|
+
valueBoundNames(): ReadonlySet<string> {
|
|
218
|
+
if (this.valueBoundNamesCache) return this.valueBoundNamesCache
|
|
219
|
+
const names = new Set<string>()
|
|
220
|
+
for (const frame of this.frames) {
|
|
221
|
+
for (const [name, binding] of frame.bindings) {
|
|
222
|
+
if (binding.source === 'item' || binding.source === 'index' || binding.source === 'destructure') {
|
|
223
|
+
names.add(name)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
this.valueBoundNamesCache = names
|
|
228
|
+
return names
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Drop-in for `resolveStaticLoopSource`'s `opts.isNameShadowed`
|
|
233
|
+
* (`packages/jsx/src/static-literal.ts:112-128`).
|
|
234
|
+
*/
|
|
235
|
+
asShadowPredicate(): (name: string) => boolean {
|
|
236
|
+
return (name: string) => this.isBound(name)
|
|
237
|
+
}
|
|
238
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1882,6 +1882,14 @@ export interface ConstantInfo {
|
|
|
1882
1882
|
parsed?: ParsedExpr
|
|
1883
1883
|
/** Value with TypeScript type annotations preserved, for .tsx output */
|
|
1884
1884
|
typedValue?: string
|
|
1885
|
+
/**
|
|
1886
|
+
* The declaration's explicit type annotation, verbatim from source
|
|
1887
|
+
* (`node.type.getText()`), when the author wrote one. Distinct from
|
|
1888
|
+
* `type`, which is also populated by inference from the initializer —
|
|
1889
|
+
* emitters must only print THIS field, never an inferred type, onto a
|
|
1890
|
+
* declaration (#2589).
|
|
1891
|
+
*/
|
|
1892
|
+
typeAnnotation?: string
|
|
1885
1893
|
valueBranches?: string[]
|
|
1886
1894
|
declarationKind: 'const' | 'let'
|
|
1887
1895
|
isExported?: boolean
|