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