esupgrade 2025.16.0 → 2025.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,7 +12,7 @@ description: CodingJoe's digital clone following his coding guidelines and best
12
12
 
13
13
  You MUST ALWAYS follow the `naming-things` guidelines. Use the following command to access the guidelines:
14
14
  ```console
15
- curl -sSL https://raw.githubusercontent.com/codingjoe/naming-things/refs/heads/main/README.md | cat
15
+ curl -sSL https://raw.githubusercontent.com/codingjoe/naming-things/refs/heads/main/README.md | head -n 500
16
16
  ```
17
17
 
18
18
  You MUST ALWAYS read the `CONTRIBUTING.md` file before planning or writing any code.
@@ -1,15 +1,15 @@
1
+ YOUR PRIORITY IS TO ENSURE THE SAFETY OF ALL TRANSFORMATIONS.
2
+
1
3
  When writing code, you MUST ALWAYS follow the [naming guidelines](https://raw.githubusercontent.com/codingjoe/naming-things/refs/heads/main/README.md).
2
4
 
3
5
  Usage:
4
6
 
5
7
  ```console
6
- curl -sL https://raw.githubusercontent.com/codingjoe/naming-things/refs/heads/main/README.md
8
+ curl -sL https://raw.githubusercontent.com/codingjoe/naming-things/refs/heads/main/README.md | head -n 500
7
9
  ```
8
10
 
9
11
  All code must be fully tested with a 100% coverage. Unreachable code must be removed.
10
12
  Follow the [CONTRIBUTING.md](./CONTRIBUTING.md) guidelines for testing and linting.
11
- The test suite will fail if the line coverage is below 100%.
12
- The test coverage will be written to stdout when running the tests via `npm test | grep -v '100.00'`.
13
13
 
14
14
  All transformers must be documented in the README.md.
15
15
 
package/README.md CHANGED
@@ -47,9 +47,9 @@ npx esupgrade --help
47
47
  ```
48
48
 
49
49
  <picture>
50
- <source media="(prefers-color-scheme: dark)" srcset="https://web-platform-dx.github.io/web-features/assets/img/baseline-wordmark-dark.svg">
51
- <source media="(prefers-color-scheme: light)" srcset="https://web-platform-dx.github.io/web-features/assets/img/baseline-wordmark.svg">
52
- <img alt="Baseline: widely available" src="https://web-platform-dx.github.io/web-features/assets/img/baseline-wordmark.svg" height="32" align="right">
50
+ <source media="(prefers-color-scheme: dark)" srcset="https://web-platform-dx.github.io/assets/img/baseline-wordmark-dark.svg">
51
+ <source media="(prefers-color-scheme: light)" srcset="https://web-platform-dx.github.io/assets/img/baseline-wordmark.svg">
52
+ <img alt="Baseline: widely available" src="https://web-platform-dx.github.io/assets/img/baseline-wordmark.svg" height="32" align="right">
53
53
  </picture>
54
54
 
55
55
  ## Browser Support & Baseline
@@ -78,9 +78,9 @@ For more information about Baseline browser support, visit [web.dev/baseline][ba
78
78
  ## Transformations
79
79
 
80
80
  <picture>
81
- <source media="(prefers-color-scheme: dark)" srcset="https://web-platform-dx.github.io/web-features/assets/img/baseline-widely-word-dark.svg">
82
- <source media="(prefers-color-scheme: light)" srcset="https://web-platform-dx.github.io/web-features/assets/img/baseline-widely-word.svg">
83
- <img alt="Baseline: widely available" src="https://web-platform-dx.github.io/web-features/assets/img/baseline-widely-word.svg" height="32" align="right">
81
+ <source media="(prefers-color-scheme: dark)" srcset="https://web-platform-dx.github.io/assets/img/baseline-widely-word-dark.svg">
82
+ <source media="(prefers-color-scheme: light)" srcset="https://web-platform-dx.github.io/assets/img/baseline-widely-word.svg">
83
+ <img alt="Baseline: widely available" src="https://web-platform-dx.github.io/assets/img/baseline-widely-word.svg" height="32" align="right">
84
84
  </picture>
85
85
 
86
86
  ### Widely available
@@ -457,6 +457,21 @@ Transforms when:
457
457
  - The first statement in the callback assigns `obj[key]` to a variable
458
458
  - The object being accessed matches the object passed to Object.keys()
459
459
 
460
+ #### `Object.keys().map()` → [Object.values()][mdn-object-values]
461
+
462
+ ```diff
463
+ -Object.keys(obj).map(key => obj[key])
464
+ +Object.values(obj)
465
+ ```
466
+
467
+ Transforms `Object.keys(obj).map(key => obj[key])` patterns to use `Object.values()` directly. This eliminates redundant property lookups and expresses intent more clearly.
468
+
469
+ Transforms when:
470
+
471
+ - The callback has exactly one parameter (the key)
472
+ - The callback body returns `obj[key]` (expression or block with a single return statement)
473
+ - The object being accessed matches the object passed to Object.keys()
474
+
460
475
  #### `indexOf()` prefix check → [String.startsWith()][mdn-startswith]
461
476
 
462
477
  ```diff
@@ -561,9 +576,9 @@ Note: The `x = x || defaultValue` pattern is NOT transformed as it has different
561
576
  - The expression is a known promise (`fetch()`, `new Promise()`, or promise methods)
562
577
 
563
578
  <picture>
564
- <source media="(prefers-color-scheme: dark)" srcset="https://web-platform-dx.github.io/web-features/assets/img/baseline-newly-word-dark.svg">
565
- <source media="(prefers-color-scheme: light)" srcset="https://web-platform-dx.github.io/web-features/assets/img/baseline-newly-word.svg">
566
- <img alt="Baseline: Newly available" src="https://web-platform-dx.github.io/web-features/assets/img/baseline-newly-word.svg" height="32" align="right">
579
+ <source media="(prefers-color-scheme: dark)" srcset="https://web-platform-dx.github.io/assets/img/baseline-newly-word-dark.svg">
580
+ <source media="(prefers-color-scheme: light)" srcset="https://web-platform-dx.github.io/assets/img/baseline-newly-word.svg">
581
+ <img alt="Baseline: Newly available" src="https://web-platform-dx.github.io/assets/img/baseline-newly-word.svg" height="32" align="right">
567
582
  </picture>
568
583
 
569
584
  ### Newly available
@@ -804,6 +819,7 @@ Furthermore, esupgrade supports JavaScript, TypeScript, and more, while lebab is
804
819
  [mdn-let]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
805
820
  [mdn-nullish-coalescing]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing
806
821
  [mdn-object-entries]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
822
+ [mdn-object-values]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values
807
823
  [mdn-promise-try]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/try
808
824
  [mdn-rest-parameters]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters
809
825
  [mdn-slice]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "esupgrade",
3
- "version": "2025.16.0",
3
+ "version": "2025.17.0",
4
4
  "description": "Auto-upgrade your JavaScript syntax",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -0,0 +1,121 @@
1
+ import { default as j } from "jscodeshift"
2
+ import { NodeTest } from "../types.js"
3
+
4
+ /**
5
+ * Check if a callback expression body returns `targetObject[keyName]`.
6
+ *
7
+ * @param {import("ast-types").ASTNode} body - The callback body
8
+ * @param {string} keyName - The key parameter name
9
+ * @param {import("ast-types").ASTNode} targetObject - The object to compare against
10
+ * @returns {boolean} True if the body returns the expected member expression
11
+ */
12
+ function isValueAccess(body, keyName, targetObject) {
13
+ switch (body.type) {
14
+ case "MemberExpression":
15
+ return (
16
+ body.computed === true &&
17
+ j.Identifier.check(body.property) &&
18
+ body.property.name === keyName &&
19
+ new NodeTest(body.object).isEqual(targetObject)
20
+ )
21
+ case "BlockStatement": {
22
+ if (body.body.length !== 1) {
23
+ return false
24
+ }
25
+ const stmt = body.body[0]
26
+ return (
27
+ j.ReturnStatement.check(stmt) &&
28
+ stmt.argument !== null &&
29
+ isValueAccess(stmt.argument, keyName, targetObject)
30
+ )
31
+ }
32
+ default:
33
+ return false
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Transform Object.keys(obj).map(key => obj[key]) to Object.values(obj).
39
+ * Converts patterns where Object.keys() is mapped to retrieve values from the same object
40
+ * to use Object.values() directly.
41
+ *
42
+ * @param {import("jscodeshift").Collection} root - The root AST collection
43
+ * @returns {boolean} True if code was modified
44
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values
45
+ */
46
+ export function objectKeysMapToValues(root) {
47
+ let modified = false
48
+
49
+ root
50
+ .find(j.CallExpression)
51
+ .filter((path) => {
52
+ const node = path.node
53
+
54
+ if (
55
+ !j.MemberExpression.check(node.callee) ||
56
+ !j.Identifier.check(node.callee.property) ||
57
+ node.callee.property.name !== "map"
58
+ ) {
59
+ return false
60
+ }
61
+
62
+ const object = node.callee.object
63
+ if (
64
+ !j.CallExpression.check(object) ||
65
+ !j.MemberExpression.check(object.callee) ||
66
+ !j.Identifier.check(object.callee.object) ||
67
+ object.callee.object.name !== "Object" ||
68
+ !j.Identifier.check(object.callee.property) ||
69
+ object.callee.property.name !== "keys"
70
+ ) {
71
+ return false
72
+ }
73
+
74
+ if (object.arguments.length !== 1) {
75
+ return false
76
+ }
77
+
78
+ if (node.arguments.length !== 1) {
79
+ return false
80
+ }
81
+
82
+ const callback = node.arguments[0]
83
+ if (
84
+ !j.ArrowFunctionExpression.check(callback) &&
85
+ !j.FunctionExpression.check(callback)
86
+ ) {
87
+ return false
88
+ }
89
+
90
+ if (callback.async || callback.generator) {
91
+ return false
92
+ }
93
+
94
+ if (callback.params.length !== 1 || !j.Identifier.check(callback.params[0])) {
95
+ return false
96
+ }
97
+
98
+ // Only allow plain identifier targets to avoid changing evaluation count
99
+ // of expressions with side-effects (getters, function calls, etc.)
100
+ if (!j.Identifier.check(object.arguments[0])) {
101
+ return false
102
+ }
103
+
104
+ const keyName = callback.params[0].name
105
+ return isValueAccess(callback.body, keyName, object.arguments[0])
106
+ })
107
+ .forEach((path) => {
108
+ const targetObject = path.node.callee.object.arguments[0]
109
+
110
+ j(path).replaceWith(
111
+ j.callExpression(
112
+ j.memberExpression(j.identifier("Object"), j.identifier("values"), false),
113
+ [targetObject],
114
+ ),
115
+ )
116
+
117
+ modified = true
118
+ })
119
+
120
+ return modified
121
+ }
@@ -20,6 +20,7 @@ export { namedArrowFunctionToNamedFunction } from "./widelyAvailable/namedArrowF
20
20
  export { nullishCoalescingOperator } from "./widelyAvailable/nullishCoalescingOperator.js"
21
21
  export { objectAssignToSpread } from "./widelyAvailable/objectAssignToSpread.js"
22
22
  export { objectKeysForEachToEntries } from "./widelyAvailable/objectKeysForEachToEntries.js"
23
+ export { objectKeysMapToValues } from "./widelyAvailable/objectKeysMapToValues.js"
23
24
  export { optionalChaining } from "./widelyAvailable/optionalChaining.js"
24
25
  export { promiseToAsyncAwait } from "./widelyAvailable/promiseToAsyncAwait.js"
25
26
  export { removeUseStrictFromModules } from "./widelyAvailable/removeUseStrictFromModules.js"
@@ -0,0 +1,109 @@
1
+ import assert from "node:assert/strict"
2
+ import { describe, suite, test } from "node:test"
3
+ import { transform } from "../../src/index.js"
4
+
5
+ suite("widely-available", () => {
6
+ describe("objectKeysMapToValues", () => {
7
+ test("transform arrow function with expression body", () => {
8
+ const result = transform(`Object.keys(obj).map(key => obj[key])`)
9
+
10
+ assert(result.modified)
11
+ assert.match(result.code, /Object\.values\(obj\)/)
12
+ assert.doesNotMatch(result.code, /Object\.keys/)
13
+ })
14
+
15
+ test("transform arrow function with block body and return", () => {
16
+ const result = transform(`Object.keys(obj).map(key => { return obj[key]; })`)
17
+
18
+ assert(result.modified)
19
+ assert.match(result.code, /Object\.values\(obj\)/)
20
+ })
21
+
22
+ test("transform regular function expression", () => {
23
+ const result = transform(
24
+ `Object.keys(obj).map(function(key) { return obj[key]; })`,
25
+ )
26
+
27
+ assert(result.modified)
28
+ assert.match(result.code, /Object\.values\(obj\)/)
29
+ })
30
+
31
+ test("transform when object is a member expression", () => {
32
+ const result = transform(
33
+ `Object.keys(config.options).map(key => config.options[key])`,
34
+ )
35
+
36
+ assert(!result.modified)
37
+ })
38
+
39
+ test("skip when key not used as index", () => {
40
+ const result = transform(`Object.keys(obj).map(key => key.toUpperCase())`)
41
+
42
+ assert(!result.modified)
43
+ })
44
+
45
+ test("skip when accessing different object", () => {
46
+ const result = transform(`Object.keys(obj).map(key => other[key])`)
47
+
48
+ assert(!result.modified)
49
+ })
50
+
51
+ test("skip when callback has multiple parameters", () => {
52
+ const result = transform(`Object.keys(obj).map((key, index) => obj[key])`)
53
+
54
+ assert(!result.modified)
55
+ })
56
+
57
+ test("skip when block body has multiple statements", () => {
58
+ const result = transform(
59
+ `Object.keys(obj).map(key => { const v = obj[key]; return v; })`,
60
+ )
61
+
62
+ assert(!result.modified)
63
+ })
64
+
65
+ test("skip when map callback is a reference", () => {
66
+ const result = transform(`Object.keys(obj).map(getter)`)
67
+
68
+ assert(!result.modified)
69
+ })
70
+
71
+ test("skip when Object.keys called with multiple arguments", () => {
72
+ const result = transform(`Object.keys(obj, extra).map(key => obj[key])`)
73
+
74
+ assert(!result.modified)
75
+ })
76
+
77
+ test("skip when map called with thisArg", () => {
78
+ const result = transform(`Object.keys(obj).map(key => obj[key], ctx)`)
79
+
80
+ assert(!result.modified)
81
+ })
82
+
83
+ test("skip when map is not called on Object.keys()", () => {
84
+ const result = transform(`[1, 2, 3].map(key => obj[key])`)
85
+
86
+ assert(!result.modified)
87
+ })
88
+
89
+ test("skip when target object is a function call", () => {
90
+ const result = transform(`Object.keys(getObj()).map(key => getObj()[key])`)
91
+
92
+ assert(!result.modified)
93
+ })
94
+
95
+ test("skip when async arrow function callback", () => {
96
+ const result = transform(`Object.keys(obj).map(async key => obj[key])`)
97
+
98
+ assert(!result.modified)
99
+ })
100
+
101
+ test("skip when generator function callback", () => {
102
+ const result = transform(
103
+ `Object.keys(obj).map(function*(key) { return obj[key]; })`,
104
+ )
105
+
106
+ assert(!result.modified)
107
+ })
108
+ })
109
+ })