@esportsplus/reactivity 0.29.21 → 0.30.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.
@@ -24,6 +24,38 @@ function getElementTypeText(typeNode, sourceFile) {
24
24
  }
25
25
  return null;
26
26
  }
27
+ function getOperator(kind) {
28
+ switch (kind) {
29
+ case ts.SyntaxKind.PlusEqualsToken: return '+';
30
+ case ts.SyntaxKind.MinusEqualsToken: return '-';
31
+ case ts.SyntaxKind.AsteriskEqualsToken: return '*';
32
+ case ts.SyntaxKind.SlashEqualsToken: return '/';
33
+ case ts.SyntaxKind.PercentEqualsToken: return '%';
34
+ case ts.SyntaxKind.AsteriskAsteriskEqualsToken: return '**';
35
+ case ts.SyntaxKind.AmpersandEqualsToken: return '&';
36
+ case ts.SyntaxKind.BarEqualsToken: return '|';
37
+ case ts.SyntaxKind.CaretEqualsToken: return '^';
38
+ default: return '+';
39
+ }
40
+ }
41
+ function isAssignmentOperator(kind) {
42
+ return kind === ts.SyntaxKind.EqualsToken ||
43
+ kind === ts.SyntaxKind.PlusEqualsToken ||
44
+ kind === ts.SyntaxKind.MinusEqualsToken ||
45
+ kind === ts.SyntaxKind.AsteriskEqualsToken ||
46
+ kind === ts.SyntaxKind.SlashEqualsToken ||
47
+ kind === ts.SyntaxKind.PercentEqualsToken ||
48
+ kind === ts.SyntaxKind.AsteriskAsteriskEqualsToken ||
49
+ kind === ts.SyntaxKind.LessThanLessThanEqualsToken ||
50
+ kind === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken ||
51
+ kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken ||
52
+ kind === ts.SyntaxKind.AmpersandEqualsToken ||
53
+ kind === ts.SyntaxKind.BarEqualsToken ||
54
+ kind === ts.SyntaxKind.CaretEqualsToken ||
55
+ kind === ts.SyntaxKind.BarBarEqualsToken ||
56
+ kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken ||
57
+ kind === ts.SyntaxKind.QuestionQuestionEqualsToken;
58
+ }
27
59
  function visit(ctx, node) {
28
60
  if (isReactiveCall(ctx.checker, node) && node.arguments.length > 0) {
29
61
  let arg = node.arguments[0], expression = ts.isAsExpression(arg) ? arg.expression : arg;
@@ -69,18 +101,45 @@ function visit(ctx, node) {
69
101
  }
70
102
  }
71
103
  }
