esupgrade 2025.17.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/.pre-commit-config.yaml +1 -1
- package/README.md +32 -0
- 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 +1 -3
- 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 +1 -0
- package/tests/widelyAvailable/object-property-extraction-to-destructuring.test.js +314 -0
package/.pre-commit-config.yaml
CHANGED
package/README.md
CHANGED
|
@@ -550,6 +550,37 @@ The transformer handles cases where `Array.from(arguments)` has already been con
|
|
|
550
550
|
|
|
551
551
|
Note: The `x = x || defaultValue` pattern is NOT transformed as it has different semantics (triggers on any falsy value, instead of `undefined`).
|
|
552
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
|
+
|
|
553
584
|
#### Promise chains → [async/await][mdn-async-await]
|
|
554
585
|
|
|
555
586
|
```diff
|
|
@@ -809,6 +840,7 @@ Furthermore, esupgrade supports JavaScript, TypeScript, and more, while lebab is
|
|
|
809
840
|
[mdn-console]: https://developer.mozilla.org/en-US/docs/Web/API/console
|
|
810
841
|
[mdn-const]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
|
|
811
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
|
|
812
844
|
[mdn-endswith]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
|
|
813
845
|
[mdn-exponentiation]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation
|
|
814
846
|
[mdn-find]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
|
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) ||
|
|
@@ -48,9 +48,7 @@ export function objectKeysMapToValues(root) {
|
|
|
48
48
|
|
|
49
49
|
root
|
|
50
50
|
.find(j.CallExpression)
|
|
51
|
-
.filter((
|
|
52
|
-
const node = path.node
|
|
53
|
-
|
|
51
|
+
.filter(({ node }) => {
|
|
54
52
|
if (
|
|
55
53
|
!j.MemberExpression.check(node.callee) ||
|
|
56
54
|
!j.Identifier.check(node.callee.property) ||
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { default as j } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
const SKIP_KEYS = new Set(["loc", "start", "end", "tokens", "comments"])
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Transform manual property extraction to destructuring in function parameters.
|
|
7
|
+
* Converts patterns where a function body begins with property extractions from a
|
|
8
|
+
* parameter into an object destructuring pattern in the parameter list.
|
|
9
|
+
*
|
|
10
|
+
* Only transforms when:
|
|
11
|
+
* - The parameter is a simple identifier (not already destructured or rest)
|
|
12
|
+
* - Leading statements are `const`/`let`/`var` declarations extracting non-computed
|
|
13
|
+
* properties from that parameter
|
|
14
|
+
* - The original parameter identifier is not referenced after the extraction zone
|
|
15
|
+
*
|
|
16
|
+
* @param {import("jscodeshift").Collection} root - The root AST collection
|
|
17
|
+
* @returns {boolean} True if code was modified
|
|
18
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
|
|
19
|
+
*/
|
|
20
|
+
export function objectPropertyExtractionToDestructuring(root) {
|
|
21
|
+
let modified = false
|
|
22
|
+
|
|
23
|
+
const functionNodes = [
|
|
24
|
+
...root.find(j.FunctionDeclaration).paths(),
|
|
25
|
+
...root.find(j.FunctionExpression).paths(),
|
|
26
|
+
...root.find(j.ArrowFunctionExpression).paths(),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
functionNodes.forEach(({ node: func }) => {
|
|
30
|
+
if (!j.BlockStatement.check(func.body)) {
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const body = func.body
|
|
35
|
+
|
|
36
|
+
func.params.forEach((param, paramIndex) => {
|
|
37
|
+
if (!j.Identifier.check(param)) {
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const paramName = param.name
|
|
42
|
+
const result = findExtractions(body, paramName)
|
|
43
|
+
|
|
44
|
+
if (result.extractions.length === 0) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (isParamUsedAfterExtractions(body, paramName, result)) {
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (isParamReferencedInOtherParams(func.params, paramIndex, paramName)) {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (wouldPromoteDirective(body, result)) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func.params[paramIndex] = buildObjectPattern(result.extractions, param)
|
|
61
|
+
removeExtractionDeclarators(body, result.extractions)
|
|
62
|
+
modified = true
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
return modified
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Find leading property extractions from a named parameter in a function body.
|
|
71
|
+
*
|
|
72
|
+
* @param {import("ast-types").namedTypes.BlockStatement} body - The function body
|
|
73
|
+
* @param {string} paramName - The parameter identifier name to extract from
|
|
74
|
+
* @returns {{ extractions: Array<{localName: string, propertyName: string, statementIndex: number, declaratorIndex: number}>, boundary: number }}
|
|
75
|
+
*/
|
|
76
|
+
function findExtractions(body, paramName) {
|
|
77
|
+
const extractions = []
|
|
78
|
+
let boundary = 0
|
|
79
|
+
|
|
80
|
+
for (let i = 0; i < body.body.length; i++) {
|
|
81
|
+
const statement = body.body[i]
|
|
82
|
+
|
|
83
|
+
if (!j.VariableDeclaration.check(statement)) {
|
|
84
|
+
break
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let hasExtractionInStatement = false
|
|
88
|
+
|
|
89
|
+
statement.declarations.forEach((declarator, declaratorIndex) => {
|
|
90
|
+
if (isPropertyExtractionFrom(declarator, paramName)) {
|
|
91
|
+
extractions.push({
|
|
92
|
+
localName: declarator.id.name,
|
|
93
|
+
propertyName: declarator.init.property.name,
|
|
94
|
+
statementIndex: i,
|
|
95
|
+
declaratorIndex,
|
|
96
|
+
})
|
|
97
|
+
hasExtractionInStatement = true
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
if (!hasExtractionInStatement) {
|
|
102
|
+
break
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
boundary = i + 1
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { extractions, boundary }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if a variable declarator is a non-computed property access from a named identifier.
|
|
113
|
+
*
|
|
114
|
+
* @param {import("ast-types").namedTypes.VariableDeclarator} declarator - The declarator to check
|
|
115
|
+
* @param {string} paramName - The identifier name to check against
|
|
116
|
+
* @returns {boolean} True if the declarator extracts a property from paramName
|
|
117
|
+
*/
|
|
118
|
+
function isPropertyExtractionFrom(declarator, paramName) {
|
|
119
|
+
return (
|
|
120
|
+
j.Identifier.check(declarator.id) &&
|
|
121
|
+
j.MemberExpression.check(declarator.init) &&
|
|
122
|
+
!declarator.init.computed &&
|
|
123
|
+
j.Identifier.check(declarator.init.object) &&
|
|
124
|
+
declarator.init.object.name === paramName &&
|
|
125
|
+
j.Identifier.check(declarator.init.property)
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check if the original parameter identifier is still referenced after the extraction zone.
|
|
131
|
+
* Uses deep traversal that crosses nested function boundaries, since the parameter
|
|
132
|
+
* name is in the outer scope and closures may reference it.
|
|
133
|
+
*
|
|
134
|
+
* @param {import("ast-types").namedTypes.BlockStatement} body - The function body
|
|
135
|
+
* @param {string} paramName - The parameter identifier name
|
|
136
|
+
* @param {{ extractions: Array, boundary: number }} result - Result from findExtractions
|
|
137
|
+
* @returns {boolean} True if the parameter is used after the extractions
|
|
138
|
+
*/
|
|
139
|
+
function isParamUsedAfterExtractions(body, paramName, result) {
|
|
140
|
+
const remainingStatements = body.body.slice(result.boundary)
|
|
141
|
+
|
|
142
|
+
if (remainingStatements.some((stmt) => deepContainsIdentifier(stmt, paramName))) {
|
|
143
|
+
return true
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return isMixedDeclaratorUsingParam(body, paramName, result.extractions)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Check if any non-extraction declarator in the extraction zone references the parameter.
|
|
151
|
+
*
|
|
152
|
+
* @param {import("ast-types").namedTypes.BlockStatement} body - The function body
|
|
153
|
+
* @param {string} paramName - The parameter identifier name
|
|
154
|
+
* @param {Array<{statementIndex: number, declaratorIndex: number}>} extractions - The found extractions
|
|
155
|
+
* @returns {boolean} True if the parameter is used in a non-extraction declarator
|
|
156
|
+
*/
|
|
157
|
+
function isMixedDeclaratorUsingParam(body, paramName, extractions) {
|
|
158
|
+
const extractionSet = new Set(
|
|
159
|
+
extractions.map(
|
|
160
|
+
({ statementIndex, declaratorIndex }) => `${statementIndex}:${declaratorIndex}`,
|
|
161
|
+
),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return body.body.some((statement, statementIndex) => {
|
|
165
|
+
if (!j.VariableDeclaration.check(statement)) {
|
|
166
|
+
return false
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return statement.declarations.some((declarator, declaratorIndex) => {
|
|
170
|
+
if (extractionSet.has(`${statementIndex}:${declaratorIndex}`)) {
|
|
171
|
+
return false
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!declarator.init) {
|
|
175
|
+
return false
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return deepContainsIdentifier(declarator.init, paramName)
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Check if removing extraction statements would promote a "use strict" string literal
|
|
185
|
+
* to a function directive. Non-simple parameter lists (destructuring) cannot have a
|
|
186
|
+
* "use strict" directive.
|
|
187
|
+
*
|
|
188
|
+
* @param {import("ast-types").namedTypes.BlockStatement} body - The function body
|
|
189
|
+
* @param {{ extractions: Array<{statementIndex: number, declaratorIndex: number}>, boundary: number }} result - Result from findExtractions
|
|
190
|
+
* @returns {boolean} True if the transformation would produce an illegal directive
|
|
191
|
+
*/
|
|
192
|
+
function wouldPromoteDirective(body, result) {
|
|
193
|
+
const extractionSet = new Set(
|
|
194
|
+
result.extractions.map(
|
|
195
|
+
({ statementIndex, declaratorIndex }) => `${statementIndex}:${declaratorIndex}`,
|
|
196
|
+
),
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
for (let i = 0; i < result.boundary; i++) {
|
|
200
|
+
const statement = body.body[i]
|
|
201
|
+
const hasRemainingDeclarators = statement.declarations.some(
|
|
202
|
+
(_, di) => !extractionSet.has(`${i}:${di}`),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
if (hasRemainingDeclarators) {
|
|
206
|
+
return false
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const nextStatement = body.body[result.boundary]
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
nextStatement !== undefined &&
|
|
214
|
+
j.ExpressionStatement.check(nextStatement) &&
|
|
215
|
+
isStringLiteralNode(nextStatement.expression) &&
|
|
216
|
+
nextStatement.expression.value === "use strict"
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Check if the parameter identifier is referenced in any other parameter in the list.
|
|
222
|
+
* Covers default values such as `function fn(obj, y = obj.x)`.
|
|
223
|
+
*
|
|
224
|
+
* @param {Array<import("ast-types").ASTNode>} params - The full parameter list
|
|
225
|
+
* @param {number} paramIndex - Index of the parameter being transformed
|
|
226
|
+
* @param {string} paramName - The parameter identifier name
|
|
227
|
+
* @returns {boolean} True if the identifier appears in another parameter
|
|
228
|
+
*/
|
|
229
|
+
function isParamReferencedInOtherParams(params, paramIndex, paramName) {
|
|
230
|
+
return params.some(
|
|
231
|
+
(param, i) => i !== paramIndex && deepContainsIdentifier(param, paramName),
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Deeply check if an identifier name appears anywhere in an AST subtree,
|
|
237
|
+
* including inside nested functions and arrow functions.
|
|
238
|
+
*
|
|
239
|
+
* @param {import("ast-types").ASTNode | null | undefined} node - The node to search
|
|
240
|
+
* @param {string} name - The identifier name to search for
|
|
241
|
+
* @returns {boolean} True if the identifier is found anywhere in the subtree
|
|
242
|
+
*/
|
|
243
|
+
function deepContainsIdentifier(node, name) {
|
|
244
|
+
if (!node || typeof node !== "object") {
|
|
245
|
+
return false
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (node.type === "Identifier" && node.name === name) {
|
|
249
|
+
return true
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
for (const key in node) {
|
|
253
|
+
if (SKIP_KEYS.has(key)) {
|
|
254
|
+
continue
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const value = node[key]
|
|
258
|
+
|
|
259
|
+
if (Array.isArray(value)) {
|
|
260
|
+
if (value.some((item) => deepContainsIdentifier(item, name))) {
|
|
261
|
+
return true
|
|
262
|
+
}
|
|
263
|
+
} else if (value && typeof value === "object") {
|
|
264
|
+
if (deepContainsIdentifier(value, name)) {
|
|
265
|
+
return true
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return false
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Check if a node is a string literal.
|
|
275
|
+
*
|
|
276
|
+
* @param {import("ast-types").ASTNode} node - The node to check
|
|
277
|
+
* @returns {boolean} True if the node is a string literal
|
|
278
|
+
*/
|
|
279
|
+
function isStringLiteralNode(node) {
|
|
280
|
+
return j.StringLiteral.check(node)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Build an ObjectPattern AST node from a list of property extractions.
|
|
285
|
+
* Preserves TypeScript type annotations from the original parameter.
|
|
286
|
+
*
|
|
287
|
+
* @param {Array<{localName: string, propertyName: string}>} extractions - The extractions to build from
|
|
288
|
+
* @param {import("ast-types").namedTypes.Identifier} originalParam - The original parameter node
|
|
289
|
+
* @returns {import("ast-types").namedTypes.ObjectPattern} The destructuring pattern
|
|
290
|
+
*/
|
|
291
|
+
function buildObjectPattern(extractions, originalParam) {
|
|
292
|
+
const properties = extractions.map(({ localName, propertyName }) => {
|
|
293
|
+
const key = j.identifier(propertyName)
|
|
294
|
+
const value = j.identifier(localName)
|
|
295
|
+
const isShorthand = localName === propertyName
|
|
296
|
+
const prop = j.objectProperty(key, value)
|
|
297
|
+
prop.shorthand = isShorthand
|
|
298
|
+
return prop
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
const pattern = j.objectPattern(properties)
|
|
302
|
+
|
|
303
|
+
if (originalParam.typeAnnotation) {
|
|
304
|
+
pattern.typeAnnotation = originalParam.typeAnnotation
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return pattern
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Remove extraction declarators from the function body, removing empty statements.
|
|
312
|
+
*
|
|
313
|
+
* @param {import("ast-types").namedTypes.BlockStatement} body - The function body to modify
|
|
314
|
+
* @param {Array<{statementIndex: number, declaratorIndex: number}>} extractions - The extractions to remove
|
|
315
|
+
*/
|
|
316
|
+
function removeExtractionDeclarators(body, extractions) {
|
|
317
|
+
const statementMap = new Map()
|
|
318
|
+
|
|
319
|
+
extractions.forEach(({ statementIndex, declaratorIndex }) => {
|
|
320
|
+
if (!statementMap.has(statementIndex)) {
|
|
321
|
+
statementMap.set(statementIndex, [])
|
|
322
|
+
}
|
|
323
|
+
statementMap.get(statementIndex).push(declaratorIndex)
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
for (const [statementIndex, declaratorIndices] of [...statementMap.entries()].sort(
|
|
327
|
+
(a, b) => b[0] - a[0],
|
|
328
|
+
)) {
|
|
329
|
+
const statement = body.body[statementIndex]
|
|
330
|
+
|
|
331
|
+
declaratorIndices
|
|
332
|
+
.slice()
|
|
333
|
+
.sort((a, b) => b - a)
|
|
334
|
+
.forEach((di) => {
|
|
335
|
+
statement.declarations.splice(di, 1)
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
if (statement.declarations.length === 0) {
|
|
339
|
+
body.body.splice(statementIndex, 1)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -26,9 +26,7 @@ export function removeUseStrictFromModules(root) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// Find and remove 'use strict' directives
|
|
29
|
-
root.find(j.Program).forEach((
|
|
30
|
-
const program = programPath.node
|
|
31
|
-
|
|
29
|
+
root.find(j.Program).forEach(({ node: program }) => {
|
|
32
30
|
// Check directives array (Babel/TSX parser stores directives here)
|
|
33
31
|
if (program.directives && Array.isArray(program.directives)) {
|
|
34
32
|
let i = 0
|
|
@@ -15,9 +15,7 @@ export function substringToStartsWith(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
|
package/src/widelyAvailable.js
CHANGED
|
@@ -21,6 +21,7 @@ export { nullishCoalescingOperator } from "./widelyAvailable/nullishCoalescingOp
|
|
|
21
21
|
export { objectAssignToSpread } from "./widelyAvailable/objectAssignToSpread.js"
|
|
22
22
|
export { objectKeysForEachToEntries } from "./widelyAvailable/objectKeysForEachToEntries.js"
|
|
23
23
|
export { objectKeysMapToValues } from "./widelyAvailable/objectKeysMapToValues.js"
|
|
24
|
+
export { objectPropertyExtractionToDestructuring } from "./widelyAvailable/objectPropertyExtractionToDestructuring.js"
|
|
24
25
|
export { optionalChaining } from "./widelyAvailable/optionalChaining.js"
|
|
25
26
|
export { promiseToAsyncAwait } from "./widelyAvailable/promiseToAsyncAwait.js"
|
|
26
27
|
export { removeUseStrictFromModules } from "./widelyAvailable/removeUseStrictFromModules.js"
|
|
@@ -0,0 +1,314 @@
|
|
|
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("objectPropertyExtractionToDestructuring", () => {
|
|
7
|
+
test("single property extraction in function declaration", () => {
|
|
8
|
+
const result = transform(`
|
|
9
|
+
function fn(obj) {
|
|
10
|
+
const x = obj.x;
|
|
11
|
+
return x;
|
|
12
|
+
}
|
|
13
|
+
`)
|
|
14
|
+
|
|
15
|
+
assert(result.modified, "transform single property extraction")
|
|
16
|
+
assert.match(result.code, /function fn\(\s*\{\s*x\s*\}/)
|
|
17
|
+
assert.doesNotMatch(result.code, /const x = obj\.x/)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test("multiple property extractions", () => {
|
|
21
|
+
const result = transform(`
|
|
22
|
+
function fn(obj) {
|
|
23
|
+
const x = obj.x;
|
|
24
|
+
const y = obj.y;
|
|
25
|
+
return x + y;
|
|
26
|
+
}
|
|
27
|
+
`)
|
|
28
|
+
|
|
29
|
+
assert(result.modified, "transform multiple property extractions")
|
|
30
|
+
assert.match(result.code, /function fn\(\s*\{\s*x,\s*y\s*\}/)
|
|
31
|
+
assert.doesNotMatch(result.code, /const x = obj\.x/)
|
|
32
|
+
assert.doesNotMatch(result.code, /const y = obj\.y/)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test("aliased extraction uses longhand destructuring", () => {
|
|
36
|
+
const result = transform(`
|
|
37
|
+
function fn(obj) {
|
|
38
|
+
const myX = obj.x;
|
|
39
|
+
return myX;
|
|
40
|
+
}
|
|
41
|
+
`)
|
|
42
|
+
|
|
43
|
+
assert(result.modified, "transform aliased property extraction")
|
|
44
|
+
assert.match(result.code, /function fn\(\s*\{\s*x:\s*myX\s*\}/)
|
|
45
|
+
assert.doesNotMatch(result.code, /const myX = obj\.x/)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test("skip when parameter is used after extractions", () => {
|
|
49
|
+
const result = transform(`
|
|
50
|
+
function fn(obj) {
|
|
51
|
+
const x = obj.x;
|
|
52
|
+
console.log(obj);
|
|
53
|
+
}
|
|
54
|
+
`)
|
|
55
|
+
|
|
56
|
+
assert.match(result.code, /const x = obj\.x/)
|
|
57
|
+
assert.match(result.code, /function fn\(obj\)/)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test("skip when parameter property is accessed after extractions", () => {
|
|
61
|
+
const result = transform(`
|
|
62
|
+
function fn(obj) {
|
|
63
|
+
const x = obj.x;
|
|
64
|
+
return x + obj.z;
|
|
65
|
+
}
|
|
66
|
+
`)
|
|
67
|
+
|
|
68
|
+
assert.match(result.code, /const x = obj\.x/)
|
|
69
|
+
assert.match(result.code, /function fn\(obj\)/)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test("skip computed property access", () => {
|
|
73
|
+
const result = transform(`
|
|
74
|
+
function fn(obj) {
|
|
75
|
+
const x = obj[key];
|
|
76
|
+
return x;
|
|
77
|
+
}
|
|
78
|
+
`)
|
|
79
|
+
|
|
80
|
+
assert(!result.modified, "skip computed property access")
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test("skip already-destructured parameter", () => {
|
|
84
|
+
const result = transform(`
|
|
85
|
+
function fn({x}) {
|
|
86
|
+
return x;
|
|
87
|
+
}
|
|
88
|
+
`)
|
|
89
|
+
|
|
90
|
+
assert(!result.modified, "skip already-destructured parameter")
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test("skip rest parameter", () => {
|
|
94
|
+
const result = transform(`
|
|
95
|
+
function fn(...args) {
|
|
96
|
+
return args;
|
|
97
|
+
}
|
|
98
|
+
`)
|
|
99
|
+
|
|
100
|
+
assert(!result.modified, "skip rest parameter")
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test("skip non-leading extraction", () => {
|
|
104
|
+
const result = transform(`
|
|
105
|
+
function fn(obj) {
|
|
106
|
+
const z = 42;
|
|
107
|
+
const x = obj.x;
|
|
108
|
+
return x + z;
|
|
109
|
+
}
|
|
110
|
+
`)
|
|
111
|
+
|
|
112
|
+
assert.match(result.code, /const x = obj\.x/)
|
|
113
|
+
assert.match(result.code, /function fn\(obj\)/)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test("stop at non-extraction in leading statements", () => {
|
|
117
|
+
const result = transform(`
|
|
118
|
+
function fn(obj) {
|
|
119
|
+
const x = obj.x;
|
|
120
|
+
const z = 42;
|
|
121
|
+
const y = obj.y;
|
|
122
|
+
return x + y + z;
|
|
123
|
+
}
|
|
124
|
+
`)
|
|
125
|
+
|
|
126
|
+
assert.match(result.code, /const x = obj\.x/)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test("function expression", () => {
|
|
130
|
+
const result = transform(`
|
|
131
|
+
const fn = function(obj) {
|
|
132
|
+
const x = obj.x;
|
|
133
|
+
return x;
|
|
134
|
+
};
|
|
135
|
+
`)
|
|
136
|
+
|
|
137
|
+
assert(result.modified, "transform function expression")
|
|
138
|
+
assert.match(result.code, /\{\s*x\s*\}/)
|
|
139
|
+
assert.doesNotMatch(result.code, /const x = obj\.x/)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test("arrow function", () => {
|
|
143
|
+
const result = transform(`
|
|
144
|
+
const fn = (obj) => {
|
|
145
|
+
const x = obj.x;
|
|
146
|
+
return x;
|
|
147
|
+
};
|
|
148
|
+
`)
|
|
149
|
+
|
|
150
|
+
assert(result.modified, "transform arrow function")
|
|
151
|
+
assert.match(result.code, /\{\s*x\s*\}/)
|
|
152
|
+
assert.doesNotMatch(result.code, /const x = obj\.x/)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test("multiple parameters transformed independently", () => {
|
|
156
|
+
const result = transform(`
|
|
157
|
+
function fn(a, b) {
|
|
158
|
+
const x = a.x;
|
|
159
|
+
const y = b.y;
|
|
160
|
+
return x + y;
|
|
161
|
+
}
|
|
162
|
+
`)
|
|
163
|
+
|
|
164
|
+
assert(result.modified, "transform multiple parameters")
|
|
165
|
+
assert.doesNotMatch(result.code, /const x = a\.x/)
|
|
166
|
+
assert.doesNotMatch(result.code, /const y = b\.y/)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test("mixed declarator statement preserves non-extraction", () => {
|
|
170
|
+
const result = transform(`
|
|
171
|
+
function fn(obj) {
|
|
172
|
+
const x = obj.x, z = 42;
|
|
173
|
+
return x + z;
|
|
174
|
+
}
|
|
175
|
+
`)
|
|
176
|
+
|
|
177
|
+
assert(result.modified, "transform mixed declarator")
|
|
178
|
+
assert.match(result.code, /const z = 42/)
|
|
179
|
+
assert.doesNotMatch(result.code, /obj/)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test("TypeScript type annotation is preserved", () => {
|
|
183
|
+
const result = transform(`
|
|
184
|
+
function fn(obj: MyType) {
|
|
185
|
+
const x = obj.x;
|
|
186
|
+
return x;
|
|
187
|
+
}
|
|
188
|
+
`)
|
|
189
|
+
|
|
190
|
+
assert(result.modified, "transform TypeScript typed parameter")
|
|
191
|
+
assert.match(result.code, /\{\s*x\s*\}:\s*MyType/)
|
|
192
|
+
assert.doesNotMatch(result.code, /const x = obj\.x/)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test("TypeScript inline type annotation is preserved", () => {
|
|
196
|
+
const result = transform(`
|
|
197
|
+
function fn(obj: { x: number }) {
|
|
198
|
+
const x = obj.x;
|
|
199
|
+
return x;
|
|
200
|
+
}
|
|
201
|
+
`)
|
|
202
|
+
|
|
203
|
+
assert(result.modified, "transform TypeScript inline typed parameter")
|
|
204
|
+
assert.match(result.code, /x: number/)
|
|
205
|
+
assert.doesNotMatch(result.code, /const x = obj\.x/)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
test("skip mixed declarator where non-extraction uses the param", () => {
|
|
209
|
+
const result = transform(`
|
|
210
|
+
function fn(obj) {
|
|
211
|
+
const x = obj.x, ref = obj;
|
|
212
|
+
return x;
|
|
213
|
+
}
|
|
214
|
+
`)
|
|
215
|
+
|
|
216
|
+
assert.match(result.code, /function fn\(obj\)/)
|
|
217
|
+
assert.match(result.code, /const x = obj\.x/)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
test("uninitialised variable declaration does not crash", () => {
|
|
221
|
+
const result = transform(`
|
|
222
|
+
function fn(obj) {
|
|
223
|
+
const x = obj.x;
|
|
224
|
+
let y;
|
|
225
|
+
return x;
|
|
226
|
+
}
|
|
227
|
+
`)
|
|
228
|
+
|
|
229
|
+
assert(result.modified, "transform with uninitialised variable in body")
|
|
230
|
+
assert.match(result.code, /function fn\(\s*\{\s*x\s*\}/)
|
|
231
|
+
assert.doesNotMatch(result.code, /const x = obj\.x/)
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
test("skip when nested function closes over the parameter", () => {
|
|
235
|
+
const result = transform(`
|
|
236
|
+
function fn(obj) {
|
|
237
|
+
const x = obj.x;
|
|
238
|
+
return () => obj;
|
|
239
|
+
}
|
|
240
|
+
`)
|
|
241
|
+
|
|
242
|
+
assert(
|
|
243
|
+
!result.modified,
|
|
244
|
+
"should not transform when nested function closes over parameter",
|
|
245
|
+
)
|
|
246
|
+
assert.match(result.code, /function fn\(obj\)/)
|
|
247
|
+
assert.match(result.code, /const x = obj\.x/)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
test("skip when extraction removal would promote use strict to a directive", () => {
|
|
251
|
+
const result = transform(`
|
|
252
|
+
function fn(obj) {
|
|
253
|
+
const x = obj.x;
|
|
254
|
+
"use strict";
|
|
255
|
+
return x;
|
|
256
|
+
}
|
|
257
|
+
`)
|
|
258
|
+
|
|
259
|
+
assert(
|
|
260
|
+
!result.modified,
|
|
261
|
+
"should not transform when removal would promote use strict directive",
|
|
262
|
+
)
|
|
263
|
+
assert.match(result.code, /function fn\(obj\)/)
|
|
264
|
+
assert.match(result.code, /const x = obj\.x/)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
test("transform when non-use-strict string literal follows extractions", () => {
|
|
268
|
+
const result = transform(`
|
|
269
|
+
function fn(obj) {
|
|
270
|
+
const x = obj.x;
|
|
271
|
+
"@jsx";
|
|
272
|
+
return x;
|
|
273
|
+
}
|
|
274
|
+
`)
|
|
275
|
+
|
|
276
|
+
assert(result.modified, "should transform when string literal is not use strict")
|
|
277
|
+
assert.match(result.code, /function fn\(\s*\{\s*x\s*\}/)
|
|
278
|
+
assert.doesNotMatch(result.code, /const x = obj\.x/)
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
test("skip when parameter is referenced in another parameter's default value", () => {
|
|
282
|
+
const result = transform(`
|
|
283
|
+
function fn(obj, y = obj.x) {
|
|
284
|
+
const x = obj.x;
|
|
285
|
+
return x + y;
|
|
286
|
+
}
|
|
287
|
+
`)
|
|
288
|
+
|
|
289
|
+
assert(
|
|
290
|
+
!result.modified,
|
|
291
|
+
"should not transform when param used in other param default",
|
|
292
|
+
)
|
|
293
|
+
assert.match(result.code, /function fn\(obj,/)
|
|
294
|
+
assert.match(result.code, /const x = obj\.x/)
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
test("skip when body contains sparse array pattern referencing parameter", () => {
|
|
298
|
+
const result = transform(`
|
|
299
|
+
function fn(obj) {
|
|
300
|
+
const x = obj.x;
|
|
301
|
+
const [, b] = obj.arr;
|
|
302
|
+
return x + b;
|
|
303
|
+
}
|
|
304
|
+
`)
|
|
305
|
+
|
|
306
|
+
assert(
|
|
307
|
+
!result.modified,
|
|
308
|
+
"should not transform when obj is used after extractions",
|
|
309
|
+
)
|
|
310
|
+
assert.match(result.code, /function fn\(obj\)/)
|
|
311
|
+
assert.match(result.code, /const x = obj\.x/)
|
|
312
|
+
})
|
|
313
|
+
})
|
|
314
|
+
})
|