@barefootjs/jsx 0.29.0 → 0.30.2
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 +17 -0
- package/dist/adapters/child-scope.d.ts.map +1 -0
- package/dist/adapters/loop-bound-names.d.ts.map +1 -1
- package/dist/analyzer-context.d.ts.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +990 -367
- package/dist/ir-to-client-js/collect-elements.d.ts +5 -25
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts +66 -2
- package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/lazy-conditional.d.ts +92 -0
- package/dist/ir-to-client-js/control-flow/plan/lazy-conditional.d.ts.map +1 -0
- package/dist/ir-to-client-js/control-flow/plan/lazy-preamble.d.ts +134 -0
- package/dist/ir-to-client-js/control-flow/plan/lazy-preamble.d.ts.map +1 -0
- package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.d.ts +46 -11
- package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/reactive-effects.d.ts +7 -0
- package/dist/ir-to-client-js/control-flow/plan/reactive-effects.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts.map +1 -1
- package/dist/ir-to-client-js/csr-substitute.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts +2 -3
- package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
- package/dist/ir-to-client-js/plan/declaration-emit.d.ts +6 -0
- package/dist/ir-to-client-js/plan/declaration-emit.d.ts.map +1 -1
- package/dist/ir-to-client-js/reactivity.d.ts +1 -10
- package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
- package/dist/ir-to-client-js/types.d.ts +12 -0
- package/dist/ir-to-client-js/types.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/prop-rewrite.d.ts +24 -7
- package/dist/prop-rewrite.d.ts.map +1 -1
- package/dist/rich-type-evidence.d.ts.map +1 -1
- package/dist/ssr-defaults.d.ts.map +1 -1
- package/dist/to-locale-date-lowering.d.ts.map +1 -1
- package/dist/types.d.ts +78 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +6 -24
- package/src/__tests__/branch-loop-plain.test.ts +28 -45
- package/src/__tests__/csr-substitution-safety-divergence.test.ts +9 -3
- package/src/__tests__/csr-template-scope-soundness.test.ts +167 -0
- package/src/__tests__/early-return-branch-switch.test.ts +96 -0
- package/src/__tests__/form-control-value-ssr.test.ts +102 -0
- package/src/__tests__/lazy-conditional.test.ts +190 -0
- package/src/__tests__/lazy-preamble.test.ts +297 -0
- package/src/__tests__/lazy-row-eligibility.test.ts +97 -28
- package/src/__tests__/loop-item-root-scope.test.ts +213 -0
- package/src/__tests__/map-multi-return-body.test.ts +30 -3
- package/src/__tests__/nested-loop-conditional.test.ts +27 -6
- package/src/__tests__/preamble-attr-reactivity.test.ts +217 -0
- package/src/__tests__/preamble-declarations.test.ts +207 -0
- package/src/__tests__/ssr-defaults.test.ts +44 -0
- package/src/adapters/child-scope.ts +22 -0
- package/src/adapters/loop-bound-names.ts +6 -0
- package/src/analyzer-context.ts +43 -0
- package/src/analyzer.ts +33 -3
- package/src/index.ts +1 -0
- package/src/ir-to-client-js/collect-elements.ts +51 -10
- package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +1 -1
- package/src/ir-to-client-js/control-flow/plan/build-composite-loop.ts +2 -2
- package/src/ir-to-client-js/control-flow/plan/build-event-delegation.ts +3 -3
- package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +2 -2
- package/src/ir-to-client-js/control-flow/plan/build-lazy-row.ts +147 -5
- package/src/ir-to-client-js/control-flow/plan/build-loop.ts +2 -2
- package/src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts +1 -0
- package/src/ir-to-client-js/control-flow/plan/lazy-conditional.ts +151 -0
- package/src/ir-to-client-js/control-flow/plan/lazy-preamble.ts +287 -0
- package/src/ir-to-client-js/control-flow/plan/lazy-row-eligibility.ts +85 -22
- package/src/ir-to-client-js/control-flow/plan/reactive-effects.ts +7 -0
- package/src/ir-to-client-js/control-flow/stringify/lazy-row.ts +133 -35
- package/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts +24 -4
- package/src/ir-to-client-js/csr-substitute.ts +6 -1
- package/src/ir-to-client-js/emit-registration.ts +1 -1
- package/src/ir-to-client-js/html-template.ts +51 -40
- package/src/ir-to-client-js/index.ts +1 -1
- package/src/ir-to-client-js/plan/build-declaration-emit.ts +2 -0
- package/src/ir-to-client-js/plan/build-static-array-child-init.ts +1 -1
- package/src/ir-to-client-js/plan/declaration-emit.ts +6 -0
- package/src/ir-to-client-js/reactivity.ts +25 -0
- package/src/ir-to-client-js/stringify/declaration-emit.ts +9 -4
- package/src/ir-to-client-js/types.ts +12 -0
- package/src/jsx-to-ir.ts +409 -24
- package/src/prop-rewrite.ts +149 -19
- package/src/rich-type-evidence.ts +6 -7
- package/src/ssr-defaults.ts +7 -2
- package/src/to-locale-date-lowering.ts +8 -2
- package/src/types.ts +80 -0
package/src/prop-rewrite.ts
CHANGED
|
@@ -3,39 +3,165 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Walks the TypeScript AST to identify destructured prop names used as
|
|
5
5
|
* value references (not as object keys, property access targets, or
|
|
6
|
-
* shorthand properties), then
|
|
6
|
+
* shorthand properties), then splices `_p.` onto exactly those
|
|
7
|
+
* references in the emitted text via a second scope-aware walk over
|
|
8
|
+
* the text's own AST.
|
|
9
|
+
*
|
|
10
|
+
* Scope model: a name bound INSIDE the expression — a nested arrow /
|
|
11
|
+
* function parameter, a block-scoped declaration, a catch variable —
|
|
12
|
+
* refers to that binding, not the prop, for the whole binding's scope.
|
|
13
|
+
* Both the discovery walk and the rewrite walk carry the same binding
|
|
14
|
+
* stack, so `items.map((title) => title.a)` never turns into the
|
|
15
|
+
* syntactically invalid `.map((_p.title) => _p.title.a)` when `title`
|
|
16
|
+
* is also a prop. (Names bound by loop callbacks that ENCLOSE the
|
|
17
|
+
* expression are the caller's job — see the `ctx.loopParams` filter in
|
|
18
|
+
* `jsx-to-ir.ts`'s `rewriteBarePropRefs` wrapper, #2222.)
|
|
7
19
|
*/
|
|
8
20
|
|
|
9
21
|
import ts from 'typescript'
|
|
10
22
|
import { PROPS_PARAM } from './ir-to-client-js/utils.ts'
|
|
11
23
|
import { createTemplateAwareStringProtector } from './ir-to-client-js/html-template.ts'
|
|
12
24
|
|
|
25
|
+
/** Collect every name introduced by a binding name (identifier or pattern). */
|
|
26
|
+
function collectBindingNames(name: ts.BindingName, out: Set<string>): void {
|
|
27
|
+
if (ts.isIdentifier(name)) {
|
|
28
|
+
out.add(name.text)
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
for (const el of name.elements) {
|
|
32
|
+
if (ts.isBindingElement(el)) collectBindingNames(el.name, out)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Names bound by `n` for its subtree, or null when `n` introduces no
|
|
38
|
+
* scope. Function-likes bind their parameters (and a function
|
|
39
|
+
* expression its own name); blocks bind their statement-level
|
|
40
|
+
* variable/function declarations; catch clauses bind their variable.
|
|
41
|
+
*/
|
|
42
|
+
function scopeFrameOf(n: ts.Node): Set<string> | null {
|
|
43
|
+
if (ts.isFunctionLike(n)) {
|
|
44
|
+
const frame = new Set<string>()
|
|
45
|
+
for (const p of n.parameters) collectBindingNames(p.name, frame)
|
|
46
|
+
if ((ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n)) && n.name) frame.add(n.name.text)
|
|
47
|
+
return frame.size > 0 ? frame : null
|
|
48
|
+
}
|
|
49
|
+
if (ts.isBlock(n)) {
|
|
50
|
+
const frame = new Set<string>()
|
|
51
|
+
for (const st of n.statements) {
|
|
52
|
+
if (ts.isVariableStatement(st)) {
|
|
53
|
+
for (const d of st.declarationList.declarations) collectBindingNames(d.name, frame)
|
|
54
|
+
} else if (ts.isFunctionDeclaration(st) && st.name) {
|
|
55
|
+
frame.add(st.name.text)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return frame.size > 0 ? frame : null
|
|
59
|
+
}
|
|
60
|
+
if (ts.isCatchClause(n) && n.variableDeclaration) {
|
|
61
|
+
const frame = new Set<string>()
|
|
62
|
+
collectBindingNames(n.variableDeclaration.name, frame)
|
|
63
|
+
return frame.size > 0 ? frame : null
|
|
64
|
+
}
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Depth-first walk that maintains the binding stack described above and
|
|
70
|
+
* reports every identifier along with whether a binding within `root`
|
|
71
|
+
* currently shadows it.
|
|
72
|
+
*/
|
|
73
|
+
function walkWithScope(
|
|
74
|
+
root: ts.Node,
|
|
75
|
+
visit: (ident: ts.Identifier, parent: ts.Node | undefined, shadowed: boolean) => void,
|
|
76
|
+
): void {
|
|
77
|
+
const scopeStack: Set<string>[] = []
|
|
78
|
+
const isShadowed = (name: string) => scopeStack.some(frame => frame.has(name))
|
|
79
|
+
function rec(n: ts.Node, parent?: ts.Node) {
|
|
80
|
+
const frame = scopeFrameOf(n)
|
|
81
|
+
if (frame) scopeStack.push(frame)
|
|
82
|
+
if (ts.isIdentifier(n)) visit(n, parent, isShadowed(n.text))
|
|
83
|
+
ts.forEachChild(n, child => rec(child, n))
|
|
84
|
+
if (frame) scopeStack.pop()
|
|
85
|
+
}
|
|
86
|
+
rec(root)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* True when `n` sits in a non-value position where a prop rewrite must
|
|
91
|
+
* never apply: an object-literal key, a member-access name, a binding
|
|
92
|
+
* position (parameter / variable / binding-element name), or a type
|
|
93
|
+
* reference.
|
|
94
|
+
*/
|
|
95
|
+
function isNonValuePosition(n: ts.Identifier, parent: ts.Node | undefined): boolean {
|
|
96
|
+
if (!parent) return false
|
|
97
|
+
if (ts.isPropertyAssignment(parent) && parent.name === n) return true
|
|
98
|
+
if (ts.isPropertyAccessExpression(parent) && parent.name === n) return true
|
|
99
|
+
if (ts.isQualifiedName(parent) && parent.right === n) return true
|
|
100
|
+
if ((ts.isParameter(parent) || ts.isVariableDeclaration(parent) || ts.isBindingElement(parent)) && parent.name === n) return true
|
|
101
|
+
if (ts.isTypeReferenceNode(parent)) return true
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
|
|
13
105
|
/**
|
|
14
106
|
* Walk an AST node for destructured-prop value references and add
|
|
15
107
|
* each found name to `out`. Same skip rules as the rewrite path —
|
|
16
|
-
* object-literal keys, shorthand properties,
|
|
17
|
-
* names
|
|
18
|
-
*
|
|
19
|
-
* branch-local prop-dep cache from
|
|
108
|
+
* object-literal keys, shorthand properties, property-access names,
|
|
109
|
+
* and names shadowed by a binding inside `node` are excluded so only
|
|
110
|
+
* true value references get picked up. Exported for callers that need
|
|
111
|
+
* the raw discovery set (e.g. the branch-local prop-dep cache from
|
|
112
|
+
* #1425).
|
|
20
113
|
*/
|
|
21
114
|
export function collectAstPropRefs(
|
|
22
115
|
node: ts.Node,
|
|
23
116
|
propNames: Set<string>,
|
|
24
117
|
out: Set<string>,
|
|
25
118
|
): void {
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
119
|
+
walkWithScope(node, (n, parent, shadowed) => {
|
|
120
|
+
if (shadowed || !propNames.has(n.text)) return
|
|
121
|
+
if (parent && ts.isShorthandPropertyAssignment(parent) && parent.name === n) return
|
|
122
|
+
if (isNonValuePosition(n, parent)) return
|
|
123
|
+
out.add(n.text)
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Scope-aware rewrite: parse `text` as an expression, walk it with the
|
|
129
|
+
* binding stack, and splice `${PROPS_PARAM}.` onto exactly the
|
|
130
|
+
* identifier references that are (a) in `propRefs` and (b) not
|
|
131
|
+
* shadowed by a binding inside the text. Shorthand properties expand
|
|
132
|
+
* (`{ org }` → `{ org: _p.org }`) so the result stays syntactically
|
|
133
|
+
* valid.
|
|
134
|
+
*
|
|
135
|
+
* Returns null when `text` does not parse cleanly as an expression —
|
|
136
|
+
* the caller falls back to the legacy regex rewrite.
|
|
137
|
+
*/
|
|
138
|
+
function applyScopedPropRefRewrite(text: string, propRefs: Set<string>): string | null {
|
|
139
|
+
// Wrap in parens so object literals and arrows parse as expressions.
|
|
140
|
+
const prefix = '('
|
|
141
|
+
const sf = ts.createSourceFile('__bf_prop_rewrite.ts', `${prefix}${text}\n)`, ts.ScriptTarget.Latest, true)
|
|
142
|
+
const parseDiagnostics = (sf as unknown as { parseDiagnostics?: unknown[] }).parseDiagnostics
|
|
143
|
+
if (parseDiagnostics && parseDiagnostics.length > 0) return null
|
|
144
|
+
|
|
145
|
+
const edits: Array<{ start: number; end: number; replacement: string }> = []
|
|
146
|
+
walkWithScope(sf, (n, parent, shadowed) => {
|
|
147
|
+
if (shadowed || !propRefs.has(n.text)) return
|
|
148
|
+
if (isNonValuePosition(n, parent)) return
|
|
149
|
+
const start = n.getStart(sf) - prefix.length
|
|
150
|
+
const end = n.getEnd() - prefix.length
|
|
151
|
+
if (start < 0 || end > text.length) return
|
|
152
|
+
if (parent && ts.isShorthandPropertyAssignment(parent) && parent.name === n) {
|
|
153
|
+
edits.push({ start, end, replacement: `${n.text}: ${PROPS_PARAM}.${n.text}` })
|
|
154
|
+
return
|
|
35
155
|
}
|
|
36
|
-
|
|
156
|
+
edits.push({ start, end, replacement: `${PROPS_PARAM}.${n.text}` })
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
if (edits.length === 0) return text
|
|
160
|
+
let result = text
|
|
161
|
+
for (const edit of edits.sort((a, b) => b.start - a.start)) {
|
|
162
|
+
result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end)
|
|
37
163
|
}
|
|
38
|
-
|
|
164
|
+
return result
|
|
39
165
|
}
|
|
40
166
|
|
|
41
167
|
/**
|
|
@@ -43,6 +169,10 @@ export function collectAstPropRefs(
|
|
|
43
169
|
* type-stripped expression text. Idempotent under `_p.X` (negative
|
|
44
170
|
* lookbehind on `_p\\.`) and skips object-literal keys via the
|
|
45
171
|
* post-match `{,` + `:` shape check.
|
|
172
|
+
*
|
|
173
|
+
* Legacy fallback for text that doesn't parse as a standalone
|
|
174
|
+
* expression — it cannot see scopes, so `applyScopedPropRefRewrite`
|
|
175
|
+
* is always tried first.
|
|
46
176
|
*/
|
|
47
177
|
export function applyRegexPropRefRewrite(
|
|
48
178
|
text: string,
|
|
@@ -77,8 +207,8 @@ export function applyRegexPropRefRewrite(
|
|
|
77
207
|
* @param extraPropRefs - Optional prop names known to appear in
|
|
78
208
|
* `text` via substitution sources the AST walk can't see (e.g.
|
|
79
209
|
* `text` was produced by inlining a branch-local whose initializer
|
|
80
|
-
* references the prop). The
|
|
81
|
-
*
|
|
210
|
+
* references the prop). The rewrite only touches genuine value
|
|
211
|
+
* references, so passing an over-broad set is safe.
|
|
82
212
|
*/
|
|
83
213
|
export function rewriteBarePropRefs(
|
|
84
214
|
text: string,
|
|
@@ -95,5 +225,5 @@ export function rewriteBarePropRefs(
|
|
|
95
225
|
}
|
|
96
226
|
}
|
|
97
227
|
if (foundPropRefs.size === 0) return undefined
|
|
98
|
-
return applyRegexPropRefRewrite(text, foundPropRefs)
|
|
228
|
+
return applyScopedPropRefRewrite(text, foundPropRefs) ?? applyRegexPropRefRewrite(text, foundPropRefs)
|
|
99
229
|
}
|
|
@@ -69,13 +69,12 @@ type EvidenceMetadata = Pick<IRMetadata, 'propsType' | 'propsObjectName' | 'prop
|
|
|
69
69
|
* check callers gate on).
|
|
70
70
|
*/
|
|
71
71
|
function isNullishArm(t: TypeInfo): boolean {
|
|
72
|
-
|
|
73
|
-
// `null`
|
|
74
|
-
// `
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
return t.kind === 'unknown' && (t.raw === 'null' || t.raw === 'undefined')
|
|
72
|
+
// Purely structural: `typeNodeToTypeInfo`'s literal-type arm lowers a
|
|
73
|
+
// `null` union member (`ts.LiteralTypeNode(NullKeyword)`) to
|
|
74
|
+
// `{ kind: 'primitive', primitive: 'null' }`, the same shape a bare
|
|
75
|
+
// `null` annotation always produced — the raw-text fallback this helper
|
|
76
|
+
// used to carry for the analyzer gap is gone with the gap.
|
|
77
|
+
return t.kind === 'primitive' && (t.primitive === 'null' || t.primitive === 'undefined')
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
export function stripUnion(type: TypeInfo | null): TypeInfo | null {
|
package/src/ssr-defaults.ts
CHANGED
|
@@ -107,15 +107,20 @@ export function extractSsrDefaults(metadata: IRMetadata): Record<string, SsrDefa
|
|
|
107
107
|
// wins via `propName`).
|
|
108
108
|
for (const p of metadata.propsParams) {
|
|
109
109
|
if (p.isRest) continue
|
|
110
|
+
// `propName` is the CALLER-facing key (`$props->{propName}` in the Perl
|
|
111
|
+
// consumer above) — for a renaming destructure (`{ n: count }`) that's
|
|
112
|
+
// `n`, not the local binding `count` the template variable is keyed by.
|
|
113
|
+
// `sourceName ?? name` is an identity for every un-aliased prop.
|
|
114
|
+
const callerPropName = p.sourceName ?? p.name
|
|
110
115
|
if (metadata.propsObjectName === null && p.defaultValue !== undefined) {
|
|
111
116
|
const value = tryStaticEval(p.defaultValue, { bindings: {}, propsLike })
|
|
112
|
-
out[p.name] = { propName:
|
|
117
|
+
out[p.name] = { propName: callerPropName, value: resultToJsonable(value) }
|
|
113
118
|
} else {
|
|
114
119
|
// No destructure default — the template reads the prop as-is;
|
|
115
120
|
// Perl's `//` operator decides what `undef` becomes. The entry
|
|
116
121
|
// still matters so the template variable exists and consumers
|
|
117
122
|
// can supply the propName even with no static fallback.
|
|
118
|
-
out[p.name] = { propName:
|
|
123
|
+
out[p.name] = { propName: callerPropName, value: null }
|
|
119
124
|
}
|
|
120
125
|
}
|
|
121
126
|
// Rest-props bag (`...props`) — tracked separately from `propsParams`
|
|
@@ -393,8 +393,14 @@ function deriveFormat(locale: string, probeOptions: Record<string, string>): Loc
|
|
|
393
393
|
*/
|
|
394
394
|
/** A quoted string-literal union member's value, or null when the member is anything else. */
|
|
395
395
|
function unionMemberLiteral(member: TypeInfo): string | null {
|
|
396
|
-
|
|
397
|
-
|
|
396
|
+
// Structural since the analyzer's literal-type arm: a string-literal
|
|
397
|
+
// member is `kind: 'primitive', primitive: 'string'` carrying its value
|
|
398
|
+
// in `literalValue`. (Previously regexed `member.raw` because literal
|
|
399
|
+
// members lowered to `kind: 'unknown'`.)
|
|
400
|
+
if (member.kind === 'primitive' && member.primitive === 'string' && member.literalValue !== undefined) {
|
|
401
|
+
return member.literalValue
|
|
402
|
+
}
|
|
403
|
+
return null
|
|
398
404
|
}
|
|
399
405
|
|
|
400
406
|
/**
|
package/src/types.ts
CHANGED
|
@@ -60,6 +60,13 @@ export interface TypeInfo {
|
|
|
60
60
|
// For primitives
|
|
61
61
|
primitive?: 'string' | 'number' | 'boolean' | 'null' | 'undefined'
|
|
62
62
|
|
|
63
|
+
/** Set when the type is a LITERAL of its primitive family (`'a'`, `42`,
|
|
64
|
+
* `true`) — the literal's source value. `typeNodeToTypeInfo` lowers a
|
|
65
|
+
* literal type to `kind: 'primitive'` + this field, so family-only
|
|
66
|
+
* consumers need no special case and value consumers never re-parse
|
|
67
|
+
* `raw`. */
|
|
68
|
+
literalValue?: string
|
|
69
|
+
|
|
63
70
|
// For objects/interfaces
|
|
64
71
|
properties?: PropertyInfo[]
|
|
65
72
|
|
|
@@ -854,6 +861,28 @@ export interface MapCallbackPreamble {
|
|
|
854
861
|
ssrText: TsxSourceText
|
|
855
862
|
/** const/let/function names the preamble declares (D5 key-derivability guard). */
|
|
856
863
|
declaredNames: string[]
|
|
864
|
+
/**
|
|
865
|
+
* The whole preamble as backend-neutral value declarations — set only when
|
|
866
|
+
* EVERY statement is one (#2447). This is the DSL SSR carrier: a template
|
|
867
|
+
* language cannot execute `segments` (that is JS text, for the client bundle
|
|
868
|
+
* and for JS-runtime SSR), but it CAN declare a per-row local, and each
|
|
869
|
+
* declaration's initializer is a {@link ParsedExpr} every adapter already
|
|
870
|
+
* knows how to emit in its own syntax — the same door `arrayParsed` /
|
|
871
|
+
* `filterPredicate.predicate` / `sortComparator` go through.
|
|
872
|
+
*
|
|
873
|
+
* All-or-nothing on purpose. A preamble is a statement SEQUENCE with
|
|
874
|
+
* order-dependent scope; lowering the declarable prefix and dropping the rest
|
|
875
|
+
* would leave later statements' effects missing with no diagnostic — the
|
|
876
|
+
* silent divergence #2447 was. So one non-declaration statement (an
|
|
877
|
+
* assignment, a loop, a call for side effects) or one initializer the
|
|
878
|
+
* expression subset cannot model leaves this `undefined`, and the Phase-1
|
|
879
|
+
* DSL gate refuses the whole loop with `/* @client *\/` guidance.
|
|
880
|
+
*
|
|
881
|
+
* Declarations appear in source order and may read earlier ones; adapters
|
|
882
|
+
* emit them in order, so an earlier local is in scope for a later
|
|
883
|
+
* initializer, exactly as in the source.
|
|
884
|
+
*/
|
|
885
|
+
declarations?: PreambleValueDeclaration[]
|
|
857
886
|
/**
|
|
858
887
|
* The subset of {@link declaredNames} that accumulate JSX leaves — the
|
|
859
888
|
* `push`/`unshift` targets of a `jsx` segment and the declaration targets of
|
|
@@ -864,6 +893,25 @@ export interface MapCallbackPreamble {
|
|
|
864
893
|
builderNames: string[]
|
|
865
894
|
}
|
|
866
895
|
|
|
896
|
+
/**
|
|
897
|
+
* One `const`/`let` value declaration from a `.map()` callback preamble, in
|
|
898
|
+
* backend-neutral form (#2447 — see {@link MapCallbackPreamble.declarations}).
|
|
899
|
+
* `name` is always a simple identifier: a destructuring binding declares
|
|
900
|
+
* several names from one initializer, which no adapter has a single per-row
|
|
901
|
+
* local form for, so that shape refuses instead of being carried here.
|
|
902
|
+
*/
|
|
903
|
+
export interface PreambleValueDeclaration {
|
|
904
|
+
/** Declared local name (simple identifier binding). */
|
|
905
|
+
name: string
|
|
906
|
+
/** Initializer, structured for per-adapter emission. */
|
|
907
|
+
valueParsed: ParsedExpr
|
|
908
|
+
/**
|
|
909
|
+
* Initializer source text, for a diagnostic that needs to quote it. Never an
|
|
910
|
+
* emission input — emission goes through `valueParsed`.
|
|
911
|
+
*/
|
|
912
|
+
raw: string
|
|
913
|
+
}
|
|
914
|
+
|
|
867
915
|
/**
|
|
868
916
|
* One loop-body expression child classified as a preamble-patched region
|
|
869
917
|
* (#2389 — see `IRLoop.preambleRegions`). `expr` is the raw (unwrapped)
|
|
@@ -1002,6 +1050,20 @@ export interface IRComponent {
|
|
|
1002
1050
|
* Omitted (undefined) for ordinary components to keep IR diffs minimal.
|
|
1003
1051
|
*/
|
|
1004
1052
|
dynamicTag?: boolean
|
|
1053
|
+
/**
|
|
1054
|
+
* True when this component is a DIRECT member of an `IRLoop.children`
|
|
1055
|
+
* array (the loop's row-root component, e.g. `{rows().map(r => <Row/>)}`).
|
|
1056
|
+
* Such a component owns its own per-row identity and gets a freshly
|
|
1057
|
+
* randomized `bf-s` — it does NOT derive its scope id from the parent
|
|
1058
|
+
* scope + mount slot the way an ordinarily-slotted child does.
|
|
1059
|
+
*
|
|
1060
|
+
* A component nested *below* the row root (e.g. `<li><Badge/></li>` where
|
|
1061
|
+
* `<li>` is the loop row root) is NOT marked — it derives its scope id
|
|
1062
|
+
* from parent scope + slot like any other slotted child. See
|
|
1063
|
+
* `derivesScopeFromSlot` in `./adapters/child-scope.ts`, which is the one
|
|
1064
|
+
* predicate every backend must consult instead of re-deriving this fact.
|
|
1065
|
+
*/
|
|
1066
|
+
loopItemRoot?: boolean
|
|
1005
1067
|
loc: SourceLocation
|
|
1006
1068
|
}
|
|
1007
1069
|
|
|
@@ -1399,6 +1461,15 @@ export interface SignalInfo {
|
|
|
1399
1461
|
* AST-level pre-rewrite).
|
|
1400
1462
|
*/
|
|
1401
1463
|
templateInitialValue?: string
|
|
1464
|
+
/**
|
|
1465
|
+
* True for the getter-elided declaration form `const [, setX] =
|
|
1466
|
+
* createSignal(...)`. `getter` then holds a synthesized internal name
|
|
1467
|
+
* (`__bfGet_<setter>`) that no expression ever references — it exists so
|
|
1468
|
+
* getter-keyed consumers (substitution env, SSR seeding) stay total.
|
|
1469
|
+
* Emit reproduces the source's `[, setter]` hole instead of introducing
|
|
1470
|
+
* the synthesized name into output.
|
|
1471
|
+
*/
|
|
1472
|
+
getterElided?: boolean
|
|
1402
1473
|
type: TypeInfo
|
|
1403
1474
|
loc: SourceLocation
|
|
1404
1475
|
/**
|
|
@@ -1458,6 +1529,15 @@ export interface SignalInfo {
|
|
|
1458
1529
|
export interface MemoInfo {
|
|
1459
1530
|
name: string
|
|
1460
1531
|
computation: string
|
|
1532
|
+
/**
|
|
1533
|
+
* `computation` with bare destructured-prop refs rewritten to `_p.X` —
|
|
1534
|
+
* the memo twin of `SignalInfo.templateInitialValue` (#2265). Consumed by
|
|
1535
|
+
* `buildSignalMemoEnv` when the memo body is inlined into the module-scope
|
|
1536
|
+
* CSR `template:` arrow, which is not a closure over init's destructured
|
|
1537
|
+
* extractions (#2468). Only set for destructured-arg components that
|
|
1538
|
+
* actually reference a prop.
|
|
1539
|
+
*/
|
|
1540
|
+
templateComputation?: string
|
|
1461
1541
|
/** Computation with TypeScript type annotations preserved, for .tsx output */
|
|
1462
1542
|
typedComputation?: string
|
|
1463
1543
|
/**
|