@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.
@@ -0,0 +1,13 @@
1
+ import { ts } from '@esportsplus/typescript';
2
+ declare function createReadCall(factory: ts.NodeFactory, ns: string, expr: ts.Expression): ts.CallExpression;
3
+ declare function createSetCall(factory: ts.NodeFactory, ns: string, target: ts.Expression, value: ts.Expression): ts.CallExpression;
4
+ declare function createSignalCall(factory: ts.NodeFactory, ns: string, initialValue: ts.Expression): ts.CallExpression;
5
+ declare function createComputedCall(factory: ts.NodeFactory, ns: string, fn: ts.Expression): ts.CallExpression;
6
+ declare function createArrayLengthCall(factory: ts.NodeFactory, arrayExpr: ts.Expression): ts.CallExpression;
7
+ declare function createArraySetCall(factory: ts.NodeFactory, arrayExpr: ts.Expression, index: ts.Expression, value: ts.Expression): ts.CallExpression;
8
+ declare function createReactiveArrayNew(factory: ts.NodeFactory, ns: string, elements: ts.Expression[]): ts.NewExpression;
9
+ declare function createDisposeCall(factory: ts.NodeFactory, ns: string, expr: ts.Expression): ts.CallExpression;
10
+ declare function createBinaryExpr(factory: ts.NodeFactory, left: ts.Expression, op: ts.BinaryOperator, right: ts.Expression): ts.BinaryExpression;
11
+ declare function createCommaExpr(factory: ts.NodeFactory, first: ts.Expression, second: ts.Expression): ts.ParenthesizedExpression;
12
+ declare function createPostfixIncrementExpr(factory: ts.NodeFactory, ns: string, tmpName: string, signalName: string, op: ts.SyntaxKind.PlusToken | ts.SyntaxKind.MinusToken): ts.CallExpression;
13
+ export { createArrayLengthCall, createArraySetCall, createBinaryExpr, createCommaExpr, createComputedCall, createDisposeCall, createPostfixIncrementExpr, createReactiveArrayNew, createReadCall, createSetCall, createSignalCall };
@@ -0,0 +1,36 @@
1
+ import { ts } from '@esportsplus/typescript';
2
+ function createReadCall(factory, ns, expr) {
3
+ return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ns), 'read'), undefined, [expr]);
4
+ }
5
+ function createSetCall(factory, ns, target, value) {
6
+ return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ns), 'set'), undefined, [target, value]);
7
+ }
8
+ function createSignalCall(factory, ns, initialValue) {
9
+ return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ns), 'signal'), undefined, [initialValue]);
10
+ }
11
+ function createComputedCall(factory, ns, fn) {
12
+ return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ns), 'computed'), undefined, [fn]);
13
+ }
14
+ function createArrayLengthCall(factory, arrayExpr) {
15
+ return factory.createCallExpression(factory.createPropertyAccessExpression(arrayExpr, '$length'), undefined, []);
16
+ }
17
+ function createArraySetCall(factory, arrayExpr, index, value) {
18
+ return factory.createCallExpression(factory.createPropertyAccessExpression(arrayExpr, '$set'), undefined, [index, value]);
19
+ }
20
+ function createReactiveArrayNew(factory, ns, elements) {
21
+ return factory.createNewExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ns), 'ReactiveArray'), undefined, elements);
22
+ }
23
+ function createDisposeCall(factory, ns, expr) {
24
+ return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ns), 'dispose'), undefined, [expr]);
25
+ }
26
+ function createBinaryExpr(factory, left, op, right) {
27
+ return factory.createBinaryExpression(left, op, right);
28
+ }
29
+ function createCommaExpr(factory, first, second) {
30
+ return factory.createParenthesizedExpression(factory.createBinaryExpression(first, ts.SyntaxKind.CommaToken, second));
31
+ }
32
+ function createPostfixIncrementExpr(factory, ns, tmpName, signalName, op) {
33
+ let tmpIdent = factory.createIdentifier(tmpName), signalIdent = factory.createIdentifier(signalName);
34
+ return factory.createCallExpression(factory.createParenthesizedExpression(factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, tmpIdent)], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), createCommaExpr(factory, createSetCall(factory, ns, signalIdent, factory.createBinaryExpression(tmpIdent, op, factory.createNumericLiteral(1))), tmpIdent))), undefined, [factory.createPropertyAccessExpression(signalIdent, 'value')]);
35
+ }
36
+ export { createArrayLengthCall, createArraySetCall, createBinaryExpr, createCommaExpr, createComputedCall, createDisposeCall, createPostfixIncrementExpr, createReactiveArrayNew, createReadCall, createSetCall, createSignalCall };
@@ -1,9 +1,7 @@
1
- import type { TransformResult } from '../types.js';
2
1
  import { mightNeedTransform } from './detector.js';
