@kudzujs/core 0.8.15 → 0.8.17

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.
@@ -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"
12
14
  import { createHandlerCodegen } from "./compiler/handler-codegen.mjs"
15
+ import { createCommandSpecializer } from "./compiler/optimize/command-specialization.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 = createCommandSpecializer({ isPrimitiveLiteral: isPrimitiveDefaultLiteral })
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 hasDependencies = result.plan.effects.some(effect => effect.dependencies?.length)
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 commandEvents = [...new Set(plans.flatMap(plan => plan.events.filter(event => event.commands).map(event => event.event)))].sort()
212
- const nativeEvents = [...new Set(plans.flatMap(plan => plan.events.filter(event => event.native).map(event => event.event)))].sort()
213
- const hasTextBindings = plans.some(plan => plan.bindings.some(binding => binding.target === "text"))
214
- const hasSvgConditions = plans.some(plan => plan.conditions.some(condition => condition.svg))
215
- const hasListConditions = plans.some(plan => plan.lists.some(list => list.conditions))
216
- const hasSvgLists = plans.some(plan => plan.lists.some(list => list.svg))
217
- const hasDeepListConditions = plans.some(plan => plan.lists.some(list => list.conditionHandlers))
218
- const hasListTextRanges = plans.some(plan => plan.lists.some(list => list.textRanges))
219
- const hasListAttributes = plans.some(plan => plan.lists.some(list => list.attributes))
220
- const hasListEvents = plans.some(plan => plan.lists.some(list => list.events))
221
- const hasListExpressions = plans.some(plan => plan.lists.some(list => list.expressions))
222
- const hasListExpressionAttributes = plans.some(plan => plan.lists.some(list => list.expressionAttributes))
223
- const hasListSeeds = plans.some(plan => plan.lists.some(list => list.seed || list.valueSeed))
224
- const hasListEffects = plans.some(plan => plan.lists.some(list => list.effects))
225
- const hasListRowHooks = plans.some(plan => plan.lists.some(list => list.rowStates?.length || list.rowRefs?.length))
226
- const hasListRowRefs = plans.some(plan => plan.lists.some(list => list.rowRefs?.length))
227
- const hasComplexListRowState = plans.some(plan => plan.lists.some(list => list.rowStates?.some(state => state.initialValue !== null && typeof state.initialValue === "object")))
228
- const hasNestedLists = plans.some(plan => plan.lists.some(list => list.ownerField))
229
- const hasCollectionSelectors = plans.some(plan => plan.lists.some(list => list.selector))
230
- const hasCalculatedCollections = plans.some(plan => plan.lists.some(list => list.source))
231
- const hasDerivedEffectDependencies = plans.some(plan => plan.effects.some(effect => effect.dependencyExpressions?.length))
232
- const hasStaticCollections = plans.some(plan => plan.lists.some(list => list.static))
233
- const hasListIndexes = plans.some(plan => plan.lists.some(list => list.indexed))
234
- const hasListStableFastPaths = plans.some(plan => plan.lists.some(list => !list.children && !list.ownerField && list.key !== null && !list.indexed && !list.reducer && !list.selector))
235
- const hasGeneralListRowHooks = hasListRowRefs || hasComplexListRowState || plans.some(plan => plan.lists.some(list => list.ownerField && (list.rowStates?.length || list.rowRefs?.length)))
236
- const hasItemDependencies = plans.some(plan => plan.effects.some(effect => effect.itemDependencies?.length))
237
- const hasListAsyncParts = hasListExpressions || hasListExpressionAttributes || hasListConditions
238
- const hasListMounts = hasListConditions || hasNestedLists || plans.some(plan => plan.lists.some(list => list.mount))
239
- const hasNestedStateCaptures = hasNestedCaptureState(plans)
240
- const hasSetterCaptures = hasCaptureType(plans, "setter")
241
- const hasEffectCaptures = plans.some(plan => plan.effects.some(effect => Object.keys(effect.scope).length))
242
- const hasNativeHandlers = nativeEntries.length > 0
243
- const hasEffects = effectEntries.length > 0
244
- const hasNavigableEffects = effectEntries.some(entry => entry.navigable)
245
- const hasNavigableOwners = effectEntries.some(entry => entry.navigable && entry.effects.some(effect => effect.owner))
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 nativeHandlers = []
699
- const effectHandlers = []
700
- const reactiveBindings = []
701
- const listExpressions = []
702
- const clientImports = new Set()
680
+ const semantic = createSemanticArtifact(relative(root, file).replaceAll(sep, "/"))
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(nativeHandlers, effectHandlers, reactiveBindings, listExpressions, assetPath(base, `assets/${handlerPath}`), file, sourceFiles, sourceIndex, staticFiles, importedAssets, cssModules, base, clientImports, workerReferences)] },
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(nativeHandlers, effectHandlers, reactiveBindings, listExpressions, handlerUrl, file, sourceFiles, sourceIndex, staticFiles, importedAssets, cssModules, base, clientImports, workerReferences) {
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,16 @@ 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
+ sourceName: source => relative(root, source.fileName).replaceAll(sep, "/"),
898
+ rejectWorkerConstructions: expression => workerCompiler.rejectConstructions(expression, expression.getSourceFile(), "Relative TypeScript Worker construction is only supported directly inside an inline useEffect() callback")
899
+ })
998
900
  const importBindings = clientImportBindings(sourceFile, file, sourceFiles)
