@esportsplus/reactivity 0.26.1 → 0.27.2

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.
@@ -1,370 +0,0 @@
1
- import { ts } from '@esportsplus/typescript';
2
- import { ast, code as c, type Range, type Replacement } from '@esportsplus/typescript/compiler';
3
- import type { Bindings } from '~/types';
4
- import { COMPILER_ENTRYPOINT, COMPILER_TYPES, PACKAGE } from '~/constants';
5
-
6
-
7
- interface ArgContext {
8
- argStart: number;
9
- innerReplacements: Replacement[];
10
- ns: string;
11
- scopedBindings: ScopeBinding[];
12
- sourceFile: ts.SourceFile;
13
- }
14
-
15
- interface ScopeBinding {
16
- name: string;
17
- scope: ts.Node;
18
- type: COMPILER_TYPES;
19
- }
20
-
21
- interface TransformContext {
22
- bindings: Bindings;
23
- computedArgRanges: Range[];
24
- hasReactiveImport: boolean;
25
- ns: string;
26
- replacements: Replacement[];
27
- scopedBindings: ScopeBinding[];
28
- sourceFile: ts.SourceFile;
29
- tmpCounter: number;
30
- }
31
-
32
-
33
- const COMPOUND_OPERATORS = new Map<ts.SyntaxKind, string>([
34
- [ts.SyntaxKind.AmpersandAmpersandEqualsToken, '&&'],
35
- [ts.SyntaxKind.AmpersandEqualsToken, '&'],
36
- [ts.SyntaxKind.AsteriskAsteriskEqualsToken, '**'],
37
- [ts.SyntaxKind.AsteriskEqualsToken, '*'],
38
- [ts.SyntaxKind.BarBarEqualsToken, '||'],
39
- [ts.SyntaxKind.BarEqualsToken, '|'],
40
- [ts.SyntaxKind.CaretEqualsToken, '^'],
41
- [ts.SyntaxKind.GreaterThanGreaterThanEqualsToken, '>>'],
42
- [ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, '>>>'],
43
- [ts.SyntaxKind.LessThanLessThanEqualsToken, '<<'],
44
- [ts.SyntaxKind.MinusEqualsToken, '-'],
45
- [ts.SyntaxKind.PercentEqualsToken, '%'],
46
- [ts.SyntaxKind.PlusEqualsToken, '+'],
47
- [ts.SyntaxKind.QuestionQuestionEqualsToken, '??'],
48
- [ts.SyntaxKind.SlashEqualsToken, '/']
49
- ]);
50
-
51
-
52
- function findBinding(bindings: ScopeBinding[], name: string, node: ts.Node): ScopeBinding | undefined {
53
- for (let i = 0, n = bindings.length; i < n; i++) {
54
- let b = bindings[i];
55
-
56
- if (b.name === name && isInScope(node, b)) {
57
- return b;
58
- }
59
- }
60
-
61
- return undefined;
62
- }
63
-
64
- function findEnclosingScope(node: ts.Node): ts.Node {
65
- let current = node.parent;
66
-
67
- while (current) {
68
- if (
69
- ts.isBlock(current) ||
70
- ts.isSourceFile(current) ||
71
- ts.isFunctionDeclaration(current) ||
72
- ts.isFunctionExpression(current) ||
73
- ts.isArrowFunction(current) ||
74
- ts.isForStatement(current) ||
75
- ts.isForInStatement(current) ||
76
- ts.isForOfStatement(current)
77
- ) {
78
- return current;
79
- }
80
-
81
- current = current.parent;
82
- }
83
-
84
- return node.getSourceFile();
85
- }
86
-
87
- function isInDeclarationInit(node: ts.Node): boolean {
88
- let parent = node.parent;
89
-
90
- return ts.isVariableDeclaration(parent) && parent.initializer === node;
91
- }
92
-
93
- function isInScope(reference: ts.Node, binding: ScopeBinding): boolean {
94
- let current: ts.Node | undefined = reference;
95
-
96
- while (current) {
97
- if (current === binding.scope) {
98
- return true;
99
- }
100
-
101
- current = current.parent;
102
- }
103
-
104
- return false;
105
- }
106
-
107
- function isReactiveReassignment(node: ts.Node): boolean {
108
- let parent = node.parent;
109
-
110
- if (
111
- ts.isBinaryExpression(parent) &&
112
- parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
113
- parent.right === node &&
114
- ts.isCallExpression(node) &&
115
- ts.isIdentifier((node as ts.CallExpression).expression) &&
116
- ((node as ts.CallExpression).expression as ts.Identifier).text === COMPILER_ENTRYPOINT
117
- ) {
118
- return true;
119
- }
120
-
121
- return false;
122
- }
123
-
124
- function isWriteContext(node: ts.Identifier): 'simple' | 'compound' | 'increment' | false {
125
- let parent = node.parent;
126
-
127
- if (ts.isBinaryExpression(parent) && parent.left === node) {
128
- let op = parent.operatorToken.kind;
129
-
130
- if (op === ts.SyntaxKind.EqualsToken) {
131
- return 'simple';
132
- }
133
-
134
- if (COMPOUND_OPERATORS.has(op)) {
135
- return 'compound';
136
- }
137
- }
138
-
139
- if (ts.isPostfixUnaryExpression(parent) || ts.isPrefixUnaryExpression(parent)) {
140
- let op = parent.operator;
141
-
142
- if (op === ts.SyntaxKind.PlusPlusToken || op === ts.SyntaxKind.MinusMinusToken) {
143
- return 'increment';
144
- }
145
- }
146
-
147
- return false;
148
- }
149
-
150
- function visit(ctx: TransformContext, node: ts.Node): void {
151
- if (
152
- ts.isImportDeclaration(node) &&
153
- ts.isStringLiteral(node.moduleSpecifier) &&
154
- node.moduleSpecifier.text.includes(PACKAGE)
155
- ) {
156
- let clause = node.importClause;
157
-
158
- if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
159
- for (let i = 0, n = clause.namedBindings.elements.length; i < n; i++) {
160
- if (clause.namedBindings.elements[i].name.text === COMPILER_ENTRYPOINT) {
161
- ctx.hasReactiveImport = true;
162
- break;
163
- }
164
- }
165
- }
166
- }
167
-
168
- if (
169
- ctx.hasReactiveImport &&
170
- ts.isCallExpression(node) &&
171
- ts.isIdentifier(node.expression) &&
172
- node.expression.text === COMPILER_ENTRYPOINT &&
173
- node.arguments.length > 0
174
- ) {
175
- let arg = node.arguments[0],
176
- classification: COMPILER_TYPES | null = COMPILER_TYPES.Signal;
177
-
178
- if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
179
- classification = COMPILER_TYPES.Computed;
180
- }
181
- else if (ts.isObjectLiteralExpression(arg) || ts.isArrayLiteralExpression(arg)) {
182
- classification = null;
183
- }
184
-
185
- if (classification) {
186
- let varName: string | null = null;
187
-
188
- if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
189
- varName = node.parent.name.text;
190
- }
191
- else if (
192
- node.parent &&
193
- ts.isBinaryExpression(node.parent) &&
194
- node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
195
- ts.isIdentifier(node.parent.left)
196
- ) {
197
- varName = node.parent.left.text;
198
- }
199
-
200
- if (varName) {
201
- let scope = findEnclosingScope(node);
202
-
203
- ctx.scopedBindings.push({ name: varName, scope, type: classification });
204
- ctx.bindings.set(varName, classification);
205
- }
206
-
207
- if (classification === COMPILER_TYPES.Computed) {
208
- let argStart = arg.getStart(ctx.sourceFile);
209
-
210
- ctx.computedArgRanges.push({ end: arg.end, start: argStart });
211
-
212
- let argCtx: ArgContext = {
213
- argStart,
214
- innerReplacements: [],
215
- ns: ctx.ns,
216
- scopedBindings: ctx.scopedBindings,
217
- sourceFile: ctx.sourceFile
218
- };
219
-
220
- visitArg(argCtx, arg);
221
-
222
- let argText = c.replace(arg.getText(ctx.sourceFile), argCtx.innerReplacements);
223
-
224
- ctx.replacements.push({
225
- end: node.end,
226
- newText: `${ctx.ns}.computed(${argText})`,
227
- start: node.pos
228
- });
229
- }
230
- else {
231
- ctx.replacements.push({
232
- end: node.end,
233
- newText: `${ctx.ns}.signal(${arg.getText(ctx.sourceFile)})`,
234
- start: node.pos
235
- });
236
- }
237
- }
238
- }
239
-
240
- if (ts.isIdentifier(node) && node.parent && !isInDeclarationInit(node.parent)) {
241
- if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
242
- ts.forEachChild(node, n => visit(ctx, n));
243
- return;
244
- }
245
-
246
- let nodeStart = node.getStart(ctx.sourceFile);
247
-
248
- if (ast.inRange(ctx.computedArgRanges, nodeStart, node.end)) {
249
- ts.forEachChild(node, n => visit(ctx, n));
250
- return;
251
- }
252
-
253
- let binding = findBinding(ctx.scopedBindings, node.text, node),
254
- name = node.text;
255
-
256
- if (binding && node.parent) {
257
- if (
258
- !isReactiveReassignment(node.parent) &&
259
- !(ts.isTypeOfExpression(node.parent) && node.parent.expression === node)
260
- ) {
261
- let writeCtx = isWriteContext(node);
262
-
263
- if (writeCtx) {
264
- if (binding.type !== COMPILER_TYPES.Computed) {
265
- let parent = node.parent;
266
-
267
- if (writeCtx === 'simple' && ts.isBinaryExpression(parent)) {
268
- ctx.replacements.push({
269
- end: parent.end,
270
- newText: `${ctx.ns}.write(${name}, ${parent.right.getText(ctx.sourceFile)})`,
271
- start: parent.pos
272
- });
273
- }
274
- else if (writeCtx === 'compound' && ts.isBinaryExpression(parent)) {
275
- let op = COMPOUND_OPERATORS.get(parent.operatorToken.kind) ?? '+';
276
-
277
- ctx.replacements.push({
278
- end: parent.end,
279
- newText: `${ctx.ns}.write(${name}, ${name}.value ${op} ${parent.right.getText(ctx.sourceFile)})`,
280
- start: parent.pos
281
- });
282
- }
283
- else if (writeCtx === 'increment') {
284
- let delta = (parent as ts.PrefixUnaryExpression | ts.PostfixUnaryExpression).operator === ts.SyntaxKind.PlusPlusToken ? '+ 1' : '- 1',
285
- isPrefix = ts.isPrefixUnaryExpression(parent);
286
-
287
- if (ts.isExpressionStatement(parent.parent)) {
288
- ctx.replacements.push({
289
- end: parent.end,
290
- newText: `${ctx.ns}.write(${name}, ${name}.value ${delta})`,
291
- start: parent.pos
292
- });
293
- }
294
- else if (isPrefix) {
295
- ctx.replacements.push({
296
- end: parent.end,
297
- newText: `(${ctx.ns}.write(${name}, ${name}.value ${delta}), ${name}.value)`,
298
- start: parent.pos
299
- });
300
- }
301
- else {
302
- let tmp = `_t${ctx.tmpCounter++}`;
303
-
304
- ctx.replacements.push({
305
- end: parent.end,
306
- newText: `((${tmp}) => (${ctx.ns}.write(${name}, ${tmp} ${delta}), ${tmp}))(${name}.value)`,
307
- start: parent.pos
308
- });
309
- }
310
- }
311
- }
312
- }
313
- else {
314
- ctx.replacements.push({
315
- end: node.end,
316
- newText: `${ctx.ns}.read(${name})`,
317
- start: node.pos
318
- });
319
- }
320
- }
321
- }
322
- }
323
-
324
- ts.forEachChild(node, n => visit(ctx, n));
325
- }
326
-
327
- function visitArg(ctx: ArgContext, node: ts.Node): void {
328
- if (ts.isIdentifier(node) && node.parent) {
329
- if (
330
- (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) ||
331
- (ts.isCallExpression(node.parent) && node.parent.expression === node)
332
- ) {
333
- ts.forEachChild(node, n => visitArg(ctx, n));
334
- return;
335
- }
336
-
337
- if (findBinding(ctx.scopedBindings, node.text, node)) {
338
- ctx.innerReplacements.push({
339
- end: node.end - ctx.argStart,
340
- newText: `${ctx.ns}.read(${node.text})`,
341
- start: node.getStart(ctx.sourceFile) - ctx.argStart
342
- });
343
- }
344
- }
345
-
346
- ts.forEachChild(node, n => visitArg(ctx, n));
347
- }
348
-
349
-
350
- export default (sourceFile: ts.SourceFile, bindings: Bindings, ns: string): string => {
351
- let code = sourceFile.getFullText(),
352
- ctx: TransformContext = {
353
- bindings,
354
- computedArgRanges: [],
355
- hasReactiveImport: false,
356
- ns,
357
- replacements: [],
358
- scopedBindings: [],
359
- sourceFile,
360
- tmpCounter: 0
361
- };
362
-
363
- visit(ctx, sourceFile);
364
-
365
- if (ctx.replacements.length === 0) {
366
- return code;
367
- }
368
-
369
- return c.replace(code, ctx.replacements);
370
- };