@esportsplus/reactivity 0.24.1 → 0.24.3
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/build/transformer/factory.d.ts +13 -0
- package/build/transformer/factory.js +36 -0
- package/build/transformer/index.d.ts +4 -6
- package/build/transformer/index.js +99 -39
- package/build/transformer/plugins/vite.js +7 -4
- package/build/transformer/transforms/array.d.ts +2 -2
- package/build/transformer/transforms/array.js +20 -22
- package/build/transformer/transforms/object.d.ts +9 -3
- package/build/transformer/transforms/object.js +88 -99
- package/build/transformer/transforms/primitives.d.ts +3 -3
- package/build/transformer/transforms/primitives.js +131 -184
- package/build/types.d.ts +6 -7
- package/package.json +1 -1
- package/src/transformer/factory.ts +141 -0
- package/src/transformer/index.ts +172 -45
- package/src/transformer/plugins/vite.ts +13 -5
- package/src/transformer/transforms/array.ts +35 -31
- package/src/transformer/transforms/object.ts +292 -137
- package/src/transformer/transforms/primitives.ts +230 -236
- package/src/types.ts +8 -9
- package/test/vite.config.ts +1 -1
- package/build/transformer/transforms/utilities.d.ts +0 -8
- package/build/transformer/transforms/utilities.js +0 -27
- package/src/transformer/transforms/utilities.ts +0 -45
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import { uid
|
|
2
|
-
import type { BindingType, Bindings } from '~/types';
|
|
3
|
-
import {
|
|
1
|
+
import { uid } from '@esportsplus/typescript/transformer';
|
|
2
|
+
import type { BindingType, Bindings, Namespaces } from '~/types';
|
|
3
|
+
import {
|
|
4
|
+
createCommaExpr,
|
|
5
|
+
createComputedCall,
|
|
6
|
+
createPostfixIncrementExpr,
|
|
7
|
+
createReadCall,
|
|
8
|
+
createSetCall,
|
|
9
|
+
createSignalCall
|
|
10
|
+
} from '../factory';
|
|
4
11
|
import { ts } from '@esportsplus/typescript';
|
|
5
12
|
|
|
6
13
|
|
|
7
|
-
interface ArgContext {
|
|
8
|
-
argStart: number;
|
|
9
|
-
innerReplacements: Replacement[];
|
|
10
|
-
neededImports: Set<string>;
|
|
11
|
-
scopedBindings: ScopeBinding[];
|
|
12
|
-
sourceFile: ts.SourceFile;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
14
|
interface ScopeBinding {
|
|
16
15
|
name: string;
|
|
17
16
|
scope: ts.Node;
|
|
@@ -20,12 +19,12 @@ interface ScopeBinding {
|
|
|
20
19
|
|
|
21
20
|
interface TransformContext {
|
|
22
21
|
bindings: Bindings;
|
|
23
|
-
|
|
22
|
+
context: ts.TransformationContext;
|
|
23
|
+
factory: ts.NodeFactory;
|
|
24
24
|
hasReactiveImport: boolean;
|
|
25
25
|
neededImports: Set<string>;
|
|
26
|
-
|
|
26
|
+
ns: Namespaces;
|
|
27
27
|
scopedBindings: ScopeBinding[];
|
|
28
|
-
sourceFile: ts.SourceFile;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
|
|
@@ -58,14 +57,14 @@ function findEnclosingScope(node: ts.Node): ts.Node {
|
|
|
58
57
|
|
|
59
58
|
while (current) {
|
|
60
59
|
if (
|
|
60
|
+
ts.isArrowFunction(current) ||
|
|
61
61
|
ts.isBlock(current) ||
|
|
62
|
-
ts.
|
|
62
|
+
ts.isForInStatement(current) ||
|
|
63
|
+
ts.isForOfStatement(current) ||
|
|
64
|
+
ts.isForStatement(current) ||
|
|
63
65
|
ts.isFunctionDeclaration(current) ||
|
|
64
66
|
ts.isFunctionExpression(current) ||
|
|
65
|
-
ts.
|
|
66
|
-
ts.isForStatement(current) ||
|
|
67
|
-
ts.isForInStatement(current) ||
|
|
68
|
-
ts.isForOfStatement(current)
|
|
67
|
+
ts.isSourceFile(current)
|
|
69
68
|
) {
|
|
70
69
|
return current;
|
|
71
70
|
}
|
|
@@ -76,70 +75,82 @@ function findEnclosingScope(node: ts.Node): ts.Node {
|
|
|
76
75
|
return node.getSourceFile();
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
function getCompoundOperator(kind: ts.SyntaxKind):
|
|
78
|
+
function getCompoundOperator(kind: ts.SyntaxKind): ts.BinaryOperator {
|
|
80
79
|
if (kind === ts.SyntaxKind.PlusEqualsToken) {
|
|
81
|
-
return
|
|
80
|
+
return ts.SyntaxKind.PlusToken;
|
|
82
81
|
}
|
|
83
82
|
else if (kind === ts.SyntaxKind.MinusEqualsToken) {
|
|
84
|
-
return
|
|
83
|
+
return ts.SyntaxKind.MinusToken;
|
|
85
84
|
}
|
|
86
85
|
else if (kind === ts.SyntaxKind.AsteriskEqualsToken) {
|
|
87
|
-
return
|
|
86
|
+
return ts.SyntaxKind.AsteriskToken;
|
|
88
87
|
}
|
|
89
88
|
else if (kind === ts.SyntaxKind.SlashEqualsToken) {
|
|
90
|
-
return
|
|
89
|
+
return ts.SyntaxKind.SlashToken;
|
|
91
90
|
}
|
|
92
91
|
else if (kind === ts.SyntaxKind.PercentEqualsToken) {
|
|
93
|
-
return
|
|
92
|
+
return ts.SyntaxKind.PercentToken;
|
|
94
93
|
}
|
|
95
94
|
else if (kind === ts.SyntaxKind.AsteriskAsteriskEqualsToken) {
|
|
96
|
-
return
|
|
95
|
+
return ts.SyntaxKind.AsteriskAsteriskToken;
|
|
97
96
|
}
|
|
98
97
|
else if (kind === ts.SyntaxKind.AmpersandEqualsToken) {
|
|
99
|
-
return
|
|
98
|
+
return ts.SyntaxKind.AmpersandToken;
|
|
100
99
|
}
|
|
101
100
|
else if (kind === ts.SyntaxKind.BarEqualsToken) {
|
|
102
|
-
return
|
|
101
|
+
return ts.SyntaxKind.BarToken;
|
|
103
102
|
}
|
|
104
103
|
else if (kind === ts.SyntaxKind.CaretEqualsToken) {
|
|
105
|
-
return
|
|
104
|
+
return ts.SyntaxKind.CaretToken;
|
|
106
105
|
}
|
|
107
106
|
else if (kind === ts.SyntaxKind.LessThanLessThanEqualsToken) {
|
|
108
|
-
return
|
|
107
|
+
return ts.SyntaxKind.LessThanLessThanToken;
|
|
109
108
|
}
|
|
110
109
|
else if (kind === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken) {
|
|
111
|
-
return
|
|
110
|
+
return ts.SyntaxKind.GreaterThanGreaterThanToken;
|
|
112
111
|
}
|
|
113
112
|
else if (kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken) {
|
|
114
|
-
return
|
|
113
|
+
return ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken;
|
|
115
114
|
}
|
|
116
115
|
else if (kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken) {
|
|
117
|
-
return
|
|
116
|
+
return ts.SyntaxKind.AmpersandAmpersandToken;
|
|
118
117
|
}
|
|
119
118
|
else if (kind === ts.SyntaxKind.BarBarEqualsToken) {
|
|
120
|
-
return
|
|
119
|
+
return ts.SyntaxKind.BarBarToken;
|
|
121
120
|
}
|
|
122
121
|
else if (kind === ts.SyntaxKind.QuestionQuestionEqualsToken) {
|
|
123
|
-
return
|
|
122
|
+
return ts.SyntaxKind.QuestionQuestionToken;
|
|
124
123
|
}
|
|
125
124
|
else {
|
|
126
|
-
return
|
|
125
|
+
return ts.SyntaxKind.PlusToken;
|
|
127
126
|
}
|
|
128
127
|
}
|
|
129
128
|
|
|
130
|
-
function
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
function isAssignmentOperator(kind: ts.SyntaxKind): 'compound' | 'simple' | false {
|
|
130
|
+
if (kind === ts.SyntaxKind.EqualsToken) {
|
|
131
|
+
return 'simple';
|
|
132
|
+
}
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
if (kind >= ts.SyntaxKind.PlusEqualsToken && kind <= ts.SyntaxKind.CaretEqualsToken) {
|
|
135
|
+
return 'compound';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (
|
|
139
|
+
kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken ||
|
|
140
|
+
kind === ts.SyntaxKind.BarBarEqualsToken ||
|
|
141
|
+
kind === ts.SyntaxKind.QuestionQuestionEqualsToken
|
|
142
|
+
) {
|
|
143
|
+
return 'compound';
|
|
137
144
|
}
|
|
138
145
|
|
|
139
146
|
return false;
|
|
140
147
|
}
|
|
141
148
|
|
|
142
|
-
function isInDeclarationInit(node: ts.Node): boolean {
|
|
149
|
+
function isInDeclarationInit(node: ts.Node | undefined): boolean {
|
|
150
|
+
if (!node || !node.parent) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
143
154
|
let parent = node.parent;
|
|
144
155
|
|
|
145
156
|
if (ts.isVariableDeclaration(parent) && parent.initializer === node) {
|
|
@@ -163,16 +174,13 @@ function isInScope(reference: ts.Node, binding: ScopeBinding): boolean {
|
|
|
163
174
|
return false;
|
|
164
175
|
}
|
|
165
176
|
|
|
166
|
-
function isReactiveReassignment(node: ts.
|
|
167
|
-
let
|
|
177
|
+
function isReactiveReassignment(node: ts.BinaryExpression): boolean {
|
|
178
|
+
let right = node.right;
|
|
168
179
|
|
|
169
180
|
if (
|
|
170
|
-
ts.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
ts.isCallExpression(node) &&
|
|
174
|
-
ts.isIdentifier((node as ts.CallExpression).expression) &&
|
|
175
|
-
((node as ts.CallExpression).expression as ts.Identifier).text === 'reactive'
|
|
181
|
+
ts.isCallExpression(right) &&
|
|
182
|
+
ts.isIdentifier(right.expression) &&
|
|
183
|
+
right.expression.text === 'reactive'
|
|
176
184
|
) {
|
|
177
185
|
return true;
|
|
178
186
|
}
|
|
@@ -180,41 +188,8 @@ function isReactiveReassignment(node: ts.Node): boolean {
|
|
|
180
188
|
return false;
|
|
181
189
|
}
|
|
182
190
|
|
|
183
|
-
function
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (ts.isBinaryExpression(parent) && parent.left === node) {
|
|
187
|
-
let op = parent.operatorToken.kind;
|
|
188
|
-
|
|
189
|
-
if (op === ts.SyntaxKind.EqualsToken) {
|
|
190
|
-
return 'simple';
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (op >= ts.SyntaxKind.PlusEqualsToken && op <= ts.SyntaxKind.CaretEqualsToken) {
|
|
194
|
-
return 'compound';
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (
|
|
198
|
-
op === ts.SyntaxKind.AmpersandAmpersandEqualsToken ||
|
|
199
|
-
op === ts.SyntaxKind.BarBarEqualsToken ||
|
|
200
|
-
op === ts.SyntaxKind.QuestionQuestionEqualsToken
|
|
201
|
-
) {
|
|
202
|
-
return 'compound';
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (ts.isPostfixUnaryExpression(parent) || ts.isPrefixUnaryExpression(parent)) {
|
|
207
|
-
let op = parent.operator;
|
|
208
|
-
|
|
209
|
-
if (op === ts.SyntaxKind.PlusPlusToken || op === ts.SyntaxKind.MinusMinusToken) {
|
|
210
|
-
return 'increment';
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function visit(ctx: TransformContext, node: ts.Node): void {
|
|
191
|
+
function visit(ctx: TransformContext, node: ts.Node): ts.Node {
|
|
192
|
+
// Check for reactive import
|
|
218
193
|
if (
|
|
219
194
|
ts.isImportDeclaration(node) &&
|
|
220
195
|
ts.isStringLiteral(node.moduleSpecifier) &&
|
|
@@ -232,6 +207,7 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
232
207
|
}
|
|
233
208
|
}
|
|
234
209
|
|
|
210
|
+
// Transform reactive() calls to signal() or computed()
|
|
235
211
|
if (
|
|
236
212
|
ctx.hasReactiveImport &&
|
|
237
213
|
ts.isCallExpression(node) &&
|
|
@@ -245,10 +221,11 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
245
221
|
if (classification) {
|
|
246
222
|
let varName: string | null = null;
|
|
247
223
|
|
|
248
|
-
if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
224
|
+
if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
249
225
|
varName = node.parent.name.text;
|
|
250
226
|
}
|
|
251
227
|
else if (
|
|
228
|
+
node.parent &&
|
|
252
229
|
ts.isBinaryExpression(node.parent) &&
|
|
253
230
|
node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
254
231
|
ts.isIdentifier(node.parent.left)
|
|
@@ -259,203 +236,220 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
259
236
|
if (varName) {
|
|
260
237
|
let scope = findEnclosingScope(node);
|
|
261
238
|
|
|
262
|
-
ctx.scopedBindings.push({ name: varName, scope, type: classification });
|
|
263
239
|
ctx.bindings.set(varName, classification);
|
|
240
|
+
ctx.scopedBindings.push({ name: varName, scope, type: classification });
|
|
264
241
|
}
|
|
265
242
|
|
|
266
243
|
if (classification === 'computed') {
|
|
267
|
-
ctx.
|
|
268
|
-
end: arg.end,
|
|
269
|
-
start: arg.getStart(ctx.sourceFile)
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
let argCtx: ArgContext = {
|
|
273
|
-
argStart: arg.getStart(ctx.sourceFile),
|
|
274
|
-
innerReplacements: [],
|
|
275
|
-
neededImports: ctx.neededImports,
|
|
276
|
-
scopedBindings: ctx.scopedBindings,
|
|
277
|
-
sourceFile: ctx.sourceFile
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
visitArg(argCtx, arg);
|
|
281
|
-
|
|
282
|
-
let argText = applyReplacements(arg.getText(ctx.sourceFile), argCtx.innerReplacements);
|
|
244
|
+
ctx.neededImports.add('computed');
|
|
283
245
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
newText: `computed(${argText})`,
|
|
287
|
-
start: node.pos
|
|
288
|
-
});
|
|
246
|
+
// Transform the function body to wrap reactive reads
|
|
247
|
+
let transformedArg = ts.visitEachChild(arg, n => visitComputedArg(ctx, n), ctx.context);
|
|
289
248
|
|
|
290
|
-
ctx.
|
|
249
|
+
return createComputedCall(ctx.factory, ctx.ns.reactivity, transformedArg as ts.Expression);
|
|
291
250
|
}
|
|
292
251
|
else {
|
|
293
|
-
let argText = arg.getText(ctx.sourceFile);
|
|
294
|
-
|
|
295
|
-
ctx.replacements.push({
|
|
296
|
-
end: node.end,
|
|
297
|
-
newText: `signal(${argText})`,
|
|
298
|
-
start: node.pos
|
|
299
|
-
});
|
|
300
|
-
|
|
301
252
|
ctx.neededImports.add('signal');
|
|
253
|
+
|
|
254
|
+
return createSignalCall(ctx.factory, ctx.ns.reactivity, arg);
|
|
302
255
|
}
|
|
303
256
|
}
|
|
304
257
|
}
|
|
305
258
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
259
|
+
// Handle binary expressions (assignments) with reactive left side
|
|
260
|
+
if (ts.isBinaryExpression(node) && ts.isIdentifier(node.left)) {
|
|
261
|
+
let assignType = isAssignmentOperator(node.operatorToken.kind);
|
|
311
262
|
|
|
312
|
-
|
|
263
|
+
if (assignType) {
|
|
264
|
+
let binding = findBinding(ctx.scopedBindings, node.left.text, node.left);
|
|
313
265
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
266
|
+
if (binding && binding.type !== 'computed' && !isReactiveReassignment(node)) {
|
|
267
|
+
ctx.neededImports.add('set');
|
|
318
268
|
|
|
319
|
-
|
|
320
|
-
|
|
269
|
+
let factory = ctx.factory,
|
|
270
|
+
name = node.left.text,
|
|
271
|
+
signalIdent = factory.createIdentifier(name),
|
|
272
|
+
transformedRight = ts.visitEachChild(node.right, n => visit(ctx, n), ctx.context) as ts.Expression;
|
|
321
273
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
!(ts.isTypeOfExpression(node.parent) && node.parent.expression === node)
|
|
326
|
-
) {
|
|
327
|
-
let writeCtx = isWriteContext(node);
|
|
328
|
-
|
|
329
|
-
if (writeCtx) {
|
|
330
|
-
if (binding.type !== 'computed') {
|
|
331
|
-
ctx.neededImports.add('set');
|
|
332
|
-
|
|
333
|
-
let parent = node.parent;
|
|
334
|
-
|
|
335
|
-
if (writeCtx === 'simple' && ts.isBinaryExpression(parent)) {
|
|
336
|
-
let valueText = parent.right.getText(ctx.sourceFile);
|
|
337
|
-
|
|
338
|
-
ctx.replacements.push({
|
|
339
|
-
end: parent.end,
|
|
340
|
-
newText: `set(${name}, ${valueText})`,
|
|
341
|
-
start: parent.pos
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
else if (writeCtx === 'compound' && ts.isBinaryExpression(parent)) {
|
|
345
|
-
let op = getCompoundOperator(parent.operatorToken.kind),
|
|
346
|
-
valueText = parent.right.getText(ctx.sourceFile);
|
|
347
|
-
|
|
348
|
-
ctx.replacements.push({
|
|
349
|
-
end: parent.end,
|
|
350
|
-
newText: `set(${name}, ${name}.value ${op} ${valueText})`,
|
|
351
|
-
start: parent.pos
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
else if (writeCtx === 'increment') {
|
|
355
|
-
let isPrefix = ts.isPrefixUnaryExpression(parent),
|
|
356
|
-
op = (parent as ts.PrefixUnaryExpression | ts.PostfixUnaryExpression).operator,
|
|
357
|
-
delta = op === ts.SyntaxKind.PlusPlusToken ? '+ 1' : '- 1';
|
|
358
|
-
|
|
359
|
-
if (ts.isExpressionStatement(parent.parent)) {
|
|
360
|
-
ctx.replacements.push({
|
|
361
|
-
end: parent.end,
|
|
362
|
-
newText: `set(${name}, ${name}.value ${delta})`,
|
|
363
|
-
start: parent.pos
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
else if (isPrefix) {
|
|
367
|
-
ctx.replacements.push({
|
|
368
|
-
end: parent.end,
|
|
369
|
-
newText: `(set(${name}, ${name}.value ${delta}), ${name}.value)`,
|
|
370
|
-
start: parent.pos
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
else {
|
|
374
|
-
let tmp = uid('tmp');
|
|
375
|
-
|
|
376
|
-
ctx.replacements.push({
|
|
377
|
-
end: parent.end,
|
|
378
|
-
newText: `((${tmp}) => (set(${name}, ${tmp} ${delta}), ${tmp}))(${name}.value)`,
|
|
379
|
-
start: parent.pos
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
274
|
+
if (assignType === 'simple') {
|
|
275
|
+
// x = value → ns.set(x, value)
|
|
276
|
+
return createSetCall(factory, ctx.ns.reactivity, signalIdent, transformedRight);
|
|
384
277
|
}
|
|
385
278
|
else {
|
|
386
|
-
|
|
279
|
+
// x += value → ns.set(x, x.value + value)
|
|
280
|
+
let op = getCompoundOperator(node.operatorToken.kind),
|
|
281
|
+
valueAccess = factory.createPropertyAccessExpression(signalIdent, 'value');
|
|
282
|
+
|
|
283
|
+
return createSetCall(
|
|
284
|
+
factory,
|
|
285
|
+
ctx.ns.reactivity,
|
|
286
|
+
signalIdent,
|
|
287
|
+
factory.createBinaryExpression(valueAccess, op, transformedRight)
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Handle prefix unary expressions (++x, --x) with reactive operand
|
|
295
|
+
if (ts.isPrefixUnaryExpression(node) && ts.isIdentifier(node.operand)) {
|
|
296
|
+
let op = node.operator;
|
|
387
297
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
298
|
+
if (op === ts.SyntaxKind.PlusPlusToken || op === ts.SyntaxKind.MinusMinusToken) {
|
|
299
|
+
let binding = findBinding(ctx.scopedBindings, node.operand.text, node.operand);
|
|
300
|
+
|
|
301
|
+
if (binding && binding.type !== 'computed') {
|
|
302
|
+
ctx.neededImports.add('set');
|
|
303
|
+
|
|
304
|
+
let delta = op === ts.SyntaxKind.PlusPlusToken ? ts.SyntaxKind.PlusToken : ts.SyntaxKind.MinusToken,
|
|
305
|
+
factory = ctx.factory,
|
|
306
|
+
name = node.operand.text,
|
|
307
|
+
signalIdent = factory.createIdentifier(name),
|
|
308
|
+
valueAccess = factory.createPropertyAccessExpression(signalIdent, 'value');
|
|
309
|
+
|
|
310
|
+
if (node.parent && ts.isExpressionStatement(node.parent)) {
|
|
311
|
+
// ++x as statement → ns.set(x, x.value + 1)
|
|
312
|
+
return createSetCall(
|
|
313
|
+
factory,
|
|
314
|
+
ctx.ns.reactivity,
|
|
315
|
+
signalIdent,
|
|
316
|
+
factory.createBinaryExpression(valueAccess, delta, factory.createNumericLiteral(1))
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
// ++x in expression → (ns.set(x, x.value + 1), x.value)
|
|
321
|
+
return createCommaExpr(
|
|
322
|
+
factory,
|
|
323
|
+
createSetCall(
|
|
324
|
+
factory,
|
|
325
|
+
ctx.ns.reactivity,
|
|
326
|
+
signalIdent,
|
|
327
|
+
factory.createBinaryExpression(valueAccess, delta, factory.createNumericLiteral(1))
|
|
328
|
+
),
|
|
329
|
+
factory.createPropertyAccessExpression(factory.createIdentifier(name), 'value')
|
|
330
|
+
);
|
|
393
331
|
}
|
|
394
332
|
}
|
|
395
333
|
}
|
|
396
334
|
}
|
|
397
335
|
|
|
398
|
-
|
|
399
|
-
|
|
336
|
+
// Handle postfix unary expressions (x++, x--) with reactive operand
|
|
337
|
+
if (ts.isPostfixUnaryExpression(node) && ts.isIdentifier(node.operand)) {
|
|
338
|
+
let op = node.operator;
|
|
339
|
+
|
|
340
|
+
if (op === ts.SyntaxKind.PlusPlusToken || op === ts.SyntaxKind.MinusMinusToken) {
|
|
341
|
+
let binding = findBinding(ctx.scopedBindings, node.operand.text, node.operand);
|
|
342
|
+
|
|
343
|
+
if (binding && binding.type !== 'computed') {
|
|
344
|
+
ctx.neededImports.add('set');
|
|
345
|
+
|
|
346
|
+
let delta = op === ts.SyntaxKind.PlusPlusToken ? ts.SyntaxKind.PlusToken : ts.SyntaxKind.MinusToken,
|
|
347
|
+
factory = ctx.factory,
|
|
348
|
+
name = node.operand.text,
|
|
349
|
+
signalIdent = factory.createIdentifier(name),
|
|
350
|
+
valueAccess = factory.createPropertyAccessExpression(signalIdent, 'value');
|
|
351
|
+
|
|
352
|
+
if (node.parent && ts.isExpressionStatement(node.parent)) {
|
|
353
|
+
// x++ as statement → ns.set(x, x.value + 1)
|
|
354
|
+
return createSetCall(
|
|
355
|
+
factory,
|
|
356
|
+
ctx.ns.reactivity,
|
|
357
|
+
signalIdent,
|
|
358
|
+
factory.createBinaryExpression(valueAccess, delta, factory.createNumericLiteral(1))
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
// x++ in expression → ((tmp) => (ns.set(x, tmp + 1), tmp))(x.value)
|
|
363
|
+
return createPostfixIncrementExpr(factory, ctx.ns.reactivity, uid('tmp'), name, delta);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
400
368
|
|
|
401
|
-
|
|
402
|
-
if (ts.isIdentifier(node)) {
|
|
369
|
+
// Handle reactive variable reads (not in write context)
|
|
370
|
+
if (ts.isIdentifier(node) && node.parent && !isInDeclarationInit(node.parent)) {
|
|
371
|
+
// Skip property names in property access expressions
|
|
403
372
|
if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
|
|
404
|
-
ts.
|
|
405
|
-
return;
|
|
373
|
+
return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
|
|
406
374
|
}
|
|
407
375
|
|
|
408
|
-
if
|
|
409
|
-
|
|
410
|
-
return;
|
|
376
|
+
// Skip if this identifier is the left side of an assignment (handled above)
|
|
377
|
+
if (ts.isBinaryExpression(node.parent) && node.parent.left === node) {
|
|
378
|
+
return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Skip if this is the operand of a unary expression (handled above)
|
|
382
|
+
if (
|
|
383
|
+
(ts.isPrefixUnaryExpression(node.parent) || ts.isPostfixUnaryExpression(node.parent)) &&
|
|
384
|
+
node.parent.operand === node
|
|
385
|
+
) {
|
|
386
|
+
return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Skip typeof checks
|
|
390
|
+
if (ts.isTypeOfExpression(node.parent) && node.parent.expression === node) {
|
|
391
|
+
return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
|
|
411
392
|
}
|
|
412
393
|
|
|
413
394
|
let binding = findBinding(ctx.scopedBindings, node.text, node);
|
|
414
395
|
|
|
415
396
|
if (binding) {
|
|
397
|
+
// Read access → ns.read(x)
|
|
416
398
|
ctx.neededImports.add('read');
|
|
417
399
|
|
|
418
|
-
ctx.
|
|
419
|
-
end: node.end - ctx.argStart,
|
|
420
|
-
newText: `read(${node.text})`,
|
|
421
|
-
start: node.getStart(ctx.sourceFile) - ctx.argStart
|
|
422
|
-
});
|
|
400
|
+
return createReadCall(ctx.factory, ctx.ns.reactivity, ctx.factory.createIdentifier(node.text));
|
|
423
401
|
}
|
|
424
402
|
}
|
|
425
403
|
|
|
426
|
-
ts.
|
|
404
|
+
return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
|
|
427
405
|
}
|
|
428
406
|
|
|
407
|
+
function visitComputedArg(ctx: TransformContext, node: ts.Node): ts.Node {
|
|
408
|
+
// Skip property names in property access
|
|
409
|
+
if (ts.isIdentifier(node) && node.parent) {
|
|
410
|
+
if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
|
|
411
|
+
return ts.visitEachChild(node, n => visitComputedArg(ctx, n), ctx.context);
|
|
412
|
+
}
|
|
429
413
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
let code = sourceFile.getFullText(),
|
|
435
|
-
ctx: TransformContext = {
|
|
436
|
-
bindings,
|
|
437
|
-
computedArgRanges: [],
|
|
438
|
-
hasReactiveImport: false,
|
|
439
|
-
neededImports: new Set<string>(),
|
|
440
|
-
replacements: [],
|
|
441
|
-
scopedBindings: [],
|
|
442
|
-
sourceFile
|
|
443
|
-
};
|
|
414
|
+
// Skip function call expressions
|
|
415
|
+
if (ts.isCallExpression(node.parent) && node.parent.expression === node) {
|
|
416
|
+
return ts.visitEachChild(node, n => visitComputedArg(ctx, n), ctx.context);
|
|
417
|
+
}
|
|
444
418
|
|
|
445
|
-
|
|
419
|
+
let binding = findBinding(ctx.scopedBindings, node.text, node);
|
|
446
420
|
|
|
447
|
-
|
|
448
|
-
|
|
421
|
+
if (binding) {
|
|
422
|
+
ctx.neededImports.add('read');
|
|
423
|
+
|
|
424
|
+
return createReadCall(ctx.factory, ctx.ns.reactivity, ctx.factory.createIdentifier(node.text));
|
|
425
|
+
}
|
|
449
426
|
}
|
|
450
427
|
|
|
451
|
-
|
|
428
|
+
return ts.visitEachChild(node, n => visitComputedArg(ctx, n), ctx.context);
|
|
429
|
+
}
|
|
452
430
|
|
|
453
|
-
if (ctx.neededImports.size > 0) {
|
|
454
|
-
result = addMissingImports(result, ctx.neededImports);
|
|
455
|
-
}
|
|
456
431
|
|
|
457
|
-
|
|
432
|
+
const createPrimitivesTransformer = (
|
|
433
|
+
bindings: Bindings,
|
|
434
|
+
neededImports: Set<string>,
|
|
435
|
+
ns: Namespaces
|
|
436
|
+
): (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile => {
|
|
437
|
+
return (context: ts.TransformationContext) => {
|
|
438
|
+
return (sourceFile: ts.SourceFile): ts.SourceFile => {
|
|
439
|
+
let ctx: TransformContext = {
|
|
440
|
+
bindings,
|
|
441
|
+
context,
|
|
442
|
+
factory: context.factory,
|
|
443
|
+
hasReactiveImport: false,
|
|
444
|
+
neededImports,
|
|
445
|
+
ns,
|
|
446
|
+
scopedBindings: []
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
return ts.visitNode(sourceFile, n => visit(ctx, n)) as ts.SourceFile;
|
|
450
|
+
};
|
|
451
|
+
};
|
|
458
452
|
};
|
|
459
453
|
|
|
460
454
|
|
|
461
|
-
export {
|
|
455
|
+
export { createPrimitivesTransformer };
|
package/src/types.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { COMPUTED, SIGNAL, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING } from './constants';
|
|
2
2
|
import { ReactiveArray, ReactiveObject } from './reactive';
|
|
3
|
-
import { ts } from '@esportsplus/typescript';
|
|
4
3
|
|
|
5
4
|
|
|
6
5
|
type BindingType = 'array' | 'computed' | 'object' | 'signal';
|
|
7
6
|
|
|
8
7
|
type Bindings = Map<string, BindingType>;
|
|
9
8
|
|
|
9
|
+
interface Namespaces {
|
|
10
|
+
array: string;
|
|
11
|
+
constants: string;
|
|
12
|
+
reactivity: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
interface Computed<T> {
|
|
11
16
|
cleanup: VoidFunction | VoidFunction[] | null;
|
|
12
17
|
deps: Link | null;
|
|
@@ -43,20 +48,14 @@ type Signal<T> = {
|
|
|
43
48
|
value: T;
|
|
44
49
|
};
|
|
45
50
|
|
|
46
|
-
interface TransformResult {
|
|
47
|
-
code: string;
|
|
48
|
-
sourceFile: ts.SourceFile;
|
|
49
|
-
transformed: boolean;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
51
|
|
|
53
52
|
export type {
|
|
54
53
|
BindingType,
|
|
55
54
|
Bindings,
|
|
56
55
|
Computed,
|
|
57
56
|
Link,
|
|
57
|
+
Namespaces,
|
|
58
58
|
ReactiveArray,
|
|
59
59
|
ReactiveObject,
|
|
60
|
-
Signal
|
|
61
|
-
TransformResult
|
|
60
|
+
Signal
|
|
62
61
|
};
|
package/test/vite.config.ts
CHANGED