999
901
  const packageBindings = packageImportBindings(sourceFile)
1000
902
  for (const [name] of packageBindings) {
@@ -1408,14 +1310,14 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
1408
1310
  const call = unwrapExpression(current.expression)
1409
1311
  if (ts.isIdentifier(call.expression) && importBindings.has(call.expression.text) && importBindings.get(call.expression.text).kind !== "namespace") {
1410
1312
  validateImportedCalculation(call, current.name.text)
1411
- for (const argument of call.arguments) collectionExpression(argument, {}, (target, message) => fail(target, message.replace("Rendered collection", "Reactive imported calculation")), allowedNames)
1313
+ for (const argument of call.arguments) collectionExpression(argument, { fail: (target, message) => fail(target, message.replace("Rendered collection", "Reactive imported calculation")), stateNames: allowedNames })
1412
1314
  return factory.createNumericLiteral(0)
1413
1315
  }
1414
1316
  }
1415
1317
  return ts.visitEachChild(current, validate, context)
1416
1318
  }
1417
1319
  const normalized = ts.visitNode(value, validate)
1418
- collectionExpression(normalized, {}, (node, message) => fail(node, message.replace("Rendered collection", "Reactive JSX local")), allowedNames)
1320
+ collectionExpression(normalized, { fail: (node, message) => fail(node, message.replace("Rendered collection", "Reactive JSX local")), stateNames: allowedNames })
1419
1321
  return
1420
1322
  }
1421
1323
  const intl = constructor.expression.expression
@@ -1425,7 +1327,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
1425
1327
  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
1328
  if (!roundAccess) fail(value, "Reactive JSX Intl.NumberFormat format() requires exactly Math.round(expression)")
1427
1329
  if (!isUnshadowedGlobal(roundAccess.expression, sourceFile)) fail(roundAccess.expression, "Reactive JSX Intl.NumberFormat requires the unshadowed global Math object")
1428
- collectionExpression(rounded.arguments[0], {}, (node, message) => fail(node, message.replace("Rendered collection", "Reactive JSX local")), allowedNames)
1330
+ collectionExpression(rounded.arguments[0], { fail: (node, message) => fail(node, message.replace("Rendered collection", "Reactive JSX local")), stateNames: allowedNames })
1429
1331
  }
