@esportsplus/reactivity 0.27.3 → 0.28.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/build/compiler/array.d.ts +2 -1
- package/build/compiler/array.js +39 -20
- package/build/compiler/index.d.ts +3 -7
- package/build/compiler/index.js +27 -50
- package/build/compiler/object.d.ts +6 -1
- package/build/compiler/object.js +14 -24
- package/build/compiler/plugins/tsc.d.ts +2 -3
- package/build/compiler/plugins/tsc.js +2 -3
- package/build/compiler/plugins/vite.js +3 -4
- package/package.json +2 -2
- package/src/compiler/array.ts +55 -28
- package/src/compiler/index.ts +38 -75
- package/src/compiler/object.ts +27 -36
- package/src/compiler/plugins/tsc.ts +2 -3
- package/src/compiler/plugins/vite.ts +4 -5
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
1
2
|
import { ts } from '@esportsplus/typescript';
|
|
2
3
|
import type { Bindings } from '../types.js';
|
|
3
|
-
declare const _default: (sourceFile: ts.SourceFile, bindings: Bindings, checker?: ts.TypeChecker) =>
|
|
4
|
+
declare const _default: (sourceFile: ts.SourceFile, bindings: Bindings, checker?: ts.TypeChecker) => ReplacementIntent[];
|
|
4
5
|
export default _default;
|
package/build/compiler/array.js
CHANGED
|
@@ -1,20 +1,42 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import { ast
|
|
2
|
+
import { ast } from '@esportsplus/typescript/compiler';
|
|
3
3
|
import { COMPILER_NAMESPACE, COMPILER_TYPES } from '../constants.js';
|
|
4
|
-
|
|
4
|
+
function getElementTypeText(typeNode, sourceFile) {
|
|
5
|
+
if (ts.isArrayTypeNode(typeNode)) {
|
|
6
|
+
return typeNode.elementType.getText(sourceFile);
|
|
7
|
+
}
|
|
8
|
+
if (ts.isTypeReferenceNode(typeNode) &&
|
|
9
|
+
ts.isIdentifier(typeNode.typeName) &&
|
|
10
|
+
typeNode.typeName.text === 'Array' &&
|
|
11
|
+
typeNode.typeArguments &&
|
|
12
|
+
typeNode.typeArguments.length > 0) {
|
|
13
|
+
return typeNode.typeArguments[0].getText(sourceFile);
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
function isReactiveCall(node) {
|
|
18
|
+
return ts.isIdentifier(node.expression) && node.expression.text === 'reactive';
|
|
19
|
+
}
|
|
5
20
|
function visit(ctx, node) {
|
|
6
|
-
if (ts.isCallExpression(node) && isReactiveCall(node
|
|
21
|
+
if (ts.isCallExpression(node) && isReactiveCall(node) && node.arguments.length > 0) {
|
|
7
22
|
let arg = node.arguments[0], expression = ts.isAsExpression(arg) ? arg.expression : arg;
|
|
8
23
|
if (ts.isArrayLiteralExpression(expression)) {
|
|
24
|
+
let elementType = null;
|
|
25
|
+
if (ts.isAsExpression(arg) && arg.type) {
|
|
26
|
+
elementType = getElementTypeText(arg.type, ctx.sourceFile);
|
|
27
|
+
}
|
|
28
|
+
else if (node.parent && ts.isVariableDeclaration(node.parent) && node.parent.type) {
|
|
29
|
+
elementType = getElementTypeText(node.parent.type, ctx.sourceFile);
|
|
30
|
+
}
|
|
9
31
|
if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
10
32
|
ctx.bindings.set(node.parent.name.text, COMPILER_TYPES.Array);
|
|
11
33
|
}
|
|
34
|
+
let typeParam = elementType ? `<${elementType}>` : '';
|
|
12
35
|
ctx.replacements.push({
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
? ` new ${COMPILER_NAMESPACE}.ReactiveArray(...${expression.getText(
|
|
16
|
-
: ` new ${COMPILER_NAMESPACE}.ReactiveArray()
|
|
17
|
-
start: node.pos
|
|
36
|
+
node,
|
|
37
|
+
generate: (sf) => expression.elements.length > 0
|
|
38
|
+
? ` new ${COMPILER_NAMESPACE}.ReactiveArray${typeParam}(...${expression.getText(sf)})`
|
|
39
|
+
: ` new ${COMPILER_NAMESPACE}.ReactiveArray${typeParam}()`
|
|
18
40
|
});
|
|
19
41
|
}
|
|
20
42
|
}
|
|
@@ -49,9 +71,8 @@ function visit(ctx, node) {
|
|
|
49
71
|
let name = ast.getExpressionName(node.expression);
|
|
50
72
|
if (name && ctx.bindings.get(name) === COMPILER_TYPES.Array) {
|
|
51
73
|
ctx.replacements.push({
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
start: node.pos
|
|
74
|
+
node,
|
|
75
|
+
generate: (sf) => `${node.expression.getText(sf)}.$length()`
|
|
55
76
|
});
|
|
56
77
|
}
|
|
57
78
|
}
|
|
@@ -60,26 +81,24 @@ function visit(ctx, node) {
|
|
|
60
81
|
ts.isElementAccessExpression(node.left)) {
|
|
61
82
|
let element = node.left, name = ast.getExpressionName(element.expression);
|
|
62
83
|
if (name && ctx.bindings.get(name) === COMPILER_TYPES.Array) {
|
|
63
|
-
let index = element.argumentExpression.getText(ctx.sourceFile), value = node.right.getText(ctx.sourceFile);
|
|
64
84
|
ctx.replacements.push({
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
85
|
+
node,
|
|
86
|
+
generate: (sf) => {
|
|
87
|
+
let index = element.argumentExpression.getText(sf), value = node.right.getText(sf);
|
|
88
|
+
return `${element.expression.getText(sf)}.$set(${index}, ${value})`;
|
|
89
|
+
}
|
|
68
90
|
});
|
|
69
91
|
}
|
|
70
92
|
}
|
|
71
93
|
ts.forEachChild(node, n => visit(ctx, n));
|
|
72
94
|
}
|
|
73
95
|
export default (sourceFile, bindings, checker) => {
|
|
74
|
-
let
|
|
96
|
+
let ctx = {
|
|
75
97
|
bindings,
|
|
76
98
|
checker,
|
|
77
99
|
replacements: [],
|
|
78
100
|
sourceFile
|
|
79
101
|
};
|
|
80
102
|
visit(ctx, sourceFile);
|
|
81
|
-
|
|
82
|
-
return code;
|
|
83
|
-
}
|
|
84
|
-
return c.replace(code, ctx.replacements);
|
|
103
|
+
return ctx.replacements;
|
|
85
104
|
};
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
declare const analyze: (sourceFile: ts.SourceFile, _program: ts.Program, context: PluginContext) => void;
|
|
5
|
-
declare const isReactiveCall: (node: ts.CallExpression, _checker?: ts.TypeChecker) => boolean;
|
|
6
|
-
declare const transform: (sourceFile: ts.SourceFile, program: ts.Program, context?: PluginContext) => TransformResult;
|
|
7
|
-
export { analyze, isReactiveCall, transform };
|
|
1
|
+
import type { Plugin } from '@esportsplus/typescript/compiler';
|
|
2
|
+
declare const plugin: Plugin;
|
|
3
|
+
export default plugin;
|
package/build/compiler/index.js
CHANGED
|
@@ -3,63 +3,40 @@ import { ast, 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
|
-
const CONTEXT_KEY = 'reactivity:analyzed';
|
|
7
|
-
let transforms = [object, array];
|
|
8
|
-
function getAnalyzedFile(context, filename) {
|
|
9
|
-
return context?.get(CONTEXT_KEY)?.get(filename);
|
|
10
|
-
}
|
|
11
6
|
function hasReactiveImport(sourceFile) {
|
|
12
7
|
return imports.find(sourceFile, PACKAGE).some(i => i.specifiers.has(COMPILER_ENTRYPOINT));
|
|
13
8
|
}
|
|
14
9
|
function isReactiveCallNode(node) {
|
|
15
10
|
return ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === COMPILER_ENTRYPOINT;
|
|
16
11
|
}
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (!files) {
|
|
23
|
-
files = new Map();
|
|
24
|
-
context.set(CONTEXT_KEY, files);
|
|
25
|
-
}
|
|
26
|
-
files.set(sourceFile.fileName, {
|
|
27
|
-
hasReactiveImport: true
|
|
28
|
-
});
|
|
29
|
-
};
|
|
30
|
-
const isReactiveCall = (node, _checker) => {
|
|
31
|
-
if (!ts.isIdentifier(node.expression)) {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
return node.expression.text === COMPILER_ENTRYPOINT;
|
|
35
|
-
};
|
|
36
|
-
const transform = (sourceFile, program, context) => {
|
|
37
|
-
let bindings = new Map(), changed = false, checker = program.getTypeChecker(), code = sourceFile.getFullText(), current = sourceFile, filename = sourceFile.fileName, result;
|
|
38
|
-
let analyzed = getAnalyzedFile(context, filename);
|
|
39
|
-
if (!analyzed) {
|
|
40
|
-
if (!hasReactiveImport(sourceFile)) {
|
|
41
|
-
return { changed: false, code, sourceFile };
|
|
12
|
+
const plugin = {
|
|
13
|
+
patterns: ['reactive(', 'reactive<'],
|
|
14
|
+
transform: (ctx) => {
|
|
15
|
+
if (!hasReactiveImport(ctx.sourceFile)) {
|
|
16
|
+
return {};
|
|
42
17
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
18
|
+
let bindings = new Map(), importsIntent = [], prepend = [], replacements = [];
|
|
19
|
+
let objectResult = object(ctx.sourceFile, bindings, ctx.checker);
|
|
20
|
+
prepend.push(...objectResult.prepend);
|
|
21
|
+
replacements.push(...objectResult.replacements);
|
|
22
|
+
let arrayResult = array(ctx.sourceFile, bindings, ctx.checker);
|
|
23
|
+
replacements.push(...arrayResult);
|
|
24
|
+
if (replacements.length > 0 || prepend.length > 0) {
|
|
25
|
+
let remove = [];
|
|
26
|
+
if (!ast.hasMatch(ctx.sourceFile, isReactiveCallNode) || replacements.length > 0) {
|
|
27
|
+
remove.push(COMPILER_ENTRYPOINT);
|
|
28
|
+
}
|
|
29
|
+
importsIntent.push({
|
|
30
|
+
namespace: COMPILER_NAMESPACE,
|
|
31
|
+
package: PACKAGE,
|
|
32
|
+
remove
|
|
33
|
+
});
|
|
59
34
|
}
|
|
60
|
-
|
|
61
|
-
|
|
35
|
+
return {
|
|
36
|
+
imports: importsIntent,
|
|
37
|
+
prepend,
|
|
38
|
+
replacements
|
|
39
|
+
};
|
|
62
40
|
}
|
|
63
|
-
return { changed, code, sourceFile };
|
|
64
41
|
};
|
|
65
|
-
export
|
|
42
|
+
export default plugin;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
1
2
|
import { ts } from '@esportsplus/typescript';
|
|
2
3
|
import type { Bindings } from '../types.js';
|
|
3
|
-
|
|
4
|
+
type ObjectTransformResult = {
|
|
5
|
+
prepend: string[];
|
|
6
|
+
replacements: ReplacementIntent[];
|
|
7
|
+
};
|
|
8
|
+
declare const _default: (sourceFile: ts.SourceFile, bindings: Bindings, checker?: ts.TypeChecker) => ObjectTransformResult;
|
|
4
9
|
export default _default;
|
package/build/compiler/object.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import {
|
|
2
|
+
import { uid } from '@esportsplus/typescript/compiler';
|
|
3
3
|
import { COMPILER_NAMESPACE, COMPILER_TYPES } from '../constants.js';
|
|
4
|
-
import { isReactiveCall } from './index.js';
|
|
5
4
|
function analyzeProperty(prop, sourceFile) {
|
|
6
5
|
if (!ts.isPropertyAssignment(prop)) {
|
|
7
6
|
return null;
|
|
@@ -111,12 +110,11 @@ function isStaticValue(node) {
|
|
|
111
110
|
}
|
|
112
111
|
return false;
|
|
113
112
|
}
|
|
113
|
+
function isReactiveCall(node) {
|
|
114
|
+
return ts.isIdentifier(node.expression) && node.expression.text === "reactive";
|
|
115
|
+
}
|
|
114
116
|
function visit(ctx, node) {
|
|
115
|
-
if (ts.
|
|
116
|
-
ctx.lastImportEnd = node.end;
|
|
117
|
-
}
|
|
118
|
-
if (ts.isCallExpression(node) &&
|
|
119
|
-
isReactiveCall(node, ctx.checker)) {
|
|
117
|
+
if (ts.isCallExpression(node) && isReactiveCall(node)) {
|
|
120
118
|
let arg = node.arguments[0];
|
|
121
119
|
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
122
120
|
let properties = [], props = arg.properties, varName = null;
|
|
@@ -142,9 +140,8 @@ function visit(ctx, node) {
|
|
|
142
140
|
}
|
|
143
141
|
ctx.calls.push({
|
|
144
142
|
className: uid('ReactiveObject'),
|
|
145
|
-
|
|
143
|
+
node,
|
|
146
144
|
properties,
|
|
147
|
-
start: node.pos,
|
|
148
145
|
varName
|
|
149
146
|
});
|
|
150
147
|
}
|
|
@@ -152,34 +149,27 @@ function visit(ctx, node) {
|
|
|
152
149
|
ts.forEachChild(node, n => visit(ctx, n));
|
|
153
150
|
}
|
|
154
151
|
export default (sourceFile, bindings, checker) => {
|
|
155
|
-
let
|
|
152
|
+
let ctx = {
|
|
156
153
|
bindings,
|
|
157
154
|
calls: [],
|
|
158
155
|
checker,
|
|
159
|
-
classCounter: 0,
|
|
160
|
-
lastImportEnd: 0,
|
|
161
156
|
sourceFile
|
|
162
157
|
};
|
|
163
158
|
visit(ctx, sourceFile);
|
|
164
159
|
if (ctx.calls.length === 0) {
|
|
165
|
-
return
|
|
160
|
+
return { prepend: [], replacements: [] };
|
|
166
161
|
}
|
|
167
|
-
let
|
|
168
|
-
replacements.push({
|
|
169
|
-
end: ctx.lastImportEnd,
|
|
170
|
-
newText: code.substring(0, ctx.lastImportEnd) + '\n' + classes + '\n',
|
|
171
|
-
start: 0
|
|
172
|
-
});
|
|
162
|
+
let prepend = [], replacements = [];
|
|
173
163
|
for (let i = 0, n = ctx.calls.length; i < n; i++) {
|
|
174
164
|
let call = ctx.calls[i];
|
|
165
|
+
prepend.push(buildClassCode(call.className, call.properties));
|
|
175
166
|
replacements.push({
|
|
176
|
-
|
|
177
|
-
|
|
167
|
+
node: call.node,
|
|
168
|
+
generate: () => ` new ${call.className}(${call.properties
|
|
178
169
|
.filter(({ isStatic, type }) => !isStatic || type === COMPILER_TYPES.Computed)
|
|
179
170
|
.map(p => p.valueText)
|
|
180
|
-
.join(', ')})
|
|
181
|
-
start: call.start
|
|
171
|
+
.join(', ')})`
|
|
182
172
|
});
|
|
183
173
|
}
|
|
184
|
-
return
|
|
174
|
+
return { prepend, replacements };
|
|
185
175
|
};
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
export default _default;
|
|
1
|
+
import plugin from '../index.js';
|
|
2
|
+
export default plugin;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
export default plugin.tsc({ analyze, transform });
|
|
1
|
+
import plugin from '../index.js';
|
|
2
|
+
export default plugin;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { PACKAGE } from '../../constants.js';
|
|
2
1
|
import { plugin } from '@esportsplus/typescript/compiler';
|
|
3
|
-
import {
|
|
2
|
+
import { PACKAGE } from '../../constants.js';
|
|
3
|
+
import reactivityPlugin from '../index.js';
|
|
4
4
|
export default plugin.vite({
|
|
5
|
-
analyze,
|
|
6
5
|
name: PACKAGE,
|
|
7
|
-
|
|
6
|
+
plugins: [reactivityPlugin]
|
|
8
7
|
});
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"@esportsplus/utilities": "^0.27.2"
|
|
5
5
|
},
|
|
6
6
|
"devDependencies": {
|
|
7
|
-
"@esportsplus/typescript": "^0.
|
|
7
|
+
"@esportsplus/typescript": "^0.25.0",
|
|
8
8
|
"@types/node": "^25.0.3",
|
|
9
9
|
"vite": "^7.3.1"
|
|
10
10
|
},
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"type": "module",
|
|
33
33
|
"types": "build/index.d.ts",
|
|
34
|
-
"version": "0.
|
|
34
|
+
"version": "0.28.1",
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "tsc",
|
|
37
37
|
"build:test": "pnpm build && vite build --config test/vite.config.ts",
|
package/src/compiler/array.ts
CHANGED
|
@@ -1,34 +1,66 @@
|
|
|
1
|
+
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
1
2
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import { ast
|
|
3
|
+
import { ast } from '@esportsplus/typescript/compiler';
|
|
3
4
|
import { COMPILER_NAMESPACE, COMPILER_TYPES } from '~/constants';
|
|
4
5
|
import type { Bindings } from '~/types';
|
|
5
|
-
import { isReactiveCall } from '.';
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
interface
|
|
8
|
+
interface VisitContext {
|
|
9
9
|
bindings: Bindings;
|
|
10
10
|
checker?: ts.TypeChecker;
|
|
11
|
-
replacements:
|
|
11
|
+
replacements: ReplacementIntent[];
|
|
12
12
|
sourceFile: ts.SourceFile;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
function
|
|
17
|
-
if (ts.
|
|
16
|
+
function getElementTypeText(typeNode: ts.TypeNode, sourceFile: ts.SourceFile): string | null {
|
|
17
|
+
if (ts.isArrayTypeNode(typeNode)) {
|
|
18
|
+
return typeNode.elementType.getText(sourceFile);
|
|
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 isReactiveCall(node: ts.CallExpression): boolean {
|
|
35
|
+
return ts.isIdentifier(node.expression) && node.expression.text === 'reactive';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function visit(ctx: VisitContext, node: ts.Node): void {
|
|
39
|
+
if (ts.isCallExpression(node) && isReactiveCall(node) && node.arguments.length > 0) {
|
|
18
40
|
let arg = node.arguments[0],
|
|
19
41
|
expression = ts.isAsExpression(arg) ? arg.expression : arg;
|
|
20
42
|
|
|
21
43
|
if (ts.isArrayLiteralExpression(expression)) {
|
|
44
|
+
let elementType: string | null = null;
|
|
45
|
+
|
|
46
|
+
if (ts.isAsExpression(arg) && arg.type) {
|
|
47
|
+
elementType = getElementTypeText(arg.type, ctx.sourceFile);
|
|
48
|
+
}
|
|
49
|
+
else if (node.parent && ts.isVariableDeclaration(node.parent) && node.parent.type) {
|
|
50
|
+
elementType = getElementTypeText(node.parent.type, ctx.sourceFile);
|
|
51
|
+
}
|
|
52
|
+
|
|
22
53
|
if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
23
54
|
ctx.bindings.set(node.parent.name.text, COMPILER_TYPES.Array);
|
|
24
55
|
}
|
|
25
56
|
|
|
57
|
+
let typeParam = elementType ? `<${elementType}>` : '';
|
|
58
|
+
|
|
26
59
|
ctx.replacements.push({
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
? ` new ${COMPILER_NAMESPACE}.ReactiveArray(...${expression.getText(
|
|
30
|
-
: ` new ${COMPILER_NAMESPACE}.ReactiveArray()
|
|
31
|
-
start: node.pos
|
|
60
|
+
node,
|
|
61
|
+
generate: (sf) => expression.elements.length > 0
|
|
62
|
+
? ` new ${COMPILER_NAMESPACE}.ReactiveArray${typeParam}(...${expression.getText(sf)})`
|
|
63
|
+
: ` new ${COMPILER_NAMESPACE}.ReactiveArray${typeParam}()`
|
|
32
64
|
});
|
|
33
65
|
}
|
|
34
66
|
}
|
|
@@ -77,9 +109,8 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
77
109
|
|
|
78
110
|
if (name && ctx.bindings.get(name) === COMPILER_TYPES.Array) {
|
|
79
111
|
ctx.replacements.push({
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
start: node.pos
|
|
112
|
+
node,
|
|
113
|
+
generate: (sf) => `${node.expression.getText(sf)}.$length()`
|
|
83
114
|
});
|
|
84
115
|
}
|
|
85
116
|
}
|
|
@@ -93,13 +124,14 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
93
124
|
name = ast.getExpressionName(element.expression);
|
|
94
125
|
|
|
95
126
|
if (name && ctx.bindings.get(name) === COMPILER_TYPES.Array) {
|
|
96
|
-
let index = element.argumentExpression.getText(ctx.sourceFile),
|
|
97
|
-
value = node.right.getText(ctx.sourceFile);
|
|
98
|
-
|
|
99
127
|
ctx.replacements.push({
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
128
|
+
node,
|
|
129
|
+
generate: (sf) => {
|
|
130
|
+
let index = element.argumentExpression.getText(sf),
|
|
131
|
+
value = node.right.getText(sf);
|
|
132
|
+
|
|
133
|
+
return `${element.expression.getText(sf)}.$set(${index}, ${value})`;
|
|
134
|
+
}
|
|
103
135
|
});
|
|
104
136
|
}
|
|
105
137
|
}
|
|
@@ -108,9 +140,8 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
108
140
|
}
|
|
109
141
|
|
|
110
142
|
|
|
111
|
-
export default (sourceFile: ts.SourceFile, bindings: Bindings, checker?: ts.TypeChecker):
|
|
112
|
-
let
|
|
113
|
-
ctx: TransformContext = {
|
|
143
|
+
export default (sourceFile: ts.SourceFile, bindings: Bindings, checker?: ts.TypeChecker): ReplacementIntent[] => {
|
|
144
|
+
let ctx: VisitContext = {
|
|
114
145
|
bindings,
|
|
115
146
|
checker,
|
|
116
147
|
replacements: [],
|
|
@@ -119,9 +150,5 @@ export default (sourceFile: ts.SourceFile, bindings: Bindings, checker?: ts.Type
|
|
|
119
150
|
|
|
120
151
|
visit(ctx, sourceFile);
|
|
121
152
|
|
|
122
|
-
|
|
123
|
-
return code;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return c.replace(code, ctx.replacements);
|
|
153
|
+
return ctx.replacements;
|
|
127
154
|
};
|
package/src/compiler/index.ts
CHANGED
|
@@ -1,27 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ImportIntent, Plugin, ReplacementIntent, TransformContext } from '@esportsplus/typescript/compiler';
|
|
2
2
|
import { ts } from '@esportsplus/typescript';
|
|
3
3
|
import { ast, imports } from '@esportsplus/typescript/compiler';
|
|
4
4
|
import { COMPILER_ENTRYPOINT, COMPILER_NAMESPACE, PACKAGE } from '~/constants';
|
|
5
|
-
import type { Bindings
|
|
5
|
+
import type { Bindings } from '~/types';
|
|
6
6
|
import array from './array';
|
|
7
7
|
import object from './object';
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
type AnalyzedFile = {
|
|
11
|
-
hasReactiveImport: boolean;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const CONTEXT_KEY = 'reactivity:analyzed';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
let transforms = [object, array];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
function getAnalyzedFile(context: PluginContext | undefined, filename: string): AnalyzedFile | undefined {
|
|
22
|
-
return (context?.get(CONTEXT_KEY) as Map<string, AnalyzedFile> | undefined)?.get(filename);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
10
|
function hasReactiveImport(sourceFile: ts.SourceFile): boolean {
|
|
26
11
|
return imports.find(sourceFile, PACKAGE).some(i => i.specifiers.has(COMPILER_ENTRYPOINT));
|
|
27
12
|
}
|
|
@@ -31,76 +16,54 @@ function isReactiveCallNode(node: ts.Node): boolean {
|
|
|
31
16
|
}
|
|
32
17
|
|
|
33
18
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
let files = context.get(CONTEXT_KEY) as Map<string, AnalyzedFile> | undefined;
|
|
19
|
+
const plugin: Plugin = {
|
|
20
|
+
patterns: ['reactive(', 'reactive<'],
|
|
40
21
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
22
|
+
transform: (ctx: TransformContext) => {
|
|
23
|
+
if (!hasReactiveImport(ctx.sourceFile)) {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
45
26
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
27
|
+
let bindings: Bindings = new Map(),
|
|
28
|
+
importsIntent: ImportIntent[] = [],
|
|
29
|
+
prepend: string[] = [],
|
|
30
|
+
replacements: ReplacementIntent[] = [];
|
|
50
31
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
32
|
+
// Run object transform
|
|
33
|
+
let objectResult = object(ctx.sourceFile, bindings, ctx.checker);
|
|
55
34
|
|
|
56
|
-
|
|
57
|
-
|
|
35
|
+
prepend.push(...objectResult.prepend);
|
|
36
|
+
replacements.push(...objectResult.replacements);
|
|
58
37
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
changed = false,
|
|
62
|
-
checker = program.getTypeChecker(),
|
|
63
|
-
code = sourceFile.getFullText(),
|
|
64
|
-
current = sourceFile,
|
|
65
|
-
filename = sourceFile.fileName,
|
|
66
|
-
result: string;
|
|
67
|
-
|
|
68
|
-
// Try to get pre-analyzed data from context
|
|
69
|
-
let analyzed = getAnalyzedFile(context, filename);
|
|
70
|
-
|
|
71
|
-
// Fall back to inline check (for Vite or when context unavailable)
|
|
72
|
-
if (!analyzed) {
|
|
73
|
-
if (!hasReactiveImport(sourceFile)) {
|
|
74
|
-
return { changed: false, code, sourceFile };
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
else if (!analyzed.hasReactiveImport) {
|
|
78
|
-
return { changed: false, code, sourceFile };
|
|
79
|
-
}
|
|
38
|
+
// Run array transform
|
|
39
|
+
let arrayResult = array(ctx.sourceFile, bindings, ctx.checker);
|
|
80
40
|
|
|
81
|
-
|
|
82
|
-
result = transforms[i](current, bindings, checker);
|
|
41
|
+
replacements.push(...arrayResult);
|
|
83
42
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
43
|
+
// Build import intent
|
|
44
|
+
if (replacements.length > 0 || prepend.length > 0) {
|
|
45
|
+
let remove: string[] = [];
|
|
90
46
|
|
|
91
|
-
|
|
92
|
-
|
|
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
|
+
}
|
|
93
52
|
|
|
94
|
-
|
|
95
|
-
|
|
53
|
+
importsIntent.push({
|
|
54
|
+
namespace: COMPILER_NAMESPACE,
|
|
55
|
+
package: PACKAGE,
|
|
56
|
+
remove
|
|
57
|
+
});
|
|
96
58
|
}
|
|
97
59
|
|
|
98
|
-
|
|
99
|
-
|
|
60
|
+
return {
|
|
61
|
+
imports: importsIntent,
|
|
62
|
+
prepend,
|
|
63
|
+
replacements
|
|
64
|
+
};
|
|
100
65
|
}
|
|
101
|
-
|
|
102
|
-
return { changed, code, sourceFile };
|
|
103
66
|
};
|
|
104
67
|
|
|
105
68
|
|
|
106
|
-
export
|
|
69
|
+
export default plugin;
|
package/src/compiler/object.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
1
2
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import {
|
|
3
|
+
import { uid } from '@esportsplus/typescript/compiler';
|
|
3
4
|
import { COMPILER_NAMESPACE, COMPILER_TYPES } from '~/constants';
|
|
4
5
|
import type { Bindings } from '~/types';
|
|
5
|
-
import { isReactiveCall } from '.';
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
interface AnalyzedProperty {
|
|
@@ -14,18 +14,15 @@ interface AnalyzedProperty {
|
|
|
14
14
|
|
|
15
15
|
interface ReactiveObjectCall {
|
|
16
16
|
className: string;
|
|
17
|
-
|
|
17
|
+
node: ts.CallExpression;
|
|
18
18
|
properties: AnalyzedProperty[];
|
|
19
|
-
start: number;
|
|
20
19
|
varName: string | null;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
interface
|
|
22
|
+
interface VisitContext {
|
|
24
23
|
bindings: Bindings;
|
|
25
24
|
calls: ReactiveObjectCall[];
|
|
26
25
|
checker?: ts.TypeChecker;
|
|
27
|
-
classCounter: number;
|
|
28
|
-
lastImportEnd: number;
|
|
29
26
|
sourceFile: ts.SourceFile;
|
|
30
27
|
}
|
|
31
28
|
|
|
@@ -169,15 +166,12 @@ function isStaticValue(node: ts.Node): boolean {
|
|
|
169
166
|
return false;
|
|
170
167
|
}
|
|
171
168
|
|
|
172
|
-
function
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
169
|
+
function isReactiveCall(node: ts.CallExpression): boolean {
|
|
170
|
+
return ts.isIdentifier(node.expression) && node.expression.text === "reactive";
|
|
171
|
+
}
|
|
176
172
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
isReactiveCall(node, ctx.checker)
|
|
180
|
-
) {
|
|
173
|
+
function visit(ctx: VisitContext, node: ts.Node): void {
|
|
174
|
+
if (ts.isCallExpression(node) && isReactiveCall(node)) {
|
|
181
175
|
let arg = node.arguments[0];
|
|
182
176
|
|
|
183
177
|
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
@@ -214,9 +208,8 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
214
208
|
|
|
215
209
|
ctx.calls.push({
|
|
216
210
|
className: uid('ReactiveObject'),
|
|
217
|
-
|
|
211
|
+
node,
|
|
218
212
|
properties,
|
|
219
|
-
start: node.pos,
|
|
220
213
|
varName
|
|
221
214
|
});
|
|
222
215
|
}
|
|
@@ -226,46 +219,44 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
226
219
|
}
|
|
227
220
|
|
|
228
221
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
222
|
+
type ObjectTransformResult = {
|
|
223
|
+
prepend: string[];
|
|
224
|
+
replacements: ReplacementIntent[];
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
export default (sourceFile: ts.SourceFile, bindings: Bindings, checker?: ts.TypeChecker): ObjectTransformResult => {
|
|
229
|
+
let ctx: VisitContext = {
|
|
232
230
|
bindings,
|
|
233
231
|
calls: [],
|
|
234
232
|
checker,
|
|
235
|
-
classCounter: 0,
|
|
236
|
-
lastImportEnd: 0,
|
|
237
233
|
sourceFile
|
|
238
234
|
};
|
|
239
235
|
|
|
240
236
|
visit(ctx, sourceFile);
|
|
241
237
|
|
|
242
238
|
if (ctx.calls.length === 0) {
|
|
243
|
-
return
|
|
239
|
+
return { prepend: [], replacements: [] };
|
|
244
240
|
}
|
|
245
241
|
|
|
246
|
-
let
|
|
247
|
-
replacements:
|
|
248
|
-
|
|
249
|
-
replacements.push({
|
|
250
|
-
end: ctx.lastImportEnd,
|
|
251
|
-
newText: code.substring(0, ctx.lastImportEnd) + '\n' + classes + '\n',
|
|
252
|
-
start: 0
|
|
253
|
-
});
|
|
242
|
+
let prepend: string[] = [],
|
|
243
|
+
replacements: ReplacementIntent[] = [];
|
|
254
244
|
|
|
255
245
|
for (let i = 0, n = ctx.calls.length; i < n; i++) {
|
|
256
246
|
let call = ctx.calls[i];
|
|
257
247
|
|
|
248
|
+
prepend.push(buildClassCode(call.className, call.properties));
|
|
249
|
+
|
|
258
250
|
replacements.push({
|
|
259
|
-
|
|
260
|
-
|
|
251
|
+
node: call.node,
|
|
252
|
+
generate: () => ` new ${call.className}(${
|
|
261
253
|
call.properties
|
|
262
254
|
.filter(({ isStatic, type }) => !isStatic || type === COMPILER_TYPES.Computed)
|
|
263
255
|
.map(p => p.valueText)
|
|
264
256
|
.join(', ')
|
|
265
|
-
})
|
|
266
|
-
start: call.start
|
|
257
|
+
})`
|
|
267
258
|
});
|
|
268
259
|
}
|
|
269
260
|
|
|
270
|
-
return
|
|
261
|
+
return { prepend, replacements };
|
|
271
262
|
};
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { PACKAGE } from '../../constants';
|
|
2
1
|
import { plugin } from '@esportsplus/typescript/compiler';
|
|
3
|
-
import {
|
|
2
|
+
import { PACKAGE } from '~/constants';
|
|
3
|
+
import reactivityPlugin from '..';
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
export default plugin.vite({
|
|
7
|
-
analyze,
|
|
8
7
|
name: PACKAGE,
|
|
9
|
-
|
|
10
|
-
});
|
|
8
|
+
plugins: [reactivityPlugin]
|
|
9
|
+
});
|