Skip to content

Commit

Permalink
feat: Add deprecated-react-child (#50)
Browse files Browse the repository at this point in the history
* feat: Add `deprecated-react-child`

* f

* f

* Add to preset-19
  • Loading branch information
eps1lon committed Apr 26, 2022
1 parent 85bae50 commit 769a434
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 41 deletions.
2 changes: 1 addition & 1 deletion .changeset/selfish-jars-film.md
@@ -1,5 +1,5 @@
---
"types-react-codemod": patch
"types-react-codemod": minor
---

Add `deprecated-react-text` and `preset-19`.
7 changes: 7 additions & 0 deletions .changeset/warm-cobras-cough.md
@@ -0,0 +1,7 @@
---
"types-react-codemod": minor
---

Add `deprecated-react-child` transform.

Part of `preset-19`.
30 changes: 26 additions & 4 deletions README.md
Expand Up @@ -34,10 +34,10 @@ $ npx types-react-codemod --help
types-react-codemod <codemod> <paths...>

Positionals:
codemod [string] [required] [choices: "context-any", "deprecated-react-text",
"deprecated-react-type", "deprecated-sfc-element", "deprecated-sfc",
"deprecated-stateless-component", "implicit-children", "preset-18",
"preset-19", "useCallback-implicit-any"]
codemod [string] [required] [choices: "context-any", "deprecated-react-child",
"deprecated-react-text", "deprecated-react-type", "deprecated-sfc-element",
"deprecated-sfc", "deprecated-stateless-component", "implicit-children",
"preset-18", "preset-19", "useCallback-implicit-any"]
paths [string] [required]

Options:
Expand Down Expand Up @@ -205,6 +205,28 @@ By default, the codemods that are definitely required to upgrade to `@types/reac
The other codemods may or may not be required.
You should select all and audit the changed files regardless.

### `deprecated-react-child`

```diff
import * as React from "react";
interface Props {
- label?: React.ReactChild;
+ label?: React.ReactElement | number | string;
}
```

#### `deprecated-react-text` false-negative pattern A

Importing `ReactChild` via aliased named import will result in the transform being skipped.

```tsx
import { ReactChild as MyReactChild } from "react";
interface Props {
// not transformed
label?: MyReactChild;
}
```

### `deprecated-react-text`

```diff
Expand Down
28 changes: 0 additions & 28 deletions bin/__tests__/__snapshots__/types-react-codemod.js.snap

This file was deleted.

29 changes: 27 additions & 2 deletions bin/__tests__/types-react-codemod.js
Expand Up @@ -14,7 +14,32 @@ describe("types-react-codemod", () => {
}

test("provides help", async () => {
// TODO: toMatchInlineSnapshot fails with "Couldn't locate all inline snapshots."
await expect(execTypesReactCodemod("--help")).resolves.toMatchSnapshot();
await expect(execTypesReactCodemod("--help")).resolves
.toMatchInlineSnapshot(`
Object {
"stderr": "",
"stdout": "types-react-codemod <codemod> <paths...>
Positionals:
codemod [string] [required] [choices: \\"context-any\\", \\"deprecated-react-child\\",
\\"deprecated-react-text\\", \\"deprecated-react-type\\", \\"deprecated-sfc-element\\",
\\"deprecated-sfc\\", \\"deprecated-stateless-component\\", \\"implicit-children\\",
\\"preset-18\\", \\"preset-19\\", \\"useCallback-implicit-any\\"]
paths [string] [required]
Options:
--version Show version number [boolean]
--help Show help [boolean]
--dry [boolean] [default: false]
--ignore-pattern [string] [default: \\"**/node_modules/**\\"]
--verbose [boolean] [default: false]
Examples:
types-react-codemod preset-18 ./ Ignores \`node_modules\` and \`build\`
--ignore-pattern folders
\\"**/{node_modules,build}/**\\"
",
}
`);
});
});
5 changes: 4 additions & 1 deletion bin/types-react-codemod.cjs
Expand Up @@ -91,7 +91,10 @@ async function main() {
message: "Pick transforms to apply",
name: "presets",
type: "checkbox",
choices: [{ checked: true, value: "deprecated-react-text" }],
choices: [
{ checked: true, value: "deprecated-react-child" },
{ checked: true, value: "deprecated-react-text" },
],
},
]);
args.push(`--preset19Transforms="${presets.join(",")}"`);
Expand Down
81 changes: 81 additions & 0 deletions transforms/__tests__/deprecated-react-child.js
@@ -0,0 +1,81 @@
import { describe, expect, test } from "@jest/globals";
import dedent from "dedent";
import * as JscodeshiftTestUtils from "jscodeshift/dist/testUtils";
import deprecatedReactChildTransform from "../deprecated-react-child";

