esupgrade 2025.3.1 → 2025.3.3
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 +28 -0
- package/bin/esupgrade.js +262 -146
- package/package.json +1 -1
- package/src/index.js +12 -9
- package/src/worker.js +26 -0
- package/tests/cli.test.js +82 -10
- package/tests/newlyAvailable.test.js +107 -263
- package/tests/widelyAvailable.test.js +16 -14
package/README.md
CHANGED
|
@@ -40,6 +40,12 @@ pre-commit run esupgrade --all-files
|
|
|
40
40
|
npx esupgrade --help
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
<picture>
|
|
44
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://web-platform-dx.github.io/web-features/assets/img/baseline-wordmark-dark.svg">
|
|
45
|
+
<source media="(prefers-color-scheme: light)" srcset="https://web-platform-dx.github.io/web-features/assets/img/baseline-wordmark.svg">
|
|
46
|
+
<img alt="Baseline: widely available" src="https://web-platform-dx.github.io/web-features/assets/img/baseline-wordmark.svg" height="32" align="right">
|
|
47
|
+
</picture>
|
|
48
|
+
|
|
43
49
|
## Browser Support & Baseline
|
|
44
50
|
|
|
45
51
|
All transformations are based on [Web Platform Baseline][baseline] features. Baseline tracks which web platform features are safe to use across browsers.
|
|
@@ -229,7 +235,28 @@ Supports:
|
|
|
229
235
|
+});
|
|
230
236
|
```
|
|
231
237
|
|
|
238
|
+
## Versioning
|
|
239
|
+
|
|
240
|
+
esupgrade uses the [calver] `YYYY.MINOR.PATCH` versioning scheme.
|
|
241
|
+
|
|
242
|
+
The year indicates the baseline version. New transformations are added in minor releases, while patches are reserved for bug fixes.
|
|
243
|
+
|
|
244
|
+
## Related Projects
|
|
245
|
+
|
|
246
|
+
Thanks to these projects for inspiring esupgrade:
|
|
247
|
+
|
|
248
|
+
- @asottile's [pyupgrade] for Python
|
|
249
|
+
- @adamchainz' [django-upgrade] for Django
|
|
250
|
+
|
|
251
|
+
### Distinction
|
|
252
|
+
|
|
253
|
+
lebab is a similar project that focuses on ECMAScript 6+ transformations without considering browser support.
|
|
254
|
+
esupgrade is distinct in that it applies transformations that are safe based on Baseline browser support.
|
|
255
|
+
Furthermore, esupgrade supports JavaScript, TypeScript, and more, while lebab is limited to JavaScript.
|
|
256
|
+
|
|
232
257
|
[baseline]: https://web.dev/baseline/
|
|
258
|
+
[calver]: https://calver.org/
|
|
259
|
+
[django-upgrade]: https://github.com/adamchainz/django-upgrade
|
|
233
260
|
[mdn-arrow-functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
|
|
234
261
|
[mdn-const]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
|
|
235
262
|
[mdn-exponentiation]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation
|
|
@@ -240,3 +267,4 @@ Supports:
|
|
|
240
267
|
[mdn-spread]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
|
|
241
268
|
[mdn-template-literals]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
|
|
242
269
|
[pre-commit]: https://pre-commit.com/
|
|
270
|
+
[pyupgrade]: https://github.com/asottile/pyupgrade
|
package/bin/esupgrade.js
CHANGED
|
@@ -2,201 +2,317 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "fs"
|
|
4
4
|
import path from "path"
|
|
5
|
+
import os from "os"
|
|
6
|
+
import { Worker } from "worker_threads"
|
|
7
|
+
import { once } from "events"
|
|
5
8
|
import { Command, Option } from "commander"
|
|
6
|
-
import {
|
|
9
|
+
import { fileURLToPath } from "url"
|
|
10
|
+
import process from "node:process"
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
13
|
+
const __dirname = path.dirname(__filename)
|
|
7
14
|
|
|
8
15
|
/**
|
|
9
16
|
* CLI tool for esupgrade
|
|
10
17
|
*/
|
|
11
18
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
new Option("--baseline <level>", "Set baseline level for transformations")
|
|
20
|
-
.choices(["widely-available", "newly-available"])
|
|
21
|
-
.default("widely-available"),
|
|
22
|
-
)
|
|
23
|
-
.option("--check", "Report which files need upgrading and exit with code 1 if any do")
|
|
24
|
-
.option(
|
|
25
|
-
"--write",
|
|
26
|
-
"Write changes to files (default: true unless only --check is specified)",
|
|
27
|
-
)
|
|
28
|
-
.action((files, options) => {
|
|
29
|
-
if (files.length === 0) {
|
|
30
|
-
console.error("Error: No files specified")
|
|
31
|
-
program.help()
|
|
32
|
-
}
|
|
19
|
+
/**
|
|
20
|
+
* Handles worker thread execution for file processing
|
|
21
|
+
*/
|
|
22
|
+
class WorkerRunner {
|
|
23
|
+
constructor(workerPath) {
|
|
24
|
+
this.workerPath = workerPath
|
|
25
|
+
}
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Run a worker thread to process a file
|
|
29
|
+
*/
|
|
30
|
+
async run(filePath, baseline) {
|
|
31
|
+
const worker = new Worker(this.workerPath, {
|
|
32
|
+
workerData: { filePath, baseline },
|
|
33
|
+
})
|
|
38
34
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
35
|
+
const [message] = await once(worker, "message")
|
|
36
|
+
return message
|
|
37
|
+
}
|
|
38
|
+
}
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Processes individual files and handles output
|
|
42
|
+
*/
|
|
43
|
+
class FileProcessor {
|
|
44
|
+
constructor(workerRunner) {
|
|
45
|
+
this.workerRunner = workerRunner
|
|
46
|
+
}
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {Object} ProcessResult
|
|
50
|
+
* @property {boolean} modified - Whether the file was modified
|
|
51
|
+
* @property {Array} changes - List of changes made
|
|
52
|
+
* @property {boolean} error - Whether an error occurred during processing
|
|
53
|
+
*/
|
|
50
54
|
|
|
51
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Process a file using a worker thread
|
|
57
|
+
*
|
|
58
|
+
* @param {string} filePath - Path to the file to process
|
|
59
|
+
* @param {Object} options - Processing options
|
|
60
|
+
* @param {string} options.baseline - Baseline level for transformations
|
|
61
|
+
* @param {boolean} options.check - Whether to only check for changes
|
|
62
|
+
* @param {boolean} options.write - Whether to write changes to file
|
|
63
|
+
* @returns {Promise<ProcessResult>} Result of processing
|
|
64
|
+
*/
|
|
65
|
+
async processFile(filePath, options) {
|
|
52
66
|
try {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
const workerResult = await this.workerRunner.run(filePath, options.baseline)
|
|
68
|
+
|
|
69
|
+
if (!workerResult.success) {
|
|
70
|
+
console.error(`✗ Error: ${filePath}: ${workerResult.error}`)
|
|
71
|
+
return { modified: false, changes: [], error: true }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const result = workerResult.result
|
|
75
|
+
|
|
76
|
+
if (result.modified) {
|
|
77
|
+
if (options.check) {
|
|
78
|
+
this.#reportChanges(filePath, result.changes)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (options.write) {
|
|
82
|
+
fs.writeFileSync(filePath, result.code, "utf8")
|
|
83
|
+
if (!options.check) {
|
|
84
|
+
console.log(`✓ ${filePath}`)
|
|
85
|
+
} else {
|
|
86
|
+
console.log(` ✓ written`)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { modified: true, changes: result.changes, error: false }
|
|
91
|
+
} else {
|
|
92
|
+
if (!options.check) {
|
|
93
|
+
console.log(` ${filePath}`)
|
|
94
|
+
}
|
|
95
|
+
return { modified: false, changes: [], error: false }
|
|
61
96
|
}
|
|
62
97
|
} catch (error) {
|
|
63
|
-
console.error(
|
|
64
|
-
|
|
98
|
+
console.error(`✗ Error: ${filePath}: ${error.message}`)
|
|
99
|
+
return { modified: false, changes: [], error: true }
|
|
65
100
|
}
|
|
66
101
|
}
|
|
67
102
|
|
|
68
|
-
|
|
103
|
+
#reportChanges(filePath, changes) {
|
|
104
|
+
const changesByType = {}
|
|
105
|
+
if (changes && changes.length > 0) {
|
|
106
|
+
for (const change of changes) {
|
|
107
|
+
if (!changesByType[change.type]) {
|
|
108
|
+
changesByType[change.type] = 0
|
|
109
|
+
}
|
|
110
|
+
changesByType[change.type]++
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const transformations = Object.keys(changesByType)
|
|
115
|
+
.map((type) =>
|
|
116
|
+
type
|
|
117
|
+
.replace(/([A-Z])/g, " $1")
|
|
118
|
+
.trim()
|
|
119
|
+
.toLowerCase(),
|
|
120
|
+
)
|
|
121
|
+
.join(", ")
|
|
122
|
+
|
|
123
|
+
console.log(`✗ ${filePath}`)
|
|
124
|
+
if (transformations) {
|
|
125
|
+
console.log(` ${transformations}`)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
69
128
|
}
|
|
70
129
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Manages a pool of workers for parallel file processing
|
|
132
|
+
*/
|
|
133
|
+
class WorkerPool {
|
|
134
|
+
constructor(fileProcessor, maxWorkers = os.cpus().length) {
|
|
135
|
+
this.fileProcessor = fileProcessor
|
|
136
|
+
this.maxWorkers = maxWorkers
|
|
137
|
+
}
|
|
74
138
|
|
|
75
|
-
|
|
76
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Process files with a worker pool for better CPU utilization
|
|
141
|
+
*/
|
|
142
|
+
async processFiles(files, options) {
|
|
143
|
+
const results = new Array(files.length)
|
|
144
|
+
let fileIndex = 0
|
|
77
145
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const ext = path.extname(entry.name)
|
|
85
|
-
if ([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"].includes(ext)) {
|
|
86
|
-
files.push(fullPath)
|
|
146
|
+
// Worker pool pattern - each worker processes files until queue is empty
|
|
147
|
+
const processNext = async () => {
|
|
148
|
+
while (fileIndex < files.length) {
|
|
149
|
+
const currentIndex = fileIndex++
|
|
150
|
+
const file = files[currentIndex]
|
|
151
|
+
results[currentIndex] = await this.fileProcessor.processFile(file, options)
|
|
87
152
|
}
|
|
88
153
|
}
|
|
89
|
-
}
|
|
90
154
|
|
|
91
|
-
|
|
155
|
+
// Start worker pool and wait for all to complete
|
|
156
|
+
const workerCount = Math.min(this.maxWorkers, files.length)
|
|
157
|
+
const workers = Array.from({ length: workerCount }, () => processNext())
|
|
158
|
+
await Promise.all(workers)
|
|
159
|
+
|
|
160
|
+
return results
|
|
161
|
+
}
|
|
92
162
|
}
|
|
93
163
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (options.check) {
|
|
101
|
-
// Group changes by type for summary
|
|
102
|
-
const changesByType = {}
|
|
103
|
-
if (result.changes && result.changes.length > 0) {
|
|
104
|
-
for (const change of result.changes) {
|
|
105
|
-
if (!changesByType[change.type]) {
|
|
106
|
-
changesByType[change.type] = 0
|
|
107
|
-
}
|
|
108
|
-
changesByType[change.type]++
|
|
109
|
-
}
|
|
110
|
-
}
|
|
164
|
+
/**
|
|
165
|
+
* Finds JavaScript files from patterns
|
|
166
|
+
*/
|
|
167
|
+
class FileFinder {
|
|
168
|
+
static IGNORED_DIRS = ["node_modules", ".git"]
|
|
169
|
+
static JS_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"]
|
|
111
170
|
|
|
112
|
-
|
|
113
|
-
.map((type) => {
|
|
114
|
-
const displayName = type
|
|
115
|
-
.replace(/([A-Z])/g, " $1")
|
|
116
|
-
.trim()
|
|
117
|
-
.toLowerCase()
|
|
118
|
-
return displayName
|
|
119
|
-
})
|
|
120
|
-
.join(", ")
|
|
121
|
-
|
|
122
|
-
console.log(`✗ ${filePath}`)
|
|
123
|
-
if (transformations) {
|
|
124
|
-
console.log(` ${transformations}`)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
171
|
+
constructor() {}
|
|
127
172
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Find files matching patterns
|
|
175
|
+
*/
|
|
176
|
+
*find(patterns) {
|
|
177
|
+
for (const pattern of patterns) {
|
|
178
|
+
try {
|
|
179
|
+
const stats = fs.statSync(pattern)
|
|
180
|
+
|
|
181
|
+
if (stats.isFile()) {
|
|
182
|
+
yield pattern
|
|
183
|
+
} else if (stats.isDirectory()) {
|
|
184
|
+
yield* this._walkDirectory(pattern)
|
|
134
185
|
}
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.error(`Error: Cannot access '${pattern}': ${error.message}`)
|
|
188
|
+
process.exit(1)
|
|
135
189
|
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
136
192
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
193
|
+
*_walkDirectory(dir) {
|
|
194
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
195
|
+
|
|
196
|
+
for (const entry of entries) {
|
|
197
|
+
const fullPath = path.join(dir, entry.name)
|
|
198
|
+
|
|
199
|
+
if (entry.isDirectory()) {
|
|
200
|
+
if (FileFinder.IGNORED_DIRS.includes(entry.name)) {
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
yield* this._walkDirectory(fullPath)
|
|
204
|
+
} else if (entry.isFile()) {
|
|
205
|
+
const ext = path.extname(entry.name)
|
|
206
|
+
if (FileFinder.JS_EXTENSIONS.includes(ext)) {
|
|
207
|
+
yield fullPath
|
|
208
|
+
}
|
|
141
209
|
}
|
|
142
|
-
return { modified: false, changes: [] }
|
|
143
210
|
}
|
|
144
|
-
} catch (error) {
|
|
145
|
-
console.error(`✗ Error: ${filePath}: ${error.message}`)
|
|
146
|
-
return { modified: false, changes: [] }
|
|
147
211
|
}
|
|
148
212
|
}
|
|
149
213
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
214
|
+
/**
|
|
215
|
+
* Orchestrates the CLI application
|
|
216
|
+
*/
|
|
217
|
+
class CLIRunner {
|
|
218
|
+
constructor(workerPath) {
|
|
219
|
+
const workerRunner = new WorkerRunner(workerPath)
|
|
220
|
+
const fileProcessor = new FileProcessor(workerRunner)
|
|
221
|
+
this.workerPool = new WorkerPool(fileProcessor)
|
|
222
|
+
this.fileFinder = new FileFinder()
|
|
156
223
|
}
|
|
157
224
|
|
|
158
|
-
|
|
159
|
-
|
|
225
|
+
/**
|
|
226
|
+
* Process files and report results
|
|
227
|
+
*/
|
|
228
|
+
async run(patterns, options) {
|
|
229
|
+
const files = [...this.fileFinder.find(patterns)]
|
|
160
230
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
modifiedCount++
|
|
165
|
-
allChanges.push(...result.changes)
|
|
231
|
+
if (files.length === 0) {
|
|
232
|
+
console.log("No JavaScript files found")
|
|
233
|
+
process.exit(0)
|
|
166
234
|
}
|
|
235
|
+
|
|
236
|
+
const results = await this.workerPool.processFiles(files, options)
|
|
237
|
+
this.#reportSummary(results, options)
|
|
167
238
|
}
|
|
168
239
|
|
|
169
|
-
|
|
170
|
-
|
|
240
|
+
#reportSummary(results, options) {
|
|
241
|
+
let modifiedCount = 0
|
|
242
|
+
const allChanges = results.flatMap((result) =>
|
|
243
|
+
result.modified ? (modifiedCount++, result.changes) : [],
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
const errorCount = results.filter((result) => result.error).length
|
|
247
|
+
|
|
171
248
|
console.log("")
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
249
|
+
|
|
250
|
+
if (options.check) {
|
|
251
|
+
if (modifiedCount > 0) {
|
|
252
|
+
const transformTypes = new Set(allChanges.map((c) => c.type))
|
|
253
|
+
const typeCount = transformTypes.size
|
|
254
|
+
const totalChanges = allChanges.length
|
|
255
|
+
|
|
256
|
+
console.log(
|
|
257
|
+
`${modifiedCount} file${modifiedCount !== 1 ? "s" : ""} need${modifiedCount === 1 ? "s" : ""} upgrading (${totalChanges} change${totalChanges !== 1 ? "s" : ""}, ${typeCount} type${typeCount !== 1 ? "s" : ""})`,
|
|
258
|
+
)
|
|
259
|
+
if (options.write) {
|
|
260
|
+
console.log("Changes have been written")
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
console.log("All files are up to date")
|
|
183
264
|
}
|
|
184
265
|
} else {
|
|
185
|
-
|
|
266
|
+
if (modifiedCount > 0) {
|
|
267
|
+
console.log(`✓ ${modifiedCount} file${modifiedCount !== 1 ? "s" : ""} upgraded`)
|
|
268
|
+
} else {
|
|
269
|
+
console.log("All files are up to date")
|
|
270
|
+
}
|
|
186
271
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
console.log("All files are up to date")
|
|
272
|
+
|
|
273
|
+
// Errors take precedence over --check flag.
|
|
274
|
+
// Exit with error code if any file processing errors occurred.
|
|
275
|
+
if (errorCount > 0) {
|
|
276
|
+
process.exit(1)
|
|
193
277
|
}
|
|
194
|
-
}
|
|
195
278
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
279
|
+
if (options.check && modifiedCount > 0) {
|
|
280
|
+
process.exit(1)
|
|
281
|
+
}
|
|
199
282
|
}
|
|
200
283
|
}
|
|
201
284
|
|
|
285
|
+
// Initialize CLI
|
|
286
|
+
const program = new Command()
|
|
287
|
+
const cliRunner = new CLIRunner(path.join(__dirname, "../src/worker.js"))
|
|
288
|
+
|
|
289
|
+
program
|
|
290
|
+
.name("esupgrade")
|
|
291
|
+
.description("Auto-upgrade your JavaScript syntax")
|
|
292
|
+
.argument("<files...>", "Files or directories to process")
|
|
293
|
+
.addOption(
|
|
294
|
+
new Option("--baseline <level>", "Set baseline level for transformations")
|
|
295
|
+
.choices(["widely-available", "newly-available"])
|
|
296
|
+
.default("widely-available"),
|
|
297
|
+
)
|
|
298
|
+
.option("--check", "Report which files need upgrading and exit with code 1 if any do")
|
|
299
|
+
.option(
|
|
300
|
+
"--write",
|
|
301
|
+
"Write changes to files (default: true unless only --check is specified)",
|
|
302
|
+
)
|
|
303
|
+
.action(async (files, options) => {
|
|
304
|
+
// Handle check/write options - they are not mutually exclusive
|
|
305
|
+
// Default: write is true unless ONLY --check is specified (no --write)
|
|
306
|
+
const shouldWrite = options.write !== undefined ? options.write : !options.check
|
|
307
|
+
const shouldCheck = options.check || false
|
|
308
|
+
|
|
309
|
+
const processingOptions = {
|
|
310
|
+
baseline: options.baseline,
|
|
311
|
+
write: shouldWrite,
|
|
312
|
+
check: shouldCheck,
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
await cliRunner.run(files, processingOptions)
|
|
316
|
+
})
|
|
317
|
+
|
|
202
318
|
program.parse()
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -11,10 +11,10 @@ import * as newlyAvailable from "./newlyAvailable.js"
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Transform JavaScript code using the specified transformers
|
|
15
|
-
* @param {string} code - The source code to transform
|
|
16
|
-
* @param {string} baseline - Baseline level ('widely-available' or 'newly-available')
|
|
17
|
-
* @returns {TransformResult}
|
|
14
|
+
* Transform JavaScript code using the specified transformers.
|
|
15
|
+
* @param {string} code - The source code to transform.
|
|
16
|
+
* @param {string} baseline - Baseline level ('widely-available' or 'newly-available').
|
|
17
|
+
* @returns {TransformResult} Object with transformed code, modification status, and changes.
|
|
18
18
|
*/
|
|
19
19
|
export function transform(code, baseline = "widely-available") {
|
|
20
20
|
const j = jscodeshift.withParser("tsx")
|
|
@@ -22,11 +22,14 @@ export function transform(code, baseline = "widely-available") {
|
|
|
22
22
|
|
|
23
23
|
let modified = false
|
|
24
24
|
const allChanges = []
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
|
|
26
|
+
const transformers =
|
|
27
|
+
baseline === "newly-available"
|
|
28
|
+
? { ...widelyAvailable, ...newlyAvailable }
|
|
29
|
+
: widelyAvailable
|
|
30
|
+
|
|
31
|
+
for (const transformer of Object.values(transformers)) {
|
|
32
|
+
const result = transformer(j, root)
|
|
30
33
|
if (result.modified) {
|
|
31
34
|
modified = true
|
|
32
35
|
allChanges.push(...result.changes)
|
package/src/worker.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { parentPort, workerData } from "worker_threads"
|
|
2
|
+
import fs from "fs"
|
|
3
|
+
import { transform } from "./index.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Worker thread for processing files in parallel.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { filePath, baseline } = workerData
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const code = fs.readFileSync(filePath, "utf8")
|
|
13
|
+
const result = transform(code, baseline)
|
|
14
|
+
|
|
15
|
+
parentPort.postMessage({
|
|
16
|
+
success: true,
|
|
17
|
+
filePath,
|
|
18
|
+
result,
|
|
19
|
+
})
|
|
20
|
+
} catch (error) {
|
|
21
|
+
parentPort.postMessage({
|
|
22
|
+
success: false,
|
|
23
|
+
filePath,
|
|
24
|
+
error: error.message,
|
|
25
|
+
})
|
|
26
|
+
}
|
package/tests/cli.test.js
CHANGED
|
@@ -27,10 +27,10 @@ describe("CLI", () => {
|
|
|
27
27
|
|
|
28
28
|
assert.match(
|
|
29
29
|
result.stderr,
|
|
30
|
-
/
|
|
30
|
+
/error: missing required argument 'files'/,
|
|
31
31
|
"displays error for no files",
|
|
32
32
|
)
|
|
33
|
-
assert.equal(result.status,
|
|
33
|
+
assert.equal(result.status, 1, "exits with 1 when showing help")
|
|
34
34
|
})
|
|
35
35
|
|
|
36
36
|
test("transform a single file with --write", () => {
|
|
@@ -46,7 +46,7 @@ describe("CLI", () => {
|
|
|
46
46
|
/const x = 1/,
|
|
47
47
|
"transforms var to const",
|
|
48
48
|
)
|
|
49
|
-
assert.match(result.stdout, /✓ 1 file
|
|
49
|
+
assert.match(result.stdout, /✓ 1 file upgraded/, "reports 1 file upgraded")
|
|
50
50
|
assert.equal(result.status, 0, "exits successfully")
|
|
51
51
|
})
|
|
52
52
|
|
|
@@ -113,7 +113,7 @@ describe("CLI", () => {
|
|
|
113
113
|
assert.match(result.stdout, /✗/, "indicates changes needed")
|
|
114
114
|
assert.match(
|
|
115
115
|
result.stdout,
|
|
116
|
-
/1 file
|
|
116
|
+
/1 file needs upgrading/,
|
|
117
117
|
"reports 1 file needs upgrading",
|
|
118
118
|
)
|
|
119
119
|
assert.equal(result.status, 1, "exits with 1 when changes needed")
|
|
@@ -178,7 +178,7 @@ describe("CLI", () => {
|
|
|
178
178
|
|
|
179
179
|
assert.match(fs.readFileSync(file1, "utf8"), /const x = 1/, "transforms file1")
|
|
180
180
|
assert.match(fs.readFileSync(file2, "utf8"), /const y = 2/, "transforms file2")
|
|
181
|
-
assert.match(result.stdout, /2
|
|
181
|
+
assert.match(result.stdout, /2 files upgraded/, "reports 2 files upgraded")
|
|
182
182
|
assert.equal(result.status, 0, "exits successfully")
|
|
183
183
|
})
|
|
184
184
|
|
|
@@ -215,7 +215,7 @@ describe("CLI", () => {
|
|
|
215
215
|
encoding: "utf8",
|
|
216
216
|
})
|
|
217
217
|
|
|
218
|
-
assert.match(result.stdout, /4
|
|
218
|
+
assert.match(result.stdout, /4 files upgraded/, "reports 4 files upgraded")
|
|
219
219
|
assert.equal(result.status, 0, "exits successfully")
|
|
220
220
|
})
|
|
221
221
|
|
|
@@ -290,7 +290,7 @@ describe("CLI", () => {
|
|
|
290
290
|
|
|
291
291
|
assert.match(fs.readFileSync(file1, "utf8"), /const x = 1/, "transforms file1")
|
|
292
292
|
assert.match(fs.readFileSync(file2, "utf8"), /const y = 2/, "transforms file2")
|
|
293
|
-
assert.match(result.stdout, /2
|
|
293
|
+
assert.match(result.stdout, /2 files upgraded/, "reports 2 files upgraded")
|
|
294
294
|
assert.equal(result.status, 0, "exits successfully")
|
|
295
295
|
})
|
|
296
296
|
|
|
@@ -361,7 +361,7 @@ describe("CLI", () => {
|
|
|
361
361
|
assert.equal(result.status, 1, "exits with 1")
|
|
362
362
|
})
|
|
363
363
|
|
|
364
|
-
test("
|
|
364
|
+
test("exit with 1 on syntax errors", () => {
|
|
365
365
|
const testFile = path.join(tempDir, "test.js")
|
|
366
366
|
fs.writeFileSync(testFile, `var x = {{{;`)
|
|
367
367
|
|
|
@@ -370,7 +370,50 @@ describe("CLI", () => {
|
|
|
370
370
|
})
|
|
371
371
|
|
|
372
372
|
assert.match(result.stderr, /✗ Error:/, "displays error for syntax issues")
|
|
373
|
-
assert.equal(result.status,
|
|
373
|
+
assert.equal(result.status, 1, "exits with 1 on errors")
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
test("exit with 1 on parsing errors with --check", () => {
|
|
377
|
+
const testFile = path.join(tempDir, "test.js")
|
|
378
|
+
fs.writeFileSync(testFile, `const a;\na = 'asdf'`)
|
|
379
|
+
|
|
380
|
+
const result = spawnSync(process.execPath, [CLI_PATH, testFile, "--check"], {
|
|
381
|
+
encoding: "utf8",
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
assert.match(result.stderr, /✗ Error:/, "displays error for parsing issues")
|
|
385
|
+
assert.equal(result.status, 1, "exits with 1 on errors with --check")
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
test("exit with 1 on parsing errors without --check", () => {
|
|
389
|
+
const testFile = path.join(tempDir, "test.js")
|
|
390
|
+
fs.writeFileSync(testFile, `const a;\na = 'asdf'`)
|
|
391
|
+
|
|
392
|
+
const result = spawnSync(process.execPath, [CLI_PATH, testFile], {
|
|
393
|
+
encoding: "utf8",
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
assert.match(result.stderr, /✗ Error:/, "displays error for parsing issues")
|
|
397
|
+
assert.equal(result.status, 1, "exits with 1 on errors without --check")
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
test("exit with 1 on errors even with valid files", () => {
|
|
401
|
+
const validFile = path.join(tempDir, "valid.js")
|
|
402
|
+
const invalidFile = path.join(tempDir, "invalid.js")
|
|
403
|
+
fs.writeFileSync(validFile, `var x = 1;`)
|
|
404
|
+
fs.writeFileSync(invalidFile, `const a;\na = 'asdf'`)
|
|
405
|
+
|
|
406
|
+
const result = spawnSync(
|
|
407
|
+
process.execPath,
|
|
408
|
+
[CLI_PATH, validFile, invalidFile, "--check"],
|
|
409
|
+
{
|
|
410
|
+
encoding: "utf8",
|
|
411
|
+
},
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
assert.match(result.stderr, /✗ Error:/, "displays error for invalid file")
|
|
415
|
+
assert.match(result.stdout, /valid\.js/, "processes valid file")
|
|
416
|
+
assert.equal(result.status, 1, "exits with 1 when any file has errors")
|
|
374
417
|
})
|
|
375
418
|
|
|
376
419
|
test("handle mixed directory and file arguments", () => {
|
|
@@ -388,7 +431,36 @@ describe("CLI", () => {
|
|
|
388
431
|
|
|
389
432
|
assert.match(fs.readFileSync(file1, "utf8"), /const x = 1/, "transforms file1")
|
|
390
433
|
assert.match(fs.readFileSync(file2, "utf8"), /const y = 2/, "transforms file2")
|
|
391
|
-
assert.match(result.stdout, /2
|
|
434
|
+
assert.match(result.stdout, /2 files upgraded/, "reports 2 files upgraded")
|
|
435
|
+
assert.equal(result.status, 0, "exits successfully")
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
test("show individual file markers for mixed results", () => {
|
|
439
|
+
const file1 = path.join(tempDir, "test1.js")
|
|
440
|
+
const file2 = path.join(tempDir, "test2.js")
|
|
441
|
+
fs.writeFileSync(file1, `var x = 1;`)
|
|
442
|
+
fs.writeFileSync(file2, `const y = 2;`) // Already upgraded
|
|
443
|
+
|
|
444
|
+
const result = spawnSync(process.execPath, [CLI_PATH, file1, file2, "--write"], {
|
|
445
|
+
encoding: "utf8",
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
assert.match(result.stdout, /✓/, "shows check mark for modified file")
|
|
449
|
+
assert.match(result.stdout, /test2\.js/, "shows unmodified file path")
|
|
392
450
|
assert.equal(result.status, 0, "exits successfully")
|
|
393
451
|
})
|
|
452
|
+
|
|
453
|
+
test("exit with 1 on file write errors", () => {
|
|
454
|
+
const testFile = path.join(tempDir, "test.js")
|
|
455
|
+
fs.writeFileSync(testFile, `var x = 1;`)
|
|
456
|
+
// Make file read-only to trigger write error
|
|
457
|
+
fs.chmodSync(testFile, 0o444)
|
|
458
|
+
|
|
459
|
+
const result = spawnSync(process.execPath, [CLI_PATH, testFile, "--write"], {
|
|
460
|
+
encoding: "utf8",
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
assert.match(result.stderr, /✗ Error:/, "displays error for write issues")
|
|
464
|
+
assert.equal(result.status, 1, "exits with 1 on write errors")
|
|
465
|
+
})
|
|
394
466
|
})
|
|
@@ -5,20 +5,12 @@ import { transform } from "../src/index.js"
|
|
|
5
5
|
describe("newly-available", () => {
|
|
6
6
|
describe("Promise.try", () => {
|
|
7
7
|
test("transforms resolve call with argument", () => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"newly-available",
|
|
12
|
-
).modified,
|
|
13
|
-
"transform Promise constructor to Promise.try",
|
|
14
|
-
)
|
|
15
|
-
assert.match(
|
|
16
|
-
transform(
|
|
17
|
-
`const p = new Promise((resolve) => resolve(getData()));`,
|
|
18
|
-
"newly-available",
|
|
19
|
-
).code,
|
|
20
|
-
/Promise\.try/,
|
|
8
|
+
const result = transform(
|
|
9
|
+
`const p = new Promise((resolve) => resolve(getData()));`,
|
|
10
|
+
"newly-available",
|
|
21
11
|
)
|
|
12
|
+
assert(result.modified, "transform Promise constructor to Promise.try")
|
|
13
|
+
assert.match(result.code, /Promise\.try/)
|
|
22
14
|
})
|
|
23
15
|
|
|
24
16
|
test("not available in widely-available baseline", () => {
|
|
@@ -33,347 +25,199 @@ describe("newly-available", () => {
|
|
|
33
25
|
})
|
|
34
26
|
|
|
35
27
|
test("transforms function passed to resolve", () => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"newly-available",
|
|
40
|
-
).modified,
|
|
28
|
+
const result = transform(
|
|
29
|
+
`const p = new Promise((resolve) => setTimeout(resolve));`,
|
|
30
|
+
"newly-available",
|
|
41
31
|
)
|
|
32
|
+
assert(result.modified)
|
|
42
33
|
assert.match(
|
|
43
|
-
|
|
44
|
-
`const p = new Promise((resolve) => setTimeout(resolve));`,
|
|
45
|
-
"newly-available",
|
|
46
|
-
).code,
|
|
34
|
+
result.code,
|
|
47
35
|
/Promise\.try\(setTimeout\)/,
|
|
48
36
|
"transform to Promise.try(setTimeout) not Promise.try(() => setTimeout(resolve))",
|
|
49
37
|
)
|
|
50
|
-
assert.doesNotMatch(
|
|
51
|
-
transform(
|
|
52
|
-
`const p = new Promise((resolve) => setTimeout(resolve));`,
|
|
53
|
-
"newly-available",
|
|
54
|
-
).code,
|
|
55
|
-
/resolve/,
|
|
56
|
-
)
|
|
38
|
+
assert.doesNotMatch(result.code, /resolve/)
|
|
57
39
|
})
|
|
58
40
|
|
|
59
41
|
test("skip when awaited", () => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
`async function foo() {
|
|
63
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
64
|
-
}`,
|
|
65
|
-
"newly-available",
|
|
66
|
-
).modified,
|
|
67
|
-
"do not transform awaited Promises",
|
|
68
|
-
)
|
|
69
|
-
assert.match(
|
|
70
|
-
transform(
|
|
71
|
-
`async function foo() {
|
|
72
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
73
|
-
}`,
|
|
74
|
-
"newly-available",
|
|
75
|
-
).code,
|
|
76
|
-
/await new Promise/,
|
|
77
|
-
)
|
|
78
|
-
assert.doesNotMatch(
|
|
79
|
-
transform(
|
|
80
|
-
`async function foo() {
|
|
42
|
+
const result = transform(
|
|
43
|
+
`async function foo() {
|
|
81
44
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
82
45
|
}`,
|
|
83
|
-
|
|
84
|
-
).code,
|
|
85
|
-
/Promise\.try/,
|
|
46
|
+
"newly-available",
|
|
86
47
|
)
|
|
48
|
+
assert(!result.modified, "do not transform awaited Promises")
|
|
49
|
+
assert.match(result.code, /await new Promise/)
|
|
50
|
+
assert.doesNotMatch(result.code, /Promise\.try/)
|
|
87
51
|
})
|
|
88
52
|
|
|
89
53
|
test("skip non-Promise constructors", () => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
"newly-available",
|
|
94
|
-
).modified,
|
|
95
|
-
)
|
|
96
|
-
assert.match(
|
|
97
|
-
transform(
|
|
98
|
-
`const p = new MyPromise((resolve) => resolve(getData()));`,
|
|
99
|
-
"newly-available",
|
|
100
|
-
).code,
|
|
101
|
-
/new MyPromise/,
|
|
54
|
+
const result = transform(
|
|
55
|
+
`const p = new MyPromise((resolve) => resolve(getData()));`,
|
|
56
|
+
"newly-available",
|
|
102
57
|
)
|
|
58
|
+
assert(!result.modified)
|
|
59
|
+
assert.match(result.code, /new MyPromise/)
|
|
103
60
|
})
|
|
104
61
|
|
|
105
62
|
test("skip with 0 arguments", () => {
|
|
106
|
-
|
|
107
|
-
assert.
|
|
108
|
-
|
|
109
|
-
/new Promise\(\)/,
|
|
110
|
-
)
|
|
63
|
+
const result = transform(`const p = new Promise();`, "newly-available")
|
|
64
|
+
assert(!result.modified)
|
|
65
|
+
assert.match(result.code, /new Promise\(\)/)
|
|
111
66
|
})
|
|
112
67
|
|
|
113
68
|
test("skip with multiple arguments", () => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
"newly-available",
|
|
118
|
-
).modified,
|
|
119
|
-
)
|
|
120
|
-
assert.match(
|
|
121
|
-
transform(
|
|
122
|
-
`const p = new Promise((resolve) => resolve(1), extraArg);`,
|
|
123
|
-
"newly-available",
|
|
124
|
-
).code,
|
|
125
|
-
/new Promise/,
|
|
69
|
+
const result = transform(
|
|
70
|
+
`const p = new Promise((resolve) => resolve(1), extraArg);`,
|
|
71
|
+
"newly-available",
|
|
126
72
|
)
|
|
73
|
+
assert(!result.modified)
|
|
74
|
+
assert.match(result.code, /new Promise/)
|
|
127
75
|
})
|
|
128
76
|
|
|
129
77
|
test("skip with non-function argument", () => {
|
|
130
|
-
|
|
131
|
-
assert.
|
|
132
|
-
|
|
133
|
-
/new Promise\(executor\)/,
|
|
134
|
-
)
|
|
78
|
+
const result = transform(`const p = new Promise(executor);`, "newly-available")
|
|
79
|
+
assert(!result.modified)
|
|
80
|
+
assert.match(result.code, /new Promise\(executor\)/)
|
|
135
81
|
})
|
|
136
82
|
|
|
137
83
|
test("skip with 0 params", () => {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
"newly-available",
|
|
142
|
-
).modified,
|
|
143
|
-
)
|
|
144
|
-
assert.match(
|
|
145
|
-
transform(
|
|
146
|
-
`const p = new Promise(() => console.log('test'));`,
|
|
147
|
-
"newly-available",
|
|
148
|
-
).code,
|
|
149
|
-
/new Promise/,
|
|
84
|
+
const result = transform(
|
|
85
|
+
`const p = new Promise(() => console.log('test'));`,
|
|
86
|
+
"newly-available",
|
|
150
87
|
)
|
|
88
|
+
assert(!result.modified)
|
|
89
|
+
assert.match(result.code, /new Promise/)
|
|
151
90
|
})
|
|
152
91
|
|
|
153
92
|
test("skip with more than 2 params", () => {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
"newly-available",
|
|
158
|
-
).modified,
|
|
159
|
-
)
|
|
160
|
-
assert.match(
|
|
161
|
-
transform(
|
|
162
|
-
`const p = new Promise((resolve, reject, extra) => resolve(1));`,
|
|
163
|
-
"newly-available",
|
|
164
|
-
).code,
|
|
165
|
-
/new Promise/,
|
|
93
|
+
const result = transform(
|
|
94
|
+
`const p = new Promise((resolve, reject, extra) => resolve(1));`,
|
|
95
|
+
"newly-available",
|
|
166
96
|
)
|
|
97
|
+
assert(!result.modified)
|
|
98
|
+
assert.match(result.code, /new Promise/)
|
|
167
99
|
})
|
|
168
100
|
|
|
169
101
|
test("transforms block statement with resolve call", () => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
"newly-available",
|
|
174
|
-
).modified,
|
|
175
|
-
)
|
|
176
|
-
assert.match(
|
|
177
|
-
transform(
|
|
178
|
-
`const p = new Promise((resolve) => { resolve(getData()); });`,
|
|
179
|
-
"newly-available",
|
|
180
|
-
).code,
|
|
181
|
-
/Promise\.try/,
|
|
102
|
+
const result = transform(
|
|
103
|
+
`const p = new Promise((resolve) => { resolve(getData()); });`,
|
|
104
|
+
"newly-available",
|
|
182
105
|
)
|
|
106
|
+
assert(result.modified)
|
|
107
|
+
assert.match(result.code, /Promise\.try/)
|
|
183
108
|
})
|
|
184
109
|
|
|
185
110
|
test("skip with arrow function expression body as function call", () => {
|
|
111
|
+
const result = transform(
|
|
112
|
+
`const p = new Promise((resolve) => computeValue());`,
|
|
113
|
+
"newly-available",
|
|
114
|
+
)
|
|
186
115
|
assert(
|
|
187
|
-
!
|
|
188
|
-
`const p = new Promise((resolve) => computeValue());`,
|
|
189
|
-
"newly-available",
|
|
190
|
-
).modified,
|
|
116
|
+
!result.modified,
|
|
191
117
|
"do not transform because computeValue() is not calling resolve",
|
|
192
118
|
)
|
|
193
|
-
assert.match(
|
|
194
|
-
transform(
|
|
195
|
-
`const p = new Promise((resolve) => computeValue());`,
|
|
196
|
-
"newly-available",
|
|
197
|
-
).code,
|
|
198
|
-
/new Promise/,
|
|
199
|
-
)
|
|
119
|
+
assert.match(result.code, /new Promise/)
|
|
200
120
|
})
|
|
201
121
|
|
|
202
122
|
test("skip with arrow function returning a value directly", () => {
|
|
123
|
+
const result = transform(
|
|
124
|
+
`const p = new Promise((resolve) => someFunction(arg1, arg2));`,
|
|
125
|
+
"newly-available",
|
|
126
|
+
)
|
|
203
127
|
assert(
|
|
204
|
-
!
|
|
205
|
-
`const p = new Promise((resolve) => someFunction(arg1, arg2));`,
|
|
206
|
-
"newly-available",
|
|
207
|
-
).modified,
|
|
128
|
+
!result.modified,
|
|
208
129
|
"do not transform function call that doesn't involve resolve",
|
|
209
130
|
)
|
|
210
|
-
assert.match(
|
|
211
|
-
transform(
|
|
212
|
-
`const p = new Promise((resolve) => someFunction(arg1, arg2));`,
|
|
213
|
-
"newly-available",
|
|
214
|
-
).code,
|
|
215
|
-
/new Promise/,
|
|
216
|
-
)
|
|
131
|
+
assert.match(result.code, /new Promise/)
|
|
217
132
|
})
|
|
218
133
|
|
|
219
134
|
test("skip with non-call expression body", () => {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
)
|
|
224
|
-
assert.match(
|
|
225
|
-
transform(`const p = new Promise((resolve) => someValue);`, "newly-available")
|
|
226
|
-
.code,
|
|
227
|
-
/new Promise/,
|
|
135
|
+
const result = transform(
|
|
136
|
+
`const p = new Promise((resolve) => someValue);`,
|
|
137
|
+
"newly-available",
|
|
228
138
|
)
|
|
139
|
+
assert(!result.modified)
|
|
140
|
+
assert.match(result.code, /new Promise/)
|
|
229
141
|
})
|
|
230
142
|
|
|
231
143
|
test("skip with wrong number of arguments to resolve", () => {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
"newly-available",
|
|
236
|
-
).modified,
|
|
237
|
-
)
|
|
238
|
-
assert.match(
|
|
239
|
-
transform(
|
|
240
|
-
`const p = new Promise((resolve) => func(resolve, extra));`,
|
|
241
|
-
"newly-available",
|
|
242
|
-
).code,
|
|
243
|
-
/new Promise/,
|
|
144
|
+
const result = transform(
|
|
145
|
+
`const p = new Promise((resolve) => func(resolve, extra));`,
|
|
146
|
+
"newly-available",
|
|
244
147
|
)
|
|
148
|
+
assert(!result.modified)
|
|
149
|
+
assert.match(result.code, /new Promise/)
|
|
245
150
|
})
|
|
246
151
|
|
|
247
152
|
test("skip with non-identifier resolve", () => {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
)
|
|
252
|
-
assert.match(
|
|
253
|
-
transform(`const p = new Promise((resolve) => func(123));`, "newly-available")
|
|
254
|
-
.code,
|
|
255
|
-
/new Promise/,
|
|
153
|
+
const result = transform(
|
|
154
|
+
`const p = new Promise((resolve) => func(123));`,
|
|
155
|
+
"newly-available",
|
|
256
156
|
)
|
|
157
|
+
assert(!result.modified)
|
|
158
|
+
assert.match(result.code, /new Promise/)
|
|
257
159
|
})
|
|
258
160
|
|
|
259
161
|
test("skip with resolve call with 0 arguments", () => {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
)
|
|
264
|
-
assert.match(
|
|
265
|
-
transform(`const p = new Promise((resolve) => resolve());`, "newly-available")
|
|
266
|
-
.code,
|
|
267
|
-
/new Promise/,
|
|
162
|
+
const result = transform(
|
|
163
|
+
`const p = new Promise((resolve) => resolve());`,
|
|
164
|
+
"newly-available",
|
|
268
165
|
)
|
|
166
|
+
assert(!result.modified)
|
|
167
|
+
assert.match(result.code, /new Promise/)
|
|
269
168
|
})
|
|
270
169
|
|
|
271
170
|
test("skip with block with multiple statements", () => {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
`const p = new Promise((resolve) => {
|
|
171
|
+
const result = transform(
|
|
172
|
+
`const p = new Promise((resolve) => {
|
|
275
173
|
const data = getData();
|
|
276
174
|
resolve(data);
|
|
277
175
|
});`,
|
|
278
|
-
|
|
279
|
-
).modified,
|
|
280
|
-
)
|
|
281
|
-
assert.match(
|
|
282
|
-
transform(
|
|
283
|
-
`const p = new Promise((resolve) => {
|
|
284
|
-
const data = getData();
|
|
285
|
-
resolve(data);
|
|
286
|
-
});`,
|
|
287
|
-
"newly-available",
|
|
288
|
-
).code,
|
|
289
|
-
/new Promise/,
|
|
176
|
+
"newly-available",
|
|
290
177
|
)
|
|
178
|
+
assert(!result.modified)
|
|
179
|
+
assert.match(result.code, /new Promise/)
|
|
291
180
|
})
|
|
292
181
|
|
|
293
182
|
test("skip with block with non-expression statement", () => {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
`const p = new Promise((resolve) => {
|
|
183
|
+
const result = transform(
|
|
184
|
+
`const p = new Promise((resolve) => {
|
|
297
185
|
if (true) resolve(1);
|
|
298
186
|
});`,
|
|
299
|
-
|
|
300
|
-
).modified,
|
|
301
|
-
)
|
|
302
|
-
assert.match(
|
|
303
|
-
transform(
|
|
304
|
-
`const p = new Promise((resolve) => {
|
|
305
|
-
if (true) resolve(1);
|
|
306
|
-
});`,
|
|
307
|
-
"newly-available",
|
|
308
|
-
).code,
|
|
309
|
-
/new Promise/,
|
|
187
|
+
"newly-available",
|
|
310
188
|
)
|
|
189
|
+
assert(!result.modified)
|
|
190
|
+
assert.match(result.code, /new Promise/)
|
|
311
191
|
})
|
|
312
192
|
|
|
313
193
|
test("transforms function expression", () => {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
"newly-available",
|
|
318
|
-
).modified,
|
|
319
|
-
)
|
|
320
|
-
assert.match(
|
|
321
|
-
transform(
|
|
322
|
-
`const p = new Promise(function(resolve) { resolve(getData()); });`,
|
|
323
|
-
"newly-available",
|
|
324
|
-
).code,
|
|
325
|
-
/Promise\.try/,
|
|
194
|
+
const result = transform(
|
|
195
|
+
`const p = new Promise(function(resolve) { resolve(getData()); });`,
|
|
196
|
+
"newly-available",
|
|
326
197
|
)
|
|
198
|
+
assert(result.modified)
|
|
199
|
+
assert.match(result.code, /Promise\.try/)
|
|
327
200
|
})
|
|
328
201
|
|
|
329
202
|
test("transforms with both resolve and reject params", () => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
"newly-available",
|
|
334
|
-
).modified,
|
|
335
|
-
)
|
|
336
|
-
assert.match(
|
|
337
|
-
transform(
|
|
338
|
-
`const p = new Promise((resolve, reject) => resolve(getData()));`,
|
|
339
|
-
"newly-available",
|
|
340
|
-
).code,
|
|
341
|
-
/Promise\.try/,
|
|
203
|
+
const result = transform(
|
|
204
|
+
`const p = new Promise((resolve, reject) => resolve(getData()));`,
|
|
205
|
+
"newly-available",
|
|
342
206
|
)
|
|
207
|
+
assert(result.modified)
|
|
208
|
+
assert.match(result.code, /Promise\.try/)
|
|
343
209
|
})
|
|
344
210
|
|
|
345
211
|
test("tracks line numbers correctly", () => {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
`// Line 1
|
|
349
|
-
const p = new Promise((resolve) => resolve(getData()));`,
|
|
350
|
-
"newly-available",
|
|
351
|
-
).modified,
|
|
352
|
-
)
|
|
353
|
-
assert.equal(
|
|
354
|
-
transform(
|
|
355
|
-
`// Line 1
|
|
356
|
-
const p = new Promise((resolve) => resolve(getData()));`,
|
|
357
|
-
"newly-available",
|
|
358
|
-
).changes.length,
|
|
359
|
-
1,
|
|
360
|
-
)
|
|
361
|
-
assert.equal(
|
|
362
|
-
transform(
|
|
363
|
-
`// Line 1
|
|
364
|
-
const p = new Promise((resolve) => resolve(getData()));`,
|
|
365
|
-
"newly-available",
|
|
366
|
-
).changes[0].type,
|
|
367
|
-
"promiseTry",
|
|
368
|
-
)
|
|
369
|
-
assert.equal(
|
|
370
|
-
transform(
|
|
371
|
-
`// Line 1
|
|
212
|
+
const result = transform(
|
|
213
|
+
`// Line 1
|
|
372
214
|
const p = new Promise((resolve) => resolve(getData()));`,
|
|
373
|
-
|
|
374
|
-
).changes[0].line,
|
|
375
|
-
2,
|
|
215
|
+
"newly-available",
|
|
376
216
|
)
|
|
217
|
+
assert(result.modified)
|
|
218
|
+
assert.equal(result.changes.length, 1)
|
|
219
|
+
assert.equal(result.changes[0].type, "promiseTry")
|
|
220
|
+
assert.equal(result.changes[0].line, 2)
|
|
377
221
|
})
|
|
378
222
|
})
|
|
379
223
|
})
|
|
@@ -1726,6 +1726,22 @@ const result = [1, 2].concat(other);`)
|
|
|
1726
1726
|
})
|
|
1727
1727
|
|
|
1728
1728
|
describe("general", () => {
|
|
1729
|
+
const input = `var x = 1;`
|
|
1730
|
+
|
|
1731
|
+
test("baseline widely-available", () => {
|
|
1732
|
+
const result = transform(input)
|
|
1733
|
+
|
|
1734
|
+
assert(result.modified, "transform with baseline widely-available")
|
|
1735
|
+
assert.match(result.code, /const x = 1/)
|
|
1736
|
+
})
|
|
1737
|
+
|
|
1738
|
+
test("baseline newly-available", () => {
|
|
1739
|
+
const result = transform(input, "newly-available")
|
|
1740
|
+
|
|
1741
|
+
assert(result.modified, "transform with baseline newly-available")
|
|
1742
|
+
assert.match(result.code, /const x = 1/)
|
|
1743
|
+
})
|
|
1744
|
+
|
|
1729
1745
|
test("no changes", () => {
|
|
1730
1746
|
const result = transform(`
|
|
1731
1747
|
const x = 1;
|
|
@@ -1745,18 +1761,4 @@ describe("general", () => {
|
|
|
1745
1761
|
assert.match(result.code, /const userName/)
|
|
1746
1762
|
assert.match(result.code, /`Hello \$\{userName\}`/)
|
|
1747
1763
|
})
|
|
1748
|
-
|
|
1749
|
-
test("baseline widely-available", () => {
|
|
1750
|
-
const result = transform(`var x = 1;`)
|
|
1751
|
-
|
|
1752
|
-
assert(result.modified, "transform with baseline widely-available")
|
|
1753
|
-
assert.match(result.code, /const x = 1/)
|
|
1754
|
-
})
|
|
1755
|
-
|
|
1756
|
-
test("baseline newly-available", () => {
|
|
1757
|
-
const result = transform(`var x = 1;`, "newly-available")
|
|
1758
|
-
|
|
1759
|
-
assert(result.modified, "transform with baseline newly-available")
|
|
1760
|
-
assert.match(result.code, /const x = 1/)
|
|
1761
|
-
})
|
|
1762
1764
|
})
|