@kudzujs/core 0.8.17 → 0.8.19

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.
@@ -0,0 +1,278 @@
1
+ import ts from "typescript"
2
+ import { isFunctionLike, isReferenceIdentifier, isShadowedByParameter, isShadowedIdentifier, sourceNodeError } from "./ast-helpers.mjs"
3
+
4
+ export function createHandlerLowering({ cloneAst, synthesizeTree }) {
5
+ return { lowerListExpression: printListExpression, lowerNativeHandler: printNativeHandler, lowerReactiveBinding: printReactiveBinding }
6
+
7
+ function printNativeHandler({ exportName, expression, captures, setters, reducers = new Map(), snapshotNested, liveStates = new Set() }) {
8
+ const factory = ts.factory
9
+ const stateNames = new Set(setters.values())
10
+ const snapshotNames = snapshotNested ? nestedStateNames(expression, setters, liveStates) : new Set()
11
+ const snapshots = new Map([...snapshotNames].map(name => [name, factory.createUniqueName("__kEffectState")]))
12
+ const captureSnapshotNames = snapshotNested ? nestedCaptureNames(expression, captures) : new Set()
13
+ const captureSnapshots = new Map([...captureSnapshotNames].map(name => [name, factory.createUniqueName("__kEffectCapture")]))
14
+ const transformer = context => root => {
15
+ const visitor = node => {
16
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && reducers.has(node.expression.text) && !isShadowedIdentifier(node.expression, expression)) {
17
+ const reducer = reducers.get(node.expression.text)
18
+ if (reducer.contextAction) {
19
+ const action = synthesizeTree(cloneAst(reducer.contextAction, factory, context))
20
+ const call = factory.createCallExpression(action, undefined, node.arguments)
21
+ ts.setParentRecursive(call, false)
22
+ return ts.visitNode(call, visitor)
23
+ }
24
+ if (reducer.store) return zustandActionDispatch(factory, reducer, node.arguments.map(argument => ts.visitNode(argument, visitor)))
25
+ if (node.arguments.length !== 1) throw sourceNodeError(node, expression.getSourceFile(), "Reducer dispatches require exactly one action")
26
+ return reducerDispatch(factory, reducer, ts.visitNode(node.arguments[0], visitor))
27
+ }
28
+ if (ts.isShorthandPropertyAssignment(node) && reducers.has(node.name.text) && !isShadowedIdentifier(node.name, expression)) {
29
+ if (reducers.get(node.name.text).contextAction) throw sourceNodeError(node, expression.getSourceFile(), "Context actions must be called directly inside an event handler")
30
+ if (reducers.get(node.name.text).store) throw sourceNodeError(node, expression.getSourceFile(), "Zustand actions must be called directly inside an event handler")
31
+ return factory.createPropertyAssignment(node.name, reducerReference(factory, reducers.get(node.name.text)))
32
+ }
33
+ if (ts.isIdentifier(node) && reducers.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, expression)) {
34
+ if (reducers.get(node.text).contextAction) throw sourceNodeError(node, expression.getSourceFile(), "Context actions must be called directly inside an event handler")
35
+ if (reducers.get(node.text).store) throw sourceNodeError(node, expression.getSourceFile(), "Zustand actions must be called directly inside an event handler")
36
+ return reducerReference(factory, reducers.get(node.text))
37
+ }
38
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && setters.has(node.expression.text) && !isShadowedIdentifier(node.expression, expression)) {
39
+ return factory.createCallExpression(
40
+ factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"),
41
+ undefined,
42
+ [factory.createStringLiteral(setters.get(node.expression.text)), ...node.arguments.map(argument => ts.visitNode(argument, visitor))]
43
+ )
44
+ }
45
+ if (ts.isShorthandPropertyAssignment(node) && setters.has(node.name.text) && !isShadowedIdentifier(node.name, expression)) {
46
+ return factory.createPropertyAssignment(node.name, setterReference(factory, setters.get(node.name.text)))
47
+ }
48
+ if (ts.isIdentifier(node) && setters.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, expression)) {
49
+ return setterReference(factory, setters.get(node.text))
50
+ }
51
+ if (ts.isShorthandPropertyAssignment(node) && stateNames.has(node.name.text) && !isShadowedIdentifier(node.name, expression)) {
52
+ if (snapshots.has(node.name.text) && insideNestedFunction(node, expression)) return factory.createPropertyAssignment(node.name, snapshots.get(node.name.text))
53
+ return factory.createPropertyAssignment(node.name, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"), undefined, [factory.createStringLiteral(node.name.text)]))
54
+ }
55
+ if (ts.isIdentifier(node) && stateNames.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, expression)) {
56
+ if (snapshots.has(node.text) && insideNestedFunction(node, expression)) return snapshots.get(node.text)
57
+ return factory.createCallExpression(
58
+ factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"),
59
+ undefined,
60
+ [factory.createStringLiteral(node.text)]
61
+ )
62
+ }
63
+ if (ts.isShorthandPropertyAssignment(node) && captures.has(node.name.text) && !isShadowedIdentifier(node.name, expression)) {
64
+ if (captureSnapshots.has(node.name.text) && insideNestedFunction(node, expression)) return factory.createPropertyAssignment(node.name, captureSnapshots.get(node.name.text))
65
+ return factory.createPropertyAssignment(node.name, scopeRead(factory, node.name.text))
66
+ }
67
+ if (ts.isIdentifier(node) && captures.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, expression)) {
68
+ if (captureSnapshots.has(node.text) && insideNestedFunction(node, expression)) return captureSnapshots.get(node.text)
69
+ return scopeRead(factory, node.text)
70
+ }
71
+ return ts.visitEachChild(node, visitor, context)
72
+ }
73
+ return ts.visitNode(root, visitor)
74
+ }
75
+ const transformed = ts.transform(expression.body, [transformer])
76
+ try {
77
+ let body = ts.isBlock(expression.body)
78
+ ? transformed.transformed[0]
79
+ : factory.createBlock([factory.createReturnStatement(transformed.transformed[0])], true)
80
+ const snapshotDeclarations = [
81
+ ...[...snapshots].map(([name, identifier]) => factory.createVariableDeclaration(identifier, undefined, undefined, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"), undefined, [factory.createStringLiteral(name)]))),
82
+ ...[...captureSnapshots].map(([name, identifier]) => factory.createVariableDeclaration(identifier, undefined, undefined, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "scope"), undefined, [factory.createStringLiteral(name)])))
83
+ ]
84
+ if (snapshotDeclarations.length) body = factory.updateBlock(body, [
85
+ factory.createVariableStatement(undefined, factory.createVariableDeclarationList(snapshotDeclarations, ts.NodeFlags.Const)),
86
+ ...body.statements
87
+ ])
88
+ const modifiers = [factory.createModifier(ts.SyntaxKind.ExportKeyword)]
89
+ if (expression.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) modifiers.push(factory.createModifier(ts.SyntaxKind.AsyncKeyword))
90
+ const declaration = factory.createFunctionDeclaration(
91
+ modifiers,
92
+ expression.asteriskToken,
93
+ exportName,
94
+ undefined,
95
+ [factory.createParameterDeclaration(undefined, undefined, "__k"), ...expression.parameters],
96
+ undefined,
97
+ body
98
+ )
99
+ return {
100
+ code: ts.createPrinter().printNode(ts.EmitHint.Unspecified, declaration, expression.getSourceFile()),
101
+ stateSnapshots: [...snapshotNames],
102
+ captureSnapshots: [...captureSnapshotNames]
103
+ }
104
+ } finally {
105
+ transformed.dispose()
106
+ }
107
+ }
108
+
109
+ function nestedCaptureNames(expression, captures) {
110
+ const names = new Set()
111
+ const visit = node => {
112
+ if (ts.isIdentifier(node) && captures.has(node.text) && isReferenceIdentifier(node) && insideNestedFunction(node, expression) && !isShadowedIdentifier(node, expression)) names.add(node.text)
113
+ ts.forEachChild(node, visit)
114
+ }
115
+ visit(expression.body)
116
+ return names
117
+ }
118
+
119
+ function nestedStateNames(expression, setters, liveStates = new Set()) {
120
+ const states = new Set(setters.values())
121
+ const names = new Set()
122
+ const visit = node => {
123
+ if (ts.isIdentifier(node) && states.has(node.text) && !liveStates.has(node.text) && isReferenceIdentifier(node) && insideNestedFunction(node, expression) && !isShadowedIdentifier(node, expression)) names.add(node.text)
124
+ ts.forEachChild(node, visit)
125
+ }
126
+ visit(expression.body)
127
+ return names
128
+ }
129
+
130
+ function insideNestedFunction(node, root) {
131
+ for (let current = node.parent; current && current !== root; current = current.parent) {
132
+ if (isFunctionLike(current)) return true
133
+ }
134
+ return false
135
+ }
136
+
137
+ function setterReference(factory, stateName) {
138
+ return factory.createArrowFunction(
139
+ undefined,
140
+ undefined,
141
+ [factory.createParameterDeclaration(undefined, undefined, "value")],
142
+ undefined,
143
+ factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
144
+ factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"), undefined, [factory.createStringLiteral(stateName), factory.createIdentifier("value")])
145
+ )
146
+ }
147
+
148
+ function reducerReference(factory, reducer) {
149
+ const action = factory.createUniqueName("__kAction")
150
+ return factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, action)], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), reducerDispatch(factory, reducer, action))
151
+ }
152
+
153
+ function reducerDispatch(factory, reducer, action) {
154
+ if (reducer.store) return zustandActionDispatch(factory, reducer, [action])
155
+ const previous = factory.createUniqueName("__kPrevious")
156
+ 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]))
157
+ return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"), undefined, [factory.createStringLiteral(reducer.state), update])
158
+ }
159
+
160
+ function zustandActionDispatch(factory, reducer, args) {
161
+ const previous = factory.createUniqueName("__kPrevious")
162
+ const current = factory.createUniqueName("__kStore")
163
+ const updateValue = factory.createUniqueName("__kUpdate")
164
+ const partial = factory.createUniqueName("__kPartial")
165
+ const action = factory.createUniqueName("__kAction")
166
+ const set = factory.createIdentifier(reducer.store.setName)
167
+ const merge = factory.createExpressionStatement(factory.createBinaryExpression(current, factory.createToken(ts.SyntaxKind.EqualsToken), factory.createObjectLiteralExpression([
168
+ factory.createSpreadAssignment(current),
169
+ factory.createSpreadAssignment(partial)
170
+ ])))
171
+ const setBody = factory.createBlock([
172
+ factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(partial, undefined, undefined, factory.createConditionalExpression(
173
+ factory.createBinaryExpression(factory.createTypeOfExpression(updateValue), factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), factory.createStringLiteral("function")),
174
+ undefined,
175
+ factory.createCallExpression(updateValue, undefined, [current]),
176
+ undefined,
177
+ updateValue
178
+ ))], ts.NodeFlags.Const)),
179
+ merge
180
+ ], true)
181
+ const body = factory.createBlock([
182
+ factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(current, undefined, undefined, factory.createObjectLiteralExpression([factory.createPropertyAssignment(reducer.store.field, previous)]))], ts.NodeFlags.Let)),
183
+ 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)),
184
+ factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(action, undefined, undefined, synthesizeTree(reducer.store.actions.get(reducer.action)))], ts.NodeFlags.Const)),
185
+ factory.createExpressionStatement(factory.createCallExpression(action, undefined, args)),
186
+ factory.createReturnStatement(factory.createPropertyAccessExpression(current, reducer.store.field))
187
+ ], true)
188
+ const update = factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, previous)], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), body)
189
+ return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"), undefined, [factory.createStringLiteral(reducer.state), update])
190
+ }
191
+
192
+ function printReactiveBinding({ exportName, expression, captures, states }) {
193
+ const factory = ts.factory
194
+ const transformer = context => root => {
195
+ const visitor = node => {
196
+ if (ts.isShorthandPropertyAssignment(node) && states.has(node.name.text)) {
197
+ return factory.createPropertyAssignment(
198
+ node.name,
199
+ factory.createCallExpression(
200
+ factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"),
201
+ undefined,
202
+ [factory.createStringLiteral(node.name.text)]
203
+ )
204
+ )
205
+ }
206
+ if (ts.isIdentifier(node) && states.has(node.text) && isReferenceIdentifier(node) && !isShadowedByParameter(node, expression)) {
207
+ return factory.createCallExpression(
208
+ factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"),
209
+ undefined,
210
+ [factory.createStringLiteral(node.text)]
211
+ )
212
+ }
213
+ if (ts.isShorthandPropertyAssignment(node) && captures.has(node.name.text)) {
214
+ return factory.createPropertyAssignment(node.name, scopeRead(factory, node.name.text))
215
+ }
216
+ if (ts.isIdentifier(node) && captures.has(node.text) && isReferenceIdentifier(node)) {
217
+ return scopeRead(factory, node.text)
218
+ }
219
+ return ts.visitEachChild(node, visitor, context)
220
+ }
221
+ return ts.visitNode(root, visitor)
222
+ }
223
+ const transformed = ts.transform(expression, [transformer])
224
+ try {
225
+ const declaration = factory.createFunctionDeclaration(
226
+ [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
227
+ undefined,
228
+ exportName,
229
+ undefined,
230
+ [factory.createParameterDeclaration(undefined, undefined, "__k")],
231
+ undefined,
232
+ factory.createBlock([factory.createReturnStatement(transformed.transformed[0])], true)
233
+ )
234
+ return ts.createPrinter().printNode(ts.EmitHint.Unspecified, declaration, expression.getSourceFile())
235
+ } finally {
236
+ transformed.dispose()
237
+ }
238
+ }
239
+
240
+ function printListExpression({ exportName, expression, item, index, states = new Set() }) {
241
+ const factory = ts.factory
242
+ const transformer = context => root => {
243
+ const visitor = node => {
244
+ if (ts.isShorthandPropertyAssignment(node) && states.has(node.name.text)) {
245
+ return factory.createPropertyAssignment(node.name, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"), undefined, [factory.createStringLiteral(node.name.text)]))
246
+ }
247
+ if (ts.isIdentifier(node) && states.has(node.text) && isReferenceIdentifier(node) && !isShadowedByParameter(node, expression)) {
248
+ return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"), undefined, [factory.createStringLiteral(node.text)])
249
+ }
250
+ return ts.visitEachChild(node, visitor, context)
251
+ }
252
+ return ts.visitNode(root, visitor)
253
+ }
254
+ const transformed = ts.transform(expression, [transformer])
255
+ const declaration = ts.factory.createFunctionDeclaration(
256
+ [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
257
+ undefined,
258
+ exportName,
259
+ undefined,
260
+ [ts.factory.createParameterDeclaration(undefined, undefined, item), ts.factory.createParameterDeclaration(undefined, undefined, index ?? "__kIndex"), ts.factory.createParameterDeclaration(undefined, undefined, "__k")],
261
+ undefined,
262
+ ts.factory.createBlock([ts.factory.createReturnStatement(transformed.transformed[0])], true)
263
+ )
264
+ try {
265
+ return ts.createPrinter().printNode(ts.EmitHint.Unspecified, declaration, expression.getSourceFile())
266
+ } finally {
267
+ transformed.dispose()
268
+ }
269
+ }
270
+
271
+ function scopeRead(factory, name) {
272
+ return factory.createCallExpression(
273
+ factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "scope"),
274
+ undefined,
275
+ [factory.createStringLiteral(name)]
276
+ )
277
+ }
278
+ }
@@ -1,23 +1,41 @@
1
1
  export function createModuleIR(file) {
2
- return { version: 1, file, signals: [], handlers: [] }
2
+ return { version: 1, file, signals: [], handlers: [], bindings: [], derived: [], imports: [], clientModules: [] }
3
3
  }
4
4
 
5
5
  export function registerCommandHandler(moduleIR, commands, source, scope = "module") {
6
6
  const slots = new Map(moduleIR.signals.map(signal => [signal.key, signal.slot]))
7
- for (const { state } of commands) {
8
- const key = `${scope}:${state}`
7
+ for (const { state, owner = scope } of commands) {
8
+ const key = `${owner}:${state}`
9
9
  if (slots.has(key)) continue
10
10
  const slot = moduleIR.signals.length
11
11
  slots.set(key, slot)
12
12
  moduleIR.signals.push({ slot, key, debugName: state })
13
13
  }
14
- const signal = state => slots.get(`${scope}:${state}`) ?? slots.get(state)
14
+ const signal = command => slots.get(`${command.owner ?? scope}:${command.state}`)
15
15
  const handler = {
16
16
  slot: moduleIR.handlers.length,
17
17
  kind: "commands",
18
- commands: commands.map(({ operation, state, value, syntax }) => ({ operation, signal: signal(state), value, ...(syntax ? { syntax } : {}) })),
18
+ commands: commands.map(({ operation, state, owner, value, syntax }) => ({ operation, signal: signal({ state, owner }), value, ...(syntax ? { syntax } : {}) })),
19
19
  ...(source ? { source } : {})
20
20
  }
21
21
  moduleIR.handlers.push(handler)
22
22
  return handler
23
23
  }
24
+
25
+ export function registerModuleHandler(moduleIR, descriptor) {
26
+ const handler = { slot: moduleIR.handlers.length, kind: "module-export", ...descriptor }
27
+ moduleIR.handlers.push(handler)
28
+ return handler
29
+ }
30
+
31
+ export function registerBinding(moduleIR, descriptor) {
32
+ const binding = { slot: moduleIR.bindings.length, kind: "module-export", ...descriptor }
33
+ moduleIR.bindings.push(binding)
34
+ return binding
35
+ }
36
+
37
+ export function registerDerived(moduleIR, descriptor) {
38
+ const derived = { slot: moduleIR.derived.length, ...descriptor }
39
+ moduleIR.derived.push(derived)
40
+ return derived
41
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.8.17",
3
+ "version": "0.8.19",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",