esupgrade 2025.3.0 → 2025.3.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/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
@@ -176,7 +176,7 @@ function processFiles(patterns, options) {
176
176
  const totalChanges = allChanges.length
177
177
 
178
178
  console.log(
179
- `${modifiedCount} file(s) need upgrading (${totalChanges} change${totalChanges !== 1 ? "s" : ""}, ${typeCount} type${typeCount !== 1 ? "s" : ""})`,
179
+ `${modifiedCount} file${modifiedCount !== 1 ? "s" : ""} need${modifiedCount === 1 ? "s" : ""} upgrading (${totalChanges} change${totalChanges !== 1 ? "s" : ""}, ${typeCount} type${typeCount !== 1 ? "s" : ""})`,
180
180
  )
181
181
  if (options.write) {
182
182
  console.log("Changes have been written")
@@ -187,7 +187,7 @@ function processFiles(patterns, options) {
187
187
  } else {
188
188
  console.log("")
189
189
  if (modifiedCount > 0) {
190
- console.log(`✓ ${modifiedCount} file(s) upgraded`)
190
+ console.log(`✓ ${modifiedCount} file${modifiedCount !== 1 ? "s" : ""} upgraded`)
191
191
  } else {
192
192
  console.log("All files are up to date")
193
193
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "esupgrade",
3
- "version": "2025.3.0",
3
+ "version": "2025.3.2",
4
4
  "description": "Auto-upgrade your JavaScript syntax",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -68,17 +68,32 @@ export function concatToTemplateLiteral(j, root) {
68
68
  return false
69
69
  }
70
70
 
71
- const addStringPart = (value) => {
71
+ // Helper to get the raw value of a string literal (preserving escape sequences)
72
+ const getRawStringValue = (node) => {
73
+ // In string literals, backslashes are used for escape sequences
74
+ // In template literals, backslashes in the raw value also need escaping
75
+ // So we need to double the backslashes: \\ -> \\\\
76
+ // Note: node.extra.rawValue is always defined for string literals with the current parser
77
+ return node.extra.rawValue.replace(/\\/g, "\\\\")
78
+ }
79
+
80
+ const addStringPart = (stringNode) => {
81
+ // Store both the raw and cooked values
82
+ const rawValue = getRawStringValue(stringNode)
83
+ const cookedValue = stringNode.value
84
+
72
85
  if (parts.length === 0 || expressions.length >= parts.length) {
73
- parts.push(value)
86
+ parts.push({ raw: rawValue, cooked: cookedValue })
74
87
  } else {
75
- parts[parts.length - 1] += value
88
+ const lastPart = parts[parts.length - 1]
89
+ lastPart.raw += rawValue
90
+ lastPart.cooked += cookedValue
76
91
  }
77
92
  }
78
93
 
79
94
  const addExpression = (expr) => {
80
95
  if (parts.length === 0) {
81
- parts.push("")
96
+ parts.push({ raw: "", cooked: "" })
82
97
  }
83
98
  expressions.push(expr)
84
99
  }
@@ -103,8 +118,8 @@ export function concatToTemplateLiteral(j, root) {
103
118
  // Left is also a + expression - recurse
104
119
  flatten(node.left, stringContext)
105
120
  } else if (isStringLiteral(node.left)) {
106
- // Left is a string literal
107
- addStringPart(node.left.value)
121
+ // Left is a string literal - use raw value to preserve escape sequences
122
+ addStringPart(node.left)
108
123
  } else {
109
124
  // Left is some other expression
110
125
  addExpression(node.left)
@@ -121,8 +136,8 @@ export function concatToTemplateLiteral(j, root) {
121
136
  flatten(node.right, rightInStringContext)
122
137
  }
123
138
  } else if (isStringLiteral(node.right)) {
124
- // Right is a string literal
125
- addStringPart(node.right.value)
139
+ // Right is a string literal - use raw value to preserve escape sequences
140
+ addStringPart(node.right)
126
141
  } else {
127
142
  // Right is some other expression
128
143
  addExpression(node.right)
@@ -135,12 +150,15 @@ export function concatToTemplateLiteral(j, root) {
135
150
 
136
151
  // Ensure we have the right number of quasis (one more than expressions)
137
152
  while (parts.length <= expressions.length) {
138
- parts.push("")
153
+ parts.push({ raw: "", cooked: "" })
139
154
  }
140
155
 
141
156
  // Create template literal
142
157
  const quasis = parts.map((part, i) =>
143
- j.templateElement({ raw: part, cooked: part }, i === parts.length - 1),
158
+ j.templateElement(
159
+ { raw: part.raw, cooked: part.cooked },
160
+ i === parts.length - 1,
161
+ ),
144
162
  )
145
163
 
146
164
  const templateLiteral = j.templateLiteral(quasis, expressions)
@@ -913,62 +931,6 @@ export function anonymousFunctionToArrow(j, root) {
913
931
  return found
914
932
  }
915
933
 
916
- // Helper to check if a node or its descendants use 'super'
917
- const usesSuper = (node) => {
918
- let found = false
919
-
920
- const visit = (n) => {
921
- if (!n || typeof n !== "object" || found) return
922
-
923
- // If we encounter a nested function, don't traverse into it
924
- // as it has its own 'super' binding context
925
- if (n.type === "FunctionExpression" || n.type === "FunctionDeclaration") {
926
- return
927
- }
928
-
929
- // Check if this is a 'super' node
930
- if (n.type === "Super") {
931
- found = true
932
- return
933
- }
934
-
935
- // Traverse all child nodes
936
- for (const key in n) {
937
- if (
938
- key === "loc" ||
939
- key === "start" ||
940
- key === "end" ||
941
- key === "tokens" ||
942
- key === "comments" ||
943
- key === "type"
944
- ) {
945
- continue
946
- }
947
- const value = n[key]
948
- if (Array.isArray(value)) {
949
- for (const item of value) {
950
- visit(item)
951
- if (found) return
952
- }
953
- } else if (value && typeof value === "object") {
954
- visit(value)
955
- }
956
- }
957
- }
958
-
959
- // Start visiting from the function body's child nodes
960
- // Don't check the body node itself, check its contents
961
- // Note: FunctionExpression.body is always a BlockStatement
962
- if (node.type === "BlockStatement" && node.body) {
963
- for (const statement of node.body) {
964
- visit(statement)
965
- if (found) break
966
- }
967
- }
968
-
969
- return found
970
- }
971
-
972
934
  root
973
935
  .find(j.FunctionExpression)
974
936
  .filter((path) => {
@@ -996,10 +958,8 @@ export function anonymousFunctionToArrow(j, root) {
996
958
  return false
997
959
  }
998
960
 
999
- // Skip if it uses 'super'
1000
- if (usesSuper(node.body)) {
1001
- return false
1002
- }
961
+ // Note: We don't need to check for 'super' because using super in a
962
+ // function expression is a syntax error and will never parse successfully
1003
963
 
1004
964
  return true
1005
965
  })