72
- if (ts.isPropertyAccessExpression(node) &&
73
- node.name.text === 'length' &&
74
- (!node.parent ||
75
- (!(ts.isBinaryExpression(node.parent) && node.parent.left === node) &&
76
- !ts.isPostfixUnaryExpression(node.parent) &&
77
- !ts.isPrefixUnaryExpression(node.parent)))) {
104
+ if (ts.isPropertyAccessExpression(node) && node.name.text === 'length') {
78
105
  let name = ast.expression.name(node.expression);
79
106
  if (name && ctx.bindings.get(name) === TYPES.Array) {
80
- ctx.replacements.push({
81
- node,
82
- generate: (sf) => `${node.expression.getText(sf)}.$length()`
83
- });
107
+ let expr = node.expression, parent = node.parent;
108
+ if (parent && ts.isBinaryExpression(parent) && parent.left === node && isAssignmentOperator(parent.operatorToken.kind)) {
109
+ let op = parent.operatorToken.kind;
110
+ if (op === ts.SyntaxKind.EqualsToken) {
111
+ ctx.replacements.push({
112
+ node: parent,
113
+ generate: (sf) => `${expr.getText(sf)}.$length = ${parent.right.getText(sf)}`
114
+ });
115
+ }
116
+ else {
117
+ ctx.replacements.push({
118
+ node: parent,
119
+ generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${getOperator(op)} ${parent.right.getText(sf)}`
120
+ });
121
+ }
122
+ }
123
+ else if (parent && ts.isPostfixUnaryExpression(parent)) {
124
+ let op = parent.operator === ts.SyntaxKind.PlusPlusToken ? '+' : '-';
125
+ ctx.replacements.push({
126
+ node: parent,
127
+ generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${op} 1`
128
+ });
129
+ }
130
+ else if (parent && ts.isPrefixUnaryExpression(parent)) {
131
+ let op = parent.operator === ts.SyntaxKind.PlusPlusToken ? '+' : '-';
132
+ ctx.replacements.push({
133
+ node: parent,
134
+ generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${op} 1`
135
+ });
136
+ }
137
+ else {
138
+ ctx.replacements.push({
139
+ node,
140
+ generate: (sf) => `${expr.getText(sf)}.$length`
141
+ });
142
+ }
84
143
  }
85
144
  }
86
145
  if (ts.isBinaryExpression(node) &&
@@ -97,6 +156,22 @@ function visit(ctx, node) {
97
156
  });
98
157
  }
99
158
  }
159
+ if (ts.isBinaryExpression(node) &&
160
+ node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
161
+ ts.isIdentifier(node.left)) {
162
+ let name = node.left.text, right = node.right;
163
+ while (ts.isAsExpression(right) || ts.isTypeAssertionExpression(right)) {
164
+ right = right.expression;
165
+ }
166
+ if (ctx.bindings.get(name) === TYPES.Array && ts.isArrayLiteralExpression(right)) {
167
+ ctx.replacements.push({
168
+ node,
169
+ generate: (sf) => right.elements.length > 0
170
+ ? `${name}.splice(0, ${name}.length, ...${right.getText(sf)})`
171
+ : `${name}.splice(0, ${name}.length)`
172
+ });
173
+ }
174
+ }
100
175
  ts.forEachChild(node, n => visit(ctx, n));
101
176
  }
102
177
  export default (sourceFile, bindings, checker) => {
@@ -38,7 +38,8 @@ declare class ReactiveArray<T> extends Array<T> {
38
38
  private _length;
39
39
  listeners: Listeners;
40
40
  constructor(...items: T[]);
41
- $length(): number;
41
+ get $length(): number;
42
+ set $length(value: number);
42
43
  $set(i: number, value: T): void;
43
44
  clear(): void;
44
45
  concat(...items: ConcatArray<T>[]): ReactiveArray<T>;
@@ -14,9 +14,15 @@ class ReactiveArray extends Array {
14
14
  super(...items);
15
15
  this._length = signal(items.length);
16
16
  }
17
- $length() {
17
+ get $length() {
18
18
  return read(this._length);
19
19
  }
20
+ set $length(value) {
21
+ if (value > super.length) {
22
+ throw Error(`@esportsplus/reactivity: cannot set length to a value larger than the current length, use splice instead.`);
23
+ }
24
+ this.splice(value, super.length);
25
+ }
20
26
  $set(i, value) {
21
27
  let prev = this[i];
22
28
  if (prev === value) {
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "@esportsplus/utilities": "^0.27.2"
5
5
  },
6
6
  "devDependencies": {
7
- "@esportsplus/typescript": "^0.28.3",
7
+ "@esportsplus/typescript": "^0.28.4",
8
8
  "@types/node": "^25.0.8",
9
9
  "vite": "^7.3.1"
10
10
  },
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "type": "module",
37
37
  "types": "build/index.d.ts",
38
- "version": "0.29.21",
38
+ "version": "0.30.0",
39
39
  "scripts": {
40
40
  "build": "tsc",
41
41
  "build:test": "pnpm build && vite build --config test/vite.config.ts",
@@ -47,13 +47,47 @@ function getElementTypeText(typeNode: ts.TypeNode, sourceFile: ts.SourceFile): s
47
47
  return null;
48
48
  }
49
49
 
50
+ function getOperator(kind: ts.SyntaxKind) {
51
+ switch (kind) {
52
+ case ts.SyntaxKind.PlusEqualsToken: return '+';
53
+ case ts.SyntaxKind.MinusEqualsToken: return '-';
54
+ case ts.SyntaxKind.AsteriskEqualsToken: return '*';
55
+ case ts.SyntaxKind.SlashEqualsToken: return '/';
56
+ case ts.SyntaxKind.PercentEqualsToken: return '%';
57
+ case ts.SyntaxKind.AsteriskAsteriskEqualsToken: return '**';
58
+ case ts.SyntaxKind.AmpersandEqualsToken: return '&';
59
+ case ts.SyntaxKind.BarEqualsToken: return '|';
60
+ case ts.SyntaxKind.CaretEqualsToken: return '^';
61
+ default: return '+';
62
+ }
63
+ }
64
+
65
+ function isAssignmentOperator(kind: ts.SyntaxKind) {
66
+ return kind === ts.SyntaxKind.EqualsToken ||
67
+ kind === ts.SyntaxKind.PlusEqualsToken ||
68
+ kind === ts.SyntaxKind.MinusEqualsToken ||
69
+ kind === ts.SyntaxKind.AsteriskEqualsToken ||
70
+ kind === ts.SyntaxKind.SlashEqualsToken ||
71
+ kind === ts.SyntaxKind.PercentEqualsToken ||
72
+ kind === ts.SyntaxKind.AsteriskAsteriskEqualsToken ||
73
+ kind === ts.SyntaxKind.LessThanLessThanEqualsToken ||
74
+ kind === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken ||
75
+ kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken ||
76
+ kind === ts.SyntaxKind.AmpersandEqualsToken ||
77
+ kind === ts.SyntaxKind.BarEqualsToken ||
78
+ kind === ts.SyntaxKind.CaretEqualsToken ||
79
+ kind === ts.SyntaxKind.BarBarEqualsToken ||
80
+ kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken ||
81
+ kind === ts.SyntaxKind.QuestionQuestionEqualsToken;
82
+ }
83
+
50
84
  function visit(ctx: VisitContext, node: ts.Node): void {
51
85
  if (isReactiveCall(ctx.checker, node) && node.arguments.length > 0) {
52
86
  let arg = node.arguments[0],
53
87
  expression = ts.isAsExpression(arg) ? arg.expression : arg;
54
88
 
55
89
  if (ts.isArrayLiteralExpression(expression)) {
56
- let elementType: string | null = null;
90
+ let elementType = null;
57
91
 
58
92
  if (ts.isAsExpression(arg) && arg.type) {
59
93
  elementType = getElementTypeText(arg.type, ctx.sourceFile);
@@ -106,25 +140,55 @@ function visit(ctx: VisitContext, node: ts.Node): void {
106
140
  }
107
141
  }
108
142
 
109
- if (
110
- ts.isPropertyAccessExpression(node) &&
111
- node.name.text === 'length' &&
112
- (
113
- !node.parent ||
114
- (
115
- !(ts.isBinaryExpression(node.parent) && node.parent.left === node) &&
116
- !ts.isPostfixUnaryExpression(node.parent) &&
117
- !ts.isPrefixUnaryExpression(node.parent)
118
- )
119
- )
120
- ) {
143
+ if (ts.isPropertyAccessExpression(node) && node.name.text === 'length') {
121
144
  let name = ast.expression.name(node.expression);
122
145
 
123
146
  if (name && ctx.bindings.get(name) === TYPES.Array) {
124
- ctx.replacements.push({
125
- node,
126
- generate: (sf) => `${node.expression.getText(sf)}.$length()`
127
- });
147
+ let expr = node.expression,
148
+ parent = node.parent;
149
+
150
+ // arr.length = value OR arr.length += value
151
+ if (parent && ts.isBinaryExpression(parent) && parent.left === node && isAssignmentOperator(parent.operatorToken.kind)) {
152
+ let op = parent.operatorToken.kind;
153
+
154
+ if (op === ts.SyntaxKind.EqualsToken) {
155
+ ctx.replacements.push({
156
+ node: parent,
157
+ generate: (sf) => `${expr.getText(sf)}.$length = ${parent.right.getText(sf)}`
158
+ });
159
+ }
160
+ else {
161
+ ctx.replacements.push({
162
+ node: parent,
163
+ generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${getOperator(op)} ${parent.right.getText(sf)}`
164
+ });
165
+ }
166
+ }
167
+ // arr.length++ or arr.length--
168
+ else if (parent && ts.isPostfixUnaryExpression(parent)) {
169
+ let op = parent.operator === ts.SyntaxKind.PlusPlusToken ? '+' : '-';
170
+
171
+ ctx.replacements.push({
172
+ node: parent,
173
+ generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${op} 1`
174
+ });
175
+ }
176
+ // ++arr.length or --arr.length
177
+ else if (parent && ts.isPrefixUnaryExpression(parent)) {
178
+ let op = parent.operator === ts.SyntaxKind.PlusPlusToken ? '+' : '-';
179
+
180
+ ctx.replacements.push({
181
+ node: parent,
182
+ generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${op} 1`
183
+ });
184
+ }
185
+ // Read-only: arr.length → arr.$length
186
+ else {
187
+ ctx.replacements.push({
188
+ node,
189
+ generate: (sf) => `${expr.getText(sf)}.$length`
190
+ });
191
+ }
128
192
  }
129
193
  }
130
194
 
@@ -147,6 +211,29 @@ function visit(ctx: VisitContext, node: ts.Node): void {
147
211
  }
148
212
  }