1430
1332
  const resolveReactiveJsxExpression = (expression, owner, setters) => {
1431
1333
  const declarations = jsxLocalDeclarations.get(owner)
@@ -1997,16 +1899,12 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
1997
1899
  if (!usedStates.size && !captures.size) return ts.visitEachChild(expression, visitor, context)
1998
1900
  usesBehavior = true
1999
1901
  usesConditional = true
2000
- return compileConditional(
1902
+ return descriptors.compileConditional(
2001
1903
  parts.kind,
2002
1904
  parts.condition,
2003
1905
  compileRenderExpression(parts.truthy, anchor),
2004
1906
  compileRenderExpression(parts.falsy, anchor),
2005
- setters,
2006
- factory,
2007
- context,
2008
- reactiveBindings,
2009
- handlerUrl
1907
+ setters
2010
1908
  )
2011
1909
  }
2012
1910
 
@@ -2106,7 +2004,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
2106
2004
  const derivedStates = initializer && !directAlias ? referencedStateNames(initializer, setters) : new Set()
2107
2005
  if (derivedStates.size) {
2108
2006
  const usedStates = new Set()
2109
- const expression = collectionExpression(initializer, {}, effectFail, stateNames, usedStates)
2007
+ const expression = collectionExpression(initializer, { fail: effectFail, stateNames, selectorStates: usedStates })
2110
2008
  if (!usedStates.size) effectFail(dependency, `useEffect() derived dependency "${dependency.text}" must read direct primitive state`)
2111
2009
  dependencyExpressions.push(expression)
2112
2010
  for (const name of usedStates) {
@@ -2162,7 +2060,15 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
2162
2060
  } else {
2163
2061
  compiledCallback = workerCompiler.rewriteEffect(compiledCallback, callbackFile, callbackSource, sourceFiles, workerReferences, factory, context)
2164
2062
  }
2165
- const descriptor = compileNativeCallback(compiledCallback, setters, reducersForNode(node, reducersByFunction), factory, effectHandlers, specializedEffect?.imports ?? importBindings, clientImports, "effect", dependencyItem, true, returns.cleanup, customHookTimerStates)
2063
+ const descriptor = descriptors.compileEffectCallback(compiledCallback, {
2064
+ setters,
2065
+ reducers: reducersForNode(node, reducersByFunction),
2066
+ importBindings: specializedEffect?.imports ?? importBindings,
2067
+ listItem: dependencyItem,
2068
+ deferValues: true,
2069
+ snapshotNested: returns.cleanup,
2070
+ liveStates: customHookTimerStates
2071
+ })
2166
2072
  for (const reference of workerReferences.slice(workerStart)) Object.assign(reference, { module: handlerUrl, handler: descriptor.exportName })
2167
2073
  usesListItem ||= Boolean(itemDependencies.length && !listEffect)
2168
2074
  usesBehavior = true
@@ -2218,19 +2124,19 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
2218
2124
 
2219
2125
  if (ts.isJsxExpression(node) && node.expression && listConditions.has(node.expression)) {
2220
2126
  const entry = listConditions.get(node.expression)
2221
- return factory.updateJsxExpression(node, compileListConditional({
2127
+ return factory.updateJsxExpression(node, descriptors.compileListConditional({
2222
2128
  ...entry,
2223
2129
  truthy: ts.visitNode(entry.truthy, visitor),
2224
2130
  falsy: ts.visitNode(entry.falsy, visitor)
2225
- }, factory, listExpressions, handlerUrl))
2131
+ }))
2226
2132
  }
2227
2133
 
2228
2134
  if (ts.isJsxExpression(node) && node.expression && listValues.has(node.expression)) {
2229
- return factory.updateJsxExpression(node, compileListValue(node.expression, listValues.get(node.expression), factory, context, listExpressions, handlerUrl))
2135
+ return factory.updateJsxExpression(node, descriptors.compileListValue(node.expression, listValues.get(node.expression)))
2230
2136
  }
2231
2137
 
2232
2138
  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), factory, context, listExpressions, handlerUrl)))
2139
+ return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, descriptors.compileListValue(node.initializer.expression, listValues.get(node.initializer.expression))))
2234
2140
  }
2235
2141
 
2236
2142
  if (ts.isJsxExpression(node) && node.initializer === undefined && node.expression && (ts.isJsxElement(node.parent) || ts.isJsxFragment(node.parent))) {
@@ -2242,7 +2148,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
2242
2148
  let listSource = listParts.state
2243
2149
  if (listParts.calculation) {
2244
2150
  usesBinding = true
2245
- listSource = compileReactiveBinding(listParts.calculation, settersForNode(node, settersByFunction), factory, context, reactiveBindings, handlerUrl, importBindings, clientImports)
2151
+ listSource = descriptors.compileReactiveBinding(listParts.calculation, { setters: settersForNode(node, settersByFunction), importBindings })
2246
2152
  }
2247
2153
  const arguments_ = [
2248
2154
  listSource,
@@ -2268,7 +2174,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
2268
2174
  if ((usedStates.size || captures.size) && !ts.isIdentifier(expression) && !containsJsx(expression)) {
2269
2175
  usesBehavior = true
2270
2176
  usesBinding = true
2271
- return factory.updateJsxExpression(node, compileReactiveBinding(expression, setters, factory, context, reactiveBindings, handlerUrl, importBindings, clientImports))
2177
+ return factory.updateJsxExpression(node, descriptors.compileReactiveBinding(expression, { setters, importBindings }))
2272
2178
  }
2273
2179
  }
2274
2180
 
@@ -2281,14 +2187,20 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
2281
2187
  if ((usedStates.size || captures.size) && !ts.isIdentifier(expression)) {
2282
2188
  usesBehavior = true
2283
2189
  usesBinding = true
2284
- const compiled = compileReactiveBinding(expression, setters, factory, context, reactiveBindings, handlerUrl, importBindings, clientImports)
2190
+ const compiled = descriptors.compileReactiveBinding(expression, { setters, importBindings })
2285
2191
  return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, compiled))
