esupgrade 2025.0.0 → 2025.0.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.
- package/.pre-commit-config.yaml +7 -6
- package/.pre-commit-hooks.yaml +4 -5
- package/README.md +57 -48
- package/bin/esupgrade.js +7 -21
- package/images/logo-dark.svg +3 -4
- package/images/logo-light.svg +1 -2
- package/package.json +1 -1
- package/src/index.js +18 -689
- package/src/newlyAvailable.js +154 -0
- package/src/widelyAvailable.js +265 -0
- package/tests/cli.test.js +358 -0
- package/tests/index.test.js +274 -0
- package/tests/newlyAvailable.test.js +221 -0
- package/tests/widelyAvailable.test.js +428 -0
- package/.idea/copilot.data.migration.ask2agent.xml +0 -6
- package/.idea/esupgrade.iml +0 -8
- package/.idea/inspectionProfiles/Project_Default.xml +0 -28
- package/.idea/inspectionProfiles/profiles_settings.xml +0 -6
- package/.idea/misc.xml +0 -7
- package/.idea/modules.xml +0 -8
- package/.idea/vcs.xml +0 -7
- package/tests/transform.test.js +0 -322
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { describe, test, beforeEach, afterEach } from "node:test"
|
|
2
|
+
import assert from "node:assert"
|
|
3
|
+
import { execSync, spawnSync } from "node:child_process"
|
|
4
|
+
import fs from "node:fs"
|
|
5
|
+
import path from "node:path"
|
|
6
|
+
import os from "node:os"
|
|
7
|
+
|
|
8
|
+
const CLI_PATH = path.join(process.cwd(), "bin", "esupgrade.js")
|
|
9
|
+
|
|
10
|
+
describe("CLI", () => {
|
|
11
|
+
let tempDir
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "esupgrade-test-"))
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
if (tempDir && fs.existsSync(tempDir)) {
|
|
19
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test("should display help when no files specified", () => {
|
|
24
|
+
const result = spawnSync(process.execPath, [CLI_PATH], {
|
|
25
|
+
encoding: "utf8",
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
assert.match(result.stderr, /Error: No files specified/)
|
|
29
|
+
// Commander shows help and exits with 0
|
|
30
|
+
assert.strictEqual(result.status, 0)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test("should transform a single file with --write", () => {
|
|
34
|
+
const testFile = path.join(tempDir, "test.js")
|
|
35
|
+
const originalCode = `var x = 1;`
|
|
36
|
+
fs.writeFileSync(testFile, originalCode)
|
|
37
|
+
|
|
38
|
+
const result = spawnSync(process.execPath, [CLI_PATH, testFile, "--write"], {
|
|
39
|
+
encoding: "utf8",
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const transformedCode = fs.readFileSync(testFile, "utf8")
|
|
43
|
+
assert.match(transformedCode, /const x = 1/)
|
|
44
|
+
assert.match(result.stdout, /Summary: 1 file\(s\) upgraded/)
|
|
45
|
+
assert.strictEqual(result.status, 0)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test("should transform files with widely-available baseline by default", () => {
|
|
49
|
+
const testFile = path.join(tempDir, "test.js")
|
|
50
|
+
const originalCode = `var x = 1;\nconst p = new Promise((resolve) => resolve(getData()));`
|
|
51
|
+
fs.writeFileSync(testFile, originalCode)
|
|
52
|
+
|
|
53
|
+
const result = spawnSync(process.execPath, [CLI_PATH, testFile, "--write"], {
|
|
54
|
+
encoding: "utf8",
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const transformedCode = fs.readFileSync(testFile, "utf8")
|
|
58
|
+
assert.match(transformedCode, /const x = 1/)
|
|
59
|
+
assert.doesNotMatch(transformedCode, /Promise\.try/) // Promise.try not in widely-available
|
|
60
|
+
assert.match(result.stdout, /widely-available/)
|
|
61
|
+
assert.strictEqual(result.status, 0)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test("should transform files with newly-available baseline", () => {
|
|
65
|
+
const testFile = path.join(tempDir, "test.js")
|
|
66
|
+
const originalCode = `var x = 1;\nconst p = new Promise((resolve) => resolve(getData()));`
|
|
67
|
+
fs.writeFileSync(testFile, originalCode)
|
|
68
|
+
|
|
69
|
+
const result = spawnSync(
|
|
70
|
+
process.execPath,
|
|
71
|
+
[CLI_PATH, testFile, "--baseline", "newly-available", "--write"],
|
|
72
|
+
{
|
|
73
|
+
encoding: "utf8",
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const transformedCode = fs.readFileSync(testFile, "utf8")
|
|
78
|
+
assert.match(transformedCode, /const x = 1/)
|
|
79
|
+
assert.match(transformedCode, /Promise\.try/) // Promise.try in newly-available
|
|
80
|
+
assert.match(result.stdout, /newly-available/)
|
|
81
|
+
assert.strictEqual(result.status, 0)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test("should check files without writing with --check", () => {
|
|
85
|
+
const testFile = path.join(tempDir, "test.js")
|
|
86
|
+
const originalCode = `var x = 1;`
|
|
87
|
+
fs.writeFileSync(testFile, originalCode)
|
|
88
|
+
|
|
89
|
+
const result = spawnSync(process.execPath, [CLI_PATH, testFile, "--check"], {
|
|
90
|
+
encoding: "utf8",
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const fileContent = fs.readFileSync(testFile, "utf8")
|
|
94
|
+
assert.strictEqual(fileContent, originalCode) // File unchanged
|
|
95
|
+
assert.match(result.stdout, /✗/)
|
|
96
|
+
assert.match(result.stdout, /1 file\(s\) need upgrading/)
|
|
97
|
+
assert.strictEqual(result.status, 1) // Exit with code 1 when changes needed
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test("should exit with 0 when --check and no changes needed", () => {
|
|
101
|
+
const testFile = path.join(tempDir, "test.js")
|
|
102
|
+
const originalCode = `const x = 1;`
|
|
103
|
+
fs.writeFileSync(testFile, originalCode)
|
|
104
|
+
|
|
105
|
+
const result = spawnSync(process.execPath, [CLI_PATH, testFile, "--check"], {
|
|
106
|
+
encoding: "utf8",
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const fileContent = fs.readFileSync(testFile, "utf8")
|
|
110
|
+
assert.strictEqual(fileContent, originalCode) // File unchanged
|
|
111
|
+
assert.match(result.stdout, /All files are already modern/)
|
|
112
|
+
assert.strictEqual(result.status, 0)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test("should support both --check and --write together", () => {
|
|
116
|
+
const testFile = path.join(tempDir, "test.js")
|
|
117
|
+
const originalCode = `var x = 1;`
|
|
118
|
+
fs.writeFileSync(testFile, originalCode)
|
|
119
|
+
|
|
120
|
+
const result = spawnSync(
|
|
121
|
+
process.execPath,
|
|
122
|
+
[CLI_PATH, testFile, "--check", "--write"],
|
|
123
|
+
{
|
|
124
|
+
encoding: "utf8",
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
const transformedCode = fs.readFileSync(testFile, "utf8")
|
|
129
|
+
assert.match(transformedCode, /const x = 1/)
|
|
130
|
+
assert.match(result.stdout, /✗/)
|
|
131
|
+
assert.match(result.stdout, /Changes written to file/)
|
|
132
|
+
assert.match(result.stdout, /1 file\(s\) upgraded/)
|
|
133
|
+
assert.strictEqual(result.status, 1) // Still exit 1 with --check
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test("should process directory recursively", () => {
|
|
137
|
+
const subDir = path.join(tempDir, "src")
|
|
138
|
+
fs.mkdirSync(subDir)
|
|
139
|
+
|
|
140
|
+
const file1 = path.join(tempDir, "test1.js")
|
|
141
|
+
const file2 = path.join(subDir, "test2.js")
|
|
142
|
+
fs.writeFileSync(file1, `var x = 1;`)
|
|
143
|
+
fs.writeFileSync(file2, `var y = 2;`)
|
|
144
|
+
|
|
145
|
+
const result = spawnSync(process.execPath, [CLI_PATH, tempDir, "--write"], {
|
|
146
|
+
encoding: "utf8",
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const transformed1 = fs.readFileSync(file1, "utf8")
|
|
150
|
+
const transformed2 = fs.readFileSync(file2, "utf8")
|
|
151
|
+
assert.match(transformed1, /const x = 1/)
|
|
152
|
+
assert.match(transformed2, /const y = 2/)
|
|
153
|
+
assert.match(result.stdout, /Processing 2 file/)
|
|
154
|
+
assert.match(result.stdout, /2 file\(s\) upgraded/)
|
|
155
|
+
assert.strictEqual(result.status, 0)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test("should skip node_modules and .git directories", () => {
|
|
159
|
+
const nodeModules = path.join(tempDir, "node_modules")
|
|
160
|
+
const gitDir = path.join(tempDir, ".git")
|
|
161
|
+
fs.mkdirSync(nodeModules)
|
|
162
|
+
fs.mkdirSync(gitDir)
|
|
163
|
+
|
|
164
|
+
const file1 = path.join(tempDir, "test.js")
|
|
165
|
+
const file2 = path.join(nodeModules, "test.js")
|
|
166
|
+
const file3 = path.join(gitDir, "test.js")
|
|
167
|
+
fs.writeFileSync(file1, `var x = 1;`)
|
|
168
|
+
fs.writeFileSync(file2, `var y = 2;`)
|
|
169
|
+
fs.writeFileSync(file3, `var z = 3;`)
|
|
170
|
+
|
|
171
|
+
const result = spawnSync(process.execPath, [CLI_PATH, tempDir, "--write"], {
|
|
172
|
+
encoding: "utf8",
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
assert.match(result.stdout, /Processing 1 file/)
|
|
176
|
+
assert.strictEqual(result.status, 0)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test("should process multiple file extensions", () => {
|
|
180
|
+
const jsFile = path.join(tempDir, "test.js")
|
|
181
|
+
const mjsFile = path.join(tempDir, "test.mjs")
|
|
182
|
+
const cjsFile = path.join(tempDir, "test.cjs")
|
|
183
|
+
const jsxFile = path.join(tempDir, "test.jsx")
|
|
184
|
+
|
|
185
|
+
fs.writeFileSync(jsFile, `var a = 1;`)
|
|
186
|
+
fs.writeFileSync(mjsFile, `var b = 2;`)
|
|
187
|
+
fs.writeFileSync(cjsFile, `var c = 3;`)
|
|
188
|
+
fs.writeFileSync(jsxFile, `var d = 4;`)
|
|
189
|
+
|
|
190
|
+
const result = spawnSync(process.execPath, [CLI_PATH, tempDir, "--write"], {
|
|
191
|
+
encoding: "utf8",
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
assert.match(result.stdout, /Processing 4 file/)
|
|
195
|
+
assert.match(result.stdout, /4 file\(s\) upgraded/)
|
|
196
|
+
assert.strictEqual(result.status, 0)
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
test("should handle TypeScript file extensions", () => {
|
|
200
|
+
const tsFile = path.join(tempDir, "test.ts")
|
|
201
|
+
const tsxFile = path.join(tempDir, "test.tsx")
|
|
202
|
+
|
|
203
|
+
fs.writeFileSync(tsFile, `var a = 1;`)
|
|
204
|
+
fs.writeFileSync(tsxFile, `var b = 2;`)
|
|
205
|
+
|
|
206
|
+
const result = spawnSync(process.execPath, [CLI_PATH, tempDir, "--write"], {
|
|
207
|
+
encoding: "utf8",
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
assert.match(result.stdout, /Processing 2 file/)
|
|
211
|
+
assert.strictEqual(result.status, 0)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
test("should error on invalid baseline", () => {
|
|
215
|
+
const testFile = path.join(tempDir, "test.js")
|
|
216
|
+
fs.writeFileSync(testFile, `var x = 1;`)
|
|
217
|
+
|
|
218
|
+
const result = spawnSync(
|
|
219
|
+
process.execPath,
|
|
220
|
+
[CLI_PATH, testFile, "--baseline", "invalid"],
|
|
221
|
+
{
|
|
222
|
+
encoding: "utf8",
|
|
223
|
+
},
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
assert.match(result.stderr, /error/)
|
|
227
|
+
assert.strictEqual(result.status, 1)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
test("should error on non-existent file", () => {
|
|
231
|
+
const result = spawnSync(
|
|
232
|
+
process.execPath,
|
|
233
|
+
[CLI_PATH, path.join(tempDir, "nonexistent.js")],
|
|
234
|
+
{
|
|
235
|
+
encoding: "utf8",
|
|
236
|
+
},
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
assert.match(result.stderr, /Error: Cannot access/)
|
|
240
|
+
assert.strictEqual(result.status, 1)
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
test("should show detailed changes with --check", () => {
|
|
244
|
+
const testFile = path.join(tempDir, "test.js")
|
|
245
|
+
const originalCode = `var x = 1;\nvar y = 2;`
|
|
246
|
+
fs.writeFileSync(testFile, originalCode)
|
|
247
|
+
|
|
248
|
+
const result = spawnSync(process.execPath, [CLI_PATH, testFile, "--check"], {
|
|
249
|
+
encoding: "utf8",
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
assert.match(result.stdout, /var to const/)
|
|
253
|
+
assert.match(result.stdout, /line/)
|
|
254
|
+
assert.strictEqual(result.status, 1)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
test("should process multiple files specified as arguments", () => {
|
|
258
|
+
const file1 = path.join(tempDir, "test1.js")
|
|
259
|
+
const file2 = path.join(tempDir, "test2.js")
|
|
260
|
+
fs.writeFileSync(file1, `var x = 1;`)
|
|
261
|
+
fs.writeFileSync(file2, `var y = 2;`)
|
|
262
|
+
|
|
263
|
+
const result = spawnSync(process.execPath, [CLI_PATH, file1, file2, "--write"], {
|
|
264
|
+
encoding: "utf8",
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
const transformed1 = fs.readFileSync(file1, "utf8")
|
|
268
|
+
const transformed2 = fs.readFileSync(file2, "utf8")
|
|
269
|
+
assert.match(transformed1, /const x = 1/)
|
|
270
|
+
assert.match(transformed2, /const y = 2/)
|
|
271
|
+
assert.match(result.stdout, /Processing 2 file/)
|
|
272
|
+
assert.strictEqual(result.status, 0)
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
test("should handle files with no changes needed", () => {
|
|
276
|
+
const testFile = path.join(tempDir, "test.js")
|
|
277
|
+
const originalCode = `const x = 1;`
|
|
278
|
+
fs.writeFileSync(testFile, originalCode)
|
|
279
|
+
|
|
280
|
+
const result = spawnSync(process.execPath, [CLI_PATH, testFile, "--write"], {
|
|
281
|
+
encoding: "utf8",
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
const fileContent = fs.readFileSync(testFile, "utf8")
|
|
285
|
+
assert.strictEqual(fileContent, originalCode)
|
|
286
|
+
assert.match(result.stdout, /All files are already modern/)
|
|
287
|
+
assert.strictEqual(result.status, 0)
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
test("should show no changes message for individual files", () => {
|
|
291
|
+
const testFile = path.join(tempDir, "test.js")
|
|
292
|
+
const originalCode = `const x = 1;`
|
|
293
|
+
fs.writeFileSync(testFile, originalCode)
|
|
294
|
+
|
|
295
|
+
const result = spawnSync(process.execPath, [CLI_PATH, testFile, "--write"], {
|
|
296
|
+
encoding: "utf8",
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
assert.match(result.stdout, /No changes:/)
|
|
300
|
+
assert.strictEqual(result.status, 0)
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
test("should handle empty directory", () => {
|
|
304
|
+
const emptyDir = path.join(tempDir, "empty")
|
|
305
|
+
fs.mkdirSync(emptyDir)
|
|
306
|
+
|
|
307
|
+
const result = spawnSync(process.execPath, [CLI_PATH, emptyDir, "--write"], {
|
|
308
|
+
encoding: "utf8",
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
assert.match(result.stdout, /No JavaScript files found/)
|
|
312
|
+
assert.strictEqual(result.status, 0)
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
test("should group changes by type in --check output", () => {
|
|
316
|
+
const testFile = path.join(tempDir, "test.js")
|
|
317
|
+
const originalCode = `var x = 1;\nvar y = 2;\nvar z = 3;`
|
|
318
|
+
fs.writeFileSync(testFile, originalCode)
|
|
319
|
+
|
|
320
|
+
const result = spawnSync(process.execPath, [CLI_PATH, testFile, "--check"], {
|
|
321
|
+
encoding: "utf8",
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
assert.match(result.stdout, /var to const/)
|
|
325
|
+
assert.match(result.stdout, /lines:/)
|
|
326
|
+
assert.strictEqual(result.status, 1)
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
test("should handle syntax errors gracefully", () => {
|
|
330
|
+
const testFile = path.join(tempDir, "test.js")
|
|
331
|
+
const invalidCode = `var x = {{{;`
|
|
332
|
+
fs.writeFileSync(testFile, invalidCode)
|
|
333
|
+
|
|
334
|
+
const result = spawnSync(process.execPath, [CLI_PATH, testFile, "--write"], {
|
|
335
|
+
encoding: "utf8",
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
assert.match(result.stderr, /Error processing/)
|
|
339
|
+
assert.strictEqual(result.status, 0) // CLI continues despite errors
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
test("should handle mixed directory and file arguments", () => {
|
|
343
|
+
const subDir = path.join(tempDir, "src")
|
|
344
|
+
fs.mkdirSync(subDir)
|
|
345
|
+
|
|
346
|
+
const file1 = path.join(tempDir, "test1.js")
|
|
347
|
+
const file2 = path.join(subDir, "test2.js")
|
|
348
|
+
fs.writeFileSync(file1, `var x = 1;`)
|
|
349
|
+
fs.writeFileSync(file2, `var y = 2;`)
|
|
350
|
+
|
|
351
|
+
const result = spawnSync(process.execPath, [CLI_PATH, file1, subDir, "--write"], {
|
|
352
|
+
encoding: "utf8",
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
assert.match(result.stdout, /Processing 2 file/)
|
|
356
|
+
assert.strictEqual(result.status, 0)
|
|
357
|
+
})
|
|
358
|
+
})
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { describe, test } from "node:test"
|
|
2
|
+
import assert from "node:assert"
|
|
3
|
+
import { transform } from "../src/index.js"
|
|
4
|
+
|
|
5
|
+
describe("index", () => {
|
|
6
|
+
describe("transform function", () => {
|
|
7
|
+
test("should return TransformResult with correct structure", () => {
|
|
8
|
+
const input = `var x = 1;`
|
|
9
|
+
const result = transform(input)
|
|
10
|
+
|
|
11
|
+
assert.ok(result.hasOwnProperty("code"))
|
|
12
|
+
assert.ok(result.hasOwnProperty("modified"))
|
|
13
|
+
assert.ok(result.hasOwnProperty("changes"))
|
|
14
|
+
assert.strictEqual(typeof result.code, "string")
|
|
15
|
+
assert.strictEqual(typeof result.modified, "boolean")
|
|
16
|
+
assert.ok(Array.isArray(result.changes))
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test("should handle empty string", () => {
|
|
20
|
+
const input = ``
|
|
21
|
+
const result = transform(input)
|
|
22
|
+
|
|
23
|
+
assert.strictEqual(result.modified, false)
|
|
24
|
+
assert.strictEqual(result.changes.length, 0)
|
|
25
|
+
assert.strictEqual(result.code, "")
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test("should handle whitespace-only string", () => {
|
|
29
|
+
const input = ` \n\n `
|
|
30
|
+
const result = transform(input)
|
|
31
|
+
|
|
32
|
+
assert.strictEqual(result.modified, false)
|
|
33
|
+
assert.strictEqual(result.changes.length, 0)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test("should handle comments only", () => {
|
|
37
|
+
const input = `// This is a comment\n/* Another comment */`
|
|
38
|
+
const result = transform(input)
|
|
39
|
+
|
|
40
|
+
assert.strictEqual(result.modified, false)
|
|
41
|
+
assert.strictEqual(result.changes.length, 0)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test("should apply multiple transformations and track all changes", () => {
|
|
45
|
+
const input = `
|
|
46
|
+
var x = 1;
|
|
47
|
+
var y = 'Hello ' + name;
|
|
48
|
+
var obj = Object.assign({}, data);
|
|
49
|
+
`
|
|
50
|
+
const result = transform(input)
|
|
51
|
+
|
|
52
|
+
assert.strictEqual(result.modified, true)
|
|
53
|
+
assert.ok(result.changes.length > 0)
|
|
54
|
+
|
|
55
|
+
// Should have changes from varToConst (which does track line numbers)
|
|
56
|
+
const changeTypes = result.changes.map((c) => c.type)
|
|
57
|
+
assert.ok(changeTypes.includes("varToConst"))
|
|
58
|
+
|
|
59
|
+
// Verify the transformations happened even if not all are tracked
|
|
60
|
+
assert.match(result.code, /const/)
|
|
61
|
+
assert.match(result.code, /`Hello/)
|
|
62
|
+
assert.match(result.code, /\.\.\.data/)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test("should aggregate changes from multiple transformers", () => {
|
|
66
|
+
const input = `
|
|
67
|
+
var a = 1;
|
|
68
|
+
var b = 'Hello ' + world;
|
|
69
|
+
Array.from(items).forEach(item => console.log(item));
|
|
70
|
+
`
|
|
71
|
+
const result = transform(input, "widely-available")
|
|
72
|
+
|
|
73
|
+
assert.strictEqual(result.modified, true)
|
|
74
|
+
|
|
75
|
+
// Should track line numbers for all changes
|
|
76
|
+
assert.ok(result.changes.every((c) => c.hasOwnProperty("line")))
|
|
77
|
+
assert.ok(result.changes.every((c) => c.hasOwnProperty("type")))
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test("should use widely-available transformers by default", () => {
|
|
81
|
+
const input = `var x = 1;`
|
|
82
|
+
const result = transform(input)
|
|
83
|
+
|
|
84
|
+
assert.strictEqual(result.modified, true)
|
|
85
|
+
assert.strictEqual(result.changes[0].type, "varToConst")
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test("should include newly-available transformers when specified", () => {
|
|
89
|
+
const input = `
|
|
90
|
+
var x = 1;
|
|
91
|
+
const p = new Promise((resolve) => resolve(getData()));
|
|
92
|
+
`
|
|
93
|
+
const result = transform(input, "newly-available")
|
|
94
|
+
|
|
95
|
+
assert.strictEqual(result.modified, true)
|
|
96
|
+
|
|
97
|
+
const changeTypes = result.changes.map((c) => c.type)
|
|
98
|
+
assert.ok(changeTypes.includes("varToConst"))
|
|
99
|
+
assert.ok(changeTypes.includes("promiseTry"))
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test("should handle complex nested structures", () => {
|
|
103
|
+
const input = `
|
|
104
|
+
function test() {
|
|
105
|
+
var result = Object.assign({}, {
|
|
106
|
+
message: 'Hello ' + name
|
|
107
|
+
});
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
`
|
|
111
|
+
const result = transform(input)
|
|
112
|
+
|
|
113
|
+
assert.strictEqual(result.modified, true)
|
|
114
|
+
assert.match(result.code, /const result/)
|
|
115
|
+
assert.match(result.code, /\.\.\./)
|
|
116
|
+
assert.match(result.code, /`Hello/)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test("should handle JSX syntax", () => {
|
|
120
|
+
const input = `
|
|
121
|
+
var Component = () => {
|
|
122
|
+
var title = 'Hello ' + name;
|
|
123
|
+
return <div>{title}</div>;
|
|
124
|
+
};
|
|
125
|
+
`
|
|
126
|
+
const result = transform(input)
|
|
127
|
+
|
|
128
|
+
assert.strictEqual(result.modified, true)
|
|
129
|
+
assert.match(result.code, /const/)
|
|
130
|
+
assert.match(result.code, /<div>/)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test("should handle TypeScript syntax", () => {
|
|
134
|
+
const input = `
|
|
135
|
+
var x: number = 1;
|
|
136
|
+
const greeting: string = 'Hello ' + name;
|
|
137
|
+
`
|
|
138
|
+
const result = transform(input)
|
|
139
|
+
|
|
140
|
+
assert.strictEqual(result.modified, true)
|
|
141
|
+
assert.match(result.code, /const x: number/)
|
|
142
|
+
assert.match(result.code, /`Hello/)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test("should preserve code formatting structure", () => {
|
|
146
|
+
const input = `var x = 1;
|
|
147
|
+
var y = 2;`
|
|
148
|
+
const result = transform(input)
|
|
149
|
+
|
|
150
|
+
// Should maintain separation between statements
|
|
151
|
+
assert.match(result.code, /const x = 1/)
|
|
152
|
+
assert.match(result.code, /const y = 2/)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test("should handle very large code", () => {
|
|
156
|
+
// Generate a large input with many var declarations
|
|
157
|
+
const lines = []
|
|
158
|
+
for (let i = 0; i < 100; i++) {
|
|
159
|
+
lines.push(`var x${i} = ${i};`)
|
|
160
|
+
}
|
|
161
|
+
const input = lines.join("\n")
|
|
162
|
+
|
|
163
|
+
const result = transform(input)
|
|
164
|
+
|
|
165
|
+
assert.strictEqual(result.modified, true)
|
|
166
|
+
assert.strictEqual(result.changes.length, 100)
|
|
167
|
+
assert.match(result.code, /const x0 = 0/)
|
|
168
|
+
assert.match(result.code, /const x99 = 99/)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test("should handle code with special characters", () => {
|
|
172
|
+
const input = `var msg = 'Hello \\n' + 'World\\t!';`
|
|
173
|
+
const result = transform(input)
|
|
174
|
+
|
|
175
|
+
assert.strictEqual(result.modified, true)
|
|
176
|
+
assert.match(result.code, /const msg/)
|
|
177
|
+
// Template literals preserve the escape sequences
|
|
178
|
+
assert.match(result.code, /`Hello/)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
test("should handle code with unicode characters", () => {
|
|
182
|
+
const input = `var msg = 'Hello ' + '世界' + '!';`
|
|
183
|
+
const result = transform(input)
|
|
184
|
+
|
|
185
|
+
assert.strictEqual(result.modified, true)
|
|
186
|
+
assert.match(result.code, /const msg/)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test("should handle all transformers returning no changes", () => {
|
|
190
|
+
const input = `const x = 1; const y = 2;`
|
|
191
|
+
const result = transform(input)
|
|
192
|
+
|
|
193
|
+
assert.strictEqual(result.modified, false)
|
|
194
|
+
assert.strictEqual(result.changes.length, 0)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test("should handle baseline parameter case sensitivity", () => {
|
|
198
|
+
const input = `var x = 1;`
|
|
199
|
+
|
|
200
|
+
// Should accept exact strings
|
|
201
|
+
const result1 = transform(input, "widely-available")
|
|
202
|
+
assert.strictEqual(result1.modified, true)
|
|
203
|
+
|
|
204
|
+
const result2 = transform(input, "newly-available")
|
|
205
|
+
assert.strictEqual(result2.modified, true)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
test("should merge transformers correctly for newly-available", () => {
|
|
209
|
+
const input = `
|
|
210
|
+
var x = 1;
|
|
211
|
+
var obj = Object.assign({}, data);
|
|
212
|
+
const p = new Promise((resolve) => resolve(getData()));
|
|
213
|
+
`
|
|
214
|
+
const result = transform(input, "newly-available")
|
|
215
|
+
|
|
216
|
+
assert.strictEqual(result.modified, true)
|
|
217
|
+
|
|
218
|
+
// Should have changes from transformers that track line numbers
|
|
219
|
+
const changeTypes = result.changes.map((c) => c.type)
|
|
220
|
+
assert.ok(changeTypes.includes("varToConst")) // from widely-available
|
|
221
|
+
assert.ok(changeTypes.includes("promiseTry")) // from newly-available
|
|
222
|
+
|
|
223
|
+
// Verify all transformations happened
|
|
224
|
+
assert.match(result.code, /const/)
|
|
225
|
+
assert.match(result.code, /\.\.\.data/)
|
|
226
|
+
assert.match(result.code, /Promise\.try/)
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
test("should handle code without location info gracefully", () => {
|
|
230
|
+
// Even if some transformations don't have location info, should still work
|
|
231
|
+
const input = `var x = 1;`
|
|
232
|
+
const result = transform(input)
|
|
233
|
+
|
|
234
|
+
assert.strictEqual(result.modified, true)
|
|
235
|
+
// Should handle missing loc info gracefully
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
test("should generate valid JavaScript output", () => {
|
|
239
|
+
const input = `
|
|
240
|
+
var x = 1;
|
|
241
|
+
var greeting = 'Hello ' + name;
|
|
242
|
+
Array.from(items).forEach(item => console.log(item));
|
|
243
|
+
`
|
|
244
|
+
const result = transform(input)
|
|
245
|
+
|
|
246
|
+
// Try to parse the output to ensure it's valid JS
|
|
247
|
+
assert.doesNotThrow(() => {
|
|
248
|
+
new Function(result.code)
|
|
249
|
+
})
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
test("should handle single-line code", () => {
|
|
253
|
+
const input = `var x = 1; var y = 2; var z = 3;`
|
|
254
|
+
const result = transform(input)
|
|
255
|
+
|
|
256
|
+
assert.strictEqual(result.modified, true)
|
|
257
|
+
assert.match(result.code, /const x = 1/)
|
|
258
|
+
assert.match(result.code, /const y = 2/)
|
|
259
|
+
assert.match(result.code, /const z = 3/)
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
test("should handle code with existing template literals", () => {
|
|
263
|
+
const input = `
|
|
264
|
+
var msg = \`Hello \${name}\`;
|
|
265
|
+
var other = 'Test ' + value;
|
|
266
|
+
`
|
|
267
|
+
const result = transform(input)
|
|
268
|
+
|
|
269
|
+
assert.strictEqual(result.modified, true)
|
|
270
|
+
assert.match(result.code, /const msg/)
|
|
271
|
+
assert.match(result.code, /const other/)
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
})
|