@esportsplus/reactivity 0.22.1 → 0.23.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.
- package/.github/workflows/bump.yml +2 -2
- package/.github/workflows/dependabot.yml +1 -1
- package/.github/workflows/publish.yml +2 -2
- package/build/index.d.ts +1 -1
- package/build/index.js +1 -1
- package/build/reactive/array.d.ts +3 -0
- package/build/reactive/array.js +32 -2
- package/build/reactive/index.d.ts +17 -14
- package/build/reactive/index.js +7 -25
- package/build/system.js +1 -1
- package/build/transformer/core/detector.d.ts +2 -0
- package/build/transformer/core/detector.js +6 -0
- package/build/transformer/core/index.d.ts +10 -0
- package/build/transformer/core/index.js +55 -0
- package/build/transformer/core/transforms/auto-dispose.d.ts +3 -0
- package/build/transformer/core/transforms/auto-dispose.js +116 -0
- package/build/transformer/core/transforms/reactive-array.d.ts +4 -0
- package/build/transformer/core/transforms/reactive-array.js +89 -0
- package/build/transformer/core/transforms/reactive-object.d.ts +4 -0
- package/build/transformer/core/transforms/reactive-object.js +155 -0
- package/build/transformer/core/transforms/reactive-primitives.d.ts +4 -0
- package/build/transformer/core/transforms/reactive-primitives.js +325 -0
- package/build/transformer/core/transforms/utilities.d.ts +9 -0
- package/build/transformer/core/transforms/utilities.js +57 -0
- package/build/transformer/plugins/esbuild.d.ts +5 -0
- package/build/transformer/plugins/esbuild.js +30 -0
- package/build/transformer/plugins/tsc.d.ts +3 -0
- package/build/transformer/plugins/tsc.js +4 -0
- package/build/transformer/plugins/vite.d.ts +5 -0
- package/build/transformer/plugins/vite.js +28 -0
- package/build/types.d.ts +14 -4
- package/package.json +34 -3
- package/readme.md +276 -2
- package/src/constants.ts +1 -1
- package/src/index.ts +1 -1
- package/src/reactive/array.ts +49 -2
- package/src/reactive/index.ts +33 -57
- package/src/system.ts +14 -5
- package/src/transformer/core/detector.ts +12 -0
- package/src/transformer/core/index.ts +82 -0
- package/src/transformer/core/transforms/auto-dispose.ts +194 -0
- package/src/transformer/core/transforms/reactive-array.ts +140 -0
- package/src/transformer/core/transforms/reactive-object.ts +244 -0
- package/src/transformer/core/transforms/reactive-primitives.ts +459 -0
- package/src/transformer/core/transforms/utilities.ts +95 -0
- package/src/transformer/plugins/esbuild.ts +46 -0
- package/src/transformer/plugins/tsc.ts +8 -0
- package/src/transformer/plugins/vite.ts +41 -0
- package/src/types.ts +24 -5
- package/test/arrays.ts +146 -0
- package/test/effects.ts +168 -0
- package/test/index.ts +8 -0
- package/test/nested.ts +201 -0
- package/test/objects.ts +106 -0
- package/test/primitives.ts +171 -0
- package/test/vite.config.ts +40 -0
- package/build/reactive/object.d.ts +0 -7
- package/build/reactive/object.js +0 -79
- package/src/reactive/object.ts +0 -116
|
@@ -5,7 +5,7 @@ on:
|
|
|
5
5
|
types: [published]
|
|
6
6
|
workflow_dispatch:
|
|
7
7
|
workflow_run:
|
|
8
|
-
workflows: [bump]
|
|
8
|
+
workflows: [bump version]
|
|
9
9
|
types:
|
|
10
10
|
- completed
|
|
11
11
|
|
|
@@ -13,4 +13,4 @@ jobs:
|
|
|
13
13
|
publish:
|
|
14
14
|
secrets:
|
|
15
15
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
16
|
-
uses: esportsplus/
|
|
16
|
+
uses: esportsplus/typescript/.github/workflows/publish.yml@main
|
package/build/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { default as reactive } from './reactive/index.js';
|
|
2
1
|
export { STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED } from './constants.js';
|
|
2
|
+
export { default as reactive } from './reactive/index.js';
|
|
3
3
|
export * from './system.js';
|
|
4
4
|
export * from './types.js';
|
package/build/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { default as reactive } from './reactive/index.js';
|
|
2
1
|
export { STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED } from './constants.js';
|
|
2
|
+
export { default as reactive } from './reactive/index.js';
|
|
3
3
|
export * from './system.js';
|
|
4
4
|
export * from './types.js';
|
|
@@ -35,8 +35,11 @@ type Listener<V> = {
|
|
|
35
35
|
};
|
|
36
36
|
type Listeners = Record<string, (Listener<any> | null)[]>;
|
|
37
37
|
declare class ReactiveArray<T> extends Array<T> {
|
|
38
|
+
private _length;
|
|
38
39
|
listeners: Listeners;
|
|
39
40
|
constructor(...items: T[]);
|
|
41
|
+
$length(): number;
|
|
42
|
+
$set(i: number, value: T): void;
|
|
40
43
|
clear(): void;
|
|
41
44
|
concat(...items: ConcatArray<T>[]): ReactiveArray<T>;
|
|
42
45
|
concat(...items: (T | ConcatArray<T>)[]): ReactiveArray<T>;
|
package/build/reactive/array.js
CHANGED
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
import { isArray } from '@esportsplus/utilities';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { read, set, signal } from '../system.js';
|
|
3
|
+
import { REACTIVE_ARRAY, REACTIVE_OBJECT } from '../constants.js';
|
|
4
|
+
function isReactiveObject(value) {
|
|
5
|
+
return value !== null && typeof value === 'object' && value[REACTIVE_OBJECT] === true;
|
|
6
|
+
}
|
|
4
7
|
class ReactiveArray extends Array {
|
|
8
|
+
_length;
|
|
5
9
|
listeners = {};
|
|
6
10
|
constructor(...items) {
|
|
7
11
|
super(...items);
|
|
12
|
+
this._length = signal(items.length);
|
|
13
|
+
}
|
|
14
|
+
$length() {
|
|
15
|
+
return read(this._length);
|
|
16
|
+
}
|
|
17
|
+
$set(i, value) {
|
|
18
|
+
let prev = this[i];
|
|
19
|
+
if (prev === value) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
this[i] = value;
|
|
23
|
+
if (i >= super.length) {
|
|
24
|
+
set(this._length, i + 1);
|
|
25
|
+
}
|
|
26
|
+
this.dispatch('set', { index: i, item: value });
|
|
8
27
|
}
|
|
9
28
|
clear() {
|
|
10
29
|
this.dispose();
|
|
30
|
+
set(this._length, 0);
|
|
11
31
|
this.dispatch('clear');
|
|
12
32
|
}
|
|
13
33
|
concat(...items) {
|
|
@@ -26,6 +46,7 @@ class ReactiveArray extends Array {
|
|
|
26
46
|
}
|
|
27
47
|
}
|
|
28
48
|
if (added.length) {
|
|
49
|
+
set(this._length, super.length);
|
|
29
50
|
this.dispatch('concat', { items: added });
|
|
30
51
|
}
|
|
31
52
|
return this;
|
|
@@ -62,6 +83,7 @@ class ReactiveArray extends Array {
|
|
|
62
83
|
item.dispose();
|
|
63
84
|
}
|
|
64
85
|
}
|
|
86
|
+
set(this._length, 0);
|
|
65
87
|
}
|
|
66
88
|
on(event, listener) {
|
|
67
89
|
let listeners = this.listeners[event];
|
|
@@ -92,6 +114,7 @@ class ReactiveArray extends Array {
|
|
|
92
114
|
pop() {
|
|
93
115
|
let item = super.pop();
|
|
94
116
|
if (item !== undefined) {
|
|
117
|
+
set(this._length, super.length);
|
|
95
118
|
if (isReactiveObject(item)) {
|
|
96
119
|
item.dispose();
|
|
97
120
|
}
|
|
@@ -100,7 +123,11 @@ class ReactiveArray extends Array {
|
|
|
100
123
|
return item;
|
|
101
124
|
}
|
|
102
125
|
push(...items) {
|
|
126
|
+
if (!items.length) {
|
|
127
|
+
return super.length;
|
|
128
|
+
}
|
|
103
129
|
let length = super.push(...items);
|
|
130
|
+
set(this._length, length);
|
|
104
131
|
this.dispatch('push', { items });
|
|
105
132
|
return length;
|
|
106
133
|
}
|
|
@@ -112,6 +139,7 @@ class ReactiveArray extends Array {
|
|
|
112
139
|
shift() {
|
|
113
140
|
let item = super.shift();
|
|
114
141
|
if (item !== undefined) {
|
|
142
|
+
set(this._length, super.length);
|
|
115
143
|
if (isReactiveObject(item)) {
|
|
116
144
|
item.dispose();
|
|
117
145
|
}
|
|
@@ -151,6 +179,7 @@ class ReactiveArray extends Array {
|
|
|
151
179
|
splice(start, deleteCount = this.length, ...items) {
|
|
152
180
|
let removed = super.splice(start, deleteCount, ...items);
|
|
153
181
|
if (items.length > 0 || removed.length > 0) {
|
|
182
|
+
set(this._length, super.length);
|
|
154
183
|
for (let i = 0, n = removed.length; i < n; i++) {
|
|
155
184
|
let item = removed[i];
|
|
156
185
|
if (isReactiveObject(item)) {
|
|
@@ -163,6 +192,7 @@ class ReactiveArray extends Array {
|
|
|
163
192
|
}
|
|
164
193
|
unshift(...items) {
|
|
165
194
|
let length = super.unshift(...items);
|
|
195
|
+
set(this._length, length);
|
|
166
196
|
this.dispatch('unshift', { items });
|
|
167
197
|
return length;
|
|
168
198
|
}
|
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { REACTIVE_OBJECT } from '../constants.js';
|
|
2
2
|
import { ReactiveArray } from './array.js';
|
|
3
|
-
|
|
4
|
-
type
|
|
5
|
-
[
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
type Guard<T> = T extends {
|
|
3
|
+
declare const READONLY: unique symbol;
|
|
4
|
+
type ReactiveObject<T extends Record<PropertyKey, unknown>> = T & {
|
|
5
|
+
[REACTIVE_OBJECT]: true;
|
|
6
|
+
dispose(): void;
|
|
7
|
+
};
|
|
8
|
+
type ReactiveObjectGuard<T> = T extends {
|
|
10
9
|
dispose: any;
|
|
11
10
|
} ? {
|
|
12
|
-
never: '[ dispose ]
|
|
11
|
+
never: '[ dispose ] is a reserved key';
|
|
13
12
|
} : T;
|
|
14
|
-
|
|
15
|
-
[
|
|
16
|
-
}
|
|
17
|
-
declare
|
|
18
|
-
|
|
13
|
+
declare function reactive<T extends () => unknown>(_input: T): ReturnType<T> & {
|
|
14
|
+
readonly [READONLY]: true;
|
|
15
|
+
};
|
|
16
|
+
declare function reactive<T extends Record<PropertyKey, any>>(_input: ReactiveObjectGuard<T>): ReactiveObject<T>;
|
|
17
|
+
declare function reactive<T>(_input: T[]): ReactiveArray<T>;
|
|
18
|
+
declare function reactive<T>(_input: T): T;
|
|
19
|
+
export default reactive;
|
|
20
|
+
export { reactive, ReactiveArray };
|
|
21
|
+
export type { ReactiveObject };
|
package/build/reactive/index.js
CHANGED
|
@@ -1,26 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { onCleanup, root } from '../system.js';
|
|
1
|
+
import { REACTIVE_OBJECT } from '../constants.js';
|
|
3
2
|
import { ReactiveArray } from './array.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
else if (isArray(input)) {
|
|
12
|
-
response = new ReactiveArray(...input);
|
|
13
|
-
}
|
|
14
|
-
if (response) {
|
|
15
|
-
if (root.disposables) {
|
|
16
|
-
dispose = true;
|
|
17
|
-
}
|
|
18
|
-
return response;
|
|
19
|
-
}
|
|
20
|
-
throw new Error(`@esportsplus/reactivity: 'reactive' received invalid input - ${JSON.stringify(input)}`);
|
|
21
|
-
});
|
|
22
|
-
if (dispose) {
|
|
23
|
-
onCleanup(() => value.dispose());
|
|
24
|
-
}
|
|
25
|
-
return value;
|
|
26
|
-
};
|
|
3
|
+
function reactive(_input) {
|
|
4
|
+
throw new Error('@esportsplus/reactivity: reactive() called at runtime. ' +
|
|
5
|
+
'Ensure vite-plugin-reactivity-compile is configured.');
|
|
6
|
+
}
|
|
7
|
+
export default reactive;
|
|
8
|
+
export { reactive, ReactiveArray };
|
package/build/system.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isObject } from '@esportsplus/utilities';
|
|
2
1
|
import { COMPUTED, SIGNAL, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_NOTIFY_MASK, STATE_RECOMPUTING } from './constants.js';
|
|
2
|
+
import { isObject } from '@esportsplus/utilities';
|
|
3
3
|
let depth = 0, heap = new Array(64), heap_i = 0, heap_n = 0, linkPool = [], linkPoolMax = 1000, microtask = queueMicrotask, notified = false, observer = null, scope = null, stabilizer = STABILIZER_IDLE, version = 0;
|
|
4
4
|
function cleanup(computed) {
|
|
5
5
|
if (!computed.cleanup) {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { mightNeedTransform as checkTransform } from '@esportsplus/typescript/transformer';
|
|
2
|
+
let regex = /import\s*\{[^}]*\breactive\b[^}]*\}\s*from\s*['"]@esportsplus\/reactivity/;
|
|
3
|
+
const mightNeedTransform = (code) => {
|
|
4
|
+
return checkTransform(code, { regex });
|
|
5
|
+
};
|
|
6
|
+
export { mightNeedTransform };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TransformOptions, TransformResult } from '../../types.js';
|
|
2
|
+
import { mightNeedTransform } from './detector.js';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
declare const createTransformer: (options?: TransformOptions) => ts.TransformerFactory<ts.SourceFile>;
|
|
5
|
+
declare const transform: (sourceFile: ts.SourceFile, options?: TransformOptions) => TransformResult;
|
|
6
|
+
export { createTransformer, mightNeedTransform, transform };
|
|
7
|
+
export { injectAutoDispose } from './transforms/auto-dispose.js';
|
|
8
|
+
export { transformReactiveArrays } from './transforms/reactive-array.js';
|
|
9
|
+
export { transformReactiveObjects } from './transforms/reactive-object.js';
|
|
10
|
+
export { transformReactivePrimitives } from './transforms/reactive-primitives.js';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { injectAutoDispose } from './transforms/auto-dispose.js';
|
|
2
|
+
import { mightNeedTransform } from './detector.js';
|
|
3
|
+
import { transformReactiveArrays } from './transforms/reactive-array.js';
|
|
4
|
+
import { transformReactiveObjects } from './transforms/reactive-object.js';
|
|
5
|
+
import { transformReactivePrimitives } from './transforms/reactive-primitives.js';
|
|
6
|
+
import ts from 'typescript';
|
|
7
|
+
const createTransformer = (options) => {
|
|
8
|
+
return () => {
|
|
9
|
+
return (sourceFile) => {
|
|
10
|
+
let result = transform(sourceFile, options);
|
|
11
|
+
return result.transformed ? result.sourceFile : sourceFile;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
const transform = (sourceFile, options) => {
|
|
16
|
+
let bindings = new Map(), code = sourceFile.getFullText(), current = sourceFile, original = code, result;
|
|
17
|
+
if (!mightNeedTransform(code)) {
|
|
18
|
+
return { code, sourceFile, transformed: false };
|
|
19
|
+
}
|
|
20
|
+
result = transformReactiveObjects(current, bindings);
|
|
21
|
+
if (result !== code) {
|
|
22
|
+
current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
23
|
+
code = result;
|
|
24
|
+
}
|
|
25
|
+
result = transformReactiveArrays(current, bindings);
|
|
26
|
+
if (result !== code) {
|
|
27
|
+
current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
28
|
+
code = result;
|
|
29
|
+
}
|
|
30
|
+
result = transformReactivePrimitives(current, bindings);
|
|
31
|
+
if (result !== code) {
|
|
32
|
+
current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
33
|
+
code = result;
|
|
34
|
+
}
|
|
35
|
+
if (options?.autoDispose) {
|
|
36
|
+
result = injectAutoDispose(current);
|
|
37
|
+
if (result !== code) {
|
|
38
|
+
current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
39
|
+
code = result;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (code === original) {
|
|
43
|
+
return { code, sourceFile, transformed: false };
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
code,
|
|
47
|
+
sourceFile: current,
|
|
48
|
+
transformed: true
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
export { createTransformer, mightNeedTransform, transform };
|
|
52
|
+
export { injectAutoDispose } from './transforms/auto-dispose.js';
|
|
53
|
+
export { transformReactiveArrays } from './transforms/reactive-array.js';
|
|
54
|
+
export { transformReactiveObjects } from './transforms/reactive-object.js';
|
|
55
|
+
export { transformReactivePrimitives } from './transforms/reactive-primitives.js';
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { uid } from '@esportsplus/typescript/transformer';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
import { applyReplacements } from './utilities.js';
|
|
4
|
+
const TRAILING_SEMICOLON = /;$/;
|
|
5
|
+
function visitFunctionBody(body, parentBody) {
|
|
6
|
+
let disposables = [], effectsToCapture = [], returnStatement = null;
|
|
7
|
+
function visit(n) {
|
|
8
|
+
if (ts.isVariableDeclaration(n) &&
|
|
9
|
+
ts.isIdentifier(n.name) &&
|
|
10
|
+
n.initializer &&
|
|
11
|
+
ts.isCallExpression(n.initializer) &&
|
|
12
|
+
ts.isIdentifier(n.initializer.expression) &&
|
|
13
|
+
n.initializer.expression.text === 'reactive') {
|
|
14
|
+
disposables.push({ name: n.name.text, type: 'reactive' });
|
|
15
|
+
}
|
|
16
|
+
if (ts.isCallExpression(n) &&
|
|
17
|
+
ts.isIdentifier(n.expression) &&
|
|
18
|
+
n.expression.text === 'effect') {
|
|
19
|
+
if (ts.isVariableDeclaration(n.parent) && ts.isIdentifier(n.parent.name)) {
|
|
20
|
+
disposables.push({ name: n.parent.name.text, type: 'effect' });
|
|
21
|
+
}
|
|
22
|
+
else if (ts.isExpressionStatement(n.parent)) {
|
|
23
|
+
let name = uid('effect');
|
|
24
|
+
effectsToCapture.push({
|
|
25
|
+
end: n.parent.end,
|
|
26
|
+
name,
|
|
27
|
+
start: n.parent.pos
|
|
28
|
+
});
|
|
29
|
+
disposables.push({ name, type: 'effect' });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (ts.isReturnStatement(n) &&
|
|
33
|
+
n.expression &&
|
|
34
|
+
(ts.isArrowFunction(n.expression) || ts.isFunctionExpression(n.expression)) &&
|
|
35
|
+
n.parent === parentBody) {
|
|
36
|
+
returnStatement = n;
|
|
37
|
+
}
|
|
38
|
+
ts.forEachChild(n, visit);
|
|
39
|
+
}
|
|
40
|
+
visit(body);
|
|
41
|
+
return { disposables, effectsToCapture, returnStatement };
|
|
42
|
+
}
|
|
43
|
+
function processFunction(node, sourceFile, edits) {
|
|
44
|
+
if (!node.body || !ts.isBlock(node.body)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
let result = visitFunctionBody(node.body, node.body), disposables = result.disposables, effectsToCapture = result.effectsToCapture, returnStatement = result.returnStatement;
|
|
48
|
+
if (disposables.length === 0 || !returnStatement || !returnStatement.expression) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
let cleanupFn = returnStatement.expression;
|
|
52
|
+
if (!cleanupFn.body) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
let disposeStatements = [];
|
|
56
|
+
for (let i = disposables.length - 1; i >= 0; i--) {
|
|
57
|
+
let d = disposables[i];
|
|
58
|
+
if (d.type === 'reactive') {
|
|
59
|
+
disposeStatements.push(`${d.name}.dispose();`);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
disposeStatements.push(`${d.name}();`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
let disposeCode = disposeStatements.join('\n');
|
|
66
|
+
if (ts.isBlock(cleanupFn.body)) {
|
|
67
|
+
edits.push({
|
|
68
|
+
cleanupBodyEnd: cleanupFn.body.statements[0]?.pos ?? cleanupFn.body.end - 1,
|
|
69
|
+
cleanupBodyStart: cleanupFn.body.pos + 1,
|
|
70
|
+
disposeCode,
|
|
71
|
+
effectsToCapture
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
edits.push({
|
|
76
|
+
cleanupBodyEnd: cleanupFn.body.end,
|
|
77
|
+
cleanupBodyStart: cleanupFn.body.pos,
|
|
78
|
+
disposeCode: `{ ${disposeCode}\n return ${cleanupFn.body.getText(sourceFile)}; }`,
|
|
79
|
+
effectsToCapture
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const injectAutoDispose = (sourceFile) => {
|
|
84
|
+
let code = sourceFile.getFullText(), edits = [];
|
|
85
|
+
function visit(node) {
|
|
86
|
+
if (ts.isFunctionDeclaration(node) ||
|
|
87
|
+
ts.isFunctionExpression(node) ||
|
|
88
|
+
ts.isArrowFunction(node)) {
|
|
89
|
+
processFunction(node, sourceFile, edits);
|
|
90
|
+
}
|
|
91
|
+
ts.forEachChild(node, visit);
|
|
92
|
+
}
|
|
93
|
+
visit(sourceFile);
|
|
94
|
+
if (edits.length === 0) {
|
|
95
|
+
return code;
|
|
96
|
+
}
|
|
97
|
+
let replacements = [];
|
|
98
|
+
for (let i = 0, n = edits.length; i < n; i++) {
|
|
99
|
+
let edit = edits[i], effects = edit.effectsToCapture;
|
|
100
|
+
for (let j = 0, m = effects.length; j < m; j++) {
|
|
101
|
+
let effect = effects[j], original = code.substring(effect.start, effect.end).trim();
|
|
102
|
+
replacements.push({
|
|
103
|
+
end: effect.end,
|
|
104
|
+
newText: `const ${effect.name} = ${original.replace(TRAILING_SEMICOLON, '')}`,
|
|
105
|
+
start: effect.start
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
replacements.push({
|
|
109
|
+
end: edit.cleanupBodyEnd,
|
|
110
|
+
newText: `\n${edit.disposeCode}`,
|
|
111
|
+
start: edit.cleanupBodyStart
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return applyReplacements(code, replacements);
|
|
115
|
+
};
|
|
116
|
+
export { injectAutoDispose };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import { applyReplacements } from './utilities.js';
|
|
3
|
+
function getPropertyPath(node) {
|
|
4
|
+
let current = node, parts = [];
|
|
5
|
+
while (ts.isPropertyAccessExpression(current)) {
|
|
6
|
+
parts.unshift(current.name.text);
|
|
7
|
+
current = current.expression;
|
|
8
|
+
}
|
|
9
|
+
if (ts.isIdentifier(current)) {
|
|
10
|
+
parts.unshift(current.text);
|
|
11
|
+
return parts.join('.');
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
function getExpressionName(node) {
|
|
16
|
+
if (ts.isIdentifier(node)) {
|
|
17
|
+
return node.text;
|
|
18
|
+
}
|
|
19
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
20
|
+
return getPropertyPath(node);
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function isAssignmentTarget(node) {
|
|
25
|
+
let parent = node.parent;
|
|
26
|
+
if ((ts.isBinaryExpression(parent) && parent.left === node) ||
|
|
27
|
+
ts.isPostfixUnaryExpression(parent) ||
|
|
28
|
+
ts.isPrefixUnaryExpression(parent)) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const transformReactiveArrays = (sourceFile, bindings) => {
|
|
34
|
+
let code = sourceFile.getFullText(), replacements = [];
|
|
35
|
+
function visit(node) {
|
|
36
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
|
|
37
|
+
if (ts.isIdentifier(node.initializer) && bindings.get(node.initializer.text) === 'array') {
|
|
38
|
+
bindings.set(node.name.text, 'array');
|
|
39
|
+
}
|
|
40
|
+
if (ts.isPropertyAccessExpression(node.initializer)) {
|
|
41
|
+
let path = getPropertyPath(node.initializer);
|
|
42
|
+
if (path && bindings.get(path) === 'array') {
|
|
43
|
+
bindings.set(node.name.text, 'array');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if ((ts.isFunctionDeclaration(node) || ts.isArrowFunction(node)) && node.parameters) {
|
|
48
|
+
for (let i = 0, n = node.parameters.length; i < n; i++) {
|
|
49
|
+
let param = node.parameters[i];
|
|
50
|
+
if ((ts.isIdentifier(param.name) && param.type) &&
|
|
51
|
+
ts.isTypeReferenceNode(param.type) &&
|
|
52
|
+
ts.isIdentifier(param.type.typeName) &&
|
|
53
|
+
param.type.typeName.text === 'ReactiveArray') {
|
|
54
|
+
bindings.set(param.name.text, 'array');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (ts.isPropertyAccessExpression(node) &&
|
|
59
|
+
node.name.text === 'length' &&
|
|
60
|
+
!isAssignmentTarget(node)) {
|
|
61
|
+
let objName = getExpressionName(node.expression);
|
|
62
|
+
if (objName && bindings.get(objName) === 'array') {
|
|
63
|
+
let objText = node.expression.getText(sourceFile);
|
|
64
|
+
replacements.push({
|
|
65
|
+
end: node.end,
|
|
66
|
+
newText: `${objText}.$length()`,
|
|
67
|
+
start: node.pos
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (ts.isBinaryExpression(node) &&
|
|
72
|
+
node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
73
|
+
ts.isElementAccessExpression(node.left)) {
|
|
74
|
+
let elemAccess = node.left, objName = getExpressionName(elemAccess.expression);
|
|
75
|
+
if (objName && bindings.get(objName) === 'array') {
|
|
76
|
+
let indexText = elemAccess.argumentExpression.getText(sourceFile), objText = elemAccess.expression.getText(sourceFile), valueText = node.right.getText(sourceFile);
|
|
77
|
+
replacements.push({
|
|
78
|
+
end: node.end,
|
|
79
|
+
newText: `${objText}.$set(${indexText}, ${valueText})`,
|
|
80
|
+
start: node.pos
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
ts.forEachChild(node, visit);
|
|
85
|
+
}
|
|
86
|
+
visit(sourceFile);
|
|
87
|
+
return applyReplacements(code, replacements);
|
|
88
|
+
};
|
|
89
|
+
export { transformReactiveArrays };
|