3
2
  import { ts } from '@esportsplus/typescript';
4
3
  declare const createTransformer: () => ts.TransformerFactory<ts.SourceFile>;
5
- declare const transform: (sourceFile: ts.SourceFile) => TransformResult;
6
- export { createTransformer, mightNeedTransform, transform };
7
- export { transformReactiveArrays } from './transforms/array.js';
8
- export { transformReactiveObjects } from './transforms/object.js';
9
- export { transformReactivePrimitives } from './transforms/primitives.js';
4
+ export { createTransformer, mightNeedTransform };
5
+ export { createArrayTransformer } from './transforms/array.js';
6
+ export { createObjectTransformer } from './transforms/object.js';
7
+ export { createPrimitivesTransformer } from './transforms/primitives.js';
@@ -1,46 +1,106 @@
1
+ import { uid } from '@esportsplus/typescript/transformer';
2
+ import { createArrayTransformer } from './transforms/array.js';
3
+ import { createObjectTransformer } from './transforms/object.js';
4
+ import { createPrimitivesTransformer } from './transforms/primitives.js';
1
5
  import { mightNeedTransform } from './detector.js';
2
- import { transformReactiveArrays } from './transforms/array.js';
3
- import { transformReactiveObjects } from './transforms/object.js';
4
- import { transformReactivePrimitives } from './transforms/primitives.js';
5
6
  import { ts } from '@esportsplus/typescript';
6
- const createTransformer = () => {
7
- return () => {
7
+ function addImportsTransformer(neededImports, ns) {
8
+ return (context) => {
8
9
  return (sourceFile) => {
9
- let result = transform(sourceFile);
10
- return result.transformed ? result.sourceFile : sourceFile;
10
+ if (neededImports.size === 0) {
11
+ return sourceFile;
12
+ }
13
+ let factory = context.factory, needsArray = false, needsConstants = false, needsReactivity = false, newStatements = [];
14
+ for (let imp of neededImports) {
15
+ if (imp === 'ReactiveArray') {
16
+ needsArray = true;
17
+ }
18
+ else if (imp === 'REACTIVE_OBJECT') {
19
+ needsConstants = true;
20
+ }
21
+ else {
22
+ needsReactivity = true;
23
+ }
24
+ }
25
+ if (needsReactivity) {
26
+ newStatements.push(factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamespaceImport(factory.createIdentifier(ns.reactivity))), factory.createStringLiteral('@esportsplus/reactivity')));
27
+ }
28
+ if (needsArray) {
29
+ newStatements.push(factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamespaceImport(factory.createIdentifier(ns.array))), factory.createStringLiteral('@esportsplus/reactivity/reactive/array')));
30
+ }
31
+ if (needsConstants) {
32
+ newStatements.push(factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamespaceImport(factory.createIdentifier(ns.constants))), factory.createStringLiteral('@esportsplus/reactivity/constants')));
33
+ }
34
+ let insertIndex = 0, statements = sourceFile.statements;
35
+ for (let i = 0, n = statements.length; i < n; i++) {
36
+ if (ts.isImportDeclaration(statements[i])) {
37
+ insertIndex = i + 1;
38
+ }
39
+ else {
40
+ break;
41
+ }
42
+ }
43
+ let updatedStatements = [
44
+ ...statements.slice(0, insertIndex),
45
+ ...newStatements,
46
+ ...statements.slice(insertIndex)
47
+ ];
48
+ return factory.updateSourceFile(sourceFile, updatedStatements);
11
49
  };
12
50
  };
