Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Find and replace type usage in type parameters of call expressions #344

Merged
merged 1 commit into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changeset/short-zoos-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"types-react-codemod": patch
---

Find and replace type usage in type parameters of call expressions

Now we properly detect that e.g. `JSX` is used in `someFunctionWithTypeParameters<JSX>()`.

Affected codemods:

- `deprecated-react-child`
- `deprecated-react-fragment`
- `deprecated-react-node-array`
- `deprecated-react-text`
- `scoped-jsx`
12 changes: 12 additions & 0 deletions transforms/__tests__/deprecated-react-child.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,16 @@ describe("transform deprecated-react-child", () => {
}"
`);
});

test("as type parameter", () => {
expect(
applyTransform(`
import * as React from 'react';
createAction<React.ReactChild>()
`),
).toMatchInlineSnapshot(`
"import * as React from 'react';
createAction<React.ReactElement | number | string>()"
`);
});
});
12 changes: 12 additions & 0 deletions transforms/__tests__/deprecated-react-fragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,16 @@ describe("transform deprecated-react-node-array", () => {
}"
`);
});

test("as type parameter", () => {
expect(
applyTransform(`
import * as React from 'react';
createComponent<React.ReactFragment>();
`),
).toMatchInlineSnapshot(`
"import * as React from 'react';
createComponent<Iterable<React.ReactNode>>();"
`);
});
});
12 changes: 12 additions & 0 deletions transforms/__tests__/deprecated-react-node-array.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,16 @@ describe("transform deprecated-react-node-array", () => {
}"
`);
});

test("in type parameters", () => {
expect(
applyTransform(`
import * as React from 'react';
createComponent<ReactNodeArray>();
`),
).toMatchInlineSnapshot(`
"import * as React from 'react';
createComponent<ReadonlyArray<ReactNode>>();"
`);
});
});
12 changes: 12 additions & 0 deletions transforms/__tests__/deprecated-react-text.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,16 @@ describe("transform deprecated-react-text", () => {
}"
`);
});

test("in type parameters", () => {
expect(
applyTransform(`
import * as React from 'react';
createComponent<React.ReactText>();
`),
).toMatchInlineSnapshot(`
"import * as React from 'react';
createComponent<number | string>();"
`);
});
});
18 changes: 17 additions & 1 deletion transforms/__tests__/scoped-jsx.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const scopedJSXTransform = require("../scoped-jsx");

