@esportsplus/reactivity 0.36.7 → 0.37.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/bench/cellx.bench.ts +4 -4
- package/bench/dynamic.bench.ts +10 -9
- package/bench/kairo.bench.ts +28 -33
- package/bench/molbench.bench.ts +4 -4
- package/bench/reactive/array.bench.ts +163 -163
- package/bench/reactive/reactive-object.bench.ts +47 -45
- package/bench/system.bench.ts +263 -263
- package/build/compiler/array.js +68 -73
- package/build/compiler/bindings.d.ts +19 -0
- package/build/compiler/bindings.js +65 -0
- package/build/compiler/index.js +15 -6
- package/build/compiler/object.js +10 -6
- package/build/compiler/primitives.js +137 -138
- package/build/compiler/types.d.ts +7 -2
- package/build/constants.d.ts +1 -1
- package/build/constants.js +4 -4
- package/build/reactive/array.js +19 -12
- package/build/system.js +1 -1
- package/package.json +2 -2
- package/pnpm-workspace.yaml +2 -4
- package/src/compiler/array.ts +86 -89
- package/src/compiler/bindings.ts +100 -0
- package/src/compiler/index.ts +22 -9
- package/src/compiler/object.ts +12 -7
- package/src/compiler/primitives.ts +190 -187
- package/src/compiler/types.ts +7 -2
- package/src/constants.ts +5 -5
- package/src/reactive/array.ts +27 -14
- package/src/system.ts +3 -1
- package/test/async-iterable.test.ts +42 -0
- package/test/compiler/compiler.test.ts +115 -45
- package/test/reactive/array.test.ts +54 -14
- package/vitest.config.ts +2 -2
package/src/compiler/array.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import { ast } from '@esportsplus/typescript/compiler';
|
|
3
2
|
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
4
3
|
import { COMPOUND_OPERATORS, NAMESPACE, TYPES } from './constants';
|
|
5
4
|
import type { Bindings, IsReactiveCall } from './types';
|
|
5
|
+
import scope from './bindings';
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
type VisitContext = {
|
|
@@ -13,25 +13,9 @@ type VisitContext = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
ts.isTypeReferenceNode(typeNode) &&
|
|
23
|
-
ts.isIdentifier(typeNode.typeName) &&
|
|
24
|
-
typeNode.typeName.text === 'Array' &&
|
|
25
|
-
typeNode.typeArguments &&
|
|
26
|
-
typeNode.typeArguments.length > 0
|
|
27
|
-
) {
|
|
28
|
-
return typeNode.typeArguments[0].getText(sourceFile);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function visit(ctx: VisitContext, node: ts.Node): void {
|
|
16
|
+
// Pass 1, in source order so an alias (`let b = a`) sees the array it copies: reactive([...])
|
|
17
|
+
// declarations, `ReactiveArray`-typed parameters, and aliases of an existing array binding
|
|
18
|
+
function declare(ctx: VisitContext, node: ts.Node): void {
|
|
35
19
|
if (ctx.isReactiveCall(node) && node.arguments.length > 0) {
|
|
36
20
|
let arg = node.arguments[0],
|
|
37
21
|
expression = ts.isAsExpression(arg) ? arg.expression : arg;
|
|
@@ -47,7 +31,7 @@ function visit(ctx: VisitContext, node: ts.Node): void {
|
|
|
47
31
|
}
|
|
48
32
|
|
|
49
33
|
if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
50
|
-
ctx.bindings
|
|
34
|
+
scope.declare(ctx.bindings, node.parent.name, TYPES.Array);
|
|
51
35
|
}
|
|
52
36
|
|
|
53
37
|
let typeParam = elementType ? `<${elementType}>` : '';
|
|
@@ -61,18 +45,14 @@ function visit(ctx: VisitContext, node: ts.Node): void {
|
|
|
61
45
|
}
|
|
62
46
|
}
|
|
63
47
|
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (path && ctx.bindings.get(path) === TYPES.Array) {
|
|
73
|
-
ctx.bindings.set(node.name.text, TYPES.Array);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
48
|
+
if (
|
|
49
|
+
ts.isVariableDeclaration(node) &&
|
|
50
|
+
ts.isIdentifier(node.name) &&
|
|
51
|
+
node.initializer &&
|
|
52
|
+
(ts.isIdentifier(node.initializer) || ts.isPropertyAccessExpression(node.initializer)) &&
|
|
53
|
+
scope.isArray(ctx.bindings, node.initializer)
|
|
54
|
+
) {
|
|
55
|
+
scope.declare(ctx.bindings, node.name, TYPES.Array);
|
|
76
56
|
}
|
|
77
57
|
|
|
78
58
|
if ((ts.isFunctionDeclaration(node) || ts.isArrowFunction(node)) && node.parameters) {
|
|
@@ -80,91 +60,105 @@ function visit(ctx: VisitContext, node: ts.Node): void {
|
|
|
80
60
|
let param = node.parameters[i];
|
|
81
61
|
|
|
82
62
|
if (
|
|
83
|
-
|
|
63
|
+
ts.isIdentifier(param.name) &&
|
|
64
|
+
param.type &&
|
|
84
65
|
ts.isTypeReferenceNode(param.type) &&
|
|
85
66
|
ts.isIdentifier(param.type.typeName) &&
|
|
86
67
|
param.type.typeName.text === 'ReactiveArray'
|
|
87
68
|
) {
|
|
88
|
-
ctx.bindings
|
|
69
|
+
scope.declare(ctx.bindings, param.name, TYPES.Array);
|
|
89
70
|
}
|
|
90
71
|
}
|
|
91
72
|
}
|
|
92
73
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (name && ctx.bindings.get(name) === TYPES.Array) {
|
|
97
|
-
let expr = node.expression,
|
|
98
|
-
parent = node.parent;
|
|
99
|
-
|
|
100
|
-
// arr.length = value OR arr.length += value
|
|
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);
|
|
103
|
-
|
|
104
|
-
if (op === undefined) {
|
|
105
|
-
ctx.replacements.push({
|
|
106
|
-
node: parent,
|
|
107
|
-
generate: (sf) => `${expr.getText(sf)}.$length = ${parent.right.getText(sf)}`
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
ctx.replacements.push({
|
|
112
|
-
node: parent,
|
|
113
|
-
generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${op} ${parent.right.getText(sf)}`
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
// arr.length++ or arr.length--
|
|
118
|
-
else if (parent && ts.isPostfixUnaryExpression(parent)) {
|
|
119
|
-
let op = parent.operator === ts.SyntaxKind.PlusPlusToken ? '+' : '-';
|
|
74
|
+
node.forEachChild(child => declare(ctx, child));
|
|
75
|
+
}
|
|
120
76
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
77
|
+
function getElementTypeText(typeNode: ts.TypeNode, sourceFile: ts.SourceFile): string | null {
|
|
78
|
+
if (ts.isArrayTypeNode(typeNode)) {
|
|
79
|
+
return typeNode.elementType.getText(sourceFile);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (
|
|
83
|
+
ts.isTypeReferenceNode(typeNode) &&
|
|
84
|
+
ts.isIdentifier(typeNode.typeName) &&
|
|
85
|
+
typeNode.typeName.text === 'Array' &&
|
|
86
|
+
typeNode.typeArguments &&
|
|
87
|
+
typeNode.typeArguments.length > 0
|
|
88
|
+
) {
|
|
89
|
+
return typeNode.typeArguments[0].getText(sourceFile);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Pass 2: rewrite `.length`, index writes and whole-array reassignment on array bindings only
|
|
96
|
+
function rewrite(ctx: VisitContext, node: ts.Node): void {
|
|
97
|
+
if (ts.isTypeNode(node)) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
129
100
|
|
|
101
|
+
if (ts.isPropertyAccessExpression(node) && node.name.text === 'length' && scope.isArray(ctx.bindings, node.expression)) {
|
|
102
|
+
let expr = node.expression,
|
|
103
|
+
parent = node.parent;
|
|
104
|
+
|
|
105
|
+
// arr.length = value OR arr.length += value
|
|
106
|
+
if (parent && ts.isBinaryExpression(parent) && parent.left === node && (parent.operatorToken.kind === ts.SyntaxKind.EqualsToken || COMPOUND_OPERATORS.has(parent.operatorToken.kind))) {
|
|
107
|
+
let op = COMPOUND_OPERATORS.get(parent.operatorToken.kind);
|
|
108
|
+
|
|
109
|
+
if (op === undefined) {
|
|
130
110
|
ctx.replacements.push({
|
|
131
111
|
node: parent,
|
|
132
|
-
generate: (sf) => `${expr.getText(sf)}.$length = ${
|
|
112
|
+
generate: (sf) => `${expr.getText(sf)}.$length = ${parent.right.getText(sf)}`
|
|
133
113
|
});
|
|
134
114
|
}
|
|
135
|
-
// Read-only: arr.length → arr.$length
|
|
136
115
|
else {
|
|
137
116
|
ctx.replacements.push({
|
|
138
|
-
node,
|
|
139
|
-
generate: (sf) => `${expr.getText(sf)}.$length`
|
|
117
|
+
node: parent,
|
|
118
|
+
generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${op} ${parent.right.getText(sf)}`
|
|
140
119
|
});
|
|
141
120
|
}
|
|
142
121
|
}
|
|
122
|
+
// arr.length++ / arr.length-- / ++arr.length / --arr.length
|
|
123
|
+
else if (parent && (ts.isPostfixUnaryExpression(parent) || ts.isPrefixUnaryExpression(parent))) {
|
|
124
|
+
let op = parent.operator === ts.SyntaxKind.PlusPlusToken ? '+' : '-';
|
|
125
|
+
|
|
126
|
+
ctx.replacements.push({
|
|
127
|
+
node: parent,
|
|
128
|
+
generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${op} 1`
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// Read-only: arr.length → arr.$length
|
|
132
|
+
else {
|
|
133
|
+
ctx.replacements.push({
|
|
134
|
+
node,
|
|
135
|
+
generate: (sf) => `${expr.getText(sf)}.$length`
|
|
136
|
+
});
|
|
137
|
+
}
|
|
143
138
|
}
|
|
144
139
|
|
|
145
140
|
if (
|
|
146
141
|
ts.isBinaryExpression(node) &&
|
|
147
142
|
node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
148
|
-
ts.isElementAccessExpression(node.left)
|
|
143
|
+
ts.isElementAccessExpression(node.left) &&
|
|
144
|
+
scope.isArray(ctx.bindings, node.left.expression)
|
|
149
145
|
) {
|
|
150
|
-
let element = node.left
|
|
151
|
-
name = ast.expression.name(element.expression);
|
|
146
|
+
let element = node.left;
|
|
152
147
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
generate: (sf) => `${element.expression.getText(sf)}.$set(
|
|
148
|
+
ctx.replacements.push({
|
|
149
|
+
node,
|
|
150
|
+
generate: (sf) => `${element.expression.getText(sf)}.$set(
|
|
157
151
|
${element.argumentExpression.getText(sf)},
|
|
158
152
|
${node.right.getText(sf)}
|
|
159
153
|
)`
|
|
160
|
-
|
|
161
|
-
}
|
|
154
|
+
});
|
|
162
155
|
}
|
|
163
156
|
|
|
164
157
|
if (
|
|
165
158
|
ts.isBinaryExpression(node) &&
|
|
166
159
|
node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
167
|
-
ts.isIdentifier(node.left)
|
|
160
|
+
ts.isIdentifier(node.left) &&
|
|
161
|
+
scope.isArray(ctx.bindings, node.left)
|
|
168
162
|
) {
|
|
169
163
|
let name = node.left.text,
|
|
170
164
|
right = node.right;
|
|
@@ -174,17 +168,19 @@ function visit(ctx: VisitContext, node: ts.Node): void {
|
|
|
174
168
|
right = right.expression;
|
|
175
169
|
}
|
|
176
170
|
|
|
177
|
-
if (
|
|
171
|
+
if (ts.isArrayLiteralExpression(right)) {
|
|
172
|
+
let elements = right;
|
|
173
|
+
|
|
178
174
|
ctx.replacements.push({
|
|
179
175
|
node,
|
|
180
|
-
generate: (sf) =>
|
|
181
|
-
? `${name}.splice(0, ${name}.length, ...${
|
|
176
|
+
generate: (sf) => elements.elements.length > 0
|
|
177
|
+
? `${name}.splice(0, ${name}.length, ...${elements.getText(sf)})`
|
|
182
178
|
: `${name}.splice(0, ${name}.length)`
|
|
183
179
|
});
|
|
184
180
|
}
|
|
185
181
|
}
|
|
186
182
|
|
|
187
|
-
node.forEachChild(
|
|
183
|
+
node.forEachChild(child => rewrite(ctx, child));
|
|
188
184
|
}
|
|
189
185
|
|
|
190
186
|
|
|
@@ -196,7 +192,8 @@ export default (sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: I
|
|
|
196
192
|
sourceFile
|
|
197
193
|
};
|
|
198
194
|
|
|
199
|
-
|
|
195
|
+
declare(ctx, sourceFile);
|
|
196
|
+
rewrite(ctx, sourceFile);
|
|
200
197
|
|
|
201
198
|
return ctx.replacements;
|
|
202
199
|
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ts } from '@esportsplus/typescript';
|
|
2
|
+
import { TYPES } from './constants';
|
|
3
|
+
import type { Bindings } from './types';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
function root(expression: ts.Expression): { path: string; root: ts.Identifier } | null {
|
|
7
|
+
let parts: string[] = [];
|
|
8
|
+
|
|
9
|
+
while (ts.isPropertyAccessExpression(expression)) {
|
|
10
|
+
parts.push(expression.name.text);
|
|
11
|
+
expression = expression.expression;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!ts.isIdentifier(expression) || parts.length === 0) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return { path: parts.reverse().join('.'), root: expression };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function symbolOf(bindings: Bindings, identifier: ts.Identifier): ts.Symbol | undefined {
|
|
22
|
+
let parent = identifier.parent;
|
|
23
|
+
|
|
24
|
+
// A shorthand property's own symbol is the object property, not the variable it reads
|
|
25
|
+
if (parent && ts.isShorthandPropertyAssignment(parent) && parent.name === identifier) {
|
|
26
|
+
return bindings.checker.getShorthandAssignmentValueSymbol(parent);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return bindings.checker.getSymbolAtLocation(identifier);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
// Bindings are keyed by symbol, so a shadowing local, parameter or property that merely shares a
|
|
34
|
+
// reactive variable's name is never rewritten. `names` keeps resolution cheap: an identifier whose
|
|
35
|
+
// text no reactive declaration uses cannot refer to one, so it never costs a checker round-trip.
|
|
36
|
+
const create = (checker: ts.Checker): Bindings => ({
|
|
37
|
+
checker,
|
|
38
|
+
names: new Set(),
|
|
39
|
+
paths: new Map(),
|
|
40
|
+
symbols: new Map()
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const declare = (bindings: Bindings, identifier: ts.Identifier, type: TYPES): void => {
|
|
44
|
+
let symbol = bindings.checker.getSymbolAtLocation(identifier);
|
|
45
|
+
|
|
46
|
+
if (symbol) {
|
|
47
|
+
bindings.names.add(identifier.text);
|
|
48
|
+
bindings.symbols.set(symbol, type);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// `state.items` where `state` is a reactive object whose `items` property is a ReactiveArray
|
|
53
|
+
const declarePath = (bindings: Bindings, identifier: ts.Identifier, path: string): void => {
|
|
54
|
+
let symbol = bindings.checker.getSymbolAtLocation(identifier);
|
|
55
|
+
|
|
56
|
+
if (!symbol) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let paths = bindings.paths.get(symbol);
|
|
61
|
+
|
|
62
|
+
if (!paths) {
|
|
63
|
+
paths = new Set();
|
|
64
|
+
bindings.paths.set(symbol, paths);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
bindings.names.add(identifier.text);
|
|
68
|
+
paths.add(path);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// True for an identifier or property path that denotes a ReactiveArray binding
|
|
72
|
+
const isArray = (bindings: Bindings, expression: ts.Expression): boolean => {
|
|
73
|
+
if (ts.isIdentifier(expression)) {
|
|
74
|
+
return resolve(bindings, expression)?.type === TYPES.Array;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let access = root(expression);
|
|
78
|
+
|
|
79
|
+
if (!access || !bindings.names.has(access.root.text)) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let symbol = symbolOf(bindings, access.root);
|
|
84
|
+
|
|
85
|
+
return symbol !== undefined && bindings.paths.get(symbol)?.has(access.path) === true;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const resolve = (bindings: Bindings, identifier: ts.Identifier): { symbol: ts.Symbol; type: TYPES } | null => {
|
|
89
|
+
if (!bindings.names.has(identifier.text)) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let symbol = symbolOf(bindings, identifier),
|
|
94
|
+
type = symbol && bindings.symbols.get(symbol);
|
|
95
|
+
|
|
96
|
+
return symbol && type !== undefined ? { symbol, type } : null;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
export default { create, declare, declarePath, isArray, resolve };
|
package/src/compiler/index.ts
CHANGED
|
@@ -2,13 +2,22 @@ import { ts } from '@esportsplus/typescript';
|
|
|
2
2
|
import { imports } from '@esportsplus/typescript/compiler';
|
|
3
3
|
import type { ImportIntent, ReplacementIntent, TransformContext } from '@esportsplus/typescript/compiler';
|
|
4
4
|
import { ENTRYPOINT, NAMESPACE, PACKAGE_NAME } from './constants';
|
|
5
|
-
import type { Bindings } from './types';
|
|
6
5
|
|
|
7
6
|
import array from './array';
|
|
8
7
|
import object from './object';
|
|
9
8
|
import primitives from './primitives';
|
|
9
|
+
import scope from './bindings';
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
// Resolved once per file: every pass asks the same question of the same nodes
|
|
13
|
+
function collect(checker: ts.Checker, node: ts.Node, calls: Set<ts.Node>): void {
|
|
14
|
+
if (isReactiveCallExpression(checker, node)) {
|
|
15
|
+
calls.add(node);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
node.forEachChild(child => collect(checker, child, calls));
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
function isReactiveCallExpression(checker: ts.Checker, node: ts.Node): node is ts.CallExpression {
|
|
13
22
|
if (!ts.isCallExpression(node)) {
|
|
14
23
|
return false;
|
|
@@ -21,9 +30,9 @@ function isReactiveCallExpression(checker: ts.Checker, node: ts.Node): node is t
|
|
|
21
30
|
return imports.includes(checker, expr, PACKAGE_NAME, ENTRYPOINT);
|
|
22
31
|
}
|
|
23
32
|
|
|
24
|
-
//
|
|
25
|
-
if (ts.isPropertyAccessExpression(expr) && expr.name.text === ENTRYPOINT) {
|
|
26
|
-
return imports.includes(checker, expr, PACKAGE_NAME);
|
|
33
|
+
// Namespace call: ns.reactive(...) — the namespace identifier carries the import origin
|
|
34
|
+
if (ts.isPropertyAccessExpression(expr) && expr.name.text === ENTRYPOINT && ts.isIdentifier(expr.expression)) {
|
|
35
|
+
return imports.includes(checker, expr.expression, PACKAGE_NAME);
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
return false;
|
|
@@ -39,22 +48,26 @@ export default {
|
|
|
39
48
|
return {};
|
|
40
49
|
}
|
|
41
50
|
|
|
42
|
-
let bindings
|
|
51
|
+
let bindings = scope.create(checker),
|
|
52
|
+
candidates = new Set<ts.Node>(),
|
|
43
53
|
intents = {
|
|
44
54
|
imports: [] as ImportIntent[],
|
|
45
55
|
prepend: [] as string[],
|
|
46
56
|
replacements: [] as ReplacementIntent[]
|
|
47
57
|
},
|
|
48
|
-
isReactiveCall = (node: ts.Node): node is ts.CallExpression => isReactiveCallExpression(checker, node),
|
|
49
58
|
sourceFile = ctx.sourceFile;
|
|
50
59
|
|
|
51
|
-
|
|
52
|
-
let { calls, replacements } = primitives(sourceFile, bindings, isReactiveCall);
|
|
60
|
+
collect(checker, sourceFile, candidates);
|
|
53
61
|
|
|
54
|
-
if (
|
|
62
|
+
if (candidates.size === 0) {
|
|
55
63
|
return {};
|
|
56
64
|
}
|
|
57
65
|
|
|
66
|
+
let isReactiveCall = (node: ts.Node): node is ts.CallExpression => candidates.has(node);
|
|
67
|
+
|
|
68
|
+
// Run primitives transform first (tracks bindings for signal/computed, collects every call)
|
|
69
|
+
let { calls, replacements } = primitives(sourceFile, bindings, isReactiveCall);
|
|
70
|
+
|
|
58
71
|
intents.replacements.push(...replacements);
|
|
59
72
|
|
|
60
73
|
// Run object transform
|
package/src/compiler/object.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { code, uid } from '@esportsplus/typescript/compiler';
|
|
|
3
3
|
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
4
4
|
import { NAMESPACE, TYPES } from './constants';
|
|
5
5
|
import type { Bindings, IsReactiveCall } from './types';
|
|
6
|
+
import scope from './bindings';
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
interface AnalyzedProperty {
|
|
@@ -223,11 +224,10 @@ function visit(ctx: VisitContext, node: ts.Node): void {
|
|
|
223
224
|
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
224
225
|
let properties: AnalyzedProperty[] = [],
|
|
225
226
|
props = arg.properties,
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
varname =
|
|
230
|
-
}
|
|
227
|
+
target = node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)
|
|
228
|
+
? node.parent.name
|
|
229
|
+
: null,
|
|
230
|
+
varname = target?.text ?? null;
|
|
231
231
|
|
|
232
232
|
for (let i = 0, n = props.length; i < n; i++) {
|
|
233
233
|
let prop = props[i];
|
|
@@ -245,9 +245,14 @@ function visit(ctx: VisitContext, node: ts.Node): void {
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
properties.push(analyzed);
|
|
248
|
+
}
|
|
248
249
|
|
|
249
|
-
|
|
250
|
-
|
|
250
|
+
// Declared only once every property analyzed: a bailed object stays a runtime reactive()
|
|
251
|
+
if (target) {
|
|
252
|
+
for (let i = 0, n = properties.length; i < n; i++) {
|
|
253
|
+
if (properties[i].type === TYPES.Array) {
|
|
254
|
+
scope.declarePath(ctx.bindings, target, properties[i].key);
|
|
255
|
+
}
|
|
251
256
|
}
|
|
252
257
|
}
|
|
253
258
|
|