13
- };
14
- const transform = (sourceFile) => {
15
- let bindings = new Map(), code = sourceFile.getFullText(), current = sourceFile, original = code, result;
16
- if (!mightNeedTransform(code)) {
17
- return { code, sourceFile, transformed: false };
18
- }
19
- result = transformReactiveObjects(current, bindings);
20
- if (result !== code) {
21
- current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
22
- code = result;
23
- }
24
- result = transformReactiveArrays(current, bindings);
25
- if (result !== code) {
26
- current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
27
- code = result;
28
- }
29
- result = transformReactivePrimitives(current, bindings);
30
- if (result !== code) {
31
- current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
32
- code = result;
33
- }
34
- if (code === original) {
35
- return { code, sourceFile, transformed: false };
36
- }
37
- return {
38
- code,
39
- sourceFile: current,
40
- transformed: true
51
+ }
52
+ function insertClassesTransformer(generatedClasses) {
53
+ return (context) => {
54
+ return (sourceFile) => {
55
+ if (generatedClasses.length === 0) {
56
+ return sourceFile;
57
+ }
58
+ let factory = context.factory;
59
+ let insertIndex = 0, statements = sourceFile.statements;
60
+ for (let i = 0, n = statements.length; i < n; i++) {
61
+ if (ts.isImportDeclaration(statements[i])) {
62
+ insertIndex = i + 1;
63
+ }
64
+ else {
65
+ break;
66
+ }
67
+ }
68
+ let classDecls = generatedClasses.map(gc => gc.classDecl), updatedStatements = [
69
+ ...statements.slice(0, insertIndex),
70
+ ...classDecls,
71
+ ...statements.slice(insertIndex)
72
+ ];
73
+ return factory.updateSourceFile(sourceFile, updatedStatements);
74
+ };
75
+ };
76
+ }
77
+ const createTransformer = () => {
78
+ return (context) => {
79
+ return (sourceFile) => {
80
+ let code = sourceFile.getFullText();
81
+ if (!mightNeedTransform(code)) {
82
+ return sourceFile;
83
+ }
84
+ let bindings = new Map(), generatedClasses = [], neededImports = new Set(), ns = {
85
+ array: uid('ra'),
86
+ constants: uid('rc'),
87
+ reactivity: uid('r')
88
+ };
89
+ let objectTransformer = createObjectTransformer(bindings, neededImports, generatedClasses, ns)(context);
90
+ sourceFile = objectTransformer(sourceFile);
91
+ let arrayTransformer = createArrayTransformer(bindings)(context);
92
+ sourceFile = arrayTransformer(sourceFile);
93
+ let primitivesTransformer = createPrimitivesTransformer(bindings, neededImports, ns)(context);
94
+ sourceFile = primitivesTransformer(sourceFile);
95
+ let classInserter = insertClassesTransformer(generatedClasses)(context);
96
+ sourceFile = classInserter(sourceFile);
97
+ let importAdder = addImportsTransformer(neededImports, ns)(context);
98
+ sourceFile = importAdder(sourceFile);
99
+ return sourceFile;
100
+ };
41
101
  };
42
102
  };
43
- export { createTransformer, mightNeedTransform, transform };
44
- export { transformReactiveArrays } from './transforms/array.js';
45
- export { transformReactiveObjects } from './transforms/object.js';
46
- export { transformReactivePrimitives } from './transforms/primitives.js';
103
+ export { createTransformer, mightNeedTransform };
104
+ export { createArrayTransformer } from './transforms/array.js';
105
+ export { createObjectTransformer } from './transforms/object.js';
106
+ export { createPrimitivesTransformer } from './transforms/primitives.js';
@@ -1,5 +1,5 @@
1
1
  import { TRANSFORM_PATTERN } from '@esportsplus/typescript/transformer';
2
- import { mightNeedTransform, transform } from '../../transformer/index.js';
2
+ import { createTransformer, mightNeedTransform } from '../../transformer/index.js';
3
3
  import { ts } from '@esportsplus/typescript';
4
4
  export default () => {
5
5
  return {
@@ -13,11 +13,14 @@ export default () => {
13
13
  return null;
14
14
  }
15
15
  try {
16
- let sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true), result = transform(sourceFile);
17
- if (!result.transformed) {
16
+ let printer = ts.createPrinter(), sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true), transformer = createTransformer(), result = ts.transform(sourceFile, [transformer]), transformed = result.transformed[0];
17
+ if (transformed === sourceFile) {
18
+ result.dispose();
18
19
  return null;
19
20
  }
20
- return { code: result.code, map: null };
21
+ let output = printer.printFile(transformed);
22
+ result.dispose();
23
+ return { code: output, map: null };
21
24
  }
22
25
  catch (error) {
23
26
  console.error(`@esportsplus/reactivity: Error transforming ${id}:`, error);
@@ -1,4 +1,4 @@
1
1
  import type { Bindings } from '../../types.js';
2
2
  import { ts } from '@esportsplus/typescript';
3
- declare const transformReactiveArrays: (sourceFile: ts.SourceFile, bindings: Bindings) => string;
4
- export { transformReactiveArrays };
3
+ declare const createArrayTransformer: (bindings: Bindings) => (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile;
4
+ export { createArrayTransformer };
@@ -1,4 +1,4 @@
1
- import { applyReplacements } from './utilities.js';
1
+ import { createArrayLengthCall, createArraySetCall } from '../factory.js';
2
2
  import { ts } from '@esportsplus/typescript';
3
3
  function getExpressionName(node) {
4
4
  if (ts.isIdentifier(node)) {
@@ -23,6 +23,9 @@ function getPropertyPath(node) {
23
23
  }
24
24
  function isAssignmentTarget(node) {
25
25
  let parent = node.parent;
26
+ if (!parent) {
27
+ return false;
28
+ }
26
29
  if ((ts.isBinaryExpression(parent) && parent.left === node) ||
27
30
  ts.isPostfixUnaryExpression(parent) ||
28
31
  ts.isPrefixUnaryExpression(parent)) {
@@ -58,12 +61,8 @@ function visit(ctx, node) {
58
61
  !isAssignmentTarget(node)) {
59
62
  let name = getExpressionName(node.expression);
60
63
  if (name && ctx.bindings.get(name) === 'array') {
61
- let objText = node.expression.getText(ctx.sourceFile);
62
- ctx.replacements.push({
63
- end: node.end,
64
- newText: `${objText}.$length()`,
65
- start: node.pos
66
- });
64
+ let transformedExpr = ts.visitEachChild(node.expression, n => visit(ctx, n), ctx.context);
65
+ return createArrayLengthCall(ctx.factory, transformedExpr);
67
66
  }
68
67
  }
69
68
  if (ts.isBinaryExpression(node) &&
@@ -71,23 +70,22 @@ function visit(ctx, node) {
71
70
  ts.isElementAccessExpression(node.left)) {
72
71
  let elemAccess = node.left, objName = getExpressionName(elemAccess.expression);
73
72
  if (objName && ctx.bindings.get(objName) === 'array') {
74
- let indexText = elemAccess.argumentExpression.getText(ctx.sourceFile), objText = elemAccess.expression.getText(ctx.sourceFile), valueText = node.right.getText(ctx.sourceFile);
75
- ctx.replacements.push({
76
- end: node.end,
77
- newText: `${objText}.$set(${indexText}, ${valueText})`,
78
- start: node.pos
79
- });
73
+ let transformedArray = ts.visitEachChild(elemAccess.expression, n => visit(ctx, n), ctx.context), transformedIndex = ts.visitEachChild(elemAccess.argumentExpression, n => visit(ctx, n), ctx.context), transformedValue = ts.visitEachChild(node.right, n => visit(ctx, n), ctx.context);
74
+ return createArraySetCall(ctx.factory, transformedArray, transformedIndex, transformedValue);
80
75
  }
81
76
  }
82
- ts.forEachChild(node, n => visit(ctx, n));
77
+ return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
83
78
  }
84
- const transformReactiveArrays = (sourceFile, bindings) => {
85
- let code = sourceFile.getFullText(), ctx = {
86
- bindings,
87
- replacements: [],
88
- sourceFile
79
+ const createArrayTransformer = (bindings) => {
80
+ return (context) => {
81
+ return (sourceFile) => {
82
+ let ctx = {
83
+ bindings,
84
+ context,
85
+ factory: context.factory
86
+ };
87
+ return ts.visitNode(sourceFile, n => visit(ctx, n));
88
+ };
89
89
  };
90
- visit(ctx, sourceFile);
91
- return applyReplacements(code, ctx.replacements);
92
90
  };
93
- export { transformReactiveArrays };
91
+ export { createArrayTransformer };
@@ -1,4 +1,10 @@
1
- import type { Bindings } from '../../types.js';
1
+ import type { Bindings, Namespaces } from '../../types.js';
2
2
  import { ts } from '@esportsplus/typescript';
3
- declare const transformReactiveObjects: (sourceFile: ts.SourceFile, bindings: Bindings) => string;
4
- export { transformReactiveObjects };
3
+ interface GeneratedClass {
4
+ classDecl: ts.ClassDeclaration;
5
+ className: string;
6
+ needsImports: Set<string>;
7
+ }
8
+ declare const createObjectTransformer: (bindings: Bindings, neededImports: Set<string>, generatedClasses: GeneratedClass[], ns: Namespaces) => (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile;
9
+ export { createObjectTransformer };
10
+ export type { GeneratedClass };
@@ -1,12 +1,6 @@
1
1
  import { uid } from '@esportsplus/typescript/transformer';
2
- import { addMissingImports, applyReplacements } from './utilities.js';
3
2
  import { ts } from '@esportsplus/typescript';
4
- const CLASS_NAME_REGEX = /class (\w+)/;
5
- const EXTRA_IMPORTS = [
6
- { module: '@esportsplus/reactivity/constants', specifier: 'REACTIVE_OBJECT' },
7
- { module: '@esportsplus/reactivity/reactive/array', specifier: 'ReactiveArray' }
8
- ];
9
- function analyzeProperty(prop, sourceFile) {
3
+ function analyzeProperty(prop) {
10
4
  if (!ts.isPropertyAssignment(prop)) {
11
5
  return null;
12
6
  }
@@ -17,51 +11,71 @@ function analyzeProperty(prop, sourceFile) {
17
11
  else {
18
12
  return null;
19
13
  }
20
- let value = prop.initializer, valueText = value.getText(sourceFile);
14
+ let value = prop.initializer;
21
15
  if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) {
22
- return { key, type: 'computed', valueText };
16
+ return { key, type: 'computed', value };
23
17
  }
24
18
  if (ts.isArrayLiteralExpression(value)) {
25
- return { key, type: 'array', valueText };
19
+ return { elements: [...value.elements], key, type: 'array', value };
26
20
  }
27
- return { key, type: 'signal', valueText };
21
+ return { key, type: 'signal', value };
28
22
  }
29
- function buildClassCode(className, properties) {
30
- let accessors = [], disposeStatements = [], fields = [];
31
- fields.push(`[REACTIVE_OBJECT] = true;`);
23
+ function buildReactiveClass(ctx, className, properties, varName) {
24
+ let factory = ctx.factory, members = [], needsImports = new Set();
25
+ needsImports.add('REACTIVE_OBJECT');
26
+ members.push(factory.createPropertyDeclaration(undefined, factory.createComputedPropertyName(factory.createPropertyAccessExpression(factory.createIdentifier(ctx.ns.constants), 'REACTIVE_OBJECT')), undefined, undefined, factory.createTrue()));
27
+ let disposeStatements = [];
32
28
  for (let i = 0, n = properties.length; i < n; i++) {
33
- let { key, type, valueText } = properties[i];
34
- if (type === 'signal') {
35
- let param = uid('v');
36
- fields.push(`#${key} = signal(${valueText});`);
37
- accessors.push(`get ${key}() { return read(this.#${key}); }`);
38
- accessors.push(`set ${key}(${param}) { set(this.#${key}, ${param}); }`);
29
+ let prop = properties[i];
30
+ if (prop.type === 'signal') {
31
+ needsImports.add('read');
32
+ needsImports.add('set');
33
+ needsImports.add('signal');
34
+ let privateName = factory.createPrivateIdentifier(`#${prop.key}`), paramName = uid('v');
35
+ members.push(factory.createPropertyDeclaration(undefined, privateName, undefined, undefined, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ctx.ns.reactivity), 'signal'), undefined, [prop.value])));
36
+ members.push(factory.createGetAccessorDeclaration(undefined, factory.createIdentifier(prop.key), [], undefined, factory.createBlock([
37
+ factory.createReturnStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ctx.ns.reactivity), 'read'), undefined, [factory.createPropertyAccessExpression(factory.createThis(), privateName)]))
38
+ ], true)));
39
+ members.push(factory.createSetAccessorDeclaration(undefined, factory.createIdentifier(prop.key), [factory.createParameterDeclaration(undefined, undefined, paramName)], factory.createBlock([
40
+ factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ctx.ns.reactivity), 'set'), undefined, [
41
+ factory.createPropertyAccessExpression(factory.createThis(), privateName),
42
+ factory.createIdentifier(paramName)
43
+ ]))
44
+ ], true)));
39
45
  }
40
- else if (type === 'array') {
41
- let elements = valueText.slice(1, -1);
42
- fields.push(`${key} = new ReactiveArray(${elements});`);
43
- disposeStatements.push(`this.${key}.dispose();`);
46
+ else if (prop.type === 'array') {
47
+ needsImports.add('ReactiveArray');
48
+ members.push(factory.createPropertyDeclaration(undefined, factory.createIdentifier(prop.key), undefined, undefined, factory.createNewExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ctx.ns.array), 'ReactiveArray'), undefined, prop.elements || [])));
49
+ if (varName) {
50
+ ctx.bindings.set(`${varName}.${prop.key}`, 'array');
51
+ }
52
+ disposeStatements.push(factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createThis(), prop.key), 'dispose'), undefined, [])));
44
53
  }
