@esportsplus/reactivity 0.37.0 → 0.37.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,20 +1,7 @@
1
1
  import { ts } from '@esportsplus/typescript';
2
- import { ast } from '@esportsplus/typescript/compiler';
3
2
  import { COMPOUND_OPERATORS, NAMESPACE, TYPES } from './constants.js';
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 visit(ctx, node) {
3
+ import scope from './bindings.js';
4
+ function declare(ctx, node) {
18
5
  if (ctx.isReactiveCall(node) && node.arguments.length > 0) {
19
6
  let arg = node.arguments[0], expression = ts.isAsExpression(arg) ? arg.expression : arg;
20
7
  if (ts.isArrayLiteralExpression(expression)) {
@@ -26,7 +13,7 @@ function visit(ctx, node) {
26
13
  elementType = getElementTypeText(node.parent.type, ctx.sourceFile);
27
14
  }
28
15
  if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
29
- ctx.bindings.set(node.parent.name.text, TYPES.Array);
16
+ scope.declare(ctx.bindings, node.parent.name, TYPES.Array);
30
17
  }
31
18
  let typeParam = elementType ? `<${elementType}>` : '';
32
19
  ctx.replacements.push({
@@ -37,100 +24,107 @@ function visit(ctx, node) {
37
24
  });
38
25
  }
39
26
  }
40
- if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
41
- if (ts.isIdentifier(node.initializer) && ctx.bindings.get(node.initializer.text) === TYPES.Array) {
42
- ctx.bindings.set(node.name.text, TYPES.Array);
43
- }
44
- if (ts.isPropertyAccessExpression(node.initializer)) {
45
- let path = ast.property.path(node.initializer);
46
- if (path && ctx.bindings.get(path) === TYPES.Array) {
47
- ctx.bindings.set(node.name.text, TYPES.Array);
48
- }
49
- }
27
+ if (ts.isVariableDeclaration(node) &&
28
+ ts.isIdentifier(node.name) &&
29
+ node.initializer &&
30
+ (ts.isIdentifier(node.initializer) || ts.isPropertyAccessExpression(node.initializer)) &&
31
+ scope.isArray(ctx.bindings, node.initializer)) {
32
+ scope.declare(ctx.bindings, node.name, TYPES.Array);
50
33
  }
51
34
  if ((ts.isFunctionDeclaration(node) || ts.isArrowFunction(node)) && node.parameters) {
52
35
  for (let i = 0, n = node.parameters.length; i < n; i++) {
53
36
  let param = node.parameters[i];
54
- if ((ts.isIdentifier(param.name) && param.type) &&
37
+ if (ts.isIdentifier(param.name) &&
38
+ param.type &&
55
39
  ts.isTypeReferenceNode(param.type) &&
56
40
  ts.isIdentifier(param.type.typeName) &&
57
41
  param.type.typeName.text === 'ReactiveArray') {
58
- ctx.bindings.set(param.name.text, TYPES.Array);
42
+ scope.declare(ctx.bindings, param.name, TYPES.Array);
59
43
  }
60
44
  }
61
45
  }
62
- if (ts.isPropertyAccessExpression(node) && node.name.text === 'length') {
63
- let name = ast.expression.name(node.expression);
64
- if (name && ctx.bindings.get(name) === TYPES.Array) {
65
- let expr = node.expression, parent = node.parent;
66
- if (parent && ts.isBinaryExpression(parent) && parent.left === node && (parent.operatorToken.kind === ts.SyntaxKind.EqualsToken || COMPOUND_OPERATORS.has(parent.operatorToken.kind))) {
67
- let op = COMPOUND_OPERATORS.get(parent.operatorToken.kind);
68
- if (op === undefined) {
69
- ctx.replacements.push({
70
- node: parent,
71
- generate: (sf) => `${expr.getText(sf)}.$length = ${parent.right.getText(sf)}`
72
- });
73
- }
74
- else {
75
- ctx.replacements.push({
76
- node: parent,
77
- generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${op} ${parent.right.getText(sf)}`
78
- });
79
- }
80
- }
81
- else if (parent && ts.isPostfixUnaryExpression(parent)) {
82
- let op = parent.operator === ts.SyntaxKind.PlusPlusToken ? '+' : '-';
83
- ctx.replacements.push({
84
- node: parent,
85
- generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${op} 1`
86
- });
87
- }
88
- else if (parent && ts.isPrefixUnaryExpression(parent)) {
89
- let op = parent.operator === ts.SyntaxKind.PlusPlusToken ? '+' : '-';
46
+ node.forEachChild(child => declare(ctx, child));
47
+ }
48
+ function getElementTypeText(typeNode, sourceFile) {
49
+ if (ts.isArrayTypeNode(typeNode)) {
50
+ return typeNode.elementType.getText(sourceFile);
51
+ }
52
+ if (ts.isTypeReferenceNode(typeNode) &&
53
+ ts.isIdentifier(typeNode.typeName) &&
54
+ typeNode.typeName.text === 'Array' &&
55
+ typeNode.typeArguments &&
56
+ typeNode.typeArguments.length > 0) {
57
+ return typeNode.typeArguments[0].getText(sourceFile);
58
+ }
59
+ return null;
60
+ }
61
+ function rewrite(ctx, node) {
62
+ if (ts.isTypeNode(node)) {
63
+ return;
64
+ }
65
+ if (ts.isPropertyAccessExpression(node) && node.name.text === 'length' && scope.isArray(ctx.bindings, node.expression)) {
66
+ let expr = node.expression, parent = node.parent;
67
+ if (parent && ts.isBinaryExpression(parent) && parent.left === node && (parent.operatorToken.kind === ts.SyntaxKind.EqualsToken || COMPOUND_OPERATORS.has(parent.operatorToken.kind))) {
68
+ let op = COMPOUND_OPERATORS.get(parent.operatorToken.kind);
69
+ if (op === undefined) {
90
70
  ctx.replacements.push({
91
71
  node: parent,
92
- generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${op} 1`
72
+ generate: (sf) => `${expr.getText(sf)}.$length = ${parent.right.getText(sf)}`
93
73
  });
94
74
  }
95
75
  else {
96
76
  ctx.replacements.push({
97
- node,
98
- generate: (sf) => `${expr.getText(sf)}.$length`
77
+ node: parent,
78
+ generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${op} ${parent.right.getText(sf)}`
99
79
  });
100
80
  }
101
81
  }
82
+ else if (parent && (ts.isPostfixUnaryExpression(parent) || ts.isPrefixUnaryExpression(parent))) {
83
+ let op = parent.operator === ts.SyntaxKind.PlusPlusToken ? '+' : '-';
84
+ ctx.replacements.push({
85
+ node: parent,
86
+ generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${op} 1`
87
+ });
88
+ }
89
+ else {
90
+ ctx.replacements.push({
91
+ node,
92
+ generate: (sf) => `${expr.getText(sf)}.$length`
93
+ });
94
+ }
102
95
  }
103
96
  if (ts.isBinaryExpression(node) &&
104
97
  node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
105
- ts.isElementAccessExpression(node.left)) {
106
- let element = node.left, name = ast.expression.name(element.expression);
107
- if (name && ctx.bindings.get(name) === TYPES.Array) {
108
- ctx.replacements.push({
109
- node,
110
- generate: (sf) => `${element.expression.getText(sf)}.$set(
98
+ ts.isElementAccessExpression(node.left) &&
99
+ scope.isArray(ctx.bindings, node.left.expression)) {
100
+ let element = node.left;
101
+ ctx.replacements.push({
102
+ node,
103
+ generate: (sf) => `${element.expression.getText(sf)}.$set(
111
104
  ${element.argumentExpression.getText(sf)},
112
105
  ${node.right.getText(sf)}
113
106
  )`
114
- });
115
- }
107
+ });
116
108
  }
117
109
  if (ts.isBinaryExpression(node) &&
118
110
  node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
119
- ts.isIdentifier(node.left)) {
111
+ ts.isIdentifier(node.left) &&
112
+ scope.isArray(ctx.bindings, node.left)) {
120
113
  let name = node.left.text, right = node.right;
121
114
  while (ts.isAsExpression(right) || ts.isTypeAssertion(right)) {
122
115
  right = right.expression;
123
116
  }
124
- if (ctx.bindings.get(name) === TYPES.Array && ts.isArrayLiteralExpression(right)) {
117
+ if (ts.isArrayLiteralExpression(right)) {
118
+ let elements = right;
125
119
  ctx.replacements.push({
126
120
  node,
127
- generate: (sf) => right.elements.length > 0
128
- ? `${name}.splice(0, ${name}.length, ...${right.getText(sf)})`
121
+ generate: (sf) => elements.elements.length > 0
122
+ ? `${name}.splice(0, ${name}.length, ...${elements.getText(sf)})`
129
123
  : `${name}.splice(0, ${name}.length)`
130
124
  });
131
125
  }
132
126
  }
133
- node.forEachChild(n => visit(ctx, n));
127
+ node.forEachChild(child => rewrite(ctx, child));
134
128
  }
135
129
  export default (sourceFile, bindings, isReactiveCall) => {
136
130
  let ctx = {
@@ -139,6 +133,7 @@ export default (sourceFile, bindings, isReactiveCall) => {
139
133
  replacements: [],
140
134
  sourceFile
141
135
  };
142
- visit(ctx, sourceFile);
136
+ declare(ctx, sourceFile);
137
+ rewrite(ctx, sourceFile);
143
138
  return ctx.replacements;
144
139
  };
@@ -0,0 +1,19 @@
1
+ import { ts } from '@esportsplus/typescript';
2
+ import { TYPES } from './constants.js';
3
+ import type { Bindings } from './types.js';
4
+ declare const create: (checker: ts.Checker) => Bindings;
5
+ declare const declare: (bindings: Bindings, identifier: ts.Identifier, type: TYPES) => void;
6
+ declare const declarePath: (bindings: Bindings, identifier: ts.Identifier, path: string) => void;
7
+ declare const isArray: (bindings: Bindings, expression: ts.Expression) => boolean;
8
+ declare const resolve: (bindings: Bindings, identifier: ts.Identifier) => {
9
+ symbol: ts.Symbol;
10
+ type: TYPES;
11
+ } | null;
12
+ declare const _default: {
13
+ create: typeof create;
14
+ declare: typeof declare;
15
+ declarePath: typeof declarePath;
16
+ isArray: typeof isArray;
17
+ resolve: typeof resolve;
18
+ };
19
+ export default _default;
@@ -0,0 +1,65 @@
1
+ import { ts } from '@esportsplus/typescript';
2
+ import { TYPES } from './constants.js';
3
+ function root(expression) {
4
+ let parts = [];
5
+ while (ts.isPropertyAccessExpression(expression)) {
6
+ parts.push(expression.name.text);
7
+ expression = expression.expression;
8
+ }
9
+ if (!ts.isIdentifier(expression) || parts.length === 0) {
10
+ return null;
11
+ }
12
+ return { path: parts.reverse().join('.'), root: expression };
13
+ }
14
+ function symbolOf(bindings, identifier) {
15
+ let parent = identifier.parent;
16
+ if (parent && ts.isShorthandPropertyAssignment(parent) && parent.name === identifier) {
17
+ return bindings.checker.getShorthandAssignmentValueSymbol(parent);
18
+ }
19
+ return bindings.checker.getSymbolAtLocation(identifier);
20
+ }
21
+ const create = (checker) => ({
22
+ checker,
23
+ names: new Set(),
24
+ paths: new Map(),
25
+ symbols: new Map()
26
+ });
27
+ const declare = (bindings, identifier, type) => {
28
+ let symbol = bindings.checker.getSymbolAtLocation(identifier);
29
+ if (symbol) {
30
+ bindings.names.add(identifier.text);
31
+ bindings.symbols.set(symbol, type);
32
+ }
33
+ };
34
+ const declarePath = (bindings, identifier, path) => {
35
+ let symbol = bindings.checker.getSymbolAtLocation(identifier);
36
+ if (!symbol) {
37
+ return;
38
+ }
39
+ let paths = bindings.paths.get(symbol);
40
+ if (!paths) {
41
+ paths = new Set();
42
+ bindings.paths.set(symbol, paths);
43
+ }
44
+ bindings.names.add(identifier.text);
45
+ paths.add(path);
46
+ };
47
+ const isArray = (bindings, expression) => {
48
+ if (ts.isIdentifier(expression)) {
49
+ return resolve(bindings, expression)?.type === TYPES.Array;
50
+ }
51
+ let access = root(expression);
52
+ if (!access || !bindings.names.has(access.root.text)) {
53
+ return false;
54
+ }
55
+ let symbol = symbolOf(bindings, access.root);
56
+ return symbol !== undefined && bindings.paths.get(symbol)?.has(access.path) === true;
57
+ };
58
+ const resolve = (bindings, identifier) => {
59
+ if (!bindings.names.has(identifier.text)) {
60
+ return null;
61
+ }
62
+ let symbol = symbolOf(bindings, identifier), type = symbol && bindings.symbols.get(symbol);
63
+ return symbol && type !== undefined ? { symbol, type } : null;
64
+ };
65
+ export default { create, declare, declarePath, isArray, resolve };
@@ -4,6 +4,13 @@ import { ENTRYPOINT, NAMESPACE, PACKAGE_NAME } from './constants.js';
4
4
  import array from './array.js';
5
5
  import object from './object.js';
6
6
  import primitives from './primitives.js';
7
+ import scope from './bindings.js';
8
+ function collect(checker, node, calls) {
9
+ if (isReactiveCallExpression(checker, node)) {
10
+ calls.add(node);
11
+ }
12
+ node.forEachChild(child => collect(checker, child, calls));
13
+ }
7
14
  function isReactiveCallExpression(checker, node) {
8
15
  if (!ts.isCallExpression(node)) {
9
16
  return false;
@@ -12,8 +19,8 @@ function isReactiveCallExpression(checker, node) {
12
19
  if (ts.isIdentifier(expr)) {
13
20
  return imports.includes(checker, expr, PACKAGE_NAME, ENTRYPOINT);
14
21
  }
15
- if (ts.isPropertyAccessExpression(expr) && expr.name.text === ENTRYPOINT) {
16
- return imports.includes(checker, expr, PACKAGE_NAME);
22
+ if (ts.isPropertyAccessExpression(expr) && expr.name.text === ENTRYPOINT && ts.isIdentifier(expr.expression)) {
23
+ return imports.includes(checker, expr.expression, PACKAGE_NAME);
17
24
  }
18
25
  return false;
19
26
  }
@@ -24,15 +31,17 @@ export default {
24
31
  if (!checker) {
25
32
  return {};
26
33
  }
27
- let bindings = new Map(), intents = {
34
+ let bindings = scope.create(checker), candidates = new Set(), intents = {
28
35
  imports: [],
29
36
  prepend: [],
30
37
  replacements: []
31
- }, isReactiveCall = (node) => isReactiveCallExpression(checker, node), sourceFile = ctx.sourceFile;
32
- let { calls, replacements } = primitives(sourceFile, bindings, isReactiveCall);
33
- if (calls.length === 0) {
38
+ }, sourceFile = ctx.sourceFile;
39
+ collect(checker, sourceFile, candidates);
40
+ if (candidates.size === 0) {
34
41
  return {};
35
42
  }
43
+ let isReactiveCall = (node) => candidates.has(node);
44
+ let { calls, replacements } = primitives(sourceFile, bindings, isReactiveCall);
36
45
  intents.replacements.push(...replacements);
37
46
  let objects = object(sourceFile, bindings, isReactiveCall);
38
47
  intents.prepend.push(...objects.prepend);
@@ -1,6 +1,7 @@
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
+ import scope from './bindings.js';
4
5
  function analyzeProperty(prop, sourceFile) {
5
6
  if (!ts.isPropertyAssignment(prop)) {
6
7
  return null;
@@ -143,10 +144,9 @@ function visit(ctx, node) {
143
144
  if (ctx.isReactiveCall(node)) {
144
145
  let arg = node.arguments[0];
145
146
  if (arg && ts.isObjectLiteralExpression(arg)) {
146
- let properties = [], props = arg.properties, varname = null;
147
- if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
148
- varname = node.parent.name.text;
149
- }
147
+ let properties = [], props = arg.properties, target = node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)
148
+ ? node.parent.name
149
+ : null, varname = target?.text ?? null;
150
150
  for (let i = 0, n = props.length; i < n; i++) {
151
151
  let prop = props[i];
152
152
  if (ts.isSpreadAssignment(prop)) {
@@ -159,8 +159,12 @@ function visit(ctx, node) {
159
159
  return;
160
160
  }
161
161
  properties.push(analyzed);
162
- if (analyzed.type === TYPES.Array && varname) {
163
- ctx.bindings.set(`${varname}.${analyzed.key}`, TYPES.Array);
162
+ }
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
+ }
164
168
  }
165
169
  }
166
170
  ctx.calls.push({