@barefootjs/jsx 0.26.2 → 0.26.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/interface.d.ts +29 -0
- package/dist/adapters/interface.d.ts.map +1 -1
- package/dist/adapters/jsx-adapter.d.ts +9 -0
- package/dist/adapters/jsx-adapter.d.ts.map +1 -1
- package/dist/adapters/loop-bound-names.d.ts.map +1 -1
- package/dist/adapters/parsed-expr-emitter.d.ts +0 -10
- package/dist/adapters/parsed-expr-emitter.d.ts.map +1 -1
- package/dist/adapters/test-adapter.d.ts.map +1 -1
- package/dist/analyzer-context.d.ts +11 -1
- package/dist/analyzer-context.d.ts.map +1 -1
- package/dist/analyzer.d.ts +40 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/expression-parser.d.ts.map +1 -1
- package/dist/index.js +1086 -269
- package/dist/ir-to-client-js/build-references.d.ts.map +1 -1
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts +14 -1
- package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.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/build-composite-loop.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/plan/build-inner-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/event-delegation.d.ts +16 -1
- package/dist/ir-to-client-js/control-flow/plan/event-delegation.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +39 -0
- 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 +12 -1
- package/dist/ir-to-client-js/control-flow/shared.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/event-delegation.d.ts +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/event-delegation.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts +19 -0
- 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/html-template.d.ts +67 -1
- 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/plan/build-static-array-child-init.d.ts.map +1 -1
- package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
- package/dist/ir-to-client-js/types.d.ts +29 -4
- package/dist/ir-to-client-js/types.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/loop-destructure.d.ts.map +1 -1
- package/dist/strip-types.d.ts +18 -0
- package/dist/strip-types.d.ts.map +1 -1
- package/dist/types.d.ts +143 -32
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +277 -33
- package/src/__tests__/client-js-generation.test.ts +18 -5
- package/src/__tests__/compiler-runtime-contract.test.ts +4 -4
- package/src/__tests__/compiler-stress-1244.test.ts +12 -1
- package/src/__tests__/delegated-handler-preamble.test.ts +153 -0
- package/src/__tests__/event-delegation-scope-2367.test.ts +108 -0
- package/src/__tests__/flatmap-segments.test.ts +262 -0
- package/src/__tests__/map-arbitrary-body.test.ts +226 -0
- package/src/__tests__/map-body-no-silent-divergence.test.ts +405 -0
- package/src/__tests__/map-multi-return-body.test.ts +172 -0
- package/src/__tests__/preamble-region-patch.test.ts +150 -0
- package/src/__tests__/static-loop-csr-materialize.test.ts +3 -2
- package/src/__tests__/unsupported-expression.test.ts +20 -2
- package/src/adapters/interface.ts +40 -0
- package/src/adapters/jsx-adapter.ts +10 -0
- package/src/adapters/loop-bound-names.ts +6 -2
- package/src/adapters/parsed-expr-emitter.ts +5 -10
- package/src/adapters/test-adapter.ts +10 -0
- package/src/analyzer-context.ts +35 -1
- package/src/analyzer.ts +162 -24
- package/src/compiler.ts +2 -2
- package/src/expression-parser.ts +27 -19
- package/src/ir-to-client-js/build-references.ts +19 -2
- package/src/ir-to-client-js/collect-elements.ts +69 -13
- package/src/ir-to-client-js/control-flow/plan/branch-loop.ts +14 -1
- package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +18 -5
- package/src/ir-to-client-js/control-flow/plan/build-component-loop.ts +4 -0
- package/src/ir-to-client-js/control-flow/plan/build-composite-loop.ts +15 -2
- package/src/ir-to-client-js/control-flow/plan/build-event-delegation.ts +32 -3
- package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +26 -3
- package/src/ir-to-client-js/control-flow/plan/build-loop.ts +53 -2
- package/src/ir-to-client-js/control-flow/plan/event-delegation.ts +16 -1
- package/src/ir-to-client-js/control-flow/plan/loop.ts +40 -0
- package/src/ir-to-client-js/control-flow/shared.ts +28 -2
- package/src/ir-to-client-js/control-flow/stringify/branch-loop.ts +25 -3
- package/src/ir-to-client-js/control-flow/stringify/event-delegation.ts +76 -21
- package/src/ir-to-client-js/control-flow/stringify/loop.ts +65 -1
- package/src/ir-to-client-js/control-flow.ts +11 -0
- package/src/ir-to-client-js/html-template.ts +244 -39
- package/src/ir-to-client-js/imports.ts +1 -1
- package/src/ir-to-client-js/plan/build-static-array-child-init.ts +22 -10
- package/src/ir-to-client-js/reactivity.ts +6 -0
- package/src/ir-to-client-js/types.ts +28 -3
- package/src/jsx-to-ir.ts +1059 -167
- package/src/loop-destructure.ts +9 -5
- package/src/rich-type-refusal.ts +6 -2
- package/src/strip-types.ts +47 -0
- package/src/types.ts +159 -35
package/src/analyzer.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import ts from 'typescript'
|
|
10
10
|
import type { ImportSpecifier, TypeInfo, ParamInfo, ReactiveFactoryInfo, DeclinedReactiveFactory, RequiredFactoryImport, FactoryRenameSite, SourceLocation } from './types.ts'
|
|
11
11
|
import { parseExpression, parseBlockBodyTolerant, foldBlockToExpr } from './expression-parser.ts'
|
|
12
|
+
import type { CallbackBodyAcceptor } from './adapters/interface.ts'
|
|
12
13
|
import { rewriteBarePropRefs } from './prop-rewrite.ts'
|
|
13
14
|
import { incrementCounter } from './instrumentation.ts'
|
|
14
15
|
import {
|
|
@@ -158,7 +159,8 @@ export function analyzeComponent(
|
|
|
158
159
|
source: string,
|
|
159
160
|
filePath: string,
|
|
160
161
|
targetComponentName?: string,
|
|
161
|
-
program?: ts.Program
|
|
162
|
+
program?: ts.Program,
|
|
163
|
+
acceptsCallbackBody?: CallbackBodyAcceptor
|
|
162
164
|
): AnalyzerContext {
|
|
163
165
|
incrementCounter('filesAnalyzed')
|
|
164
166
|
// Track whether the caller supplied a shared ts.Program. Used downstream
|
|
@@ -229,7 +231,7 @@ export function analyzeComponent(
|
|
|
229
231
|
}
|
|
230
232
|
}
|
|
231
233
|
|
|
232
|
-
const ctx = createAnalyzerContext(sourceFile, filePath)
|
|
234
|
+
const ctx = createAnalyzerContext(sourceFile, filePath, acceptsCallbackBody)
|
|
233
235
|
ctx.checker = checker
|
|
234
236
|
|
|
235
237
|
// Reactive-factory prescan results (#931, #2325 cross-file + object-
|
|
@@ -2077,18 +2079,77 @@ function extractSingleJsxReturn(
|
|
|
2077
2079
|
return jsxReturn
|
|
2078
2080
|
}
|
|
2079
2081
|
|
|
2080
|
-
type JsxReturnNode = ts.JsxElement | ts.JsxSelfClosingElement | ts.JsxFragment
|
|
2082
|
+
export type JsxReturnNode = ts.JsxElement | ts.JsxSelfClosingElement | ts.JsxFragment
|
|
2083
|
+
|
|
2084
|
+
/**
|
|
2085
|
+
* The branch structure extracted from a multi-return JSX body — an if/else-if
|
|
2086
|
+
* chain or a `switch`. Shared by the helper-function fold and (Stage 2 of
|
|
2087
|
+
* `spec/callback-fidelity.md`) the `.map()` callback-body fold.
|
|
2088
|
+
*/
|
|
2089
|
+
export interface MultiReturnJsxBranches {
|
|
2090
|
+
branches: Array<{
|
|
2091
|
+
condition: ts.Expression
|
|
2092
|
+
jsxReturn: JsxReturnNode | null
|
|
2093
|
+
/**
|
|
2094
|
+
* For a `switch` with fallthrough (`case 'a': case 'b': return X`), the
|
|
2095
|
+
* additional case expressions that share this branch's return. The fold
|
|
2096
|
+
* OR-joins them with `condition` into `disc === a || disc === b`. Only set
|
|
2097
|
+
* on switch-sourced branches.
|
|
2098
|
+
*/
|
|
2099
|
+
extraCaseConditions?: ts.Expression[]
|
|
2100
|
+
}>
|
|
2101
|
+
fallback: JsxReturnNode | null
|
|
2102
|
+
switchDiscriminant?: ts.Expression
|
|
2103
|
+
/**
|
|
2104
|
+
* Leading `const` / `let` declarations before the branches, collected only
|
|
2105
|
+
* when the caller passes `allowPreamble` (the `.map()` body fold — Stage 2 of
|
|
2106
|
+
* `spec/callback-fidelity.md`). A JS-runtime adapter folds them into a
|
|
2107
|
+
* per-iteration preamble; a DSL adapter can't carry a loop-local into a
|
|
2108
|
+
* conditional branch template, so the `.map()` lowering refuses (BF021 +
|
|
2109
|
+
* `/* @client *\/`) rather than render wrong output. Empty for the
|
|
2110
|
+
* helper-function inliner, which passes no `allowPreamble` and still bails on
|
|
2111
|
+
* any variable statement.
|
|
2112
|
+
*/
|
|
2113
|
+
preamble?: ts.VariableStatement[]
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
/**
|
|
2117
|
+
* A leading-`const` preamble is only safe to fold when every branch (and the
|
|
2118
|
+
* fallback) returns real JSX. If any returns `null`, the loop item can be null
|
|
2119
|
+
* and the compiler routes the loop through the anchored conditional-item
|
|
2120
|
+
* runtime (`mapArrayAnchored`), where a preamble local computed before the
|
|
2121
|
+
* conditional interacts badly with the per-item hydration anchor — observed as
|
|
2122
|
+
* a keyed-reactivity break on a real component (the pivot-table demo, whose
|
|
2123
|
+
* `.map()` body is `const f = …; if (!f) return null; return <div key={fid}>`
|
|
2124
|
+
* and which carries a workaround note about exactly this compiler behavior).
|
|
2125
|
+
* When unsafe, bail so the body falls through to the single-return path
|
|
2126
|
+
* unchanged. Stage 2 of `spec/callback-fidelity.md`.
|
|
2127
|
+
*/
|
|
2128
|
+
function preambleUnsafe(
|
|
2129
|
+
preamble: ts.VariableStatement[],
|
|
2130
|
+
branches: Array<{ jsxReturn: JsxReturnNode | null }>,
|
|
2131
|
+
fallback: JsxReturnNode | null,
|
|
2132
|
+
): boolean {
|
|
2133
|
+
if (preamble.length === 0) return false
|
|
2134
|
+
return fallback === null || branches.some(b => b.jsxReturn === null)
|
|
2135
|
+
}
|
|
2081
2136
|
|
|
2082
2137
|
/**
|
|
2083
2138
|
* Extract conditional branches from a multi-return JSX helper function body.
|
|
2084
2139
|
* Supports if/else if chains and switch statements where every branch
|
|
2085
2140
|
* returns JSX or null. Returns null for unsupported patterns.
|
|
2086
2141
|
*/
|
|
2087
|
-
function extractMultiReturnJsxBranches(
|
|
2088
|
-
body: ts.Block
|
|
2089
|
-
|
|
2090
|
-
|
|
2142
|
+
export function extractMultiReturnJsxBranches(
|
|
2143
|
+
body: ts.Block,
|
|
2144
|
+
allowPreamble = false,
|
|
2145
|
+
): MultiReturnJsxBranches | null {
|
|
2146
|
+
const branches: Array<{
|
|
2147
|
+
condition: ts.Expression
|
|
2148
|
+
jsxReturn: JsxReturnNode | null
|
|
2149
|
+
extraCaseConditions?: ts.Expression[]
|
|
2150
|
+
}> = []
|
|
2091
2151
|
let fallback: JsxReturnNode | null = null
|
|
2152
|
+
const preamble: ts.VariableStatement[] = []
|
|
2092
2153
|
|
|
2093
2154
|
const stmts = body.statements
|
|
2094
2155
|
for (let i = 0; i < stmts.length; i++) {
|
|
@@ -2125,7 +2186,8 @@ function extractMultiReturnJsxBranches(
|
|
|
2125
2186
|
return null
|
|
2126
2187
|
}
|
|
2127
2188
|
if (branches.length === 0) return null
|
|
2128
|
-
|
|
2189
|
+
if (preambleUnsafe(preamble, branches, fallback)) return null
|
|
2190
|
+
return { branches, fallback, preamble }
|
|
2129
2191
|
}
|
|
2130
2192
|
break
|
|
2131
2193
|
}
|
|
@@ -2151,23 +2213,49 @@ function extractMultiReturnJsxBranches(
|
|
|
2151
2213
|
const hasDefault = stmt.caseBlock.clauses.some(c => ts.isDefaultClause(c))
|
|
2152
2214
|
if (!hasDefault) return null
|
|
2153
2215
|
|
|
2216
|
+
// Case labels that fall through to the next clause's body — an empty
|
|
2217
|
+
// `case 'a':` immediately before `case 'b': return X` — share that
|
|
2218
|
+
// return. Accumulate their expressions and attach them to the branch of
|
|
2219
|
+
// the clause that finally carries the body. Stage 2 of
|
|
2220
|
+
// spec/callback-fidelity.md.
|
|
2221
|
+
let pendingCases: ts.Expression[] = []
|
|
2154
2222
|
for (const clause of stmt.caseBlock.clauses) {
|
|
2223
|
+
if (ts.isCaseClause(clause) && clause.statements.length === 0) {
|
|
2224
|
+
pendingCases.push(clause.expression)
|
|
2225
|
+
continue
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2155
2228
|
const jsxReturn = findJsxReturnInCaseClause(clause)
|
|
2156
2229
|
const nullReturn = findNullReturnInCaseClause(clause)
|
|
2157
2230
|
if (!jsxReturn && !nullReturn) return null
|
|
2231
|
+
// Reject clauses that carry extra statements around the return (a
|
|
2232
|
+
// local `const`, an expression statement, …): folding drops them, the
|
|
2233
|
+
// same branch-local-preamble gap as `isDirectReturnBlock`. A bare
|
|
2234
|
+
// `break` is fine. (#2377 review.)
|
|
2235
|
+
if (!caseClauseIsDirectReturn(clause)) return null
|
|
2158
2236
|
|
|
2159
2237
|
if (ts.isCaseClause(clause)) {
|
|
2160
2238
|
branches.push({
|
|
2161
2239
|
condition: clause.expression,
|
|
2162
2240
|
jsxReturn: jsxReturn ?? null,
|
|
2241
|
+
extraCaseConditions: pendingCases.length > 0 ? pendingCases : undefined,
|
|
2163
2242
|
})
|
|
2243
|
+
pendingCases = []
|
|
2164
2244
|
} else {
|
|
2245
|
+
// `default`. A fallthrough INTO default (`case 'a': default:`) would
|
|
2246
|
+
// need the accumulated cases to also hit the default body — an
|
|
2247
|
+
// unusual shape; bail rather than guess.
|
|
2248
|
+
if (pendingCases.length > 0) return null
|
|
2165
2249
|
fallback = jsxReturn ?? null
|
|
2166
2250
|
}
|
|
2167
2251
|
}
|
|
2252
|
+
// Trailing empty case labels with no following body (`case 'z':` last,
|
|
2253
|
+
// no return anywhere after) — nothing to attach them to; bail.
|
|
2254
|
+
if (pendingCases.length > 0) return null
|
|
2168
2255
|
|
|
2169
2256
|
if (branches.length === 0) return null
|
|
2170
|
-
|
|
2257
|
+
if (preambleUnsafe(preamble, branches, fallback)) return null
|
|
2258
|
+
return { branches, fallback, switchDiscriminant: stmt.expression, preamble }
|
|
2171
2259
|
}
|
|
2172
2260
|
|
|
2173
2261
|
// Trailing return <jsx> or return null — this is the fallback
|
|
@@ -2183,16 +2271,36 @@ function extractMultiReturnJsxBranches(
|
|
|
2183
2271
|
continue
|
|
2184
2272
|
}
|
|
2185
2273
|
|
|
2186
|
-
//
|
|
2187
|
-
//
|
|
2188
|
-
|
|
2274
|
+
// Leading `const`/`let` before any branch — collected as a preamble when
|
|
2275
|
+
// the caller allows it (the `.map()` body fold; Stage 2 of
|
|
2276
|
+
// spec/callback-fidelity.md). The JS-runtime fold emits it once per
|
|
2277
|
+
// iteration ahead of the conditional; a DSL target refuses (BF021 +
|
|
2278
|
+
// `/* @client */`) since it can't carry the loop-local into a branch
|
|
2279
|
+
// template. Without `allowPreamble`, or once branches have started, reject:
|
|
2280
|
+
// the helper-function inliner has no place to emit it, and a mid-chain
|
|
2281
|
+
// local referenced in a later condition/JSX would become undefined.
|
|
2282
|
+
if (ts.isVariableStatement(stmt)) {
|
|
2283
|
+
// Only `const` / `let` are collectable — a `var` hoists to the enclosing
|
|
2284
|
+
// function scope with different (redeclaration / TDZ-free) semantics that
|
|
2285
|
+
// the per-iteration preamble emit wouldn't preserve, so reject it.
|
|
2286
|
+
// (#2379 review.)
|
|
2287
|
+
const declFlags = stmt.declarationList.flags
|
|
2288
|
+
const isConstOrLet =
|
|
2289
|
+
(declFlags & ts.NodeFlags.Const) !== 0 || (declFlags & ts.NodeFlags.Let) !== 0
|
|
2290
|
+
if (allowPreamble && isConstOrLet && branches.length === 0 && fallback === null) {
|
|
2291
|
+
preamble.push(stmt)
|
|
2292
|
+
continue
|
|
2293
|
+
}
|
|
2294
|
+
return null
|
|
2295
|
+
}
|
|
2189
2296
|
|
|
2190
2297
|
// Any other statement type → unsupported pattern
|
|
2191
2298
|
return null
|
|
2192
2299
|
}
|
|
2193
2300
|
|
|
2194
2301
|
if (branches.length === 0) return null
|
|
2195
|
-
|
|
2302
|
+
if (preambleUnsafe(preamble, branches, fallback)) return null
|
|
2303
|
+
return { branches, fallback, preamble }
|
|
2196
2304
|
}
|
|
2197
2305
|
|
|
2198
2306
|
/**
|
|
@@ -2208,18 +2316,17 @@ function isDirectReturnBlock(node: ts.Statement): boolean {
|
|
|
2208
2316
|
for (const stmt of node.statements) {
|
|
2209
2317
|
if (ts.isReturnStatement(stmt)) {
|
|
2210
2318
|
returnCount++
|
|
2211
|
-
|
|
2212
|
-
ts.isIfStatement(stmt) ||
|
|
2213
|
-
ts.isSwitchStatement(stmt) ||
|
|
2214
|
-
ts.isForStatement(stmt) ||
|
|
2215
|
-
ts.isForOfStatement(stmt) ||
|
|
2216
|
-
ts.isForInStatement(stmt) ||
|
|
2217
|
-
ts.isWhileStatement(stmt) ||
|
|
2218
|
-
ts.isDoStatement(stmt) ||
|
|
2219
|
-
ts.isTryStatement(stmt)
|
|
2220
|
-
) {
|
|
2221
|
-
return false
|
|
2319
|
+
continue
|
|
2222
2320
|
}
|
|
2321
|
+
// Any non-return statement — a local `const`/`let`, an expression
|
|
2322
|
+
// statement, or nested control flow (if/switch/for/while/try) — means
|
|
2323
|
+
// this is not a *direct* return block. Folding it into an
|
|
2324
|
+
// `IRConditional` branch would silently DROP the statement: the neutral
|
|
2325
|
+
// IR has no per-branch carrier for a local, so a branch-local
|
|
2326
|
+
// `const x = …; return <A>{x}</A>` renders `x` undefined (ReferenceError
|
|
2327
|
+
// at SSR/hydration). Bail so the caller falls back conservatively.
|
|
2328
|
+
// (#2377 review; Stage 2 of spec/callback-fidelity.md.)
|
|
2329
|
+
return false
|
|
2223
2330
|
}
|
|
2224
2331
|
return returnCount === 1
|
|
2225
2332
|
}
|
|
@@ -2253,6 +2360,37 @@ function findJsxReturnInCaseClause(
|
|
|
2253
2360
|
return null
|
|
2254
2361
|
}
|
|
2255
2362
|
|
|
2363
|
+
/**
|
|
2364
|
+
* True when a `switch` case/default clause contains exactly one `return` and
|
|
2365
|
+
* nothing else that would be dropped by the fold (a trailing `break` is
|
|
2366
|
+
* allowed). Rejects clauses with a local `const`/`let`, an expression
|
|
2367
|
+
* statement, or nested control flow — those have no per-branch preamble
|
|
2368
|
+
* carrier in the folded `IRConditional`. (#2377 review; Stage 2 of
|
|
2369
|
+
* spec/callback-fidelity.md.)
|
|
2370
|
+
*/
|
|
2371
|
+
function caseClauseIsDirectReturn(clause: ts.CaseClause | ts.DefaultClause): boolean {
|
|
2372
|
+
let returnCount = 0
|
|
2373
|
+
let seenReturn = false
|
|
2374
|
+
for (const stmt of clause.statements) {
|
|
2375
|
+
if (ts.isReturnStatement(stmt)) {
|
|
2376
|
+
returnCount++
|
|
2377
|
+
seenReturn = true
|
|
2378
|
+
continue
|
|
2379
|
+
}
|
|
2380
|
+
if (ts.isBreakStatement(stmt)) {
|
|
2381
|
+
// A `break` BEFORE the return makes the return unreachable — at runtime
|
|
2382
|
+
// the case exits and the callback falls through to `undefined`, so
|
|
2383
|
+
// folding it as if it returned JSX would render the wrong branch. Only a
|
|
2384
|
+
// trailing break (after the return; dead but harmless) is allowed.
|
|
2385
|
+
// (#2378 review.)
|
|
2386
|
+
if (!seenReturn) return false
|
|
2387
|
+
continue
|
|
2388
|
+
}
|
|
2389
|
+
return false
|
|
2390
|
+
}
|
|
2391
|
+
return returnCount === 1
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2256
2394
|
function findNullReturnInCaseClause(
|
|
2257
2395
|
clause: ts.CaseClause | ts.DefaultClause
|
|
2258
2396
|
): boolean {
|
package/src/compiler.ts
CHANGED
|
@@ -119,7 +119,7 @@ function compileMultipleComponents(
|
|
|
119
119
|
const program = options.program ?? (needsTypeBasedDetection(source) ? createProgramForFile(source, filePath)?.program : undefined)
|
|
120
120
|
|
|
121
121
|
for (const componentName of componentNames) {
|
|
122
|
-
const ctx = analyzeComponent(source, filePath, componentName, program)
|
|
122
|
+
const ctx = analyzeComponent(source, filePath, componentName, program, adapter.acceptsCallbackBody)
|
|
123
123
|
|
|
124
124
|
if (!ctx.jsxReturn) {
|
|
125
125
|
errors.push(...ctx.errors)
|
|
@@ -556,7 +556,7 @@ export function compileJSX(
|
|
|
556
556
|
}
|
|
557
557
|
|
|
558
558
|
// Single component flow
|
|
559
|
-
const ctx = analyzeComponent(compileSource, filePath, undefined, options.program)
|
|
559
|
+
const ctx = analyzeComponent(compileSource, filePath, undefined, options.program, options.adapter.acceptsCallbackBody)
|
|
560
560
|
|
|
561
561
|
if (!ctx.jsxReturn) {
|
|
562
562
|
errors.push(...ctx.errors)
|
package/src/expression-parser.ts
CHANGED
|
@@ -364,25 +364,22 @@ export interface SupportResult {
|
|
|
364
364
|
// evaluate JS at runtime via hono/jsx) so this set only constrains
|
|
365
365
|
// the template-language adapters.
|
|
366
366
|
const UNSUPPORTED_METHODS = new Set([
|
|
367
|
-
// Higher-order array methods.
|
|
368
|
-
// `
|
|
369
|
-
//
|
|
370
|
-
// `
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
// `
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
// message. The rest stay refused — see #1448 Tier C for the design
|
|
384
|
-
// questions. `forEach` carries a tailored reason (see
|
|
385
|
-
// `UNSUPPORTED_METHOD_REASONS`).
|
|
367
|
+
// Higher-order array methods. `filter`, `every`, `some`, `find`,
|
|
368
|
+
// `findIndex`, `findLast`, `findLastIndex`, `reduce`, `reduceRight`
|
|
369
|
+
// and `flatMap` *with an arrow-callback argument* are recognized by
|
|
370
|
+
// `asCallbackMethodCall` and lower via the runtime evaluator
|
|
371
|
+
// (`serializeParsedExpr`) in the `call` arm ABOVE this gate — an
|
|
372
|
+
// off-subset callback body refuses there with a "complex callback"
|
|
373
|
+
// reason, not here. They stay listed in this set so the shapes that
|
|
374
|
+
// recognition can't catch — a BARE method reference (`arr.filter`
|
|
375
|
+
// uncalled) or a function-reference callback (no arrow) — still refuse
|
|
376
|
+
// loudly. (`reduce` / `reduceRight` are NOT folded into a structured
|
|
377
|
+
// `array-method` / `ReduceOp` form: #2018 P5 leaves them as a generic
|
|
378
|
+
// `call` whose reducer body + initial value serialize to the
|
|
379
|
+
// evaluator.) `map` is intercepted as an IRLoop when its callback
|
|
380
|
+
// returns JSX, and as a `CALLBACK_METHODS` evaluator lowering
|
|
381
|
+
// (`map_eval`, #2073) when it returns a value. `forEach` carries a
|
|
382
|
+
// tailored reason (see `UNSUPPORTED_METHOD_REASONS`).
|
|
386
383
|
// `flat` is no longer here — `.flat(depth?)` lowers via the
|
|
387
384
|
// `array-method` IR (structured `FlatDepth`) + `bf_flat` (Go) /
|
|
388
385
|
// `bf->flat` (Mojo). `flatMap` stays listed as a fallback: the
|
|
@@ -395,6 +392,17 @@ const UNSUPPORTED_METHODS = new Set([
|
|
|
395
392
|
// `IRLoop` upstream. See #1448.
|
|
396
393
|
'filter', 'map', 'reduce', 'reduceRight', 'every', 'some',
|
|
397
394
|
'forEach', 'flatMap',
|
|
395
|
+
// `fill` has no template lowering on any DSL adapter (it mutates the
|
|
396
|
+
// receiver in place and returns it — no ParsedExpr / evaluator form).
|
|
397
|
+
// Before it was listed here `isSupported` reported it "supported" and
|
|
398
|
+
// the adapters emitted a raw `.fill(...)` method call with no build
|
|
399
|
+
// diagnostic — the same silent footgun the string methods below
|
|
400
|
+
// documented before they were gated: it only surfaced as a crash at
|
|
401
|
+
// template-render time. Listing it makes the build fail loudly with
|
|
402
|
+
// BF101, pointing users at the `/* @client */` escape (a JS-runtime
|
|
403
|
+
// adapter still runs it verbatim, since those skip `isSupported`).
|
|
404
|
+
// See spec/callback-fidelity.md.
|
|
405
|
+
'fill',
|
|
398
406
|
// #1448 Tier A — Array methods. Each method PR adds the lowering
|
|
399
407
|
// (typically a new `array-method` variant or runtime helper) and
|
|
400
408
|
// removes its row here. See packages/adapter-tests/fixtures/methods/.
|
|
@@ -35,6 +35,7 @@ import type {
|
|
|
35
35
|
ReferenceSource,
|
|
36
36
|
ReferencesGraph,
|
|
37
37
|
} from '../types.ts'
|
|
38
|
+
import { preambleAnalysisText } from '../types.ts'
|
|
38
39
|
import type { IRVisitor as WalkerVisitor } from './walker.ts'
|
|
39
40
|
import { attrValueToString } from './utils.ts'
|
|
40
41
|
import type { ClientJsContext } from './types.ts'
|
|
@@ -151,7 +152,14 @@ export function buildReferencesGraph(ctx: ClientJsContext, irRoot: IRNode): Refe
|
|
|
151
152
|
}
|
|
152
153
|
if (elem.filterPredicate) addExprEdges(ROOT_SOURCE, elem.filterPredicate.raw, 'template-closure')
|
|
153
154
|
if (elem.sortComparator) addExprEdges(ROOT_SOURCE, elem.sortComparator.raw, 'template-closure')
|
|
154
|
-
if (elem.
|
|
155
|
+
if (elem.preamble) addExprEdges(ROOT_SOURCE, preambleAnalysisText(elem.preamble), 'template-closure')
|
|
156
|
+
// flatMap descriptor bodies (`elem.flatMapClient`) are NOT traced here:
|
|
157
|
+
// the rendered body embeds leaf HTML template literals, and the regex
|
|
158
|
+
// identifier extractor would turn tag/attr names into false-positive
|
|
159
|
+
// edges. The Phase 3 loop visitor traces the same content structurally
|
|
160
|
+
// (js segments via `preambleAnalysisText`, leaf attrs/keys/interpolations
|
|
161
|
+
// via `walkIR` on `flatMapCallback` segments) — that's what keeps
|
|
162
|
+
// `const maxTags = _p.maxTags` in the extracted props.
|
|
155
163
|
for (const attr of elem.bindings.reactiveAttrs) {
|
|
156
164
|
addExprEdges(ROOT_SOURCE, attr.expression, 'template-closure')
|
|
157
165
|
}
|
|
@@ -316,7 +324,16 @@ export function buildReferencesGraph(ctx: ClientJsContext, irRoot: IRNode): Refe
|
|
|
316
324
|
addExprEdges(ROOT_SOURCE, l.array, 'template-closure')
|
|
317
325
|
if (l.filterPredicate) addExprEdges(ROOT_SOURCE, l.filterPredicate.raw, 'template-closure')
|
|
318
326
|
if (l.sortComparator) addExprEdges(ROOT_SOURCE, l.sortComparator.raw, 'template-closure')
|
|
319
|
-
if (l.
|
|
327
|
+
if (l.preamble) addExprEdges(ROOT_SOURCE, preambleAnalysisText(l.preamble), 'template-closure')
|
|
328
|
+
// flatMap bodies: js segments reference init-scope names; segment
|
|
329
|
+
// leaves are IR nodes off `children`, so `descend()` never reaches
|
|
330
|
+
// them — walk them explicitly (mirrors attachParsedExpressions).
|
|
331
|
+
if (l.flatMapCallback) {
|
|
332
|
+
addExprEdges(ROOT_SOURCE, preambleAnalysisText(l.flatMapCallback), 'template-closure')
|
|
333
|
+
for (const seg of l.flatMapCallback.segments) {
|
|
334
|
+
if (seg.kind === 'jsx') walkIR(seg.ir, null, visitor)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
320
337
|
descend()
|
|
321
338
|
if (l.childComponent) walkChildComponent(l.childComponent)
|
|
322
339
|
if (l.nestedComponents) {
|
|
@@ -6,7 +6,7 @@ import { type IRNode, type IRElement, type IRComponent, type IRLoop, type IRProp
|
|
|
6
6
|
import type { ClientJsContext, ConditionalBranchChildComponent, ConditionalBranchReactiveAttr, BranchLoop, ConditionalBranchTextEffect, ConditionalElement, LoopChildBindings, LoopChildBranchSummary, LoopChildConditional, LoopOffset, NestedLoop } from './types.ts'
|
|
7
7
|
import { attrValueToString, freeIdsFromRefs, quotePropName, PROPS_PARAM } from './utils.ts'
|
|
8
8
|
import { classifyReactivity, decideWrapForAttr, decideWrapForChildProp, decideWrapFromAstFlags, collectEventHandlersFromIR, collectConditionalBranchEvents, collectConditionalBranchRefs, collectConditionalBranchChildComponents, collectLoopChildEventsWithNesting, collectLoopChildReactiveAttrs, collectLoopChildReactiveTexts, collectLoopChildRefs, emptyLoopChildBindings } from './reactivity.ts'
|
|
9
|
-
import { irToHtmlTemplate, irToPlaceholderTemplate, irChildrenToJsExpr, buildLoopSkeletonTemplate, computeSkeletonSlotPaths, type SkeletonSlotPaths } from './html-template.ts'
|
|
9
|
+
import { irToHtmlTemplate, irToPlaceholderTemplate, irChildrenToJsExpr, buildLoopSkeletonTemplate, computeSkeletonSlotPaths, renderFlatMapClientBody, renderFlatMapProjectionClientBody, flatMapCallbackHasKeyedLeaf, type SkeletonSlotPaths } from './html-template.ts'
|
|
10
10
|
import { templateRootIsSvg } from './control-flow/stringify/template-parse.ts'
|
|
11
11
|
import { expandDynamicPropValue, expandConstantForReactivity } from './prop-handling.ts'
|
|
12
12
|
import { walkIR, stopAt } from './walker.ts'
|
|
@@ -393,7 +393,7 @@ export function collectInnerLoops(
|
|
|
393
393
|
objectIteration: n.objectIteration,
|
|
394
394
|
containerSlotId: scope.parentSlotId,
|
|
395
395
|
template,
|
|
396
|
-
|
|
396
|
+
preamble: n.preamble,
|
|
397
397
|
refsOuterParam: refsOuter,
|
|
398
398
|
childComponents,
|
|
399
399
|
insideConditional: !flat && scope.insideCond ? true : undefined,
|
|
@@ -650,10 +650,26 @@ export function collectElements(
|
|
|
650
650
|
// are extracted explicitly below for the closure capture set.
|
|
651
651
|
if (!l.slotId || inCond) return
|
|
652
652
|
|
|
653
|
+
// flatMap PROJECTION loop: the only child is a nested IRLoop lowered
|
|
654
|
+
// from `flatMap(it => it.tags.map(...))`. The children exist for SSR
|
|
655
|
+
// templatization (nested {{range}} on DSL adapters); the CLIENT
|
|
656
|
+
// reconciles the flattened leaves through the descriptor mapArray
|
|
657
|
+
// path synthesized from the same inner loop, so none of the
|
|
658
|
+
// children-derived client machinery (bindings, inner-loop plans,
|
|
659
|
+
// per-row template) applies — leaf wiring was refused at IR build.
|
|
660
|
+
const projectionInner =
|
|
661
|
+
l.method === 'flatMap' && l.children.length === 1 && l.children[0].type === 'loop'
|
|
662
|
+
? l.children[0]
|
|
663
|
+
: undefined
|
|
664
|
+
|
|
653
665
|
const childHandlers: string[] = []
|
|
654
|
-
const bindings =
|
|
655
|
-
|
|
656
|
-
|
|
666
|
+
const bindings = projectionInner
|
|
667
|
+
? emptyLoopChildBindings()
|
|
668
|
+
: collectLoopChildBindings(l.children, ctx, siblingOffsets, l.param, l.paramBindings)
|
|
669
|
+
if (!projectionInner) {
|
|
670
|
+
for (const child of l.children) {
|
|
671
|
+
childHandlers.push(...collectEventHandlersFromIR(child))
|
|
672
|
+
}
|
|
657
673
|
}
|
|
658
674
|
|
|
659
675
|
if (l.childComponent) {
|
|
@@ -668,7 +684,9 @@ export function collectElements(
|
|
|
668
684
|
// Determine rendering strategy for dynamic arrays:
|
|
669
685
|
// Use element reconciliation when the loop body has nested components,
|
|
670
686
|
// or when inner loops need their own mapArray for events/reactive text.
|
|
671
|
-
const { useElementReconciliation, innerLoops } =
|
|
687
|
+
const { useElementReconciliation, innerLoops } = projectionInner
|
|
688
|
+
? { useElementReconciliation: false, innerLoops: undefined }
|
|
689
|
+
: decideLoopRendering(l, siblingOffsets, ctx)
|
|
672
690
|
|
|
673
691
|
let template = ''
|
|
674
692
|
let staticItemTemplate: string | undefined
|
|
@@ -691,7 +709,7 @@ export function collectElements(
|
|
|
691
709
|
// `data-key`, mirroring the SSR template's renderChild emit.
|
|
692
710
|
staticItemTemplate = irToHtmlTemplate(l.children[0], buildRestSpreadNames(ctx), 0, undefined, undefined, /* insideLoop */ true)
|
|
693
711
|
}
|
|
694
|
-
} else if (l.children[0]) {
|
|
712
|
+
} else if (l.children[0] && !projectionInner) {
|
|
695
713
|
// Pass loopParams so expressions are wrapped at generation time,
|
|
696
714
|
// avoiding post-hoc regex wrapping that corrupts literal attribute values.
|
|
697
715
|
// Forward destructured bindings (#951) so references like `cfg.color`
|
|
@@ -776,7 +794,21 @@ export function collectElements(
|
|
|
776
794
|
raw: l.sortComparator.raw,
|
|
777
795
|
} : undefined,
|
|
778
796
|
chainOrder: l.chainOrder,
|
|
779
|
-
|
|
797
|
+
preamble: l.preamble,
|
|
798
|
+
preambleRegions: l.preambleRegions,
|
|
799
|
+
flatMapClient: projectionInner
|
|
800
|
+
? {
|
|
801
|
+
params: l.index ? `(${l.param}, ${l.index})` : `(${l.param})`,
|
|
802
|
+
body: renderFlatMapProjectionClientBody(projectionInner, buildRestSpreadNames(ctx)),
|
|
803
|
+
keyed: projectionInner.key !== null,
|
|
804
|
+
}
|
|
805
|
+
: l.flatMapCallback
|
|
806
|
+
? {
|
|
807
|
+
params: l.flatMapCallback.params,
|
|
808
|
+
body: renderFlatMapClientBody(l.flatMapCallback, buildRestSpreadNames(ctx)),
|
|
809
|
+
keyed: flatMapCallbackHasKeyedLeaf(l.flatMapCallback),
|
|
810
|
+
}
|
|
811
|
+
: undefined,
|
|
780
812
|
})
|
|
781
813
|
// Don't descend — loop-scoped variables are only available inside the iteration.
|
|
782
814
|
},
|
|
@@ -1069,8 +1101,16 @@ function collectBranchLoops(
|
|
|
1069
1101
|
// reactive-text collection on inner loops reached from this call path
|
|
1070
1102
|
// is handled at the enclosing branch-loop level below (`if (ctx)` block
|
|
1071
1103
|
// around `collectLoopChildReactiveTexts`), not per inner loop.
|
|
1072
|
-
|
|
1073
|
-
|
|
1104
|
+
// flatMap PROJECTION loop — see the top-level handler's note: children
|
|
1105
|
+
// are SSR-only; the client rides the descriptor mapArray path.
|
|
1106
|
+
const projectionInner =
|
|
1107
|
+
n.method === 'flatMap' && n.children.length === 1 && n.children[0].type === 'loop'
|
|
1108
|
+
? n.children[0]
|
|
1109
|
+
: undefined
|
|
1110
|
+
|
|
1111
|
+
const { useElementReconciliation, innerLoops: innerLoopsCollected } = projectionInner
|
|
1112
|
+
? { useElementReconciliation: false, innerLoops: undefined }
|
|
1113
|
+
: decideLoopRendering(n, siblingOffsets, undefined)
|
|
1074
1114
|
|
|
1075
1115
|
// Build the item template from loop children.
|
|
1076
1116
|
// Use loopDepth=0: this loop gets its own reconcileElements (independent
|
|
@@ -1080,7 +1120,9 @@ function collectBranchLoops(
|
|
|
1080
1120
|
// use `param()` to read the current item value.
|
|
1081
1121
|
let childTemplate: string
|
|
1082
1122
|
const branchLoopParamSpec = [{ param: n.param, bindings: n.paramBindings }]
|
|
1083
|
-
if (
|
|
1123
|
+
if (projectionInner) {
|
|
1124
|
+
childTemplate = '' // descriptor renderItem builds from d.h, not a row template
|
|
1125
|
+
} else if (useElementReconciliation && n.children[0]) {
|
|
1084
1126
|
childTemplate = irToPlaceholderTemplate(n.children[0], restNames, 0, branchLoopParamSpec)
|
|
1085
1127
|
} else {
|
|
1086
1128
|
childTemplate = n.children.map(c => irToHtmlTemplate(c, undefined, 0, branchLoopParamSpec)).join('')
|
|
@@ -1092,7 +1134,7 @@ function collectBranchLoops(
|
|
|
1092
1134
|
// memos). Previously these were only collected for composite loops,
|
|
1093
1135
|
// which caused reactive reads inside simple loop bodies to silently
|
|
1094
1136
|
// no-op for existing items.
|
|
1095
|
-
const branchBindings = ctx
|
|
1137
|
+
const branchBindings = ctx && !projectionInner
|
|
1096
1138
|
? collectLoopChildBindings(n.children, ctx, siblingOffsets, n.param, n.paramBindings)
|
|
1097
1139
|
: emptyLoopChildBindings()
|
|
1098
1140
|
|
|
@@ -1111,7 +1153,8 @@ function collectBranchLoops(
|
|
|
1111
1153
|
objectIteration: n.objectIteration,
|
|
1112
1154
|
template: childTemplate,
|
|
1113
1155
|
containerSlotId: containerSlot,
|
|
1114
|
-
|
|
1156
|
+
preamble: n.preamble,
|
|
1157
|
+
preambleRegions: n.preambleRegions,
|
|
1115
1158
|
nestedComponents: useElementReconciliation ? n.nestedComponents : undefined,
|
|
1116
1159
|
bindings: branchBindings,
|
|
1117
1160
|
innerLoops: useElementReconciliation ? innerLoopsCollected : undefined,
|
|
@@ -1126,6 +1169,19 @@ function collectBranchLoops(
|
|
|
1126
1169
|
raw: n.sortComparator.raw,
|
|
1127
1170
|
} : undefined,
|
|
1128
1171
|
chainOrder: n.chainOrder,
|
|
1172
|
+
flatMapClient: projectionInner
|
|
1173
|
+
? {
|
|
1174
|
+
params: n.index ? `(${n.param}, ${n.index})` : `(${n.param})`,
|
|
1175
|
+
body: renderFlatMapProjectionClientBody(projectionInner, restNames),
|
|
1176
|
+
keyed: projectionInner.key !== null,
|
|
1177
|
+
}
|
|
1178
|
+
: n.flatMapCallback
|
|
1179
|
+
? {
|
|
1180
|
+
params: n.flatMapCallback.params,
|
|
1181
|
+
body: renderFlatMapClientBody(n.flatMapCallback, restNames),
|
|
1182
|
+
keyed: flatMapCallbackHasKeyedLeaf(n.flatMapCallback),
|
|
1183
|
+
}
|
|
1184
|
+
: undefined,
|
|
1129
1185
|
})
|
|
1130
1186
|
// Don't recurse into the loop — nested loops are handled by the loop's own reconciliation.
|
|
1131
1187
|
},
|
|
@@ -15,12 +15,20 @@
|
|
|
15
15
|
* pair.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type { CompositeLoopPlan, LoopChildRefBinding } from './loop.ts'
|
|
18
|
+
import type { CompositeLoopPlan, LoopChildRefBinding, PreambleRegionPlan } from './loop.ts'
|
|
19
19
|
import type { EventDelegationPlan } from './event-delegation.ts'
|
|
20
20
|
import type { ReactiveEffectsPlan } from './reactive-effects.ts'
|
|
21
21
|
|
|
22
22
|
export interface BranchPlainLoopPlan {
|
|
23
|
+
/**
|
|
24
|
+
* flatMap descriptor mode — see `PlainLoopVariant.flatMapLeafItem`
|
|
25
|
+
* (plan/loop.ts): `arrayExpr` already flattens through the descriptor
|
|
26
|
+
* body and the stringifier emits the build-or-patch renderItem.
|
|
27
|
+
*/
|
|
28
|
+
flatMapLeafItem?: boolean
|
|
23
29
|
kind: 'plain'
|
|
30
|
+
/** See LoopPlanCommon.rowConstruction — required on every variant. */
|
|
31
|
+
rowConstruction: 'string-template' | 'dom-ops'
|
|
24
32
|
/**
|
|
25
33
|
* Variable suffix for the container lookup — e.g. for slotId `s4` the
|
|
26
34
|
* stringifier emits `__loop_s4` and queries via `$(__branchScope, 's4')`.
|
|
@@ -76,6 +84,11 @@ export interface BranchPlainLoopPlan {
|
|
|
76
84
|
* Undefined off → byte-identical (SR8).
|
|
77
85
|
*/
|
|
78
86
|
profileLoopId?: string
|
|
87
|
+
/**
|
|
88
|
+
* Preamble-patched regions (#2389) — non-empty forces the multi-line
|
|
89
|
+
* renderItem layout, mirroring the top-level plain plan.
|
|
90
|
+
*/
|
|
91
|
+
preambleRegions: readonly PreambleRegionPlan[]
|
|
79
92
|
}
|
|
80
93
|
|
|
81
94
|
export interface BranchCompositeLoopPlan {
|
|
@@ -15,7 +15,8 @@ import { buildChainedArrayExpr, varSlotId, wrapLoopParamAsAccessor } from '../..
|
|
|
15
15
|
import { buildBranchCompositePlan } from './build-composite-loop.ts'
|
|
16
16
|
import { buildBranchLoopDelegationPlan } from './build-event-delegation.ts'
|
|
17
17
|
import { buildReactiveEffectsPlan } from './build-reactive-effects.ts'
|
|
18
|
-
import { destructureLoopParam, loopKeyFn, buildChildRefBindings } from '../shared.ts'
|
|
18
|
+
import { destructureLoopParam, loopKeyFn, buildChildRefBindings, buildPreambleRegionPlans } from '../shared.ts'
|
|
19
|
+
import { renderPreamble, irToHtmlTemplate } from '../../html-template.ts'
|
|
19
20
|
import type {
|
|
20
21
|
BranchCompositeLoopPlan,
|
|
21
22
|
BranchLoopPlan,
|
|
@@ -42,20 +43,31 @@ export function buildBranchLoopPlan(loop: BranchLoop, profileComponentName?: str
|
|
|
42
43
|
|| loop.bindings.reactiveTexts.length > 0
|
|
43
44
|
|| loop.bindings.conditionals.length > 0
|
|
44
45
|
|
|
46
|
+
// flatMap descriptor mode — see buildPlainLoopPlan (build-loop.ts).
|
|
47
|
+
const fm = loop.flatMapClient
|
|
45
48
|
const plan: BranchPlainLoopPlan = {
|
|
46
49
|
kind: 'plain',
|
|
50
|
+
rowConstruction: 'string-template',
|
|
47
51
|
containerSlotId,
|
|
48
52
|
containerVar,
|
|
49
53
|
markerId: loop.markerId,
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
flatMapLeafItem: fm ? true : undefined,
|
|
55
|
+
arrayExpr: fm
|
|
56
|
+
? `(${buildChainedArrayExpr(loop)}).flatMap(${fm.params} => ${fm.body})`
|
|
57
|
+
: buildChainedArrayExpr(loop),
|
|
58
|
+
keyFn: fm
|
|
59
|
+
? (fm.keyed ? '(__bfD, __bfI) => String(__bfD.k ?? __bfI)' : 'null')
|
|
60
|
+
: loopKeyFn(loop),
|
|
52
61
|
paramHead,
|
|
53
62
|
paramUnwrap,
|
|
54
63
|
indexParam: loop.index || '__idx',
|
|
55
64
|
// Wrap loop-param references to signal-accessor form so the preamble
|
|
56
65
|
// matches the template literal's already-wrapped reads (#1065).
|
|
57
|
-
mapPreambleWrapped: loop.
|
|
58
|
-
?
|
|
66
|
+
mapPreambleWrapped: loop.preamble
|
|
67
|
+
? renderPreamble(loop.preamble, {
|
|
68
|
+
transformJs: (t) => wrapLoopParamAsAccessor(t, loop.param, loop.paramBindings),
|
|
69
|
+
renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: loop.param, bindings: loop.paramBindings }], undefined, true),
|
|
70
|
+
})
|
|
59
71
|
: '',
|
|
60
72
|
template: loop.template,
|
|
61
73
|
reactiveEffects: hasReactiveEffects
|
|
@@ -70,6 +82,7 @@ export function buildBranchLoopPlan(loop: BranchLoop, profileComponentName?: str
|
|
|
70
82
|
: null,
|
|
71
83
|
eventDelegation: buildBranchLoopDelegationPlan(loop, cv, profileComponentName),
|
|
72
84
|
childRefs: buildChildRefBindings(loop.bindings.refs, loop.param, loop.paramBindings),
|
|
85
|
+
preambleRegions: buildPreambleRegionPlans(loop.preambleRegions, loop.param, loop.paramBindings),
|
|
73
86
|
bodyIsMultiRoot: loop.bodyIsMultiRoot ?? false,
|
|
74
87
|
profileLoopId: profileComponentName ? `${profileComponentName}#binding:${containerSlotId}` : undefined,
|
|
75
88
|
}
|