Skip to content

Commit fe0e9dc

Browse files
author
Sebastian Silbermann
committed
fix: Find and replace type usage in type parameters of call expressions
1 parent f05624f commit fe0e9dc

12 files changed

Lines changed: 229 additions & 49 deletions

.changeset/short-zoos-type.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"types-react-codemod": patch
3+
---
4+
5+
Find and replace type usage in type parameters of call expressions
6+
7+
Now we properly detect that e.g. `JSX` is used in `someFunctionWithTypeParameters<JSX>()`.
8+
9+
Affected codemods:
10+
11+
- `deprecated-react-child`
12+
- `deprecated-react-fragment`
13+
- `deprecated-react-node-array`
14+
- `deprecated-react-text`
15+
- `scoped-jsx`

transforms/__tests__/deprecated-react-child.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,16 @@ describe("transform deprecated-react-child", () => {
7878
}"
7979
`);
8080
});
81+
82+
test("as type parameter", () => {
83+
expect(
84+
applyTransform(`
85+
import * as React from 'react';
86+
createAction<React.ReactChild>()
87+
`),
88+
).toMatchInlineSnapshot(`
89+
"import * as React from 'react';
90+
createAction<React.ReactElement | number | string>()"
91+
`);
92+
});
8193
});

transforms/__tests__/deprecated-react-fragment.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,16 @@ describe("transform deprecated-react-node-array", () => {
126126
}"
127127
`);
128128
});
129+
130+
test("as type parameter", () => {
131+
expect(
132+
applyTransform(`
133+
import * as React from 'react';
134+
createComponent<React.ReactFragment>();
135+
`),
136+
).toMatchInlineSnapshot(`
137+
"import * as React from 'react';
138+
createComponent<Iterable<React.ReactNode>>();"
139+
`);
140+
});
129141
});

transforms/__tests__/deprecated-react-node-array.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,16 @@ describe("transform deprecated-react-node-array", () => {
126126
}"
127127
`);
128128
});
129+
130+
test("in type parameters", () => {
131+
expect(
132+
applyTransform(`
133+
import * as React from 'react';
134+
createComponent<ReactNodeArray>();
135+
`),
136+
).toMatchInlineSnapshot(`
137+
"import * as React from 'react';
138+
createComponent<ReadonlyArray<ReactNode>>();"
139+
`);
140+
});
129141
});

transforms/__tests__/deprecated-react-text.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,16 @@ describe("transform deprecated-react-text", () => {
7878
}"
7979
`);
8080
});
81+
82+
test("in type parameters", () => {
83+
expect(
84+
applyTransform(`
85+
import * as React from 'react';
86+
createComponent<React.ReactText>();
87+
`),
88+
).toMatchInlineSnapshot(`
89+
"import * as React from 'react';
90+
createComponent<number | string>();"
91+
`);
92+
});
8193
});

transforms/__tests__/scoped-jsx.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const scopedJSXTransform = require("../scoped-jsx");
55

