@kudzujs/core 0.8.15 → 0.8.16
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/MIGRATION_ROADMAP.md +247 -0
- package/PERFORMANCE.md +212 -0
- package/README.md +1 -1
- package/RELEASES.md +32 -0
- package/docs/next-architecture/README.md +42 -0
- package/docs/next-architecture/compiler-current-architecture.md +71 -0
- package/docs/next-architecture/goal-a-compiler-foundation.md +152 -0
- package/docs/next-architecture/goal-b-optimization-benchmarks.md +59 -0
- package/docs/next-architecture/goal-c-state-resource-research.md +50 -0
- package/docs/next-architecture/goal-d-routing-compatibility-decisions.md +51 -0
- package/docs/next-architecture/performance-gates.md +50 -0
- package/docs/next-architecture/versioning.md +42 -0
- package/framework/README.md +6 -1
- package/framework/build.mjs +103 -617
- package/framework/compiler/collection-analysis.mjs +187 -0
- package/framework/compiler/descriptor-session.mjs +222 -0
- package/framework/compiler/event-command-pass.mjs +35 -0
- package/framework/compiler/react-migration-pass.mjs +3 -2
- package/framework/compiler/route-capability-planner.mjs +118 -0
- package/framework/compiler/zustand-pass.mjs +95 -0
- package/package.json +4 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import ts from "typescript"
|
|
2
|
+
import { nearestFunction, unwrapExpression } from "./ast-helpers.mjs"
|
|
3
|
+
|
|
4
|
+
export const pureCollectionMethods = new Set(["at", "charAt", "charCodeAt", "concat", "endsWith", "includes", "indexOf", "join", "lastIndexOf", "localeCompare", "padEnd", "padStart", "repeat", "replace", "replaceAll", "slice", "startsWith", "substring", "toLowerCase", "toUpperCase", "trim", "trimEnd", "trimStart"])
|
|
5
|
+
export const mutatingCollectionMethods = new Set(["copyWithin", "fill", "pop", "push", "reverse", "shift", "sort", "splice", "unshift"])
|
|
6
|
+
export const pureCollectionMathMethods = new Set(["abs", "ceil", "floor", "max", "min", "pow", "round", "sign", "sqrt", "trunc"])
|
|
7
|
+
|
|
8
|
+
export function isArrayFromCall(value) {
|
|
9
|
+
return ts.isCallExpression(value) && ts.isPropertyAccessExpression(value.expression) && ts.isIdentifier(value.expression.expression) && value.expression.expression.text === "Array" && value.expression.name.text === "from"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function collectionParameters(callback, label, fail) {
|
|
13
|
+
if (!ts.isArrowFunction(callback) || callback.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword) || callback.parameters.length < 1 || callback.parameters.length > 2 || callback.parameters.some(parameter => !ts.isIdentifier(parameter.name) || parameter.dotDotDotToken || parameter.initializer || parameter.questionToken)) fail(callback, `${label} callback must be a synchronous arrow function with (item) or (item, index) identifier parameters`)
|
|
14
|
+
return { item: callback.parameters[0].name.text, index: callback.parameters[1]?.name.text }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function analyzeCollectionPipeline(expression, options) {
|
|
18
|
+
const {
|
|
19
|
+
setters = new Map(),
|
|
20
|
+
declarations,
|
|
21
|
+
fail,
|
|
22
|
+
aliases = new Set(),
|
|
23
|
+
importedCollections = new Set(),
|
|
24
|
+
stateNames = new Set(),
|
|
25
|
+
importedCollectionTransforms = new Map(),
|
|
26
|
+
calculatedCollection,
|
|
27
|
+
staticCollection
|
|
28
|
+
} = options
|
|
29
|
+
const nestedOptions = { setters, declarations, fail, aliases, importedCollections, stateNames, importedCollectionTransforms, calculatedCollection, staticCollection }
|
|
30
|
+
const value = unwrapExpression(expression)
|
|
31
|
+
if (ts.isIdentifier(value)) {
|
|
32
|
+
if ([...setters.values()].includes(value.text)) {
|
|
33
|
+
const localStatic = staticCollection?.(value.text)
|
|
34
|
+
return { state: value, static: localStatic, localStatic, selector: [], selectorStates: new Set() }
|
|
35
|
+
}
|
|
36
|
+
if (importedCollections.has(value.text)) return { state: value, static: true, selector: [], selectorStates: new Set() }
|
|
37
|
+
const entries = declarations?.get(value.text)
|
|
38
|
+
if (!entries) return undefined
|
|
39
|
+
if (entries.length !== 1 || aliases.has(value.text) || entries[0].node.parent?.parent?.parent !== nearestFunction(entries[0].node)?.body) fail(value, `Rendered collection alias "${value.text}" must be one top-level immutable local`)
|
|
40
|
+
aliases.add(value.text)
|
|
41
|
+
const source = analyzeCollectionPipeline(entries[0].initializer, nestedOptions)
|
|
42
|
+
aliases.delete(value.text)
|
|
43
|
+
return source && { ...source, aliasDeclarations: [...(source.aliasDeclarations ?? []), entries[0].node], aliasUses: [...(source.aliasUses ?? []), value] }
|
|
44
|
+
}
|
|
45
|
+
if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression)) {
|
|
46
|
+
const calculation = calculatedCollection?.(value)
|
|
47
|
+
if (calculation) return { calculation, selector: [], selectorStates: new Set() }
|
|
48
|
+
return { state: undefined, ownerField: value.name.text, selector: [], parentItem: value.expression.text }
|
|
49
|
+
}
|
|
50
|
+
if (ts.isCallExpression(value) && ts.isIdentifier(value.expression) && importedCollectionTransforms.has(value.expression.text)) {
|
|
51
|
+
const transform = importedCollectionTransforms.get(value.expression.text)
|
|
52
|
+
const parameter = transform.parameters[0]
|
|
53
|
+
if (value.arguments.length !== 1 || transform.parameters.length !== 1 || transform.asteriskToken || transform.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword) || !parameter || !ts.isIdentifier(parameter.name) || parameter.dotDotDotToken || parameter.initializer || parameter.questionToken) fail(value, `Imported collection transform "${value.expression.text}" must be synchronous with exactly one identifier parameter and one argument`)
|
|
54
|
+
const returned = ts.isBlock(transform.body)
|
|
55
|
+
? transform.body.statements.length === 1 && ts.isReturnStatement(transform.body.statements[0]) ? transform.body.statements[0].expression : undefined
|
|
56
|
+
: transform.body
|
|
57
|
+
if (!returned) fail(value, `Imported collection transform "${value.expression.text}" must contain only one returned collection expression`)
|
|
58
|
+
const transformSource = analyzeCollectionPipeline(returned, { setters: new Map([[parameter.name.text, parameter.name.text]]), fail, stateNames: new Set([parameter.name.text]) })
|
|
59
|
+
if (!transformSource?.state || transformSource.state.text !== parameter.name.text || transformSource.selectorStates.size) fail(value, `Imported collection transform "${value.expression.text}" must return a supported pure pipeline rooted only in its parameter`)
|
|
60
|
+
const source = analyzeCollectionPipeline(value.arguments[0], nestedOptions)
|
|
61
|
+
if (!source) fail(value.arguments[0], `Imported collection transform "${value.expression.text}" requires a supported collection argument`)
|
|
62
|
+
return { ...source, selector: [...source.selector, ...transformSource.selector] }
|
|
63
|
+
}
|
|
64
|
+
if (ts.isCallExpression(value) && ts.isPropertyAccessExpression(value.expression)) {
|
|
65
|
+
const method = value.expression.name.text
|
|
66
|
+
if (method === "filter") {
|
|
67
|
+
if (value.arguments.length !== 1) fail(value, "Rendered collection filter() requires one inline predicate")
|
|
68
|
+
const source = analyzeCollectionPipeline(value.expression.expression, nestedOptions)
|
|
69
|
+
if (!source) return undefined
|
|
70
|
+
const parameters = collectionParameters(value.arguments[0], "Rendered collection filter()", fail)
|
|
71
|
+
const selectorStates = new Set(source.selectorStates)
|
|
72
|
+
return { ...source, selector: [...source.selector, ["filter", collectionExpression(unwrapExpression(value.arguments[0].body), { parameters, fail, stateNames, selectorStates })]], selectorStates }
|
|
73
|
+
}
|
|
74
|
+
if (method === "flatMap") {
|
|
75
|
+
if (value.arguments.length !== 1) fail(value, "Rendered collection flatMap() requires one inline projector")
|
|
76
|
+
const source = analyzeCollectionPipeline(value.expression.expression, nestedOptions)
|
|
77
|
+
if (!source) return undefined
|
|
78
|
+
const parameters = collectionParameters(value.arguments[0], "Rendered collection flatMap()", fail)
|
|
79
|
+
const field = directProperty(value.arguments[0].body, parameters.item)
|
|
80
|
+
if (!field) fail(value.arguments[0].body, `Rendered collection flatMap() projector must be ${parameters.item}.<field>`)
|
|
81
|
+
if (["__proto__", "constructor", "prototype"].includes(field)) fail(value.arguments[0].body, `Rendered collection property "${field}" is not supported`)
|
|
82
|
+
return { ...source, selector: [...source.selector, ["flatMap", field]] }
|
|
83
|
+
}
|
|
84
|
+
if (method === "slice") {
|
|
85
|
+
if (value.arguments.length < 1 || value.arguments.length > 2) fail(value, "Rendered collection slice() requires a start and optional end")
|
|
86
|
+
const source = analyzeCollectionPipeline(value.expression.expression, nestedOptions)
|
|
87
|
+
if (!source) return undefined
|
|
88
|
+
const selectorStates = new Set(source.selectorStates)
|
|
89
|
+
const start = collectionExpression(value.arguments[0], { fail, stateNames, selectorStates })
|
|
90
|
+
const end = value.arguments[1] && collectionExpression(value.arguments[1], { fail, stateNames, selectorStates })
|
|
91
|
+
return { ...source, selector: [...source.selector, ["slice", start, end]], selectorStates }
|
|
92
|
+
}
|
|
93
|
+
if (method === "toSorted") {
|
|
94
|
+
if (value.arguments.length !== 1) fail(value, "Rendered collection toSorted() requires one inline comparator")
|
|
95
|
+
const source = analyzeCollectionPipeline(value.expression.expression, nestedOptions)
|
|
96
|
+
if (!source) return undefined
|
|
97
|
+
const comparator = value.arguments[0]
|
|
98
|
+
const parameters = collectionParameters(comparator, "Rendered collection toSorted()", fail)
|
|
99
|
+
if (comparator.parameters.length !== 2 || ts.isBlock(comparator.body)) fail(comparator, "Rendered collection toSorted() comparator must be a synchronous expression arrow with (left, right) identifier parameters")
|
|
100
|
+
const selectorStates = new Set(source.selectorStates)
|
|
101
|
+
const encoded = collectionExpression(comparator.body, { parameters, fail, stateNames, selectorStates })
|
|
102
|
+
return { ...source, selector: [...source.selector, ["sort", encoded]], selectorStates }
|
|
103
|
+
}
|
|
104
|
+
if (method === "sort") fail(value, "Rendered collections cannot use mutating sort(); use toSorted()")
|
|
105
|
+
}
|
|
106
|
+
if (isArrayFromCall(value)) {
|
|
107
|
+
if (value.arguments.length < 1 || value.arguments.length > 2) fail(value, "Rendered Array.from() requires an anchor and optional inline mapper")
|
|
108
|
+
const source = analyzeCollectionPipeline(value.arguments[0], nestedOptions)
|
|
109
|
+
if (!source) return undefined
|
|
110
|
+
let mapper
|
|
111
|
+
if (value.arguments[1]) {
|
|
112
|
+
const parameters = collectionParameters(value.arguments[1], "Rendered Array.from() mapper", fail)
|
|
113
|
+
const selectorStates = new Set(source.selectorStates)
|
|
114
|
+
mapper = collectionExpression(unwrapExpression(value.arguments[1].body), { parameters, fail, stateNames, selectorStates })
|
|
115
|
+
source.selectorStates = selectorStates
|
|
116
|
+
}
|
|
117
|
+
return { ...source, selector: [...source.selector, ["from", mapper]] }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function collectionExpression(expression, { parameters = {}, fail, stateNames = new Set(), selectorStates = new Set() }) {
|
|
122
|
+
const encode = node => {
|
|
123
|
+
node = unwrapExpression(node)
|
|
124
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isNumericLiteral(node)) return ["value", ts.isNumericLiteral(node) ? Number(node.text) : node.text]
|
|
125
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) return ["value", true]
|
|
126
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) return ["value", false]
|
|
127
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) return ["value", null]
|
|
128
|
+
if (ts.isIdentifier(node)) {
|
|
129
|
+
if (node.text === parameters.item) return ["item"]
|
|
130
|
+
if (node.text === parameters.index) return ["index"]
|
|
131
|
+
if (node.text === "undefined") return ["undefined"]
|
|
132
|
+
if (stateNames.has(node.text)) {
|
|
133
|
+
selectorStates.add(node.text)
|
|
134
|
+
return ["state", node.text]
|
|
135
|
+
}
|
|
136
|
+
fail(node, `Rendered collection expression identifier "${node.text}" is not allowed`)
|
|
137
|
+
}
|
|
138
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
139
|
+
if (["__proto__", "constructor", "prototype"].includes(node.name.text)) fail(node, `Rendered collection property "${node.name.text}" is not supported`)
|
|
140
|
+
return ["get", encode(node.expression), node.name.text, Boolean(node.questionDotToken)]
|
|
141
|
+
}
|
|
142
|
+
if (ts.isElementAccessExpression(node)) {
|
|
143
|
+
const key = node.argumentExpression
|
|
144
|
+
if (!ts.isStringLiteral(key) && !ts.isNumericLiteral(key)) fail(node, "Rendered collection computed properties require a direct string or numeric literal key")
|
|
145
|
+
if (ts.isStringLiteral(key) && ["__proto__", "constructor", "prototype"].includes(key.text)) fail(node, `Rendered collection property "${key.text}" is not supported`)
|
|
146
|
+
return ["get", encode(node.expression), ts.isNumericLiteral(key) ? Number(key.text) : key.text, Boolean(node.questionDotToken)]
|
|
147
|
+
}
|
|
148
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
149
|
+
const operator = node.operator === ts.SyntaxKind.ExclamationToken ? "!" : node.operator === ts.SyntaxKind.PlusToken ? "+" : node.operator === ts.SyntaxKind.MinusToken ? "-" : undefined
|
|
150
|
+
if (!operator) fail(node, "Rendered collection expression uses an unsupported unary operator")
|
|
151
|
+
return ["unary", operator, encode(node.operand)]
|
|
152
|
+
}
|
|
153
|
+
if (ts.isTypeOfExpression(node)) return ["unary", "typeof", encode(node.expression)]
|
|
154
|
+
if (ts.isBinaryExpression(node)) {
|
|
155
|
+
const operator = node.operatorToken.getText()
|
|
156
|
+
if (!new Set(["&&", "||", "??", "===", "!==", "==", "!=", "<", "<=", ">", ">=", "+", "-", "*", "/", "%"]).has(operator)) fail(node, `Rendered collection expression operator "${operator}" is not supported`)
|
|
157
|
+
return ["binary", operator, encode(node.left), encode(node.right)]
|
|
158
|
+
}
|
|
159
|
+
if (ts.isConditionalExpression(node)) return ["conditional", encode(node.condition), encode(node.whenTrue), encode(node.whenFalse)]
|
|
160
|
+
if (ts.isArrayLiteralExpression(node) && !node.elements.some(ts.isSpreadElement)) return ["array", ...node.elements.map(encode)]
|
|
161
|
+
if (ts.isObjectLiteralExpression(node)) return ["object", ...node.properties.map(property => {
|
|
162
|
+
if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name) && !ts.isStringLiteral(property.name) && !ts.isNumericLiteral(property.name)) fail(property, "Rendered collection mapper objects require direct properties")
|
|
163
|
+
return [property.name.text, encode(property.initializer)]
|
|
164
|
+
})]
|
|
165
|
+
if (ts.isTemplateExpression(node)) return ["template", [node.head.text, ...node.templateSpans.map(span => span.literal.text)], node.templateSpans.map(span => encode(span.expression))]
|
|
166
|
+
if (ts.isCallExpression(node)) {
|
|
167
|
+
if (ts.isIdentifier(node.expression) && ["Boolean", "Number", "String"].includes(node.expression.text)) return ["global", node.expression.text, ...node.arguments.map(encode)]
|
|
168
|
+
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
169
|
+
const method = node.expression.name.text
|
|
170
|
+
if (ts.isIdentifier(node.expression.expression) && node.expression.expression.text === "Math" && pureCollectionMathMethods.has(method)) return ["math", method, ...node.arguments.map(encode)]
|
|
171
|
+
if (pureCollectionMethods.has(method)) return ["call", encode(node.expression.expression), method, ...node.arguments.map(encode)]
|
|
172
|
+
if (mutatingCollectionMethods.has(method)) fail(node, `Rendered collection expressions cannot call mutating method "${method}"`)
|
|
173
|
+
}
|
|
174
|
+
fail(node, "Rendered collection expressions cannot call arbitrary functions")
|
|
175
|
+
}
|
|
176
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isAwaitExpression(node) || ts.isNewExpression(node) || ts.isYieldExpression(node) || ts.isDeleteExpression(node) || ts.isPostfixUnaryExpression(node)) fail(node, "Rendered collection expressions must be pure and synchronous")
|
|
177
|
+
fail(node, "Rendered collection expression is not supported")
|
|
178
|
+
}
|
|
179
|
+
return encode(expression)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function directProperty(expression, objectName) {
|
|
183
|
+
const value = unwrapExpression(expression)
|
|
184
|
+
if (!ts.isPropertyAccessExpression(value) || !ts.isIdentifier(value.expression)) return undefined
|
|
185
|
+
if (objectName !== undefined && value.expression.text !== objectName) return undefined
|
|
186
|
+
return value.name.text
|
|
187
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import ts from "typescript"
|
|
2
|
+
import { bindingNames, isFunctionLike, isReferenceIdentifier, isShadowedByParameter, isShadowedIdentifier, unwrapExpression } from "./ast-helpers.mjs"
|
|
3
|
+
|
|
4
|
+
export function createSemanticArtifact() {
|
|
5
|
+
return { nativeHandlers: [], effectHandlers: [], reactiveBindings: [], listExpressions: [], clientImports: new Set() }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function createDescriptorSession({ semantic, handlerUrl, factory, context, compileEventCommand, isPrimitiveLiteral, rejectWorkerConstructions }) {
|
|
9
|
+
const { nativeHandlers, effectHandlers, reactiveBindings, listExpressions, clientImports } = semantic
|
|
10
|
+
|
|
11
|
+
function compileListExpression(read, expression, item, index, states = new Set()) {
|
|
12
|
+
const exportName = `listExpression${listExpressions.length}`
|
|
13
|
+
listExpressions.push({ exportName, expression, item, index, states })
|
|
14
|
+
const arguments_ = [read, factory.createStringLiteral(handlerUrl), factory.createStringLiteral(exportName)]
|
|
15
|
+
if (states.size) arguments_.push(factory.createArrayLiteralExpression([...states].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)]))))
|
|
16
|
+
return factory.createCallExpression(factory.createIdentifier("__kListExpression"), undefined, arguments_)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function compileListConditional(entry) {
|
|
20
|
+
const exportName = `listExpression${listExpressions.length}`
|
|
21
|
+
listExpressions.push({ exportName, expression: entry.condition, item: entry.item, index: entry.index })
|
|
22
|
+
const read = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), entry.condition)
|
|
23
|
+
const thunk = branch => factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), branch)
|
|
24
|
+
return factory.createCallExpression(factory.createIdentifier("__kListConditional"), undefined, [
|
|
25
|
+
factory.createStringLiteral(entry.kind), read, thunk(entry.truthy), thunk(entry.falsy), factory.createStringLiteral(handlerUrl), factory.createStringLiteral(exportName)
|
|
26
|
+
])
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function compileListValue(expression, entry) {
|
|
30
|
+
const rewrite = node => {
|
|
31
|
+
if (ts.isShorthandPropertyAssignment(node) && entry.states?.has(node.name.text)) return factory.createPropertyAssignment(node.name, factory.createPropertyAccessExpression(node.name, "value"))
|
|
32
|
+
if (ts.isIdentifier(node) && entry.states?.has(node.text) && isReferenceIdentifier(node)) return factory.createPropertyAccessExpression(node, "value")
|
|
33
|
+
return ts.visitEachChild(node, rewrite, context)
|
|
34
|
+
}
|
|
35
|
+
const initial = entry.states?.size ? ts.visitNode(expression, rewrite) : expression
|
|
36
|
+
const read = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), initial)
|
|
37
|
+
return entry.field
|
|
38
|
+
? factory.createCallExpression(factory.createIdentifier("__kListField"), undefined, [read, factory.createStringLiteral(entry.field)])
|
|
39
|
+
: compileListExpression(read, expression, entry.item, entry.index, entry.states)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function compileReactiveBinding(expression, { setters, importBindings = new Map() }) {
|
|
43
|
+
const parts = conditionalParts(expression)
|
|
44
|
+
const state = parts && directStateIdentifier(parts.condition, setters)
|
|
45
|
+
if (state && isPrimitiveLiteral(parts.truthy) && isPrimitiveLiteral(parts.falsy)) {
|
|
46
|
+
return factory.createCallExpression(factory.createIdentifier("__kSelect"), undefined, [state, parts.truthy, parts.falsy])
|
|
47
|
+
}
|
|
48
|
+
return factory.createCallExpression(factory.createIdentifier("__kBinding"), undefined, compileReactiveExpression(expression, setters, importBindings))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function compileConditional(kind, expression, truthy, falsy, setters) {
|
|
52
|
+
const state = directStateIdentifier(expression, setters)
|
|
53
|
+
const thunk = branch => factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), branch)
|
|
54
|
+
if (state) return factory.createCallExpression(factory.createIdentifier("__kStateConditional"), undefined, [factory.createStringLiteral(kind), state, thunk(truthy), thunk(falsy)])
|
|
55
|
+
const [initial, ...descriptor] = compileReactiveExpression(expression, setters)
|
|
56
|
+
return factory.createCallExpression(factory.createIdentifier("__kConditional"), undefined, [factory.createStringLiteral(kind), initial, thunk(truthy), thunk(falsy), ...descriptor])
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function compileReactiveExpression(expression, setters, importBindings = new Map()) {
|
|
60
|
+
const usedStates = referencedStateNames(expression, setters)
|
|
61
|
+
const importedNames = referencedImportedBindings(expression, importBindings)
|
|
62
|
+
const imports = [...importedNames].map(name => importBindings.get(name))
|
|
63
|
+
registerClientImports(imports)
|
|
64
|
+
const captures = new Set([...captureNames(expression, expression, setters)].filter(name => !importedNames.has(name)))
|
|
65
|
+
const exportName = `binding${reactiveBindings.length}`
|
|
66
|
+
reactiveBindings.push({ exportName, expression, captures, states: usedStates, imports })
|
|
67
|
+
const states = [...usedStates].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)]))
|
|
68
|
+
const scope = [...captures].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)]))
|
|
69
|
+
const stateNames = new Set(usedStates)
|
|
70
|
+
const rewriteInitial = node => {
|
|
71
|
+
if (ts.isShorthandPropertyAssignment(node) && stateNames.has(node.name.text)) return factory.createPropertyAssignment(node.name, factory.createPropertyAccessExpression(node.name, "value"))
|
|
72
|
+
if (ts.isIdentifier(node) && stateNames.has(node.text) && isReferenceIdentifier(node) && !isShadowedByParameter(node, expression)) return factory.createPropertyAccessExpression(node, "value")
|
|
73
|
+
if (ts.isShorthandPropertyAssignment(node) && captures.has(node.name.text)) return factory.createPropertyAssignment(node.name, factory.createCallExpression(factory.createIdentifier("__kBindingValue"), undefined, [node.name]))
|
|
74
|
+
if (ts.isIdentifier(node) && captures.has(node.text) && isReferenceIdentifier(node)) return factory.createCallExpression(factory.createIdentifier("__kBindingValue"), undefined, [node])
|
|
75
|
+
return ts.visitEachChild(node, rewriteInitial, context)
|
|
76
|
+
}
|
|
77
|
+
return [
|
|
78
|
+
ts.visitNode(expression, rewriteInitial),
|
|
79
|
+
factory.createStringLiteral(handlerUrl),
|
|
80
|
+
factory.createStringLiteral(exportName),
|
|
81
|
+
factory.createArrayLiteralExpression(states),
|
|
82
|
+
factory.createArrayLiteralExpression(scope)
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function compileEvent(expression, { setters, reducers, functions, listItem, importBindings }) {
|
|
87
|
+
if (ts.isIdentifier(expression)) expression = functions.get(expression.text)
|
|
88
|
+
if (!expression || (!ts.isArrowFunction(expression) && !ts.isFunctionExpression(expression) && !ts.isFunctionDeclaration(expression))) return undefined
|
|
89
|
+
const optimized = referencedReducerDispatches(expression.body, reducers, expression).size ? undefined : compileOptimizedEvent(expression, setters)
|
|
90
|
+
if (optimized) return optimized
|
|
91
|
+
rejectWorkerConstructions(expression)
|
|
92
|
+
const descriptor = compileNativeCallback(expression, { setters, reducers, entries: nativeHandlers, importBindings, prefix: "handler", listItem })
|
|
93
|
+
return factory.createCallExpression(factory.createIdentifier("__kNativeBehavior"), undefined, [
|
|
94
|
+
factory.createStringLiteral(handlerUrl), factory.createStringLiteral(descriptor.exportName), descriptor.states, descriptor.scope
|
|
95
|
+
])
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function compileEffectCallback(expression, options) {
|
|
99
|
+
return compileNativeCallback(expression, { ...options, entries: effectHandlers, prefix: "effect" })
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function compileNativeCallback(expression, { setters, reducers, entries, importBindings, prefix, listItem, deferValues = false, snapshotNested = false, liveStates = new Set() }) {
|
|
103
|
+
const allCaptures = nativeCaptureNames(expression, setters)
|
|
104
|
+
const usedReducers = referencedReducerDispatches(expression.body, reducers, expression)
|
|
105
|
+
const imports = [...referencedImportedBindings(expression, importBindings)].map(name => importBindings.get(name))
|
|
106
|
+
imports.push(...[...usedReducers].map(name => reducers.get(name).import).filter(Boolean))
|
|
107
|
+
const captures = new Set([...allCaptures].filter(name => !importBindings.has(name) && !usedReducers.has(name)))
|
|
108
|
+
registerClientImports(imports)
|
|
109
|
+
const usedStates = referencedStateNames(expression.body, setters, expression)
|
|
110
|
+
for (const name of usedReducers) {
|
|
111
|
+
const reducer = reducers.get(name)
|
|
112
|
+
if (reducer.contextAction) for (const state of referencedStateNames(reducer.contextAction.body, reducer.states, reducer.contextAction)) usedStates.add(state)
|
|
113
|
+
}
|
|
114
|
+
const exportName = `${prefix}${entries.length}`
|
|
115
|
+
entries.push({ exportName, expression, captures, imports, liveStates, setters: new Map([...setters].filter(([, state]) => usedStates.has(state))), reducers: new Map([...reducers].filter(([name]) => usedReducers.has(name))), snapshotNested })
|
|
116
|
+
const value = name => deferValues
|
|
117
|
+
? factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createIdentifier(name))
|
|
118
|
+
: factory.createIdentifier(name)
|
|
119
|
+
return {
|
|
120
|
+
exportName,
|
|
121
|
+
states: factory.createArrayLiteralExpression([...usedStates].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), value(name)]))),
|
|
122
|
+
scope: factory.createArrayLiteralExpression([...captures].map(name => factory.createArrayLiteralExpression([
|
|
123
|
+
factory.createStringLiteral(name),
|
|
124
|
+
name === (typeof listItem === "string" ? listItem : listItem?.item)
|
|
125
|
+
? factory.createCallExpression(factory.createIdentifier("__kListItem"), undefined, [])
|
|
126
|
+
: name === listItem?.index ? factory.createCallExpression(factory.createIdentifier("__kListIndex"), undefined, []) : value(name)
|
|
127
|
+
])))
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function compileOptimizedEvent(expression, setters) {
|
|
132
|
+
const statements = ts.isBlock(expression.body) ? expression.body.statements : [factory.createExpressionStatement(expression.body)]
|
|
133
|
+
const commands = statements.map(statement => ts.isExpressionStatement(statement) ? compileEventCommand(statement.expression, setters, factory) : undefined)
|
|
134
|
+
if (!commands.length || commands.some(command => !command)) return undefined
|
|
135
|
+
return factory.createCallExpression(factory.createIdentifier("__kBehavior"), undefined, [factory.createArrayLiteralExpression(commands)])
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function registerClientImports(imports) {
|
|
139
|
+
for (const entry of imports) if (!entry.package) clientImports.add(entry.target)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { compileConditional, compileEffectCallback, compileEvent, compileListConditional, compileListValue, compileReactiveBinding }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function directStateIdentifier(expression, setters) {
|
|
146
|
+
const value = unwrapExpression(expression)
|
|
147
|
+
return ts.isIdentifier(value) && new Set(setters.values()).has(value.text) ? value : undefined
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function conditionalParts(expression) {
|
|
151
|
+
const unwrap = node => ts.isParenthesizedExpression(node) ? unwrap(node.expression) : node
|
|
152
|
+
const value = unwrap(expression)
|
|
153
|
+
if (ts.isBinaryExpression(value) && value.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) return { condition: value.left, truthy: unwrap(value.right), falsy: ts.factory.createNull() }
|
|
154
|
+
if (ts.isConditionalExpression(value)) return { condition: value.condition, truthy: unwrap(value.whenTrue), falsy: unwrap(value.whenFalse) }
|
|
155
|
+
return undefined
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function referencedReducerDispatches(root, reducers, scopeRoot = root) {
|
|
159
|
+
const used = new Set()
|
|
160
|
+
const visit = node => {
|
|
161
|
+
if (ts.isIdentifier(node) && reducers.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, scopeRoot)) used.add(node.text)
|
|
162
|
+
ts.forEachChild(node, visit)
|
|
163
|
+
}
|
|
164
|
+
visit(root)
|
|
165
|
+
return used
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function referencedStateNames(root, setters, scopeRoot = root) {
|
|
169
|
+
const stateNames = new Set(setters.values())
|
|
170
|
+
const used = new Set()
|
|
171
|
+
const visit = node => {
|
|
172
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && setters.has(node.expression.text) && !isShadowedIdentifier(node.expression, scopeRoot)) used.add(setters.get(node.expression.text))
|
|
173
|
+
if (ts.isIdentifier(node) && setters.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, scopeRoot)) used.add(setters.get(node.text))
|
|
174
|
+
if (ts.isIdentifier(node) && stateNames.has(node.text) && isReferenceIdentifier(node) && !isShadowedByParameter(node, scopeRoot)) used.add(node.text)
|
|
175
|
+
ts.forEachChild(node, visit)
|
|
176
|
+
}
|
|
177
|
+
visit(root)
|
|
178
|
+
return used
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function nativeCaptureNames(expression, setters) {
|
|
182
|
+
return captureNames(expression, expression.body, setters)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function referencedImportedBindings(expression, imports) {
|
|
186
|
+
const names = new Set()
|
|
187
|
+
const visit = node => {
|
|
188
|
+
if (ts.isIdentifier(node) && imports.has(node.text) && isReferenceIdentifier(node)) names.add(node.text)
|
|
189
|
+
ts.forEachChild(node, visit)
|
|
190
|
+
}
|
|
191
|
+
visit(expression.body ?? expression)
|
|
192
|
+
return names
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function captureNames(declarationRoot, referenceRoot, setters) {
|
|
196
|
+
const local = new Set()
|
|
197
|
+
if (!isFunctionLike(declarationRoot)) {
|
|
198
|
+
const collectDeclarations = node => {
|
|
199
|
+
if (ts.isVariableDeclaration(node)) for (const name of bindingNames(node.name)) local.add(name)
|
|
200
|
+
if (ts.isParameter(node)) for (const name of bindingNames(node.name)) local.add(name)
|
|
201
|
+
if ((ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) && node.name) local.add(node.name.text)
|
|
202
|
+
ts.forEachChild(node, collectDeclarations)
|
|
203
|
+
}
|
|
204
|
+
collectDeclarations(declarationRoot)
|
|
205
|
+
}
|
|
206
|
+
const stateNames = new Set(setters.values())
|
|
207
|
+
const captures = new Set()
|
|
208
|
+
const visit = node => {
|
|
209
|
+
if (ts.isTypeNode(node)) return
|
|
210
|
+
if (ts.isIdentifier(node)) {
|
|
211
|
+
const declared = isFunctionLike(declarationRoot) ? isShadowedIdentifier(node, declarationRoot) : local.has(node.text)
|
|
212
|
+
if (isReferenceIdentifier(node) && !declared && !setters.has(node.text) && !stateNames.has(node.text) && !nativeGlobals.has(node.text)) captures.add(node.text)
|
|
213
|
+
}
|
|
214
|
+
ts.forEachChild(node, visit)
|
|
215
|
+
}
|
|
216
|
+
visit(referenceRoot)
|
|
217
|
+
return captures
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const nativeGlobals = new Set([
|
|
221
|
+
"Array", "ArrayBuffer", "BigInt", "Blob", "Boolean", "Date", "Error", "Event", "FileReader", "FormData", "Infinity", "IntersectionObserver", "Intl", "JSON", "Map", "Math", "NaN", "Number", "Object", "Promise", "Proxy", "RangeError", "ReferenceError", "Reflect", "RegExp", "Set", "String", "Symbol", "TypeError", "URL", "URLSearchParams", "WeakMap", "WeakSet", "WebSocket", "Worker", "alert", "atob", "btoa", "cancelAnimationFrame", "clearInterval", "clearTimeout", "console", "crypto", "document", "fetch", "globalThis", "history", "isFinite", "isNaN", "localStorage", "location", "navigator", "parseFloat", "parseInt", "performance", "queueMicrotask", "requestAnimationFrame", "setInterval", "setTimeout", "structuredClone", "undefined", "window"
|
|
222
|
+
])
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import ts from "typescript"
|
|
2
|
+
|
|
3
|
+
export function createEventCommandCompiler({ isPrimitiveLiteral, synthesizeSerializableStateLiteral }) {
|
|
4
|
+
return function compileEventCommand(expression, setters, factory) {
|
|
5
|
+
if (ts.isCallExpression(expression) && ts.isPropertyAccessExpression(expression.expression) && ts.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "console" && expression.expression.name.text === "log" && expression.arguments.length === 2 && ts.isStringLiteral(expression.arguments[0]) && ts.isIdentifier(expression.arguments[1]) && [...setters.values()].includes(expression.arguments[1].text)) {
|
|
6
|
+
return command(factory, "log", expression.arguments[1], factory.createStringLiteral(expression.arguments[0].text))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (!ts.isCallExpression(expression) || !ts.isIdentifier(expression.expression) || expression.arguments.length !== 1) return undefined
|
|
10
|
+
const stateName = setters.get(expression.expression.text)
|
|
11
|
+
if (!stateName) return undefined
|
|
12
|
+
|
|
13
|
+
const state = factory.createIdentifier(stateName)
|
|
14
|
+
const value = expression.arguments[0]
|
|
15
|
+
if (ts.isBinaryExpression(value) && ts.isIdentifier(value.left) && value.left.text === stateName && ts.isNumericLiteral(value.right)) {
|
|
16
|
+
if (value.operatorToken.kind !== ts.SyntaxKind.PlusToken && value.operatorToken.kind !== ts.SyntaxKind.MinusToken) return undefined
|
|
17
|
+
return command(factory, "add", state, numericExpression(factory, Number(value.right.text), value.operatorToken.kind === ts.SyntaxKind.MinusToken))
|
|
18
|
+
}
|
|
19
|
+
if (ts.isArrowFunction(value) && value.parameters.length === 1 && ts.isIdentifier(value.parameters[0].name) && ts.isBinaryExpression(value.body) && ts.isIdentifier(value.body.left) && value.body.left.text === value.parameters[0].name.text && ts.isNumericLiteral(value.body.right)) {
|
|
20
|
+
if (value.body.operatorToken.kind !== ts.SyntaxKind.PlusToken && value.body.operatorToken.kind !== ts.SyntaxKind.MinusToken) return undefined
|
|
21
|
+
return command(factory, "add", state, numericExpression(factory, Number(value.body.right.text), value.body.operatorToken.kind === ts.SyntaxKind.MinusToken))
|
|
22
|
+
}
|
|
23
|
+
if (isPrimitiveLiteral(value)) return command(factory, "set", state, synthesizeSerializableStateLiteral(value, factory))
|
|
24
|
+
return undefined
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function command(factory, operation, state, value) {
|
|
29
|
+
return factory.createArrayLiteralExpression([factory.createStringLiteral(operation), state, value])
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function numericExpression(factory, value, negative) {
|
|
33
|
+
const literal = factory.createNumericLiteral(value)
|
|
34
|
+
return negative ? factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, literal) : literal
|
|
35
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import ts from "typescript"
|
|
2
2
|
import { bindingNames, importDeclarationNames, isFunctionLike, isLocalConst, isReferenceIdentifier, isShadowedByParameter, isShadowedIdentifier, loopDeclaresName, nearestFunction, nearestFunctionLike, referenceIdentifiers, sourceNodeError, statementDeclaresName, unwrapExpression } from "./ast-helpers.mjs"
|
|
3
|
+
import { analyzeCollectionPipeline, isArrayFromCall } from "./collection-analysis.mjs"
|
|
3
4
|
|
|
4
|
-
export function createReactMigrationPass({ cloneAst,
|
|
5
|
+
export function createReactMigrationPass({ cloneAst, jsxTagName }) {
|
|
5
6
|
function normalizeReactMigrationSyntax(sourceFile, factory, context, importedCollections = new Set()) {
|
|
6
7
|
const supported = new Set(["createContext", "useContext", "useEffect", "useId", "useReducer", "useRef", "useState"])
|
|
7
8
|
const erased = new Set(["forwardRef", "memo", "useCallback", "useMemo"])
|
|
@@ -291,7 +292,7 @@ export function createReactMigrationPass({ cloneAst, isArrayFromCall, jsxTagName
|
|
|
291
292
|
function reactMemoCollection(expression, states, importedCollections, sourceFile) {
|
|
292
293
|
const setters = new Map([...states].map(state => [state, state]))
|
|
293
294
|
const fail = (node, message) => { throw sourceNodeError(node, sourceFile, message) }
|
|
294
|
-
return
|
|
295
|
+
return analyzeCollectionPipeline(expression, { setters, fail, importedCollections, stateNames: states })
|
|
295
296
|
}
|
|
296
297
|
|
|
297
298
|
function reactMemoComponentExpression(identifier, sourceFile, factory, context) {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export function usesRouteDependencyRuntime({ plan, navigable, hasBindings, hasLists }) {
|
|
2
|
+
const hasDependencies = plan.effects.some(effect => effect.dependencies?.length)
|
|
3
|
+
return !navigable && hasDependencies && !plan.effects.some(effect => effect.owner) && !hasBindings && !hasLists && !plan.events.some(event => event.native)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function planRouteCapabilities(plans, { routes = new Map(), navigationRouteCount = 0 } = {}) {
|
|
7
|
+
const commandEvents = new Set()
|
|
8
|
+
const nativeEvents = new Set()
|
|
9
|
+
const bindings = { count: 0, text: false, svgConditions: false }
|
|
10
|
+
const lists = {
|
|
11
|
+
count: 0,
|
|
12
|
+
styleCount: 0,
|
|
13
|
+
conditions: false,
|
|
14
|
+
svg: false,
|
|
15
|
+
deepConditions: false,
|
|
16
|
+
textRanges: false,
|
|
17
|
+
attributes: false,
|
|
18
|
+
events: false,
|
|
19
|
+
expressions: false,
|
|
20
|
+
expressionAttributes: false,
|
|
21
|
+
seeds: false,
|
|
22
|
+
effects: false,
|
|
23
|
+
rowHooks: false,
|
|
24
|
+
rowRefs: false,
|
|
25
|
+
complexRowState: false,
|
|
26
|
+
nested: false,
|
|
27
|
+
selectors: false,
|
|
28
|
+
calculated: false,
|
|
29
|
+
static: false,
|
|
30
|
+
indexes: false,
|
|
31
|
+
stableFastPaths: false,
|
|
32
|
+
generalRowHooks: false,
|
|
33
|
+
asyncParts: false,
|
|
34
|
+
mounts: false
|
|
35
|
+
}
|
|
36
|
+
const effects = { any: false, derivedDependencies: false, itemDependencies: false, captures: false, navigable: false, navigableOwners: false }
|
|
37
|
+
|
|
38
|
+
for (let index = 0; index < plans.length; index++) {
|
|
39
|
+
const plan = plans[index]
|
|
40
|
+
const route = routes.get(plan.route)
|
|
41
|
+
for (const event of plan.events) {
|
|
42
|
+
if (event.commands) commandEvents.add(event.event)
|
|
43
|
+
if (event.native) nativeEvents.add(event.event)
|
|
44
|
+
}
|
|
45
|
+
bindings.text ||= plan.bindings.some(binding => binding.target === "text")
|
|
46
|
+
bindings.svgConditions ||= plan.conditions.some(condition => condition.svg)
|
|
47
|
+
effects.any ||= plan.effects.length > 0
|
|
48
|
+
effects.derivedDependencies ||= plan.effects.some(effect => effect.dependencyExpressions?.length)
|
|
49
|
+
effects.itemDependencies ||= plan.effects.some(effect => effect.itemDependencies?.length)
|
|
50
|
+
effects.captures ||= plan.effects.some(effect => Object.keys(effect.scope).length)
|
|
51
|
+
effects.navigable ||= Boolean(route?.navigable && plan.effects.length)
|
|
52
|
+
effects.navigableOwners ||= Boolean(route?.navigable && plan.effects.some(effect => effect.owner))
|
|
53
|
+
for (const list of plan.lists) {
|
|
54
|
+
lists.conditions ||= Boolean(list.conditions)
|
|
55
|
+
lists.svg ||= Boolean(list.svg)
|
|
56
|
+
lists.deepConditions ||= Boolean(list.conditionHandlers)
|
|
57
|
+
lists.textRanges ||= Boolean(list.textRanges)
|
|
58
|
+
lists.attributes ||= Boolean(list.attributes)
|
|
59
|
+
lists.events ||= Boolean(list.events)
|
|
60
|
+
lists.expressions ||= Boolean(list.expressions)
|
|
61
|
+
lists.expressionAttributes ||= Boolean(list.expressionAttributes)
|
|
62
|
+
lists.seeds ||= Boolean(list.seed || list.valueSeed)
|
|
63
|
+
lists.effects ||= Boolean(list.effects)
|
|
64
|
+
lists.rowHooks ||= Boolean(list.rowStates?.length || list.rowRefs?.length)
|
|
65
|
+
lists.rowRefs ||= Boolean(list.rowRefs?.length)
|
|
66
|
+
lists.complexRowState ||= Boolean(list.rowStates?.some(state => state.initialValue !== null && typeof state.initialValue === "object"))
|
|
67
|
+
lists.nested ||= Boolean(list.ownerField)
|
|
68
|
+
lists.selectors ||= Boolean(list.selector)
|
|
69
|
+
lists.calculated ||= Boolean(list.source)
|
|
70
|
+
lists.static ||= Boolean(list.static)
|
|
71
|
+
lists.indexes ||= Boolean(list.indexed)
|
|
72
|
+
lists.stableFastPaths ||= !list.children && !list.ownerField && list.key !== null && !list.indexed && !list.reducer && !list.selector
|
|
73
|
+
lists.generalRowHooks ||= Boolean(list.ownerField && (list.rowStates?.length || list.rowRefs?.length))
|
|
74
|
+
lists.mounts ||= Boolean(list.mount)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const routeEntries = [...routes.values()]
|
|
79
|
+
const routeCounts = {
|
|
80
|
+
behaviors: routeEntries.filter(route => route.hasBehaviors).length,
|
|
81
|
+
regularBehaviors: routeEntries.filter(route => route.hasBehaviors && !route.usesDependencyRuntime).length,
|
|
82
|
+
regularStateSeeds: routeEntries.filter(route => route.hasStateSeed && !route.usesDependencyRuntime).length,
|
|
83
|
+
dependencyStateSeeds: routeEntries.filter(route => route.hasStateSeed && route.usesDependencyRuntime).length
|
|
84
|
+
}
|
|
85
|
+
bindings.count = routeEntries.filter(route => route.hasBindings).length
|
|
86
|
+
lists.count = routeEntries.filter(route => route.hasLists).length
|
|
87
|
+
lists.styleCount = routeEntries.filter(route => route.hasListStyles).length
|
|
88
|
+
lists.generalRowHooks ||= lists.rowRefs || lists.complexRowState
|
|
89
|
+
lists.asyncParts = lists.expressions || lists.expressionAttributes || lists.conditions
|
|
90
|
+
lists.mounts ||= lists.conditions || lists.nested
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
routes: routeCounts,
|
|
94
|
+
events: { command: [...commandEvents].sort(), native: [...nativeEvents].sort(), hasNativeHandlers: nativeEvents.size > 0 },
|
|
95
|
+
bindings,
|
|
96
|
+
lists,
|
|
97
|
+
effects,
|
|
98
|
+
captures: { nestedState: hasNestedCaptureState(plans), setter: hasCaptureType(plans, "setter") },
|
|
99
|
+
runtime: {
|
|
100
|
+
shared: Boolean(bindings.count || lists.count || nativeEvents.size || navigationRouteCount),
|
|
101
|
+
dependency: routeEntries.some(route => route.usesDependencyRuntime)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function hasCaptureType(value, type) {
|
|
107
|
+
if (!value || typeof value !== "object") return false
|
|
108
|
+
if (value.type === type) return true
|
|
109
|
+
return (Array.isArray(value) ? value : Object.values(value)).some(entry => hasCaptureType(entry, type))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function hasNestedCaptureState(value, insideCapture = false) {
|
|
113
|
+
if (!value || typeof value !== "object") return false
|
|
114
|
+
if (value.type === "state") return insideCapture
|
|
115
|
+
if (value.type === "array") return value.value.some(entry => hasNestedCaptureState(entry, true))
|
|
116
|
+
if (value.type === "object") return value.value.some(([, entry]) => hasNestedCaptureState(entry, true))
|
|
117
|
+
return (Array.isArray(value) ? value : Object.values(value)).some(entry => hasNestedCaptureState(entry, false))
|
|
118
|
+
}
|