function applyTransform(source, options = {}) {
return JscodeshiftTestUtils.applyTransform(scopedJSXTransform, options, {
path: "test.d.ts",
path: "test.tsx",
source: dedent(source),
});
}
Expand Down Expand Up @@ -175,4 +175,20 @@ describe("transform scoped-jsx", () => {
declare const attributes: JSX.LibraryManagedAttributes<A, B>;"
`);
});

test("detects usage in type parameters", () => {
expect(
applyTransform(`
import React, { useMemo } from 'react'

[].reduce<JSX.Element[]>((acc, component, i) => {});
[].reduce((acc, component, i) => {})
`),
).toMatchInlineSnapshot(`
"import React, { useMemo, type JSX } from 'react';

[].reduce<JSX.Element[]>((acc, component, i) => {});
[].reduce((acc, component, i) => {})"
`);
});
});
23 changes: 18 additions & 5 deletions transforms/deprecated-react-child.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
const parseSync = require("./utils/parseSync");
const {
findTSTypeReferenceCollections,
} = require("./utils/jscodeshift-bugfixes");

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

const changedIdentifiers = ast
.find(j.TSTypeReference, (node) => {
const reactChildTypeReferences = findTSTypeReferenceCollections(
j,
ast,
(node) => {
const { typeName } = node;
/**
* @type {import('jscodeshift').Identifier | null}
Expand All @@ -24,8 +29,12 @@ const deprecatedReactChildTransform = (file, api) => {
}

return identifier !== null && identifier.name === "ReactChild";
})
.replaceWith(() => {
},
);

let didChangeIdentifiers = false;
for (const typeReferences of reactChildTypeReferences) {
const changedIdentifiers = typeReferences.replaceWith(() => {
// `React.ReactElement | number | string`
return j.tsUnionType([
// React.ReactElement
Expand All @@ -39,9 +48,13 @@ const deprecatedReactChildTransform = (file, api) => {
j.tsStringKeyword(),
]);
});
if (changedIdentifiers.length > 0) {
didChangeIdentifiers = true;
}
}

// Otherwise some files will be marked as "modified" because formatting changed
if (changedIdentifiers.length > 0) {
if (didChangeIdentifiers) {
return ast.toSource();
}
return file.source;
Expand Down
46 changes: 33 additions & 13 deletions transforms/deprecated-react-fragment.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
const parseSync = require("./utils/parseSync");
const {
findTSTypeReferenceCollections,
} = require("./utils/jscodeshift-bugfixes");
/**
* @type {import('jscodeshift').Transform}
*/
const deprecatedReactFragmentTransform = (file, api) => {
const j = api.jscodeshift;
const ast = parseSync(file);

let hasChanges = false;

const hasReactNodeImport = ast.find(j.ImportSpecifier, (node) => {
const { imported, local } = node;
return (
Expand All @@ -24,6 +29,9 @@ const deprecatedReactFragmentTransform = (file, api) => {
(local == null || local.name === "ReactFragment")
);
});
if (reactFragmentImports.length > 0) {
hasChanges = true;
}

if (hasReactNodeImport.length > 0) {
reactFragmentImports.remove();
Expand All @@ -40,15 +48,19 @@ const deprecatedReactFragmentTransform = (file, api) => {
});
}

const changedIdentifiers = ast
.find(j.TSTypeReference, (node) => {
const reactFragmentTypeReferences = findTSTypeReferenceCollections(
j,
ast,
(node) => {
const { typeName } = node;

return (
typeName.type === "Identifier" && typeName.name === "ReactFragment"
);
})
.replaceWith(() => {
},
);
for (const typeReferences of reactFragmentTypeReferences) {
const changedIdentifiers = typeReferences.replaceWith(() => {
// `Iterable<ReactNode>`
return j.tsTypeReference(
j.identifier("Iterable"),
Expand All @@ -57,18 +69,26 @@ const deprecatedReactFragmentTransform = (file, api) => {
]),
);
});
if (changedIdentifiers.length > 0) {
hasChanges = true;
}
}

const changedQualifiedNames = ast
.find(j.TSTypeReference, (node) => {
const reactFragmentQualifiedNamesReferences = findTSTypeReferenceCollections(
j,
ast,
(node) => {
const { typeName } = node;

return (
typeName.type === "TSQualifiedName" &&
typeName.right.type === "Identifier" &&
typeName.right.name === "ReactFragment"
);
})
.replaceWith((path) => {
},
);
for (const typeReferences of reactFragmentQualifiedNamesReferences) {
const changedQualifiedNames = typeReferences.replaceWith((path) => {
const { node } = path;
const typeName = /** @type {import('jscodeshift').TSQualifiedName} */ (
node.typeName
Expand All @@ -83,13 +103,13 @@ const deprecatedReactFragmentTransform = (file, api) => {
]),
);
});
if (changedQualifiedNames.length > 0) {
hasChanges = true;
}
}

// Otherwise some files will be marked as "modified" because formatting changed
if (
changedIdentifiers.length > 0 ||
changedQualifiedNames.length > 0 ||
reactFragmentImports.length > 0
) {
if (hasChanges) {
return ast.toSource();
}
return file.source;
Expand Down
48 changes: 35 additions & 13 deletions transforms/deprecated-react-node-array.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
const parseSync = require("./utils/parseSync");
const {
findTSTypeReferenceCollections,
} = require("./utils/jscodeshift-bugfixes");

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

let hasChanges = false;

const hasReactNodeImport = ast.find(j.ImportSpecifier, (node) => {
const { imported, local } = node;
return (
Expand All @@ -25,6 +30,9 @@ const deprecatedReactNodeArrayTransform = (file, api) => {
(local == null || local.name === "ReactNodeArray")
);
});
if (reactNodeArrayImports.length > 0) {
hasChanges = true;
}

if (hasReactNodeImport.length > 0) {
reactNodeArrayImports.remove();
Expand All @@ -42,15 +50,19 @@ const deprecatedReactNodeArrayTransform = (file, api) => {
});
}

const changedIdentifiers = ast
.find(j.TSTypeReference, (node) => {
const reactNodeArrayTypeReferences = findTSTypeReferenceCollections(
j,
ast,
(node) => {
const { typeName } = node;

return (
typeName.type === "Identifier" && typeName.name === "ReactNodeArray"
);
})
.replaceWith(() => {
},
);
for (const typeReferences of reactNodeArrayTypeReferences) {
const changedIdentifiers = typeReferences.replaceWith(() => {
// `ReadonlyArray<ReactNode>`
return j.tsTypeReference(
j.identifier("ReadonlyArray"),
Expand All @@ -60,17 +72,26 @@ const deprecatedReactNodeArrayTransform = (file, api) => {
);
});

const changedQualifiedNames = ast
.find(j.TSTypeReference, (node) => {
if (changedIdentifiers.length > 0) {
hasChanges = true;
}
}

const reactNodeArrayQualifiedTypeReferences = findTSTypeReferenceCollections(
j,
ast,
(node) => {
const { typeName } = node;

return (
typeName.type === "TSQualifiedName" &&
typeName.right.type === "Identifier" &&
typeName.right.name === "ReactNodeArray"
);
})
.replaceWith((path) => {
},
);
for (const typeReferences of reactNodeArrayQualifiedTypeReferences) {
const changedQualifiedNames = typeReferences.replaceWith((path) => {
const { node } = path;
const typeName = /** @type {import('jscodeshift').TSQualifiedName} */ (
node.typeName
Expand All @@ -86,12 +107,13 @@ const deprecatedReactNodeArrayTransform = (file, api) => {
);
});

if (changedQualifiedNames.length > 0) {
hasChanges = true;
}
}

// Otherwise some files will be marked as "modified" because formatting changed
if (
changedIdentifiers.length > 0 ||
changedQualifiedNames.length > 0 ||
reactNodeArrayImports.length > 0
) {
if (hasChanges) {
return ast.toSource();
}
return file.source;
Expand Down
Loading