@barefootjs/jsx 0.33.3 → 0.33.6
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/child-scope.d.ts +13 -0
- package/dist/adapters/child-scope.d.ts.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/errors.d.ts +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1392 -1117
- package/dist/ir-to-client-js/build-references.d.ts.map +1 -1
- package/dist/ir-to-client-js/child-components.d.ts.map +1 -1
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
- package/dist/ir-to-client-js/component-scope.d.ts +20 -0
- package/dist/ir-to-client-js/component-scope.d.ts.map +1 -1
- package/dist/ir-to-client-js/compute-inlinability.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-component-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +2 -4
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/shared.d.ts +4 -3
- package/dist/ir-to-client-js/control-flow/shared.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/component-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow.d.ts.map +1 -1
- package/dist/ir-to-client-js/csr-substitute.d.ts +35 -9
- package/dist/ir-to-client-js/csr-substitute.d.ts.map +1 -1
- package/dist/ir-to-client-js/emit-reactive.d.ts +10 -0
- package/dist/ir-to-client-js/emit-reactive.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts +49 -2
- package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
- package/dist/ir-to-client-js/imports.d.ts +2 -2
- package/dist/ir-to-client-js/imports.d.ts.map +1 -1
- package/dist/ir-to-client-js/index.d.ts.map +1 -1
- package/dist/ir-to-client-js/rewrite-props-object.d.ts +12 -1
- package/dist/ir-to-client-js/rewrite-props-object.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/prop-rewrite.d.ts +5 -2
- package/dist/prop-rewrite.d.ts.map +1 -1
- package/dist/props-binding.d.ts +49 -11
- package/dist/props-binding.d.ts.map +1 -1
- package/dist/types.d.ts +19 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +15 -15
- package/src/__tests__/aliased-import-child-registry-2777.test.ts +112 -0
- package/src/__tests__/date-lowering.test.ts +18 -4
- package/src/__tests__/fragment-body-loop-key.test.ts +95 -0
- package/src/__tests__/inner-loop-mathml-namespace.test.ts +1 -1
- package/src/__tests__/inner-loop-svg-namespace.test.ts +4 -4
- package/src/__tests__/issue-2724-prop-alias-loop-not-static.test.ts +355 -0
- package/src/__tests__/issue-2737-props-object-alias-template.test.ts +86 -0
- package/src/__tests__/issue-2756-input-skeleton-bails.test.ts +123 -0
- package/src/__tests__/issue-2778-getter-alias-csr-template.test.ts +113 -0
- package/src/__tests__/issue-2806-rest-bag-template-rewrite.test.ts +136 -0
- package/src/__tests__/issue-2835-props-type-member-name-collision.test.ts +259 -0
- package/src/__tests__/loop-item-root-scope.test.ts +7 -3
- package/src/__tests__/markup-prop-brand.test.ts +165 -26
- package/src/__tests__/multi-root-loop-body.test.ts +7 -3
- package/src/__tests__/namespace-import-primitive.test.ts +244 -0
- package/src/__tests__/preamble-declarations.test.ts +42 -0
- package/src/__tests__/rewrite-destructured-props.test.ts +53 -0
- package/src/__tests__/static-loop-csr-materialize.test.ts +2 -2
- package/src/adapters/child-scope.ts +23 -0
- package/src/analyzer.ts +130 -0
- package/src/errors.ts +14 -1
- package/src/index.ts +7 -0
- package/src/ir-to-client-js/build-references.ts +7 -0
- package/src/ir-to-client-js/child-components.ts +8 -1
- package/src/ir-to-client-js/collect-elements.ts +10 -38
- package/src/ir-to-client-js/component-scope.ts +60 -0
- package/src/ir-to-client-js/compute-inlinability.ts +46 -11
- package/src/ir-to-client-js/control-flow/plan/build-component-loop.ts +19 -1
- package/src/ir-to-client-js/control-flow/plan/loop.ts +2 -4
- package/src/ir-to-client-js/control-flow/shared.ts +12 -5
- package/src/ir-to-client-js/control-flow/stringify/component-loop.ts +7 -0
- package/src/ir-to-client-js/control-flow/stringify/loop.ts +8 -2
- package/src/ir-to-client-js/control-flow.ts +12 -0
- package/src/ir-to-client-js/csr-substitute.ts +82 -16
- package/src/ir-to-client-js/emit-reactive.ts +29 -13
- package/src/ir-to-client-js/emit-registration.ts +1 -1
- package/src/ir-to-client-js/html-template.ts +194 -82
- package/src/ir-to-client-js/imports.ts +4 -0
- package/src/ir-to-client-js/index.ts +34 -23
- package/src/ir-to-client-js/plan/build-static-array-child-init.ts +7 -2
- package/src/ir-to-client-js/rewrite-props-object.ts +14 -0
- package/src/jsx-to-ir.ts +318 -54
- package/src/prop-rewrite.ts +23 -5
- package/src/props-binding.ts +74 -18
- package/src/types.ts +19 -5
package/src/prop-rewrite.ts
CHANGED
|
@@ -132,6 +132,16 @@ export function collectAstPropRefs(
|
|
|
132
132
|
* (`{ org }` → `{ org: _p.org }`) so the result stays syntactically
|
|
133
133
|
* valid.
|
|
134
134
|
*
|
|
135
|
+
* `replacementFor` overrides the substitution text for a value
|
|
136
|
+
* reference (default `${PROPS_PARAM}.<callerKey>`) — e.g. the reactive
|
|
137
|
+
* client-JS emit path (`emit-reactive.ts`'s `rewriteDestructuredPropsInExpr`)
|
|
138
|
+
* wraps it in a `(_p.x ?? <default>)` fallback for a prop with a
|
|
139
|
+
* destructure default. Kept as a caller override rather than a
|
|
140
|
+
* duplicate AST walk in that module, per the "one decision, one
|
|
141
|
+
* implementation" rule (CLAUDE.md) — this walk is already the one place
|
|
142
|
+
* that correctly distinguishes a value reference from an object-literal
|
|
143
|
+
* key / property-access name / shorthand property / binding name.
|
|
144
|
+
*
|
|
135
145
|
* Returns null when `text` does not parse cleanly as an expression —
|
|
136
146
|
* the caller falls back to the legacy regex rewrite.
|
|
137
147
|
*/
|
|
@@ -139,6 +149,7 @@ function applyScopedPropRefRewrite(
|
|
|
139
149
|
text: string,
|
|
140
150
|
propRefs: Set<string>,
|
|
141
151
|
propAliases?: ReadonlyMap<string, string>,
|
|
152
|
+
replacementFor?: (localName: string, callerKey: string) => string,
|
|
142
153
|
): string | null {
|
|
143
154
|
// Wrap in parens so object literals and arrows parse as expressions.
|
|
144
155
|
const prefix = '('
|
|
@@ -157,11 +168,12 @@ function applyScopedPropRefRewrite(
|
|
|
157
168
|
// — #2524 CSR half); the local binding (`n.text`) only survives on the
|
|
158
169
|
// left of a shorthand expansion.
|
|
159
170
|
const callerKey = propAliases?.get(n.text) ?? n.text
|
|
171
|
+
const value = replacementFor ? replacementFor(n.text, callerKey) : `${PROPS_PARAM}.${callerKey}`
|
|
160
172
|
if (parent && ts.isShorthandPropertyAssignment(parent) && parent.name === n) {
|
|
161
|
-
edits.push({ start, end, replacement: `${n.text}: ${
|
|
173
|
+
edits.push({ start, end, replacement: `${n.text}: ${value}` })
|
|
162
174
|
return
|
|
163
175
|
}
|
|
164
|
-
edits.push({ start, end, replacement:
|
|
176
|
+
edits.push({ start, end, replacement: value })
|
|
165
177
|
})
|
|
166
178
|
|
|
167
179
|
if (edits.length === 0) return text
|
|
@@ -186,6 +198,7 @@ export function applyRegexPropRefRewrite(
|
|
|
186
198
|
text: string,
|
|
187
199
|
propRefs: Iterable<string>,
|
|
188
200
|
propAliases?: ReadonlyMap<string, string>,
|
|
201
|
+
replacementFor?: (localName: string, callerKey: string) => string,
|
|
189
202
|
): string {
|
|
190
203
|
const { protect, restore } = createTemplateAwareStringProtector()
|
|
191
204
|
let result = protect(text)
|
|
@@ -193,6 +206,7 @@ export function applyRegexPropRefRewrite(
|
|
|
193
206
|
for (const propName of propRefs) {
|
|
194
207
|
// `_p` is always keyed by the caller-facing name (#2524 CSR half).
|
|
195
208
|
const callerKey = propAliases?.get(propName) ?? propName
|
|
209
|
+
const value = replacementFor ? replacementFor(propName, callerKey) : `${PROPS_PARAM}.${callerKey}`
|
|
196
210
|
const pattern = new RegExp(`(?<!${PROPS_PARAM}\\.)(?<!['"\\w.-])\\b${propName}\\b(?![a-zA-Z0-9_$])`, 'g')
|
|
197
211
|
result = result.replace(pattern, (match, offset, str) => {
|
|
198
212
|
// Skip object literal keys: preceded by { or , and followed by :
|
|
@@ -201,7 +215,7 @@ export function applyRegexPropRefRewrite(
|
|
|
201
215
|
const before = str.slice(0, offset)
|
|
202
216
|
if (/[{,]\s*$/.test(before)) return match
|
|
203
217
|
}
|
|
204
|
-
return
|
|
218
|
+
return value
|
|
205
219
|
})
|
|
206
220
|
}
|
|
207
221
|
|
|
@@ -225,6 +239,9 @@ export function applyRegexPropRefRewrite(
|
|
|
225
239
|
* `_p` is always keyed by the caller-facing name (#2524 CSR half); a name
|
|
226
240
|
* absent from this map emits `_p.<name>` unchanged (the un-aliased case,
|
|
227
241
|
* where `sourceName ?? name` is an identity).
|
|
242
|
+
* @param replacementFor - Override the substitution text for a value
|
|
243
|
+
* reference (default `${PROPS_PARAM}.<callerKey>`) — see
|
|
244
|
+
* `applyScopedPropRefRewrite`'s docstring.
|
|
228
245
|
*/
|
|
229
246
|
export function rewriteBarePropRefs(
|
|
230
247
|
text: string,
|
|
@@ -232,6 +249,7 @@ export function rewriteBarePropRefs(
|
|
|
232
249
|
propNames: Set<string>,
|
|
233
250
|
extraPropRefs?: ReadonlySet<string>,
|
|
234
251
|
propAliases?: ReadonlyMap<string, string>,
|
|
252
|
+
replacementFor?: (localName: string, callerKey: string) => string,
|
|
235
253
|
): string | undefined {
|
|
236
254
|
// Walk AST to find which prop names are actually used as value references
|
|
237
255
|
const foundPropRefs = new Set<string>()
|
|
@@ -243,7 +261,7 @@ export function rewriteBarePropRefs(
|
|
|
243
261
|
}
|
|
244
262
|
if (foundPropRefs.size === 0) return undefined
|
|
245
263
|
return (
|
|
246
|
-
applyScopedPropRefRewrite(text, foundPropRefs, propAliases) ??
|
|
247
|
-
applyRegexPropRefRewrite(text, foundPropRefs, propAliases)
|
|
264
|
+
applyScopedPropRefRewrite(text, foundPropRefs, propAliases, replacementFor) ??
|
|
265
|
+
applyRegexPropRefRewrite(text, foundPropRefs, propAliases, replacementFor)
|
|
248
266
|
)
|
|
249
267
|
}
|
package/src/props-binding.ts
CHANGED
|
@@ -69,6 +69,31 @@ export function buildPropAliasMap(params: readonly ParamInfo[]): Map<string, str
|
|
|
69
69
|
return map
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* The props-parameter shape needed to answer "is `name` an actual local
|
|
74
|
+
* binding introduced by the props parameter" — as opposed to
|
|
75
|
+
* `ctx.patterns.props`, which for a whole `(props: Props)` parameter holds
|
|
76
|
+
* every TYPE-MEMBER name (`extractPropsFromTypeMembers`) for regex prop-
|
|
77
|
+
* access matching, not real bindings.
|
|
78
|
+
*/
|
|
79
|
+
export interface PropsParamBindings {
|
|
80
|
+
propsParams: readonly ParamInfo[]
|
|
81
|
+
propsObjectName: string | null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Local names actually bound by the props parameter. Empty for a whole
|
|
86
|
+
* `(props: Props)` parameter: `propsParams` there is type-member names
|
|
87
|
+
* (nothing local to shadow with), and `props` itself is `propsObjectName`,
|
|
88
|
+
* checked separately by callers.
|
|
89
|
+
*/
|
|
90
|
+
export function boundPropLocalNames(b: PropsParamBindings): ReadonlySet<string> {
|
|
91
|
+
if (b.propsObjectName !== null) return EMPTY_SET
|
|
92
|
+
return new Set(b.propsParams.filter(p => !p.isRest).map(p => p.name))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const EMPTY_SET: ReadonlySet<string> = new Set()
|
|
96
|
+
|
|
72
97
|
/**
|
|
73
98
|
* The two component bindings that forward the caller's leftover props:
|
|
74
99
|
* the destructured `...rest` binding and a whole undestructured `(props)`
|
|
@@ -82,10 +107,52 @@ export interface RestSpreadBindings {
|
|
|
82
107
|
}
|
|
83
108
|
|
|
84
109
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
110
|
+
* Walks a bare `const x__alias = <name>` hop chain recorded in
|
|
111
|
+
* `constantValues` (name → initializer text) until `terminal` recognizes
|
|
112
|
+
* the current name, returning what it recognized. `null` when the chain
|
|
113
|
+
* runs out (a constant whose value isn't a bare identifier — a real
|
|
114
|
+
* computed object — stops the walk) or degenerates into a cycle.
|
|
115
|
+
*
|
|
116
|
+
* The one alias-hop walker in the compiler, shared by every caller that
|
|
117
|
+
* needs "does this name ultimately reach some fixed binding" rather than
|
|
118
|
+
* a bespoke copy per caller — `resolveRestSpreadOriginCore` below,
|
|
119
|
+
* `resolveGetterAliases` (`csr-substitute.ts`, #2778), and
|
|
120
|
+
* `isArrayExprDirectPropRef` (`jsx-to-ir.ts`, #2724, called directly rather
|
|
121
|
+
* than through a `*Core`-style wrapper) are its callers, differing only in
|
|
122
|
+
* what `terminal` recognizes as a hit — and, for `isArrayExprDirectPropRef`,
|
|
123
|
+
* in also narrowing `constantValues` itself (`propAliasHopCandidates`)
|
|
124
|
+
* before passing it in. `terminal` is checked BEFORE the constant-value
|
|
125
|
+
* lookup so a name that is itself a target take priority over any
|
|
126
|
+
* same-named local shadowing it (preserves `resolveRestSpreadOriginCore`'s
|
|
127
|
+
* original hop order) — a caller whose `terminal` can itself be fooled by a
|
|
128
|
+
* shadowed name (e.g. a scope-unaware lookup keyed only by name) must guard
|
|
129
|
+
* that inside `terminal`, since this walker has no scope/binding awareness
|
|
130
|
+
* of its own to do it centrally.
|
|
131
|
+
*
|
|
132
|
+
* `visited` guards a constant cycle (`const a = b; const b = a`); walking
|
|
133
|
+
* hop by hop (not a precomputed set) is what lets a multi-hop alias
|
|
134
|
+
* (`const p2 = props; const p3 = p2`) resolve through every link.
|
|
135
|
+
*/
|
|
136
|
+
export function resolveAliasOrigin<T>(
|
|
137
|
+
constantValues: ReadonlyMap<string, string | undefined>,
|
|
138
|
+
name: string,
|
|
139
|
+
terminal: (name: string) => T | null,
|
|
140
|
+
): T | null {
|
|
141
|
+
const visited = new Set<string>()
|
|
142
|
+
let current: string | undefined = name.trim()
|
|
143
|
+
while (current !== undefined && !visited.has(current)) {
|
|
144
|
+
const hit = terminal(current)
|
|
145
|
+
if (hit !== null) return hit
|
|
146
|
+
visited.add(current)
|
|
147
|
+
current = constantValues.get(current)?.trim()
|
|
148
|
+
}
|
|
149
|
+
return null
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Which of `bindings` the name `name` ultimately reaches. `'rest'` for the
|
|
154
|
+
* destructured rest binding, `'props'` for the whole props object, `null`
|
|
155
|
+
* for anything else.
|
|
89
156
|
*
|
|
90
157
|
* The single definition of "this `{...spread}` forwards the caller's
|
|
91
158
|
* leftover props", shared by the two phases that must agree on it: Phase 1
|
|
@@ -96,26 +163,15 @@ export interface RestSpreadBindings {
|
|
|
96
163
|
* `spreadAttrs({...})` merge. Two copies of the rule would let an element
|
|
97
164
|
* qualify for one and not the other, which is exactly the silent-drop
|
|
98
165
|
* shape #2754 reports.
|
|
99
|
-
*
|
|
100
|
-
* Walks hop by hop (not a precomputed set) so a multi-hop alias
|
|
101
|
-
* (`const p2 = props; const p3 = p2`) resolves through every link;
|
|
102
|
-
* `visited` guards a constant cycle (`const a = b; const b = a`). A
|
|
103
|
-
* constant whose value isn't a bare identifier on the chain (a real
|
|
104
|
-
* computed object) stops the walk, so a genuinely different spread
|
|
105
|
-
* expression is never mistaken for the rest object.
|
|
106
166
|
*/
|
|
107
167
|
export function resolveRestSpreadOriginCore(
|
|
108
168
|
bindings: RestSpreadBindings,
|
|
109
169
|
constantValues: ReadonlyMap<string, string | undefined>,
|
|
110
170
|
name: string,
|
|
111
171
|
): 'rest' | 'props' | null {
|
|
112
|
-
|
|
113
|
-
let current: string | undefined = name.trim()
|
|
114
|
-
while (current !== undefined && !visited.has(current)) {
|
|
172
|
+
return resolveAliasOrigin(constantValues, name, (current) => {
|
|
115
173
|
if (bindings.restPropsName && current === bindings.restPropsName) return 'rest'
|
|
116
174
|
if (bindings.propsObjectName && current === bindings.propsObjectName) return 'props'
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
return null
|
|
175
|
+
return null
|
|
176
|
+
})
|
|
121
177
|
}
|
package/src/types.ts
CHANGED
|
@@ -594,7 +594,9 @@ export interface IRLoop {
|
|
|
594
594
|
isStaticArray: boolean
|
|
595
595
|
|
|
596
596
|
/**
|
|
597
|
-
* True when the array is a direct prop reference promoted to mapArray
|
|
597
|
+
* True when the array is a direct prop reference promoted to mapArray
|
|
598
|
+
* (#1586) — including one reached only through a bare `const x = y`
|
|
599
|
+
* alias-hop chain (`isArrayExprDirectPropRef`, jsx-to-ir.ts, #2724).
|
|
598
600
|
* Adapters use this to include the array in JSON serialization (bf-p)
|
|
599
601
|
* even though isStaticArray is false — the client's mapArray needs the
|
|
600
602
|
* initial data for hydration, unlike signal-backed arrays.
|
|
@@ -936,6 +938,19 @@ export interface MapCallbackPreamble {
|
|
|
936
938
|
* expression subset cannot model leaves this `undefined`, and the Phase-1
|
|
937
939
|
* DSL gate refuses the whole loop with `/* @client *\/` guidance.
|
|
938
940
|
*
|
|
941
|
+
* ONE exception (#2797): a declaration whose initializer is a function
|
|
942
|
+
* literal is ELIDED (contributes no entry here) rather than refusing the
|
|
943
|
+
* preamble, when EVERY read of that name outside its own binding sits
|
|
944
|
+
* inside an event-handler `JsxAttribute` (`/^on[A-Z]/`) value position —
|
|
945
|
+
* see `neutralPreambleDeclarations`'s `everyReadIsEventHandlerPropValue`
|
|
946
|
+
* check. Every DSL adapter's own SSR component-prop builder already skips
|
|
947
|
+
* an event-handler prop outright, so a name read only that way never needs
|
|
948
|
+
* an SSR representation; the client-JS preamble (`segments`) still carries
|
|
949
|
+
* the real closure. This can leave `declarations` a legally EMPTY array
|
|
950
|
+
* (all-declared names elided) rather than `undefined` — the Phase-1 gate
|
|
951
|
+
* checks falsiness, and `[]` is truthy, so an empty array reads as "fully
|
|
952
|
+
* declared, nothing to emit," not a refusal.
|
|
953
|
+
*
|
|
939
954
|
* Declarations appear in source order and may read earlier ones; adapters
|
|
940
955
|
* emit them in order, so an earlier local is in scope for a later
|
|
941
956
|
* initializer, exactly as in the source.
|
|
@@ -1535,10 +1550,9 @@ export interface SignalInfo {
|
|
|
1535
1550
|
* `templateCondition` fields on other IR positions. Destructured mode
|
|
1536
1551
|
* only (`propsObjectName === null`); undefined when no rewrite was
|
|
1537
1552
|
* needed (no destructured prop referenced) or the component is in
|
|
1538
|
-
* object-props mode (there, `rewritePropsObjectRef
|
|
1539
|
-
*
|
|
1540
|
-
*
|
|
1541
|
-
* AST-level pre-rewrite).
|
|
1553
|
+
* object-props mode (there, `rewritePropsObjectRef` handles `props.x` →
|
|
1554
|
+
* `_p.x` on the final joined/substituted string instead, since a bare
|
|
1555
|
+
* `props.x` textual match works without an AST-level pre-rewrite).
|
|
1542
1556
|
*/
|
|
1543
1557
|
templateInitialValue?: string
|
|
1544
1558
|
/**
|