esupgrade 2025.0.2 → 2025.1.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.
package/README.md CHANGED
@@ -73,7 +73,7 @@ For more information about Baseline browser support, visit [web.dev/baseline][ba
73
73
 
74
74
  ### Widely available
75
75
 
76
- #### `var` → `let`/`const`
76
+ #### `var` → [const][mdn-const] & [let][mdn-let]
77
77
 
78
78
  ```diff
79
79
  -var x = 1;
@@ -84,7 +84,7 @@ For more information about Baseline browser support, visit [web.dev/baseline][ba
84
84
  +y = 3;
85
85
  ```
86
86
 
87
- #### String concatenation → Template literals
87
+ #### String concatenation → [Template literals][mdn-template-literals]
88
88
 
89
89
  ```diff
90
90
  -const greeting = 'Hello ' + name + '!';
@@ -93,7 +93,7 @@ For more information about Baseline browser support, visit [web.dev/baseline][ba
93
93
  +const message = `You have ${count} items`;
94
94
  ```
95
95
 
96
- #### `Array.from().forEach()` → `for...of` loops
96
+ #### `Array.from().forEach()` → [`for...of` loops][mdn-for-of]
97
97
 
98
98
  ```diff
99
99
  -Array.from(items).forEach(item => {
@@ -104,7 +104,21 @@ For more information about Baseline browser support, visit [web.dev/baseline][ba
104
104
  +}
105
105
  ```
106
106
 
107
- #### `Object.assign({}, ...)` → Object spread
107
+ #### `Array.from()` → [Array spread [...]][mdn-spread]
108
+
109
+ ```diff
110
+ -const doubled = Array.from(numbers).map(n => n * 2);
111
+ -const filtered = Array.from(items).filter(x => x > 5);
112
+ -const arr = Array.from(iterable);
113
+ +const doubled = [...numbers].map(n => n * 2);
114
+ +const filtered = [...items].filter(x => x > 5);
115
+ +const arr = [...iterable];
116
+ ```
117
+
118
+ > [!NOTE]
119
+ > `Array.from()` with a mapping function or thisArg is not converted.
120
+
121
+ #### `Object.assign({}, ...)` → [Object spread {...}][mdn-spread]
108
122
 
109
123
  ```diff
110
124
  -const obj = Object.assign({}, obj1, obj2);
@@ -113,7 +127,7 @@ For more information about Baseline browser support, visit [web.dev/baseline][ba
113
127
  +const copy = { ...original };
114
128
  ```
115
129
 
116
- #### `.concat()` → Array spread
130
+ #### `.concat()` → [Array spread [...]][mdn-spread]
117
131
 
118
132
  ```diff
119
133
  -const combined = arr1.concat(arr2, arr3);
@@ -122,7 +136,7 @@ For more information about Baseline browser support, visit [web.dev/baseline][ba
122
136
  +const withItem = [...array, item];
123
137
  ```
124
138
 
125
- #### Function expressions → Arrow functions
139
+ #### Function expressions → [Arrow functions][mdn-arrow-functions]
126
140
 
127
141
  ```diff
128
142
  -const fn = function(x) { return x * 2; };
@@ -145,7 +159,7 @@ For more information about Baseline browser support, visit [web.dev/baseline][ba
145
159
  > [!CAUTION]
146
160
  > These transformations are mainly to harden code for future releases and should be used with caution.
147
161
 
148
- #### `new Promise((resolve) => { ... })` → `Promise.try(() => { ... })`
162
+ #### `new Promise((resolve) => { ... })` → [Promise.try][mdn-promise-try]
149
163
 
150
164
  ```diff
151
165
  -new Promise((resolve) => {
@@ -158,4 +172,11 @@ For more information about Baseline browser support, visit [web.dev/baseline][ba
158
172
  ```
159
173
 
160
174
  [baseline]: https://web.dev/baseline/
175
+ [mdn-arrow-functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
176
+ [mdn-const]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
177
+ [mdn-for-of]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
178
+ [mdn-let]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
179
+ [mdn-promise-try]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/try
180
+ [mdn-spread]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
181
+ [mdn-template-literals]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
161
182
  [pre-commit]: https://pre-commit.com/
@@ -1,10 +1,10 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" width="640" height="320" font-family="Segoe UI, system-ui, sans-serif" viewBox="0 0 1280 640">
2
2
  <circle cx="260" cy="320" r="200" fill="#f7df1e"/>
3
3
  <path fill="none" stroke="#191919" stroke-width="48" d="m167 409 95-95 95 95m-190 -95 95-95 95 95m415 50"/>
4
- <text x="241" y="148" fill="#c9d1d9" font-size="92" font-weight="bold" transform="matrix(1.6 0 0 1.6 120 120)">
4
+ <text x="241" y="148" fill="#c9d1d9" font-size="90" font-weight="bold" transform="matrix(1.6 0 0 1.6 120 120)">
5
5
  esupgrade
6
6
  </text>
7
- <text x="241" y="192" fill="#c9d1d9" font-size="28" font-style="italic" transform="matrix(1.6 0 0 1.6 120 120)">
7
+ <text x="241" y="192" fill="#c9d1d9" font-size="27" font-style="italic" transform="matrix(1.6 0 0 1.6 120 120)">
8
8
  Auto-upgrade your JavaScript syntax
9
9
  </text>
10
10
  </svg>
@@ -1,10 +1,10 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" width="640" height="320" font-family="Segoe UI, system-ui, sans-serif" viewBox="0 0 1280 640">
2
2
  <circle cx="260" cy="320" r="200" fill="#f7df1e"/>
3
3
  <path fill="none" stroke="#191919" stroke-width="48" d="m167 409 95-95 95 95m-190 -95 95-95 95 95m415 50"/>
4
- <text x="241" y="148" font-size="92" font-weight="bold" transform="matrix(1.6 0 0 1.6 120 120)">
4
+ <text x="241" y="148" font-size="90" font-weight="bold" transform="matrix(1.6 0 0 1.6 120 120)">
5
5
  esupgrade
6
6
  </text>
7
- <text x="241" y="192" font-size="28" font-style="italic" transform="matrix(1.6 0 0 1.6 120 120)">
7
+ <text x="241" y="192" font-size="27" font-style="italic" transform="matrix(1.6 0 0 1.6 120 120)">
8
8
  Auto-upgrade your JavaScript syntax
9
9
  </text>
10
10
  </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "esupgrade",
3
- "version": "2025.0.2",
3
+ "version": "2025.1.0",
4
4
  "description": "Auto-upgrade your JavaScript syntax",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * Transform new Promise((resolve, reject) => { resolve(fn()) }) to Promise.try(fn)
3
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/try
3
4
  */
4
5
  export function promiseTry(j, root) {
5
6
  let modified = false
@@ -1,5 +1,7 @@
1
1
  /**
2
2
  * Transform var to const
3
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
4
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
3
5
  */
4
6
  export function varToConst(j, root) {
5
7
  let modified = false
@@ -21,6 +23,7 @@ export function varToConst(j, root) {
21
23
 
22
24
  /**
23
25
  * Transform string concatenation to template literals
26
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
24
27
  */
25
28
  export function concatToTemplateLiteral(j, root) {
26
29
  let modified = false
@@ -94,6 +97,7 @@ export function concatToTemplateLiteral(j, root) {
94
97
 
95
98
  /**
96
99
  * Transform Object.assign({}, ...) to object spread
100
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
97
101
  */
98
102
  export function objectAssignToSpread(j, root) {
99
103
  let modified = false
@@ -128,6 +132,7 @@ export function objectAssignToSpread(j, root) {
128
132
 
129
133
  /**
130
134
  * Transform Array.from().forEach() to for...of
135
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
131
136
  */
132
137
  export function arrayFromForEachToForOf(j, root) {
133
138
  let modified = false
@@ -215,6 +220,7 @@ export function arrayFromForEachToForOf(j, root) {
215
220
 
216
221
  /**
217
222
  * Transform for...of Object.keys() loops to for...in
223
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in
218
224
  */
219
225
  export function forOfKeysToForIn(j, root) {
220
226
  let modified = false
@@ -263,3 +269,69 @@ export function forOfKeysToForIn(j, root) {
263
269
 
264
270
  return { modified, changes }
265
271
  }
272
+
273
+ /**
274
+ * Transform Array.from(obj) to [...obj] spread syntax
275
+ * This handles cases like Array.from(obj).map(), .filter(), .some(), etc.
276
+ * that are not covered by the forEach transformer
277
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
278
+ */
279
+ export function arrayFromToSpread(j, root) {
280
+ let modified = false
281
+ const changes = []
282
+
283
+ root
284
+ .find(j.CallExpression)
285
+ .filter((path) => {
286
+ const node = path.node
287
+
288
+ // Check if this is Array.from() call
289
+ if (
290
+ !j.MemberExpression.check(node.callee) ||
291
+ !j.Identifier.check(node.callee.object) ||
292
+ node.callee.object.name !== "Array" ||
293
+ !j.Identifier.check(node.callee.property) ||
294
+ node.callee.property.name !== "from"
295
+ ) {
296
+ return false
297
+ }
298
+
299
+ // Must have exactly one argument (the iterable)
300
+ // If there's a second argument (mapping function), we should not transform
301
+ if (node.arguments.length !== 1) {
302
+ return false
303
+ }
304
+
305
+ // Don't transform if this is Array.from().forEach()
306
+ // as that's handled by arrayFromForEachToForOf
307
+ const parent = path.parent.node
308
+ if (
309
+ j.MemberExpression.check(parent) &&
310
+ j.Identifier.check(parent.property) &&
311
+ parent.property.name === "forEach"
312
+ ) {
313
+ return false
314
+ }
315
+
316
+ return true
317
+ })
318
+ .forEach((path) => {
319
+ const node = path.node
320
+ const iterable = node.arguments[0]
321
+
322
+ // Create array with spread element
323
+ const spreadArray = j.arrayExpression([j.spreadElement(iterable)])
324
+
325
+ j(path).replaceWith(spreadArray)
326
+
327
+ modified = true
328
+ if (node.loc) {
329
+ changes.push({
330
+ type: "arrayFromToSpread",
331
+ line: node.loc.start.line,
332
+ })
333
+ }
334
+ })
335
+
336
+ return { modified, changes }
337
+ }
@@ -231,6 +231,143 @@ for (const key of Object.keys(obj)) {
231
231
  assert.match(result.code, /for \(const \[a, b\] of items\)/)
232
232
  })
233
233
  })
234
+
235
+ describe("Array.from() to spread", () => {
236
+ test("Array.from().map() to [...].map()", () => {
237
+ const input = `const doubled = Array.from(numbers).map(n => n * 2);`
238
+
239
+ const result = transform(input)
240
+
241
+ assert.strictEqual(result.modified, true)
242
+ assert.match(result.code, /\[\.\.\.numbers\]\.map/)
243
+ assert.doesNotMatch(result.code, /Array\.from/)
244
+ })
245
+
246
+ test("Array.from().filter() to [...].filter()", () => {
247
+ const input = `const filtered = Array.from(items).filter(x => x > 5);`
248
+
249
+ const result = transform(input)
250
+
251
+ assert.strictEqual(result.modified, true)
252
+ assert.match(result.code, /\[\.\.\.items\]\.filter/)
253
+ })
254
+
255
+ test("Array.from().some() to [...].some()", () => {
256
+ const input = `const hasValue = Array.from(collection).some(item => item.active);`
257
+
258
+ const result = transform(input)
259
+
260
+ assert.strictEqual(result.modified, true)
261
+ assert.match(result.code, /\[\.\.\.collection\]\.some/)
262
+ })
263
+
264
+ test("Array.from().every() to [...].every()", () => {
265
+ const input = `const allValid = Array.from(items).every(x => x.valid);`
266
+
267
+ const result = transform(input)
268
+
269
+ assert.strictEqual(result.modified, true)
270
+ assert.match(result.code, /\[\.\.\.items\]\.every/)
271
+ })
272
+
273
+ test("Array.from().find() to [...].find()", () => {
274
+ const input = `const found = Array.from(elements).find(el => el.id === 'target');`
275
+
276
+ const result = transform(input)
277
+
278
+ assert.strictEqual(result.modified, true)
279
+ assert.match(result.code, /\[\.\.\.elements\]\.find/)
280
+ })
281
+
282
+ test("Array.from().reduce() to [...].reduce()", () => {
283
+ const input = `const sum = Array.from(values).reduce((a, b) => a + b, 0);`
284
+
285
+ const result = transform(input)
286
+
287
+ assert.strictEqual(result.modified, true)
288
+ assert.match(result.code, /\[\.\.\.values\]\.reduce/)
289
+ })
290
+
291
+ test("Array.from() standalone to [...]", () => {
292
+ const input = `const arr = Array.from(iterable);`
293
+
294
+ const result = transform(input)
295
+
296
+ assert.strictEqual(result.modified, true)
297
+ assert.match(result.code, /const arr = \[\.\.\.iterable\]/)
298
+ })
299
+
300
+ test("Array.from() with property access", () => {
301
+ const input = `const length = Array.from(items).length;`
302
+
303
+ const result = transform(input)
304
+
305
+ assert.strictEqual(result.modified, true)
306
+ assert.match(result.code, /\[\.\.\.items\]\.length/)
307
+ })
308
+
309
+ test("Array.from().forEach() should NOT be transformed (handled by other transformer)", () => {
310
+ const input = `Array.from(items).forEach(item => console.log(item));`
311
+
312
+ const result = transform(input)
313
+
314
+ // Should be transformed by arrayFromForEachToForOf, not arrayFromToSpread
315
+ assert.strictEqual(result.modified, true)
316
+ assert.match(result.code, /for \(const item of items\)/)
317
+ assert.doesNotMatch(result.code, /\[\.\.\./)
318
+ })
319
+
320
+ test("Array.from() with mapping function should NOT be transformed", () => {
321
+ const input = `const doubled = Array.from(numbers, n => n * 2);`
322
+
323
+ const result = transform(input)
324
+
325
+ // Should not transform because there's a mapping function
326
+ assert.strictEqual(result.modified, false)
327
+ assert.match(result.code, /Array\.from\(numbers, n => n \* 2\)/)
328
+ })
329
+
330
+ test("Array.from() with thisArg should NOT be transformed", () => {
331
+ const input = `const result = Array.from(items, function(x) { return x * this.multiplier; }, context);`
332
+
333
+ const result = transform(input)
334
+
335
+ // Should not transform because there are 3 arguments
336
+ assert.strictEqual(result.modified, false)
337
+ assert.match(result.code, /Array\.from/)
338
+ })
339
+
340
+ test("Array.from() chained methods", () => {
341
+ const input = `const result = Array.from(set).map(x => x * 2).filter(x => x > 10);`
342
+
343
+ const result = transform(input)
344
+
345
+ assert.strictEqual(result.modified, true)
346
+ assert.match(result.code, /\[\.\.\.set\]\.map/)
347
+ })
348
+
349
+ test("Array.from() with complex iterable", () => {
350
+ const input = `const arr = Array.from(document.querySelectorAll('.item'));`
351
+
352
+ const result = transform(input)
353
+
354
+ assert.strictEqual(result.modified, true)
355
+ assert.match(result.code, /\[\.\.\.document\.querySelectorAll\('\.item'\)\]/)
356
+ })
357
+
358
+ test("Array.from() tracks line numbers", () => {
359
+ const input = `// Line 1
360
+ const result = Array.from(items).map(x => x * 2);`
361
+
362
+ const result = transform(input)
363
+
364
+ assert.strictEqual(result.modified, true)
365
+ assert.strictEqual(result.changes.length, 1)
366
+ assert.strictEqual(result.changes[0].type, "arrayFromToSpread")
367
+ assert.strictEqual(result.changes[0].line, 2)
368
+ })
369
+ })
370
+
234
371
  describe("const and let", () => {
235
372
  test("var to const when not reassigned", () => {
236
373
  const input = `