@esportsplus/reactivity 0.24.4 → 0.25.0

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,10 +1,16 @@
1
+ import { Prettify } from '@esportsplus/utilities';
1
2
  import { REACTIVE_OBJECT } from '../constants.js';
2
3
  import { ReactiveArray } from './array.js';
3
4
  declare const READONLY: unique symbol;
4
- type ReactiveObject<T extends Record<PropertyKey, unknown>> = T & {
5
+ type Infer<T> = T extends (...args: unknown[]) => Promise<infer R> ? R | undefined : T extends (...args: any[]) => infer R ? R : T extends (infer U)[] ? ReactiveArray<U> : T extends ReactiveObject<any> ? T : T extends Record<PropertyKey, unknown> ? {
6
+ [K in keyof T]: T[K];
7
+ } : T;
8
+ type ReactiveObject<T> = T extends Record<PropertyKey, unknown> ? Prettify<{
9
+ [K in keyof T]: Infer<T[K]>;
10
+ } & {
5
11
  [REACTIVE_OBJECT]: true;
6
- dispose(): void;
7
- };
12
+ dispose: VoidFunction;
13
+ }> : T extends (infer U)[] ? ReactiveArray<U> : never;
8
14
  type ReactiveObjectGuard<T> = T extends {
9
15
  dispose: any;
10
16
  } ? {
@@ -15,7 +21,6 @@ declare function reactive<T extends () => unknown>(_input: T): ReturnType<T> & {
15
21
  };
16
22
  declare function reactive<T extends Record<PropertyKey, any>>(_input: ReactiveObjectGuard<T>): ReactiveObject<T>;
17
23
  declare function reactive<T>(_input: T[]): ReactiveArray<T>;
18
- declare function reactive<T>(_input: T): T;
19
24
  export default reactive;
20
25
  export { reactive, ReactiveArray };
21
26
  export type { ReactiveObject };
@@ -1,7 +1,9 @@
1
+ import type { TransformResult } from '../types.js';
1
2
  import { mightNeedTransform } from './detector.js';
2
3
  import { ts } from '@esportsplus/typescript';
3
4
  declare const createTransformer: () => ts.TransformerFactory<ts.SourceFile>;
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';
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';
@@ -1,113 +1,46 @@
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';
5
1
  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';
6
5
  import { ts } from '@esportsplus/typescript';
7
- function addImportsTransformer(neededImports, ns) {
8
- return (context) => {
9
- return (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 filteredStatements = [], insertIndex = 0, statements = sourceFile.statements;
35
- for (let i = 0, n = statements.length; i < n; i++) {
36
- let stmt = statements[i];
37
- if (ts.isImportDeclaration(stmt)) {
38
- insertIndex = filteredStatements.length + 1;
39
- if (ts.isStringLiteral(stmt.moduleSpecifier)) {
40
- let module = stmt.moduleSpecifier.text;
41
- if (module === '@esportsplus/reactivity' ||
42
- module === '@esportsplus/reactivity/reactive/array' ||
43
- module === '@esportsplus/reactivity/constants') {
44
- continue;
45
- }
46
- }
47
- }
48
- filteredStatements.push(stmt);
49
- }
50
- let updatedStatements = [
51
- ...filteredStatements.slice(0, insertIndex),
52
- ...newStatements,
53
- ...filteredStatements.slice(insertIndex)
54
- ];
55
- return factory.updateSourceFile(sourceFile, updatedStatements);
56
- };
57
- };
58
- }
59
- function insertClassesTransformer(generatedClasses) {
60
- return (context) => {
61
- return (sourceFile) => {
62
- if (generatedClasses.length === 0) {
63
- return sourceFile;
64
- }
65
- let factory = context.factory;
66
- let insertIndex = 0, statements = sourceFile.statements;
67
- for (let i = 0, n = statements.length; i < n; i++) {
68
- if (ts.isImportDeclaration(statements[i])) {
69
- insertIndex = i + 1;
70
- }
71
- else {
72
- break;
73
- }
74
- }
75
- let classDecls = generatedClasses.map(gc => gc.classDecl), updatedStatements = [
76
- ...statements.slice(0, insertIndex),
77
- ...classDecls,
78
- ...statements.slice(insertIndex)
79
- ];
80
- return factory.updateSourceFile(sourceFile, updatedStatements);
81
- };
82
- };
83
- }
84
6
  const createTransformer = () => {
85
- return (context) => {
7
+ return () => {
86
8
  return (sourceFile) => {
87
- let code = sourceFile.getFullText();
88
- if (!mightNeedTransform(code)) {
89
- return sourceFile;
90
- }
91
- let bindings = new Map(), generatedClasses = [], neededImports = new Set(), ns = {
92
- array: uid('ra'),
93
- constants: uid('rc'),
94
- reactivity: uid('r')
95
- };
96
- let objectTransformer = createObjectTransformer(bindings, neededImports, generatedClasses, ns)(context);
97
- sourceFile = objectTransformer(sourceFile);
98
- let arrayTransformer = createArrayTransformer(bindings)(context);
99
- sourceFile = arrayTransformer(sourceFile);
100
- let primitivesTransformer = createPrimitivesTransformer(bindings, neededImports, ns)(context);
101
- sourceFile = primitivesTransformer(sourceFile);
102
- let classInserter = insertClassesTransformer(generatedClasses)(context);
103
- sourceFile = classInserter(sourceFile);
104
- let importAdder = addImportsTransformer(neededImports, ns)(context);
105
- sourceFile = importAdder(sourceFile);
106
- return sourceFile;
9
+ let result = transform(sourceFile);
10
+ return result.transformed ? result.sourceFile : sourceFile;
107
11
  };
108
12
  };
109
13
  };
110
- export { createTransformer, mightNeedTransform };
111
- export { createArrayTransformer } from './transforms/array.js';
112
- export { createObjectTransformer } from './transforms/object.js';
113
- export { createPrimitivesTransformer } from './transforms/primitives.js';
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
41
+ };
42
+ };
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';
@@ -1,5 +1,5 @@
1
1
  import { TRANSFORM_PATTERN } from '@esportsplus/typescript/transformer';
