@esportsplus/reactivity 0.37.2 → 0.38.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/build/compiler/array.d.ts +8 -1
- package/build/compiler/array.js +109 -93
- package/build/compiler/bindings.d.ts +18 -12
- package/build/compiler/bindings.js +172 -52
- package/build/compiler/constants.d.ts +1 -0
- package/build/compiler/constants.js +1 -0
- package/build/compiler/index.d.ts +11 -3
- package/build/compiler/index.js +96 -34
- package/build/compiler/object.js +11 -14
- package/build/compiler/plugins/vite.d.ts +14 -0
- package/build/compiler/primitives.d.ts +6 -0
- package/build/compiler/primitives.js +99 -67
- package/build/compiler/types.d.ts +5 -4
- package/build/types.d.ts +1 -1
- package/package.json +2 -2
- package/pnpm-workspace.yaml +1 -1
- package/src/compiler/array.ts +162 -120
- package/src/compiler/bindings.ts +259 -56
- package/src/compiler/constants.ts +1 -0
- package/src/compiler/index.ts +142 -46
- package/src/compiler/object.ts +17 -17
- package/src/compiler/primitives.ts +139 -88
- package/src/compiler/types.ts +7 -4
- package/src/types.ts +2 -1
- package/test/compiler/compiler.test.ts +152 -48
- package/test/compiler/coverage.test.ts +164 -0
package/build/compiler/index.js
CHANGED
|
@@ -1,55 +1,112 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import { imports } from '@esportsplus/typescript/compiler';
|
|
3
2
|
import { ENTRYPOINT, NAMESPACE, PACKAGE_NAME } from './constants.js';
|
|
4
3
|
import array from './array.js';
|
|
5
4
|
import object from './object.js';
|
|
6
5
|
import primitives from './primitives.js';
|
|
7
6
|
import scope from './bindings.js';
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
function failed(sourceFile, failures) {
|
|
8
|
+
let lines = [];
|
|
9
|
+
for (let i = 0, n = failures.length; i < n; i++) {
|
|
10
|
+
let { message, node } = failures[i], position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
11
|
+
lines.push(` ${sourceFile.fileName}:${position.line + 1}:${position.character + 1} ${message}: ${node.getText(sourceFile).split('\n')[0]}`);
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
+
return new Error(`${PACKAGE_NAME}: code that must be compiled cannot be:\n${lines.join('\n')}`);
|
|
13
14
|
}
|
|
14
|
-
function
|
|
15
|
-
|
|
15
|
+
function isConstList(node) {
|
|
16
|
+
return node !== undefined && ts.isVariableDeclarationList(node) && (node.flags & ts.NodeFlags.Const) !== 0;
|
|
17
|
+
}
|
|
18
|
+
function isLowered(site) {
|
|
19
|
+
let parent = site.parent;
|
|
20
|
+
while (parent && ts.isParenthesizedExpression(parent)) {
|
|
21
|
+
site = parent;
|
|
22
|
+
parent = site.parent;
|
|
23
|
+
}
|
|
24
|
+
if (!parent) {
|
|
16
25
|
return false;
|
|
17
26
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
if (ts.isCallExpression(parent)) {
|
|
28
|
+
return parent.expression === site;
|
|
29
|
+
}
|
|
30
|
+
if (ts.isVariableDeclaration(parent)) {
|
|
31
|
+
return parent.initializer === site && ts.isIdentifier(parent.name) && isConstList(parent.parent);
|
|
21
32
|
}
|
|
22
|
-
if (ts.
|
|
23
|
-
|
|
33
|
+
if (ts.isBindingElement(parent) && parent.propertyName === site) {
|
|
34
|
+
let declaration = parent;
|
|
35
|
+
while (declaration && !ts.isVariableDeclaration(declaration)) {
|
|
36
|
+
declaration = declaration.parent;
|
|
37
|
+
}
|
|
38
|
+
return declaration !== undefined && isConstList(declaration.parent);
|
|
24
39
|
}
|
|
25
40
|
return false;
|
|
26
41
|
}
|
|
42
|
+
function siteOf(identifier) {
|
|
43
|
+
let parent = identifier.parent;
|
|
44
|
+
if ((ts.isPropertyAccessExpression(parent) && parent.name === identifier) ||
|
|
45
|
+
(ts.isElementAccessExpression(parent) && parent.argumentExpression === identifier)) {
|
|
46
|
+
return parent;
|
|
47
|
+
}
|
|
48
|
+
return identifier;
|
|
49
|
+
}
|
|
27
50
|
export default {
|
|
28
|
-
patterns: ['reactive(', 'reactive<'],
|
|
29
51
|
transform: (ctx) => {
|
|
30
|
-
let checker = ctx.checker;
|
|
31
|
-
|
|
32
|
-
|
|
52
|
+
let { checker, program, sourceFile } = ctx, bindings = scope.create(checker, program, sourceFile), calls = new Set(), dependencies = new Set(), failures = [], self = sourceFile.fileName.toLowerCase();
|
|
53
|
+
for (let [identifier, found] of bindings.origins) {
|
|
54
|
+
let entry = scope.holds(bindings, found);
|
|
55
|
+
if (!entry && scope.kind(bindings, found) === null) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
for (let file of [...found.through, found.declaration.path]) {
|
|
59
|
+
if (file.toLowerCase() !== self) {
|
|
60
|
+
dependencies.add(file);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!entry) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
let site = siteOf(identifier), parent = site.parent;
|
|
67
|
+
while (parent && ts.isParenthesizedExpression(parent)) {
|
|
68
|
+
parent = parent.parent;
|
|
69
|
+
}
|
|
70
|
+
if (parent && ts.isCallExpression(parent) && scope.unwrap(parent.expression) === site) {
|
|
71
|
+
calls.add(parent);
|
|
72
|
+
}
|
|
73
|
+
else if (!isLowered(site)) {
|
|
74
|
+
failures.push({
|
|
75
|
+
message: `${ENTRYPOINT} is compiled away, so it can only be called (or aliased with const); it cannot be used as a value`,
|
|
76
|
+
node: site
|
|
77
|
+
});
|
|
78
|
+
}
|
|
33
79
|
}
|
|
34
|
-
let
|
|
80
|
+
let isReactiveCall = (node) => calls.has(node), intents = {
|
|
35
81
|
imports: [],
|
|
36
82
|
prepend: [],
|
|
37
83
|
replacements: []
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
let isReactiveCall = (node) => candidates.has(node);
|
|
44
|
-
let { calls, replacements } = primitives(sourceFile, bindings, isReactiveCall);
|
|
45
|
-
intents.replacements.push(...replacements);
|
|
84
|
+
};
|
|
85
|
+
let lowered = primitives(sourceFile, bindings, isReactiveCall);
|
|
86
|
+
failures.push(...lowered.failures);
|
|
87
|
+
intents.replacements.push(...lowered.replacements);
|
|
46
88
|
let objects = object(sourceFile, bindings, isReactiveCall);
|
|
47
89
|
intents.prepend.push(...objects.prepend);
|
|
48
90
|
intents.replacements.push(...objects.replacements);
|
|
49
|
-
|
|
91
|
+
let arrays = array(sourceFile, bindings, isReactiveCall);
|
|
92
|
+
failures.push(...arrays.failures);
|
|
93
|
+
intents.replacements.push(...arrays.replacements);
|
|
94
|
+
for (let i = 0, n = arrays.reactive.length; i < n; i++) {
|
|
95
|
+
let root = arrays.reactive[i];
|
|
96
|
+
while (ts.isPropertyAccessExpression(root) || ts.isElementAccessExpression(root) || ts.isParenthesizedExpression(root)) {
|
|
97
|
+
root = root.expression;
|
|
98
|
+
}
|
|
99
|
+
let found = ts.isIdentifier(root) ? scope.origin(bindings, root) : null;
|
|
100
|
+
if (found && found.declaration.path.toLowerCase() !== self) {
|
|
101
|
+
dependencies.add(found.declaration.path);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (failures.length > 0) {
|
|
105
|
+
throw failed(sourceFile, failures);
|
|
106
|
+
}
|
|
50
107
|
let transformed = new Set(intents.replacements.map(r => r.node));
|
|
51
|
-
for (let i = 0, n = calls.length; i < n; i++) {
|
|
52
|
-
let call = calls[i];
|
|
108
|
+
for (let i = 0, n = lowered.calls.length; i < n; i++) {
|
|
109
|
+
let call = lowered.calls[i];
|
|
53
110
|
if (transformed.has(call) || transformed.has(call.expression)) {
|
|
54
111
|
continue;
|
|
55
112
|
}
|
|
@@ -58,11 +115,16 @@ export default {
|
|
|
58
115
|
node: call
|
|
59
116
|
});
|
|
60
117
|
}
|
|
61
|
-
intents.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
118
|
+
if (intents.replacements.length === 0 && intents.prepend.length === 0) {
|
|
119
|
+
return dependencies.size > 0 ? { dependencies: [...dependencies] } : {};
|
|
120
|
+
}
|
|
121
|
+
if (calls.size > 0 || lowered.replacements.length > 0 || intents.prepend.length > 0 || arrays.namespaced) {
|
|
122
|
+
intents.imports.push({
|
|
123
|
+
namespace: NAMESPACE,
|
|
124
|
+
package: PACKAGE_NAME,
|
|
125
|
+
remove: [ENTRYPOINT]
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return { dependencies: [...dependencies], ...intents };
|
|
67
129
|
}
|
|
68
130
|
};
|
package/build/compiler/object.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
2
|
import { code, uid } from '@esportsplus/typescript/compiler';
|
|
3
3
|
import { NAMESPACE, TYPES } from './constants.js';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
function analyzeProperty(prop, bindings) {
|
|
5
|
+
let sourceFile = bindings.sourceFile;
|
|
6
6
|
if (!ts.isPropertyAssignment(prop)) {
|
|
7
7
|
return null;
|
|
8
8
|
}
|
|
@@ -38,6 +38,10 @@ function analyzeProperty(prop, sourceFile) {
|
|
|
38
38
|
}
|
|
39
39
|
return { isStatic, key, type: TYPES.Array, valueText };
|
|
40
40
|
}
|
|
41
|
+
let type = isStaticValue(unwrapped) ? undefined : bindings.checker.getTypeAtLocation(value);
|
|
42
|
+
if (type && (bindings.checker.isArrayType(type) || bindings.checker.isTupleType(type))) {
|
|
43
|
+
return { isStatic: false, key, type: TYPES.Array, valueText };
|
|
44
|
+
}
|
|
41
45
|
return {
|
|
42
46
|
isStatic: isStaticValue(value),
|
|
43
47
|
key,
|
|
@@ -144,29 +148,22 @@ function visit(ctx, node) {
|
|
|
144
148
|
if (ctx.isReactiveCall(node)) {
|
|
145
149
|
let arg = node.arguments[0];
|
|
146
150
|
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
147
|
-
let properties = [], props = arg.properties,
|
|
148
|
-
? node.parent.name
|
|
149
|
-
: null
|
|
151
|
+
let properties = [], props = arg.properties, varname = node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)
|
|
152
|
+
? node.parent.name.text
|
|
153
|
+
: null;
|
|
150
154
|
for (let i = 0, n = props.length; i < n; i++) {
|
|
151
155
|
let prop = props[i];
|
|
152
156
|
if (ts.isSpreadAssignment(prop)) {
|
|
153
157
|
node.forEachChild(n => visit(ctx, n));
|
|
154
158
|
return;
|
|
155
159
|
}
|
|
156
|
-
let analyzed = analyzeProperty(prop, ctx.
|
|
160
|
+
let analyzed = analyzeProperty(prop, ctx.bindings);
|
|
157
161
|
if (!analyzed) {
|
|
158
162
|
node.forEachChild(n => visit(ctx, n));
|
|
159
163
|
return;
|
|
160
164
|
}
|
|
161
165
|
properties.push(analyzed);
|
|
162
166
|
}
|
|
163
|
-
if (target) {
|
|
164
|
-
for (let i = 0, n = properties.length; i < n; i++) {
|
|
165
|
-
if (properties[i].type === TYPES.Array) {
|
|
166
|
-
scope.declarePath(ctx.bindings, target, properties[i].key);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
167
|
ctx.calls.push({
|
|
171
168
|
classname: uid('ReactiveObject'),
|
|
172
169
|
node,
|
|
@@ -193,7 +190,7 @@ export default (sourceFile, bindings, isReactiveCall) => {
|
|
|
193
190
|
replacements.push({
|
|
194
191
|
generate: () => {
|
|
195
192
|
let args = call.properties
|
|
196
|
-
.filter(({ isStatic, type }) => typehint || !isStatic || type
|
|
193
|
+
.filter(({ isStatic, type }) => typehint || !isStatic || type !== TYPES.Signal)
|
|
197
194
|
.map(p => p.valueText)
|
|
198
195
|
.join(', ');
|
|
199
196
|
return typehint
|
|
@@ -5,7 +5,21 @@ declare const _default: ({ root }?: {
|
|
|
5
5
|
closeWatcher: () => void;
|
|
6
6
|
configResolved: (config: unknown) => void;
|
|
7
7
|
enforce: 'pre';
|
|
8
|
+
handleHotUpdate: <M>(ctx: {
|
|
9
|
+
file: string;
|
|
10
|
+
modules: M[];
|
|
11
|
+
server: {
|
|
12
|
+
moduleGraph: {
|
|
13
|
+
getModulesByFile(file: string): Set<M> | undefined;
|
|
14
|
+
invalidateModule(module: M, seen: Set<M>, timestamp: number, isHmr: boolean): void;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
timestamp: number;
|
|
18
|
+
}) => M[] | undefined;
|
|
8
19
|
name: string;
|
|
20
|
+
renderChunk: (code: string, chunk: {
|
|
21
|
+
fileName: string;
|
|
22
|
+
}) => null;
|
|
9
23
|
transform: (code: string, id: string) => {
|
|
10
24
|
code: string;
|
|
11
25
|
map: import("@esportsplus/typescript/compiler").SourceMapV3;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
2
|
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
3
3
|
import type { Bindings, IsReactiveCall } from './types.js';
|
|
4
|
+
type Failure = {
|
|
5
|
+
message: string;
|
|
6
|
+
node: ts.Node;
|
|
7
|
+
};
|
|
4
8
|
type PrimitivesTransformResult = {
|
|
5
9
|
calls: ts.CallExpression[];
|
|
10
|
+
failures: Failure[];
|
|
6
11
|
replacements: ReplacementIntent[];
|
|
7
12
|
};
|
|
8
13
|
export default _default;
|
|
9
14
|
declare function _default(sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: IsReactiveCall): PrimitivesTransformResult;
|
|
15
|
+
export type { Failure };
|
|
@@ -3,38 +3,33 @@ import { COMPOUND_OPERATORS, NAMESPACE, TYPES } from './constants.js';
|
|
|
3
3
|
import scope from './bindings.js';
|
|
4
4
|
function declare(ctx, node) {
|
|
5
5
|
if (ctx.isReactiveCall(node)) {
|
|
6
|
-
let call = node;
|
|
6
|
+
let call = node, kind = scope.classify(ctx.bindings, call);
|
|
7
7
|
ctx.calls.push(call);
|
|
8
|
-
if (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
classification = null;
|
|
23
|
-
ctx.replacements.push({
|
|
24
|
-
generate: () => `${NAMESPACE}.reactive`,
|
|
25
|
-
node: call.expression
|
|
26
|
-
});
|
|
27
|
-
}
|
|
8
|
+
if (kind === null) {
|
|
9
|
+
ctx.failures.push({
|
|
10
|
+
message: 'reactive() takes one argument whose kind (value, computed, array or object) is provable; ' +
|
|
11
|
+
'pass a literal or give the argument a concrete type (not any, unknown or a mixed union)',
|
|
12
|
+
node: call
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
else if (kind === TYPES.Signal || kind === TYPES.Computed) {
|
|
16
|
+
if (!isBound(call)) {
|
|
17
|
+
ctx.failures.push({
|
|
18
|
+
message: `a reactive ${kind === TYPES.Signal ? 'value' : 'computed'} must initialize a variable (or be a module's ` +
|
|
19
|
+
'default export); anywhere else its signal is never read',
|
|
20
|
+
node: call
|
|
21
|
+
});
|
|
28
22
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
ctx.replacements.push({
|
|
24
|
+
generate: () => kind === TYPES.Computed ? `${NAMESPACE}.computed` : `${NAMESPACE}.signal`,
|
|
25
|
+
node: call.expression
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
let arg = scope.unwrap(call.arguments[0]);
|
|
30
|
+
if (!ts.isArrayLiteralExpression(arg) && !ts.isObjectLiteralExpression(arg)) {
|
|
34
31
|
ctx.replacements.push({
|
|
35
|
-
generate: () =>
|
|
36
|
-
? `${NAMESPACE}.computed`
|
|
37
|
-
: `${NAMESPACE}.signal`,
|
|
32
|
+
generate: () => `${NAMESPACE}.reactive`,
|
|
38
33
|
node: call.expression
|
|
39
34
|
});
|
|
40
35
|
}
|
|
@@ -42,12 +37,33 @@ function declare(ctx, node) {
|
|
|
42
37
|
}
|
|
43
38
|
node.forEachChild(child => declare(ctx, child));
|
|
44
39
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
40
|
+
function isBound(call) {
|
|
41
|
+
let node = call, parent = node.parent;
|
|
42
|
+
while (parent &&
|
|
43
|
+
(ts.isAsExpression(parent) || ts.isNonNullExpression(parent) || ts.isParenthesizedExpression(parent) || ts.isSatisfiesExpression(parent))) {
|
|
44
|
+
node = parent;
|
|
45
|
+
parent = node.parent;
|
|
46
|
+
}
|
|
47
|
+
if (!parent) {
|
|
47
48
|
return false;
|
|
48
49
|
}
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
if (ts.isVariableDeclaration(parent)) {
|
|
51
|
+
return parent.initializer === node && ts.isIdentifier(parent.name);
|
|
52
|
+
}
|
|
53
|
+
if (ts.isBinaryExpression(parent)) {
|
|
54
|
+
return parent.operatorToken.kind === ts.SyntaxKind.EqualsToken && parent.right === node && ts.isIdentifier(parent.left);
|
|
55
|
+
}
|
|
56
|
+
return ts.isExportAssignment(parent);
|
|
57
|
+
}
|
|
58
|
+
function isNamespace(bindings, expression) {
|
|
59
|
+
let value = scope.unwrap(expression);
|
|
60
|
+
if (ts.isAwaitExpression(value)) {
|
|
61
|
+
value = scope.unwrap(value.expression);
|
|
62
|
+
}
|
|
63
|
+
if (ts.isCallExpression(value)) {
|
|
64
|
+
return value.expression.kind === ts.SyntaxKind.ImportKeyword;
|
|
65
|
+
}
|
|
66
|
+
return ts.isIdentifier(value) && scope.origin(bindings, value)?.declaration.kind === ts.SyntaxKind.SourceFile;
|
|
51
67
|
}
|
|
52
68
|
function isPatternTarget(node) {
|
|
53
69
|
let current = node;
|
|
@@ -67,24 +83,30 @@ function isPatternTarget(node) {
|
|
|
67
83
|
return (ts.isBinaryExpression(parent) && parent.operatorToken.kind === ts.SyntaxKind.EqualsToken && parent.left === current) ||
|
|
68
84
|
((ts.isForOfStatement(parent) || ts.isForInStatement(parent)) && parent.initializer === current);
|
|
69
85
|
}
|
|
70
|
-
function
|
|
86
|
+
function readNode(identifier) {
|
|
87
|
+
let parent = identifier.parent;
|
|
88
|
+
if ((ts.isPropertyAccessExpression(parent) && parent.name === identifier) ||
|
|
89
|
+
(ts.isElementAccessExpression(parent) && parent.argumentExpression === identifier)) {
|
|
90
|
+
return parent;
|
|
91
|
+
}
|
|
92
|
+
return identifier;
|
|
93
|
+
}
|
|
94
|
+
function reference(ctx, node, kind) {
|
|
71
95
|
let parent = node.parent;
|
|
72
|
-
if (!parent
|
|
96
|
+
if (!parent) {
|
|
73
97
|
return;
|
|
74
98
|
}
|
|
75
|
-
|
|
76
|
-
if (!binding || binding.type === TYPES.Array) {
|
|
99
|
+
if (ts.isBinaryExpression(parent) && parent.operatorToken.kind === ts.SyntaxKind.EqualsToken && parent.left === node && ctx.isReactiveCall(scope.unwrap(parent.right))) {
|
|
77
100
|
return;
|
|
78
101
|
}
|
|
79
|
-
if ((
|
|
80
|
-
|
|
81
|
-
isPatternTarget(node)) {
|
|
102
|
+
if (isPatternTarget(node)) {
|
|
103
|
+
ctx.failures.push({ message: 'a reactive binding cannot be assigned through a destructuring pattern; assign it directly', node });
|
|
82
104
|
return;
|
|
83
105
|
}
|
|
84
|
-
let
|
|
106
|
+
let text = node.getText(ctx.bindings.sourceFile);
|
|
85
107
|
if (ts.isShorthandPropertyAssignment(parent)) {
|
|
86
108
|
ctx.replacements.push({
|
|
87
|
-
generate: () => `${
|
|
109
|
+
generate: () => `${text}: ${NAMESPACE}.read(${text})`,
|
|
88
110
|
node: parent
|
|
89
111
|
});
|
|
90
112
|
return;
|
|
@@ -107,25 +129,26 @@ function reference(ctx, node) {
|
|
|
107
129
|
}
|
|
108
130
|
if (!writeCtx) {
|
|
109
131
|
ctx.replacements.push({
|
|
110
|
-
generate: () => `${NAMESPACE}.read(${
|
|
132
|
+
generate: () => `${NAMESPACE}.read(${text})`,
|
|
111
133
|
node
|
|
112
134
|
});
|
|
113
135
|
return;
|
|
114
136
|
}
|
|
115
|
-
if (
|
|
137
|
+
if (kind === TYPES.Computed) {
|
|
138
|
+
ctx.failures.push({ message: 'a reactive computed is read-only', node });
|
|
116
139
|
return;
|
|
117
140
|
}
|
|
118
141
|
if (writeCtx === 'simple' && ts.isBinaryExpression(parent)) {
|
|
119
142
|
let right = parent.right;
|
|
120
143
|
ctx.replacements.push({
|
|
121
|
-
generate: (sf) => `${NAMESPACE}.write(${
|
|
144
|
+
generate: (sf) => `${NAMESPACE}.write(${text}, ${right.getText(sf)})`,
|
|
122
145
|
node: parent
|
|
123
146
|
});
|
|
124
147
|
}
|
|
125
148
|
else if (writeCtx === 'compound' && ts.isBinaryExpression(parent)) {
|
|
126
149
|
let op = COMPOUND_OPERATORS.get(parent.operatorToken.kind) ?? '+', right = parent.right;
|
|
127
150
|
ctx.replacements.push({
|
|
128
|
-
generate: (sf) => `${NAMESPACE}.write(${
|
|
151
|
+
generate: (sf) => `${NAMESPACE}.write(${text}, ${text}.value ${op} ${right.getText(sf)})`,
|
|
129
152
|
node: parent
|
|
130
153
|
});
|
|
131
154
|
}
|
|
@@ -133,54 +156,63 @@ function reference(ctx, node) {
|
|
|
133
156
|
let unary = parent, delta = unary.operator === ts.SyntaxKind.PlusPlusToken ? '+ 1' : '- 1';
|
|
134
157
|
if (ts.isExpressionStatement(unary.parent)) {
|
|
135
158
|
ctx.replacements.push({
|
|
136
|
-
generate: () => `${NAMESPACE}.write(${
|
|
159
|
+
generate: () => `${NAMESPACE}.write(${text}, ${text}.value ${delta})`,
|
|
137
160
|
node: unary
|
|
138
161
|
});
|
|
139
162
|
}
|
|
140
163
|
else if (ts.isPrefixUnaryExpression(unary)) {
|
|
141
164
|
ctx.replacements.push({
|
|
142
|
-
generate: () => `(${NAMESPACE}.write(${
|
|
165
|
+
generate: () => `(${NAMESPACE}.write(${text}, ${text}.value ${delta}), ${text}.value)`,
|
|
143
166
|
node: unary
|
|
144
167
|
});
|
|
145
168
|
}
|
|
146
169
|
else {
|
|
147
170
|
let tmp = `_t${ctx.tmpCounter++}`;
|
|
148
171
|
ctx.replacements.push({
|
|
149
|
-
generate: () => `((${tmp}) => (${NAMESPACE}.write(${
|
|
172
|
+
generate: () => `((${tmp}) => (${NAMESPACE}.write(${text}, ${tmp} ${delta}), ${tmp}))(${text}.value)`,
|
|
150
173
|
node: unary
|
|
151
174
|
});
|
|
152
175
|
}
|
|
153
176
|
}
|
|
154
177
|
}
|
|
155
|
-
function rewrite(ctx
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
178
|
+
function rewrite(ctx) {
|
|
179
|
+
let { bindings } = ctx;
|
|
180
|
+
for (let [identifier, found] of bindings.origins) {
|
|
181
|
+
let kind = scope.kind(bindings, found);
|
|
182
|
+
if (kind !== TYPES.Signal && kind !== TYPES.Computed) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
let parent = identifier.parent;
|
|
186
|
+
if (ts.isBindingElement(parent) && parent.propertyName === identifier) {
|
|
187
|
+
ctx.failures.push({ message: 'a reactive binding cannot be destructured (it would copy its signal); read it through the namespace', node: identifier });
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
reference(ctx, readNode(identifier), kind);
|
|
162
191
|
}
|
|
163
|
-
|
|
192
|
+
unpacked(ctx, bindings.sourceFile);
|
|
164
193
|
}
|
|
165
|
-
function
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
194
|
+
function unpacked(ctx, node) {
|
|
195
|
+
if (ts.isBindingElement(node) && !node.propertyName && ts.isObjectBindingPattern(node.parent)) {
|
|
196
|
+
let initializer = node.parent.parent.initializer;
|
|
197
|
+
if (initializer && isNamespace(ctx.bindings, initializer)) {
|
|
198
|
+
let kind = scope.kind(ctx.bindings, scope.origin(ctx.bindings, node));
|
|
199
|
+
if (kind === TYPES.Signal || kind === TYPES.Computed) {
|
|
200
|
+
ctx.failures.push({ message: 'a reactive binding cannot be destructured (it would copy its signal); read it through the namespace', node });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
172
203
|
}
|
|
173
|
-
|
|
204
|
+
node.forEachChild(child => unpacked(ctx, child));
|
|
174
205
|
}
|
|
175
206
|
export default (sourceFile, bindings, isReactiveCall) => {
|
|
176
207
|
let ctx = {
|
|
177
208
|
bindings,
|
|
178
209
|
calls: [],
|
|
210
|
+
failures: [],
|
|
179
211
|
isReactiveCall,
|
|
180
212
|
replacements: [],
|
|
181
213
|
tmpCounter: 0
|
|
182
214
|
};
|
|
183
215
|
declare(ctx, sourceFile);
|
|
184
|
-
rewrite(ctx
|
|
185
|
-
return { calls: ctx.calls, replacements: ctx.replacements };
|
|
216
|
+
rewrite(ctx);
|
|
217
|
+
return { calls: ctx.calls, failures: ctx.failures, replacements: ctx.replacements };
|
|
186
218
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { ts } from '@esportsplus/typescript';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Origin } from '@esportsplus/typescript/compiler';
|
|
3
3
|
type Bindings = {
|
|
4
4
|
checker: ts.Checker;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
entries: Set<string>;
|
|
6
|
+
origins: Map<ts.Node, Origin>;
|
|
7
|
+
program: ts.Program;
|
|
8
|
+
sourceFile: ts.SourceFile;
|
|
8
9
|
};
|
|
9
10
|
type IsReactiveCall = (node: ts.Node) => node is ts.CallExpression;
|
|
10
11
|
export type { Bindings, IsReactiveCall };
|
package/build/types.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ interface Link {
|
|
|
33
33
|
declare const READONLY: unique symbol;
|
|
34
34
|
type Reactive<T> = T extends (...args: never[]) => infer R ? Settled<R> & {
|
|
35
35
|
readonly [READONLY]: true;
|
|
36
|
-
} : T extends (infer U)[] ? U[] & Pick<ReactiveArray<U>, 'clear' | 'dispose' | 'on' | 'once'> : T extends Record<PropertyKey, unknown> ? {
|
|
36
|
+
} : T extends (infer U)[] ? U[] & Pick<ReactiveArray<U>, '$length' | '$set' | 'clear' | 'dispose' | 'on' | 'once'> : T extends Record<PropertyKey, unknown> ? {
|
|
37
37
|
[K in keyof T]: T[K] extends (infer U)[] ? Reactive<U[]> : T[K];
|
|
38
38
|
} & {
|
|
39
39
|
dispose: VoidFunction;
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"@esportsplus/utilities": "^0.28.6"
|
|
5
5
|
},
|
|
6
6
|
"devDependencies": {
|
|
7
|
-
"@esportsplus/typescript": "^0.
|
|
7
|
+
"@esportsplus/typescript": "^0.37.0",
|
|
8
8
|
"@types/node": "^26.4.1",
|
|
9
9
|
"vite": "^8.2.2",
|
|
10
10
|
"vitest": "^5.0.0"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"type": "module",
|
|
38
38
|
"types": "build/index.d.ts",
|
|
39
|
-
"version": "0.
|
|
39
|
+
"version": "0.38.0",
|
|
40
40
|
"scripts": {
|
|
41
41
|
"agent:bench": "vitest bench --run",
|
|
42
42
|
"agent:test": "tsc --noEmit && vitest run",
|
package/pnpm-workspace.yaml
CHANGED
|
@@ -2,7 +2,7 @@ allowBuilds:
|
|
|
2
2
|
esbuild: true
|
|
3
3
|
minimumReleaseAgeExclude:
|
|
4
4
|
- '@esportsplus/utilities@0.28.1 || 0.28.3 || 0.28.4 || 0.28.6'
|
|
5
|
-
- '@esportsplus/typescript@0.31.0 || 0.32.0 || 0.33.0 || 0.33.2 || 0.33.4 || 0.34.1 || 0.34.3 || 0.34.4 || 0.36.0 || 0.36.1'
|
|
5
|
+
- '@esportsplus/typescript@0.31.0 || 0.32.0 || 0.33.0 || 0.33.2 || 0.33.4 || 0.34.1 || 0.34.3 || 0.34.4 || 0.36.0 || 0.36.1 || 0.37.0'
|
|
6
6
|
- tsc-alias@1.9.4
|
|
7
7
|
- '@vitest/mocker@5.0.0'
|
|
8
8
|
- '@vitest/spy@5.0.0'
|