@esportsplus/reactivity 0.37.0 → 0.37.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.
@@ -1,40 +1,7 @@
1
1
  import { ts } from '@esportsplus/typescript';
2
2
  import { COMPOUND_OPERATORS, NAMESPACE, TYPES } from './constants.js';
3
- function inScope(reference, binding) {
4
- let current = reference;
5
- while (current) {
6
- if (current === binding.scope) {
7
- return true;
8
- }
9
- current = current.parent;
10
- }
11
- return false;
12
- }
13
- function isScope(node) {
14
- return ts.isArrowFunction(node) ||
15
- ts.isBlock(node) ||
16
- ts.isCatchClause(node) ||
17
- ts.isForInStatement(node) ||
18
- ts.isForOfStatement(node) ||
19
- ts.isForStatement(node) ||
20
- ts.isFunctionDeclaration(node) ||
21
- ts.isFunctionExpression(node) ||
22
- ts.isSourceFile(node);
23
- }
24
- function scopeOf(node) {
25
- let current = node.parent, depth = 0, scope = node.getSourceFile();
26
- while (current) {
27
- if (isScope(current)) {
28
- if (scope === node.getSourceFile() && !ts.isSourceFile(current)) {
29
- scope = current;
30
- }
31
- depth++;
32
- }
33
- current = current.parent;
34
- }
35
- return { depth, scope };
36
- }
37
- function visit(ctx, node) {
3
+ import scope from './bindings.js';
4
+ function declare(ctx, node) {
38
5
  if (ctx.isReactiveCall(node)) {
39
6
  let call = node;
40
7
  ctx.calls.push(call);
@@ -52,32 +19,20 @@ function visit(ctx, node) {
52
19
  classification = null;
53
20
  }
54
21
  else if (ts.isCallExpression(unwrapped) || ts.isIdentifier(unwrapped)) {
22
+ classification = null;
55
23
  ctx.replacements.push({
56
24
  generate: () => `${NAMESPACE}.reactive`,
57
25
  node: call.expression
58
26
  });
59
- node.forEachChild(n => visit(ctx, n));
60
- return;
61
27
  }
62
28
  }
63
- if (classification) {
64
- let varname = null;
65
- if (call.parent && ts.isVariableDeclaration(call.parent) && ts.isIdentifier(call.parent.name)) {
66
- varname = call.parent.name.text;
67
- }
68
- else if (call.parent &&
69
- ts.isBinaryExpression(call.parent) &&
70
- call.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
71
- ts.isIdentifier(call.parent.left)) {
72
- varname = call.parent.left.text;
73
- }
74
- if (varname) {
75
- let { depth, scope } = scopeOf(call);
76
- ctx.bindings.set(varname, classification);
77
- ctx.scopedBindings.push({ depth, name: varname, scope, type: classification });
29
+ if (classification !== null) {
30
+ let target = targetOf(call), type = classification;
31
+ if (target) {
32
+ scope.declare(ctx.bindings, target, type);
78
33
  }
79
34
  ctx.replacements.push({
80
- generate: () => classification === TYPES.Computed
35
+ generate: () => type === TYPES.Computed
81
36
  ? `${NAMESPACE}.computed`
82
37
  : `${NAMESPACE}.signal`,
83
38
  node: call.expression
@@ -85,92 +40,137 @@ function visit(ctx, node) {
85
40
  }
86
41
  }
87
42
  }
88
- if (ts.isIdentifier(node) &&
89
- node.parent &&
90
- !(ts.isVariableDeclaration(node.parent) && node.parent.name === node)) {
91
- if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
92
- node.forEachChild(n => visit(ctx, n));
93
- return;
43
+ node.forEachChild(child => declare(ctx, child));
44
+ }
45
+ function isName(node, parent) {
46
+ if (ts.isShorthandPropertyAssignment(parent)) {
47
+ return false;
48
+ }
49
+ let slots = parent;
50
+ return slots.name === node || slots.propertyName === node || slots.label === node;
51
+ }
52
+ function isPatternTarget(node) {
53
+ let current = node;
54
+ while (current.parent &&
55
+ (ts.isShorthandPropertyAssignment(current.parent) ||
56
+ ts.isPropertyAssignment(current.parent) ||
57
+ ts.isSpreadAssignment(current.parent) ||
58
+ ts.isSpreadElement(current.parent) ||
59
+ ts.isObjectLiteralExpression(current.parent) ||
60
+ ts.isArrayLiteralExpression(current.parent))) {
61
+ current = current.parent;
62
+ }
63
+ let parent = current.parent;
64
+ if (current === node || !parent) {
65
+ return false;
66
+ }
67
+ return (ts.isBinaryExpression(parent) && parent.operatorToken.kind === ts.SyntaxKind.EqualsToken && parent.left === current) ||
68
+ ((ts.isForOfStatement(parent) || ts.isForInStatement(parent)) && parent.initializer === current);
69
+ }
70
+ function reference(ctx, node) {
71
+ let parent = node.parent;
72
+ if (!parent || ts.isExportSpecifier(parent) || ts.isImportSpecifier(parent) || isName(node, parent)) {
73
+ return;
74
+ }
75
+ let binding = scope.resolve(ctx.bindings, node);
76
+ if (!binding || binding.type === TYPES.Array) {
77
+ return;
78
+ }
79
+ if ((ts.isBinaryExpression(parent) && parent.operatorToken.kind === ts.SyntaxKind.EqualsToken && parent.left === node && ctx.isReactiveCall(parent.right)) ||
80
+ (ts.isTypeOfExpression(parent) && parent.expression === node) ||
81
+ isPatternTarget(node)) {
82
+ return;
83
+ }
84
+ let name = node.text;
85
+ if (ts.isShorthandPropertyAssignment(parent)) {
86
+ ctx.replacements.push({
87
+ generate: () => `${name}: ${NAMESPACE}.read(${name})`,
88
+ node: parent
89
+ });
90
+ return;
91
+ }
92
+ let writeCtx;
93
+ if (ts.isBinaryExpression(parent) && parent.left === node) {
94
+ let op = parent.operatorToken.kind;
95
+ if (op === ts.SyntaxKind.EqualsToken) {
96
+ writeCtx = 'simple';
94
97
  }
95
- let bindings = ctx.scopedBindings, binding, name = node.text;
96
- for (let i = 0, n = bindings.length; i < n; i++) {
97
- let b = bindings[i];
98
- if (b.name === name && (!binding || b.depth >= binding.depth) && inScope(node, b)) {
99
- binding = b;
100
- }
98
+ else if (COMPOUND_OPERATORS.has(op)) {
99
+ writeCtx = 'compound';
101
100
  }
102
- if (binding && node.parent) {
103
- let parent = node.parent;
104
- if (!(ts.isBinaryExpression(parent) &&
105
- parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
106
- ctx.isReactiveCall(parent.right)) &&
107
- !(ts.isTypeOfExpression(parent) && parent.expression === node)) {
108
- let writeCtx;
109
- if (ts.isBinaryExpression(parent) && parent.left === node) {
110
- let op = parent.operatorToken.kind;
111
- if (op === ts.SyntaxKind.EqualsToken) {
112
- writeCtx = 'simple';
113
- }
114
- else if (COMPOUND_OPERATORS.has(op)) {
115
- writeCtx = 'compound';
116
- }
117
- }
118
- else if (ts.isPostfixUnaryExpression(parent) || ts.isPrefixUnaryExpression(parent)) {
119
- let op = parent.operator;
120
- if (op === ts.SyntaxKind.MinusMinusToken || op === ts.SyntaxKind.PlusPlusToken) {
121
- writeCtx = 'increment';
122
- }
123
- }
124
- if (writeCtx) {
125
- if (binding.type !== TYPES.Computed) {
126
- if (writeCtx === 'simple' && ts.isBinaryExpression(parent)) {
127
- let right = parent.right;
128
- ctx.replacements.push({
129
- generate: (sf) => `${NAMESPACE}.write(${name}, ${right.getText(sf)})`,
130
- node: parent
131
- });
132
- }
133
- else if (writeCtx === 'compound' && ts.isBinaryExpression(parent)) {
134
- let op = COMPOUND_OPERATORS.get(parent.operatorToken.kind) ?? '+', right = parent.right;
135
- ctx.replacements.push({
136
- generate: (sf) => `${NAMESPACE}.write(${name}, ${name}.value ${op} ${right.getText(sf)})`,
137
- node: parent
138
- });
139
- }
140
- else if (writeCtx === 'increment') {
141
- let delta = parent.operator === ts.SyntaxKind.PlusPlusToken ? '+ 1' : '- 1', isPrefix = ts.isPrefixUnaryExpression(parent);
142
- if (ts.isExpressionStatement(parent.parent)) {
143
- ctx.replacements.push({
144
- generate: () => `${NAMESPACE}.write(${name}, ${name}.value ${delta})`,
145
- node: parent
146
- });
147
- }
148
- else if (isPrefix) {
149
- ctx.replacements.push({
150
- generate: () => `(${NAMESPACE}.write(${name}, ${name}.value ${delta}), ${name}.value)`,
151
- node: parent
152
- });
153
- }
154
- else {
155
- let tmp = `_t${ctx.tmpCounter++}`;
156
- ctx.replacements.push({
157
- generate: () => `((${tmp}) => (${NAMESPACE}.write(${name}, ${tmp} ${delta}), ${tmp}))(${name}.value)`,
158
- node: parent
159
- });
160
- }
161
- }
162
- }
163
- }
164
- else {
165
- ctx.replacements.push({
166
- generate: () => `${NAMESPACE}.read(${name})`,
167
- node
168
- });
169
- }
170
- }
101
+ }
102
+ else if (ts.isPostfixUnaryExpression(parent) || ts.isPrefixUnaryExpression(parent)) {
103
+ let op = parent.operator;
104
+ if (op === ts.SyntaxKind.MinusMinusToken || op === ts.SyntaxKind.PlusPlusToken) {
105
+ writeCtx = 'increment';
171
106
  }
172
107
  }
173
- node.forEachChild(n => visit(ctx, n));
108
+ if (!writeCtx) {
109
+ ctx.replacements.push({
110
+ generate: () => `${NAMESPACE}.read(${name})`,
111
+ node
112
+ });
113
+ return;
114
+ }
115
+ if (binding.type === TYPES.Computed) {
116
+ return;
117
+ }
118
+ if (writeCtx === 'simple' && ts.isBinaryExpression(parent)) {
119
+ let right = parent.right;
120
+ ctx.replacements.push({
121
+ generate: (sf) => `${NAMESPACE}.write(${name}, ${right.getText(sf)})`,
122
+ node: parent
123
+ });
124
+ }
125
+ else if (writeCtx === 'compound' && ts.isBinaryExpression(parent)) {
126
+ let op = COMPOUND_OPERATORS.get(parent.operatorToken.kind) ?? '+', right = parent.right;
127
+ ctx.replacements.push({
128
+ generate: (sf) => `${NAMESPACE}.write(${name}, ${name}.value ${op} ${right.getText(sf)})`,
129
+ node: parent
130
+ });
131
+ }
132
+ else if (writeCtx === 'increment') {
133
+ let unary = parent, delta = unary.operator === ts.SyntaxKind.PlusPlusToken ? '+ 1' : '- 1';
134
+ if (ts.isExpressionStatement(unary.parent)) {
135
+ ctx.replacements.push({
136
+ generate: () => `${NAMESPACE}.write(${name}, ${name}.value ${delta})`,
137
+ node: unary
138
+ });
139
+ }
140
+ else if (ts.isPrefixUnaryExpression(unary)) {
141
+ ctx.replacements.push({
142
+ generate: () => `(${NAMESPACE}.write(${name}, ${name}.value ${delta}), ${name}.value)`,
143
+ node: unary
144
+ });
145
+ }
146
+ else {
147
+ let tmp = `_t${ctx.tmpCounter++}`;
148
+ ctx.replacements.push({
149
+ generate: () => `((${tmp}) => (${NAMESPACE}.write(${name}, ${tmp} ${delta}), ${tmp}))(${name}.value)`,
150
+ node: unary
151
+ });
152
+ }
153
+ }
154
+ }
155
+ function rewrite(ctx, node) {
156
+ if (ts.isTypeNode(node)) {
157
+ return;
158
+ }
159
+ if (ts.isIdentifier(node)) {
160
+ reference(ctx, node);
161
+ return;
162
+ }
163
+ node.forEachChild(child => rewrite(ctx, child));
164
+ }
165
+ function targetOf(call) {
166
+ let parent = call.parent;
167
+ if (parent && ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
168
+ return parent.name;
169
+ }
170
+ if (parent && ts.isBinaryExpression(parent) && parent.operatorToken.kind === ts.SyntaxKind.EqualsToken && ts.isIdentifier(parent.left)) {
171
+ return parent.left;
172
+ }
173
+ return null;
174
174
  }
175
175
  export default (sourceFile, bindings, isReactiveCall) => {
176
176
  let ctx = {
@@ -178,10 +178,9 @@ export default (sourceFile, bindings, isReactiveCall) => {
178
178
  calls: [],
179
179
  isReactiveCall,
180
180
  replacements: [],
181
- scopedBindings: [],
182
- sourceFile,
183
181
  tmpCounter: 0
184
182
  };
185
- visit(ctx, sourceFile);
183
+ declare(ctx, sourceFile);
184
+ rewrite(ctx, sourceFile);
186
185
  return { calls: ctx.calls, replacements: ctx.replacements };
187
186
  };
@@ -1,5 +1,10 @@
1
1
  import type { ts } from '@esportsplus/typescript';
2
- import { TYPES } from './constants.js';
3
- type Bindings = Map<string, TYPES>;
2
+ import type { TYPES } from './constants.js';
3
+ type Bindings = {
4
+ checker: ts.Checker;
5
+ names: Set<string>;
6
+ paths: Map<ts.Symbol, Set<string>>;
7
+ symbols: Map<ts.Symbol, TYPES>;
8
+ };
4
9
  type IsReactiveCall = (node: ts.Node) => node is ts.CallExpression;
5
10
  export type { Bindings, IsReactiveCall };
@@ -140,12 +140,13 @@ class ReactiveArray extends Array {
140
140
  this.on(event, listener);
141
141
  }
142
142
  pop() {
143
- let item = super.pop();
144
- if (item !== undefined) {
145
- dispose(item);
146
- write(this._length, this.length);
147
- this.dispatch('pop', { item });
143
+ if (this.length === 0) {
144
+ return undefined;
148
145
  }
146
+ let item = super.pop();
147
+ dispose(item);
148
+ write(this._length, this.length);
149
+ this.dispatch('pop', { item });
149
150
  return item;
150
151
  }
151
152
  push(...items) {
@@ -163,12 +164,13 @@ class ReactiveArray extends Array {
163
164
  return this;
164
165
  }
165
166
  shift() {
166
- let item = super.shift();
167
- if (item !== undefined) {
168
- dispose(item);
169
- write(this._length, this.length);
170
- this.dispatch('shift', { item });
167
+ if (this.length === 0) {
168
+ return undefined;
171
169
  }
170
+ let item = super.shift();
171
+ dispose(item);
172
+ write(this._length, this.length);
173
+ this.dispatch('shift', { item });
172
174
  return item;
173
175
  }
174
176
  sort(fn) {
@@ -200,13 +202,18 @@ class ReactiveArray extends Array {
200
202
  return this;
201
203
  }
202
204
  splice(start, deleteCount = this.length, ...items) {
203
- let removed = super.splice(start, deleteCount, ...items);
205
+ let length = this.length, removed = super.splice(start, deleteCount, ...items);
204
206
  if (items.length > 0 || removed.length > 0) {
205
207
  write(this._length, this.length);
206
208
  for (let i = 0, n = removed.length; i < n; i++) {
207
209
  dispose(removed[i]);
208
210
  }
209
- this.dispatch('splice', { deleteCount, items, start });
211
+ let index = Math.trunc(start) || 0;
212
+ this.dispatch('splice', {
213
+ deleteCount: removed.length,
214
+ items,
215
+ start: index < 0 ? Math.max(length + index, 0) : Math.min(index, length)
216
+ });
210
217
  }
211
218
  return removed;
212
219
  }
package/build/system.js CHANGED
@@ -451,7 +451,7 @@ function makeAsyncComputed(factory) {
451
451
  else if (result != null && typeof result[Symbol.asyncIterator] === 'function') {
452
452
  let it = result[Symbol.asyncIterator]();
453
453
  onCleanup(() => {
454
- it.return?.();
454
+ it.return?.().then(undefined, () => { });
455
455
  });
456
456
  let step = (r) => {
457
457
  if (stale()) {
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "@esportsplus/utilities": "^0.28.4"
5
5
  },
6
6
  "devDependencies": {
7
- "@esportsplus/typescript": "^0.34.4",
7
+ "@esportsplus/typescript": "^0.36.0",
8
8
  "@types/node": "^26.4.1",
9
9
  "vite": "^8.2.2",
10
10
  "vitest": "^5.0.0"
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "type": "module",
38
38
  "types": "build/index.d.ts",
39
- "version": "0.37.0",
39
+ "version": "0.37.1",
40
40
  "scripts": {
41
41
  "agent:bench": "vitest bench --run",
42
42
  "agent:test": "tsc --noEmit && vitest run",
@@ -1,10 +1,8 @@
1
1
  allowBuilds:
2
2
  esbuild: true
3
3
  minimumReleaseAgeExclude:
4
- - '@esportsplus/utilities@0.28.4'
5
- - '@esportsplus/typescript@0.34.4'
6
- - '@esportsplus/utilities@0.28.1 || 0.28.3'
7
- - '@esportsplus/typescript@0.31.0 || 0.32.0 || 0.33.0 || 0.33.2 || 0.33.4 || 0.34.1 || 0.34.3'
4
+ - '@esportsplus/utilities@0.28.1 || 0.28.3 || 0.28.4'
5
+ - '@esportsplus/typescript@0.31.0 || 0.32.0 || 0.33.0 || 0.33.2 || 0.33.4 || 0.34.1 || 0.34.3 || 0.34.4 || 0.36.0'
8
6
  - tsc-alias@1.9.4
9
7
  - '@vitest/mocker@5.0.0'
10
8
  - '@vitest/spy@5.0.0'