66
function applyTransform(source, options = {}) {
77
return JscodeshiftTestUtils.applyTransform(scopedJSXTransform, options, {
8-
path: "test.d.ts",
8+
path: "test.tsx",
99
source: dedent(source),
1010
});
1111
}
@@ -175,4 +175,20 @@ describe("transform scoped-jsx", () => {
175175
declare const attributes: JSX.LibraryManagedAttributes<A, B>;"
176176
`);
177177
});
178+
179+
test("detects usage in type parameters", () => {
180+
expect(
181+
applyTransform(`
182+
import React, { useMemo } from 'react'
183+
184+
[].reduce<JSX.Element[]>((acc, component, i) => {});
185+
[].reduce((acc, component, i) => {})
186+
`),
187+
).toMatchInlineSnapshot(`
188+
"import React, { useMemo, type JSX } from 'react';
189+
190+
[].reduce<JSX.Element[]>((acc, component, i) => {});
191+
[].reduce((acc, component, i) => {})"
192+
`);
193+
});
178194
});

transforms/deprecated-react-child.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
const parseSync = require("./utils/parseSync");
2+
const {
3+
findTSTypeReferenceCollections,
4+
} = require("./utils/jscodeshift-bugfixes");
25

36
/**
47
* @type {import('jscodeshift').Transform}
@@ -7,8 +10,10 @@ const deprecatedReactChildTransform = (file, api) => {
710
const j = api.jscodeshift;
811
const ast = parseSync(file);
912

10-
const changedIdentifiers = ast
11-
.find(j.TSTypeReference, (node) => {
13+
const reactChildTypeReferences = findTSTypeReferenceCollections(
14+
j,
15+
ast,
16+
(node) => {
1217
const { typeName } = node;
1318
/**
1419
* @type {import('jscodeshift').Identifier | null}
@@ -24,8 +29,12 @@ const deprecatedReactChildTransform = (file, api) => {
2429
}
2530

2631
return identifier !== null && identifier.name === "ReactChild";
27-
})
28-
.replaceWith(() => {
32+
},
33+
);
34+
35+
let didChangeIdentifiers = false;
36+
for (const typeReferences of reactChildTypeReferences) {
37+
const changedIdentifiers = typeReferences.replaceWith(() => {
2938
// `React.ReactElement | number | string`
3039
return j.tsUnionType([
3140
// React.ReactElement
@@ -39,9 +48,13 @@ const deprecatedReactChildTransform = (file, api) => {
3948
j.tsStringKeyword(),
4049
]);
4150
});
51+
if (changedIdentifiers.length > 0) {
52+
didChangeIdentifiers = true;
53+
}
54+
}
4255

4356
// Otherwise some files will be marked as "modified" because formatting changed
44-
if (changedIdentifiers.length > 0) {
57+
if (didChangeIdentifiers) {
4558
return ast.toSource();
4659
}
4760
return file.source;

transforms/deprecated-react-fragment.js

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
const parseSync = require("./utils/parseSync");
2+
const {
3+
findTSTypeReferenceCollections,
4+
} = require("./utils/jscodeshift-bugfixes");
25
/**
36
* @type {import('jscodeshift').Transform}
47
*/
58
const deprecatedReactFragmentTransform = (file, api) => {
69
const j = api.jscodeshift;
710
const ast = parseSync(file);
811

12+
let hasChanges = false;
13+
914
const hasReactNodeImport = ast.find(j.ImportSpecifier, (node) => {
1015
const { imported, local } = node;
1116
return (
@@ -24,6 +29,9 @@ const deprecatedReactFragmentTransform = (file, api) => {
2429
(local == null || local.name === "ReactFragment")
2530
);
2631
});
32+
if (reactFragmentImports.length > 0) {
33+
hasChanges = true;
34+
}
2735

2836
if (hasReactNodeImport.length > 0) {
2937
reactFragmentImports.remove();
@@ -40,15 +48,19 @@ const deprecatedReactFragmentTransform = (file, api) => {
4048
});
4149
}
4250

43-
const changedIdentifiers = ast
44-
.find(j.TSTypeReference, (node) => {
51+
const reactFragmentTypeReferences = findTSTypeReferenceCollections(
52+
j,
53+
ast,
54+
(node) => {
4555
const { typeName } = node;
4656

4757
return (
4858
typeName.type === "Identifier" && typeName.name === "ReactFragment"
4959
);
50-
})
51-
.replaceWith(() => {
60+
},
61+
);
62+
for (const typeReferences of reactFragmentTypeReferences) {
63+
const changedIdentifiers = typeReferences.replaceWith(() => {
5264
// `Iterable<ReactNode>`
5365
return j.tsTypeReference(
5466
j.identifier("Iterable"),
@@ -57,18 +69,26 @@ const deprecatedReactFragmentTransform = (file, api) => {
5769
]),
5870
);
5971
});
72+
if (changedIdentifiers.length > 0) {
73+
hasChanges = true;
74+
}
75+
}
6076

61-
const changedQualifiedNames = ast
62-
.find(j.TSTypeReference, (node) => {
77+
const reactFragmentQualifiedNamesReferences = findTSTypeReferenceCollections(
78+
j,
79+
ast,
80+
(node) => {
6381
const { typeName } = node;
6482

6583
return (
6684
typeName.type === "TSQualifiedName" &&
6785
typeName.right.type === "Identifier" &&
6886
typeName.right.name === "ReactFragment"
6987
);
70-
})
71-
.replaceWith((path) => {
88+
},
89+
);
90+
for (const typeReferences of reactFragmentQualifiedNamesReferences) {
91+
const changedQualifiedNames = typeReferences.replaceWith((path) => {
7292
const { node } = path;
7393
const typeName = /** @type {import('jscodeshift').TSQualifiedName} */ (
7494
node.typeName
@@ -83,13 +103,13 @@ const deprecatedReactFragmentTransform = (file, api) => {
83103
]),
84104
);
85105
});
106+
if (changedQualifiedNames.length > 0) {
107+
hasChanges = true;
108+
}
109+
}
86110

87111
// Otherwise some files will be marked as "modified" because formatting changed
88-
if (
89-
changedIdentifiers.length > 0 ||
90-
changedQualifiedNames.length > 0 ||
91-
reactFragmentImports.length > 0
92-
) {
112+
if (hasChanges) {
93113
return ast.toSource();
94114
}
95115
return file.source;

transforms/deprecated-react-node-array.js

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
const parseSync = require("./utils/parseSync");
2+
const {
3+
findTSTypeReferenceCollections,
4+
} = require("./utils/jscodeshift-bugfixes");
25

36
/**
47
* @type {import('jscodeshift').Transform}
@@ -7,6 +10,8 @@ const deprecatedReactNodeArrayTransform = (file, api) => {
710
const j = api.jscodeshift;
811
const ast = parseSync(file);
912

13+
let hasChanges = false;
14+
1015
const hasReactNodeImport = ast.find(j.ImportSpecifier, (node) => {
1116
const { imported, local } = node;
1217
return (
@@ -25,6 +30,9 @@ const deprecatedReactNodeArrayTransform = (file, api) => {
2530
(local == null || local.name === "ReactNodeArray")
2631
);
2732
});
33+
if (reactNodeArrayImports.length > 0) {
34+
hasChanges = true;
35+
}
2836

2937
if (hasReactNodeImport.length > 0) {
3038
reactNodeArrayImports.remove();
@@ -42,15 +50,19 @@ const deprecatedReactNodeArrayTransform = (file, api) => {
4250
});
4351
}
4452

45-
const changedIdentifiers = ast
46-
.find(j.TSTypeReference, (node) => {
53+
const reactNodeArrayTypeReferences = findTSTypeReferenceCollections(
54+
j,
55+
ast,
56+
(node) => {
4757
const { typeName } = node;
4858

4959
return (
5060
typeName.type === "Identifier" && typeName.name === "ReactNodeArray"
5161
);
52-
})
53-
.replaceWith(() => {
62+
},
63+
);
64+
for (const typeReferences of reactNodeArrayTypeReferences) {
65+
const changedIdentifiers = typeReferences.replaceWith(() => {
5466
// `ReadonlyArray<ReactNode>`
5567
return j.tsTypeReference(
5668
j.identifier("ReadonlyArray"),
@@ -60,17 +72,26 @@ const deprecatedReactNodeArrayTransform = (file, api) => {
6072
);
6173
});
6274

63-
const changedQualifiedNames = ast
64-
.find(j.TSTypeReference, (node) => {
75+
if (changedIdentifiers.length > 0) {
76+
hasChanges = true;
77+
}
78+
}
79+
80+
const reactNodeArrayQualifiedTypeReferences = findTSTypeReferenceCollections(
81+
j,
82+
ast,
83+
(node) => {
6584
const { typeName } = node;
6685

6786
return (
6887
typeName.type === "TSQualifiedName" &&
6988
typeName.right.type === "Identifier" &&
7089
typeName.right.name === "ReactNodeArray"
7190
);
72-
})
73-
.replaceWith((path) => {
91+
},
92+
);
93+
for (const typeReferences of reactNodeArrayQualifiedTypeReferences) {
94+
const changedQualifiedNames = typeReferences.replaceWith((path) => {
7495
const { node } = path;
7596
const typeName = /** @type {import('jscodeshift').TSQualifiedName} */ (
7697
node.typeName
@@ -86,12 +107,13 @@ const deprecatedReactNodeArrayTransform = (file, api) => {
86107
);
87108
});
88109

110+
if (changedQualifiedNames.length > 0) {
111+
hasChanges = true;
112+
}
113+
}
114+
89115
// Otherwise some files will be marked as "modified" because formatting changed
90-
if (
91-
changedIdentifiers.length > 0 ||
92-
changedQualifiedNames.length > 0 ||
93-
reactNodeArrayImports.length > 0
94-
) {
116+
if (hasChanges) {
95117
return ast.toSource();
96118
}
97119
return file.source;

0 commit comments

Comments
 (0)