@kudzujs/core 0.8.14 → 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 +34 -6
- package/RELEASES.md +64 -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 +23 -2
- package/framework/build.mjs +285 -3569
- package/framework/compiler/animation-frame-pass.mjs +103 -0
- package/framework/compiler/ast-helpers.mjs +181 -0
- package/framework/compiler/browser-signal-passes.mjs +182 -0
- package/framework/compiler/collection-analysis.mjs +187 -0
- package/framework/compiler/custom-hook-timer-pass.mjs +126 -0
- package/framework/compiler/descriptor-session.mjs +222 -0
- package/framework/compiler/effect-codegen.mjs +884 -0
- package/framework/compiler/event-command-pass.mjs +35 -0
- package/framework/compiler/handler-codegen.mjs +296 -0
- package/framework/compiler/normalization-pipeline.mjs +9 -0
- package/framework/compiler/react-migration-pass.mjs +339 -0
- package/framework/compiler/render-control-pass.mjs +96 -0
- package/framework/compiler/route-capability-planner.mjs +118 -0
- package/framework/compiler/router-pass.mjs +245 -0
- package/framework/compiler/worker-compiler.mjs +163 -0
- package/framework/compiler/zustand-pass.mjs +95 -0
- package/framework/dev-server.mjs +244 -0
- package/package.json +4 -1
package/framework/build.mjs
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
|
-
import { createServer } from "node:http"
|
|
2
1
|
import { createHash, randomUUID } from "node:crypto"
|
|
3
|
-
import { cp, mkdir, readFile, readdir, realpath, rm, stat,
|
|
2
|
+
import { cp, mkdir, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises"
|
|
4
3
|
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path"
|
|
5
4
|
import { pathToFileURL } from "node:url"
|
|
6
5
|
import { build as bundle, transform } from "esbuild"
|
|
7
6
|
import ts from "typescript"
|
|
7
|
+
import { normalizeEffectAnimationFrameRefs } from "./compiler/animation-frame-pass.mjs"
|
|
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
|
+
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"
|
|
11
|
+
import { normalizeCustomHookTimerRefs } from "./compiler/custom-hook-timer-pass.mjs"
|
|
12
|
+
import { captureNames, createDescriptorSession, createSemanticArtifact, nativeCaptureNames, referencedReducerDispatches, referencedStateNames } from "./compiler/descriptor-session.mjs"
|
|
13
|
+
import { createEffectCodegen } from "./compiler/effect-codegen.mjs"
|
|
14
|
+
import { createEventCommandCompiler } from "./compiler/event-command-pass.mjs"
|
|
15
|
+
import { createHandlerCodegen } from "./compiler/handler-codegen.mjs"
|
|
16
|
+
import { applyNormalizationPasses } from "./compiler/normalization-pipeline.mjs"
|
|
17
|
+
import { createReactMigrationPass, reactMemoExpression } from "./compiler/react-migration-pass.mjs"
|
|
18
|
+
import { normalizeRenderControlFlow } from "./compiler/render-control-pass.mjs"
|
|
19
|
+
import { createRouterPass } from "./compiler/router-pass.mjs"
|
|
20
|
+
import { planRouteCapabilities, usesRouteDependencyRuntime } from "./compiler/route-capability-planner.mjs"
|
|
21
|
+
import { createWorkerCompiler } from "./compiler/worker-compiler.mjs"
|
|
22
|
+
import { createZustandPass } from "./compiler/zustand-pass.mjs"
|
|
8
23
|
import { renderPage } from "./core.mjs"
|
|
9
|
-
import {
|
|
24
|
+
import { parseDevHost, parseDevPort, startDevServer } from "./dev-server.mjs"
|
|
25
|
+
|
|
26
|
+
export { parseDevHost, parseDevPort }
|
|
10
27
|
|
|
11
28
|
const root = process.cwd()
|
|
12
29
|
const sourceDirectory = join(root, "src")
|
|
@@ -14,8 +31,8 @@ const pagesDirectory = join(sourceDirectory, "pages")
|
|
|
14
31
|
const workDirectory = join(root, ".kudzu")
|
|
15
32
|
const outputDirectory = join(root, "dist")
|
|
16
33
|
const staticAssetExtensions = new Set([".avif", ".gif", ".ico", ".jpeg", ".jpg", ".otf", ".png", ".svg", ".ttf", ".webp", ".woff", ".woff2"])
|
|
17
|
-
|
|
18
|
-
const
|
|
34
|
+
const compileEventCommand = createEventCommandCompiler({ isPrimitiveLiteral: isPrimitiveDefaultLiteral, synthesizeSerializableStateLiteral })
|
|
35
|
+
const { analyzeZustandStores, normalizeZustandMigrationSyntax } = createZustandPass({ isSerializableStateLiteral, nativeCaptureNames, sourceDirectory })
|
|
19
36
|
|
|
20
37
|
export async function build({ quiet = false, minify = true } = {}) {
|
|
21
38
|
const config = await loadConfig()
|
|
@@ -63,14 +80,8 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
63
80
|
if (handlerModule) handlerModules.push(handlerModule)
|
|
64
81
|
}
|
|
65
82
|
|
|
66
|
-
let behaviorCount = 0
|
|
67
|
-
let regularBehaviorCount = 0
|
|
68
|
-
let bindingCount = 0
|
|
69
|
-
let listCount = 0
|
|
70
|
-
let listStyleCount = 0
|
|
71
|
-
let regularStateSeedCount = 0
|
|
72
|
-
let dependencyStateSeedCount = 0
|
|
73
83
|
const plans = []
|
|
84
|
+
const routeCapabilities = new Map()
|
|
74
85
|
const pageEntries = []
|
|
75
86
|
const effectEntries = []
|
|
76
87
|
const nativeEntries = []
|
|
@@ -151,27 +162,24 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
151
162
|
navigationGroup.hasEffects ||= result.hasEffects
|
|
152
163
|
navigationGroup.hasParams ||= result.hasParams
|
|
153
164
|
}
|
|
154
|
-
const
|
|
155
|
-
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 })
|
|
156
166
|
pageEntries.push({ route, html: result.html, usesDependencyRuntime })
|
|
157
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
|
+
})
|
|
158
177
|
if (result.hasParams) paramEntries.push({ path: paramPath, schema: runtimeSchema, params: result.plan.params, searchParams: result.plan.searchParams, searchParamsWritable: result.plan.searchParamsWritable, usesDependencyRuntime, navigable })
|
|
159
178
|
if (result.hasEffects) effectEntries.push({ path: effectPath, effects: runtimeEffects(result.plan.effects, navigable), paramPath: result.hasParams ? paramPath : undefined, usesDependencyRuntime, navigable })
|
|
160
179
|
if (result.plan.events.some(event => event.native)) nativeEntries.push({
|
|
161
180
|
path: nativePath,
|
|
162
181
|
modules: [...new Set(result.plan.events.filter(event => event.native).map(event => event.native.module))]
|
|
163
182
|
})
|
|
164
|
-
if (result.hasBehaviors) {
|
|
165
|
-
behaviorCount++
|
|
166
|
-
if (!usesDependencyRuntime) regularBehaviorCount++
|
|
167
|
-
}
|
|
168
|
-
if (result.hasBindings) bindingCount++
|
|
169
|
-
if (result.hasLists) listCount++
|
|
170
|
-
if (result.hasListStyles) listStyleCount++
|
|
171
|
-
if (result.hasStateSeed) {
|
|
172
|
-
if (usesDependencyRuntime) dependencyStateSeedCount++
|
|
173
|
-
else regularStateSeedCount++
|
|
174
|
-
}
|
|
175
183
|
}
|
|
176
184
|
}
|
|
177
185
|
|
|
@@ -189,7 +197,7 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
189
197
|
const renderedEffects = new Set(plans.flatMap(plan => plan.effects.map(effect => `${effect.module}:${effect.handler}`)))
|
|
190
198
|
const renderedWorkerReferences = workerReferences.filter(reference => renderedEffects.has(`${reference.module}:${reference.handler}`))
|
|
191
199
|
if (renderedWorkerReferences.length && await exists(join(publicDirectory, "assets", "workers"))) throw new Error("public/assets/workers collides with Kudzu's generated Worker asset namespace")
|
|
192
|
-
const workerAssets = await
|
|
200
|
+
const workerAssets = await workerCompiler.emit(renderedWorkerReferences, sourceFileSet, assetsDirectory, base, minify)
|
|
193
201
|
for (const module of emittedHandlerModules) {
|
|
194
202
|
for (const reference of workerReferences) {
|
|
195
203
|
if (reference.module !== assetPath(base, `assets/${module.path}`)) continue
|
|
@@ -198,43 +206,41 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
198
206
|
}
|
|
199
207
|
if (module.code.includes("/__kudzu_worker_")) throw new Error(`Worker URL placeholder survived in ${module.path}`)
|
|
200
208
|
}
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const hasSharedRuntime = bindingCount || listCount || hasNativeHandlers || navigationRoutes.length
|
|
237
|
-
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
|
|
238
244
|
const runtimeName = usesDependencyRuntime => usesDependencyRuntime ? "kudzu-deps.js" : "kudzu.js"
|
|
239
245
|
for (const entry of pageEntries) {
|
|
240
246
|
const routeDirectory = join(outputDirectory, entry.route)
|
|
@@ -399,11 +405,7 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
399
405
|
for (const entry of effectEntries) {
|
|
400
406
|
const output = join(assetsDirectory, entry.path)
|
|
401
407
|
await mkdir(dirname(output), { recursive: true })
|
|
402
|
-
await writeJavaScript(output, entry.navigable
|
|
403
|
-
? entry.effects.some(effect => effect.owner)
|
|
404
|
-
? printOwnedNavigableEffectEntry(entry.effects, output, emittedHandlerModules, assetsDirectory, base)
|
|
405
|
-
: printNavigableEffectEntry(entry.effects, output, emittedHandlerModules, assetsDirectory, base)
|
|
406
|
-
: printEffectEntry(entry.effects, output, emittedHandlerModules, assetsDirectory, base, entry.paramPath, runtimeName(entry.usesDependencyRuntime)), minify)
|
|
408
|
+
await writeJavaScript(output, printEffectEntry(entry.effects, output, emittedHandlerModules, assetsDirectory, base, entry.paramPath, runtimeName(entry.usesDependencyRuntime), entry.navigable), minify)
|
|
407
409
|
}
|
|
408
410
|
const clientModules = await collectClientModules(emittedHandlerModules.flatMap(module => module.clientImports), sourceFileSet)
|
|
409
411
|
for (const file of clientModules) {
|
|
@@ -540,559 +542,6 @@ async function printNativeEntry(entry, assetsDirectory, base, minify) {
|
|
|
540
542
|
await writeJavaScript(output, `import { registerNativeModules } from ${JSON.stringify(runtime)}\n${imports}\nregisterNativeModules([${registrations}])`, minify)
|
|
541
543
|
}
|
|
542
544
|
|
|
543
|
-
function printEffectEntry(effects, output, handlerModules, assetsDirectory, base, paramPath, runtimeName) {
|
|
544
|
-
const hasCleanup = effects.some(effect => effect.cleanup)
|
|
545
|
-
const hasDependencies = effects.some(effect => effect.dependencies?.length || effect.itemDependencies?.length)
|
|
546
|
-
const hasOwners = effects.some(effect => effect.owner)
|
|
547
|
-
const hasDependencyExpressions = effects.some(effect => effect.dependencyExpressions?.length)
|
|
548
|
-
const moduleUrls = [...new Set(effects.map(effect => effect.module))]
|
|
549
|
-
const modules = moduleUrls.map(url => {
|
|
550
|
-
const module = handlerModules.find(entry => assetPath(base, `assets/${entry.path}`) === url)
|
|
551
|
-
if (!module) throw new Error(`Effect handler module was not emitted: ${url}`)
|
|
552
|
-
return module
|
|
553
|
-
})
|
|
554
|
-
const imports = [
|
|
555
|
-
hasCleanup || hasDependencies || hasOwners
|
|
556
|
-
? `import * as __kRuntime from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, runtimeName)))}\nconst { browserState, commitDom } = __kRuntime`
|
|
557
|
-
: `import { browserState, commitDom } from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, runtimeName)))}`,
|
|
558
|
-
`import { createEffectContext } from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, "kudzu-effect.js")))}`,
|
|
559
|
-
...(hasDependencyExpressions ? [`import { evaluateCollectionExpression as __kEvaluateDependency } from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, "kudzu-collection-selector.js")))}`] : []),
|
|
560
|
-
...(paramPath ? [`import ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, paramPath)))}`] : []),
|
|
561
|
-
...modules.map((module, index) => `import * as __kEffectModule${index} from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, module.path)))}`)
|
|
562
|
-
]
|
|
563
|
-
const entries = moduleUrls.map((url, index) => `[${JSON.stringify(url)}, __kEffectModule${index}]`).join(",")
|
|
564
|
-
if (hasOwners) return printOwnedEffectEntry(imports, effects, entries)
|
|
565
|
-
if (effects.length === 1 && effects[0].dependencies?.length === 1 && !hasDependencyExpressions) return printSingleDependencyEffect(imports, effects[0], hasCleanup)
|
|
566
|
-
const disposal = hasCleanup ? `
|
|
567
|
-
let disposed = false
|
|
568
|
-
const dispose = root => {
|
|
569
|
-
if (root !== document || disposed) return
|
|
570
|
-
disposed = true
|
|
571
|
-
active = false
|
|
572
|
-
pending.clear()
|
|
573
|
-
for (const record of records) invokeCleanup(record)
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
if (__kRuntime.registerUnmountHook) __kRuntime.registerUnmountHook(dispose)
|
|
577
|
-
addEventListener("pagehide", event => {
|
|
578
|
-
if (event.persisted) return
|
|
579
|
-
if (__kRuntime.unmountDom) __kRuntime.unmountDom(document)
|
|
580
|
-
else dispose(document)
|
|
581
|
-
})` : ""
|
|
582
|
-
if (hasDependencies) return `${imports.join("\n")}
|
|
583
|
-
const effects = ${inlineJson(effects)}
|
|
584
|
-
const modules = new Map([${entries}])
|
|
585
|
-
const records = effects.map((effect, index) => ({ effect, index, values: undefined, cleanup: undefined, token: undefined }))
|
|
586
|
-
const dependencies = new Map()
|
|
587
|
-
const pending = new Set()
|
|
588
|
-
let scheduled = false
|
|
589
|
-
let flushing = false
|
|
590
|
-
let active = true
|
|
591
|
-
for (const record of records) {
|
|
592
|
-
for (const id of record.effect.dependencies ?? []) {
|
|
593
|
-
const subscribers = dependencies.get(id) ?? new Set()
|
|
594
|
-
subscribers.add(record)
|
|
595
|
-
dependencies.set(id, subscribers)
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
__kRuntime.registerCommitter(id => {
|
|
599
|
-
if (!active) return
|
|
600
|
-
for (const record of dependencies.get(id) ?? []) pending.add(record)
|
|
601
|
-
schedule()
|
|
602
|
-
})
|
|
603
|
-
for (const record of records) {
|
|
604
|
-
try {
|
|
605
|
-
record.values = readDependencies(record)
|
|
606
|
-
invoke(record)
|
|
607
|
-
} catch (error) {
|
|
608
|
-
console.error(error)
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
function schedule() {
|
|
612
|
-
if (!pending.size || scheduled || flushing) return
|
|
613
|
-
scheduled = true
|
|
614
|
-
queueMicrotask(flush)
|
|
615
|
-
}
|
|
616
|
-
async function flush() {
|
|
617
|
-
scheduled = false
|
|
618
|
-
if (!active) return pending.clear()
|
|
619
|
-
flushing = true
|
|
620
|
-
try {
|
|
621
|
-
const selected = [...pending].sort((left, right) => left.index - right.index)
|
|
622
|
-
pending.clear()
|
|
623
|
-
const changed = []
|
|
624
|
-
for (const record of selected) {
|
|
625
|
-
try {
|
|
626
|
-
const values = readDependencies(record)
|
|
627
|
-
if (!record.values || values.some((value, index) => !Object.is(value, record.values[index]))) {
|
|
628
|
-
record.values = values
|
|
629
|
-
changed.push(record)
|
|
630
|
-
}
|
|
631
|
-
} catch (error) {
|
|
632
|
-
console.error(error)
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
for (const record of changed) await invokeCleanup(record)
|
|
636
|
-
if (active) for (const record of changed) invoke(record)
|
|
637
|
-
} finally {
|
|
638
|
-
flushing = false
|
|
639
|
-
if (active) schedule()
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
function readDependencies(record) {
|
|
643
|
-
${hasDependencyExpressions ? printDerivedDependencyRead("browserState") : ""}
|
|
644
|
-
return (record.effect.dependencies ?? []).map(id => {
|
|
645
|
-
const value = browserState.get(id)
|
|
646
|
-
if (value !== null && typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0))) throw new Error("useEffect() dependency state must remain a JSON-safe primitive")
|
|
647
|
-
return value
|
|
648
|
-
})
|
|
649
|
-
}
|
|
650
|
-
function invoke(record) {
|
|
651
|
-
try {
|
|
652
|
-
const effect = record.effect
|
|
653
|
-
const token = { active: true }
|
|
654
|
-
record.token = token
|
|
655
|
-
const result = modules.get(effect.module)[effect.handler](createEffectContext(browserState, effect.states, commitDom, effect.scope, () => active && token.active && record.token === token))
|
|
656
|
-
if (effect.cleanup && typeof result === "function") record.cleanup = result
|
|
657
|
-
else if (result && typeof result.then === "function") result.catch(error => console.error(error))
|
|
658
|
-
} catch (error) {
|
|
659
|
-
console.error(error)
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
async function invokeCleanup(record) {
|
|
663
|
-
if (record.token) record.token.active = false
|
|
664
|
-
record.token = undefined
|
|
665
|
-
const cleanup = record.cleanup
|
|
666
|
-
record.cleanup = undefined
|
|
667
|
-
if (!cleanup) return
|
|
668
|
-
try {
|
|
669
|
-
await cleanup()
|
|
670
|
-
} catch (error) {
|
|
671
|
-
console.error(error)
|
|
672
|
-
}
|
|
673
|
-
}${disposal}`
|
|
674
|
-
if (!hasCleanup) return `${imports.join("\n")}
|
|
675
|
-
const effects = ${inlineJson(effects)}
|
|
676
|
-
const modules = new Map([${entries}])
|
|
677
|
-
for (const effect of effects) {
|
|
678
|
-
try {
|
|
679
|
-
const result = modules.get(effect.module)[effect.handler](createEffectContext(browserState, effect.states, commitDom, effect.scope))
|
|
680
|
-
if (result && typeof result.then === "function") result.catch(error => console.error(error))
|
|
681
|
-
} catch (error) {
|
|
682
|
-
console.error(error)
|
|
683
|
-
}
|
|
684
|
-
}`
|
|
685
|
-
return `${imports.join("\n")}
|
|
686
|
-
const effects = ${inlineJson(effects)}
|
|
687
|
-
const modules = new Map([${entries}])
|
|
688
|
-
const cleanups = []
|
|
689
|
-
for (const effect of effects) {
|
|
690
|
-
try {
|
|
691
|
-
const result = modules.get(effect.module)[effect.handler](createEffectContext(browserState, effect.states, commitDom, effect.scope))
|
|
692
|
-
if (effect.cleanup && typeof result === "function") cleanups.push(result)
|
|
693
|
-
else if (result && typeof result.then === "function") result.catch(error => console.error(error))
|
|
694
|
-
} catch (error) {
|
|
695
|
-
console.error(error)
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
let cleaned = false
|
|
699
|
-
const dispose = root => {
|
|
700
|
-
if (root !== document || cleaned) return
|
|
701
|
-
cleaned = true
|
|
702
|
-
for (const cleanup of cleanups) {
|
|
703
|
-
try {
|
|
704
|
-
const result = cleanup()
|
|
705
|
-
if (result && typeof result.then === "function") result.catch(error => console.error(error))
|
|
706
|
-
} catch (error) {
|
|
707
|
-
console.error(error)
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
cleanups.length = 0
|
|
711
|
-
}
|
|
712
|
-
if (__kRuntime.registerUnmountHook) __kRuntime.registerUnmountHook(dispose)
|
|
713
|
-
addEventListener("pagehide", event => {
|
|
714
|
-
if (event.persisted) return
|
|
715
|
-
if (__kRuntime.unmountDom) __kRuntime.unmountDom(document)
|
|
716
|
-
else dispose(document)
|
|
717
|
-
})`
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
function printNavigableEffectEntry(effects, output, handlerModules, assetsDirectory, base) {
|
|
721
|
-
const hasDependencyExpressions = effects.some(effect => effect.dependencyExpressions?.length)
|
|
722
|
-
const moduleUrls = [...new Set(effects.map(effect => effect.module))]
|
|
723
|
-
const modules = moduleUrls.map(url => {
|
|
724
|
-
const module = handlerModules.find(entry => assetPath(base, `assets/${entry.path}`) === url)
|
|
725
|
-
if (!module) throw new Error(`Effect handler module was not emitted: ${url}`)
|
|
726
|
-
return module
|
|
727
|
-
})
|
|
728
|
-
const imports = [
|
|
729
|
-
`import * as __kRuntime from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, "kudzu.js")))}`,
|
|
730
|
-
`import { createEffectContext } from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, "kudzu-effect.js")))}`,
|
|
731
|
-
...(hasDependencyExpressions ? [`import { evaluateCollectionExpression as __kEvaluateDependency } from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, "kudzu-collection-selector.js")))}`] : []),
|
|
732
|
-
...modules.map((module, index) => `import * as __kEffectModule${index} from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, module.path)))}`)
|
|
733
|
-
]
|
|
734
|
-
const entries = moduleUrls.map((url, index) => `[${JSON.stringify(url)}, __kEffectModule${index}]`).join(",")
|
|
735
|
-
return `${imports.join("\n")}
|
|
736
|
-
const effects = ${inlineJson(effects)}
|
|
737
|
-
const modules = new Map([${entries}])
|
|
738
|
-
export const mountLayoutEffects = () => mount("layout")
|
|
739
|
-
export const mountRouteEffects = () => mount("route")
|
|
740
|
-
function mount(lifetime) {
|
|
741
|
-
let active = true
|
|
742
|
-
let flushing
|
|
743
|
-
const records = effects.filter(effect => effect.lifetime === lifetime).map((effect, index) => ({ effect, index, values: undefined, cleanup: undefined, token: undefined }))
|
|
744
|
-
const dependencies = new Map()
|
|
745
|
-
const pending = new Set()
|
|
746
|
-
let scheduled = false
|
|
747
|
-
for (const record of records) for (const id of record.effect.dependencies ?? []) {
|
|
748
|
-
const subscribers = dependencies.get(id) ?? new Set()
|
|
749
|
-
subscribers.add(record)
|
|
750
|
-
dependencies.set(id, subscribers)
|
|
751
|
-
}
|
|
752
|
-
const unsubscribe = dependencies.size ? __kRuntime.registerCommitter(id => {
|
|
753
|
-
if (!active) return
|
|
754
|
-
for (const record of dependencies.get(id) ?? []) pending.add(record)
|
|
755
|
-
if (pending.size && !scheduled && !flushing) {
|
|
756
|
-
scheduled = true
|
|
757
|
-
queueMicrotask(flush)
|
|
758
|
-
}
|
|
759
|
-
}) : undefined
|
|
760
|
-
for (const record of records) {
|
|
761
|
-
try {
|
|
762
|
-
record.values = readDependencies(record)
|
|
763
|
-
invoke(record)
|
|
764
|
-
} catch (error) {
|
|
765
|
-
console.error(error)
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
async function flush() {
|
|
769
|
-
scheduled = false
|
|
770
|
-
if (!active) return pending.clear()
|
|
771
|
-
const operation = (async () => {
|
|
772
|
-
const selected = [...pending].sort((left, right) => left.index - right.index)
|
|
773
|
-
pending.clear()
|
|
774
|
-
const changed = []
|
|
775
|
-
for (const record of selected) {
|
|
776
|
-
try {
|
|
777
|
-
const values = readDependencies(record)
|
|
778
|
-
if (!record.values || values.some((value, index) => !Object.is(value, record.values[index]))) {
|
|
779
|
-
record.values = values
|
|
780
|
-
changed.push(record)
|
|
781
|
-
}
|
|
782
|
-
} catch (error) {
|
|
783
|
-
console.error(error)
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
for (const record of changed) await cleanup(record)
|
|
787
|
-
if (active) for (const record of changed) invoke(record)
|
|
788
|
-
})()
|
|
789
|
-
flushing = operation
|
|
790
|
-
try { await operation } finally {
|
|
791
|
-
if (flushing === operation) flushing = undefined
|
|
792
|
-
if (active && pending.size && !scheduled) {
|
|
793
|
-
scheduled = true
|
|
794
|
-
queueMicrotask(flush)
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
function readDependencies(record) {
|
|
799
|
-
${hasDependencyExpressions ? printDerivedDependencyRead("__kRuntime.browserState") : ""}
|
|
800
|
-
return (record.effect.dependencies ?? []).map(id => {
|
|
801
|
-
const value = __kRuntime.browserState.get(id)
|
|
802
|
-
if (value !== null && typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0))) throw new Error("useEffect() dependency state must remain a JSON-safe primitive")
|
|
803
|
-
return value
|
|
804
|
-
})
|
|
805
|
-
}
|
|
806
|
-
function invoke(record) {
|
|
807
|
-
const token = { active: true }
|
|
808
|
-
record.token = token
|
|
809
|
-
try {
|
|
810
|
-
const effect = record.effect
|
|
811
|
-
const result = modules.get(effect.module)[effect.handler](createEffectContext(__kRuntime.browserState, effect.states, __kRuntime.commitDom, effect.scope, () => active && token.active && record.token === token))
|
|
812
|
-
if (effect.cleanup && typeof result === "function") record.cleanup = result
|
|
813
|
-
else if (result && typeof result.then === "function") result.catch(error => console.error(error))
|
|
814
|
-
} catch (error) {
|
|
815
|
-
console.error(error)
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
async function cleanup(record) {
|
|
819
|
-
if (record.token) record.token.active = false
|
|
820
|
-
record.token = undefined
|
|
821
|
-
const current = record.cleanup
|
|
822
|
-
record.cleanup = undefined
|
|
823
|
-
if (!current) return
|
|
824
|
-
try { await current() } catch (error) { console.error(error) }
|
|
825
|
-
}
|
|
826
|
-
let disposal
|
|
827
|
-
return async function dispose() {
|
|
828
|
-
if (disposal) return disposal
|
|
829
|
-
disposal = (async () => {
|
|
830
|
-
active = false
|
|
831
|
-
unsubscribe?.()
|
|
832
|
-
pending.clear()
|
|
833
|
-
for (const record of records) if (record.token) record.token.active = false
|
|
834
|
-
if (flushing) await flushing
|
|
835
|
-
for (const record of records) await cleanup(record)
|
|
836
|
-
})()
|
|
837
|
-
return disposal
|
|
838
|
-
}
|
|
839
|
-
}`
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
function printOwnedNavigableEffectEntry(effects, output, handlerModules, assetsDirectory, base) {
|
|
843
|
-
const hasItemDependencies = effects.some(effect => effect.itemDependencies?.length)
|
|
844
|
-
const hasDependencyExpressions = effects.some(effect => effect.dependencyExpressions?.length)
|
|
845
|
-
const moduleUrls = [...new Set(effects.map(effect => effect.module))]
|
|
846
|
-
const modules = moduleUrls.map(url => {
|
|
847
|
-
const module = handlerModules.find(entry => assetPath(base, `assets/${entry.path}`) === url)
|
|
848
|
-
if (!module) throw new Error(`Effect handler module was not emitted: ${url}`)
|
|
849
|
-
return module
|
|
850
|
-
})
|
|
851
|
-
const imports = [
|
|
852
|
-
`import * as __kRuntime from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, "kudzu.js")))}`,
|
|
853
|
-
`import { createEffectContext } from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, "kudzu-effect.js")))}`,
|
|
854
|
-
...(hasDependencyExpressions ? [`import { evaluateCollectionExpression as __kEvaluateDependency } from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, "kudzu-collection-selector.js")))}`] : []),
|
|
855
|
-
...modules.map((module, index) => `import * as __kEffectModule${index} from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, module.path)))}`)
|
|
856
|
-
]
|
|
857
|
-
const entries = moduleUrls.map((url, index) => `[${JSON.stringify(url)}, __kEffectModule${index}]`).join(",")
|
|
858
|
-
return `${imports.join("\n")}
|
|
859
|
-
const effects = ${inlineJson(effects)}
|
|
860
|
-
const modules = new Map([${entries}])
|
|
861
|
-
export const mountLayoutEffects = () => mount("layout")
|
|
862
|
-
export const mountRouteEffects = () => mount("route")
|
|
863
|
-
function mount(lifetime) {
|
|
864
|
-
let active = true
|
|
865
|
-
let flushing
|
|
866
|
-
let order = 0
|
|
867
|
-
const selectedEffects = effects.map((effect, index) => ({ effect, index })).filter(entry => entry.effect.lifetime === lifetime)
|
|
868
|
-
const records = new Set()
|
|
869
|
-
const owners = new Map()
|
|
870
|
-
const listTemplates = new Map()
|
|
871
|
-
const registrations = new WeakMap()
|
|
872
|
-
const dependencies = new Map()
|
|
873
|
-
const pending = new Set()
|
|
874
|
-
const startedCleanups = new Set()
|
|
875
|
-
let scheduled = false
|
|
876
|
-
for (const template of selectedEffects) {
|
|
877
|
-
if (template.effect.list) listTemplates.set(template.effect.owner, template)
|
|
878
|
-
else {
|
|
879
|
-
const record = createRecord(template, !template.effect.owner)
|
|
880
|
-
if (template.effect.owner) owners.set(template.effect.owner, record)
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
const unsubscribeCommitter = selectedEffects.some(({ effect }) => effect.dependencies?.length) ? __kRuntime.registerCommitter(id => {
|
|
884
|
-
if (!active) return
|
|
885
|
-
for (const record of dependencies.get(id) ?? []) if (record.mounted) pending.add(record)
|
|
886
|
-
schedule()
|
|
887
|
-
}) : undefined
|
|
888
|
-
const unsubscribeMount = __kRuntime.registerMountHook(mountOwned)
|
|
889
|
-
const unsubscribeUnmount = __kRuntime.registerUnmountHook(unmountOwned)
|
|
890
|
-
${hasItemDependencies ? `const unsubscribeItems = [...new Set(selectedEffects.filter(({ effect }) => effect.itemDependencies?.length).map(({ effect }) => effect.listState))].map(listState => __kRuntime.registerListItemHook(listState, root => {
|
|
891
|
-
if (!active) return
|
|
892
|
-
for (const record of registrations.get(root) ?? []) if (record.mounted && record.effect.itemDependencies) pending.add(record)
|
|
893
|
-
schedule()
|
|
894
|
-
}))
|
|
895
|
-
` : ""}for (const record of records) if (record.mounted) start(record)
|
|
896
|
-
mountOwned(document)
|
|
897
|
-
function createRecord(template, mounted = true) {
|
|
898
|
-
const record = { ...template, order: order++, mounted, marker: undefined, version: 0, values: undefined, cleanup: undefined, disposal: undefined, token: undefined }
|
|
899
|
-
records.add(record)
|
|
900
|
-
registerDependencies(record)
|
|
901
|
-
return record
|
|
902
|
-
}
|
|
903
|
-
function registerDependencies(record) {
|
|
904
|
-
for (const id of record.effect.dependencies ?? []) {
|
|
905
|
-
const subscribers = dependencies.get(id) ?? new Set()
|
|
906
|
-
subscribers.add(record)
|
|
907
|
-
dependencies.set(id, subscribers)
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
function unregisterDependencies(record) {
|
|
911
|
-
for (const id of record.effect.dependencies ?? []) {
|
|
912
|
-
const subscribers = dependencies.get(id)
|
|
913
|
-
subscribers?.delete(record)
|
|
914
|
-
if (!subscribers?.size) dependencies.delete(id)
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
function mountOwned(root) {
|
|
918
|
-
if (!active) return
|
|
919
|
-
for (const marker of matching(root)) {
|
|
920
|
-
if (!marker.isConnected) continue
|
|
921
|
-
if (marker.dataset.kEffects) {
|
|
922
|
-
if (registrations.has(marker)) continue
|
|
923
|
-
const rowRecords = JSON.parse(marker.dataset.kEffects).flatMap(owner => {
|
|
924
|
-
const template = listTemplates.get(owner)
|
|
925
|
-
if (!template) return []
|
|
926
|
-
const record = createRecord(template)
|
|
927
|
-
record.marker = marker
|
|
928
|
-
start(record)
|
|
929
|
-
return [record]
|
|
930
|
-
})
|
|
931
|
-
if (rowRecords.length) registrations.set(marker, rowRecords)
|
|
932
|
-
continue
|
|
933
|
-
}
|
|
934
|
-
const record = owners.get(marker.dataset.kEffect)
|
|
935
|
-
if (record && !record.mounted) mountRecord(record, marker)
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
function unmountOwned(root) {
|
|
939
|
-
if (!active) return
|
|
940
|
-
for (const marker of matching(root)) {
|
|
941
|
-
const rowRecords = registrations.get(marker)
|
|
942
|
-
if (rowRecords) {
|
|
943
|
-
for (const record of rowRecords) unmountRecord(record, true)
|
|
944
|
-
registrations.delete(marker)
|
|
945
|
-
continue
|
|
946
|
-
}
|
|
947
|
-
const record = owners.get(marker.dataset.kEffect)
|
|
948
|
-
if (record?.marker === marker) unmountRecord(record)
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
function matching(root) {
|
|
952
|
-
const selector = "template[data-k-effect],[data-k-effects]"
|
|
953
|
-
return [...(root.matches?.(selector) ? [root] : []), ...(root.querySelectorAll?.(selector) ?? [])]
|
|
954
|
-
}
|
|
955
|
-
function mountRecord(record, marker) {
|
|
956
|
-
record.mounted = true
|
|
957
|
-
record.marker = marker
|
|
958
|
-
const version = ++record.version
|
|
959
|
-
const begin = () => {
|
|
960
|
-
if (active && record.mounted && record.version === version && marker.isConnected) start(record)
|
|
961
|
-
}
|
|
962
|
-
if (record.disposal) record.disposal.then(begin)
|
|
963
|
-
else begin()
|
|
964
|
-
}
|
|
965
|
-
function unmountRecord(record, dynamic = false) {
|
|
966
|
-
if (!record.mounted) return
|
|
967
|
-
record.mounted = false
|
|
968
|
-
record.marker = undefined
|
|
969
|
-
record.version++
|
|
970
|
-
pending.delete(record)
|
|
971
|
-
if (dynamic) {
|
|
972
|
-
unregisterDependencies(record)
|
|
973
|
-
records.delete(record)
|
|
974
|
-
}
|
|
975
|
-
void cleanup(record)
|
|
976
|
-
}
|
|
977
|
-
function start(record) {
|
|
978
|
-
try {
|
|
979
|
-
record.values = readDependencies(record)
|
|
980
|
-
invoke(record)
|
|
981
|
-
} catch (error) {
|
|
982
|
-
console.error(error)
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
function schedule() {
|
|
986
|
-
if (!pending.size || scheduled || flushing) return
|
|
987
|
-
scheduled = true
|
|
988
|
-
queueMicrotask(flush)
|
|
989
|
-
}
|
|
990
|
-
async function flush() {
|
|
991
|
-
scheduled = false
|
|
992
|
-
if (!active) return pending.clear()
|
|
993
|
-
const operation = (async () => {
|
|
994
|
-
const changed = []
|
|
995
|
-
const selected = [...pending].filter(record => record.mounted).sort((left, right) => left.index - right.index || left.order - right.order)
|
|
996
|
-
pending.clear()
|
|
997
|
-
for (const record of selected) {
|
|
998
|
-
try {
|
|
999
|
-
const values = readDependencies(record)
|
|
1000
|
-
if (!record.values || values.some((value, index) => !Object.is(value, record.values[index]))) changed.push([record, record.version])
|
|
1001
|
-
} catch (error) {
|
|
1002
|
-
console.error(error)
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
for (const [record] of changed) await cleanup(record)
|
|
1006
|
-
if (active) for (const [record, version] of changed) if (record.mounted && record.version === version) {
|
|
1007
|
-
try {
|
|
1008
|
-
record.values = readDependencies(record)
|
|
1009
|
-
invoke(record)
|
|
1010
|
-
} catch (error) {
|
|
1011
|
-
record.values = undefined
|
|
1012
|
-
console.error(error)
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
})()
|
|
1016
|
-
flushing = operation
|
|
1017
|
-
try { await operation } finally {
|
|
1018
|
-
if (flushing === operation) flushing = undefined
|
|
1019
|
-
if (active) schedule()
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
function readDependencies(record) {
|
|
1023
|
-
${hasDependencyExpressions ? printDerivedDependencyRead("__kRuntime.browserState") : ""}
|
|
1024
|
-
const values = (record.effect.dependencies ?? []).map(id => {
|
|
1025
|
-
const value = __kRuntime.browserState.get(id)
|
|
1026
|
-
if (value !== null && typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0))) throw new Error("useEffect() dependency state must remain a JSON-safe primitive")
|
|
1027
|
-
return value
|
|
1028
|
-
})
|
|
1029
|
-
${hasItemDependencies ? `if (record.effect.itemDependencies) {
|
|
1030
|
-
const item = JSON.parse(record.marker.dataset.kEffectItem)
|
|
1031
|
-
for (const field of record.effect.itemDependencies) {
|
|
1032
|
-
const value = item[field]
|
|
1033
|
-
if (value !== null && typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0))) throw new Error(\`useEffect() keyed item dependency "\${field}" must remain a JSON-safe primitive\`)
|
|
1034
|
-
values.push(value)
|
|
1035
|
-
}
|
|
1036
|
-
}` : ""}
|
|
1037
|
-
return values
|
|
1038
|
-
}
|
|
1039
|
-
function invoke(record) {
|
|
1040
|
-
const token = { active: true }
|
|
1041
|
-
record.token = token
|
|
1042
|
-
try {
|
|
1043
|
-
const effect = record.effect
|
|
1044
|
-
const scope = effect.list
|
|
1045
|
-
? Object.fromEntries(Object.entries(effect.scope).map(([name, value]) => [name, value?.type === "list-item" ? JSON.parse(record.marker.dataset.kEffectItem) : value]))
|
|
1046
|
-
: effect.scope
|
|
1047
|
-
const result = modules.get(effect.module)[effect.handler](createEffectContext(__kRuntime.browserState, effect.states, __kRuntime.commitDom, scope, () => active && token.active && record.token === token))
|
|
1048
|
-
if (effect.cleanup && typeof result === "function") record.cleanup = result
|
|
1049
|
-
else if (result && typeof result.then === "function") result.catch(error => console.error(error))
|
|
1050
|
-
} catch (error) {
|
|
1051
|
-
console.error(error)
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
function cleanup(record) {
|
|
1055
|
-
if (record.token) record.token.active = false
|
|
1056
|
-
record.token = undefined
|
|
1057
|
-
if (record.disposal) return record.disposal
|
|
1058
|
-
const current = record.cleanup
|
|
1059
|
-
record.cleanup = undefined
|
|
1060
|
-
if (!current) return Promise.resolve()
|
|
1061
|
-
const disposal = (async () => {
|
|
1062
|
-
try { await current() } catch (error) { console.error(error) }
|
|
1063
|
-
})()
|
|
1064
|
-
record.disposal = disposal
|
|
1065
|
-
startedCleanups.add(disposal)
|
|
1066
|
-
disposal.finally(() => {
|
|
1067
|
-
startedCleanups.delete(disposal)
|
|
1068
|
-
if (record.disposal === disposal) record.disposal = undefined
|
|
1069
|
-
})
|
|
1070
|
-
return disposal
|
|
1071
|
-
}
|
|
1072
|
-
let disposal
|
|
1073
|
-
return async function dispose() {
|
|
1074
|
-
if (disposal) return disposal
|
|
1075
|
-
disposal = (async () => {
|
|
1076
|
-
active = false
|
|
1077
|
-
unsubscribeCommitter?.()
|
|
1078
|
-
${hasItemDependencies ? "for (const unsubscribe of unsubscribeItems) unsubscribe()\n " : ""}unsubscribeMount()
|
|
1079
|
-
unsubscribeUnmount()
|
|
1080
|
-
pending.clear()
|
|
1081
|
-
for (const record of records) if (record.token) record.token.active = false
|
|
1082
|
-
if (flushing) await flushing
|
|
1083
|
-
const mounted = [...records].filter(record => record.mounted).sort((left, right) => left.index - right.index || left.order - right.order)
|
|
1084
|
-
for (const record of mounted) {
|
|
1085
|
-
record.mounted = false
|
|
1086
|
-
await cleanup(record)
|
|
1087
|
-
}
|
|
1088
|
-
await Promise.all([...startedCleanups])
|
|
1089
|
-
records.clear()
|
|
1090
|
-
})()
|
|
1091
|
-
return disposal
|
|
1092
|
-
}
|
|
1093
|
-
}`
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
545
|
function runtimeEffects(effects, lifetimes = false) {
|
|
1097
546
|
return effects.map(effect => ({
|
|
1098
547
|
module: effect.module,
|
|
@@ -1109,328 +558,6 @@ function runtimeEffects(effects, lifetimes = false) {
|
|
|
1109
558
|
}))
|
|
1110
559
|
}
|
|
1111
560
|
|
|
1112
|
-
function printDerivedDependencyRead(state) {
|
|
1113
|
-
return ` if (record.effect.dependencyExpressions) return record.effect.dependencyExpressions.map(expression => {
|
|
1114
|
-
const value = __kEvaluateDependency(expression, undefined, undefined, name => ${state}.get(record.effect.dependencyStates[name]))
|
|
1115
|
-
if (value !== null && typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0))) throw new Error("useEffect() derived dependency must remain a JSON-safe primitive")
|
|
1116
|
-
return value
|
|
1117
|
-
})`
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
function printOwnedEffectEntry(imports, effects, entries) {
|
|
1121
|
-
const hasItemDependencies = effects.some(effect => effect.itemDependencies?.length)
|
|
1122
|
-
const hasOrdinaryDependencies = effects.some(effect => effect.dependencies?.length)
|
|
1123
|
-
const hasDependencyExpressions = effects.some(effect => effect.dependencyExpressions?.length)
|
|
1124
|
-
const hasRowState = effects.some(effect => JSON.stringify([effect.dependencies, effect.dependencyStates, effect.states, effect.scope]).includes("$k"))
|
|
1125
|
-
return `${imports.join("\n")}
|
|
1126
|
-
const effects = ${inlineJson(effects)}
|
|
1127
|
-
const modules = new Map([${entries}])
|
|
1128
|
-
${hasItemDependencies ? "let order = 0\n" : ""}const records = effects.map((effect, index) => effect.list ? undefined : createRecord(effect, index)).filter(Boolean)
|
|
1129
|
-
const listTemplates = new Map(effects.map((effect, index) => effect.list ? [effect.owner, { effect, index }] : undefined).filter(Boolean))
|
|
1130
|
-
const owners = new Map(records.filter(record => record.effect.owner).map(record => [record.effect.owner, record]))
|
|
1131
|
-
const listRegistrations = new WeakMap()
|
|
1132
|
-
const mountedRecords = new Set(records.filter(record => record.mounted))
|
|
1133
|
-
const dependencies = new Map()
|
|
1134
|
-
const pending = new Set()
|
|
1135
|
-
let scheduled = false
|
|
1136
|
-
let flushing = false
|
|
1137
|
-
let active = true
|
|
1138
|
-
for (const record of records) registerDependencies(record)
|
|
1139
|
-
function createRecord(effect, index${hasRowState ? ", marker" : ""}) {
|
|
1140
|
-
return { effect: ${hasRowState ? "marker ? specializeRowEffect(effect, marker) : effect" : "effect"}, index, ${hasItemDependencies ? "order: order++, " : ""}mounted: !effect.owner, marker: undefined, version: 0, values: undefined, cleanup: undefined, disposal: undefined, token: undefined }
|
|
1141
|
-
}
|
|
1142
|
-
${hasRowState ? `function specializeRowEffect(effect, marker) {
|
|
1143
|
-
const path = marker.dataset.kRowPath
|
|
1144
|
-
const id = value => typeof value === "string" ? value.replace("$k", path) : value
|
|
1145
|
-
const capture = value => value?.type === "state" || value?.type === "setter" || value?.type === "ref" ? { ...value, id: id(value.id) } : value?.type === "array" ? { ...value, value: value.value.map(capture) } : value?.type === "object" ? { ...value, value: value.value.map(([key, entry]) => [key, capture(entry)]) } : value
|
|
1146
|
-
return { ...effect, dependencies: effect.dependencies?.map(id), dependencyStates: effect.dependencyStates && Object.fromEntries(Object.entries(effect.dependencyStates).map(([name, value]) => [name, id(value)])), states: Object.fromEntries(Object.entries(effect.states).map(([name, value]) => [name, id(value)])), scope: Object.fromEntries(Object.entries(effect.scope).map(([name, value]) => [name, capture(value)])) }
|
|
1147
|
-
}
|
|
1148
|
-
` : ""}
|
|
1149
|
-
function registerDependencies(record) {
|
|
1150
|
-
for (const id of record.effect.dependencies ?? []) {
|
|
1151
|
-
const subscribers = dependencies.get(id) ?? new Set()
|
|
1152
|
-
subscribers.add(record)
|
|
1153
|
-
dependencies.set(id, subscribers)
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
function unregisterDependencies(record) {
|
|
1157
|
-
for (const id of record.effect.dependencies ?? []) {
|
|
1158
|
-
const subscribers = dependencies.get(id)
|
|
1159
|
-
subscribers?.delete(record)
|
|
1160
|
-
if (!subscribers?.size) dependencies.delete(id)
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
${hasItemDependencies ? `if (${hasOrdinaryDependencies}) ` : ""}__kRuntime.registerCommitter(id => {
|
|
1164
|
-
if (!active) return
|
|
1165
|
-
for (const record of dependencies.get(id) ?? []) if (record.mounted) pending.add(record)
|
|
1166
|
-
schedule()
|
|
1167
|
-
})
|
|
1168
|
-
${hasItemDependencies ? `for (const listState of new Set(effects.filter(effect => effect.itemDependencies?.length).map(effect => effect.listState))) __kRuntime.registerListItemHook(listState, root => {
|
|
1169
|
-
if (!active) return
|
|
1170
|
-
for (const record of listRegistrations.get(root) ?? []) if (record.mounted && record.effect.itemDependencies) pending.add(record)
|
|
1171
|
-
schedule()
|
|
1172
|
-
})
|
|
1173
|
-
` : ""}__kRuntime.registerMountHook(root => {
|
|
1174
|
-
if (!active) return
|
|
1175
|
-
for (const marker of matching(root)) {
|
|
1176
|
-
if (marker.dataset.kEffects) {
|
|
1177
|
-
if (listRegistrations.has(marker)) continue
|
|
1178
|
-
const rowRecords = JSON.parse(marker.dataset.kEffects).map(owner => {
|
|
1179
|
-
const template = listTemplates.get(owner)
|
|
1180
|
-
if (!template) throw new Error("Keyed row effect template was not emitted")
|
|
1181
|
-
const record = createRecord(template.effect, template.index${hasRowState ? ", marker" : ""})
|
|
1182
|
-
registerDependencies(record)
|
|
1183
|
-
mount(record, marker)
|
|
1184
|
-
return record
|
|
1185
|
-
})
|
|
1186
|
-
listRegistrations.set(marker, rowRecords)
|
|
1187
|
-
continue
|
|
1188
|
-
}
|
|
1189
|
-
const record = owners.get(marker.dataset.kEffect)
|
|
1190
|
-
if (!record?.mounted) mount(record, marker)
|
|
1191
|
-
}
|
|
1192
|
-
})
|
|
1193
|
-
__kRuntime.registerUnmountHook(root => {
|
|
1194
|
-
if (root === document) {
|
|
1195
|
-
if (!active) return
|
|
1196
|
-
active = false
|
|
1197
|
-
pending.clear()
|
|
1198
|
-
for (const record of [...mountedRecords]) unmount(record, record.effect.list)
|
|
1199
|
-
return
|
|
1200
|
-
}
|
|
1201
|
-
for (const marker of matching(root)) {
|
|
1202
|
-
const rowRecords = listRegistrations.get(marker)
|
|
1203
|
-
if (rowRecords) {
|
|
1204
|
-
for (const record of rowRecords) unmount(record, true)
|
|
1205
|
-
listRegistrations.delete(marker)
|
|
1206
|
-
continue
|
|
1207
|
-
}
|
|
1208
|
-
const record = owners.get(marker.dataset.kEffect)
|
|
1209
|
-
if (record?.marker === marker) unmount(record)
|
|
1210
|
-
}
|
|
1211
|
-
})
|
|
1212
|
-
for (const record of records) if (record.mounted) start(record)
|
|
1213
|
-
__kRuntime.mountDom(document)
|
|
1214
|
-
addEventListener("pagehide", event => {
|
|
1215
|
-
if (!event.persisted) __kRuntime.unmountDom(document)
|
|
1216
|
-
})
|
|
1217
|
-
function matching(root) {
|
|
1218
|
-
const selector = "template[data-k-effect],[data-k-effects]"
|
|
1219
|
-
return [...(root.matches?.(selector) ? [root] : []), ...(root.querySelectorAll?.(selector) ?? [])]
|
|
1220
|
-
}
|
|
1221
|
-
function mount(record, marker) {
|
|
1222
|
-
record.mounted = true
|
|
1223
|
-
record.marker = marker
|
|
1224
|
-
mountedRecords.add(record)
|
|
1225
|
-
const version = ++record.version
|
|
1226
|
-
const begin = () => {
|
|
1227
|
-
if (!active || !record.mounted || record.version !== version || !marker.isConnected) return
|
|
1228
|
-
start(record)
|
|
1229
|
-
}
|
|
1230
|
-
if (record.disposal) record.disposal.then(begin)
|
|
1231
|
-
else begin()
|
|
1232
|
-
}
|
|
1233
|
-
function unmount(record, dynamic = false) {
|
|
1234
|
-
if (!record.mounted) return
|
|
1235
|
-
record.mounted = false
|
|
1236
|
-
record.marker = undefined
|
|
1237
|
-
mountedRecords.delete(record)
|
|
1238
|
-
if (dynamic) unregisterDependencies(record)
|
|
1239
|
-
record.version++
|
|
1240
|
-
pending.delete(record)
|
|
1241
|
-
invokeCleanup(record)
|
|
1242
|
-
}
|
|
1243
|
-
function start(record) {
|
|
1244
|
-
try {
|
|
1245
|
-
record.values = readDependencies(record)
|
|
1246
|
-
invoke(record)
|
|
1247
|
-
} catch (error) {
|
|
1248
|
-
console.error(error)
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
function schedule() {
|
|
1252
|
-
if (!pending.size || scheduled || flushing) return
|
|
1253
|
-
scheduled = true
|
|
1254
|
-
queueMicrotask(flush)
|
|
1255
|
-
}
|
|
1256
|
-
async function flush() {
|
|
1257
|
-
scheduled = false
|
|
1258
|
-
if (!active) return pending.clear()
|
|
1259
|
-
flushing = true
|
|
1260
|
-
try {
|
|
1261
|
-
const selected = [...pending].filter(record => record.mounted).sort((left, right) => left.index - right.index${hasItemDependencies ? " || left.order - right.order" : ""})
|
|
1262
|
-
pending.clear()
|
|
1263
|
-
const changed = []
|
|
1264
|
-
for (const record of selected) {
|
|
1265
|
-
try {
|
|
1266
|
-
const values = readDependencies(record)
|
|
1267
|
-
if (!record.values || values.some((value, index) => !Object.is(value, record.values[index]))) changed.push([record, record.version])
|
|
1268
|
-
} catch (error) {
|
|
1269
|
-
console.error(error)
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
for (const [record] of changed) await invokeCleanup(record)
|
|
1273
|
-
if (active) for (const [record, version] of changed) if (record.mounted && record.version === version) {
|
|
1274
|
-
try {
|
|
1275
|
-
record.values = readDependencies(record)
|
|
1276
|
-
invoke(record)
|
|
1277
|
-
} catch (error) {
|
|
1278
|
-
record.values = undefined
|
|
1279
|
-
console.error(error)
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
} finally {
|
|
1283
|
-
flushing = false
|
|
1284
|
-
if (active) schedule()
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
function readDependencies(record) {
|
|
1288
|
-
${hasDependencyExpressions ? printDerivedDependencyRead("browserState") : ""}
|
|
1289
|
-
const values = (record.effect.dependencies ?? []).map(id => {
|
|
1290
|
-
const value = browserState.get(id)
|
|
1291
|
-
if (value !== null && typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0))) throw new Error("useEffect() dependency state must remain a JSON-safe primitive")
|
|
1292
|
-
return value
|
|
1293
|
-
})
|
|
1294
|
-
${hasItemDependencies ? `if (record.effect.itemDependencies) {
|
|
1295
|
-
const item = JSON.parse(record.marker.dataset.kEffectItem)
|
|
1296
|
-
for (const field of record.effect.itemDependencies) {
|
|
1297
|
-
const value = item[field]
|
|
1298
|
-
if (value !== null && typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0))) throw new Error(\`useEffect() keyed item dependency "\${field}" must remain a JSON-safe primitive\`)
|
|
1299
|
-
values.push(value)
|
|
1300
|
-
}
|
|
1301
|
-
}` : ""}
|
|
1302
|
-
return values
|
|
1303
|
-
}
|
|
1304
|
-
function invoke(record) {
|
|
1305
|
-
const token = { active: true }
|
|
1306
|
-
record.token = token
|
|
1307
|
-
try {
|
|
1308
|
-
const effect = record.effect
|
|
1309
|
-
const scope = effect.list
|
|
1310
|
-
? Object.fromEntries(Object.entries(effect.scope).map(([name, value]) => [name, value?.type === "list-item" ? JSON.parse(record.marker.dataset.kEffectItem) : value]))
|
|
1311
|
-
: effect.scope
|
|
1312
|
-
const result = modules.get(effect.module)[effect.handler](createEffectContext(browserState, effect.states, commitDom, scope, () => active && token.active && record.token === token))
|
|
1313
|
-
if (effect.cleanup && typeof result === "function") record.cleanup = result
|
|
1314
|
-
else if (result && typeof result.then === "function") result.catch(error => console.error(error))
|
|
1315
|
-
} catch (error) {
|
|
1316
|
-
console.error(error)
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
function invokeCleanup(record) {
|
|
1320
|
-
if (record.token) record.token.active = false
|
|
1321
|
-
record.token = undefined
|
|
1322
|
-
if (record.disposal) return record.disposal
|
|
1323
|
-
const cleanup = record.cleanup
|
|
1324
|
-
record.cleanup = undefined
|
|
1325
|
-
if (!cleanup) return Promise.resolve()
|
|
1326
|
-
const disposal = (async () => {
|
|
1327
|
-
try {
|
|
1328
|
-
await cleanup()
|
|
1329
|
-
} catch (error) {
|
|
1330
|
-
console.error(error)
|
|
1331
|
-
}
|
|
1332
|
-
})()
|
|
1333
|
-
record.disposal = disposal
|
|
1334
|
-
disposal.finally(() => {
|
|
1335
|
-
if (record.disposal === disposal) record.disposal = undefined
|
|
1336
|
-
})
|
|
1337
|
-
return disposal
|
|
1338
|
-
}`
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
function printSingleDependencyEffect(imports, effect, hasCleanup) {
|
|
1342
|
-
const disposal = hasCleanup ? `
|
|
1343
|
-
const dispose = root => {
|
|
1344
|
-
if (root !== document || !active) return
|
|
1345
|
-
active = false
|
|
1346
|
-
pending = false
|
|
1347
|
-
invokeCleanup()
|
|
1348
|
-
}
|
|
1349
|
-
if (__kRuntime.registerUnmountHook) __kRuntime.registerUnmountHook(dispose)
|
|
1350
|
-
addEventListener("pagehide", event => {
|
|
1351
|
-
if (event.persisted) return
|
|
1352
|
-
if (__kRuntime.unmountDom) __kRuntime.unmountDom(document)
|
|
1353
|
-
else dispose(document)
|
|
1354
|
-
})` : ""
|
|
1355
|
-
return `${imports.join("\n")}
|
|
1356
|
-
const effect = ${inlineJson(effect)}
|
|
1357
|
-
const dependency = effect.dependencies[0]
|
|
1358
|
-
let value
|
|
1359
|
-
let cleanup
|
|
1360
|
-
let token
|
|
1361
|
-
let active = true
|
|
1362
|
-
let pending = false
|
|
1363
|
-
let scheduled = false
|
|
1364
|
-
let running = false
|
|
1365
|
-
__kRuntime.registerCommitter(id => {
|
|
1366
|
-
if (active && id === dependency) {
|
|
1367
|
-
pending = true
|
|
1368
|
-
schedule()
|
|
1369
|
-
}
|
|
1370
|
-
})
|
|
1371
|
-
try {
|
|
1372
|
-
value = readDependency()
|
|
1373
|
-
invoke()
|
|
1374
|
-
} catch (error) {
|
|
1375
|
-
console.error(error)
|
|
1376
|
-
}
|
|
1377
|
-
function schedule() {
|
|
1378
|
-
if (!pending || scheduled || running) return
|
|
1379
|
-
scheduled = true
|
|
1380
|
-
queueMicrotask(flush)
|
|
1381
|
-
}
|
|
1382
|
-
async function flush() {
|
|
1383
|
-
scheduled = false
|
|
1384
|
-
if (!active) return
|
|
1385
|
-
let next
|
|
1386
|
-
try {
|
|
1387
|
-
next = readDependency()
|
|
1388
|
-
} catch (error) {
|
|
1389
|
-
console.error(error)
|
|
1390
|
-
return
|
|
1391
|
-
}
|
|
1392
|
-
pending = false
|
|
1393
|
-
if (Object.is(next, value)) return
|
|
1394
|
-
value = next
|
|
1395
|
-
running = true
|
|
1396
|
-
try {
|
|
1397
|
-
await invokeCleanup()
|
|
1398
|
-
if (active) invoke()
|
|
1399
|
-
} finally {
|
|
1400
|
-
running = false
|
|
1401
|
-
if (active) schedule()
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
function readDependency() {
|
|
1405
|
-
const next = browserState.get(dependency)
|
|
1406
|
-
if (next !== null && typeof next !== "string" && typeof next !== "boolean" && !(typeof next === "number" && Number.isFinite(next) && !Object.is(next, -0))) throw new Error("useEffect() dependency state must remain a JSON-safe primitive")
|
|
1407
|
-
return next
|
|
1408
|
-
}
|
|
1409
|
-
function invoke() {
|
|
1410
|
-
try {
|
|
1411
|
-
const current = { active: true }
|
|
1412
|
-
token = current
|
|
1413
|
-
const result = __kEffectModule0[effect.handler](createEffectContext(browserState, effect.states, commitDom, effect.scope, () => active && current.active && token === current))
|
|
1414
|
-
if (effect.cleanup && typeof result === "function") cleanup = result
|
|
1415
|
-
else if (result && typeof result.then === "function") result.catch(error => console.error(error))
|
|
1416
|
-
} catch (error) {
|
|
1417
|
-
console.error(error)
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
async function invokeCleanup() {
|
|
1421
|
-
if (token) token.active = false
|
|
1422
|
-
token = undefined
|
|
1423
|
-
const current = cleanup
|
|
1424
|
-
cleanup = undefined
|
|
1425
|
-
if (!current) return
|
|
1426
|
-
try {
|
|
1427
|
-
await current()
|
|
1428
|
-
} catch (error) {
|
|
1429
|
-
console.error(error)
|
|
1430
|
-
}
|
|
1431
|
-
}${disposal}`
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
561
|
function printParamEntry(schema, params, searchParams, searchParamsWritable, output, assetsDirectory, base, runtimeName, navigable) {
|
|
1435
562
|
const hasSearch = searchParams.length || searchParamsWritable
|
|
1436
563
|
const signature = hasSearch ? "pathname, search" : "pathname"
|
|
@@ -1500,20 +627,6 @@ addEventListener("popstate", () => ${searchParams.length ? "initializeSearch(loc
|
|
|
1500
627
|
${searchInitializer}${prefix}${pathname}${query}${suffix}${writer}`
|
|
1501
628
|
}
|
|
1502
629
|
|
|
1503
|
-
function hasCaptureType(value, type) {
|
|
1504
|
-
if (!value || typeof value !== "object") return false
|
|
1505
|
-
if (value.type === type) return true
|
|
1506
|
-
return (Array.isArray(value) ? value : Object.values(value)).some(entry => hasCaptureType(entry, type))
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
function hasNestedCaptureState(value, insideCapture = false) {
|
|
1510
|
-
if (!value || typeof value !== "object") return false
|
|
1511
|
-
if (value.type === "state") return insideCapture
|
|
1512
|
-
if (value.type === "array") return value.value.some(entry => hasNestedCaptureState(entry, true))
|
|
1513
|
-
if (value.type === "object") return value.value.some(([, entry]) => hasNestedCaptureState(entry, true))
|
|
1514
|
-
return (Array.isArray(value) ? value : Object.values(value)).some(entry => hasNestedCaptureState(entry, false))
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
630
|
export function specializeRuntime(source, events, hasStateSeed) {
|
|
1518
631
|
const specialized = specializeEvents(source, events)
|
|
1519
632
|
if (hasStateSeed) return specialized
|
|
@@ -1543,225 +656,17 @@ async function writeBundledJavaScript(file, source, minify, define) {
|
|
|
1543
656
|
await writeFile(file, result.outputFiles[0].contents)
|
|
1544
657
|
}
|
|
1545
658
|
|
|
1546
|
-
export function parseDevPort(value) {
|
|
1547
|
-
if (value === undefined || value.trim() === "") return 3000
|
|
1548
|
-
if (!/^\d+$/.test(value)) throw new Error(`Invalid dev server port: ${value}`)
|
|
1549
|
-
const port = Number(value)
|
|
1550
|
-
if (port > 65535) throw new Error(`Invalid dev server port: ${value}`)
|
|
1551
|
-
return port
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
export function parseDevHost(value) {
|
|
1555
|
-
return value?.trim() || "127.0.0.1"
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
659
|
export async function dev({ port = parseDevPort(process.env.PORT), host = parseDevHost(process.env.HOST) } = {}) {
|
|
1559
660
|
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error(`Invalid dev server port: ${port}`)
|
|
1560
661
|
if (typeof host !== "string" || !host.trim()) throw new Error(`Invalid dev server host: ${host}`)
|
|
1561
662
|
const base = normalizeBase((await loadConfig()).base)
|
|
1562
|
-
|
|
1563
|
-
let buildError
|
|
1564
|
-
let revision = 0
|
|
1565
|
-
const session = randomUUID()
|
|
1566
|
-
try {
|
|
1567
|
-
await build({ minify: false })
|
|
1568
|
-
revision++
|
|
1569
|
-
} catch (error) {
|
|
1570
|
-
buildError = errorText(error)
|
|
1571
|
-
console.error(error)
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
const clients = new Set()
|
|
1575
|
-
|
|
1576
|
-
const server = createServer(async (request, response) => {
|
|
1577
|
-
try {
|
|
1578
|
-
const url = new URL(request.url, "http://localhost")
|
|
1579
|
-
const rawPathname = url.pathname
|
|
1580
|
-
const pathname = decodeURIComponent(rawPathname)
|
|
1581
|
-
if (pathname === "/__kudzu_reload") {
|
|
1582
|
-
response.writeHead(200, {
|
|
1583
|
-
"content-type": "text/event-stream; charset=utf-8",
|
|
1584
|
-
"cache-control": "no-cache, no-transform",
|
|
1585
|
-
connection: "keep-alive"
|
|
1586
|
-
})
|
|
1587
|
-
response.write(": connected\n\n")
|
|
1588
|
-
clients.add(response)
|
|
1589
|
-
request.on("close", () => clients.delete(response))
|
|
1590
|
-
if (buildError) sendEvent(response, "build-error", buildError)
|
|
1591
|
-
else if (url.searchParams.get("session") !== session || url.searchParams.get("revision") !== String(revision)) sendEvent(response, "reload")
|
|
1592
|
-
return
|
|
1593
|
-
}
|
|
1594
|
-
if (pathname === "/__kudzu_dev.js") {
|
|
1595
|
-
response.writeHead(200, { "content-type": "text/javascript; charset=utf-8", "cache-control": "no-store" })
|
|
1596
|
-
response.end(await readFile(new URL("./dev-state.js", import.meta.url)))
|
|
1597
|
-
return
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
const relativePath = stripBaseStrict(pathname, decodeURIComponent(base)).replace(/^\/+/, "")
|
|
1601
|
-
let file = resolve(outputDirectory, relativePath)
|
|
1602
|
-
if (!file.startsWith(`${outputDirectory}${sep}`) && file !== outputDirectory) throw new Error("Invalid path")
|
|
1603
|
-
|
|
1604
|
-
if ((await exists(file)) && (await stat(file)).isDirectory()) file = join(file, "index.html")
|
|
1605
|
-
if (!(await exists(file)) && !extname(file)) file = join(file, "index.html")
|
|
1606
|
-
let matchedRoute
|
|
1607
|
-
if (!(await exists(file)) && !buildError) {
|
|
1608
|
-
const plan = JSON.parse(await readFile(join(workDirectory, "kudzu-plan.json"), "utf8"))
|
|
1609
|
-
const rewrite = plan.rewrites?.find(entry => runtimePathValues(rawPathname, entry, browserPath(base)))
|
|
1610
|
-
if (rewrite) {
|
|
1611
|
-
file = resolve(outputDirectory, rewrite.file)
|
|
1612
|
-
matchedRoute = rewrite.pattern
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
const isHtml = extname(file) === ".html"
|
|
1616
|
-
const content = isHtml
|
|
1617
|
-
? injectDevClient(buildError ? errorPage(buildError) : await readFile(file, "utf8"), session, revision, buildError ? [] : await devSchema(withBase(base, stripBaseStrict(pathname, decodeURIComponent(base))), matchedRoute))
|
|
1618
|
-
: await readFile(file)
|
|
1619
|
-
response.writeHead(200, {
|
|
1620
|
-
"content-type": contentType(file),
|
|
1621
|
-
"cache-control": "no-store"
|
|
1622
|
-
})
|
|
1623
|
-
response.end(content)
|
|
1624
|
-
} catch {
|
|
1625
|
-
response.writeHead(404, { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" })
|
|
1626
|
-
response.end("Not found")
|
|
1627
|
-
}
|
|
1628
|
-
})
|
|
1629
|
-
|
|
1630
|
-
const listeningPort = await listenDevServer(server, port, host)
|
|
1631
|
-
console.log(`Kudzu dev server: http://${host}:${listeningPort}`)
|
|
1632
|
-
|
|
1633
|
-
let timer
|
|
1634
|
-
let rebuilding = false
|
|
1635
|
-
let pending = false
|
|
1636
|
-
let changedFile
|
|
1637
|
-
const rebuild = async () => {
|
|
1638
|
-
if (rebuilding) {
|
|
1639
|
-
pending = true
|
|
1640
|
-
return
|
|
1641
|
-
}
|
|
1642
|
-
rebuilding = true
|
|
1643
|
-
do {
|
|
1644
|
-
pending = false
|
|
1645
|
-
try {
|
|
1646
|
-
await build({ quiet: true, minify: false })
|
|
1647
|
-
buildError = undefined
|
|
1648
|
-
revision++
|
|
1649
|
-
console.log(`Rebuilt after ${changedFile ?? "source change"}`)
|
|
1650
|
-
for (const client of clients) sendEvent(client, "reload")
|
|
1651
|
-
} catch (error) {
|
|
1652
|
-
buildError = errorText(error)
|
|
1653
|
-
console.error(error)
|
|
1654
|
-
for (const client of clients) sendEvent(client, "build-error", buildError)
|
|
1655
|
-
}
|
|
1656
|
-
} while (pending)
|
|
1657
|
-
rebuilding = false
|
|
1658
|
-
}
|
|
1659
|
-
const watcher = watch(sourceDirectory, { recursive: true })
|
|
1660
|
-
for await (const event of watcher) {
|
|
1661
|
-
changedFile = event.filename
|
|
1662
|
-
clearTimeout(timer)
|
|
1663
|
-
timer = setTimeout(rebuild, 80)
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
async function listenDevServer(server, port, host) {
|
|
1668
|
-
let candidate = port
|
|
1669
|
-
while (true) {
|
|
1670
|
-
try {
|
|
1671
|
-
await new Promise((resolve, reject) => {
|
|
1672
|
-
const onError = error => {
|
|
1673
|
-
server.off("listening", onListening)
|
|
1674
|
-
reject(error)
|
|
1675
|
-
}
|
|
1676
|
-
const onListening = () => {
|
|
1677
|
-
server.off("error", onError)
|
|
1678
|
-
resolve()
|
|
1679
|
-
}
|
|
1680
|
-
server.once("error", onError)
|
|
1681
|
-
server.once("listening", onListening)
|
|
1682
|
-
server.listen(candidate, host)
|
|
1683
|
-
})
|
|
1684
|
-
return server.address().port
|
|
1685
|
-
} catch (error) {
|
|
1686
|
-
if (error.code !== "EADDRINUSE" || candidate === 0 || candidate === 65535) throw error
|
|
1687
|
-
console.log(`Port ${candidate} is in use, trying ${candidate + 1}`)
|
|
1688
|
-
candidate++
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
function injectDevClient(html, session, revision, schema) {
|
|
1694
|
-
return `${html}${devClient(session, revision, schema).replace("binding|list|native", "binding|deps|list|native")}`
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
function stripBaseStrict(path, base) {
|
|
1698
|
-
if (!base) return path
|
|
1699
|
-
if (path === base) return "/"
|
|
1700
|
-
if (path.startsWith(`${base}/`)) return path.slice(base.length)
|
|
1701
|
-
throw new Error("Path is outside the configured base")
|
|
1702
|
-
}
|
|
1703
|
-
|
|
1704
|
-
async function devSchema(pathname, matchedRoute) {
|
|
1705
|
-
try {
|
|
1706
|
-
const plan = JSON.parse(await readFile(join(workDirectory, "kudzu-plan.json"), "utf8"))
|
|
1707
|
-
const route = matchedRoute ?? (pathname.replace(/\/(?:index\.html)?$/, "") || "/")
|
|
1708
|
-
return stateSchema(plan.routes.find(entry => entry.route === route)?.states ?? [])
|
|
1709
|
-
} catch {
|
|
1710
|
-
return []
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
|
-
function runtimePathValues(pathname, rewrite, base) {
|
|
1715
|
-
try {
|
|
1716
|
-
let path = stripBrowserBase(pathname, base)
|
|
1717
|
-
if (path.length > 1 && path.endsWith("/")) path = path.slice(0, -1)
|
|
1718
|
-
const rawSegments = path.slice(1).split("/")
|
|
1719
|
-
if (rawSegments.length !== rewrite.segments.length) return undefined
|
|
1720
|
-
const values = Object.create(null)
|
|
1721
|
-
for (let index = 0; index < rewrite.segments.length; index++) {
|
|
1722
|
-
const segment = rewrite.segments[index]
|
|
1723
|
-
const value = decodeRuntimeSegment(rawSegments[index], Boolean(segment.param))
|
|
1724
|
-
if (segment.literal !== undefined && value !== segment.literal) return undefined
|
|
1725
|
-
if (segment.param) values[segment.param] = value
|
|
1726
|
-
}
|
|
1727
|
-
return values
|
|
1728
|
-
} catch {
|
|
1729
|
-
return undefined
|
|
1730
|
-
}
|
|
1731
|
-
}
|
|
1732
|
-
|
|
1733
|
-
function stripBrowserBase(path, base) {
|
|
1734
|
-
if (!base) return path
|
|
1735
|
-
const pathSegments = path.slice(1).split("/")
|
|
1736
|
-
const baseSegments = base.slice(1).split("/").map(segment => decodeURIComponent(segment))
|
|
1737
|
-
if (pathSegments.length < baseSegments.length || baseSegments.some((segment, index) => decodeRuntimeSegment(pathSegments[index], false) !== segment)) throw new Error("Path is outside the configured base")
|
|
1738
|
-
return `/${pathSegments.slice(baseSegments.length).join("/")}`
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
function decodeRuntimeSegment(raw, param) {
|
|
1742
|
-
if (param && /%(?:2f|5c)/i.test(raw)) throw new Error("Encoded separator")
|
|
1743
|
-
const value = decodeURIComponent(raw)
|
|
1744
|
-
const decodedDots = value.replace(/%2e/gi, ".")
|
|
1745
|
-
if (param && (!value || value === "." || value === ".." || decodedDots === "." || decodedDots === ".." || /[\\/?#]/.test(value) || [...value].some(character => character.charCodeAt(0) < 32 || character.charCodeAt(0) >= 127 && character.charCodeAt(0) <= 159) || /%(?:2f|5c)/i.test(value))) throw new Error("Invalid runtime parameter")
|
|
1746
|
-
return value
|
|
663
|
+
return startDevServer({ build, port, host, base, sourceDirectory, workDirectory, outputDirectory })
|
|
1747
664
|
}
|
|
1748
665
|
|
|
1749
666
|
function inlineJson(value) {
|
|
1750
667
|
return JSON.stringify(value).replaceAll("<", "\\u003c").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029")
|
|
1751
668
|
}
|
|
1752
669
|
|
|
1753
|
-
function errorPage(error) {
|
|
1754
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Kudzu build error</title></head><body><div id="__kudzu_error" role="alert" aria-live="assertive" style="position:fixed;inset:0;overflow:auto;padding:2rem;background:#200;color:#fff;font:16px/1.5 ui-monospace,monospace"><strong>Kudzu build error</strong><pre style="white-space:pre-wrap">${escapeHtml(error)}</pre></div></body></html>`
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
function errorText(error) {
|
|
1758
|
-
return String(error?.message ?? error)
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
function sendEvent(response, event, data = "") {
|
|
1762
|
-
response.write(`event: ${event}\n${String(data).replaceAll("\r", "").split("\n").map(line => `data: ${line}\n`).join("")}\n`)
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
670
|
function escapeHtml(value) {
|
|
1766
671
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")
|
|
1767
672
|
}
|
|
@@ -1772,11 +677,7 @@ function escapeAttribute(value) {
|
|
|
1772
677
|
|
|
1773
678
|
async function compile(file, sourceFiles, sourceIndex, staticFiles, importedAssets, cssModules, base, workerReferences) {
|
|
1774
679
|
const source = sourceIndex.get(file)
|
|
1775
|
-
const
|
|
1776
|
-
const effectHandlers = []
|
|
1777
|
-
const reactiveBindings = []
|
|
1778
|
-
const listExpressions = []
|
|
1779
|
-
const clientImports = new Set()
|
|
680
|
+
const semantic = createSemanticArtifact()
|
|
1780
681
|
const handlerPath = `handlers/${relative(sourceDirectory, file).replaceAll(sep, "/").replace(/\.(?:ts|tsx)$/, ".js")}`
|
|
1781
682
|
const result = ts.transpileModule(source, {
|
|
1782
683
|
fileName: file,
|
|
@@ -1786,7 +687,7 @@ async function compile(file, sourceFiles, sourceIndex, staticFiles, importedAsse
|
|
|
1786
687
|
jsx: ts.JsxEmit.ReactJSX,
|
|
1787
688
|
jsxImportSource: "@kudzujs/core"
|
|
1788
689
|
},
|
|
1789
|
-
transformers: { before: [createKudzuTransformer(
|
|
690
|
+
transformers: { before: [createKudzuTransformer({ semantic, handlerUrl: assetPath(base, `assets/${handlerPath}`), file, sourceFiles, sourceIndex, staticFiles, importedAssets, cssModules, base, workerReferences })] },
|
|
1790
691
|
reportDiagnostics: true
|
|
1791
692
|
})
|
|
1792
693
|
|
|
@@ -1801,14 +702,10 @@ async function compile(file, sourceFiles, sourceIndex, staticFiles, importedAsse
|
|
|
1801
702
|
await mkdir(resolve(output, ".."), { recursive: true })
|
|
1802
703
|
await writeFile(output, result.outputText)
|
|
1803
704
|
|
|
705
|
+
const { nativeHandlers, effectHandlers, reactiveBindings, listExpressions, clientImports } = semantic
|
|
1804
706
|
if (!nativeHandlers.length && !effectHandlers.length && !reactiveBindings.length && !listExpressions.length) return undefined
|
|
1805
707
|
const callbacks = [...nativeHandlers, ...effectHandlers]
|
|
1806
|
-
const moduleSource =
|
|
1807
|
-
printClientImports([...callbacks, ...reactiveBindings].flatMap(handler => handler.imports ?? []), handlerPath),
|
|
1808
|
-
...callbacks.map(handler => printNativeHandler(handler)),
|
|
1809
|
-
...reactiveBindings.map(entry => printReactiveBinding(entry)),
|
|
1810
|
-
...listExpressions.map(entry => printListExpression(entry))
|
|
1811
|
-
].join("\n")
|
|
708
|
+
const moduleSource = printHandlerModule({ callbacks, reactiveBindings, listExpressions, handlerPath })
|
|
1812
709
|
const moduleResult = ts.transpileModule(moduleSource, {
|
|
1813
710
|
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext },
|
|
1814
711
|
reportDiagnostics: true
|
|
@@ -1840,259 +737,18 @@ function reachableSourceFiles(entries, sourceFiles, sourceIndex) {
|
|
|
1840
737
|
const sourceFile = parseSourceFile(file, sourceIndex.get(file))
|
|
1841
738
|
const visit = node => {
|
|
1842
739
|
const specifier = (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && runtimeModuleReference(node) && node.moduleSpecifier
|
|
1843
|
-
if (specifier && ts.isStringLiteral(specifier) && specifier.text.startsWith(".") && !isStaticImport(specifier.text)) {
|
|
1844
|
-
try { queue.push(resolveSourceImport(file, specifier.text, sourceFiles)) } catch {}
|
|
1845
|
-
}
|
|
1846
|
-
const worker = relativeWorkerCandidate(node, sourceFile)
|
|
1847
|
-
if (worker && ts.isStringLiteral(worker.url.arguments[0]) && worker.url.arguments[0].text.endsWith(".worker.ts")) {
|
|
1848
|
-
try { queue.push(resolveSourceImport(file, worker.url.arguments[0].text, sourceFiles)) } catch {}
|
|
1849
|
-
}
|
|
1850
|
-
ts.forEachChild(node, visit)
|
|
1851
|
-
}
|
|
1852
|
-
visit(sourceFile)
|
|
1853
|
-
}
|
|
1854
|
-
return [...reachable].sort()
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
function normalizeReactRouterSyntax(sourceFile, factory, context, base) {
|
|
1858
|
-
const links = new Set()
|
|
1859
|
-
const params = new Set()
|
|
1860
|
-
const searchHooks = new Set()
|
|
1861
|
-
const navigateHooks = new Set()
|
|
1862
|
-
for (const statement of sourceFile.statements) {
|
|
1863
|
-
if ((ts.isExportDeclaration(statement) || ts.isImportDeclaration(statement)) && statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === "react-router-dom") {
|
|
1864
|
-
if (ts.isExportDeclaration(statement)) throw sourceNodeError(statement, sourceFile, "React Router exports are not supported; import Link directly where it renders")
|
|
1865
|
-
const clause = statement.importClause
|
|
1866
|
-
if (clause?.isTypeOnly) continue
|
|
1867
|
-
if (!clause) throw sourceNodeError(statement, sourceFile, "Side-effect React Router imports are not supported")
|
|
1868
|
-
if (clause.name) throw sourceNodeError(clause.name, sourceFile, "React Router default imports are not supported; use named Link, useParams, useSearchParams, or useNavigate imports")
|
|
1869
|
-
const bindings = clause.namedBindings
|
|
1870
|
-
if (!bindings || ts.isNamespaceImport(bindings)) throw sourceNodeError(bindings ?? statement, sourceFile, "React Router namespace imports are not supported; use named Link, useParams, useSearchParams, or useNavigate imports")
|
|
1871
|
-
for (const entry of bindings.elements) {
|
|
1872
|
-
if (entry.isTypeOnly) continue
|
|
1873
|
-
const imported = (entry.propertyName ?? entry.name).text
|
|
1874
|
-
if (imported === "NavLink") throw sourceNodeError(entry, sourceFile, "React Router NavLink active-route semantics cannot be erased to a native anchor")
|
|
1875
|
-
if (imported === "Link") links.add(entry.name.text)
|
|
1876
|
-
else if (imported === "useParams") params.add(entry.name.text)
|
|
1877
|
-
else if (imported === "useSearchParams") searchHooks.add(entry.name.text)
|
|
1878
|
-
else if (imported === "useNavigate") navigateHooks.add(entry.name.text)
|
|
1879
|
-
else throw sourceNodeError(entry, sourceFile, `React Router ${imported} is not supported; only named Link, useParams, useSearchParams, and useNavigate imports can be lowered`)
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
if (!links.size && !params.size && !searchHooks.size && !navigateHooks.size) return sourceFile
|
|
1884
|
-
|
|
1885
|
-
let searchHelper = "__kUseSearchParam"
|
|
1886
|
-
while (sourceFile.text.includes(searchHelper)) searchHelper += "_"
|
|
1887
|
-
let searchWriterHelper = "__kUseSearchParamsWriter"
|
|
1888
|
-
while (sourceFile.text.includes(searchWriterHelper)) searchWriterHelper += "_"
|
|
1889
|
-
const searchDeclarations = new Set()
|
|
1890
|
-
const searchReads = new Map()
|
|
1891
|
-
const searchWrites = new Map()
|
|
1892
|
-
const searchObjects = []
|
|
1893
|
-
const collectSearchHooks = node => {
|
|
1894
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && searchHooks.has(node.expression.text) && !isShadowedIdentifier(node.expression, sourceFile)) {
|
|
1895
|
-
const declaration = node.parent
|
|
1896
|
-
const statement = declaration?.parent?.parent
|
|
1897
|
-
const owner = nearestFunction(node)
|
|
1898
|
-
const first = ts.isVariableDeclaration(declaration) && ts.isArrayBindingPattern(declaration.name) ? declaration.name.elements[0] : undefined
|
|
1899
|
-
const second = ts.isVariableDeclaration(declaration) && ts.isArrayBindingPattern(declaration.name) ? declaration.name.elements[1] : undefined
|
|
1900
|
-
if (node.questionDotToken || node.arguments.length || node.typeArguments?.length || !ts.isVariableDeclaration(declaration) || declaration.initializer !== node || !isLocalConst(declaration) || !owner || statement?.parent !== owner.body || !ts.isBindingElement(first) || !ts.isIdentifier(first.name) || declaration.name.elements.length > 2 || second && (!ts.isBindingElement(second) || !ts.isIdentifier(second.name))) {
|
|
1901
|
-
throw sourceNodeError(node, sourceFile, "React Router useSearchParams must initialize one top-level const [params] or [params, setParams] binding")
|
|
1902
|
-
}
|
|
1903
|
-
const entry = { name: first.name.text, setter: second?.name.text, declaration, statement, owner }
|
|
1904
|
-
searchDeclarations.add(declaration)
|
|
1905
|
-
searchObjects.push(entry)
|
|
1906
|
-
}
|
|
1907
|
-
ts.forEachChild(node, collectSearchHooks)
|
|
1908
|
-
}
|
|
1909
|
-
collectSearchHooks(sourceFile)
|
|
1910
|
-
const localBindingShadowed = (node, entry, name = entry.name) => {
|
|
1911
|
-
for (let current = node.parent; current && current !== entry.owner; current = current.parent) {
|
|
1912
|
-
if (isFunctionLike(current) && (current.parameters.some(parameter => bindingNames(parameter.name).includes(name)) || functionVarDeclaresName(current, name))) return true
|
|
1913
|
-
if (ts.isBlock(current) && current.statements.some(statement => statement !== entry.statement && statementDeclaresName(statement, name))) return true
|
|
1914
|
-
if (ts.isCaseBlock(current) && current.clauses.some(clause => clause.statements.some(statement => statement !== entry.statement && statementDeclaresName(statement, name)))) return true
|
|
1915
|
-
if (ts.isCatchClause(current) && current.variableDeclaration && bindingNames(current.variableDeclaration.name).includes(name)) return true
|
|
1916
|
-
if ((ts.isForStatement(current) || ts.isForInStatement(current) || ts.isForOfStatement(current)) && loopDeclaresName(current, name)) return true
|
|
1917
|
-
}
|
|
1918
|
-
return false
|
|
1919
|
-
}
|
|
1920
|
-
for (const entry of searchObjects) {
|
|
1921
|
-
const collectReads = node => {
|
|
1922
|
-
if (ts.isIdentifier(node) && node.text === entry.name && isReferenceIdentifier(node) && !localBindingShadowed(node, entry)) {
|
|
1923
|
-
if (entry.setter && ts.isCallExpression(node.parent) && node.parent.arguments.includes(node) && ts.isIdentifier(node.parent.expression) && node.parent.expression.text === entry.setter) return
|
|
1924
|
-
const property = node.parent
|
|
1925
|
-
const call = property?.parent
|
|
1926
|
-
const declaration = call?.parent
|
|
1927
|
-
const statement = declaration?.parent?.parent
|
|
1928
|
-
if (!ts.isPropertyAccessExpression(property) || property.expression !== node || property.name.text !== "get" || !ts.isCallExpression(call) || call.expression !== property || call.questionDotToken || call.typeArguments?.length || call.arguments.length !== 1 || !ts.isStringLiteral(call.arguments[0])) {
|
|
1929
|
-
throw sourceNodeError(node, sourceFile, 'React Router search parameters only support direct get("static-name") reads')
|
|
1930
|
-
}
|
|
1931
|
-
if (!ts.isVariableDeclaration(declaration) || declaration.initializer !== call || !ts.isIdentifier(declaration.name) || !isLocalConst(declaration) || statement?.parent !== entry.owner.body) {
|
|
1932
|
-
throw sourceNodeError(call, sourceFile, "React Router search parameter get() must directly initialize one top-level const identifier")
|
|
1933
|
-
}
|
|
1934
|
-
searchReads.set(call, call.arguments[0])
|
|
1935
|
-
return
|
|
1936
|
-
}
|
|
1937
|
-
ts.forEachChild(node, collectReads)
|
|
1938
|
-
}
|
|
1939
|
-
collectReads(entry.owner.body)
|
|
1940
|
-
if (!entry.setter) continue
|
|
1941
|
-
const collectWrites = node => {
|
|
1942
|
-
if (ts.isIdentifier(node) && node.text === entry.setter && isReferenceIdentifier(node) && !localBindingShadowed(node, entry, entry.setter)) {
|
|
1943
|
-
const call = node.parent
|
|
1944
|
-
if (!ts.isCallExpression(call) || call.expression !== node || call.questionDotToken || call.typeArguments?.length || nearestFunction(call) === entry.owner) throw sourceNodeError(node, sourceFile, "React Router search parameter setters may only be called directly from a nested browser callback")
|
|
1945
|
-
if (call.arguments.length < 1 || call.arguments.length > 2) throw sourceNodeError(call, sourceFile, "React Router search parameter setters require one inline updater and optional { replace: true }")
|
|
1946
|
-
const updater = unwrapExpression(call.arguments[0])
|
|
1947
|
-
if ((!ts.isArrowFunction(updater) && !ts.isFunctionExpression(updater)) || updater.asteriskToken || updater.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword) || updater.parameters.length !== 1 || !ts.isIdentifier(updater.parameters[0].name)) throw sourceNodeError(call.arguments[0], sourceFile, "React Router search parameter setters require one synchronous inline updater with one identifier parameter")
|
|
1948
|
-
let replace = false
|
|
1949
|
-
if (call.arguments.length === 2) {
|
|
1950
|
-
const options = unwrapExpression(call.arguments[1])
|
|
1951
|
-
const property = ts.isObjectLiteralExpression(options) && options.properties.length === 1 ? options.properties[0] : undefined
|
|
1952
|
-
const name = property && ts.isPropertyAssignment(property) && !ts.isComputedPropertyName(property.name) && (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) ? property.name.text : undefined
|
|
1953
|
-
if (name !== "replace" || property.initializer.kind !== ts.SyntaxKind.TrueKeyword) throw sourceNodeError(call.arguments[1], sourceFile, "React Router search parameter setters only support exactly { replace: true } as a second argument")
|
|
1954
|
-
replace = true
|
|
1955
|
-
}
|
|
1956
|
-
searchWrites.set(call, { updater, replace })
|
|
1957
|
-
return
|
|
1958
|
-
}
|
|
1959
|
-
ts.forEachChild(node, collectWrites)
|
|
1960
|
-
}
|
|
1961
|
-
collectWrites(entry.owner.body)
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
|
-
const navigateDeclarations = new Set()
|
|
1965
|
-
const navigateCalls = new Map()
|
|
1966
|
-
const navigateFunctions = []
|
|
1967
|
-
const collectNavigateHooks = node => {
|
|
1968
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && navigateHooks.has(node.expression.text) && !isShadowedIdentifier(node.expression, sourceFile)) {
|
|
1969
|
-
const declaration = node.parent
|
|
1970
|
-
const statement = declaration?.parent?.parent
|
|
1971
|
-
const owner = nearestFunction(node)
|
|
1972
|
-
if (node.questionDotToken || node.arguments.length || node.typeArguments?.length || !ts.isVariableDeclaration(declaration) || declaration.initializer !== node || !ts.isIdentifier(declaration.name) || !isLocalConst(declaration) || !owner || statement?.parent !== owner.body) {
|
|
1973
|
-
throw sourceNodeError(node, sourceFile, "React Router useNavigate must initialize one top-level const identifier in a component")
|
|
1974
|
-
}
|
|
1975
|
-
const entry = { name: declaration.name.text, declaration, statement, owner }
|
|
1976
|
-
navigateDeclarations.add(declaration)
|
|
1977
|
-
navigateFunctions.push(entry)
|
|
1978
|
-
}
|
|
1979
|
-
ts.forEachChild(node, collectNavigateHooks)
|
|
1980
|
-
}
|
|
1981
|
-
collectNavigateHooks(sourceFile)
|
|
1982
|
-
for (const entry of navigateFunctions) {
|
|
1983
|
-
const collectCalls = node => {
|
|
1984
|
-
if (ts.isIdentifier(node) && node.text === entry.name && isReferenceIdentifier(node) && !localBindingShadowed(node, entry)) {
|
|
1985
|
-
const call = node.parent
|
|
1986
|
-
if (!ts.isCallExpression(call) || call.expression !== node || call.questionDotToken || call.typeArguments?.length || nearestFunction(call) === entry.owner) {
|
|
1987
|
-
throw sourceNodeError(node, sourceFile, "React Router navigate bindings may only be called directly from a nested browser callback")
|
|
1988
|
-
}
|
|
1989
|
-
if (call.arguments.length < 1 || call.arguments.length > 2 || !ts.isStringLiteral(call.arguments[0])) {
|
|
1990
|
-
throw sourceNodeError(call, sourceFile, 'React Router useNavigate requires a static root-relative navigate("/path") destination')
|
|
1991
|
-
}
|
|
1992
|
-
const destination = call.arguments[0].text
|
|
1993
|
-
const pathname = destination.match(/^[^?#]*/)[0]
|
|
1994
|
-
let decoded
|
|
1995
|
-
try { decoded = decodeURIComponent(pathname) } catch { throw sourceNodeError(call.arguments[0], sourceFile, 'React Router useNavigate requires a safe static root-relative navigate("/path") destination') }
|
|
1996
|
-
if (!destination.startsWith("/") || destination.startsWith("//") || /%(?:2f|5c)/i.test(pathname) || /[\\\0]/.test(decoded) || decoded.split("/").includes("..") || [...decoded].some(character => character.charCodeAt(0) < 32 || character.charCodeAt(0) >= 127 && character.charCodeAt(0) <= 159)) throw sourceNodeError(call.arguments[0], sourceFile, 'React Router useNavigate requires a safe static root-relative navigate("/path") destination')
|
|
1997
|
-
let method = "assign"
|
|
1998
|
-
if (call.arguments.length === 2) {
|
|
1999
|
-
const options = unwrapExpression(call.arguments[1])
|
|
2000
|
-
const property = ts.isObjectLiteralExpression(options) && options.properties.length === 1 ? options.properties[0] : undefined
|
|
2001
|
-
const name = property && ts.isPropertyAssignment(property) && !ts.isComputedPropertyName(property.name) && (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) ? property.name.text : undefined
|
|
2002
|
-
if (name !== "replace" || property.initializer.kind !== ts.SyntaxKind.TrueKeyword) throw sourceNodeError(call.arguments[1], sourceFile, 'React Router useNavigate only supports exactly { replace: true } as a second argument')
|
|
2003
|
-
method = "replace"
|
|
2004
|
-
}
|
|
2005
|
-
navigateCalls.set(call, { method, destination: withBase(base, destination) })
|
|
2006
|
-
return
|
|
2007
|
-
}
|
|
2008
|
-
ts.forEachChild(node, collectCalls)
|
|
2009
|
-
}
|
|
2010
|
-
collectCalls(entry.owner.body)
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
const routerProps = new Set(["discover", "end", "prefetch", "preventScrollReset", "relative", "reloadDocument", "replace", "state", "viewTransition"])
|
|
2014
|
-
const attributes = attributesNode => {
|
|
2015
|
-
const output = []
|
|
2016
|
-
let destination
|
|
2017
|
-
for (const property of attributesNode.properties) {
|
|
2018
|
-
if (ts.isJsxSpreadAttribute(property)) throw sourceNodeError(property, sourceFile, "React Router Link does not support spread attributes during native anchor lowering")
|
|
2019
|
-
const name = property.name.text
|
|
2020
|
-
if (name === "href") throw sourceNodeError(property, sourceFile, "React Router Link must not declare href; Kudzu derives it from to")
|
|
2021
|
-
if (routerProps.has(name)) throw sourceNodeError(property, sourceFile, `React Router Link prop ${JSON.stringify(name)} cannot be erased to a native anchor`)
|
|
2022
|
-
if (name !== "to") {
|
|
2023
|
-
output.push(ts.visitEachChild(property, visitor, context))
|
|
2024
|
-
continue
|
|
740
|
+
if (specifier && ts.isStringLiteral(specifier) && specifier.text.startsWith(".") && !isStaticImport(specifier.text)) {
|
|
741
|
+
try { queue.push(resolveSourceImport(file, specifier.text, sourceFiles)) } catch {}
|
|
2025
742
|
}
|
|
2026
|
-
|
|
2027
|
-
if (
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
try { decoded = decodeURIComponent(pathname) } catch { throw sourceNodeError(property.initializer, sourceFile, 'React Router Link requires a safe static root-relative to="/path"') }
|
|
2032
|
-
if (!destination.startsWith("/") || destination.startsWith("//") || /%(?:2f|5c)/i.test(pathname) || /[\\\0]/.test(decoded) || decoded.split("/").includes("..") || [...decoded].some(character => character.charCodeAt(0) < 32 || character.charCodeAt(0) >= 127 && character.charCodeAt(0) <= 159)) throw sourceNodeError(property.initializer, sourceFile, 'React Router Link requires a safe static root-relative to="/path"')
|
|
2033
|
-
output.push(factory.createJsxAttribute(factory.createIdentifier("href"), factory.createStringLiteral(withBase(base, destination))))
|
|
2034
|
-
}
|
|
2035
|
-
if (destination === undefined) throw sourceNodeError(attributesNode.parent, sourceFile, "React Router Link requires exactly one static root-relative to attribute")
|
|
2036
|
-
return factory.updateJsxAttributes(attributesNode, output)
|
|
2037
|
-
}
|
|
2038
|
-
const importedLink = tag => ts.isIdentifier(tag) && links.has(tag.text) && !isShadowedIdentifier(tag, sourceFile)
|
|
2039
|
-
const visitor = node => {
|
|
2040
|
-
if (ts.isVariableStatement(node) && node.declarationList.declarations.some(declaration => searchDeclarations.has(declaration) || navigateDeclarations.has(declaration))) {
|
|
2041
|
-
const declarations = node.declarationList.declarations.flatMap(declaration => {
|
|
2042
|
-
if (navigateDeclarations.has(declaration)) return []
|
|
2043
|
-
if (!searchDeclarations.has(declaration)) return [ts.visitEachChild(declaration, visitor, context)]
|
|
2044
|
-
const entry = searchObjects.find(candidate => candidate.declaration === declaration)
|
|
2045
|
-
if (!entry?.setter) return []
|
|
2046
|
-
return [factory.updateVariableDeclaration(declaration, declaration.name, declaration.exclamationToken, declaration.type, factory.createCallExpression(factory.createIdentifier(searchWriterHelper), undefined, []))]
|
|
2047
|
-
})
|
|
2048
|
-
if (!declarations.length) return undefined
|
|
2049
|
-
return factory.updateVariableStatement(node, node.modifiers, factory.updateVariableDeclarationList(node.declarationList, declarations))
|
|
2050
|
-
}
|
|
2051
|
-
if (ts.isCallExpression(node) && searchReads.has(node)) return factory.createCallExpression(factory.createIdentifier(searchHelper), undefined, [searchReads.get(node)])
|
|
2052
|
-
if (ts.isCallExpression(node) && searchWrites.has(node)) {
|
|
2053
|
-
const { updater, replace } = searchWrites.get(node)
|
|
2054
|
-
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("globalThis"), "__kSetSearchParams"), undefined, [ts.visitNode(updater, visitor), replace ? factory.createTrue() : factory.createFalse()])
|
|
2055
|
-
}
|
|
2056
|
-
if (ts.isCallExpression(node) && navigateCalls.has(node)) {
|
|
2057
|
-
const { method, destination } = navigateCalls.get(node)
|
|
2058
|
-
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier("globalThis"), "location"), method), undefined, [factory.createStringLiteral(destination)])
|
|
2059
|
-
}
|
|
2060
|
-
if (ts.isJsxElement(node) && importedLink(node.openingElement.tagName)) {
|
|
2061
|
-
const opening = factory.updateJsxOpeningElement(node.openingElement, factory.createIdentifier("a"), node.openingElement.typeArguments, attributes(node.openingElement.attributes))
|
|
2062
|
-
const closing = factory.updateJsxClosingElement(node.closingElement, factory.createIdentifier("a"))
|
|
2063
|
-
return factory.updateJsxElement(node, opening, ts.visitNodes(node.children, visitor), closing)
|
|
2064
|
-
}
|
|
2065
|
-
if (ts.isJsxSelfClosingElement(node) && importedLink(node.tagName)) return factory.updateJsxSelfClosingElement(node, factory.createIdentifier("a"), node.typeArguments, attributes(node.attributes))
|
|
2066
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && params.has(node.expression.text) && !isShadowedIdentifier(node.expression, sourceFile)) {
|
|
2067
|
-
if (node.questionDotToken || node.arguments.length || (node.typeArguments?.length ?? 0) > 1) throw sourceNodeError(node, sourceFile, "React Router useParams must be called directly without runtime arguments and with at most one type argument")
|
|
2068
|
-
return node
|
|
2069
|
-
}
|
|
2070
|
-
if (ts.isIdentifier(node) && links.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, sourceFile)) throw sourceNodeError(node, sourceFile, "React Router Link imports may only be used as direct JSX elements")
|
|
2071
|
-
if (ts.isIdentifier(node) && params.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, sourceFile)) throw sourceNodeError(node, sourceFile, "React Router useParams imports may only be called directly")
|
|
2072
|
-
if (ts.isIdentifier(node) && searchHooks.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, sourceFile)) throw sourceNodeError(node, sourceFile, "React Router useSearchParams imports may only initialize the supported top-level tuple binding")
|
|
2073
|
-
if (ts.isIdentifier(node) && navigateHooks.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, sourceFile)) throw sourceNodeError(node, sourceFile, "React Router useNavigate imports may only initialize the supported top-level navigate binding")
|
|
2074
|
-
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === "react-router-dom") {
|
|
2075
|
-
const clause = node.importClause
|
|
2076
|
-
if (!clause || clause.isTypeOnly) return node
|
|
2077
|
-
const bindings = clause.namedBindings
|
|
2078
|
-
if (!bindings || !ts.isNamedImports(bindings)) return node
|
|
2079
|
-
const elements = bindings.elements.filter(entry => entry.isTypeOnly || !["Link", "useParams", "useSearchParams", "useNavigate"].includes((entry.propertyName ?? entry.name).text))
|
|
2080
|
-
if (!elements.length) return undefined
|
|
2081
|
-
return factory.updateImportDeclaration(node, node.modifiers, factory.updateImportClause(clause, clause.isTypeOnly, undefined, factory.updateNamedImports(bindings, elements)), node.moduleSpecifier, node.attributes)
|
|
743
|
+
const worker = workerCompiler.candidate(node, sourceFile)
|
|
744
|
+
if (worker && ts.isStringLiteral(worker.url.arguments[0]) && worker.url.arguments[0].text.endsWith(".worker.ts")) {
|
|
745
|
+
try { queue.push(resolveSourceImport(file, worker.url.arguments[0].text, sourceFiles)) } catch {}
|
|
746
|
+
}
|
|
747
|
+
ts.forEachChild(node, visit)
|
|
2082
748
|
}
|
|
2083
|
-
|
|
749
|
+
visit(sourceFile)
|
|
2084
750
|
}
|
|
2085
|
-
|
|
2086
|
-
if (!params.size && !searchHooks.size) return normalized
|
|
2087
|
-
const imports = [
|
|
2088
|
-
...[...params].map(name => factory.createImportSpecifier(false, name === "useParams" ? undefined : factory.createIdentifier("useParams"), factory.createIdentifier(name))),
|
|
2089
|
-
...(searchReads.size ? [factory.createImportSpecifier(false, factory.createIdentifier("useSearchParam"), factory.createIdentifier(searchHelper))] : []),
|
|
2090
|
-
...(searchObjects.some(entry => entry.setter) ? [factory.createImportSpecifier(false, factory.createIdentifier("useSearchParamsWriter"), factory.createIdentifier(searchWriterHelper))] : [])
|
|
2091
|
-
]
|
|
2092
|
-
const declaration = factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports(imports)), factory.createStringLiteral("@kudzujs/core"))
|
|
2093
|
-
const statements = [...normalized.statements]
|
|
2094
|
-
statements.splice(statements.findLastIndex(statement => ts.isImportDeclaration(statement)) + 1, 0, declaration)
|
|
2095
|
-
return factory.updateSourceFile(normalized, statements)
|
|
751
|
+
return [...reachable].sort()
|
|
2096
752
|
}
|
|
2097
753
|
|
|
2098
754
|
function normalizeClsxSyntax(sourceFile, factory, context) {
|
|
@@ -2142,360 +798,6 @@ function normalizeClsxSyntax(sourceFile, factory, context) {
|
|
|
2142
798
|
return ts.visitNode(sourceFile, visitor)
|
|
2143
799
|
}
|
|
2144
800
|
|
|
2145
|
-
function analyzeZustandStores(sourceFile) {
|
|
2146
|
-
const createNames = new Set()
|
|
2147
|
-
for (const statement of sourceFile.statements) {
|
|
2148
|
-
if (!ts.isImportDeclaration(statement) || statement.importClause?.isTypeOnly || !ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== "zustand") continue
|
|
2149
|
-
const bindings = statement.importClause?.namedBindings
|
|
2150
|
-
if (statement.importClause?.name || !bindings || !ts.isNamedImports(bindings)) throw sourceNodeError(statement, sourceFile, "Zustand migration input requires a named create import")
|
|
2151
|
-
for (const entry of bindings.elements) {
|
|
2152
|
-
if (entry.isTypeOnly) continue
|
|
2153
|
-
if ((entry.propertyName ?? entry.name).text !== "create") throw sourceNodeError(entry, sourceFile, "Only Zustand create is supported")
|
|
2154
|
-
createNames.add(entry.name.text)
|
|
2155
|
-
}
|
|
2156
|
-
}
|
|
2157
|
-
const stores = new Map()
|
|
2158
|
-
if (!createNames.size) return stores
|
|
2159
|
-
for (const statement of sourceFile.statements) {
|
|
2160
|
-
if (!ts.isVariableStatement(statement) || !statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)) continue
|
|
2161
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
2162
|
-
if (!ts.isIdentifier(declaration.name) || !declaration.initializer || !ts.isCallExpression(declaration.initializer) || !ts.isIdentifier(declaration.initializer.expression) || !createNames.has(declaration.initializer.expression.text)) continue
|
|
2163
|
-
const callback = declaration.initializer.arguments[0]
|
|
2164
|
-
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")
|
|
2165
|
-
const body = unwrapExpression(callback.body)
|
|
2166
|
-
if (!ts.isObjectLiteralExpression(body)) throw sourceNodeError(callback.body, sourceFile, "Zustand create() initializer must return one object literal")
|
|
2167
|
-
const data = []
|
|
2168
|
-
const actions = new Map()
|
|
2169
|
-
for (const property of body.properties) {
|
|
2170
|
-
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")
|
|
2171
|
-
const name = property.name.text
|
|
2172
|
-
const value = unwrapExpression(property.initializer)
|
|
2173
|
-
if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) actions.set(name, value)
|
|
2174
|
-
else data.push({ name, value })
|
|
2175
|
-
}
|
|
2176
|
-
if (data.length !== 1 || !isSerializableStateLiteral(data[0].value)) throw sourceNodeError(body, sourceFile, "Zustand migration stores require exactly one directly serializable data property")
|
|
2177
|
-
if (!actions.size) throw sourceNodeError(body, sourceFile, "Zustand migration stores require at least one action")
|
|
2178
|
-
for (const [name, action] of actions) {
|
|
2179
|
-
if (action.asteriskToken || action.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) throw sourceNodeError(action, sourceFile, `Zustand action ${JSON.stringify(name)} must be synchronous`)
|
|
2180
|
-
const capture = [...nativeCaptureNames(action, new Map())].find(entry => entry !== callback.parameters[0].name.text)
|
|
2181
|
-
if (capture) throw sourceNodeError(action, sourceFile, `Zustand action ${JSON.stringify(name)} cannot capture ${JSON.stringify(capture)}`)
|
|
2182
|
-
const validateAction = node => {
|
|
2183
|
-
if (ts.isAwaitExpression(node) || ts.isYieldExpression(node) || ts.isNewExpression(node)) throw sourceNodeError(node, sourceFile, `Zustand action ${JSON.stringify(name)} must be synchronous`)
|
|
2184
|
-
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`)
|
|
2185
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === callback.parameters[0].name.text && !isShadowedIdentifier(node.expression, action)) {
|
|
2186
|
-
if (nearestFunction(node) !== action) throw sourceNodeError(node, sourceFile, `Zustand action ${JSON.stringify(name)} must call set directly`)
|
|
2187
|
-
if (node.arguments.length !== 1) throw sourceNodeError(node, sourceFile, `Zustand action ${JSON.stringify(name)} set() requires exactly one partial update`)
|
|
2188
|
-
}
|
|
2189
|
-
ts.forEachChild(node, validateAction)
|
|
2190
|
-
}
|
|
2191
|
-
validateAction(action.body)
|
|
2192
|
-
}
|
|
2193
|
-
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 })
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
const visit = node => {
|
|
2197
|
-
const recognized = ts.isIdentifier(node) && ts.isCallExpression(node.parent) && node.parent.expression === node && [...stores.values()].some(store => store.declaration.initializer === node.parent)
|
|
2198
|
-
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")
|
|
2199
|
-
ts.forEachChild(node, visit)
|
|
2200
|
-
}
|
|
2201
|
-
visit(sourceFile)
|
|
2202
|
-
return stores
|
|
2203
|
-
}
|
|
2204
|
-
|
|
2205
|
-
function normalizeZustandMigrationSyntax(sourceFile, factory, context) {
|
|
2206
|
-
const stores = analyzeZustandStores(sourceFile)
|
|
2207
|
-
if (!stores.size) {
|
|
2208
|
-
const declaration = sourceFile.statements.find(statement => ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === "zustand" && !statement.importClause?.isTypeOnly)
|
|
2209
|
-
if (declaration) throw sourceNodeError(declaration, sourceFile, "Zustand create must directly initialize an exported const store")
|
|
2210
|
-
return sourceFile
|
|
2211
|
-
}
|
|
2212
|
-
const identity = name => `${relative(sourceDirectory, sourceFile.fileName).replaceAll(sep, "/")}#${name}`
|
|
2213
|
-
const visitor = node => {
|
|
2214
|
-
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && stores.has(node.name.text)) {
|
|
2215
|
-
const store = stores.get(node.name.text)
|
|
2216
|
-
return factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, factory.createCallExpression(factory.createIdentifier("__kCreateStore"), undefined, [
|
|
2217
|
-
factory.createStringLiteral(identity(store.name)),
|
|
2218
|
-
factory.createStringLiteral(store.field),
|
|
2219
|
-
store.initialValue,
|
|
2220
|
-
factory.createArrayLiteralExpression([...store.actions.keys()].map(name => factory.createStringLiteral(name)))
|
|
2221
|
-
]))
|
|
2222
|
-
}
|
|
2223
|
-
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === "zustand") return undefined
|
|
2224
|
-
return ts.visitEachChild(node, visitor, context)
|
|
2225
|
-
}
|
|
2226
|
-
const normalized = ts.visitNode(sourceFile, visitor)
|
|
2227
|
-
const declaration = factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, undefined, factory.createIdentifier("__kCreateStore"))])), factory.createStringLiteral("@kudzujs/core"))
|
|
2228
|
-
const statements = [...normalized.statements]
|
|
2229
|
-
statements.splice(statements.findLastIndex(statement => ts.isImportDeclaration(statement)) + 1, 0, declaration)
|
|
2230
|
-
return factory.updateSourceFile(normalized, statements)
|
|
2231
|
-
}
|
|
2232
|
-
|
|
2233
|
-
function normalizeReactMigrationSyntax(sourceFile, factory, context, importedCollections = new Set()) {
|
|
2234
|
-
const supported = new Set(["createContext", "useContext", "useEffect", "useId", "useReducer", "useRef", "useState"])
|
|
2235
|
-
const erased = new Set(["forwardRef", "memo", "useCallback", "useMemo"])
|
|
2236
|
-
const aliases = new Map()
|
|
2237
|
-
const reactObjects = new Set()
|
|
2238
|
-
for (const statement of sourceFile.statements) {
|
|
2239
|
-
if (!ts.isImportDeclaration(statement) || statement.importClause?.isTypeOnly || !ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== "react") continue
|
|
2240
|
-
if (statement.importClause?.name) reactObjects.add(statement.importClause.name.text)
|
|
2241
|
-
const bindings = statement.importClause?.namedBindings
|
|
2242
|
-
if (bindings && ts.isNamespaceImport(bindings)) reactObjects.add(bindings.name.text)
|
|
2243
|
-
if (bindings && ts.isNamedImports(bindings)) for (const entry of bindings.elements) {
|
|
2244
|
-
const imported = (entry.propertyName ?? entry.name).text
|
|
2245
|
-
if (!entry.isTypeOnly && (supported.has(imported) || erased.has(imported))) aliases.set(entry.name.text, imported)
|
|
2246
|
-
else if (!entry.isTypeOnly && /^use[A-Z]/.test(imported)) throw sourceNodeError(entry, sourceFile, `React ${imported} is not supported by Kudzu migration input`)
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
if (!aliases.size && !reactObjects.size) return sourceFile
|
|
2250
|
-
|
|
2251
|
-
const migrationCallName = call => {
|
|
2252
|
-
if (ts.isIdentifier(call.expression) && aliases.has(call.expression.text) && !isShadowedIdentifier(call.expression, sourceFile)) return aliases.get(call.expression.text)
|
|
2253
|
-
if (ts.isPropertyAccessExpression(call.expression) && ts.isIdentifier(call.expression.expression) && reactObjects.has(call.expression.expression.text) && !isShadowedIdentifier(call.expression.expression, sourceFile)) return call.expression.name.text
|
|
2254
|
-
return undefined
|
|
2255
|
-
}
|
|
2256
|
-
const ownerStateNames = owner => {
|
|
2257
|
-
const names = new Set()
|
|
2258
|
-
const collect = node => {
|
|
2259
|
-
if (node !== owner && isFunctionLike(node)) return
|
|
2260
|
-
if (ts.isVariableDeclaration(node) && ts.isArrayBindingPattern(node.name) && node.initializer && ts.isCallExpression(node.initializer) && ["useReducer", "useState"].includes(migrationCallName(node.initializer))) {
|
|
2261
|
-
const state = node.name.elements[0]
|
|
2262
|
-
if (state && ts.isBindingElement(state) && ts.isIdentifier(state.name)) names.add(state.name.text)
|
|
2263
|
-
}
|
|
2264
|
-
ts.forEachChild(node, collect)
|
|
2265
|
-
}
|
|
2266
|
-
collect(owner)
|
|
2267
|
-
return names
|
|
2268
|
-
}
|
|
2269
|
-
|
|
2270
|
-
const validate = node => {
|
|
2271
|
-
if (ts.isTypeNode(node)) return
|
|
2272
|
-
if (ts.isIdentifier(node) && aliases.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, sourceFile) && !(ts.isCallExpression(node.parent) && node.parent.expression === node)) throw sourceNodeError(node, sourceFile, `Aliased React ${aliases.get(node.text)} must be called directly`)
|
|
2273
|
-
if (ts.isIdentifier(node) && reactObjects.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, sourceFile) && !(ts.isPropertyAccessExpression(node.parent) && node.parent.expression === node)) throw sourceNodeError(node, sourceFile, "React default or namespace imports may only be used for direct supported members or React.Fragment")
|
|
2274
|
-
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && reactObjects.has(node.expression.text) && !isShadowedIdentifier(node.expression, sourceFile)) {
|
|
2275
|
-
const name = node.name.text
|
|
2276
|
-
if (name !== "Fragment" && !(ts.isCallExpression(node.parent) && node.parent.expression === node && (supported.has(name) || erased.has(name)))) throw sourceNodeError(node, sourceFile, `React.${name} is not supported; use a directly supported hook call or React.Fragment`)
|
|
2277
|
-
}
|
|
2278
|
-
ts.forEachChild(node, validate)
|
|
2279
|
-
}
|
|
2280
|
-
validate(sourceFile)
|
|
2281
|
-
|
|
2282
|
-
const memoLocals = new Map()
|
|
2283
|
-
const collectMemoLocals = node => {
|
|
2284
|
-
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isCallExpression(node.initializer) && migrationCallName(node.initializer) === "useMemo") {
|
|
2285
|
-
if (!isLocalConst(node)) throw sourceNodeError(node, sourceFile, "React useMemo() local values must use const declarations")
|
|
2286
|
-
const callback = node.initializer.arguments[0]
|
|
2287
|
-
if (callback && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))) {
|
|
2288
|
-
const expression = lowerReactMemoCollectionExpression(reactMemoExpression(callback), factory)
|
|
2289
|
-
const owner = nearestFunction(node)
|
|
2290
|
-
if (owner && expression) {
|
|
2291
|
-
const entries = memoLocals.get(owner) ?? new Map()
|
|
2292
|
-
if (entries.has(node.name.text)) throw sourceNodeError(node.name, sourceFile, `React useMemo() local ${JSON.stringify(node.name.text)} must be unique within its component`)
|
|
2293
|
-
entries.set(node.name.text, { declaration: node, expression })
|
|
2294
|
-
memoLocals.set(owner, entries)
|
|
2295
|
-
}
|
|
2296
|
-
}
|
|
2297
|
-
}
|
|
2298
|
-
ts.forEachChild(node, collectMemoLocals)
|
|
2299
|
-
}
|
|
2300
|
-
collectMemoLocals(sourceFile)
|
|
2301
|
-
const memoLocalIsShadowed = (node, owner, entry) => {
|
|
2302
|
-
if (isShadowedByParameter(node, owner)) return true
|
|
2303
|
-
const declarationStatement = entry.declaration.parent?.parent
|
|
2304
|
-
for (let current = node.parent; current && current !== owner; current = current.parent) {
|
|
2305
|
-
if (ts.isFunctionExpression(current) && current.name?.text === node.text) return true
|
|
2306
|
-
if (ts.isBlock(current) && current.statements.some(statement => statement !== declarationStatement && statementDeclaresName(statement, node.text))) return true
|
|
2307
|
-
if (ts.isCaseBlock(current) && current.clauses.some(clause => clause.statements.some(statement => statement !== declarationStatement && statementDeclaresName(statement, node.text)))) return true
|
|
2308
|
-
if (ts.isCatchClause(current) && current.variableDeclaration && bindingNames(current.variableDeclaration.name).includes(node.text)) return true
|
|
2309
|
-
if ((ts.isForStatement(current) || ts.isForInStatement(current) || ts.isForOfStatement(current)) && loopDeclaresName(current, node.text)) return true
|
|
2310
|
-
}
|
|
2311
|
-
return false
|
|
2312
|
-
}
|
|
2313
|
-
for (const [owner, entries] of memoLocals) for (const [name, entry] of entries) {
|
|
2314
|
-
const visit = node => {
|
|
2315
|
-
if (ts.isIdentifier(node) && node.text === name && isReferenceIdentifier(node) && nearestFunctionLike(node) !== owner && !memoLocalIsShadowed(node, owner, entry)) throw sourceNodeError(node, sourceFile, `React useMemo() local ${JSON.stringify(name)} cannot be captured by a nested function`)
|
|
2316
|
-
ts.forEachChild(node, visit)
|
|
2317
|
-
}
|
|
2318
|
-
visit(owner.body)
|
|
2319
|
-
}
|
|
2320
|
-
|
|
2321
|
-
const required = new Set()
|
|
2322
|
-
const imported = new Set()
|
|
2323
|
-
const visitor = node => {
|
|
2324
|
-
if (ts.isVariableStatement(node)) {
|
|
2325
|
-
const entries = memoLocals.get(nearestFunction(node))
|
|
2326
|
-
if (entries) {
|
|
2327
|
-
for (const declaration of node.declarationList.declarations) if (ts.isIdentifier(declaration.name) && entries.has(declaration.name.text) && declaration.initializer) ts.visitNode(declaration.initializer, visitor)
|
|
2328
|
-
const declarations = node.declarationList.declarations.filter(declaration => !ts.isIdentifier(declaration.name) || !entries.has(declaration.name.text))
|
|
2329
|
-
if (!declarations.length) return undefined
|
|
2330
|
-
if (declarations.length !== node.declarationList.declarations.length) return factory.updateVariableStatement(node, node.modifiers, factory.updateVariableDeclarationList(node.declarationList, declarations.map(declaration => ts.visitEachChild(declaration, visitor, context))))
|
|
2331
|
-
}
|
|
2332
|
-
}
|
|
2333
|
-
if (ts.isIdentifier(node) && isReferenceIdentifier(node)) {
|
|
2334
|
-
const owner = nearestFunctionLike(node)
|
|
2335
|
-
const entry = memoLocals.get(owner)?.get(node.text)
|
|
2336
|
-
if (entry && !memoLocalIsShadowed(node, owner, entry)) return ts.visitNode(cloneAst(entry.expression, factory, context), visitor)
|
|
2337
|
-
}
|
|
2338
|
-
if (ts.isCallExpression(node)) {
|
|
2339
|
-
const name = migrationCallName(node)
|
|
2340
|
-
if (name === "forwardRef") return ts.visitNode(lowerReactForwardRef(node, sourceFile, factory), visitor)
|
|
2341
|
-
if (name === "memo") {
|
|
2342
|
-
if (node.arguments.length !== 1 || !(ts.isArrowFunction(node.arguments[0]) || ts.isFunctionExpression(node.arguments[0]) || ts.isIdentifier(node.arguments[0]))) throw sourceNodeError(node, sourceFile, "React memo() requires exactly one function component or component identifier")
|
|
2343
|
-
if (ts.isIdentifier(node.arguments[0]) && isShadowedIdentifier(node.arguments[0], sourceFile)) throw sourceNodeError(node.arguments[0], sourceFile, "React memo() component identifiers must resolve to an unshadowed same-file top-level function")
|
|
2344
|
-
const component = ts.isIdentifier(node.arguments[0]) ? reactMemoComponentExpression(node.arguments[0], sourceFile, factory, context) : node.arguments[0]
|
|
2345
|
-
if (!component) throw sourceNodeError(node.arguments[0], sourceFile, "React memo() identifiers must name a same-file top-level function component")
|
|
2346
|
-
return ts.visitNode(component, visitor)
|
|
2347
|
-
}
|
|
2348
|
-
if (name === "useCallback") {
|
|
2349
|
-
if (node.arguments.length !== 2 || !ts.isArrayLiteralExpression(node.arguments[1]) || !(ts.isArrowFunction(node.arguments[0]) || ts.isFunctionExpression(node.arguments[0]))) throw sourceNodeError(node, sourceFile, "React useCallback() requires an inline function and a literal dependency array")
|
|
2350
|
-
const dependency = node.arguments[1].elements.find(entry => !isReactCallbackDependency(entry))
|
|
2351
|
-
if (dependency) throw sourceNodeError(dependency, sourceFile, "React useCallback() dependencies must be identifiers or primitive literals")
|
|
2352
|
-
const dependencies = new Set(node.arguments[1].elements.map(unwrapExpression).filter(ts.isIdentifier).map(entry => entry.text))
|
|
2353
|
-
const owner = nearestFunction(node)
|
|
2354
|
-
const stale = owner && [...ownerStateNames(owner)].find(state => referenceIdentifiers(node.arguments[0], state).length && !dependencies.has(state))
|
|
2355
|
-
if (stale) throw sourceNodeError(node.arguments[1], sourceFile, `React useCallback() must list captured state ${JSON.stringify(stale)} as a dependency`)
|
|
2356
|
-
return ts.visitNode(node.arguments[0], visitor)
|
|
2357
|
-
}
|
|
2358
|
-
if (name === "useMemo") {
|
|
2359
|
-
if (node.arguments.length !== 2 || !ts.isArrayLiteralExpression(node.arguments[1]) || !(ts.isArrowFunction(node.arguments[0]) || ts.isFunctionExpression(node.arguments[0]))) throw sourceNodeError(node, sourceFile, "React useMemo() requires an inline function and a literal dependency array")
|
|
2360
|
-
const callback = node.arguments[0]
|
|
2361
|
-
if (callback.parameters.length || callback.asteriskToken || callback.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) throw sourceNodeError(callback, sourceFile, "React useMemo() callback must be synchronous and parameterless")
|
|
2362
|
-
const dependency = node.arguments[1].elements.find(entry => !isReactCallbackDependency(entry))
|
|
2363
|
-
if (dependency) throw sourceNodeError(dependency, sourceFile, "React useMemo() dependencies must be identifiers or primitive literals")
|
|
2364
|
-
const expression = lowerReactMemoCollectionExpression(reactMemoExpression(callback), factory)
|
|
2365
|
-
const dependencies = new Set(node.arguments[1].elements.map(unwrapExpression).filter(ts.isIdentifier).map(entry => entry.text))
|
|
2366
|
-
const owner = nearestFunction(node)
|
|
2367
|
-
const states = owner ? ownerStateNames(owner) : new Set()
|
|
2368
|
-
const collection = expression && reactMemoCollection(expression, states, importedCollections, sourceFile)
|
|
2369
|
-
if (!expression || !collection && !isPureReactMemoExpression(expression)) throw sourceNodeError(callback.body, sourceFile, "React useMemo() callback must return one pure expression or analyzable collection pipeline")
|
|
2370
|
-
if (!collection) {
|
|
2371
|
-
const unsupported = [...reactMemoReferenceNames(expression)].find(reference => !states.has(reference))
|
|
2372
|
-
if (unsupported) throw sourceNodeError(expression, sourceFile, `React useMemo() pure expressions may only reference direct local state; found ${JSON.stringify(unsupported)}`)
|
|
2373
|
-
}
|
|
2374
|
-
const collectionDependencies = collection ? new Set([...collection.selectorStates, ...(collection.static ? [] : [collection.state.text])]) : undefined
|
|
2375
|
-
const stale = collection
|
|
2376
|
-
? [...collectionDependencies].find(state => !dependencies.has(state))
|
|
2377
|
-
: [...states].find(state => referenceIdentifiers(expression, state).length && !dependencies.has(state))
|
|
2378
|
-
if (stale) throw sourceNodeError(node.arguments[1], sourceFile, `React useMemo() must list captured state ${JSON.stringify(stale)} as a dependency`)
|
|
2379
|
-
return ts.visitNode(expression, visitor)
|
|
2380
|
-
}
|
|
2381
|
-
if (name && supported.has(name)) {
|
|
2382
|
-
required.add(name)
|
|
2383
|
-
return factory.updateCallExpression(node, factory.createIdentifier(name), node.typeArguments, ts.visitNodes(node.arguments, visitor))
|
|
2384
|
-
}
|
|
2385
|
-
}
|
|
2386
|
-
if (ts.isImportDeclaration(node) && !node.importClause?.isTypeOnly && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === "react") {
|
|
2387
|
-
const clause = node.importClause
|
|
2388
|
-
if (!clause) return node
|
|
2389
|
-
let bindings = clause.namedBindings
|
|
2390
|
-
if (bindings && ts.isNamedImports(bindings)) {
|
|
2391
|
-
const entries = []
|
|
2392
|
-
for (const entry of bindings.elements) {
|
|
2393
|
-
const name = (entry.propertyName ?? entry.name).text
|
|
2394
|
-
if (entry.isTypeOnly) continue
|
|
2395
|
-
if (!entry.isTypeOnly && erased.has(name)) continue
|
|
2396
|
-
if (!entry.isTypeOnly && supported.has(name)) {
|
|
2397
|
-
if (imported.has(name)) continue
|
|
2398
|
-
imported.add(name)
|
|
2399
|
-
required.add(name)
|
|
2400
|
-
entries.push(factory.createImportSpecifier(false, undefined, factory.createIdentifier(name)))
|
|
2401
|
-
} else {
|
|
2402
|
-
entries.push(entry)
|
|
2403
|
-
}
|
|
2404
|
-
}
|
|
2405
|
-
bindings = entries.length ? factory.updateNamedImports(bindings, entries) : undefined
|
|
2406
|
-
}
|
|
2407
|
-
if (!clause.name && !bindings) return undefined
|
|
2408
|
-
return factory.updateImportDeclaration(node, node.modifiers, factory.updateImportClause(clause, clause.isTypeOnly, clause.name, bindings), node.moduleSpecifier, node.attributes)
|
|
2409
|
-
}
|
|
2410
|
-
return ts.visitEachChild(node, visitor, context)
|
|
2411
|
-
}
|
|
2412
|
-
let normalized = ts.visitNode(sourceFile, visitor)
|
|
2413
|
-
const missing = [...required].filter(name => !imported.has(name)).sort()
|
|
2414
|
-
if (!missing.length) return normalized
|
|
2415
|
-
for (const name of missing) {
|
|
2416
|
-
const collision = sourceFile.statements.some(statement => statementDeclaresName(statement, name) || ts.isImportDeclaration(statement) && importDeclarationNames(statement).includes(name) && statement.moduleSpecifier.text !== "react")
|
|
2417
|
-
if (collision) throw sourceNodeError(sourceFile, sourceFile, `React.${name} cannot be normalized because ${JSON.stringify(name)} is already declared`)
|
|
2418
|
-
}
|
|
2419
|
-
const declaration = factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports(missing.map(name => factory.createImportSpecifier(false, undefined, factory.createIdentifier(name))))), factory.createStringLiteral("react"))
|
|
2420
|
-
const statements = [...normalized.statements]
|
|
2421
|
-
const lastImport = statements.findLastIndex(statement => ts.isImportDeclaration(statement))
|
|
2422
|
-
statements.splice(lastImport + 1, 0, declaration)
|
|
2423
|
-
normalized = factory.updateSourceFile(normalized, statements)
|
|
2424
|
-
return normalized
|
|
2425
|
-
}
|
|
2426
|
-
|
|
2427
|
-
function lowerReactForwardRef(call, sourceFile, factory) {
|
|
2428
|
-
const declaration = call.parent
|
|
2429
|
-
const statement = declaration?.parent?.parent
|
|
2430
|
-
if (!ts.isVariableDeclaration(declaration) || declaration.initializer !== call || !ts.isIdentifier(declaration.name) || !statement || !ts.isVariableStatement(statement) || (statement.declarationList.flags & ts.NodeFlags.Const) === 0 || statement.parent !== sourceFile) {
|
|
2431
|
-
throw sourceNodeError(call, sourceFile, "React forwardRef() must directly initialize one top-level const component")
|
|
2432
|
-
}
|
|
2433
|
-
if (call.arguments.length !== 1 || !ts.isArrowFunction(call.arguments[0]) && !ts.isFunctionExpression(call.arguments[0])) throw sourceNodeError(call, sourceFile, "React forwardRef() requires exactly one inline render function")
|
|
2434
|
-
const callback = call.arguments[0]
|
|
2435
|
-
if (callback.asteriskToken || callback.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) throw sourceNodeError(callback, sourceFile, "React forwardRef() render function must be synchronous and cannot be a generator")
|
|
2436
|
-
if (callback.parameters.length !== 2) throw sourceNodeError(callback, sourceFile, "React forwardRef() render function must declare exactly (props, ref)")
|
|
2437
|
-
const [props, ref] = callback.parameters
|
|
2438
|
-
if (props.dotDotDotToken || props.initializer || !ts.isIdentifier(props.name) && !ts.isObjectBindingPattern(props.name)) throw sourceNodeError(props, sourceFile, "React forwardRef() props must use one identifier or a flat object binding")
|
|
2439
|
-
if (ref.dotDotDotToken || ref.initializer || !ts.isIdentifier(ref.name)) throw sourceNodeError(ref, sourceFile, "React forwardRef() ref parameter must be one identifier")
|
|
2440
|
-
|
|
2441
|
-
let elements
|
|
2442
|
-
if (ts.isIdentifier(props.name)) {
|
|
2443
|
-
elements = [
|
|
2444
|
-
factory.createBindingElement(undefined, factory.createIdentifier("ref"), factory.createIdentifier(ref.name.text)),
|
|
2445
|
-
factory.createBindingElement(factory.createToken(ts.SyntaxKind.DotDotDotToken), undefined, factory.createIdentifier(props.name.text))
|
|
2446
|
-
]
|
|
2447
|
-
} else {
|
|
2448
|
-
for (const element of props.name.elements) {
|
|
2449
|
-
const property = (element.propertyName ?? element.name)
|
|
2450
|
-
if (!ts.isIdentifier(element.name) || property.text === "ref") throw sourceNodeError(element, sourceFile, property.text === "ref" ? "React forwardRef() props must not declare ref; Kudzu supplies ref through the second parameter" : "React forwardRef() props must use one identifier or a flat object binding")
|
|
2451
|
-
}
|
|
2452
|
-
const rest = props.name.elements.findIndex(element => Boolean(element.dotDotDotToken))
|
|
2453
|
-
elements = [...props.name.elements]
|
|
2454
|
-
elements.splice(rest < 0 ? elements.length : rest, 0, factory.createBindingElement(undefined, factory.createIdentifier("ref"), factory.createIdentifier(ref.name.text)))
|
|
2455
|
-
}
|
|
2456
|
-
|
|
2457
|
-
const last = ts.isBlock(callback.body) ? callback.body.statements.at(-1) : undefined
|
|
2458
|
-
let returnCount = 0
|
|
2459
|
-
const countReturns = node => {
|
|
2460
|
-
if (node !== callback.body && isFunctionLike(node)) return
|
|
2461
|
-
if (ts.isReturnStatement(node)) returnCount++
|
|
2462
|
-
ts.forEachChild(node, countReturns)
|
|
2463
|
-
}
|
|
2464
|
-
countReturns(callback.body)
|
|
2465
|
-
const returned = ts.isBlock(callback.body)
|
|
2466
|
-
? last && ts.isReturnStatement(last) ? last.expression : undefined
|
|
2467
|
-
: callback.body
|
|
2468
|
-
const root = returned && unwrapExpression(returned)
|
|
2469
|
-
const tag = root && jsxTagName(root)
|
|
2470
|
-
if ((ts.isBlock(callback.body) && returnCount !== 1) || !root || !ts.isJsxElement(root) && !ts.isJsxSelfClosingElement(root) || !ts.isIdentifier(tag) || tag.text[0] !== tag.text[0].toLowerCase()) throw sourceNodeError(callback.body, sourceFile, "React forwardRef() render function must directly return one intrinsic JSX element")
|
|
2471
|
-
const attributes = ts.isJsxElement(root) ? root.openingElement.attributes : root.attributes
|
|
2472
|
-
const forwarded = attributes.properties.filter(attribute => ts.isJsxAttribute(attribute) && attribute.name.text === "ref" && ts.isJsxExpression(attribute.initializer) && ts.isIdentifier(attribute.initializer.expression) && attribute.initializer.expression.text === ref.name.text)
|
|
2473
|
-
if (forwarded.length !== 1 || referenceIdentifiers(callback.body, ref.name.text).length !== 1) throw sourceNodeError(ref, sourceFile, "React forwardRef() ref must be forwarded exactly once as ref={ref} on the direct intrinsic root")
|
|
2474
|
-
|
|
2475
|
-
const parameter = factory.updateParameterDeclaration(props, props.modifiers, undefined, factory.createObjectBindingPattern(elements), props.questionToken, props.type, undefined)
|
|
2476
|
-
return ts.isArrowFunction(callback)
|
|
2477
|
-
? factory.updateArrowFunction(callback, callback.modifiers, callback.typeParameters, [parameter], callback.type, callback.equalsGreaterThanToken, callback.body)
|
|
2478
|
-
: factory.updateFunctionExpression(callback, callback.modifiers, undefined, callback.name, callback.typeParameters, [parameter], callback.type, callback.body)
|
|
2479
|
-
}
|
|
2480
|
-
|
|
2481
|
-
function validateUseIdSyntax(sourceFile) {
|
|
2482
|
-
const imported = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) && ["@kudzujs/core", "react"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings) && statement.importClause.namedBindings.elements.some(entry => !entry.propertyName && entry.name.text === "useId"))
|
|
2483
|
-
if (!imported) return
|
|
2484
|
-
const visit = node => {
|
|
2485
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "useId" && !isShadowedIdentifier(node.expression, sourceFile)) {
|
|
2486
|
-
if (node.arguments.length) throw sourceNodeError(node, sourceFile, "useId() does not accept arguments")
|
|
2487
|
-
const declaration = node.parent
|
|
2488
|
-
const statement = declaration?.parent?.parent
|
|
2489
|
-
const owner = nearestFunction(node)
|
|
2490
|
-
if (!ts.isVariableDeclaration(declaration) || declaration.initializer !== node || !ts.isIdentifier(declaration.name) || !statement || !ts.isVariableStatement(statement) || (statement.declarationList.flags & ts.NodeFlags.Const) === 0 || !owner || !ts.isBlock(owner.body) || statement.parent !== owner.body) {
|
|
2491
|
-
throw sourceNodeError(node, sourceFile, "useId() must be assigned to one top-level const identifier in a component")
|
|
2492
|
-
}
|
|
2493
|
-
}
|
|
2494
|
-
ts.forEachChild(node, visit)
|
|
2495
|
-
}
|
|
2496
|
-
visit(sourceFile)
|
|
2497
|
-
}
|
|
2498
|
-
|
|
2499
801
|
function normalizeLazyStateInitializers(sourceFile, factory, context, file, sourceFiles, sourceIndex) {
|
|
2500
802
|
const bindings = new Set()
|
|
2501
803
|
for (const statement of sourceFile.statements) {
|
|
@@ -2544,519 +846,56 @@ function normalizeLazyStateInitializers(sourceFile, factory, context, file, sour
|
|
|
2544
846
|
return ts.visitNode(sourceFile, visitor)
|
|
2545
847
|
}
|
|
2546
848
|
|
|
2547
|
-
function
|
|
2548
|
-
const
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ["filter", "flatMap", "slice", "toSorted"].includes(node.expression.name.text)) {
|
|
2574
|
-
return factory.updateCallExpression(node, factory.updatePropertyAccessExpression(node.expression, visit(node.expression.expression), node.expression.name), node.typeArguments, node.arguments)
|
|
2575
|
-
}
|
|
2576
|
-
if (isArrayFromCall(node)) return factory.updateCallExpression(node, node.expression, node.typeArguments, [visit(node.arguments[0]), ...node.arguments.slice(1)])
|
|
2577
|
-
return node
|
|
2578
|
-
}
|
|
2579
|
-
return visit(expression)
|
|
2580
|
-
}
|
|
2581
|
-
|
|
2582
|
-
function reactMemoCollection(expression, states, importedCollections, sourceFile) {
|
|
2583
|
-
const setters = new Map([...states].map(state => [state, state]))
|
|
2584
|
-
const fail = (node, message) => { throw sourceNodeError(node, sourceFile, message) }
|
|
2585
|
-
return renderedCollectionSource(expression, setters, undefined, fail, new Set(), importedCollections, states)
|
|
2586
|
-
}
|
|
2587
|
-
|
|
2588
|
-
function reactMemoComponentExpression(identifier, sourceFile, factory, context) {
|
|
2589
|
-
for (const statement of sourceFile.statements) {
|
|
2590
|
-
if (ts.isFunctionDeclaration(statement) && statement.name?.text === identifier.text && statement.body) {
|
|
2591
|
-
const clone = cloneAst(statement, factory, context)
|
|
2592
|
-
return factory.createFunctionExpression(clone.modifiers?.filter(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword), clone.asteriskToken, clone.name, clone.typeParameters, clone.parameters, clone.type, clone.body)
|
|
2593
|
-
}
|
|
2594
|
-
if (!ts.isVariableStatement(statement)) continue
|
|
2595
|
-
const declaration = statement.declarationList.declarations.find(entry => ts.isIdentifier(entry.name) && entry.name.text === identifier.text)
|
|
2596
|
-
if (declaration?.initializer && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) return cloneAst(declaration.initializer, factory, context)
|
|
2597
|
-
}
|
|
2598
|
-
return undefined
|
|
2599
|
-
}
|
|
2600
|
-
|
|
2601
|
-
function isPureReactMemoExpression(node) {
|
|
2602
|
-
node = unwrapExpression(node)
|
|
2603
|
-
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword || node.kind === ts.SyntaxKind.NullKeyword) return true
|
|
2604
|
-
if (ts.isParenthesizedExpression(node)) return isPureReactMemoExpression(node.expression)
|
|
2605
|
-
if (ts.isPrefixUnaryExpression(node)) return ![ts.SyntaxKind.PlusPlusToken, ts.SyntaxKind.MinusMinusToken].includes(node.operator) && isPureReactMemoExpression(node.operand)
|
|
2606
|
-
if (ts.isBinaryExpression(node)) return node.operatorToken.kind < ts.SyntaxKind.FirstAssignment && isPureReactMemoExpression(node.left) && isPureReactMemoExpression(node.right)
|
|
2607
|
-
if (ts.isConditionalExpression(node)) return isPureReactMemoExpression(node.condition) && isPureReactMemoExpression(node.whenTrue) && isPureReactMemoExpression(node.whenFalse)
|
|
2608
|
-
if (ts.isTemplateExpression(node)) return node.templateSpans.every(span => isPureReactMemoExpression(span.expression))
|
|
2609
|
-
return false
|
|
2610
|
-
}
|
|
2611
|
-
|
|
2612
|
-
function reactMemoReferenceNames(root) {
|
|
2613
|
-
const names = new Set()
|
|
2614
|
-
const visit = node => {
|
|
2615
|
-
if (ts.isIdentifier(node) && isReferenceIdentifier(node)) names.add(node.text)
|
|
2616
|
-
ts.forEachChild(node, visit)
|
|
2617
|
-
}
|
|
2618
|
-
visit(root)
|
|
2619
|
-
return names
|
|
2620
|
-
}
|
|
2621
|
-
|
|
2622
|
-
const customHookTimerStatePrefix = "__kTimerState_"
|
|
2623
|
-
const customHookTimerSetterPrefix = "__kSetTimerState_"
|
|
2624
|
-
const customHookTimerStatesBySource = new WeakMap()
|
|
2625
|
-
|
|
2626
|
-
function normalizeCustomHookTimerRefs(sourceFile, factory, context) {
|
|
2627
|
-
const timerCall = (node, name) => ts.isCallExpression(node) && (
|
|
2628
|
-
ts.isIdentifier(node.expression) && node.expression.text === name ||
|
|
2629
|
-
ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression) && node.expression.expression.text === "window" && node.expression.name.text === name
|
|
2630
|
-
)
|
|
2631
|
-
const currentAccess = (node, name) => ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === name && node.name.text === "current"
|
|
2632
|
-
const clearStatement = (node, name) => {
|
|
2633
|
-
if (!ts.isIfStatement(node) || node.elseStatement || !currentAccess(unwrapExpression(node.expression), name)) return undefined
|
|
2634
|
-
const statement = ts.isBlock(node.thenStatement) && node.thenStatement.statements.length === 1 ? node.thenStatement.statements[0] : node.thenStatement
|
|
2635
|
-
if (!ts.isExpressionStatement(statement) || !timerCall(statement.expression, "clearTimeout") || statement.expression.arguments.length !== 1 || !currentAccess(unwrapExpression(statement.expression.arguments[0]), name)) return undefined
|
|
2636
|
-
return { condition: unwrapExpression(node.expression), argument: unwrapExpression(statement.expression.arguments[0]) }
|
|
2637
|
-
}
|
|
2638
|
-
const analyze = (hook, hookName) => {
|
|
2639
|
-
if (!/^use[A-Z]/.test(hookName) || hook.parameters.length || !hook.body || !ts.isBlock(hook.body)) return undefined
|
|
2640
|
-
const returnedStatement = hook.body.statements.at(-1)
|
|
2641
|
-
const returned = returnedStatement && ts.isReturnStatement(returnedStatement) && returnedStatement.expression ? unwrapExpression(returnedStatement.expression) : undefined
|
|
2642
|
-
if (!returned || !ts.isObjectLiteralExpression(returned)) return undefined
|
|
2643
|
-
const returnedNames = new Set(returned.properties.filter(ts.isShorthandPropertyAssignment).map(property => property.name.text))
|
|
2644
|
-
const callbacks = new Map()
|
|
2645
|
-
const refs = []
|
|
2646
|
-
for (const statement of hook.body.statements) {
|
|
2647
|
-
if (!ts.isVariableStatement(statement) || !(statement.declarationList.flags & ts.NodeFlags.Const)) continue
|
|
2648
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
2649
|
-
if (ts.isIdentifier(declaration.name) && declaration.initializer && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) callbacks.set(declaration.name.text, declaration.initializer)
|
|
2650
|
-
if (ts.isIdentifier(declaration.name) && declaration.initializer && ts.isCallExpression(declaration.initializer) && ts.isIdentifier(declaration.initializer.expression) && declaration.initializer.expression.text === "useRef" && declaration.initializer.arguments.length === 1 && declaration.initializer.arguments[0].kind === ts.SyntaxKind.NullKeyword) refs.push({ declaration, name: declaration.name.text })
|
|
2651
|
-
}
|
|
2652
|
-
}
|
|
2653
|
-
const candidates = []
|
|
2654
|
-
for (const ref of refs) {
|
|
2655
|
-
const assignments = []
|
|
2656
|
-
const accesses = []
|
|
2657
|
-
const clearStatements = []
|
|
2658
|
-
const collect = node => {
|
|
2659
|
-
if (currentAccess(node, ref.name)) accesses.push(node)
|
|
2660
|
-
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken && currentAccess(unwrapExpression(node.left), ref.name)) assignments.push(node)
|
|
2661
|
-
const clear = clearStatement(node, ref.name)
|
|
2662
|
-
if (clear) clearStatements.push({ node, ...clear })
|
|
2663
|
-
ts.forEachChild(node, collect)
|
|
2664
|
-
}
|
|
2665
|
-
collect(hook.body)
|
|
2666
|
-
if (assignments.some(assignment => timerCall(unwrapExpression(assignment.right), "setTimeout"))) candidates.push({ ...ref, assignments, accesses, clearStatements })
|
|
2667
|
-
}
|
|
2668
|
-
if (!candidates.length) return undefined
|
|
2669
|
-
if (candidates.length !== 1) throw sourceNodeError(hook, sourceFile, "Relative custom hooks may own only one private timeout ref")
|
|
2670
|
-
const timer = candidates[0]
|
|
2671
|
-
if (timer.assignments.length !== 1) throw sourceNodeError(timer.declaration, sourceFile, "Private timeout refs require one direct timer.current = setTimeout(...) assignment")
|
|
2672
|
-
const assignment = timer.assignments[0]
|
|
2673
|
-
const timeout = unwrapExpression(assignment.right)
|
|
2674
|
-
const timeoutCallback = timeout.arguments[0]
|
|
2675
|
-
const delay = timeout.arguments[1]
|
|
2676
|
-
if (!timerCall(timeout, "setTimeout") || timeout.arguments.length !== 2 || !timeoutCallback || !(ts.isArrowFunction(timeoutCallback) || ts.isFunctionExpression(timeoutCallback)) || timeoutCallback.parameters.length || !delay || !ts.isNumericLiteral(unwrapExpression(delay))) throw sourceNodeError(assignment, sourceFile, "Private timeout refs require setTimeout() with one zero-argument callback and a numeric literal delay")
|
|
2677
|
-
const callback = nearestFunction(assignment)
|
|
2678
|
-
const callbackName = [...callbacks].find(([, value]) => value === callback)?.[0]
|
|
2679
|
-
if (!callbackName || !returnedNames.has(callbackName) || !ts.isBlock(callback.body) || !ts.isExpressionStatement(assignment.parent) || assignment.parent.parent !== callback.body) throw sourceNodeError(assignment, sourceFile, "Private timeout refs must be assigned directly inside one returned custom-hook callback")
|
|
2680
|
-
const callbackClear = timer.clearStatements.find(entry => nearestFunction(entry.node) === callback)
|
|
2681
|
-
if (!callbackClear || callbackClear.node.parent !== callback.body || callback.body.statements.indexOf(callbackClear.node) >= callback.body.statements.indexOf(assignment.parent)) throw sourceNodeError(callback, sourceFile, "Private timeout callbacks must directly clear the previous timer before assigning its replacement")
|
|
2682
|
-
const effectCalls = hook.body.statements.flatMap(statement => {
|
|
2683
|
-
if (!ts.isExpressionStatement(statement) || !ts.isCallExpression(statement.expression) || !ts.isIdentifier(statement.expression.expression) || statement.expression.expression.text !== "useEffect") return []
|
|
2684
|
-
return [statement.expression]
|
|
2685
|
-
})
|
|
2686
|
-
let cleanupClear
|
|
2687
|
-
for (const effect of effectCalls) {
|
|
2688
|
-
const [setup, dependencies] = effect.arguments
|
|
2689
|
-
if (!(ts.isArrowFunction(setup) || ts.isFunctionExpression(setup)) || !ts.isArrayLiteralExpression(dependencies) || dependencies.elements.length) continue
|
|
2690
|
-
const returns = effectReturns(setup)
|
|
2691
|
-
if (returns.cleanups.length !== 1) continue
|
|
2692
|
-
const cleanup = returns.cleanups[0]
|
|
2693
|
-
const entry = timer.clearStatements.find(candidate => nearestFunction(candidate.node) === cleanup)
|
|
2694
|
-
if (entry && ts.isBlock(cleanup.body) && cleanup.body.statements.length === 1 && cleanup.body.statements[0] === entry.node) cleanupClear = entry
|
|
2695
|
-
}
|
|
2696
|
-
if (!cleanupClear) throw sourceNodeError(timer.declaration, sourceFile, "Private timeout refs require one empty-dependency effect that directly clears the timer on cleanup")
|
|
2697
|
-
const accepted = new Set([assignment.left, callbackClear.condition, callbackClear.argument, cleanupClear.condition, cleanupClear.argument].map(unwrapExpression))
|
|
2698
|
-
const unsupported = timer.accesses.find(access => !accepted.has(access))
|
|
2699
|
-
if (unsupported) throw sourceNodeError(unsupported, sourceFile, "Private timeout refs may only be read by their direct replacement and cleanup guards")
|
|
2700
|
-
const identity = createHash("sha256").update(`${sourceFile.fileName}:${hook.pos}:${timer.name}`).digest("hex").slice(0, 10)
|
|
2701
|
-
const stateName = `${customHookTimerStatePrefix}${identity}`
|
|
2702
|
-
const setterName = `${customHookTimerSetterPrefix}${identity}`
|
|
2703
|
-
if (referencesIdentifier(hook.body, stateName) || referencesIdentifier(hook.body, setterName)) throw sourceNodeError(timer.declaration, sourceFile, "Private timeout ref conflicts with compiler-owned bindings")
|
|
2704
|
-
return { assignment, declaration: timer.declaration, refName: timer.name, returned, stateName, setterName }
|
|
2705
|
-
}
|
|
2706
|
-
const timerStates = new Set()
|
|
2707
|
-
const transform = (hook, hookName) => {
|
|
2708
|
-
const timer = analyze(hook, hookName)
|
|
2709
|
-
if (!timer) return undefined
|
|
2710
|
-
timerStates.add(timer.stateName)
|
|
2711
|
-
const timerVisitor = current => {
|
|
2712
|
-
if (current === timer.declaration) {
|
|
2713
|
-
const binding = factory.createArrayBindingPattern([
|
|
2714
|
-
factory.createBindingElement(undefined, undefined, timer.stateName),
|
|
2715
|
-
factory.createBindingElement(undefined, undefined, timer.setterName)
|
|
2716
|
-
])
|
|
2717
|
-
const initializer = factory.updateCallExpression(current.initializer, factory.createIdentifier("useState"), current.initializer.typeArguments, current.initializer.arguments)
|
|
2718
|
-
return factory.updateVariableDeclaration(current, binding, current.exclamationToken, undefined, initializer)
|
|
2719
|
-
}
|
|
2720
|
-
if (current === timer.assignment) return factory.createCallExpression(factory.createIdentifier(timer.setterName), undefined, [ts.visitNode(current.right, timerVisitor)])
|
|
2721
|
-
if (currentAccess(current, timer.refName)) return factory.createIdentifier(timer.stateName)
|
|
2722
|
-
if (current === timer.returned) return factory.updateObjectLiteralExpression(current, [
|
|
2723
|
-
...current.properties,
|
|
2724
|
-
factory.createShorthandPropertyAssignment(timer.stateName),
|
|
2725
|
-
factory.createShorthandPropertyAssignment(timer.setterName)
|
|
2726
|
-
])
|
|
2727
|
-
return ts.visitEachChild(current, timerVisitor, context)
|
|
2728
|
-
}
|
|
2729
|
-
return ts.visitEachChild(hook, timerVisitor, context)
|
|
2730
|
-
}
|
|
2731
|
-
const visitor = node => {
|
|
2732
|
-
if (ts.isFunctionDeclaration(node)) {
|
|
2733
|
-
const hookName = node.name?.text ?? (node.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword) ? "useDefault" : "")
|
|
2734
|
-
const transformed = transform(node, hookName)
|
|
2735
|
-
if (transformed) return transformed
|
|
2736
|
-
}
|
|
2737
|
-
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))) {
|
|
2738
|
-
const transformed = transform(node.initializer, node.name.text)
|
|
2739
|
-
if (transformed) return factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, transformed)
|
|
2740
|
-
}
|
|
2741
|
-
return ts.visitEachChild(node, visitor, context)
|
|
2742
|
-
}
|
|
2743
|
-
const normalized = ts.visitNode(sourceFile, visitor)
|
|
2744
|
-
customHookTimerStatesBySource.set(normalized, timerStates)
|
|
2745
|
-
return normalized
|
|
2746
|
-
}
|
|
2747
|
-
|
|
2748
|
-
function normalizeMediaQueryExternalStores(sourceFile, factory, context) {
|
|
2749
|
-
const imports = sourceFile.statements.filter(statement => ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === "react" && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings))
|
|
2750
|
-
const externalStoreImport = imports.flatMap(statement => statement.importClause.namedBindings.elements.map(entry => ({ entry, statement }))).find(({ entry }) => !entry.isTypeOnly && !entry.propertyName && entry.name.text === "useSyncExternalStore")
|
|
2751
|
-
if (!externalStoreImport) return sourceFile
|
|
2752
|
-
const returnedExpression = callback => {
|
|
2753
|
-
if (!(ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) || callback.parameters.length) return undefined
|
|
2754
|
-
if (!ts.isBlock(callback.body)) return unwrapExpression(callback.body)
|
|
2755
|
-
if (callback.body.statements.length !== 1 || !ts.isReturnStatement(callback.body.statements[0]) || !callback.body.statements[0].expression) return undefined
|
|
2756
|
-
return unwrapExpression(callback.body.statements[0].expression)
|
|
2757
|
-
}
|
|
2758
|
-
const matchMediaQuery = expression => {
|
|
2759
|
-
expression = unwrapExpression(expression)
|
|
2760
|
-
if (!ts.isCallExpression(expression) || expression.arguments.length !== 1 || !ts.isStringLiteral(unwrapExpression(expression.arguments[0])) || !ts.isPropertyAccessExpression(expression.expression) || !ts.isIdentifier(expression.expression.expression) || expression.expression.expression.text !== "window" || expression.expression.name.text !== "matchMedia" || !isUnshadowedGlobal(expression.expression.expression, sourceFile)) return undefined
|
|
2761
|
-
return unwrapExpression(expression.arguments[0]).text
|
|
2762
|
-
}
|
|
2763
|
-
const mediaListener = (statement, method, media, callback) => ts.isExpressionStatement(statement) && ts.isCallExpression(statement.expression) && statement.expression.arguments.length === 2 && ts.isPropertyAccessExpression(statement.expression.expression) && ts.isIdentifier(statement.expression.expression.expression) && statement.expression.expression.expression.text === media && statement.expression.expression.name.text === method && ts.isStringLiteral(unwrapExpression(statement.expression.arguments[0])) && unwrapExpression(statement.expression.arguments[0]).text === "change" && ts.isIdentifier(unwrapExpression(statement.expression.arguments[1])) && unwrapExpression(statement.expression.arguments[1]).text === callback
|
|
2764
|
-
const candidates = new Map()
|
|
2765
|
-
let index = 0
|
|
2766
|
-
const inspect = node => {
|
|
2767
|
-
if (!ts.isVariableStatement(node) || !(node.declarationList.flags & ts.NodeFlags.Const) || node.declarationList.declarations.length !== 1) {
|
|
2768
|
-
ts.forEachChild(node, inspect)
|
|
2769
|
-
return
|
|
2770
|
-
}
|
|
2771
|
-
const declaration = node.declarationList.declarations[0]
|
|
2772
|
-
const call = declaration.initializer && unwrapExpression(declaration.initializer)
|
|
2773
|
-
if (!ts.isIdentifier(declaration.name) || !call || !ts.isCallExpression(call) || !ts.isIdentifier(call.expression) || call.expression.text !== "useSyncExternalStore" || isShadowedIdentifier(call.expression, sourceFile)) {
|
|
2774
|
-
ts.forEachChild(node, inspect)
|
|
2775
|
-
return
|
|
2776
|
-
}
|
|
2777
|
-
if (call.arguments.length !== 3) throw sourceNodeError(call, sourceFile, "Media query useSyncExternalStore() requires subscribe, browser snapshot, and false server snapshot callbacks")
|
|
2778
|
-
const [subscribe, snapshot, serverSnapshot] = call.arguments.map(unwrapExpression)
|
|
2779
|
-
if (!(ts.isArrowFunction(subscribe) || ts.isFunctionExpression(subscribe)) || subscribe.parameters.length !== 1 || !ts.isIdentifier(subscribe.parameters[0].name) || !ts.isBlock(subscribe.body)) throw sourceNodeError(subscribe, sourceFile, "Media query subscriptions require one inline callback parameter and block body")
|
|
2780
|
-
if (subscribe.body.statements.length !== 3) throw sourceNodeError(subscribe, sourceFile, "Media query subscriptions must add and remove one matching change listener")
|
|
2781
|
-
const callback = subscribe.parameters[0].name.text
|
|
2782
|
-
const [mediaStatement, addStatement, returnStatement] = subscribe.body.statements
|
|
2783
|
-
const mediaDeclaration = ts.isVariableStatement(mediaStatement) && (mediaStatement.declarationList.flags & ts.NodeFlags.Const) && mediaStatement.declarationList.declarations.length === 1 ? mediaStatement.declarationList.declarations[0] : undefined
|
|
2784
|
-
const media = mediaDeclaration && ts.isIdentifier(mediaDeclaration.name) ? mediaDeclaration.name.text : undefined
|
|
2785
|
-
const query = mediaDeclaration?.initializer && matchMediaQuery(mediaDeclaration.initializer)
|
|
2786
|
-
const cleanup = ts.isReturnStatement(returnStatement) && returnStatement.expression ? unwrapExpression(returnStatement.expression) : undefined
|
|
2787
|
-
const cleanupStatement = cleanup && (ts.isArrowFunction(cleanup) || ts.isFunctionExpression(cleanup)) && !cleanup.parameters.length
|
|
2788
|
-
? ts.isBlock(cleanup.body) ? cleanup.body.statements.length === 1 ? cleanup.body.statements[0] : undefined : factory.createExpressionStatement(cleanup.body)
|
|
2789
|
-
: undefined
|
|
2790
|
-
if (!media || !query || !mediaListener(addStatement, "addEventListener", media, callback) || !cleanupStatement || !mediaListener(cleanupStatement, "removeEventListener", media, callback)) throw sourceNodeError(subscribe, sourceFile, "Media query subscriptions must add and remove one matching change listener")
|
|
2791
|
-
const snapshotValue = returnedExpression(snapshot)
|
|
2792
|
-
const snapshotQuery = snapshotValue && ts.isPropertyAccessExpression(snapshotValue) && snapshotValue.name.text === "matches" ? matchMediaQuery(snapshotValue.expression) : undefined
|
|
2793
|
-
const serverValue = returnedExpression(serverSnapshot)
|
|
2794
|
-
if (snapshotQuery !== query || !serverValue || serverValue.kind !== ts.SyntaxKind.FalseKeyword) throw sourceNodeError(call, sourceFile, "Media query external stores require matching static snapshots and a false server fallback")
|
|
2795
|
-
const owner = nearestFunction(node)
|
|
2796
|
-
const topLevelOwner = owner && (owner.parent === sourceFile || ts.isVariableDeclaration(owner.parent) && owner.parent.parent?.parent?.parent === sourceFile)
|
|
2797
|
-
if (!topLevelOwner || !owner.body || !ts.isBlock(owner.body) || node.parent !== owner.body) throw sourceNodeError(declaration, sourceFile, "Media query external stores must initialize one top-level component const")
|
|
2798
|
-
for (const name of ["useEffect", "useState"]) if (owner.parameters.some(parameter => bindingNames(parameter.name).includes(name)) || functionVarDeclaresName(owner, name) || owner.body.statements.some(statement => statementDeclaresName(statement, name))) throw sourceNodeError(declaration, sourceFile, `Media query external stores conflict with component-local ${name}`)
|
|
2799
|
-
let setter = `__kSetMediaQuery${index++}`
|
|
2800
|
-
while (sourceFile.text.includes(setter)) setter = `__kSetMediaQuery${index++}`
|
|
2801
|
-
candidates.set(node, { declaration, query, setter })
|
|
2802
|
-
}
|
|
2803
|
-
inspect(sourceFile)
|
|
2804
|
-
const references = referenceIdentifiers(sourceFile, "useSyncExternalStore")
|
|
2805
|
-
if (references.length !== candidates.size) throw sourceNodeError(references.find(reference => ![...candidates.values()].some(candidate => insideNode(reference, candidate.declaration.initializer))) ?? externalStoreImport.entry, sourceFile, "useSyncExternalStore is supported only for direct static media query declarations")
|
|
2806
|
-
const directHooks = new Set(imports.flatMap(statement => statement.importClause.namedBindings.elements.filter(entry => !entry.propertyName).map(entry => entry.name.text)))
|
|
2807
|
-
const missingHooks = ["useEffect", "useState"].filter(name => !directHooks.has(name))
|
|
2808
|
-
for (const name of missingHooks) {
|
|
2809
|
-
const collision = sourceFile.statements.some(statement => statementDeclaresName(statement, name) || ts.isImportDeclaration(statement) && importDeclarationNames(statement).includes(name))
|
|
2810
|
-
if (collision) throw sourceNodeError([...candidates.values()][0].declaration, sourceFile, `Media query external stores conflict with local ${name}`)
|
|
2811
|
-
}
|
|
2812
|
-
|
|
2813
|
-
const visitor = node => {
|
|
2814
|
-
const candidate = ts.isVariableStatement(node) ? candidates.get(node) : undefined
|
|
2815
|
-
if (candidate) {
|
|
2816
|
-
const state = factory.createVariableStatement(node.modifiers, factory.createVariableDeclarationList([
|
|
2817
|
-
factory.createVariableDeclaration(factory.createArrayBindingPattern([
|
|
2818
|
-
factory.createBindingElement(undefined, undefined, candidate.declaration.name),
|
|
2819
|
-
factory.createBindingElement(undefined, undefined, candidate.setter)
|
|
2820
|
-
]), undefined, undefined, factory.createCallExpression(factory.createIdentifier("useState"), undefined, [factory.createFalse()]))
|
|
2821
|
-
], ts.NodeFlags.Const))
|
|
2822
|
-
const media = factory.createIdentifier("media")
|
|
2823
|
-
const update = factory.createIdentifier("update")
|
|
2824
|
-
const mediaCall = factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("window"), "matchMedia"), undefined, [factory.createStringLiteral(candidate.query)])
|
|
2825
|
-
const updateCallback = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createCallExpression(factory.createIdentifier(candidate.setter), undefined, [factory.createPropertyAccessExpression(media, "matches")]))
|
|
2826
|
-
const listener = method => factory.createCallExpression(factory.createPropertyAccessExpression(media, method), undefined, [factory.createStringLiteral("change"), update])
|
|
2827
|
-
const cleanup = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), listener("removeEventListener"))
|
|
2828
|
-
const setup = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createBlock([
|
|
2829
|
-
factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(media, undefined, undefined, mediaCall)], ts.NodeFlags.Const)),
|
|
2830
|
-
factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(update, undefined, undefined, updateCallback)], ts.NodeFlags.Const)),
|
|
2831
|
-
factory.createExpressionStatement(factory.createCallExpression(update, undefined, [])),
|
|
2832
|
-
factory.createExpressionStatement(listener("addEventListener")),
|
|
2833
|
-
factory.createReturnStatement(cleanup)
|
|
2834
|
-
], true))
|
|
2835
|
-
const effectCall = factory.createCallExpression(factory.createIdentifier("useEffect"), undefined, [setup, factory.createArrayLiteralExpression()])
|
|
2836
|
-
ts.setOriginalNode(effectCall, candidate.declaration.initializer)
|
|
2837
|
-
ts.setTextRange(effectCall, candidate.declaration.initializer)
|
|
2838
|
-
return [state, factory.createExpressionStatement(effectCall)]
|
|
2839
|
-
}
|
|
2840
|
-
if (node === externalStoreImport.statement) {
|
|
2841
|
-
const clause = node.importClause
|
|
2842
|
-
const bindings = clause.namedBindings
|
|
2843
|
-
const elements = bindings.elements.filter(entry => entry !== externalStoreImport.entry)
|
|
2844
|
-
for (const name of missingHooks) elements.push(factory.createImportSpecifier(false, undefined, factory.createIdentifier(name)))
|
|
2845
|
-
return factory.updateImportDeclaration(node, node.modifiers, factory.updateImportClause(clause, false, clause.name, factory.updateNamedImports(bindings, elements)), node.moduleSpecifier, node.attributes)
|
|
2846
|
-
}
|
|
2847
|
-
return ts.visitEachChild(node, visitor, context)
|
|
2848
|
-
}
|
|
2849
|
-
return ts.visitNode(sourceFile, visitor)
|
|
2850
|
-
}
|
|
2851
|
-
|
|
2852
|
-
function insideNode(node, root) {
|
|
2853
|
-
for (let current = node; current; current = current.parent) if (current === root) return true
|
|
2854
|
-
return false
|
|
2855
|
-
}
|
|
2856
|
-
|
|
2857
|
-
function normalizeNavigatorCapabilityConditions(sourceFile, factory, context) {
|
|
2858
|
-
const candidates = new Map()
|
|
2859
|
-
let index = 0
|
|
2860
|
-
const inspect = node => {
|
|
2861
|
-
if (!ts.isVariableStatement(node) || !(node.declarationList.flags & ts.NodeFlags.Const) || node.declarationList.declarations.length !== 1) {
|
|
2862
|
-
ts.forEachChild(node, inspect)
|
|
2863
|
-
return
|
|
2864
|
-
}
|
|
2865
|
-
const declaration = node.declarationList.declarations[0]
|
|
2866
|
-
const value = declaration.initializer && unwrapExpression(declaration.initializer)
|
|
2867
|
-
if (!ts.isIdentifier(declaration.name) || !value || !ts.isBinaryExpression(value) || value.operatorToken.kind !== ts.SyntaxKind.InKeyword || !ts.isStringLiteral(unwrapExpression(value.left)) || !ts.isIdentifier(unwrapExpression(value.right)) || unwrapExpression(value.right).text !== "navigator" || !isUnshadowedGlobal(unwrapExpression(value.right), sourceFile)) {
|
|
2868
|
-
ts.forEachChild(node, inspect)
|
|
2869
|
-
return
|
|
2870
|
-
}
|
|
2871
|
-
const owner = nearestFunction(node)
|
|
2872
|
-
const topLevelOwner = owner && (owner.parent === sourceFile || ts.isVariableDeclaration(owner.parent) && owner.parent.parent?.parent?.parent === sourceFile)
|
|
2873
|
-
if (!topLevelOwner || !owner.body || !ts.isBlock(owner.body) || node.parent !== owner.body) throw sourceNodeError(declaration, sourceFile, "Navigator capability conditions must be top-level component const declarations")
|
|
2874
|
-
const references = referenceIdentifiers(owner.body, declaration.name.text)
|
|
2875
|
-
const condition = references.length === 1 ? references[0] : undefined
|
|
2876
|
-
const structural = condition && ts.isBinaryExpression(condition.parent) && condition.parent.left === condition && condition.parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken && containsJsx(condition.parent.right) && ts.isJsxExpression(condition.parent.parent) && condition.parent.parent.expression === condition.parent
|
|
2877
|
-
if (!structural) throw sourceNodeError(declaration, sourceFile, "Navigator capability values may only control one direct JSX && branch")
|
|
2878
|
-
for (const name of ["useEffect", "useState"]) if (owner.parameters.some(parameter => bindingNames(parameter.name).includes(name)) || functionVarDeclaresName(owner, name) || owner.body.statements.some(statement => statementDeclaresName(statement, name))) throw sourceNodeError(declaration, sourceFile, `Navigator capability conditions conflict with component-local ${name}`)
|
|
2879
|
-
let setter = `__kSetNavigatorCapability${index++}`
|
|
2880
|
-
while (sourceFile.text.includes(setter)) setter = `__kSetNavigatorCapability${index++}`
|
|
2881
|
-
candidates.set(node, { declaration, property: unwrapExpression(value.left).text, setter })
|
|
2882
|
-
}
|
|
2883
|
-
inspect(sourceFile)
|
|
2884
|
-
if (!candidates.size) return sourceFile
|
|
2885
|
-
|
|
2886
|
-
const visitor = node => {
|
|
2887
|
-
const candidate = ts.isVariableStatement(node) ? candidates.get(node) : undefined
|
|
2888
|
-
if (candidate) {
|
|
2889
|
-
const state = factory.createVariableStatement(node.modifiers, factory.createVariableDeclarationList([
|
|
2890
|
-
factory.createVariableDeclaration(factory.createArrayBindingPattern([
|
|
2891
|
-
factory.createBindingElement(undefined, undefined, candidate.declaration.name),
|
|
2892
|
-
factory.createBindingElement(undefined, undefined, candidate.setter)
|
|
2893
|
-
]), undefined, undefined, factory.createCallExpression(factory.createIdentifier("useState"), undefined, [factory.createFalse()]))
|
|
2894
|
-
], ts.NodeFlags.Const))
|
|
2895
|
-
const capability = factory.createBinaryExpression(factory.createStringLiteral(candidate.property), factory.createToken(ts.SyntaxKind.InKeyword), factory.createIdentifier("navigator"))
|
|
2896
|
-
const setup = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createBlock([
|
|
2897
|
-
factory.createExpressionStatement(factory.createCallExpression(factory.createIdentifier(candidate.setter), undefined, [capability]))
|
|
2898
|
-
], true))
|
|
2899
|
-
const effectCall = factory.createCallExpression(factory.createIdentifier("useEffect"), undefined, [setup, factory.createArrayLiteralExpression()])
|
|
2900
|
-
ts.setOriginalNode(effectCall, candidate.declaration.initializer)
|
|
2901
|
-
ts.setTextRange(effectCall, candidate.declaration.initializer)
|
|
2902
|
-
const effect = factory.createExpressionStatement(effectCall)
|
|
2903
|
-
return [state, effect]
|
|
2904
|
-
}
|
|
2905
|
-
return ts.visitEachChild(node, visitor, context)
|
|
2906
|
-
}
|
|
2907
|
-
let normalized = ts.visitNode(sourceFile, visitor)
|
|
2908
|
-
const hookImports = normalized.statements.filter(statement => ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier) && ["react", "@kudzujs/core"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings))
|
|
2909
|
-
const hookImport = hookImports[0]
|
|
2910
|
-
const bindings = hookImport?.importClause.namedBindings
|
|
2911
|
-
const imported = new Set(hookImports.flatMap(statement => statement.importClause.namedBindings.elements.map(entry => entry.name.text)))
|
|
2912
|
-
const missing = ["useEffect", "useState"].filter(name => !imported.has(name))
|
|
2913
|
-
if (!missing.length) return normalized
|
|
2914
|
-
for (const name of missing) if (normalized.statements.some(statement => !hookImports.includes(statement) && (statementDeclaresName(statement, name) || ts.isImportDeclaration(statement) && importDeclarationNames(statement).includes(name)))) throw sourceNodeError([...candidates.values()][0].declaration, sourceFile, `Navigator capability conditions conflict with local ${name}`)
|
|
2915
|
-
if (hookImport && bindings && ts.isNamedImports(bindings)) {
|
|
2916
|
-
const statements = normalized.statements.map(statement => statement === hookImport ? factory.updateImportDeclaration(statement, statement.modifiers, factory.updateImportClause(statement.importClause, false, statement.importClause.name, factory.updateNamedImports(bindings, [
|
|
2917
|
-
...bindings.elements,
|
|
2918
|
-
...missing.map(name => factory.createImportSpecifier(false, undefined, factory.createIdentifier(name)))
|
|
2919
|
-
])), statement.moduleSpecifier, statement.attributes) : statement)
|
|
2920
|
-
return factory.updateSourceFile(normalized, statements)
|
|
2921
|
-
}
|
|
2922
|
-
const declaration = factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports(missing.map(name => factory.createImportSpecifier(false, undefined, factory.createIdentifier(name))))), factory.createStringLiteral("@kudzujs/core"))
|
|
2923
|
-
const statements = [...normalized.statements]
|
|
2924
|
-
statements.splice(statements.findLastIndex(statement => ts.isImportDeclaration(statement)) + 1, 0, declaration)
|
|
2925
|
-
return factory.updateSourceFile(normalized, statements)
|
|
2926
|
-
}
|
|
2927
|
-
|
|
2928
|
-
function normalizeEffectAnimationFrameRefs(sourceFile, factory, context) {
|
|
2929
|
-
const frameCall = (node, name) => ts.isCallExpression(node) && (
|
|
2930
|
-
ts.isIdentifier(node.expression) && node.expression.text === name ||
|
|
2931
|
-
ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression) && node.expression.expression.text === "window" && node.expression.name.text === name
|
|
2932
|
-
)
|
|
2933
|
-
const unshadowedFrameCall = (node, owner) => {
|
|
2934
|
-
const name = ts.isIdentifier(node.expression) ? node.expression : ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression) ? node.expression.expression : undefined
|
|
2935
|
-
return name && !isShadowedIdentifier(name, owner) && !sourceFile.statements.some(statement => statementDeclaresName(statement, name.text) || ts.isImportDeclaration(statement) && importDeclarationNames(statement).includes(name.text))
|
|
2936
|
-
}
|
|
2937
|
-
const currentAccess = (node, name) => ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === name && node.name.text === "current"
|
|
2938
|
-
const inside = (node, root) => {
|
|
2939
|
-
for (let current = node; current; current = current.parent) if (current === root) return true
|
|
2940
|
-
return false
|
|
2941
|
-
}
|
|
2942
|
-
const directOrGuarded = (statement, body, name, negated) => {
|
|
2943
|
-
if (statement.parent === body) return true
|
|
2944
|
-
let branch = statement
|
|
2945
|
-
if (ts.isBlock(statement.parent) && statement.parent.statements.length === 1) branch = statement.parent
|
|
2946
|
-
const conditional = branch.parent
|
|
2947
|
-
if (!ts.isIfStatement(conditional) || conditional.thenStatement !== branch || conditional.parent !== body || conditional.elseStatement) return false
|
|
2948
|
-
let condition = unwrapExpression(conditional.expression)
|
|
2949
|
-
const isNegated = ts.isPrefixUnaryExpression(condition) && condition.operator === ts.SyntaxKind.ExclamationToken
|
|
2950
|
-
if (isNegated) condition = unwrapExpression(condition.operand)
|
|
2951
|
-
return isNegated === negated && currentAccess(condition, name)
|
|
2952
|
-
}
|
|
2953
|
-
const hasUseRefImport = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier) && ["react", "@kudzujs/core"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings) && statement.importClause.namedBindings.elements.some(entry => !entry.propertyName && entry.name.text === "useRef"))
|
|
2954
|
-
const hasUseEffectImport = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier) && ["react", "@kudzujs/core"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings) && statement.importClause.namedBindings.elements.some(entry => !entry.propertyName && entry.name.text === "useEffect"))
|
|
2955
|
-
const replacements = new Set()
|
|
2956
|
-
const inspect = node => {
|
|
2957
|
-
if (!hasUseRefImport || !ts.isVariableDeclaration(node) || !ts.isIdentifier(node.name) || !node.initializer || !ts.isCallExpression(node.initializer) || !ts.isIdentifier(node.initializer.expression) || node.initializer.expression.text !== "useRef" || isShadowedIdentifier(node.initializer.expression, sourceFile) || node.initializer.arguments.length !== 1 || !ts.isNumericLiteral(node.initializer.arguments[0]) || Number(node.initializer.arguments[0].text) !== 0) {
|
|
2958
|
-
ts.forEachChild(node, inspect)
|
|
2959
|
-
return
|
|
2960
|
-
}
|
|
2961
|
-
const owner = nearestFunction(node)
|
|
2962
|
-
if (!owner?.body || !ts.isBlock(owner.body)) return
|
|
2963
|
-
const accesses = []
|
|
2964
|
-
const collect = current => {
|
|
2965
|
-
if (currentAccess(current, node.name.text) && !isShadowedIdentifier(current.expression, owner.body)) accesses.push(current)
|
|
2966
|
-
ts.forEachChild(current, collect)
|
|
2967
|
-
}
|
|
2968
|
-
collect(owner.body)
|
|
2969
|
-
const frameAssignments = accesses.filter(access => ts.isBinaryExpression(access.parent) && unwrapExpression(access.parent.left) === access && access.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken && frameCall(unwrapExpression(access.parent.right), "requestAnimationFrame") && unshadowedFrameCall(unwrapExpression(access.parent.right), owner))
|
|
2970
|
-
if (!frameAssignments.length) return
|
|
2971
|
-
const invalidReference = referenceIdentifiers(owner.body, node.name.text).find(reference => !ts.isPropertyAccessExpression(reference.parent) || reference.parent.expression !== reference || reference.parent.name.text !== "current")
|
|
2972
|
-
if (invalidReference) throw sourceNodeError(invalidReference, sourceFile, "Animation frame refs may only use direct .current reads and assignments")
|
|
2973
|
-
const statement = node.parent?.parent
|
|
2974
|
-
const topLevelOwner = owner.parent === sourceFile || ts.isVariableDeclaration(owner.parent) && owner.parent.parent?.parent?.parent === sourceFile
|
|
2975
|
-
if (!topLevelOwner || !ts.isVariableStatement(statement) || !(statement.declarationList.flags & ts.NodeFlags.Const) || statement.declarationList.declarations.length !== 1 || statement.parent !== owner.body) throw sourceNodeError(node, sourceFile, "Animation frame refs must be one top-level component const")
|
|
2976
|
-
if (frameAssignments.length !== 1) throw sourceNodeError(node, sourceFile, "Animation frame refs require one direct ref.current = requestAnimationFrame(callback) assignment")
|
|
2977
|
-
const frame = unwrapExpression(frameAssignments[0].parent.right)
|
|
2978
|
-
const frameCallback = frame.arguments.length === 1 && ts.isIdentifier(unwrapExpression(frame.arguments[0])) ? unwrapExpression(frame.arguments[0]) : undefined
|
|
2979
|
-
const effectCalls = owner.body.statements.flatMap(statement => hasUseEffectImport && ts.isExpressionStatement(statement) && ts.isCallExpression(statement.expression) && ts.isIdentifier(statement.expression.expression) && statement.expression.expression.text === "useEffect" && !isShadowedIdentifier(statement.expression.expression, sourceFile) ? [statement.expression] : [])
|
|
2980
|
-
const effects = effectCalls.filter(effect => effect.arguments[0] && inside(frameAssignments[0], effect.arguments[0]))
|
|
2981
|
-
if (effects.length !== 1) throw sourceNodeError(node, sourceFile, "Animation frame refs must belong to one inline component effect")
|
|
2982
|
-
const effect = effects[0]
|
|
2983
|
-
const callback = effect.arguments[0]
|
|
2984
|
-
if (!(ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) || !ts.isBlock(callback.body)) throw sourceNodeError(callback, sourceFile, "Animation frame refs require one inline block-bodied effect")
|
|
2985
|
-
if (accesses.some(access => !inside(access, callback))) throw sourceNodeError(node, sourceFile, "Animation frame refs may only be used inside their owning effect")
|
|
2986
|
-
const callbacks = new Map()
|
|
2987
|
-
for (const statement of callback.body.statements) {
|
|
2988
|
-
if (ts.isVariableStatement(statement)) for (const declaration of statement.declarationList.declarations) {
|
|
2989
|
-
if (ts.isIdentifier(declaration.name) && declaration.initializer && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) callbacks.set(declaration.name.text, declaration.initializer)
|
|
2990
|
-
}
|
|
2991
|
-
if (ts.isFunctionDeclaration(statement) && statement.name && statement.body) callbacks.set(statement.name.text, statement)
|
|
2992
|
-
}
|
|
2993
|
-
const frameOwner = nearestFunction(frameAssignments[0])
|
|
2994
|
-
const shadowedCallback = frameCallback && isShadowedIdentifier(frameCallback, callback.body)
|
|
2995
|
-
const update = frameCallback && !shadowedCallback && callbacks.get(frameCallback.text)
|
|
2996
|
-
if (!update) throw sourceNodeError(frame, sourceFile, "Animation frame refs require a direct local callback")
|
|
2997
|
-
const frameStatement = frameAssignments[0].parent.parent
|
|
2998
|
-
if (!frameOwner || ![...callbacks.values()].includes(frameOwner) || !ts.isBlock(frameOwner.body) || !ts.isExpressionStatement(frameStatement) || !directOrGuarded(frameStatement, frameOwner.body, node.name.text, true)) throw sourceNodeError(frameAssignments[0], sourceFile, "Animation frame requests must be assigned directly inside one local scheduler")
|
|
2999
|
-
const resetAssignments = accesses.filter(access => ts.isBinaryExpression(access.parent) && unwrapExpression(access.parent.left) === access && access.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken && ts.isNumericLiteral(unwrapExpression(access.parent.right)) && Number(unwrapExpression(access.parent.right).text) === 0)
|
|
3000
|
-
const resetStatement = resetAssignments[0]?.parent.parent
|
|
3001
|
-
if (resetAssignments.length !== 1 || nearestFunction(resetAssignments[0]) !== update || !ts.isBlock(update.body) || !ts.isExpressionStatement(resetStatement) || resetStatement.parent !== update.body) throw sourceNodeError(update, sourceFile, "Animation frame callbacks must directly reset their ref to 0")
|
|
3002
|
-
const writes = accesses.filter(access => ts.isBinaryExpression(access.parent) && unwrapExpression(access.parent.left) === access && access.parent.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && access.parent.operatorToken.kind <= ts.SyntaxKind.LastAssignment)
|
|
3003
|
-
if (writes.length !== 2) throw sourceNodeError(node, sourceFile, "Animation frame refs may only be assigned by their request and reset operations")
|
|
3004
|
-
const returns = effectReturns(callback)
|
|
3005
|
-
const cancellations = []
|
|
3006
|
-
const collectCancellations = current => {
|
|
3007
|
-
const argument = current.arguments?.[0] && unwrapExpression(current.arguments[0])
|
|
3008
|
-
if (frameCall(current, "cancelAnimationFrame") && unshadowedFrameCall(current, owner) && current.arguments.length === 1 && currentAccess(argument, node.name.text) && !isShadowedIdentifier(argument.expression, owner.body)) cancellations.push(current)
|
|
3009
|
-
ts.forEachChild(current, collectCancellations)
|
|
849
|
+
function normalizeCompilerSource(sourceFile, { base, context, file, importedCollections, importedStaticCollections, sourceFiles, sourceIndex }) {
|
|
850
|
+
const factory = context.factory
|
|
851
|
+
let customHookTimerStates = new Set()
|
|
852
|
+
sourceFile = applyNormalizationPasses(sourceFile, [
|
|
853
|
+
...(importedStaticCollections ? [source => normalizeImportedStaticCollections(source, importedStaticCollections, factory, context)] : []),
|
|
854
|
+
source => normalizeReactRouterSyntax(source, factory, context, base),
|
|
855
|
+
source => normalizeClsxSyntax(source, factory, context),
|
|
856
|
+
source => normalizeMediaQueryExternalStores(source, factory, context),
|
|
857
|
+
source => normalizeReactMigrationSyntax(source, factory, context, importedCollections ?? importedSerializableCollectionNames(source, file, sourceFiles, sourceIndex)),
|
|
858
|
+
source => normalizeNavigatorCapabilityConditions(source, factory, context),
|
|
859
|
+
source => normalizeEffectAnimationFrameRefs(source, factory, context),
|
|
860
|
+
source => {
|
|
861
|
+
const result = normalizeCustomHookTimerRefs(source, factory, context)
|
|
862
|
+
customHookTimerStates = result.timerStates
|
|
863
|
+
return result.sourceFile
|
|
864
|
+
},
|
|
865
|
+
source => {
|
|
866
|
+
validateUseIdSyntax(source)
|
|
867
|
+
return source
|
|
868
|
+
},
|
|
869
|
+
source => normalizeLazyStateInitializers(source, factory, context, file, sourceFiles, sourceIndex),
|
|
870
|
+
source => normalizeZustandMigrationSyntax(source, factory, context),
|
|
871
|
+
source => normalizeRenderControlFlow(source, factory, context),
|
|
872
|
+
source => {
|
|
873
|
+
workerCompiler.rejectOrdinaryImports(source, file, sourceFiles)
|
|
874
|
+
return source
|
|
3010
875
|
}
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
const cleanup = cancellation && returns.cleanups.find(candidate => nearestFunction(cancellation) === candidate)
|
|
3014
|
-
const cancellationStatement = cancellation?.parent
|
|
3015
|
-
if (cancellations.length !== 1 || !cleanup || !ts.isBlock(cleanup.body) || !ts.isExpressionStatement(cancellationStatement) || !directOrGuarded(cancellationStatement, cleanup.body, node.name.text, false)) throw sourceNodeError(node, sourceFile, "Animation frame refs require direct cancellation in effect cleanup")
|
|
3016
|
-
replacements.add(node)
|
|
3017
|
-
}
|
|
3018
|
-
inspect(sourceFile)
|
|
3019
|
-
if (!replacements.size) return sourceFile
|
|
3020
|
-
const visitor = node => {
|
|
3021
|
-
if (replacements.has(node)) return factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, factory.createObjectLiteralExpression([
|
|
3022
|
-
factory.createPropertyAssignment("current", factory.createNumericLiteral(0))
|
|
3023
|
-
]))
|
|
3024
|
-
return ts.visitEachChild(node, visitor, context)
|
|
3025
|
-
}
|
|
3026
|
-
return ts.visitNode(sourceFile, visitor)
|
|
876
|
+
])
|
|
877
|
+
return { sourceFile, customHookTimerStates }
|
|
3027
878
|
}
|
|
3028
879
|
|
|
3029
|
-
function createKudzuTransformer(
|
|
880
|
+
function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourceIndex, staticFiles, importedAssets, cssModules, base, workerReferences }) {
|
|
881
|
+
const { nativeHandlers, effectHandlers } = semantic
|
|
3030
882
|
return context => sourceFile => {
|
|
3031
|
-
const factory = context.factory
|
|
3032
883
|
const hasLinkElements = /<link/i.test(sourceFile.text)
|
|
3033
884
|
const importedStaticCollections = importedSerializableCollections(sourceFile, file, sourceFiles, sourceIndex)
|
|
3034
885
|
const importedCollections = new Set(importedStaticCollections.keys())
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
ts.setParentRecursive(sourceFile, false)
|
|
3049
|
-
sourceFile = normalizeCustomHookTimerRefs(sourceFile, factory, context)
|
|
3050
|
-
const customHookTimerStates = customHookTimerStatesBySource.get(sourceFile) ?? new Set()
|
|
3051
|
-
ts.setParentRecursive(sourceFile, false)
|
|
3052
|
-
validateUseIdSyntax(sourceFile)
|
|
3053
|
-
sourceFile = normalizeLazyStateInitializers(sourceFile, factory, context, file, sourceFiles, sourceIndex)
|
|
3054
|
-
ts.setParentRecursive(sourceFile, false)
|
|
3055
|
-
sourceFile = normalizeZustandMigrationSyntax(sourceFile, factory, context)
|
|
3056
|
-
ts.setParentRecursive(sourceFile, false)
|
|
3057
|
-
sourceFile = normalizeRenderControlFlow(sourceFile, factory, context)
|
|
3058
|
-
ts.setParentRecursive(sourceFile, false)
|
|
3059
|
-
rejectOrdinaryWorkerImports(sourceFile, file, sourceFiles)
|
|
886
|
+
const normalized = normalizeCompilerSource(sourceFile, { base, context, file, importedCollections, importedStaticCollections, sourceFiles, sourceIndex })
|
|
887
|
+
sourceFile = normalized.sourceFile
|
|
888
|
+
const { customHookTimerStates } = normalized
|
|
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
|
+
})
|
|
3060
899
|
const importBindings = clientImportBindings(sourceFile, file, sourceFiles)
|
|
3061
900
|
const packageBindings = packageImportBindings(sourceFile)
|
|
3062
901
|
for (const [name] of packageBindings) {
|
|
@@ -3065,36 +904,14 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
3065
904
|
if (invalid) throw sourceNodeError(invalid, sourceFile, `Package import ${JSON.stringify(name)} may only be referenced directly inside JSX event handlers`)
|
|
3066
905
|
}
|
|
3067
906
|
const hasUseEffectImport = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) && ["@kudzujs/core", "react"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings) && statement.importClause.namedBindings.elements.some(entry => !entry.propertyName && entry.name.text === "useEffect"))
|
|
3068
|
-
const
|
|
3069
|
-
const importedTimerStates = new Map()
|
|
907
|
+
const importedSourceCache = new Map()
|
|
3070
908
|
const importedSource = target => {
|
|
3071
|
-
let
|
|
3072
|
-
if (!
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
imported = normalizeClsxSyntax(imported, factory, context)
|
|
3076
|
-
ts.setParentRecursive(imported, false)
|
|
3077
|
-
imported = normalizeMediaQueryExternalStores(imported, factory, context)
|
|
3078
|
-
ts.setParentRecursive(imported, false)
|
|
3079
|
-
imported = normalizeReactMigrationSyntax(imported, factory, context, importedSerializableCollectionNames(imported, target, sourceFiles, sourceIndex))
|
|
3080
|
-
ts.setParentRecursive(imported, false)
|
|
3081
|
-
imported = normalizeNavigatorCapabilityConditions(imported, factory, context)
|
|
3082
|
-
ts.setParentRecursive(imported, false)
|
|
3083
|
-
imported = normalizeEffectAnimationFrameRefs(imported, factory, context)
|
|
3084
|
-
ts.setParentRecursive(imported, false)
|
|
3085
|
-
imported = normalizeCustomHookTimerRefs(imported, factory, context)
|
|
3086
|
-
importedTimerStates.set(target, customHookTimerStatesBySource.get(imported) ?? new Set())
|
|
3087
|
-
ts.setParentRecursive(imported, false)
|
|
3088
|
-
validateUseIdSyntax(imported)
|
|
3089
|
-
imported = normalizeLazyStateInitializers(imported, factory, context, target, sourceFiles, sourceIndex)
|
|
3090
|
-
ts.setParentRecursive(imported, false)
|
|
3091
|
-
imported = normalizeZustandMigrationSyntax(imported, factory, context)
|
|
3092
|
-
ts.setParentRecursive(imported, false)
|
|
3093
|
-
imported = normalizeRenderControlFlow(imported, factory, context)
|
|
3094
|
-
ts.setParentRecursive(imported, false)
|
|
3095
|
-
importedSources.set(target, imported)
|
|
909
|
+
let result = importedSourceCache.get(target)
|
|
910
|
+
if (!result) {
|
|
911
|
+
result = normalizeCompilerSource(parseSourceFile(target, sourceIndex.get(target)), { base, context, file: target, sourceFiles, sourceIndex })
|
|
912
|
+
importedSourceCache.set(target, result)
|
|
3096
913
|
}
|
|
3097
|
-
return
|
|
914
|
+
return result.sourceFile
|
|
3098
915
|
}
|
|
3099
916
|
const importedCollectionTransforms = new Map()
|
|
3100
917
|
const importedCalculationFunctions = new Map()
|
|
@@ -3255,7 +1072,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
3255
1072
|
const capture = nativeCaptureNames(callback, states).values().next().value
|
|
3256
1073
|
if (capture) throw sourceNodeError(callback, hookSource, `Relative custom hook callback ${JSON.stringify(name)} cannot capture private binding ${JSON.stringify(capture)}`)
|
|
3257
1074
|
}
|
|
3258
|
-
const privateStates = new Set([...states.values()].filter(state =>
|
|
1075
|
+
const privateStates = new Set([...states.values()].filter(state => importedSourceCache.get(hookSource.fileName)?.customHookTimerStates.has(state)))
|
|
3259
1076
|
const analysis = { callbacks, fields, privateStates, states }
|
|
3260
1077
|
customHooks.set(key, analysis)
|
|
3261
1078
|
return analysis
|
|
@@ -3492,14 +1309,14 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
3492
1309
|
const call = unwrapExpression(current.expression)
|
|
3493
1310
|
if (ts.isIdentifier(call.expression) && importBindings.has(call.expression.text) && importBindings.get(call.expression.text).kind !== "namespace") {
|
|
3494
1311
|
validateImportedCalculation(call, current.name.text)
|
|
3495
|
-
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 })
|
|
3496
1313
|
return factory.createNumericLiteral(0)
|
|
3497
1314
|
}
|
|
3498
1315
|
}
|
|
3499
1316
|
return ts.visitEachChild(current, validate, context)
|
|
3500
1317
|
}
|
|
3501
1318
|
const normalized = ts.visitNode(value, validate)
|
|
3502
|
-
collectionExpression(normalized, {
|
|
1319
|
+
collectionExpression(normalized, { fail: (node, message) => fail(node, message.replace("Rendered collection", "Reactive JSX local")), stateNames: allowedNames })
|
|
3503
1320
|
return
|
|
3504
1321
|
}
|
|
3505
1322
|
const intl = constructor.expression.expression
|
|
@@ -3509,7 +1326,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
3509
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
|
|
3510
1327
|
if (!roundAccess) fail(value, "Reactive JSX Intl.NumberFormat format() requires exactly Math.round(expression)")
|
|
3511
1328
|
if (!isUnshadowedGlobal(roundAccess.expression, sourceFile)) fail(roundAccess.expression, "Reactive JSX Intl.NumberFormat requires the unshadowed global Math object")
|
|
3512
|
-
collectionExpression(rounded.arguments[0], {
|
|
1329
|
+
collectionExpression(rounded.arguments[0], { fail: (node, message) => fail(node, message.replace("Rendered collection", "Reactive JSX local")), stateNames: allowedNames })
|
|
3513
1330
|
}
|
|
3514
1331
|
const resolveReactiveJsxExpression = (expression, owner, setters) => {
|
|
3515
1332
|
const declarations = jsxLocalDeclarations.get(owner)
|
|
@@ -4081,16 +1898,12 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
4081
1898
|
if (!usedStates.size && !captures.size) return ts.visitEachChild(expression, visitor, context)
|
|
4082
1899
|
usesBehavior = true
|
|
4083
1900
|
usesConditional = true
|
|
4084
|
-
return compileConditional(
|
|
1901
|
+
return descriptors.compileConditional(
|
|
4085
1902
|
parts.kind,
|
|
4086
1903
|
parts.condition,
|
|
4087
1904
|
compileRenderExpression(parts.truthy, anchor),
|
|
4088
1905
|
compileRenderExpression(parts.falsy, anchor),
|
|
4089
|
-
setters
|
|
4090
|
-
factory,
|
|
4091
|
-
context,
|
|
4092
|
-
reactiveBindings,
|
|
4093
|
-
handlerUrl
|
|
1906
|
+
setters
|
|
4094
1907
|
)
|
|
4095
1908
|
}
|
|
4096
1909
|
|
|
@@ -4190,7 +2003,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
4190
2003
|
const derivedStates = initializer && !directAlias ? referencedStateNames(initializer, setters) : new Set()
|
|
4191
2004
|
if (derivedStates.size) {
|
|
4192
2005
|
const usedStates = new Set()
|
|
4193
|
-
const expression = collectionExpression(initializer, {
|
|
2006
|
+
const expression = collectionExpression(initializer, { fail: effectFail, stateNames, selectorStates: usedStates })
|
|
4194
2007
|
if (!usedStates.size) effectFail(dependency, `useEffect() derived dependency "${dependency.text}" must read direct primitive state`)
|
|
4195
2008
|
dependencyExpressions.push(expression)
|
|
4196
2009
|
for (const name of usedStates) {
|
|
@@ -4242,11 +2055,19 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
4242
2055
|
}
|
|
4243
2056
|
if (listEffect && callbackFile !== file) {
|
|
4244
2057
|
const originalCallback = listEffect.source.arguments[0]
|
|
4245
|
-
|
|
2058
|
+
workerCompiler.rejectConstructions(originalCallback, callbackSource, "Relative TypeScript Worker construction in imported keyed-row effects is not supported; construct the Worker in a directly compiled page or local component effect")
|
|
4246
2059
|
} else {
|
|
4247
|
-
compiledCallback =
|
|
4248
|
-
}
|
|
4249
|
-
const descriptor =
|
|
2060
|
+
compiledCallback = workerCompiler.rewriteEffect(compiledCallback, callbackFile, callbackSource, sourceFiles, workerReferences, factory, context)
|
|
2061
|
+
}
|
|
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
|
+
})
|
|
4250
2071
|
for (const reference of workerReferences.slice(workerStart)) Object.assign(reference, { module: handlerUrl, handler: descriptor.exportName })
|
|
4251
2072
|
usesListItem ||= Boolean(itemDependencies.length && !listEffect)
|
|
4252
2073
|
usesBehavior = true
|
|
@@ -4302,19 +2123,19 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
4302
2123
|
|
|
4303
2124
|
if (ts.isJsxExpression(node) && node.expression && listConditions.has(node.expression)) {
|
|
4304
2125
|
const entry = listConditions.get(node.expression)
|
|
4305
|
-
return factory.updateJsxExpression(node, compileListConditional({
|
|
2126
|
+
return factory.updateJsxExpression(node, descriptors.compileListConditional({
|
|
4306
2127
|
...entry,
|
|
4307
2128
|
truthy: ts.visitNode(entry.truthy, visitor),
|
|
4308
2129
|
falsy: ts.visitNode(entry.falsy, visitor)
|
|
4309
|
-
}
|
|
2130
|
+
}))
|
|
4310
2131
|
}
|
|
4311
2132
|
|
|
4312
2133
|
if (ts.isJsxExpression(node) && node.expression && listValues.has(node.expression)) {
|
|
4313
|
-
return factory.updateJsxExpression(node, compileListValue(node.expression, listValues.get(node.expression)
|
|
2134
|
+
return factory.updateJsxExpression(node, descriptors.compileListValue(node.expression, listValues.get(node.expression)))
|
|
4314
2135
|
}
|
|
4315
2136
|
|
|
4316
2137
|
if (ts.isJsxAttribute(node) && node.initializer && ts.isJsxExpression(node.initializer) && node.initializer.expression && listValues.has(node.initializer.expression)) {
|
|
4317
|
-
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))))
|
|
4318
2139
|
}
|
|
4319
2140
|
|
|
4320
2141
|
if (ts.isJsxExpression(node) && node.initializer === undefined && node.expression && (ts.isJsxElement(node.parent) || ts.isJsxFragment(node.parent))) {
|
|
@@ -4326,7 +2147,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
4326
2147
|
let listSource = listParts.state
|
|
4327
2148
|
if (listParts.calculation) {
|
|
4328
2149
|
usesBinding = true
|
|
4329
|
-
listSource = compileReactiveBinding(listParts.calculation, settersForNode(node, settersByFunction),
|
|
2150
|
+
listSource = descriptors.compileReactiveBinding(listParts.calculation, { setters: settersForNode(node, settersByFunction), importBindings })
|
|
4330
2151
|
}
|
|
4331
2152
|
const arguments_ = [
|
|
4332
2153
|
listSource,
|
|
@@ -4352,7 +2173,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
4352
2173
|
if ((usedStates.size || captures.size) && !ts.isIdentifier(expression) && !containsJsx(expression)) {
|
|
4353
2174
|
usesBehavior = true
|
|
4354
2175
|
usesBinding = true
|
|
4355
|
-
return factory.updateJsxExpression(node, compileReactiveBinding(expression, setters,
|
|
2176
|
+
return factory.updateJsxExpression(node, descriptors.compileReactiveBinding(expression, { setters, importBindings }))
|
|
4356
2177
|
}
|
|
4357
2178
|
}
|
|
4358
2179
|
|
|
@@ -4365,14 +2186,20 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
4365
2186
|
if ((usedStates.size || captures.size) && !ts.isIdentifier(expression)) {
|
|
4366
2187
|
usesBehavior = true
|
|
4367
2188
|
usesBinding = true
|
|
4368
|
-
const compiled = compileReactiveBinding(expression, setters,
|
|
2189
|
+
const compiled = descriptors.compileReactiveBinding(expression, { setters, importBindings })
|
|
4369
2190
|
return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, compiled))
|
|
4370
2191
|
}
|
|
4371
2192
|
}
|
|
4372
2193
|
|
|
4373
2194
|
if (ts.isJsxAttribute(node) && node.initializer && ts.isJsxExpression(node.initializer) && node.initializer.expression && /^on[A-Z]/.test(node.name.text)) {
|
|
4374
2195
|
const setters = settersForNode(node, settersByFunction)
|
|
4375
|
-
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
|
+
})
|
|
4376
2203
|
if (event) {
|
|
4377
2204
|
usesBehavior = true
|
|
4378
2205
|
return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, event))
|
|
@@ -4416,106 +2243,12 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
4416
2243
|
if (usesComponentEffects) behaviorImports.push(factory.createImportSpecifier(false, factory.createIdentifier("useEffect"), factory.createIdentifier("__kComponentUseEffect")))
|
|
4417
2244
|
if (usesBinding || usesConditional) behaviorImports.push(factory.createImportSpecifier(false, factory.createIdentifier("bindingValue"), factory.createIdentifier("__kBindingValue")))
|
|
4418
2245
|
const behaviorImport = factory.createImportDeclaration(
|
|
4419
|
-
undefined,
|
|
4420
|
-
factory.createImportClause(false, undefined, factory.createNamedImports(behaviorImports)),
|
|
4421
|
-
factory.createStringLiteral("@kudzujs/core")
|
|
4422
|
-
)
|
|
4423
|
-
return factory.updateSourceFile(transformed, [behaviorImport, ...transformed.statements])
|
|
4424
|
-
}
|
|
4425
|
-
}
|
|
4426
|
-
|
|
4427
|
-
function normalizeRenderControlFlow(sourceFile, factory, context) {
|
|
4428
|
-
const normalizeStatements = statements => {
|
|
4429
|
-
const nested = statements.map(statement => ts.visitEachChild(statement, visitNested, context))
|
|
4430
|
-
const assigned = []
|
|
4431
|
-
for (let index = 0; index < nested.length; index++) {
|
|
4432
|
-
const statement = nested[index]
|
|
4433
|
-
const next = nested[index + 1]
|
|
4434
|
-
const declaration = singleUninitializedLet(statement)
|
|
4435
|
-
const assignment = declaration && next && assignmentConditional(next, declaration.name.text, factory)
|
|
4436
|
-
if (declaration && assignment) {
|
|
4437
|
-
const updated = factory.updateVariableDeclaration(declaration, declaration.name, declaration.exclamationToken, declaration.type, assignment)
|
|
4438
|
-
const list = factory.createVariableDeclarationList([updated], ts.NodeFlags.Const)
|
|
4439
|
-
assigned.push(factory.updateVariableStatement(statement, statement.modifiers, list))
|
|
4440
|
-
index++
|
|
4441
|
-
} else {
|
|
4442
|
-
assigned.push(statement)
|
|
4443
|
-
}
|
|
4444
|
-
}
|
|
4445
|
-
|
|
4446
|
-
if (!assigned.length) return assigned
|
|
4447
|
-
const finalIf = returnConditional(assigned.at(-1), factory)
|
|
4448
|
-
if (finalIf) return [...assigned.slice(0, -1), factory.createReturnStatement(finalIf)]
|
|
4449
|
-
if (!ts.isReturnStatement(assigned.at(-1)) || !assigned.at(-1).expression) return assigned
|
|
4450
|
-
let expression = assigned.at(-1).expression
|
|
4451
|
-
let start = assigned.length - 1
|
|
4452
|
-
while (start > 0) {
|
|
4453
|
-
const previous = assigned[start - 1]
|
|
4454
|
-
if (!ts.isIfStatement(previous) || previous.elseStatement) break
|
|
4455
|
-
const truthy = returnOnlyExpression(previous.thenStatement)
|
|
4456
|
-
if (!truthy) break
|
|
4457
|
-
expression = factory.createConditionalExpression(previous.expression, factory.createToken(ts.SyntaxKind.QuestionToken), truthy, factory.createToken(ts.SyntaxKind.ColonToken), expression)
|
|
4458
|
-
start--
|
|
4459
|
-
}
|
|
4460
|
-
return start === assigned.length - 1 ? assigned : [...assigned.slice(0, start), factory.createReturnStatement(expression)]
|
|
4461
|
-
}
|
|
4462
|
-
|
|
4463
|
-
const visitNested = node => {
|
|
4464
|
-
if (ts.isBlock(node)) return factory.updateBlock(node, normalizeStatements([...node.statements]))
|
|
4465
|
-
if (isFunctionLike(node) && ts.isBlock(node.body)) {
|
|
4466
|
-
if (!isRenderFunction(node)) return node
|
|
4467
|
-
const body = factory.updateBlock(node.body, normalizeStatements([...node.body.statements]))
|
|
4468
|
-
if (ts.isFunctionDeclaration(node)) return factory.updateFunctionDeclaration(node, node.modifiers, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, body)
|
|
4469
|
-
if (ts.isFunctionExpression(node)) return factory.updateFunctionExpression(node, node.modifiers, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, body)
|
|
4470
|
-
if (ts.isArrowFunction(node)) return factory.updateArrowFunction(node, node.modifiers, node.typeParameters, node.parameters, node.type, node.equalsGreaterThanToken, body)
|
|
4471
|
-
}
|
|
4472
|
-
return ts.visitEachChild(node, visitNested, context)
|
|
4473
|
-
}
|
|
4474
|
-
|
|
4475
|
-
return ts.visitEachChild(sourceFile, visitNested, context)
|
|
4476
|
-
}
|
|
4477
|
-
|
|
4478
|
-
function isRenderFunction(node) {
|
|
4479
|
-
if (ts.isFunctionDeclaration(node)) return node.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword) || Boolean(node.name && /^[A-Z]/.test(node.name.text))
|
|
4480
|
-
const declaration = node.parent
|
|
4481
|
-
return ts.isVariableDeclaration(declaration) && ts.isIdentifier(declaration.name) && /^[A-Z]/.test(declaration.name.text)
|
|
4482
|
-
}
|
|
4483
|
-
|
|
4484
|
-
function singleUninitializedLet(statement) {
|
|
4485
|
-
if (!ts.isVariableStatement(statement) || (statement.declarationList.flags & ts.NodeFlags.Let) === 0 || statement.declarationList.declarations.length !== 1) return undefined
|
|
4486
|
-
const declaration = statement.declarationList.declarations[0]
|
|
4487
|
-
return ts.isIdentifier(declaration.name) && !declaration.initializer ? declaration : undefined
|
|
4488
|
-
}
|
|
4489
|
-
|
|
4490
|
-
function assignmentConditional(statement, name, factory) {
|
|
4491
|
-
if (!ts.isIfStatement(statement) || !statement.elseStatement) return undefined
|
|
4492
|
-
const truthy = assignmentOnlyExpression(statement.thenStatement, name)
|
|
4493
|
-
const falsy = ts.isIfStatement(statement.elseStatement)
|
|
4494
|
-
? assignmentConditional(statement.elseStatement, name, factory)
|
|
4495
|
-
: assignmentOnlyExpression(statement.elseStatement, name)
|
|
4496
|
-
if (!truthy || !falsy) return undefined
|
|
4497
|
-
return factory.createConditionalExpression(statement.expression, factory.createToken(ts.SyntaxKind.QuestionToken), truthy, factory.createToken(ts.SyntaxKind.ColonToken), falsy)
|
|
4498
|
-
}
|
|
4499
|
-
|
|
4500
|
-
function assignmentOnlyExpression(statement, name) {
|
|
4501
|
-
const candidate = ts.isBlock(statement) && statement.statements.length === 1 ? statement.statements[0] : statement
|
|
4502
|
-
if (!ts.isExpressionStatement(candidate) || !ts.isBinaryExpression(candidate.expression) || candidate.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken || !ts.isIdentifier(candidate.expression.left) || candidate.expression.left.text !== name) return undefined
|
|
4503
|
-
return candidate.expression.right
|
|
4504
|
-
}
|
|
4505
|
-
|
|
4506
|
-
function returnConditional(statement, factory) {
|
|
4507
|
-
if (!ts.isIfStatement(statement) || !statement.elseStatement) return undefined
|
|
4508
|
-
const truthy = returnOnlyExpression(statement.thenStatement)
|
|
4509
|
-
const falsy = ts.isIfStatement(statement.elseStatement)
|
|
4510
|
-
? returnConditional(statement.elseStatement, factory)
|
|
4511
|
-
: returnOnlyExpression(statement.elseStatement)
|
|
4512
|
-
if (!truthy || !falsy) return undefined
|
|
4513
|
-
return factory.createConditionalExpression(statement.expression, factory.createToken(ts.SyntaxKind.QuestionToken), truthy, factory.createToken(ts.SyntaxKind.ColonToken), falsy)
|
|
4514
|
-
}
|
|
4515
|
-
|
|
4516
|
-
function returnOnlyExpression(statement) {
|
|
4517
|
-
const candidate = ts.isBlock(statement) && statement.statements.length === 1 ? statement.statements[0] : statement
|
|
4518
|
-
return ts.isReturnStatement(candidate) && candidate.expression ? candidate.expression : undefined
|
|
2246
|
+
undefined,
|
|
2247
|
+
factory.createImportClause(false, undefined, factory.createNamedImports(behaviorImports)),
|
|
2248
|
+
factory.createStringLiteral("@kudzujs/core")
|
|
2249
|
+
)
|
|
2250
|
+
return factory.updateSourceFile(transformed, [behaviorImport, ...transformed.statements])
|
|
2251
|
+
}
|
|
4519
2252
|
}
|
|
4520
2253
|
|
|
4521
2254
|
function containsRenderControl(root, knownLocals) {
|
|
@@ -4534,7 +2267,9 @@ function keyedListParts(expression, setters, declarations, fail, aliases = new S
|
|
|
4534
2267
|
const value = unwrapExpression(expression)
|
|
4535
2268
|
const directFrom = isArrayFromCall(value) && value.arguments.length === 2 && containsJsx(value.arguments[1])
|
|
4536
2269
|
if (!directFrom && (!ts.isCallExpression(value) || value.arguments.length !== 1 || !ts.isPropertyAccessExpression(value.expression) || value.expression.name.text !== "map")) return undefined
|
|
4537
|
-
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
|
+
})
|
|
4538
2273
|
if (!collection?.state && !collection?.calculation) return undefined
|
|
4539
2274
|
if (directFrom) collection.selector.push(["from", undefined])
|
|
4540
2275
|
let callback = directFrom ? value.arguments[1] : value.arguments[0]
|
|
@@ -4544,7 +2279,7 @@ function keyedListParts(expression, setters, declarations, fail, aliases = new S
|
|
|
4544
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")
|
|
4545
2280
|
const declaration = root.statements[0].declarationList.declarations[0]
|
|
4546
2281
|
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) fail(declaration, "Computed child collections must initialize one const identifier")
|
|
4547
|
-
const computed =
|
|
2282
|
+
const computed = analyzeCollectionPipeline(declaration.initializer, { fail, importedCollectionTransforms })
|
|
4548
2283
|
if (!computed?.ownerField || computed.parentItem !== parameters.item) fail(declaration.initializer, `Computed child collections must start from ${parameters.item}.<field>`)
|
|
4549
2284
|
const returned = root.statements[1].expression
|
|
4550
2285
|
if (identifierReferenceCount(returned, declaration.name.text) !== 1) fail(declaration.name, `Computed child collection alias "${declaration.name.text}" must be used exactly once`)
|
|
@@ -4568,7 +2303,7 @@ function keyedListParts(expression, setters, declarations, fail, aliases = new S
|
|
|
4568
2303
|
function nestedKeyedListParts(expression, parentItem, fail) {
|
|
4569
2304
|
const value = unwrapExpression(expression)
|
|
4570
2305
|
if (!ts.isCallExpression(value) || value.arguments.length !== 1 || !ts.isPropertyAccessExpression(value.expression) || value.expression.name.text !== "map") return undefined
|
|
4571
|
-
let collection =
|
|
2306
|
+
let collection = analyzeCollectionPipeline(value.expression.expression, { fail })
|
|
4572
2307
|
if (!collection?.ownerField || collection.parentItem !== parentItem) return undefined
|
|
4573
2308
|
let callback = value.arguments[0]
|
|
4574
2309
|
const parameters = collectionParameters(callback, "Nested keyed list map", fail)
|
|
@@ -4600,175 +2335,13 @@ function conditionalKeyedMapRoot(callback, root, parameters, collection, fail, s
|
|
|
4600
2335
|
}
|
|
4601
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")
|
|
4602
2337
|
const selectorStates = new Set(collection.selectorStates)
|
|
4603
|
-
const selector = collectionExpression(condition, parameters, fail, stateNames, selectorStates)
|
|
2338
|
+
const selector = collectionExpression(condition, { parameters, fail, stateNames, selectorStates })
|
|
4604
2339
|
const normalized = factory.updateArrowFunction(callback, callback.modifiers, callback.typeParameters, callback.parameters, callback.type, callback.equalsGreaterThanToken, rendered)
|
|
4605
2340
|
ts.setParentRecursive(normalized, false)
|
|
4606
2341
|
normalized.parent = parent
|
|
4607
2342
|
return { callback: normalized, root: rendered, collection: { ...collection, selector: [...collection.selector, ["filter", selector]], selectorStates } }
|
|
4608
2343
|
}
|
|
4609
2344
|
|
|
4610
|
-
function renderedCollectionSource(expression, setters, declarations, fail, aliases, importedCollections = new Set(), stateNames = new Set(), importedCollectionTransforms = new Map(), factory = ts.factory, context, calculatedCollection, staticCollection) {
|
|
4611
|
-
const value = unwrapExpression(expression)
|
|
4612
|
-
if (ts.isIdentifier(value)) {
|
|
4613
|
-
if ([...setters.values()].includes(value.text)) {
|
|
4614
|
-
const localStatic = staticCollection?.(value.text)
|
|
4615
|
-
return { state: value, static: localStatic, localStatic, selector: [], selectorStates: new Set() }
|
|
4616
|
-
}
|
|
4617
|
-
if (importedCollections.has(value.text)) return { state: value, static: true, selector: [], selectorStates: new Set() }
|
|
4618
|
-
const entries = declarations?.get(value.text)
|
|
4619
|
-
if (!entries) return undefined
|
|
4620
|
-
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`)
|
|
4621
|
-
aliases.add(value.text)
|
|
4622
|
-
const source = renderedCollectionSource(entries[0].initializer, setters, declarations, fail, aliases, importedCollections, stateNames, importedCollectionTransforms, factory, context, calculatedCollection, staticCollection)
|
|
4623
|
-
aliases.delete(value.text)
|
|
4624
|
-
return source && { ...source, aliasDeclarations: [...(source.aliasDeclarations ?? []), entries[0].node], aliasUses: [...(source.aliasUses ?? []), value] }
|
|
4625
|
-
}
|
|
4626
|
-
if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression)) {
|
|
4627
|
-
const calculation = calculatedCollection?.(value)
|
|
4628
|
-
if (calculation) return { calculation, selector: [], selectorStates: new Set() }
|
|
4629
|
-
return { state: undefined, ownerField: value.name.text, selector: [], parentItem: value.expression.text }
|
|
4630
|
-
}
|
|
4631
|
-
if (ts.isCallExpression(value) && ts.isIdentifier(value.expression) && importedCollectionTransforms.has(value.expression.text)) {
|
|
4632
|
-
const transform = importedCollectionTransforms.get(value.expression.text)
|
|
4633
|
-
const parameter = transform.parameters[0]
|
|
4634
|
-
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`)
|
|
4635
|
-
const returned = ts.isBlock(transform.body)
|
|
4636
|
-
? transform.body.statements.length === 1 && ts.isReturnStatement(transform.body.statements[0]) ? transform.body.statements[0].expression : undefined
|
|
4637
|
-
: transform.body
|
|
4638
|
-
if (!returned) fail(value, `Imported collection transform "${value.expression.text}" must contain only one returned collection expression`)
|
|
4639
|
-
const transformSource = renderedCollectionSource(returned, new Map([[parameter.name.text, parameter.name.text]]), undefined, fail, new Set(), new Set(), new Set([parameter.name.text]))
|
|
4640
|
-
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`)
|
|
4641
|
-
const source = renderedCollectionSource(value.arguments[0], setters, declarations, fail, aliases, importedCollections, stateNames, importedCollectionTransforms, factory, context, calculatedCollection, staticCollection)
|
|
4642
|
-
if (!source) fail(value.arguments[0], `Imported collection transform "${value.expression.text}" requires a supported collection argument`)
|
|
4643
|
-
return { ...source, selector: [...source.selector, ...transformSource.selector] }
|
|
4644
|
-
}
|
|
4645
|
-
if (ts.isCallExpression(value) && ts.isPropertyAccessExpression(value.expression)) {
|
|
4646
|
-
const method = value.expression.name.text
|
|
4647
|
-
if (method === "filter") {
|
|
4648
|
-
if (value.arguments.length !== 1) fail(value, "Rendered collection filter() requires one inline predicate")
|
|
4649
|
-
const source = renderedCollectionSource(value.expression.expression, setters, declarations, fail, aliases, importedCollections, stateNames, importedCollectionTransforms, factory, context, calculatedCollection, staticCollection)
|
|
4650
|
-
if (!source) return undefined
|
|
4651
|
-
const parameters = collectionParameters(value.arguments[0], "Rendered collection filter()", fail)
|
|
4652
|
-
const selectorStates = new Set(source.selectorStates)
|
|
4653
|
-
return { ...source, selector: [...source.selector, ["filter", collectionExpression(unwrapExpression(value.arguments[0].body), parameters, fail, stateNames, selectorStates)]], selectorStates }
|
|
4654
|
-
}
|
|
4655
|
-
if (method === "flatMap") {
|
|
4656
|
-
if (value.arguments.length !== 1) fail(value, "Rendered collection flatMap() requires one inline projector")
|
|
4657
|
-
const source = renderedCollectionSource(value.expression.expression, setters, declarations, fail, aliases, importedCollections, stateNames, importedCollectionTransforms, factory, context, calculatedCollection, staticCollection)
|
|
4658
|
-
if (!source) return undefined
|
|
4659
|
-
const parameters = collectionParameters(value.arguments[0], "Rendered collection flatMap()", fail)
|
|
4660
|
-
const field = directProperty(value.arguments[0].body, parameters.item)
|
|
4661
|
-
if (!field) fail(value.arguments[0].body, `Rendered collection flatMap() projector must be ${parameters.item}.<field>`)
|
|
4662
|
-
if (["__proto__", "constructor", "prototype"].includes(field)) fail(value.arguments[0].body, `Rendered collection property "${field}" is not supported`)
|
|
4663
|
-
return { ...source, selector: [...source.selector, ["flatMap", field]] }
|
|
4664
|
-
}
|
|
4665
|
-
if (method === "slice") {
|
|
4666
|
-
if (value.arguments.length < 1 || value.arguments.length > 2) fail(value, "Rendered collection slice() requires a start and optional end")
|
|
4667
|
-
const source = renderedCollectionSource(value.expression.expression, setters, declarations, fail, aliases, importedCollections, stateNames, importedCollectionTransforms, factory, context, calculatedCollection, staticCollection)
|
|
4668
|
-
if (!source) return undefined
|
|
4669
|
-
const selectorStates = new Set(source.selectorStates)
|
|
4670
|
-
const start = collectionExpression(value.arguments[0], {}, fail, stateNames, selectorStates)
|
|
4671
|
-
const end = value.arguments[1] && collectionExpression(value.arguments[1], {}, fail, stateNames, selectorStates)
|
|
4672
|
-
return { ...source, selector: [...source.selector, ["slice", start, end]], selectorStates }
|
|
4673
|
-
}
|
|
4674
|
-
if (method === "toSorted") {
|
|
4675
|
-
if (value.arguments.length !== 1) fail(value, "Rendered collection toSorted() requires one inline comparator")
|
|
4676
|
-
const source = renderedCollectionSource(value.expression.expression, setters, declarations, fail, aliases, importedCollections, stateNames, importedCollectionTransforms, factory, context, calculatedCollection, staticCollection)
|
|
4677
|
-
if (!source) return undefined
|
|
4678
|
-
const comparator = value.arguments[0]
|
|
4679
|
-
const parameters = collectionParameters(comparator, "Rendered collection toSorted()", fail)
|
|
4680
|
-
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")
|
|
4681
|
-
const selectorStates = new Set(source.selectorStates)
|
|
4682
|
-
const expression = collectionExpression(comparator.body, parameters, fail, stateNames, selectorStates)
|
|
4683
|
-
return { ...source, selector: [...source.selector, ["sort", expression]], selectorStates }
|
|
4684
|
-
}
|
|
4685
|
-
if (method === "sort") fail(value, "Rendered collections cannot use mutating sort(); use toSorted()")
|
|
4686
|
-
}
|
|
4687
|
-
if (isArrayFromCall(value)) {
|
|
4688
|
-
if (value.arguments.length < 1 || value.arguments.length > 2) fail(value, "Rendered Array.from() requires an anchor and optional inline mapper")
|
|
4689
|
-
const source = renderedCollectionSource(value.arguments[0], setters, declarations, fail, aliases, importedCollections, stateNames, importedCollectionTransforms, factory, context, calculatedCollection, staticCollection)
|
|
4690
|
-
if (!source) return undefined
|
|
4691
|
-
let mapper
|
|
4692
|
-
if (value.arguments[1]) {
|
|
4693
|
-
const parameters = collectionParameters(value.arguments[1], "Rendered Array.from() mapper", fail)
|
|
4694
|
-
const selectorStates = new Set(source.selectorStates)
|
|
4695
|
-
mapper = collectionExpression(unwrapExpression(value.arguments[1].body), parameters, fail, stateNames, selectorStates)
|
|
4696
|
-
source.selectorStates = selectorStates
|
|
4697
|
-
}
|
|
4698
|
-
return { ...source, selector: [...source.selector, ["from", mapper]] }
|
|
4699
|
-
}
|
|
4700
|
-
}
|
|
4701
|
-
|
|
4702
|
-
function isArrayFromCall(value) {
|
|
4703
|
-
return ts.isCallExpression(value) && ts.isPropertyAccessExpression(value.expression) && ts.isIdentifier(value.expression.expression) && value.expression.expression.text === "Array" && value.expression.name.text === "from"
|
|
4704
|
-
}
|
|
4705
|
-
|
|
4706
|
-
function collectionParameters(callback, label, fail) {
|
|
4707
|
-
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`)
|
|
4708
|
-
return { item: callback.parameters[0].name.text, index: callback.parameters[1]?.name.text }
|
|
4709
|
-
}
|
|
4710
|
-
|
|
4711
|
-
function collectionExpression(expression, parameters, fail, stateNames = new Set(), selectorStates = new Set()) {
|
|
4712
|
-
const encode = node => {
|
|
4713
|
-
node = unwrapExpression(node)
|
|
4714
|
-
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isNumericLiteral(node)) return ["value", ts.isNumericLiteral(node) ? Number(node.text) : node.text]
|
|
4715
|
-
if (node.kind === ts.SyntaxKind.TrueKeyword) return ["value", true]
|
|
4716
|
-
if (node.kind === ts.SyntaxKind.FalseKeyword) return ["value", false]
|
|
4717
|
-
if (node.kind === ts.SyntaxKind.NullKeyword) return ["value", null]
|
|
4718
|
-
if (ts.isIdentifier(node)) {
|
|
4719
|
-
if (node.text === parameters.item) return ["item"]
|
|
4720
|
-
if (node.text === parameters.index) return ["index"]
|
|
4721
|
-
if (node.text === "undefined") return ["undefined"]
|
|
4722
|
-
if (stateNames.has(node.text)) {
|
|
4723
|
-
selectorStates.add(node.text)
|
|
4724
|
-
return ["state", node.text]
|
|
4725
|
-
}
|
|
4726
|
-
fail(node, `Rendered collection expression identifier "${node.text}" is not allowed`)
|
|
4727
|
-
}
|
|
4728
|
-
if (ts.isPropertyAccessExpression(node)) {
|
|
4729
|
-
if (["__proto__", "constructor", "prototype"].includes(node.name.text)) fail(node, `Rendered collection property "${node.name.text}" is not supported`)
|
|
4730
|
-
return ["get", encode(node.expression), node.name.text, Boolean(node.questionDotToken)]
|
|
4731
|
-
}
|
|
4732
|
-
if (ts.isElementAccessExpression(node)) {
|
|
4733
|
-
const key = node.argumentExpression
|
|
4734
|
-
if (!ts.isStringLiteral(key) && !ts.isNumericLiteral(key)) fail(node, "Rendered collection computed properties require a direct string or numeric literal key")
|
|
4735
|
-
if (ts.isStringLiteral(key) && ["__proto__", "constructor", "prototype"].includes(key.text)) fail(node, `Rendered collection property "${key.text}" is not supported`)
|
|
4736
|
-
return ["get", encode(node.expression), ts.isNumericLiteral(key) ? Number(key.text) : key.text, Boolean(node.questionDotToken)]
|
|
4737
|
-
}
|
|
4738
|
-
if (ts.isPrefixUnaryExpression(node)) {
|
|
4739
|
-
const operator = node.operator === ts.SyntaxKind.ExclamationToken ? "!" : node.operator === ts.SyntaxKind.PlusToken ? "+" : node.operator === ts.SyntaxKind.MinusToken ? "-" : undefined
|
|
4740
|
-
if (!operator) fail(node, "Rendered collection expression uses an unsupported unary operator")
|
|
4741
|
-
return ["unary", operator, encode(node.operand)]
|
|
4742
|
-
}
|
|
4743
|
-
if (ts.isTypeOfExpression(node)) return ["unary", "typeof", encode(node.expression)]
|
|
4744
|
-
if (ts.isBinaryExpression(node)) {
|
|
4745
|
-
const operator = node.operatorToken.getText()
|
|
4746
|
-
if (!new Set(["&&", "||", "??", "===", "!==", "==", "!=", "<", "<=", ">", ">=", "+", "-", "*", "/", "%"]).has(operator)) fail(node, `Rendered collection expression operator "${operator}" is not supported`)
|
|
4747
|
-
return ["binary", operator, encode(node.left), encode(node.right)]
|
|
4748
|
-
}
|
|
4749
|
-
if (ts.isConditionalExpression(node)) return ["conditional", encode(node.condition), encode(node.whenTrue), encode(node.whenFalse)]
|
|
4750
|
-
if (ts.isArrayLiteralExpression(node) && !node.elements.some(ts.isSpreadElement)) return ["array", ...node.elements.map(encode)]
|
|
4751
|
-
if (ts.isObjectLiteralExpression(node)) return ["object", ...node.properties.map(property => {
|
|
4752
|
-
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")
|
|
4753
|
-
return [property.name.text, encode(property.initializer)]
|
|
4754
|
-
})]
|
|
4755
|
-
if (ts.isTemplateExpression(node)) return ["template", [node.head.text, ...node.templateSpans.map(span => span.literal.text)], node.templateSpans.map(span => encode(span.expression))]
|
|
4756
|
-
if (ts.isCallExpression(node)) {
|
|
4757
|
-
if (ts.isIdentifier(node.expression) && ["Boolean", "Number", "String"].includes(node.expression.text)) return ["global", node.expression.text, ...node.arguments.map(encode)]
|
|
4758
|
-
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
4759
|
-
const method = node.expression.name.text
|
|
4760
|
-
if (ts.isIdentifier(node.expression.expression) && node.expression.expression.text === "Math" && pureMathMethods.has(method)) return ["math", method, ...node.arguments.map(encode)]
|
|
4761
|
-
if (pureListMethods.has(method)) return ["call", encode(node.expression.expression), method, ...node.arguments.map(encode)]
|
|
4762
|
-
if (mutatingListMethods.has(method)) fail(node, `Rendered collection expressions cannot call mutating method "${method}"`)
|
|
4763
|
-
}
|
|
4764
|
-
fail(node, "Rendered collection expressions cannot call arbitrary functions")
|
|
4765
|
-
}
|
|
4766
|
-
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")
|
|
4767
|
-
fail(node, "Rendered collection expression is not supported")
|
|
4768
|
-
}
|
|
4769
|
-
return encode(expression)
|
|
4770
|
-
}
|
|
4771
|
-
|
|
4772
2345
|
function jsonExpression(value, factory) {
|
|
4773
2346
|
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("JSON"), "parse"), undefined, [factory.createStringLiteral(JSON.stringify(value))])
|
|
4774
2347
|
}
|
|
@@ -4851,16 +2424,6 @@ function runtimeImportNames(sourceFile, relative) {
|
|
|
4851
2424
|
return names
|
|
4852
2425
|
}
|
|
4853
2426
|
|
|
4854
|
-
function referenceIdentifiers(root, name) {
|
|
4855
|
-
const references = []
|
|
4856
|
-
const visit = node => {
|
|
4857
|
-
if (ts.isIdentifier(node) && node.text === name && isReferenceIdentifier(node) && !isShadowedIdentifier(node, root)) references.push(node)
|
|
4858
|
-
ts.forEachChild(node, visit)
|
|
4859
|
-
}
|
|
4860
|
-
visit(root)
|
|
4861
|
-
return references
|
|
4862
|
-
}
|
|
4863
|
-
|
|
4864
2427
|
function insideJsxEventHandler(node, root) {
|
|
4865
2428
|
for (let current = node.parent; current && current !== root.parent; current = current.parent) {
|
|
4866
2429
|
if (ts.isJsxAttribute(current) && /^on[A-Z]/.test(current.name.text)) return true
|
|
@@ -5385,564 +2948,126 @@ function isJsxSyntaxIdentifier(node) {
|
|
|
5385
2948
|
const parent = node.parent
|
|
5386
2949
|
return (ts.isJsxOpeningElement(parent) || ts.isJsxClosingElement(parent) || ts.isJsxSelfClosingElement(parent)) && parent.tagName === node || ts.isJsxAttribute(parent) && parent.name === node
|
|
5387
2950
|
}
|
|
5388
|
-
|
|
5389
|
-
function
|
|
5390
|
-
return
|
|
5391
|
-
}
|
|
5392
|
-
|
|
5393
|
-
function isDestructuredParameter(identifier, fn) {
|
|
5394
|
-
return fn?.parameters.some(parameter => ts.isObjectBindingPattern(parameter.name) && parameter.name.elements.some(element => ts.isIdentifier(element.name) && element.name.text === identifier.text)) ?? false
|
|
5395
|
-
}
|
|
5396
|
-
|
|
5397
|
-
function isExportedDeclaration(node) {
|
|
5398
|
-
const statement = ts.isVariableDeclaration(node) ? node.parent?.parent : node
|
|
5399
|
-
return statement?.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword || modifier.kind === ts.SyntaxKind.DefaultKeyword) ?? false
|
|
5400
|
-
}
|
|
5401
|
-
|
|
5402
|
-
function jsxTagUses(root, name) {
|
|
5403
|
-
const uses = []
|
|
5404
|
-
const visit = node => {
|
|
5405
|
-
const tag = ts.isJsxElement(node) ? node.openingElement.tagName : ts.isJsxSelfClosingElement(node) ? node.tagName : undefined
|
|
5406
|
-
if (tag && ts.isIdentifier(tag) && tag.text === name) uses.push(node)
|
|
5407
|
-
ts.forEachChild(node, visit)
|
|
5408
|
-
}
|
|
5409
|
-
visit(root)
|
|
5410
|
-
return uses
|
|
5411
|
-
}
|
|
5412
|
-
|
|
5413
|
-
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"])
|
|
5414
|
-
const mutatingListMethods = new Set(["copyWithin", "fill", "pop", "push", "reverse", "shift", "sort", "splice", "unshift"])
|
|
5415
|
-
const pureMathMethods = new Set(["abs", "ceil", "floor", "max", "min", "pow", "round", "sign", "sqrt", "trunc"])
|
|
5416
|
-
const pureListGlobals = new Set(["Boolean", "Infinity", "Math", "NaN", "Number", "String", "undefined"])
|
|
5417
|
-
const assignmentOperators = new Set([
|
|
5418
|
-
ts.SyntaxKind.EqualsToken, ts.SyntaxKind.PlusEqualsToken, ts.SyntaxKind.MinusEqualsToken,
|
|
5419
|
-
ts.SyntaxKind.AsteriskEqualsToken, ts.SyntaxKind.AsteriskAsteriskEqualsToken, ts.SyntaxKind.SlashEqualsToken,
|
|
5420
|
-
ts.SyntaxKind.PercentEqualsToken, ts.SyntaxKind.LessThanLessThanEqualsToken, ts.SyntaxKind.GreaterThanGreaterThanEqualsToken,
|
|
5421
|
-
ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, ts.SyntaxKind.AmpersandEqualsToken, ts.SyntaxKind.BarEqualsToken,
|
|
5422
|
-
ts.SyntaxKind.CaretEqualsToken, ts.SyntaxKind.BarBarEqualsToken, ts.SyntaxKind.AmpersandAmpersandEqualsToken,
|
|
5423
|
-
ts.SyntaxKind.QuestionQuestionEqualsToken
|
|
5424
|
-
])
|
|
5425
|
-
|
|
5426
|
-
function validateListExpression(expression, item, source, fail, index, states = new Set()) {
|
|
5427
|
-
const visit = node => {
|
|
5428
|
-
if (ts.isTypeNode(node)) return
|
|
5429
|
-
if (ts.isElementAccessExpression(node) && referencesIdentifier(node.expression, item)) {
|
|
5430
|
-
const key = node.argumentExpression
|
|
5431
|
-
if (!ts.isStringLiteral(key) && !ts.isNumericLiteral(key)) fail(source, "Derived keyed list item computed properties require a direct string or numeric literal key")
|
|
5432
|
-
if (ts.isStringLiteral(key) && ["__proto__", "constructor", "prototype"].includes(key.text)) fail(source, `Derived keyed list item property "${key.text}" is not supported`)
|
|
5433
|
-
}
|
|
5434
|
-
if (ts.isPropertyAccessExpression(node) && ["__proto__", "constructor", "prototype"].includes(node.name.text) || ts.isElementAccessExpression(node) && ts.isStringLiteral(node.argumentExpression) && ["__proto__", "constructor", "prototype"].includes(node.argumentExpression.text)) {
|
|
5435
|
-
fail(source, "Derived keyed list item expressions cannot read __proto__, prototype, or constructor")
|
|
5436
|
-
}
|
|
5437
|
-
if (ts.isBinaryExpression(node) && assignmentOperators.has(node.operatorToken.kind) || ts.isPostfixUnaryExpression(node) || ts.isPrefixUnaryExpression(node) && [ts.SyntaxKind.PlusPlusToken, ts.SyntaxKind.MinusMinusToken].includes(node.operator)) {
|
|
5438
|
-
fail(source, "Derived keyed list item expressions must be pure; assignments and updates are not supported")
|
|
5439
|
-
}
|
|
5440
|
-
if (ts.isDeleteExpression(node) || ts.isAwaitExpression(node) || ts.isNewExpression(node) || ts.isYieldExpression(node)) {
|
|
5441
|
-
fail(source, "Derived keyed list item expressions must be synchronous and side-effect free; delete, await, yield, and new are not supported")
|
|
5442
|
-
}
|
|
5443
|
-
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isTaggedTemplateExpression(node)) {
|
|
5444
|
-
fail(source, "Derived keyed list item expressions cannot create or invoke arbitrary functions")
|
|
5445
|
-
}
|
|
5446
|
-
if (ts.isCallExpression(node)) {
|
|
5447
|
-
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
5448
|
-
const method = node.expression.name.text
|
|
5449
|
-
if (mutatingListMethods.has(method)) fail(source, `Derived keyed list item expressions cannot call mutating method "${method}"`)
|
|
5450
|
-
const receiver = node.expression.expression
|
|
5451
|
-
const mathCall = ts.isIdentifier(receiver) && receiver.text === "Math" && pureMathMethods.has(method)
|
|
5452
|
-
if (!mathCall && !pureListMethods.has(method)) fail(source, `Derived keyed list item expressions cannot call arbitrary method "${method}"`)
|
|
5453
|
-
} else if (!ts.isIdentifier(node.expression) || !["Boolean", "Number", "String"].includes(node.expression.text)) {
|
|
5454
|
-
fail(source, "Derived keyed list item expressions cannot call arbitrary functions")
|
|
5455
|
-
}
|
|
5456
|
-
}
|
|
5457
|
-
if (ts.isIdentifier(node) && isReferenceIdentifier(node) && !isJsxSyntaxIdentifier(node) && node.text !== item && node.text !== index && !states.has(node.text) && !pureListGlobals.has(node.text)) {
|
|
5458
|
-
fail(source, `Derived keyed list item expression identifier "${node.text}" is not allowed`)
|
|
5459
|
-
}
|
|
5460
|
-
ts.forEachChild(node, visit)
|
|
5461
|
-
}
|
|
5462
|
-
visit(expression)
|
|
5463
|
-
}
|
|
5464
|
-
|
|
5465
|
-
function containsJsx(root) {
|
|
5466
|
-
let found = false
|
|
5467
|
-
const visit = node => {
|
|
5468
|
-
if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node)) found = true
|
|
5469
|
-
if (!found) ts.forEachChild(node, visit)
|
|
5470
|
-
}
|
|
5471
|
-
visit(root)
|
|
5472
|
-
return found
|
|
5473
|
-
}
|
|
5474
|
-
|
|
5475
|
-
function compileListExpression(read, expression, item, factory, listExpressions, handlerUrl, index, states = new Set()) {
|
|
5476
|
-
const exportName = `listExpression${listExpressions.length}`
|
|
5477
|
-
listExpressions.push({ exportName, expression, item, index, states })
|
|
5478
|
-
const arguments_ = [read, factory.createStringLiteral(handlerUrl), factory.createStringLiteral(exportName)]
|
|
5479
|
-
if (states.size) arguments_.push(factory.createArrayLiteralExpression([...states].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)]))))
|
|
5480
|
-
return factory.createCallExpression(factory.createIdentifier("__kListExpression"), undefined, arguments_)
|
|
5481
|
-
}
|
|
5482
|
-
|
|
5483
|
-
function compileListConditional(entry, factory, listExpressions, handlerUrl) {
|
|
5484
|
-
const exportName = `listExpression${listExpressions.length}`
|
|
5485
|
-
listExpressions.push({ exportName, expression: entry.condition, item: entry.item, index: entry.index })
|
|
5486
|
-
const read = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), entry.condition)
|
|
5487
|
-
const thunk = branch => factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), branch)
|
|
5488
|
-
return factory.createCallExpression(factory.createIdentifier("__kListConditional"), undefined, [
|
|
5489
|
-
factory.createStringLiteral(entry.kind), read, thunk(entry.truthy), thunk(entry.falsy), factory.createStringLiteral(handlerUrl), factory.createStringLiteral(exportName)
|
|
5490
|
-
])
|
|
5491
|
-
}
|
|
5492
|
-
|
|
5493
|
-
function compileListValue(expression, entry, factory, context, listExpressions, handlerUrl) {
|
|
5494
|
-
const rewrite = node => {
|
|
5495
|
-
if (ts.isShorthandPropertyAssignment(node) && entry.states?.has(node.name.text)) return factory.createPropertyAssignment(node.name, factory.createPropertyAccessExpression(node.name, "value"))
|
|
5496
|
-
if (ts.isIdentifier(node) && entry.states?.has(node.text) && isReferenceIdentifier(node)) return factory.createPropertyAccessExpression(node, "value")
|
|
5497
|
-
return ts.visitEachChild(node, rewrite, context)
|
|
5498
|
-
}
|
|
5499
|
-
const initial = entry.states?.size ? ts.visitNode(expression, rewrite) : expression
|
|
5500
|
-
const read = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), initial)
|
|
5501
|
-
return entry.field
|
|
5502
|
-
? factory.createCallExpression(factory.createIdentifier("__kListField"), undefined, [read, factory.createStringLiteral(entry.field)])
|
|
5503
|
-
: compileListExpression(read, expression, entry.item, factory, listExpressions, handlerUrl, entry.index, entry.states)
|
|
5504
|
-
}
|
|
5505
|
-
|
|
5506
|
-
function directProperty(expression, objectName) {
|
|
5507
|
-
const value = unwrapExpression(expression)
|
|
5508
|
-
if (!ts.isPropertyAccessExpression(value) || !ts.isIdentifier(value.expression)) return undefined
|
|
5509
|
-
if (objectName !== undefined && value.expression.text !== objectName) return undefined
|
|
5510
|
-
return value.name.text
|
|
5511
|
-
}
|
|
5512
|
-
|
|
5513
|
-
function keyedListParentTag(node) {
|
|
5514
|
-
for (let current = node.parent; current; current = current.parent) {
|
|
5515
|
-
if (ts.isJsxElement(current)) return current.openingElement.tagName.getText().toLowerCase()
|
|
5516
|
-
}
|
|
5517
|
-
return undefined
|
|
5518
|
-
}
|
|
5519
|
-
|
|
5520
|
-
function referencesIdentifier(root, name) {
|
|
5521
|
-
let found = false
|
|
5522
|
-
const visit = node => {
|
|
5523
|
-
if (ts.isIdentifier(node) && node.text === name && isReferenceIdentifier(node)) found = true
|
|
5524
|
-
if (!found) ts.forEachChild(node, visit)
|
|
5525
|
-
}
|
|
5526
|
-
visit(root)
|
|
5527
|
-
return found
|
|
5528
|
-
}
|
|
5529
|
-
|
|
5530
|
-
function identifierReferenceCount(root, name) {
|
|
5531
|
-
return identifierReferences(root, name).length
|
|
5532
|
-
}
|
|
5533
|
-
|
|
5534
|
-
function identifierReferences(root, name) {
|
|
5535
|
-
const references = []
|
|
5536
|
-
const visit = node => {
|
|
5537
|
-
if (ts.isIdentifier(node) && node.text === name && isReferenceIdentifier(node) && !ts.isJsxClosingElement(node.parent)) references.push(node)
|
|
5538
|
-
ts.forEachChild(node, visit)
|
|
5539
|
-
}
|
|
5540
|
-
visit(root)
|
|
5541
|
-
return references
|
|
5542
|
-
}
|
|
5543
|
-
|
|
5544
|
-
function unwrapExpression(node) {
|
|
5545
|
-
return ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isTypeAssertionExpression(node) || ts.isNonNullExpression(node) || ts.isSatisfiesExpression(node) ? unwrapExpression(node.expression) : node
|
|
5546
|
-
}
|
|
5547
|
-
|
|
5548
|
-
function isLocalConst(node) {
|
|
5549
|
-
const list = node.parent
|
|
5550
|
-
const statement = list?.parent
|
|
5551
|
-
return ts.isVariableDeclarationList(list) && (list.flags & ts.NodeFlags.Const) !== 0 && ts.isVariableStatement(statement)
|
|
5552
|
-
}
|
|
5553
|
-
|
|
5554
|
-
function isJsxLocalValue(expression, known) {
|
|
5555
|
-
const value = unwrapExpression(expression)
|
|
5556
|
-
if (ts.isJsxElement(value) || ts.isJsxSelfClosingElement(value) || ts.isJsxFragment(value)) return true
|
|
5557
|
-
if (ts.isIdentifier(value)) return known.has(value.text)
|
|
5558
|
-
const parts = conditionalParts(value)
|
|
5559
|
-
return Boolean(parts && (isJsxLocalValue(parts.truthy, known) || isJsxLocalValue(parts.falsy, known)))
|
|
5560
|
-
}
|
|
5561
|
-
|
|
5562
|
-
function compileReactiveBinding(expression, setters, factory, context, reactiveBindings, handlerUrl, importBindings = new Map(), clientImports = new Set()) {
|
|
5563
|
-
const parts = conditionalParts(expression)
|
|
5564
|
-
const state = parts && directStateIdentifier(parts.condition, setters)
|
|
5565
|
-
if (state && isPrimitiveDefaultLiteral(parts.truthy) && isPrimitiveDefaultLiteral(parts.falsy)) {
|
|
5566
|
-
return factory.createCallExpression(factory.createIdentifier("__kSelect"), undefined, [state, parts.truthy, parts.falsy])
|
|
5567
|
-
}
|
|
5568
|
-
return factory.createCallExpression(factory.createIdentifier("__kBinding"), undefined, compileReactiveExpression(expression, setters, factory, context, reactiveBindings, handlerUrl, importBindings, clientImports))
|
|
5569
|
-
}
|
|
5570
|
-
|
|
5571
|
-
function compileConditional(kind, expression, truthy, falsy, setters, factory, context, reactiveBindings, handlerUrl) {
|
|
5572
|
-
const state = directStateIdentifier(expression, setters)
|
|
5573
|
-
const thunk = branch => factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), branch)
|
|
5574
|
-
if (state) return factory.createCallExpression(factory.createIdentifier("__kStateConditional"), undefined, [factory.createStringLiteral(kind), state, thunk(truthy), thunk(falsy)])
|
|
5575
|
-
const [initial, ...descriptor] = compileReactiveExpression(expression, setters, factory, context, reactiveBindings, handlerUrl)
|
|
5576
|
-
return factory.createCallExpression(factory.createIdentifier("__kConditional"), undefined, [factory.createStringLiteral(kind), initial, thunk(truthy), thunk(falsy), ...descriptor])
|
|
5577
|
-
}
|
|
5578
|
-
|
|
5579
|
-
function directStateIdentifier(expression, setters) {
|
|
5580
|
-
const value = unwrapExpression(expression)
|
|
5581
|
-
return ts.isIdentifier(value) && new Set(setters.values()).has(value.text) ? value : undefined
|
|
5582
|
-
}
|
|
5583
|
-
|
|
5584
|
-
function compileReactiveExpression(expression, setters, factory, context, reactiveBindings, handlerUrl, importBindings = new Map(), clientImports = new Set()) {
|
|
5585
|
-
const usedStates = referencedStateNames(expression, setters)
|
|
5586
|
-
const importedNames = referencedImportedBindings(expression, importBindings)
|
|
5587
|
-
const imports = [...importedNames].map(name => importBindings.get(name))
|
|
5588
|
-
for (const entry of imports) if (!entry.package) clientImports.add(entry.target)
|
|
5589
|
-
const captures = new Set([...captureNames(expression, expression, setters)].filter(name => !importedNames.has(name)))
|
|
5590
|
-
const exportName = `binding${reactiveBindings.length}`
|
|
5591
|
-
reactiveBindings.push({ exportName, expression, captures, states: usedStates, imports })
|
|
5592
|
-
const states = [...usedStates].map(name => factory.createArrayLiteralExpression([
|
|
5593
|
-
factory.createStringLiteral(name),
|
|
5594
|
-
factory.createIdentifier(name)
|
|
5595
|
-
]))
|
|
5596
|
-
const scope = [...captures].map(name => factory.createArrayLiteralExpression([
|
|
5597
|
-
factory.createStringLiteral(name),
|
|
5598
|
-
factory.createIdentifier(name)
|
|
5599
|
-
]))
|
|
5600
|
-
const stateNames = new Set(usedStates)
|
|
5601
|
-
const rewriteInitial = node => {
|
|
5602
|
-
if (ts.isShorthandPropertyAssignment(node) && stateNames.has(node.name.text)) {
|
|
5603
|
-
return factory.createPropertyAssignment(node.name, factory.createPropertyAccessExpression(node.name, "value"))
|
|
5604
|
-
}
|
|
5605
|
-
if (ts.isIdentifier(node) && stateNames.has(node.text) && isReferenceIdentifier(node) && !isShadowedByParameter(node, expression)) {
|
|
5606
|
-
return factory.createPropertyAccessExpression(node, "value")
|
|
5607
|
-
}
|
|
5608
|
-
if (ts.isShorthandPropertyAssignment(node) && captures.has(node.name.text)) {
|
|
5609
|
-
return factory.createPropertyAssignment(node.name, factory.createCallExpression(factory.createIdentifier("__kBindingValue"), undefined, [node.name]))
|
|
5610
|
-
}
|
|
5611
|
-
if (ts.isIdentifier(node) && captures.has(node.text) && isReferenceIdentifier(node)) {
|
|
5612
|
-
return factory.createCallExpression(factory.createIdentifier("__kBindingValue"), undefined, [node])
|
|
5613
|
-
}
|
|
5614
|
-
return ts.visitEachChild(node, rewriteInitial, context)
|
|
5615
|
-
}
|
|
5616
|
-
return [
|
|
5617
|
-
ts.visitNode(expression, rewriteInitial),
|
|
5618
|
-
factory.createStringLiteral(handlerUrl),
|
|
5619
|
-
factory.createStringLiteral(exportName),
|
|
5620
|
-
factory.createArrayLiteralExpression(states),
|
|
5621
|
-
factory.createArrayLiteralExpression(scope)
|
|
5622
|
-
]
|
|
5623
|
-
}
|
|
5624
|
-
|
|
5625
|
-
function conditionalParts(expression) {
|
|
5626
|
-
const unwrap = node => ts.isParenthesizedExpression(node) ? unwrap(node.expression) : node
|
|
5627
|
-
const value = unwrap(expression)
|
|
5628
|
-
if (ts.isBinaryExpression(value) && value.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
|
|
5629
|
-
return { kind: "and", condition: value.left, truthy: unwrap(value.right), falsy: factoryNull() }
|
|
5630
|
-
}
|
|
5631
|
-
if (ts.isConditionalExpression(value)) {
|
|
5632
|
-
return { kind: "ternary", condition: value.condition, truthy: unwrap(value.whenTrue), falsy: unwrap(value.whenFalse) }
|
|
5633
|
-
}
|
|
5634
|
-
return undefined
|
|
5635
|
-
}
|
|
5636
|
-
|
|
5637
|
-
function factoryNull() {
|
|
5638
|
-
return ts.factory.createNull()
|
|
5639
|
-
}
|
|
5640
|
-
|
|
5641
|
-
function compileEvent(expression, setters, reducers, functions, factory, nativeHandlers, handlerUrl, listItem, importBindings, clientImports) {
|
|
5642
|
-
if (ts.isIdentifier(expression)) expression = functions.get(expression.text)
|
|
5643
|
-
if (!expression || (!ts.isArrowFunction(expression) && !ts.isFunctionExpression(expression) && !ts.isFunctionDeclaration(expression))) return undefined
|
|
5644
|
-
|
|
5645
|
-
const optimized = referencedReducerDispatches(expression.body, reducers, expression).size ? undefined : compileOptimizedEvent(expression, setters, factory)
|
|
5646
|
-
if (optimized) return optimized
|
|
5647
|
-
|
|
5648
|
-
rejectWorkerConstructions(expression, expression.getSourceFile(), "Relative TypeScript Worker construction is only supported directly inside an inline useEffect() callback")
|
|
5649
|
-
const descriptor = compileNativeCallback(expression, setters, reducers, factory, nativeHandlers, importBindings, clientImports, "handler", listItem)
|
|
5650
|
-
return factory.createCallExpression(factory.createIdentifier("__kNativeBehavior"), undefined, [
|
|
5651
|
-
factory.createStringLiteral(handlerUrl),
|
|
5652
|
-
factory.createStringLiteral(descriptor.exportName),
|
|
5653
|
-
descriptor.states,
|
|
5654
|
-
descriptor.scope
|
|
5655
|
-
])
|
|
5656
|
-
}
|
|
5657
|
-
|
|
5658
|
-
function compileNativeCallback(expression, setters, reducers, factory, entries, importBindings, clientImports, prefix, listItem, deferValues = false, snapshotNested = false, liveStates = new Set()) {
|
|
5659
|
-
const allCaptures = nativeCaptureNames(expression, setters)
|
|
5660
|
-
const usedReducers = referencedReducerDispatches(expression.body, reducers, expression)
|
|
5661
|
-
const imports = [...referencedImportedBindings(expression, importBindings)].map(name => importBindings.get(name))
|
|
5662
|
-
imports.push(...[...usedReducers].map(name => reducers.get(name).import).filter(Boolean))
|
|
5663
|
-
const captures = new Set([...allCaptures].filter(name => !importBindings.has(name) && !usedReducers.has(name)))
|
|
5664
|
-
for (const entry of imports) if (!entry.package) clientImports.add(entry.target)
|
|
5665
|
-
const usedStates = nativeStateNames(expression, setters)
|
|
5666
|
-
for (const name of usedReducers) {
|
|
5667
|
-
const reducer = reducers.get(name)
|
|
5668
|
-
if (reducer.contextAction) for (const state of referencedStateNames(reducer.contextAction.body, reducer.states, reducer.contextAction)) usedStates.add(state)
|
|
5669
|
-
}
|
|
5670
|
-
const exportName = `${prefix}${entries.length}`
|
|
5671
|
-
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 })
|
|
5672
|
-
const value = name => deferValues
|
|
5673
|
-
? factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createIdentifier(name))
|
|
5674
|
-
: factory.createIdentifier(name)
|
|
5675
|
-
return {
|
|
5676
|
-
exportName,
|
|
5677
|
-
states: factory.createArrayLiteralExpression([...usedStates].map(name => factory.createArrayLiteralExpression([
|
|
5678
|
-
factory.createStringLiteral(name),
|
|
5679
|
-
value(name)
|
|
5680
|
-
]))),
|
|
5681
|
-
scope: factory.createArrayLiteralExpression([...captures].map(name => factory.createArrayLiteralExpression([
|
|
5682
|
-
factory.createStringLiteral(name),
|
|
5683
|
-
name === (typeof listItem === "string" ? listItem : listItem?.item)
|
|
5684
|
-
? factory.createCallExpression(factory.createIdentifier("__kListItem"), undefined, [])
|
|
5685
|
-
: name === listItem?.index
|
|
5686
|
-
? factory.createCallExpression(factory.createIdentifier("__kListIndex"), undefined, [])
|
|
5687
|
-
: value(name)
|
|
5688
|
-
])))
|
|
5689
|
-
}
|
|
5690
|
-
}
|
|
5691
|
-
|
|
5692
|
-
function referencedReducerDispatches(root, reducers, scopeRoot = root) {
|
|
5693
|
-
const used = new Set()
|
|
5694
|
-
const visit = node => {
|
|
5695
|
-
if (ts.isIdentifier(node) && reducers.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, scopeRoot)) used.add(node.text)
|
|
5696
|
-
ts.forEachChild(node, visit)
|
|
5697
|
-
}
|
|
5698
|
-
visit(root)
|
|
5699
|
-
return used
|
|
5700
|
-
}
|
|
5701
|
-
|
|
5702
|
-
function nativeStateNames(expression, setters) {
|
|
5703
|
-
return referencedStateNames(expression.body, setters, expression)
|
|
5704
|
-
}
|
|
5705
|
-
|
|
5706
|
-
function referencedStateNames(root, setters, scopeRoot = root) {
|
|
5707
|
-
const stateNames = new Set(setters.values())
|
|
5708
|
-
const used = new Set()
|
|
5709
|
-
const visit = node => {
|
|
5710
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && setters.has(node.expression.text) && !isShadowedIdentifier(node.expression, scopeRoot)) used.add(setters.get(node.expression.text))
|
|
5711
|
-
if (ts.isIdentifier(node) && setters.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, scopeRoot)) used.add(setters.get(node.text))
|
|
5712
|
-
if (ts.isIdentifier(node) && stateNames.has(node.text) && isReferenceIdentifier(node) && !isShadowedByParameter(node, scopeRoot)) used.add(node.text)
|
|
5713
|
-
ts.forEachChild(node, visit)
|
|
5714
|
-
}
|
|
5715
|
-
visit(root)
|
|
5716
|
-
return used
|
|
5717
|
-
}
|
|
5718
|
-
|
|
5719
|
-
function compileOptimizedEvent(expression, setters, factory) {
|
|
5720
|
-
const statements = ts.isBlock(expression.body) ? expression.body.statements : [factory.createExpressionStatement(expression.body)]
|
|
5721
|
-
const commands = statements.map(statement => {
|
|
5722
|
-
if (!ts.isExpressionStatement(statement)) return undefined
|
|
5723
|
-
return compileEventCommand(statement.expression, setters, factory)
|
|
5724
|
-
})
|
|
5725
|
-
if (!commands.length || commands.some(command => !command)) return undefined
|
|
5726
|
-
|
|
5727
|
-
return factory.createCallExpression(factory.createIdentifier("__kBehavior"), undefined, [factory.createArrayLiteralExpression(commands)])
|
|
5728
|
-
}
|
|
5729
|
-
|
|
5730
|
-
const nativeGlobals = new Set([
|
|
5731
|
-
"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"
|
|
5732
|
-
])
|
|
5733
|
-
|
|
5734
|
-
function rewriteEffectWorkers(callback, file, sourceFile, sourceFiles, workerReferences, factory, context) {
|
|
5735
|
-
const visit = node => {
|
|
5736
|
-
const candidate = relativeWorkerCandidate(node, sourceFile)
|
|
5737
|
-
if (candidate) {
|
|
5738
|
-
if (nearestFunction(node) !== callback) throw sourceNodeError(node, sourceFile, "Relative TypeScript Worker construction must be directly inside the inline useEffect() callback, not a nested function")
|
|
5739
|
-
const { worker, url, specifier, options } = validateWorkerCandidate(candidate, sourceFile)
|
|
5740
|
-
const target = resolve(dirname(file), specifier)
|
|
5741
|
-
const sourceRelative = relative(sourceDirectory, target)
|
|
5742
|
-
if (sourceRelative.startsWith(`..${sep}`) || sourceRelative === ".." || resolve(sourceDirectory, sourceRelative) !== target) throw sourceNodeError(url.arguments[0], sourceFile, "Relative TypeScript Worker source must remain under src/")
|
|
5743
|
-
if (!sourceFiles.has(target)) throw sourceNodeError(url.arguments[0], sourceFile, `Relative TypeScript Worker ${JSON.stringify(specifier)} must resolve to an existing .worker.ts file under src/`)
|
|
5744
|
-
const identity = `${sourceRelative.replaceAll(sep, "/")}:${ts.getOriginalNode(node).getStart(sourceFile)}`
|
|
5745
|
-
const placeholder = `/__kudzu_worker_${createHash("sha256").update(identity).digest("hex").slice(0, 16)}__.js`
|
|
5746
|
-
workerReferences.push({ root: target, placeholder })
|
|
5747
|
-
return factory.updateNewExpression(worker, worker.expression, worker.typeArguments, [factory.createStringLiteral(placeholder), options])
|
|
5748
|
-
}
|
|
5749
|
-
return ts.visitEachChild(node, visit, context)
|
|
5750
|
-
}
|
|
5751
|
-
return ts.visitEachChild(callback, visit, context)
|
|
5752
|
-
}
|
|
5753
|
-
|
|
5754
|
-
function rejectWorkerConstructions(expression, sourceFile, message) {
|
|
5755
|
-
const visit = node => {
|
|
5756
|
-
if (relativeWorkerCandidate(node, sourceFile)) throw sourceNodeError(node, sourceFile, message)
|
|
5757
|
-
ts.forEachChild(node, visit)
|
|
5758
|
-
}
|
|
5759
|
-
visit(expression.body ?? expression)
|
|
5760
|
-
}
|
|
5761
|
-
|
|
5762
|
-
function relativeWorkerCandidate(node, sourceFile) {
|
|
5763
|
-
if (!ts.isNewExpression(node) || !ts.isIdentifier(node.expression) || node.expression.text !== "Worker") return undefined
|
|
5764
|
-
const first = node.arguments?.[0]
|
|
5765
|
-
if (!first || !ts.isNewExpression(first) || !ts.isIdentifier(first.expression) || first.expression.text !== "URL") return undefined
|
|
5766
|
-
const specifier = first.arguments?.[0]
|
|
5767
|
-
const base = first.arguments?.[1]
|
|
5768
|
-
const relativeLiteral = ts.isStringLiteral(specifier) && (specifier.text.startsWith("./") || specifier.text.startsWith("../"))
|
|
5769
|
-
if (!relativeLiteral && !(specifier && !ts.isStringLiteral(specifier) && base && isImportMetaUrl(base))) return undefined
|
|
5770
|
-
return { worker: node, url: first, sourceFile }
|
|
5771
|
-
}
|
|
5772
|
-
|
|
5773
|
-
function validateWorkerCandidate(candidate, sourceFile) {
|
|
5774
|
-
const { worker, url } = candidate
|
|
5775
|
-
if (!isUnshadowedGlobal(worker.expression, sourceFile)) throw sourceNodeError(worker.expression, sourceFile, "Relative TypeScript Workers require the unshadowed global Worker constructor")
|
|
5776
|
-
if (!isUnshadowedGlobal(url.expression, sourceFile)) throw sourceNodeError(url.expression, sourceFile, "Relative TypeScript Workers require the unshadowed global URL constructor")
|
|
5777
|
-
if (url.arguments?.length !== 2 || !isImportMetaUrl(url.arguments[1])) throw sourceNodeError(url, sourceFile, "Relative TypeScript Workers require new URL(relativeLiteral, import.meta.url)")
|
|
5778
|
-
const specifierNode = url.arguments[0]
|
|
5779
|
-
if (!ts.isStringLiteral(specifierNode) || !(specifierNode.text.startsWith("./") || specifierNode.text.startsWith("../"))) throw sourceNodeError(specifierNode, sourceFile, "Relative TypeScript Worker paths must be relative string literals")
|
|
5780
|
-
if (/[\\?#]/.test(specifierNode.text) || !specifierNode.text.endsWith(".worker.ts")) throw sourceNodeError(specifierNode, sourceFile, "Relative TypeScript Worker paths must end in .worker.ts")
|
|
5781
|
-
if (worker.arguments?.length !== 2) throw sourceNodeError(worker, sourceFile, 'Relative TypeScript Workers require exactly { type: "module" } as the second argument')
|
|
5782
|
-
const options = worker.arguments[1]
|
|
5783
|
-
if (!ts.isObjectLiteralExpression(options) || options.properties.length !== 1) throw sourceNodeError(options, sourceFile, 'Relative TypeScript Workers require exactly { type: "module" } as the second argument')
|
|
5784
|
-
const property = options.properties[0]
|
|
5785
|
-
const name = ts.isPropertyAssignment(property) && !ts.isComputedPropertyName(property.name) && (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) ? property.name.text : undefined
|
|
5786
|
-
if (name !== "type" || !ts.isStringLiteral(property.initializer) || property.initializer.text !== "module") throw sourceNodeError(property, sourceFile, 'Relative TypeScript Workers require exactly { type: "module" } as the second argument')
|
|
5787
|
-
return { worker, url, specifier: specifierNode.text, options }
|
|
5788
|
-
}
|
|
5789
|
-
|
|
5790
|
-
function isImportMetaUrl(node) {
|
|
5791
|
-
return ts.isPropertyAccessExpression(node) && node.name.text === "url" && ts.isMetaProperty(node.expression) && node.expression.keywordToken === ts.SyntaxKind.ImportKeyword && node.expression.name.text === "meta"
|
|
5792
|
-
}
|
|
5793
|
-
|
|
5794
|
-
function isUnshadowedGlobal(identifier, sourceFile) {
|
|
5795
|
-
if (isShadowedIdentifier(identifier, sourceFile)) return false
|
|
5796
|
-
return !sourceFile.statements.some(statement => {
|
|
5797
|
-
if (statementDeclaresName(statement, identifier.text)) return true
|
|
5798
|
-
if (!ts.isImportDeclaration(statement) || !statement.importClause) return false
|
|
5799
|
-
const clause = statement.importClause
|
|
5800
|
-
if (clause.name?.text === identifier.text) return true
|
|
5801
|
-
if (clause.namedBindings && ts.isNamespaceImport(clause.namedBindings)) return clause.namedBindings.name.text === identifier.text
|
|
5802
|
-
return clause.namedBindings && ts.isNamedImports(clause.namedBindings) ? clause.namedBindings.elements.some(entry => entry.name.text === identifier.text) : false
|
|
5803
|
-
})
|
|
2951
|
+
|
|
2952
|
+
function isDestructuredParameter(identifier, fn) {
|
|
2953
|
+
return fn?.parameters.some(parameter => ts.isObjectBindingPattern(parameter.name) && parameter.name.elements.some(element => ts.isIdentifier(element.name) && element.name.text === identifier.text)) ?? false
|
|
5804
2954
|
}
|
|
5805
2955
|
|
|
5806
|
-
function
|
|
5807
|
-
|
|
2956
|
+
function isExportedDeclaration(node) {
|
|
2957
|
+
const statement = ts.isVariableDeclaration(node) ? node.parent?.parent : node
|
|
2958
|
+
return statement?.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword || modifier.kind === ts.SyntaxKind.DefaultKeyword) ?? false
|
|
5808
2959
|
}
|
|
5809
2960
|
|
|
5810
|
-
function
|
|
5811
|
-
const
|
|
2961
|
+
function jsxTagUses(root, name) {
|
|
2962
|
+
const uses = []
|
|
5812
2963
|
const visit = node => {
|
|
5813
|
-
|
|
2964
|
+
const tag = ts.isJsxElement(node) ? node.openingElement.tagName : ts.isJsxSelfClosingElement(node) ? node.tagName : undefined
|
|
2965
|
+
if (tag && ts.isIdentifier(tag) && tag.text === name) uses.push(node)
|
|
5814
2966
|
ts.forEachChild(node, visit)
|
|
5815
2967
|
}
|
|
5816
|
-
visit(
|
|
5817
|
-
return
|
|
2968
|
+
visit(root)
|
|
2969
|
+
return uses
|
|
5818
2970
|
}
|
|
5819
2971
|
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
const stateNames = new Set(setters.values())
|
|
5832
|
-
const captures = new Set()
|
|
2972
|
+
const pureListGlobals = new Set(["Boolean", "Infinity", "Math", "NaN", "Number", "String", "undefined"])
|
|
2973
|
+
const assignmentOperators = new Set([
|
|
2974
|
+
ts.SyntaxKind.EqualsToken, ts.SyntaxKind.PlusEqualsToken, ts.SyntaxKind.MinusEqualsToken,
|
|
2975
|
+
ts.SyntaxKind.AsteriskEqualsToken, ts.SyntaxKind.AsteriskAsteriskEqualsToken, ts.SyntaxKind.SlashEqualsToken,
|
|
2976
|
+
ts.SyntaxKind.PercentEqualsToken, ts.SyntaxKind.LessThanLessThanEqualsToken, ts.SyntaxKind.GreaterThanGreaterThanEqualsToken,
|
|
2977
|
+
ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, ts.SyntaxKind.AmpersandEqualsToken, ts.SyntaxKind.BarEqualsToken,
|
|
2978
|
+
ts.SyntaxKind.CaretEqualsToken, ts.SyntaxKind.BarBarEqualsToken, ts.SyntaxKind.AmpersandAmpersandEqualsToken,
|
|
2979
|
+
ts.SyntaxKind.QuestionQuestionEqualsToken
|
|
2980
|
+
])
|
|
2981
|
+
|
|
2982
|
+
function validateListExpression(expression, item, source, fail, index, states = new Set()) {
|
|
5833
2983
|
const visit = node => {
|
|
5834
2984
|
if (ts.isTypeNode(node)) return
|
|
5835
|
-
if (ts.
|
|
5836
|
-
const
|
|
5837
|
-
if (
|
|
2985
|
+
if (ts.isElementAccessExpression(node) && referencesIdentifier(node.expression, item)) {
|
|
2986
|
+
const key = node.argumentExpression
|
|
2987
|
+
if (!ts.isStringLiteral(key) && !ts.isNumericLiteral(key)) fail(source, "Derived keyed list item computed properties require a direct string or numeric literal key")
|
|
2988
|
+
if (ts.isStringLiteral(key) && ["__proto__", "constructor", "prototype"].includes(key.text)) fail(source, `Derived keyed list item property "${key.text}" is not supported`)
|
|
2989
|
+
}
|
|
2990
|
+
if (ts.isPropertyAccessExpression(node) && ["__proto__", "constructor", "prototype"].includes(node.name.text) || ts.isElementAccessExpression(node) && ts.isStringLiteral(node.argumentExpression) && ["__proto__", "constructor", "prototype"].includes(node.argumentExpression.text)) {
|
|
2991
|
+
fail(source, "Derived keyed list item expressions cannot read __proto__, prototype, or constructor")
|
|
2992
|
+
}
|
|
2993
|
+
if (ts.isBinaryExpression(node) && assignmentOperators.has(node.operatorToken.kind) || ts.isPostfixUnaryExpression(node) || ts.isPrefixUnaryExpression(node) && [ts.SyntaxKind.PlusPlusToken, ts.SyntaxKind.MinusMinusToken].includes(node.operator)) {
|
|
2994
|
+
fail(source, "Derived keyed list item expressions must be pure; assignments and updates are not supported")
|
|
2995
|
+
}
|
|
2996
|
+
if (ts.isDeleteExpression(node) || ts.isAwaitExpression(node) || ts.isNewExpression(node) || ts.isYieldExpression(node)) {
|
|
2997
|
+
fail(source, "Derived keyed list item expressions must be synchronous and side-effect free; delete, await, yield, and new are not supported")
|
|
2998
|
+
}
|
|
2999
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isTaggedTemplateExpression(node)) {
|
|
3000
|
+
fail(source, "Derived keyed list item expressions cannot create or invoke arbitrary functions")
|
|
3001
|
+
}
|
|
3002
|
+
if (ts.isCallExpression(node)) {
|
|
3003
|
+
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
3004
|
+
const method = node.expression.name.text
|
|
3005
|
+
if (mutatingListMethods.has(method)) fail(source, `Derived keyed list item expressions cannot call mutating method "${method}"`)
|
|
3006
|
+
const receiver = node.expression.expression
|
|
3007
|
+
const mathCall = ts.isIdentifier(receiver) && receiver.text === "Math" && pureMathMethods.has(method)
|
|
3008
|
+
if (!mathCall && !pureListMethods.has(method)) fail(source, `Derived keyed list item expressions cannot call arbitrary method "${method}"`)
|
|
3009
|
+
} else if (!ts.isIdentifier(node.expression) || !["Boolean", "Number", "String"].includes(node.expression.text)) {
|
|
3010
|
+
fail(source, "Derived keyed list item expressions cannot call arbitrary functions")
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
if (ts.isIdentifier(node) && isReferenceIdentifier(node) && !isJsxSyntaxIdentifier(node) && node.text !== item && node.text !== index && !states.has(node.text) && !pureListGlobals.has(node.text)) {
|
|
3014
|
+
fail(source, `Derived keyed list item expression identifier "${node.text}" is not allowed`)
|
|
5838
3015
|
}
|
|
5839
3016
|
ts.forEachChild(node, visit)
|
|
5840
3017
|
}
|
|
5841
|
-
visit(
|
|
5842
|
-
return captures
|
|
3018
|
+
visit(expression)
|
|
5843
3019
|
}
|
|
5844
3020
|
|
|
5845
|
-
function
|
|
5846
|
-
|
|
5847
|
-
|
|
3021
|
+
function directProperty(expression, objectName) {
|
|
3022
|
+
const value = unwrapExpression(expression)
|
|
3023
|
+
if (!ts.isPropertyAccessExpression(value) || !ts.isIdentifier(value.expression)) return undefined
|
|
3024
|
+
if (objectName !== undefined && value.expression.text !== objectName) return undefined
|
|
3025
|
+
return value.name.text
|
|
5848
3026
|
}
|
|
5849
3027
|
|
|
5850
|
-
function
|
|
5851
|
-
const parent = node.parent
|
|
5852
|
-
if (!parent) return true
|
|
5853
|
-
if ((ts.isPropertyAccessExpression(parent) && parent.name === node) ||
|
|
5854
|
-
(ts.isPropertyAssignment(parent) && parent.name === node) ||
|
|
5855
|
-
(ts.isMethodDeclaration(parent) && parent.name === node) ||
|
|
5856
|
-
((ts.isGetAccessorDeclaration(parent) || ts.isSetAccessorDeclaration(parent)) && parent.name === node) ||
|
|
5857
|
-
(ts.isVariableDeclaration(parent) && parent.name === node) ||
|
|
5858
|
-
(ts.isParameter(parent) && parent.name === node) ||
|
|
5859
|
-
(ts.isFunctionDeclaration(parent) && parent.name === node) ||
|
|
5860
|
-
(ts.isJsxAttribute(parent) && parent.name === node) ||
|
|
5861
|
-
(ts.isBindingElement(parent) && (parent.name === node || parent.propertyName === node)) ||
|
|
5862
|
-
ts.isImportSpecifier(parent) || ts.isImportClause(parent)) return false
|
|
5863
|
-
return true
|
|
5864
|
-
}
|
|
5865
|
-
|
|
5866
|
-
function nearestFunction(node) {
|
|
3028
|
+
function keyedListParentTag(node) {
|
|
5867
3029
|
for (let current = node.parent; current; current = current.parent) {
|
|
5868
|
-
if (ts.
|
|
3030
|
+
if (ts.isJsxElement(current)) return current.openingElement.tagName.getText().toLowerCase()
|
|
5869
3031
|
}
|
|
5870
3032
|
return undefined
|
|
5871
3033
|
}
|
|
5872
3034
|
|
|
5873
|
-
function
|
|
5874
|
-
|
|
5875
|
-
return undefined
|
|
5876
|
-
}
|
|
5877
|
-
|
|
5878
|
-
function isShadowedByParameter(node, scopeRoot) {
|
|
5879
|
-
for (let current = node.parent; current; current = current.parent) {
|
|
5880
|
-
if (isFunctionLike(current) && current.parameters.some(parameter => bindingNames(parameter.name).includes(node.text))) return true
|
|
5881
|
-
if (current === scopeRoot) break
|
|
5882
|
-
}
|
|
5883
|
-
return false
|
|
3035
|
+
function identifierReferenceCount(root, name) {
|
|
3036
|
+
return identifierReferences(root, name).length
|
|
5884
3037
|
}
|
|
5885
3038
|
|
|
5886
|
-
function
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
for (let current = node.parent; current; current = current.parent) {
|
|
5892
|
-
if (current === scopeRoot) break
|
|
5893
|
-
if (ts.isFunctionExpression(current) && current.name?.text === node.text) return true
|
|
5894
|
-
if (ts.isBlock(current) && current.statements.some(statement => statementDeclaresName(statement, node.text))) return true
|
|
5895
|
-
if (ts.isCaseBlock(current) && current.clauses.some(clause => clause.statements.some(statement => statementDeclaresName(statement, node.text)))) return true
|
|
5896
|
-
if (ts.isCatchClause(current) && current.variableDeclaration && bindingNames(current.variableDeclaration.name).includes(node.text)) return true
|
|
5897
|
-
if ((ts.isForStatement(current) || ts.isForInStatement(current) || ts.isForOfStatement(current)) && loopDeclaresName(current, node.text)) return true
|
|
5898
|
-
if (isFunctionLike(current) && functionVarDeclaresName(current, node.text)) return true
|
|
3039
|
+
function identifierReferences(root, name) {
|
|
3040
|
+
const references = []
|
|
3041
|
+
const visit = node => {
|
|
3042
|
+
if (ts.isIdentifier(node) && node.text === name && isReferenceIdentifier(node) && !ts.isJsxClosingElement(node.parent)) references.push(node)
|
|
3043
|
+
ts.forEachChild(node, visit)
|
|
5899
3044
|
}
|
|
5900
|
-
|
|
3045
|
+
visit(root)
|
|
3046
|
+
return references
|
|
5901
3047
|
}
|
|
5902
3048
|
|
|
5903
|
-
function
|
|
5904
|
-
|
|
5905
|
-
if (ts.
|
|
5906
|
-
if (
|
|
5907
|
-
|
|
3049
|
+
function isJsxLocalValue(expression, known) {
|
|
3050
|
+
const value = unwrapExpression(expression)
|
|
3051
|
+
if (ts.isJsxElement(value) || ts.isJsxSelfClosingElement(value) || ts.isJsxFragment(value)) return true
|
|
3052
|
+
if (ts.isIdentifier(value)) return known.has(value.text)
|
|
3053
|
+
const parts = conditionalParts(value)
|
|
3054
|
+
return Boolean(parts && (isJsxLocalValue(parts.truthy, known) || isJsxLocalValue(parts.falsy, known)))
|
|
5908
3055
|
}
|
|
5909
3056
|
|
|
5910
|
-
function
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
specifier = node.moduleSpecifier
|
|
5916
|
-
runtime = runtimeModuleReference(node)
|
|
5917
|
-
} else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference) && ts.isStringLiteral(node.moduleReference.expression)) {
|
|
5918
|
-
specifier = node.moduleReference.expression
|
|
5919
|
-
runtime = !node.isTypeOnly
|
|
5920
|
-
}
|
|
5921
|
-
if (!runtime || !specifier?.text.startsWith(".")) continue
|
|
5922
|
-
let target
|
|
5923
|
-
try {
|
|
5924
|
-
target = resolveSourceImport(file, specifier.text, sourceFiles)
|
|
5925
|
-
} catch {
|
|
5926
|
-
continue
|
|
5927
|
-
}
|
|
5928
|
-
if (target.endsWith(".worker.ts")) throw sourceNodeError(specifier, sourceFile, "Worker source modules cannot be imported or re-exported as ordinary runtime modules; use new Worker(new URL(relative.worker.ts, import.meta.url), { type: \"module\" }) inside an inline useEffect() callback")
|
|
3057
|
+
function conditionalParts(expression) {
|
|
3058
|
+
const unwrap = node => ts.isParenthesizedExpression(node) ? unwrap(node.expression) : node
|
|
3059
|
+
const value = unwrap(expression)
|
|
3060
|
+
if (ts.isBinaryExpression(value) && value.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
|
|
3061
|
+
return { kind: "and", condition: value.left, truthy: unwrap(value.right), falsy: factoryNull() }
|
|
5929
3062
|
}
|
|
3063
|
+
if (ts.isConditionalExpression(value)) {
|
|
3064
|
+
return { kind: "ternary", condition: value.condition, truthy: unwrap(value.whenTrue), falsy: unwrap(value.whenFalse) }
|
|
3065
|
+
}
|
|
3066
|
+
return undefined
|
|
5930
3067
|
}
|
|
5931
3068
|
|
|
5932
|
-
function
|
|
5933
|
-
|
|
5934
|
-
return declaration && ts.isVariableDeclarationList(declaration) && declaration.declarations.some(entry => bindingNames(entry.name).includes(name))
|
|
5935
|
-
}
|
|
5936
|
-
|
|
5937
|
-
function functionVarDeclaresName(fn, name) {
|
|
5938
|
-
let found = false
|
|
5939
|
-
const visit = node => {
|
|
5940
|
-
if (found || node !== fn.body && isFunctionLike(node)) return
|
|
5941
|
-
if (ts.isVariableDeclarationList(node) && (node.flags & ts.NodeFlags.BlockScoped) === 0 && node.declarations.some(entry => bindingNames(entry.name).includes(name))) found = true
|
|
5942
|
-
if (!found) ts.forEachChild(node, visit)
|
|
5943
|
-
}
|
|
5944
|
-
if (fn.body) visit(fn.body)
|
|
5945
|
-
return found
|
|
3069
|
+
function factoryNull() {
|
|
3070
|
+
return ts.factory.createNull()
|
|
5946
3071
|
}
|
|
5947
3072
|
|
|
5948
3073
|
function settersForNode(node, settersByFunction) {
|
|
@@ -6097,40 +3222,6 @@ function localComponentDeclaration(sourceFile, name) {
|
|
|
6097
3222
|
return undefined
|
|
6098
3223
|
}
|
|
6099
3224
|
|
|
6100
|
-
function sourceNodeError(node, fallbackSource, message) {
|
|
6101
|
-
const original = ts.getOriginalNode(node)
|
|
6102
|
-
const sourceFile = original.getSourceFile?.()?.fileName ? original.getSourceFile() : fallbackSource
|
|
6103
|
-
const position = sourceFile.getLineAndCharacterOfPosition(original.getStart(sourceFile))
|
|
6104
|
-
return new Error(`${sourceFile.fileName}:${position.line + 1}:${position.character + 1} ${message}`)
|
|
6105
|
-
}
|
|
6106
|
-
|
|
6107
|
-
function sourceLocation(node, fallbackSource) {
|
|
6108
|
-
const original = ts.getOriginalNode(node)
|
|
6109
|
-
const sourceFile = original.getSourceFile?.()?.fileName ? original.getSourceFile() : fallbackSource
|
|
6110
|
-
const position = sourceFile.getLineAndCharacterOfPosition(original.getStart(sourceFile))
|
|
6111
|
-
return `${sourceFile.fileName}:${position.line + 1}:${position.character + 1}`
|
|
6112
|
-
}
|
|
6113
|
-
|
|
6114
|
-
function effectReturns(callback) {
|
|
6115
|
-
let cleanup = false
|
|
6116
|
-
let invalid
|
|
6117
|
-
const cleanups = []
|
|
6118
|
-
const visit = node => {
|
|
6119
|
-
if (invalid || node !== callback.body && isFunctionLike(node)) return
|
|
6120
|
-
if (ts.isReturnStatement(node) && node.expression) {
|
|
6121
|
-
const expression = unwrapExpression(node.expression)
|
|
6122
|
-
if (ts.isArrowFunction(expression) || ts.isFunctionExpression(expression)) {
|
|
6123
|
-
cleanup = true
|
|
6124
|
-
cleanups.push(expression)
|
|
6125
|
-
}
|
|
6126
|
-
else invalid = node
|
|
6127
|
-
}
|
|
6128
|
-
if (!invalid) ts.forEachChild(node, visit)
|
|
6129
|
-
}
|
|
6130
|
-
visit(callback.body)
|
|
6131
|
-
return { cleanup, cleanups, invalid }
|
|
6132
|
-
}
|
|
6133
|
-
|
|
6134
3225
|
function validateEffectOwnedBrowserResources(callback, returns, fail) {
|
|
6135
3226
|
const observers = []
|
|
6136
3227
|
const frameAssignments = []
|
|
@@ -6155,86 +3246,6 @@ function validateEffectOwnedBrowserResources(callback, returns, fail) {
|
|
|
6155
3246
|
}
|
|
6156
3247
|
}
|
|
6157
3248
|
|
|
6158
|
-
function printClientImports(entries, handlerPath) {
|
|
6159
|
-
const unique = new Map(entries.map(entry => [`${entry.target}:${entry.kind}:${entry.imported ?? ""}:${entry.local}`, entry]))
|
|
6160
|
-
const groups = Map.groupBy(unique.values(), entry => entry.target)
|
|
6161
|
-
const imports = []
|
|
6162
|
-
for (const [target, group] of groups) {
|
|
6163
|
-
const specifier = group[0].package ? target : relativeModulePath(handlerPath, clientModulePath(target))
|
|
6164
|
-
const defaults = group.filter(entry => entry.kind === "default")
|
|
6165
|
-
const named = group.filter(entry => entry.kind === "named")
|
|
6166
|
-
if (defaults.length === 1 || named.length) imports.push(`import ${defaults.length === 1 ? `${defaults[0].local}${named.length ? ", " : ""}` : ""}${named.length ? `{ ${named.map(entry => entry.imported === entry.local ? entry.local : `${entry.imported} as ${entry.local}`).join(", ")} }` : ""} from ${JSON.stringify(specifier)}`)
|
|
6167
|
-
if (defaults.length > 1) for (const entry of defaults) imports.push(`import ${entry.local} from ${JSON.stringify(specifier)}`)
|
|
6168
|
-
for (const entry of group.filter(entry => entry.kind === "namespace")) imports.push(`import * as ${entry.local} from ${JSON.stringify(specifier)}`)
|
|
6169
|
-
}
|
|
6170
|
-
return imports.join("\n")
|
|
6171
|
-
}
|
|
6172
|
-
|
|
6173
|
-
async function emitWorkers(references, sourceFiles, assetsDirectory, base, minify) {
|
|
6174
|
-
const roots = [...new Set(references.map(reference => reference.root))].sort()
|
|
6175
|
-
if (!roots.length) return new Map()
|
|
6176
|
-
await validateWorkerGraphs(roots, sourceFiles)
|
|
6177
|
-
const workerDirectory = join(assetsDirectory, "workers")
|
|
6178
|
-
await mkdir(workerDirectory, { recursive: true })
|
|
6179
|
-
const result = await bundle({
|
|
6180
|
-
absWorkingDir: root,
|
|
6181
|
-
entryPoints: roots,
|
|
6182
|
-
outbase: sourceDirectory,
|
|
6183
|
-
outdir: workerDirectory,
|
|
6184
|
-
entryNames: "[dir]/[name]-[hash]",
|
|
6185
|
-
chunkNames: "chunks/[name]-[hash]",
|
|
6186
|
-
bundle: true,
|
|
6187
|
-
splitting: true,
|
|
6188
|
-
format: "esm",
|
|
6189
|
-
platform: "browser",
|
|
6190
|
-
target: "es2022",
|
|
6191
|
-
minify,
|
|
6192
|
-
legalComments: "none",
|
|
6193
|
-
metafile: true,
|
|
6194
|
-
logLevel: "silent"
|
|
6195
|
-
})
|
|
6196
|
-
const emitted = new Map()
|
|
6197
|
-
for (const [output, metadata] of Object.entries(result.metafile.outputs)) {
|
|
6198
|
-
if (!metadata.entryPoint) continue
|
|
6199
|
-
const entry = resolve(root, metadata.entryPoint)
|
|
6200
|
-
const rootReferences = references.filter(reference => reference.root === entry)
|
|
6201
|
-
const outputFile = resolve(root, output)
|
|
6202
|
-
const url = assetPath(base, relative(outputDirectory, outputFile).replaceAll(sep, "/"))
|
|
6203
|
-
for (const reference of rootReferences) emitted.set(reference.placeholder, url)
|
|
6204
|
-
}
|
|
6205
|
-
for (const reference of references) if (!emitted.has(reference.placeholder)) throw new Error(`Worker entry was not emitted: ${relative(root, reference.root)}`)
|
|
6206
|
-
return emitted
|
|
6207
|
-
}
|
|
6208
|
-
|
|
6209
|
-
async function validateWorkerGraphs(roots, sourceFiles) {
|
|
6210
|
-
const visited = new Set()
|
|
6211
|
-
const queue = [...roots]
|
|
6212
|
-
while (queue.length) {
|
|
6213
|
-
const file = queue.shift()
|
|
6214
|
-
if (visited.has(file)) continue
|
|
6215
|
-
visited.add(file)
|
|
6216
|
-
const sourceFile = parseSourceFile(file, await readFile(file, "utf8"))
|
|
6217
|
-
if (containsJsx(sourceFile)) throw sourceNodeError(sourceFile, sourceFile, "Worker modules must not contain JSX")
|
|
6218
|
-
const visit = node => {
|
|
6219
|
-
if (ts.isImportEqualsDeclaration(node)) throw sourceNodeError(node, sourceFile, "TypeScript import-equals declarations are not supported in Worker modules; use a relative ESM import")
|
|
6220
|
-
if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) throw sourceNodeError(node, sourceFile, "Dynamic imports are not supported in Worker modules")
|
|
6221
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "require") throw sourceNodeError(node, sourceFile, "require() is not supported in Worker modules")
|
|
6222
|
-
ts.forEachChild(node, visit)
|
|
6223
|
-
}
|
|
6224
|
-
visit(sourceFile)
|
|
6225
|
-
for (const node of sourceFile.statements) {
|
|
6226
|
-
if ((!ts.isImportDeclaration(node) && !ts.isExportDeclaration(node)) || !node.moduleSpecifier || !ts.isStringLiteral(node.moduleSpecifier) || !runtimeModuleReference(node)) continue
|
|
6227
|
-
if (!node.moduleSpecifier.text.startsWith(".")) throw sourceNodeError(node.moduleSpecifier, sourceFile, "Worker modules may only use relative runtime imports")
|
|
6228
|
-
try {
|
|
6229
|
-
queue.push(resolveSourceImport(file, node.moduleSpecifier.text, sourceFiles))
|
|
6230
|
-
} catch (error) {
|
|
6231
|
-
const message = error.message.slice(error.message.indexOf("Relative import"))
|
|
6232
|
-
throw sourceNodeError(node.moduleSpecifier, sourceFile, message)
|
|
6233
|
-
}
|
|
6234
|
-
}
|
|
6235
|
-
}
|
|
6236
|
-
}
|
|
6237
|
-
|
|
6238
3249
|
async function collectClientModules(entries, sourceFiles) {
|
|
6239
3250
|
const modules = new Set()
|
|
6240
3251
|
const queue = [...new Set(entries)]
|
|
@@ -6243,7 +3254,7 @@ async function collectClientModules(entries, sourceFiles) {
|
|
|
6243
3254
|
if (modules.has(file)) continue
|
|
6244
3255
|
const source = await readFile(file, "utf8")
|
|
6245
3256
|
const sourceFile = parseSourceFile(file, source)
|
|
6246
|
-
|
|
3257
|
+
workerCompiler.rejectConstructions(sourceFile, sourceFile, "Relative TypeScript Worker construction is only supported directly inside an inline useEffect() callback, not imported client helpers")
|
|
6247
3258
|
if (containsJsx(sourceFile)) throw new Error(`${relative(root, file)} Imported client helpers must not contain JSX`)
|
|
6248
3259
|
rejectUnsupportedClientImports(sourceFile, file)
|
|
6249
3260
|
modules.add(file)
|
|
@@ -6570,310 +3581,6 @@ function relativeModulePath(from, to) {
|
|
|
6570
3581
|
return path.startsWith(".") ? path : `./${path}`
|
|
6571
3582
|
}
|
|
6572
3583
|
|
|
6573
|
-
function printNativeHandler({ exportName, expression, captures, setters, reducers = new Map(), snapshotNested, liveStates = new Set() }) {
|
|
6574
|
-
const factory = ts.factory
|
|
6575
|
-
const stateNames = new Set(setters.values())
|
|
6576
|
-
const snapshotNames = snapshotNested ? nestedStateNames(expression, setters, liveStates) : new Set()
|
|
6577
|
-
const snapshots = new Map([...snapshotNames].map(name => [name, factory.createUniqueName("__kEffectState")]))
|
|
6578
|
-
const captureSnapshotNames = snapshotNested ? nestedCaptureNames(expression, captures) : new Set()
|
|
6579
|
-
const captureSnapshots = new Map([...captureSnapshotNames].map(name => [name, factory.createUniqueName("__kEffectCapture")]))
|
|
6580
|
-
const transformer = context => root => {
|
|
6581
|
-
const visitor = node => {
|
|
6582
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && reducers.has(node.expression.text) && !isShadowedIdentifier(node.expression, expression)) {
|
|
6583
|
-
const reducer = reducers.get(node.expression.text)
|
|
6584
|
-
if (reducer.contextAction) {
|
|
6585
|
-
const action = synthesizeTree(cloneAst(reducer.contextAction, factory, context))
|
|
6586
|
-
const call = factory.createCallExpression(action, undefined, node.arguments)
|
|
6587
|
-
ts.setParentRecursive(call, false)
|
|
6588
|
-
return ts.visitNode(call, visitor)
|
|
6589
|
-
}
|
|
6590
|
-
if (reducer.store) return zustandActionDispatch(factory, reducer, node.arguments.map(argument => ts.visitNode(argument, visitor)))
|
|
6591
|
-
if (node.arguments.length !== 1) throw sourceNodeError(node, expression.getSourceFile(), "Reducer dispatches require exactly one action")
|
|
6592
|
-
return reducerDispatch(factory, reducer, ts.visitNode(node.arguments[0], visitor))
|
|
6593
|
-
}
|
|
6594
|
-
if (ts.isShorthandPropertyAssignment(node) && reducers.has(node.name.text) && !isShadowedIdentifier(node.name, expression)) {
|
|
6595
|
-
if (reducers.get(node.name.text).contextAction) throw sourceNodeError(node, expression.getSourceFile(), "Context actions must be called directly inside an event handler")
|
|
6596
|
-
if (reducers.get(node.name.text).store) throw sourceNodeError(node, expression.getSourceFile(), "Zustand actions must be called directly inside an event handler")
|
|
6597
|
-
return factory.createPropertyAssignment(node.name, reducerReference(factory, reducers.get(node.name.text)))
|
|
6598
|
-
}
|
|
6599
|
-
if (ts.isIdentifier(node) && reducers.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, expression)) {
|
|
6600
|
-
if (reducers.get(node.text).contextAction) throw sourceNodeError(node, expression.getSourceFile(), "Context actions must be called directly inside an event handler")
|
|
6601
|
-
if (reducers.get(node.text).store) throw sourceNodeError(node, expression.getSourceFile(), "Zustand actions must be called directly inside an event handler")
|
|
6602
|
-
return reducerReference(factory, reducers.get(node.text))
|
|
6603
|
-
}
|
|
6604
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && setters.has(node.expression.text) && !isShadowedIdentifier(node.expression, expression)) {
|
|
6605
|
-
return factory.createCallExpression(
|
|
6606
|
-
factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"),
|
|
6607
|
-
undefined,
|
|
6608
|
-
[factory.createStringLiteral(setters.get(node.expression.text)), ...node.arguments.map(argument => ts.visitNode(argument, visitor))]
|
|
6609
|
-
)
|
|
6610
|
-
}
|
|
6611
|
-
if (ts.isShorthandPropertyAssignment(node) && setters.has(node.name.text) && !isShadowedIdentifier(node.name, expression)) {
|
|
6612
|
-
return factory.createPropertyAssignment(node.name, setterReference(factory, setters.get(node.name.text)))
|
|
6613
|
-
}
|
|
6614
|
-
if (ts.isIdentifier(node) && setters.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, expression)) {
|
|
6615
|
-
return setterReference(factory, setters.get(node.text))
|
|
6616
|
-
}
|
|
6617
|
-
if (ts.isShorthandPropertyAssignment(node) && stateNames.has(node.name.text) && !isShadowedIdentifier(node.name, expression)) {
|
|
6618
|
-
if (snapshots.has(node.name.text) && insideNestedFunction(node, expression)) return factory.createPropertyAssignment(node.name, snapshots.get(node.name.text))
|
|
6619
|
-
return factory.createPropertyAssignment(node.name, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"), undefined, [factory.createStringLiteral(node.name.text)]))
|
|
6620
|
-
}
|
|
6621
|
-
if (ts.isIdentifier(node) && stateNames.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, expression)) {
|
|
6622
|
-
if (snapshots.has(node.text) && insideNestedFunction(node, expression)) return snapshots.get(node.text)
|
|
6623
|
-
return factory.createCallExpression(
|
|
6624
|
-
factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"),
|
|
6625
|
-
undefined,
|
|
6626
|
-
[factory.createStringLiteral(node.text)]
|
|
6627
|
-
)
|
|
6628
|
-
}
|
|
6629
|
-
if (ts.isShorthandPropertyAssignment(node) && captures.has(node.name.text) && !isShadowedIdentifier(node.name, expression)) {
|
|
6630
|
-
if (captureSnapshots.has(node.name.text) && insideNestedFunction(node, expression)) return factory.createPropertyAssignment(node.name, captureSnapshots.get(node.name.text))
|
|
6631
|
-
return factory.createPropertyAssignment(node.name, scopeRead(factory, node.name.text))
|
|
6632
|
-
}
|
|
6633
|
-
if (ts.isIdentifier(node) && captures.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, expression)) {
|
|
6634
|
-
if (captureSnapshots.has(node.text) && insideNestedFunction(node, expression)) return captureSnapshots.get(node.text)
|
|
6635
|
-
return scopeRead(factory, node.text)
|
|
6636
|
-
}
|
|
6637
|
-
return ts.visitEachChild(node, visitor, context)
|
|
6638
|
-
}
|
|
6639
|
-
return ts.visitNode(root, visitor)
|
|
6640
|
-
}
|
|
6641
|
-
const transformed = ts.transform(expression.body, [transformer])
|
|
6642
|
-
try {
|
|
6643
|
-
let body = ts.isBlock(expression.body)
|
|
6644
|
-
? transformed.transformed[0]
|
|
6645
|
-
: factory.createBlock([factory.createReturnStatement(transformed.transformed[0])], true)
|
|
6646
|
-
const snapshotDeclarations = [
|
|
6647
|
-
...[...snapshots].map(([name, identifier]) => factory.createVariableDeclaration(identifier, undefined, undefined, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"), undefined, [factory.createStringLiteral(name)]))),
|
|
6648
|
-
...[...captureSnapshots].map(([name, identifier]) => factory.createVariableDeclaration(identifier, undefined, undefined, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "scope"), undefined, [factory.createStringLiteral(name)])))
|
|
6649
|
-
]
|
|
6650
|
-
if (snapshotDeclarations.length) body = factory.updateBlock(body, [
|
|
6651
|
-
factory.createVariableStatement(undefined, factory.createVariableDeclarationList(snapshotDeclarations, ts.NodeFlags.Const)),
|
|
6652
|
-
...body.statements
|
|
6653
|
-
])
|
|
6654
|
-
const modifiers = [factory.createModifier(ts.SyntaxKind.ExportKeyword)]
|
|
6655
|
-
if (expression.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) modifiers.push(factory.createModifier(ts.SyntaxKind.AsyncKeyword))
|
|
6656
|
-
const declaration = factory.createFunctionDeclaration(
|
|
6657
|
-
modifiers,
|
|
6658
|
-
expression.asteriskToken,
|
|
6659
|
-
exportName,
|
|
6660
|
-
undefined,
|
|
6661
|
-
[factory.createParameterDeclaration(undefined, undefined, "__k"), ...expression.parameters],
|
|
6662
|
-
undefined,
|
|
6663
|
-
body
|
|
6664
|
-
)
|
|
6665
|
-
return ts.createPrinter().printNode(ts.EmitHint.Unspecified, declaration, expression.getSourceFile())
|
|
6666
|
-
} finally {
|
|
6667
|
-
transformed.dispose()
|
|
6668
|
-
}
|
|
6669
|
-
}
|
|
6670
|
-
|
|
6671
|
-
function nestedCaptureNames(expression, captures) {
|
|
6672
|
-
const names = new Set()
|
|
6673
|
-
const visit = node => {
|
|
6674
|
-
if (ts.isIdentifier(node) && captures.has(node.text) && isReferenceIdentifier(node) && insideNestedFunction(node, expression) && !isShadowedIdentifier(node, expression)) names.add(node.text)
|
|
6675
|
-
ts.forEachChild(node, visit)
|
|
6676
|
-
}
|
|
6677
|
-
visit(expression.body)
|
|
6678
|
-
return names
|
|
6679
|
-
}
|
|
6680
|
-
|
|
6681
|
-
function nestedStateNames(expression, setters, liveStates = new Set()) {
|
|
6682
|
-
const states = new Set(setters.values())
|
|
6683
|
-
const names = new Set()
|
|
6684
|
-
const visit = node => {
|
|
6685
|
-
if (ts.isIdentifier(node) && states.has(node.text) && !liveStates.has(node.text) && isReferenceIdentifier(node) && insideNestedFunction(node, expression) && !isShadowedIdentifier(node, expression)) names.add(node.text)
|
|
6686
|
-
ts.forEachChild(node, visit)
|
|
6687
|
-
}
|
|
6688
|
-
visit(expression.body)
|
|
6689
|
-
return names
|
|
6690
|
-
}
|
|
6691
|
-
|
|
6692
|
-
function insideNestedFunction(node, root) {
|
|
6693
|
-
for (let current = node.parent; current && current !== root; current = current.parent) {
|
|
6694
|
-
if (isFunctionLike(current)) return true
|
|
6695
|
-
}
|
|
6696
|
-
return false
|
|
6697
|
-
}
|
|
6698
|
-
|
|
6699
|
-
function setterReference(factory, stateName) {
|
|
6700
|
-
return factory.createArrowFunction(
|
|
6701
|
-
undefined,
|
|
6702
|
-
undefined,
|
|
6703
|
-
[factory.createParameterDeclaration(undefined, undefined, "value")],
|
|
6704
|
-
undefined,
|
|
6705
|
-
factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
|
|
6706
|
-
factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"), undefined, [factory.createStringLiteral(stateName), factory.createIdentifier("value")])
|
|
6707
|
-
)
|
|
6708
|
-
}
|
|
6709
|
-
|
|
6710
|
-
function reducerReference(factory, reducer) {
|
|
6711
|
-
const action = factory.createUniqueName("__kAction")
|
|
6712
|
-
return factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, action)], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), reducerDispatch(factory, reducer, action))
|
|
6713
|
-
}
|
|
6714
|
-
|
|
6715
|
-
function reducerDispatch(factory, reducer, action) {
|
|
6716
|
-
if (reducer.store) return zustandActionDispatch(factory, reducer, [action])
|
|
6717
|
-
const previous = factory.createUniqueName("__kPrevious")
|
|
6718
|
-
const update = factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, previous)], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createCallExpression(factory.createIdentifier(reducer.reducer), undefined, [previous, action]))
|
|
6719
|
-
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"), undefined, [factory.createStringLiteral(reducer.state), update])
|
|
6720
|
-
}
|
|
6721
|
-
|
|
6722
|
-
function zustandActionDispatch(factory, reducer, args) {
|
|
6723
|
-
const previous = factory.createUniqueName("__kPrevious")
|
|
6724
|
-
const current = factory.createUniqueName("__kStore")
|
|
6725
|
-
const updateValue = factory.createUniqueName("__kUpdate")
|
|
6726
|
-
const partial = factory.createUniqueName("__kPartial")
|
|
6727
|
-
const action = factory.createUniqueName("__kAction")
|
|
6728
|
-
const set = factory.createIdentifier(reducer.store.setName)
|
|
6729
|
-
const merge = factory.createExpressionStatement(factory.createBinaryExpression(current, factory.createToken(ts.SyntaxKind.EqualsToken), factory.createObjectLiteralExpression([
|
|
6730
|
-
factory.createSpreadAssignment(current),
|
|
6731
|
-
factory.createSpreadAssignment(partial)
|
|
6732
|
-
])))
|
|
6733
|
-
const setBody = factory.createBlock([
|
|
6734
|
-
factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(partial, undefined, undefined, factory.createConditionalExpression(
|
|
6735
|
-
factory.createBinaryExpression(factory.createTypeOfExpression(updateValue), factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), factory.createStringLiteral("function")),
|
|
6736
|
-
undefined,
|
|
6737
|
-
factory.createCallExpression(updateValue, undefined, [current]),
|
|
6738
|
-
undefined,
|
|
6739
|
-
updateValue
|
|
6740
|
-
))], ts.NodeFlags.Const)),
|
|
6741
|
-
merge
|
|
6742
|
-
], true)
|
|
6743
|
-
const body = factory.createBlock([
|
|
6744
|
-
factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(current, undefined, undefined, factory.createObjectLiteralExpression([factory.createPropertyAssignment(reducer.store.field, previous)]))], ts.NodeFlags.Let)),
|
|
6745
|
-
factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(set, undefined, undefined, factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, updateValue)], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), setBody))], ts.NodeFlags.Const)),
|
|
6746
|
-
factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(action, undefined, undefined, synthesizeTree(reducer.store.actions.get(reducer.action)))], ts.NodeFlags.Const)),
|
|
6747
|
-
factory.createExpressionStatement(factory.createCallExpression(action, undefined, args)),
|
|
6748
|
-
factory.createReturnStatement(factory.createPropertyAccessExpression(current, reducer.store.field))
|
|
6749
|
-
], true)
|
|
6750
|
-
const update = factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, previous)], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), body)
|
|
6751
|
-
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"), undefined, [factory.createStringLiteral(reducer.state), update])
|
|
6752
|
-
}
|
|
6753
|
-
|
|
6754
|
-
function printReactiveBinding({ exportName, expression, captures, states }) {
|
|
6755
|
-
const factory = ts.factory
|
|
6756
|
-
const transformer = context => root => {
|
|
6757
|
-
const visitor = node => {
|
|
6758
|
-
if (ts.isShorthandPropertyAssignment(node) && states.has(node.name.text)) {
|
|
6759
|
-
return factory.createPropertyAssignment(
|
|
6760
|
-
node.name,
|
|
6761
|
-
factory.createCallExpression(
|
|
6762
|
-
factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"),
|
|
6763
|
-
undefined,
|
|
6764
|
-
[factory.createStringLiteral(node.name.text)]
|
|
6765
|
-
)
|
|
6766
|
-
)
|
|
6767
|
-
}
|
|
6768
|
-
if (ts.isIdentifier(node) && states.has(node.text) && isReferenceIdentifier(node) && !isShadowedByParameter(node, expression)) {
|
|
6769
|
-
return factory.createCallExpression(
|
|
6770
|
-
factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"),
|
|
6771
|
-
undefined,
|
|
6772
|
-
[factory.createStringLiteral(node.text)]
|
|
6773
|
-
)
|
|
6774
|
-
}
|
|
6775
|
-
if (ts.isShorthandPropertyAssignment(node) && captures.has(node.name.text)) {
|
|
6776
|
-
return factory.createPropertyAssignment(node.name, scopeRead(factory, node.name.text))
|
|
6777
|
-
}
|
|
6778
|
-
if (ts.isIdentifier(node) && captures.has(node.text) && isReferenceIdentifier(node)) {
|
|
6779
|
-
return scopeRead(factory, node.text)
|
|
6780
|
-
}
|
|
6781
|
-
return ts.visitEachChild(node, visitor, context)
|
|
6782
|
-
}
|
|
6783
|
-
return ts.visitNode(root, visitor)
|
|
6784
|
-
}
|
|
6785
|
-
const transformed = ts.transform(expression, [transformer])
|
|
6786
|
-
try {
|
|
6787
|
-
const declaration = factory.createFunctionDeclaration(
|
|
6788
|
-
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
6789
|
-
undefined,
|
|
6790
|
-
exportName,
|
|
6791
|
-
undefined,
|
|
6792
|
-
[factory.createParameterDeclaration(undefined, undefined, "__k")],
|
|
6793
|
-
undefined,
|
|
6794
|
-
factory.createBlock([factory.createReturnStatement(transformed.transformed[0])], true)
|
|
6795
|
-
)
|
|
6796
|
-
return ts.createPrinter().printNode(ts.EmitHint.Unspecified, declaration, expression.getSourceFile())
|
|
6797
|
-
} finally {
|
|
6798
|
-
transformed.dispose()
|
|
6799
|
-
}
|
|
6800
|
-
}
|
|
6801
|
-
|
|
6802
|
-
function printListExpression({ exportName, expression, item, index, states = new Set() }) {
|
|
6803
|
-
const factory = ts.factory
|
|
6804
|
-
const transformer = context => root => {
|
|
6805
|
-
const visitor = node => {
|
|
6806
|
-
if (ts.isShorthandPropertyAssignment(node) && states.has(node.name.text)) {
|
|
6807
|
-
return factory.createPropertyAssignment(node.name, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"), undefined, [factory.createStringLiteral(node.name.text)]))
|
|
6808
|
-
}
|
|
6809
|
-
if (ts.isIdentifier(node) && states.has(node.text) && isReferenceIdentifier(node) && !isShadowedByParameter(node, expression)) {
|
|
6810
|
-
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"), undefined, [factory.createStringLiteral(node.text)])
|
|
6811
|
-
}
|
|
6812
|
-
return ts.visitEachChild(node, visitor, context)
|
|
6813
|
-
}
|
|
6814
|
-
return ts.visitNode(root, visitor)
|
|
6815
|
-
}
|
|
6816
|
-
const transformed = ts.transform(expression, [transformer])
|
|
6817
|
-
const declaration = ts.factory.createFunctionDeclaration(
|
|
6818
|
-
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
6819
|
-
undefined,
|
|
6820
|
-
exportName,
|
|
6821
|
-
undefined,
|
|
6822
|
-
[ts.factory.createParameterDeclaration(undefined, undefined, item), ts.factory.createParameterDeclaration(undefined, undefined, index ?? "__kIndex"), ts.factory.createParameterDeclaration(undefined, undefined, "__k")],
|
|
6823
|
-
undefined,
|
|
6824
|
-
ts.factory.createBlock([ts.factory.createReturnStatement(transformed.transformed[0])], true)
|
|
6825
|
-
)
|
|
6826
|
-
try {
|
|
6827
|
-
return ts.createPrinter().printNode(ts.EmitHint.Unspecified, declaration, expression.getSourceFile())
|
|
6828
|
-
} finally {
|
|
6829
|
-
transformed.dispose()
|
|
6830
|
-
}
|
|
6831
|
-
}
|
|
6832
|
-
|
|
6833
|
-
function scopeRead(factory, name) {
|
|
6834
|
-
return factory.createCallExpression(
|
|
6835
|
-
factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "scope"),
|
|
6836
|
-
undefined,
|
|
6837
|
-
[factory.createStringLiteral(name)]
|
|
6838
|
-
)
|
|
6839
|
-
}
|
|
6840
|
-
|
|
6841
|
-
function compileEventCommand(expression, setters, factory) {
|
|
6842
|
-
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)) {
|
|
6843
|
-
return command(factory, "log", expression.arguments[1], factory.createStringLiteral(expression.arguments[0].text))
|
|
6844
|
-
}
|
|
6845
|
-
|
|
6846
|
-
if (!ts.isCallExpression(expression) || !ts.isIdentifier(expression.expression) || expression.arguments.length !== 1) return undefined
|
|
6847
|
-
const stateName = setters.get(expression.expression.text)
|
|
6848
|
-
if (!stateName) return undefined
|
|
6849
|
-
|
|
6850
|
-
const state = factory.createIdentifier(stateName)
|
|
6851
|
-
const value = expression.arguments[0]
|
|
6852
|
-
if (ts.isBinaryExpression(value) && ts.isIdentifier(value.left) && value.left.text === stateName && ts.isNumericLiteral(value.right)) {
|
|
6853
|
-
if (value.operatorToken.kind !== ts.SyntaxKind.PlusToken && value.operatorToken.kind !== ts.SyntaxKind.MinusToken) return undefined
|
|
6854
|
-
return command(factory, "add", state, numericExpression(factory, Number(value.right.text), value.operatorToken.kind === ts.SyntaxKind.MinusToken))
|
|
6855
|
-
}
|
|
6856
|
-
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)) {
|
|
6857
|
-
if (value.body.operatorToken.kind !== ts.SyntaxKind.PlusToken && value.body.operatorToken.kind !== ts.SyntaxKind.MinusToken) return undefined
|
|
6858
|
-
return command(factory, "add", state, numericExpression(factory, Number(value.body.right.text), value.body.operatorToken.kind === ts.SyntaxKind.MinusToken))
|
|
6859
|
-
}
|
|
6860
|
-
if (isPrimitiveLiteral(value)) return command(factory, "set", state, synthesizeSerializableStateLiteral(value, factory))
|
|
6861
|
-
return undefined
|
|
6862
|
-
}
|
|
6863
|
-
|
|
6864
|
-
function command(factory, operation, state, value) {
|
|
6865
|
-
return factory.createArrayLiteralExpression([factory.createStringLiteral(operation), state, value])
|
|
6866
|
-
}
|
|
6867
|
-
|
|
6868
|
-
function isPrimitiveLiteral(node) {
|
|
6869
|
-
return isPrimitiveDefaultLiteral(node)
|
|
6870
|
-
}
|
|
6871
|
-
|
|
6872
|
-
function numericExpression(factory, value, negative) {
|
|
6873
|
-
const literal = factory.createNumericLiteral(value)
|
|
6874
|
-
return negative ? factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, literal) : literal
|
|
6875
|
-
}
|
|
6876
|
-
|
|
6877
3584
|
function compiledPath(file) {
|
|
6878
3585
|
return join(workDirectory, relative(sourceDirectory, file)).replace(/\.(?:ts|tsx)$/, ".mjs")
|
|
6879
3586
|
}
|
|
@@ -7025,6 +3732,25 @@ function withBase(base, path) {
|
|
|
7025
3732
|
return base ? `${base}${path}` : path
|
|
7026
3733
|
}
|
|
7027
3734
|
|
|
3735
|
+
const workerCompiler = createWorkerCompiler({
|
|
3736
|
+
root,
|
|
3737
|
+
sourceDirectory,
|
|
3738
|
+
outputDirectory,
|
|
3739
|
+
assetPath,
|
|
3740
|
+
parseSourceFile,
|
|
3741
|
+
resolveSourceImport,
|
|
3742
|
+
runtimeModuleReference
|
|
3743
|
+
})
|
|
3744
|
+
|
|
3745
|
+
const printEffectEntry = createEffectCodegen({ assetPath, inlineJson, relativeModulePath })
|
|
3746
|
+
const printHandlerModule = createHandlerCodegen({
|
|
3747
|
+
cloneAst,
|
|
3748
|
+
synthesizeTree,
|
|
3749
|
+
resolveClientImport: (entry, handlerPath) => entry.package ? entry.target : relativeModulePath(handlerPath, clientModulePath(entry.target))
|
|
3750
|
+
})
|
|
3751
|
+
const { normalizeReactMigrationSyntax, validateUseIdSyntax } = createReactMigrationPass({ cloneAst, jsxTagName })
|
|
3752
|
+
const normalizeReactRouterSyntax = createRouterPass({ withBase })
|
|
3753
|
+
|
|
7028
3754
|
async function staticPathEntries(module, file) {
|
|
7029
3755
|
if (typeof module.getStaticPaths !== "function") return [{ params: {}, props: {} }]
|
|
7030
3756
|
const entries = await module.getStaticPaths()
|
|
@@ -7109,13 +3835,3 @@ async function exists(path) {
|
|
|
7109
3835
|
return false
|
|
7110
3836
|
}
|
|
7111
3837
|
}
|
|
7112
|
-
|
|
7113
|
-
function contentType(file) {
|
|
7114
|
-
return {
|
|
7115
|
-
".html": "text/html; charset=utf-8",
|
|
7116
|
-
".css": "text/css; charset=utf-8",
|
|
7117
|
-
".js": "text/javascript; charset=utf-8",
|
|
7118
|
-
".json": "application/json; charset=utf-8",
|
|
7119
|
-
".svg": "image/svg+xml"
|
|
7120
|
-
}[extname(file)] ?? "application/octet-stream"
|
|
7121
|
-
}
|