149
213
 
214
+ if (
215
+ ts.isBinaryExpression(node) &&
216
+ node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
217
+ ts.isIdentifier(node.left)
218
+ ) {
219
+ let name = node.left.text,
220
+ right = node.right;
221
+
222
+ // Unwrap "as" expressions: arr = [] as Type[]
223
+ while (ts.isAsExpression(right) || ts.isTypeAssertionExpression(right)) {
224
+ right = right.expression;
225
+ }
226
+
227
+ if (ctx.bindings.get(name) === TYPES.Array && ts.isArrayLiteralExpression(right)) {
228
+ ctx.replacements.push({
229
+ node,
230
+ generate: (sf) => right.elements.length > 0
231
+ ? `${name}.splice(0, ${name}.length, ...${right.getText(sf)})`
232
+ : `${name}.splice(0, ${name}.length)`
233
+ });
234
+ }
235
+ }
236
+
150
237
  ts.forEachChild(node, n => visit(ctx, n));
151
238
  }
152
239
 
@@ -64,10 +64,19 @@ class ReactiveArray<T> extends Array<T> {
64
64
  }
65
65
 
66
66
 
67
- $length() {
67
+ get $length() {
68
68
  return read(this._length);
69
69
  }
70
70
 
71
+ set $length(value: number) {
72
+ if (value > super.length) {
73
+ throw Error(`@esportsplus/reactivity: cannot set length to a value larger than the current length, use splice instead.`);
74
+ }
75
+
76
+ this.splice(value, super.length);
77
+ }
78
+
79
+
71
80
  $set(i: number, value: T) {
72
81
  let prev = this[i];
73
82