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

feat: Add deprecated-react-child #50

Merged
merged 4 commits into from
Apr 26, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/selfish-jars-film.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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