From 18c710e03c6ffd7909ca391d0cbee078e85d0304 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Tue, 26 Apr 2022 20:57:47 +0200 Subject: [PATCH 1/4] feat: Add `deprecated-react-child` --- .changeset/warm-cobras-cough.md | 7 ++ README.md | 30 ++++++- .../__snapshots__/types-react-codemod.js.snap | 28 ------- bin/__tests__/types-react-codemod.js | 29 ++++++- .../__tests__/deprecated-react-child.js | 81 +++++++++++++++++++ transforms/__tests__/preset-19.js | 13 +-- transforms/deprecated-react-child.js | 47 +++++++++++ transforms/preset-19.js | 4 + 8 files changed, 200 insertions(+), 39 deletions(-) create mode 100644 .changeset/warm-cobras-cough.md delete mode 100644 bin/__tests__/__snapshots__/types-react-codemod.js.snap create mode 100644 transforms/__tests__/deprecated-react-child.js create mode 100644 transforms/deprecated-react-child.js diff --git a/.changeset/warm-cobras-cough.md b/.changeset/warm-cobras-cough.md new file mode 100644 index 00000000..374dfb9f --- /dev/null +++ b/.changeset/warm-cobras-cough.md @@ -0,0 +1,7 @@ +--- +"types-react-codemod": minor +--- + +Add `deprecated-react-child` transform. + +Part of `preset-19`. diff --git a/README.md b/README.md index f6604128..68ab04a6 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,10 @@ $ npx types-react-codemod --help types-react-codemod 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: @@ -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 diff --git a/bin/__tests__/__snapshots__/types-react-codemod.js.snap b/bin/__tests__/__snapshots__/types-react-codemod.js.snap deleted file mode 100644 index 5f3ed211..00000000 --- a/bin/__tests__/__snapshots__/types-react-codemod.js.snap +++ /dev/null @@ -1,28 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`types-react-codemod provides help 1`] = ` -Object { - "stderr": "", - "stdout": "types-react-codemod - -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\\"] - 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}/**\\" -", -} -`; diff --git a/bin/__tests__/types-react-codemod.js b/bin/__tests__/types-react-codemod.js index 3026cffc..ec0c82f4 100644 --- a/bin/__tests__/types-react-codemod.js +++ b/bin/__tests__/types-react-codemod.js @@ -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 + + 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}/**\\" + ", + } + `); }); }); diff --git a/transforms/__tests__/deprecated-react-child.js b/transforms/__tests__/deprecated-react-child.js new file mode 100644 index 00000000..1dbe5a06 --- /dev/null +++ b/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; + }" + `); + }); +}); diff --git a/transforms/__tests__/preset-19.js b/transforms/__tests__/preset-19.js index d0a466c6..ab682131 100644 --- a/transforms/__tests__/preset-19.js +++ b/transforms/__tests__/preset-19.js @@ -4,6 +4,7 @@ import * as JscodeshiftTestUtils from "jscodeshift/dist/testUtils"; describe("preset-19", () => { let preset19Transform; + let deprecatedReactChildTransform; let deprecatedReactTextTransform; function applyTransform(source, options = {}) { @@ -29,6 +30,7 @@ describe("preset-19", () => { return transform; } + deprecatedReactChildTransform = mockTransform("../deprecated-react-child"); deprecatedReactTextTransform = mockTransform("../deprecated-react-text"); preset19Transform = require("../preset-19"); @@ -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(); }); }); diff --git a/transforms/deprecated-react-child.js b/transforms/deprecated-react-child.js new file mode 100644 index 00000000..6077da7f --- /dev/null +++ b/transforms/deprecated-react-child.js @@ -0,0 +1,47 @@ +const parseSync = require("./utils/parseSync"); + +/** + * @type {import('jscodeshift').Transform} + */ +const deprecatedReactTextTransform = (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 deprecatedReactTextTransform; diff --git a/transforms/preset-19.js b/transforms/preset-19.js index 97752446..79a83e41 100644 --- a/transforms/preset-19.js +++ b/transforms/preset-19.js @@ -1,3 +1,4 @@ +import deprecatedReactChildTransform from "./deprecated-react-child"; import deprecatedReactTextTransform from "./deprecated-react-text"; /** @@ -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); } From 58688d8084b79eef9f312a300803a2c35c709275 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Tue, 26 Apr 2022 20:58:37 +0200 Subject: [PATCH 2/4] f --- .changeset/selfish-jars-film.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/selfish-jars-film.md b/.changeset/selfish-jars-film.md index 0622f4cf..a8b08d4e 100644 --- a/.changeset/selfish-jars-film.md +++ b/.changeset/selfish-jars-film.md @@ -1,5 +1,5 @@ --- -"types-react-codemod": patch +"types-react-codemod": minor --- Add `deprecated-react-text` and `preset-19`. From 71510c19a6cdca88c7f5d932e66f61634a0c57b4 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Tue, 26 Apr 2022 20:59:38 +0200 Subject: [PATCH 3/4] f --- transforms/deprecated-react-child.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transforms/deprecated-react-child.js b/transforms/deprecated-react-child.js index 6077da7f..054fe392 100644 --- a/transforms/deprecated-react-child.js +++ b/transforms/deprecated-react-child.js @@ -3,7 +3,7 @@ const parseSync = require("./utils/parseSync"); /** * @type {import('jscodeshift').Transform} */ -const deprecatedReactTextTransform = (file, api) => { +const deprecatedReactChildTransform = (file, api) => { const j = api.jscodeshift; const ast = parseSync(file); @@ -44,4 +44,4 @@ const deprecatedReactTextTransform = (file, api) => { return file.source; }; -export default deprecatedReactTextTransform; +export default deprecatedReactChildTransform; From 4336123047d61ef88da8b0261c7a33f0c154b91c Mon Sep 17 00:00:00 2001 From: eps1lon Date: Tue, 26 Apr 2022 21:00:48 +0200 Subject: [PATCH 4/4] Add to preset-19 --- bin/types-react-codemod.cjs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/types-react-codemod.cjs b/bin/types-react-codemod.cjs index 6dcd683b..d0c60b17 100755 --- a/bin/types-react-codemod.cjs +++ b/bin/types-react-codemod.cjs @@ -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(",")}"`);