2
- import { createTransformer, mightNeedTransform } from '../../transformer/index.js';
2
+ import { mightNeedTransform, transform } from '../../transformer/index.js';
3
3
  import { ts } from '@esportsplus/typescript';
4
4
  export default () => {
5
5
  return {
@@ -13,14 +13,11 @@ export default () => {
13
13
  return null;
14
14
  }
15
15
  try {
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();
16
+ let sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true), result = transform(sourceFile);
17
+ if (!result.transformed) {
19
18
  return null;
20
19
  }
21
- let output = printer.printFile(transformed);
22
- result.dispose();
23
- return { code: output, map: null };
20
+ return { code: result.code, map: null };
24
21
  }
25
22
  catch (error) {
26
23
  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 createArrayTransformer: (bindings: Bindings) => (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile;
4
- export { createArrayTransformer };
3
+ declare const transformReactiveArrays: (sourceFile: ts.SourceFile, bindings: Bindings) => string;
4
+ export { transformReactiveArrays };
@@ -1,4 +1,4 @@
1
- import { createArrayLengthCall, createArraySetCall } from '../factory.js';
1
+ import { applyReplacements } from './utilities.js';
2
2
  import { ts } from '@esportsplus/typescript';
3
3
  function getExpressionName(node) {
4
4
  if (ts.isIdentifier(node)) {
@@ -23,9 +23,6 @@ function getPropertyPath(node) {
23
23
  }
24
24
  function isAssignmentTarget(node) {
25
25
  let parent = node.parent;
26
- if (!parent) {
27
- return false;
28
- }
29
26
  if ((ts.isBinaryExpression(parent) && parent.left === node) ||
30
27
  ts.isPostfixUnaryExpression(parent) ||
31
28
  ts.isPrefixUnaryExpression(parent)) {
@@ -61,8 +58,12 @@ function visit(ctx, node) {
61
58
  !isAssignmentTarget(node)) {
62
59
  let name = getExpressionName(node.expression);
63
60
  if (name && ctx.bindings.get(name) === 'array') {
64
- let transformedExpr = ts.visitEachChild(node.expression, n => visit(ctx, n), ctx.context);
65
- return createArrayLengthCall(ctx.factory, transformedExpr);
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
+ });
66
67
  }
67
68
  }
68
69
  if (ts.isBinaryExpression(node) &&
@@ -70,22 +71,23 @@ function visit(ctx, node) {
70
71
  ts.isElementAccessExpression(node.left)) {
71
72
  let elemAccess = node.left, objName = getExpressionName(elemAccess.expression);
72
73
  if (objName && ctx.bindings.get(objName) === 'array') {
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);
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
+ });
75
80
  }
76
81
  }
77
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
82
+ ts.forEachChild(node, n => visit(ctx, n));
78
83
  }
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
- };
84
+ const transformReactiveArrays = (sourceFile, bindings) => {
85
+ let code = sourceFile.getFullText(), ctx = {
86
+ bindings,
87
+ replacements: [],
88
+ sourceFile
89
89
  };
90
+ visit(ctx, sourceFile);
91
+ return applyReplacements(code, ctx.replacements);
90
92
  };
91
- export { createArrayTransformer };
93
+ export { transformReactiveArrays };
@@ -1,10 +1,4 @@
1
- import type { Bindings, Namespaces } from '../../types.js';
1
+ import type { Bindings } from '../../types.js';
2
2
  import { ts } from '@esportsplus/typescript';
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 };
3
+ declare const transformReactiveObjects: (sourceFile: ts.SourceFile, bindings: Bindings) => string;
4
+ export { transformReactiveObjects };
@@ -1,6 +1,12 @@
1
1
  import { uid } from '@esportsplus/typescript/transformer';