2286
2192
  }
2287
2193
  }
2288
2194
 
2289
2195
  if (ts.isJsxAttribute(node) && node.initializer && ts.isJsxExpression(node.initializer) && node.initializer.expression && /^on[A-Z]/.test(node.name.text)) {
2290
2196
  const setters = settersForNode(node, settersByFunction)
2291
- const event = compileEvent(node.initializer.expression, setters, reducersForNode(node, reducersByFunction), functionsForNode(node), factory, nativeHandlers, handlerUrl, listEventItems.get(node), new Map([...importBindings, ...packageBindings]), clientImports)
2197
+ const event = descriptors.compileEvent(node.initializer.expression, {
2198
+ setters,
2199
+ reducers: reducersForNode(node, reducersByFunction),
2200
+ functions: functionsForNode(node),
2201
+ listItem: listEventItems.get(node),
2202
+ importBindings: new Map([...importBindings, ...packageBindings])
2203
+ })
2292
2204
  if (event) {
2293
2205
  usesBehavior = true
2294
2206
  return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, event))
@@ -2356,7 +2268,9 @@ function keyedListParts(expression, setters, declarations, fail, aliases = new S
2356
2268
  const value = unwrapExpression(expression)
2357
2269
  const directFrom = isArrayFromCall(value) && value.arguments.length === 2 && containsJsx(value.arguments[1])
2358
2270
  if (!directFrom && (!ts.isCallExpression(value) || value.arguments.length !== 1 || !ts.isPropertyAccessExpression(value.expression) || value.expression.name.text !== "map")) return undefined
2359
- let collection = renderedCollectionSource(directFrom ? value.arguments[0] : value.expression.expression, setters, declarations, fail, aliases, importedCollections, new Set(setters.values()), importedCollectionTransforms, factory, context, calculatedCollection, staticCollection)
2271
+ let collection = analyzeCollectionPipeline(directFrom ? value.arguments[0] : value.expression.expression, {
2272
+ setters, declarations, fail, aliases, importedCollections, stateNames: new Set(setters.values()), importedCollectionTransforms, calculatedCollection, staticCollection
2273
+ })
2360
2274
  if (!collection?.state && !collection?.calculation) return undefined
2361
2275
  if (directFrom) collection.selector.push(["from", undefined])
2362
2276
  let callback = directFrom ? value.arguments[1] : value.arguments[0]
@@ -2366,7 +2280,7 @@ function keyedListParts(expression, setters, declarations, fail, aliases = new S
2366
2280
  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
2281
  const declaration = root.statements[0].declarationList.declarations[0]
2368
2282
  if (!ts.isIdentifier(declaration.name) || !declaration.initializer) fail(declaration, "Computed child collections must initialize one const identifier")
2369
- const computed = renderedCollectionSource(declaration.initializer, new Map(), undefined, fail, new Set(), new Set(), new Set(), importedCollectionTransforms, factory, context)
2283
+ const computed = analyzeCollectionPipeline(declaration.initializer, { fail, importedCollectionTransforms })
2370
2284
  if (!computed?.ownerField || computed.parentItem !== parameters.item) fail(declaration.initializer, `Computed child collections must start from ${parameters.item}.<field>`)
2371
2285
  const returned = root.statements[1].expression
2372
2286
  if (identifierReferenceCount(returned, declaration.name.text) !== 1) fail(declaration.name, `Computed child collection alias "${declaration.name.text}" must be used exactly once`)
@@ -2390,7 +2304,7 @@ function keyedListParts(expression, setters, declarations, fail, aliases = new S
2390
2304
  function nestedKeyedListParts(expression, parentItem, fail) {
2391
2305
  const value = unwrapExpression(expression)
2392
2306
  if (!ts.isCallExpression(value) || value.arguments.length !== 1 || !ts.isPropertyAccessExpression(value.expression) || value.expression.name.text !== "map") return undefined
2393
- let collection = renderedCollectionSource(value.expression.expression, new Map(), undefined, fail, new Set())
2307
+ let collection = analyzeCollectionPipeline(value.expression.expression, { fail })
2394
2308
  if (!collection?.ownerField || collection.parentItem !== parentItem) return undefined
2395
2309
  let callback = value.arguments[0]
2396
2310
  const parameters = collectionParameters(callback, "Nested keyed list map", fail)
@@ -2422,175 +2336,13 @@ function conditionalKeyedMapRoot(callback, root, parameters, collection, fail, s
2422
2336
  }
2423
2337
  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
2338
  const selectorStates = new Set(collection.selectorStates)
2425
- const selector = collectionExpression(condition, parameters, fail, stateNames, selectorStates)
2339
+ const selector = collectionExpression(condition, { parameters, fail, stateNames, selectorStates })
2426
2340
  const normalized = factory.updateArrowFunction(callback, callback.modifiers, callback.typeParameters, callback.parameters, callback.type, callback.equalsGreaterThanToken, rendered)
2427
2341
  ts.setParentRecursive(normalized, false)
2428
2342
  normalized.parent = parent
2429
2343
  return { callback: normalized, root: rendered, collection: { ...collection, selector: [...collection.selector, ["filter", selector]], selectorStates } }
2430
2344
  }
2431
2345
 
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
2346
  function jsonExpression(value, factory) {
2595
2347
  return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("JSON"), "parse"), undefined, [factory.createStringLiteral(JSON.stringify(value))])
2596
2348
  }
@@ -3218,9 +2970,6 @@ function jsxTagUses(root, name) {
3218
2970
  return uses
3219
2971
  }
3220
2972
 
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
2973
  const pureListGlobals = new Set(["Boolean", "Infinity", "Math", "NaN", "Number", "String", "undefined"])
3225
2974
  const assignmentOperators = new Set([
3226
2975
  ts.SyntaxKind.EqualsToken, ts.SyntaxKind.PlusEqualsToken, ts.SyntaxKind.MinusEqualsToken,
@@ -3270,37 +3019,6 @@ function validateListExpression(expression, item, source, fail, index, states =
3270
3019
  visit(expression)
3271
3020
  }
3272
3021
 
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
3022
  function directProperty(expression, objectName) {
3305
3023
  const value = unwrapExpression(expression)
3306
3024
  if (!ts.isPropertyAccessExpression(value) || !ts.isIdentifier(value.expression)) return undefined
@@ -3337,69 +3055,6 @@ function isJsxLocalValue(expression, known) {
3337
3055
  return Boolean(parts && (isJsxLocalValue(parts.truthy, known) || isJsxLocalValue(parts.falsy, known)))
3338
3056
  }
3339
3057
 
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
3058
  function conditionalParts(expression) {
3404
3059
  const unwrap = node => ts.isParenthesizedExpression(node) ? unwrap(node.expression) : node
3405
3060
  const value = unwrap(expression)
@@ -3416,138 +3071,6 @@ function factoryNull() {
3416
3071
  return ts.factory.createNull()
3417
3072
  }
3418
3073
 
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
3074
  function settersForNode(node, settersByFunction) {
3552
3075
  for (let current = node.parent; current; current = current.parent) {
3553
3076
  if (!ts.isFunctionDeclaration(current) && !ts.isFunctionExpression(current) && !ts.isArrowFunction(current)) continue
@@ -4059,42 +3582,6 @@ function relativeModulePath(from, to) {
4059
3582
  return path.startsWith(".") ? path : `./${path}`
4060
3583
  }
4061
3584
 
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
3585
  function compiledPath(file) {
4099
3586
  return join(workDirectory, relative(sourceDirectory, file)).replace(/\.(?:ts|tsx)$/, ".mjs")
4100
3587
  }
@@ -4262,7 +3749,7 @@ const printHandlerModule = createHandlerCodegen({
4262
3749
  synthesizeTree,
4263
3750
  resolveClientImport: (entry, handlerPath) => entry.package ? entry.target : relativeModulePath(handlerPath, clientModulePath(entry.target))
4264
3751
  })
4265
- const { normalizeReactMigrationSyntax, validateUseIdSyntax } = createReactMigrationPass({ cloneAst, isArrayFromCall, jsxTagName, renderedCollectionSource })
3752
+ const { normalizeReactMigrationSyntax, validateUseIdSyntax } = createReactMigrationPass({ cloneAst, jsxTagName })
4266
3753
  const normalizeReactRouterSyntax = createRouterPass({ withBase })
4267
3754
 
4268
3755
  async function staticPathEntries(module, file) {