@esportsplus/reactivity 0.34.0 → 0.36.1
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/README.md +9 -7
- package/bench/reactive/array.bench.ts +17 -17
- package/build/compiler/array.d.ts +2 -2
- package/build/compiler/array.js +10 -52
- package/build/compiler/constants.d.ts +4 -4
- package/build/compiler/constants.js +20 -4
- package/build/compiler/index.js +25 -38
- package/build/compiler/object.d.ts +2 -2
- package/build/compiler/object.js +5 -16
- package/build/compiler/primitives.d.ts +6 -2
- package/build/compiler/primitives.js +31 -38
- package/build/compiler/types.d.ts +3 -1
- package/build/constants.d.ts +2 -2
- package/build/constants.js +2 -2
- package/build/reactive/array.d.ts +1 -1
- package/build/reactive/array.js +26 -15
- package/build/reactive/index.js +5 -14
- package/build/reactive/object.js +1 -1
- package/build/system.d.ts +1 -4
- package/build/system.js +105 -83
- package/build/types.d.ts +2 -7
- package/package.json +5 -5
- package/pnpm-workspace.yaml +2 -1
- package/src/compiler/array.ts +12 -62
- package/src/compiler/constants.ts +21 -5
- package/src/compiler/index.ts +38 -69
- package/src/compiler/object.ts +7 -24
- package/src/compiler/primitives.ts +51 -49
- package/src/compiler/types.ts +4 -1
- package/src/constants.ts +4 -4
- package/src/reactive/array.ts +33 -17
- package/src/reactive/index.ts +5 -17
- package/src/reactive/object.ts +1 -1
- package/src/system.ts +138 -101
- package/src/types.ts +2 -9
- package/test/async-computed.test.ts +2 -2
- package/test/compiler/compiler.test.ts +38 -22
- package/test/effects.test.ts +37 -1
- package/test/lib/uncaught.ts +26 -0
- package/test/reactive/array.test.ts +169 -99
- package/test/reactive/nested.test.ts +14 -14
- package/test/reactive/objects.test.ts +1 -1
- package/test/reactive/reactive.test.ts +49 -0
- package/test/system.test.ts +130 -22
package/src/compiler/array.ts
CHANGED
|
@@ -1,34 +1,18 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import { ast
|
|
2
|
+
import { ast } from '@esportsplus/typescript/compiler';
|
|
3
3
|
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
4
|
-
import {
|
|
5
|
-
import type { Bindings } from './types';
|
|
4
|
+
import { COMPOUND_OPERATORS, NAMESPACE, TYPES } from './constants';
|
|
5
|
+
import type { Bindings, IsReactiveCall } from './types';
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
type VisitContext = {
|
|
9
9
|
bindings: Bindings;
|
|
10
|
-
|
|
10
|
+
isReactiveCall: IsReactiveCall;
|
|
11
11
|
replacements: ReplacementIntent[];
|
|
12
12
|
sourceFile: ts.SourceFile;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
function isReactiveCall(checker: ts.Checker | undefined, node: ts.Node): node is ts.CallExpression {
|
|
17
|
-
if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression)) {
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
let expr = node.expression;
|
|
22
|
-
|
|
23
|
-
// Use checker to verify symbol origin (handles re-exports)
|
|
24
|
-
if (checker) {
|
|
25
|
-
return imports.includes(checker, expr, PACKAGE_NAME, ENTRYPOINT);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Fallback without checker: match by name only
|
|
29
|
-
return expr.text === ENTRYPOINT;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
16
|
function getElementTypeText(typeNode: ts.TypeNode, sourceFile: ts.SourceFile): string | null {
|
|
33
17
|
if (ts.isArrayTypeNode(typeNode)) {
|
|
34
18
|
return typeNode.elementType.getText(sourceFile);
|
|
@@ -47,42 +31,8 @@ function getElementTypeText(typeNode: ts.TypeNode, sourceFile: ts.SourceFile): s
|
|
|
47
31
|
return null;
|
|
48
32
|
}
|
|
49
33
|
|
|
50
|
-
function getOperator(kind: ts.SyntaxKind) {
|
|
51
|
-
switch (kind) {
|
|
52
|
-
case ts.SyntaxKind.PlusEqualsToken: return '+';
|
|
53
|
-
case ts.SyntaxKind.MinusEqualsToken: return '-';
|
|
54
|
-
case ts.SyntaxKind.AsteriskEqualsToken: return '*';
|
|
55
|
-
case ts.SyntaxKind.SlashEqualsToken: return '/';
|
|
56
|
-
case ts.SyntaxKind.PercentEqualsToken: return '%';
|
|
57
|
-
case ts.SyntaxKind.AsteriskAsteriskEqualsToken: return '**';
|
|
58
|
-
case ts.SyntaxKind.AmpersandEqualsToken: return '&';
|
|
59
|
-
case ts.SyntaxKind.BarEqualsToken: return '|';
|
|
60
|
-
case ts.SyntaxKind.CaretEqualsToken: return '^';
|
|
61
|
-
default: return '+';
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function isAssignmentOperator(kind: ts.SyntaxKind) {
|
|
66
|
-
return kind === ts.SyntaxKind.EqualsToken ||
|
|
67
|
-
kind === ts.SyntaxKind.PlusEqualsToken ||
|
|
68
|
-
kind === ts.SyntaxKind.MinusEqualsToken ||
|
|
69
|
-
kind === ts.SyntaxKind.AsteriskEqualsToken ||
|
|
70
|
-
kind === ts.SyntaxKind.SlashEqualsToken ||
|
|
71
|
-
kind === ts.SyntaxKind.PercentEqualsToken ||
|
|
72
|
-
kind === ts.SyntaxKind.AsteriskAsteriskEqualsToken ||
|
|
73
|
-
kind === ts.SyntaxKind.LessThanLessThanEqualsToken ||
|
|
74
|
-
kind === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken ||
|
|
75
|
-
kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken ||
|
|
76
|
-
kind === ts.SyntaxKind.AmpersandEqualsToken ||
|
|
77
|
-
kind === ts.SyntaxKind.BarEqualsToken ||
|
|
78
|
-
kind === ts.SyntaxKind.CaretEqualsToken ||
|
|
79
|
-
kind === ts.SyntaxKind.BarBarEqualsToken ||
|
|
80
|
-
kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken ||
|
|
81
|
-
kind === ts.SyntaxKind.QuestionQuestionEqualsToken;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
34
|
function visit(ctx: VisitContext, node: ts.Node): void {
|
|
85
|
-
if (isReactiveCall(
|
|
35
|
+
if (ctx.isReactiveCall(node) && node.arguments.length > 0) {
|
|
86
36
|
let arg = node.arguments[0],
|
|
87
37
|
expression = ts.isAsExpression(arg) ? arg.expression : arg;
|
|
88
38
|
|
|
@@ -105,7 +55,7 @@ function visit(ctx: VisitContext, node: ts.Node): void {
|
|
|
105
55
|
ctx.replacements.push({
|
|
106
56
|
node,
|
|
107
57
|
generate: (sf) => expression.elements.length > 0
|
|
108
|
-
? ` new ${NAMESPACE}.ReactiveArray${typeParam}(
|
|
58
|
+
? ` new ${NAMESPACE}.ReactiveArray${typeParam}(${expression.getText(sf)})`
|
|
109
59
|
: ` new ${NAMESPACE}.ReactiveArray${typeParam}()`
|
|
110
60
|
});
|
|
111
61
|
}
|
|
@@ -148,10 +98,10 @@ function visit(ctx: VisitContext, node: ts.Node): void {
|
|
|
148
98
|
parent = node.parent;
|
|
149
99
|
|
|
150
100
|
// arr.length = value OR arr.length += value
|
|
151
|
-
if (parent && ts.isBinaryExpression(parent) && parent.left === node &&
|
|
152
|
-
let op = parent.operatorToken.kind;
|
|
101
|
+
if (parent && ts.isBinaryExpression(parent) && parent.left === node && (parent.operatorToken.kind === ts.SyntaxKind.EqualsToken || COMPOUND_OPERATORS.has(parent.operatorToken.kind))) {
|
|
102
|
+
let op = COMPOUND_OPERATORS.get(parent.operatorToken.kind);
|
|
153
103
|
|
|
154
|
-
if (op ===
|
|
104
|
+
if (op === undefined) {
|
|
155
105
|
ctx.replacements.push({
|
|
156
106
|
node: parent,
|
|
157
107
|
generate: (sf) => `${expr.getText(sf)}.$length = ${parent.right.getText(sf)}`
|
|
@@ -160,7 +110,7 @@ function visit(ctx: VisitContext, node: ts.Node): void {
|
|
|
160
110
|
else {
|
|
161
111
|
ctx.replacements.push({
|
|
162
112
|
node: parent,
|
|
163
|
-
generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${
|
|
113
|
+
generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${op} ${parent.right.getText(sf)}`
|
|
164
114
|
});
|
|
165
115
|
}
|
|
166
116
|
}
|
|
@@ -238,10 +188,10 @@ function visit(ctx: VisitContext, node: ts.Node): void {
|
|
|
238
188
|
}
|
|
239
189
|
|
|
240
190
|
|
|
241
|
-
export default (sourceFile: ts.SourceFile, bindings: Bindings,
|
|
191
|
+
export default (sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: IsReactiveCall): ReplacementIntent[] => {
|
|
242
192
|
let ctx: VisitContext = {
|
|
243
193
|
bindings,
|
|
244
|
-
|
|
194
|
+
isReactiveCall,
|
|
245
195
|
replacements: [],
|
|
246
196
|
sourceFile
|
|
247
197
|
};
|
|
@@ -1,9 +1,26 @@
|
|
|
1
|
+
import { ts } from '@esportsplus/typescript';
|
|
1
2
|
import { uid } from '@esportsplus/typescript/compiler';
|
|
2
3
|
|
|
3
4
|
|
|
4
|
-
const
|
|
5
|
+
const COMPOUND_OPERATORS = new Map<ts.SyntaxKind, string>([
|
|
6
|
+
[ts.SyntaxKind.AmpersandAmpersandEqualsToken, '&&'],
|
|
7
|
+
[ts.SyntaxKind.AmpersandEqualsToken, '&'],
|
|
8
|
+
[ts.SyntaxKind.AsteriskAsteriskEqualsToken, '**'],
|
|
9
|
+
[ts.SyntaxKind.AsteriskEqualsToken, '*'],
|
|
10
|
+
[ts.SyntaxKind.BarBarEqualsToken, '||'],
|
|
11
|
+
[ts.SyntaxKind.BarEqualsToken, '|'],
|
|
12
|
+
[ts.SyntaxKind.CaretEqualsToken, '^'],
|
|
13
|
+
[ts.SyntaxKind.GreaterThanGreaterThanEqualsToken, '>>'],
|
|
14
|
+
[ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, '>>>'],
|
|
15
|
+
[ts.SyntaxKind.LessThanLessThanEqualsToken, '<<'],
|
|
16
|
+
[ts.SyntaxKind.MinusEqualsToken, '-'],
|
|
17
|
+
[ts.SyntaxKind.PercentEqualsToken, '%'],
|
|
18
|
+
[ts.SyntaxKind.PlusEqualsToken, '+'],
|
|
19
|
+
[ts.SyntaxKind.QuestionQuestionEqualsToken, '??'],
|
|
20
|
+
[ts.SyntaxKind.SlashEqualsToken, '/']
|
|
21
|
+
]);
|
|
5
22
|
|
|
6
|
-
const
|
|
23
|
+
const ENTRYPOINT = 'reactive';
|
|
7
24
|
|
|
8
25
|
const NAMESPACE = uid('reactivity');
|
|
9
26
|
|
|
@@ -11,12 +28,11 @@ const NAMESPACE = uid('reactivity');
|
|
|
11
28
|
const TYPES = {
|
|
12
29
|
Array: 0,
|
|
13
30
|
Computed: 1,
|
|
14
|
-
|
|
15
|
-
Signal: 3
|
|
31
|
+
Signal: 2
|
|
16
32
|
} as const;
|
|
17
33
|
|
|
18
34
|
type TYPES = typeof TYPES[keyof typeof TYPES];
|
|
19
35
|
|
|
20
36
|
|
|
21
|
-
export {
|
|
37
|
+
export { COMPOUND_OPERATORS, ENTRYPOINT, NAMESPACE, TYPES };
|
|
22
38
|
export { PACKAGE_NAME } from '../constants';
|
package/src/compiler/index.ts
CHANGED
|
@@ -9,26 +9,6 @@ import object from './object';
|
|
|
9
9
|
import primitives from './primitives';
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
type FindRemainingContext = {
|
|
13
|
-
checker: ts.Checker;
|
|
14
|
-
replacements: ReplacementIntent[];
|
|
15
|
-
sourceFile: ts.SourceFile;
|
|
16
|
-
transformedNodes: Set<ts.Node>;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
function findRemainingCalls(
|
|
21
|
-
checker: ts.Checker,
|
|
22
|
-
sourceFile: ts.SourceFile,
|
|
23
|
-
transformedNodes: Set<ts.Node>
|
|
24
|
-
): ReplacementIntent[] {
|
|
25
|
-
let ctx: FindRemainingContext = { checker, replacements: [], sourceFile, transformedNodes };
|
|
26
|
-
|
|
27
|
-
visit(ctx, sourceFile);
|
|
28
|
-
|
|
29
|
-
return ctx.replacements;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
12
|
function isReactiveCallExpression(checker: ts.Checker, node: ts.Node): node is ts.CallExpression {
|
|
33
13
|
if (!ts.isCallExpression(node)) {
|
|
34
14
|
return false;
|
|
@@ -49,38 +29,13 @@ function isReactiveCallExpression(checker: ts.Checker, node: ts.Node): node is t
|
|
|
49
29
|
return false;
|
|
50
30
|
}
|
|
51
31
|
|
|
52
|
-
function hasReactiveCalls(checker: ts.Checker, node: ts.Node): boolean {
|
|
53
|
-
if (isReactiveCallExpression(checker, node)) {
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let found = false;
|
|
58
|
-
|
|
59
|
-
node.forEachChild(child => {
|
|
60
|
-
if (!found && hasReactiveCalls(checker, child)) {
|
|
61
|
-
found = true;
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
return found;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function visit(ctx: FindRemainingContext, node: ts.Node): void {
|
|
69
|
-
if (isReactiveCallExpression(ctx.checker, node) && !ctx.transformedNodes.has(node) && !ctx.transformedNodes.has(node.expression)) {
|
|
70
|
-
ctx.replacements.push({
|
|
71
|
-
generate: () => `${NAMESPACE}.reactive(${node.arguments.map(a => a.getText(ctx.sourceFile)).join(', ')})`,
|
|
72
|
-
node
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
node.forEachChild(n => visit(ctx, n));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
32
|
|
|
80
33
|
export default {
|
|
81
34
|
patterns: ['reactive(', 'reactive<'],
|
|
82
35
|
transform: (ctx: TransformContext) => {
|
|
83
|
-
|
|
36
|
+
let checker = ctx.checker;
|
|
37
|
+
|
|
38
|
+
if (!checker) {
|
|
84
39
|
return {};
|
|
85
40
|
}
|
|
86
41
|
|
|
@@ -89,36 +44,50 @@ export default {
|
|
|
89
44
|
imports: [] as ImportIntent[],
|
|
90
45
|
prepend: [] as string[],
|
|
91
46
|
replacements: [] as ReplacementIntent[]
|
|
92
|
-
}
|
|
47
|
+
},
|
|
48
|
+
isReactiveCall = (node: ts.Node): node is ts.CallExpression => isReactiveCallExpression(checker, node),
|
|
49
|
+
sourceFile = ctx.sourceFile;
|
|
93
50
|
|
|
94
|
-
// Run primitives transform first (tracks bindings for signal/computed)
|
|
95
|
-
|
|
96
|
-
...primitives(ctx.sourceFile, bindings, (node: ts.Node) => isReactiveCallExpression(ctx.checker!, node))
|
|
97
|
-
);
|
|
51
|
+
// Run primitives transform first (tracks bindings for signal/computed, collects every call)
|
|
52
|
+
let { calls, replacements } = primitives(sourceFile, bindings, isReactiveCall);
|
|
98
53
|
|
|
99
|
-
|
|
100
|
-
|
|
54
|
+
if (calls.length === 0) {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
101
57
|
|
|
102
|
-
intents.prepend.push(...prepend);
|
|
103
58
|
intents.replacements.push(...replacements);
|
|
104
59
|
|
|
60
|
+
// Run object transform
|
|
61
|
+
let objects = object(sourceFile, bindings, isReactiveCall);
|
|
62
|
+
|
|
63
|
+
intents.prepend.push(...objects.prepend);
|
|
64
|
+
intents.replacements.push(...objects.replacements);
|
|
65
|
+
|
|
105
66
|
// Run array transform separately ( avoid race conditions )
|
|
106
|
-
intents.replacements.push(...array(
|
|
107
|
-
|
|
108
|
-
//
|
|
109
|
-
intents.replacements.
|
|
110
|
-
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
67
|
+
intents.replacements.push(...array(sourceFile, bindings, isReactiveCall));
|
|
68
|
+
|
|
69
|
+
// Calls no transform claimed fall through to the runtime reactive()
|
|
70
|
+
let transformed = new Set(intents.replacements.map(r => r.node));
|
|
71
|
+
|
|
72
|
+
for (let i = 0, n = calls.length; i < n; i++) {
|
|
73
|
+
let call = calls[i];
|
|
74
|
+
|
|
75
|
+
if (transformed.has(call) || transformed.has(call.expression)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
intents.replacements.push({
|
|
80
|
+
generate: () => `${NAMESPACE}.reactive(${call.arguments.map(a => a.getText(sourceFile)).join(', ')})`,
|
|
81
|
+
node: call
|
|
119
82
|
});
|
|
120
83
|
}
|
|
121
84
|
|
|
85
|
+
intents.imports.push({
|
|
86
|
+
namespace: NAMESPACE,
|
|
87
|
+
package: PACKAGE_NAME,
|
|
88
|
+
remove: [ENTRYPOINT]
|
|
89
|
+
});
|
|
90
|
+
|
|
122
91
|
return intents;
|
|
123
92
|
}
|
|
124
93
|
};
|
package/src/compiler/object.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import { code,
|
|
2
|
+
import { code, uid } from '@esportsplus/typescript/compiler';
|
|
3
3
|
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
4
|
-
import {
|
|
5
|
-
import type { Bindings } from './types';
|
|
4
|
+
import { NAMESPACE, TYPES } from './constants';
|
|
5
|
+
import type { Bindings, IsReactiveCall } from './types';
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
interface AnalyzedProperty {
|
|
@@ -28,7 +28,7 @@ interface ReactiveObjectCall {
|
|
|
28
28
|
interface VisitContext {
|
|
29
29
|
bindings: Bindings;
|
|
30
30
|
calls: ReactiveObjectCall[];
|
|
31
|
-
|
|
31
|
+
isReactiveCall: IsReactiveCall;
|
|
32
32
|
sourceFile: ts.SourceFile;
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -216,24 +216,8 @@ function isStaticValue(node: ts.Node): boolean {
|
|
|
216
216
|
(ts.isPrefixUnaryExpression(node) && ts.isNumericLiteral(node.operand));
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
function isReactiveCall(checker: ts.Checker | undefined, node: ts.Node): node is ts.CallExpression {
|
|
220
|
-
if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression)) {
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
let expr = node.expression;
|
|
225
|
-
|
|
226
|
-
// Use checker to verify symbol origin (handles re-exports)
|
|
227
|
-
if (checker) {
|
|
228
|
-
return imports.includes(checker, expr, PACKAGE_NAME, ENTRYPOINT);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Fallback without checker: match by name only
|
|
232
|
-
return expr.text === ENTRYPOINT;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
219
|
function visit(ctx: VisitContext, node: ts.Node): void {
|
|
236
|
-
if (isReactiveCall(
|
|
220
|
+
if (ctx.isReactiveCall(node)) {
|
|
237
221
|
let arg = node.arguments[0];
|
|
238
222
|
|
|
239
223
|
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
@@ -243,7 +227,6 @@ function visit(ctx: VisitContext, node: ts.Node): void {
|
|
|
243
227
|
|
|
244
228
|
if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
245
229
|
varname = node.parent.name.text;
|
|
246
|
-
ctx.bindings.set(varname, TYPES.Object);
|
|
247
230
|
}
|
|
248
231
|
|
|
249
232
|
for (let i = 0, n = props.length; i < n; i++) {
|
|
@@ -282,11 +265,11 @@ function visit(ctx: VisitContext, node: ts.Node): void {
|
|
|
282
265
|
}
|
|
283
266
|
|
|
284
267
|
|
|
285
|
-
export default (sourceFile: ts.SourceFile, bindings: Bindings,
|
|
268
|
+
export default (sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: IsReactiveCall): ObjectTransformResult => {
|
|
286
269
|
let ctx: VisitContext = {
|
|
287
270
|
bindings,
|
|
288
271
|
calls: [],
|
|
289
|
-
|
|
272
|
+
isReactiveCall,
|
|
290
273
|
sourceFile
|
|
291
274
|
};
|
|
292
275
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
2
|
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
3
|
-
import { NAMESPACE, TYPES } from './constants';
|
|
4
|
-
import type { Bindings } from './types';
|
|
3
|
+
import { COMPOUND_OPERATORS, NAMESPACE, TYPES } from './constants';
|
|
4
|
+
import type { Bindings, IsReactiveCall } from './types';
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
interface ScopeBinding {
|
|
8
|
+
depth: number;
|
|
8
9
|
name: string;
|
|
9
10
|
scope: ts.Node;
|
|
10
11
|
type: TYPES;
|
|
@@ -12,31 +13,18 @@ interface ScopeBinding {
|
|
|
12
13
|
|
|
13
14
|
interface TransformContext {
|
|
14
15
|
bindings: Bindings;
|
|
15
|
-
|
|
16
|
+
calls: ts.CallExpression[];
|
|
17
|
+
isReactiveCall: IsReactiveCall;
|
|
16
18
|
replacements: ReplacementIntent[];
|
|
17
19
|
scopedBindings: ScopeBinding[];
|
|
18
20
|
sourceFile: ts.SourceFile;
|
|
19
21
|
tmpCounter: number;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
[
|
|
25
|
-
|
|
26
|
-
[ts.SyntaxKind.AsteriskAsteriskEqualsToken, '**'],
|
|
27
|
-
[ts.SyntaxKind.AsteriskEqualsToken, '*'],
|
|
28
|
-
[ts.SyntaxKind.BarBarEqualsToken, '||'],
|
|
29
|
-
[ts.SyntaxKind.BarEqualsToken, '|'],
|
|
30
|
-
[ts.SyntaxKind.CaretEqualsToken, '^'],
|
|
31
|
-
[ts.SyntaxKind.GreaterThanGreaterThanEqualsToken, '>>'],
|
|
32
|
-
[ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, '>>>'],
|
|
33
|
-
[ts.SyntaxKind.LessThanLessThanEqualsToken, '<<'],
|
|
34
|
-
[ts.SyntaxKind.MinusEqualsToken, '-'],
|
|
35
|
-
[ts.SyntaxKind.PercentEqualsToken, '%'],
|
|
36
|
-
[ts.SyntaxKind.PlusEqualsToken, '+'],
|
|
37
|
-
[ts.SyntaxKind.QuestionQuestionEqualsToken, '??'],
|
|
38
|
-
[ts.SyntaxKind.SlashEqualsToken, '/']
|
|
39
|
-
]);
|
|
24
|
+
type PrimitivesTransformResult = {
|
|
25
|
+
calls: ts.CallExpression[];
|
|
26
|
+
replacements: ReplacementIntent[];
|
|
27
|
+
};
|
|
40
28
|
|
|
41
29
|
|
|
42
30
|
function inScope(reference: ts.Node, binding: ScopeBinding): boolean {
|
|
@@ -53,9 +41,44 @@ function inScope(reference: ts.Node, binding: ScopeBinding): boolean {
|
|
|
53
41
|
return false;
|
|
54
42
|
}
|
|
55
43
|
|
|
44
|
+
function isScope(node: ts.Node): boolean {
|
|
45
|
+
return ts.isArrowFunction(node) ||
|
|
46
|
+
ts.isBlock(node) ||
|
|
47
|
+
ts.isCatchClause(node) ||
|
|
48
|
+
ts.isForInStatement(node) ||
|
|
49
|
+
ts.isForOfStatement(node) ||
|
|
50
|
+
ts.isForStatement(node) ||
|
|
51
|
+
ts.isFunctionDeclaration(node) ||
|
|
52
|
+
ts.isFunctionExpression(node) ||
|
|
53
|
+
ts.isSourceFile(node);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Innermost enclosing scope plus its nesting depth, so shadowed names resolve to the closest binding
|
|
57
|
+
function scopeOf(node: ts.Node): { depth: number; scope: ts.Node } {
|
|
58
|
+
let current: ts.Node | undefined = node.parent,
|
|
59
|
+
depth = 0,
|
|
60
|
+
scope: ts.Node = node.getSourceFile();
|
|
61
|
+
|
|
62
|
+
while (current) {
|
|
63
|
+
if (isScope(current)) {
|
|
64
|
+
if (scope === node.getSourceFile() && !ts.isSourceFile(current)) {
|
|
65
|
+
scope = current;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
depth++;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
current = current.parent;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { depth, scope };
|
|
75
|
+
}
|
|
76
|
+
|
|
56
77
|
function visit(ctx: TransformContext, node: ts.Node): void {
|
|
57
78
|
if (ctx.isReactiveCall(node)) {
|
|
58
|
-
let call = node
|
|
79
|
+
let call = node;
|
|
80
|
+
|
|
81
|
+
ctx.calls.push(call);
|
|
59
82
|
|
|
60
83
|
if (call.arguments.length > 0) {
|
|
61
84
|
let arg = call.arguments[0],
|
|
@@ -101,32 +124,10 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
101
124
|
}
|
|
102
125
|
|
|
103
126
|
if (varname) {
|
|
104
|
-
let
|
|
105
|
-
scope;
|
|
106
|
-
|
|
107
|
-
while (current) {
|
|
108
|
-
if (
|
|
109
|
-
ts.isArrowFunction(current) ||
|
|
110
|
-
ts.isBlock(current) ||
|
|
111
|
-
ts.isForInStatement(current) ||
|
|
112
|
-
ts.isForOfStatement(current) ||
|
|
113
|
-
ts.isForStatement(current) ||
|
|
114
|
-
ts.isFunctionDeclaration(current) ||
|
|
115
|
-
ts.isFunctionExpression(current) ||
|
|
116
|
-
ts.isSourceFile(current)
|
|
117
|
-
) {
|
|
118
|
-
scope = current;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
current = current.parent;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (!scope) {
|
|
125
|
-
scope = call.getSourceFile();
|
|
126
|
-
}
|
|
127
|
+
let { depth, scope } = scopeOf(call);
|
|
127
128
|
|
|
128
129
|
ctx.bindings.set(varname, classification);
|
|
129
|
-
ctx.scopedBindings.push({ name: varname, scope, type: classification });
|
|
130
|
+
ctx.scopedBindings.push({ depth, name: varname, scope, type: classification });
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
// Replace just the 'reactive' identifier with the appropriate namespace function
|
|
@@ -159,7 +160,7 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
159
160
|
for (let i = 0, n = bindings.length; i < n; i++) {
|
|
160
161
|
let b = bindings[i];
|
|
161
162
|
|
|
162
|
-
if (b.name === name && inScope(node, b)) {
|
|
163
|
+
if (b.name === name && (!binding || b.depth >= binding.depth) && inScope(node, b)) {
|
|
163
164
|
binding = b;
|
|
164
165
|
}
|
|
165
166
|
}
|
|
@@ -255,9 +256,10 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
255
256
|
}
|
|
256
257
|
|
|
257
258
|
|
|
258
|
-
export default (sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall:
|
|
259
|
+
export default (sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: IsReactiveCall): PrimitivesTransformResult => {
|
|
259
260
|
let ctx: TransformContext = {
|
|
260
261
|
bindings,
|
|
262
|
+
calls: [],
|
|
261
263
|
isReactiveCall,
|
|
262
264
|
replacements: [],
|
|
263
265
|
scopedBindings: [],
|
|
@@ -267,5 +269,5 @@ export default (sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: (
|
|
|
267
269
|
|
|
268
270
|
visit(ctx, sourceFile);
|
|
269
271
|
|
|
270
|
-
return ctx.replacements;
|
|
272
|
+
return { calls: ctx.calls, replacements: ctx.replacements };
|
|
271
273
|
};
|
package/src/compiler/types.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import type { ts } from '@esportsplus/typescript';
|
|
1
2
|
import { TYPES } from './constants';
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
type Bindings = Map<string, TYPES>;
|
|
5
6
|
|
|
7
|
+
type IsReactiveCall = (node: ts.Node) => node is ts.CallExpression;
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
|
|
10
|
+
export type { Bindings, IsReactiveCall };
|
package/src/constants.ts
CHANGED
|
@@ -8,6 +8,8 @@ const REACTIVE_OBJECT = Symbol('reactivity.reactive.object');
|
|
|
8
8
|
|
|
9
9
|
const SIGNAL = Symbol('reactivity.signal');
|
|
10
10
|
|
|
11
|
+
const STABILIZER_DEFERRED = 4;
|
|
12
|
+
|
|
11
13
|
const STABILIZER_IDLE = 0;
|
|
12
14
|
|
|
13
15
|
const STABILIZER_RESCHEDULE = 1;
|
|
@@ -16,8 +18,6 @@ const STABILIZER_RUNNING = 2;
|
|
|
16
18
|
|
|
17
19
|
const STABILIZER_SCHEDULED = 3;
|
|
18
20
|
|
|
19
|
-
const STATE_NONE = 0;
|
|
20
|
-
|
|
21
21
|
const STATE_CHECK = 1 << 0;
|
|
22
22
|
|
|
23
23
|
const STATE_DIRTY = 1 << 1;
|
|
@@ -40,6 +40,6 @@ export {
|
|
|
40
40
|
PACKAGE_NAME,
|
|
41
41
|
REACTIVE_ARRAY, REACTIVE_OBJECT,
|
|
42
42
|
SIGNAL,
|
|
43
|
-
STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED,
|
|
44
|
-
STATE_CHECK, STATE_COMPUTED, STATE_DIRTY, STATE_EFFECT, STATE_ERROR, STATE_IN_HEAP,
|
|
43
|
+
STABILIZER_DEFERRED, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED,
|
|
44
|
+
STATE_CHECK, STATE_COMPUTED, STATE_DIRTY, STATE_EFFECT, STATE_ERROR, STATE_IN_HEAP, STATE_NOTIFY_MASK, STATE_RECOMPUTING
|
|
45
45
|
};
|