esupgrade 2025.0.0 → 2025.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Transform new Promise((resolve, reject) => { resolve(fn()) }) to Promise.try(fn)
3
+ */
4
+ export function promiseTry(j, root) {
5
+ let modified = false
6
+ const changes = []
7
+
8
+ root
9
+ .find(j.NewExpression)
10
+ .filter((path) => {
11
+ const node = path.node
12
+ // Check if this is new Promise(...)
13
+ if (!j.Identifier.check(node.callee) || node.callee.name !== "Promise") {
14
+ return false
15
+ }
16
+
17
+ // Skip if this Promise is being awaited
18
+ // Check if parent is an AwaitExpression
19
+ if (path.parent && j.AwaitExpression.check(path.parent.node)) {
20
+ return false
21
+ }
22
+
23
+ // Check if there's one argument that's a function
24
+ if (node.arguments.length !== 1) {
25
+ return false
26
+ }
27
+
28
+ const executor = node.arguments[0]
29
+ if (
30
+ !j.ArrowFunctionExpression.check(executor) &&
31
+ !j.FunctionExpression.check(executor)
32
+ ) {
33
+ return false
34
+ }
35
+
36
+ // Check if function has 1-2 params (resolve, reject)
37
+ if (executor.params.length < 1 || executor.params.length > 2) {
38
+ return false
39
+ }
40
+
41
+ // Check if body is a block with single resolve() call or expression body
42
+ const body = executor.body
43
+
44
+ // For arrow functions with expression body: (resolve) => expr
45
+ if (!j.BlockStatement.check(body)) {
46
+ // Check if expression is resolve(something) or func(resolve)
47
+ if (j.CallExpression.check(body)) {
48
+ const callExpr = body
49
+ // Pattern: (resolve) => resolve(expr)
50
+ if (
51
+ j.Identifier.check(callExpr.callee) &&
52
+ j.Identifier.check(executor.params[0]) &&
53
+ callExpr.callee.name === executor.params[0].name &&
54
+ callExpr.arguments.length > 0
55
+ ) {
56
+ return true
57
+ }
58
+ // Pattern: (resolve) => func(resolve) - resolve must be the ONLY argument
59
+ if (
60
+ callExpr.arguments.length === 1 &&
61
+ j.Identifier.check(callExpr.arguments[0]) &&
62
+ j.Identifier.check(executor.params[0]) &&
63
+ callExpr.arguments[0].name === executor.params[0].name
64
+ ) {
65
+ return true
66
+ }
67
+ }
68
+ return false
69
+ }
70
+
71
+ // For functions with block body containing single resolve(expr) call
72
+ if (body.body.length === 1 && j.ExpressionStatement.check(body.body[0])) {
73
+ const expr = body.body[0].expression
74
+ if (
75
+ j.CallExpression.check(expr) &&
76
+ j.Identifier.check(expr.callee) &&
77
+ expr.callee.name === executor.params[0].name
78
+ ) {
79
+ return true
80
+ }
81
+ }
82
+
83
+ return false
84
+ })
85
+ .forEach((path) => {
86
+ const node = path.node
87
+ const executor = node.arguments[0]
88
+ const body = executor.body
89
+ const resolveParam = executor.params[0]
90
+
91
+ let expression
92
+ let tryArg
93
+
94
+ // Extract the expression
95
+ if (!j.BlockStatement.check(body)) {
96
+ // Arrow function with expression body: (resolve) => expr
97
+ expression = body
98
+
99
+ // Check if expression is a call where resolve is passed as the only argument
100
+ // e.g., (resolve) => setTimeout(resolve) should become Promise.try(setTimeout)
101
+ if (
102
+ j.CallExpression.check(expression) &&
103
+ expression.arguments.length === 1 &&
104
+ j.Identifier.check(expression.arguments[0]) &&
105
+ j.Identifier.check(resolveParam) &&
106
+ expression.arguments[0].name === resolveParam.name
107
+ ) {
108
+ // Use the callee directly (e.g., setTimeout)
109
+ tryArg = expression.callee
110
+ }
111
+ // Check if expression is resolve(something)
112
+ else if (
113
+ j.CallExpression.check(expression) &&
114
+ j.Identifier.check(expression.callee) &&
115
+ j.Identifier.check(resolveParam) &&
116
+ expression.callee.name === resolveParam.name &&
117
+ expression.arguments.length > 0
118
+ ) {
119
+ // Extract the argument from resolve(arg) and wrap in arrow function
120
+ expression = expression.arguments[0]
121
+ tryArg = j.arrowFunctionExpression([], expression)
122
+ }
123
+ // Note: No else needed - filter ensures only the above patterns reach here
124
+ } else if (body.body.length === 1 && j.ExpressionStatement.check(body.body[0])) {
125
+ // Block with resolve(expr) call
126
+ const callExpr = body.body[0].expression
127
+ if (j.CallExpression.check(callExpr) && callExpr.arguments.length > 0) {
128
+ expression = callExpr.arguments[0]
129
+ // Wrap expression in arrow function for Promise.try
130
+ tryArg = j.arrowFunctionExpression([], expression)
131
+ }
132
+ }
133
+
134
+ if (tryArg) {
135
+ // Create Promise.try(fn)
136
+ const promiseTryCall = j.callExpression(
137
+ j.memberExpression(j.identifier("Promise"), j.identifier("try")),
138
+ [tryArg],
139
+ )
140
+
141
+ j(path).replaceWith(promiseTryCall)
142
+
143
+ modified = true
144
+ if (node.loc) {
145
+ changes.push({
146
+ type: "promiseTry",
147
+ line: node.loc.start.line,
148
+ })
149
+ }
150
+ }
151
+ })
152
+
153
+ return { modified, changes }
154
+ }
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Transform var to const
3
+ */
4
+ export function varToConst(j, root) {
5
+ let modified = false
6
+ const changes = []
7
+
8
+ root.find(j.VariableDeclaration, { kind: "var" }).forEach((path) => {
9
+ path.node.kind = "const"
10
+ modified = true
11
+ if (path.node.loc) {
12
+ changes.push({
13
+ type: "varToConst",
14
+ line: path.node.loc.start.line,
15
+ })
16
+ }
17
+ })
18
+
19
+ return { modified, changes }
20
+ }
21
+
22
+ /**
23
+ * Transform string concatenation to template literals
24
+ */
25
+ export function concatToTemplateLiteral(j, root) {
26
+ let modified = false
27
+ const changes = []
28
+
29
+ root
30
+ .find(j.BinaryExpression, { operator: "+" })
31
+ .filter((path) => {
32
+ // Only transform if at least one operand is a string literal
33
+ const hasStringLiteral = (node) => {
34
+ if (
35
+ j.StringLiteral.check(node) ||
36
+ (j.Literal.check(node) && typeof node.value === "string")
37
+ ) {
38
+ return true
39
+ }
40
+ if (j.BinaryExpression.check(node) && node.operator === "+") {
41
+ return hasStringLiteral(node.left) || hasStringLiteral(node.right)
42
+ }
43
+ return false
44
+ }
45
+ return hasStringLiteral(path.node)
46
+ })
47
+ .forEach((path) => {
48
+ const parts = []
49
+ const expressions = []
50
+
51
+ const flatten = (node) => {
52
+ if (j.BinaryExpression.check(node) && node.operator === "+") {
53
+ flatten(node.left)
54
+ flatten(node.right)
55
+ } else if (
56
+ j.StringLiteral.check(node) ||
57
+ (j.Literal.check(node) && typeof node.value === "string")
58
+ ) {
59
+ // Add string literal value
60
+ if (parts.length === 0 || expressions.length >= parts.length) {
61
+ parts.push(node.value)
62
+ } else {
63
+ parts[parts.length - 1] += node.value
64
+ }
65
+ } else {
66
+ // Add expression
67
+ if (parts.length === 0) {
68
+ parts.push("")
69
+ }
70
+ expressions.push(node)
71
+ }
72
+ }
73
+
74
+ flatten(path.node)
75
+
76
+ // Ensure we have the right number of quasis (one more than expressions)
77
+ while (parts.length <= expressions.length) {
78
+ parts.push("")
79
+ }
80
+
81
+ // Create template literal
82
+ const quasis = parts.map((part, i) =>
83
+ j.templateElement({ raw: part, cooked: part }, i === parts.length - 1),
84
+ )
85
+
86
+ const templateLiteral = j.templateLiteral(quasis, expressions)
87
+ j(path).replaceWith(templateLiteral)
88
+
89
+ modified = true
90
+ })
91
+
92
+ return { modified, changes }
93
+ }
94
+
95
+ /**
96
+ * Transform Object.assign({}, ...) to object spread
97
+ */
98
+ export function objectAssignToSpread(j, root) {
99
+ let modified = false
100
+ const changes = []
101
+
102
+ root
103
+ .find(j.CallExpression, {
104
+ callee: {
105
+ type: "MemberExpression",
106
+ object: { name: "Object" },
107
+ property: { name: "assign" },
108
+ },
109
+ })
110
+ .filter((path) => {
111
+ // First argument must be empty object literal
112
+ const firstArg = path.node.arguments[0]
113
+ return j.ObjectExpression.check(firstArg) && firstArg.properties.length === 0
114
+ })
115
+ .forEach((path) => {
116
+ const spreadProperties = path.node.arguments
117
+ .slice(1)
118
+ .map((arg) => j.spreadElement(arg))
119
+
120
+ const objectExpression = j.objectExpression(spreadProperties)
121
+ j(path).replaceWith(objectExpression)
122
+
123
+ modified = true
124
+ })
125
+
126
+ return { modified, changes }
127
+ }
128
+
129
+ /**
130
+ * Transform Array.from().forEach() to for...of
131
+ */
132
+ export function arrayFromForEachToForOf(j, root) {
133
+ let modified = false
134
+ const changes = []
135
+
136
+ root
137
+ .find(j.CallExpression)
138
+ .filter((path) => {
139
+ const node = path.node
140
+ // Check if this is a forEach call
141
+ if (
142
+ !j.MemberExpression.check(node.callee) ||
143
+ !j.Identifier.check(node.callee.property) ||
144
+ node.callee.property.name !== "forEach"
145
+ ) {
146
+ return false
147
+ }
148
+
149
+ // Check if the object is Array.from()
150
+ const object = node.callee.object
151
+ if (
152
+ !j.CallExpression.check(object) ||
153
+ !j.MemberExpression.check(object.callee) ||
154
+ !j.Identifier.check(object.callee.object) ||
155
+ object.callee.object.name !== "Array" ||
156
+ !j.Identifier.check(object.callee.property) ||
157
+ object.callee.property.name !== "from"
158
+ ) {
159
+ return false
160
+ }
161
+
162
+ return true
163
+ })
164
+ .forEach((path) => {
165
+ const node = path.node
166
+ const iterable = node.callee.object.arguments[0]
167
+ const callback = node.arguments[0]
168
+
169
+ // Only transform if callback is a function
170
+ if (
171
+ callback &&
172
+ (j.ArrowFunctionExpression.check(callback) ||
173
+ j.FunctionExpression.check(callback))
174
+ ) {
175
+ // Only transform if:
176
+ // 1. Callback has exactly 1 parameter (element only), OR
177
+ // 2. Callback has 2+ params AND first param is a destructuring pattern (e.g., [key, value])
178
+ // This handles cases like Array.from(Object.entries(obj)).forEach(([k, v]) => ...)
179
+ const params = callback.params
180
+ const canTransform =
181
+ params.length === 1 || (params.length >= 2 && j.ArrayPattern.check(params[0]))
182
+
183
+ if (canTransform) {
184
+ const itemParam = callback.params[0]
185
+ const body = callback.body
186
+
187
+ // Create for...of loop
188
+ const forOfLoop = j.forOfStatement(
189
+ j.variableDeclaration("const", [j.variableDeclarator(itemParam)]),
190
+ iterable,
191
+ j.BlockStatement.check(body)
192
+ ? body
193
+ : j.blockStatement([j.expressionStatement(body)]),
194
+ )
195
+
196
+ // Replace the expression statement containing the forEach call
197
+ const statement = path.parent
198
+ if (j.ExpressionStatement.check(statement.node)) {
199
+ j(statement).replaceWith(forOfLoop)
200
+
201
+ modified = true
202
+ if (node.loc) {
203
+ changes.push({
204
+ type: "arrayFromForEachToForOf",
205
+ line: node.loc.start.line,
206
+ })
207
+ }
208
+ }
209
+ }
210
+ }
211
+ })
212
+
213
+ return { modified, changes }
214
+ }
215
+
216
+ /**
217
+ * Transform for...of Object.keys() loops to for...in
218
+ */
219
+ export function forOfKeysToForIn(j, root) {
220
+ let modified = false
221
+ const changes = []
222
+
223
+ root
224
+ .find(j.ForOfStatement)
225
+ .filter((path) => {
226
+ const node = path.node
227
+ const right = node.right
228
+
229
+ // Check if iterating over Object.keys() call
230
+ if (
231
+ j.CallExpression.check(right) &&
232
+ j.MemberExpression.check(right.callee) &&
233
+ j.Identifier.check(right.callee.object) &&
234
+ right.callee.object.name === "Object" &&
235
+ j.Identifier.check(right.callee.property) &&
236
+ right.callee.property.name === "keys" &&
237
+ right.arguments.length === 1
238
+ ) {
239
+ return true
240
+ }
241
+
242
+ return false
243
+ })
244
+ .forEach((path) => {
245
+ const node = path.node
246
+ const left = node.left
247
+ const objectArg = node.right.arguments[0]
248
+ const body = node.body
249
+
250
+ // Create for...in loop
251
+ const forInLoop = j.forInStatement(left, objectArg, body)
252
+
253
+ j(path).replaceWith(forInLoop)
254
+
255
+ modified = true
256
+ if (node.loc) {
257
+ changes.push({
258
+ type: "forOfKeysToForIn",
259
+ line: node.loc.start.line,
260
+ })
261
+ }
262
+ })
263
+
264
+ return { modified, changes }
265
+ }