@esportsplus/reactivity 0.28.1 → 0.29.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.js +12 -12
- package/build/compiler/index.js +59 -12
- package/build/compiler/object.js +3 -6
- package/build/compiler/primitives.d.ts +5 -0
- package/build/compiler/primitives.js +181 -0
- package/build/reactive/index.d.ts +1 -0
- package/package.json +1 -1
- package/src/compiler/array.ts +18 -18
- package/src/compiler/index.ts +87 -16
- package/src/compiler/object.ts +6 -11
- package/src/compiler/primitives.ts +261 -0
- package/src/reactive/index.ts +3 -2
- package/test/primitives.ts +73 -157
package/build/compiler/array.js
CHANGED
|
@@ -14,11 +14,11 @@ function getElementTypeText(typeNode, sourceFile) {
|
|
|
14
14
|
}
|
|
15
15
|
return null;
|
|
16
16
|
}
|
|
17
|
-
function isReactiveCall(node) {
|
|
18
|
-
return ts.isIdentifier(node.expression) && node.expression.text === 'reactive';
|
|
19
|
-
}
|
|
20
17
|
function visit(ctx, node) {
|
|
21
|
-
if (ts.isCallExpression(node) &&
|
|
18
|
+
if (ts.isCallExpression(node) &&
|
|
19
|
+
ts.isIdentifier(node.expression) &&
|
|
20
|
+
node.expression.text === 'reactive' &&
|
|
21
|
+
node.arguments.length > 0) {
|
|
22
22
|
let arg = node.arguments[0], expression = ts.isAsExpression(arg) ? arg.expression : arg;
|
|
23
23
|
if (ts.isArrayLiteralExpression(expression)) {
|
|
24
24
|
let elementType = null;
|
|
@@ -62,12 +62,12 @@ function visit(ctx, node) {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
let parent = node.parent;
|
|
66
65
|
if (ts.isPropertyAccessExpression(node) &&
|
|
67
66
|
node.name.text === 'length' &&
|
|
68
|
-
(
|
|
69
|
-
ts.
|
|
70
|
-
|
|
67
|
+
(!node.parent ||
|
|
68
|
+
(!(ts.isBinaryExpression(node.parent) && node.parent.left === node) &&
|
|
69
|
+
!ts.isPostfixUnaryExpression(node.parent) &&
|
|
70
|
+
!ts.isPrefixUnaryExpression(node.parent)))) {
|
|
71
71
|
let name = ast.getExpressionName(node.expression);
|
|
72
72
|
if (name && ctx.bindings.get(name) === COMPILER_TYPES.Array) {
|
|
73
73
|
ctx.replacements.push({
|
|
@@ -83,10 +83,10 @@ function visit(ctx, node) {
|
|
|
83
83
|
if (name && ctx.bindings.get(name) === COMPILER_TYPES.Array) {
|
|
84
84
|
ctx.replacements.push({
|
|
85
85
|
node,
|
|
86
|
-
generate: (sf) => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
generate: (sf) => `${element.expression.getText(sf)}.$set(
|
|
87
|
+
${element.argumentExpression.getText(sf)},
|
|
88
|
+
${node.right.getText(sf)}
|
|
89
|
+
)`
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
}
|
package/build/compiler/index.js
CHANGED
|
@@ -1,13 +1,51 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import {
|
|
2
|
+
import { imports } from '@esportsplus/typescript/compiler';
|
|
3
3
|
import { COMPILER_ENTRYPOINT, COMPILER_NAMESPACE, PACKAGE } from '../constants.js';
|
|
4
4
|
import array from './array.js';
|
|
5
5
|
import object from './object.js';
|
|
6
|
+
import primitives from './primitives.js';
|
|
6
7
|
function hasReactiveImport(sourceFile) {
|
|
7
8
|
return imports.find(sourceFile, PACKAGE).some(i => i.specifiers.has(COMPILER_ENTRYPOINT));
|
|
8
9
|
}
|
|
9
|
-
function
|
|
10
|
-
|
|
10
|
+
function isReactiveSymbol(checker, node) {
|
|
11
|
+
let symbol = checker.getSymbolAtLocation(node);
|
|
12
|
+
if (!symbol) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
16
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
17
|
+
}
|
|
18
|
+
let declarations = symbol.getDeclarations();
|
|
19
|
+
if (!declarations || declarations.length === 0) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
for (let i = 0, n = declarations.length; i < n; i++) {
|
|
23
|
+
let decl = declarations[i], sourceFile = decl.getSourceFile();
|
|
24
|
+
if (sourceFile.fileName.includes(PACKAGE) || sourceFile.fileName.includes('reactivity')) {
|
|
25
|
+
if (symbol.name === COMPILER_ENTRYPOINT) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
function isReactiveCallExpression(checker, node) {
|
|
33
|
+
if (!ts.isCallExpression(node)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
let expr = node.expression;
|
|
37
|
+
if (ts.isIdentifier(expr)) {
|
|
38
|
+
if (expr.text === COMPILER_ENTRYPOINT) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (checker) {
|
|
42
|
+
return isReactiveSymbol(checker, expr);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (ts.isPropertyAccessExpression(expr) && expr.name.text === COMPILER_ENTRYPOINT && checker) {
|
|
46
|
+
return isReactiveSymbol(checker, expr);
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
11
49
|
}
|
|
12
50
|
const plugin = {
|
|
13
51
|
patterns: ['reactive(', 'reactive<'],
|
|
@@ -15,21 +53,30 @@ const plugin = {
|
|
|
15
53
|
if (!hasReactiveImport(ctx.sourceFile)) {
|
|
16
54
|
return {};
|
|
17
55
|
}
|
|
18
|
-
let bindings = new Map(), importsIntent = [], prepend = [], replacements = [];
|
|
56
|
+
let bindings = new Map(), importsIntent = [], isReactiveCall = (node) => isReactiveCallExpression(ctx.checker, node), prepend = [], replacements = [];
|
|
57
|
+
replacements.push(...primitives(ctx.sourceFile, bindings, isReactiveCall, ctx.checker));
|
|
19
58
|
let objectResult = object(ctx.sourceFile, bindings, ctx.checker);
|
|
20
59
|
prepend.push(...objectResult.prepend);
|
|
21
|
-
replacements.push(...objectResult.replacements);
|
|
22
|
-
let
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
60
|
+
replacements.push(...objectResult.replacements, ...array(ctx.sourceFile, bindings, ctx.checker));
|
|
61
|
+
let transformedNodes = new Set(replacements.map(r => r.node));
|
|
62
|
+
function findRemainingReactiveCalls(node) {
|
|
63
|
+
if (isReactiveCall(node)) {
|
|
64
|
+
let call = node;
|
|
65
|
+
if (!transformedNodes.has(call) && !transformedNodes.has(call.expression)) {
|
|
66
|
+
replacements.push({
|
|
67
|
+
generate: () => `${COMPILER_NAMESPACE}.reactive(${call.arguments.map(a => a.getText(ctx.sourceFile)).join(', ')})`,
|
|
68
|
+
node: call
|
|
69
|
+
});
|
|
70
|
+
}
|
|
28
71
|
}
|
|
72
|
+
ts.forEachChild(node, findRemainingReactiveCalls);
|
|
73
|
+
}
|
|
74
|
+
findRemainingReactiveCalls(ctx.sourceFile);
|
|
75
|
+
if (replacements.length > 0 || prepend.length > 0) {
|
|
29
76
|
importsIntent.push({
|
|
30
77
|
namespace: COMPILER_NAMESPACE,
|
|
31
78
|
package: PACKAGE,
|
|
32
|
-
remove
|
|
79
|
+
remove: [COMPILER_ENTRYPOINT]
|
|
33
80
|
});
|
|
34
81
|
}
|
|
35
82
|
return {
|
package/build/compiler/object.js
CHANGED
|
@@ -110,11 +110,8 @@ function isStaticValue(node) {
|
|
|
110
110
|
}
|
|
111
111
|
return false;
|
|
112
112
|
}
|
|
113
|
-
function isReactiveCall(node) {
|
|
114
|
-
return ts.isIdentifier(node.expression) && node.expression.text === "reactive";
|
|
115
|
-
}
|
|
116
113
|
function visit(ctx, node) {
|
|
117
|
-
if (ts.isCallExpression(node) &&
|
|
114
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'reactive') {
|
|
118
115
|
let arg = node.arguments[0];
|
|
119
116
|
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
120
117
|
let properties = [], props = arg.properties, varName = null;
|
|
@@ -164,11 +161,11 @@ export default (sourceFile, bindings, checker) => {
|
|
|
164
161
|
let call = ctx.calls[i];
|
|
165
162
|
prepend.push(buildClassCode(call.className, call.properties));
|
|
166
163
|
replacements.push({
|
|
167
|
-
node: call.node,
|
|
168
164
|
generate: () => ` new ${call.className}(${call.properties
|
|
169
165
|
.filter(({ isStatic, type }) => !isStatic || type === COMPILER_TYPES.Computed)
|
|
170
166
|
.map(p => p.valueText)
|
|
171
|
-
.join(', ')})
|
|
167
|
+
.join(', ')})`,
|
|
168
|
+
node: call.node,
|
|
172
169
|
});
|
|
173
170
|
}
|
|
174
171
|
return { prepend, replacements };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
2
|
+
import { ts } from '@esportsplus/typescript';
|
|
3
|
+
import type { Bindings } from '../types.js';
|
|
4
|
+
declare const _default: (sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: (node: ts.Node) => boolean, checker?: ts.TypeChecker) => ReplacementIntent[];
|
|
5
|
+
export default _default;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { ts } from '@esportsplus/typescript';
|
|
2
|
+
import { COMPILER_NAMESPACE, COMPILER_TYPES } from '../constants.js';
|
|
3
|
+
const COMPOUND_OPERATORS = new Map([
|
|
4
|
+
[ts.SyntaxKind.AmpersandAmpersandEqualsToken, '&&'],
|
|
5
|
+
[ts.SyntaxKind.AmpersandEqualsToken, '&'],
|
|
6
|
+
[ts.SyntaxKind.AsteriskAsteriskEqualsToken, '**'],
|
|
7
|
+
[ts.SyntaxKind.AsteriskEqualsToken, '*'],
|
|
8
|
+
[ts.SyntaxKind.BarBarEqualsToken, '||'],
|
|
9
|
+
[ts.SyntaxKind.BarEqualsToken, '|'],
|
|
10
|
+
[ts.SyntaxKind.CaretEqualsToken, '^'],
|
|
11
|
+
[ts.SyntaxKind.GreaterThanGreaterThanEqualsToken, '>>'],
|
|
12
|
+
[ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, '>>>'],
|
|
13
|
+
[ts.SyntaxKind.LessThanLessThanEqualsToken, '<<'],
|
|
14
|
+
[ts.SyntaxKind.MinusEqualsToken, '-'],
|
|
15
|
+
[ts.SyntaxKind.PercentEqualsToken, '%'],
|
|
16
|
+
[ts.SyntaxKind.PlusEqualsToken, '+'],
|
|
17
|
+
[ts.SyntaxKind.QuestionQuestionEqualsToken, '??'],
|
|
18
|
+
[ts.SyntaxKind.SlashEqualsToken, '/']
|
|
19
|
+
]);
|
|
20
|
+
function isInScope(reference, binding) {
|
|
21
|
+
let current = reference;
|
|
22
|
+
while (current) {
|
|
23
|
+
if (current === binding.scope) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
current = current.parent;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
function visit(ctx, node) {
|
|
31
|
+
if (ctx.isReactiveCall(node)) {
|
|
32
|
+
let call = node;
|
|
33
|
+
if (call.arguments.length > 0) {
|
|
34
|
+
let arg = call.arguments[0], classification = COMPILER_TYPES.Signal;
|
|
35
|
+
if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
|
|
36
|
+
classification = COMPILER_TYPES.Computed;
|
|
37
|
+
}
|
|
38
|
+
else if (ts.isArrayLiteralExpression(arg) || ts.isObjectLiteralExpression(arg)) {
|
|
39
|
+
classification = null;
|
|
40
|
+
}
|
|
41
|
+
if (classification) {
|
|
42
|
+
let varName = null;
|
|
43
|
+
if (call.parent && ts.isVariableDeclaration(call.parent) && ts.isIdentifier(call.parent.name)) {
|
|
44
|
+
varName = call.parent.name.text;
|
|
45
|
+
}
|
|
46
|
+
else if (call.parent &&
|
|
47
|
+
ts.isBinaryExpression(call.parent) &&
|
|
48
|
+
call.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
49
|
+
ts.isIdentifier(call.parent.left)) {
|
|
50
|
+
varName = call.parent.left.text;
|
|
51
|
+
}
|
|
52
|
+
if (varName) {
|
|
53
|
+
let current = call.parent, scope;
|
|
54
|
+
while (current) {
|
|
55
|
+
if (ts.isArrowFunction(current) ||
|
|
56
|
+
ts.isBlock(current) ||
|
|
57
|
+
ts.isForInStatement(current) ||
|
|
58
|
+
ts.isForOfStatement(current) ||
|
|
59
|
+
ts.isForStatement(current) ||
|
|
60
|
+
ts.isFunctionDeclaration(current) ||
|
|
61
|
+
ts.isFunctionExpression(current) ||
|
|
62
|
+
ts.isSourceFile(current)) {
|
|
63
|
+
scope = current;
|
|
64
|
+
}
|
|
65
|
+
current = current.parent;
|
|
66
|
+
}
|
|
67
|
+
if (!scope) {
|
|
68
|
+
scope = call.getSourceFile();
|
|
69
|
+
}
|
|
70
|
+
ctx.scopedBindings.push({ name: varName, scope, type: classification });
|
|
71
|
+
ctx.bindings.set(varName, classification);
|
|
72
|
+
}
|
|
73
|
+
ctx.replacements.push({
|
|
74
|
+
generate: () => classification === COMPILER_TYPES.Computed
|
|
75
|
+
? `${COMPILER_NAMESPACE}.computed`
|
|
76
|
+
: `${COMPILER_NAMESPACE}.signal`,
|
|
77
|
+
node: call.expression
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (ts.isIdentifier(node) &&
|
|
83
|
+
node.parent &&
|
|
84
|
+
!(ts.isVariableDeclaration(node.parent) && node.parent.name === node)) {
|
|
85
|
+
if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
|
|
86
|
+
ts.forEachChild(node, n => visit(ctx, n));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
let bindings = ctx.scopedBindings, binding, name = node.text;
|
|
90
|
+
for (let i = 0, n = bindings.length; i < n; i++) {
|
|
91
|
+
let b = bindings[i];
|
|
92
|
+
if (b.name === name && isInScope(node, b)) {
|
|
93
|
+
binding = b;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (binding && node.parent) {
|
|
97
|
+
let parent = node.parent;
|
|
98
|
+
if (!(ts.isBinaryExpression(parent) &&
|
|
99
|
+
parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
100
|
+
ctx.isReactiveCall(parent.right)) &&
|
|
101
|
+
!(ts.isTypeOfExpression(parent) && parent.expression === node)) {
|
|
102
|
+
let writeCtx;
|
|
103
|
+
if (ts.isBinaryExpression(parent) && parent.left === node) {
|
|
104
|
+
let op = parent.operatorToken.kind;
|
|
105
|
+
if (op === ts.SyntaxKind.EqualsToken) {
|
|
106
|
+
writeCtx = 'simple';
|
|
107
|
+
}
|
|
108
|
+
else if (COMPOUND_OPERATORS.has(op)) {
|
|
109
|
+
writeCtx = 'compound';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else if (ts.isPostfixUnaryExpression(parent) || ts.isPrefixUnaryExpression(parent)) {
|
|
113
|
+
let op = parent.operator;
|
|
114
|
+
if (op === ts.SyntaxKind.MinusMinusToken || op === ts.SyntaxKind.PlusPlusToken) {
|
|
115
|
+
writeCtx = 'increment';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (writeCtx) {
|
|
119
|
+
if (binding.type !== COMPILER_TYPES.Computed) {
|
|
120
|
+
if (writeCtx === 'simple' && ts.isBinaryExpression(parent)) {
|
|
121
|
+
let right = parent.right;
|
|
122
|
+
ctx.replacements.push({
|
|
123
|
+
generate: (sf) => `${COMPILER_NAMESPACE}.write(${name}, ${right.getText(sf)})`,
|
|
124
|
+
node: parent
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
else if (writeCtx === 'compound' && ts.isBinaryExpression(parent)) {
|
|
128
|
+
let op = COMPOUND_OPERATORS.get(parent.operatorToken.kind) ?? '+', right = parent.right;
|
|
129
|
+
ctx.replacements.push({
|
|
130
|
+
generate: (sf) => `${COMPILER_NAMESPACE}.write(${name}, ${name}.value ${op} ${right.getText(sf)})`,
|
|
131
|
+
node: parent
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
else if (writeCtx === 'increment') {
|
|
135
|
+
let delta = parent.operator === ts.SyntaxKind.PlusPlusToken ? '+ 1' : '- 1', isPrefix = ts.isPrefixUnaryExpression(parent);
|
|
136
|
+
if (ts.isExpressionStatement(parent.parent)) {
|
|
137
|
+
ctx.replacements.push({
|
|
138
|
+
generate: () => `${COMPILER_NAMESPACE}.write(${name}, ${name}.value ${delta})`,
|
|
139
|
+
node: parent
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else if (isPrefix) {
|
|
143
|
+
ctx.replacements.push({
|
|
144
|
+
generate: () => `(${COMPILER_NAMESPACE}.write(${name}, ${name}.value ${delta}), ${name}.value)`,
|
|
145
|
+
node: parent
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
let tmp = `_t${ctx.tmpCounter++}`;
|
|
150
|
+
ctx.replacements.push({
|
|
151
|
+
generate: () => `((${tmp}) => (${COMPILER_NAMESPACE}.write(${name}, ${tmp} ${delta}), ${tmp}))(${name}.value)`,
|
|
152
|
+
node: parent
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
ctx.replacements.push({
|
|
160
|
+
generate: () => `${COMPILER_NAMESPACE}.read(${name})`,
|
|
161
|
+
node
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
ts.forEachChild(node, n => visit(ctx, n));
|
|
168
|
+
}
|
|
169
|
+
export default (sourceFile, bindings, isReactiveCall, checker) => {
|
|
170
|
+
let ctx = {
|
|
171
|
+
bindings,
|
|
172
|
+
checker,
|
|
173
|
+
isReactiveCall,
|
|
174
|
+
replacements: [],
|
|
175
|
+
scopedBindings: [],
|
|
176
|
+
sourceFile,
|
|
177
|
+
tmpCounter: 0
|
|
178
|
+
};
|
|
179
|
+
visit(ctx, sourceFile);
|
|
180
|
+
return ctx.replacements;
|
|
181
|
+
};
|
|
@@ -8,5 +8,6 @@ type Guard<T> = T extends Record<PropertyKey, unknown> ? T extends {
|
|
|
8
8
|
} : T : never;
|
|
9
9
|
declare function reactive<T extends unknown[]>(input: T): Reactive<T>;
|
|
10
10
|
declare function reactive<T extends Record<PropertyKey, unknown>>(input: Guard<T>): Reactive<T>;
|
|
11
|
+
declare function reactive<T>(input: T): Reactive<T>;
|
|
11
12
|
export default reactive;
|
|
12
13
|
export { reactive, ReactiveArray, ReactiveObject };
|
package/package.json
CHANGED
package/src/compiler/array.ts
CHANGED
|
@@ -31,12 +31,13 @@ function getElementTypeText(typeNode: ts.TypeNode, sourceFile: ts.SourceFile): s
|
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function isReactiveCall(node: ts.CallExpression): boolean {
|
|
35
|
-
return ts.isIdentifier(node.expression) && node.expression.text === 'reactive';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
34
|
function visit(ctx: VisitContext, node: ts.Node): void {
|
|
39
|
-
if (
|
|
35
|
+
if (
|
|
36
|
+
ts.isCallExpression(node) &&
|
|
37
|
+
ts.isIdentifier(node.expression) &&
|
|
38
|
+
node.expression.text === 'reactive' &&
|
|
39
|
+
node.arguments.length > 0
|
|
40
|
+
) {
|
|
40
41
|
let arg = node.arguments[0],
|
|
41
42
|
expression = ts.isAsExpression(arg) ? arg.expression : arg;
|
|
42
43
|
|
|
@@ -94,16 +95,17 @@ function visit(ctx: VisitContext, node: ts.Node): void {
|
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
let parent = node.parent;
|
|
98
|
-
|
|
99
98
|
if (
|
|
100
99
|
ts.isPropertyAccessExpression(node) &&
|
|
101
100
|
node.name.text === 'length' &&
|
|
102
|
-
(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
(
|
|
102
|
+
!node.parent ||
|
|
103
|
+
(
|
|
104
|
+
!(ts.isBinaryExpression(node.parent) && node.parent.left === node) &&
|
|
105
|
+
!ts.isPostfixUnaryExpression(node.parent) &&
|
|
106
|
+
!ts.isPrefixUnaryExpression(node.parent)
|
|
107
|
+
)
|
|
108
|
+
)
|
|
107
109
|
) {
|
|
108
110
|
let name = ast.getExpressionName(node.expression);
|
|
109
111
|
|
|
@@ -126,12 +128,10 @@ function visit(ctx: VisitContext, node: ts.Node): void {
|
|
|
126
128
|
if (name && ctx.bindings.get(name) === COMPILER_TYPES.Array) {
|
|
127
129
|
ctx.replacements.push({
|
|
128
130
|
node,
|
|
129
|
-
generate: (sf) => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return `${element.expression.getText(sf)}.$set(${index}, ${value})`;
|
|
134
|
-
}
|
|
131
|
+
generate: (sf) => `${element.expression.getText(sf)}.$set(
|
|
132
|
+
${element.argumentExpression.getText(sf)},
|
|
133
|
+
${node.right.getText(sf)}
|
|
134
|
+
)`
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
137
|
}
|
package/src/compiler/index.ts
CHANGED
|
@@ -1,18 +1,77 @@
|
|
|
1
1
|
import type { ImportIntent, Plugin, ReplacementIntent, TransformContext } from '@esportsplus/typescript/compiler';
|
|
2
2
|
import { ts } from '@esportsplus/typescript';
|
|
3
|
-
import {
|
|
3
|
+
import { imports } from '@esportsplus/typescript/compiler';
|
|
4
4
|
import { COMPILER_ENTRYPOINT, COMPILER_NAMESPACE, PACKAGE } from '~/constants';
|
|
5
5
|
import type { Bindings } from '~/types';
|
|
6
6
|
import array from './array';
|
|
7
7
|
import object from './object';
|
|
8
|
+
import primitives from './primitives';
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
function hasReactiveImport(sourceFile: ts.SourceFile): boolean {
|
|
11
12
|
return imports.find(sourceFile, PACKAGE).some(i => i.specifiers.has(COMPILER_ENTRYPOINT));
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
function
|
|
15
|
-
|
|
15
|
+
function isReactiveSymbol(checker: ts.TypeChecker, node: ts.Node): boolean {
|
|
16
|
+
let symbol = checker.getSymbolAtLocation(node);
|
|
17
|
+
|
|
18
|
+
if (!symbol) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Follow aliases to original symbol
|
|
23
|
+
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
24
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let declarations = symbol.getDeclarations();
|
|
28
|
+
|
|
29
|
+
if (!declarations || declarations.length === 0) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (let i = 0, n = declarations.length; i < n; i++) {
|
|
34
|
+
let decl = declarations[i],
|
|
35
|
+
sourceFile = decl.getSourceFile();
|
|
36
|
+
|
|
37
|
+
// Check if declaration is from our package
|
|
38
|
+
if (sourceFile.fileName.includes(PACKAGE) || sourceFile.fileName.includes('reactivity')) {
|
|
39
|
+
// Verify it's the reactive export
|
|
40
|
+
if (symbol.name === COMPILER_ENTRYPOINT) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isReactiveCallExpression(checker: ts.TypeChecker | undefined, node: ts.Node): node is ts.CallExpression {
|
|
50
|
+
if (!ts.isCallExpression(node)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let expr = node.expression;
|
|
55
|
+
|
|
56
|
+
// Direct call: reactive(...) or aliasedName(...)
|
|
57
|
+
if (ts.isIdentifier(expr)) {
|
|
58
|
+
// Fast path: literal "reactive"
|
|
59
|
+
if (expr.text === COMPILER_ENTRYPOINT) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Use checker to resolve aliases
|
|
64
|
+
if (checker) {
|
|
65
|
+
return isReactiveSymbol(checker, expr);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Property access: ns.reactive(...)
|
|
70
|
+
if (ts.isPropertyAccessExpression(expr) && expr.name.text === COMPILER_ENTRYPOINT && checker) {
|
|
71
|
+
return isReactiveSymbol(checker, expr);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return false;
|
|
16
75
|
}
|
|
17
76
|
|
|
18
77
|
|
|
@@ -26,34 +85,46 @@ const plugin: Plugin = {
|
|
|
26
85
|
|
|
27
86
|
let bindings: Bindings = new Map(),
|
|
28
87
|
importsIntent: ImportIntent[] = [],
|
|
88
|
+
isReactiveCall = (node: ts.Node) => isReactiveCallExpression(ctx.checker, node),
|
|
29
89
|
prepend: string[] = [],
|
|
30
90
|
replacements: ReplacementIntent[] = [];
|
|
31
91
|
|
|
92
|
+
// Run primitives transform first (tracks bindings for signal/computed)
|
|
93
|
+
replacements.push(...primitives(ctx.sourceFile, bindings, isReactiveCall, ctx.checker));
|
|
94
|
+
|
|
32
95
|
// Run object transform
|
|
33
96
|
let objectResult = object(ctx.sourceFile, bindings, ctx.checker);
|
|
34
97
|
|
|
35
98
|
prepend.push(...objectResult.prepend);
|
|
36
|
-
replacements.push(...objectResult.replacements);
|
|
99
|
+
replacements.push(...objectResult.replacements, ...array(ctx.sourceFile, bindings, ctx.checker));
|
|
100
|
+
|
|
101
|
+
// Find remaining reactive() calls that weren't transformed and replace with namespace version
|
|
102
|
+
let transformedNodes = new Set(replacements.map(r => r.node));
|
|
103
|
+
|
|
104
|
+
function findRemainingReactiveCalls(node: ts.Node): void {
|
|
105
|
+
if (isReactiveCall(node)) {
|
|
106
|
+
let call = node as ts.CallExpression;
|
|
107
|
+
|
|
108
|
+
// Check if call or its expression has already been transformed
|
|
109
|
+
if (!transformedNodes.has(call) && !transformedNodes.has(call.expression)) {
|
|
110
|
+
replacements.push({
|
|
111
|
+
generate: () => `${COMPILER_NAMESPACE}.reactive(${call.arguments.map(a => a.getText(ctx.sourceFile)).join(', ')})`,
|
|
112
|
+
node: call
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
37
116
|
|
|
38
|
-
|
|
39
|
-
|
|
117
|
+
ts.forEachChild(node, findRemainingReactiveCalls);
|
|
118
|
+
}
|
|
40
119
|
|
|
41
|
-
|
|
120
|
+
findRemainingReactiveCalls(ctx.sourceFile);
|
|
42
121
|
|
|
43
122
|
// Build import intent
|
|
44
123
|
if (replacements.length > 0 || prepend.length > 0) {
|
|
45
|
-
let remove: string[] = [];
|
|
46
|
-
|
|
47
|
-
// Check if we still have reactive() calls after transform
|
|
48
|
-
// This is a heuristic - if we have no replacements for reactive calls, keep the import
|
|
49
|
-
if (!ast.hasMatch(ctx.sourceFile, isReactiveCallNode) || replacements.length > 0) {
|
|
50
|
-
remove.push(COMPILER_ENTRYPOINT);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
124
|
importsIntent.push({
|
|
54
125
|
namespace: COMPILER_NAMESPACE,
|
|
55
126
|
package: PACKAGE,
|
|
56
|
-
remove
|
|
127
|
+
remove: [COMPILER_ENTRYPOINT]
|
|
57
128
|
});
|
|
58
129
|
}
|
|
59
130
|
|
package/src/compiler/object.ts
CHANGED
|
@@ -166,12 +166,8 @@ function isStaticValue(node: ts.Node): boolean {
|
|
|
166
166
|
return false;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
function isReactiveCall(node: ts.CallExpression): boolean {
|
|
170
|
-
return ts.isIdentifier(node.expression) && node.expression.text === "reactive";
|
|
171
|
-
}
|
|
172
|
-
|
|
173
169
|
function visit(ctx: VisitContext, node: ts.Node): void {
|
|
174
|
-
if (ts.isCallExpression(node) &&
|
|
170
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'reactive') {
|
|
175
171
|
let arg = node.arguments[0];
|
|
176
172
|
|
|
177
173
|
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
@@ -246,15 +242,14 @@ export default (sourceFile: ts.SourceFile, bindings: Bindings, checker?: ts.Type
|
|
|
246
242
|
let call = ctx.calls[i];
|
|
247
243
|
|
|
248
244
|
prepend.push(buildClassCode(call.className, call.properties));
|
|
249
|
-
|
|
250
245
|
replacements.push({
|
|
251
|
-
node: call.node,
|
|
252
246
|
generate: () => ` new ${call.className}(${
|
|
253
247
|
call.properties
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
})
|
|
248
|
+
.filter(({ isStatic, type }) => !isStatic || type === COMPILER_TYPES.Computed)
|
|
249
|
+
.map(p => p.valueText)
|
|
250
|
+
.join(', ')
|
|
251
|
+
})`,
|
|
252
|
+
node: call.node,
|
|
258
253
|
});
|
|
259
254
|
}
|
|
260
255
|
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
2
|
+
import { ts } from '@esportsplus/typescript';
|
|
3
|
+
import { COMPILER_NAMESPACE, COMPILER_TYPES } from '~/constants';
|
|
4
|
+
import type { Bindings } from '~/types';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
interface ScopeBinding {
|
|
8
|
+
name: string;
|
|
9
|
+
scope: ts.Node;
|
|
10
|
+
type: COMPILER_TYPES;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface TransformContext {
|
|
14
|
+
bindings: Bindings;
|
|
15
|
+
checker?: ts.TypeChecker;
|
|
16
|
+
isReactiveCall: (node: ts.Node) => boolean;
|
|
17
|
+
replacements: ReplacementIntent[];
|
|
18
|
+
scopedBindings: ScopeBinding[];
|
|
19
|
+
sourceFile: ts.SourceFile;
|
|
20
|
+
tmpCounter: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
const COMPOUND_OPERATORS = new Map<ts.SyntaxKind, string>([
|
|
25
|
+
[ts.SyntaxKind.AmpersandAmpersandEqualsToken, '&&'],
|
|
26
|
+
[ts.SyntaxKind.AmpersandEqualsToken, '&'],
|
|
27
|
+
[ts.SyntaxKind.AsteriskAsteriskEqualsToken, '**'],
|
|
28
|
+
[ts.SyntaxKind.AsteriskEqualsToken, '*'],
|
|
29
|
+
[ts.SyntaxKind.BarBarEqualsToken, '||'],
|
|
30
|
+
[ts.SyntaxKind.BarEqualsToken, '|'],
|
|
31
|
+
[ts.SyntaxKind.CaretEqualsToken, '^'],
|
|
32
|
+
[ts.SyntaxKind.GreaterThanGreaterThanEqualsToken, '>>'],
|
|
33
|
+
[ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, '>>>'],
|
|
34
|
+
[ts.SyntaxKind.LessThanLessThanEqualsToken, '<<'],
|
|
35
|
+
[ts.SyntaxKind.MinusEqualsToken, '-'],
|
|
36
|
+
[ts.SyntaxKind.PercentEqualsToken, '%'],
|
|
37
|
+
[ts.SyntaxKind.PlusEqualsToken, '+'],
|
|
38
|
+
[ts.SyntaxKind.QuestionQuestionEqualsToken, '??'],
|
|
39
|
+
[ts.SyntaxKind.SlashEqualsToken, '/']
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
function isInScope(reference: ts.Node, binding: ScopeBinding): boolean {
|
|
44
|
+
let current: ts.Node | undefined = reference;
|
|
45
|
+
|
|
46
|
+
while (current) {
|
|
47
|
+
if (current === binding.scope) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
current = current.parent;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function visit(ctx: TransformContext, node: ts.Node): void {
|
|
58
|
+
if (ctx.isReactiveCall(node)) {
|
|
59
|
+
let call = node as ts.CallExpression;
|
|
60
|
+
|
|
61
|
+
if (call.arguments.length > 0) {
|
|
62
|
+
let arg = call.arguments[0],
|
|
63
|
+
classification: COMPILER_TYPES | null = COMPILER_TYPES.Signal;
|
|
64
|
+
|
|
65
|
+
if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
|
|
66
|
+
classification = COMPILER_TYPES.Computed;
|
|
67
|
+
}
|
|
68
|
+
else if (ts.isArrayLiteralExpression(arg) || ts.isObjectLiteralExpression(arg)) {
|
|
69
|
+
classification = null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (classification) {
|
|
73
|
+
let varName: string | null = null;
|
|
74
|
+
|
|
75
|
+
if (call.parent && ts.isVariableDeclaration(call.parent) && ts.isIdentifier(call.parent.name)) {
|
|
76
|
+
varName = call.parent.name.text;
|
|
77
|
+
}
|
|
78
|
+
else if (
|
|
79
|
+
call.parent &&
|
|
80
|
+
ts.isBinaryExpression(call.parent) &&
|
|
81
|
+
call.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
82
|
+
ts.isIdentifier(call.parent.left)
|
|
83
|
+
) {
|
|
84
|
+
varName = call.parent.left.text;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (varName) {
|
|
88
|
+
let current = call.parent,
|
|
89
|
+
scope;
|
|
90
|
+
|
|
91
|
+
while (current) {
|
|
92
|
+
if (
|
|
93
|
+
ts.isArrowFunction(current) ||
|
|
94
|
+
ts.isBlock(current) ||
|
|
95
|
+
ts.isForInStatement(current) ||
|
|
96
|
+
ts.isForOfStatement(current) ||
|
|
97
|
+
ts.isForStatement(current) ||
|
|
98
|
+
ts.isFunctionDeclaration(current) ||
|
|
99
|
+
ts.isFunctionExpression(current) ||
|
|
100
|
+
ts.isSourceFile(current)
|
|
101
|
+
) {
|
|
102
|
+
scope = current;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
current = current.parent;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!scope) {
|
|
109
|
+
scope = call.getSourceFile();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
ctx.scopedBindings.push({ name: varName, scope, type: classification });
|
|
113
|
+
ctx.bindings.set(varName, classification);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Replace just the 'reactive' identifier with the appropriate namespace function
|
|
117
|
+
ctx.replacements.push({
|
|
118
|
+
generate: () => classification === COMPILER_TYPES.Computed
|
|
119
|
+
? `${COMPILER_NAMESPACE}.computed`
|
|
120
|
+
: `${COMPILER_NAMESPACE}.signal`,
|
|
121
|
+
node: call.expression
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Continue visiting children - inner identifiers will get their own ReplacementIntents
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (
|
|
130
|
+
ts.isIdentifier(node) &&
|
|
131
|
+
node.parent &&
|
|
132
|
+
!(ts.isVariableDeclaration(node.parent) && node.parent.name === node)
|
|
133
|
+
) {
|
|
134
|
+
if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
|
|
135
|
+
ts.forEachChild(node, n => visit(ctx, n));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let bindings = ctx.scopedBindings,
|
|
140
|
+
binding,
|
|
141
|
+
name = node.text;
|
|
142
|
+
|
|
143
|
+
for (let i = 0, n = bindings.length; i < n; i++) {
|
|
144
|
+
let b = bindings[i];
|
|
145
|
+
|
|
146
|
+
if (b.name === name && isInScope(node, b)) {
|
|
147
|
+
binding = b;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (binding && node.parent) {
|
|
152
|
+
let parent = node.parent;
|
|
153
|
+
|
|
154
|
+
if (
|
|
155
|
+
!(
|
|
156
|
+
ts.isBinaryExpression(parent) &&
|
|
157
|
+
parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
158
|
+
ctx.isReactiveCall(parent.right)
|
|
159
|
+
) &&
|
|
160
|
+
!(ts.isTypeOfExpression(parent) && parent.expression === node)
|
|
161
|
+
) {
|
|
162
|
+
let writeCtx;
|
|
163
|
+
|
|
164
|
+
if (ts.isBinaryExpression(parent) && parent.left === node) {
|
|
165
|
+
let op = parent.operatorToken.kind;
|
|
166
|
+
|
|
167
|
+
if (op === ts.SyntaxKind.EqualsToken) {
|
|
168
|
+
writeCtx = 'simple';
|
|
169
|
+
}
|
|
170
|
+
else if (COMPOUND_OPERATORS.has(op)) {
|
|
171
|
+
writeCtx = 'compound';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else if (ts.isPostfixUnaryExpression(parent) || ts.isPrefixUnaryExpression(parent)) {
|
|
175
|
+
let op = parent.operator;
|
|
176
|
+
|
|
177
|
+
if (op === ts.SyntaxKind.MinusMinusToken || op === ts.SyntaxKind.PlusPlusToken) {
|
|
178
|
+
writeCtx = 'increment';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (writeCtx) {
|
|
183
|
+
if (binding.type !== COMPILER_TYPES.Computed) {
|
|
184
|
+
if (writeCtx === 'simple' && ts.isBinaryExpression(parent)) {
|
|
185
|
+
let right = parent.right;
|
|
186
|
+
|
|
187
|
+
ctx.replacements.push({
|
|
188
|
+
generate: (sf) => `${COMPILER_NAMESPACE}.write(${name}, ${right.getText(sf)})`,
|
|
189
|
+
node: parent
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
else if (writeCtx === 'compound' && ts.isBinaryExpression(parent)) {
|
|
193
|
+
let op = COMPOUND_OPERATORS.get(parent.operatorToken.kind) ?? '+',
|
|
194
|
+
right = parent.right;
|
|
195
|
+
|
|
196
|
+
ctx.replacements.push({
|
|
197
|
+
generate: (sf) => `${COMPILER_NAMESPACE}.write(${name}, ${name}.value ${op} ${right.getText(sf)})`,
|
|
198
|
+
node: parent
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
else if (writeCtx === 'increment') {
|
|
202
|
+
let delta = (parent as ts.PostfixUnaryExpression | ts.PrefixUnaryExpression).operator === ts.SyntaxKind.PlusPlusToken ? '+ 1' : '- 1',
|
|
203
|
+
isPrefix = ts.isPrefixUnaryExpression(parent);
|
|
204
|
+
|
|
205
|
+
if (ts.isExpressionStatement(parent.parent)) {
|
|
206
|
+
ctx.replacements.push({
|
|
207
|
+
generate: () => `${COMPILER_NAMESPACE}.write(${name}, ${name}.value ${delta})`,
|
|
208
|
+
node: parent
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
else if (isPrefix) {
|
|
212
|
+
ctx.replacements.push({
|
|
213
|
+
generate: () => `(${COMPILER_NAMESPACE}.write(${name}, ${name}.value ${delta}), ${name}.value)`,
|
|
214
|
+
node: parent
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
let tmp = `_t${ctx.tmpCounter++}`;
|
|
219
|
+
|
|
220
|
+
ctx.replacements.push({
|
|
221
|
+
generate: () => `((${tmp}) => (${COMPILER_NAMESPACE}.write(${name}, ${tmp} ${delta}), ${tmp}))(${name}.value)`,
|
|
222
|
+
node: parent
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
ctx.replacements.push({
|
|
230
|
+
generate: () => `${COMPILER_NAMESPACE}.read(${name})`,
|
|
231
|
+
node
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
ts.forEachChild(node, n => visit(ctx, n));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
export default (
|
|
243
|
+
sourceFile: ts.SourceFile,
|
|
244
|
+
bindings: Bindings,
|
|
245
|
+
isReactiveCall: (node: ts.Node) => boolean,
|
|
246
|
+
checker?: ts.TypeChecker
|
|
247
|
+
) => {
|
|
248
|
+
let ctx: TransformContext = {
|
|
249
|
+
bindings,
|
|
250
|
+
checker,
|
|
251
|
+
isReactiveCall,
|
|
252
|
+
replacements: [],
|
|
253
|
+
scopedBindings: [],
|
|
254
|
+
sourceFile,
|
|
255
|
+
tmpCounter: 0
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
visit(ctx, sourceFile);
|
|
259
|
+
|
|
260
|
+
return ctx.replacements;
|
|
261
|
+
};
|
package/src/reactive/index.ts
CHANGED
|
@@ -16,7 +16,8 @@ type Guard<T> =
|
|
|
16
16
|
|
|
17
17
|
function reactive<T extends unknown[]>(input: T): Reactive<T>;
|
|
18
18
|
function reactive<T extends Record<PropertyKey, unknown>>(input: Guard<T>): Reactive<T>;
|
|
19
|
-
function reactive<T
|
|
19
|
+
function reactive<T>(input: T): Reactive<T>;
|
|
20
|
+
function reactive<T>(input: T): Reactive<T> {
|
|
20
21
|
let dispose = false,
|
|
21
22
|
value = root(() => {
|
|
22
23
|
let response: Reactive<T> | undefined;
|
|
@@ -40,7 +41,7 @@ function reactive<T extends unknown[] | Record<PropertyKey, unknown>>(input: T):
|
|
|
40
41
|
});
|
|
41
42
|
|
|
42
43
|
if (dispose) {
|
|
43
|
-
onCleanup(() => value.dispose());
|
|
44
|
+
onCleanup(() => (value as any as { dispose: VoidFunction }).dispose());
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
return value;
|
package/test/primitives.ts
CHANGED
|
@@ -1,171 +1,87 @@
|
|
|
1
|
-
|
|
2
|
-
import { effect, reactive } from '@esportsplus/reactivity';
|
|
1
|
+
import { reactive } from '@esportsplus/reactivity';
|
|
3
2
|
|
|
4
3
|
|
|
5
|
-
//
|
|
6
|
-
// Standalone Signal Primitives
|
|
7
|
-
// =============================================================================
|
|
8
|
-
|
|
9
|
-
console.log('=== Standalone Signal Primitives ===');
|
|
10
|
-
|
|
4
|
+
// Signal creation
|
|
11
5
|
let count = reactive(0);
|
|
12
|
-
let name = reactive('
|
|
6
|
+
let name = reactive('test');
|
|
13
7
|
let flag = reactive(true);
|
|
8
|
+
let nullable = reactive<string | null>(null);
|
|
14
9
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
// Computed creation
|
|
11
|
+
let doubled = reactive(() => count * 2);
|
|
12
|
+
let greeting = reactive(() => `Hello ${name}!`);
|
|
13
|
+
let complex = reactive(() => flag ? count : 0);
|
|
18
14
|
|
|
19
|
-
//
|
|
15
|
+
// Read access
|
|
16
|
+
console.log(count);
|
|
17
|
+
console.log(name);
|
|
18
|
+
console.log(doubled);
|
|
19
|
+
|
|
20
|
+
// Write access - simple assignment
|
|
20
21
|
count = 10;
|
|
21
|
-
name = '
|
|
22
|
+
name = 'world';
|
|
22
23
|
flag = false;
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
console.log('\n=== Increment/Decrement ===');
|
|
72
|
-
|
|
73
|
-
let counter = reactive(0);
|
|
74
|
-
|
|
75
|
-
counter++;
|
|
76
|
-
console.log('After counter++:', counter);
|
|
77
|
-
|
|
78
|
-
++counter;
|
|
79
|
-
console.log('After ++counter:', counter);
|
|
80
|
-
|
|
81
|
-
counter--;
|
|
82
|
-
console.log('After counter--:', counter);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// =============================================================================
|
|
86
|
-
// Mixed Standalone and Object Primitives
|
|
87
|
-
// =============================================================================
|
|
88
|
-
|
|
89
|
-
console.log('\n=== Mixed Standalone and Object ===');
|
|
90
|
-
|
|
91
|
-
let multiplier = reactive(2);
|
|
92
|
-
|
|
93
|
-
let obj = reactive({
|
|
94
|
-
value: 10,
|
|
95
|
-
scaled: () => obj.value * multiplier
|
|
25
|
+
// Compound assignment operators
|
|
26
|
+
count += 5;
|
|
27
|
+
count -= 2;
|
|
28
|
+
count *= 3;
|
|
29
|
+
count /= 2;
|
|
30
|
+
count %= 7;
|
|
31
|
+
count **= 2;
|
|
32
|
+
count &= 0xFF;
|
|
33
|
+
count |= 0x0F;
|
|
34
|
+
count ^= 0xAA;
|
|
35
|
+
count <<= 2;
|
|
36
|
+
count >>= 1;
|
|
37
|
+
count >>>= 1;
|
|
38
|
+
count &&= 1;
|
|
39
|
+
count ||= 0;
|
|
40
|
+
count ??= 42;
|
|
41
|
+
|
|
42
|
+
// Increment/decrement - statement context
|
|
43
|
+
count++;
|
|
44
|
+
count--;
|
|
45
|
+
++count;
|
|
46
|
+
--count;
|
|
47
|
+
|
|
48
|
+
// Increment/decrement - expression context (prefix)
|
|
49
|
+
let a = ++count;
|
|
50
|
+
let b = --count;
|
|
51
|
+
console.log(a, b);
|
|
52
|
+
|
|
53
|
+
// Increment/decrement - expression context (postfix)
|
|
54
|
+
let c = count++;
|
|
55
|
+
let d = count--;
|
|
56
|
+
console.log(c, d);
|
|
57
|
+
|
|
58
|
+
// Nested reads in computed
|
|
59
|
+
let x = reactive(1);
|
|
60
|
+
let y = reactive(2);
|
|
61
|
+
let sum = reactive(() => x + y);
|
|
62
|
+
let product = reactive(() => x * y);
|
|
63
|
+
let nested = reactive(() => sum + product);
|
|
64
|
+
|
|
65
|
+
// Conditional reads
|
|
66
|
+
let conditional = reactive(() => {
|
|
67
|
+
if (flag) {
|
|
68
|
+
return x + y;
|
|
69
|
+
}
|
|
70
|
+
return 0;
|
|
96
71
|
});
|
|
97
72
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
console.log('After multiplier = 3:');
|
|
103
|
-
console.log(' obj.scaled:', obj.scaled);
|
|
104
|
-
|
|
105
|
-
obj.value = 20;
|
|
106
|
-
console.log('After obj.value = 20:');
|
|
107
|
-
console.log(' obj.scaled:', obj.scaled);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// =============================================================================
|
|
111
|
-
// Effects with Standalone Primitives
|
|
112
|
-
// =============================================================================
|
|
113
|
-
|
|
114
|
-
console.log('\n=== Effects with Standalone Primitives ===');
|
|
115
|
-
|
|
116
|
-
let effectCount = 0;
|
|
117
|
-
let watched = reactive(0);
|
|
118
|
-
|
|
119
|
-
let cleanup = effect(() => {
|
|
120
|
-
effectCount++;
|
|
121
|
-
console.log(`Effect #${effectCount}: watched = ${watched}`);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
watched = 1;
|
|
125
|
-
watched = 2;
|
|
126
|
-
watched = 3;
|
|
127
|
-
|
|
128
|
-
cleanup();
|
|
129
|
-
|
|
130
|
-
watched = 4; // Should not trigger effect
|
|
131
|
-
console.log('After cleanup, watched set to 4 (no effect should run)');
|
|
132
|
-
console.log('Total effect runs:', effectCount);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// =============================================================================
|
|
136
|
-
// String Template Computeds
|
|
137
|
-
// =============================================================================
|
|
138
|
-
|
|
139
|
-
console.log('\n=== String Template Computeds ===');
|
|
140
|
-
|
|
141
|
-
let firstName = reactive('John');
|
|
142
|
-
let lastName = reactive('Doe');
|
|
143
|
-
let fullName = reactive(() => `${firstName} ${lastName}`);
|
|
144
|
-
|
|
145
|
-
console.log('Full name:', fullName);
|
|
146
|
-
|
|
147
|
-
firstName = 'Jane';
|
|
148
|
-
console.log('After firstName = Jane:', fullName);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
// =============================================================================
|
|
152
|
-
// Object-wrapped Primitives (original tests)
|
|
153
|
-
// =============================================================================
|
|
154
|
-
|
|
155
|
-
console.log('\n=== Object-wrapped Primitives ===');
|
|
156
|
-
|
|
157
|
-
let state = reactive({
|
|
158
|
-
count: 0,
|
|
159
|
-
flag: true,
|
|
160
|
-
name: 'initial'
|
|
161
|
-
});
|
|
73
|
+
// Function with reactive reads
|
|
74
|
+
function calculate() {
|
|
75
|
+
return count + x + y;
|
|
76
|
+
}
|
|
162
77
|
|
|
163
|
-
|
|
78
|
+
// Arrow function with reactive reads
|
|
79
|
+
const calc = () => count * 2;
|
|
164
80
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
81
|
+
// Reactive in loop
|
|
82
|
+
for (let i = 0; i < 10; i++) {
|
|
83
|
+
count += i;
|
|
84
|
+
}
|
|
168
85
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
console.log('After assignment - flag:', state.flag);
|
|
86
|
+
// Reassignment with new reactive
|
|
87
|
+
count = reactive(100);
|