45
- else if (type === 'computed') {
46
- fields.push(`#${key}: Computed<unknown> | null = null;`);
47
- accessors.push(`get ${key}() { return read(this.#${key} ??= computed(${valueText})); }`);
48
- disposeStatements.push(`if (this.#${key}) dispose(this.#${key});`);
54
+ else if (prop.type === 'computed') {
55
+ needsImports.add('computed');
56
+ needsImports.add('dispose');
57
+ needsImports.add('read');
58
+ let privateName = factory.createPrivateIdentifier(`#${prop.key}`);
59
+ members.push(factory.createPropertyDeclaration(undefined, privateName, undefined, factory.createUnionTypeNode([
60
+ factory.createTypeReferenceNode('Computed', [
61
+ factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)
62
+ ]),
63
+ factory.createLiteralTypeNode(factory.createNull())
64
+ ]), factory.createNull()));
65
+ members.push(factory.createGetAccessorDeclaration(undefined, factory.createIdentifier(prop.key), [], undefined, factory.createBlock([
66
+ factory.createReturnStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ctx.ns.reactivity), 'read'), undefined, [
67
+ factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createThis(), privateName), ts.SyntaxKind.QuestionQuestionEqualsToken, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ctx.ns.reactivity), 'computed'), undefined, [prop.value]))
68
+ ]))
69
+ ], true)));
70
+ disposeStatements.push(factory.createIfStatement(factory.createPropertyAccessExpression(factory.createThis(), privateName), factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ctx.ns.reactivity), 'dispose'), undefined, [factory.createPropertyAccessExpression(factory.createThis(), privateName)]))));
49
71
  }
