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.
- package/.github/agents/superjoe.agent.md +1 -1
- package/.github/copilot-instructions.md +3 -3
- package/README.md +25 -9
- package/package.json +1 -1
- package/src/widelyAvailable/objectKeysMapToValues.js +121 -0
- package/src/widelyAvailable.js +1 -0
- package/tests/widelyAvailable/object-keys-map-to-values.test.js +109 -0
|
@@ -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 |
|
|
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/
|
|
51
|
-
<source media="(prefers-color-scheme: light)" srcset="https://web-platform-dx.github.io/
|
|
52
|
-
<img alt="Baseline: widely available" src="https://web-platform-dx.github.io/
|
|
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/
|
|
82
|
-
<source media="(prefers-color-scheme: light)" srcset="https://web-platform-dx.github.io/
|
|
83
|
-
<img alt="Baseline: widely available" src="https://web-platform-dx.github.io/
|
|
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/
|
|
565
|
-
<source media="(prefers-color-scheme: light)" srcset="https://web-platform-dx.github.io/
|
|
566
|
-
<img alt="Baseline: Newly available" src="https://web-platform-dx.github.io/
|
|
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
|
@@ -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
|
+
}
|
package/src/widelyAvailable.js
CHANGED
|
@@ -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
|
+
})
|