@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.
@@ -1,17 +1,16 @@
1
- import { uid, type Range } from '@esportsplus/typescript/transformer';
2
- import type { BindingType, Bindings } from '~/types';
3
- import { addMissingImports, applyReplacements, Replacement } from './utilities';
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
- computedArgRanges: Range[];
22
+ context: ts.TransformationContext;
23
+ factory: ts.NodeFactory;
24
24
  hasReactiveImport: boolean;
25
25
  neededImports: Set<string>;
26
- replacements: Replacement[];
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.isSourceFile(current) ||
62
+ ts.isForInStatement(current) ||
63
+ ts.isForOfStatement(current) ||
64
+ ts.isForStatement(current) ||
63
65
  ts.isFunctionDeclaration(current) ||
64
66
  ts.isFunctionExpression(current) ||
65
- ts.isArrowFunction(current) ||
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): string {
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 isInComputedRange(ranges: Range[], start: number, end: number): boolean {
131
- for (let i = 0, n = ranges.length; i < n; i++) {
132
- let r = ranges[i];
129
+ function isAssignmentOperator(kind: ts.SyntaxKind): 'compound' | 'simple' | false {
130
+ if (kind === ts.SyntaxKind.EqualsToken) {
131
+ return 'simple';
132
+ }
133
133
 
134
- if (start >= r.start && end <= r.end) {
135
- return true;
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.Node): boolean {
167
- let parent = node.parent;
177
+ function isReactiveReassignment(node: ts.BinaryExpression): boolean {
178
+ let right = node.right;
168
179
 
169
180
  if (
170
- ts.isBinaryExpression(parent) &&
171
- parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
172
- parent.right === node &&
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 isWriteContext(node: ts.Identifier): 'simple' | 'compound' | 'increment' | false {
184
- let parent = node.parent;
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.computedArgRanges.push({
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
- ctx.replacements.push({
285
- end: node.end,
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.neededImports.add('computed');
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
- if (ts.isIdentifier(node) && !isInDeclarationInit(node.parent)) {
307
- if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
308
- ts.forEachChild(node, n => visit(ctx, n));
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
- let nodeStart = node.getStart(ctx.sourceFile);
263
+ if (assignType) {
264
+ let binding = findBinding(ctx.scopedBindings, node.left.text, node.left);
313
265
 
314
- if (isInComputedRange(ctx.computedArgRanges, nodeStart, node.end)) {
315
- ts.forEachChild(node, n => visit(ctx, n));
316
- return;
317
- }
266
+ if (binding && binding.type !== 'computed' && !isReactiveReassignment(node)) {
267
+ ctx.neededImports.add('set');
318
268
 
319
- let binding = findBinding(ctx.scopedBindings, node.text, node),
320
- name = node.text;
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
- if (binding) {
323
- if (
324
- !isReactiveReassignment(node.parent) &&
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
- ctx.neededImports.add('read');
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
- ctx.replacements.push({
389
- end: node.end,
390
- newText: `read(${name})`,
391
- start: node.pos
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
- ts.forEachChild(node, n => visit(ctx, n));
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
- function visitArg(ctx: ArgContext, node: ts.Node): void {
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.forEachChild(node, n => visitArg(ctx, n));
405
- return;
373
+ return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
406
374
  }
407
375
 
408
- if (ts.isCallExpression(node.parent) && node.parent.expression === node) {
409
- ts.forEachChild(node, n => visitArg(ctx, n));
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.innerReplacements.push({
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.forEachChild(node, n => visitArg(ctx, n));
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
- const transformReactivePrimitives = (
431
- sourceFile: ts.SourceFile,
432
- bindings: Bindings
433
- ): string => {
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
- visit(ctx, sourceFile);
419
+ let binding = findBinding(ctx.scopedBindings, node.text, node);
446
420
 
447
- if (ctx.replacements.length === 0) {
448
- return code;
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
- let result = applyReplacements(code, ctx.replacements);
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
- return result;
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 { transformReactivePrimitives };
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
  };
@@ -1,6 +1,6 @@
1
1
  import { resolve } from 'path';
2
2
  import { defineConfig } from 'vite';
3
- import { plugin as reactivity } from '../build/plugins/vite.js';
3
+ import reactivity from '../build/transformer/plugins/vite.js';
4
4
 
5
5
 
6
6
  export default defineConfig({