50
72
  }
51
- return `
52
- class ${className} {
53
- ${fields.join('\n')}
54
- ${accessors.join('\n')}
55
-
56
- dispose() {
57
- ${disposeStatements.length > 0 ? disposeStatements.join('\n') : ''}
58
- }
59
- }
60
- `;
73
+ members.push(factory.createMethodDeclaration(undefined, undefined, 'dispose', undefined, undefined, [], undefined, factory.createBlock(disposeStatements, true)));
74
+ needsImports.forEach(imp => ctx.neededImports.add(imp));
75
+ return factory.createClassDeclaration(undefined, className, undefined, undefined, members);
61
76
  }
62
77
  function visit(ctx, node) {
63
78
  if (ts.isImportDeclaration(node)) {
64
- ctx.lastImportEnd = node.end;
65
79
  if (ts.isStringLiteral(node.moduleSpecifier) &&
66
80
  node.moduleSpecifier.text.includes('@esportsplus/reactivity')) {
67
81
  let clause = node.importClause;
@@ -81,83 +95,58 @@ function visit(ctx, node) {
81
95
  ts.isIdentifier(node.expression) &&
82
96
  node.expression.text === 'reactive') {
83
97
  let arg = node.arguments[0];
98
+ if (arg && ts.isArrayLiteralExpression(arg)) {
99
+ let varName = null;
100
+ if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
101
+ varName = node.parent.name.text;
102
+ ctx.bindings.set(varName, 'array');
103
+ }
104
+ ctx.neededImports.add('ReactiveArray');
105
+ return ctx.factory.createNewExpression(ctx.factory.createPropertyAccessExpression(ctx.factory.createIdentifier(ctx.ns.array), 'ReactiveArray'), undefined, [...arg.elements]);
106
+ }
84
107
  if (arg && ts.isObjectLiteralExpression(arg)) {
85
108
  let varName = null;
86
109
  if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
87
110
  varName = node.parent.name.text;
88
111
  ctx.bindings.set(varName, 'object');
89
112
  }
90
- let needsImports = new Set(), properties = [];
91
- needsImports.add('REACTIVE_OBJECT');
92
- let props = arg.properties;
113
+ let properties = [], props = arg.properties;
93
114
  for (let i = 0, n = props.length; i < n; i++) {
94
115
  let prop = props[i];
95
116
  if (ts.isSpreadAssignment(prop)) {
96
- ts.forEachChild(node, n => visit(ctx, n));
97
- return;
117
+ return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
98
118
  }
99
- let analyzed = analyzeProperty(prop, ctx.sourceFile);
119
+ let analyzed = analyzeProperty(prop);
100
120
  if (!analyzed) {
101
- ts.forEachChild(node, n => visit(ctx, n));
102
- return;
121
+ return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
103
122
  }
104
123
  properties.push(analyzed);
105
- if (analyzed.type === 'signal') {
106
- needsImports.add('read');
107
- needsImports.add('set');
108
- needsImports.add('signal');
109
- }
110
- else if (analyzed.type === 'array') {
111
- needsImports.add('ReactiveArray');
112
- if (varName) {
113
- ctx.bindings.set(`${varName}.${analyzed.key}`, 'array');
114
- }
115
- }
116
- else if (analyzed.type === 'computed') {
117
- needsImports.add('computed');
118
- needsImports.add('dispose');
119
- needsImports.add('read');
120
- }
121
124
  }
122
- needsImports.forEach(imp => ctx.allNeededImports.add(imp));
123
- ctx.calls.push({
124
- end: node.end,
125
- generatedClass: buildClassCode(uid('ReactiveObject'), properties),
126
- needsImports,
127
- start: node.pos,
128
- varName
125
+ let className = uid('ReactiveObject'), classDecl = buildReactiveClass(ctx, className, properties, varName);
126
+ ctx.generatedClasses.push({
127
+ classDecl,
128
+ className,
129
+ needsImports: new Set(ctx.neededImports)
129
130
  });
131
+ return ctx.factory.createNewExpression(ctx.factory.createIdentifier(className), undefined, []);
130
132
  }
131
133
  }
132
- ts.forEachChild(node, n => visit(ctx, n));
134
+ return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
133
135
  }
134
- const transformReactiveObjects = (sourceFile, bindings) => {
135
- let code = sourceFile.getFullText(), ctx = {
136
- allNeededImports: new Set(),
137
- bindings,
138
- calls: [],
139
- hasReactiveImport: false,
140
- lastImportEnd: 0,
141
- sourceFile
136
+ const createObjectTransformer = (bindings, neededImports, generatedClasses, ns) => {
137
+ return (context) => {
138
+ return (sourceFile) => {
139
+ let ctx = {
140
+ bindings,
141
+ context,
142
+ factory: context.factory,
143
+ generatedClasses,
144
+ hasReactiveImport: false,
145
+ neededImports,
146
+ ns
147
+ };
148
+ return ts.visitNode(sourceFile, n => visit(ctx, n));
149
+ };
142
150
  };
143
- visit(ctx, sourceFile);
144
- if (ctx.calls.length === 0) {
145
- return code;
146
- }
147
- let replacements = [];
148
- replacements.push({
149
- end: ctx.lastImportEnd,
150
- newText: code.substring(0, ctx.lastImportEnd) + '\n' + ctx.calls.map(c => c.generatedClass).join('\n') + '\n',
151
- start: 0
152
- });
153
- for (let i = 0, n = ctx.calls.length; i < n; i++) {
154
- let call = ctx.calls[i], classMatch = call.generatedClass.match(CLASS_NAME_REGEX);
155
- replacements.push({
156
- end: call.end,
157
- newText: ` new ${classMatch ? classMatch[1] : 'ReactiveObject'}()`,
158
- start: call.start
159
- });
160
- }
161
- return addMissingImports(applyReplacements(code, replacements), ctx.allNeededImports, EXTRA_IMPORTS);
162
151
  };
163
- export { transformReactiveObjects };
152
+ export { createObjectTransformer };
@@ -1,4 +1,4 @@
1
- import type { Bindings } from '../../types.js';
1
+ import type { Bindings, Namespaces } from '../../types.js';
2
2
  import { ts } from '@esportsplus/typescript';
3
- declare const transformReactivePrimitives: (sourceFile: ts.SourceFile, bindings: Bindings) => string;
4
- export { transformReactivePrimitives };
3
+ declare const createPrimitivesTransformer: (bindings: Bindings, neededImports: Set<string>, ns: Namespaces) => (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile;
4
+ export { createPrimitivesTransformer };