esupgrade 2025.16.0 → 2025.18.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/.pre-commit-config.yaml +1 -1
- package/README.md +57 -9
- package/package.json +1 -1
- package/src/jQuery/nextToNextElementSibling.js +1 -2
- package/src/jQuery/prevToPreviousElementSibling.js +1 -2
- package/src/jQuery/readyToDOMContentLoaded.js +1 -2
- package/src/jQuery/siblingsToSiblingsArray.js +1 -2
- package/src/types.js +4 -5
- package/src/widelyAvailable/argumentsToRestParameters.js +1 -2
- package/src/widelyAvailable/arrayConcatToSpread.js +1 -3
- package/src/widelyAvailable/arrayFilterToFind.js +1 -3
- package/src/widelyAvailable/arrayFromForEachToForOf.js +1 -2
- package/src/widelyAvailable/arraySliceToSpread.js +1 -3
- package/src/widelyAvailable/consoleLogToInfo.js +2 -5
- package/src/widelyAvailable/constructorToClass.js +2 -4
- package/src/widelyAvailable/defaultParameterValues.js +1 -3
- package/src/widelyAvailable/forLoopToForOf.js +1 -3
- package/src/widelyAvailable/globalContextToGlobalThis.js +1 -2
- package/src/widelyAvailable/indexOfToIncludes.js +1 -3
- package/src/widelyAvailable/indexOfToStartsWith.js +1 -3
- package/src/widelyAvailable/iterableForEachToForOf.js +1 -2
- package/src/widelyAvailable/lastIndexOfToEndsWith.js +1 -3
- package/src/widelyAvailable/namedArrowFunctionToNamedFunction.js +1 -3
- package/src/widelyAvailable/nullishCoalescingOperator.js +1 -3
- package/src/widelyAvailable/objectKeysForEachToEntries.js +1 -3
- package/src/widelyAvailable/objectKeysMapToValues.js +119 -0
- package/src/widelyAvailable/objectPropertyExtractionToDestructuring.js +342 -0
- package/src/widelyAvailable/removeUseStrictFromModules.js +1 -3
- package/src/widelyAvailable/substrToSlice.js +1 -3
- package/src/widelyAvailable/substringToStartsWith.js +1 -3
- package/src/widelyAvailable.js +2 -0
- package/tests/widelyAvailable/object-keys-map-to-values.test.js +109 -0
- package/tests/widelyAvailable/object-property-extraction-to-destructuring.test.js +314 -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/.pre-commit-config.yaml
CHANGED
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
|
|
@@ -535,6 +550,37 @@ The transformer handles cases where `Array.from(arguments)` has already been con
|
|
|
535
550
|
|
|
536
551
|
Note: The `x = x || defaultValue` pattern is NOT transformed as it has different semantics (triggers on any falsy value, instead of `undefined`).
|
|
537
552
|
|
|
553
|
+
#### Manual property extraction → [Destructuring parameters][mdn-destructuring]
|
|
554
|
+
|
|
555
|
+
```diff
|
|
556
|
+
-function fn(obj) {
|
|
557
|
+
- const x = obj.x;
|
|
558
|
+
- const y = obj.y;
|
|
559
|
+
- // use x and y
|
|
560
|
+
-}
|
|
561
|
+
+function fn({x, y}) {
|
|
562
|
+
+ // use x and y
|
|
563
|
+
+}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Transforms functions where the body begins by extracting properties from a parameter into object destructuring in the parameter list. Aliased extractions use longhand syntax:
|
|
567
|
+
|
|
568
|
+
```diff
|
|
569
|
+
-function fn(obj) {
|
|
570
|
+
- const myX = obj.x;
|
|
571
|
+
-}
|
|
572
|
+
+function fn({x: myX}) {
|
|
573
|
+
+}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
Transforms when:
|
|
577
|
+
|
|
578
|
+
- The parameter is a simple identifier (not already destructured or a rest parameter)
|
|
579
|
+
- Leading statements are variable declarations extracting non-computed properties from that parameter
|
|
580
|
+
- The original parameter identifier is not referenced after the extraction zone
|
|
581
|
+
|
|
582
|
+
TypeScript type annotations on the original parameter are preserved on the resulting destructuring pattern.
|
|
583
|
+
|
|
538
584
|
#### Promise chains → [async/await][mdn-async-await]
|
|
539
585
|
|
|
540
586
|
```diff
|
|
@@ -561,9 +607,9 @@ Note: The `x = x || defaultValue` pattern is NOT transformed as it has different
|
|
|
561
607
|
- The expression is a known promise (`fetch()`, `new Promise()`, or promise methods)
|
|
562
608
|
|
|
563
609
|
<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/
|
|
610
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://web-platform-dx.github.io/assets/img/baseline-newly-word-dark.svg">
|
|
611
|
+
<source media="(prefers-color-scheme: light)" srcset="https://web-platform-dx.github.io/assets/img/baseline-newly-word.svg">
|
|
612
|
+
<img alt="Baseline: Newly available" src="https://web-platform-dx.github.io/assets/img/baseline-newly-word.svg" height="32" align="right">
|
|
567
613
|
</picture>
|
|
568
614
|
|
|
569
615
|
### Newly available
|
|
@@ -794,6 +840,7 @@ Furthermore, esupgrade supports JavaScript, TypeScript, and more, while lebab is
|
|
|
794
840
|
[mdn-console]: https://developer.mozilla.org/en-US/docs/Web/API/console
|
|
795
841
|
[mdn-const]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
|
|
796
842
|
[mdn-default-parameters]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters
|
|
843
|
+
[mdn-destructuring]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
|
|
797
844
|
[mdn-endswith]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
|
|
798
845
|
[mdn-exponentiation]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation
|
|
799
846
|
[mdn-find]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
|
|
@@ -804,6 +851,7 @@ Furthermore, esupgrade supports JavaScript, TypeScript, and more, while lebab is
|
|
|
804
851
|
[mdn-let]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
|
|
805
852
|
[mdn-nullish-coalescing]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing
|
|
806
853
|
[mdn-object-entries]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
|
|
854
|
+
[mdn-object-values]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values
|
|
807
855
|
[mdn-promise-try]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/try
|
|
808
856
|
[mdn-rest-parameters]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters
|
|
809
857
|
[mdn-slice]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice
|
package/package.json
CHANGED
|
@@ -11,8 +11,7 @@ export function nextToNextElementSibling(root) {
|
|
|
11
11
|
let modified = false
|
|
12
12
|
root
|
|
13
13
|
.find(j.CallExpression)
|
|
14
|
-
.filter((
|
|
15
|
-
const node = path.node
|
|
14
|
+
.filter(({ node }) => {
|
|
16
15
|
if (!j.MemberExpression.check(node.callee)) return false
|
|
17
16
|
if (!j.Identifier.check(node.callee.property)) return false
|
|
18
17
|
if (node.callee.property.name !== "next") return false
|
|
@@ -11,8 +11,7 @@ export function prevToPreviousElementSibling(root) {
|
|
|
11
11
|
let modified = false
|
|
12
12
|
root
|
|
13
13
|
.find(j.CallExpression)
|
|
14
|
-
.filter((
|
|
15
|
-
const node = path.node
|
|
14
|
+
.filter(({ node }) => {
|
|
16
15
|
if (!j.MemberExpression.check(node.callee)) return false
|
|
17
16
|
if (!j.Identifier.check(node.callee.property)) return false
|
|
18
17
|
if (node.callee.property.name !== "prev") return false
|
|
@@ -12,8 +12,7 @@ export function readyToDOMContentLoaded(root) {
|
|
|
12
12
|
.find(j.CallExpression, {
|
|
13
13
|
callee: { type: "MemberExpression" },
|
|
14
14
|
})
|
|
15
|
-
.filter((
|
|
16
|
-
const node = path.node
|
|
15
|
+
.filter(({ node }) => {
|
|
17
16
|
const callee = node.callee
|
|
18
17
|
if (!j.Identifier.check(callee.property)) return false
|
|
19
18
|
if (callee.property.name !== "ready") return false
|
|
@@ -11,8 +11,7 @@ export function siblingsToSiblingsArray(root) {
|
|
|
11
11
|
let modified = false
|
|
12
12
|
root
|
|
13
13
|
.find(j.CallExpression)
|
|
14
|
-
.filter((
|
|
15
|
-
const node = path.node
|
|
14
|
+
.filter(({ node }) => {
|
|
16
15
|
if (!j.MemberExpression.check(node.callee)) return false
|
|
17
16
|
if (!j.Identifier.check(node.callee.property)) return false
|
|
18
17
|
if (node.callee.property.name !== "siblings") return false
|
package/src/types.js
CHANGED
|
@@ -504,9 +504,9 @@ export class NodeTest {
|
|
|
504
504
|
*/
|
|
505
505
|
hasSideEffects() {
|
|
506
506
|
const paramNames = new Set(
|
|
507
|
-
this.node.params.flatMap((param) =>
|
|
508
|
-
|
|
509
|
-
),
|
|
507
|
+
this.node.params.flatMap((param) => [
|
|
508
|
+
...new NodeTest(param).extractIdentifiersFromPattern(),
|
|
509
|
+
]),
|
|
510
510
|
)
|
|
511
511
|
return this.#hasNodeSideEffects(this.node.body, paramNames)
|
|
512
512
|
}
|
|
@@ -1066,8 +1066,7 @@ export function processMultipleDeclarators(root, path) {
|
|
|
1066
1066
|
* @param {string} name - Name to check for shadowing
|
|
1067
1067
|
* @returns {boolean} True if the identifier is shadowed
|
|
1068
1068
|
*/
|
|
1069
|
-
export function isShadowed(
|
|
1070
|
-
let scope = path.scope
|
|
1069
|
+
export function isShadowed({ scope }, name) {
|
|
1071
1070
|
while (scope) {
|
|
1072
1071
|
if (scope.getBindings()[name]) {
|
|
1073
1072
|
return true
|
|
@@ -24,8 +24,7 @@ export function argumentsToRestParameters(root) {
|
|
|
24
24
|
...root.find(j.FunctionExpression).paths(),
|
|
25
25
|
]
|
|
26
26
|
|
|
27
|
-
functionNodes.forEach((
|
|
28
|
-
const func = path.node
|
|
27
|
+
functionNodes.forEach(({ node: func }) => {
|
|
29
28
|
if (
|
|
30
29
|
(func.params.length > 0 &&
|
|
31
30
|
j.RestElement.check(func.params[func.params.length - 1])) ||
|
|
@@ -12,8 +12,7 @@ export function consoleLogToInfo(root) {
|
|
|
12
12
|
|
|
13
13
|
root
|
|
14
14
|
.find(j.CallExpression)
|
|
15
|
-
.filter((
|
|
16
|
-
const node = path.node
|
|
15
|
+
.filter(({ node }) => {
|
|
17
16
|
// Check if this is a console.log() call
|
|
18
17
|
if (
|
|
19
18
|
!j.MemberExpression.check(node.callee) ||
|
|
@@ -27,9 +26,7 @@ export function consoleLogToInfo(root) {
|
|
|
27
26
|
|
|
28
27
|
return true
|
|
29
28
|
})
|
|
30
|
-
.forEach((
|
|
31
|
-
const node = path.node
|
|
32
|
-
|
|
29
|
+
.forEach(({ node }) => {
|
|
33
30
|
// Replace the property name from 'log' to 'info'
|
|
34
31
|
node.callee.property.name = "info"
|
|
35
32
|
|
|
@@ -61,8 +61,7 @@ export function constructorToClass(root) {
|
|
|
61
61
|
// Pattern 1: ConstructorName.prototype.methodName = ...
|
|
62
62
|
root
|
|
63
63
|
.find(j.ExpressionStatement)
|
|
64
|
-
.filter((
|
|
65
|
-
const node = path.node
|
|
64
|
+
.filter(({ node }) => {
|
|
66
65
|
if (!j.AssignmentExpression.check(node.expression)) {
|
|
67
66
|
return false
|
|
68
67
|
}
|
|
@@ -105,8 +104,7 @@ export function constructorToClass(root) {
|
|
|
105
104
|
// Pattern 2: ConstructorName.prototype = { methodName: function() {...}, ... }
|
|
106
105
|
root
|
|
107
106
|
.find(j.ExpressionStatement)
|
|
108
|
-
.filter((
|
|
109
|
-
const node = path.node
|
|
107
|
+
.filter(({ node }) => {
|
|
110
108
|
if (!j.AssignmentExpression.check(node.expression)) {
|
|
111
109
|
return false
|
|
112
110
|
}
|
|
@@ -27,9 +27,7 @@ export function defaultParameterValues(root) {
|
|
|
27
27
|
...root.find(j.ArrowFunctionExpression).paths(),
|
|
28
28
|
]
|
|
29
29
|
|
|
30
|
-
functionNodes.forEach((
|
|
31
|
-
const func = path.node
|
|
32
|
-
|
|
30
|
+
functionNodes.forEach(({ node: func }) => {
|
|
33
31
|
// Skip if function body is not a block statement
|
|
34
32
|
if (!j.BlockStatement.check(func.body)) {
|
|
35
33
|
return
|
|
@@ -14,9 +14,7 @@ export function forLoopToForOf(root) {
|
|
|
14
14
|
|
|
15
15
|
root
|
|
16
16
|
.find(j.ForStatement)
|
|
17
|
-
.filter((
|
|
18
|
-
const node = path.node
|
|
19
|
-
|
|
17
|
+
.filter(({ node }) => {
|
|
20
18
|
// Check init: must be let/const i = 0
|
|
21
19
|
if (!j.VariableDeclaration.check(node.init)) {
|
|
22
20
|
return false
|
|
@@ -14,8 +14,7 @@ export function globalContextToGlobalThis(root) {
|
|
|
14
14
|
|
|
15
15
|
root
|
|
16
16
|
.find(j.CallExpression)
|
|
17
|
-
.filter((
|
|
18
|
-
const node = path.node
|
|
17
|
+
.filter(({ node }) => {
|
|
19
18
|
// Check if this is a call to Function constructor
|
|
20
19
|
if (
|
|
21
20
|
!j.Identifier.check(node.callee) ||
|
|
@@ -16,9 +16,7 @@ export function indexOfToIncludes(root) {
|
|
|
16
16
|
|
|
17
17
|
root
|
|
18
18
|
.find(j.BinaryExpression)
|
|
19
|
-
.filter((
|
|
20
|
-
const node = path.node
|
|
21
|
-
|
|
19
|
+
.filter(({ node }) => {
|
|
22
20
|
// Check for comparison operators: !==, ===, >, >=, <, <=
|
|
23
21
|
if (!["!==", "===", ">", ">=", "<", "<="].includes(node.operator)) {
|
|
24
22
|
return false
|
|
@@ -14,9 +14,7 @@ export function indexOfToStartsWith(root) {
|
|
|
14
14
|
|
|
15
15
|
root
|
|
16
16
|
.find(j.BinaryExpression)
|
|
17
|
-
.filter((
|
|
18
|
-
const node = path.node
|
|
19
|
-
|
|
17
|
+
.filter(({ node }) => {
|
|
20
18
|
// Check for === or !== operators
|
|
21
19
|
if (!["===", "!=="].includes(node.operator)) {
|
|
22
20
|
return false
|
|
@@ -15,9 +15,7 @@ export function lastIndexOfToEndsWith(root) {
|
|
|
15
15
|
|
|
16
16
|
root
|
|
17
17
|
.find(j.BinaryExpression)
|
|
18
|
-
.filter((
|
|
19
|
-
const node = path.node
|
|
20
|
-
|
|
18
|
+
.filter(({ node }) => {
|
|
21
19
|
// Check for === or !== operators
|
|
22
20
|
if (!["===", "!=="].includes(node.operator)) {
|
|
23
21
|
return false
|
|
@@ -27,9 +27,7 @@ export function namedArrowFunctionToNamedFunction(root) {
|
|
|
27
27
|
|
|
28
28
|
root
|
|
29
29
|
.find(j.VariableDeclaration)
|
|
30
|
-
.filter((
|
|
31
|
-
const node = path.node
|
|
32
|
-
|
|
30
|
+
.filter(({ node }) => {
|
|
33
31
|
// Must have exactly one declarator
|
|
34
32
|
if (node.declarations.length !== 1) {
|
|
35
33
|
return false
|
|
@@ -13,9 +13,7 @@ export function nullishCoalescingOperator(root) {
|
|
|
13
13
|
|
|
14
14
|
root
|
|
15
15
|
.find(j.ConditionalExpression)
|
|
16
|
-
.filter((
|
|
17
|
-
const node = path.node
|
|
18
|
-
|
|
16
|
+
.filter(({ node }) => {
|
|
19
17
|
// Test must be a logical AND expression
|
|
20
18
|
if (!j.LogicalExpression.check(node.test) || node.test.operator !== "&&") {
|
|
21
19
|
return false
|
|
@@ -15,9 +15,7 @@ export function objectKeysForEachToEntries(root) {
|
|
|
15
15
|
|
|
16
16
|
root
|
|
17
17
|
.find(j.CallExpression)
|
|
18
|
-
.filter((
|
|
19
|
-
const node = path.node
|
|
20
|
-
|
|
18
|
+
.filter(({ node }) => {
|
|
21
19
|
// Check if this is a forEach call
|
|
22
20
|
if (
|
|
23
21
|
!j.MemberExpression.check(node.callee) ||
|
|
@@ -0,0 +1,119 @@
|
|
|
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(({ node }) => {
|
|
52
|
+
if (
|
|
53
|
+
!j.MemberExpression.check(node.callee) ||
|
|
54
|
+
!j.Identifier.check(node.callee.property) ||
|
|
55
|
+
node.callee.property.name !== "map"
|
|
56
|
+
) {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const object = node.callee.object
|
|
61
|
+
if (
|
|
62
|
+
!j.CallExpression.check(object) ||
|
|
63
|
+
!j.MemberExpression.check(object.callee) ||
|
|
64
|
+
!j.Identifier.check(object.callee.object) ||
|
|
65
|
+
object.callee.object.name !== "Object" ||
|
|
66
|
+
!j.Identifier.check(object.callee.property) ||
|
|
67
|
+
object.callee.property.name !== "keys"
|
|
68
|
+
) {
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (object.arguments.length !== 1) {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (node.arguments.length !== 1) {
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const callback = node.arguments[0]
|
|
81
|
+
if (
|
|
82
|
+
!j.ArrowFunctionExpression.check(callback) &&
|
|
83
|
+
!j.FunctionExpression.check(callback)
|
|
84
|
+
) {
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (callback.async || callback.generator) {
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (callback.params.length !== 1 || !j.Identifier.check(callback.params[0])) {
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Only allow plain identifier targets to avoid changing evaluation count
|
|
97
|
+
// of expressions with side-effects (getters, function calls, etc.)
|
|
98
|
+
if (!j.Identifier.check(object.arguments[0])) {
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const keyName = callback.params[0].name
|
|
103
|
+
return isValueAccess(callback.body, keyName, object.arguments[0])
|
|
104
|
+
})
|
|
105
|
+
.forEach((path) => {
|
|
106
|
+
const targetObject = path.node.callee.object.arguments[0]
|
|
107
|
+
|
|
108
|
+
j(path).replaceWith(
|
|
109
|
+
j.callExpression(
|
|
110
|
+
j.memberExpression(j.identifier("Object"), j.identifier("values"), false),
|
|
111
|
+
[targetObject],
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
modified = true
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
return modified
|
|
119
|
+
}
|