@esportsplus/reactivity 0.24.5 → 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)) {
@@ -34,9 +31,6 @@ function isAssignmentTarget(node) {
34
31
  return false;
35
32
  }
36
33
  function visit(ctx, node) {
37
- if (ts.isImportDeclaration(node)) {
38
- return node;
39
- }
40
34
  if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
41
35
  if (ts.isIdentifier(node.initializer) && ctx.bindings.get(node.initializer.text) === 'array') {
42
36
  ctx.bindings.set(node.name.text, 'array');
@@ -64,8 +58,12 @@ function visit(ctx, node) {
64
58
  !isAssignmentTarget(node)) {
65
59
  let name = getExpressionName(node.expression);
66
60
  if (name && ctx.bindings.get(name) === 'array') {
67
- let transformedExpr = ts.visitEachChild(node.expression, n => visit(ctx, n), ctx.context);
68
- 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
+ });
69
67
  }
70
68
  }
71
69
  if (ts.isBinaryExpression(node) &&
@@ -73,22 +71,23 @@ function visit(ctx, node) {
73
71
  ts.isElementAccessExpression(node.left)) {
74
72
  let elemAccess = node.left, objName = getExpressionName(elemAccess.expression);
75
73
  if (objName && ctx.bindings.get(objName) === 'array') {
76
- 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);
77
- 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
+ });
78
80
  }
79
81
  }
80
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
82
+ ts.forEachChild(node, n => visit(ctx, n));
81
83
  }
82
- const createArrayTransformer = (bindings) => {
83
- return (context) => {
84
- return (sourceFile) => {
85
- let ctx = {
86
- bindings,
87
- context,
88
- factory: context.factory
89
- };
90
- return ts.visitNode(sourceFile, n => visit(ctx, n));
91
- };
84
+ const transformReactiveArrays = (sourceFile, bindings) => {
85
+ let code = sourceFile.getFullText(), ctx = {
86
+ bindings,
87
+ replacements: [],
88
+ sourceFile
92
89
  };
90
+ visit(ctx, sourceFile);
91
+ return applyReplacements(code, ctx.replacements);
93
92
  };
94
- 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;
@@ -89,65 +75,89 @@ function visit(ctx, node) {
89
75
  }
90
76
  }
91
77
  }
92
- return node;
93
78
  }
94
79
  if (ctx.hasReactiveImport &&
95
80
  ts.isCallExpression(node) &&
96
81
  ts.isIdentifier(node.expression) &&
97
82
  node.expression.text === 'reactive') {
98
83
  let arg = node.arguments[0];
99
- if (arg && ts.isArrayLiteralExpression(arg)) {
100
- let varName = null;
101
- if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
102
- varName = node.parent.name.text;
103
- ctx.bindings.set(varName, 'array');
104
- }
105
- ctx.neededImports.add('ReactiveArray');
106
- return ctx.factory.createNewExpression(ctx.factory.createPropertyAccessExpression(ctx.factory.createIdentifier(ctx.ns.array), 'ReactiveArray'), undefined, [...arg.elements]);
107
- }
108
84
  if (arg && ts.isObjectLiteralExpression(arg)) {
109
85
  let varName = null;
110
86
  if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
111
87
  varName = node.parent.name.text;
112
88
  ctx.bindings.set(varName, 'object');
113
89
  }
114
- let properties = [], props = arg.properties;
90
+ let needsImports = new Set(), properties = [];
91
+ needsImports.add('REACTIVE_OBJECT');
92
+ let props = arg.properties;
115
93
  for (let i = 0, n = props.length; i < n; i++) {
116
94
  let prop = props[i];
117
95
  if (ts.isSpreadAssignment(prop)) {
118
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
96
+ ts.forEachChild(node, n => visit(ctx, n));
97
+ return;
119
98
  }
120
- let analyzed = analyzeProperty(prop);
99
+ let analyzed = analyzeProperty(prop, ctx.sourceFile);
121
100
  if (!analyzed) {
122
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
101
+ ts.forEachChild(node, n => visit(ctx, n));
102
+ return;
123
103
  }
124
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
+ }
125
121
  }
126
- let className = uid('ReactiveObject'), classDecl = buildReactiveClass(ctx, className, properties, varName);
127
- ctx.generatedClasses.push({
128
- classDecl,
129
- className,
130
- 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
131
129
  });
132
- return ctx.factory.createNewExpression(ctx.factory.createIdentifier(className), undefined, []);
133
130
  }
134
131
  }
135
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
132
+ ts.forEachChild(node, n => visit(ctx, n));
136
133
  }
137
- const createObjectTransformer = (bindings, neededImports, generatedClasses, ns) => {
138
- return (context) => {
139
- return (sourceFile) => {
140
- let ctx = {
141
- bindings,
142
- context,
143
- factory: context.factory,
144
- generatedClasses,
145
- hasReactiveImport: false,
146
- neededImports,
147
- ns
148
- };
149
- return ts.visitNode(sourceFile, n => visit(ctx, n));
150
- };
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
151
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);
152
162
  };
153
- 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 };