@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
package/framework/build.mjs
CHANGED
|
@@ -7,14 +7,19 @@ import ts from "typescript"
|
|
|
7
7
|
import { normalizeEffectAnimationFrameRefs } from "./compiler/animation-frame-pass.mjs"
|
|
8
8
|
import { bindingNames, containsJsx, effectReturns, functionVarDeclaresName, importDeclarationNames, isFunctionLike, isLocalConst, isReferenceIdentifier, isShadowedByParameter, isShadowedIdentifier, isUnshadowedGlobal, nearestFunction, referenceIdentifiers, referencesIdentifier, sourceLocation, sourceNodeError, statementDeclaresName, unwrapExpression } from "./compiler/ast-helpers.mjs"
|
|
9
9
|
import { normalizeMediaQueryExternalStores, normalizeNavigatorCapabilityConditions } from "./compiler/browser-signal-passes.mjs"
|
|
10
|
+
import { analyzeCollectionPipeline, collectionExpression, collectionParameters, isArrayFromCall, mutatingCollectionMethods as mutatingListMethods, pureCollectionMathMethods as pureMathMethods, pureCollectionMethods as pureListMethods } from "./compiler/collection-analysis.mjs"
|
|
10
11
|
import { normalizeCustomHookTimerRefs } from "./compiler/custom-hook-timer-pass.mjs"
|
|
12
|
+
import { captureNames, createDescriptorSession, createSemanticArtifact, nativeCaptureNames, referencedReducerDispatches, referencedStateNames } from "./compiler/descriptor-session.mjs"
|
|
11
13
|
import { createEffectCodegen } from "./compiler/effect-codegen.mjs"
|
|
14
|
+
import { createEventCommandCompiler } from "./compiler/event-command-pass.mjs"
|
|
12
15
|
import { createHandlerCodegen } from "./compiler/handler-codegen.mjs"
|
|
13
16
|
import { applyNormalizationPasses } from "./compiler/normalization-pipeline.mjs"
|
|
14
17
|
import { createReactMigrationPass, reactMemoExpression } from "./compiler/react-migration-pass.mjs"
|
|
15
18
|
import { normalizeRenderControlFlow } from "./compiler/render-control-pass.mjs"
|
|
16
19
|
import { createRouterPass } from "./compiler/router-pass.mjs"
|
|
20
|
+
import { planRouteCapabilities, usesRouteDependencyRuntime } from "./compiler/route-capability-planner.mjs"
|
|
17
21
|
import { createWorkerCompiler } from "./compiler/worker-compiler.mjs"
|
|
22
|
+
import { createZustandPass } from "./compiler/zustand-pass.mjs"
|
|
18
23
|
import { renderPage } from "./core.mjs"
|
|
19
24
|
import { parseDevHost, parseDevPort, startDevServer } from "./dev-server.mjs"
|
|
20
25
|
|
|
@@ -26,6 +31,8 @@ const pagesDirectory = join(sourceDirectory, "pages")
|
|
|
26
31
|
const workDirectory = join(root, ".kudzu")
|
|
27
32
|
const outputDirectory = join(root, "dist")
|
|
28
33
|
const staticAssetExtensions = new Set([".avif", ".gif", ".ico", ".jpeg", ".jpg", ".otf", ".png", ".svg", ".ttf", ".webp", ".woff", ".woff2"])
|
|
34
|
+
const compileEventCommand = createEventCommandCompiler({ isPrimitiveLiteral: isPrimitiveDefaultLiteral, synthesizeSerializableStateLiteral })
|
|
35
|
+
const { analyzeZustandStores, normalizeZustandMigrationSyntax } = createZustandPass({ isSerializableStateLiteral, nativeCaptureNames, sourceDirectory })
|
|
29
36
|
|
|
30
37
|
export async function build({ quiet = false, minify = true } = {}) {
|
|
31
38
|
const config = await loadConfig()
|
|
@@ -73,14 +80,8 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
73
80
|
if (handlerModule) handlerModules.push(handlerModule)
|
|
74
81
|
}
|
|
75
82
|
|
|
76
|
-
let behaviorCount = 0
|
|
77
|
-
let regularBehaviorCount = 0
|
|
78
|
-
let bindingCount = 0
|
|
79
|
-
let listCount = 0
|
|
80
|
-
let listStyleCount = 0
|
|
81
|
-
let regularStateSeedCount = 0
|
|
82
|
-
let dependencyStateSeedCount = 0
|
|
83
83
|
const plans = []
|
|
84
|
+
const routeCapabilities = new Map()
|
|
84
85
|
const pageEntries = []
|
|
85
86
|
const effectEntries = []
|
|
86
87
|
const nativeEntries = []
|
|
@@ -161,27 +162,24 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
161
162
|
navigationGroup.hasEffects ||= result.hasEffects
|
|
162
163
|
navigationGroup.hasParams ||= result.hasParams
|
|
163
164
|
}
|
|
164
|
-
const
|
|
165
|
-
const usesDependencyRuntime = !navigable && hasDependencies && !result.plan.effects.some(effect => effect.owner) && !result.hasBindings && !result.hasLists && !result.plan.events.some(event => event.native)
|
|
165
|
+
const usesDependencyRuntime = usesRouteDependencyRuntime({ plan: result.plan, navigable, hasBindings: result.hasBindings, hasLists: result.hasLists })
|
|
166
166
|
pageEntries.push({ route, html: result.html, usesDependencyRuntime })
|
|
167
167
|
plans.push({ route: routePath, ...result.plan })
|
|
168
|
+
routeCapabilities.set(routePath, {
|
|
169
|
+
navigable,
|
|
170
|
+
usesDependencyRuntime,
|
|
171
|
+
hasBehaviors: result.hasBehaviors,
|
|
172
|
+
hasBindings: result.hasBindings,
|
|
173
|
+
hasLists: result.hasLists,
|
|
174
|
+
hasListStyles: result.hasListStyles,
|
|
175
|
+
hasStateSeed: result.hasStateSeed
|
|
176
|
+
})
|
|
168
177
|
if (result.hasParams) paramEntries.push({ path: paramPath, schema: runtimeSchema, params: result.plan.params, searchParams: result.plan.searchParams, searchParamsWritable: result.plan.searchParamsWritable, usesDependencyRuntime, navigable })
|
|
169
178
|
if (result.hasEffects) effectEntries.push({ path: effectPath, effects: runtimeEffects(result.plan.effects, navigable), paramPath: result.hasParams ? paramPath : undefined, usesDependencyRuntime, navigable })
|
|
170
179
|
if (result.plan.events.some(event => event.native)) nativeEntries.push({
|
|
171
180
|
path: nativePath,
|
|
172
181
|
modules: [...new Set(result.plan.events.filter(event => event.native).map(event => event.native.module))]
|
|
173
182
|
})
|
|
174
|
-
if (result.hasBehaviors) {
|
|
175
|
-
behaviorCount++
|
|
176
|
-
if (!usesDependencyRuntime) regularBehaviorCount++
|
|
177
|
-
}
|
|
178
|
-
if (result.hasBindings) bindingCount++
|
|
179
|
-
if (result.hasLists) listCount++
|
|
180
|
-
if (result.hasListStyles) listStyleCount++
|
|
181
|
-
if (result.hasStateSeed) {
|
|
182
|
-
if (usesDependencyRuntime) dependencyStateSeedCount++
|
|
183
|
-
else regularStateSeedCount++
|
|
184
|
-
}
|
|
185
183
|
}
|
|
186
184
|
}
|
|
187
185
|
|
|
@@ -208,43 +206,41 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
208
206
|
}
|
|
209
207
|
if (module.code.includes("/__kudzu_worker_")) throw new Error(`Worker URL placeholder survived in ${module.path}`)
|
|
210
208
|
}
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const hasSharedRuntime = bindingCount || listCount || hasNativeHandlers || navigationRoutes.length
|
|
247
|
-
const hasDependencyRuntime = pageEntries.some(entry => entry.usesDependencyRuntime)
|
|
209
|
+
const capabilityManifest = planRouteCapabilities(plans, { routes: routeCapabilities, navigationRouteCount: navigationRoutes.length })
|
|
210
|
+
const {
|
|
211
|
+
routes: { behaviors: behaviorCount, regularBehaviors: regularBehaviorCount, regularStateSeeds: regularStateSeedCount, dependencyStateSeeds: dependencyStateSeedCount },
|
|
212
|
+
events: { command: commandEvents, native: nativeEvents, hasNativeHandlers },
|
|
213
|
+
bindings: { count: bindingCount, text: hasTextBindings, svgConditions: hasSvgConditions },
|
|
214
|
+
lists: {
|
|
215
|
+
count: listCount,
|
|
216
|
+
styleCount: listStyleCount,
|
|
217
|
+
conditions: hasListConditions,
|
|
218
|
+
svg: hasSvgLists,
|
|
219
|
+
deepConditions: hasDeepListConditions,
|
|
220
|
+
textRanges: hasListTextRanges,
|
|
221
|
+
attributes: hasListAttributes,
|
|
222
|
+
events: hasListEvents,
|
|
223
|
+
expressions: hasListExpressions,
|
|
224
|
+
expressionAttributes: hasListExpressionAttributes,
|
|
225
|
+
seeds: hasListSeeds,
|
|
226
|
+
effects: hasListEffects,
|
|
227
|
+
rowHooks: hasListRowHooks,
|
|
228
|
+
rowRefs: hasListRowRefs,
|
|
229
|
+
complexRowState: hasComplexListRowState,
|
|
230
|
+
nested: hasNestedLists,
|
|
231
|
+
selectors: hasCollectionSelectors,
|
|
232
|
+
calculated: hasCalculatedCollections,
|
|
233
|
+
static: hasStaticCollections,
|
|
234
|
+
indexes: hasListIndexes,
|
|
235
|
+
stableFastPaths: hasListStableFastPaths,
|
|
236
|
+
generalRowHooks: hasGeneralListRowHooks,
|
|
237
|
+
asyncParts: hasListAsyncParts,
|
|
238
|
+
mounts: hasListMounts
|
|
239
|
+
},
|
|
240
|
+
effects: { any: hasEffects, derivedDependencies: hasDerivedEffectDependencies, itemDependencies: hasItemDependencies, captures: hasEffectCaptures, navigable: hasNavigableEffects, navigableOwners: hasNavigableOwners },
|
|
241
|
+
captures: { nestedState: hasNestedStateCaptures, setter: hasSetterCaptures },
|
|
242
|
+
runtime: { shared: hasSharedRuntime, dependency: hasDependencyRuntime }
|
|
243
|
+
} = capabilityManifest
|
|
248
244
|
const runtimeName = usesDependencyRuntime => usesDependencyRuntime ? "kudzu-deps.js" : "kudzu.js"
|
|
249
245
|
for (const entry of pageEntries) {
|
|
250
246
|
const routeDirectory = join(outputDirectory, entry.route)
|
|
@@ -631,20 +627,6 @@ addEventListener("popstate", () => ${searchParams.length ? "initializeSearch(loc
|
|
|
631
627
|
${searchInitializer}${prefix}${pathname}${query}${suffix}${writer}`
|
|
632
628
|
}
|
|
633
629
|
|
|
634
|
-
function hasCaptureType(value, type) {
|
|
635
|
-
if (!value || typeof value !== "object") return false
|
|
636
|
-
if (value.type === type) return true
|
|
637
|
-
return (Array.isArray(value) ? value : Object.values(value)).some(entry => hasCaptureType(entry, type))
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
function hasNestedCaptureState(value, insideCapture = false) {
|
|
641
|
-
if (!value || typeof value !== "object") return false
|
|
642
|
-
if (value.type === "state") return insideCapture
|
|
643
|
-
if (value.type === "array") return value.value.some(entry => hasNestedCaptureState(entry, true))
|
|
644
|
-
if (value.type === "object") return value.value.some(([, entry]) => hasNestedCaptureState(entry, true))
|
|
645
|
-
return (Array.isArray(value) ? value : Object.values(value)).some(entry => hasNestedCaptureState(entry, false))
|
|
646
|
-
}
|
|
647
|
-
|
|
648
630
|
export function specializeRuntime(source, events, hasStateSeed) {
|
|
649
631
|
const specialized = specializeEvents(source, events)
|
|
650
632
|
if (hasStateSeed) return specialized
|
|
@@ -695,11 +677,7 @@ function escapeAttribute(value) {
|
|
|
695
677
|
|
|
696
678
|
async function compile(file, sourceFiles, sourceIndex, staticFiles, importedAssets, cssModules, base, workerReferences) {
|
|
697
679
|
const source = sourceIndex.get(file)
|
|
698
|
-
const
|
|
699
|
-
const effectHandlers = []
|
|
700
|
-
const reactiveBindings = []
|
|
701
|
-
const listExpressions = []
|
|
702
|
-
const clientImports = new Set()
|
|
680
|
+
const semantic = createSemanticArtifact()
|
|
703
681
|
const handlerPath = `handlers/${relative(sourceDirectory, file).replaceAll(sep, "/").replace(/\.(?:ts|tsx)$/, ".js")}`
|
|
704
682
|
const result = ts.transpileModule(source, {
|
|
705
683
|
fileName: file,
|
|
@@ -709,7 +687,7 @@ async function compile(file, sourceFiles, sourceIndex, staticFiles, importedAsse
|
|
|
709
687
|
jsx: ts.JsxEmit.ReactJSX,
|
|
710
688
|
jsxImportSource: "@kudzujs/core"
|
|
711
689
|
},
|
|
712
|
-
transformers: { before: [createKudzuTransformer(
|
|
690
|
+
transformers: { before: [createKudzuTransformer({ semantic, handlerUrl: assetPath(base, `assets/${handlerPath}`), file, sourceFiles, sourceIndex, staticFiles, importedAssets, cssModules, base, workerReferences })] },
|
|
713
691
|
reportDiagnostics: true
|
|
714
692
|
})
|
|
715
693
|
|
|
@@ -724,6 +702,7 @@ async function compile(file, sourceFiles, sourceIndex, staticFiles, importedAsse
|
|
|
724
702
|
await mkdir(resolve(output, ".."), { recursive: true })
|
|
725
703
|
await writeFile(output, result.outputText)
|
|
726
704
|
|
|
705
|
+
const { nativeHandlers, effectHandlers, reactiveBindings, listExpressions, clientImports } = semantic
|
|
727
706
|
if (!nativeHandlers.length && !effectHandlers.length && !reactiveBindings.length && !listExpressions.length) return undefined
|
|
728
707
|
const callbacks = [...nativeHandlers, ...effectHandlers]
|
|
729
708
|
const moduleSource = printHandlerModule({ callbacks, reactiveBindings, listExpressions, handlerPath })
|
|
@@ -819,94 +798,6 @@ function normalizeClsxSyntax(sourceFile, factory, context) {
|
|
|
819
798
|
return ts.visitNode(sourceFile, visitor)
|
|
820
799
|
}
|
|
821
800
|
|
|
822
|
-
function analyzeZustandStores(sourceFile) {
|
|
823
|
-
const createNames = new Set()
|
|
824
|
-
for (const statement of sourceFile.statements) {
|
|
825
|
-
if (!ts.isImportDeclaration(statement) || statement.importClause?.isTypeOnly || !ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== "zustand") continue
|
|
826
|
-
const bindings = statement.importClause?.namedBindings
|
|
827
|
-
if (statement.importClause?.name || !bindings || !ts.isNamedImports(bindings)) throw sourceNodeError(statement, sourceFile, "Zustand migration input requires a named create import")
|
|
828
|
-
for (const entry of bindings.elements) {
|
|
829
|
-
if (entry.isTypeOnly) continue
|
|
830
|
-
if ((entry.propertyName ?? entry.name).text !== "create") throw sourceNodeError(entry, sourceFile, "Only Zustand create is supported")
|
|
831
|
-
createNames.add(entry.name.text)
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
const stores = new Map()
|
|
835
|
-
if (!createNames.size) return stores
|
|
836
|
-
for (const statement of sourceFile.statements) {
|
|
837
|
-
if (!ts.isVariableStatement(statement) || !statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)) continue
|
|
838
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
839
|
-
if (!ts.isIdentifier(declaration.name) || !declaration.initializer || !ts.isCallExpression(declaration.initializer) || !ts.isIdentifier(declaration.initializer.expression) || !createNames.has(declaration.initializer.expression.text)) continue
|
|
840
|
-
const callback = declaration.initializer.arguments[0]
|
|
841
|
-
if (declaration.initializer.arguments.length !== 1 || !callback || (!ts.isArrowFunction(callback) && !ts.isFunctionExpression(callback)) || callback.parameters.length !== 1 || !ts.isIdentifier(callback.parameters[0].name) || callback.asteriskToken || callback.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) throw sourceNodeError(declaration.initializer, sourceFile, "Zustand create() requires one synchronous initializer with one set parameter")
|
|
842
|
-
const body = unwrapExpression(callback.body)
|
|
843
|
-
if (!ts.isObjectLiteralExpression(body)) throw sourceNodeError(callback.body, sourceFile, "Zustand create() initializer must return one object literal")
|
|
844
|
-
const data = []
|
|
845
|
-
const actions = new Map()
|
|
846
|
-
for (const property of body.properties) {
|
|
847
|
-
if (!ts.isPropertyAssignment(property) || !property.name || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name))) throw sourceNodeError(property, sourceFile, "Zustand store entries must be ordinary properties")
|
|
848
|
-
const name = property.name.text
|
|
849
|
-
const value = unwrapExpression(property.initializer)
|
|
850
|
-
if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) actions.set(name, value)
|
|
851
|
-
else data.push({ name, value })
|
|
852
|
-
}
|
|
853
|
-
if (data.length !== 1 || !isSerializableStateLiteral(data[0].value)) throw sourceNodeError(body, sourceFile, "Zustand migration stores require exactly one directly serializable data property")
|
|
854
|
-
if (!actions.size) throw sourceNodeError(body, sourceFile, "Zustand migration stores require at least one action")
|
|
855
|
-
for (const [name, action] of actions) {
|
|
856
|
-
if (action.asteriskToken || action.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) throw sourceNodeError(action, sourceFile, `Zustand action ${JSON.stringify(name)} must be synchronous`)
|
|
857
|
-
const capture = [...nativeCaptureNames(action, new Map())].find(entry => entry !== callback.parameters[0].name.text)
|
|
858
|
-
if (capture) throw sourceNodeError(action, sourceFile, `Zustand action ${JSON.stringify(name)} cannot capture ${JSON.stringify(capture)}`)
|
|
859
|
-
const validateAction = node => {
|
|
860
|
-
if (ts.isAwaitExpression(node) || ts.isYieldExpression(node) || ts.isNewExpression(node)) throw sourceNodeError(node, sourceFile, `Zustand action ${JSON.stringify(name)} must be synchronous`)
|
|
861
|
-
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ["then", "catch", "finally"].includes(node.expression.name.text)) throw sourceNodeError(node, sourceFile, `Zustand action ${JSON.stringify(name)} cannot schedule asynchronous updates`)
|
|
862
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === callback.parameters[0].name.text && !isShadowedIdentifier(node.expression, action)) {
|
|
863
|
-
if (nearestFunction(node) !== action) throw sourceNodeError(node, sourceFile, `Zustand action ${JSON.stringify(name)} must call set directly`)
|
|
864
|
-
if (node.arguments.length !== 1) throw sourceNodeError(node, sourceFile, `Zustand action ${JSON.stringify(name)} set() requires exactly one partial update`)
|
|
865
|
-
}
|
|
866
|
-
ts.forEachChild(node, validateAction)
|
|
867
|
-
}
|
|
868
|
-
validateAction(action.body)
|
|
869
|
-
}
|
|
870
|
-
stores.set(declaration.name.text, { name: declaration.name.text, setName: callback.parameters[0].name.text, field: data[0].name, initialValue: data[0].value, actions, declaration })
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
const visit = node => {
|
|
874
|
-
const recognized = ts.isIdentifier(node) && ts.isCallExpression(node.parent) && node.parent.expression === node && [...stores.values()].some(store => store.declaration.initializer === node.parent)
|
|
875
|
-
if (ts.isIdentifier(node) && createNames.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, sourceFile) && !recognized) throw sourceNodeError(node, sourceFile, "Zustand create must directly initialize an exported const store")
|
|
876
|
-
ts.forEachChild(node, visit)
|
|
877
|
-
}
|
|
878
|
-
visit(sourceFile)
|
|
879
|
-
return stores
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
function normalizeZustandMigrationSyntax(sourceFile, factory, context) {
|
|
883
|
-
const stores = analyzeZustandStores(sourceFile)
|
|
884
|
-
if (!stores.size) {
|
|
885
|
-
const declaration = sourceFile.statements.find(statement => ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === "zustand" && !statement.importClause?.isTypeOnly)
|
|
886
|
-
if (declaration) throw sourceNodeError(declaration, sourceFile, "Zustand create must directly initialize an exported const store")
|
|
887
|
-
return sourceFile
|
|
888
|
-
}
|
|
889
|
-
const identity = name => `${relative(sourceDirectory, sourceFile.fileName).replaceAll(sep, "/")}#${name}`
|
|
890
|
-
const visitor = node => {
|
|
891
|
-
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && stores.has(node.name.text)) {
|
|
892
|
-
const store = stores.get(node.name.text)
|
|
893
|
-
return factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, factory.createCallExpression(factory.createIdentifier("__kCreateStore"), undefined, [
|
|
894
|
-
factory.createStringLiteral(identity(store.name)),
|
|
895
|
-
factory.createStringLiteral(store.field),
|
|
896
|
-
store.initialValue,
|
|
897
|
-
factory.createArrayLiteralExpression([...store.actions.keys()].map(name => factory.createStringLiteral(name)))
|
|
898
|
-
]))
|
|
899
|
-
}
|
|
900
|
-
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === "zustand") return undefined
|
|
901
|
-
return ts.visitEachChild(node, visitor, context)
|
|
902
|
-
}
|
|
903
|
-
const normalized = ts.visitNode(sourceFile, visitor)
|
|
904
|
-
const declaration = factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, undefined, factory.createIdentifier("__kCreateStore"))])), factory.createStringLiteral("@kudzujs/core"))
|
|
905
|
-
const statements = [...normalized.statements]
|
|
906
|
-
statements.splice(statements.findLastIndex(statement => ts.isImportDeclaration(statement)) + 1, 0, declaration)
|
|
907
|
-
return factory.updateSourceFile(normalized, statements)
|
|
908
|
-
}
|
|
909
|
-
|
|
910
801
|
function normalizeLazyStateInitializers(sourceFile, factory, context, file, sourceFiles, sourceIndex) {
|
|
911
802
|
const bindings = new Set()
|
|
912
803
|
for (const statement of sourceFile.statements) {
|
|
@@ -986,7 +877,8 @@ function normalizeCompilerSource(sourceFile, { base, context, file, importedColl
|
|
|
986
877
|
return { sourceFile, customHookTimerStates }
|
|
987
878
|
}
|
|
988
879
|
|
|
989
|
-
function createKudzuTransformer(
|
|
880
|
+
function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourceIndex, staticFiles, importedAssets, cssModules, base, workerReferences }) {
|
|
881
|
+
const { nativeHandlers, effectHandlers } = semantic
|
|
990
882
|
return context => sourceFile => {
|
|
991
883
|
const hasLinkElements = /<link/i.test(sourceFile.text)
|
|
992
884
|
const importedStaticCollections = importedSerializableCollections(sourceFile, file, sourceFiles, sourceIndex)
|
|
@@ -995,6 +887,15 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
995
887
|
sourceFile = normalized.sourceFile
|
|
996
888
|
const { customHookTimerStates } = normalized
|
|
997
889
|
const factory = context.factory
|
|
890
|
+
const descriptors = createDescriptorSession({
|
|
891
|
+
semantic,
|
|
892
|
+
handlerUrl,
|
|
893
|
+
factory,
|
|
894
|
+
context,
|
|
895
|
+
compileEventCommand,
|
|
896
|
+
isPrimitiveLiteral: isPrimitiveDefaultLiteral,
|
|
897
|
+
rejectWorkerConstructions: expression => workerCompiler.rejectConstructions(expression, expression.getSourceFile(), "Relative TypeScript Worker construction is only supported directly inside an inline useEffect() callback")
|
|
898
|
+
})
|
|
998
899
|
const importBindings = clientImportBindings(sourceFile, file, sourceFiles)
|
|
999
900
|
const packageBindings = packageImportBindings(sourceFile)
|
|
1000
901
|
for (const [name] of packageBindings) {
|
|
@@ -1408,14 +1309,14 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
1408
1309
|
const call = unwrapExpression(current.expression)
|
|
1409
1310
|
if (ts.isIdentifier(call.expression) && importBindings.has(call.expression.text) && importBindings.get(call.expression.text).kind !== "namespace") {
|
|
1410
1311
|
validateImportedCalculation(call, current.name.text)
|
|
1411
|
-
for (const argument of call.arguments) collectionExpression(argument, {
|
|
1312
|
+
for (const argument of call.arguments) collectionExpression(argument, { fail: (target, message) => fail(target, message.replace("Rendered collection", "Reactive imported calculation")), stateNames: allowedNames })
|
|
1412
1313
|
return factory.createNumericLiteral(0)
|
|
1413
1314
|
}
|
|
1414
1315
|
}
|
|
1415
1316
|
return ts.visitEachChild(current, validate, context)
|
|
1416
1317
|
}
|
|
1417
1318
|
const normalized = ts.visitNode(value, validate)
|
|
1418
|
-
collectionExpression(normalized, {
|
|
1319
|
+
collectionExpression(normalized, { fail: (node, message) => fail(node, message.replace("Rendered collection", "Reactive JSX local")), stateNames: allowedNames })
|
|
1419
1320
|
return
|
|
1420
1321
|
}
|
|
1421
1322
|
const intl = constructor.expression.expression
|
|
@@ -1425,7 +1326,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
1425
1326
|
const roundAccess = rounded && ts.isCallExpression(rounded) && !rounded.questionDotToken && rounded.arguments.length === 1 && ts.isPropertyAccessExpression(rounded.expression) && !rounded.expression.questionDotToken && rounded.expression.name.text === "round" && ts.isIdentifier(rounded.expression.expression) && rounded.expression.expression.text === "Math" ? rounded.expression : undefined
|
|
1426
1327
|
if (!roundAccess) fail(value, "Reactive JSX Intl.NumberFormat format() requires exactly Math.round(expression)")
|
|
1427
1328
|
if (!isUnshadowedGlobal(roundAccess.expression, sourceFile)) fail(roundAccess.expression, "Reactive JSX Intl.NumberFormat requires the unshadowed global Math object")
|
|
1428
|
-
collectionExpression(rounded.arguments[0], {
|
|
1329
|
+
collectionExpression(rounded.arguments[0], { fail: (node, message) => fail(node, message.replace("Rendered collection", "Reactive JSX local")), stateNames: allowedNames })
|
|
1429
1330
|
}
|
|
1430
1331
|
const resolveReactiveJsxExpression = (expression, owner, setters) => {
|
|
1431
1332
|
const declarations = jsxLocalDeclarations.get(owner)
|
|
@@ -1997,16 +1898,12 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
1997
1898
|
if (!usedStates.size && !captures.size) return ts.visitEachChild(expression, visitor, context)
|
|
1998
1899
|
usesBehavior = true
|
|
1999
1900
|
usesConditional = true
|
|
2000
|
-
return compileConditional(
|
|
1901
|
+
return descriptors.compileConditional(
|
|
2001
1902
|
parts.kind,
|
|
2002
1903
|
parts.condition,
|
|
2003
1904
|
compileRenderExpression(parts.truthy, anchor),
|
|
2004
1905
|
compileRenderExpression(parts.falsy, anchor),
|
|
2005
|
-
setters
|
|
2006
|
-
factory,
|
|
2007
|
-
context,
|
|
2008
|
-
reactiveBindings,
|
|
2009
|
-
handlerUrl
|
|
1906
|
+
setters
|
|
2010
1907
|
)
|
|
2011
1908
|
}
|
|
2012
1909
|
|
|
@@ -2106,7 +2003,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
2106
2003
|
const derivedStates = initializer && !directAlias ? referencedStateNames(initializer, setters) : new Set()
|
|
2107
2004
|
if (derivedStates.size) {
|
|
2108
2005
|
const usedStates = new Set()
|
|
2109
|
-
const expression = collectionExpression(initializer, {
|
|
2006
|
+
const expression = collectionExpression(initializer, { fail: effectFail, stateNames, selectorStates: usedStates })
|
|
2110
2007
|
if (!usedStates.size) effectFail(dependency, `useEffect() derived dependency "${dependency.text}" must read direct primitive state`)
|
|
2111
2008
|
dependencyExpressions.push(expression)
|
|
2112
2009
|
for (const name of usedStates) {
|
|
@@ -2162,7 +2059,15 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
2162
2059
|
} else {
|
|
2163
2060
|
compiledCallback = workerCompiler.rewriteEffect(compiledCallback, callbackFile, callbackSource, sourceFiles, workerReferences, factory, context)
|
|
2164
2061
|
}
|
|
2165
|
-
const descriptor =
|
|
2062
|
+
const descriptor = descriptors.compileEffectCallback(compiledCallback, {
|
|
2063
|
+
setters,
|
|
2064
|
+
reducers: reducersForNode(node, reducersByFunction),
|
|
2065
|
+
importBindings: specializedEffect?.imports ?? importBindings,
|
|
2066
|
+
listItem: dependencyItem,
|
|
2067
|
+
deferValues: true,
|
|
2068
|
+
snapshotNested: returns.cleanup,
|
|
2069
|
+
liveStates: customHookTimerStates
|
|
2070
|
+
})
|
|
2166
2071
|
for (const reference of workerReferences.slice(workerStart)) Object.assign(reference, { module: handlerUrl, handler: descriptor.exportName })
|
|
2167
2072
|
usesListItem ||= Boolean(itemDependencies.length && !listEffect)
|
|
2168
2073
|
usesBehavior = true
|
|
@@ -2218,19 +2123,19 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
2218
2123
|
|
|
2219
2124
|
if (ts.isJsxExpression(node) && node.expression && listConditions.has(node.expression)) {
|
|
2220
2125
|
const entry = listConditions.get(node.expression)
|
|
2221
|
-
return factory.updateJsxExpression(node, compileListConditional({
|
|
2126
|
+
return factory.updateJsxExpression(node, descriptors.compileListConditional({
|
|
2222
2127
|
...entry,
|
|
2223
2128
|
truthy: ts.visitNode(entry.truthy, visitor),
|
|
2224
2129
|
falsy: ts.visitNode(entry.falsy, visitor)
|
|
2225
|
-
}
|
|
2130
|
+
}))
|
|
2226
2131
|
}
|
|
2227
2132
|
|
|
2228
2133
|
if (ts.isJsxExpression(node) && node.expression && listValues.has(node.expression)) {
|
|
2229
|
-
return factory.updateJsxExpression(node, compileListValue(node.expression, listValues.get(node.expression)
|
|
2134
|
+
return factory.updateJsxExpression(node, descriptors.compileListValue(node.expression, listValues.get(node.expression)))
|
|
2230
2135
|
}
|
|
2231
2136
|
|
|
2232
2137
|
if (ts.isJsxAttribute(node) && node.initializer && ts.isJsxExpression(node.initializer) && node.initializer.expression && listValues.has(node.initializer.expression)) {
|
|
2233
|
-
return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, compileListValue(node.initializer.expression, listValues.get(node.initializer.expression)
|
|
2138
|
+
return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, descriptors.compileListValue(node.initializer.expression, listValues.get(node.initializer.expression))))
|
|
2234
2139
|
}
|
|
2235
2140
|
|
|
2236
2141
|
if (ts.isJsxExpression(node) && node.initializer === undefined && node.expression && (ts.isJsxElement(node.parent) || ts.isJsxFragment(node.parent))) {
|
|
@@ -2242,7 +2147,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
2242
2147
|
let listSource = listParts.state
|
|
2243
2148
|
if (listParts.calculation) {
|
|
2244
2149
|
usesBinding = true
|
|
2245
|
-
listSource = compileReactiveBinding(listParts.calculation, settersForNode(node, settersByFunction),
|
|
2150
|
+
listSource = descriptors.compileReactiveBinding(listParts.calculation, { setters: settersForNode(node, settersByFunction), importBindings })
|
|
2246
2151
|
}
|
|
2247
2152
|
const arguments_ = [
|
|
2248
2153
|
listSource,
|
|
@@ -2268,7 +2173,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
2268
2173
|
if ((usedStates.size || captures.size) && !ts.isIdentifier(expression) && !containsJsx(expression)) {
|
|
2269
2174
|
usesBehavior = true
|
|
2270
2175
|
usesBinding = true
|
|
2271
|
-
return factory.updateJsxExpression(node, compileReactiveBinding(expression, setters,
|
|
2176
|
+
return factory.updateJsxExpression(node, descriptors.compileReactiveBinding(expression, { setters, importBindings }))
|
|
2272
2177
|
}
|
|
2273
2178
|
}
|
|
2274
2179
|
|
|
@@ -2281,14 +2186,20 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
2281
2186
|
if ((usedStates.size || captures.size) && !ts.isIdentifier(expression)) {
|
|
2282
2187
|
usesBehavior = true
|
|
2283
2188
|
usesBinding = true
|
|
2284
|
-
const compiled = compileReactiveBinding(expression, setters,
|
|
2189
|
+
const compiled = descriptors.compileReactiveBinding(expression, { setters, importBindings })
|
|
2285
2190
|
return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, compiled))
|
|
2286
2191
|
}
|
|
2287
2192
|
}
|
|
2288
2193
|
|
|
2289
2194
|
if (ts.isJsxAttribute(node) && node.initializer && ts.isJsxExpression(node.initializer) && node.initializer.expression && /^on[A-Z]/.test(node.name.text)) {
|
|
2290
2195
|
const setters = settersForNode(node, settersByFunction)
|
|
2291
|
-
const event = compileEvent(node.initializer.expression,
|
|
2196
|
+
const event = descriptors.compileEvent(node.initializer.expression, {
|
|
2197
|
+
setters,
|
|
2198
|
+
reducers: reducersForNode(node, reducersByFunction),
|
|
2199
|
+
functions: functionsForNode(node),
|
|
2200
|
+
listItem: listEventItems.get(node),
|
|
2201
|
+
importBindings: new Map([...importBindings, ...packageBindings])
|
|
2202
|
+
})
|
|
2292
2203
|
if (event) {
|
|
2293
2204
|
usesBehavior = true
|
|
2294
2205
|
return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, event))
|
|
@@ -2356,7 +2267,9 @@ function keyedListParts(expression, setters, declarations, fail, aliases = new S
|
|
|
2356
2267
|
const value = unwrapExpression(expression)
|
|
2357
2268
|
const directFrom = isArrayFromCall(value) && value.arguments.length === 2 && containsJsx(value.arguments[1])
|
|
2358
2269
|
if (!directFrom && (!ts.isCallExpression(value) || value.arguments.length !== 1 || !ts.isPropertyAccessExpression(value.expression) || value.expression.name.text !== "map")) return undefined
|
|
2359
|
-
let collection =
|
|
2270
|
+
let collection = analyzeCollectionPipeline(directFrom ? value.arguments[0] : value.expression.expression, {
|
|
2271
|
+
setters, declarations, fail, aliases, importedCollections, stateNames: new Set(setters.values()), importedCollectionTransforms, calculatedCollection, staticCollection
|
|
2272
|
+
})
|
|
2360
2273
|
if (!collection?.state && !collection?.calculation) return undefined
|
|
2361
2274
|
if (directFrom) collection.selector.push(["from", undefined])
|
|
2362
2275
|
let callback = directFrom ? value.arguments[1] : value.arguments[0]
|
|
@@ -2366,7 +2279,7 @@ function keyedListParts(expression, setters, declarations, fail, aliases = new S
|
|
|
2366
2279
|
if (!context || root.statements.length !== 2 || !ts.isVariableStatement(root.statements[0]) || (root.statements[0].declarationList.flags & ts.NodeFlags.Const) === 0 || root.statements[0].declarationList.declarations.length !== 1 || !ts.isReturnStatement(root.statements[1]) || !root.statements[1].expression) fail(root, "Block-bodied keyed list map callbacks require one computed child collection const and a final JSX return")
|
|
2367
2280
|
const declaration = root.statements[0].declarationList.declarations[0]
|
|
2368
2281
|
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) fail(declaration, "Computed child collections must initialize one const identifier")
|
|
2369
|
-
const computed =
|
|
2282
|
+
const computed = analyzeCollectionPipeline(declaration.initializer, { fail, importedCollectionTransforms })
|
|
2370
2283
|
if (!computed?.ownerField || computed.parentItem !== parameters.item) fail(declaration.initializer, `Computed child collections must start from ${parameters.item}.<field>`)
|
|
2371
2284
|
const returned = root.statements[1].expression
|
|
2372
2285
|
if (identifierReferenceCount(returned, declaration.name.text) !== 1) fail(declaration.name, `Computed child collection alias "${declaration.name.text}" must be used exactly once`)
|
|
@@ -2390,7 +2303,7 @@ function keyedListParts(expression, setters, declarations, fail, aliases = new S
|
|
|
2390
2303
|
function nestedKeyedListParts(expression, parentItem, fail) {
|
|
2391
2304
|
const value = unwrapExpression(expression)
|
|
2392
2305
|
if (!ts.isCallExpression(value) || value.arguments.length !== 1 || !ts.isPropertyAccessExpression(value.expression) || value.expression.name.text !== "map") return undefined
|
|
2393
|
-
let collection =
|
|
2306
|
+
let collection = analyzeCollectionPipeline(value.expression.expression, { fail })
|
|
2394
2307
|
if (!collection?.ownerField || collection.parentItem !== parentItem) return undefined
|
|
2395
2308
|
let callback = value.arguments[0]
|
|
2396
2309
|
const parameters = collectionParameters(callback, "Nested keyed list map", fail)
|
|
@@ -2422,175 +2335,13 @@ function conditionalKeyedMapRoot(callback, root, parameters, collection, fail, s
|
|
|
2422
2335
|
}
|
|
2423
2336
|
if (parameters.index) fail(callback.parameters[1], "Conditional keyed map callbacks cannot use a map index because filtering changes index semantics; use an explicit filter(...).map((item, index) => ...) when a filtered index is intended")
|
|
2424
2337
|
const selectorStates = new Set(collection.selectorStates)
|
|
2425
|
-
const selector = collectionExpression(condition, parameters, fail, stateNames, selectorStates)
|
|
2338
|
+
const selector = collectionExpression(condition, { parameters, fail, stateNames, selectorStates })
|
|
2426
2339
|
const normalized = factory.updateArrowFunction(callback, callback.modifiers, callback.typeParameters, callback.parameters, callback.type, callback.equalsGreaterThanToken, rendered)
|
|
2427
2340
|
ts.setParentRecursive(normalized, false)
|
|
2428
2341
|
normalized.parent = parent
|
|
2429
2342
|
return { callback: normalized, root: rendered, collection: { ...collection, selector: [...collection.selector, ["filter", selector]], selectorStates } }
|
|
2430
2343
|
}
|
|
2431
2344
|
|
|
2432
|
-
function renderedCollectionSource(expression, setters, declarations, fail, aliases, importedCollections = new Set(), stateNames = new Set(), importedCollectionTransforms = new Map(), factory = ts.factory, context, calculatedCollection, staticCollection) {
|
|
2433
|
-
const value = unwrapExpression(expression)
|
|
2434
|
-
if (ts.isIdentifier(value)) {
|
|
2435
|
-
if ([...setters.values()].includes(value.text)) {
|
|
2436
|
-
const localStatic = staticCollection?.(value.text)
|
|
2437
|
-
return { state: value, static: localStatic, localStatic, selector: [], selectorStates: new Set() }
|
|
2438
|
-
}
|
|
2439
|
-
if (importedCollections.has(value.text)) return { state: value, static: true, selector: [], selectorStates: new Set() }
|
|
2440
|
-
const entries = declarations?.get(value.text)
|
|
2441
|
-
if (!entries) return undefined
|
|
2442
|
-
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`)
|
|
2443
|
-
aliases.add(value.text)
|
|
2444
|
-
const source = renderedCollectionSource(entries[0].initializer, setters, declarations, fail, aliases, importedCollections, stateNames, importedCollectionTransforms, factory, context, calculatedCollection, staticCollection)
|
|
2445
|
-
aliases.delete(value.text)
|
|
2446
|
-
return source && { ...source, aliasDeclarations: [...(source.aliasDeclarations ?? []), entries[0].node], aliasUses: [...(source.aliasUses ?? []), value] }
|
|
2447
|
-
}
|
|
2448
|
-
if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression)) {
|
|
2449
|
-
const calculation = calculatedCollection?.(value)
|
|
2450
|
-
if (calculation) return { calculation, selector: [], selectorStates: new Set() }
|
|
2451
|
-
return { state: undefined, ownerField: value.name.text, selector: [], parentItem: value.expression.text }
|
|
2452
|
-
}
|
|
2453
|
-
if (ts.isCallExpression(value) && ts.isIdentifier(value.expression) && importedCollectionTransforms.has(value.expression.text)) {
|
|
2454
|
-
const transform = importedCollectionTransforms.get(value.expression.text)
|
|
2455
|
-
const parameter = transform.parameters[0]
|
|
2456
|
-
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`)
|
|
2457
|
-
const returned = ts.isBlock(transform.body)
|
|
2458
|
-
? transform.body.statements.length === 1 && ts.isReturnStatement(transform.body.statements[0]) ? transform.body.statements[0].expression : undefined
|
|
2459
|
-
: transform.body
|
|
2460
|
-
if (!returned) fail(value, `Imported collection transform "${value.expression.text}" must contain only one returned collection expression`)
|
|
2461
|
-
const transformSource = renderedCollectionSource(returned, new Map([[parameter.name.text, parameter.name.text]]), undefined, fail, new Set(), new Set(), new Set([parameter.name.text]))
|
|
2462
|
-
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`)
|
|
2463
|
-
const source = renderedCollectionSource(value.arguments[0], setters, declarations, fail, aliases, importedCollections, stateNames, importedCollectionTransforms, factory, context, calculatedCollection, staticCollection)
|
|
2464
|
-
if (!source) fail(value.arguments[0], `Imported collection transform "${value.expression.text}" requires a supported collection argument`)
|
|
2465
|
-
return { ...source, selector: [...source.selector, ...transformSource.selector] }
|
|
2466
|
-
}
|
|
2467
|
-
if (ts.isCallExpression(value) && ts.isPropertyAccessExpression(value.expression)) {
|
|
2468
|
-
const method = value.expression.name.text
|
|
2469
|
-
if (method === "filter") {
|
|
2470
|
-
if (value.arguments.length !== 1) fail(value, "Rendered collection filter() requires one inline predicate")
|
|
2471
|
-
const source = renderedCollectionSource(value.expression.expression, setters, declarations, fail, aliases, importedCollections, stateNames, importedCollectionTransforms, factory, context, calculatedCollection, staticCollection)
|
|
2472
|
-
if (!source) return undefined
|
|
2473
|
-
const parameters = collectionParameters(value.arguments[0], "Rendered collection filter()", fail)
|
|
2474
|
-
const selectorStates = new Set(source.selectorStates)
|
|
2475
|
-
return { ...source, selector: [...source.selector, ["filter", collectionExpression(unwrapExpression(value.arguments[0].body), parameters, fail, stateNames, selectorStates)]], selectorStates }
|
|
2476
|
-
}
|
|
2477
|
-
if (method === "flatMap") {
|
|
2478
|
-
if (value.arguments.length !== 1) fail(value, "Rendered collection flatMap() requires one inline projector")
|
|
2479
|
-
const source = renderedCollectionSource(value.expression.expression, setters, declarations, fail, aliases, importedCollections, stateNames, importedCollectionTransforms, factory, context, calculatedCollection, staticCollection)
|
|
2480
|
-
if (!source) return undefined
|
|
2481
|
-
const parameters = collectionParameters(value.arguments[0], "Rendered collection flatMap()", fail)
|
|
2482
|
-
const field = directProperty(value.arguments[0].body, parameters.item)
|
|
2483
|
-
if (!field) fail(value.arguments[0].body, `Rendered collection flatMap() projector must be ${parameters.item}.<field>`)
|
|
2484
|
-
if (["__proto__", "constructor", "prototype"].includes(field)) fail(value.arguments[0].body, `Rendered collection property "${field}" is not supported`)
|
|
2485
|
-
return { ...source, selector: [...source.selector, ["flatMap", field]] }
|
|
2486
|
-
}
|
|
2487
|
-
if (method === "slice") {
|
|
2488
|
-
if (value.arguments.length < 1 || value.arguments.length > 2) fail(value, "Rendered collection slice() requires a start and optional end")
|
|
2489
|
-
const source = renderedCollectionSource(value.expression.expression, setters, declarations, fail, aliases, importedCollections, stateNames, importedCollectionTransforms, factory, context, calculatedCollection, staticCollection)
|
|
2490
|
-
if (!source) return undefined
|
|
2491
|
-
const selectorStates = new Set(source.selectorStates)
|
|
2492
|
-
const start = collectionExpression(value.arguments[0], {}, fail, stateNames, selectorStates)
|
|
2493
|
-
const end = value.arguments[1] && collectionExpression(value.arguments[1], {}, fail, stateNames, selectorStates)
|
|
2494
|
-
return { ...source, selector: [...source.selector, ["slice", start, end]], selectorStates }
|
|
2495
|
-
}
|
|
2496
|
-
if (method === "toSorted") {
|
|
2497
|
-
if (value.arguments.length !== 1) fail(value, "Rendered collection toSorted() requires one inline comparator")
|
|
2498
|
-
const source = renderedCollectionSource(value.expression.expression, setters, declarations, fail, aliases, importedCollections, stateNames, importedCollectionTransforms, factory, context, calculatedCollection, staticCollection)
|
|
2499
|
-
if (!source) return undefined
|
|
2500
|
-
const comparator = value.arguments[0]
|
|
2501
|
-
const parameters = collectionParameters(comparator, "Rendered collection toSorted()", fail)
|
|
2502
|
-
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")
|
|
2503
|
-
const selectorStates = new Set(source.selectorStates)
|
|
2504
|
-
const expression = collectionExpression(comparator.body, parameters, fail, stateNames, selectorStates)
|
|
2505
|
-
return { ...source, selector: [...source.selector, ["sort", expression]], selectorStates }
|
|
2506
|
-
}
|
|
2507
|
-
if (method === "sort") fail(value, "Rendered collections cannot use mutating sort(); use toSorted()")
|
|
2508
|
-
}
|
|
2509
|
-
if (isArrayFromCall(value)) {
|
|
2510
|
-
if (value.arguments.length < 1 || value.arguments.length > 2) fail(value, "Rendered Array.from() requires an anchor and optional inline mapper")
|
|
2511
|
-
const source = renderedCollectionSource(value.arguments[0], setters, declarations, fail, aliases, importedCollections, stateNames, importedCollectionTransforms, factory, context, calculatedCollection, staticCollection)
|
|
2512
|
-
if (!source) return undefined
|
|
2513
|
-
let mapper
|
|
2514
|
-
if (value.arguments[1]) {
|
|
2515
|
-
const parameters = collectionParameters(value.arguments[1], "Rendered Array.from() mapper", fail)
|
|
2516
|
-
const selectorStates = new Set(source.selectorStates)
|
|
2517
|
-
mapper = collectionExpression(unwrapExpression(value.arguments[1].body), parameters, fail, stateNames, selectorStates)
|
|
2518
|
-
source.selectorStates = selectorStates
|
|
2519
|
-
}
|
|
2520
|
-
return { ...source, selector: [...source.selector, ["from", mapper]] }
|
|
2521
|
-
}
|
|
2522
|
-
}
|
|
2523
|
-
|
|
2524
|
-
function isArrayFromCall(value) {
|
|
2525
|
-
return ts.isCallExpression(value) && ts.isPropertyAccessExpression(value.expression) && ts.isIdentifier(value.expression.expression) && value.expression.expression.text === "Array" && value.expression.name.text === "from"
|
|
2526
|
-
}
|
|
2527
|
-
|
|
2528
|
-
function collectionParameters(callback, label, fail) {
|
|
2529
|
-
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`)
|
|
2530
|
-
return { item: callback.parameters[0].name.text, index: callback.parameters[1]?.name.text }
|
|
2531
|
-
}
|
|
2532
|
-
|
|
2533
|
-
function collectionExpression(expression, parameters, fail, stateNames = new Set(), selectorStates = new Set()) {
|
|
2534
|
-
const encode = node => {
|
|
2535
|
-
node = unwrapExpression(node)
|
|
2536
|
-
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isNumericLiteral(node)) return ["value", ts.isNumericLiteral(node) ? Number(node.text) : node.text]
|
|
2537
|
-
if (node.kind === ts.SyntaxKind.TrueKeyword) return ["value", true]
|
|
2538
|
-
if (node.kind === ts.SyntaxKind.FalseKeyword) return ["value", false]
|
|
2539
|
-
if (node.kind === ts.SyntaxKind.NullKeyword) return ["value", null]
|
|
2540
|
-
if (ts.isIdentifier(node)) {
|
|
2541
|
-
if (node.text === parameters.item) return ["item"]
|
|
2542
|
-
if (node.text === parameters.index) return ["index"]
|
|
2543
|
-
if (node.text === "undefined") return ["undefined"]
|
|
2544
|
-
if (stateNames.has(node.text)) {
|
|
2545
|
-
selectorStates.add(node.text)
|
|
2546
|
-
return ["state", node.text]
|
|
2547
|
-
}
|
|
2548
|
-
fail(node, `Rendered collection expression identifier "${node.text}" is not allowed`)
|
|
2549
|
-
}
|
|
2550
|
-
if (ts.isPropertyAccessExpression(node)) {
|
|
2551
|
-
if (["__proto__", "constructor", "prototype"].includes(node.name.text)) fail(node, `Rendered collection property "${node.name.text}" is not supported`)
|
|
2552
|
-
return ["get", encode(node.expression), node.name.text, Boolean(node.questionDotToken)]
|
|
2553
|
-
}
|
|
2554
|
-
if (ts.isElementAccessExpression(node)) {
|
|
2555
|
-
const key = node.argumentExpression
|
|
2556
|
-
if (!ts.isStringLiteral(key) && !ts.isNumericLiteral(key)) fail(node, "Rendered collection computed properties require a direct string or numeric literal key")
|
|
2557
|
-
if (ts.isStringLiteral(key) && ["__proto__", "constructor", "prototype"].includes(key.text)) fail(node, `Rendered collection property "${key.text}" is not supported`)
|
|
2558
|
-
return ["get", encode(node.expression), ts.isNumericLiteral(key) ? Number(key.text) : key.text, Boolean(node.questionDotToken)]
|
|
2559
|
-
}
|
|
2560
|
-
if (ts.isPrefixUnaryExpression(node)) {
|
|
2561
|
-
const operator = node.operator === ts.SyntaxKind.ExclamationToken ? "!" : node.operator === ts.SyntaxKind.PlusToken ? "+" : node.operator === ts.SyntaxKind.MinusToken ? "-" : undefined
|
|
2562
|
-
if (!operator) fail(node, "Rendered collection expression uses an unsupported unary operator")
|
|
2563
|
-
return ["unary", operator, encode(node.operand)]
|
|
2564
|
-
}
|
|
2565
|
-
if (ts.isTypeOfExpression(node)) return ["unary", "typeof", encode(node.expression)]
|
|
2566
|
-
if (ts.isBinaryExpression(node)) {
|
|
2567
|
-
const operator = node.operatorToken.getText()
|
|
2568
|
-
if (!new Set(["&&", "||", "??", "===", "!==", "==", "!=", "<", "<=", ">", ">=", "+", "-", "*", "/", "%"]).has(operator)) fail(node, `Rendered collection expression operator "${operator}" is not supported`)
|
|
2569
|
-
return ["binary", operator, encode(node.left), encode(node.right)]
|
|
2570
|
-
}
|
|
2571
|
-
if (ts.isConditionalExpression(node)) return ["conditional", encode(node.condition), encode(node.whenTrue), encode(node.whenFalse)]
|
|
2572
|
-
if (ts.isArrayLiteralExpression(node) && !node.elements.some(ts.isSpreadElement)) return ["array", ...node.elements.map(encode)]
|
|
2573
|
-
if (ts.isObjectLiteralExpression(node)) return ["object", ...node.properties.map(property => {
|
|
2574
|
-
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")
|
|
2575
|
-
return [property.name.text, encode(property.initializer)]
|
|
2576
|
-
})]
|
|
2577
|
-
if (ts.isTemplateExpression(node)) return ["template", [node.head.text, ...node.templateSpans.map(span => span.literal.text)], node.templateSpans.map(span => encode(span.expression))]
|
|
2578
|
-
if (ts.isCallExpression(node)) {
|
|
2579
|
-
if (ts.isIdentifier(node.expression) && ["Boolean", "Number", "String"].includes(node.expression.text)) return ["global", node.expression.text, ...node.arguments.map(encode)]
|
|
2580
|
-
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
2581
|
-
const method = node.expression.name.text
|
|
2582
|
-
if (ts.isIdentifier(node.expression.expression) && node.expression.expression.text === "Math" && pureMathMethods.has(method)) return ["math", method, ...node.arguments.map(encode)]
|
|
2583
|
-
if (pureListMethods.has(method)) return ["call", encode(node.expression.expression), method, ...node.arguments.map(encode)]
|
|
2584
|
-
if (mutatingListMethods.has(method)) fail(node, `Rendered collection expressions cannot call mutating method "${method}"`)
|
|
2585
|
-
}
|
|
2586
|
-
fail(node, "Rendered collection expressions cannot call arbitrary functions")
|
|
2587
|
-
}
|
|
2588
|
-
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")
|
|
2589
|
-
fail(node, "Rendered collection expression is not supported")
|
|
2590
|
-
}
|
|
2591
|
-
return encode(expression)
|
|
2592
|
-
}
|
|
2593
|
-
|
|
2594
2345
|
function jsonExpression(value, factory) {
|
|
2595
2346
|
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("JSON"), "parse"), undefined, [factory.createStringLiteral(JSON.stringify(value))])
|
|
2596
2347
|
}
|
|
@@ -3218,9 +2969,6 @@ function jsxTagUses(root, name) {
|
|
|
3218
2969
|
return uses
|
|
3219
2970
|
}
|
|
3220
2971
|
|
|
3221
|
-
const pureListMethods = 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"])
|
|
3222
|
-
const mutatingListMethods = new Set(["copyWithin", "fill", "pop", "push", "reverse", "shift", "sort", "splice", "unshift"])
|
|
3223
|
-
const pureMathMethods = new Set(["abs", "ceil", "floor", "max", "min", "pow", "round", "sign", "sqrt", "trunc"])
|
|
3224
2972
|
const pureListGlobals = new Set(["Boolean", "Infinity", "Math", "NaN", "Number", "String", "undefined"])
|
|
3225
2973
|
const assignmentOperators = new Set([
|
|
3226
2974
|
ts.SyntaxKind.EqualsToken, ts.SyntaxKind.PlusEqualsToken, ts.SyntaxKind.MinusEqualsToken,
|
|
@@ -3270,37 +3018,6 @@ function validateListExpression(expression, item, source, fail, index, states =
|
|
|
3270
3018
|
visit(expression)
|
|
3271
3019
|
}
|
|
3272
3020
|
|
|
3273
|
-
function compileListExpression(read, expression, item, factory, listExpressions, handlerUrl, index, states = new Set()) {
|
|
3274
|
-
const exportName = `listExpression${listExpressions.length}`
|
|
3275
|
-
listExpressions.push({ exportName, expression, item, index, states })
|
|
3276
|
-
const arguments_ = [read, factory.createStringLiteral(handlerUrl), factory.createStringLiteral(exportName)]
|
|
3277
|
-
if (states.size) arguments_.push(factory.createArrayLiteralExpression([...states].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)]))))
|
|
3278
|
-
return factory.createCallExpression(factory.createIdentifier("__kListExpression"), undefined, arguments_)
|
|
3279
|
-
}
|
|
3280
|
-
|
|
3281
|
-
function compileListConditional(entry, factory, listExpressions, handlerUrl) {
|
|
3282
|
-
const exportName = `listExpression${listExpressions.length}`
|
|
3283
|
-
listExpressions.push({ exportName, expression: entry.condition, item: entry.item, index: entry.index })
|
|
3284
|
-
const read = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), entry.condition)
|
|
3285
|
-
const thunk = branch => factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), branch)
|
|
3286
|
-
return factory.createCallExpression(factory.createIdentifier("__kListConditional"), undefined, [
|
|
3287
|
-
factory.createStringLiteral(entry.kind), read, thunk(entry.truthy), thunk(entry.falsy), factory.createStringLiteral(handlerUrl), factory.createStringLiteral(exportName)
|
|
3288
|
-
])
|
|
3289
|
-
}
|
|
3290
|
-
|
|
3291
|
-
function compileListValue(expression, entry, factory, context, listExpressions, handlerUrl) {
|
|
3292
|
-
const rewrite = node => {
|
|
3293
|
-
if (ts.isShorthandPropertyAssignment(node) && entry.states?.has(node.name.text)) return factory.createPropertyAssignment(node.name, factory.createPropertyAccessExpression(node.name, "value"))
|
|
3294
|
-
if (ts.isIdentifier(node) && entry.states?.has(node.text) && isReferenceIdentifier(node)) return factory.createPropertyAccessExpression(node, "value")
|
|
3295
|
-
return ts.visitEachChild(node, rewrite, context)
|
|
3296
|
-
}
|
|
3297
|
-
const initial = entry.states?.size ? ts.visitNode(expression, rewrite) : expression
|
|
3298
|
-
const read = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), initial)
|
|
3299
|
-
return entry.field
|
|
3300
|
-
? factory.createCallExpression(factory.createIdentifier("__kListField"), undefined, [read, factory.createStringLiteral(entry.field)])
|
|
3301
|
-
: compileListExpression(read, expression, entry.item, factory, listExpressions, handlerUrl, entry.index, entry.states)
|
|
3302
|
-
}
|
|
3303
|
-
|
|
3304
3021
|
function directProperty(expression, objectName) {
|
|
3305
3022
|
const value = unwrapExpression(expression)
|
|
3306
3023
|
if (!ts.isPropertyAccessExpression(value) || !ts.isIdentifier(value.expression)) return undefined
|
|
@@ -3337,69 +3054,6 @@ function isJsxLocalValue(expression, known) {
|
|
|
3337
3054
|
return Boolean(parts && (isJsxLocalValue(parts.truthy, known) || isJsxLocalValue(parts.falsy, known)))
|
|
3338
3055
|
}
|
|
3339
3056
|
|
|
3340
|
-
function compileReactiveBinding(expression, setters, factory, context, reactiveBindings, handlerUrl, importBindings = new Map(), clientImports = new Set()) {
|
|
3341
|
-
const parts = conditionalParts(expression)
|
|
3342
|
-
const state = parts && directStateIdentifier(parts.condition, setters)
|
|
3343
|
-
if (state && isPrimitiveDefaultLiteral(parts.truthy) && isPrimitiveDefaultLiteral(parts.falsy)) {
|
|
3344
|
-
return factory.createCallExpression(factory.createIdentifier("__kSelect"), undefined, [state, parts.truthy, parts.falsy])
|
|
3345
|
-
}
|
|
3346
|
-
return factory.createCallExpression(factory.createIdentifier("__kBinding"), undefined, compileReactiveExpression(expression, setters, factory, context, reactiveBindings, handlerUrl, importBindings, clientImports))
|
|
3347
|
-
}
|
|
3348
|
-
|
|
3349
|
-
function compileConditional(kind, expression, truthy, falsy, setters, factory, context, reactiveBindings, handlerUrl) {
|
|
3350
|
-
const state = directStateIdentifier(expression, setters)
|
|
3351
|
-
const thunk = branch => factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), branch)
|
|
3352
|
-
if (state) return factory.createCallExpression(factory.createIdentifier("__kStateConditional"), undefined, [factory.createStringLiteral(kind), state, thunk(truthy), thunk(falsy)])
|
|
3353
|
-
const [initial, ...descriptor] = compileReactiveExpression(expression, setters, factory, context, reactiveBindings, handlerUrl)
|
|
3354
|
-
return factory.createCallExpression(factory.createIdentifier("__kConditional"), undefined, [factory.createStringLiteral(kind), initial, thunk(truthy), thunk(falsy), ...descriptor])
|
|
3355
|
-
}
|
|
3356
|
-
|
|
3357
|
-
function directStateIdentifier(expression, setters) {
|
|
3358
|
-
const value = unwrapExpression(expression)
|
|
3359
|
-
return ts.isIdentifier(value) && new Set(setters.values()).has(value.text) ? value : undefined
|
|
3360
|
-
}
|
|
3361
|
-
|
|
3362
|
-
function compileReactiveExpression(expression, setters, factory, context, reactiveBindings, handlerUrl, importBindings = new Map(), clientImports = new Set()) {
|
|
3363
|
-
const usedStates = referencedStateNames(expression, setters)
|
|
3364
|
-
const importedNames = referencedImportedBindings(expression, importBindings)
|
|
3365
|
-
const imports = [...importedNames].map(name => importBindings.get(name))
|
|
3366
|
-
for (const entry of imports) if (!entry.package) clientImports.add(entry.target)
|
|
3367
|
-
const captures = new Set([...captureNames(expression, expression, setters)].filter(name => !importedNames.has(name)))
|
|
3368
|
-
const exportName = `binding${reactiveBindings.length}`
|
|
3369
|
-
reactiveBindings.push({ exportName, expression, captures, states: usedStates, imports })
|
|
3370
|
-
const states = [...usedStates].map(name => factory.createArrayLiteralExpression([
|
|
3371
|
-
factory.createStringLiteral(name),
|
|
3372
|
-
factory.createIdentifier(name)
|
|
3373
|
-
]))
|
|
3374
|
-
const scope = [...captures].map(name => factory.createArrayLiteralExpression([
|
|
3375
|
-
factory.createStringLiteral(name),
|
|
3376
|
-
factory.createIdentifier(name)
|
|
3377
|
-
]))
|
|
3378
|
-
const stateNames = new Set(usedStates)
|
|
3379
|
-
const rewriteInitial = node => {
|
|
3380
|
-
if (ts.isShorthandPropertyAssignment(node) && stateNames.has(node.name.text)) {
|
|
3381
|
-
return factory.createPropertyAssignment(node.name, factory.createPropertyAccessExpression(node.name, "value"))
|
|
3382
|
-
}
|
|
3383
|
-
if (ts.isIdentifier(node) && stateNames.has(node.text) && isReferenceIdentifier(node) && !isShadowedByParameter(node, expression)) {
|
|
3384
|
-
return factory.createPropertyAccessExpression(node, "value")
|
|
3385
|
-
}
|
|
3386
|
-
if (ts.isShorthandPropertyAssignment(node) && captures.has(node.name.text)) {
|
|
3387
|
-
return factory.createPropertyAssignment(node.name, factory.createCallExpression(factory.createIdentifier("__kBindingValue"), undefined, [node.name]))
|
|
3388
|
-
}
|
|
3389
|
-
if (ts.isIdentifier(node) && captures.has(node.text) && isReferenceIdentifier(node)) {
|
|
3390
|
-
return factory.createCallExpression(factory.createIdentifier("__kBindingValue"), undefined, [node])
|
|
3391
|
-
}
|
|
3392
|
-
return ts.visitEachChild(node, rewriteInitial, context)
|
|
3393
|
-
}
|
|
3394
|
-
return [
|
|
3395
|
-
ts.visitNode(expression, rewriteInitial),
|
|
3396
|
-
factory.createStringLiteral(handlerUrl),
|
|
3397
|
-
factory.createStringLiteral(exportName),
|
|
3398
|
-
factory.createArrayLiteralExpression(states),
|
|
3399
|
-
factory.createArrayLiteralExpression(scope)
|
|
3400
|
-
]
|
|
3401
|
-
}
|
|
3402
|
-
|
|
3403
3057
|
function conditionalParts(expression) {
|
|
3404
3058
|
const unwrap = node => ts.isParenthesizedExpression(node) ? unwrap(node.expression) : node
|
|
3405
3059
|
const value = unwrap(expression)
|
|
@@ -3416,138 +3070,6 @@ function factoryNull() {
|
|
|
3416
3070
|
return ts.factory.createNull()
|
|
3417
3071
|
}
|
|
3418
3072
|
|
|
3419
|
-
function compileEvent(expression, setters, reducers, functions, factory, nativeHandlers, handlerUrl, listItem, importBindings, clientImports) {
|
|
3420
|
-
if (ts.isIdentifier(expression)) expression = functions.get(expression.text)
|
|
3421
|
-
if (!expression || (!ts.isArrowFunction(expression) && !ts.isFunctionExpression(expression) && !ts.isFunctionDeclaration(expression))) return undefined
|
|
3422
|
-
|
|
3423
|
-
const optimized = referencedReducerDispatches(expression.body, reducers, expression).size ? undefined : compileOptimizedEvent(expression, setters, factory)
|
|
3424
|
-
if (optimized) return optimized
|
|
3425
|
-
|
|
3426
|
-
workerCompiler.rejectConstructions(expression, expression.getSourceFile(), "Relative TypeScript Worker construction is only supported directly inside an inline useEffect() callback")
|
|
3427
|
-
const descriptor = compileNativeCallback(expression, setters, reducers, factory, nativeHandlers, importBindings, clientImports, "handler", listItem)
|
|
3428
|
-
return factory.createCallExpression(factory.createIdentifier("__kNativeBehavior"), undefined, [
|
|
3429
|
-
factory.createStringLiteral(handlerUrl),
|
|
3430
|
-
factory.createStringLiteral(descriptor.exportName),
|
|
3431
|
-
descriptor.states,
|
|
3432
|
-
descriptor.scope
|
|
3433
|
-
])
|
|
3434
|
-
}
|
|
3435
|
-
|
|
3436
|
-
function compileNativeCallback(expression, setters, reducers, factory, entries, importBindings, clientImports, prefix, listItem, deferValues = false, snapshotNested = false, liveStates = new Set()) {
|
|
3437
|
-
const allCaptures = nativeCaptureNames(expression, setters)
|
|
3438
|
-
const usedReducers = referencedReducerDispatches(expression.body, reducers, expression)
|
|
3439
|
-
const imports = [...referencedImportedBindings(expression, importBindings)].map(name => importBindings.get(name))
|
|
3440
|
-
imports.push(...[...usedReducers].map(name => reducers.get(name).import).filter(Boolean))
|
|
3441
|
-
const captures = new Set([...allCaptures].filter(name => !importBindings.has(name) && !usedReducers.has(name)))
|
|
3442
|
-
for (const entry of imports) if (!entry.package) clientImports.add(entry.target)
|
|
3443
|
-
const usedStates = nativeStateNames(expression, setters)
|
|
3444
|
-
for (const name of usedReducers) {
|
|
3445
|
-
const reducer = reducers.get(name)
|
|
3446
|
-
if (reducer.contextAction) for (const state of referencedStateNames(reducer.contextAction.body, reducer.states, reducer.contextAction)) usedStates.add(state)
|
|
3447
|
-
}
|
|
3448
|
-
const exportName = `${prefix}${entries.length}`
|
|
3449
|
-
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 })
|
|
3450
|
-
const value = name => deferValues
|
|
3451
|
-
? factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createIdentifier(name))
|
|
3452
|
-
: factory.createIdentifier(name)
|
|
3453
|
-
return {
|
|
3454
|
-
exportName,
|
|
3455
|
-
states: factory.createArrayLiteralExpression([...usedStates].map(name => factory.createArrayLiteralExpression([
|
|
3456
|
-
factory.createStringLiteral(name),
|
|
3457
|
-
value(name)
|
|
3458
|
-
]))),
|
|
3459
|
-
scope: factory.createArrayLiteralExpression([...captures].map(name => factory.createArrayLiteralExpression([
|
|
3460
|
-
factory.createStringLiteral(name),
|
|
3461
|
-
name === (typeof listItem === "string" ? listItem : listItem?.item)
|
|
3462
|
-
? factory.createCallExpression(factory.createIdentifier("__kListItem"), undefined, [])
|
|
3463
|
-
: name === listItem?.index
|
|
3464
|
-
? factory.createCallExpression(factory.createIdentifier("__kListIndex"), undefined, [])
|
|
3465
|
-
: value(name)
|
|
3466
|
-
])))
|
|
3467
|
-
}
|
|
3468
|
-
}
|
|
3469
|
-
|
|
3470
|
-
function referencedReducerDispatches(root, reducers, scopeRoot = root) {
|
|
3471
|
-
const used = new Set()
|
|
3472
|
-
const visit = node => {
|
|
3473
|
-
if (ts.isIdentifier(node) && reducers.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, scopeRoot)) used.add(node.text)
|
|
3474
|
-
ts.forEachChild(node, visit)
|
|
3475
|
-
}
|
|
3476
|
-
visit(root)
|
|
3477
|
-
return used
|
|
3478
|
-
}
|
|
3479
|
-
|
|
3480
|
-
function nativeStateNames(expression, setters) {
|
|
3481
|
-
return referencedStateNames(expression.body, setters, expression)
|
|
3482
|
-
}
|
|
3483
|
-
|
|
3484
|
-
function referencedStateNames(root, setters, scopeRoot = root) {
|
|
3485
|
-
const stateNames = new Set(setters.values())
|
|
3486
|
-
const used = new Set()
|
|
3487
|
-
const visit = node => {
|
|
3488
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && setters.has(node.expression.text) && !isShadowedIdentifier(node.expression, scopeRoot)) used.add(setters.get(node.expression.text))
|
|
3489
|
-
if (ts.isIdentifier(node) && setters.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, scopeRoot)) used.add(setters.get(node.text))
|
|
3490
|
-
if (ts.isIdentifier(node) && stateNames.has(node.text) && isReferenceIdentifier(node) && !isShadowedByParameter(node, scopeRoot)) used.add(node.text)
|
|
3491
|
-
ts.forEachChild(node, visit)
|
|
3492
|
-
}
|
|
3493
|
-
visit(root)
|
|
3494
|
-
return used
|
|
3495
|
-
}
|
|
3496
|
-
|
|
3497
|
-
function compileOptimizedEvent(expression, setters, factory) {
|
|
3498
|
-
const statements = ts.isBlock(expression.body) ? expression.body.statements : [factory.createExpressionStatement(expression.body)]
|
|
3499
|
-
const commands = statements.map(statement => {
|
|
3500
|
-
if (!ts.isExpressionStatement(statement)) return undefined
|
|
3501
|
-
return compileEventCommand(statement.expression, setters, factory)
|
|
3502
|
-
})
|
|
3503
|
-
if (!commands.length || commands.some(command => !command)) return undefined
|
|
3504
|
-
|
|
3505
|
-
return factory.createCallExpression(factory.createIdentifier("__kBehavior"), undefined, [factory.createArrayLiteralExpression(commands)])
|
|
3506
|
-
}
|
|
3507
|
-
|
|
3508
|
-
const nativeGlobals = new Set([
|
|
3509
|
-
"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"
|
|
3510
|
-
])
|
|
3511
|
-
|
|
3512
|
-
function nativeCaptureNames(expression, setters) {
|
|
3513
|
-
return captureNames(expression, expression.body, setters)
|
|
3514
|
-
}
|
|
3515
|
-
|
|
3516
|
-
function referencedImportedBindings(expression, imports) {
|
|
3517
|
-
const names = new Set()
|
|
3518
|
-
const visit = node => {
|
|
3519
|
-
if (ts.isIdentifier(node) && imports.has(node.text) && isReferenceIdentifier(node)) names.add(node.text)
|
|
3520
|
-
ts.forEachChild(node, visit)
|
|
3521
|
-
}
|
|
3522
|
-
visit(expression.body ?? expression)
|
|
3523
|
-
return names
|
|
3524
|
-
}
|
|
3525
|
-
|
|
3526
|
-
function captureNames(declarationRoot, referenceRoot, setters) {
|
|
3527
|
-
const local = new Set()
|
|
3528
|
-
if (!isFunctionLike(declarationRoot)) {
|
|
3529
|
-
const collectDeclarations = node => {
|
|
3530
|
-
if (ts.isVariableDeclaration(node)) for (const name of bindingNames(node.name)) local.add(name)
|
|
3531
|
-
if (ts.isParameter(node)) for (const name of bindingNames(node.name)) local.add(name)
|
|
3532
|
-
if ((ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) && node.name) local.add(node.name.text)
|
|
3533
|
-
ts.forEachChild(node, collectDeclarations)
|
|
3534
|
-
}
|
|
3535
|
-
collectDeclarations(declarationRoot)
|
|
3536
|
-
}
|
|
3537
|
-
const stateNames = new Set(setters.values())
|
|
3538
|
-
const captures = new Set()
|
|
3539
|
-
const visit = node => {
|
|
3540
|
-
if (ts.isTypeNode(node)) return
|
|
3541
|
-
if (ts.isIdentifier(node)) {
|
|
3542
|
-
const declared = isFunctionLike(declarationRoot) ? isShadowedIdentifier(node, declarationRoot) : local.has(node.text)
|
|
3543
|
-
if (isReferenceIdentifier(node) && !declared && !setters.has(node.text) && !stateNames.has(node.text) && !nativeGlobals.has(node.text)) captures.add(node.text)
|
|
3544
|
-
}
|
|
3545
|
-
ts.forEachChild(node, visit)
|
|
3546
|
-
}
|
|
3547
|
-
visit(referenceRoot)
|
|
3548
|
-
return captures
|
|
3549
|
-
}
|
|
3550
|
-
|
|
3551
3073
|
function settersForNode(node, settersByFunction) {
|
|
3552
3074
|
for (let current = node.parent; current; current = current.parent) {
|
|
3553
3075
|
if (!ts.isFunctionDeclaration(current) && !ts.isFunctionExpression(current) && !ts.isArrowFunction(current)) continue
|
|
@@ -4059,42 +3581,6 @@ function relativeModulePath(from, to) {
|
|
|
4059
3581
|
return path.startsWith(".") ? path : `./${path}`
|
|
4060
3582
|
}
|
|
4061
3583
|
|
|
4062
|
-
function compileEventCommand(expression, setters, factory) {
|
|
4063
|
-
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)) {
|
|
4064
|
-
return command(factory, "log", expression.arguments[1], factory.createStringLiteral(expression.arguments[0].text))
|
|
4065
|
-
}
|
|
4066
|
-
|
|
4067
|
-
if (!ts.isCallExpression(expression) || !ts.isIdentifier(expression.expression) || expression.arguments.length !== 1) return undefined
|
|
4068
|
-
const stateName = setters.get(expression.expression.text)
|
|
4069
|
-
if (!stateName) return undefined
|
|
4070
|
-
|
|
4071
|
-
const state = factory.createIdentifier(stateName)
|
|
4072
|
-
const value = expression.arguments[0]
|
|
4073
|
-
if (ts.isBinaryExpression(value) && ts.isIdentifier(value.left) && value.left.text === stateName && ts.isNumericLiteral(value.right)) {
|
|
4074
|
-
if (value.operatorToken.kind !== ts.SyntaxKind.PlusToken && value.operatorToken.kind !== ts.SyntaxKind.MinusToken) return undefined
|
|
4075
|
-
return command(factory, "add", state, numericExpression(factory, Number(value.right.text), value.operatorToken.kind === ts.SyntaxKind.MinusToken))
|
|
4076
|
-
}
|
|
4077
|
-
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)) {
|
|
4078
|
-
if (value.body.operatorToken.kind !== ts.SyntaxKind.PlusToken && value.body.operatorToken.kind !== ts.SyntaxKind.MinusToken) return undefined
|
|
4079
|
-
return command(factory, "add", state, numericExpression(factory, Number(value.body.right.text), value.body.operatorToken.kind === ts.SyntaxKind.MinusToken))
|
|
4080
|
-
}
|
|
4081
|
-
if (isPrimitiveLiteral(value)) return command(factory, "set", state, synthesizeSerializableStateLiteral(value, factory))
|
|
4082
|
-
return undefined
|
|
4083
|
-
}
|
|
4084
|
-
|
|
4085
|
-
function command(factory, operation, state, value) {
|
|
4086
|
-
return factory.createArrayLiteralExpression([factory.createStringLiteral(operation), state, value])
|
|
4087
|
-
}
|
|
4088
|
-
|
|
4089
|
-
function isPrimitiveLiteral(node) {
|
|
4090
|
-
return isPrimitiveDefaultLiteral(node)
|
|
4091
|
-
}
|
|
4092
|
-
|
|
4093
|
-
function numericExpression(factory, value, negative) {
|
|
4094
|
-
const literal = factory.createNumericLiteral(value)
|
|
4095
|
-
return negative ? factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, literal) : literal
|
|
4096
|
-
}
|
|
4097
|
-
|
|
4098
3584
|
function compiledPath(file) {
|
|
4099
3585
|
return join(workDirectory, relative(sourceDirectory, file)).replace(/\.(?:ts|tsx)$/, ".mjs")
|
|
4100
3586
|
}
|
|
@@ -4262,7 +3748,7 @@ const printHandlerModule = createHandlerCodegen({
|
|
|
4262
3748
|
synthesizeTree,
|
|
4263
3749
|
resolveClientImport: (entry, handlerPath) => entry.package ? entry.target : relativeModulePath(handlerPath, clientModulePath(entry.target))
|
|
4264
3750
|
})
|
|
4265
|
-
const { normalizeReactMigrationSyntax, validateUseIdSyntax } = createReactMigrationPass({ cloneAst,
|
|
3751
|
+
const { normalizeReactMigrationSyntax, validateUseIdSyntax } = createReactMigrationPass({ cloneAst, jsxTagName })
|
|
4266
3752
|
const normalizeReactRouterSyntax = createRouterPass({ withBase })
|
|
4267
3753
|
|
|
4268
3754
|
async function staticPathEntries(module, file) {
|