function applyTransform(source, options = {}) {
return JscodeshiftTestUtils.applyTransform(
deprecatedReactChildTransform,
options,
{
path: "test.d.ts",
source: dedent(source),
}
);
}

describe("transform deprecated-react-child", () => {
test("not modified", () => {
expect(
applyTransform(`
import * as React from 'react';
interface Props {
children?: ReactNode;
}
`)
).toMatchInlineSnapshot(`
"import * as React from 'react';
interface Props {
children?: ReactNode;
}"
`);
});

test("named import", () => {
expect(
applyTransform(`
import { ReactChild } from 'react';
interface Props {
children?: ReactChild;
}
`)
).toMatchInlineSnapshot(`
"import { ReactChild } from 'react';
interface Props {
children?: React.ReactElement | number | string;
}"
`);
});

test("false-negative named renamed import", () => {
expect(
applyTransform(`
import { ReactChild as MyReactChild } from 'react';
interface Props {
children?: MyReactChild;
}
`)
).toMatchInlineSnapshot(`
"import { ReactChild as MyReactChild } from 'react';
interface Props {
children?: MyReactChild;
}"
`);
});

test("namespace import", () => {
expect(
applyTransform(`
import * as React from 'react';
interface Props {
children?: React.ReactChild;
}
`)
).toMatchInlineSnapshot(`
"import * as React from 'react';
interface Props {
children?: React.ReactElement | number | string;
}"
`);
});
});
13 changes: 8 additions & 5 deletions transforms/__tests__/preset-19.js
Expand Up @@ -4,6 +4,7 @@ import * as JscodeshiftTestUtils from "jscodeshift/dist/testUtils";

describe("preset-19", () => {
let preset19Transform;
let deprecatedReactChildTransform;
let deprecatedReactTextTransform;

function applyTransform(source, options = {}) {
Expand All @@ -29,6 +30,7 @@ describe("preset-19", () => {
return transform;
}

deprecatedReactChildTransform = mockTransform("../deprecated-react-child");
deprecatedReactTextTransform = mockTransform("../deprecated-react-text");

preset19Transform = require("../preset-19");
Expand All @@ -39,18 +41,19 @@ describe("preset-19", () => {
preset19Transforms: "deprecated-react-text",
});

expect(deprecatedReactChildTransform).not.toHaveBeenCalled();
expect(deprecatedReactTextTransform).toHaveBeenCalled();
});

test("applies all", () => {
applyTransform("", {
preset19Transforms: ["deprecated-react-text"].join(","),
});

applyTransform("", {
preset19Transforms: "deprecated-react-text",
preset19Transforms: [
"deprecated-react-child",
"deprecated-react-text",
].join(","),
});

expect(deprecatedReactChildTransform).toHaveBeenCalled();
expect(deprecatedReactTextTransform).toHaveBeenCalled();
});
});
47 changes: 47 additions & 0 deletions transforms/deprecated-react-child.js
@@ -0,0 +1,47 @@
const parseSync = require("./utils/parseSync");

/**
* @type {import('jscodeshift').Transform}
*/
const deprecatedReactChildTransform = (file, api) => {
const j = api.jscodeshift;
const ast = parseSync(file);

const changedIdentifiers = ast
.find(j.TSTypeReference, (node) => {
const { typeName } = node;
/**
* @type {import('jscodeshift').Identifier | null}
*/
let identifier = null;
if (typeName.type === "Identifier") {
identifier = typeName;
} else if (
typeName.type === "TSQualifiedName" &&
typeName.right.type === "Identifier"
) {
identifier = typeName.right;
}

return identifier !== null && identifier.name === "ReactChild";
})
.replaceWith(() => {
// `React.ReactElement | number | string`
return j.tsUnionType([
// React.ReactElement
j.tsTypeReference(
j.tsQualifiedName(j.identifier("React"), j.identifier("ReactElement"))
),
j.tsNumberKeyword(),
j.tsStringKeyword(),
]);
});

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

export default deprecatedReactChildTransform;
4 changes: 4 additions & 0 deletions transforms/preset-19.js
@@ -1,3 +1,4 @@
import deprecatedReactChildTransform from "./deprecated-react-child";
import deprecatedReactTextTransform from "./deprecated-react-text";

/**
Expand All @@ -11,6 +12,9 @@ const transform = (file, api, options) => {
* @type {import('jscodeshift').Transform[]}
*/
const transforms = [];
if (transformNames.has("deprecated-react-child")) {
transforms.push(deprecatedReactChildTransform);
}
if (transformNames.has("deprecated-react-text")) {
transforms.push(deprecatedReactTextTransform);
}
Expand Down

0 comments on commit 769a434

Please sign in to comment.