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