From 115dedb872bd6af18e5ef5697c5d892419ac7fc5 Mon Sep 17 00:00:00 2001 From: Sasha Mysak Date: Wed, 3 Apr 2024 17:07:43 +0200 Subject: [PATCH 01/11] feat: react v19 codemods --- .gitignore | 1 + babel.config.js | 4 + .../remove-context-provider/.codemodrc.json | 13 + codemods/remove-context-provider/README.md | 35 + .../__testfixtures__/fixture1.input.js | 9 + .../__testfixtures__/fixture1.output.js | 9 + .../__testfixtures__/fixture2.input.js | 9 + .../__testfixtures__/fixture2.output.js | 9 + .../__testfixtures__/fixture3.input.js | 9 + .../__testfixtures__/fixture3.output.js | 9 + .../__testfixtures__/fixture4.input.ts | 9 + .../__testfixtures__/fixture4.output.ts | 9 + .../__testfixtures__/fixture5.input.ts | 9 + .../__testfixtures__/fixture5.output.ts | 9 + .../__testfixtures__/fixture6.input.ts | 9 + .../__testfixtures__/fixture6.output.ts | 9 + .../remove-context-provider/__tests__/test.ts | 164 +++++ codemods/remove-context-provider/package.json | 17 + codemods/remove-context-provider/src/index.ts | 37 + .../remove-context-provider/tsconfig.json | 23 + codemods/remove-forward-ref/.codemodrc.json | 14 + codemods/remove-forward-ref/README.md | 25 + .../__testfixtures__/fixture1.input.js | 5 + .../__testfixtures__/fixture1.output.js | 4 + .../__testfixtures__/fixture2.input.js | 5 + .../__testfixtures__/fixture2.output.js | 4 + .../__testfixtures__/fixture3.input.js | 5 + .../__testfixtures__/fixture3.output.js | 4 + .../__testfixtures__/fixture4.input.ts | 6 + .../__testfixtures__/fixture4.output.ts | 6 + .../__testfixtures__/fixture5.input.js | 5 + .../__testfixtures__/fixture5.output.js | 6 + .../__testfixtures__/fixture6.input.js | 5 + .../__testfixtures__/fixture6.output.js | 3 + .../__testfixtures__/fixture7.input.js | 5 + .../__testfixtures__/fixture7.output.js | 4 + .../__testfixtures__/fixture8.input.ts | 6 + .../__testfixtures__/fixture8.output.ts | 5 + .../__testfixtures__/fixture9.input.ts | 8 + .../__testfixtures__/fixture9.output.ts | 6 + codemods/remove-forward-ref/__tests__/test.ts | 227 ++++++ codemods/remove-forward-ref/package.json | 17 + codemods/remove-forward-ref/src/index.ts | 248 +++++++ codemods/remove-forward-ref/tsconfig.json | 23 + .../remove-memoization-hooks/.codemodrc.json | 13 + codemods/remove-memoization-hooks/README.md | 5 + .../__testfixtures__/fixture1.input.js | 8 + .../__testfixtures__/fixture1.output.js | 6 + .../__testfixtures__/fixture2.input.js | 8 + .../__testfixtures__/fixture2.output.js | 6 + .../__testfixtures__/fixture3.input.js | 7 + .../__testfixtures__/fixture3.output.js | 5 + .../__testfixtures__/fixture4.input.js | 21 + .../__testfixtures__/fixture4.output.js | 21 + .../__testfixtures__/fixture5.input.ts | 8 + .../__testfixtures__/fixture5.output.ts | 6 + .../__testfixtures__/fixture6.input.ts | 8 + .../__testfixtures__/fixture6.output.ts | 6 + .../__testfixtures__/fixture7.input.ts | 7 + .../__testfixtures__/fixture7.output.ts | 7 + .../__testfixtures__/fixture8.input.ts | 21 + .../__testfixtures__/fixture8.output.ts | 21 + .../__tests__/test.ts | 210 ++++++ .../remove-memoization-hooks/package.json | 17 + .../remove-memoization-hooks/src/index.ts | 56 ++ .../remove-memoization-hooks/tsconfig.json | 23 + codemods/use-context-hook/.codemodrc.json | 13 + codemods/use-context-hook/README.md | 25 + .../__testfixtures__/fixture1.input.js | 4 + .../__testfixtures__/fixture1.output.js | 4 + .../__testfixtures__/fixture2.input.js | 4 + .../__testfixtures__/fixture2.output.js | 4 + .../__testfixtures__/fixture3.input.js | 1 + .../__testfixtures__/fixture3.output.js | 1 + .../__testfixtures__/fixture4.input.ts | 11 + .../__testfixtures__/fixture4.output.ts | 11 + .../__testfixtures__/fixture5.input.ts | 11 + .../__testfixtures__/fixture5.output.ts | 11 + .../__testfixtures__/fixture6.input.ts | 8 + .../__testfixtures__/fixture6.output.ts | 8 + codemods/use-context-hook/__tests__/test.ts | 176 +++++ codemods/use-context-hook/package.json | 17 + codemods/use-context-hook/src/index.ts | 67 ++ codemods/use-context-hook/tsconfig.json | 23 + package.json | 6 +- scripts/jest/config.base.js | 2 +- yarn.lock | 683 +++++++++++++++++- 87 files changed, 2590 insertions(+), 38 deletions(-) create mode 100644 codemods/remove-context-provider/.codemodrc.json create mode 100644 codemods/remove-context-provider/README.md create mode 100644 codemods/remove-context-provider/__testfixtures__/fixture1.input.js create mode 100644 codemods/remove-context-provider/__testfixtures__/fixture1.output.js create mode 100644 codemods/remove-context-provider/__testfixtures__/fixture2.input.js create mode 100644 codemods/remove-context-provider/__testfixtures__/fixture2.output.js create mode 100644 codemods/remove-context-provider/__testfixtures__/fixture3.input.js create mode 100644 codemods/remove-context-provider/__testfixtures__/fixture3.output.js create mode 100644 codemods/remove-context-provider/__testfixtures__/fixture4.input.ts create mode 100644 codemods/remove-context-provider/__testfixtures__/fixture4.output.ts create mode 100644 codemods/remove-context-provider/__testfixtures__/fixture5.input.ts create mode 100644 codemods/remove-context-provider/__testfixtures__/fixture5.output.ts create mode 100644 codemods/remove-context-provider/__testfixtures__/fixture6.input.ts create mode 100644 codemods/remove-context-provider/__testfixtures__/fixture6.output.ts create mode 100644 codemods/remove-context-provider/__tests__/test.ts create mode 100644 codemods/remove-context-provider/package.json create mode 100644 codemods/remove-context-provider/src/index.ts create mode 100644 codemods/remove-context-provider/tsconfig.json create mode 100644 codemods/remove-forward-ref/.codemodrc.json create mode 100644 codemods/remove-forward-ref/README.md create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture1.input.js create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture1.output.js create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture2.input.js create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture2.output.js create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture3.input.js create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture3.output.js create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture4.input.ts create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture4.output.ts create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture5.input.js create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture5.output.js create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture6.input.js create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture6.output.js create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture7.input.js create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture7.output.js create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture8.input.ts create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture8.output.ts create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture9.input.ts create mode 100644 codemods/remove-forward-ref/__testfixtures__/fixture9.output.ts create mode 100644 codemods/remove-forward-ref/__tests__/test.ts create mode 100644 codemods/remove-forward-ref/package.json create mode 100644 codemods/remove-forward-ref/src/index.ts create mode 100644 codemods/remove-forward-ref/tsconfig.json create mode 100644 codemods/remove-memoization-hooks/.codemodrc.json create mode 100644 codemods/remove-memoization-hooks/README.md create mode 100644 codemods/remove-memoization-hooks/__testfixtures__/fixture1.input.js create mode 100644 codemods/remove-memoization-hooks/__testfixtures__/fixture1.output.js create mode 100644 codemods/remove-memoization-hooks/__testfixtures__/fixture2.input.js create mode 100644 codemods/remove-memoization-hooks/__testfixtures__/fixture2.output.js create mode 100644 codemods/remove-memoization-hooks/__testfixtures__/fixture3.input.js create mode 100644 codemods/remove-memoization-hooks/__testfixtures__/fixture3.output.js create mode 100644 codemods/remove-memoization-hooks/__testfixtures__/fixture4.input.js create mode 100644 codemods/remove-memoization-hooks/__testfixtures__/fixture4.output.js create mode 100644 codemods/remove-memoization-hooks/__testfixtures__/fixture5.input.ts create mode 100644 codemods/remove-memoization-hooks/__testfixtures__/fixture5.output.ts create mode 100644 codemods/remove-memoization-hooks/__testfixtures__/fixture6.input.ts create mode 100644 codemods/remove-memoization-hooks/__testfixtures__/fixture6.output.ts create mode 100644 codemods/remove-memoization-hooks/__testfixtures__/fixture7.input.ts create mode 100644 codemods/remove-memoization-hooks/__testfixtures__/fixture7.output.ts create mode 100644 codemods/remove-memoization-hooks/__testfixtures__/fixture8.input.ts create mode 100644 codemods/remove-memoization-hooks/__testfixtures__/fixture8.output.ts create mode 100644 codemods/remove-memoization-hooks/__tests__/test.ts create mode 100644 codemods/remove-memoization-hooks/package.json create mode 100644 codemods/remove-memoization-hooks/src/index.ts create mode 100644 codemods/remove-memoization-hooks/tsconfig.json create mode 100644 codemods/use-context-hook/.codemodrc.json create mode 100644 codemods/use-context-hook/README.md create mode 100644 codemods/use-context-hook/__testfixtures__/fixture1.input.js create mode 100644 codemods/use-context-hook/__testfixtures__/fixture1.output.js create mode 100644 codemods/use-context-hook/__testfixtures__/fixture2.input.js create mode 100644 codemods/use-context-hook/__testfixtures__/fixture2.output.js create mode 100644 codemods/use-context-hook/__testfixtures__/fixture3.input.js create mode 100644 codemods/use-context-hook/__testfixtures__/fixture3.output.js create mode 100644 codemods/use-context-hook/__testfixtures__/fixture4.input.ts create mode 100644 codemods/use-context-hook/__testfixtures__/fixture4.output.ts create mode 100644 codemods/use-context-hook/__testfixtures__/fixture5.input.ts create mode 100644 codemods/use-context-hook/__testfixtures__/fixture5.output.ts create mode 100644 codemods/use-context-hook/__testfixtures__/fixture6.input.ts create mode 100644 codemods/use-context-hook/__testfixtures__/fixture6.output.ts create mode 100644 codemods/use-context-hook/__tests__/test.ts create mode 100644 codemods/use-context-hook/package.json create mode 100644 codemods/use-context-hook/src/index.ts create mode 100644 codemods/use-context-hook/tsconfig.json diff --git a/.gitignore b/.gitignore index 6ec345e172e5e..f9d363c0c49d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_STORE +dist node_modules scripts/flow/*/.flowconfig .flowconfig diff --git a/babel.config.js b/babel.config.js index f8a28b20cc87d..eb9480d309387 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,6 +1,10 @@ 'use strict'; module.exports = { + presets: [ + ['@babel/preset-env', {targets: {node: 'current'}}], + '@babel/preset-typescript', + ], plugins: [ '@babel/plugin-syntax-jsx', '@babel/plugin-transform-flow-strip-types', diff --git a/codemods/remove-context-provider/.codemodrc.json b/codemods/remove-context-provider/.codemodrc.json new file mode 100644 index 0000000000000..2e1c68d234d8c --- /dev/null +++ b/codemods/remove-context-provider/.codemodrc.json @@ -0,0 +1,13 @@ +{ + "version": "1.0.0", + "name": "react/19/remove-context-provider", + "private": false, + "engine": "jscodeshift", + "meta": { + "tags": ["react"], + "useCaseCategory": "migration" + }, + "applicability": { + "from": [["react", "<=", "^18.0.0"]] + } +} diff --git a/codemods/remove-context-provider/README.md b/codemods/remove-context-provider/README.md new file mode 100644 index 0000000000000..fa1b733d5ffbc --- /dev/null +++ b/codemods/remove-context-provider/README.md @@ -0,0 +1,35 @@ +# Change Context.Provider to Context + +## Description + +This codemod will remove the usage of `Provider` for contexts; e.g., Context.Provider to Context + +## Example + +### Before: + +```tsx +function App() { + const [theme, setTheme] = useState('light'); + // ... + return ( + + + + ); +} +``` + +### After: + +```tsx +function App() { + const [theme, setTheme] = useState('light'); + // ... + return ( + + + + ); +} +``` \ No newline at end of file diff --git a/codemods/remove-context-provider/__testfixtures__/fixture1.input.js b/codemods/remove-context-provider/__testfixtures__/fixture1.input.js new file mode 100644 index 0000000000000..a066cc86af5e4 --- /dev/null +++ b/codemods/remove-context-provider/__testfixtures__/fixture1.input.js @@ -0,0 +1,9 @@ +function App() { + const [theme, setTheme] = useState('light'); + + return ( + + + + ); +} \ No newline at end of file diff --git a/codemods/remove-context-provider/__testfixtures__/fixture1.output.js b/codemods/remove-context-provider/__testfixtures__/fixture1.output.js new file mode 100644 index 0000000000000..84f317b83b1bd --- /dev/null +++ b/codemods/remove-context-provider/__testfixtures__/fixture1.output.js @@ -0,0 +1,9 @@ +function App() { + const [theme, setTheme] = useState('light'); + + return ( + + + + ); +} \ No newline at end of file diff --git a/codemods/remove-context-provider/__testfixtures__/fixture2.input.js b/codemods/remove-context-provider/__testfixtures__/fixture2.input.js new file mode 100644 index 0000000000000..18cb6e6c52a7b --- /dev/null +++ b/codemods/remove-context-provider/__testfixtures__/fixture2.input.js @@ -0,0 +1,9 @@ +function App() { + const [theme, setTheme] = useState('light'); + + return ( + + + + ); +} \ No newline at end of file diff --git a/codemods/remove-context-provider/__testfixtures__/fixture2.output.js b/codemods/remove-context-provider/__testfixtures__/fixture2.output.js new file mode 100644 index 0000000000000..9ef65738c692d --- /dev/null +++ b/codemods/remove-context-provider/__testfixtures__/fixture2.output.js @@ -0,0 +1,9 @@ +function App() { + const [theme, setTheme] = useState('light'); + + return ( + + + + ); +} \ No newline at end of file diff --git a/codemods/remove-context-provider/__testfixtures__/fixture3.input.js b/codemods/remove-context-provider/__testfixtures__/fixture3.input.js new file mode 100644 index 0000000000000..9ef65738c692d --- /dev/null +++ b/codemods/remove-context-provider/__testfixtures__/fixture3.input.js @@ -0,0 +1,9 @@ +function App() { + const [theme, setTheme] = useState('light'); + + return ( + + + + ); +} \ No newline at end of file diff --git a/codemods/remove-context-provider/__testfixtures__/fixture3.output.js b/codemods/remove-context-provider/__testfixtures__/fixture3.output.js new file mode 100644 index 0000000000000..9ef65738c692d --- /dev/null +++ b/codemods/remove-context-provider/__testfixtures__/fixture3.output.js @@ -0,0 +1,9 @@ +function App() { + const [theme, setTheme] = useState('light'); + + return ( + + + + ); +} \ No newline at end of file diff --git a/codemods/remove-context-provider/__testfixtures__/fixture4.input.ts b/codemods/remove-context-provider/__testfixtures__/fixture4.input.ts new file mode 100644 index 0000000000000..978720546270f --- /dev/null +++ b/codemods/remove-context-provider/__testfixtures__/fixture4.input.ts @@ -0,0 +1,9 @@ +function App({ url }: { url: string }) { + const [theme, setTheme] = useState<'light' | 'dark'>('light'); + + return ( + + + + ); +} \ No newline at end of file diff --git a/codemods/remove-context-provider/__testfixtures__/fixture4.output.ts b/codemods/remove-context-provider/__testfixtures__/fixture4.output.ts new file mode 100644 index 0000000000000..f1d45694f4883 --- /dev/null +++ b/codemods/remove-context-provider/__testfixtures__/fixture4.output.ts @@ -0,0 +1,9 @@ +function App({ url }: { url: string }) { + const [theme, setTheme] = useState<'light' | 'dark'>('light'); + + return ( + + + + ); +} \ No newline at end of file diff --git a/codemods/remove-context-provider/__testfixtures__/fixture5.input.ts b/codemods/remove-context-provider/__testfixtures__/fixture5.input.ts new file mode 100644 index 0000000000000..b8d7261041a1b --- /dev/null +++ b/codemods/remove-context-provider/__testfixtures__/fixture5.input.ts @@ -0,0 +1,9 @@ +function App({ url }: { url: string }) { + const [theme, setTheme] = useState<'light' | 'dark'>('light'); + + return ( + + + + ); +} \ No newline at end of file diff --git a/codemods/remove-context-provider/__testfixtures__/fixture5.output.ts b/codemods/remove-context-provider/__testfixtures__/fixture5.output.ts new file mode 100644 index 0000000000000..357cc7c77c62c --- /dev/null +++ b/codemods/remove-context-provider/__testfixtures__/fixture5.output.ts @@ -0,0 +1,9 @@ +function App({ url }: { url: string }) { + const [theme, setTheme] = useState<'light' | 'dark'>('light'); + + return ( + + + + ); +} \ No newline at end of file diff --git a/codemods/remove-context-provider/__testfixtures__/fixture6.input.ts b/codemods/remove-context-provider/__testfixtures__/fixture6.input.ts new file mode 100644 index 0000000000000..357cc7c77c62c --- /dev/null +++ b/codemods/remove-context-provider/__testfixtures__/fixture6.input.ts @@ -0,0 +1,9 @@ +function App({ url }: { url: string }) { + const [theme, setTheme] = useState<'light' | 'dark'>('light'); + + return ( + + + + ); +} \ No newline at end of file diff --git a/codemods/remove-context-provider/__testfixtures__/fixture6.output.ts b/codemods/remove-context-provider/__testfixtures__/fixture6.output.ts new file mode 100644 index 0000000000000..357cc7c77c62c --- /dev/null +++ b/codemods/remove-context-provider/__testfixtures__/fixture6.output.ts @@ -0,0 +1,9 @@ +function App({ url }: { url: string }) { + const [theme, setTheme] = useState<'light' | 'dark'>('light'); + + return ( + + + + ); +} \ No newline at end of file diff --git a/codemods/remove-context-provider/__tests__/test.ts b/codemods/remove-context-provider/__tests__/test.ts new file mode 100644 index 0000000000000..d6e5abfc92fb5 --- /dev/null +++ b/codemods/remove-context-provider/__tests__/test.ts @@ -0,0 +1,164 @@ +import assert from 'node:assert/strict'; +import {readFile} from 'node:fs/promises'; +import {join} from 'node:path'; +import jscodeshift, {API, FileInfo} from 'jscodeshift'; +import transform from '../src/index.js'; + +export const buildApi = (parser: string | undefined): API => ({ + j: parser ? jscodeshift.withParser(parser) : jscodeshift, + jscodeshift: parser ? jscodeshift.withParser(parser) : jscodeshift, + stats: () => { + console.error( + 'The stats function was called, which is not supported on purpose' + ); + }, + report: () => { + console.error( + 'The report function was called, which is not supported on purpose' + ); + }, +}); + +describe('Context.Provider -> Context', () => { + describe('javascript code', () => { + it('should replace ThemeContext.Provider with ThemeContext', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture1.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture1.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js')); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should replace Context.Provider with Context', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture2.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture2.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js')); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should do nothing if .Provider does not exist', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture3.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture3.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js')); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + }); + + describe('typescript code', () => { + it('should replace ThemeContext.Provider with ThemeContext', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture4.input.ts'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture4.output.ts'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx')); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should replace Context.Provider with Context', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture5.input.ts'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture5.output.ts'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx')); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should do nothing if .Provider does not exist', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture6.input.ts'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture6.output.ts'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx')); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + }); +}); diff --git a/codemods/remove-context-provider/package.json b/codemods/remove-context-provider/package.json new file mode 100644 index 0000000000000..7b86ae43f751e --- /dev/null +++ b/codemods/remove-context-provider/package.json @@ -0,0 +1,17 @@ +{ + "name": "remove-context-provider", + "version": "1.0.0", + "dependencies": {}, + "devDependencies": { + "@types/node": "^20.12.3", + "@types/jscodeshift": "^0.11.10", + "jscodeshift": "^0.15.1", + "typescript": "^5.2.2" + }, + "files": [ + "./README.md", + "./.codemodrc.json", + "./dist/index.cjs" + ], + "type": "module" +} diff --git a/codemods/remove-context-provider/src/index.ts b/codemods/remove-context-provider/src/index.ts new file mode 100644 index 0000000000000..1ba778878b564 --- /dev/null +++ b/codemods/remove-context-provider/src/index.ts @@ -0,0 +1,37 @@ +import type {API, FileInfo} from 'jscodeshift'; + +export default function transform( + file: FileInfo, + api: API +): string | undefined { + const j = api.jscodeshift; + const root = j(file.source); + + root.findJSXElements().forEach(elementPath => { + const {value} = elementPath; + const elements = [value.openingElement, value.closingElement]; + elements.forEach(element => { + if (!element) { + return; + } + if ( + !j.JSXMemberExpression.check(element.name) || + !j.JSXIdentifier.check(element.name.object) + ) { + return; + } + + const objectName = element.name.object.name; + const propertyName = element.name.property.name; + + if ( + objectName.toLocaleLowerCase().includes('context') && + propertyName === 'Provider' + ) { + element.name = element.name.object; + } + }); + }); + + return root.toSource(); +} diff --git a/codemods/remove-context-provider/tsconfig.json b/codemods/remove-context-provider/tsconfig.json new file mode 100644 index 0000000000000..a30abbbea378c --- /dev/null +++ b/codemods/remove-context-provider/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "module": "NodeNext", + "skipLibCheck": true, + "strict": true, + "target": "ES6", + "allowJs": true + }, + "include": [ + "./src/**/*.ts", + "./src/**/*.js", + "./test/**/*.ts", + "./test/**/*.js" + ], + "exclude": ["node_modules", "./dist/**/*"], + "ts-node": { + "transpileOnly": true + } +} \ No newline at end of file diff --git a/codemods/remove-forward-ref/.codemodrc.json b/codemods/remove-forward-ref/.codemodrc.json new file mode 100644 index 0000000000000..1506189110e2f --- /dev/null +++ b/codemods/remove-forward-ref/.codemodrc.json @@ -0,0 +1,14 @@ +{ + "version": "1.0.0", + "private": false, + "name": "react/19/remove-forward-ref", + "description": "Codemod to remove React.forwardRef function that will be deprecated in next major React release", + "engine": "jscodeshift", + "meta": { + "tags": ["react"], + "useCaseCategory": "migration" + }, + "applicability": { + "from": [["react", "<=", "^18.0.0"]] + } +} diff --git a/codemods/remove-forward-ref/README.md b/codemods/remove-forward-ref/README.md new file mode 100644 index 0000000000000..484713f7a1642 --- /dev/null +++ b/codemods/remove-forward-ref/README.md @@ -0,0 +1,25 @@ +# Replace forwardRef with ref prop + +## Description + +React.forwardRef will be deprecated for Function Components in near future. This codemod removes forwardRef function. + +## Example + +### Before: + +```jsx +import { forwardRef } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + // ... +}); +``` + +### After: + +```tsx +const MyInput = function MyInput({ ref, ...otherProps }) { + // ... +}; +``` diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture1.input.js b/codemods/remove-forward-ref/__testfixtures__/fixture1.input.js new file mode 100644 index 0000000000000..faf9c4c943827 --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture1.input.js @@ -0,0 +1,5 @@ +import { forwardRef } from 'react'; + +const MyInput = forwardRef((props, ref) => { + return null; +}); \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture1.output.js b/codemods/remove-forward-ref/__testfixtures__/fixture1.output.js new file mode 100644 index 0000000000000..30d50bcba73a0 --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture1.output.js @@ -0,0 +1,4 @@ +const MyInput = props => { + const { ref } = props; + return null; +}; \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture2.input.js b/codemods/remove-forward-ref/__testfixtures__/fixture2.input.js new file mode 100644 index 0000000000000..ee8f84e0c2d6a --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture2.input.js @@ -0,0 +1,5 @@ +import { forwardRef } from 'react'; + +const MyInput = forwardRef(function A(props, ref) { + return null; +}); \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture2.output.js b/codemods/remove-forward-ref/__testfixtures__/fixture2.output.js new file mode 100644 index 0000000000000..f60690b34366b --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture2.output.js @@ -0,0 +1,4 @@ +const MyInput = function A(props) { + const { ref } = props; + return null; +}; \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture3.input.js b/codemods/remove-forward-ref/__testfixtures__/fixture3.input.js new file mode 100644 index 0000000000000..aa2f3cc8bd16d --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture3.input.js @@ -0,0 +1,5 @@ +import { forwardRef } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + return null; +}); \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture3.output.js b/codemods/remove-forward-ref/__testfixtures__/fixture3.output.js new file mode 100644 index 0000000000000..20c8ac77ad6ca --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture3.output.js @@ -0,0 +1,4 @@ +const MyInput = function MyInput(props) { + const { ref } = props; + return null; +}; \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture4.input.ts b/codemods/remove-forward-ref/__testfixtures__/fixture4.input.ts new file mode 100644 index 0000000000000..7e4b01ae19ba6 --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture4.input.ts @@ -0,0 +1,6 @@ +import type { X } from "react"; +import { forwardRef, type Y } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + return null; +}); \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture4.output.ts b/codemods/remove-forward-ref/__testfixtures__/fixture4.output.ts new file mode 100644 index 0000000000000..e39ce2bef8cc1 --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture4.output.ts @@ -0,0 +1,6 @@ +import type { X } from "react"; +import { type Y } from 'react'; +const MyInput = function MyInput(props) { + const { ref } = props; + return null; +}; \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture5.input.js b/codemods/remove-forward-ref/__testfixtures__/fixture5.input.js new file mode 100644 index 0000000000000..e6675dc46725d --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture5.input.js @@ -0,0 +1,5 @@ +import { forwardRef, useState } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + return null; +}); \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture5.output.js b/codemods/remove-forward-ref/__testfixtures__/fixture5.output.js new file mode 100644 index 0000000000000..aa1f8a5eac609 --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture5.output.js @@ -0,0 +1,6 @@ +import { useState } from 'react'; + +const MyInput = function MyInput(props) { + const { ref } = props; + return null; +}; \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture6.input.js b/codemods/remove-forward-ref/__testfixtures__/fixture6.input.js new file mode 100644 index 0000000000000..b7bb0d7f78d9f --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture6.input.js @@ -0,0 +1,5 @@ +import { forwardRef } from 'react'; + +const MyInput = forwardRef(function MyInput({ onChange }, ref) { + return +}); \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture6.output.js b/codemods/remove-forward-ref/__testfixtures__/fixture6.output.js new file mode 100644 index 0000000000000..78f5ef78e9440 --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture6.output.js @@ -0,0 +1,3 @@ +const MyInput = function MyInput({ ref, onChange }) { + return +}; \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture7.input.js b/codemods/remove-forward-ref/__testfixtures__/fixture7.input.js new file mode 100644 index 0000000000000..2d66977de9861 --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture7.input.js @@ -0,0 +1,5 @@ +import { forwardRef } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + return +}); \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture7.output.js b/codemods/remove-forward-ref/__testfixtures__/fixture7.output.js new file mode 100644 index 0000000000000..13b6383fcf9da --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture7.output.js @@ -0,0 +1,4 @@ +const MyInput = function MyInput(props) { + const { ref } = props; + return +}; \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture8.input.ts b/codemods/remove-forward-ref/__testfixtures__/fixture8.input.ts new file mode 100644 index 0000000000000..606f1f83e5822 --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture8.input.ts @@ -0,0 +1,6 @@ +import { forwardRef } from 'react'; +type Props = { a: 1 }; + +const MyInput = forwardRef((props, ref) => { + return null; +}); \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture8.output.ts b/codemods/remove-forward-ref/__testfixtures__/fixture8.output.ts new file mode 100644 index 0000000000000..3f9eafba0f7fe --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture8.output.ts @@ -0,0 +1,5 @@ +type Props = { a: 1 }; +const MyInput = (props: Props & { ref: React.RefObject; }) => { + const { ref } = props; + return null; +}; \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture9.input.ts b/codemods/remove-forward-ref/__testfixtures__/fixture9.input.ts new file mode 100644 index 0000000000000..cae62026a9327 --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture9.input.ts @@ -0,0 +1,8 @@ +import { forwardRef } from 'react'; + +const MyComponent = forwardRef(function Component( + myProps: Props, + myRef: React.ForwardedRef + ) { + return null; +}); \ No newline at end of file diff --git a/codemods/remove-forward-ref/__testfixtures__/fixture9.output.ts b/codemods/remove-forward-ref/__testfixtures__/fixture9.output.ts new file mode 100644 index 0000000000000..78e73af32c45e --- /dev/null +++ b/codemods/remove-forward-ref/__testfixtures__/fixture9.output.ts @@ -0,0 +1,6 @@ +const MyComponent = function Component( + myProps: Props & { ref: React.RefObject; } +) { + const { ref: myRef } = myProps; + return null; +}; \ No newline at end of file diff --git a/codemods/remove-forward-ref/__tests__/test.ts b/codemods/remove-forward-ref/__tests__/test.ts new file mode 100644 index 0000000000000..5fe05dffefa9b --- /dev/null +++ b/codemods/remove-forward-ref/__tests__/test.ts @@ -0,0 +1,227 @@ +import assert from 'node:assert/strict'; +import {readFile} from 'node:fs/promises'; +import {join} from 'node:path'; +import jscodeshift, {API, FileInfo} from 'jscodeshift'; +import transform from '../src/index.js'; + +export const buildApi = (parser: string | undefined): API => ({ + j: parser ? jscodeshift.withParser(parser) : jscodeshift, + jscodeshift: parser ? jscodeshift.withParser(parser) : jscodeshift, + stats: () => { + console.error( + 'The stats function was called, which is not supported on purpose' + ); + }, + report: () => { + console.error( + 'The report function was called, which is not supported on purpose' + ); + }, +}); + +describe('react/remove-forward-ref', () => { + it('Unwraps the render function: render function is ArrowFunctionExpression', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture1.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture1.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.js', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx')); + + assert.deepEqual( + actualOutput?.replace(/\s/gm, ''), + OUTPUT.replace(/\s/gm, '') + ); + }); + + it('Unwraps the render function: render function is FunctionExpression', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture2.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture2.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.js', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx')); + + assert.deepEqual( + actualOutput?.replace(/\s/gm, ''), + OUTPUT.replace(/\s/gm, '') + ); + }); + + it('forwardRef import: removes the import when only forwardRef is a single specifier', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture3.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture3.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.js', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx')); + + assert.deepEqual( + actualOutput?.replace(/\s/gm, ''), + OUTPUT.replace(/\s/gm, '') + ); + }); + + it('forwardRef import: should not remove type imports', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture4.input.ts'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture4.output.ts'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.js', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx')); + + assert.deepEqual( + actualOutput?.replace(/\s/gm, ''), + OUTPUT.replace(/\s/gm, '') + ); + }); + + it('forwardRef import: removes forwardRef specifier', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture5.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture5.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.js', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx')); + + assert.deepEqual( + actualOutput?.replace(/\s/gm, ''), + OUTPUT.replace(/\s/gm, '') + ); + }); + + it('Replaces the second arg of the render function: props are ObjectPattern', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture6.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture6.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.js', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx')); + + assert.deepEqual( + actualOutput?.replace(/\s/gm, ''), + OUTPUT.replace(/\s/gm, '') + ); + }); + + it('Replaces the second arg of the render function: props are Identifier', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture7.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture7.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.js', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx')); + + assert.deepEqual( + actualOutput?.replace(/\s/gm, ''), + OUTPUT.replace(/\s/gm, '') + ); + }); + + it('Typescript: reuses forwardRef typeArguments', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture8.input.ts'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture8.output.ts'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.js', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx')); + assert.deepEqual( + actualOutput?.replace(/\s/gm, ''), + OUTPUT.replace(/\s/gm, '') + ); + }); + + it('Typescript: reuses wrapped function type arguments', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture9.input.ts'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture9.output.ts'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.js', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx')); + assert.deepEqual( + actualOutput?.replace(/\s/gm, ''), + OUTPUT.replace(/\s/gm, '') + ); + }); +}); diff --git a/codemods/remove-forward-ref/package.json b/codemods/remove-forward-ref/package.json new file mode 100644 index 0000000000000..a8c8cfe86c0ff --- /dev/null +++ b/codemods/remove-forward-ref/package.json @@ -0,0 +1,17 @@ +{ + "name": "remove-forward-ref", + "version": "1.0.0", + "dependencies": {}, + "devDependencies": { + "@types/node": "^20.12.3", + "@types/jscodeshift": "^0.11.10", + "jscodeshift": "^0.15.1", + "typescript": "^5.2.2" + }, + "files": [ + "./README.md", + "./.codemodrc.json", + "./dist/index.cjs" + ], + "type": "module" +} diff --git a/codemods/remove-forward-ref/src/index.ts b/codemods/remove-forward-ref/src/index.ts new file mode 100644 index 0000000000000..94ea9ee1b2458 --- /dev/null +++ b/codemods/remove-forward-ref/src/index.ts @@ -0,0 +1,248 @@ +import type { + API, + BlockStatement, + FileInfo, + Identifier, + JSCodeshift, + TSTypeReference, + Transform, +} from "jscodeshift"; + +// Props & { ref: React.RefObject} +const buildPropsAndRefIntersectionTypeAnnotation = ( + j: JSCodeshift, + propType: TSTypeReference, + refType: TSTypeReference, +) => + j.tsTypeAnnotation( + j.tsIntersectionType([ + propType, + j.tsTypeLiteral([ + j.tsPropertySignature.from({ + key: j.identifier("ref"), + typeAnnotation: j.tsTypeAnnotation( + j.tsTypeReference.from({ + typeName: j.tsQualifiedName( + j.identifier("React"), + j.identifier("RefObject"), + ), + typeParameters: j.tsTypeParameterInstantiation([refType]), + }), + ), + }), + ]), + ]), + ); + +// const { ref } = props; +const buildRefArgVariableDeclaration = ( + j: JSCodeshift, + refArgName: string, + propArgName: string, +) => + j.variableDeclaration("const", [ + j.variableDeclarator( + j.objectPattern([ + j.objectProperty.from({ + shorthand: true, + key: j.identifier("ref"), + value: j.identifier(refArgName), + }), + ]), + j.identifier(propArgName), + ), + ]); + +// React.ForwardedRef => HTMLButtonElement +const getRefTypeFromRefArg = (j: JSCodeshift, refArg: Identifier) => { + const typeReference = refArg.typeAnnotation?.typeAnnotation; + + if ( + !j.TSTypeReference.check(typeReference) || + !j.TSQualifiedName.check(typeReference.typeName) + ) { + return null; + } + + const { right } = typeReference.typeName; + + if (!j.Identifier.check(right) || right.name === "forwardedRef") { + return null; + } + + const [firstTypeParameter] = typeReference.typeParameters?.params ?? []; + + if (!j.TSTypeReference.check(firstTypeParameter)) { + return null; + } + + return firstTypeParameter; +}; + +export default function transform(file: FileInfo, api: API) { + const { j } = api; + + const root = j(file.source); + + let dirtyFlag = false; + + root + .find(j.CallExpression, { + callee: { + type: "Identifier", + name: "forwardRef", + }, + }) + .replaceWith((callExpressionPath) => { + const [renderFunctionArg] = callExpressionPath.node.arguments; + + if ( + !j.FunctionExpression.check(renderFunctionArg) && + !j.ArrowFunctionExpression.check(renderFunctionArg) + ) { + return null; + } + + const [propsArg, refArg] = renderFunctionArg.params; + + if (!j.Identifier.check(refArg) || propsArg === undefined) { + return null; + } + + const refArgTypeReference = getRefTypeFromRefArg(j, refArg); + + // remove refArg + renderFunctionArg.params.splice(1, 1); + + const refArgName = refArg.name; + + // if props are ObjectPattern, push ref as ObjectProperty + if (j.ObjectPattern.check(propsArg)) { + propsArg.properties.unshift( + j.objectProperty.from({ + shorthand: true, + key: j.identifier(refArgName), + value: j.identifier(refArgName), + }), + ); + + // update prop arg type + const propsArgTypeReference = propsArg.typeAnnotation?.typeAnnotation; + + if ( + j.TSTypeReference.check(propsArgTypeReference) && + j.TSTypeReference.check(refArgTypeReference) + ) { + propsArg.typeAnnotation = buildPropsAndRefIntersectionTypeAnnotation( + j, + propsArgTypeReference, + refArgTypeReference, + ); + } + } + + // if props arg is Identifier, push ref variable declaration to the function body + if (j.Identifier.check(propsArg)) { + // if we have arrow function with implicit return, we want to wrap it with BlockStatement + if ( + j.ArrowFunctionExpression.check(renderFunctionArg) && + !j.BlockStatement.check(renderFunctionArg.body) + ) { + renderFunctionArg.body = j.blockStatement.from({ + body: [j.returnStatement(renderFunctionArg.body)], + }); + } + + const newDeclaration = buildRefArgVariableDeclaration( + j, + refArg.name, + propsArg.name, + ); + + (renderFunctionArg.body as BlockStatement).body.unshift(newDeclaration); + + const propsArgTypeReference = propsArg.typeAnnotation?.typeAnnotation; + + if ( + j.TSTypeReference.check(propsArgTypeReference) && + j.TSTypeReference.check(refArgTypeReference) + ) { + propsArg.typeAnnotation = buildPropsAndRefIntersectionTypeAnnotation( + j, + propsArgTypeReference, + refArgTypeReference, + ); + } + } + + /** + * Transform ts types: forwardRef type arguments are used + */ + + // @ts-expect-error Property 'typeParameters' does not exist on type 'CallExpression'. + const typeParameters = callExpressionPath.node.typeParameters; + + // if typeParameters are used in forwardRef generic, reuse them to annotate props type + // forwardRef((props) => { ... }) ====> (props: Props & { ref: React.RefObject }) => { ... } + if ( + j.TSTypeParameterInstantiation.check(typeParameters) && + propsArg !== undefined && + "typeAnnotation" in propsArg + ) { + const [refType, propType] = typeParameters.params; + + if ( + j.TSTypeReference.check(refType) && + j.TSTypeReference.check(propType) + ) { + propsArg.typeAnnotation = buildPropsAndRefIntersectionTypeAnnotation( + j, + propType, + refType, + ); + } + } + + dirtyFlag = true; + return renderFunctionArg; + }); + + /** + * handle import + */ + if (dirtyFlag) { + root + .find(j.ImportDeclaration, { + source: { + value: "react", + }, + }) + .forEach((importDeclarationPath) => { + const { specifiers, importKind } = importDeclarationPath.node; + + if (importKind !== "value") { + return; + } + + if (specifiers === undefined) { + return; + } + + const forwardRefImportSpecifierIndex = specifiers.findIndex( + (s) => j.ImportSpecifier.check(s) && s.imported.name === "forwardRef", + ); + + specifiers.splice(forwardRefImportSpecifierIndex, 1); + }) + .filter((importDeclarationPath) => { + const { specifiers } = importDeclarationPath.node; + // remove the import if there are no more specifiers left after removing forwardRef + return specifiers === undefined || specifiers.length === 0; + }) + .remove(); + } + + return root.toSource(); +} + +transform satisfies Transform; diff --git a/codemods/remove-forward-ref/tsconfig.json b/codemods/remove-forward-ref/tsconfig.json new file mode 100644 index 0000000000000..a30abbbea378c --- /dev/null +++ b/codemods/remove-forward-ref/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "module": "NodeNext", + "skipLibCheck": true, + "strict": true, + "target": "ES6", + "allowJs": true + }, + "include": [ + "./src/**/*.ts", + "./src/**/*.js", + "./test/**/*.ts", + "./test/**/*.js" + ], + "exclude": ["node_modules", "./dist/**/*"], + "ts-node": { + "transpileOnly": true + } +} \ No newline at end of file diff --git a/codemods/remove-memoization-hooks/.codemodrc.json b/codemods/remove-memoization-hooks/.codemodrc.json new file mode 100644 index 0000000000000..45e8e5676cc22 --- /dev/null +++ b/codemods/remove-memoization-hooks/.codemodrc.json @@ -0,0 +1,13 @@ +{ + "version": "1.0.0", + "name": "react/19/remove-memoization", + "private": false, + "engine": "jscodeshift", + "meta": { + "tags": ["react"], + "useCaseCategory": "migration" + }, + "applicability": { + "from": [["react", "<=", "18"]] + } +} diff --git a/codemods/remove-memoization-hooks/README.md b/codemods/remove-memoization-hooks/README.md new file mode 100644 index 0000000000000..c505093e2ec8f --- /dev/null +++ b/codemods/remove-memoization-hooks/README.md @@ -0,0 +1,5 @@ +# Remove Manual Memoization Hooks + +## Description + +This codemod will remove manual memoization hooks: `useCallback`, `useMemo` and `memo`. This codemod goes hand in hand with React Compiler. diff --git a/codemods/remove-memoization-hooks/__testfixtures__/fixture1.input.js b/codemods/remove-memoization-hooks/__testfixtures__/fixture1.input.js new file mode 100644 index 0000000000000..b85ec0590e28a --- /dev/null +++ b/codemods/remove-memoization-hooks/__testfixtures__/fixture1.input.js @@ -0,0 +1,8 @@ +import { useCallback } from 'react'; + +function Component() { + const selectedDateMin3DaysDifference = useCallback(() => { + const diff = today.diff(selectedDate, "days"); + return diff > 3 || diff < -3; + }, [today, selectedDate]); +} \ No newline at end of file diff --git a/codemods/remove-memoization-hooks/__testfixtures__/fixture1.output.js b/codemods/remove-memoization-hooks/__testfixtures__/fixture1.output.js new file mode 100644 index 0000000000000..494a423856305 --- /dev/null +++ b/codemods/remove-memoization-hooks/__testfixtures__/fixture1.output.js @@ -0,0 +1,6 @@ +function Component() { + const selectedDateMin3DaysDifference = () => { + const diff = today.diff(selectedDate, "days"); + return diff > 3 || diff < -3; + }; +} \ No newline at end of file diff --git a/codemods/remove-memoization-hooks/__testfixtures__/fixture2.input.js b/codemods/remove-memoization-hooks/__testfixtures__/fixture2.input.js new file mode 100644 index 0000000000000..8a9f3e5a3b448 --- /dev/null +++ b/codemods/remove-memoization-hooks/__testfixtures__/fixture2.input.js @@ -0,0 +1,8 @@ +import { useMemo } from 'react'; + +function Component() { + const selectedDateMin3DaysDifference = useMemo(() => { + const diff = today.diff(selectedDate, "days"); + return diff > 3 || diff < -3; + }, [today, selectedDate]); +} \ No newline at end of file diff --git a/codemods/remove-memoization-hooks/__testfixtures__/fixture2.output.js b/codemods/remove-memoization-hooks/__testfixtures__/fixture2.output.js new file mode 100644 index 0000000000000..494a423856305 --- /dev/null +++ b/codemods/remove-memoization-hooks/__testfixtures__/fixture2.output.js @@ -0,0 +1,6 @@ +function Component() { + const selectedDateMin3DaysDifference = () => { + const diff = today.diff(selectedDate, "days"); + return diff > 3 || diff < -3; + }; +} \ No newline at end of file diff --git a/codemods/remove-memoization-hooks/__testfixtures__/fixture3.input.js b/codemods/remove-memoization-hooks/__testfixtures__/fixture3.input.js new file mode 100644 index 0000000000000..a2809f30eea27 --- /dev/null +++ b/codemods/remove-memoization-hooks/__testfixtures__/fixture3.input.js @@ -0,0 +1,7 @@ +import { memo } from 'react'; + +const MyComponent = ({ name }) => { + return
Hello, {name}!
; +}; + +const MemoizedMyComponent = memo(MyComponent); \ No newline at end of file diff --git a/codemods/remove-memoization-hooks/__testfixtures__/fixture3.output.js b/codemods/remove-memoization-hooks/__testfixtures__/fixture3.output.js new file mode 100644 index 0000000000000..6e0f9111d76ee --- /dev/null +++ b/codemods/remove-memoization-hooks/__testfixtures__/fixture3.output.js @@ -0,0 +1,5 @@ +const MyComponent = ({ name }) => { + return
Hello, {name}!
; +}; + +const MemoizedMyComponent = MyComponent; \ No newline at end of file diff --git a/codemods/remove-memoization-hooks/__testfixtures__/fixture4.input.js b/codemods/remove-memoization-hooks/__testfixtures__/fixture4.input.js new file mode 100644 index 0000000000000..a175d1883057a --- /dev/null +++ b/codemods/remove-memoization-hooks/__testfixtures__/fixture4.input.js @@ -0,0 +1,21 @@ +import React from 'react'; + +function Component() { + const state = React.useState(); + + const example1 = React.useMemo(() => { + const diff = today.diff(selectedDate, "days"); + return diff > 3 || diff < -3; + }, [today, selectedDate]); + + const example2 = React.useCallback(() => { + const diff = today.diff(selectedDate, "days"); + return diff > 3 || diff < -3; + }, [today, selectedDate]); +} + +const MyComponent = ({ name }) => { + return
Hello, {name}!
; +}; + +const MemoizedMyComponent = React.memo(MyComponent); \ No newline at end of file diff --git a/codemods/remove-memoization-hooks/__testfixtures__/fixture4.output.js b/codemods/remove-memoization-hooks/__testfixtures__/fixture4.output.js new file mode 100644 index 0000000000000..aa4e313a53c7e --- /dev/null +++ b/codemods/remove-memoization-hooks/__testfixtures__/fixture4.output.js @@ -0,0 +1,21 @@ +import React from 'react'; + +function Component() { + const state = React.useState(); + + const example1 = () => { + const diff = today.diff(selectedDate, "days"); + return diff > 3 || diff < -3; + }; + + const example2 = () => { + const diff = today.diff(selectedDate, "days"); + return diff > 3 || diff < -3; + }; +} + +const MyComponent = ({ name }) => { + return
Hello, {name}!
; +}; + +const MemoizedMyComponent = MyComponent; diff --git a/codemods/remove-memoization-hooks/__testfixtures__/fixture5.input.ts b/codemods/remove-memoization-hooks/__testfixtures__/fixture5.input.ts new file mode 100644 index 0000000000000..63437cccb6c29 --- /dev/null +++ b/codemods/remove-memoization-hooks/__testfixtures__/fixture5.input.ts @@ -0,0 +1,8 @@ +import { useCallback } from 'react'; + +function Component({ url }: { url: string }) { + const selectedDateMin3DaysDifference = useCallback(() => { + const diff = today.diff(selectedDate, "days"); + return diff > 3 || diff < -3; + }, [today, selectedDate]); +} \ No newline at end of file diff --git a/codemods/remove-memoization-hooks/__testfixtures__/fixture5.output.ts b/codemods/remove-memoization-hooks/__testfixtures__/fixture5.output.ts new file mode 100644 index 0000000000000..9ed5b47588a9a --- /dev/null +++ b/codemods/remove-memoization-hooks/__testfixtures__/fixture5.output.ts @@ -0,0 +1,6 @@ +function Component({ url }: { url: string }) { + const selectedDateMin3DaysDifference = () => { + const diff = today.diff(selectedDate, "days"); + return diff > 3 || diff < -3; + }; +} \ No newline at end of file diff --git a/codemods/remove-memoization-hooks/__testfixtures__/fixture6.input.ts b/codemods/remove-memoization-hooks/__testfixtures__/fixture6.input.ts new file mode 100644 index 0000000000000..522b68bd9541c --- /dev/null +++ b/codemods/remove-memoization-hooks/__testfixtures__/fixture6.input.ts @@ -0,0 +1,8 @@ +import { useMemo } from 'react'; + +function Component({ url }: { url: string }) { + const selectedDateMin3DaysDifference = useMemo(() => { + const diff = today.diff(selectedDate, "days"); + return diff > 3 || diff < -3; + }, [today, selectedDate]); +} \ No newline at end of file diff --git a/codemods/remove-memoization-hooks/__testfixtures__/fixture6.output.ts b/codemods/remove-memoization-hooks/__testfixtures__/fixture6.output.ts new file mode 100644 index 0000000000000..9ed5b47588a9a --- /dev/null +++ b/codemods/remove-memoization-hooks/__testfixtures__/fixture6.output.ts @@ -0,0 +1,6 @@ +function Component({ url }: { url: string }) { + const selectedDateMin3DaysDifference = () => { + const diff = today.diff(selectedDate, "days"); + return diff > 3 || diff < -3; + }; +} \ No newline at end of file diff --git a/codemods/remove-memoization-hooks/__testfixtures__/fixture7.input.ts b/codemods/remove-memoization-hooks/__testfixtures__/fixture7.input.ts new file mode 100644 index 0000000000000..9d4e37e5aba29 --- /dev/null +++ b/codemods/remove-memoization-hooks/__testfixtures__/fixture7.input.ts @@ -0,0 +1,7 @@ +import { memo, type ReactNode } from 'react'; + +const MyComponent = ({ name } : { name: string }) => { + return
Hello, {name}!
; +}; + +const MemoizedMyComponent: ReactNode = memo(MyComponent); \ No newline at end of file diff --git a/codemods/remove-memoization-hooks/__testfixtures__/fixture7.output.ts b/codemods/remove-memoization-hooks/__testfixtures__/fixture7.output.ts new file mode 100644 index 0000000000000..809a24b3d1233 --- /dev/null +++ b/codemods/remove-memoization-hooks/__testfixtures__/fixture7.output.ts @@ -0,0 +1,7 @@ +import { type ReactNode } from 'react'; + +const MyComponent = ({ name } : { name: string }) => { + return
Hello, {name}!
; + }; + +const MemoizedMyComponent: ReactNode = MyComponent; \ No newline at end of file diff --git a/codemods/remove-memoization-hooks/__testfixtures__/fixture8.input.ts b/codemods/remove-memoization-hooks/__testfixtures__/fixture8.input.ts new file mode 100644 index 0000000000000..1fd3598a46b13 --- /dev/null +++ b/codemods/remove-memoization-hooks/__testfixtures__/fixture8.input.ts @@ -0,0 +1,21 @@ +import React from 'react'; + +function Component({ url }: { url: string }) { + const state = React.useState(); + + const example1 = React.useMemo(() => { + const diff = today.diff(selectedDate, "days"); + return diff > 3 || diff < -3; + }, [today, selectedDate]); + + const example2 = React.useCallback(() => { + const diff = today.diff(selectedDate, "days"); + return diff > 3 || diff < -3; + }, [today, selectedDate]); +} + +const MyComponent = ({ name } : { name: string }) => { + return
Hello, {name}!
; +}; + +const MemoizedMyComponent: React.ReactNode = React.memo(MyComponent); \ No newline at end of file diff --git a/codemods/remove-memoization-hooks/__testfixtures__/fixture8.output.ts b/codemods/remove-memoization-hooks/__testfixtures__/fixture8.output.ts new file mode 100644 index 0000000000000..875c0753f3616 --- /dev/null +++ b/codemods/remove-memoization-hooks/__testfixtures__/fixture8.output.ts @@ -0,0 +1,21 @@ +import React from 'react'; + +function Component({ url }: { url: string }) { + const state = React.useState(); + + const example1 = () => { + const diff = today.diff(selectedDate, "days"); + return diff > 3 || diff < -3; + }; + + const example2 = () => { + const diff = today.diff(selectedDate, "days"); + return diff > 3 || diff < -3; + }; +} + +const MyComponent = ({ name } : { name: string }) => { + return
Hello, {name}!
; +}; + +const MemoizedMyComponent: React.ReactNode = MyComponent; \ No newline at end of file diff --git a/codemods/remove-memoization-hooks/__tests__/test.ts b/codemods/remove-memoization-hooks/__tests__/test.ts new file mode 100644 index 0000000000000..0f4c67d33cd9d --- /dev/null +++ b/codemods/remove-memoization-hooks/__tests__/test.ts @@ -0,0 +1,210 @@ +import assert from 'node:assert/strict'; +import {readFile} from 'node:fs/promises'; +import {join} from 'node:path'; +import jscodeshift, {API, FileInfo} from 'jscodeshift'; +import transform from '../src/index.js'; + +export const buildApi = (parser: string | undefined): API => ({ + j: parser ? jscodeshift.withParser(parser) : jscodeshift, + jscodeshift: parser ? jscodeshift.withParser(parser) : jscodeshift, + stats: () => { + console.error( + 'The stats function was called, which is not supported on purpose' + ); + }, + report: () => { + console.error( + 'The report function was called, which is not supported on purpose' + ); + }, +}); + +describe('react/19/remove-memoization-hooks', () => { + describe('javascript code', () => { + it('should remove useCallback', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture1.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture1.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js')); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should remove useMemo', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture2.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture2.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js')); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should remove memo', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture3.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture3.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js')); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should remove React.useMemo, React.useCallback, React.memo', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture4.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture4.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js')); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + }); + + describe('typescript code', () => { + it('should remove useCallback', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture5.input.ts'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture5.output.ts'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx')); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should remove useMemo', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture6.input.ts'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture6.output.ts'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx')); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should remove memo', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture7.input.ts'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture7.output.ts'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx')); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should remove React.useMemo, React.useCallback, React.memo', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture8.input.ts'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture8.output.ts'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx')); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + }); +}); diff --git a/codemods/remove-memoization-hooks/package.json b/codemods/remove-memoization-hooks/package.json new file mode 100644 index 0000000000000..d85b44326be8f --- /dev/null +++ b/codemods/remove-memoization-hooks/package.json @@ -0,0 +1,17 @@ +{ + "name": "remove-memoization-hooks", + "version": "1.0.0", + "dependencies": {}, + "devDependencies": { + "@types/node": "^20.12.3", + "@types/jscodeshift": "^0.11.10", + "jscodeshift": "^0.15.1", + "typescript": "^5.2.2" + }, + "files": [ + "./README.md", + "./.codemodrc.json", + "./dist/index.cjs" + ], + "type": "module" +} diff --git a/codemods/remove-memoization-hooks/src/index.ts b/codemods/remove-memoization-hooks/src/index.ts new file mode 100644 index 0000000000000..1e5c42476668a --- /dev/null +++ b/codemods/remove-memoization-hooks/src/index.ts @@ -0,0 +1,56 @@ +import type {API, FileInfo} from 'jscodeshift'; + +export default function transform( + file: FileInfo, + api: API +): string | undefined { + const j = api.jscodeshift; + const root = j(file.source); + + const hooksToRemove = ['useMemo', 'useCallback', 'memo']; + + root.find(j.ImportDeclaration).forEach(path => { + if (path.node.specifiers?.length === 0) { + return; + } + + const specifiers = + path.node.specifiers?.filter(specifier => { + if (specifier.type === 'ImportSpecifier') { + return !hooksToRemove.includes(specifier.imported.name); + } + return specifier; + }) ?? []; + + if (specifiers.length === 0) { + j(path).remove(); + } else { + path.node.specifiers = specifiers; + } + }); + + hooksToRemove.forEach(hook => { + root + .find(j.CallExpression, { + callee: { + type: 'Identifier', + name: hook, + }, + }) + .replaceWith(path => path.value.arguments[0]); + }); + + hooksToRemove.forEach(hook => { + root + .find(j.CallExpression, { + callee: { + type: 'MemberExpression', + object: {name: 'React'}, + property: {name: hook}, + }, + }) + .replaceWith(path => path.value.arguments[0]); + }); + + return root.toSource(); +} diff --git a/codemods/remove-memoization-hooks/tsconfig.json b/codemods/remove-memoization-hooks/tsconfig.json new file mode 100644 index 0000000000000..a30abbbea378c --- /dev/null +++ b/codemods/remove-memoization-hooks/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "module": "NodeNext", + "skipLibCheck": true, + "strict": true, + "target": "ES6", + "allowJs": true + }, + "include": [ + "./src/**/*.ts", + "./src/**/*.js", + "./test/**/*.ts", + "./test/**/*.js" + ], + "exclude": ["node_modules", "./dist/**/*"], + "ts-node": { + "transpileOnly": true + } +} \ No newline at end of file diff --git a/codemods/use-context-hook/.codemodrc.json b/codemods/use-context-hook/.codemodrc.json new file mode 100644 index 0000000000000..92bb5b08299b7 --- /dev/null +++ b/codemods/use-context-hook/.codemodrc.json @@ -0,0 +1,13 @@ +{ + "version": "1.0.0", + "name": "react/19/use-context-hook", + "private": false, + "engine": "jscodeshift", + "meta": { + "tags": ["react"], + "useCaseCategory": "migration" + }, + "applicability": { + "from": [["react", "<=", "18"]] + } +} diff --git a/codemods/use-context-hook/README.md b/codemods/use-context-hook/README.md new file mode 100644 index 0000000000000..14b6f0040a57b --- /dev/null +++ b/codemods/use-context-hook/README.md @@ -0,0 +1,25 @@ +# Change useContext usage to use hook + +## Description + +This codemod will convert the usage of `useContext` to the new hook format, introduced in React v19. + +## Example + +### Before: + +```tsx +import { useContext } from "react"; +import ThemeContext from "./ThemeContext"; + +const theme = useContext(ThemeContext); +``` + +### After: + +```tsx +import { use } from "react"; +import ThemeContext from "./ThemeContext"; + +const theme = use(ThemeContext); +``` diff --git a/codemods/use-context-hook/__testfixtures__/fixture1.input.js b/codemods/use-context-hook/__testfixtures__/fixture1.input.js new file mode 100644 index 0000000000000..979a8e471ca91 --- /dev/null +++ b/codemods/use-context-hook/__testfixtures__/fixture1.input.js @@ -0,0 +1,4 @@ +import { useContext } from "react"; +import ThemeContext from "./ThemeContext"; + +const theme = useContext(ThemeContext); \ No newline at end of file diff --git a/codemods/use-context-hook/__testfixtures__/fixture1.output.js b/codemods/use-context-hook/__testfixtures__/fixture1.output.js new file mode 100644 index 0000000000000..e251b5734be14 --- /dev/null +++ b/codemods/use-context-hook/__testfixtures__/fixture1.output.js @@ -0,0 +1,4 @@ +import { use } from "react"; +import ThemeContext from "./ThemeContext"; + +const theme = use(ThemeContext); \ No newline at end of file diff --git a/codemods/use-context-hook/__testfixtures__/fixture2.input.js b/codemods/use-context-hook/__testfixtures__/fixture2.input.js new file mode 100644 index 0000000000000..67718d9887320 --- /dev/null +++ b/codemods/use-context-hook/__testfixtures__/fixture2.input.js @@ -0,0 +1,4 @@ +import React from "react"; +import ThemeContext from "./ThemeContext"; + +const theme = React.useContext(ThemeContext); \ No newline at end of file diff --git a/codemods/use-context-hook/__testfixtures__/fixture2.output.js b/codemods/use-context-hook/__testfixtures__/fixture2.output.js new file mode 100644 index 0000000000000..f7f504f5d558b --- /dev/null +++ b/codemods/use-context-hook/__testfixtures__/fixture2.output.js @@ -0,0 +1,4 @@ +import React from "react"; +import ThemeContext from "./ThemeContext"; + +const theme = React.use(ThemeContext); \ No newline at end of file diff --git a/codemods/use-context-hook/__testfixtures__/fixture3.input.js b/codemods/use-context-hook/__testfixtures__/fixture3.input.js new file mode 100644 index 0000000000000..75bfeb59b5ffc --- /dev/null +++ b/codemods/use-context-hook/__testfixtures__/fixture3.input.js @@ -0,0 +1 @@ +const theme = trpc.useContext(); \ No newline at end of file diff --git a/codemods/use-context-hook/__testfixtures__/fixture3.output.js b/codemods/use-context-hook/__testfixtures__/fixture3.output.js new file mode 100644 index 0000000000000..75bfeb59b5ffc --- /dev/null +++ b/codemods/use-context-hook/__testfixtures__/fixture3.output.js @@ -0,0 +1 @@ +const theme = trpc.useContext(); \ No newline at end of file diff --git a/codemods/use-context-hook/__testfixtures__/fixture4.input.ts b/codemods/use-context-hook/__testfixtures__/fixture4.input.ts new file mode 100644 index 0000000000000..74640584c78f5 --- /dev/null +++ b/codemods/use-context-hook/__testfixtures__/fixture4.input.ts @@ -0,0 +1,11 @@ +import { useContext } from "react"; +import ThemeContext from "./ThemeContext"; + +function Component({ + appUrl, +}: { + appUrl: string; +}) { + const theme = useContext(ThemeContext); + return
; +}; \ No newline at end of file diff --git a/codemods/use-context-hook/__testfixtures__/fixture4.output.ts b/codemods/use-context-hook/__testfixtures__/fixture4.output.ts new file mode 100644 index 0000000000000..d44d7ae85ee2e --- /dev/null +++ b/codemods/use-context-hook/__testfixtures__/fixture4.output.ts @@ -0,0 +1,11 @@ +import { use } from "react"; +import ThemeContext from "./ThemeContext"; + +function Component({ + appUrl, +}: { + appUrl: string; +}) { + const theme = use(ThemeContext); + return
; +}; \ No newline at end of file diff --git a/codemods/use-context-hook/__testfixtures__/fixture5.input.ts b/codemods/use-context-hook/__testfixtures__/fixture5.input.ts new file mode 100644 index 0000000000000..062dab59bdf85 --- /dev/null +++ b/codemods/use-context-hook/__testfixtures__/fixture5.input.ts @@ -0,0 +1,11 @@ +import React from "react"; +import ThemeContext from "./ThemeContext"; + +function Component({ + appUrl, +}: { + appUrl: string; +}) { + const theme = React.useContext(ThemeContext); + return
; +}; \ No newline at end of file diff --git a/codemods/use-context-hook/__testfixtures__/fixture5.output.ts b/codemods/use-context-hook/__testfixtures__/fixture5.output.ts new file mode 100644 index 0000000000000..a873ec9a8491f --- /dev/null +++ b/codemods/use-context-hook/__testfixtures__/fixture5.output.ts @@ -0,0 +1,11 @@ +import React from "react"; +import ThemeContext from "./ThemeContext"; + +function Component({ + appUrl, +}: { + appUrl: string; +}) { + const theme = React.use(ThemeContext); + return
; +}; \ No newline at end of file diff --git a/codemods/use-context-hook/__testfixtures__/fixture6.input.ts b/codemods/use-context-hook/__testfixtures__/fixture6.input.ts new file mode 100644 index 0000000000000..88397ec83f204 --- /dev/null +++ b/codemods/use-context-hook/__testfixtures__/fixture6.input.ts @@ -0,0 +1,8 @@ +function Component({ + appUrl, +}: { + appUrl: string; +}) { + const theme = trpc.useContext(); + return
; +}; \ No newline at end of file diff --git a/codemods/use-context-hook/__testfixtures__/fixture6.output.ts b/codemods/use-context-hook/__testfixtures__/fixture6.output.ts new file mode 100644 index 0000000000000..88397ec83f204 --- /dev/null +++ b/codemods/use-context-hook/__testfixtures__/fixture6.output.ts @@ -0,0 +1,8 @@ +function Component({ + appUrl, +}: { + appUrl: string; +}) { + const theme = trpc.useContext(); + return
; +}; \ No newline at end of file diff --git a/codemods/use-context-hook/__tests__/test.ts b/codemods/use-context-hook/__tests__/test.ts new file mode 100644 index 0000000000000..3ebf62e0e6a2c --- /dev/null +++ b/codemods/use-context-hook/__tests__/test.ts @@ -0,0 +1,176 @@ +import assert from 'node:assert/strict'; +import {readFile} from 'node:fs/promises'; +import {join} from 'node:path'; +import jscodeshift, {API, FileInfo} from 'jscodeshift'; +import transform from '../src/index.js'; + +export const buildApi = (parser: string | undefined): API => ({ + j: parser ? jscodeshift.withParser(parser) : jscodeshift, + jscodeshift: parser ? jscodeshift.withParser(parser) : jscodeshift, + stats: () => { + console.error( + 'The stats function was called, which is not supported on purpose' + ); + }, + report: () => { + console.error( + 'The report function was called, which is not supported on purpose' + ); + }, +}); + +describe('react/19/use-context-hook: useContext -> use', () => { + describe('javascript code', () => { + it('should replace useContext with use', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture1.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture1.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should replace React.useContext with use', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture2.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture2.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should not replace any.useContext() with use', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture3.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture3.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + }); + + describe('typescript code', () => { + it('should replace useContext with use', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture4.input.ts'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture4.output.ts'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should replace React.useContext with use', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture5.input.ts'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture5.output.ts'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should not replace any.useContext() with use', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture6.input.ts'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture6.output.ts'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('tsx'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + }); +}); diff --git a/codemods/use-context-hook/package.json b/codemods/use-context-hook/package.json new file mode 100644 index 0000000000000..8faca62580bef --- /dev/null +++ b/codemods/use-context-hook/package.json @@ -0,0 +1,17 @@ +{ + "name": "use-context-hook", + "version": "1.0.0", + "dependencies": {}, + "devDependencies": { + "@types/node": "^20.12.3", + "@types/jscodeshift": "^0.11.10", + "jscodeshift": "^0.15.1", + "typescript": "^5.2.2" + }, + "files": [ + "./README.md", + "./.codemodrc.json", + "./dist/index.cjs" + ], + "type": "module" +} diff --git a/codemods/use-context-hook/src/index.ts b/codemods/use-context-hook/src/index.ts new file mode 100644 index 0000000000000..b905e2df1f93c --- /dev/null +++ b/codemods/use-context-hook/src/index.ts @@ -0,0 +1,67 @@ +import type {API, FileInfo, Options} from 'jscodeshift'; + +export default function transform( + file: FileInfo, + api: API, + options?: Options +): string | undefined { + const j = api.jscodeshift; + const root = j(file.source); + + // Get default import from react + const defaultReactImport = + root + .find(j.ImportDeclaration, { + source: {value: 'react'}, + specifiers: [{type: 'ImportDefaultSpecifier'}], + }) + .paths() + .at(0) + ?.node.specifiers?.at(0)?.local?.name ?? 'React'; + + // For usages like `import React from 'react'; React.useContext(ThemeContext)` + root + .find(j.MemberExpression, { + object: {name: defaultReactImport}, + property: {name: 'useContext'}, + }) + .forEach(path => { + const identifierPath = j(path) + .find(j.Identifier, {name: 'useContext'}) + .paths() + .at(0); + + const newIdentifier = j.identifier.from({name: 'use'}); + + identifierPath?.replace(newIdentifier); + }); + + // Get useContext import name + const useContextImport = root + .find(j.ImportDeclaration, { + source: {value: 'react'}, + specifiers: [{type: 'ImportSpecifier', imported: {name: 'useContext'}}], + }) + .paths() + .at(0) + ?.node.specifiers?.at(0)?.local?.name; + + if (useContextImport) { + // For usages like `import { useContext } from 'react'; useContext(ThemeContext)` + root.find(j.Identifier, {name: useContextImport}).forEach(path => { + // If parent is a member expression, we don't want that change, we handle React.useContext separately + if (path.parentPath.node.type === 'MemberExpression') { + return; + } + + // In all other cases, replace usages of imported useContext with use + if (path.node.type === 'Identifier') { + const newIdentifier = j.identifier.from({name: 'use'}); + + path.replace(newIdentifier); + } + }); + } + + return root.toSource(); +} diff --git a/codemods/use-context-hook/tsconfig.json b/codemods/use-context-hook/tsconfig.json new file mode 100644 index 0000000000000..a30abbbea378c --- /dev/null +++ b/codemods/use-context-hook/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "module": "NodeNext", + "skipLibCheck": true, + "strict": true, + "target": "ES6", + "allowJs": true + }, + "include": [ + "./src/**/*.ts", + "./src/**/*.js", + "./test/**/*.ts", + "./test/**/*.js" + ], + "exclude": ["node_modules", "./dist/**/*"], + "ts-node": { + "transpileOnly": true + } +} \ No newline at end of file diff --git a/package.json b/package.json index 785bbfe63113b..a309819d6b4a4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "private": true, "workspaces": [ - "packages/*" + "packages/*", + "codemods/*" ], "devDependencies": { "@babel/cli": "^7.10.5", @@ -35,11 +36,14 @@ "@babel/plugin-transform-template-literals": "^7.10.5", "@babel/preset-flow": "^7.10.4", "@babel/preset-react": "^7.23.3", + "@babel/preset-typescript": "^7.24.1", "@babel/traverse": "^7.11.0", "@rollup/plugin-babel": "^6.0.3", "@rollup/plugin-commonjs": "^24.0.1", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", + "@types/jest": "^29.5.12", + "@types/node": "^20.12.3", "abortcontroller-polyfill": "^1.7.5", "art": "0.10.1", "babel-plugin-syntax-trailing-function-commas": "^6.5.0", diff --git a/scripts/jest/config.base.js b/scripts/jest/config.base.js index a887e66b7dd3c..61700d9fd0fb6 100644 --- a/scripts/jest/config.base.js +++ b/scripts/jest/config.base.js @@ -16,7 +16,7 @@ module.exports = { testRegex: '/__tests__/[^/]*(\\.js|\\.coffee|[^d]\\.ts)$', moduleFileExtensions: ['js', 'json', 'node', 'coffee', 'ts'], rootDir: process.cwd(), - roots: ['/packages', '/scripts'], + roots: ['/packages', '/scripts', '/codemods'], collectCoverageFrom: ['packages/**/*.js'], fakeTimers: { enableGlobally: true, diff --git a/yarn.lock b/yarn.lock index 8670efde1275b..bc1da8844b76d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,6 +10,14 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + "@babel/cli@^7.10.5": version "7.10.5" resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.10.5.tgz#57df2987c8cf89d0fc7d4b157ec59d7619f1b77a" @@ -54,6 +62,14 @@ dependencies: "@babel/highlight" "^7.18.6" +"@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.1", "@babel/code-frame@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== + dependencies: + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" + "@babel/code-frame@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" @@ -80,6 +96,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.14.tgz#4106fc8b755f3e3ee0a0a7c27dde5de1d2b2baf8" integrity sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw== +"@babel/compat-data@^7.23.5": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.1.tgz#31c1f66435f2a9c329bb5716a6d6186c516c3742" + integrity sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA== + "@babel/core@^7.0.0", "@babel/core@^7.11.6", "@babel/core@^7.12.3": version "7.20.12" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.12.tgz#7930db57443c6714ad216953d1356dac0eb8496d" @@ -144,6 +165,27 @@ semver "^6.3.0" source-map "^0.5.0" +"@babel/core@^7.23.0": + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.3.tgz#568864247ea10fbd4eff04dda1e05f9e2ea985c3" + integrity sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.2" + "@babel/generator" "^7.24.1" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.24.1" + "@babel/parser" "^7.24.1" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/eslint-parser@^7.11.4": version "7.11.4" resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.11.4.tgz#f79bac69088097a8418f5c67fc462c89a72c2f48" @@ -189,6 +231,16 @@ "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" +"@babel/generator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.1.tgz#e67e06f68568a4ebf194d1c6014235344f0476d0" + integrity sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A== + dependencies: + "@babel/types" "^7.24.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + "@babel/generator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.3.tgz#0e22c005b0a94c1c74eafe19ef78ce53a4d45c03" @@ -284,6 +336,17 @@ lru-cache "^5.1.1" semver "^6.3.0" +"@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.10.4": version "7.10.5" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz#9f61446ba80e8240b0a5c85c6fdac8459d6f259d" @@ -322,6 +385,21 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/helper-split-export-declaration" "^7.18.6" +"@babel/helper-create-class-features-plugin@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz#db58bf57137b623b916e24874ab7188d93d7f68f" + integrity sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.24.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8" @@ -345,6 +423,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + "@babel/helper-explode-assignable-expression@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.4.tgz#40a1cd917bff1288f699a94a75b37a1a2dbd8c7c" @@ -379,6 +462,14 @@ "@babel/template" "^7.18.10" "@babel/types" "^7.19.0" +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + "@babel/helper-function-name@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" @@ -430,6 +521,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-member-expression-to-functions@^7.10.4", "@babel/helper-member-expression-to-functions@^7.10.5": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz#ae69c83d84ee82f4b42f96e2a09410935a8f26df" @@ -451,6 +549,13 @@ dependencies: "@babel/types" "^7.20.7" +"@babel/helper-member-expression-to-functions@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz#9263e88cc5e41d39ec18c9a3e0eced59a3e7d366" + integrity sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA== + dependencies: + "@babel/types" "^7.23.0" + "@babel/helper-module-imports@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" @@ -520,6 +625,17 @@ "@babel/traverse" "^7.20.10" "@babel/types" "^7.20.7" +"@babel/helper-module-transforms@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" + integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + "@babel/helper-optimise-call-expression@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" @@ -541,6 +657,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-optimise-call-expression@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" + integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" @@ -566,6 +689,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== +"@babel/helper-plugin-utils@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz#945681931a52f15ce879fd5b86ce2dae6d3d7f2a" + integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w== + "@babel/helper-regex@^7.10.4": version "7.10.5" resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.5.tgz#32dfbb79899073c415557053a19bd055aae50ae0" @@ -616,6 +744,15 @@ "@babel/traverse" "^7.20.7" "@babel/types" "^7.20.7" +"@babel/helper-replace-supers@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz#7085bd19d4a0b7ed8f405c1ed73ccb70f323abc1" + integrity sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-simple-access@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" @@ -638,6 +775,13 @@ dependencies: "@babel/types" "^7.20.2" +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers@^7.11.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz#eec162f112c2f58d3af0af125e3bb57665146729" @@ -659,6 +803,13 @@ dependencies: "@babel/types" "^7.20.0" +"@babel/helper-skip-transparent-expression-wrappers@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" + integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" @@ -680,6 +831,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-split-export-declaration@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" @@ -727,7 +885,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== -"@babel/helper-validator-option@^7.22.15": +"@babel/helper-validator-option@^7.22.15", "@babel/helper-validator-option@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== @@ -769,6 +927,15 @@ "@babel/traverse" "^7.20.13" "@babel/types" "^7.20.7" +"@babel/helpers@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.1.tgz#183e44714b9eba36c3038e442516587b1e0a1a94" + integrity sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg== + dependencies: + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" + "@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" @@ -796,6 +963,16 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" + integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/node@^7.14.7": version "7.14.7" resolved "https://registry.yarnpkg.com/@babel/node/-/node-7.14.7.tgz#0090e83e726027ea682240718ca39e4b625b15ad" @@ -838,6 +1015,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.4.tgz#d5f92f57cf2c74ffe9b37981c0e72fee7311372e" integrity sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng== +"@babel/parser@^7.23.0", "@babel/parser@^7.24.0", "@babel/parser@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.1.tgz#1e416d3627393fab1cb5b0f2f1796a100ae9133a" + integrity sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg== + "@babel/plugin-external-helpers@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-external-helpers/-/plugin-external-helpers-7.10.4.tgz#40d38e8e48a1fa3766ab43496253266ca26783ce" @@ -1095,6 +1277,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-syntax-flow@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.1.tgz#875c25e3428d7896c87589765fc8b9d32f24bd8d" + integrity sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" @@ -1130,6 +1319,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-jsx@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz#3f6ca04b8c841811dbc3c5c5f837934e0d626c10" + integrity sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -1207,6 +1403,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" +"@babel/plugin-syntax-typescript@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz#b3bcc51f396d15f3591683f90239de143c076844" + integrity sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-typescript@^7.7.2": version "7.20.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz#4e9a0cfc769c85689b77a2e642d24e9f697fc8c7" @@ -1265,6 +1468,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-transform-class-properties@^7.22.5": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz#bcbf1aef6ba6085cfddec9fc8d58871cf011fc29" + integrity sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-classes@^7.0.0": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz#f438216f094f6bb31dc266ebfab8ff05aecad073" @@ -1362,6 +1573,14 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-flow" "^7.10.4" +"@babel/plugin-transform-flow-strip-types@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.24.1.tgz#fa8d0a146506ea195da1671d38eed459242b2dcc" + integrity sha512-iIYPIWt3dUmUKKE10s3W+jsQ3icFkw0JyRVyY1B7G4yK/nngAOHLVx8xlhA6b/Jzl/Y0nis8gjqhqKtRDQqHWQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-flow" "^7.24.1" + "@babel/plugin-transform-for-of@^7.0.0": version "7.18.8" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1" @@ -1459,6 +1678,15 @@ "@babel/helper-simple-access" "^7.16.0" babel-plugin-dynamic-import-node "^2.3.3" +"@babel/plugin-transform-modules-commonjs@^7.23.0", "@babel/plugin-transform-modules-commonjs@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz#e71ba1d0d69e049a22bf90b3867e263823d3f1b9" + integrity sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-simple-access" "^7.22.5" + "@babel/plugin-transform-modules-systemjs@^7.10.4": version "7.10.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz#6270099c854066681bae9e05f87e1b9cadbe8c85" @@ -1491,6 +1719,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-transform-nullish-coalescing-operator@^7.22.11": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz#0cd494bb97cb07d428bd651632cb9d4140513988" + integrity sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-transform-object-super@^7.0.0": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" @@ -1507,6 +1743,15 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/helper-replace-supers" "^7.10.4" +"@babel/plugin-transform-optional-chaining@^7.23.0": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz#26e588acbedce1ab3519ac40cc748e380c5291e6" + integrity sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.20.7": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz#0ee349e9d1bc96e78e3b37a7af423a4078a7083f" @@ -1522,6 +1767,14 @@ "@babel/helper-get-function-arity" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-transform-private-methods@^7.22.5": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz#a0faa1ae87eff077e1e47a5ec81c3aef383dc15a" + integrity sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-property-literals@^7.0.0": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" @@ -1720,6 +1973,16 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript" "^7.16.0" +"@babel/plugin-transform-typescript@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.1.tgz#5c05e28bb76c7dfe7d6c5bed9951324fd2d3ab07" + integrity sha512-liYSESjX2fZ7JyBFkYG78nfvHlMKE6IpNdTVnxmlYUR+j5ZLsitFbaAE+eJSK2zPPkNWNw4mXL51rQ8WrvdK0w== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-typescript" "^7.24.1" + "@babel/plugin-transform-unicode-escapes@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz#feae523391c7651ddac115dae0a9d06857892007" @@ -1825,6 +2088,15 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-transform-flow-strip-types" "^7.10.4" +"@babel/preset-flow@^7.22.15": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.24.1.tgz#da7196c20c2d7dd4e98cfd8b192fe53b5eb6f0bb" + integrity sha512-sWCV2G9pcqZf+JHyv/RyqEIpFypxdCSxWIxQjpdaQxenNog7cN1pr76hg8u0Fz8Qgg0H4ETkGcJnXL8d4j0PPA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-transform-flow-strip-types" "^7.24.1" + "@babel/preset-modules@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72" @@ -1870,6 +2142,17 @@ "@babel/helper-validator-option" "^7.14.5" "@babel/plugin-transform-typescript" "^7.16.0" +"@babel/preset-typescript@^7.23.0", "@babel/preset-typescript@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz#89bdf13a3149a17b3b2a2c9c62547f06db8845ec" + integrity sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-syntax-jsx" "^7.24.1" + "@babel/plugin-transform-modules-commonjs" "^7.24.1" + "@babel/plugin-transform-typescript" "^7.24.1" + "@babel/register@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.14.5.tgz#d0eac615065d9c2f1995842f85d6e56c345f3233" @@ -1881,6 +2164,17 @@ pirates "^4.0.0" source-map-support "^0.5.16" +"@babel/register@^7.22.15": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.23.7.tgz#485a5e7951939d21304cae4af1719fdb887bc038" + integrity sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ== + dependencies: + clone-deep "^4.0.1" + find-cache-dir "^2.0.0" + make-dir "^2.1.0" + pirates "^4.0.6" + source-map-support "^0.5.16" + "@babel/runtime-corejs2@^7.2.0": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.8.3.tgz#b62a61e0c41a90d2d91181fda6de21cecd3a9734" @@ -1944,6 +2238,15 @@ "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" +"@babel/template@^7.22.15", "@babel/template@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" + integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + "@babel/template@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8" @@ -2029,6 +2332,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.1.tgz#d65c36ac9dd17282175d1e4a3c49d5b7988f530c" + integrity sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ== + dependencies: + "@babel/code-frame" "^7.24.1" + "@babel/generator" "^7.24.1" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.24.1" + "@babel/types" "^7.24.0" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c" @@ -2081,6 +2400,15 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@babel/types@^7.23.0", "@babel/types@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" + integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -2280,6 +2608,13 @@ dependencies: jest-get-type "^29.4.2" +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + "@jest/expect@^29.4.2": version "29.4.2" resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.4.2.tgz#2d4a6a41b29380957c5094de19259f87f194578b" @@ -2354,6 +2689,13 @@ dependencies: "@sinclair/typebox" "^0.25.16" +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jest/source-map@^29.4.2": version "29.4.2" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.4.2.tgz#f9815d59e25cd3d6828e41489cd239271018d153" @@ -2427,6 +2769,18 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -2453,16 +2807,35 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/resolve-uri@3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + "@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + "@jridgewell/source-map@^0.3.2": version "0.3.3" resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.3.tgz#8108265659d4c33e72ffe14e33d6cc5eb59f2fda" @@ -2476,7 +2849,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@jridgewell/sourcemap-codec@^1.4.15": +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== @@ -2497,6 +2870,14 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" @@ -2838,6 +3219,11 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.21.tgz#763b05a4b472c93a8db29b2c3e359d55b29ce272" integrity sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -3063,6 +3449,22 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/jest@^29.5.12": + version "29.5.12" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544" + integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/jscodeshift@^0.11.10": + version "0.11.11" + resolved "https://registry.yarnpkg.com/@types/jscodeshift/-/jscodeshift-0.11.11.tgz#30d7c986f372cd63c670017371da8fbced2b7acf" + integrity sha512-d7CAfFGOupj5qCDqMODXxNz2/NwCv/Lha78ZFbnr6qpk3K98iSB8I+ig9ERE2+EeYML352VMRsjPyOpeA+04eQ== + dependencies: + ast-types "^0.14.1" + recast "^0.20.3" + "@types/jsdom@^20.0.0": version "20.0.1" resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.1.tgz#07c14bc19bd2f918c1929541cdaacae894744808" @@ -3124,6 +3526,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.14.tgz#5465ce598486a703caddbefe8603f8a2cffa3461" integrity sha512-wvzClDGQXOCVNU4APPopC2KtMYukaF1MN/W3xAmslx22Z4/IF1/izDMekuyoUlwfnDHYCIZGaj7jMwnJKBTxKw== +"@types/node@^20.12.3": + version "20.12.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.3.tgz#d6658c2c7776c1cad93534bb45428195ed840c65" + integrity sha512-sD+ia2ubTeWrOu+YMF+MTAB7E+O7qsMqAbMfW7DG3K1URwhZ5hN1pLlRVGbf4wDFzSfikL05M17EyorS86jShw== + dependencies: + undici-types "~5.26.4" + "@types/prettier@^1.0.0 || ^2.0.0 || ^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-3.0.0.tgz#e9bc8160230d3a461dab5c5b41cceef1ef723057" @@ -4220,6 +4629,20 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= +ast-types@0.14.2, ast-types@^0.14.1: + version "0.14.2" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.14.2.tgz#600b882df8583e3cd4f2df5fa20fa83759d4bdfd" + integrity sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA== + dependencies: + tslib "^2.0.1" + +ast-types@^0.16.1: + version "0.16.1" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.16.1.tgz#7a9da1617c9081bc121faafe91711b4c8bb81da2" + integrity sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg== + dependencies: + tslib "^2.0.1" + astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" @@ -4305,7 +4728,7 @@ babel-code-frame@^6.26.0: esutils "^2.0.2" js-tokens "^3.0.2" -babel-core@^7.0.0-bridge: +babel-core@^7.0.0-bridge, babel-core@^7.0.0-bridge.0: version "7.0.0-bridge.0" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg== @@ -4944,6 +5367,16 @@ browserslist@^4.17.5: node-releases "^2.0.1" picocolors "^1.0.0" +browserslist@^4.22.2: + version "4.23.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" + integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== + dependencies: + caniuse-lite "^1.0.30001587" + electron-to-chromium "^1.4.668" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + browserslist@^4.8.3: version "4.8.5" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.8.5.tgz#691af4e327ac877b25e7a3f7ee869c4ef36cdea3" @@ -5176,6 +5609,11 @@ caniuse-lite@^1.0.30001449: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001450.tgz#022225b91200589196b814b51b1bbe45144cf74f" integrity sha512-qMBmvmQmFXaSxexkjjfMvD5rnDL0+m+dUMZKoDYsGG8iZN29RuYh9eRoMvKsT6uMAWlyUUGDEQGJJYjzCIO9ew== +caniuse-lite@^1.0.30001587: + version "1.0.30001605" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz#ca12d7330dd8bcb784557eb9aa64f0037870d9d6" + integrity sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ== + capture-stack-trace@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" @@ -5204,7 +5642,7 @@ chalk@4.1.0, chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@4.x: +chalk@4.x, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -6432,6 +6870,11 @@ diff-sequences@^29.4.2: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.2.tgz#711fe6bd8a5869fe2539cee4a5152425ff671fda" integrity sha512-R6P0Y6PrsH3n4hUXxL3nns0rbRk6Q33js3ygJBeEpbzLzgcNuJ61+u0RXasFpTKISw99TxUzFnumSnRLsjhLaw== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + diff@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" @@ -6670,6 +7113,11 @@ electron-to-chromium@^1.4.284: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== +electron-to-chromium@^1.4.668: + version "1.4.724" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.724.tgz#e0a86fe4d3d0e05a4d7b032549d79608078f830d" + integrity sha512-RTRvkmRkGhNBPPpdrgtDKvmOEYTrPlXDfc0J/Nfq5s29tEahAwhiX4mmhNzj6febWMleulxVYPh7QwCSL/EldA== + electron@^23.1.2: version "23.1.2" resolved "https://registry.yarnpkg.com/electron/-/electron-23.1.2.tgz#f03e361c94f8dd2407963b5461d19aadea335316" @@ -7201,7 +7649,7 @@ espree@^7.3.0, espree@^7.3.1: acorn-jsx "^5.3.1" eslint-visitor-keys "^1.3.0" -esprima@4.0.1, esprima@^4.0.0, esprima@^4.0.1: +esprima@4.0.1, esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -7400,6 +7848,17 @@ expect@=27.2.5: jest-message-util "^27.2.5" jest-regex-util "^27.0.6" +expect@^29.0.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + expect@^29.4.2: version "29.4.2" resolved "https://registry.yarnpkg.com/expect/-/expect-29.4.2.tgz#2ae34eb88de797c64a1541ad0f1e2ea8a7a7b492" @@ -7962,6 +8421,11 @@ flow-bin@^0.232.0: resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.232.0.tgz#80587406cbb3a74577151ad27c6058b2a468c215" integrity sha512-7uOycTN+Ys2nYRJRig5S2yN41ZokW7bC4K1GC4nCDa/3FAZLP5/mQbee6UjxFBP9MC4yUYi17bdFTFzCH8bHeg== +flow-parser@0.*: + version "0.232.0" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.232.0.tgz#db93a660e7017bd366290944c3328ca506ca7d2b" + integrity sha512-U8vcKyYdM+Kb0tPzfPJ5JyPMU0uXKwHxp0L6BcEc+wBlbTW9qRhOqV5DeGXclgclVvtqQNGEG8Strj/b6c/IxA== + flow-remove-types@^2.232.0: version "2.232.0" resolved "https://registry.yarnpkg.com/flow-remove-types/-/flow-remove-types-2.232.0.tgz#a4333fee2524b57220791a130955f48e0b107f1a" @@ -9894,6 +10358,16 @@ jest-diff@^29.4.2: jest-get-type "^29.4.2" pretty-format "^29.4.2" +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-docblock@^29.4.2: version "29.4.2" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.2.tgz#c78a95eedf9a24c0a6cc16cf2abdc4b8b0f2531b" @@ -9956,6 +10430,11 @@ jest-get-type@^29.4.2: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.2.tgz#7cb63f154bca8d8f57364d01614477d466fa43fe" integrity sha512-vERN30V5i2N6lqlFu4ljdTqQAgrkTFMC9xaIIfOPYBw04pufjXRty5RuXBiB1d72tGbURa/UgoiHB90ruOSivg== +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + jest-haste-map@^29.4.2: version "29.4.2" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.4.2.tgz#9112df3f5121e643f1b2dcbaa86ab11b0b90b49a" @@ -10013,6 +10492,16 @@ jest-matcher-utils@^29.4.2: jest-get-type "^29.4.2" pretty-format "^29.4.2" +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-message-util@^27.2.5: version "27.3.1" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.3.1.tgz#f7c25688ad3410ab10bcb862bcfe3152345c6436" @@ -10043,6 +10532,21 @@ jest-message-util@^29.4.2: slash "^3.0.0" stack-utils "^2.0.3" +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + jest-mock@^29.4.2: version "29.4.2" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.4.2.tgz#e1054be66fb3e975d26d4528fcde6979e4759de8" @@ -10193,6 +10697,18 @@ jest-util@^29.4.2: graceful-fs "^4.2.9" picomatch "^2.2.3" +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + jest-validate@^29.4.2: version "29.4.2" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.4.2.tgz#3b3f8c4910ab9a3442d2512e2175df6b3f77b915" @@ -10311,6 +10827,32 @@ jsc-safe-url@^0.2.4: resolved "https://registry.yarnpkg.com/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz#141c14fbb43791e88d5dc64e85a374575a83477a" integrity sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q== +jscodeshift@^0.15.1: + version "0.15.2" + resolved "https://registry.yarnpkg.com/jscodeshift/-/jscodeshift-0.15.2.tgz#145563860360b4819a558c75c545f39683e5a0be" + integrity sha512-FquR7Okgmc4Sd0aEDwqho3rEiKR3BdvuG9jfdHjLJ6JQoWSMpavug3AoIfnfWhxFlf+5pzQh8qjqz0DWFrNQzA== + dependencies: + "@babel/core" "^7.23.0" + "@babel/parser" "^7.23.0" + "@babel/plugin-transform-class-properties" "^7.22.5" + "@babel/plugin-transform-modules-commonjs" "^7.23.0" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.11" + "@babel/plugin-transform-optional-chaining" "^7.23.0" + "@babel/plugin-transform-private-methods" "^7.22.5" + "@babel/preset-flow" "^7.22.15" + "@babel/preset-typescript" "^7.23.0" + "@babel/register" "^7.22.15" + babel-core "^7.0.0-bridge.0" + chalk "^4.1.2" + flow-parser "0.*" + graceful-fs "^4.2.4" + micromatch "^4.0.4" + neo-async "^2.5.0" + node-dir "^0.1.17" + recast "^0.23.3" + temp "^0.8.4" + write-file-atomic "^2.3.0" + jsdom@^20.0.0: version "20.0.3" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db" @@ -10433,7 +10975,7 @@ json5@^2.1.2, json5@^2.1.3: dependencies: minimist "^1.2.5" -json5@^2.2.2: +json5@^2.2.2, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== @@ -11242,6 +11784,13 @@ minimalistic-assert@^1.0.0: dependencies: brace-expansion "^1.1.7" +minimatch@^3.0.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + minimatch@^5.0.1: version "5.1.6" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" @@ -11437,6 +11986,13 @@ node-cleanup@^2.1.2: resolved "https://registry.yarnpkg.com/node-cleanup/-/node-cleanup-2.1.2.tgz#7ac19abd297e09a7f72a71545d951b517e4dde2c" integrity sha1-esGavSl+Caf3KnFUXZUbUX5N3iw= +node-dir@^0.1.17: + version "0.1.17" + resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" + integrity sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg== + dependencies: + minimatch "^3.0.2" + node-environment-flags@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" @@ -11544,6 +12100,11 @@ node-releases@^2.0.1: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + node-releases@^2.0.8: version "2.0.9" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.9.tgz#fe66405285382b0c4ac6bcfbfbe7e8a510650b4d" @@ -12413,6 +12974,11 @@ pirates@^4.0.4: resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== +pirates@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + pixelmatch@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-5.2.1.tgz#9e4e4f4aa59648208a31310306a5bed5522b0d65" @@ -12623,6 +13189,15 @@ pretty-format@^27.2.5, pretty-format@^27.3.1: ansi-styles "^5.0.0" react-is "^17.0.1" +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + pretty-format@^29.4.1: version "29.4.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.4.1.tgz#0da99b532559097b8254298da7c75a0785b1751c" @@ -13142,6 +13717,27 @@ readline-sync@^1.4.9: resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b" integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw== +recast@^0.20.3: + version "0.20.5" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.20.5.tgz#8e2c6c96827a1b339c634dd232957d230553ceae" + integrity sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ== + dependencies: + ast-types "0.14.2" + esprima "~4.0.0" + source-map "~0.6.1" + tslib "^2.0.1" + +recast@^0.23.3: + version "0.23.6" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.23.6.tgz#198fba74f66143a30acc81929302d214ce4e3bfa" + integrity sha512-9FHoNjX1yjuesMwuthAmPKabxYQdOgihFYmT5ebXfYGBcnqXZf3WOVz+5foEZ8Y83P4ZY6yQD5GMmtV+pgCCAQ== + dependencies: + ast-types "^0.16.1" + esprima "~4.0.0" + source-map "~0.6.1" + tiny-invariant "^1.3.3" + tslib "^2.0.1" + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -13484,7 +14080,7 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@2.6.3: +rimraf@2.6.3, rimraf@~2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== @@ -13800,6 +14396,11 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + semver@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.1.tgz#29104598a197d6cbe4733eeecbe968f7b43a9667" @@ -14459,7 +15060,7 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -14494,15 +15095,6 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -14563,7 +15155,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -14598,13 +15190,6 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" -strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -14877,6 +15462,13 @@ temp-dir@^1.0.0: resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= +temp@^0.8.4: + version "0.8.4" + resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.4.tgz#8c97a33a4770072e0a05f919396c7665a7dd59f2" + integrity sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg== + dependencies: + rimraf "~2.6.2" + tempfile@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-2.0.0.tgz#6b0446856a9b1114d1856ffcbe509cccb0977265" @@ -15004,6 +15596,11 @@ timers-browserify@^1.0.1: dependencies: process "~0.11.0" +tiny-invariant@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + tiny-warning@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" @@ -15182,6 +15779,11 @@ tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.2.tgz#9c79d83272c9a7aaf166f73915c9667ecdde3cc9" integrity sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg== +tslib@^2.0.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tslib@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" @@ -15287,6 +15889,11 @@ typescript@^3.7.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== +typescript@^5.2.2: + version "5.4.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff" + integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== + ua-parser-js@^0.7.18, ua-parser-js@^0.7.9: version "0.7.20" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.20.tgz#7527178b82f6a62a0f243d1f94fd30e3e3c21098" @@ -15315,6 +15922,11 @@ unc-path-regex@^0.1.0, unc-path-regex@^0.1.2: resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -15426,6 +16038,14 @@ update-browserslist-db@^1.0.10: escalade "^3.1.1" picocolors "^1.0.0" +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + update-notifier@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.0.0.tgz#f344a6f8b03e00e31b323d632a0e632e9f0e0654" @@ -16040,7 +16660,7 @@ workerize-loader@^2.0.2: dependencies: loader-utils "^2.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -16058,15 +16678,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -16081,7 +16692,7 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write-file-atomic@^2.0.0: +write-file-atomic@^2.0.0, write-file-atomic@^2.3.0: version "2.4.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== From 15f05cc59ecd5f1d2f2e98ef7f0da4b83e495fe8 Mon Sep 17 00:00:00 2001 From: Dmytro Hryshyn Date: Thu, 4 Apr 2024 13:09:01 +0300 Subject: [PATCH 02/11] add jest config for codemod project, add codemod project options --- codemods/remove-context-provider/__tests__/test.ts | 2 +- codemods/remove-forward-ref/__tests__/test.ts | 2 +- .../remove-memoization-hooks/__tests__/test.ts | 2 +- codemods/use-context-hook/__tests__/test.ts | 4 ++-- scripts/jest/config.codemod.js | 14 ++++++++++++++ scripts/jest/jest-cli.js | 7 +++++-- 6 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 scripts/jest/config.codemod.js diff --git a/codemods/remove-context-provider/__tests__/test.ts b/codemods/remove-context-provider/__tests__/test.ts index d6e5abfc92fb5..9cf9ae9974052 100644 --- a/codemods/remove-context-provider/__tests__/test.ts +++ b/codemods/remove-context-provider/__tests__/test.ts @@ -2,7 +2,7 @@ import assert from 'node:assert/strict'; import {readFile} from 'node:fs/promises'; import {join} from 'node:path'; import jscodeshift, {API, FileInfo} from 'jscodeshift'; -import transform from '../src/index.js'; +import transform from '../src/index.ts'; export const buildApi = (parser: string | undefined): API => ({ j: parser ? jscodeshift.withParser(parser) : jscodeshift, diff --git a/codemods/remove-forward-ref/__tests__/test.ts b/codemods/remove-forward-ref/__tests__/test.ts index 5fe05dffefa9b..0ebc3484fe925 100644 --- a/codemods/remove-forward-ref/__tests__/test.ts +++ b/codemods/remove-forward-ref/__tests__/test.ts @@ -2,7 +2,7 @@ import assert from 'node:assert/strict'; import {readFile} from 'node:fs/promises'; import {join} from 'node:path'; import jscodeshift, {API, FileInfo} from 'jscodeshift'; -import transform from '../src/index.js'; +import transform from '../src/index.ts'; export const buildApi = (parser: string | undefined): API => ({ j: parser ? jscodeshift.withParser(parser) : jscodeshift, diff --git a/codemods/remove-memoization-hooks/__tests__/test.ts b/codemods/remove-memoization-hooks/__tests__/test.ts index 0f4c67d33cd9d..dc356637a9f21 100644 --- a/codemods/remove-memoization-hooks/__tests__/test.ts +++ b/codemods/remove-memoization-hooks/__tests__/test.ts @@ -2,7 +2,7 @@ import assert from 'node:assert/strict'; import {readFile} from 'node:fs/promises'; import {join} from 'node:path'; import jscodeshift, {API, FileInfo} from 'jscodeshift'; -import transform from '../src/index.js'; +import transform from '../src/index.ts'; export const buildApi = (parser: string | undefined): API => ({ j: parser ? jscodeshift.withParser(parser) : jscodeshift, diff --git a/codemods/use-context-hook/__tests__/test.ts b/codemods/use-context-hook/__tests__/test.ts index 3ebf62e0e6a2c..cf538a14990a0 100644 --- a/codemods/use-context-hook/__tests__/test.ts +++ b/codemods/use-context-hook/__tests__/test.ts @@ -1,8 +1,8 @@ import assert from 'node:assert/strict'; import {readFile} from 'node:fs/promises'; import {join} from 'node:path'; -import jscodeshift, {API, FileInfo} from 'jscodeshift'; -import transform from '../src/index.js'; +import jscodeshift, { type API, type FileInfo} from 'jscodeshift'; +import transform from '../src/index.ts'; export const buildApi = (parser: string | undefined): API => ({ j: parser ? jscodeshift.withParser(parser) : jscodeshift, diff --git a/scripts/jest/config.codemod.js b/scripts/jest/config.codemod.js new file mode 100644 index 0000000000000..fcdf99392ae01 --- /dev/null +++ b/scripts/jest/config.codemod.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = { + globalSetup: require.resolve('./setupGlobal.js'), + modulePathIgnorePatterns: [ + '/packages', + ], + testMatch: ['**/__tests__/**'], + moduleFileExtensions: ['js', 'ts'], + rootDir: process.cwd(), + roots: ['/codemods'], + testEnvironment: 'node', + testRunner: 'jest-circus/runner', +}; diff --git a/scripts/jest/jest-cli.js b/scripts/jest/jest-cli.js index 22098a190561e..fe0d99811ca95 100644 --- a/scripts/jest/jest-cli.js +++ b/scripts/jest/jest-cli.js @@ -10,6 +10,7 @@ const semver = require('semver'); const ossConfig = './scripts/jest/config.source.js'; const wwwConfig = './scripts/jest/config.source-www.js'; const devToolsConfig = './scripts/jest/config.build-devtools.js'; +const codemodConfig = './scripts/jest/config.codemod.js'; // TODO: These configs are separate but should be rolled into the configs above // so that the CLI can provide them as options for any of the configs. @@ -38,7 +39,7 @@ const argv = yargs requiresArg: true, type: 'string', default: 'default', - choices: ['default', 'devtools'], + choices: ['default', 'devtools', 'codemod'], }, releaseChannel: { alias: 'r', @@ -269,7 +270,9 @@ function validateOptions() { function getCommandArgs() { // Add the correct Jest config. const args = ['./scripts/jest/jest.js', '--config']; - if (argv.project === 'devtools') { + if (argv.project === 'codemod') { + args.push(codemodConfig); + } else if (argv.project === 'devtools') { args.push(devToolsConfig); } else if (argv.build) { args.push(buildConfig); From 0a30757b13287375adeeb71389efd8a93f9fe1d7 Mon Sep 17 00:00:00 2001 From: Sasha Mysak Date: Fri, 5 Apr 2024 16:14:33 +0200 Subject: [PATCH 03/11] add replace-act codemod and update configs --- .circleci/config.yml | 3 + codemods/replace-act-import/.codemodrc.json | 12 ++ codemods/replace-act-import/README.md | 42 +++++ .../__testfixtures__/fixture1.input.js | 2 + .../__testfixtures__/fixture1.output.js | 2 + .../__testfixtures__/fixture2.input.js | 2 + .../__testfixtures__/fixture2.output.js | 2 + .../__testfixtures__/fixture3.input.js | 2 + .../__testfixtures__/fixture3.output.js | 2 + .../__testfixtures__/fixture4.input.js | 2 + .../__testfixtures__/fixture4.output.js | 2 + .../__testfixtures__/fixture5.input.js | 4 + .../__testfixtures__/fixture5.output.js | 3 + .../__testfixtures__/fixture6.input.js | 3 + .../__testfixtures__/fixture6.output.js | 2 + codemods/replace-act-import/__tests__/test.ts | 174 ++++++++++++++++++ codemods/replace-act-import/package.json | 17 ++ codemods/replace-act-import/src/index.ts | 156 ++++++++++++++++ codemods/replace-act-import/tsconfig.json | 23 +++ scripts/jest/config.base.js | 2 +- .../{config.codemod.js => config.codemods.js} | 0 scripts/jest/jest-cli.js | 8 +- 22 files changed, 460 insertions(+), 5 deletions(-) create mode 100644 codemods/replace-act-import/.codemodrc.json create mode 100644 codemods/replace-act-import/README.md create mode 100644 codemods/replace-act-import/__testfixtures__/fixture1.input.js create mode 100644 codemods/replace-act-import/__testfixtures__/fixture1.output.js create mode 100644 codemods/replace-act-import/__testfixtures__/fixture2.input.js create mode 100644 codemods/replace-act-import/__testfixtures__/fixture2.output.js create mode 100644 codemods/replace-act-import/__testfixtures__/fixture3.input.js create mode 100644 codemods/replace-act-import/__testfixtures__/fixture3.output.js create mode 100644 codemods/replace-act-import/__testfixtures__/fixture4.input.js create mode 100644 codemods/replace-act-import/__testfixtures__/fixture4.output.js create mode 100644 codemods/replace-act-import/__testfixtures__/fixture5.input.js create mode 100644 codemods/replace-act-import/__testfixtures__/fixture5.output.js create mode 100644 codemods/replace-act-import/__testfixtures__/fixture6.input.js create mode 100644 codemods/replace-act-import/__testfixtures__/fixture6.output.js create mode 100644 codemods/replace-act-import/__tests__/test.ts create mode 100644 codemods/replace-act-import/package.json create mode 100644 codemods/replace-act-import/src/index.ts create mode 100644 codemods/replace-act-import/tsconfig.json rename scripts/jest/{config.codemod.js => config.codemods.js} (100%) diff --git a/.circleci/config.yml b/.circleci/config.yml index f2c743e4525cf..cb44edca71a84 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -501,6 +501,9 @@ workflows: - "-r=www-modern --env=development --variant=true" - "-r=www-modern --env=production --variant=true" + # Codemods + - "--project=codemods -r=experimental" + # TODO: Test more persistent configurations? - '-r=stable --env=development --persistent' - '-r=experimental --env=development --persistent' diff --git a/codemods/replace-act-import/.codemodrc.json b/codemods/replace-act-import/.codemodrc.json new file mode 100644 index 0000000000000..2f9338e705f7f --- /dev/null +++ b/codemods/replace-act-import/.codemodrc.json @@ -0,0 +1,12 @@ +{ + "version": "1.0.0", + "name": "react/19/replace-act-import", + "private": false, + "engine": "jscodeshift", + "meta": { + "tags": ["react", "migration"] + }, + "applicability": { + "from": [["react", "<=", "18"]] + } +} diff --git a/codemods/replace-act-import/README.md b/codemods/replace-act-import/README.md new file mode 100644 index 0000000000000..982b6743e9495 --- /dev/null +++ b/codemods/replace-act-import/README.md @@ -0,0 +1,42 @@ +# Replace react dom test utils with react + +## Description + +This codemod will replace the usages of `TestUtils.act()` to use `React.act()`, introduced in React v19. + +## Examples + +### Before + +```ts +import { act } from 'react-dom/test-utils'; + +act(); +``` + +### After + +```ts +import { act } from "react"; + +act(); +``` + + + +### Before + +```ts +import * as ReactDOMTestUtils from 'react-dom/test-utils'; + +ReactDOMTestUtils.act(); +``` + +### After + +```ts +import * as React from "react"; + +React.act(); +``` + diff --git a/codemods/replace-act-import/__testfixtures__/fixture1.input.js b/codemods/replace-act-import/__testfixtures__/fixture1.input.js new file mode 100644 index 0000000000000..f2492fc0a4972 --- /dev/null +++ b/codemods/replace-act-import/__testfixtures__/fixture1.input.js @@ -0,0 +1,2 @@ +import {act} from 'react-dom/test-utils'; +act(); diff --git a/codemods/replace-act-import/__testfixtures__/fixture1.output.js b/codemods/replace-act-import/__testfixtures__/fixture1.output.js new file mode 100644 index 0000000000000..5bd165a4686c4 --- /dev/null +++ b/codemods/replace-act-import/__testfixtures__/fixture1.output.js @@ -0,0 +1,2 @@ +import {act} from 'react'; +act(); diff --git a/codemods/replace-act-import/__testfixtures__/fixture2.input.js b/codemods/replace-act-import/__testfixtures__/fixture2.input.js new file mode 100644 index 0000000000000..d77a870b7d737 --- /dev/null +++ b/codemods/replace-act-import/__testfixtures__/fixture2.input.js @@ -0,0 +1,2 @@ +import ReactTestUtils from 'react-dom/test-utils'; +ReactTestUtils.act(); diff --git a/codemods/replace-act-import/__testfixtures__/fixture2.output.js b/codemods/replace-act-import/__testfixtures__/fixture2.output.js new file mode 100644 index 0000000000000..29a945350c775 --- /dev/null +++ b/codemods/replace-act-import/__testfixtures__/fixture2.output.js @@ -0,0 +1,2 @@ +import React from 'react'; +React.act(); diff --git a/codemods/replace-act-import/__testfixtures__/fixture3.input.js b/codemods/replace-act-import/__testfixtures__/fixture3.input.js new file mode 100644 index 0000000000000..c8114f65590ad --- /dev/null +++ b/codemods/replace-act-import/__testfixtures__/fixture3.input.js @@ -0,0 +1,2 @@ +import * as ReactTestUtils from 'react-dom/test-utils'; +ReactTestUtils.act(); diff --git a/codemods/replace-act-import/__testfixtures__/fixture3.output.js b/codemods/replace-act-import/__testfixtures__/fixture3.output.js new file mode 100644 index 0000000000000..35875f9ae285a --- /dev/null +++ b/codemods/replace-act-import/__testfixtures__/fixture3.output.js @@ -0,0 +1,2 @@ +import * as React from 'react'; +React.act(); diff --git a/codemods/replace-act-import/__testfixtures__/fixture4.input.js b/codemods/replace-act-import/__testfixtures__/fixture4.input.js new file mode 100644 index 0000000000000..33fe1298c3d98 --- /dev/null +++ b/codemods/replace-act-import/__testfixtures__/fixture4.input.js @@ -0,0 +1,2 @@ +import {other} from 'react-dom/test-utils'; +other.thing(); diff --git a/codemods/replace-act-import/__testfixtures__/fixture4.output.js b/codemods/replace-act-import/__testfixtures__/fixture4.output.js new file mode 100644 index 0000000000000..33fe1298c3d98 --- /dev/null +++ b/codemods/replace-act-import/__testfixtures__/fixture4.output.js @@ -0,0 +1,2 @@ +import {other} from 'react-dom/test-utils'; +other.thing(); diff --git a/codemods/replace-act-import/__testfixtures__/fixture5.input.js b/codemods/replace-act-import/__testfixtures__/fixture5.input.js new file mode 100644 index 0000000000000..cb6a1dbaba6e0 --- /dev/null +++ b/codemods/replace-act-import/__testfixtures__/fixture5.input.js @@ -0,0 +1,4 @@ +import * as React from 'react'; +import * as ReactTestUtils from 'react-dom/test-utils'; + +ReactTestUtils.act(); diff --git a/codemods/replace-act-import/__testfixtures__/fixture5.output.js b/codemods/replace-act-import/__testfixtures__/fixture5.output.js new file mode 100644 index 0000000000000..eda28ed7fbc94 --- /dev/null +++ b/codemods/replace-act-import/__testfixtures__/fixture5.output.js @@ -0,0 +1,3 @@ +import * as React from 'react'; + +React.act(); diff --git a/codemods/replace-act-import/__testfixtures__/fixture6.input.js b/codemods/replace-act-import/__testfixtures__/fixture6.input.js new file mode 100644 index 0000000000000..ba92563c6c9ff --- /dev/null +++ b/codemods/replace-act-import/__testfixtures__/fixture6.input.js @@ -0,0 +1,3 @@ +import {FC} from 'react'; +import {act} from 'react-dom/test-utils'; +act(); diff --git a/codemods/replace-act-import/__testfixtures__/fixture6.output.js b/codemods/replace-act-import/__testfixtures__/fixture6.output.js new file mode 100644 index 0000000000000..980480b391618 --- /dev/null +++ b/codemods/replace-act-import/__testfixtures__/fixture6.output.js @@ -0,0 +1,2 @@ +import {FC, act} from 'react'; +act(); diff --git a/codemods/replace-act-import/__tests__/test.ts b/codemods/replace-act-import/__tests__/test.ts new file mode 100644 index 0000000000000..71dc64946d98d --- /dev/null +++ b/codemods/replace-act-import/__tests__/test.ts @@ -0,0 +1,174 @@ +import assert from 'node:assert/strict'; +import {readFile} from 'node:fs/promises'; +import {join} from 'node:path'; +import jscodeshift, {type API, type FileInfo} from 'jscodeshift'; +import transform from '../src/index.ts'; + +export const buildApi = (parser: string | undefined): API => ({ + j: parser ? jscodeshift.withParser(parser) : jscodeshift, + jscodeshift: parser ? jscodeshift.withParser(parser) : jscodeshift, + stats: () => { + console.error( + 'The stats function was called, which is not supported on purpose' + ); + }, + report: () => { + console.error( + 'The report function was called, which is not supported on purpose' + ); + }, +}); + +describe('react/19/replace-act-import: TestUtils.act -> React.act', () => { + describe('javascript code', () => { + it('should replace direct import with import from react', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture1.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture1.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should replace TestUtils.act with React.act', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture2.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture2.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should properly replace star import', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture3.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture3.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should not replace other imports from test utils', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture4.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture4.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should not add react import if one is already present', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture5.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture5.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should add import specifier to existing import if it exists', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture6.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture6.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + }); +}); diff --git a/codemods/replace-act-import/package.json b/codemods/replace-act-import/package.json new file mode 100644 index 0000000000000..61d27cda5c4a1 --- /dev/null +++ b/codemods/replace-act-import/package.json @@ -0,0 +1,17 @@ +{ + "name": "replace-act-import", + "version": "1.0.0", + "dependencies": {}, + "devDependencies": { + "@types/node": "^20.12.3", + "@types/jscodeshift": "^0.11.10", + "jscodeshift": "^0.15.1", + "typescript": "^5.2.2" + }, + "files": [ + "./README.md", + "./.codemodrc.json", + "./dist/index.cjs" + ], + "type": "module" +} diff --git a/codemods/replace-act-import/src/index.ts b/codemods/replace-act-import/src/index.ts new file mode 100644 index 0000000000000..71d480220e721 --- /dev/null +++ b/codemods/replace-act-import/src/index.ts @@ -0,0 +1,156 @@ +import type {API, FileInfo, Options} from 'jscodeshift'; + +export default function transform( + file: FileInfo, + api: API, + options?: Options +): string | undefined { + const j = api.jscodeshift; + const root = j(file.source); + + // Get default import from test utils + const defaultUtilsImportName = root + .find(j.ImportDeclaration, { + source: {value: 'react-dom/test-utils'}, + specifiers: [{type: 'ImportDefaultSpecifier'}], + }) + .paths() + .at(0) + ?.node.specifiers?.at(0)?.local?.name; + + // Get default import from test utils + const starUtilsImportName = root + .find(j.ImportDeclaration, { + source: {value: 'react-dom/test-utils'}, + specifiers: [{type: 'ImportNamespaceSpecifier'}], + }) + .paths() + .at(0) + ?.node.specifiers?.at(0)?.local?.name; + + const utilsCalleeName = defaultUtilsImportName ?? starUtilsImportName; + const utilsCalleeType: any = defaultUtilsImportName + ? 'ImportDefaultSpecifier' + : 'ImportNamespaceSpecifier'; + + // For usages like `import * as ReactTestUtils from 'react-dom/test-utils'; ReactTestUtils.act()` + const actAccessExpressions = root.find(j.MemberExpression, { + object: {name: utilsCalleeName}, + property: {name: 'act'}, + }); + + if (actAccessExpressions.length > 0) { + // React import + const defaultReactImportName = root + .find(j.ImportDeclaration, {source: {value: 'react'}}) + .paths() + .at(0) + ?.node.specifiers?.at(0)?.local?.name; + + if (!defaultReactImportName) { + const importNode = + utilsCalleeType === 'ImportDefaultSpecifier' + ? j.importDefaultSpecifier + : j.importNamespaceSpecifier; + + const reactImport = j.importDeclaration( + [importNode(j.identifier('React'))], + j.literal('react') + ); + + root.get().node.program.body.unshift(reactImport); + } + + actAccessExpressions.forEach(path => { + const accessedPath = j(path) + .find(j.Identifier, {name: utilsCalleeName}) + .paths() + .at(0); + + const newIdentifier = j.identifier.from({ + name: defaultReactImportName ?? 'React', + }); + + accessedPath?.replace(newIdentifier); + }); + + // Remove the old import + root + .find(j.ImportDeclaration, { + source: {value: 'react-dom/test-utils'}, + specifiers: [{type: utilsCalleeType}], + }) + .remove(); + } + + // Get act direct import from test utils + // const actImport = root + // .find(j.ImportDeclaration, { + // source: {value: 'react-dom/test-utils'}, + // specifiers: [{type: 'ImportSpecifier', imported: {name: 'act'}}], + // }) + // .at(0); + + // if (actImport) { + // // actImport.remove(); + // // const reactImport = root + // // .find(j.ImportDeclaration, { + // // source: {value: 'react'}, + // // specifiers: [{type: 'ImportSpecifier'}], + // // }) + // // .paths() + // // .at(0); + + // // if (!reactImport) { + // // const newReactImport = j.importDeclaration( + // // [j.importSpecifier(j.identifier('act'))], + // // j.literal('react') + // // ); + + // // root.find(j.VariableDeclaration).at(0).get().insertBefore(newReactImport); + // // } else { + // // const newImportSpecifier = j.importSpecifier( + // // j.identifier('act'), + // // j.identifier('act') + // // ); + + // // reactImport?.node.specifiers?.push(newImportSpecifier); + // // } + + // } + + root + .find(j.ImportDeclaration, { + source: {value: 'react-dom/test-utils'}, + specifiers: [{type: 'ImportSpecifier', imported: {name: 'act'}}], + }) + .forEach(path => { + const newImportSpecifier = j.importSpecifier( + j.identifier('act'), + j.identifier('act') + ); + + const existingReactImportCollection = root.find(j.ImportDeclaration, { + source: {value: 'react'}, + specifiers: [{type: 'ImportSpecifier'}], + }); + + if (existingReactImportCollection.length > 0) { + existingReactImportCollection + .paths() + .at(0) + ?.node.specifiers?.push(newImportSpecifier); + + path.prune(); + } else { + const newImportDeclaration = j.importDeclaration( + [newImportSpecifier], + j.literal('react') + ); + + path.replace(newImportDeclaration); + } + }); + + return root.toSource(); +} diff --git a/codemods/replace-act-import/tsconfig.json b/codemods/replace-act-import/tsconfig.json new file mode 100644 index 0000000000000..a30abbbea378c --- /dev/null +++ b/codemods/replace-act-import/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "module": "NodeNext", + "skipLibCheck": true, + "strict": true, + "target": "ES6", + "allowJs": true + }, + "include": [ + "./src/**/*.ts", + "./src/**/*.js", + "./test/**/*.ts", + "./test/**/*.js" + ], + "exclude": ["node_modules", "./dist/**/*"], + "ts-node": { + "transpileOnly": true + } +} \ No newline at end of file diff --git a/scripts/jest/config.base.js b/scripts/jest/config.base.js index 61700d9fd0fb6..a887e66b7dd3c 100644 --- a/scripts/jest/config.base.js +++ b/scripts/jest/config.base.js @@ -16,7 +16,7 @@ module.exports = { testRegex: '/__tests__/[^/]*(\\.js|\\.coffee|[^d]\\.ts)$', moduleFileExtensions: ['js', 'json', 'node', 'coffee', 'ts'], rootDir: process.cwd(), - roots: ['/packages', '/scripts', '/codemods'], + roots: ['/packages', '/scripts'], collectCoverageFrom: ['packages/**/*.js'], fakeTimers: { enableGlobally: true, diff --git a/scripts/jest/config.codemod.js b/scripts/jest/config.codemods.js similarity index 100% rename from scripts/jest/config.codemod.js rename to scripts/jest/config.codemods.js diff --git a/scripts/jest/jest-cli.js b/scripts/jest/jest-cli.js index fe0d99811ca95..1596c2a384fa0 100644 --- a/scripts/jest/jest-cli.js +++ b/scripts/jest/jest-cli.js @@ -10,7 +10,7 @@ const semver = require('semver'); const ossConfig = './scripts/jest/config.source.js'; const wwwConfig = './scripts/jest/config.source-www.js'; const devToolsConfig = './scripts/jest/config.build-devtools.js'; -const codemodConfig = './scripts/jest/config.codemod.js'; +const codemodsConfig = './scripts/jest/config.codemods.js'; // TODO: These configs are separate but should be rolled into the configs above // so that the CLI can provide them as options for any of the configs. @@ -39,7 +39,7 @@ const argv = yargs requiresArg: true, type: 'string', default: 'default', - choices: ['default', 'devtools', 'codemod'], + choices: ['default', 'devtools', 'codemods'], }, releaseChannel: { alias: 'r', @@ -270,8 +270,8 @@ function validateOptions() { function getCommandArgs() { // Add the correct Jest config. const args = ['./scripts/jest/jest.js', '--config']; - if (argv.project === 'codemod') { - args.push(codemodConfig); + if (argv.project === 'codemods') { + args.push(codemodsConfig); } else if (argv.project === 'devtools') { args.push(devToolsConfig); } else if (argv.build) { From 4bbb1f8c889f5f06a46e89918f0965965ce988cd Mon Sep 17 00:00:00 2001 From: Sasha Mysak Date: Tue, 9 Apr 2024 19:03:10 +0200 Subject: [PATCH 04/11] feat: adds react v19 useFormState() replacement codemod and fixes tsconfigs --- .../remove-context-provider/tsconfig.json | 8 +- codemods/remove-forward-ref/tsconfig.json | 8 +- .../remove-memoization-hooks/tsconfig.json | 8 +- codemods/replace-act-import/package.json | 6 +- codemods/replace-act-import/tsconfig.json | 8 +- .../replace-use-form-state/.codemodrc.json | 12 ++ codemods/replace-use-form-state/README.md | 72 ++++++++ .../__testfixtures__/fixture1.input.js | 15 ++ .../__testfixtures__/fixture1.output.js | 15 ++ .../__testfixtures__/fixture2.input.js | 6 + .../__testfixtures__/fixture2.output.js | 6 + .../__testfixtures__/fixture3.input.js | 6 + .../__testfixtures__/fixture3.output.js | 6 + .../__testfixtures__/fixture4.input.js | 6 + .../__testfixtures__/fixture4.output.js | 6 + .../__testfixtures__/fixture5.input.js | 8 + .../__testfixtures__/fixture5.output.js | 8 + .../__testfixtures__/fixture6.input.js | 8 + .../__testfixtures__/fixture6.output.js | 8 + .../replace-use-form-state/__tests__/test.ts | 172 ++++++++++++++++++ codemods/replace-use-form-state/package.json | 13 ++ codemods/replace-use-form-state/src/index.ts | 95 ++++++++++ codemods/replace-use-form-state/tsconfig.json | 25 +++ codemods/use-context-hook/tsconfig.json | 8 +- 24 files changed, 513 insertions(+), 20 deletions(-) create mode 100644 codemods/replace-use-form-state/.codemodrc.json create mode 100644 codemods/replace-use-form-state/README.md create mode 100644 codemods/replace-use-form-state/__testfixtures__/fixture1.input.js create mode 100644 codemods/replace-use-form-state/__testfixtures__/fixture1.output.js create mode 100644 codemods/replace-use-form-state/__testfixtures__/fixture2.input.js create mode 100644 codemods/replace-use-form-state/__testfixtures__/fixture2.output.js create mode 100644 codemods/replace-use-form-state/__testfixtures__/fixture3.input.js create mode 100644 codemods/replace-use-form-state/__testfixtures__/fixture3.output.js create mode 100644 codemods/replace-use-form-state/__testfixtures__/fixture4.input.js create mode 100644 codemods/replace-use-form-state/__testfixtures__/fixture4.output.js create mode 100644 codemods/replace-use-form-state/__testfixtures__/fixture5.input.js create mode 100644 codemods/replace-use-form-state/__testfixtures__/fixture5.output.js create mode 100644 codemods/replace-use-form-state/__testfixtures__/fixture6.input.js create mode 100644 codemods/replace-use-form-state/__testfixtures__/fixture6.output.js create mode 100644 codemods/replace-use-form-state/__tests__/test.ts create mode 100644 codemods/replace-use-form-state/package.json create mode 100644 codemods/replace-use-form-state/src/index.ts create mode 100644 codemods/replace-use-form-state/tsconfig.json diff --git a/codemods/remove-context-provider/tsconfig.json b/codemods/remove-context-provider/tsconfig.json index a30abbbea378c..5717e5c332cf3 100644 --- a/codemods/remove-context-provider/tsconfig.json +++ b/codemods/remove-context-provider/tsconfig.json @@ -8,13 +8,15 @@ "skipLibCheck": true, "strict": true, "target": "ES6", - "allowJs": true + "allowJs": true, + "allowImportingTsExtensions": true, + "noEmit": true }, "include": [ "./src/**/*.ts", "./src/**/*.js", - "./test/**/*.ts", - "./test/**/*.js" + "./__tests__/**/*.ts", + "./__tests__/**/*.js" ], "exclude": ["node_modules", "./dist/**/*"], "ts-node": { diff --git a/codemods/remove-forward-ref/tsconfig.json b/codemods/remove-forward-ref/tsconfig.json index a30abbbea378c..5717e5c332cf3 100644 --- a/codemods/remove-forward-ref/tsconfig.json +++ b/codemods/remove-forward-ref/tsconfig.json @@ -8,13 +8,15 @@ "skipLibCheck": true, "strict": true, "target": "ES6", - "allowJs": true + "allowJs": true, + "allowImportingTsExtensions": true, + "noEmit": true }, "include": [ "./src/**/*.ts", "./src/**/*.js", - "./test/**/*.ts", - "./test/**/*.js" + "./__tests__/**/*.ts", + "./__tests__/**/*.js" ], "exclude": ["node_modules", "./dist/**/*"], "ts-node": { diff --git a/codemods/remove-memoization-hooks/tsconfig.json b/codemods/remove-memoization-hooks/tsconfig.json index a30abbbea378c..5717e5c332cf3 100644 --- a/codemods/remove-memoization-hooks/tsconfig.json +++ b/codemods/remove-memoization-hooks/tsconfig.json @@ -8,13 +8,15 @@ "skipLibCheck": true, "strict": true, "target": "ES6", - "allowJs": true + "allowJs": true, + "allowImportingTsExtensions": true, + "noEmit": true }, "include": [ "./src/**/*.ts", "./src/**/*.js", - "./test/**/*.ts", - "./test/**/*.js" + "./__tests__/**/*.ts", + "./__tests__/**/*.js" ], "exclude": ["node_modules", "./dist/**/*"], "ts-node": { diff --git a/codemods/replace-act-import/package.json b/codemods/replace-act-import/package.json index 61d27cda5c4a1..6aaaa1adebcd1 100644 --- a/codemods/replace-act-import/package.json +++ b/codemods/replace-act-import/package.json @@ -8,10 +8,6 @@ "jscodeshift": "^0.15.1", "typescript": "^5.2.2" }, - "files": [ - "./README.md", - "./.codemodrc.json", - "./dist/index.cjs" - ], + "files": ["./README.md", "./.codemodrc.json", "./dist/index.cjs"], "type": "module" } diff --git a/codemods/replace-act-import/tsconfig.json b/codemods/replace-act-import/tsconfig.json index a30abbbea378c..5717e5c332cf3 100644 --- a/codemods/replace-act-import/tsconfig.json +++ b/codemods/replace-act-import/tsconfig.json @@ -8,13 +8,15 @@ "skipLibCheck": true, "strict": true, "target": "ES6", - "allowJs": true + "allowJs": true, + "allowImportingTsExtensions": true, + "noEmit": true }, "include": [ "./src/**/*.ts", "./src/**/*.js", - "./test/**/*.ts", - "./test/**/*.js" + "./__tests__/**/*.ts", + "./__tests__/**/*.js" ], "exclude": ["node_modules", "./dist/**/*"], "ts-node": { diff --git a/codemods/replace-use-form-state/.codemodrc.json b/codemods/replace-use-form-state/.codemodrc.json new file mode 100644 index 0000000000000..9deaabf4c236f --- /dev/null +++ b/codemods/replace-use-form-state/.codemodrc.json @@ -0,0 +1,12 @@ +{ + "version": "1.0.0", + "name": "react/19/replace-use-form-state", + "private": false, + "engine": "jscodeshift", + "meta": { + "tags": ["react", "migration"] + }, + "applicability": { + "from": [["react", "<=", "18"]] + } +} diff --git a/codemods/replace-use-form-state/README.md b/codemods/replace-use-form-state/README.md new file mode 100644 index 0000000000000..58c4cc19521e4 --- /dev/null +++ b/codemods/replace-use-form-state/README.md @@ -0,0 +1,72 @@ +# Replace react useFormState with useActionState + +## Description + +This codemod will replace the usages of `useFormState()` to use `useActionState()`, introduced in React v19. + +## Examples + +### Before + +```ts +import { useFormState } from "react-dom"; + +async function increment(previousState, formData) { + return previousState + 1; +} + +function StatefulForm({}) { + const [state, formAction] = useFormState(increment, 0); + return ( +
+ {state} + +
+ ) +} +``` + +### After + +```ts +import { useActionState } from "react-dom"; + +async function increment(previousState, formData) { + return previousState + 1; +} + +function StatefulForm({}) { + const [state, formAction] = useActionState(increment, 0); + return ( +
+ {state} + +
+ ) +} +``` + + + +### Before + +```ts +import * as ReactDOM from "react-dom"; + +function StatefulForm({}) { + const [state, formAction] = ReactDOM.useFormState(increment, 0); + return
; +} +``` + +### After + +```ts +import * as ReactDOM from "react-dom"; + +function StatefulForm({}) { + const [state, formAction] = ReactDOM.useActionState(increment, 0); + return
; +} +``` + diff --git a/codemods/replace-use-form-state/__testfixtures__/fixture1.input.js b/codemods/replace-use-form-state/__testfixtures__/fixture1.input.js new file mode 100644 index 0000000000000..0ddc910f33d98 --- /dev/null +++ b/codemods/replace-use-form-state/__testfixtures__/fixture1.input.js @@ -0,0 +1,15 @@ +import { useFormState } from "react-dom"; + +async function increment(previousState, formData) { + return previousState + 1; +} + +function StatefulForm({}) { + const [state, formAction] = useFormState(increment, 0); + return ( +
+ {state} + +
+ ); +} diff --git a/codemods/replace-use-form-state/__testfixtures__/fixture1.output.js b/codemods/replace-use-form-state/__testfixtures__/fixture1.output.js new file mode 100644 index 0000000000000..14c66038f2fc9 --- /dev/null +++ b/codemods/replace-use-form-state/__testfixtures__/fixture1.output.js @@ -0,0 +1,15 @@ +import { useActionState } from "react-dom"; + +async function increment(previousState, formData) { + return previousState + 1; +} + +function StatefulForm({}) { + const [state, formAction] = useActionState(increment, 0); + return ( +
+ {state} + +
+ ); +} diff --git a/codemods/replace-use-form-state/__testfixtures__/fixture2.input.js b/codemods/replace-use-form-state/__testfixtures__/fixture2.input.js new file mode 100644 index 0000000000000..645ac55720dc9 --- /dev/null +++ b/codemods/replace-use-form-state/__testfixtures__/fixture2.input.js @@ -0,0 +1,6 @@ +import ReactDOM from "react-dom"; + +function StatefulForm({}) { + const [state, formAction] = ReactDOM.useFormState(increment, 0); + return
; +} diff --git a/codemods/replace-use-form-state/__testfixtures__/fixture2.output.js b/codemods/replace-use-form-state/__testfixtures__/fixture2.output.js new file mode 100644 index 0000000000000..d185a4e1bfcdd --- /dev/null +++ b/codemods/replace-use-form-state/__testfixtures__/fixture2.output.js @@ -0,0 +1,6 @@ +import ReactDOM from "react-dom"; + +function StatefulForm({}) { + const [state, formAction] = ReactDOM.useActionState(increment, 0); + return
; +} diff --git a/codemods/replace-use-form-state/__testfixtures__/fixture3.input.js b/codemods/replace-use-form-state/__testfixtures__/fixture3.input.js new file mode 100644 index 0000000000000..48968985d27ee --- /dev/null +++ b/codemods/replace-use-form-state/__testfixtures__/fixture3.input.js @@ -0,0 +1,6 @@ +import * as ReactDOM from "react-dom"; + +function StatefulForm({}) { + const [state, formAction] = ReactDOM.useFormState(increment, 0); + return
; +} diff --git a/codemods/replace-use-form-state/__testfixtures__/fixture3.output.js b/codemods/replace-use-form-state/__testfixtures__/fixture3.output.js new file mode 100644 index 0000000000000..1375f0adbfe8a --- /dev/null +++ b/codemods/replace-use-form-state/__testfixtures__/fixture3.output.js @@ -0,0 +1,6 @@ +import * as ReactDOM from "react-dom"; + +function StatefulForm({}) { + const [state, formAction] = ReactDOM.useActionState(increment, 0); + return
; +} diff --git a/codemods/replace-use-form-state/__testfixtures__/fixture4.input.js b/codemods/replace-use-form-state/__testfixtures__/fixture4.input.js new file mode 100644 index 0000000000000..a882243aa46a6 --- /dev/null +++ b/codemods/replace-use-form-state/__testfixtures__/fixture4.input.js @@ -0,0 +1,6 @@ +import { other } from "react-dom"; + +function StatefulForm({}) { + const otherResult = other(); + return
; +} diff --git a/codemods/replace-use-form-state/__testfixtures__/fixture4.output.js b/codemods/replace-use-form-state/__testfixtures__/fixture4.output.js new file mode 100644 index 0000000000000..a882243aa46a6 --- /dev/null +++ b/codemods/replace-use-form-state/__testfixtures__/fixture4.output.js @@ -0,0 +1,6 @@ +import { other } from "react-dom"; + +function StatefulForm({}) { + const otherResult = other(); + return
; +} diff --git a/codemods/replace-use-form-state/__testfixtures__/fixture5.input.js b/codemods/replace-use-form-state/__testfixtures__/fixture5.input.js new file mode 100644 index 0000000000000..cd95cc72bad1b --- /dev/null +++ b/codemods/replace-use-form-state/__testfixtures__/fixture5.input.js @@ -0,0 +1,8 @@ +import { createPortal, useFormState } from "react-dom"; + +function StatefulForm({}) { + const [state, formAction] = useFormState(increment, 0); + + createPortal(); + return
; +} diff --git a/codemods/replace-use-form-state/__testfixtures__/fixture5.output.js b/codemods/replace-use-form-state/__testfixtures__/fixture5.output.js new file mode 100644 index 0000000000000..14d336c1a79a0 --- /dev/null +++ b/codemods/replace-use-form-state/__testfixtures__/fixture5.output.js @@ -0,0 +1,8 @@ +import { createPortal, useActionState } from "react-dom"; + +function StatefulForm({}) { + const [state, formAction] = useActionState(increment, 0); + + createPortal(); + return
; +} diff --git a/codemods/replace-use-form-state/__testfixtures__/fixture6.input.js b/codemods/replace-use-form-state/__testfixtures__/fixture6.input.js new file mode 100644 index 0000000000000..afe791daa21f0 --- /dev/null +++ b/codemods/replace-use-form-state/__testfixtures__/fixture6.input.js @@ -0,0 +1,8 @@ +import { createPortal, useFormState as UFS } from "react-dom"; + +function StatefulForm({}) { + const [state, formAction] = UFS(increment, 0); + + createPortal(); + return
; +} diff --git a/codemods/replace-use-form-state/__testfixtures__/fixture6.output.js b/codemods/replace-use-form-state/__testfixtures__/fixture6.output.js new file mode 100644 index 0000000000000..f0772cb801ebd --- /dev/null +++ b/codemods/replace-use-form-state/__testfixtures__/fixture6.output.js @@ -0,0 +1,8 @@ +import { createPortal, useActionState as UFS } from "react-dom"; + +function StatefulForm({}) { + const [state, formAction] = UFS(increment, 0); + + createPortal(); + return
; +} diff --git a/codemods/replace-use-form-state/__tests__/test.ts b/codemods/replace-use-form-state/__tests__/test.ts new file mode 100644 index 0000000000000..49714c3bcf8a6 --- /dev/null +++ b/codemods/replace-use-form-state/__tests__/test.ts @@ -0,0 +1,172 @@ +import assert from 'node:assert/strict'; +import {readFile} from 'node:fs/promises'; +import {join} from 'node:path'; +import jscodeshift, {type API, type FileInfo} from 'jscodeshift'; +import transform from '../src/index.ts'; + +export const buildApi = (parser: string | undefined): API => ({ + j: parser ? jscodeshift.withParser(parser) : jscodeshift, + jscodeshift: parser ? jscodeshift.withParser(parser) : jscodeshift, + stats: () => { + console.error( + 'The stats function was called, which is not supported on purpose' + ); + }, + report: () => { + console.error( + 'The report function was called, which is not supported on purpose' + ); + }, +}); + +describe('react/19/replace-use-form-state: useFormState() -> useActionState()', () => { + it('should replace direct import with new useActionState import', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture1.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture1.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should replace ReactDOM.useFormState with ReactDOM.useActionState', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture2.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture2.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should properly replace star import', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture3.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture3.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should not replace other imports react-dom', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture4.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture4.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should add useActionState import in existing import clause if other imports are present', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture5.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture5.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); + + it('should correctly work with aliased imports', async () => { + const INPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture6.input.js'), + 'utf-8' + ); + const OUTPUT = await readFile( + join(__dirname, '..', '__testfixtures__/fixture6.output.js'), + 'utf-8' + ); + + const fileInfo: FileInfo = { + path: 'index.ts', + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi('js'), { + quote: 'single', + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ''), + OUTPUT.replace(/\W/gm, '') + ); + }); +}); diff --git a/codemods/replace-use-form-state/package.json b/codemods/replace-use-form-state/package.json new file mode 100644 index 0000000000000..e7cb152456f36 --- /dev/null +++ b/codemods/replace-use-form-state/package.json @@ -0,0 +1,13 @@ +{ + "name": "replace-use-form-state", + "version": "1.0.0", + "dependencies": {}, + "devDependencies": { + "@types/node": "^20.12.3", + "@types/jscodeshift": "^0.11.10", + "jscodeshift": "^0.15.1", + "typescript": "^5.2.2" + }, + "files": ["./README.md", "./.codemodrc.json", "./dist/index.cjs"], + "type": "module" +} diff --git a/codemods/replace-use-form-state/src/index.ts b/codemods/replace-use-form-state/src/index.ts new file mode 100644 index 0000000000000..29771a7d0fd4f --- /dev/null +++ b/codemods/replace-use-form-state/src/index.ts @@ -0,0 +1,95 @@ +import type { API, FileInfo, Options } from "jscodeshift"; + +export default function transform( + file: FileInfo, + api: API, + options?: Options, +): string | undefined { + const j = api.jscodeshift; + const root = j(file.source); + + // Get default import from react-dom + const defaultImportName = root + .find(j.ImportDeclaration, { + source: { value: "react-dom" }, + specifiers: [{ type: "ImportDefaultSpecifier" }], + }) + .paths() + .at(0) + ?.node.specifiers?.at(0)?.local?.name; + + // Get default import from test utils + const starImportName = root + .find(j.ImportDeclaration, { + source: { value: "react-dom" }, + specifiers: [{ type: "ImportNamespaceSpecifier" }], + }) + .paths() + .at(0) + ?.node.specifiers?.at(0)?.local?.name; + + const utilsCalleeName = defaultImportName ?? starImportName; + const utilsCalleeType: any = defaultImportName + ? "ImportDefaultSpecifier" + : "ImportNamespaceSpecifier"; + + // For usages like `import * as ReactDOM from 'react-dom'; ReactDOM.useFormState()` + const actAccessExpressions = root.find(j.MemberExpression, { + object: { name: utilsCalleeName }, + property: { name: "useFormState" }, + }); + + if (actAccessExpressions.length > 0) { + // React import + + actAccessExpressions.forEach((path) => { + j(path) + .find(j.Identifier, { name: "useFormState" }) + .paths() + .at(0) + ?.replace(j.identifier("useActionState")); + }); + } + + // For direct imports, such as `import { useFormState } from 'react-dom';` + const reactDOMImportCollection = root.find(j.ImportDeclaration, { + source: { value: "react-dom" }, + }); + + const reactDOMImportPath = reactDOMImportCollection.paths().at(0); + + if (!reactDOMImportPath) { + return root.toSource(); + } + + const specifier = reactDOMImportPath.node.specifiers?.find( + (s) => s.type === "ImportSpecifier" && s.imported.name === "useFormState", + ); + + if (!specifier || !j.ImportSpecifier.check(specifier)) { + return root.toSource(); + } + + const usedName = specifier.local?.name ?? specifier.imported.name; + + // Replace import name + reactDOMImportCollection + .find(j.ImportSpecifier, { imported: { name: "useFormState" } }) + .forEach((path) => { + path.replace( + j.importSpecifier( + j.identifier("useActionState"), + j.identifier(usedName), + ), + ); + }); + + // Means it's not aliased, so we also change identifier names, not only import + if (specifier?.local?.name === "useFormState") { + root.find(j.Identifier, { name: "useFormState" }).forEach((path) => { + path.replace(j.identifier("useActionState")); + }); + } + + return root.toSource(); +} diff --git a/codemods/replace-use-form-state/tsconfig.json b/codemods/replace-use-form-state/tsconfig.json new file mode 100644 index 0000000000000..2cda28bff22d3 --- /dev/null +++ b/codemods/replace-use-form-state/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "module": "NodeNext", + "skipLibCheck": true, + "strict": true, + "target": "ES6", + "allowJs": true, + "allowImportingTsExtensions": true, + "noEmit": true + }, + "include": [ + "./src/**/*.ts", + "./src/**/*.js", + "__tests__/**/*.ts", + "__tests__/**/*.js" + ], + "exclude": ["node_modules", "./dist/**/*"], + "ts-node": { + "transpileOnly": true + } +} diff --git a/codemods/use-context-hook/tsconfig.json b/codemods/use-context-hook/tsconfig.json index a30abbbea378c..5717e5c332cf3 100644 --- a/codemods/use-context-hook/tsconfig.json +++ b/codemods/use-context-hook/tsconfig.json @@ -8,13 +8,15 @@ "skipLibCheck": true, "strict": true, "target": "ES6", - "allowJs": true + "allowJs": true, + "allowImportingTsExtensions": true, + "noEmit": true }, "include": [ "./src/**/*.ts", "./src/**/*.js", - "./test/**/*.ts", - "./test/**/*.js" + "./__tests__/**/*.ts", + "./__tests__/**/*.js" ], "exclude": ["node_modules", "./dist/**/*"], "ts-node": { From c5a66fff43c18d1046e16d5e80eb8085b40f9e3d Mon Sep 17 00:00:00 2001 From: Sasha Mysak Date: Wed, 10 Apr 2024 10:49:03 +0200 Subject: [PATCH 05/11] fix: remove useCaseCategory field from configs --- codemods/remove-context-provider/.codemodrc.json | 3 +-- codemods/remove-forward-ref/.codemodrc.json | 3 +-- codemods/remove-memoization-hooks/.codemodrc.json | 3 +-- codemods/use-context-hook/.codemodrc.json | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/codemods/remove-context-provider/.codemodrc.json b/codemods/remove-context-provider/.codemodrc.json index 2e1c68d234d8c..366144215ae6f 100644 --- a/codemods/remove-context-provider/.codemodrc.json +++ b/codemods/remove-context-provider/.codemodrc.json @@ -4,8 +4,7 @@ "private": false, "engine": "jscodeshift", "meta": { - "tags": ["react"], - "useCaseCategory": "migration" + "tags": ["react", "migration"] }, "applicability": { "from": [["react", "<=", "^18.0.0"]] diff --git a/codemods/remove-forward-ref/.codemodrc.json b/codemods/remove-forward-ref/.codemodrc.json index 1506189110e2f..e4b8bcea814a0 100644 --- a/codemods/remove-forward-ref/.codemodrc.json +++ b/codemods/remove-forward-ref/.codemodrc.json @@ -5,8 +5,7 @@ "description": "Codemod to remove React.forwardRef function that will be deprecated in next major React release", "engine": "jscodeshift", "meta": { - "tags": ["react"], - "useCaseCategory": "migration" + "tags": ["react", "migration"] }, "applicability": { "from": [["react", "<=", "^18.0.0"]] diff --git a/codemods/remove-memoization-hooks/.codemodrc.json b/codemods/remove-memoization-hooks/.codemodrc.json index 45e8e5676cc22..2071e7ccf60eb 100644 --- a/codemods/remove-memoization-hooks/.codemodrc.json +++ b/codemods/remove-memoization-hooks/.codemodrc.json @@ -4,8 +4,7 @@ "private": false, "engine": "jscodeshift", "meta": { - "tags": ["react"], - "useCaseCategory": "migration" + "tags": ["react", "migration"] }, "applicability": { "from": [["react", "<=", "18"]] diff --git a/codemods/use-context-hook/.codemodrc.json b/codemods/use-context-hook/.codemodrc.json index 92bb5b08299b7..0640bf04ae492 100644 --- a/codemods/use-context-hook/.codemodrc.json +++ b/codemods/use-context-hook/.codemodrc.json @@ -4,8 +4,7 @@ "private": false, "engine": "jscodeshift", "meta": { - "tags": ["react"], - "useCaseCategory": "migration" + "tags": ["react", "migration"] }, "applicability": { "from": [["react", "<=", "18"]] From 9ce03507cd730bf87cd00abdee9425e75ac636b9 Mon Sep 17 00:00:00 2001 From: Mohab Sameh Date: Wed, 24 Apr 2024 20:32:14 +0200 Subject: [PATCH 06/11] add ref to arrow ref codemod --- .../ref-to-arrow-function-ref/.codemodrc.json | 13 +++ codemods/ref-to-arrow-function-ref/README.md | 14 +++ .../__testfixtures__/fixture1.input.tsx | 1 + .../__testfixtures__/fixture1.output.tsx | 5 ++ .../ref-to-arrow-function-ref/package.json | 19 ++++ .../ref-to-arrow-function-ref/src/index.ts | 88 +++++++++++++++++++ .../ref-to-arrow-function-ref/test/test.ts | 47 ++++++++++ .../ref-to-arrow-function-ref/tsconfig.json | 23 +++++ .../vitest.config.ts | 7 ++ 9 files changed, 217 insertions(+) create mode 100644 codemods/ref-to-arrow-function-ref/.codemodrc.json create mode 100644 codemods/ref-to-arrow-function-ref/README.md create mode 100644 codemods/ref-to-arrow-function-ref/__testfixtures__/fixture1.input.tsx create mode 100644 codemods/ref-to-arrow-function-ref/__testfixtures__/fixture1.output.tsx create mode 100644 codemods/ref-to-arrow-function-ref/package.json create mode 100644 codemods/ref-to-arrow-function-ref/src/index.ts create mode 100644 codemods/ref-to-arrow-function-ref/test/test.ts create mode 100644 codemods/ref-to-arrow-function-ref/tsconfig.json create mode 100644 codemods/ref-to-arrow-function-ref/vitest.config.ts diff --git a/codemods/ref-to-arrow-function-ref/.codemodrc.json b/codemods/ref-to-arrow-function-ref/.codemodrc.json new file mode 100644 index 0000000000000..08b37abc782a5 --- /dev/null +++ b/codemods/ref-to-arrow-function-ref/.codemodrc.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://codemod-utils.s3.us-west-1.amazonaws.com/configuration_schema.json", + "version": "1.0.1", + "private": false, + "name": "ref-to-arrow-function", + "engine": "jscodeshift", + "meta": { + "tags": ["react", "migration"] + }, + "applicability": { + "from": [["react", "<=", "18"]] + } +} diff --git a/codemods/ref-to-arrow-function-ref/README.md b/codemods/ref-to-arrow-function-ref/README.md new file mode 100644 index 0000000000000..3038b6e32533b --- /dev/null +++ b/codemods/ref-to-arrow-function-ref/README.md @@ -0,0 +1,14 @@ +This codemod migrates string refs (deprecated) to callback refs. + +## Before + +```ts +< div ref = 'refName' / > ; +``` + +## After + +```ts +< div ref = { ref => this.refs.refName = ref } +/> +``` diff --git a/codemods/ref-to-arrow-function-ref/__testfixtures__/fixture1.input.tsx b/codemods/ref-to-arrow-function-ref/__testfixtures__/fixture1.input.tsx new file mode 100644 index 0000000000000..0f45e5e119037 --- /dev/null +++ b/codemods/ref-to-arrow-function-ref/__testfixtures__/fixture1.input.tsx @@ -0,0 +1 @@ +
; diff --git a/codemods/ref-to-arrow-function-ref/__testfixtures__/fixture1.output.tsx b/codemods/ref-to-arrow-function-ref/__testfixtures__/fixture1.output.tsx new file mode 100644 index 0000000000000..0a6016a5ccb8f --- /dev/null +++ b/codemods/ref-to-arrow-function-ref/__testfixtures__/fixture1.output.tsx @@ -0,0 +1,5 @@ +
{ + this.refs.refName = ref; + }} +/>; diff --git a/codemods/ref-to-arrow-function-ref/package.json b/codemods/ref-to-arrow-function-ref/package.json new file mode 100644 index 0000000000000..a532b86afd5e6 --- /dev/null +++ b/codemods/ref-to-arrow-function-ref/package.json @@ -0,0 +1,19 @@ +{ + "name": "ref-to-arrow-function", + "author": "codemod", + "dependencies": {}, + "devDependencies": { + "@types/node": "20.9.0", + "typescript": "5.2.2", + "vitest": "^1.0.1", + "jscodeshift": "^0.15.1", + "@types/jscodeshift": "^0.11.10" + }, + "scripts": { + "test": "vitest run", + "test:watch": "vitest watch" + }, + "license": "MIT", + "files": ["README.md", ".codemodrc.json", "./dist/index.cjs"], + "type": "module" +} diff --git a/codemods/ref-to-arrow-function-ref/src/index.ts b/codemods/ref-to-arrow-function-ref/src/index.ts new file mode 100644 index 0000000000000..e760d6295c967 --- /dev/null +++ b/codemods/ref-to-arrow-function-ref/src/index.ts @@ -0,0 +1,88 @@ +import type { API, FileInfo, Options } from "jscodeshift"; +export default function transform( + file: FileInfo, + api: API, + options?: Options, +): string | undefined { + const j = api.jscodeshift; + const root = j(file.source); + + // Helper function to preserve comments + function replaceWithComments(path, newNode) { + // If the original node had comments, add them to the new node + if (path.node.comments) { + newNode.comments = path.node.comments; + } + + // Replace the node + j(path).replaceWith(newNode); + } + + // Find JSX elements with ref attribute + root + .find(j.JSXElement, { + openingElement: { + attributes: [ + { + type: "JSXAttribute", + name: { + type: "JSXIdentifier", + name: "ref", + }, + }, + ], + }, + }) + .forEach((path) => { + // Get the ref name + const refName = path.node.openingElement.attributes.find( + (attr) => attr.name.name === "ref", + ).value.value; + + // Create new ref attribute + const newRefAttr = j.jsxAttribute( + j.jsxIdentifier("ref"), + j.jsxExpressionContainer( + j.arrowFunctionExpression( + [j.jsxIdentifier("ref")], + j.blockStatement([ + j.expressionStatement( + j.assignmentExpression( + "=", + j.memberExpression( + j.memberExpression( + j.thisExpression(), + j.identifier("refs"), + ), + j.identifier(refName), + ), + j.identifier("ref"), + ), + ), + ]), + ), + ), + ); + + // Replace old ref attribute with new one + const newAttributes = path.node.openingElement.attributes.map((attr) => + attr.name.name === "ref" ? newRefAttr : attr, + ); + const newOpeningElement = j.jsxOpeningElement( + path.node.openingElement.name, + newAttributes, + path.node.openingElement.selfClosing, + ); + const newElement = j.jsxElement( + newOpeningElement, + path.node.closingElement, + path.node.children, + path.node.selfClosing, + ); + + // Replace old element with new one, preserving comments + replaceWithComments(path, newElement); + }); + + return root.toSource(); +} diff --git a/codemods/ref-to-arrow-function-ref/test/test.ts b/codemods/ref-to-arrow-function-ref/test/test.ts new file mode 100644 index 0000000000000..d3b1634a512a5 --- /dev/null +++ b/codemods/ref-to-arrow-function-ref/test/test.ts @@ -0,0 +1,47 @@ +import assert from "node:assert"; +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; +import jscodeshift, { type API } from "jscodeshift"; +import { describe, it } from "vitest"; +import transform from "../src/index.js"; + +const buildApi = (parser: string | undefined): API => ({ + j: parser ? jscodeshift.withParser(parser) : jscodeshift, + jscodeshift: parser ? jscodeshift.withParser(parser) : jscodeshift, + stats: () => { + console.error( + "The stats function was called, which is not supported on purpose", + ); + }, + report: () => { + console.error( + "The report function was called, which is not supported on purpose", + ); + }, +}); + +describe("react/19/ref-to-arrow-function", () => { + it("test #1", async () => { + const INPUT = await readFile( + join(__dirname, "..", "__testfixtures__/fixture1.input.tsx"), + "utf-8", + ); + const OUTPUT = await readFile( + join(__dirname, "..", "__testfixtures__/fixture1.output.tsx"), + "utf-8", + ); + + const actualOutput = transform( + { + path: "index.js", + source: INPUT, + }, + buildApi("tsx"), + ); + + assert.deepEqual( + actualOutput?.replace(/\s/gm, ""), + OUTPUT.replace(/\s/gm, ""), + ); + }); +}); diff --git a/codemods/ref-to-arrow-function-ref/tsconfig.json b/codemods/ref-to-arrow-function-ref/tsconfig.json new file mode 100644 index 0000000000000..817a56caa65f8 --- /dev/null +++ b/codemods/ref-to-arrow-function-ref/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "module": "NodeNext", + "skipLibCheck": true, + "strict": true, + "target": "ES6", + "allowJs": true + }, + "include": [ + "./src/**/*.ts", + "./src/**/*.js", + "./test/**/*.ts", + "./test/**/*.js" + ], + "exclude": ["node_modules", "./dist/**/*"], + "ts-node": { + "transpileOnly": true + } +} diff --git a/codemods/ref-to-arrow-function-ref/vitest.config.ts b/codemods/ref-to-arrow-function-ref/vitest.config.ts new file mode 100644 index 0000000000000..3f5723c83dbb3 --- /dev/null +++ b/codemods/ref-to-arrow-function-ref/vitest.config.ts @@ -0,0 +1,7 @@ +import { configDefaults, defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: [...configDefaults.include, "**/test/*.ts"], + }, +}); From 96644eeec0028f8a744a25309ab832b4b5ae1677 Mon Sep 17 00:00:00 2001 From: Mohab Sameh Date: Wed, 24 Apr 2024 20:32:36 +0200 Subject: [PATCH 07/11] add git meta --- codemods/ref-to-arrow-function-ref/.codemodrc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codemods/ref-to-arrow-function-ref/.codemodrc.json b/codemods/ref-to-arrow-function-ref/.codemodrc.json index 08b37abc782a5..ab0db84815ff4 100644 --- a/codemods/ref-to-arrow-function-ref/.codemodrc.json +++ b/codemods/ref-to-arrow-function-ref/.codemodrc.json @@ -5,7 +5,8 @@ "name": "ref-to-arrow-function", "engine": "jscodeshift", "meta": { - "tags": ["react", "migration"] + "tags": ["react", "migration"], + "git": "https://github.com/codemod-com/react/tree/codemods-v19/codemods/" }, "applicability": { "from": [["react", "<=", "18"]] From 245692d3c2a1056a01e11d40e6e35a69dfec0514 Mon Sep 17 00:00:00 2001 From: Mohab Sameh Date: Wed, 24 Apr 2024 20:35:51 +0200 Subject: [PATCH 08/11] fix github meta --- codemods/ref-to-arrow-function-ref/.codemodrc.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codemods/ref-to-arrow-function-ref/.codemodrc.json b/codemods/ref-to-arrow-function-ref/.codemodrc.json index ab0db84815ff4..724ce179917b6 100644 --- a/codemods/ref-to-arrow-function-ref/.codemodrc.json +++ b/codemods/ref-to-arrow-function-ref/.codemodrc.json @@ -1,12 +1,12 @@ { "$schema": "https://codemod-utils.s3.us-west-1.amazonaws.com/configuration_schema.json", - "version": "1.0.1", + "version": "1.0.2", "private": false, "name": "ref-to-arrow-function", "engine": "jscodeshift", "meta": { "tags": ["react", "migration"], - "git": "https://github.com/codemod-com/react/tree/codemods-v19/codemods/" + "git": "https://github.com/codemod-com/react/tree/codemods-v19/codemods/ref-to-arrow-function-ref" }, "applicability": { "from": [["react", "<=", "18"]] From c68f451aa68d628cef56202ff875f3006ff3871a Mon Sep 17 00:00:00 2001 From: Mohab Sameh Date: Wed, 24 Apr 2024 20:48:34 +0200 Subject: [PATCH 09/11] update react ref codemod --- codemods/ref-to-arrow-function-ref/.codemodrc.json | 5 +++-- .../ref-to-arrow-function-ref/{test => __tests__}/test.ts | 0 2 files changed, 3 insertions(+), 2 deletions(-) rename codemods/ref-to-arrow-function-ref/{test => __tests__}/test.ts (100%) diff --git a/codemods/ref-to-arrow-function-ref/.codemodrc.json b/codemods/ref-to-arrow-function-ref/.codemodrc.json index 724ce179917b6..2fdc59257f48a 100644 --- a/codemods/ref-to-arrow-function-ref/.codemodrc.json +++ b/codemods/ref-to-arrow-function-ref/.codemodrc.json @@ -2,13 +2,14 @@ "$schema": "https://codemod-utils.s3.us-west-1.amazonaws.com/configuration_schema.json", "version": "1.0.2", "private": false, - "name": "ref-to-arrow-function", + "name": "react/19/ref-to-arrow-function", "engine": "jscodeshift", "meta": { "tags": ["react", "migration"], "git": "https://github.com/codemod-com/react/tree/codemods-v19/codemods/ref-to-arrow-function-ref" }, "applicability": { - "from": [["react", "<=", "18"]] + "from": [["react", "<=", "18"]], + "to": [["react", "=", "19"]] } } diff --git a/codemods/ref-to-arrow-function-ref/test/test.ts b/codemods/ref-to-arrow-function-ref/__tests__/test.ts similarity index 100% rename from codemods/ref-to-arrow-function-ref/test/test.ts rename to codemods/ref-to-arrow-function-ref/__tests__/test.ts From 05fe92bcee21bd789ab000b1cfec2ac331742f26 Mon Sep 17 00:00:00 2001 From: Mohab Sameh Date: Wed, 24 Apr 2024 20:49:31 +0200 Subject: [PATCH 10/11] bump codemod version --- codemods/ref-to-arrow-function-ref/.codemodrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codemods/ref-to-arrow-function-ref/.codemodrc.json b/codemods/ref-to-arrow-function-ref/.codemodrc.json index 2fdc59257f48a..b8876e28e65e6 100644 --- a/codemods/ref-to-arrow-function-ref/.codemodrc.json +++ b/codemods/ref-to-arrow-function-ref/.codemodrc.json @@ -1,6 +1,6 @@ { "$schema": "https://codemod-utils.s3.us-west-1.amazonaws.com/configuration_schema.json", - "version": "1.0.2", + "version": "1.0.3", "private": false, "name": "react/19/ref-to-arrow-function", "engine": "jscodeshift", From 42cd890ef6c6532a77a987e75cd242f61baf82ce Mon Sep 17 00:00:00 2001 From: Aleksy Rybicki Date: Thu, 25 Apr 2024 20:26:39 +0100 Subject: [PATCH 11/11] Fixed ai issues --- .../ref-to-arrow-function-ref/src/index.ts | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/codemods/ref-to-arrow-function-ref/src/index.ts b/codemods/ref-to-arrow-function-ref/src/index.ts index e760d6295c967..cb49309041311 100644 --- a/codemods/ref-to-arrow-function-ref/src/index.ts +++ b/codemods/ref-to-arrow-function-ref/src/index.ts @@ -20,24 +20,17 @@ export default function transform( // Find JSX elements with ref attribute root - .find(j.JSXElement, { - openingElement: { - attributes: [ - { - type: "JSXAttribute", - name: { - type: "JSXIdentifier", - name: "ref", - }, - }, - ], - }, - }) + // Find JSX elements with a ref attribute + .find(j.JSXElement) .forEach((path) => { // Get the ref name const refName = path.node.openingElement.attributes.find( - (attr) => attr.name.name === "ref", - ).value.value; + (attr) => attr?.name?.name === "ref", + )?.value?.value; + + if (typeof refName !== "string") { + return; + } // Create new ref attribute const newRefAttr = j.jsxAttribute( @@ -66,7 +59,7 @@ export default function transform( // Replace old ref attribute with new one const newAttributes = path.node.openingElement.attributes.map((attr) => - attr.name.name === "ref" ? newRefAttr : attr, + attr?.name?.name === "ref" ? newRefAttr : attr, ); const newOpeningElement = j.jsxOpeningElement( path.node.openingElement.name,