2
+ import { addMissingImports, applyReplacements } from './utilities.js';
2
3
  import { ts } from '@esportsplus/typescript';
3
- function analyzeProperty(prop) {
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) {
4
10
  if (!ts.isPropertyAssignment(prop)) {
5
11
  return null;
6
12
  }
@@ -11,71 +17,51 @@ function analyzeProperty(prop) {
11
17
  else {
12
18
  return null;
13
19
  }
14
- let value = prop.initializer;
20
+ let value = prop.initializer, valueText = value.getText(sourceFile);
15
21
  if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) {
16
- return { key, type: 'computed', value };
22
+ return { key, type: 'computed', valueText };
17
23
  }
18
24
  if (ts.isArrayLiteralExpression(value)) {
19
- return { elements: [...value.elements], key, type: 'array', value };
25
+ return { key, type: 'array', valueText };
20
26
  }
21
- return { key, type: 'signal', value };
27
+ return { key, type: 'signal', valueText };
22
28
  }
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 = [];
29
+ function buildClassCode(className, properties) {
30
+ let accessors = [], disposeStatements = [], fields = [];
31
+ fields.push(`[REACTIVE_OBJECT] = true;`);
28
32
  for (let i = 0, n = properties.length; i < n; i++) {
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)));
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}); }`);
45
39
  }
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, [])));
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();`);
53
44
  }
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)]))));
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});`);
71
49
  }
72
50
  }
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);
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
+ `;
76
61
  }
77
62
  function visit(ctx, node) {
78
63
  if (ts.isImportDeclaration(node)) {
64
+ ctx.lastImportEnd = node.end;
79
65
  if (ts.isStringLiteral(node.moduleSpecifier) &&
80
66
  node.moduleSpecifier.text.includes('@esportsplus/reactivity')) {
81
67
  let clause = node.importClause;
@@ -95,58 +81,83 @@ function visit(ctx, node) {
95
81
  ts.isIdentifier(node.expression) &&
96
82
  node.expression.text === 'reactive') {
97
83
  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
- }
107
84
  if (arg && ts.isObjectLiteralExpression(arg)) {
108
85
  let varName = null;
109
86
  if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
110
87
  varName = node.parent.name.text;
111
88
  ctx.bindings.set(varName, 'object');
112
89
  }
113
- let properties = [], props = arg.properties;
90
+ let needsImports = new Set(), properties = [];
91
+ needsImports.add('REACTIVE_OBJECT');
92
+ let props = arg.properties;
114
93
  for (let i = 0, n = props.length; i < n; i++) {
115
94
  let prop = props[i];
116
95
  if (ts.isSpreadAssignment(prop)) {
117
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
96
+ ts.forEachChild(node, n => visit(ctx, n));
97
+ return;
118
98
  }
119
- let analyzed = analyzeProperty(prop);
99
+ let analyzed = analyzeProperty(prop, ctx.sourceFile);
120
100
  if (!analyzed) {
121
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
101
+ ts.forEachChild(node, n => visit(ctx, n));
102
+ return;
122
103
  }
123
104
  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
+ }
124
121
  }
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)
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
130
129
  });
131
- return ctx.factory.createNewExpression(ctx.factory.createIdentifier(className), undefined, []);
132
130
  }
133
131
  }
134
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
132
+ ts.forEachChild(node, n => visit(ctx, n));
135
133
  }
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
- };
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
150
142
  };
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);
151
162
  };
152
- export { createObjectTransformer };
163
+ export { transformReactiveObjects };
@@ -1,4 +1,4 @@
1
- import type { Bindings, Namespaces } from '../../types.js';
1
+ import type { Bindings } from '../../types.js';
2
2
  import { ts } from '@esportsplus/typescript';
3
- declare const createPrimitivesTransformer: (bindings: Bindings, neededImports: Set<string>, ns: Namespaces) => (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile;
4
- export { createPrimitivesTransformer };
3
+ declare const transformReactivePrimitives: (sourceFile: ts.SourceFile, bindings: Bindings) => string;
4
+ export { transformReactivePrimitives };