diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6b15d2..9489fee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,8 +5,10 @@ on: pull_request: jobs: - docs-and-lint: + checks: runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 @@ -17,32 +19,5 @@ jobs: node-version: 20 cache: pnpm - run: pnpm install --frozen-lockfile - - run: pnpm run lint - - active: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - with: - version: 9.14.2 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: pnpm - - run: pnpm install --frozen-lockfile - - run: pnpm run test:active - - run: pnpm run check-types:active - - legacy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - with: - version: 9.14.2 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: pnpm - - run: pnpm run test:legacy + - run: pnpm run test + - run: pnpm run check-types diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0e6af29..3931051 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,60 +1,44 @@ # Contributing -Thanks for your interest in contributing to react-codemod! +This repository is a pnpm workspace of JSSG codemods under `codemods/*`. -## Development setup +## Setup ```bash -# Install dependencies pnpm install - -# Run all tests -pnpm test - -# Type-check all codemods -pnpm check-types ``` -## Making changes - -1. Create a branch from `main`. -2. Make your changes and add or update tests. -3. Run `pnpm test` and `pnpm check-types` to verify everything passes. -4. Add a changeset (see below). -5. Open a pull request. +Required local checks before opening a pull request: -## Adding a changeset +```bash +pnpm run test +pnpm run check-types +``` -This repo uses [Changesets](https://github.com/changesets/changesets) for versioning and releases. Every PR that changes a codemod must include a changeset. +Or run the combined check: ```bash -pnpm changeset +pnpm run ci ``` -Follow the prompts to: -1. Select the affected codemod(s). -2. Choose the semver bump type — **patch** for bug fixes, **minor** for new features, **major** for breaking changes. -3. Write a short summary of the change. +## Adding or updating codemods -This creates a markdown file in `.changeset/` that should be committed with your PR. +- Keep each codemod self-contained under `codemods//`. +- Update the codemod package README when behavior or usage changes. +- Add or extend snapshot fixtures under the codemod’s `tests/` directory for every behavioral change. -## Release workflow +## Versioning and publishing -1. Merge a PR with one or more changesets into `main`. -2. CI automatically opens a **Version Packages** PR that bumps versions in `package.json` and `codemod.yaml`. -3. Merge the version PR — git tags are created and the updated codemods are published to the Codemod registry. +This repo uses Changesets to version codemod packages. -## Adding a new codemod +To prepare a release entry for your change: -Each codemod lives in its own directory under `codemods/jssg/`: - -``` -codemods/jssg// - scripts/codemod.ts # Codemod logic (jssg / ast-grep) - tests/ # Input/expected test fixtures - codemod.yaml # Codemod manifest - workflow.yaml # Execution workflow - package.json +```bash +pnpm changeset ``` -Use an existing codemod as a reference when creating a new one. +When release changesets land on `main`, GitHub Actions: + +1. opens or updates the release PR via Changesets +2. tags released codemod package versions as `@v` +3. publishes each tagged codemod through `codemod/publish-action@v1` diff --git a/LEGACY.md b/LEGACY.md deleted file mode 100644 index 730dcb0..0000000 --- a/LEGACY.md +++ /dev/null @@ -1,32 +0,0 @@ -# Legacy Codemods - -The following jscodeshift-based codemods from the original [`react-codemod`](https://github.com/reactjs/react-codemod) project are available under [`codemods/legacy/transforms/`](./codemods/legacy/transforms/). - -These codemods are preserved for compatibility and can be run directly with jscodeshift. - -## Catalog - -- [`create-element-to-jsx`](./codemods/legacy/transforms/create-element-to-jsx.js) — convert `React.createElement` calls to JSX -- [`error-boundaries`](./codemods/legacy/transforms/error-boundaries.js) — rename `unstable_handleError` to `componentDidCatch` -- [`findDOMNode`](./codemods/legacy/transforms/findDOMNode.js) — update `getDOMNode()` calls to `React.findDOMNode()` -- [`manual-bind-to-arrow`](./codemods/legacy/transforms/manual-bind-to-arrow.js) — convert manual function bindings to arrow functions -- [`pure-component`](./codemods/legacy/transforms/pure-component.js) — convert render-only class components to functional components -- [`pure-render-mixin`](./codemods/legacy/transforms/pure-render-mixin.js) — remove `PureRenderMixin` and inline `shouldComponentUpdate` -- [`React-DOM-to-react-dom-factories`](./codemods/legacy/transforms/React-DOM-to-react-dom-factories.js) — convert `React.DOM.div(...)` to `React.createElement('div', ...)` -- [`ReactNative-View-propTypes`](./codemods/legacy/transforms/ReactNative-View-propTypes.js) — replace `View.propTypes` with `ViewPropTypes` -- [`react-to-react-dom`](./codemods/legacy/transforms/react-to-react-dom.js) — update code for the `react` / `react-dom` package split -- [`remove-context-provider`](./codemods/legacy/transforms/remove-context-provider.ts) — convert `Context.Provider` elements to `Context` -- [`remove-forward-ref`](./codemods/legacy/transforms/remove-forward-ref.ts) — remove usages of `forwardRef` -- [`rename-unsafe-lifecycles`](./codemods/legacy/transforms/rename-unsafe-lifecycles.js) — add `UNSAFE_` prefix to deprecated lifecycle hooks -- [`sort-comp`](./codemods/legacy/transforms/sort-comp.js) — enforce React component method ordering -- [`update-react-imports`](./codemods/legacy/transforms/update-react-imports.js) — remove redundant React imports and convert to named imports -- [`class`](./codemods/legacy/transforms/class.js) — convert `React.createClass` calls to ES6 classes - -## Running - -From `codemods/legacy/`: - -```bash -pnpm install -pnpm test -``` diff --git a/PARITY_STATUS.md b/PARITY_STATUS.md index 328e542..902d30b 100644 --- a/PARITY_STATUS.md +++ b/PARITY_STATUS.md @@ -1,11 +1,12 @@ # Parity Status -Last updated: 2026-04-15 +Last updated: 2026-04-21 Status meanings: - `Certified`: replacement-grade confidence. Tests are green, public test posture is portable, and there are no known logic gaps versus the original jscodeshift codemod. May include safe extensions on edge cases the original did not handle, as long as original behavior remains unbroken. -- `Legacy`: available as a jscodeshift codemod under `codemods/legacy/`. Not yet ported to JSSG. +- `Fixture-Verified`: JSSG port exists in this branch, package fixture suites are green, and type checks pass. May include differential/error/warning tests, but replacement-grade real-repo parity has not yet been established to the same standard as `Certified`. +- `Deferred`: intentionally out of scope for now. Not ported to JSSG yet. ## JSSG Codemods @@ -17,23 +18,23 @@ Status meanings: | `replace-reactdom-render` | `replace-reactdom-render.ts` | `Certified` | Full fixture coverage plus multi-alias regression tests are green. | | `react-proptypes-to-prop-types` | `React-PropTypes-to-prop-types.js` | `Certified` | Full original fixture surface is green. No JSSG-specific rollout blocker found. | | `use-context-hook` | `use-context-hook.ts` | `Certified` | Full fixture coverage plus multi-import regression tests are green. | - -## Legacy Codemods - -These codemods are available as jscodeshift transforms under [`codemods/legacy/transforms/`](./codemods/legacy/transforms/): +| `create-element-to-jsx` | `create-element-to-jsx.js` | `Fixture-Verified` | Imported from `align-with-legacy-codemods` on 2026-04-20. 34 fixture cases plus error/differential checks are green. | +| `error-boundaries` | `error-boundaries.js` | `Fixture-Verified` | Imported from `align-with-legacy-codemods` on 2026-04-20. Package fixtures and type checks are green. | +| `find-dom-node` | `findDOMNode.js` | `Fixture-Verified` | Imported from `align-with-legacy-codemods` on 2026-04-20. Package fixtures and type checks are green. | +| `manual-bind-to-arrow` | `manual-bind-to-arrow.js` | `Fixture-Verified` | Imported from `align-with-legacy-codemods` on 2026-04-20. Package fixtures and type checks are green. | +| `pure-component` | `pure-component.js` | `Fixture-Verified` | Imported from `align-with-legacy-codemods` on 2026-04-20. 11 fixtures plus warning/differential checks are green. | +| `pure-render-mixin` | `pure-render-mixin.js` | `Fixture-Verified` | Imported from `align-with-legacy-codemods` on 2026-04-20. Package fixtures and type checks are green. | +| `react-dom-to-react-dom-factories` | `React-DOM-to-react-dom-factories.js` | `Fixture-Verified` | Imported from `align-with-legacy-codemods` on 2026-04-20. Package fixtures and type checks are green. | +| `react-native-view-prop-types` | `ReactNative-View-propTypes.js` | `Fixture-Verified` | Imported from `align-with-legacy-codemods` on 2026-04-20. Package fixtures and type checks are green. | +| `react-to-react-dom` | `react-to-react-dom.js` | `Fixture-Verified` | Imported from `align-with-legacy-codemods` on 2026-04-20. 14 fixtures plus error tests are green. | +| `remove-context-provider` | `remove-context-provider.ts` | `Fixture-Verified` | Imported from `align-with-legacy-codemods` on 2026-04-20. Package fixtures and type checks are green. | +| `remove-forward-ref` | `remove-forward-ref.ts` | `Fixture-Verified` | Imported from `align-with-legacy-codemods` on 2026-04-20. Package fixtures and type checks are green. | +| `rename-unsafe-lifecycles` | `rename-unsafe-lifecycles.js` | `Fixture-Verified` | Imported from `align-with-legacy-codemods` on 2026-04-20. Package fixtures and type checks are green. | +| `sort-comp` | `sort-comp.js` | `Fixture-Verified` | Imported from `align-with-legacy-codemods` on 2026-04-20. Package fixtures and type checks are green. | +| `update-react-imports` | `update-react-imports.js` | `Fixture-Verified` | Imported from `align-with-legacy-codemods` on 2026-04-20. 33 fixtures are green. | + +## Deferred Codemods + +These codemods are still deferred and do not have a JSSG port in this repo: - `class` -- `create-element-to-jsx` -- `error-boundaries` -- `findDOMNode` -- `manual-bind-to-arrow` -- `pure-component` -- `pure-render-mixin` -- `React-DOM-to-react-dom-factories` -- `ReactNative-View-propTypes` -- `react-to-react-dom` -- `remove-context-provider` -- `remove-forward-ref` -- `rename-unsafe-lifecycles` -- `sort-comp` -- `update-react-imports` diff --git a/README.md b/README.md index 0f21006..26af61a 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,11 @@ # React Codemods -This repository contains a collection of codemods to help update React apps. +This repository contains the public JSSG codemods for React migrations and cleanup work. All codemods are free and open source, with the source code available in this repository. ## Usage -We recommend using the [`codemod`](https://go.codemod.com/github) command for running codemods. - ```bash npx codemod --target ``` @@ -15,11 +13,11 @@ npx codemod --target - `` — name of the codemod (see available codemods below) - `` — files or directory to transform -Check [codemod docs](https://go.codemod.com/cli-docs) for the full list of available commands. +Check [Codemod docs](https://go.codemod.com/cli-docs) for the full list of available commands. ## Available Codemods -All React codemods are also available in the [Codemod Registry](https://go.codemod.com/react-codemods). +All React codemods are also available in the [Codemod Registry](https://app.codemod.com/registry?q=scope:@react-new). #### `react-19-migration-recipe` @@ -29,7 +27,7 @@ Runs all React 19 migration codemods in sequence. npx codemod @react-new/react-19-migration-recipe --target ``` -See [`react-19-migration-recipe`](./codemods/jssg/react-19-migration-recipe/) for details. +See [@react-new/react-19-migration-recipe](https://app.codemod.com/registry/@react-new/react-19-migration-recipe). #### `use-context-hook` @@ -39,7 +37,7 @@ Replaces usages of `React.useContext(...)` with `React.use(...)`. npx codemod @react-new/use-context-hook --target ``` -See [`use-context-hook`](./codemods/jssg/use-context-hook/) for details. +See [@react-new/use-context-hook](https://app.codemod.com/registry/@react-new/use-context-hook). #### `replace-act-import` @@ -49,7 +47,7 @@ Updates `act` import path from `react-dom/test-utils` to `react`. npx codemod @react-new/replace-act-import --target ``` -See [`replace-act-import`](./codemods/jssg/replace-act-import/) for details. +See [@react-new/replace-act-import](https://app.codemod.com/registry/@react-new/replace-act-import). #### `replace-string-ref` @@ -59,7 +57,7 @@ Replaces deprecated string refs with callback refs. npx codemod @react-new/replace-string-ref --target ``` -See [`replace-string-ref`](./codemods/jssg/replace-string-ref/) for details. +See [@react-new/replace-string-ref](https://app.codemod.com/registry/@react-new/replace-string-ref). #### `replace-use-form-state` @@ -69,7 +67,7 @@ Replaces usages of `useFormState()` with `useActionState()`. npx codemod @react-new/replace-use-form-state --target ``` -See [`replace-use-form-state`](./codemods/jssg/replace-use-form-state/) for details. +See [@react-new/replace-use-form-state](https://app.codemod.com/registry/@react-new/replace-use-form-state). #### `replace-reactdom-render` @@ -79,7 +77,7 @@ Replaces usages of `ReactDOM.render()` with `createRoot(node).render()`. npx codemod @react-new/replace-reactdom-render --target ``` -See [`replace-reactdom-render`](./codemods/jssg/replace-reactdom-render/) for details. +See [@react-new/replace-reactdom-render](https://app.codemod.com/registry/@react-new/replace-reactdom-render). #### `react-proptypes-to-prop-types` @@ -89,11 +87,24 @@ Replaces `React.PropTypes` references with the `prop-types` package and adds the npx codemod @react-new/react-proptypes-to-prop-types --target ``` -See [`react-proptypes-to-prop-types`](./codemods/jssg/react-proptypes-to-prop-types/) for details. - -#### Legacy Codemods - -Additional jscodeshift-based codemods from the original `react-codemod` project are available under [`codemods/legacy/`](./codemods/legacy/). See [LEGACY.md](./LEGACY.md) for the full catalog. +See [@react-new/react-proptypes-to-prop-types](https://app.codemod.com/registry/@react-new/react-proptypes-to-prop-types). + +#### Additional useful React codemods + +- [`create-element-to-jsx`](https://app.codemod.com/registry/@react-new/create-element-to-jsx) +- [`error-boundaries`](https://app.codemod.com/registry/@react-new/error-boundaries) +- [`find-dom-node`](https://app.codemod.com/registry/@react-new/find-dom-node) +- [`manual-bind-to-arrow`](https://app.codemod.com/registry/@react-new/manual-bind-to-arrow) +- [`pure-component`](https://app.codemod.com/registry/@react-new/pure-component) +- [`pure-render-mixin`](https://app.codemod.com/registry/@react-new/pure-render-mixin) +- [`react-dom-to-react-dom-factories`](https://app.codemod.com/registry/@react-new/react-dom-to-react-dom-factories) +- [`react-native-view-prop-types`](https://app.codemod.com/registry/@react-new/react-native-view-prop-types) +- [`react-to-react-dom`](https://app.codemod.com/registry/@react-new/react-to-react-dom) +- [`remove-context-provider`](https://app.codemod.com/registry/@react-new/remove-context-provider) +- [`remove-forward-ref`](https://app.codemod.com/registry/@react-new/remove-forward-ref) +- [`rename-unsafe-lifecycles`](https://app.codemod.com/registry/@react-new/rename-unsafe-lifecycles) +- [`sort-comp`](https://app.codemod.com/registry/@react-new/sort-comp) +- [`update-react-imports`](https://app.codemod.com/registry/@react-new/update-react-imports) ## Development @@ -114,16 +125,6 @@ Run type checking: pnpm run check-types ``` -Run legacy codemod tests: - -```bash -pnpm run test:legacy -``` - ## Support and Contributing -If you want to contribute, you're welcome to submit a pull request. - -## License - -react-codemod is [MIT licensed](./LICENSE). +If you want to contribute, see [CONTRIBUTING.md](./CONTRIBUTING.md). diff --git a/TESTING_PLAN.md b/TESTING_PLAN.md index 962d1e5..ae4944e 100644 --- a/TESTING_PLAN.md +++ b/TESTING_PLAN.md @@ -4,12 +4,24 @@ ## Executive Summary -We searched public GitHub repos for the exact code patterns each of our 6 JSSG codemods targets. Below are the recommended test repos, organized by codemod, with match counts and React version context. +We searched public GitHub repos for the exact code patterns each of our 6 React 19-targeted JSSG codemods targets. Below are the recommended test repos, organized by codemod, with match counts and React version context. **Key finding:** `useFormState` from `react-dom` has **zero real-world adoption**. No open-source repo imports `useFormState` from `react-dom`. The API was renamed to `useActionState` before it saw meaningful use. **Additional modern spot-check:** As of 2026-04-17, `calcom/cal.com` redirects to `calcom/cal.diy`. It is a useful React 18/19 TypeScript monorepo target for validating `use-context-hook` and `replace-act-import` on current code. +**Phase 2 import note:** On 2026-04-20, 14 additional JSSG codemods were brought forward from `align-with-legacy-codemods`. These codemods target older or niche migration surfaces, so their immediate validation strategy is fixture-first rather than open-source repo-first. Only `class` remains legacy-only. + +**Coverage audit (2026-04-21):** Current upstream `reactjs/react-codemod` `HEAD` is `5207d594fad6f8b39c51fd7edd2bcb51047dc872`. Its `transforms/` directory contains 21 transforms. This repo now has JSSG implementations for 20 of them; the only upstream transform without a JSSG counterpart here is `class`. + +**Additional rollout research (2026-04-21):** A second repo sweep found three especially useful follow-on candidates beyond the original four test repos: +- `salesforce/design-system-react` at `825de01` (React 17) remains a high-value second source for `replace-reactdom-render`, `replace-act-import`, and `use-context-hook`. +- `MetaMask/metamask-extension` at `9c3b57c` (React 17) is a strong `replace-act-import` validation target with 18 real test-file imports. +- `react-native-snap-carousel` at `9c39995` gives `react-native-view-prop-types` an exact real-world source surface across 4 files, including the tricky case where `ViewPropTypes` is already imported. +- `DataTurks` at `039d57e` gives `error-boundaries` an exact `unstable_handleError` source hit in production code. +- `airbnb/react-dates` at `b7bad38` is a useful class-heavy repo for generic class codemods such as `pure-component` and `sort-comp`, though it does not expose many exact legacy API hits. +- `rsuite` at `0b1482d` still has a large raw `ReactDOM.render` count, but current HEAD is heavily docs/example-driven, so it is a lower-quality source than `salesforce/design-system-react` for rollout decisions. + --- ## Recommended Test Repos @@ -166,6 +178,59 @@ For modern React 18/19 coverage, `calcom/cal.com` is a strong fourth repo even t --- +## Phase 2 — Imported Codemods + +The codemods below were imported into the current branch from `align-with-legacy-codemods` on 2026-04-20. Because many of them target legacy code patterns that are harder to find reliably in modern public repos, the first evaluation pass should rely on their curated fixture suites, differential tests, and targeted error/warning tests. + +| Codemod | Initial Evaluation Surface | Next Real-Repo Priority | +|---------|----------------------------|-------------------------| +| `create-element-to-jsx` | 34 fixtures + error/differential tests | High | +| `error-boundaries` | 2 fixtures | Medium | +| `find-dom-node` | 9 fixtures | High | +| `manual-bind-to-arrow` | 11 fixtures | Medium | +| `pure-component` | 11 fixtures + warning/differential tests | High | +| `pure-render-mixin` | 7 fixtures | Medium | +| `react-dom-to-react-dom-factories` | 10 fixtures | Low | +| `react-native-view-prop-types` | 12 fixtures | Low | +| `react-to-react-dom` | 14 fixtures + error tests | High | +| `remove-context-provider` | 7 fixtures | Medium | +| `remove-forward-ref` | 17 fixtures | Medium | +| `rename-unsafe-lifecycles` | 9 fixtures | High | +| `sort-comp` | 11 fixtures | Medium | +| `update-react-imports` | 33 fixtures | High | + +For these imported codemods, the recommended evaluation order is: + +1. Keep fixture suites green in this repo. +2. Run targeted side-by-side repo sampling for the highest-priority packages. +3. Promote individual codemods from fixture-verified to replacement-grade only after real-repo parity is demonstrated. + +Real-repo candidate matches already found in the current testing repos: + +| Codemod | Repo Candidate | Notes | +|---------|----------------|-------| +| `create-element-to-jsx` | `react-quickly` | Real `React.createElement(...)` source files in chapter examples | +| `manual-bind-to-arrow` | `react-quickly` | Constructor `.bind(this)` patterns in JSX source files | +| `find-dom-node` | `react-quickly` | Sparse source hits in spare-parts examples; many other matches are library internals | +| `react-dom-to-react-dom-factories` | `react-quickly` | Legacy `React.DOM.*` example app under bundled React examples | +| `rename-unsafe-lifecycles` | `nylas-mail` | Strong real-world usage across app source and internal packages | +| `remove-forward-ref` | `calcom/cal.diy` | Modern `forwardRef(...)` usage in app and UI packages | +| `remove-context-provider` | `calcom/cal.diy` | Many `Context.Provider` wrappers in source packages | +| `update-react-imports` | `youzan/zent`, `calcom/cal.diy` | Broad modern React import surface, especially in TS/TSX | + +Additional real-repo candidate matches confirmed on 2026-04-21: + +| Codemod | Repo Candidate | Notes | +|---------|----------------|-------| +| `error-boundaries` | `DataTurks` | Exact `unstable_handleError` hit in `bazaar/src/components/ErrorBoundary/ErrorBoundary.js` | +| `react-native-view-prop-types` | `react-native-snap-carousel` | Exact `View.propTypes` source hits in 4 files; includes existing `ViewPropTypes` import edge case | +| `pure-component` | `airbnb/react-dates` | 21 class-component files on current HEAD, mostly wrappers/examples and a few library classes | +| `sort-comp` | `airbnb/react-dates`, `salesforce/design-system-react` | Good class-heavy surfaces for behavior comparison, even though the pattern itself is structural rather than API-specific | +| `replace-reactdom-render` | `salesforce/design-system-react` | 316 real JS/JSX files under `components/`, much stronger than docs-heavy `rsuite` HEAD for rollout decisions | +| `replace-act-import` | `MetaMask/metamask-extension` | 18 exact imports under `ui/` tests | + +--- + ## Test Execution Plan ### For each codemod × repo pair: @@ -205,3 +270,4 @@ For modern React 18/19 coverage, `calcom/cal.com` is a strong fourth repo even t - `useFormState` testing is synthetic-only; recommend documenting this proactively for the React team - `useContext` is so widespread that any React 16.8+ repo works; no need for a dedicated test repo - `calcom/cal.com` redirected to `calcom/cal.diy` on GitHub by 2026-04-17; keep both names in documentation for discoverability +- Imported codemods from `align-with-legacy-codemods` should be evaluated fixture-first in this branch before expanding to repo-based parity work diff --git a/TESTING_REPORT.md b/TESTING_REPORT.md index 87889bf..378b133 100644 --- a/TESTING_REPORT.md +++ b/TESTING_REPORT.md @@ -7,9 +7,10 @@ | Item | Detail | |------|--------| | **JSSG codemods** | `@react-new/*` (v0.1.0, published to Codemod Registry; regression fixes pending republish) | -| **Legacy codemods** | `react/19/*` (jscodeshift, from Codemod Registry) | +| **Legacy codemods** | `react/19/*` (jscodeshift, from Codemod Registry) for published transforms; official `reactjs/react-codemod` checkout at `5207d594fad6f8b39c51fd7edd2bcb51047dc872` for unpublished legacy transforms | | **CLI** | `codemod@latest` with `--no-interactive` flag | | **Test repos** | youzan/zent (React 17, TS), azat-co/react-quickly (React ~15, JS/JSX), atlassian/react-beautiful-dnd (React 16.13, JS+Flow), calcom/cal.diy (redirect from calcom/cal.com as of 2026-04-17, React 18/19 monorepo, tested at `v6.2.0` / `1c193cc`) | +| **Phase 2 import verification** | On 2026-04-20, imported 14 additional JSSG codemods from `align-with-legacy-codemods` and verified them with `pnpm run test`, `pnpm run check-types`, and `pnpm run ci` on this branch | --- @@ -17,20 +18,117 @@ | Codemod | Verdict | JSSG Files | Legacy Files | Notes | |---------|---------|:----------:|:------------:|-------| -| `replace-reactdom-render` | **Perfect parity** | 4 | 4 | Fixed: now handles `unmountComponentAtNode` + correct indentation | -| `replace-act-import` | **JSSG wins** | **6** | 1 | JSSG transforms 6× more files on `react-beautiful-dnd`; `cal.com` `v6.2.0` is an additional 1-file parity spot-check | -| `use-context-hook` | **JSSG wins** | **30** | 29 | `youzan/zent` was byte-identical; `cal.com` adds 2 real call sites and avoids 1 unused-import false positive | +| `replace-reactdom-render` | **Safe but conservative** | 4 | 4 | `youzan/zent` remains clean; on `salesforce/design-system-react`, JSSG now skips unsafe helper patterns that rely on the return value of `ReactDOM.render(...)` | +| `replace-act-import` | **JSSG wins** | **18** | 18 | `react-beautiful-dnd` still shows the 6× coverage win; `MetaMask` adds an 18-file semantic-parity check | +| `use-context-hook` | **JSSG wins** | **30** | 29 | `youzan/zent` was byte-identical; `cal.com` adds 2 real call sites and avoids 1 unused-import false positive; `salesforce/design-system-react` adds a 6-file JS spot-check | | `replace-string-ref` | **JSSG wins** | **5** | 0 | Legacy skips `.jsx` files entirely | | `replace-use-form-state` | **Perfect parity** | 1 | 1 | Fixed: now moves import from `react-dom` to `react` | -| `react-proptypes-to-prop-types` | No comparison | 2 | — | No legacy counterpart on registry | - -**Bottom line**: 0 regressions, 3 areas where JSSG outperforms, 2 perfect parity, 1 unverifiable. +| `react-proptypes-to-prop-types` | **JSSG wins** | **135** | 109 | Official legacy transform is not on the registry, but local jscodeshift evaluation shows JSSG handles 26 additional real files that the upstream transform errors on | + +**Bottom line**: real-repo coverage is broader than before. The last confirmed functional regression class from this pass was in `replace-reactdom-render`, and it is now closed by conservatively skipping return-value-dependent helper patterns instead of rewriting them unsafely. The imported codemods also gained two stronger real-repo signals: `error-boundaries` now has exact-source parity on `DataTurks`, and `react-native-view-prop-types` now has a real-world safety win on `react-native-snap-carousel`. + +## Speed Benchmarks + +These timing numbers are **single-run wall-clock** measurements on this machine. They exclude repo copy/setup time and measure only the codemod command itself on the same target slices used for behavior evaluation. + +For JSSG, the timings below now use the local `#2106` CLI implementation from `fix/fix-performance-jssg` at commit `8fafc226`, executed via the local debug binary: + +- `/Users/mohabsameh/Downloads/codemod-fix-performance-jssg/target/debug/codemod` + +For legacy, the timings use local `jscodeshift`, with: + +- official upstream `reactjs/react-codemod` transform files for the current React 19 codemods and for `react-proptypes-to-prop-types` +- the local legacy snapshot under `codemods/legacy/transforms` for the imported codemods, because that is the implementation used in our parity work + +Overall result with the `#2106` JSSG binary on this machine: **JSSG was faster on 18 of 21 benchmarked codemod/repo pairs; legacy was faster on 3 of 21**. + +I also sanity-checked the two suspect cases against local source builds of: + +- `codemod-cli@1.7.10` (`88e4544c`) +- local `main` (`9d3d2616`) +- `fix/fix-performance-jssg` (`8fafc226`) + +On those apples-to-apples local-binary runs, `#2106` was faster than both `1.7.10` and `main` for: + +- `replace-string-ref__react-quickly` +- `use-context-hook__zent` + +### Speed — Current Codemods + +| Codemod | Repo Slice | Input Files | Changed Files (J/L) | JSSG | jscodeshift | Faster | Notes | +|---------|------------|:-----------:|:-------------------:|-----:|------------:|:------:|-------| +| `replace-reactdom-render` | `zent` `packages/zent/src` | 690 | 4 / 2 | 7.330s | 2.847s | Legacy | Legacy was faster but also errored on a TS file and transformed fewer files | +| `replace-reactdom-render` | `salesforce` render slice | 314 | 1 / 6 | 1.270s | 1.534s | JSSG | JSSG intentionally skips unsafe return-value helper patterns here | +| `replace-act-import` | `react-beautiful-dnd` `test/` | 300 | 6 / 2 | 1.686s | 1.880s | JSSG | Legacy was slower and transformed fewer files due parser failures | +| `replace-act-import` | `cal.diy` 1-file spot-check | 1 | 1 / 1 | 0.496s | 0.433s | Legacy | Tiny slice; startup cost dominates | +| `replace-act-import` | `MetaMask` matched tests | 18 | 18 / 18 | 0.561s | 1.216s | JSSG | Strong JSSG speed win at equal transformed-file count | +| `use-context-hook` | `zent` `packages/zent/src` | 690 | 47 / 47 | 1.520s | 3.765s | JSSG | Large-slice JSSG win with equal transformed-file count | +| `use-context-hook` | `cal.diy` matched source slice | 31 | 30 / 29 | 0.568s | 1.464s | JSSG | JSSG also transforms one additional real file | +| `use-context-hook` | `salesforce` matched source slice | 6 | 6 / 6 | 0.542s | 0.866s | JSSG | Even on the tiny slice, `#2106` is now faster | +| `replace-string-ref` | `react-quickly` full repo | 650 | 5 / 0 | 23.536s | 55.541s | JSSG | Still much faster than legacy in practice; `#2106` is also faster than local `1.7.10` and local `main` on this case | +| `react-proptypes-to-prop-types` | `react-quickly` authored slice | 5 | 5 / 2 | 0.637s | 0.840s | JSSG | JSSG is now faster here and still handles all 5 authored files | +| `react-proptypes-to-prop-types` | `nylas-mail` `.jsx` slice | 135 | 135 / 109 | 0.931s | 1.606s | JSSG | JSSG is faster and handles 26 more real files than official legacy | + +### Speed — Imported Codemods + +| Codemod | Repo Slice | Input Files | Changed Files (J/L) | JSSG | jscodeshift | Faster | Notes | +|---------|------------|:-----------:|:-------------------:|-----:|------------:|:------:|-------| +| `create-element-to-jsx` | `react-quickly` source slice | 18 | 1 / 1 | 0.746s | 1.031s | JSSG | Clear JSSG win | +| `manual-bind-to-arrow` | `react-quickly` bind slice | 13 | 13 / 13 | 0.535s | 0.858s | JSSG | `#2106` flips this row from legacy-faster to JSSG-faster | +| `find-dom-node` | `react-quickly` spare-parts slice | 15 | 0 / 0 | 1.282s | 4.054s | JSSG | No-op scan; JSSG is much faster | +| `error-boundaries` | `DataTurks` exact-source file | 1 | 1 / 1 | 0.465s | 0.522s | JSSG | Tiny slice, but `#2106` still edges legacy | +| `react-dom-to-react-dom-factories` | `react-quickly` jQuery Mobile example | 1 | 1 / 1 | 0.536s | 0.509s | Legacy | One of only three legacy-faster rows | +| `rename-unsafe-lifecycles` | `nylas-mail` app source slice | 45 | 45 / 45 | 0.738s | 1.212s | JSSG | Clean JSSG speed win at equal file count | +| `remove-forward-ref` | `cal.diy` matched source slice | 7 | 4 / 4 | 0.543s | 0.809s | JSSG | `#2106` flips this row from legacy-faster to JSSG-faster | +| `remove-context-provider` | `cal.diy` matched source slice | 24 | 22 / 22 | 0.497s | 1.074s | JSSG | Strong JSSG win | +| `react-native-view-prop-types` | `react-native-snap-carousel` source slice | 4 | 4 / 4 | 0.555s | 0.683s | JSSG | `#2106` flips this row from legacy-faster to JSSG-faster; JSSG is also safer on the real repo because it avoids duplicate-import invalid output | +| `update-react-imports` | `zent` TS/TSX slice | 305 | 4 / 1 | 0.823s | 1.437s | JSSG | Legacy is slower and transforms fewer files because of parser limitations | + +### Imported Codemods — Fixture Verification Summary + +| Codemod | Evaluation Type | Result | Notes | +|---------|-----------------|--------|-------| +| `create-element-to-jsx` | 34 fixtures + error/differential tests | **Green** | Strongest imported parity signal in this pass | +| `error-boundaries` | 2 fixtures | **Green** | Import landed cleanly; real-repo sampling still pending | +| `find-dom-node` | 9 fixtures | **Green** | Fixture suite and type checks are green | +| `manual-bind-to-arrow` | 12 fixtures | **Green** | Fixture suite, regression fixture, and type checks are green | +| `pure-component` | 11 fixtures + warning/differential tests | **Green** | Includes checked-in parity fixtures and warning behavior checks | +| `pure-render-mixin` | 7 fixtures | **Green** | Fixture suite and type checks are green | +| `react-dom-to-react-dom-factories` | 11 fixtures | **Green** | Fixture suite, nested-call regression fixture, and type checks are green | +| `react-native-view-prop-types` | 12 fixtures | **Green** | Fixture suite and type checks are green | +| `react-to-react-dom` | 14 fixtures + error tests | **Green** | Includes explicit error-path coverage | +| `remove-context-provider` | 7 fixtures | **Green** | Fixture suite and type checks are green | +| `remove-forward-ref` | 18 fixtures | **Green** | Fixture suite, generic-signature regression fixture, and type checks are green | +| `rename-unsafe-lifecycles` | 9 fixtures | **Green** | Fixture suite and type checks are green | +| `sort-comp` | 11 fixtures | **Green** | Fixture suite and type checks are green | +| `update-react-imports` | 33 fixtures | **Green** | Large imported fixture surface is green | + +A follow-up repo-based investigation on 2026-04-20 re-ran the sampled imported codemods and used normalized AST comparison (including JSX literal whitespace normalization) to separate printer drift from semantic drift. That pass confirmed that the suspected formatting-only disparities were indeed non-semantic, and it exposed three true JSSG gaps. All three were patched on this branch and re-evaluated against the same repo slices. + +#### Imported Codemods — Real Repo Sampling + +After the import, I checked whether the imported codemods have real use cases in the repos already referenced by the testing plan. They do. The first side-by-side sampling pass below used targeted source-only slices to avoid generated bundles and third-party vendored code. + +| Codemod | Repo Slice | Verdict | JSSG Files | Legacy Files | Notes | +|---------|------------|:-------:|:----------:|:------------:|-------| +| `create-element-to-jsx` | `react-quickly` source chapters (`ch03/ch05/ch09/ch10/ch11/ch17`) | Semantic parity | 1 | 1 | Both only transformed `ch17/node/email.js`; normalized ASTs match after JSX literal whitespace normalization | +| `manual-bind-to-arrow` | `react-quickly` source files with constructor binds | Semantic parity (fixed) | 13 | 13 | Fixed anonymous class-expression support and constructor-line cleanup; all 13 transformed files now normalize equal | +| `find-dom-node` | `react-quickly` spare-parts source hits | No actionable source hit | 0 | 0 | Filtered source hits were skipped by both transforms | +| `error-boundaries` | `DataTurks` error boundary component | Semantic parity | 1 | 1 | Exact `unstable_handleError` rename in production source; outputs normalize equal | +| `react-dom-to-react-dom-factories` | `react-quickly` jQuery Mobile example app | Semantic parity (fixed) | 1 | 1 | Fixed overlapping nested factory edits; the transformed file now normalizes equal end-to-end | +| `rename-unsafe-lifecycles` | `nylas-mail` app source + internal packages | Semantic parity | 45 | 45 | Same file set transformed; all 45 transformed files normalize equal | +| `remove-forward-ref` | `calcom/cal.diy` matched source files | JSSG ahead | 4 | 2 | Fixed dropped generic signature preservation; the 2 overlapping files now normalize equal and JSSG still handles 2 real generic/member-expression cases that legacy skips | +| `remove-context-provider` | `calcom/cal.diy` matched source files | Semantic parity | 22 | 22 | Same file set transformed; all 22 transformed files normalize equal | +| `react-native-view-prop-types` | `react-native-snap-carousel` source files | **JSSG safer** | 4 | 4 | JSSG now preserves valid existing `ViewPropTypes` imports; legacy still emits duplicate imports and becomes syntactically invalid in all 4 overlapping files | +| `update-react-imports` | `youzan/zent` TS/TSX source slice | Inconclusive on coverage; no semantic drift | 4 | 1 | Legacy still hits a parser error on the TS-heavy slice; the overlapping file normalizes equal and the 3 JSSG-only rewrites are whitespace-only no-ops | + +Key conclusion from the imported-codemod disparity investigation: after the 2026-04-20 fixes and the 2026-04-21 `react-native-view-prop-types` import-duplication fix, the sampled imported codemods no longer have any confirmed JSSG semantic regressions versus legacy. The remaining non-identical outputs are either printer drift with normalized-AST parity (`create-element-to-jsx`, `rename-unsafe-lifecycles`, `remove-context-provider`, `manual-bind-to-arrow`, `react-dom-to-react-dom-factories`) or intentional JSSG-only coverage differences (`remove-forward-ref`). `update-react-imports` still needs a cleaner comparison target if we want a fair file-coverage verdict beyond the legacy parser failure. --- ## Detailed Findings -### 1. `replace-reactdom-render` — **Perfect Parity** (Fixed) +### 1. `replace-reactdom-render` — **Parity on zent, Conservative Skip on salesforce** **Repo**: youzan/zent (`packages/zent/src/`) @@ -72,9 +170,34 @@ Example transformation (Notify.tsx): + ); ``` +#### Additional rollout check: `salesforce/design-system-react` (`825de01`, React 17) + +This second repo changed the verdict. + +| Metric | JSSG | Legacy | +|--------|:----:|:------:| +| Files transformed | 1 | 6 | +| Overlap files | 1 | 1 | +| Overlap semantic parity | ✅ | — | + +The `salesforce` repro exposed the real root cause: some helper files rely on the return value of `ReactDOM.render(...)`, typically via `return ReactDOM.render(...)` in test setup helpers. Rewriting those to `createRoot(...).render(...)` is not semantics-preserving because `root.render(...)` does not return the rendered instance. The legacy codemod rewrites those files anyway; that is not safe. + +Current local JSSG behavior is now intentionally conservative: +- direct statement-form `ReactDOM.render(...)` calls still transform +- files that use the `render(...)` return value are skipped entirely + +On `salesforce/design-system-react`, that means JSSG currently skips the five unsafe helper files below instead of performing an invalid rewrite: +- `components/input/__tests__/input.browser-test.jsx` +- `components/menu-dropdown/__tests__/dropdown.browser-test.jsx` +- `components/menu-picklist/__tests__/picklist-base.browser-test.jsx` +- `components/slider/__tests__/slider.browser-test.jsx` +- `components/textarea/__tests__/textarea.browser-test.jsx` + +The one remaining overlap file, `components/modal/trigger.jsx`, differs only by formatting/comment placement in the current comparison. + #### Recommendation -No action needed — regressions resolved. +Treat the current divergence as an intentional safety gap rather than a remaining regression. `youzan/zent` remains clean, and the `salesforce` repro is now handled by skipping unsafe return-value patterns instead of corrupting files. --- @@ -111,6 +234,10 @@ File transformed: `packages/ui/components/form/color-picker/colorpicker.test.tsx +import { act } from "react"; ``` +#### Additional rollout check: `MetaMask/metamask-extension` (`9c3b57c`, React 17) + +On current `MetaMask`, both codemods transform the same 18 files under `ui/`, and the overlapping outputs are semantically equal. This is a useful complement to `react-beautiful-dnd` because it exercises a modern TS-heavy test tree without changing the verdict: JSSG is still safe here, and the previous `react-beautiful-dnd` coverage win still stands. + #### Recommendation No action needed — JSSG is strictly better here. @@ -176,9 +303,13 @@ The legacy-only file does **not** contain a `useContext(...)` call; it only had +import { createContext, use, useEffect, useMemo, useState } from "react"; ``` +#### Additional rollout check: `salesforce/design-system-react` (`825de01`, React 17) + +On the 6 real `useContext(...)` files under `components/`, both codemods transform the same file set. Four outputs normalize equal directly. The remaining two inspected diffs in `components/data-table/cell.jsx` and `components/data-table/private/row.jsx` are wrapper-parenthesis / indentation drift only; I did not find a semantic behavior change there. + #### Recommendation -No action needed — zent remains byte-identical parity, and cal.com shows a safe modern-repo improvement. +No action needed — zent remains byte-identical parity, cal.com shows a safe modern-repo improvement, and salesforce adds a small JS-side parity spot-check. --- @@ -252,26 +383,130 @@ No action needed — regressions resolved. --- -### 6. `react-proptypes-to-prop-types` — **No Legacy Comparison** +### 6. `react-proptypes-to-prop-types` — **JSSG Outperforms Official Legacy jscodeshift** + +The legacy counterpart (`React-PropTypes-to-prop-types`) is **not published on the Codemod Registry**, but it does exist in the upstream `reactjs/react-codemod` repo. For this comparison I ran the official transform directly with local `jscodeshift` from a checkout at commit `5207d594fad6f8b39c51fd7edd2bcb51047dc872`. + +#### Repo slice 1: `azat-co/react-quickly` authored source files + +To avoid vendored React bundles, I compared an authored slice containing the real source hits: -**Repo**: azat-co/react-quickly (`ch13/`) +- `ch13/router/jsx/content.jsx` +- `spare-parts/ch08-es5/prop-types/script.jsx` +- `spare-parts/ch11-old/router/react-router-active-component.js` +- `spare-parts/tooltip-logger (mixin)/jsx/button.jsx` +- `spare-parts/tooltip-logger (mixin)/js/button.js` -The legacy counterpart (`React-PropTypes-to-prop-types`) is **not published on the Codemod Registry**, so no direct comparison is possible. +| Metric | JSSG | Official legacy | +|--------|:----:|:---------------:| +| Files transformed | **5** | 2 | +| Legacy errors | — | 3 | +| Common-file semantic parity | **2/2** | — | -The JSSG codemod works correctly on the 2 files tested: +The overlapping 2 files are semantically equivalent after normalization. + +The 3 JSSG-only files are real authored source files that the official legacy transform errors on with `No PropTypes import found!`. The reason is clear from the inputs: those files use `React.PropTypes` with global `React`, so the upstream codemod cannot infer where to insert the `prop-types` import/require. JSSG handles them cleanly. + +Representative JSSG-only example: ```diff -+const PropTypes = require('prop-types'); ++var PropTypes = require('prop-types'); ... --React.PropTypes.object.isRequired -+PropTypes.object.isRequired +- handler: React.PropTypes.func.isRequired, +- title: React.PropTypes.string, ++ handler: PropTypes.func.isRequired, ++ title: PropTypes.string, +``` + +#### Repo slice 2: `nylas/nylas-mail` `.jsx` authored files + +For a larger production slice, I compared all `.jsx` files under: + +- `packages/client-app/src` +- `packages/client-sync/src` +- `packages/client-app/internal_packages` +- `packages/client-app/static` + +that still reference `React.PropTypes`. + +| Metric | JSSG | Official legacy | +|--------|:----:|:---------------:| +| Files transformed | **135** | 109 | +| Legacy errors | — | 26 | +| Common-file overlap | 109 | 109 | +| Common-file semantic parity | **109/109** after inspection | — | + +The official legacy transform fails on 26 real files, mostly with `No PropTypes import found!`. A representative example is `packages/client-app/src/components/code-snippet.jsx`, which imports `React` from `nylas-exports` instead of from `react` directly: + +```js +import {React} from 'nylas-exports'; +... +CodeSnippet.propTypes = { + intro: React.PropTypes.string, +} +``` + +JSSG handles this case and emits valid output: + +```diff ++import PropTypes from 'prop-types'; +... +- intro: React.PropTypes.string, ++ intro: PropTypes.string, ``` -It intelligently uses `require()` syntax matching the file's existing module system (CommonJS). +The only non-identical overlap files I found on manual inspection were: + +- `packages/client-app/src/components/nylas-calendar/calendar-toggles.jsx` +- `packages/client-app/src/components/nylas-calendar/week-view.jsx` + +Those differences are import/comment formatting drift only; I did not find a transformation-logic difference there. + +#### Follow-up one-by-one validation of JSSG-only files + +After the repo comparison, I validated every JSSG-only transformed file individually. + +- `react-quickly`: all 3 JSSG-only files contain real `React.PropTypes` member expressions in authored source, parse cleanly after transformation, and correctly receive a CommonJS `prop-types` binding (`var`/`const require(...)`) matching the file style. +- `nylas-mail`: all 26 JSSG-only files contain real `React.PropTypes` member expressions and now parse cleanly after transformation. + +This pass did uncover one real JSSG issue on current-branch output: when inserting a new `import PropTypes from 'prop-types';` after the last existing import, the codemod could glue the new import onto the previous import line in some files (for example `packages/client-app/src/components/dropdown-menu.jsx`). I fixed that insertion offset and added a regression fixture for the `nylas-exports` import shape. After rerunning the repo slice, all 26 JSSG-only `nylas-mail` files parse successfully and retain zero `React.PropTypes` references. #### Recommendation -Consider publishing the legacy transform to the registry to enable future comparison, or treat the JSSG version as the canonical implementation. +Treat `react-proptypes-to-prop-types` as directly compared now. The official upstream jscodeshift transform is more brittle on real repos than JSSG, and JSSG is the stronger implementation on both `react-quickly` and `nylas-mail`. + +--- + +### 7. Imported Codemods — **Ported into This Branch and Verified** + +The following codemods were originally ported to JSSG on `align-with-legacy-codemods` and imported into the current branch on April 20, 2026, while preserving the six newer superseding codemods already present here: + +- `create-element-to-jsx` +- `error-boundaries` +- `find-dom-node` +- `manual-bind-to-arrow` +- `pure-component` +- `pure-render-mixin` +- `react-dom-to-react-dom-factories` +- `react-native-view-prop-types` +- `react-to-react-dom` +- `remove-context-provider` +- `remove-forward-ref` +- `rename-unsafe-lifecycles` +- `sort-comp` +- `update-react-imports` + +Post-import verification on this branch: + +- `pnpm run test` ✅ +- `pnpm run check-types` ✅ +- `pnpm run ci` ✅ + +Interpretation: + +- The branch now carries 20 active JSSG codemods under `codemods/`. +- The imported 14 codemods are fixture-verified, not yet real-repo certified. +- `class` is the only codemod still legacy-only. --- @@ -286,12 +521,21 @@ All regressions found during initial testing have been fixed and retested. | 1 | `replace-reactdom-render` | Missing `unmountComponentAtNode()` pattern | ✅ Added member + named import matching for `unmountComponentAtNode` → `createRoot().unmount()` | | 2 | `replace-reactdom-render` | Indentation bugs (byte offset vs char index with non-ASCII) | ✅ Rewrote `getIndent()` to use line-based approach; added `reindentText()` for multi-line JSX | | 3 | `replace-use-form-state` | Import source not changed from `react-dom` to `react` | ✅ Rewrote import handling: direct node replacement with source splitting, quote preservation, alias support | +| 4 | `manual-bind-to-arrow` | Missed anonymous `class` expressions assigned to `module.exports`, so `react-quickly/ch13/naive-router/jsx/router.jsx` was skipped | ✅ Expanded class lookup to cover class expressions and fixed constructor-line deletion so the remaining constructor body stays well-formed | +| 5 | `remove-forward-ref` | Rebuilt function-expression wrappers dropped generic type parameters (and could also drop return-type syntax) in real code such as `FormActions.tsx` | ✅ Preserved the original signature prefix/suffix around rewritten params and added a generic-signature regression fixture | +| 6 | `react-dom-to-react-dom-factories` | Nested `React.DOM.*` replacements were lost because the transform emitted overlapping outer/inner edits | ✅ Rewrote only top-level matches and recursively transformed nested factory calls inside their argument subtrees; added a nested-call regression fixture | +| 7 | `react-native-view-prop-types` | Duplicate `ViewPropTypes` imports when the file already imported `ViewPropTypes`, producing invalid output on `react-native-snap-carousel` | ✅ Reused the existing `ViewPropTypes` binding instead of inserting another import/specifier; added a real-world regression fixture | +| 8 | `replace-reactdom-render` | Real-world helper files used `return ReactDOM.render(...)`; rewriting them changed semantics and, earlier in investigation, could corrupt surrounding code | ✅ Added safety guards and regression fixtures so the codemod now skips return-value-dependent render patterns instead of rewriting them unsafely | +| 9 | `react-proptypes-to-prop-types` | In some import-style files, the inserted `prop-types` import could be glued onto the previous import line, producing invalid syntax | ✅ Insert after the full previous import statement (including its line break) and added a `nylas-exports` regression fixture | ### Remaining | Codemod | Status | |---------|--------| -| `replace-act-import` | No action needed — JSSG outperforms legacy (6× coverage) | -| `use-context-hook` | No action needed — zent is byte-identical and cal.com shows a safe extension (30 files vs 29, with 28 overlapping diffs identical) | +| `replace-reactdom-render` | No remaining confirmed functional regression, but an intentional coverage gap remains: return-value-dependent helper patterns are skipped for safety instead of being rewritten like legacy | +| `replace-act-import` | No action needed — JSSG still wins overall, and `MetaMask` adds an 18-file semantic-parity check | +| `use-context-hook` | No action needed — zent is byte-identical, cal.com shows a safe extension, and salesforce adds a 6-file JS-side parity spot-check | | `replace-string-ref` | No action needed — JSSG outperforms legacy (handles `.jsx` files) | -| `react-proptypes-to-prop-types` | No action needed — works correctly; no legacy to compare against | +| `react-proptypes-to-prop-types` | No action needed — official upstream jscodeshift comparison now exists, and JSSG is stronger on both sampled repo slices | +| Imported 14 codemods | Branch integration is green. The sampled imported codemods now have stronger repo-based evidence: `error-boundaries` has exact-source parity on `DataTurks`, `react-native-view-prop-types` is safer than legacy on `react-native-snap-carousel`, and `update-react-imports` still needs a cleaner comparison target beyond the current legacy parser failure | +| `class` | Still legacy-only — no JSSG port exists on this branch yet | diff --git a/codemods/create-element-to-jsx/README.md b/codemods/create-element-to-jsx/README.md new file mode 100644 index 0000000..0287553 --- /dev/null +++ b/codemods/create-element-to-jsx/README.md @@ -0,0 +1,9 @@ +# create-element-to-jsx + +Convert `React.createElement(...)` calls to JSX. + +## Usage + +```bash +npx codemod @react-new/create-element-to-jsx --target +``` diff --git a/codemods/create-element-to-jsx/codemod.yaml b/codemods/create-element-to-jsx/codemod.yaml new file mode 100644 index 0000000..ecc6c7e --- /dev/null +++ b/codemods/create-element-to-jsx/codemod.yaml @@ -0,0 +1,19 @@ +schema_version: "1.0" + +name: "@react-new/create-element-to-jsx" +version: "0.1.1" +description: "Convert React.createElement calls to JSX" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + +targets: + languages: ["tsx"] + +keywords: ["React", "JSX"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/create-element-to-jsx/package.json b/codemods/create-element-to-jsx/package.json new file mode 100644 index 0000000..e34f973 --- /dev/null +++ b/codemods/create-element-to-jsx/package.json @@ -0,0 +1,15 @@ +{ + "name": "@react-new/create-element-to-jsx", + "version": "0.1.1", + "description": "Convert React.createElement calls to JSX", + "type": "module", + "scripts": { + "test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts && node --test ./scripts/error-tests.mjs ./scripts/differential-tests.mjs", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@codemod.com/jssg-types": "latest", + "@types/node": "latest", + "typescript": "latest" + } +} diff --git a/codemods/create-element-to-jsx/scripts/codemod.ts b/codemods/create-element-to-jsx/scripts/codemod.ts new file mode 100644 index 0000000..458ea1c --- /dev/null +++ b/codemods/create-element-to-jsx/scripts/codemod.ts @@ -0,0 +1,1277 @@ +import type { Transform, Edit, SgNode } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { useMetricAtom } from "codemod:metrics"; + +type Conversion = { + code: string; + count: number; + hasComments: boolean; +}; + +type TagDescriptor = { + text: string; + identifierComments: string[]; +}; + +type AttrDescriptor = { + leadingBlocks: string[]; + inline: string; + hasComments: boolean; +}; + +type RenderedElement = { + core: string; + count: number; + leadingComments: string[]; + trailingComments: string[]; + hasComments: boolean; + expressionTrailingComments?: string[]; +}; + +type ArgumentLayout = { + args: SgNode[]; + boundaryComments: SgNode[][]; +}; + +const REACT_MODULES = new Set(["React", "react", "react/addons", "react-native"]); + +function metricFile(filename: string): string { + const cwd = process.cwd() + "/"; + return filename.startsWith(cwd) ? filename.slice(cwd.length) : filename; +} + +function lineIndent(source: string, index: number): string { + let lineStart = index; + while (lineStart > 0 && source[lineStart - 1] !== "\n") { + lineStart--; + } + + let end = lineStart; + while (end < source.length && (source[end] === " " || source[end] === "\t")) { + end++; + } + + return source.slice(lineStart, end); +} + +function indentAll(text: string, indent: string): string { + return text + .split("\n") + .map((line) => `${indent}${line}`) + .join("\n"); +} + +function prefixFirstLine(text: string, prefix: string): string { + const newline = text.indexOf("\n"); + if (newline === -1) { + return `${prefix}${text}`; + } + + return `${prefix}${text.slice(0, newline)}${text.slice(newline)}`; +} + +function startsLowercase(value: string): boolean { + const first = value[0]; + return first !== undefined && first >= "a" && first <= "z"; +} + +function encodeJSXTextValue(value: string): string { + return value.replaceAll("<", "<").replaceAll(">", ">"); +} + +function stringValue(node: SgNode): string { + if (node.kind() !== "string") { + throw new Error(`Expected string node, got "${node.kind()}"`); + } + + const evaluated = Function(`"use strict"; return (${node.text()});`)() as unknown; + if (typeof evaluated !== "string") { + throw new Error(`Expected string literal, got "${typeof evaluated}"`); + } + + return evaluated; +} + +function isReactModuleName(value: string | null): boolean { + return value !== null && REACT_MODULES.has(value); +} + +function stringNodeFrom(node: SgNode): SgNode | null { + if (node.kind() === "string") { + return node; + } + + return node.find({ rule: { kind: "string" } }); +} + +function requireSource(call: SgNode): string | null { + const callee = call.field("function"); + if (!callee || callee.kind() !== "identifier" || callee.text() !== "require") { + return null; + } + + const args = callArguments(call); + const firstArg = args[0]; + if (!firstArg || firstArg.kind() !== "string") { + return null; + } + + return stringValue(firstArg); +} + +function importSource(node: SgNode): string | null { + const sourceNode = node.field("source") ?? stringNodeFrom(node); + if (!sourceNode || sourceNode.kind() !== "string") { + return null; + } + + return stringValue(sourceNode); +} + +function hasReact(rootNode: SgNode): boolean { + for (const importNode of rootNode.findAll({ rule: { kind: "import_statement" } })) { + if (isReactModuleName(importSource(importNode))) { + return true; + } + } + + for (const call of rootNode.findAll({ rule: { kind: "call_expression" } })) { + if (isReactModuleName(requireSource(call))) { + return true; + } + } + + return false; +} + +function callArguments(call: SgNode): SgNode[] { + const args = call.field("arguments"); + if (!args) { + return []; + } + + return args.children().filter((child) => child.isNamed() && child.kind() !== "comment"); +} + +function isReactCreateElementCall(node: SgNode): boolean { + if (node.kind() !== "call_expression") { + return false; + } + + const callee = node.field("function"); + if (!callee || callee.kind() !== "member_expression") { + return false; + } + + const object = callee.field("object"); + const property = callee.field("property"); + + return object?.kind() === "identifier" && + object.text() === "React" && + property?.kind() === "property_identifier" && + property.text() === "createElement"; +} + +function closestCreateElementAncestor(node: SgNode): SgNode | null { + for (const ancestor of node.ancestors()) { + if (isReactCreateElementCall(ancestor)) { + return ancestor; + } + } + + return null; +} + +function ownCommentTexts(call: SgNode): string[] { + return call.findAll({ rule: { kind: "comment" } }) + .filter((comment) => closestCreateElementAncestor(comment)?.id() === call.id()) + .sort((left, right) => left.range().start.index - right.range().start.index) + .map((comment) => comment.text()); +} + +function ownCommentNodes(call: SgNode): SgNode[] { + return call.findAll({ rule: { kind: "comment" } }) + .filter((comment) => closestCreateElementAncestor(comment)?.id() === call.id()) + .sort((left, right) => left.range().start.index - right.range().start.index); +} + +function directCommentNodes(node: SgNode | null): SgNode[] { + if (!node) { + return []; + } + + return node.children() + .filter((child) => child.kind() === "comment") + .sort((left, right) => left.range().start.index - right.range().start.index); +} + +function directCommentTexts(node: SgNode | null): string[] { + return directCommentNodes(node).map((comment) => comment.text()); +} + +function subtractComments(comments: SgNode[], excluded: SgNode[]): SgNode[] { + const excludedIds = new Set(excluded.map((comment) => comment.id())); + return comments.filter((comment) => !excludedIds.has(comment.id())); +} + +function splitDirectComments(node: SgNode, comments = directCommentNodes(node)): { + before: SgNode[]; + after: SgNode[]; + all: SgNode[]; +} { + const nodeText = node.text(); + const nodeStart = node.range().start.index; + const nodeEnd = node.range().end.index; + let tokenStart = nodeStart; + let tokenEnd = nodeEnd; + + if (comments.length > 0) { + const start = Math.min(nodeStart, ...comments.map((comment) => comment.range().start.index)); + const end = Math.max(nodeEnd, ...comments.map((comment) => comment.range().end.index)); + const segment = node.getRoot().root().text().slice(start, end); + const tokenOffset = segment.indexOf(nodeText); + if (tokenOffset >= 0) { + tokenStart = start + tokenOffset; + tokenEnd = tokenStart + nodeText.length; + } + } + + return { + before: comments.filter((comment) => comment.range().end.index <= tokenStart), + after: comments.filter((comment) => comment.range().start.index >= tokenEnd), + all: comments, + }; +} + +function joinCommentTexts(comments: SgNode[], source: string): string { + if (comments.length === 0) { + return ""; + } + + let text = comments[0]!.text(); + for (let i = 1; i < comments.length; i++) { + const previous = comments[i - 1]!; + const current = comments[i]!; + const between = source.slice(previous.range().end.index, current.range().start.index); + text += between.includes("\n") ? "" : between; + text += current.text(); + } + + return text; +} + +function commentsPrefixText(comments: SgNode[], nextStart: number, source: string): string { + if (comments.length === 0) { + return ""; + } + + const last = comments[comments.length - 1]!; + return `${joinCommentTexts(comments, source)}${source.slice(last.range().end.index, nextStart)}`; +} + +function trailingInlineCommentTexts(comments: SgNode[], anchorEnd: number, source: string): string[] { + if (comments.length === 0) { + return []; + } + + const texts: string[] = []; + let cursor = anchorEnd; + for (const comment of comments) { + texts.push(`${source.slice(cursor, comment.range().start.index)}${comment.text()}`); + cursor = comment.range().end.index; + } + return texts; +} + +function tokenSourceWithComments(node: SgNode, rendered: string, source: string): string { + const split = splitDirectComments(node); + const beforeStart = split.before.length > 0 + ? Math.min(...split.before.map((comment) => comment.range().start.index)) + : node.range().start.index; + const beforeText = source.slice(beforeStart, node.range().start.index); + const afterEnd = split.after.length > 0 + ? Math.max(...split.after.map((comment) => comment.range().end.index)) + : node.range().end.index; + const afterText = source.slice(node.range().end.index, afterEnd); + return `${beforeText}${rendered}${afterText}`; +} + +function commentBlocks(comments: SgNode[], source: string): string[] { + if (comments.length === 0) { + return []; + } + + const blocks = [comments[0]!.text()]; + for (let i = 1; i < comments.length; i++) { + const previous = comments[i - 1]!; + const current = comments[i]!; + const between = source.slice(previous.range().end.index, current.range().start.index); + if (between.includes("\n")) { + blocks.push(current.text()); + } else { + blocks[blocks.length - 1] += between + current.text(); + } + } + + return blocks; +} + +function isLineComment(text: string): boolean { + return text.startsWith("//"); +} + +function argumentLayout(call: SgNode): ArgumentLayout { + const argsNode = call.field("arguments"); + if (!argsNode) { + return { args: [], boundaryComments: [[]] }; + } + + const rawChildren = argsNode.children(); + const args: SgNode[] = []; + const boundaryComments: SgNode[][] = [[]]; + let seenArgs = 0; + + for (const child of rawChildren) { + if (child.kind() === "comment") { + boundaryComments[seenArgs]!.push(child); + continue; + } + + if (child.isNamed()) { + args.push(child); + seenArgs++; + if (!boundaryComments[seenArgs]) { + boundaryComments[seenArgs] = []; + } + } + } + + return { args, boundaryComments }; +} + +function detachedLeadingComments(call: SgNode): SgNode[] { + const parent = call.parent(); + if (!parent || parent.kind() === "arguments" || parent.kind() === "call_expression") { + return []; + } + + return parent.children() + .filter((child) => child.kind() === "comment" && child.range().end.index <= call.range().start.index) + .sort((left, right) => left.range().start.index - right.range().start.index); +} + +function splitBoundaryCommentsByComma( + anchorNode: SgNode, + comments: SgNode[], + source: string, +): { beforeComma: SgNode[]; afterComma: SgNode[] } { + const beforeComma: SgNode[] = []; + const afterComma: SgNode[] = []; + for (const comment of comments) { + const between = source.slice(anchorNode.range().end.index, comment.range().start.index); + if (between.includes(",")) { + afterComma.push(comment); + } else { + beforeComma.push(comment); + } + } + return { beforeComma, afterComma }; +} + +function tagDescriptor(node: SgNode): TagDescriptor | null { + if (node.kind() === "identifier" || node.kind() === "property_identifier") { + return { text: node.text(), identifierComments: directCommentTexts(node) }; + } + + if (node.kind() === "string") { + return { text: stringValue(node), identifierComments: directCommentTexts(node) }; + } + + if (node.kind() !== "member_expression") { + return null; + } + + const object = node.field("object"); + const property = node.field("property"); + if (!object || !property) { + return null; + } + + const objectTag = tagDescriptor(object); + const propertyTag = tagDescriptor(property); + if (!objectTag || !propertyTag) { + return null; + } + + return { + text: `${objectTag.text}.${propertyTag.text}`, + identifierComments: [ + ...directCommentTexts(node), + ...objectTag.identifierComments, + ...propertyTag.identifierComments, + ], + }; +} + +function renderTokenWithComments(node: SgNode, text: string, source: string): string { + return tokenSourceWithComments(node, text, source); +} + +function renderPropValue(node: SgNode, source: string): string { + if (node.kind() === "string") { + const literal = stringValue(node); + if (canLiteralBePropString(node, literal)) { + return renderTokenWithComments(node, `"${literal}"`, source); + } + } + + const split = splitDirectComments(node); + return `{${joinCommentTexts(split.before, source)}${node.text()}${joinCommentTexts(split.after, source)}}`; +} + +function spreadAttrDescriptor(node: SgNode, source: string): AttrDescriptor { + const argument = spreadArgument(node); + if (!argument) { + throw new Error("Expected spread element argument"); + } + + return { + leadingBlocks: commentBlocks(directCommentNodes(node), source), + inline: `{...${tokenSourceWithComments(argument, argument.text(), source)}}`, + hasComments: directCommentNodes(node).length > 0 || directCommentNodes(argument).length > 0, + }; +} + +function pairInlineDescriptor( + node: SgNode, + source: string, + prefixInlineComments: string[], + suffixInlineComments: string[], + leadingBlocks: string[], +): AttrDescriptor { + const key = node.field("key"); + const value = node.field("value"); + if (!key || !value) { + throw new Error("Expected object property to have key and value"); + } + + const pairComments = directCommentNodes(node); + const keyAfter = pairComments[0] ? [pairComments[0]!] : []; + const valueBefore = pairComments.slice(1); + const keyText = `${prefixInlineComments.join("")}${attrName(key)}${joinCommentTexts(keyAfter, source)}`; + const valueText = (() => { + if (value.kind() === "string") { + const literal = stringValue(value); + if (canLiteralBePropString(value, literal)) { + return `${commentsPrefixText(valueBefore, value.range().start.index, source)}"${literal}"${suffixInlineComments.join("")}`; + } + } + return `{${commentsPrefixText(valueBefore, value.range().start.index, source)}${value.text()}}${suffixInlineComments.join("")}`; + })(); + + return { + leadingBlocks, + inline: `${keyText}=${valueText}`, + hasComments: + leadingBlocks.length > 0 || + prefixInlineComments.length > 0 || + suffixInlineComments.length > 0 || + pairComments.length > 0, + }; +} + +function propsToDescriptors(node: SgNode | null, source: string): { + attrs: AttrDescriptor[]; + trailingElementComments: string[]; + consumedBoundaryComments: string[]; +} { + if (!node || node.kind() === "null") { + return { attrs: [], trailingElementComments: [], consumedBoundaryComments: [] }; + } + + if (isMemberCall(node, "React", "__spread") || isMemberCall(node, "Object", "assign")) { + const callee = node.field("function"); + const calleeObject = callee?.field("object") ?? null; + const calleeProperty = callee?.field("property") ?? null; + const attrs: AttrDescriptor[] = []; + const trailing = [ + ...directCommentTexts(node), + ...directCommentTexts(callee), + ...directCommentTexts(calleeObject), + ...directCommentTexts(calleeProperty), + ]; + + for (const arg of callArguments(node)) { + const nested = propsToDescriptors(arg, source); + attrs.push(...nested.attrs); + trailing.push(...nested.trailingElementComments); + } + + return { attrs, trailingElementComments: trailing, consumedBoundaryComments: [] }; + } + + if ( + node.kind() === "identifier" || + node.kind() === "member_expression" || + node.kind() === "call_expression" + ) { + return { + attrs: [{ + leadingBlocks: [], + inline: `{...${tokenSourceWithComments(node, node.text(), source)}}`, + hasComments: directCommentNodes(node).length > 0, + }], + trailingElementComments: [], + consumedBoundaryComments: [], + }; + } + + if (node.kind() !== "object") { + throw new Error(`Unexpected attribute of type "${legacyType(node)}"`); + } + + const attrs: AttrDescriptor[] = []; + const objectComments = directCommentNodes(node); + const namedChildrenList = node.children().filter((child) => child.isNamed() && child.kind() !== "comment"); + const pendingLeadingBlocks = new Map(); + const consumedObjectComments = new Set(); + + if (namedChildrenList.length === 0) { + return { + attrs, + trailingElementComments: objectComments.map((comment) => comment.text()), + consumedBoundaryComments: [], + }; + } + + for (let index = 0; index < namedChildrenList.length; index++) { + const child = namedChildrenList[index]!; + const nextChild = namedChildrenList[index + 1]; + + if (child.kind() === "spread_element") { + attrs.push(spreadAttrDescriptor(child, source)); + continue; + } + + if (child.kind() !== "pair") { + continue; + } + + const key = child.field("key"); + const value = child.field("value"); + if (!key || !value) { + continue; + } + + const commentsBefore = objectComments.filter((comment) => + !consumedObjectComments.has(comment.id()) && + comment.range().end.index <= key.range().start.index && + comment.range().start.index >= node.range().start.index + ); + const commentsAfterValue = objectComments.filter((comment) => + !consumedObjectComments.has(comment.id()) && + comment.range().start.index >= value.range().end.index && + comment.range().end.index <= (nextChild?.range().start.index ?? node.range().end.index) + ); + + const beforeComma: SgNode[] = []; + const afterComma: SgNode[] = []; + for (const comment of commentsAfterValue) { + const between = source.slice(value.range().end.index, comment.range().start.index); + if (between.includes(",")) { + afterComma.push(comment); + } else { + beforeComma.push(comment); + } + } + + const trailingBlocksForCurrent = !nextChild + ? (commentsBefore.length > 0 && afterComma.length > 0 + ? [`${commentBlocks(commentsBefore, source).join("")}${afterComma.map((comment) => comment.text()).join("")}`] + : commentBlocks(afterComma, source)) + : []; + const currentLeadingBlocks = [ + ...(pendingLeadingBlocks.get(index) ?? []), + ...((!nextChild && afterComma.length > 0) + ? [] + : (index === 0 ? [] : commentBlocks(commentsBefore, source))), + ...trailingBlocksForCurrent, + ]; + const inlinePrefix = index === 0 ? commentsBefore.map((comment) => comment.text()) : []; + const inlineSuffix = beforeComma.map((comment) => comment.text()); + + attrs.push(pairInlineDescriptor(child, source, inlinePrefix, inlineSuffix, currentLeadingBlocks)); + [...commentsBefore, ...beforeComma, ...afterComma].forEach((comment) => consumedObjectComments.add(comment.id())); + + const carryBlocks = nextChild ? commentBlocks(afterComma, source) : []; + if (carryBlocks.length > 0) { + const targetIndex = nextChild ? index + 1 : index; + pendingLeadingBlocks.set(targetIndex, [ + ...(pendingLeadingBlocks.get(targetIndex) ?? []), + ...carryBlocks, + ]); + } + } + + return { + attrs, + trailingElementComments: [], + consumedBoundaryComments: [], + }; +} + +function jsxReference(node: SgNode): string | null { + if (node.kind() === "identifier" || node.kind() === "property_identifier") { + return node.text(); + } + + if (node.kind() === "string") { + return stringValue(node); + } + + if (node.kind() !== "member_expression") { + return null; + } + + const object = node.field("object"); + const property = node.field("property"); + if (!object || !property) { + return null; + } + + const objectRef = jsxReference(object); + const propertyRef = jsxReference(property); + if (!objectRef || !propertyRef) { + return null; + } + + return `${objectRef}.${propertyRef}`; +} + +function topLevelJsxTag(node: SgNode): string | null { + const reference = jsxReference(node); + if (!reference) { + return null; + } + + if (node.kind() === "identifier" && startsLowercase(node.text())) { + return null; + } + + if (node.kind() === "string" && !startsLowercase(stringValue(node))) { + return null; + } + + return reference; +} + +function legacyType(node: SgNode): string { + switch (node.kind()) { + case "call_expression": + return "CallExpression"; + case "identifier": + return "Identifier"; + case "member_expression": + return "MemberExpression"; + case "object": + return "ObjectExpression"; + case "spread_element": + return "SpreadElement"; + case "string": + case "number": + case "true": + case "false": + case "null": + return "Literal"; + default: + return node.kind(); + } +} + +function canLiteralBePropString(node: SgNode, value: string): boolean { + return !node.text().includes("\\") && !value.includes("\""); +} + +function spreadArgument(node: SgNode): SgNode | null { + return node.children().find((child) => child.isNamed() && child.kind() !== "comment") ?? null; +} + +function attrName(node: SgNode): string { + if (node.kind() === "property_identifier" || node.kind() === "identifier") { + return node.text(); + } + + if (node.kind() === "string") { + return stringValue(node); + } + + throw new Error(`Unexpected property key type "${legacyType(node)}"`); +} + +function pairToAttribute(pair: SgNode): string { + const key = pair.field("key"); + const value = pair.field("value"); + if (!key || !value) { + throw new Error("Expected object property to have key and value"); + } + + const name = attrName(key); + if (value.kind() === "string") { + const literal = stringValue(value); + if (canLiteralBePropString(value, literal)) { + return `${name}="${literal}"`; + } + } + + return `${name}={${value.text()}}`; +} + +function isMemberCall(node: SgNode, objectName: string, propertyName: string): boolean { + if (node.kind() !== "call_expression") { + return false; + } + + const callee = node.field("function"); + if (!callee || callee.kind() !== "member_expression") { + return false; + } + + const object = callee.field("object"); + const property = callee.field("property"); + + return object?.kind() === "identifier" && + object.text() === objectName && + property?.kind() === "property_identifier" && + property.text() === propertyName; +} + +function propsToAttributes(node: SgNode | null): string[] { + if (!node || node.kind() === "null") { + return []; + } + + if (isMemberCall(node, "React", "__spread") || isMemberCall(node, "Object", "assign")) { + return callArguments(node).flatMap((arg) => propsToAttributes(arg)); + } + + if ( + node.kind() === "identifier" || + node.kind() === "member_expression" || + node.kind() === "call_expression" + ) { + return [`{...${node.text()}}`]; + } + + if (node.kind() !== "object") { + throw new Error(`Unexpected attribute of type "${legacyType(node)}"`); + } + + const attributes: string[] = []; + for (const child of node.children()) { + if (!child.isNamed()) { + continue; + } + + if (child.kind() === "comment") { + continue; + } + + if (child.kind() === "spread_element") { + const argument = spreadArgument(child); + if (!argument) { + throw new Error("Expected spread element argument"); + } + + attributes.push(`{...${argument.text()}}`); + continue; + } + + if (child.kind() === "pair") { + attributes.push(pairToAttribute(child)); + } + } + + return attributes; +} + +function renderExpression(code: string, baseIndent: string): string { + if (!code.includes("\n")) { + return `{${code}}`; + } + + return `{\n${indentAll(code, `${baseIndent} `)}\n${baseIndent}}`; +} + +function renderSimpleChild(node: SgNode, baseIndent: string): { code: string; count: number } { + if (node.kind() === "string") { + const value = stringValue(node); + if (value !== "" && value.trim() === value) { + return { code: encodeJSXTextValue(value), count: 0 }; + } + + return { code: renderExpression(node.text(), baseIndent), count: 0 }; + } + + if (node.kind() === "spread_element") { + const argument = spreadArgument(node); + if (!argument) { + throw new Error("Expected spread child argument"); + } + + return { code: renderExpression(argument.text(), baseIndent), count: 0 }; + } + + if (isReactCreateElementCall(node)) { + const nested = convertSimpleCall(node, baseIndent); + if (!nested) { + return { code: renderExpression(node.text(), baseIndent), count: 0 }; + } + + return { + code: nested.hasComments ? renderExpression(nested.code, baseIndent) : nested.code, + count: nested.count, + }; + } + + return { code: renderExpression(node.text(), baseIndent), count: 0 }; +} + +function convertSimpleCall(call: SgNode, baseIndent: string): Conversion | null { + const args = callArguments(call); + const elementArg = args[0]; + if (!elementArg) { + return null; + } + + const tag = topLevelJsxTag(elementArg); + if (!tag) { + return null; + } + + const propsArg = args[1] ?? null; + const attributes = propsToAttributes(propsArg); + const childIndent = `${baseIndent} `; + const childResults = args.slice(2).map((child) => renderSimpleChild(child, childIndent)); + const attributeSuffix = attributes.length > 0 ? ` ${attributes.join(" ")}` : ""; + + let jsx = `<${tag}${attributeSuffix}`; + let count = 1; + for (const child of childResults) { + count += child.count; + } + + if (childResults.length === 0) { + jsx += " />"; + } else { + const childLines = childResults.map((child) => prefixFirstLine(child.code, childIndent)); + jsx += `>\n${childLines.join("\n")}\n${baseIndent}`; + } + + return { code: jsx, count, hasComments: false }; +} + +function renderAttributeDescriptor(attr: AttrDescriptor, indent: string): string { + const mergedBlocks = [...attr.leadingBlocks]; + if (mergedBlocks.length >= 2 && mergedBlocks[mergedBlocks.length - 1]!.startsWith("//")) { + mergedBlocks[mergedBlocks.length - 2] = `${mergedBlocks[mergedBlocks.length - 2]!}${mergedBlocks[mergedBlocks.length - 1]!}`; + mergedBlocks.pop(); + } + const lines = mergedBlocks.map((block) => `${indent}${block}`); + lines.push(`${indent}${attr.inline}`); + return lines.join("\n"); +} + +function renderExpressionContainer( + core: string, + leadingComments: string[], + trailingComments: string[], + _indent: string, +): string { + const leadingText = leadingComments.join(""); + const trailingText = trailingComments.join(""); + + if (leadingText.length === 0 && trailingText.length === 0 && !core.includes("\n")) { + return `{${core}}`; + } + + if (leadingText.length > 0) { + if (core.includes("\n")) { + return `{\n${core}${trailingText}\n}`; + } + if (!leadingComments.some(isLineComment)) { + return `{${leadingText}${core}${trailingText}}`; + } + return `{${leadingText}\n${core}${trailingText}}`; + } + + if (trailingText.length > 0 && !core.includes("\n")) { + return `{${core}${trailingText}}`; + } + + return `{\n${core}\n}`; +} + +function renderElementCore( + tag: string, + attrs: AttrDescriptor[], + children: { code: string; count: number }[], + baseIndent: string, +): string { + const attrIndent = `${baseIndent} `; + const hasCommentedAttrs = attrs.some((attr) => attr.leadingBlocks.length > 0); + + if (children.length === 0) { + if (attrs.length === 0) { + return `<${tag} />`; + } + + if (hasCommentedAttrs) { + return `<${tag}\n${attrs.map((attr) => renderAttributeDescriptor(attr, attrIndent)).join("\n")}\n${baseIndent}/>`; + } + + return `<${tag} ${attrs.map((attr) => attr.inline).join(" ")} />`; + } + + let opening = `<${tag}>`; + if (attrs.length > 0) { + if (hasCommentedAttrs) { + const attrLines = attrs.map((attr) => renderAttributeDescriptor(attr, attrIndent)); + attrLines[attrLines.length - 1] = `${attrLines[attrLines.length - 1]!}>`; + opening = `<${tag}\n${attrLines.join("\n")}`; + } else { + opening = `<${tag} ${attrs.map((attr) => attr.inline).join(" ")}>`; + } + } + + const childIndent = `${baseIndent} `; + const renderedChildren = children.map((child) => indentAll(child.code, childIndent)); + return `${opening}\n${renderedChildren.join("\n")}\n${baseIndent}`; +} + +function finalizeRenderedElement(rendered: RenderedElement, baseIndent: string): string { + if (!rendered.hasComments) { + return rendered.core; + } + + if (!rendered.core.includes("\n") && rendered.leadingComments.length === 0 && rendered.trailingComments.length > 0) { + return `${rendered.core}${rendered.trailingComments.join("")}`; + } + + if (rendered.leadingComments.length === 0 && rendered.trailingComments.length > 0) { + return `${rendered.core}${rendered.trailingComments.join("")}`; + } + + const innerIndent = `${baseIndent} `; + const lines: string[] = []; + if (rendered.leadingComments.length > 0) { + if (rendered.leadingComments.some(isLineComment)) { + lines.push(...rendered.leadingComments.map((comment) => `${innerIndent}${comment}`)); + lines.push(indentAll(rendered.core, innerIndent)); + } else { + const indentedCore = indentAll(rendered.core, innerIndent); + const coreLines = indentedCore.split("\n"); + coreLines[0] = `${innerIndent}${rendered.leadingComments.join("")}${coreLines[0]!.slice(innerIndent.length)}`; + lines.push(coreLines.join("\n")); + } + } else { + lines.push(indentAll(rendered.core, innerIndent)); + } + + if (rendered.trailingComments.length > 0) { + lines.push(`${innerIndent}${rendered.trailingComments.join("")}`); + } + + return `(\n${lines.join("\n")}\n${baseIndent})`; +} + +function commentedChild( + node: SgNode, + leadingBoundaryComments: SgNode[], + trailingBoundaryComments: SgNode[], + baseIndent: string, + source: string, +): { code: string; count: number } { + let boundaryLeading = [ + ...leadingBoundaryComments.map((comment) => comment.text()), + ...trailingBoundaryComments.map((comment) => comment.text()), + ]; + let trailingInlineComments: string[] = []; + if (isReactCreateElementCall(node) && trailingBoundaryComments.length > 0) { + const split = splitBoundaryCommentsByComma(node, trailingBoundaryComments, source); + boundaryLeading = [ + ...leadingBoundaryComments.map((comment) => comment.text()), + ...split.beforeComma.filter((comment) => isLineComment(comment.text())).map((comment) => comment.text()), + ...split.afterComma.map((comment) => comment.text()), + ]; + trailingInlineComments = split.beforeComma + .filter((comment) => !isLineComment(comment.text())) + .map((comment) => comment.text()); + } + const directLeading = isReactCreateElementCall(node) ? [] : directCommentTexts(node); + const leadingComments = [...boundaryLeading, ...directLeading]; + + if (node.kind() === "string") { + const value = stringValue(node); + if (leadingComments.length === 0 && value !== "" && value.trim() === value) { + return { code: encodeJSXTextValue(value), count: 0 }; + } + + return { + code: renderExpressionContainer(node.text(), leadingComments, [], baseIndent), + count: 0, + }; + } + + if (node.kind() === "spread_element") { + const argument = spreadArgument(node); + if (!argument) { + throw new Error("Expected spread child argument"); + } + + return { + code: renderExpressionContainer(argument.text(), leadingComments, [], baseIndent), + count: 0, + }; + } + + if (isReactCreateElementCall(node)) { + const nested = convertCommentedCall(node, baseIndent, source); + if (!nested) { + return { + code: renderExpressionContainer(node.text(), leadingComments, [], baseIndent), + count: 0, + }; + } + + if (!nested.hasComments && leadingComments.length === 0) { + return { code: nested.core, count: nested.count }; + } + + return { + code: renderExpressionContainer( + nested.core, + [...leadingComments, ...nested.leadingComments], + [ + ...trailingInlineCommentTexts( + trailingBoundaryComments.filter((comment) => !isLineComment(comment.text())), + node.range().end.index, + source, + ), + ...(nested.expressionTrailingComments ?? nested.trailingComments), + ], + baseIndent, + ), + count: nested.count, + }; + } + + return { + code: renderExpressionContainer(node.text(), leadingComments, [], baseIndent), + count: 0, + }; +} + +function convertCommentedCall(call: SgNode, baseIndent: string, source: string): RenderedElement | null { + const layout = argumentLayout(call); + const args = layout.args; + const elementArg = args[0]; + if (!elementArg) { + return null; + } + + const tag = topLevelJsxTag(elementArg); + if (!tag) { + return null; + } + + const tagInfo = tagDescriptor(elementArg); + if (!tagInfo) { + return null; + } + + const callLeadingComments = [ + ...detachedLeadingComments(call).map((comment) => comment.text()), + ...directCommentTexts(call), + ]; + const callee = call.field("function"); + const calleeLeadingComments = [ + ...directCommentTexts(callee), + ...directCommentTexts(callee?.field("object") ?? null), + ...directCommentTexts(callee?.field("property") ?? null), + ]; + + const beforeFirst = layout.boundaryComments[0] ?? []; + const betweenTagAndProps = layout.boundaryComments[1] ?? []; + const propsArg = args[1] ?? null; + const propsIsSpreadLike = propsArg !== null && + (propsArg.kind() === "identifier" || propsArg.kind() === "member_expression" || propsArg.kind() === "call_expression"); + const propsIsNull = propsArg?.kind() === "null"; + const leadingComments = [ + ...callLeadingComments, + ...calleeLeadingComments, + ...beforeFirst.filter((comment) => isLineComment(comment.text())).map((comment) => comment.text()), + ...betweenTagAndProps.filter((comment) => isLineComment(comment.text())).map((comment) => comment.text()), + ]; + const baseTagPropsTrailing = (propsIsSpreadLike || propsIsNull) + ? [] + : betweenTagAndProps.filter((comment) => !isLineComment(comment.text())).map((comment) => comment.text()); + const trailingComments = !propsArg && elementArg.kind() === "member_expression" + ? [ + ...beforeFirst.filter((comment) => !isLineComment(comment.text())).map((comment) => comment.text()), + ...baseTagPropsTrailing, + ...tagInfo.identifierComments, + ] + : [ + ...tagInfo.identifierComments, + ...beforeFirst.filter((comment) => !isLineComment(comment.text())).map((comment) => comment.text()), + ...baseTagPropsTrailing, + ]; + + const propsBoundaryAfter = propsArg && args.length === 2 + ? (layout.boundaryComments[2] ?? []) + : []; + const props = propsToDescriptors(propsArg, source); + if (propsArg && (propsArg.kind() === "identifier" || propsArg.kind() === "member_expression" || propsArg.kind() === "call_expression")) { + const spreadAttr = props.attrs[0]; + if (spreadAttr) { + const beforeText = betweenTagAndProps.map((comment) => comment.text()).join(""); + const afterText = propsBoundaryAfter.map((comment) => comment.text()).join(""); + spreadAttr.inline = `{...${beforeText}${propsArg.text()}${afterText}}`; + spreadAttr.hasComments ||= beforeText.length > 0 || afterText.length > 0; + } + } + if (propsIsNull) { + const nullPropComments = [ + ...betweenTagAndProps.map((comment) => comment.text()), + ...propsBoundaryAfter.map((comment) => comment.text()), + ]; + if (nullPropComments.length > 0) { + trailingComments.push(` ${nullPropComments[0]!}`, ...nullPropComments.slice(1)); + } + } + trailingComments.push(...props.trailingElementComments); + + const childIndent = `${baseIndent} `; + const children = args.slice(2).map((child, index) => { + const argIndex = index + 2; + let boundaryBefore = argIndex === 2 ? (layout.boundaryComments[argIndex] ?? []) : []; + if (argIndex === 2 && propsArg) { + const split = splitBoundaryCommentsByComma(propsArg, boundaryBefore, source); + if (propsIsSpreadLike) { + boundaryBefore = [...split.beforeComma, ...split.afterComma]; + } else { + trailingComments.push(...split.beforeComma.map((comment) => comment.text())); + boundaryBefore = split.afterComma; + } + } + const boundaryAfter = layout.boundaryComments[argIndex + 1] ?? []; + return commentedChild(child, boundaryBefore, boundaryAfter, childIndent, source); + }); + + const core = renderElementCore(tagInfo.text, props.attrs, children, ""); + let count = 1; + for (const child of children) { + count += child.count; + } + + return { + core, + count, + leadingComments, + trailingComments, + hasComments: + leadingComments.length > 0 || + trailingComments.length > 0 || + props.attrs.some((attr) => attr.leadingBlocks.length > 0) || + children.some((child) => child.code.includes("\n")), + // Legacy recast printing duplicates a wrapped member-expression element's + // element-level trailing comments, then prints identifier-local comments once + // more. Keep those buckets separate and derive the wrapped form from them. + expressionTrailingComments: + !propsArg && elementArg.kind() === "member_expression" + ? [ + ...trailingComments, + ...trailingComments, + ...tagInfo.identifierComments, + ] + : undefined, + }; +} + +function explicitRequireDisabled(value: unknown): boolean { + return value === false || value === "false"; +} + +const transform: Transform = async (root, options) => { + const rootNode = root.root(); + const source = rootNode.text(); + const edits: Edit[] = []; + const metric = useMetricAtom("create-element-to-jsx-conversions"); + const convertibility = new Map(); + let totalConversions = 0; + + if (!explicitRequireDisabled(options.params?.["explicit-require"]) && !hasReact(rootNode)) { + return null; + } + + const canConvertCall = (call: SgNode): boolean => { + const cached = convertibility.get(call.id()); + if (cached !== undefined) { + return cached; + } + + const args = callArguments(call); + const elementArg = args[0]; + const convertible = elementArg !== undefined && topLevelJsxTag(elementArg) !== null; + convertibility.set(call.id(), convertible); + return convertible; + }; + + const createElementCalls = rootNode.findAll({ rule: { kind: "call_expression" } }) + .filter(isReactCreateElementCall); + + for (const call of createElementCalls) { + const coveredByAncestor = call.ancestors().some((ancestor) => + isReactCreateElementCall(ancestor) && canConvertCall(ancestor) + ); + if (coveredByAncestor) { + continue; + } + + const baseIndent = lineIndent(source, call.range().start.index); + const conversion = ownCommentNodes(call).length > 0 + ? convertCommentedCall(call, baseIndent, source) + : convertSimpleCall(call, baseIndent); + if (!conversion) { + continue; + } + + const code = "core" in conversion ? finalizeRenderedElement(conversion, baseIndent) : conversion.code; + if ("core" in conversion) { + const detachedComments = detachedLeadingComments(call); + const replacementStart = detachedComments.length > 0 + ? Math.min(...detachedComments.map((comment) => comment.range().start.index)) + : call.range().start.index; + edits.push({ + startPos: replacementStart, + endPos: call.range().end.index, + insertedText: code, + }); + } else { + edits.push(call.replace(code)); + } + totalConversions += conversion.count; + } + + if (edits.length === 0) { + return null; + } + + metric.increment({ + file: metricFile(root.filename()), + }, totalConversions); + + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/create-element-to-jsx/scripts/differential-tests.mjs b/codemods/create-element-to-jsx/scripts/differential-tests.mjs new file mode 100644 index 0000000..bfd1d5b --- /dev/null +++ b/codemods/create-element-to-jsx/scripts/differential-tests.mjs @@ -0,0 +1,69 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, writeFileSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { spawnSync } from "node:child_process"; + +const __filename = fileURLToPath(import.meta.url); +const packageDir = path.dirname(path.dirname(__filename)); +const workflowPath = path.join(packageDir, "workflow.yaml"); +const testsDir = path.join(packageDir, "tests"); + +function readFixture(name, file) { + return readFileSync(path.join(testsDir, name, file), "utf8").trim(); +} + +function runJssg(source) { + const dir = mkdtempSync(path.join(tmpdir(), "ce2jsx-jssg-")); + const inputPath = path.join(dir, "input.tsx"); + writeFileSync(inputPath, source, "utf8"); + writeFileSync(path.join(dir, ".gitignore"), "", "utf8"); + + try { + spawnSync("git", ["init", "-q"], { cwd: dir }); + spawnSync("git", ["add", "input.tsx", ".gitignore"], { cwd: dir }); + spawnSync("git", ["commit", "-qm", "init"], { cwd: dir }); + + const result = spawnSync( + "pnpm", + [ + "dlx", + "codemod@latest", + "workflow", + "run", + "-w", + workflowPath, + "--target", + dir, + "--allow-dirty", + ], + { + cwd: packageDir, + encoding: "utf8", + timeout: 20000, + }, + ); + assert.strictEqual(result.status, 0, `${result.stdout}\n${result.stderr}`); + return readFileSync(inputPath, "utf8").trim(); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +} + +const parityCases = [ + "member-simple-comments", + "spread-prop-comments", + "member-child-comments-alt", + "comment-only-props-string-child", +]; + +for (const name of parityCases) { + test(`matches checked-in parity fixture for ${name}`, () => { + const input = readFixture(name, "input.tsx"); + const expected = readFixture(name, "expected.tsx"); + const jssgOutput = runJssg(input); + assert.strictEqual(jssgOutput, expected); + }); +} diff --git a/codemods/create-element-to-jsx/scripts/error-tests.mjs b/codemods/create-element-to-jsx/scripts/error-tests.mjs new file mode 100644 index 0000000..5480d1f --- /dev/null +++ b/codemods/create-element-to-jsx/scripts/error-tests.mjs @@ -0,0 +1,47 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, writeFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { spawnSync } from "node:child_process"; + +const __filename = fileURLToPath(import.meta.url); +const packageDir = path.dirname(path.dirname(__filename)); +const workflowPath = path.join(packageDir, "workflow.yaml"); + +function runCodemod(input) { + const target = mkdtempSync(path.join(tmpdir(), "create-element-to-jsx-")); + const filePath = path.join(target, "input.tsx"); + writeFileSync(filePath, input, "utf8"); + + try { + return spawnSync( + "pnpm", + [ + "dlx", + "codemod@latest", + "workflow", + "run", + "-w", + workflowPath, + "--target", + target, + "--allow-dirty", + ], + { + cwd: packageDir, + encoding: "utf8", + }, + ); + } finally { + rmSync(target, { recursive: true, force: true }); + } +} + +test("throws on unsupported props literal", () => { + const result = runCodemod("var React = require('react/addons');\nReact.createElement('foo', 1);\n"); + const combined = `${result.stdout}\n${result.stderr}`; + assert.match(combined, /Unexpected attribute of type .*Literal/); + assert.match(combined, /Failed to execute codemod|InitializationFailed/); +}); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-allow-member-expression.output.js b/codemods/create-element-to-jsx/tests/allow-member-expression/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-allow-member-expression.output.js rename to codemods/create-element-to-jsx/tests/allow-member-expression/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-allow-member-expression.input.js b/codemods/create-element-to-jsx/tests/allow-member-expression/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-allow-member-expression.input.js rename to codemods/create-element-to-jsx/tests/allow-member-expression/input.tsx diff --git a/codemods/create-element-to-jsx/tests/allow-member-expression/metrics.json b/codemods/create-element-to-jsx/tests/allow-member-expression/metrics.json new file mode 100644 index 0000000..d3b422e --- /dev/null +++ b/codemods/create-element-to-jsx/tests/allow-member-expression/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/allow-member-expression/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-arg-spread.output.js b/codemods/create-element-to-jsx/tests/arg-spread/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-arg-spread.output.js rename to codemods/create-element-to-jsx/tests/arg-spread/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-arg-spread.input.js b/codemods/create-element-to-jsx/tests/arg-spread/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-arg-spread.input.js rename to codemods/create-element-to-jsx/tests/arg-spread/input.tsx diff --git a/codemods/create-element-to-jsx/tests/arg-spread/metrics.json b/codemods/create-element-to-jsx/tests/arg-spread/metrics.json new file mode 100644 index 0000000..3c3571e --- /dev/null +++ b/codemods/create-element-to-jsx/tests/arg-spread/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/arg-spread/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-as-children.output.js b/codemods/create-element-to-jsx/tests/call-as-children/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-as-children.output.js rename to codemods/create-element-to-jsx/tests/call-as-children/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-as-children.input.js b/codemods/create-element-to-jsx/tests/call-as-children/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-as-children.input.js rename to codemods/create-element-to-jsx/tests/call-as-children/input.tsx diff --git a/codemods/create-element-to-jsx/tests/call-as-children/metrics.json b/codemods/create-element-to-jsx/tests/call-as-children/metrics.json new file mode 100644 index 0000000..9b83be3 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/call-as-children/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/call-as-children/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-expression-as-prop.output.js b/codemods/create-element-to-jsx/tests/call-expression-as-prop/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-expression-as-prop.output.js rename to codemods/create-element-to-jsx/tests/call-expression-as-prop/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-expression-as-prop.input.js b/codemods/create-element-to-jsx/tests/call-expression-as-prop/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-expression-as-prop.input.js rename to codemods/create-element-to-jsx/tests/call-expression-as-prop/input.tsx diff --git a/codemods/create-element-to-jsx/tests/call-expression-as-prop/metrics.json b/codemods/create-element-to-jsx/tests/call-expression-as-prop/metrics.json new file mode 100644 index 0000000..1901fdb --- /dev/null +++ b/codemods/create-element-to-jsx/tests/call-expression-as-prop/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/call-expression-as-prop/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-literal.output.js b/codemods/create-element-to-jsx/tests/children-literal/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-literal.output.js rename to codemods/create-element-to-jsx/tests/children-literal/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-literal.input.js b/codemods/create-element-to-jsx/tests/children-literal/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-literal.input.js rename to codemods/create-element-to-jsx/tests/children-literal/input.tsx diff --git a/codemods/create-element-to-jsx/tests/children-literal/metrics.json b/codemods/create-element-to-jsx/tests/children-literal/metrics.json new file mode 100644 index 0000000..bff6a4f --- /dev/null +++ b/codemods/create-element-to-jsx/tests/children-literal/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/children-literal/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-map.output.js b/codemods/create-element-to-jsx/tests/children-map/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-map.output.js rename to codemods/create-element-to-jsx/tests/children-map/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-map.input.js b/codemods/create-element-to-jsx/tests/children-map/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-map.input.js rename to codemods/create-element-to-jsx/tests/children-map/input.tsx diff --git a/codemods/create-element-to-jsx/tests/children-map/metrics.json b/codemods/create-element-to-jsx/tests/children-map/metrics.json new file mode 100644 index 0000000..2b46493 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/children-map/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/children-map/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-mixed-empty-string.output.js b/codemods/create-element-to-jsx/tests/children-mixed-empty-string/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-mixed-empty-string.output.js rename to codemods/create-element-to-jsx/tests/children-mixed-empty-string/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-mixed-empty-string.input.js b/codemods/create-element-to-jsx/tests/children-mixed-empty-string/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-mixed-empty-string.input.js rename to codemods/create-element-to-jsx/tests/children-mixed-empty-string/input.tsx diff --git a/codemods/create-element-to-jsx/tests/children-mixed-empty-string/metrics.json b/codemods/create-element-to-jsx/tests/children-mixed-empty-string/metrics.json new file mode 100644 index 0000000..5bf7bb6 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/children-mixed-empty-string/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/children-mixed-empty-string/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children.output.js b/codemods/create-element-to-jsx/tests/children/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children.output.js rename to codemods/create-element-to-jsx/tests/children/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children.input.js b/codemods/create-element-to-jsx/tests/children/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children.input.js rename to codemods/create-element-to-jsx/tests/children/input.tsx diff --git a/codemods/create-element-to-jsx/tests/children/metrics.json b/codemods/create-element-to-jsx/tests/children/metrics.json new file mode 100644 index 0000000..3737f1c --- /dev/null +++ b/codemods/create-element-to-jsx/tests/children/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/children/input.tsx" + }, + "count": 3 + } + ] +} \ No newline at end of file diff --git a/codemods/create-element-to-jsx/tests/comment-only-props-string-child/expected.tsx b/codemods/create-element-to-jsx/tests/comment-only-props-string-child/expected.tsx new file mode 100644 index 0000000..7b85a75 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/comment-only-props-string-child/expected.tsx @@ -0,0 +1,4 @@ +var React = require('react'); + + x +/*P*/; diff --git a/codemods/create-element-to-jsx/tests/comment-only-props-string-child/input.tsx b/codemods/create-element-to-jsx/tests/comment-only-props-string-child/input.tsx new file mode 100644 index 0000000..9f279a8 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/comment-only-props-string-child/input.tsx @@ -0,0 +1,2 @@ +var React = require('react'); +React.createElement(Foo, {/*P*/}, 'x'); diff --git a/codemods/create-element-to-jsx/tests/comment-only-props-string-child/metrics.json b/codemods/create-element-to-jsx/tests/comment-only-props-string-child/metrics.json new file mode 100644 index 0000000..df02d4e --- /dev/null +++ b/codemods/create-element-to-jsx/tests/comment-only-props-string-child/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/comment-only-props-string-child/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-computed-component.input.js b/codemods/create-element-to-jsx/tests/computed-component/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-computed-component.input.js rename to codemods/create-element-to-jsx/tests/computed-component/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-computed-component.output.js b/codemods/create-element-to-jsx/tests/computed-component/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-computed-component.output.js rename to codemods/create-element-to-jsx/tests/computed-component/input.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-deep-nesting.output.js b/codemods/create-element-to-jsx/tests/deep-nesting/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-deep-nesting.output.js rename to codemods/create-element-to-jsx/tests/deep-nesting/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-deep-nesting.input.js b/codemods/create-element-to-jsx/tests/deep-nesting/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-deep-nesting.input.js rename to codemods/create-element-to-jsx/tests/deep-nesting/input.tsx diff --git a/codemods/create-element-to-jsx/tests/deep-nesting/metrics.json b/codemods/create-element-to-jsx/tests/deep-nesting/metrics.json new file mode 100644 index 0000000..ab5be8b --- /dev/null +++ b/codemods/create-element-to-jsx/tests/deep-nesting/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/deep-nesting/input.tsx" + }, + "count": 7 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-element-comment-positioning.output.js b/codemods/create-element-to-jsx/tests/element-comment-positioning/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-element-comment-positioning.output.js rename to codemods/create-element-to-jsx/tests/element-comment-positioning/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-element-comment-positioning.input.js b/codemods/create-element-to-jsx/tests/element-comment-positioning/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-element-comment-positioning.input.js rename to codemods/create-element-to-jsx/tests/element-comment-positioning/input.tsx diff --git a/codemods/create-element-to-jsx/tests/element-comment-positioning/metrics.json b/codemods/create-element-to-jsx/tests/element-comment-positioning/metrics.json new file mode 100644 index 0000000..56d512e --- /dev/null +++ b/codemods/create-element-to-jsx/tests/element-comment-positioning/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/element-comment-positioning/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-escaped-string.output.js b/codemods/create-element-to-jsx/tests/escaped-string/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-escaped-string.output.js rename to codemods/create-element-to-jsx/tests/escaped-string/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-escaped-string.input.js b/codemods/create-element-to-jsx/tests/escaped-string/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-escaped-string.input.js rename to codemods/create-element-to-jsx/tests/escaped-string/input.tsx diff --git a/codemods/create-element-to-jsx/tests/escaped-string/metrics.json b/codemods/create-element-to-jsx/tests/escaped-string/metrics.json new file mode 100644 index 0000000..a4d2834 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/escaped-string/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/escaped-string/input.tsx" + }, + "count": 5 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-gt-lt-entities.output.js b/codemods/create-element-to-jsx/tests/gt-lt-entities/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-gt-lt-entities.output.js rename to codemods/create-element-to-jsx/tests/gt-lt-entities/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-gt-lt-entities.input.js b/codemods/create-element-to-jsx/tests/gt-lt-entities/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-gt-lt-entities.input.js rename to codemods/create-element-to-jsx/tests/gt-lt-entities/input.tsx diff --git a/codemods/create-element-to-jsx/tests/gt-lt-entities/metrics.json b/codemods/create-element-to-jsx/tests/gt-lt-entities/metrics.json new file mode 100644 index 0000000..359e1f6 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/gt-lt-entities/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/gt-lt-entities/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-ignore-bad-capitalization.output.js b/codemods/create-element-to-jsx/tests/ignore-bad-capitalization/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-ignore-bad-capitalization.output.js rename to codemods/create-element-to-jsx/tests/ignore-bad-capitalization/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-ignore-bad-capitalization.input.js b/codemods/create-element-to-jsx/tests/ignore-bad-capitalization/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-ignore-bad-capitalization.input.js rename to codemods/create-element-to-jsx/tests/ignore-bad-capitalization/input.tsx diff --git a/codemods/create-element-to-jsx/tests/ignore-bad-capitalization/metrics.json b/codemods/create-element-to-jsx/tests/ignore-bad-capitalization/metrics.json new file mode 100644 index 0000000..d414e38 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/ignore-bad-capitalization/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/ignore-bad-capitalization/input.tsx" + }, + "count": 5 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-prop.output.js b/codemods/create-element-to-jsx/tests/literal-prop/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-prop.output.js rename to codemods/create-element-to-jsx/tests/literal-prop/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-prop.input.js b/codemods/create-element-to-jsx/tests/literal-prop/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-prop.input.js rename to codemods/create-element-to-jsx/tests/literal-prop/input.tsx diff --git a/codemods/create-element-to-jsx/tests/literal-prop/metrics.json b/codemods/create-element-to-jsx/tests/literal-prop/metrics.json new file mode 100644 index 0000000..d9ec56c --- /dev/null +++ b/codemods/create-element-to-jsx/tests/literal-prop/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/literal-prop/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-spacing.output.js b/codemods/create-element-to-jsx/tests/literal-spacing/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-spacing.output.js rename to codemods/create-element-to-jsx/tests/literal-spacing/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-spacing.input.js b/codemods/create-element-to-jsx/tests/literal-spacing/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-spacing.input.js rename to codemods/create-element-to-jsx/tests/literal-spacing/input.tsx diff --git a/codemods/create-element-to-jsx/tests/literal-spacing/metrics.json b/codemods/create-element-to-jsx/tests/literal-spacing/metrics.json new file mode 100644 index 0000000..5b0df95 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/literal-spacing/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/literal-spacing/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/create-element-to-jsx/tests/member-child-comments-alt/expected.tsx b/codemods/create-element-to-jsx/tests/member-child-comments-alt/expected.tsx new file mode 100644 index 0000000..4d793b3 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/member-child-comments-alt/expected.tsx @@ -0,0 +1,4 @@ +var React = require('react'); +
+ { /*E*//*A*//*D*//*B*//*C*//*A*//*D*//*B*//*C*//*B*//*C*/} +
; diff --git a/codemods/create-element-to-jsx/tests/member-child-comments-alt/input.tsx b/codemods/create-element-to-jsx/tests/member-child-comments-alt/input.tsx new file mode 100644 index 0000000..067f04a --- /dev/null +++ b/codemods/create-element-to-jsx/tests/member-child-comments-alt/input.tsx @@ -0,0 +1,2 @@ +var React = require('react'); +React.createElement('div', null, React.createElement(/*A*/Foo/*B*/./*C*/Bar/*D*/) /*E*/); diff --git a/codemods/create-element-to-jsx/tests/member-child-comments-alt/metrics.json b/codemods/create-element-to-jsx/tests/member-child-comments-alt/metrics.json new file mode 100644 index 0000000..990a6a2 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/member-child-comments-alt/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/member-child-comments-alt/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-member-expression-as-prop.output.js b/codemods/create-element-to-jsx/tests/member-expression-as-prop/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-member-expression-as-prop.output.js rename to codemods/create-element-to-jsx/tests/member-expression-as-prop/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-member-expression-as-prop.input.js b/codemods/create-element-to-jsx/tests/member-expression-as-prop/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-member-expression-as-prop.input.js rename to codemods/create-element-to-jsx/tests/member-expression-as-prop/input.tsx diff --git a/codemods/create-element-to-jsx/tests/member-expression-as-prop/metrics.json b/codemods/create-element-to-jsx/tests/member-expression-as-prop/metrics.json new file mode 100644 index 0000000..76d7ff6 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/member-expression-as-prop/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/member-expression-as-prop/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/create-element-to-jsx/tests/member-simple-comments/expected.tsx b/codemods/create-element-to-jsx/tests/member-simple-comments/expected.tsx new file mode 100644 index 0000000..db7c05f --- /dev/null +++ b/codemods/create-element-to-jsx/tests/member-simple-comments/expected.tsx @@ -0,0 +1,2 @@ +var React = require('react'); +/*A*//*D*//*B*//*C*/; diff --git a/codemods/create-element-to-jsx/tests/member-simple-comments/input.tsx b/codemods/create-element-to-jsx/tests/member-simple-comments/input.tsx new file mode 100644 index 0000000..f3c8928 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/member-simple-comments/input.tsx @@ -0,0 +1,2 @@ +var React = require('react'); +React.createElement(/*A*/Foo/*B*/./*C*/Bar/*D*/); diff --git a/codemods/create-element-to-jsx/tests/member-simple-comments/metrics.json b/codemods/create-element-to-jsx/tests/member-simple-comments/metrics.json new file mode 100644 index 0000000..f78cdca --- /dev/null +++ b/codemods/create-element-to-jsx/tests/member-simple-comments/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/member-simple-comments/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-props-arg.output.js b/codemods/create-element-to-jsx/tests/no-props-arg/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-props-arg.output.js rename to codemods/create-element-to-jsx/tests/no-props-arg/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-props-arg.input.js b/codemods/create-element-to-jsx/tests/no-props-arg/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-props-arg.input.js rename to codemods/create-element-to-jsx/tests/no-props-arg/input.tsx diff --git a/codemods/create-element-to-jsx/tests/no-props-arg/metrics.json b/codemods/create-element-to-jsx/tests/no-props-arg/metrics.json new file mode 100644 index 0000000..0eed063 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/no-props-arg/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/no-props-arg/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/create-element-to-jsx/tests/no-react-valid-explicit-require/expected.tsx b/codemods/create-element-to-jsx/tests/no-react-valid-explicit-require/expected.tsx new file mode 100644 index 0000000..8fb7b0c --- /dev/null +++ b/codemods/create-element-to-jsx/tests/no-react-valid-explicit-require/expected.tsx @@ -0,0 +1,3 @@ +var React = require('foo'); + +; diff --git a/codemods/create-element-to-jsx/tests/no-react-valid-explicit-require/input.tsx b/codemods/create-element-to-jsx/tests/no-react-valid-explicit-require/input.tsx new file mode 100644 index 0000000..789178e --- /dev/null +++ b/codemods/create-element-to-jsx/tests/no-react-valid-explicit-require/input.tsx @@ -0,0 +1,3 @@ +var React = require('foo'); + +React.createElement(Foo, null); diff --git a/codemods/create-element-to-jsx/tests/no-react-valid-explicit-require/metrics.json b/codemods/create-element-to-jsx/tests/no-react-valid-explicit-require/metrics.json new file mode 100644 index 0000000..cccf0c4 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/no-react-valid-explicit-require/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/no-react-valid-explicit-require/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/create-element-to-jsx/tests/no-react-valid-explicit-require/test.config.json b/codemods/create-element-to-jsx/tests/no-react-valid-explicit-require/test.config.json new file mode 100644 index 0000000..4858435 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/no-react-valid-explicit-require/test.config.json @@ -0,0 +1,5 @@ +{ + "params": { + "explicit-require": false + } +} diff --git a/codemods/create-element-to-jsx/tests/no-react-valid/expected.tsx b/codemods/create-element-to-jsx/tests/no-react-valid/expected.tsx new file mode 100644 index 0000000..789178e --- /dev/null +++ b/codemods/create-element-to-jsx/tests/no-react-valid/expected.tsx @@ -0,0 +1,3 @@ +var React = require('foo'); + +React.createElement(Foo, null); diff --git a/codemods/create-element-to-jsx/tests/no-react-valid/input.tsx b/codemods/create-element-to-jsx/tests/no-react-valid/input.tsx new file mode 100644 index 0000000..789178e --- /dev/null +++ b/codemods/create-element-to-jsx/tests/no-react-valid/input.tsx @@ -0,0 +1,3 @@ +var React = require('foo'); + +React.createElement(Foo, null); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-react.input.js b/codemods/create-element-to-jsx/tests/no-react/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-react.input.js rename to codemods/create-element-to-jsx/tests/no-react/expected.tsx diff --git a/codemods/create-element-to-jsx/tests/no-react/input.tsx b/codemods/create-element-to-jsx/tests/no-react/input.tsx new file mode 100644 index 0000000..aa81e70 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/no-react/input.tsx @@ -0,0 +1,3 @@ +var React = require('foo'); + +React.createElement(Foo, 'la'); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-object-assign.output.js b/codemods/create-element-to-jsx/tests/object-assign/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-object-assign.output.js rename to codemods/create-element-to-jsx/tests/object-assign/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-object-assign.input.js b/codemods/create-element-to-jsx/tests/object-assign/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-object-assign.input.js rename to codemods/create-element-to-jsx/tests/object-assign/input.tsx diff --git a/codemods/create-element-to-jsx/tests/object-assign/metrics.json b/codemods/create-element-to-jsx/tests/object-assign/metrics.json new file mode 100644 index 0000000..09a364c --- /dev/null +++ b/codemods/create-element-to-jsx/tests/object-assign/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/object-assign/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-preserve-comments.output.js b/codemods/create-element-to-jsx/tests/preserve-comments/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-preserve-comments.output.js rename to codemods/create-element-to-jsx/tests/preserve-comments/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-preserve-comments.input.js b/codemods/create-element-to-jsx/tests/preserve-comments/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-preserve-comments.input.js rename to codemods/create-element-to-jsx/tests/preserve-comments/input.tsx diff --git a/codemods/create-element-to-jsx/tests/preserve-comments/metrics.json b/codemods/create-element-to-jsx/tests/preserve-comments/metrics.json new file mode 100644 index 0000000..dfde4e1 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/preserve-comments/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/preserve-comments/input.tsx" + }, + "count": 8 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-array.output.js b/codemods/create-element-to-jsx/tests/props-array/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-array.output.js rename to codemods/create-element-to-jsx/tests/props-array/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-array.input.js b/codemods/create-element-to-jsx/tests/props-array/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-array.input.js rename to codemods/create-element-to-jsx/tests/props-array/input.tsx diff --git a/codemods/create-element-to-jsx/tests/props-array/metrics.json b/codemods/create-element-to-jsx/tests/props-array/metrics.json new file mode 100644 index 0000000..666ddb5 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/props-array/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/props-array/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-boolean.output.js b/codemods/create-element-to-jsx/tests/props-boolean/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-boolean.output.js rename to codemods/create-element-to-jsx/tests/props-boolean/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-boolean.input.js b/codemods/create-element-to-jsx/tests/props-boolean/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-boolean.input.js rename to codemods/create-element-to-jsx/tests/props-boolean/input.tsx diff --git a/codemods/create-element-to-jsx/tests/props-boolean/metrics.json b/codemods/create-element-to-jsx/tests/props-boolean/metrics.json new file mode 100644 index 0000000..5cf9562 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/props-boolean/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/props-boolean/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props.output.js b/codemods/create-element-to-jsx/tests/props/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props.output.js rename to codemods/create-element-to-jsx/tests/props/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props.input.js b/codemods/create-element-to-jsx/tests/props/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props.input.js rename to codemods/create-element-to-jsx/tests/props/input.tsx diff --git a/codemods/create-element-to-jsx/tests/props/metrics.json b/codemods/create-element-to-jsx/tests/props/metrics.json new file mode 100644 index 0000000..e85cae5 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/props/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/props/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-react-spread.output.js b/codemods/create-element-to-jsx/tests/react-spread/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-react-spread.output.js rename to codemods/create-element-to-jsx/tests/react-spread/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-react-spread.input.js b/codemods/create-element-to-jsx/tests/react-spread/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-react-spread.input.js rename to codemods/create-element-to-jsx/tests/react-spread/input.tsx diff --git a/codemods/create-element-to-jsx/tests/react-spread/metrics.json b/codemods/create-element-to-jsx/tests/react-spread/metrics.json new file mode 100644 index 0000000..af33fc4 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/react-spread/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/react-spread/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-single-element.output.js b/codemods/create-element-to-jsx/tests/single-element/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-single-element.output.js rename to codemods/create-element-to-jsx/tests/single-element/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-single-element.input.js b/codemods/create-element-to-jsx/tests/single-element/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-single-element.input.js rename to codemods/create-element-to-jsx/tests/single-element/input.tsx diff --git a/codemods/create-element-to-jsx/tests/single-element/metrics.json b/codemods/create-element-to-jsx/tests/single-element/metrics.json new file mode 100644 index 0000000..3dc19f2 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/single-element/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/single-element/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/create-element-to-jsx/tests/spread-prop-comments/expected.tsx b/codemods/create-element-to-jsx/tests/spread-prop-comments/expected.tsx new file mode 100644 index 0000000..3a8cec3 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/spread-prop-comments/expected.tsx @@ -0,0 +1,4 @@ +var React = require('react'); +
+ {/*B*/'x'} +
; diff --git a/codemods/create-element-to-jsx/tests/spread-prop-comments/input.tsx b/codemods/create-element-to-jsx/tests/spread-prop-comments/input.tsx new file mode 100644 index 0000000..95b2b96 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/spread-prop-comments/input.tsx @@ -0,0 +1,2 @@ +var React = require('react'); +React.createElement('div', /*A*/getProps()/*B*/, 'x'); diff --git a/codemods/create-element-to-jsx/tests/spread-prop-comments/metrics.json b/codemods/create-element-to-jsx/tests/spread-prop-comments/metrics.json new file mode 100644 index 0000000..e5b890a --- /dev/null +++ b/codemods/create-element-to-jsx/tests/spread-prop-comments/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/spread-prop-comments/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread-props.output.js b/codemods/create-element-to-jsx/tests/spread-props/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread-props.output.js rename to codemods/create-element-to-jsx/tests/spread-props/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread-props.input.js b/codemods/create-element-to-jsx/tests/spread-props/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread-props.input.js rename to codemods/create-element-to-jsx/tests/spread-props/input.tsx diff --git a/codemods/create-element-to-jsx/tests/spread-props/metrics.json b/codemods/create-element-to-jsx/tests/spread-props/metrics.json new file mode 100644 index 0000000..09ceb40 --- /dev/null +++ b/codemods/create-element-to-jsx/tests/spread-props/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/spread-props/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread.output.js b/codemods/create-element-to-jsx/tests/spread/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread.output.js rename to codemods/create-element-to-jsx/tests/spread/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread.input.js b/codemods/create-element-to-jsx/tests/spread/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread.input.js rename to codemods/create-element-to-jsx/tests/spread/input.tsx diff --git a/codemods/create-element-to-jsx/tests/spread/metrics.json b/codemods/create-element-to-jsx/tests/spread/metrics.json new file mode 100644 index 0000000..5fd18ec --- /dev/null +++ b/codemods/create-element-to-jsx/tests/spread/metrics.json @@ -0,0 +1,10 @@ +{ + "create-element-to-jsx-conversions": [ + { + "cardinality": { + "file": "tests/spread/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tsconfig.json b/codemods/create-element-to-jsx/tsconfig.json similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tsconfig.json rename to codemods/create-element-to-jsx/tsconfig.json diff --git a/codemods/jssg/replace-reactdom-render/workflow.yaml b/codemods/create-element-to-jsx/workflow.yaml similarity index 100% rename from codemods/jssg/replace-reactdom-render/workflow.yaml rename to codemods/create-element-to-jsx/workflow.yaml diff --git a/codemods/error-boundaries/README.md b/codemods/error-boundaries/README.md new file mode 100644 index 0000000..f02bcf7 --- /dev/null +++ b/codemods/error-boundaries/README.md @@ -0,0 +1,9 @@ +# error-boundaries + +Rename `unstable_handleError` to `componentDidCatch` in React class components and `createClass` component definitions. + +## Usage + +```bash +npx codemod @react-new/error-boundaries --target +``` diff --git a/codemods/error-boundaries/codemod.yaml b/codemods/error-boundaries/codemod.yaml new file mode 100644 index 0000000..29cc2e7 --- /dev/null +++ b/codemods/error-boundaries/codemod.yaml @@ -0,0 +1,19 @@ +schema_version: "1.0" + +name: "@react-new/error-boundaries" +version: "0.1.1" +description: "Rename unstable_handleError to componentDidCatch" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + +targets: + languages: ["tsx"] + +keywords: ["React"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/error-boundaries/package.json b/codemods/error-boundaries/package.json new file mode 100644 index 0000000..b467f97 --- /dev/null +++ b/codemods/error-boundaries/package.json @@ -0,0 +1,15 @@ +{ + "name": "@react-new/error-boundaries", + "version": "0.1.1", + "description": "Rename unstable_handleError to componentDidCatch", + "type": "module", + "scripts": { + "test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@codemod.com/jssg-types": "latest", + "typescript": "latest", + "@types/node": "latest" + } +} diff --git a/codemods/error-boundaries/scripts/codemod.ts b/codemods/error-boundaries/scripts/codemod.ts new file mode 100644 index 0000000..79c8434 --- /dev/null +++ b/codemods/error-boundaries/scripts/codemod.ts @@ -0,0 +1,90 @@ +import type { Transform, Edit, SgNode } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { useMetricAtom } from "codemod:metrics"; + +function metricFile(filename: string): string { + const cwd = process.cwd() + "/"; + return filename.startsWith(cwd) ? filename.slice(cwd.length) : filename; +} + + +const transform: Transform = async (root) => { + const rootNode = root.root(); + const edits: Edit[] = []; + + const renameMetric = useMetricAtom("error-boundary-renames"); + + const classLifecycleNames = rootNode.findAll({ + rule: { + kind: "property_identifier", + regex: "^unstable_handleError$", + inside: { + any: [ + { + kind: "method_definition", + inside: { kind: "class_body" }, + }, + { + kind: "public_field_definition", + inside: { kind: "class_body" }, + }, + ], + }, + }, + }); + + const createClassCalls = rootNode.findAll({ + rule: { + kind: "call_expression", + has: { + field: "function", + kind: "identifier", + regex: "^(createReactClass|createClass)$", + }, + }, + }); + + const createClassLifecycleNames: SgNode[] = []; + for (const call of createClassCalls) { + const args = call.field("arguments"); + if (!args) continue; + const argList = args.children().filter( + (c) => c.kind() !== "(" && c.kind() !== ")" && c.kind() !== ",", + ); + const configObj = argList[0]; + if (configObj) { + createClassLifecycleNames.push( + ...configObj.findAll({ + rule: { + kind: "property_identifier" as const, + regex: "^unstable_handleError$", + }, + }), + ); + } + } + + for (const node of classLifecycleNames) { + edits.push(node.replace("componentDidCatch")); + } + for (const node of createClassLifecycleNames) { + edits.push(node.replace("componentDidCatch")); + } + + if (edits.length > 0) { + renameMetric.increment( + { + file: metricFile(root.filename()), + }, + edits.length, + ); + } + + if (edits.length === 0) { + return null; + } + + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/legacy/transforms/__testfixtures__/error-boundaries/class-component.output.js b/codemods/error-boundaries/tests/class-component/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/error-boundaries/class-component.output.js rename to codemods/error-boundaries/tests/class-component/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/error-boundaries/class-component.input.js b/codemods/error-boundaries/tests/class-component/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/error-boundaries/class-component.input.js rename to codemods/error-boundaries/tests/class-component/input.tsx diff --git a/codemods/error-boundaries/tests/class-component/metrics.json b/codemods/error-boundaries/tests/class-component/metrics.json new file mode 100644 index 0000000..ce4a6fe --- /dev/null +++ b/codemods/error-boundaries/tests/class-component/metrics.json @@ -0,0 +1,10 @@ +{ + "error-boundary-renames": [ + { + "cardinality": { + "file": "tests/class-component/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/error-boundaries/create-class-component.output.js b/codemods/error-boundaries/tests/create-class-component/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/error-boundaries/create-class-component.output.js rename to codemods/error-boundaries/tests/create-class-component/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/error-boundaries/create-class-component.input.js b/codemods/error-boundaries/tests/create-class-component/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/error-boundaries/create-class-component.input.js rename to codemods/error-boundaries/tests/create-class-component/input.tsx diff --git a/codemods/error-boundaries/tests/create-class-component/metrics.json b/codemods/error-boundaries/tests/create-class-component/metrics.json new file mode 100644 index 0000000..21136e9 --- /dev/null +++ b/codemods/error-boundaries/tests/create-class-component/metrics.json @@ -0,0 +1,10 @@ +{ + "error-boundary-renames": [ + { + "cardinality": { + "file": "tests/create-class-component/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-act-import/tsconfig.json b/codemods/error-boundaries/tsconfig.json similarity index 100% rename from codemods/jssg/replace-act-import/tsconfig.json rename to codemods/error-boundaries/tsconfig.json diff --git a/codemods/jssg/replace-string-ref/workflow.yaml b/codemods/error-boundaries/workflow.yaml similarity index 100% rename from codemods/jssg/replace-string-ref/workflow.yaml rename to codemods/error-boundaries/workflow.yaml diff --git a/codemods/find-dom-node/README.md b/codemods/find-dom-node/README.md new file mode 100644 index 0000000..35c11df --- /dev/null +++ b/codemods/find-dom-node/README.md @@ -0,0 +1,13 @@ +# find-dom-node + +Replace `.getDOMNode()` calls with `React.findDOMNode(...)`. + +## Usage + +```bash +npx codemod @react-new/find-dom-node --target +``` + +## Options + +- `explicit-require`: when `false`, run even if no React import/require is present. Default: `true`. diff --git a/codemods/find-dom-node/codemod.yaml b/codemods/find-dom-node/codemod.yaml new file mode 100644 index 0000000..4ae3da0 --- /dev/null +++ b/codemods/find-dom-node/codemod.yaml @@ -0,0 +1,19 @@ +schema_version: "1.0" + +name: "@react-new/find-dom-node" +version: "0.1.1" +description: "Replace getDOMNode() with React.findDOMNode()" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + +targets: + languages: ["tsx"] + +keywords: ["React"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/find-dom-node/package.json b/codemods/find-dom-node/package.json new file mode 100644 index 0000000..9174401 --- /dev/null +++ b/codemods/find-dom-node/package.json @@ -0,0 +1,18 @@ +{ + "name": "@react-new/find-dom-node", + "version": "0.1.1", + "description": "Replace getDOMNode() with React.findDOMNode()", + "type": "module", + "scripts": { + "test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@codemod.com/jssg-types": "latest", + "typescript": "latest", + "@types/node": "latest" + }, + "dependencies": { + "@jssg/utils": "^0.0.2" + } +} diff --git a/codemods/find-dom-node/scripts/codemod.ts b/codemods/find-dom-node/scripts/codemod.ts new file mode 100644 index 0000000..b98dc0a --- /dev/null +++ b/codemods/find-dom-node/scripts/codemod.ts @@ -0,0 +1,175 @@ +import type { Transform, Edit, SgNode } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { useMetricAtom } from "codemod:metrics"; + +const REACT_MODULES = new Set(["React", "react", "react/addons", "react-native"]); + +function metricFile(filename: string): string { + const cwd = process.cwd() + "/"; + return filename.startsWith(cwd) ? filename.slice(cwd.length) : filename; +} + +function stringValue(node: SgNode): string | null { + const fragment = node.find({ rule: { kind: "string_fragment" } }); + if (fragment) return fragment.text(); + const text = node.text(); + return text.length >= 2 ? text.slice(1, -1) : null; +} + +function callArguments(call: SgNode): SgNode[] { + const args = call.field("arguments"); + if (!args) return []; + return args.children().filter((child) => child.isNamed() && child.kind() !== "comment"); +} + +function requireSource(call: SgNode): string | null { + const callee = call.field("function"); + if (!callee || callee.kind() !== "identifier" || callee.text() !== "require") return null; + const firstArg = callArguments(call)[0]; + return firstArg?.kind() === "string" ? stringValue(firstArg) : null; +} + +function importSource(node: SgNode): string | null { + const source = node.field("source") ?? node.find({ rule: { kind: "string" } }); + return source ? stringValue(source) : null; +} + +function hasReact(rootNode: SgNode): boolean { + for (const importNode of rootNode.findAll({ rule: { kind: "import_statement" } })) { + if (REACT_MODULES.has(importSource(importNode) ?? "")) return true; + } + + for (const call of rootNode.findAll({ rule: { kind: "call_expression" } })) { + if (REACT_MODULES.has(requireSource(call) ?? "")) return true; + } + + return false; +} + +function explicitRequireDisabled(value: unknown): boolean { + return value === false || value === "false"; +} + +function isReactCreateClassCall(node: SgNode | null): node is SgNode { + if (!node || node.kind() !== "call_expression") return false; + const callee = node.field("function"); + return callee?.kind() === "member_expression" && + callee.field("object")?.kind() === "identifier" && + callee.field("object")?.text() === "React" && + callee.field("property")?.kind() === "property_identifier" && + callee.field("property")?.text() === "createClass"; +} + +function isModuleExportsMember(node: SgNode | null): boolean { + return node?.kind() === "member_expression" && + node.field("object")?.kind() === "identifier" && + node.field("object")?.text() === "module" && + node.field("property")?.kind() === "property_identifier" && + node.field("property")?.text() === "exports"; +} + +function createClassContainers(rootNode: SgNode): SgNode[] { + const containers: SgNode[] = []; + + for (const declarator of rootNode.findAll({ rule: { kind: "variable_declarator" } })) { + if (isReactCreateClassCall(declarator.field("value"))) containers.push(declarator); + } + + for (const assignment of rootNode.findAll({ rule: { kind: "assignment_expression" } })) { + if (isModuleExportsMember(assignment.field("left")) && isReactCreateClassCall(assignment.field("right"))) { + containers.push(assignment); + } + } + + return containers; +} + +function isThisRefsBase(node: SgNode | null): boolean { + return node?.kind() === "member_expression" && + node.field("object")?.kind() === "this" && + node.field("property")?.kind() === "property_identifier" && + node.field("property")?.text() === "refs"; +} + +function accessObject(node: SgNode | null): SgNode | null { + if (!node) return null; + if (node.kind() === "member_expression" || node.kind() === "subscript_expression") { + return node.field("object"); + } + return null; +} + +function isThisRefsChain(node: SgNode | null): boolean { + if (!node) return false; + const object = accessObject(node); + if (!object) return false; + if (isThisRefsBase(object)) return true; + return isThisRefsChain(object); +} + +function isDirectRefsAlias(node: SgNode | null): boolean { + if (!node) return false; + const object = accessObject(node); + return isThisRefsBase(object); +} + +function isGetDOMNodeCall(call: SgNode, objectMatcher: (node: SgNode | null) => boolean): boolean { + const callee = call.field("function"); + return callee?.kind() === "member_expression" && + callee.field("property")?.kind() === "property_identifier" && + callee.field("property")?.text() === "getDOMNode" && + objectMatcher(callee.field("object")); +} + +const transform: Transform = async (root, options) => { + const rootNode = root.root(); + if (!explicitRequireDisabled(options.params?.["explicit-require"]) && !hasReact(rootNode)) { + return null; + } + + const metric = useMetricAtom("find-dom-node-replacements"); + const edits: Edit[] = []; + let replacements = 0; + + for (const container of createClassContainers(rootNode)) { + for (const call of container.findAll({ rule: { kind: "call_expression" } })) { + if (isGetDOMNodeCall(call, (node) => node?.kind() === "this")) { + edits.push(call.replace("React.findDOMNode(this)")); + replacements++; + continue; + } + + if (isGetDOMNodeCall(call, isThisRefsChain)) { + const target = call.field("function")?.field("object"); + if (!target) continue; + edits.push(call.replace(`React.findDOMNode(${target.text()})`)); + replacements++; + } + } + + for (const declarator of container.findAll({ rule: { kind: "variable_declarator" } })) { + const name = declarator.field("name"); + const value = declarator.field("value"); + if (name?.kind() !== "identifier" || !isDirectRefsAlias(value)) continue; + + const functionExpression = declarator.ancestors().find((ancestor) => ancestor.kind() === "function_expression"); + if (!functionExpression) continue; + + for (const call of functionExpression.findAll({ rule: { kind: "call_expression" } })) { + if (!isGetDOMNodeCall(call, (node) => node?.kind() === "identifier" && node.text() === name.text())) { + continue; + } + + edits.push(call.replace(`React.findDOMNode(${name.text()})`)); + replacements++; + } + } + } + + if (edits.length === 0) return null; + + metric.increment({ file: metricFile(root.filename()) }, replacements); + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/find-dom-node/tests/computed-ref-alias/expected.tsx b/codemods/find-dom-node/tests/computed-ref-alias/expected.tsx new file mode 100644 index 0000000..b66b1ac --- /dev/null +++ b/codemods/find-dom-node/tests/computed-ref-alias/expected.tsx @@ -0,0 +1,8 @@ +var React = require('React'); +var C = React.createClass({ + render: function() { + var ref = 'foo'; + var thing = this.refs[ref]; + return React.findDOMNode(thing); + } +}); diff --git a/codemods/find-dom-node/tests/computed-ref-alias/input.tsx b/codemods/find-dom-node/tests/computed-ref-alias/input.tsx new file mode 100644 index 0000000..cd5129a --- /dev/null +++ b/codemods/find-dom-node/tests/computed-ref-alias/input.tsx @@ -0,0 +1,8 @@ +var React = require('React'); +var C = React.createClass({ + render: function() { + var ref = 'foo'; + var thing = this.refs[ref]; + return thing.getDOMNode(); + } +}); diff --git a/codemods/find-dom-node/tests/computed-ref-alias/metrics.json b/codemods/find-dom-node/tests/computed-ref-alias/metrics.json new file mode 100644 index 0000000..fe6a6e0 --- /dev/null +++ b/codemods/find-dom-node/tests/computed-ref-alias/metrics.json @@ -0,0 +1,10 @@ +{ + "find-dom-node-replacements": [ + { + "cardinality": { + "file": "tests/computed-ref-alias/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/find-dom-node/tests/deep-refs-chain/expected.tsx b/codemods/find-dom-node/tests/deep-refs-chain/expected.tsx new file mode 100644 index 0000000..dee893c --- /dev/null +++ b/codemods/find-dom-node/tests/deep-refs-chain/expected.tsx @@ -0,0 +1,6 @@ +var React = require('React'); +var C = React.createClass({ + render: function() { + return React.findDOMNode(this.refs.main.refs.list); + } +}); diff --git a/codemods/find-dom-node/tests/deep-refs-chain/input.tsx b/codemods/find-dom-node/tests/deep-refs-chain/input.tsx new file mode 100644 index 0000000..7f94962 --- /dev/null +++ b/codemods/find-dom-node/tests/deep-refs-chain/input.tsx @@ -0,0 +1,6 @@ +var React = require('React'); +var C = React.createClass({ + render: function() { + return this.refs.main.refs.list.getDOMNode(); + } +}); diff --git a/codemods/find-dom-node/tests/deep-refs-chain/metrics.json b/codemods/find-dom-node/tests/deep-refs-chain/metrics.json new file mode 100644 index 0000000..ae90bb8 --- /dev/null +++ b/codemods/find-dom-node/tests/deep-refs-chain/metrics.json @@ -0,0 +1,10 @@ +{ + "find-dom-node-replacements": [ + { + "cardinality": { + "file": "tests/deep-refs-chain/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/find-dom-node/tests/es6-class-no-change/expected.tsx b/codemods/find-dom-node/tests/es6-class-no-change/expected.tsx new file mode 100644 index 0000000..464aee7 --- /dev/null +++ b/codemods/find-dom-node/tests/es6-class-no-change/expected.tsx @@ -0,0 +1,7 @@ +var React = require('React'); + +class C extends React.Component { + render() { + return this.getDOMNode(); + } +} diff --git a/codemods/find-dom-node/tests/es6-class-no-change/input.tsx b/codemods/find-dom-node/tests/es6-class-no-change/input.tsx new file mode 100644 index 0000000..464aee7 --- /dev/null +++ b/codemods/find-dom-node/tests/es6-class-no-change/input.tsx @@ -0,0 +1,7 @@ +var React = require('React'); + +class C extends React.Component { + render() { + return this.getDOMNode(); + } +} diff --git a/codemods/find-dom-node/tests/export-default-no-change/expected.tsx b/codemods/find-dom-node/tests/export-default-no-change/expected.tsx new file mode 100644 index 0000000..97fa6eb --- /dev/null +++ b/codemods/find-dom-node/tests/export-default-no-change/expected.tsx @@ -0,0 +1,7 @@ +var React = require('React'); + +export default React.createClass({ + render: function() { + return this.getDOMNode(); + }, +}); diff --git a/codemods/find-dom-node/tests/export-default-no-change/input.tsx b/codemods/find-dom-node/tests/export-default-no-change/input.tsx new file mode 100644 index 0000000..97fa6eb --- /dev/null +++ b/codemods/find-dom-node/tests/export-default-no-change/input.tsx @@ -0,0 +1,7 @@ +var React = require('React'); + +export default React.createClass({ + render: function() { + return this.getDOMNode(); + }, +}); diff --git a/codemods/legacy/transforms/__testfixtures__/findDOMNode.output.js b/codemods/find-dom-node/tests/findDOMNode/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/findDOMNode.output.js rename to codemods/find-dom-node/tests/findDOMNode/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/findDOMNode.input.js b/codemods/find-dom-node/tests/findDOMNode/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/findDOMNode.input.js rename to codemods/find-dom-node/tests/findDOMNode/input.tsx diff --git a/codemods/find-dom-node/tests/findDOMNode/metrics.json b/codemods/find-dom-node/tests/findDOMNode/metrics.json new file mode 100644 index 0000000..960ef58 --- /dev/null +++ b/codemods/find-dom-node/tests/findDOMNode/metrics.json @@ -0,0 +1,10 @@ +{ + "find-dom-node-replacements": [ + { + "cardinality": { + "file": "tests/findDOMNode/input.tsx" + }, + "count": 5 + } + ] +} \ No newline at end of file diff --git a/codemods/find-dom-node/tests/helper-no-change/expected.tsx b/codemods/find-dom-node/tests/helper-no-change/expected.tsx new file mode 100644 index 0000000..281d7fa --- /dev/null +++ b/codemods/find-dom-node/tests/helper-no-change/expected.tsx @@ -0,0 +1,2 @@ +const helper = foo(); +helper.getDOMNode(); diff --git a/codemods/find-dom-node/tests/helper-no-change/input.tsx b/codemods/find-dom-node/tests/helper-no-change/input.tsx new file mode 100644 index 0000000..281d7fa --- /dev/null +++ b/codemods/find-dom-node/tests/helper-no-change/input.tsx @@ -0,0 +1,2 @@ +const helper = foo(); +helper.getDOMNode(); diff --git a/codemods/find-dom-node/tests/module-exports/expected.tsx b/codemods/find-dom-node/tests/module-exports/expected.tsx new file mode 100644 index 0000000..cab9f4c --- /dev/null +++ b/codemods/find-dom-node/tests/module-exports/expected.tsx @@ -0,0 +1,7 @@ +var React = require('React'); + +module.exports = React.createClass({ + render: function() { + return React.findDOMNode(this); + }, +}); diff --git a/codemods/find-dom-node/tests/module-exports/input.tsx b/codemods/find-dom-node/tests/module-exports/input.tsx new file mode 100644 index 0000000..9658cf5 --- /dev/null +++ b/codemods/find-dom-node/tests/module-exports/input.tsx @@ -0,0 +1,7 @@ +var React = require('React'); + +module.exports = React.createClass({ + render: function() { + return this.getDOMNode(); + }, +}); diff --git a/codemods/find-dom-node/tests/module-exports/metrics.json b/codemods/find-dom-node/tests/module-exports/metrics.json new file mode 100644 index 0000000..42a4df1 --- /dev/null +++ b/codemods/find-dom-node/tests/module-exports/metrics.json @@ -0,0 +1,10 @@ +{ + "find-dom-node-replacements": [ + { + "cardinality": { + "file": "tests/module-exports/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/find-dom-node/tests/no-react-create-class-explicit-require/expected.tsx b/codemods/find-dom-node/tests/no-react-create-class-explicit-require/expected.tsx new file mode 100644 index 0000000..dd2e29f --- /dev/null +++ b/codemods/find-dom-node/tests/no-react-create-class-explicit-require/expected.tsx @@ -0,0 +1,5 @@ +var Composer = React.createClass({ + render: function() { + return React.findDOMNode(this); + }, +}); diff --git a/codemods/find-dom-node/tests/no-react-create-class-explicit-require/input.tsx b/codemods/find-dom-node/tests/no-react-create-class-explicit-require/input.tsx new file mode 100644 index 0000000..8b8bbf5 --- /dev/null +++ b/codemods/find-dom-node/tests/no-react-create-class-explicit-require/input.tsx @@ -0,0 +1,5 @@ +var Composer = React.createClass({ + render: function() { + return this.getDOMNode(); + }, +}); diff --git a/codemods/find-dom-node/tests/no-react-create-class-explicit-require/metrics.json b/codemods/find-dom-node/tests/no-react-create-class-explicit-require/metrics.json new file mode 100644 index 0000000..6b828be --- /dev/null +++ b/codemods/find-dom-node/tests/no-react-create-class-explicit-require/metrics.json @@ -0,0 +1,10 @@ +{ + "find-dom-node-replacements": [ + { + "cardinality": { + "file": "tests/no-react-create-class-explicit-require/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/find-dom-node/tests/no-react-create-class-explicit-require/test.config.json b/codemods/find-dom-node/tests/no-react-create-class-explicit-require/test.config.json new file mode 100644 index 0000000..4858435 --- /dev/null +++ b/codemods/find-dom-node/tests/no-react-create-class-explicit-require/test.config.json @@ -0,0 +1,5 @@ +{ + "params": { + "explicit-require": false + } +} diff --git a/codemods/find-dom-node/tests/no-react-create-class/expected.tsx b/codemods/find-dom-node/tests/no-react-create-class/expected.tsx new file mode 100644 index 0000000..8b8bbf5 --- /dev/null +++ b/codemods/find-dom-node/tests/no-react-create-class/expected.tsx @@ -0,0 +1,5 @@ +var Composer = React.createClass({ + render: function() { + return this.getDOMNode(); + }, +}); diff --git a/codemods/find-dom-node/tests/no-react-create-class/input.tsx b/codemods/find-dom-node/tests/no-react-create-class/input.tsx new file mode 100644 index 0000000..8b8bbf5 --- /dev/null +++ b/codemods/find-dom-node/tests/no-react-create-class/input.tsx @@ -0,0 +1,5 @@ +var Composer = React.createClass({ + render: function() { + return this.getDOMNode(); + }, +}); diff --git a/codemods/jssg/replace-reactdom-render/tsconfig.json b/codemods/find-dom-node/tsconfig.json similarity index 100% rename from codemods/jssg/replace-reactdom-render/tsconfig.json rename to codemods/find-dom-node/tsconfig.json diff --git a/codemods/jssg/use-context-hook/workflow.yaml b/codemods/find-dom-node/workflow.yaml similarity index 100% rename from codemods/jssg/use-context-hook/workflow.yaml rename to codemods/find-dom-node/workflow.yaml diff --git a/codemods/legacy/.babelrc b/codemods/legacy/.babelrc deleted file mode 100644 index 5295ecd..0000000 --- a/codemods/legacy/.babelrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "presets": ["@babel/preset-env"], - "plugins": ["@babel/plugin-proposal-object-rest-spread"] -} diff --git a/codemods/legacy/jest/require-actual.d.ts b/codemods/legacy/jest/require-actual.d.ts deleted file mode 100644 index 09957ab..0000000 --- a/codemods/legacy/jest/require-actual.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare namespace NodeJS { - interface Require { - requireActual(moduleName: string): unknown; - } -} diff --git a/codemods/legacy/jest/setup-require-actual.js b/codemods/legacy/jest/setup-require-actual.js deleted file mode 100644 index 81a0e55..0000000 --- a/codemods/legacy/jest/setup-require-actual.js +++ /dev/null @@ -1,6 +0,0 @@ -Object.defineProperty(Function.prototype, "requireActual", { - value: jest.requireActual, - configurable: true, - writable: true, -}); - diff --git a/codemods/legacy/package.json b/codemods/legacy/package.json deleted file mode 100644 index 09252c9..0000000 --- a/codemods/legacy/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "@react-codemod-preview/legacy", - "version": "0.1.0", - "private": true, - "description": "Legacy jscodeshift snapshot for non-priority react-codemod transforms", - "scripts": { - "test": "jest --runInBand", - "test:ci": "jest --runInBand transforms/__tests__/class-test.js transforms/__tests__/custom-sort-group.js transforms/__tests__/custom-sort.js transforms/__tests__/error-boundaries.js transforms/__tests__/findDOMNode-test.js transforms/__tests__/pure-render-mixin-test.js transforms/__tests__/react-to-react-dom-test.js transforms/__tests__/React-DOM-to-react-dom-factories-test.js transforms/__tests__/sort-comp-test.js" - }, - "jest": { - "globals": { - "baseDir": "../../" - }, - "setupFilesAfterEnv": [ - "/jest/setup-require-actual.js" - ], - "testEnvironment": "node", - "roots": [ - "transforms" - ], - "transform": { - "^.+\\.jsx?$": "babel-jest", - "^.+\\.tsx?$": "ts-jest" - } - }, - "dependencies": { - "jscodeshift": "^0.11.0" - }, - "devDependencies": { - "@babel/core": "^7.6.4", - "@babel/plugin-proposal-object-rest-spread": "^7.6.2", - "@babel/preset-env": "^7.6.3", - "@types/jest": "^24.9.0", - "babel-eslint": "^10.0.3", - "babel-jest": "^24.9.0", - "eslint": "^6.6.0", - "eslint-plugin-react": "^7.16.0", - "jest": "^24.9.0", - "ts-jest": "^24.3.0", - "typescript": "4.8.4" - } -} diff --git a/codemods/legacy/pnpm-lock.yaml b/codemods/legacy/pnpm-lock.yaml deleted file mode 100644 index 72e1c10..0000000 --- a/codemods/legacy/pnpm-lock.yaml +++ /dev/null @@ -1,6904 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - jscodeshift: - specifier: ^0.11.0 - version: 0.11.0(@babel/preset-env@7.29.2(@babel/core@7.29.0)) - devDependencies: - '@babel/core': - specifier: ^7.6.4 - version: 7.29.0 - '@babel/plugin-proposal-object-rest-spread': - specifier: ^7.6.2 - version: 7.20.7(@babel/core@7.29.0) - '@babel/preset-env': - specifier: ^7.6.3 - version: 7.29.2(@babel/core@7.29.0) - '@types/jest': - specifier: ^24.9.0 - version: 24.9.1 - babel-eslint: - specifier: ^10.0.3 - version: 10.1.0(eslint@6.8.0) - babel-jest: - specifier: ^24.9.0 - version: 24.9.0(@babel/core@7.29.0) - eslint: - specifier: ^6.6.0 - version: 6.8.0 - eslint-plugin-react: - specifier: ^7.16.0 - version: 7.37.5(eslint@6.8.0) - jest: - specifier: ^24.9.0 - version: 24.9.0 - ts-jest: - specifier: ^24.3.0 - version: 24.3.0(jest@24.9.0) - typescript: - specifier: 4.8.4 - version: 4.8.4 - -packages: - - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.29.0': - resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-annotate-as-pure@7.27.3': - resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-create-class-features-plugin@7.28.6': - resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-create-regexp-features-plugin@7.28.5': - resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-define-polyfill-provider@0.6.8': - resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-member-expression-to-functions@7.28.5': - resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-optimise-call-expression@7.27.1': - resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-plugin-utils@7.28.6': - resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} - engines: {node: '>=6.9.0'} - - '@babel/helper-remap-async-to-generator@7.27.1': - resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-replace-supers@7.28.6': - resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': - resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-wrap-function@7.28.6': - resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.29.2': - resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.29.2': - resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': - resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1': - resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1': - resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': - resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.13.0 - - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6': - resolution: {integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-proposal-class-properties@7.18.6': - resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6': - resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-proposal-object-rest-spread@7.20.7': - resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-proposal-optional-chaining@7.21.0': - resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': - resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-flow@7.28.6': - resolution: {integrity: sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-assertions@7.28.6': - resolution: {integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-attributes@7.28.6': - resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-jsx@7.28.6': - resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-object-rest-spread@7.8.3': - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-chaining@7.8.3': - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.28.6': - resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-unicode-sets-regex@7.18.6': - resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-transform-arrow-functions@7.27.1': - resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-async-generator-functions@7.29.0': - resolution: {integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-async-to-generator@7.28.6': - resolution: {integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-block-scoped-functions@7.27.1': - resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-block-scoping@7.28.6': - resolution: {integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-class-properties@7.28.6': - resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-class-static-block@7.28.6': - resolution: {integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.12.0 - - '@babel/plugin-transform-classes@7.28.6': - resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-computed-properties@7.28.6': - resolution: {integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-destructuring@7.28.5': - resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-dotall-regex@7.28.6': - resolution: {integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-duplicate-keys@7.27.1': - resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0': - resolution: {integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-transform-dynamic-import@7.27.1': - resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-explicit-resource-management@7.28.6': - resolution: {integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-exponentiation-operator@7.28.6': - resolution: {integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-export-namespace-from@7.27.1': - resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-flow-strip-types@7.27.1': - resolution: {integrity: sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-for-of@7.27.1': - resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-function-name@7.27.1': - resolution: {integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-json-strings@7.28.6': - resolution: {integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-literals@7.27.1': - resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-logical-assignment-operators@7.28.6': - resolution: {integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-member-expression-literals@7.27.1': - resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-amd@7.27.1': - resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-commonjs@7.28.6': - resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-systemjs@7.29.0': - resolution: {integrity: sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-umd@7.27.1': - resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-named-capturing-groups-regex@7.29.0': - resolution: {integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-transform-new-target@7.27.1': - resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-nullish-coalescing-operator@7.28.6': - resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-numeric-separator@7.28.6': - resolution: {integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-object-rest-spread@7.28.6': - resolution: {integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-object-super@7.27.1': - resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-optional-catch-binding@7.28.6': - resolution: {integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-optional-chaining@7.28.6': - resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-parameters@7.27.7': - resolution: {integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-private-methods@7.28.6': - resolution: {integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-private-property-in-object@7.28.6': - resolution: {integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-property-literals@7.27.1': - resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-regenerator@7.29.0': - resolution: {integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-regexp-modifiers@7.28.6': - resolution: {integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-transform-reserved-words@7.27.1': - resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-shorthand-properties@7.27.1': - resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-spread@7.28.6': - resolution: {integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-sticky-regex@7.27.1': - resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-template-literals@7.27.1': - resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-typeof-symbol@7.27.1': - resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-typescript@7.28.6': - resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-escapes@7.27.1': - resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-property-regex@7.28.6': - resolution: {integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-regex@7.27.1': - resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-sets-regex@7.28.6': - resolution: {integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/preset-env@7.29.2': - resolution: {integrity: sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/preset-flow@7.27.1': - resolution: {integrity: sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/preset-modules@0.1.6-no-external-plugins': - resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} - peerDependencies: - '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 - - '@babel/preset-typescript@7.28.5': - resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/register@7.28.6': - resolution: {integrity: sha512-pgcbbEl/dWQYb6L6Yew6F94rdwygfuv+vJ/tXfwIOYAfPB6TNWpXUMEtEq3YuTeHRdvMIhvz13bkT9CNaS+wqA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} - engines: {node: '>=6.9.0'} - - '@cnakazawa/watch@1.0.4': - resolution: {integrity: sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==} - engines: {node: '>=0.1.95'} - hasBin: true - - '@jest/console@24.9.0': - resolution: {integrity: sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==} - engines: {node: '>= 6'} - - '@jest/core@24.9.0': - resolution: {integrity: sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A==} - engines: {node: '>= 6'} - - '@jest/environment@24.9.0': - resolution: {integrity: sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==} - engines: {node: '>= 6'} - - '@jest/fake-timers@24.9.0': - resolution: {integrity: sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==} - engines: {node: '>= 6'} - - '@jest/reporters@24.9.0': - resolution: {integrity: sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==} - engines: {node: '>= 6'} - - '@jest/source-map@24.9.0': - resolution: {integrity: sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==} - engines: {node: '>= 6'} - - '@jest/test-result@24.9.0': - resolution: {integrity: sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==} - engines: {node: '>= 6'} - - '@jest/test-sequencer@24.9.0': - resolution: {integrity: sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==} - engines: {node: '>= 6'} - - '@jest/transform@24.9.0': - resolution: {integrity: sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==} - engines: {node: '>= 6'} - - '@jest/types@24.9.0': - resolution: {integrity: sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==} - engines: {node: '>= 6'} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.27.0': - resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.28.0': - resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} - - '@types/istanbul-lib-coverage@2.0.6': - resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - - '@types/istanbul-lib-report@3.0.3': - resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} - - '@types/istanbul-reports@1.1.2': - resolution: {integrity: sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==} - - '@types/jest@24.9.1': - resolution: {integrity: sha512-Fb38HkXSVA4L8fGKEZ6le5bB8r6MRWlOCZbVuWZcmOMSCd2wCYOwN1ibj8daIoV9naq7aaOZjrLCoCMptKU/4Q==} - - '@types/stack-utils@1.0.1': - resolution: {integrity: sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==} - - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - - '@types/yargs@13.0.12': - resolution: {integrity: sha512-qCxJE1qgz2y0hA4pIxjBR+PelCH0U5CK1XJXFwCNqfmliatKp47UCXXE9Dyk1OXBDLvsCF57TqQEJaeLfDYEOQ==} - - abab@2.0.6: - resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} - deprecated: Use your platform's native atob() and btoa() methods instead - - acorn-globals@4.3.4: - resolution: {integrity: sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==} - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn-walk@6.2.0: - resolution: {integrity: sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==} - engines: {node: '>=0.4.0'} - - acorn@5.7.4: - resolution: {integrity: sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==} - engines: {node: '>=0.4.0'} - hasBin: true - - acorn@6.4.2: - resolution: {integrity: sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==} - engines: {node: '>=0.4.0'} - hasBin: true - - acorn@7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} - engines: {node: '>=0.4.0'} - hasBin: true - - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - - ansi-escapes@3.2.0: - resolution: {integrity: sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==} - engines: {node: '>=4'} - - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - - ansi-regex@3.0.1: - resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==} - engines: {node: '>=4'} - - ansi-regex@4.1.1: - resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} - engines: {node: '>=6'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - anymatch@2.0.0: - resolution: {integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==} - - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - - arr-diff@4.0.0: - resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==} - engines: {node: '>=0.10.0'} - - arr-flatten@1.1.0: - resolution: {integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==} - engines: {node: '>=0.10.0'} - - arr-union@3.1.0: - resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==} - engines: {node: '>=0.10.0'} - - array-buffer-byte-length@1.0.2: - resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} - engines: {node: '>= 0.4'} - - array-equal@1.0.2: - resolution: {integrity: sha512-gUHx76KtnhEgB3HOuFYiCm3FIdEs6ocM2asHvNTkfu/Y09qQVrrVVaOKENmS2KkSaGoxgXNqC+ZVtR/n0MOkSA==} - - array-includes@3.1.9: - resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} - engines: {node: '>= 0.4'} - - array-unique@0.3.2: - resolution: {integrity: sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==} - engines: {node: '>=0.10.0'} - - array.prototype.findlast@1.2.5: - resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} - engines: {node: '>= 0.4'} - - array.prototype.flat@1.3.3: - resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} - engines: {node: '>= 0.4'} - - array.prototype.flatmap@1.3.3: - resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} - engines: {node: '>= 0.4'} - - array.prototype.reduce@1.0.8: - resolution: {integrity: sha512-DwuEqgXFBwbmZSRqt3BpQigWNUoqw9Ml2dTWdF3B2zQlQX4OeUE0zyuzX0fX0IbTvjdkZbcBTU3idgpO78qkTw==} - engines: {node: '>= 0.4'} - - array.prototype.tosorted@1.1.4: - resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} - engines: {node: '>= 0.4'} - - arraybuffer.prototype.slice@1.0.4: - resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} - engines: {node: '>= 0.4'} - - asn1@0.2.6: - resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} - - assert-plus@1.0.0: - resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} - engines: {node: '>=0.8'} - - assign-symbols@1.0.0: - resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} - engines: {node: '>=0.10.0'} - - ast-types@0.14.2: - resolution: {integrity: sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==} - engines: {node: '>=4'} - - astral-regex@1.0.0: - resolution: {integrity: sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==} - engines: {node: '>=4'} - - async-function@1.0.0: - resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} - engines: {node: '>= 0.4'} - - async-limiter@1.0.1: - resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} - - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - atob@2.1.2: - resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} - engines: {node: '>= 4.5.0'} - hasBin: true - - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} - - aws-sign2@0.7.0: - resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} - - aws4@1.13.2: - resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} - - babel-core@7.0.0-bridge.0: - resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - babel-eslint@10.1.0: - resolution: {integrity: sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==} - engines: {node: '>=6'} - deprecated: babel-eslint is now @babel/eslint-parser. This package will no longer receive updates. - peerDependencies: - eslint: '>= 4.12.1' - - babel-jest@24.9.0: - resolution: {integrity: sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==} - engines: {node: '>= 6'} - peerDependencies: - '@babel/core': ^7.0.0 - - babel-plugin-istanbul@5.2.0: - resolution: {integrity: sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==} - engines: {node: '>=6'} - - babel-plugin-jest-hoist@24.9.0: - resolution: {integrity: sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==} - engines: {node: '>= 6'} - - babel-plugin-polyfill-corejs2@0.4.17: - resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - - babel-plugin-polyfill-corejs3@0.14.2: - resolution: {integrity: sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - - babel-plugin-polyfill-regenerator@0.6.8: - resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - - babel-preset-jest@24.9.0: - resolution: {integrity: sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==} - engines: {node: '>= 6'} - peerDependencies: - '@babel/core': ^7.0.0 - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - base@0.11.2: - resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} - engines: {node: '>=0.10.0'} - - baseline-browser-mapping@2.10.19: - resolution: {integrity: sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==} - engines: {node: '>=6.0.0'} - hasBin: true - - bcrypt-pbkdf@1.0.2: - resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} - - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - - brace-expansion@1.1.14: - resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} - - braces@2.3.2: - resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==} - engines: {node: '>=0.10.0'} - - browser-process-hrtime@1.0.0: - resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} - - browser-resolve@1.11.3: - resolution: {integrity: sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==} - - browserslist@4.28.2: - resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - bs-logger@0.2.6: - resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} - engines: {node: '>= 6'} - - bser@2.1.1: - resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} - - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - cache-base@1.0.1: - resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} - engines: {node: '>=0.10.0'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bind@1.0.9: - resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - camelcase@4.1.0: - resolution: {integrity: sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==} - engines: {node: '>=4'} - - camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - - caniuse-lite@1.0.30001788: - resolution: {integrity: sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==} - - capture-exit@2.0.0: - resolution: {integrity: sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==} - engines: {node: 6.* || 8.* || >= 10.*} - - caseless@0.12.0: - resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} - - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - - ci-info@2.0.0: - resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} - - class-utils@0.3.6: - resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==} - engines: {node: '>=0.10.0'} - - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} - - cli-width@3.0.0: - resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} - engines: {node: '>= 10'} - - cliui@5.0.0: - resolution: {integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==} - - clone-deep@4.0.1: - resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} - engines: {node: '>=6'} - - co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - - collection-visit@1.0.0: - resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==} - engines: {node: '>=0.10.0'} - - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - colors@1.4.0: - resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} - engines: {node: '>=0.1.90'} - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - - component-emitter@1.3.1: - resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - copy-descriptor@0.1.1: - resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==} - engines: {node: '>=0.10.0'} - - core-js-compat@3.49.0: - resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} - - core-util-is@1.0.2: - resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} - - cross-spawn@6.0.6: - resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} - engines: {node: '>=4.8'} - - cssom@0.3.8: - resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} - - cssstyle@1.4.0: - resolution: {integrity: sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==} - - dashdash@1.14.1: - resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} - engines: {node: '>=0.10'} - - data-urls@1.1.0: - resolution: {integrity: sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==} - - data-view-buffer@1.0.2: - resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} - engines: {node: '>= 0.4'} - - data-view-byte-length@1.0.2: - resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} - engines: {node: '>= 0.4'} - - data-view-byte-offset@1.0.1: - resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} - engines: {node: '>= 0.4'} - - debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - - decode-uri-component@0.2.2: - resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} - engines: {node: '>=0.10'} - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} - - define-property@0.2.5: - resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==} - engines: {node: '>=0.10.0'} - - define-property@1.0.0: - resolution: {integrity: sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==} - engines: {node: '>=0.10.0'} - - define-property@2.0.2: - resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==} - engines: {node: '>=0.10.0'} - - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - detect-newline@2.1.0: - resolution: {integrity: sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==} - engines: {node: '>=0.10.0'} - - diff-sequences@24.9.0: - resolution: {integrity: sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==} - engines: {node: '>= 6'} - - doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - - domexception@1.0.1: - resolution: {integrity: sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==} - deprecated: Use your platform's native DOMException instead - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - ecc-jsbn@0.1.2: - resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} - - electron-to-chromium@1.5.336: - resolution: {integrity: sha512-AbH9q9J455r/nLmdNZes0G0ZKcRX73FicwowalLs6ijwOmCJSRRrLX63lcAlzy9ux3dWK1w1+1nsBJEWN11hcQ==} - - emoji-regex@7.0.3: - resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - - error-ex@1.3.4: - resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - - es-abstract@1.24.2: - resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} - engines: {node: '>= 0.4'} - - es-array-method-boxes-properly@1.0.0: - resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-iterator-helpers@1.3.2: - resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==} - engines: {node: '>= 0.4'} - - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - - es-shim-unscopables@1.1.0: - resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} - engines: {node: '>= 0.4'} - - es-to-primitive@1.3.0: - resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} - engines: {node: '>= 0.4'} - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - - escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - - escodegen@1.14.3: - resolution: {integrity: sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==} - engines: {node: '>=4.0'} - hasBin: true - - eslint-plugin-react@7.37.5: - resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - - eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} - - eslint-utils@1.4.3: - resolution: {integrity: sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==} - engines: {node: '>=6'} - - eslint-visitor-keys@1.3.0: - resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} - engines: {node: '>=4'} - - eslint@6.8.0: - resolution: {integrity: sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==} - engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. - hasBin: true - - espree@6.2.1: - resolution: {integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==} - engines: {node: '>=6.0.0'} - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - exec-sh@0.3.6: - resolution: {integrity: sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==} - - execa@1.0.0: - resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==} - engines: {node: '>=6'} - - exit@0.1.2: - resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} - engines: {node: '>= 0.8.0'} - - expand-brackets@2.1.4: - resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==} - engines: {node: '>=0.10.0'} - - expect@24.9.0: - resolution: {integrity: sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==} - engines: {node: '>= 6'} - - extend-shallow@2.0.1: - resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} - engines: {node: '>=0.10.0'} - - extend-shallow@3.0.2: - resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==} - engines: {node: '>=0.10.0'} - - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - - external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} - - extglob@2.0.4: - resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==} - engines: {node: '>=0.10.0'} - - extsprintf@1.3.0: - resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} - engines: {'0': node >=0.6.0} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fb-watchman@2.0.2: - resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} - - figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} - - file-entry-cache@5.0.1: - resolution: {integrity: sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==} - engines: {node: '>=4'} - - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - - fill-range@4.0.0: - resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==} - engines: {node: '>=0.10.0'} - - find-cache-dir@2.1.0: - resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} - engines: {node: '>=6'} - - find-up@3.0.0: - resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} - engines: {node: '>=6'} - - flat-cache@2.0.1: - resolution: {integrity: sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==} - engines: {node: '>=4'} - - flatted@2.0.2: - resolution: {integrity: sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==} - - flow-parser@0.309.0: - resolution: {integrity: sha512-poYRskeIXiHsE19Fb9sRE/CV7PYOq21j3lS5vKr27ujFBvSAhmCbbilAonJ0/u0Uai+Xgyq30/twHQeQc2Ngiw==} - engines: {node: '>=0.4.0'} - - for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} - - for-in@1.0.2: - resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} - engines: {node: '>=0.10.0'} - - forever-agent@0.6.1: - resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} - - form-data@2.3.3: - resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} - engines: {node: '>= 0.12'} - - fragment-cache@0.2.1: - resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==} - engines: {node: '>=0.10.0'} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - fsevents@1.2.13: - resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==} - engines: {node: '>= 4.0'} - os: [darwin] - deprecated: Upgrade to fsevents v2 to mitigate potential security issues - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} - engines: {node: '>= 0.4'} - - functional-red-black-tree@1.0.1: - resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} - - functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - - generator-function@2.0.1: - resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} - engines: {node: '>= 0.4'} - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - get-stream@4.1.0: - resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} - engines: {node: '>=6'} - - get-symbol-description@1.1.0: - resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} - engines: {node: '>= 0.4'} - - get-value@2.0.6: - resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} - engines: {node: '>=0.10.0'} - - getpass@0.1.7: - resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - - globals@12.4.0: - resolution: {integrity: sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==} - engines: {node: '>=8'} - - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - growly@1.3.0: - resolution: {integrity: sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==} - - har-schema@2.0.0: - resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} - engines: {node: '>=4'} - - har-validator@5.1.5: - resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} - engines: {node: '>=6'} - deprecated: this library is no longer supported - - has-bigints@1.1.0: - resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} - engines: {node: '>= 0.4'} - - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-proto@1.2.0: - resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} - engines: {node: '>= 0.4'} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - - has-value@0.3.1: - resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==} - engines: {node: '>=0.10.0'} - - has-value@1.0.0: - resolution: {integrity: sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==} - engines: {node: '>=0.10.0'} - - has-values@0.1.4: - resolution: {integrity: sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==} - engines: {node: '>=0.10.0'} - - has-values@1.0.0: - resolution: {integrity: sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==} - engines: {node: '>=0.10.0'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - hosted-git-info@2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - - html-encoding-sniffer@1.0.2: - resolution: {integrity: sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==} - - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - - http-signature@1.2.0: - resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} - engines: {node: '>=0.8', npm: '>=1.3.7'} - - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - - ignore@4.0.6: - resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} - engines: {node: '>= 4'} - - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - - import-local@2.0.0: - resolution: {integrity: sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==} - engines: {node: '>=6'} - hasBin: true - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - inquirer@7.3.3: - resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} - engines: {node: '>=8.0.0'} - - internal-slot@1.1.0: - resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} - engines: {node: '>= 0.4'} - - invariant@2.2.4: - resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} - - is-accessor-descriptor@1.0.1: - resolution: {integrity: sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==} - engines: {node: '>= 0.10'} - - is-array-buffer@3.0.5: - resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} - engines: {node: '>= 0.4'} - - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - is-async-function@2.1.1: - resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} - engines: {node: '>= 0.4'} - - is-bigint@1.1.0: - resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} - engines: {node: '>= 0.4'} - - is-boolean-object@1.2.2: - resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} - engines: {node: '>= 0.4'} - - is-buffer@1.1.6: - resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} - - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - - is-ci@2.0.0: - resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} - hasBin: true - - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - - is-data-descriptor@1.0.1: - resolution: {integrity: sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==} - engines: {node: '>= 0.4'} - - is-data-view@1.0.2: - resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} - engines: {node: '>= 0.4'} - - is-date-object@1.1.0: - resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} - engines: {node: '>= 0.4'} - - is-descriptor@0.1.7: - resolution: {integrity: sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==} - engines: {node: '>= 0.4'} - - is-descriptor@1.0.3: - resolution: {integrity: sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==} - engines: {node: '>= 0.4'} - - is-extendable@0.1.1: - resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} - engines: {node: '>=0.10.0'} - - is-extendable@1.0.1: - resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} - engines: {node: '>=0.10.0'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-finalizationregistry@1.1.1: - resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} - engines: {node: '>= 0.4'} - - is-fullwidth-code-point@2.0.0: - resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} - engines: {node: '>=4'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-generator-fn@2.1.0: - resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} - engines: {node: '>=6'} - - is-generator-function@1.1.2: - resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} - engines: {node: '>= 0.4'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} - - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - - is-number-object@1.1.1: - resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} - engines: {node: '>= 0.4'} - - is-number@3.0.0: - resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==} - engines: {node: '>=0.10.0'} - - is-plain-object@2.0.4: - resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} - engines: {node: '>=0.10.0'} - - is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} - - is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} - - is-shared-array-buffer@1.0.4: - resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} - engines: {node: '>= 0.4'} - - is-stream@1.1.0: - resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} - engines: {node: '>=0.10.0'} - - is-string@1.1.1: - resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} - engines: {node: '>= 0.4'} - - is-symbol@1.1.1: - resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} - engines: {node: '>= 0.4'} - - is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} - - is-typedarray@1.0.0: - resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - - is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} - - is-weakref@1.1.1: - resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} - engines: {node: '>= 0.4'} - - is-weakset@2.0.4: - resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} - engines: {node: '>= 0.4'} - - is-windows@1.0.2: - resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} - engines: {node: '>=0.10.0'} - - is-wsl@1.1.0: - resolution: {integrity: sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==} - engines: {node: '>=4'} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - isobject@2.1.0: - resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} - engines: {node: '>=0.10.0'} - - isobject@3.0.1: - resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} - engines: {node: '>=0.10.0'} - - isstream@0.1.2: - resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} - - istanbul-lib-coverage@2.0.5: - resolution: {integrity: sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==} - engines: {node: '>=6'} - - istanbul-lib-instrument@3.3.0: - resolution: {integrity: sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==} - engines: {node: '>=6'} - - istanbul-lib-report@2.0.8: - resolution: {integrity: sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==} - engines: {node: '>=6'} - - istanbul-lib-source-maps@3.0.6: - resolution: {integrity: sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==} - engines: {node: '>=6'} - - istanbul-reports@2.2.7: - resolution: {integrity: sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==} - engines: {node: '>=6'} - - iterator.prototype@1.1.5: - resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} - engines: {node: '>= 0.4'} - - jest-changed-files@24.9.0: - resolution: {integrity: sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg==} - engines: {node: '>= 6'} - - jest-cli@24.9.0: - resolution: {integrity: sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg==} - engines: {node: '>= 6'} - hasBin: true - - jest-config@24.9.0: - resolution: {integrity: sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==} - engines: {node: '>= 6'} - - jest-diff@24.9.0: - resolution: {integrity: sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==} - engines: {node: '>= 6'} - - jest-docblock@24.9.0: - resolution: {integrity: sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA==} - engines: {node: '>= 6'} - - jest-each@24.9.0: - resolution: {integrity: sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==} - engines: {node: '>= 6'} - - jest-environment-jsdom@24.9.0: - resolution: {integrity: sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA==} - engines: {node: '>= 6'} - - jest-environment-node@24.9.0: - resolution: {integrity: sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==} - engines: {node: '>= 6'} - - jest-get-type@24.9.0: - resolution: {integrity: sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==} - engines: {node: '>= 6'} - - jest-haste-map@24.9.0: - resolution: {integrity: sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==} - engines: {node: '>= 6'} - - jest-jasmine2@24.9.0: - resolution: {integrity: sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw==} - engines: {node: '>= 6'} - - jest-leak-detector@24.9.0: - resolution: {integrity: sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA==} - engines: {node: '>= 6'} - - jest-matcher-utils@24.9.0: - resolution: {integrity: sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==} - engines: {node: '>= 6'} - - jest-message-util@24.9.0: - resolution: {integrity: sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==} - engines: {node: '>= 6'} - - jest-mock@24.9.0: - resolution: {integrity: sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==} - engines: {node: '>= 6'} - - jest-pnp-resolver@1.2.3: - resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true - - jest-regex-util@24.9.0: - resolution: {integrity: sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==} - engines: {node: '>= 6'} - - jest-resolve-dependencies@24.9.0: - resolution: {integrity: sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g==} - engines: {node: '>= 6'} - - jest-resolve@24.9.0: - resolution: {integrity: sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ==} - engines: {node: '>= 6'} - - jest-runner@24.9.0: - resolution: {integrity: sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==} - engines: {node: '>= 6'} - - jest-runtime@24.9.0: - resolution: {integrity: sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==} - engines: {node: '>= 6'} - hasBin: true - - jest-serializer@24.9.0: - resolution: {integrity: sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==} - engines: {node: '>= 6'} - - jest-snapshot@24.9.0: - resolution: {integrity: sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==} - engines: {node: '>= 6'} - - jest-util@24.9.0: - resolution: {integrity: sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==} - engines: {node: '>= 6'} - - jest-validate@24.9.0: - resolution: {integrity: sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==} - engines: {node: '>= 6'} - - jest-watcher@24.9.0: - resolution: {integrity: sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw==} - engines: {node: '>= 6'} - - jest-worker@24.9.0: - resolution: {integrity: sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==} - engines: {node: '>= 6'} - - jest@24.9.0: - resolution: {integrity: sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw==} - engines: {node: '>= 6'} - hasBin: true - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-yaml@3.14.2: - resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} - hasBin: true - - jsbn@0.1.1: - resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} - - jscodeshift@0.11.0: - resolution: {integrity: sha512-SdRK2C7jjs4k/kT2mwtO07KJN9RnjxtKn03d9JVj6c3j9WwaLcFYsICYDnLAzY0hp+wG2nxl+Cm2jWLiNVYb8g==} - hasBin: true - peerDependencies: - '@babel/preset-env': ^7.1.6 - - jsdom@11.12.0: - resolution: {integrity: sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==} - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json-parse-better-errors@1.0.2: - resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-schema@0.4.0: - resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsprim@1.4.2: - resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} - engines: {node: '>=0.6.0'} - - jsx-ast-utils@3.3.5: - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} - engines: {node: '>=4.0'} - - kind-of@3.2.2: - resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} - engines: {node: '>=0.10.0'} - - kind-of@4.0.0: - resolution: {integrity: sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==} - engines: {node: '>=0.10.0'} - - kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - - left-pad@1.3.0: - resolution: {integrity: sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==} - deprecated: use String.prototype.padStart() - - leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} - - levn@0.3.0: - resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} - engines: {node: '>= 0.8.0'} - - load-json-file@4.0.0: - resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} - engines: {node: '>=4'} - - locate-path@3.0.0: - resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} - engines: {node: '>=6'} - - lodash.debounce@4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - - lodash.sortby@4.7.0: - resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - - lodash@4.18.1: - resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} - - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - make-dir@2.1.0: - resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} - engines: {node: '>=6'} - - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - - makeerror@1.0.12: - resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - - map-cache@0.2.2: - resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} - engines: {node: '>=0.10.0'} - - map-visit@1.0.0: - resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} - engines: {node: '>=0.10.0'} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - micromatch@3.1.10: - resolution: {integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==} - engines: {node: '>=0.10.0'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - mixin-deep@1.3.2: - resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} - engines: {node: '>=0.10.0'} - - mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} - hasBin: true - - ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - mute-stream@0.0.8: - resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} - - nan@2.26.2: - resolution: {integrity: sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==} - - nanomatch@1.2.13: - resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} - engines: {node: '>=0.10.0'} - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - - nice-try@1.0.5: - resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} - - node-dir@0.1.17: - resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} - engines: {node: '>= 0.10.5'} - - node-exports-info@1.6.0: - resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} - engines: {node: '>= 0.4'} - - node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - - node-notifier@5.4.5: - resolution: {integrity: sha512-tVbHs7DyTLtzOiN78izLA85zRqB9NvEXkAf014Vx3jtSvn/xBl6bR8ZYifj+dFcFrKI21huSQgJZ6ZtL3B4HfQ==} - - node-releases@2.0.37: - resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} - - normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - - normalize-path@2.1.1: - resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} - engines: {node: '>=0.10.0'} - - npm-run-path@2.0.2: - resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} - engines: {node: '>=4'} - - nwsapi@2.2.23: - resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} - - oauth-sign@0.9.0: - resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-copy@0.1.0: - resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - object-visit@1.0.1: - resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==} - engines: {node: '>=0.10.0'} - - object.assign@4.1.7: - resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} - engines: {node: '>= 0.4'} - - object.entries@1.1.9: - resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} - engines: {node: '>= 0.4'} - - object.fromentries@2.0.8: - resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} - engines: {node: '>= 0.4'} - - object.getownpropertydescriptors@2.1.9: - resolution: {integrity: sha512-mt8YM6XwsTTovI+kdZdHSxoyF2DI59up034orlC9NfweclcWOt7CVascNNLp6U+bjFVCVCIh9PwS76tDM/rH8g==} - engines: {node: '>= 0.4'} - - object.pick@1.3.0: - resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==} - engines: {node: '>=0.10.0'} - - object.values@1.2.1: - resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} - engines: {node: '>= 0.4'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - optionator@0.8.3: - resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} - engines: {node: '>= 0.8.0'} - - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - - own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} - engines: {node: '>= 0.4'} - - p-each-series@1.0.0: - resolution: {integrity: sha512-J/e9xiZZQNrt+958FFzJ+auItsBGq+UrQ7nE89AUP7UOTtjHnkISANXLdayhVzh538UnLMCSlf13lFfRIAKQOA==} - engines: {node: '>=4'} - - p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} - - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - - p-locate@3.0.0: - resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} - engines: {node: '>=6'} - - p-reduce@1.0.0: - resolution: {integrity: sha512-3Tx1T3oM1xO/Y8Gj0sWyE78EIJZ+t+aEmXUdvQgvGmSMri7aPTHoovbXEreWKkL5j21Er60XAWLTzKbAKYOujQ==} - engines: {node: '>=4'} - - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-json@4.0.0: - resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} - engines: {node: '>=4'} - - parse5@4.0.0: - resolution: {integrity: sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==} - - pascalcase@0.1.1: - resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==} - engines: {node: '>=0.10.0'} - - path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-key@2.0.1: - resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} - engines: {node: '>=4'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-type@3.0.0: - resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} - engines: {node: '>=4'} - - performance-now@2.1.0: - resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - pify@3.0.0: - resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} - engines: {node: '>=4'} - - pify@4.0.1: - resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} - engines: {node: '>=6'} - - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} - - pkg-dir@3.0.0: - resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==} - engines: {node: '>=6'} - - pn@1.1.0: - resolution: {integrity: sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==} - - posix-character-classes@0.1.1: - resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} - engines: {node: '>=0.10.0'} - - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} - - prelude-ls@1.1.2: - resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} - engines: {node: '>= 0.8.0'} - - pretty-format@24.9.0: - resolution: {integrity: sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==} - engines: {node: '>= 6'} - - progress@2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} - - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - - psl@1.15.0: - resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} - - pump@3.0.4: - resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - qs@6.5.5: - resolution: {integrity: sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==} - engines: {node: '>=0.6'} - - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - - read-pkg-up@4.0.0: - resolution: {integrity: sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==} - engines: {node: '>=6'} - - read-pkg@3.0.0: - resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} - engines: {node: '>=4'} - - realpath-native@1.1.0: - resolution: {integrity: sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==} - engines: {node: '>=4'} - - recast@0.20.5: - resolution: {integrity: sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ==} - engines: {node: '>= 4'} - - reflect.getprototypeof@1.0.10: - resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} - engines: {node: '>= 0.4'} - - regenerate-unicode-properties@10.2.2: - resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} - engines: {node: '>=4'} - - regenerate@1.4.2: - resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} - - regex-not@1.0.2: - resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==} - engines: {node: '>=0.10.0'} - - regexp.prototype.flags@1.5.4: - resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} - engines: {node: '>= 0.4'} - - regexpp@2.0.1: - resolution: {integrity: sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==} - engines: {node: '>=6.5.0'} - - regexpu-core@6.4.0: - resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} - engines: {node: '>=4'} - - regjsgen@0.8.0: - resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} - - regjsparser@0.13.1: - resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} - hasBin: true - - remove-trailing-separator@1.1.0: - resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==} - - repeat-element@1.1.4: - resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==} - engines: {node: '>=0.10.0'} - - repeat-string@1.6.1: - resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} - engines: {node: '>=0.10'} - - request-promise-core@1.1.4: - resolution: {integrity: sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==} - engines: {node: '>=0.10.0'} - peerDependencies: - request: ^2.34 - - request-promise-native@1.0.9: - resolution: {integrity: sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==} - engines: {node: '>=0.12.0'} - deprecated: request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142 - peerDependencies: - request: ^2.34 - - request@2.88.2: - resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} - engines: {node: '>= 6'} - deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - require-main-filename@2.0.0: - resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - - resolve-cwd@2.0.0: - resolution: {integrity: sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg==} - engines: {node: '>=4'} - - resolve-from@3.0.0: - resolution: {integrity: sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==} - engines: {node: '>=4'} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - resolve-url@0.2.1: - resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} - deprecated: https://github.com/lydell/resolve-url#deprecated - - resolve@1.1.7: - resolution: {integrity: sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==} - - resolve@1.22.12: - resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} - engines: {node: '>= 0.4'} - hasBin: true - - resolve@2.0.0-next.6: - resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==} - engines: {node: '>= 0.4'} - hasBin: true - - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} - - ret@0.1.15: - resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} - engines: {node: '>=0.12'} - - rimraf@2.6.3: - resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - - rimraf@2.7.1: - resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - - rsvp@4.8.5: - resolution: {integrity: sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==} - engines: {node: 6.* || >= 7.*} - - run-async@2.4.1: - resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} - engines: {node: '>=0.12.0'} - - rxjs@6.6.7: - resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} - engines: {npm: '>=2.0.0'} - - safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} - engines: {node: '>=0.4'} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safe-push-apply@1.0.0: - resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} - engines: {node: '>= 0.4'} - - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} - - safe-regex@1.1.0: - resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - sane@4.1.0: - resolution: {integrity: sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==} - engines: {node: 6.* || 8.* || >= 10.*} - deprecated: some dependency vulnerabilities fixed, support for node < 10 dropped, and newer ECMAScript syntax/features added - hasBin: true - - sax@1.6.0: - resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} - engines: {node: '>=11.0.0'} - - semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} - - set-proto@1.0.0: - resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} - engines: {node: '>= 0.4'} - - set-value@2.0.1: - resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} - engines: {node: '>=0.10.0'} - - shallow-clone@3.0.1: - resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} - engines: {node: '>=8'} - - shebang-command@1.2.0: - resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} - engines: {node: '>=0.10.0'} - - shebang-regex@1.0.0: - resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} - engines: {node: '>=0.10.0'} - - shellwords@0.1.1: - resolution: {integrity: sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==} - - side-channel-list@1.0.1: - resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - slash@2.0.0: - resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==} - engines: {node: '>=6'} - - slice-ansi@2.1.0: - resolution: {integrity: sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==} - engines: {node: '>=6'} - - snapdragon-node@2.1.1: - resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==} - engines: {node: '>=0.10.0'} - - snapdragon-util@3.0.1: - resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==} - engines: {node: '>=0.10.0'} - - snapdragon@0.8.2: - resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==} - engines: {node: '>=0.10.0'} - - source-map-resolve@0.5.3: - resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} - deprecated: See https://github.com/lydell/source-map-resolve#deprecated - - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - - source-map-url@0.4.1: - resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==} - deprecated: See https://github.com/lydell/source-map-url#deprecated - - source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} - engines: {node: '>=0.10.0'} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - - spdx-license-ids@3.0.23: - resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} - - split-string@3.1.0: - resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} - engines: {node: '>=0.10.0'} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - sshpk@1.18.0: - resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} - engines: {node: '>=0.10.0'} - hasBin: true - - stack-utils@1.0.5: - resolution: {integrity: sha512-KZiTzuV3CnSnSvgMRrARVCj+Ht7rMbauGDK0LdVFRGyenwdylpajAp4Q0i6SX8rEmbTpMMf6ryq2gb8pPq2WgQ==} - engines: {node: '>=8'} - - static-extend@0.1.2: - resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} - engines: {node: '>=0.10.0'} - - stealthy-require@1.1.1: - resolution: {integrity: sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==} - engines: {node: '>=0.10.0'} - - stop-iteration-iterator@1.1.0: - resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} - engines: {node: '>= 0.4'} - - string-length@2.0.0: - resolution: {integrity: sha512-Qka42GGrS8Mm3SZ+7cH8UXiIWI867/b/Z/feQSpQx/rbfB8UGknGEZVaUQMOUVj+soY6NpWAxily63HI1OckVQ==} - engines: {node: '>=4'} - - string-width@3.1.0: - resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==} - engines: {node: '>=6'} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string.prototype.matchall@4.0.12: - resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} - engines: {node: '>= 0.4'} - - string.prototype.repeat@1.0.0: - resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} - - string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} - engines: {node: '>= 0.4'} - - string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} - engines: {node: '>= 0.4'} - - string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} - - strip-ansi@4.0.0: - resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==} - engines: {node: '>=4'} - - strip-ansi@5.2.0: - resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} - engines: {node: '>=6'} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - - strip-eof@1.0.0: - resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} - engines: {node: '>=0.10.0'} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - - supports-color@6.1.0: - resolution: {integrity: sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==} - engines: {node: '>=6'} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - - table@5.4.6: - resolution: {integrity: sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==} - engines: {node: '>=6.0.0'} - - temp@0.8.4: - resolution: {integrity: sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==} - engines: {node: '>=6.0.0'} - - test-exclude@5.2.3: - resolution: {integrity: sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==} - engines: {node: '>=6'} - - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - - throat@4.1.0: - resolution: {integrity: sha512-wCVxLDcFxw7ujDxaeJC6nfl2XfHJNYs8yUYJnvMgtPEFlttP9tHSfRUv2vBe6C4hkVFPWoP1P6ZccbYjmSEkKA==} - - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} - - tmpl@1.0.5: - resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - - to-object-path@0.3.0: - resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} - engines: {node: '>=0.10.0'} - - to-regex-range@2.1.1: - resolution: {integrity: sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==} - engines: {node: '>=0.10.0'} - - to-regex@3.0.2: - resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==} - engines: {node: '>=0.10.0'} - - tough-cookie@2.5.0: - resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} - engines: {node: '>=0.8'} - - tr46@1.0.1: - resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} - - ts-jest@24.3.0: - resolution: {integrity: sha512-Hb94C/+QRIgjVZlJyiWwouYUF+siNJHJHknyspaOcZ+OQAIdFG/UrdQVXw/0B8Z3No34xkUXZJpOTy9alOWdVQ==} - engines: {node: '>= 6'} - hasBin: true - peerDependencies: - jest: '>=24 <25' - - tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - - tweetnacl@0.14.5: - resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} - - type-check@0.3.2: - resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} - engines: {node: '>= 0.8.0'} - - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - - type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - - typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} - - typed-array-byte-length@1.0.3: - resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} - engines: {node: '>= 0.4'} - - typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} - engines: {node: '>= 0.4'} - - typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} - engines: {node: '>= 0.4'} - - typescript@4.8.4: - resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==} - engines: {node: '>=4.2.0'} - hasBin: true - - unbox-primitive@1.1.0: - resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} - engines: {node: '>= 0.4'} - - unicode-canonical-property-names-ecmascript@2.0.1: - resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} - engines: {node: '>=4'} - - unicode-match-property-ecmascript@2.0.0: - resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} - engines: {node: '>=4'} - - unicode-match-property-value-ecmascript@2.2.1: - resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==} - engines: {node: '>=4'} - - unicode-property-aliases-ecmascript@2.2.0: - resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} - engines: {node: '>=4'} - - union-value@1.0.1: - resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} - engines: {node: '>=0.10.0'} - - unset-value@1.0.0: - resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==} - engines: {node: '>=0.10.0'} - - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - urix@0.1.0: - resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} - deprecated: Please see https://github.com/lydell/urix#deprecated - - use@3.1.1: - resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==} - engines: {node: '>=0.10.0'} - - util.promisify@1.1.3: - resolution: {integrity: sha512-GIEaZ6o86fj09Wtf0VfZ5XP7tmd4t3jM5aZCgmBi231D0DB1AEBa3Aa6MP48DMsAIi96WkpWLimIWVwOjbDMOw==} - engines: {node: '>= 0.8'} - - uuid@3.4.0: - resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} - deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. - hasBin: true - - v8-compile-cache@2.4.0: - resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==} - - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - - verror@1.10.0: - resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} - engines: {'0': node >=0.6.0} - - w3c-hr-time@1.0.2: - resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==} - deprecated: Use your platform's native performance.now() and performance.timeOrigin. - - walker@1.0.8: - resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - - webidl-conversions@4.0.2: - resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - - whatwg-encoding@1.0.5: - resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==} - deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation - - whatwg-mimetype@2.3.0: - resolution: {integrity: sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==} - - whatwg-url@6.5.0: - resolution: {integrity: sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==} - - whatwg-url@7.1.0: - resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} - - which-boxed-primitive@1.1.1: - resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} - engines: {node: '>= 0.4'} - - which-builtin-type@1.2.1: - resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} - engines: {node: '>= 0.4'} - - which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} - - which-module@2.0.1: - resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - - which-typed-array@1.1.20: - resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} - engines: {node: '>= 0.4'} - - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - wrap-ansi@5.1.0: - resolution: {integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==} - engines: {node: '>=6'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - write-file-atomic@2.4.1: - resolution: {integrity: sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==} - - write-file-atomic@2.4.3: - resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==} - - write@1.0.3: - resolution: {integrity: sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==} - engines: {node: '>=4'} - - ws@5.2.4: - resolution: {integrity: sha512-fFCejsuC8f9kOSu9FYaOw8CdO68O3h5v0lg4p74o8JqWpwTf9tniOD+nOB78aWoVSS6WptVUmDrp/KPsMVBWFQ==} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - xml-name-validator@3.0.0: - resolution: {integrity: sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==} - - y18n@4.0.3: - resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yargs-parser@10.1.0: - resolution: {integrity: sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==} - - yargs-parser@13.1.2: - resolution: {integrity: sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==} - - yargs@13.3.2: - resolution: {integrity: sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==} - -snapshots: - - '@babel/code-frame@7.29.0': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.29.0': {} - - '@babel/core@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.29.2 - '@babel/parser': 7.29.2 - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.29.1': - dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/helper-annotate-as-pure@7.27.3': - dependencies: - '@babel/types': 7.29.0 - - '@babel/helper-compilation-targets@7.28.6': - dependencies: - '@babel/compat-data': 7.29.0 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.2 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.28.5 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.29.0 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-annotate-as-pure': 7.27.3 - regexpu-core: 6.4.0 - semver: 6.3.1 - - '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - debug: 4.4.3 - lodash.debounce: 4.0.8 - resolve: 1.22.12 - transitivePeerDependencies: - - supports-color - - '@babel/helper-globals@7.28.0': {} - - '@babel/helper-member-expression-to-functions@7.28.5': - dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-imports@7.28.6': - dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-optimise-call-expression@7.27.1': - dependencies: - '@babel/types': 7.29.0 - - '@babel/helper-plugin-utils@7.28.6': {} - - '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-wrap-function': 7.28.6 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-member-expression-to-functions': 7.28.5 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': - dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.28.5': {} - - '@babel/helper-validator-option@7.27.1': {} - - '@babel/helper-wrap-function@7.28.6': - dependencies: - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helpers@7.29.2': - dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - - '@babel/parser@7.29.2': - dependencies: - '@babel/types': 7.29.0 - - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) - - '@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.29.0)': - dependencies: - '@babel/compat-data': 7.29.0 - '@babel/core': 7.29.0 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) - - '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - - '@babel/plugin-syntax-flow@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-globals': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/template': 7.28.6 - - '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-flow': 7.28.6(@babel/core@7.29.0) - - '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/preset-env@7.29.2(@babel/core@7.29.0)': - dependencies: - '@babel/compat-data': 7.29.0 - '@babel/core': 7.29.0 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) - '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) - '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) - '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) - babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) - babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.0) - babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) - core-js-compat: 3.49.0 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/preset-flow@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.0) - - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/types': 7.29.0 - esutils: 2.0.3 - - '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) - transitivePeerDependencies: - - supports-color - - '@babel/register@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - clone-deep: 4.0.1 - find-cache-dir: 2.1.0 - make-dir: 2.1.0 - pirates: 4.0.7 - source-map-support: 0.5.21 - - '@babel/template@7.28.6': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - - '@babel/traverse@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.2 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.29.0': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - - '@cnakazawa/watch@1.0.4': - dependencies: - exec-sh: 0.3.6 - minimist: 1.2.8 - - '@jest/console@24.9.0': - dependencies: - '@jest/source-map': 24.9.0 - chalk: 2.4.2 - slash: 2.0.0 - - '@jest/core@24.9.0': - dependencies: - '@jest/console': 24.9.0 - '@jest/reporters': 24.9.0 - '@jest/test-result': 24.9.0 - '@jest/transform': 24.9.0 - '@jest/types': 24.9.0 - ansi-escapes: 3.2.0 - chalk: 2.4.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 24.9.0 - jest-config: 24.9.0 - jest-haste-map: 24.9.0 - jest-message-util: 24.9.0 - jest-regex-util: 24.9.0 - jest-resolve: 24.9.0 - jest-resolve-dependencies: 24.9.0 - jest-runner: 24.9.0 - jest-runtime: 24.9.0 - jest-snapshot: 24.9.0 - jest-util: 24.9.0 - jest-validate: 24.9.0 - jest-watcher: 24.9.0 - micromatch: 3.1.10 - p-each-series: 1.0.0 - realpath-native: 1.1.0 - rimraf: 2.7.1 - slash: 2.0.0 - strip-ansi: 5.2.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - '@jest/environment@24.9.0': - dependencies: - '@jest/fake-timers': 24.9.0 - '@jest/transform': 24.9.0 - '@jest/types': 24.9.0 - jest-mock: 24.9.0 - transitivePeerDependencies: - - supports-color - - '@jest/fake-timers@24.9.0': - dependencies: - '@jest/types': 24.9.0 - jest-message-util: 24.9.0 - jest-mock: 24.9.0 - transitivePeerDependencies: - - supports-color - - '@jest/reporters@24.9.0': - dependencies: - '@jest/environment': 24.9.0 - '@jest/test-result': 24.9.0 - '@jest/transform': 24.9.0 - '@jest/types': 24.9.0 - chalk: 2.4.2 - exit: 0.1.2 - glob: 7.2.3 - istanbul-lib-coverage: 2.0.5 - istanbul-lib-instrument: 3.3.0 - istanbul-lib-report: 2.0.8 - istanbul-lib-source-maps: 3.0.6 - istanbul-reports: 2.2.7 - jest-haste-map: 24.9.0 - jest-resolve: 24.9.0 - jest-runtime: 24.9.0 - jest-util: 24.9.0 - jest-worker: 24.9.0 - node-notifier: 5.4.5 - slash: 2.0.0 - source-map: 0.6.1 - string-length: 2.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - '@jest/source-map@24.9.0': - dependencies: - callsites: 3.1.0 - graceful-fs: 4.2.11 - source-map: 0.6.1 - - '@jest/test-result@24.9.0': - dependencies: - '@jest/console': 24.9.0 - '@jest/types': 24.9.0 - '@types/istanbul-lib-coverage': 2.0.6 - - '@jest/test-sequencer@24.9.0': - dependencies: - '@jest/test-result': 24.9.0 - jest-haste-map: 24.9.0 - jest-runner: 24.9.0 - jest-runtime: 24.9.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - '@jest/transform@24.9.0': - dependencies: - '@babel/core': 7.29.0 - '@jest/types': 24.9.0 - babel-plugin-istanbul: 5.2.0 - chalk: 2.4.2 - convert-source-map: 1.9.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.11 - jest-haste-map: 24.9.0 - jest-regex-util: 24.9.0 - jest-util: 24.9.0 - micromatch: 3.1.10 - pirates: 4.0.7 - realpath-native: 1.1.0 - slash: 2.0.0 - source-map: 0.6.1 - write-file-atomic: 2.4.1 - transitivePeerDependencies: - - supports-color - - '@jest/types@24.9.0': - dependencies: - '@types/istanbul-lib-coverage': 2.0.6 - '@types/istanbul-reports': 1.1.2 - '@types/yargs': 13.0.12 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.28.0 - - '@types/babel__generator@7.27.0': - dependencies: - '@babel/types': 7.29.0 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - - '@types/babel__traverse@7.28.0': - dependencies: - '@babel/types': 7.29.0 - - '@types/istanbul-lib-coverage@2.0.6': {} - - '@types/istanbul-lib-report@3.0.3': - dependencies: - '@types/istanbul-lib-coverage': 2.0.6 - - '@types/istanbul-reports@1.1.2': - dependencies: - '@types/istanbul-lib-coverage': 2.0.6 - '@types/istanbul-lib-report': 3.0.3 - - '@types/jest@24.9.1': - dependencies: - jest-diff: 24.9.0 - - '@types/stack-utils@1.0.1': {} - - '@types/yargs-parser@21.0.3': {} - - '@types/yargs@13.0.12': - dependencies: - '@types/yargs-parser': 21.0.3 - - abab@2.0.6: {} - - acorn-globals@4.3.4: - dependencies: - acorn: 6.4.2 - acorn-walk: 6.2.0 - - acorn-jsx@5.3.2(acorn@7.4.1): - dependencies: - acorn: 7.4.1 - - acorn-walk@6.2.0: {} - - acorn@5.7.4: {} - - acorn@6.4.2: {} - - acorn@7.4.1: {} - - ajv@6.14.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - - ansi-escapes@3.2.0: {} - - ansi-escapes@4.3.2: - dependencies: - type-fest: 0.21.3 - - ansi-regex@3.0.1: {} - - ansi-regex@4.1.1: {} - - ansi-regex@5.0.1: {} - - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - anymatch@2.0.0: - dependencies: - micromatch: 3.1.10 - normalize-path: 2.1.1 - transitivePeerDependencies: - - supports-color - - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 - - arr-diff@4.0.0: {} - - arr-flatten@1.1.0: {} - - arr-union@3.1.0: {} - - array-buffer-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - is-array-buffer: 3.0.5 - - array-equal@1.0.2: {} - - array-includes@3.1.9: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - is-string: 1.1.1 - math-intrinsics: 1.1.0 - - array-unique@0.3.2: {} - - array.prototype.findlast@1.2.5: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - es-shim-unscopables: 1.1.0 - - array.prototype.flat@1.3.3: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-shim-unscopables: 1.1.0 - - array.prototype.flatmap@1.3.3: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-shim-unscopables: 1.1.0 - - array.prototype.reduce@1.0.8: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-array-method-boxes-properly: 1.0.0 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - is-string: 1.1.1 - - array.prototype.tosorted@1.1.4: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-shim-unscopables: 1.1.0 - - arraybuffer.prototype.slice@1.0.4: - dependencies: - array-buffer-byte-length: 1.0.2 - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - is-array-buffer: 3.0.5 - - asn1@0.2.6: - dependencies: - safer-buffer: 2.1.2 - - assert-plus@1.0.0: {} - - assign-symbols@1.0.0: {} - - ast-types@0.14.2: - dependencies: - tslib: 2.8.1 - - astral-regex@1.0.0: {} - - async-function@1.0.0: {} - - async-limiter@1.0.1: {} - - asynckit@0.4.0: {} - - atob@2.1.2: {} - - available-typed-arrays@1.0.7: - dependencies: - possible-typed-array-names: 1.1.0 - - aws-sign2@0.7.0: {} - - aws4@1.13.2: {} - - babel-core@7.0.0-bridge.0(@babel/core@7.29.0): - dependencies: - '@babel/core': 7.29.0 - - babel-eslint@10.1.0(eslint@6.8.0): - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.2 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - eslint: 6.8.0 - eslint-visitor-keys: 1.3.0 - resolve: 1.22.12 - transitivePeerDependencies: - - supports-color - - babel-jest@24.9.0(@babel/core@7.29.0): - dependencies: - '@babel/core': 7.29.0 - '@jest/transform': 24.9.0 - '@jest/types': 24.9.0 - '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 5.2.0 - babel-preset-jest: 24.9.0(@babel/core@7.29.0) - chalk: 2.4.2 - slash: 2.0.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-istanbul@5.2.0: - dependencies: - '@babel/helper-plugin-utils': 7.28.6 - find-up: 3.0.0 - istanbul-lib-instrument: 3.3.0 - test-exclude: 5.2.3 - transitivePeerDependencies: - - supports-color - - babel-plugin-jest-hoist@24.9.0: - dependencies: - '@types/babel__traverse': 7.28.0 - - babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0): - dependencies: - '@babel/compat-data': 7.29.0 - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.0): - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) - core-js-compat: 3.49.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.0): - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) - transitivePeerDependencies: - - supports-color - - babel-preset-jest@24.9.0(@babel/core@7.29.0): - dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) - babel-plugin-jest-hoist: 24.9.0 - - balanced-match@1.0.2: {} - - base@0.11.2: - dependencies: - cache-base: 1.0.1 - class-utils: 0.3.6 - component-emitter: 1.3.1 - define-property: 1.0.0 - isobject: 3.0.1 - mixin-deep: 1.3.2 - pascalcase: 0.1.1 - - baseline-browser-mapping@2.10.19: {} - - bcrypt-pbkdf@1.0.2: - dependencies: - tweetnacl: 0.14.5 - - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - optional: true - - brace-expansion@1.1.14: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - braces@2.3.2: - dependencies: - arr-flatten: 1.1.0 - array-unique: 0.3.2 - extend-shallow: 2.0.1 - fill-range: 4.0.0 - isobject: 3.0.1 - repeat-element: 1.1.4 - snapdragon: 0.8.2 - snapdragon-node: 2.1.1 - split-string: 3.1.0 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - - browser-process-hrtime@1.0.0: {} - - browser-resolve@1.11.3: - dependencies: - resolve: 1.1.7 - - browserslist@4.28.2: - dependencies: - baseline-browser-mapping: 2.10.19 - caniuse-lite: 1.0.30001788 - electron-to-chromium: 1.5.336 - node-releases: 2.0.37 - update-browserslist-db: 1.2.3(browserslist@4.28.2) - - bs-logger@0.2.6: - dependencies: - fast-json-stable-stringify: 2.1.0 - - bser@2.1.1: - dependencies: - node-int64: 0.4.0 - - buffer-from@1.1.2: {} - - cache-base@1.0.1: - dependencies: - collection-visit: 1.0.0 - component-emitter: 1.3.1 - get-value: 2.0.6 - has-value: 1.0.0 - isobject: 3.0.1 - set-value: 2.0.1 - to-object-path: 0.3.0 - union-value: 1.0.1 - unset-value: 1.0.0 - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bind@1.0.9: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - get-intrinsic: 1.3.0 - set-function-length: 1.2.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - - callsites@3.1.0: {} - - camelcase@4.1.0: {} - - camelcase@5.3.1: {} - - caniuse-lite@1.0.30001788: {} - - capture-exit@2.0.0: - dependencies: - rsvp: 4.8.5 - - caseless@0.12.0: {} - - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - chardet@0.7.0: {} - - ci-info@2.0.0: {} - - class-utils@0.3.6: - dependencies: - arr-union: 3.1.0 - define-property: 0.2.5 - isobject: 3.0.1 - static-extend: 0.1.2 - - cli-cursor@3.1.0: - dependencies: - restore-cursor: 3.1.0 - - cli-width@3.0.0: {} - - cliui@5.0.0: - dependencies: - string-width: 3.1.0 - strip-ansi: 5.2.0 - wrap-ansi: 5.1.0 - - clone-deep@4.0.1: - dependencies: - is-plain-object: 2.0.4 - kind-of: 6.0.3 - shallow-clone: 3.0.1 - - co@4.6.0: {} - - collection-visit@1.0.0: - dependencies: - map-visit: 1.0.0 - object-visit: 1.0.1 - - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.3: {} - - color-name@1.1.4: {} - - colors@1.4.0: {} - - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - - commondir@1.0.1: {} - - component-emitter@1.3.1: {} - - concat-map@0.0.1: {} - - convert-source-map@1.9.0: {} - - convert-source-map@2.0.0: {} - - copy-descriptor@0.1.1: {} - - core-js-compat@3.49.0: - dependencies: - browserslist: 4.28.2 - - core-util-is@1.0.2: {} - - cross-spawn@6.0.6: - dependencies: - nice-try: 1.0.5 - path-key: 2.0.1 - semver: 5.7.2 - shebang-command: 1.2.0 - which: 1.3.1 - - cssom@0.3.8: {} - - cssstyle@1.4.0: - dependencies: - cssom: 0.3.8 - - dashdash@1.14.1: - dependencies: - assert-plus: 1.0.0 - - data-urls@1.1.0: - dependencies: - abab: 2.0.6 - whatwg-mimetype: 2.3.0 - whatwg-url: 7.1.0 - - data-view-buffer@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - data-view-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - data-view-byte-offset@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - debug@2.6.9: - dependencies: - ms: 2.0.0 - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - decamelize@1.2.0: {} - - decode-uri-component@0.2.2: {} - - deep-is@0.1.4: {} - - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 - - define-properties@1.2.1: - dependencies: - define-data-property: 1.1.4 - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 - - define-property@0.2.5: - dependencies: - is-descriptor: 0.1.7 - - define-property@1.0.0: - dependencies: - is-descriptor: 1.0.3 - - define-property@2.0.2: - dependencies: - is-descriptor: 1.0.3 - isobject: 3.0.1 - - delayed-stream@1.0.0: {} - - detect-newline@2.1.0: {} - - diff-sequences@24.9.0: {} - - doctrine@2.1.0: - dependencies: - esutils: 2.0.3 - - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 - - domexception@1.0.1: - dependencies: - webidl-conversions: 4.0.2 - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - ecc-jsbn@0.1.2: - dependencies: - jsbn: 0.1.1 - safer-buffer: 2.1.2 - - electron-to-chromium@1.5.336: {} - - emoji-regex@7.0.3: {} - - emoji-regex@8.0.0: {} - - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - - error-ex@1.3.4: - dependencies: - is-arrayish: 0.2.1 - - es-abstract@1.24.2: - dependencies: - array-buffer-byte-length: 1.0.2 - arraybuffer.prototype.slice: 1.0.4 - available-typed-arrays: 1.0.7 - call-bind: 1.0.9 - call-bound: 1.0.4 - data-view-buffer: 1.0.2 - data-view-byte-length: 1.0.2 - data-view-byte-offset: 1.0.1 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - es-set-tostringtag: 2.1.0 - es-to-primitive: 1.3.0 - function.prototype.name: 1.1.8 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - get-symbol-description: 1.1.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - internal-slot: 1.1.0 - is-array-buffer: 3.0.5 - is-callable: 1.2.7 - is-data-view: 1.0.2 - is-negative-zero: 2.0.3 - is-regex: 1.2.1 - is-set: 2.0.3 - is-shared-array-buffer: 1.0.4 - is-string: 1.1.1 - is-typed-array: 1.1.15 - is-weakref: 1.1.1 - math-intrinsics: 1.1.0 - object-inspect: 1.13.4 - object-keys: 1.1.1 - object.assign: 4.1.7 - own-keys: 1.0.1 - regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.3 - safe-push-apply: 1.0.0 - safe-regex-test: 1.1.0 - set-proto: 1.0.0 - stop-iteration-iterator: 1.1.0 - string.prototype.trim: 1.2.10 - string.prototype.trimend: 1.0.9 - string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.3 - typed-array-byte-length: 1.0.3 - typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.7 - unbox-primitive: 1.1.0 - which-typed-array: 1.1.20 - - es-array-method-boxes-properly@1.0.0: {} - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-iterator-helpers@1.3.2: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-set-tostringtag: 2.1.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - internal-slot: 1.1.0 - iterator.prototype: 1.1.5 - math-intrinsics: 1.1.0 - - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - - es-set-tostringtag@2.1.0: - dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - es-shim-unscopables@1.1.0: - dependencies: - hasown: 2.0.2 - - es-to-primitive@1.3.0: - dependencies: - is-callable: 1.2.7 - is-date-object: 1.1.0 - is-symbol: 1.1.1 - - escalade@3.2.0: {} - - escape-string-regexp@1.0.5: {} - - escape-string-regexp@2.0.0: {} - - escodegen@1.14.3: - dependencies: - esprima: 4.0.1 - estraverse: 4.3.0 - esutils: 2.0.3 - optionator: 0.8.3 - optionalDependencies: - source-map: 0.6.1 - - eslint-plugin-react@7.37.5(eslint@6.8.0): - dependencies: - array-includes: 3.1.9 - array.prototype.findlast: 1.2.5 - array.prototype.flatmap: 1.3.3 - array.prototype.tosorted: 1.1.4 - doctrine: 2.1.0 - es-iterator-helpers: 1.3.2 - eslint: 6.8.0 - estraverse: 5.3.0 - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - minimatch: 3.1.5 - object.entries: 1.1.9 - object.fromentries: 2.0.8 - object.values: 1.2.1 - prop-types: 15.8.1 - resolve: 2.0.0-next.6 - semver: 6.3.1 - string.prototype.matchall: 4.0.12 - string.prototype.repeat: 1.0.0 - - eslint-scope@5.1.1: - dependencies: - esrecurse: 4.3.0 - estraverse: 4.3.0 - - eslint-utils@1.4.3: - dependencies: - eslint-visitor-keys: 1.3.0 - - eslint-visitor-keys@1.3.0: {} - - eslint@6.8.0: - dependencies: - '@babel/code-frame': 7.29.0 - ajv: 6.14.0 - chalk: 2.4.2 - cross-spawn: 6.0.6 - debug: 4.4.3 - doctrine: 3.0.0 - eslint-scope: 5.1.1 - eslint-utils: 1.4.3 - eslint-visitor-keys: 1.3.0 - espree: 6.2.1 - esquery: 1.7.0 - esutils: 2.0.3 - file-entry-cache: 5.0.1 - functional-red-black-tree: 1.0.1 - glob-parent: 5.1.2 - globals: 12.4.0 - ignore: 4.0.6 - import-fresh: 3.3.1 - imurmurhash: 0.1.4 - inquirer: 7.3.3 - is-glob: 4.0.3 - js-yaml: 3.14.2 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.3.0 - lodash: 4.18.1 - minimatch: 3.1.5 - mkdirp: 0.5.6 - natural-compare: 1.4.0 - optionator: 0.8.3 - progress: 2.0.3 - regexpp: 2.0.1 - semver: 6.3.1 - strip-ansi: 5.2.0 - strip-json-comments: 3.1.1 - table: 5.4.6 - text-table: 0.2.0 - v8-compile-cache: 2.4.0 - transitivePeerDependencies: - - supports-color - - espree@6.2.1: - dependencies: - acorn: 7.4.1 - acorn-jsx: 5.3.2(acorn@7.4.1) - eslint-visitor-keys: 1.3.0 - - esprima@4.0.1: {} - - esquery@1.7.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@4.3.0: {} - - estraverse@5.3.0: {} - - esutils@2.0.3: {} - - exec-sh@0.3.6: {} - - execa@1.0.0: - dependencies: - cross-spawn: 6.0.6 - get-stream: 4.1.0 - is-stream: 1.1.0 - npm-run-path: 2.0.2 - p-finally: 1.0.0 - signal-exit: 3.0.7 - strip-eof: 1.0.0 - - exit@0.1.2: {} - - expand-brackets@2.1.4: - dependencies: - debug: 2.6.9 - define-property: 0.2.5 - extend-shallow: 2.0.1 - posix-character-classes: 0.1.1 - regex-not: 1.0.2 - snapdragon: 0.8.2 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - - expect@24.9.0: - dependencies: - '@jest/types': 24.9.0 - ansi-styles: 3.2.1 - jest-get-type: 24.9.0 - jest-matcher-utils: 24.9.0 - jest-message-util: 24.9.0 - jest-regex-util: 24.9.0 - transitivePeerDependencies: - - supports-color - - extend-shallow@2.0.1: - dependencies: - is-extendable: 0.1.1 - - extend-shallow@3.0.2: - dependencies: - assign-symbols: 1.0.0 - is-extendable: 1.0.1 - - extend@3.0.2: {} - - external-editor@3.1.0: - dependencies: - chardet: 0.7.0 - iconv-lite: 0.4.24 - tmp: 0.0.33 - - extglob@2.0.4: - dependencies: - array-unique: 0.3.2 - define-property: 1.0.0 - expand-brackets: 2.1.4 - extend-shallow: 2.0.1 - fragment-cache: 0.2.1 - regex-not: 1.0.2 - snapdragon: 0.8.2 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - - extsprintf@1.3.0: {} - - fast-deep-equal@3.1.3: {} - - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - - fb-watchman@2.0.2: - dependencies: - bser: 2.1.1 - - figures@3.2.0: - dependencies: - escape-string-regexp: 1.0.5 - - file-entry-cache@5.0.1: - dependencies: - flat-cache: 2.0.1 - - file-uri-to-path@1.0.0: - optional: true - - fill-range@4.0.0: - dependencies: - extend-shallow: 2.0.1 - is-number: 3.0.0 - repeat-string: 1.6.1 - to-regex-range: 2.1.1 - - find-cache-dir@2.1.0: - dependencies: - commondir: 1.0.1 - make-dir: 2.1.0 - pkg-dir: 3.0.0 - - find-up@3.0.0: - dependencies: - locate-path: 3.0.0 - - flat-cache@2.0.1: - dependencies: - flatted: 2.0.2 - rimraf: 2.6.3 - write: 1.0.3 - - flatted@2.0.2: {} - - flow-parser@0.309.0: {} - - for-each@0.3.5: - dependencies: - is-callable: 1.2.7 - - for-in@1.0.2: {} - - forever-agent@0.6.1: {} - - form-data@2.3.3: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - - fragment-cache@0.2.1: - dependencies: - map-cache: 0.2.2 - - fs.realpath@1.0.0: {} - - fsevents@1.2.13: - dependencies: - bindings: 1.5.0 - nan: 2.26.2 - optional: true - - function-bind@1.1.2: {} - - function.prototype.name@1.1.8: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - functions-have-names: 1.2.3 - hasown: 2.0.2 - is-callable: 1.2.7 - - functional-red-black-tree@1.0.1: {} - - functions-have-names@1.2.3: {} - - generator-function@2.0.1: {} - - gensync@1.0.0-beta.2: {} - - get-caller-file@2.0.5: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - get-stream@4.1.0: - dependencies: - pump: 3.0.4 - - get-symbol-description@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - - get-value@2.0.6: {} - - getpass@0.1.7: - dependencies: - assert-plus: 1.0.0 - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.5 - once: 1.4.0 - path-is-absolute: 1.0.1 - - globals@12.4.0: - dependencies: - type-fest: 0.8.1 - - globalthis@1.0.4: - dependencies: - define-properties: 1.2.1 - gopd: 1.2.0 - - gopd@1.2.0: {} - - graceful-fs@4.2.11: {} - - growly@1.3.0: {} - - har-schema@2.0.0: {} - - har-validator@5.1.5: - dependencies: - ajv: 6.14.0 - har-schema: 2.0.0 - - has-bigints@1.1.0: {} - - has-flag@3.0.0: {} - - has-flag@4.0.0: {} - - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 - - has-proto@1.2.0: - dependencies: - dunder-proto: 1.0.1 - - has-symbols@1.1.0: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - - has-value@0.3.1: - dependencies: - get-value: 2.0.6 - has-values: 0.1.4 - isobject: 2.1.0 - - has-value@1.0.0: - dependencies: - get-value: 2.0.6 - has-values: 1.0.0 - isobject: 3.0.1 - - has-values@0.1.4: {} - - has-values@1.0.0: - dependencies: - is-number: 3.0.0 - kind-of: 4.0.0 - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - hosted-git-info@2.8.9: {} - - html-encoding-sniffer@1.0.2: - dependencies: - whatwg-encoding: 1.0.5 - - html-escaper@2.0.2: {} - - http-signature@1.2.0: - dependencies: - assert-plus: 1.0.0 - jsprim: 1.4.2 - sshpk: 1.18.0 - - iconv-lite@0.4.24: - dependencies: - safer-buffer: 2.1.2 - - ignore@4.0.6: {} - - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - import-local@2.0.0: - dependencies: - pkg-dir: 3.0.0 - resolve-cwd: 2.0.0 - - imurmurhash@0.1.4: {} - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - - inquirer@7.3.3: - dependencies: - ansi-escapes: 4.3.2 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-width: 3.0.0 - external-editor: 3.1.0 - figures: 3.2.0 - lodash: 4.18.1 - mute-stream: 0.0.8 - run-async: 2.4.1 - rxjs: 6.6.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - through: 2.3.8 - - internal-slot@1.1.0: - dependencies: - es-errors: 1.3.0 - hasown: 2.0.2 - side-channel: 1.1.0 - - invariant@2.2.4: - dependencies: - loose-envify: 1.4.0 - - is-accessor-descriptor@1.0.1: - dependencies: - hasown: 2.0.2 - - is-array-buffer@3.0.5: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - - is-arrayish@0.2.1: {} - - is-async-function@2.1.1: - dependencies: - async-function: 1.0.0 - call-bound: 1.0.4 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - - is-bigint@1.1.0: - dependencies: - has-bigints: 1.1.0 - - is-boolean-object@1.2.2: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-buffer@1.1.6: {} - - is-callable@1.2.7: {} - - is-ci@2.0.0: - dependencies: - ci-info: 2.0.0 - - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - - is-data-descriptor@1.0.1: - dependencies: - hasown: 2.0.2 - - is-data-view@1.0.2: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - is-typed-array: 1.1.15 - - is-date-object@1.1.0: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-descriptor@0.1.7: - dependencies: - is-accessor-descriptor: 1.0.1 - is-data-descriptor: 1.0.1 - - is-descriptor@1.0.3: - dependencies: - is-accessor-descriptor: 1.0.1 - is-data-descriptor: 1.0.1 - - is-extendable@0.1.1: {} - - is-extendable@1.0.1: - dependencies: - is-plain-object: 2.0.4 - - is-extglob@2.1.1: {} - - is-finalizationregistry@1.1.1: - dependencies: - call-bound: 1.0.4 - - is-fullwidth-code-point@2.0.0: {} - - is-fullwidth-code-point@3.0.0: {} - - is-generator-fn@2.1.0: {} - - is-generator-function@1.1.2: - dependencies: - call-bound: 1.0.4 - generator-function: 2.0.1 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-map@2.0.3: {} - - is-negative-zero@2.0.3: {} - - is-number-object@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-number@3.0.0: - dependencies: - kind-of: 3.2.2 - - is-plain-object@2.0.4: - dependencies: - isobject: 3.0.1 - - is-regex@1.2.1: - dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - is-set@2.0.3: {} - - is-shared-array-buffer@1.0.4: - dependencies: - call-bound: 1.0.4 - - is-stream@1.1.0: {} - - is-string@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-symbol@1.1.1: - dependencies: - call-bound: 1.0.4 - has-symbols: 1.1.0 - safe-regex-test: 1.1.0 - - is-typed-array@1.1.15: - dependencies: - which-typed-array: 1.1.20 - - is-typedarray@1.0.0: {} - - is-weakmap@2.0.2: {} - - is-weakref@1.1.1: - dependencies: - call-bound: 1.0.4 - - is-weakset@2.0.4: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - - is-windows@1.0.2: {} - - is-wsl@1.1.0: {} - - isarray@1.0.0: {} - - isarray@2.0.5: {} - - isexe@2.0.0: {} - - isobject@2.1.0: - dependencies: - isarray: 1.0.0 - - isobject@3.0.1: {} - - isstream@0.1.2: {} - - istanbul-lib-coverage@2.0.5: {} - - istanbul-lib-instrument@3.3.0: - dependencies: - '@babel/generator': 7.29.1 - '@babel/parser': 7.29.2 - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - istanbul-lib-coverage: 2.0.5 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - istanbul-lib-report@2.0.8: - dependencies: - istanbul-lib-coverage: 2.0.5 - make-dir: 2.1.0 - supports-color: 6.1.0 - - istanbul-lib-source-maps@3.0.6: - dependencies: - debug: 4.4.3 - istanbul-lib-coverage: 2.0.5 - make-dir: 2.1.0 - rimraf: 2.7.1 - source-map: 0.6.1 - transitivePeerDependencies: - - supports-color - - istanbul-reports@2.2.7: - dependencies: - html-escaper: 2.0.2 - - iterator.prototype@1.1.5: - dependencies: - define-data-property: 1.1.4 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - has-symbols: 1.1.0 - set-function-name: 2.0.2 - - jest-changed-files@24.9.0: - dependencies: - '@jest/types': 24.9.0 - execa: 1.0.0 - throat: 4.1.0 - - jest-cli@24.9.0: - dependencies: - '@jest/core': 24.9.0 - '@jest/test-result': 24.9.0 - '@jest/types': 24.9.0 - chalk: 2.4.2 - exit: 0.1.2 - import-local: 2.0.0 - is-ci: 2.0.0 - jest-config: 24.9.0 - jest-util: 24.9.0 - jest-validate: 24.9.0 - prompts: 2.4.2 - realpath-native: 1.1.0 - yargs: 13.3.2 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - jest-config@24.9.0: - dependencies: - '@babel/core': 7.29.0 - '@jest/test-sequencer': 24.9.0 - '@jest/types': 24.9.0 - babel-jest: 24.9.0(@babel/core@7.29.0) - chalk: 2.4.2 - glob: 7.2.3 - jest-environment-jsdom: 24.9.0 - jest-environment-node: 24.9.0 - jest-get-type: 24.9.0 - jest-jasmine2: 24.9.0 - jest-regex-util: 24.9.0 - jest-resolve: 24.9.0 - jest-util: 24.9.0 - jest-validate: 24.9.0 - micromatch: 3.1.10 - pretty-format: 24.9.0 - realpath-native: 1.1.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - jest-diff@24.9.0: - dependencies: - chalk: 2.4.2 - diff-sequences: 24.9.0 - jest-get-type: 24.9.0 - pretty-format: 24.9.0 - - jest-docblock@24.9.0: - dependencies: - detect-newline: 2.1.0 - - jest-each@24.9.0: - dependencies: - '@jest/types': 24.9.0 - chalk: 2.4.2 - jest-get-type: 24.9.0 - jest-util: 24.9.0 - pretty-format: 24.9.0 - transitivePeerDependencies: - - supports-color - - jest-environment-jsdom@24.9.0: - dependencies: - '@jest/environment': 24.9.0 - '@jest/fake-timers': 24.9.0 - '@jest/types': 24.9.0 - jest-mock: 24.9.0 - jest-util: 24.9.0 - jsdom: 11.12.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - jest-environment-node@24.9.0: - dependencies: - '@jest/environment': 24.9.0 - '@jest/fake-timers': 24.9.0 - '@jest/types': 24.9.0 - jest-mock: 24.9.0 - jest-util: 24.9.0 - transitivePeerDependencies: - - supports-color - - jest-get-type@24.9.0: {} - - jest-haste-map@24.9.0: - dependencies: - '@jest/types': 24.9.0 - anymatch: 2.0.0 - fb-watchman: 2.0.2 - graceful-fs: 4.2.11 - invariant: 2.2.4 - jest-serializer: 24.9.0 - jest-util: 24.9.0 - jest-worker: 24.9.0 - micromatch: 3.1.10 - sane: 4.1.0 - walker: 1.0.8 - optionalDependencies: - fsevents: 1.2.13 - transitivePeerDependencies: - - supports-color - - jest-jasmine2@24.9.0: - dependencies: - '@babel/traverse': 7.29.0 - '@jest/environment': 24.9.0 - '@jest/test-result': 24.9.0 - '@jest/types': 24.9.0 - chalk: 2.4.2 - co: 4.6.0 - expect: 24.9.0 - is-generator-fn: 2.1.0 - jest-each: 24.9.0 - jest-matcher-utils: 24.9.0 - jest-message-util: 24.9.0 - jest-runtime: 24.9.0 - jest-snapshot: 24.9.0 - jest-util: 24.9.0 - pretty-format: 24.9.0 - throat: 4.1.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - jest-leak-detector@24.9.0: - dependencies: - jest-get-type: 24.9.0 - pretty-format: 24.9.0 - - jest-matcher-utils@24.9.0: - dependencies: - chalk: 2.4.2 - jest-diff: 24.9.0 - jest-get-type: 24.9.0 - pretty-format: 24.9.0 - - jest-message-util@24.9.0: - dependencies: - '@babel/code-frame': 7.29.0 - '@jest/test-result': 24.9.0 - '@jest/types': 24.9.0 - '@types/stack-utils': 1.0.1 - chalk: 2.4.2 - micromatch: 3.1.10 - slash: 2.0.0 - stack-utils: 1.0.5 - transitivePeerDependencies: - - supports-color - - jest-mock@24.9.0: - dependencies: - '@jest/types': 24.9.0 - - jest-pnp-resolver@1.2.3(jest-resolve@24.9.0): - optionalDependencies: - jest-resolve: 24.9.0 - - jest-regex-util@24.9.0: {} - - jest-resolve-dependencies@24.9.0: - dependencies: - '@jest/types': 24.9.0 - jest-regex-util: 24.9.0 - jest-snapshot: 24.9.0 - transitivePeerDependencies: - - supports-color - - jest-resolve@24.9.0: - dependencies: - '@jest/types': 24.9.0 - browser-resolve: 1.11.3 - chalk: 2.4.2 - jest-pnp-resolver: 1.2.3(jest-resolve@24.9.0) - realpath-native: 1.1.0 - - jest-runner@24.9.0: - dependencies: - '@jest/console': 24.9.0 - '@jest/environment': 24.9.0 - '@jest/test-result': 24.9.0 - '@jest/types': 24.9.0 - chalk: 2.4.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 24.9.0 - jest-docblock: 24.9.0 - jest-haste-map: 24.9.0 - jest-jasmine2: 24.9.0 - jest-leak-detector: 24.9.0 - jest-message-util: 24.9.0 - jest-resolve: 24.9.0 - jest-runtime: 24.9.0 - jest-util: 24.9.0 - jest-worker: 24.9.0 - source-map-support: 0.5.21 - throat: 4.1.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - jest-runtime@24.9.0: - dependencies: - '@jest/console': 24.9.0 - '@jest/environment': 24.9.0 - '@jest/source-map': 24.9.0 - '@jest/transform': 24.9.0 - '@jest/types': 24.9.0 - '@types/yargs': 13.0.12 - chalk: 2.4.2 - exit: 0.1.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-config: 24.9.0 - jest-haste-map: 24.9.0 - jest-message-util: 24.9.0 - jest-mock: 24.9.0 - jest-regex-util: 24.9.0 - jest-resolve: 24.9.0 - jest-snapshot: 24.9.0 - jest-util: 24.9.0 - jest-validate: 24.9.0 - realpath-native: 1.1.0 - slash: 2.0.0 - strip-bom: 3.0.0 - yargs: 13.3.2 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - jest-serializer@24.9.0: {} - - jest-snapshot@24.9.0: - dependencies: - '@babel/types': 7.29.0 - '@jest/types': 24.9.0 - chalk: 2.4.2 - expect: 24.9.0 - jest-diff: 24.9.0 - jest-get-type: 24.9.0 - jest-matcher-utils: 24.9.0 - jest-message-util: 24.9.0 - jest-resolve: 24.9.0 - mkdirp: 0.5.6 - natural-compare: 1.4.0 - pretty-format: 24.9.0 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - jest-util@24.9.0: - dependencies: - '@jest/console': 24.9.0 - '@jest/fake-timers': 24.9.0 - '@jest/source-map': 24.9.0 - '@jest/test-result': 24.9.0 - '@jest/types': 24.9.0 - callsites: 3.1.0 - chalk: 2.4.2 - graceful-fs: 4.2.11 - is-ci: 2.0.0 - mkdirp: 0.5.6 - slash: 2.0.0 - source-map: 0.6.1 - transitivePeerDependencies: - - supports-color - - jest-validate@24.9.0: - dependencies: - '@jest/types': 24.9.0 - camelcase: 5.3.1 - chalk: 2.4.2 - jest-get-type: 24.9.0 - leven: 3.1.0 - pretty-format: 24.9.0 - - jest-watcher@24.9.0: - dependencies: - '@jest/test-result': 24.9.0 - '@jest/types': 24.9.0 - '@types/yargs': 13.0.12 - ansi-escapes: 3.2.0 - chalk: 2.4.2 - jest-util: 24.9.0 - string-length: 2.0.0 - transitivePeerDependencies: - - supports-color - - jest-worker@24.9.0: - dependencies: - merge-stream: 2.0.0 - supports-color: 6.1.0 - - jest@24.9.0: - dependencies: - import-local: 2.0.0 - jest-cli: 24.9.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - js-tokens@4.0.0: {} - - js-yaml@3.14.2: - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - - jsbn@0.1.1: {} - - jscodeshift@0.11.0(@babel/preset-env@7.29.2(@babel/core@7.29.0)): - dependencies: - '@babel/core': 7.29.0 - '@babel/parser': 7.29.2 - '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.29.0) - '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.29.0) - '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/preset-env': 7.29.2(@babel/core@7.29.0) - '@babel/preset-flow': 7.27.1(@babel/core@7.29.0) - '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) - '@babel/register': 7.28.6(@babel/core@7.29.0) - babel-core: 7.0.0-bridge.0(@babel/core@7.29.0) - colors: 1.4.0 - flow-parser: 0.309.0 - graceful-fs: 4.2.11 - micromatch: 3.1.10 - neo-async: 2.6.2 - node-dir: 0.1.17 - recast: 0.20.5 - temp: 0.8.4 - write-file-atomic: 2.4.3 - transitivePeerDependencies: - - supports-color - - jsdom@11.12.0: - dependencies: - abab: 2.0.6 - acorn: 5.7.4 - acorn-globals: 4.3.4 - array-equal: 1.0.2 - cssom: 0.3.8 - cssstyle: 1.4.0 - data-urls: 1.1.0 - domexception: 1.0.1 - escodegen: 1.14.3 - html-encoding-sniffer: 1.0.2 - left-pad: 1.3.0 - nwsapi: 2.2.23 - parse5: 4.0.0 - pn: 1.1.0 - request: 2.88.2 - request-promise-native: 1.0.9(request@2.88.2) - sax: 1.6.0 - symbol-tree: 3.2.4 - tough-cookie: 2.5.0 - w3c-hr-time: 1.0.2 - webidl-conversions: 4.0.2 - whatwg-encoding: 1.0.5 - whatwg-mimetype: 2.3.0 - whatwg-url: 6.5.0 - ws: 5.2.4 - xml-name-validator: 3.0.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - jsesc@3.1.0: {} - - json-parse-better-errors@1.0.2: {} - - json-schema-traverse@0.4.1: {} - - json-schema@0.4.0: {} - - json-stable-stringify-without-jsonify@1.0.1: {} - - json-stringify-safe@5.0.1: {} - - json5@2.2.3: {} - - jsprim@1.4.2: - dependencies: - assert-plus: 1.0.0 - extsprintf: 1.3.0 - json-schema: 0.4.0 - verror: 1.10.0 - - jsx-ast-utils@3.3.5: - dependencies: - array-includes: 3.1.9 - array.prototype.flat: 1.3.3 - object.assign: 4.1.7 - object.values: 1.2.1 - - kind-of@3.2.2: - dependencies: - is-buffer: 1.1.6 - - kind-of@4.0.0: - dependencies: - is-buffer: 1.1.6 - - kind-of@6.0.3: {} - - kleur@3.0.3: {} - - left-pad@1.3.0: {} - - leven@3.1.0: {} - - levn@0.3.0: - dependencies: - prelude-ls: 1.1.2 - type-check: 0.3.2 - - load-json-file@4.0.0: - dependencies: - graceful-fs: 4.2.11 - parse-json: 4.0.0 - pify: 3.0.0 - strip-bom: 3.0.0 - - locate-path@3.0.0: - dependencies: - p-locate: 3.0.0 - path-exists: 3.0.0 - - lodash.debounce@4.0.8: {} - - lodash.memoize@4.1.2: {} - - lodash.sortby@4.7.0: {} - - lodash@4.18.1: {} - - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - make-dir@2.1.0: - dependencies: - pify: 4.0.1 - semver: 5.7.2 - - make-error@1.3.6: {} - - makeerror@1.0.12: - dependencies: - tmpl: 1.0.5 - - map-cache@0.2.2: {} - - map-visit@1.0.0: - dependencies: - object-visit: 1.0.1 - - math-intrinsics@1.1.0: {} - - merge-stream@2.0.0: {} - - micromatch@3.1.10: - dependencies: - arr-diff: 4.0.0 - array-unique: 0.3.2 - braces: 2.3.2 - define-property: 2.0.2 - extend-shallow: 3.0.2 - extglob: 2.0.4 - fragment-cache: 0.2.1 - kind-of: 6.0.3 - nanomatch: 1.2.13 - object.pick: 1.3.0 - regex-not: 1.0.2 - snapdragon: 0.8.2 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - mimic-fn@2.1.0: {} - - minimatch@3.1.5: - dependencies: - brace-expansion: 1.1.14 - - minimist@1.2.8: {} - - mixin-deep@1.3.2: - dependencies: - for-in: 1.0.2 - is-extendable: 1.0.1 - - mkdirp@0.5.6: - dependencies: - minimist: 1.2.8 - - ms@2.0.0: {} - - ms@2.1.3: {} - - mute-stream@0.0.8: {} - - nan@2.26.2: - optional: true - - nanomatch@1.2.13: - dependencies: - arr-diff: 4.0.0 - array-unique: 0.3.2 - define-property: 2.0.2 - extend-shallow: 3.0.2 - fragment-cache: 0.2.1 - is-windows: 1.0.2 - kind-of: 6.0.3 - object.pick: 1.3.0 - regex-not: 1.0.2 - snapdragon: 0.8.2 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - - natural-compare@1.4.0: {} - - neo-async@2.6.2: {} - - nice-try@1.0.5: {} - - node-dir@0.1.17: - dependencies: - minimatch: 3.1.5 - - node-exports-info@1.6.0: - dependencies: - array.prototype.flatmap: 1.3.3 - es-errors: 1.3.0 - object.entries: 1.1.9 - semver: 6.3.1 - - node-int64@0.4.0: {} - - node-notifier@5.4.5: - dependencies: - growly: 1.3.0 - is-wsl: 1.1.0 - semver: 5.7.2 - shellwords: 0.1.1 - which: 1.3.1 - - node-releases@2.0.37: {} - - normalize-package-data@2.5.0: - dependencies: - hosted-git-info: 2.8.9 - resolve: 1.22.12 - semver: 5.7.2 - validate-npm-package-license: 3.0.4 - - normalize-path@2.1.1: - dependencies: - remove-trailing-separator: 1.1.0 - - npm-run-path@2.0.2: - dependencies: - path-key: 2.0.1 - - nwsapi@2.2.23: {} - - oauth-sign@0.9.0: {} - - object-assign@4.1.1: {} - - object-copy@0.1.0: - dependencies: - copy-descriptor: 0.1.1 - define-property: 0.2.5 - kind-of: 3.2.2 - - object-inspect@1.13.4: {} - - object-keys@1.1.1: {} - - object-visit@1.0.1: - dependencies: - isobject: 3.0.1 - - object.assign@4.1.7: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - has-symbols: 1.1.0 - object-keys: 1.1.1 - - object.entries@1.1.9: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - object.fromentries@2.0.8: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.1 - - object.getownpropertydescriptors@2.1.9: - dependencies: - array.prototype.reduce: 1.0.8 - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.1 - gopd: 1.2.0 - safe-array-concat: 1.1.3 - - object.pick@1.3.0: - dependencies: - isobject: 3.0.1 - - object.values@1.2.1: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - - optionator@0.8.3: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.3.0 - prelude-ls: 1.1.2 - type-check: 0.3.2 - word-wrap: 1.2.5 - - os-tmpdir@1.0.2: {} - - own-keys@1.0.1: - dependencies: - get-intrinsic: 1.3.0 - object-keys: 1.1.1 - safe-push-apply: 1.0.0 - - p-each-series@1.0.0: - dependencies: - p-reduce: 1.0.0 - - p-finally@1.0.0: {} - - p-limit@2.3.0: - dependencies: - p-try: 2.2.0 - - p-locate@3.0.0: - dependencies: - p-limit: 2.3.0 - - p-reduce@1.0.0: {} - - p-try@2.2.0: {} - - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - parse-json@4.0.0: - dependencies: - error-ex: 1.3.4 - json-parse-better-errors: 1.0.2 - - parse5@4.0.0: {} - - pascalcase@0.1.1: {} - - path-exists@3.0.0: {} - - path-is-absolute@1.0.1: {} - - path-key@2.0.1: {} - - path-parse@1.0.7: {} - - path-type@3.0.0: - dependencies: - pify: 3.0.0 - - performance-now@2.1.0: {} - - picocolors@1.1.1: {} - - pify@3.0.0: {} - - pify@4.0.1: {} - - pirates@4.0.7: {} - - pkg-dir@3.0.0: - dependencies: - find-up: 3.0.0 - - pn@1.1.0: {} - - posix-character-classes@0.1.1: {} - - possible-typed-array-names@1.1.0: {} - - prelude-ls@1.1.2: {} - - pretty-format@24.9.0: - dependencies: - '@jest/types': 24.9.0 - ansi-regex: 4.1.1 - ansi-styles: 3.2.1 - react-is: 16.13.1 - - progress@2.0.3: {} - - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - - prop-types@15.8.1: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - - psl@1.15.0: - dependencies: - punycode: 2.3.1 - - pump@3.0.4: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - - punycode@2.3.1: {} - - qs@6.5.5: {} - - react-is@16.13.1: {} - - read-pkg-up@4.0.0: - dependencies: - find-up: 3.0.0 - read-pkg: 3.0.0 - - read-pkg@3.0.0: - dependencies: - load-json-file: 4.0.0 - normalize-package-data: 2.5.0 - path-type: 3.0.0 - - realpath-native@1.1.0: - dependencies: - util.promisify: 1.1.3 - - recast@0.20.5: - dependencies: - ast-types: 0.14.2 - esprima: 4.0.1 - source-map: 0.6.1 - tslib: 2.8.1 - - reflect.getprototypeof@1.0.10: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - which-builtin-type: 1.2.1 - - regenerate-unicode-properties@10.2.2: - dependencies: - regenerate: 1.4.2 - - regenerate@1.4.2: {} - - regex-not@1.0.2: - dependencies: - extend-shallow: 3.0.2 - safe-regex: 1.1.0 - - regexp.prototype.flags@1.5.4: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-errors: 1.3.0 - get-proto: 1.0.1 - gopd: 1.2.0 - set-function-name: 2.0.2 - - regexpp@2.0.1: {} - - regexpu-core@6.4.0: - dependencies: - regenerate: 1.4.2 - regenerate-unicode-properties: 10.2.2 - regjsgen: 0.8.0 - regjsparser: 0.13.1 - unicode-match-property-ecmascript: 2.0.0 - unicode-match-property-value-ecmascript: 2.2.1 - - regjsgen@0.8.0: {} - - regjsparser@0.13.1: - dependencies: - jsesc: 3.1.0 - - remove-trailing-separator@1.1.0: {} - - repeat-element@1.1.4: {} - - repeat-string@1.6.1: {} - - request-promise-core@1.1.4(request@2.88.2): - dependencies: - lodash: 4.18.1 - request: 2.88.2 - - request-promise-native@1.0.9(request@2.88.2): - dependencies: - request: 2.88.2 - request-promise-core: 1.1.4(request@2.88.2) - stealthy-require: 1.1.1 - tough-cookie: 2.5.0 - - request@2.88.2: - dependencies: - aws-sign2: 0.7.0 - aws4: 1.13.2 - caseless: 0.12.0 - combined-stream: 1.0.8 - extend: 3.0.2 - forever-agent: 0.6.1 - form-data: 2.3.3 - har-validator: 5.1.5 - http-signature: 1.2.0 - is-typedarray: 1.0.0 - isstream: 0.1.2 - json-stringify-safe: 5.0.1 - mime-types: 2.1.35 - oauth-sign: 0.9.0 - performance-now: 2.1.0 - qs: 6.5.5 - safe-buffer: 5.2.1 - tough-cookie: 2.5.0 - tunnel-agent: 0.6.0 - uuid: 3.4.0 - - require-directory@2.1.1: {} - - require-main-filename@2.0.0: {} - - resolve-cwd@2.0.0: - dependencies: - resolve-from: 3.0.0 - - resolve-from@3.0.0: {} - - resolve-from@4.0.0: {} - - resolve-url@0.2.1: {} - - resolve@1.1.7: {} - - resolve@1.22.12: - dependencies: - es-errors: 1.3.0 - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - resolve@2.0.0-next.6: - dependencies: - es-errors: 1.3.0 - is-core-module: 2.16.1 - node-exports-info: 1.6.0 - object-keys: 1.1.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - restore-cursor@3.1.0: - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - - ret@0.1.15: {} - - rimraf@2.6.3: - dependencies: - glob: 7.2.3 - - rimraf@2.7.1: - dependencies: - glob: 7.2.3 - - rsvp@4.8.5: {} - - run-async@2.4.1: {} - - rxjs@6.6.7: - dependencies: - tslib: 1.14.1 - - safe-array-concat@1.1.3: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - has-symbols: 1.1.0 - isarray: 2.0.5 - - safe-buffer@5.2.1: {} - - safe-push-apply@1.0.0: - dependencies: - es-errors: 1.3.0 - isarray: 2.0.5 - - safe-regex-test@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-regex: 1.2.1 - - safe-regex@1.1.0: - dependencies: - ret: 0.1.15 - - safer-buffer@2.1.2: {} - - sane@4.1.0: - dependencies: - '@cnakazawa/watch': 1.0.4 - anymatch: 2.0.0 - capture-exit: 2.0.0 - exec-sh: 0.3.6 - execa: 1.0.0 - fb-watchman: 2.0.2 - micromatch: 3.1.10 - minimist: 1.2.8 - walker: 1.0.8 - transitivePeerDependencies: - - supports-color - - sax@1.6.0: {} - - semver@5.7.2: {} - - semver@6.3.1: {} - - set-blocking@2.0.0: {} - - set-function-length@1.2.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - - set-function-name@2.0.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 - - set-proto@1.0.0: - dependencies: - dunder-proto: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - - set-value@2.0.1: - dependencies: - extend-shallow: 2.0.1 - is-extendable: 0.1.1 - is-plain-object: 2.0.4 - split-string: 3.1.0 - - shallow-clone@3.0.1: - dependencies: - kind-of: 6.0.3 - - shebang-command@1.2.0: - dependencies: - shebang-regex: 1.0.0 - - shebang-regex@1.0.0: {} - - shellwords@0.1.1: {} - - side-channel-list@1.0.1: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.1 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - - signal-exit@3.0.7: {} - - sisteransi@1.0.5: {} - - slash@2.0.0: {} - - slice-ansi@2.1.0: - dependencies: - ansi-styles: 3.2.1 - astral-regex: 1.0.0 - is-fullwidth-code-point: 2.0.0 - - snapdragon-node@2.1.1: - dependencies: - define-property: 1.0.0 - isobject: 3.0.1 - snapdragon-util: 3.0.1 - - snapdragon-util@3.0.1: - dependencies: - kind-of: 3.2.2 - - snapdragon@0.8.2: - dependencies: - base: 0.11.2 - debug: 2.6.9 - define-property: 0.2.5 - extend-shallow: 2.0.1 - map-cache: 0.2.2 - source-map: 0.5.7 - source-map-resolve: 0.5.3 - use: 3.1.1 - transitivePeerDependencies: - - supports-color - - source-map-resolve@0.5.3: - dependencies: - atob: 2.1.2 - decode-uri-component: 0.2.2 - resolve-url: 0.2.1 - source-map-url: 0.4.1 - urix: 0.1.0 - - source-map-support@0.5.21: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - - source-map-url@0.4.1: {} - - source-map@0.5.7: {} - - source-map@0.6.1: {} - - spdx-correct@3.2.0: - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.23 - - spdx-exceptions@2.5.0: {} - - spdx-expression-parse@3.0.1: - dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.23 - - spdx-license-ids@3.0.23: {} - - split-string@3.1.0: - dependencies: - extend-shallow: 3.0.2 - - sprintf-js@1.0.3: {} - - sshpk@1.18.0: - dependencies: - asn1: 0.2.6 - assert-plus: 1.0.0 - bcrypt-pbkdf: 1.0.2 - dashdash: 1.14.1 - ecc-jsbn: 0.1.2 - getpass: 0.1.7 - jsbn: 0.1.1 - safer-buffer: 2.1.2 - tweetnacl: 0.14.5 - - stack-utils@1.0.5: - dependencies: - escape-string-regexp: 2.0.0 - - static-extend@0.1.2: - dependencies: - define-property: 0.2.5 - object-copy: 0.1.0 - - stealthy-require@1.1.1: {} - - stop-iteration-iterator@1.1.0: - dependencies: - es-errors: 1.3.0 - internal-slot: 1.1.0 - - string-length@2.0.0: - dependencies: - astral-regex: 1.0.0 - strip-ansi: 4.0.0 - - string-width@3.1.0: - dependencies: - emoji-regex: 7.0.3 - is-fullwidth-code-point: 2.0.0 - strip-ansi: 5.2.0 - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string.prototype.matchall@4.0.12: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-symbols: 1.1.0 - internal-slot: 1.1.0 - regexp.prototype.flags: 1.5.4 - set-function-name: 2.0.2 - side-channel: 1.1.0 - - string.prototype.repeat@1.0.0: - dependencies: - define-properties: 1.2.1 - es-abstract: 1.24.2 - - string.prototype.trim@1.2.10: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-data-property: 1.1.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.1 - has-property-descriptors: 1.0.2 - - string.prototype.trimend@1.0.9: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - string.prototype.trimstart@1.0.8: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - strip-ansi@4.0.0: - dependencies: - ansi-regex: 3.0.1 - - strip-ansi@5.2.0: - dependencies: - ansi-regex: 4.1.1 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-bom@3.0.0: {} - - strip-eof@1.0.0: {} - - strip-json-comments@3.1.1: {} - - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - - supports-color@6.1.0: - dependencies: - has-flag: 3.0.0 - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-preserve-symlinks-flag@1.0.0: {} - - symbol-tree@3.2.4: {} - - table@5.4.6: - dependencies: - ajv: 6.14.0 - lodash: 4.18.1 - slice-ansi: 2.1.0 - string-width: 3.1.0 - - temp@0.8.4: - dependencies: - rimraf: 2.6.3 - - test-exclude@5.2.3: - dependencies: - glob: 7.2.3 - minimatch: 3.1.5 - read-pkg-up: 4.0.0 - require-main-filename: 2.0.0 - - text-table@0.2.0: {} - - throat@4.1.0: {} - - through@2.3.8: {} - - tmp@0.0.33: - dependencies: - os-tmpdir: 1.0.2 - - tmpl@1.0.5: {} - - to-object-path@0.3.0: - dependencies: - kind-of: 3.2.2 - - to-regex-range@2.1.1: - dependencies: - is-number: 3.0.0 - repeat-string: 1.6.1 - - to-regex@3.0.2: - dependencies: - define-property: 2.0.2 - extend-shallow: 3.0.2 - regex-not: 1.0.2 - safe-regex: 1.1.0 - - tough-cookie@2.5.0: - dependencies: - psl: 1.15.0 - punycode: 2.3.1 - - tr46@1.0.1: - dependencies: - punycode: 2.3.1 - - ts-jest@24.3.0(jest@24.9.0): - dependencies: - bs-logger: 0.2.6 - buffer-from: 1.1.2 - fast-json-stable-stringify: 2.1.0 - jest: 24.9.0 - json5: 2.2.3 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - mkdirp: 0.5.6 - resolve: 1.22.12 - semver: 5.7.2 - yargs-parser: 10.1.0 - - tslib@1.14.1: {} - - tslib@2.8.1: {} - - tunnel-agent@0.6.0: - dependencies: - safe-buffer: 5.2.1 - - tweetnacl@0.14.5: {} - - type-check@0.3.2: - dependencies: - prelude-ls: 1.1.2 - - type-fest@0.21.3: {} - - type-fest@0.8.1: {} - - typed-array-buffer@1.0.3: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-typed-array: 1.1.15 - - typed-array-byte-length@1.0.3: - dependencies: - call-bind: 1.0.9 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 - - typed-array-byte-offset@1.0.4: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.9 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 - reflect.getprototypeof: 1.0.10 - - typed-array-length@1.0.7: - dependencies: - call-bind: 1.0.9 - for-each: 0.3.5 - gopd: 1.2.0 - is-typed-array: 1.1.15 - possible-typed-array-names: 1.1.0 - reflect.getprototypeof: 1.0.10 - - typescript@4.8.4: {} - - unbox-primitive@1.1.0: - dependencies: - call-bound: 1.0.4 - has-bigints: 1.1.0 - has-symbols: 1.1.0 - which-boxed-primitive: 1.1.1 - - unicode-canonical-property-names-ecmascript@2.0.1: {} - - unicode-match-property-ecmascript@2.0.0: - dependencies: - unicode-canonical-property-names-ecmascript: 2.0.1 - unicode-property-aliases-ecmascript: 2.2.0 - - unicode-match-property-value-ecmascript@2.2.1: {} - - unicode-property-aliases-ecmascript@2.2.0: {} - - union-value@1.0.1: - dependencies: - arr-union: 3.1.0 - get-value: 2.0.6 - is-extendable: 0.1.1 - set-value: 2.0.1 - - unset-value@1.0.0: - dependencies: - has-value: 0.3.1 - isobject: 3.0.1 - - update-browserslist-db@1.2.3(browserslist@4.28.2): - dependencies: - browserslist: 4.28.2 - escalade: 3.2.0 - picocolors: 1.1.1 - - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - - urix@0.1.0: {} - - use@3.1.1: {} - - util.promisify@1.1.3: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-data-property: 1.1.4 - define-properties: 1.2.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - for-each: 0.3.5 - get-intrinsic: 1.3.0 - has-proto: 1.2.0 - has-symbols: 1.1.0 - object.getownpropertydescriptors: 2.1.9 - safe-array-concat: 1.1.3 - - uuid@3.4.0: {} - - v8-compile-cache@2.4.0: {} - - validate-npm-package-license@3.0.4: - dependencies: - spdx-correct: 3.2.0 - spdx-expression-parse: 3.0.1 - - verror@1.10.0: - dependencies: - assert-plus: 1.0.0 - core-util-is: 1.0.2 - extsprintf: 1.3.0 - - w3c-hr-time@1.0.2: - dependencies: - browser-process-hrtime: 1.0.0 - - walker@1.0.8: - dependencies: - makeerror: 1.0.12 - - webidl-conversions@4.0.2: {} - - whatwg-encoding@1.0.5: - dependencies: - iconv-lite: 0.4.24 - - whatwg-mimetype@2.3.0: {} - - whatwg-url@6.5.0: - dependencies: - lodash.sortby: 4.7.0 - tr46: 1.0.1 - webidl-conversions: 4.0.2 - - whatwg-url@7.1.0: - dependencies: - lodash.sortby: 4.7.0 - tr46: 1.0.1 - webidl-conversions: 4.0.2 - - which-boxed-primitive@1.1.1: - dependencies: - is-bigint: 1.1.0 - is-boolean-object: 1.2.2 - is-number-object: 1.1.1 - is-string: 1.1.1 - is-symbol: 1.1.1 - - which-builtin-type@1.2.1: - dependencies: - call-bound: 1.0.4 - function.prototype.name: 1.1.8 - has-tostringtag: 1.0.2 - is-async-function: 2.1.1 - is-date-object: 1.1.0 - is-finalizationregistry: 1.1.1 - is-generator-function: 1.1.2 - is-regex: 1.2.1 - is-weakref: 1.1.1 - isarray: 2.0.5 - which-boxed-primitive: 1.1.1 - which-collection: 1.0.2 - which-typed-array: 1.1.20 - - which-collection@1.0.2: - dependencies: - is-map: 2.0.3 - is-set: 2.0.3 - is-weakmap: 2.0.2 - is-weakset: 2.0.4 - - which-module@2.0.1: {} - - which-typed-array@1.1.20: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.9 - call-bound: 1.0.4 - for-each: 0.3.5 - get-proto: 1.0.1 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - - which@1.3.1: - dependencies: - isexe: 2.0.0 - - word-wrap@1.2.5: {} - - wrap-ansi@5.1.0: - dependencies: - ansi-styles: 3.2.1 - string-width: 3.1.0 - strip-ansi: 5.2.0 - - wrappy@1.0.2: {} - - write-file-atomic@2.4.1: - dependencies: - graceful-fs: 4.2.11 - imurmurhash: 0.1.4 - signal-exit: 3.0.7 - - write-file-atomic@2.4.3: - dependencies: - graceful-fs: 4.2.11 - imurmurhash: 0.1.4 - signal-exit: 3.0.7 - - write@1.0.3: - dependencies: - mkdirp: 0.5.6 - - ws@5.2.4: - dependencies: - async-limiter: 1.0.1 - - xml-name-validator@3.0.0: {} - - y18n@4.0.3: {} - - yallist@3.1.1: {} - - yargs-parser@10.1.0: - dependencies: - camelcase: 4.1.0 - - yargs-parser@13.1.2: - dependencies: - camelcase: 5.3.1 - decamelize: 1.2.0 - - yargs@13.3.2: - dependencies: - cliui: 5.0.0 - find-up: 3.0.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - require-main-filename: 2.0.0 - set-blocking: 2.0.0 - string-width: 3.1.0 - which-module: 2.0.1 - y18n: 4.0.3 - yargs-parser: 13.1.2 diff --git a/codemods/legacy/transforms/React-DOM-to-react-dom-factories.js b/codemods/legacy/transforms/React-DOM-to-react-dom-factories.js deleted file mode 100644 index 55a8b4a..0000000 --- a/codemods/legacy/transforms/React-DOM-to-react-dom-factories.js +++ /dev/null @@ -1,177 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -module.exports = function(file, api, options) { - const j = api.jscodeshift; - const printOptions = options.printOptions || { quote: 'single' }; - const root = j(file.source); - - let hasModifications; - - const DOMModuleName = 'DOM'; - - /** - * Replaces 'DOM' with 'createElement' in places where we grab 'DOM' out of - * 'React' with destructuring. - * Note that this only picks up 'DOM' when required from React or - * require('react') - */ - const replaceDestructuredDOMStatement = (j, root) => { - let hasModifications = false; - //--------- - // First update import statments. eg: - // import { - // DOM, - // foo, - // } from 'react'; - root - .find(j.ImportDeclaration) - .filter( - path => - path.node.specifiers.filter( - specifier => - specifier.imported && specifier.imported.name === DOMModuleName - ).length > 0 && path.node.source.value === 'react' - ) - .forEach(path => { - hasModifications = true; - - // Replace the DOM key with 'createElement' - path.node.specifiers = path.node.specifiers.map(specifier => { - if (specifier.imported && specifier.imported.name === DOMModuleName) { - return j.importSpecifier(j.identifier('createElement')); - } else { - return specifier; - } - }); - }); - - //--------- - // Next update require statments. - // This matches both - // const { - // Component, - // DOM, - // } = React; - // and - // const { - // Component, - // DOM, - // } = require('react'); - root - .find(j.ObjectPattern) - .filter( - path => - path.parent.node.init && - // matches '} = React;' - (path.parent.node.init.name === 'React' || - // matches "} = require('react');" - (path.parent.node.init.type === 'CallExpression' && - path.parent.node.init.callee.name === 'require' && - path.parent.node.init.arguments[0].value === 'react')) && - path.node.properties.some(property => { - return property.key.name === DOMModuleName; - }) - ) - .forEach(path => { - hasModifications = true; - - // Replace the DOM key with 'createElement' - path.node.properties = path.node.properties.map(property => { - if (property.key.name === DOMModuleName) { - return j.identifier('createElement'); - } else { - return property; - } - }); - }); - return hasModifications; - }; - - hasModifications = - replaceDestructuredDOMStatement(j, root) || hasModifications; - - if (hasModifications) { - // if we 'hasModifications' then we found and replaced a reference to - // '{DOM} = React;' or '{DOM} = require('react');' - // In this case we need to update 'DOM.' syntax - - /** - * Update cases where DOM.div is being called - * eg 'foo = DOM.div('a'...' - * replace with 'foo = createElement('div', 'a'...' - */ - function replaceDOMReferences(j, root) { - let hasModifications = false; - - const isDOMIdentifier = path => - path.node.name === DOMModuleName && - path.parent.parent.node.type === 'CallExpression'; - - root - .find(j.Identifier) - .filter(isDOMIdentifier) - .forEach(path => { - hasModifications = true; - - const DOMargs = path.parent.parent.node.arguments; - const DOMFactoryPath = path.parent.node.property; - const DOMFactoryType = DOMFactoryPath.name; - - // DOM.div(... -> createElement(... - j(path.parent).replaceWith(j.identifier('createElement')); - // createElement(... -> createElement('div', ... - DOMargs.unshift(j.literal(DOMFactoryType)); - }); - - return hasModifications; - } - - hasModifications = replaceDOMReferences(j, root) || hasModifications; - } - - /** - * Update React.DOM references - * eg 'foo = React.DOM.div('a'...' - * replace with 'foo = React.createElement('div', 'a'...' - */ - function replaceReactDOMReferences(j, root) { - let hasModifications = false; - - // matches 'React.DOM' - const isReactDOMIdentifier = path => - path.node.name === DOMModuleName && - (path.parent.node.type === 'MemberExpression' && - path.parent.node.object.name === 'React'); - - root - .find(j.Identifier) - .filter(isReactDOMIdentifier) - .forEach(path => { - hasModifications = true; - const DOMargs = path.parent.parent.parent.node.arguments; - const DOMFactoryPath = path.parent.parent.node.property; - const DOMFactoryType = DOMFactoryPath.name; - - // React.DOM.div(... -> React.DOM.createElement(... - path.parent.parent.node.property = j.identifier('createElement'); - // React.DOM.createElement(... -> React.createElement(... - j(path.parent).replaceWith(j.identifier('React')); - // React.createElement(... -> React.createElement('div'... - DOMargs.unshift(j.literal(DOMFactoryType)); - }); - - return hasModifications; - } - - hasModifications = replaceReactDOMReferences(j, root) || hasModifications; - - return hasModifications ? root.toSource(printOptions) : null; -}; diff --git a/codemods/legacy/transforms/ReactNative-View-propTypes.js b/codemods/legacy/transforms/ReactNative-View-propTypes.js deleted file mode 100644 index 70ec81a..0000000 --- a/codemods/legacy/transforms/ReactNative-View-propTypes.js +++ /dev/null @@ -1,218 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -const isReactNativeImport = path => - path.parent.node.source.value === 'react-native'; - -const isReactNativeRequire = path => - path.node.arguments.some(argument => argument.value === 'react-native'); - -const isRootViewReference = path => - path.node.name === 'View' && - (path.parent.node.type !== 'MemberExpression' || - path.parent.node.object === path.node) && - (path.node.type !== 'JSXIdentifier' || - path.parent.node.type === 'JSXOpeningElement') && - (path.parent.node.type !== 'ImportSpecifier' || - path.parent.node.imported === path.node) && - (path.parent.node.type !== 'Property' || path.parent.node.key === path.node); - -const isViewImport = path => - path.node.specifiers.some( - specifier => - (specifier.imported && specifier.imported.name === 'View') || - (specifier.local && specifier.local.name === 'View') - ); - -const isViewRequire = path => - path.node.callee.type === 'Identifier' && - path.parent.node.type === 'VariableDeclarator' && - ((path.parent.node.id.type === 'Identifier' && - path.parent.node.id.name === 'View') || - (path.parent.node.id.type === 'ObjectPattern' && - path.parent.node.id.properties.some( - property => property.value.name === 'View' - ))); - -const isViewPropTypes = path => - path.node.name === 'propTypes' && - path.parent.node.type === 'MemberExpression' && - path.parent.value.object.name === 'View'; - -// Note that this codemod may introduce an unnecessary newline before certain types of imports -// This is not a problem with the codemod but with recast -// See https://github.com/facebook/jscodeshift/issues/185 -// See https://github.com/benjamn/recast/issues/371 -module.exports = function(file, api, options) { - const j = api.jscodeshift; - - const printOptions = options.printOptions || { quote: 'single' }; - let root = j(file.source); - - let numMatchedPaths = 0; - - // Search for all View references - const viewReferenceCount = root.find(j.Identifier).filter(isRootViewReference) - .length; - - // Replace View.propTypes with ViewPropTypes - root - .find(j.Identifier) - .filter(isViewPropTypes) - .forEach(path => { - numMatchedPaths++; - - j(path.parent).replaceWith(j.identifier('ViewPropTypes')); - }); - - // Add ViewPropTypes import/require() - if (numMatchedPaths > 0) { - const fileUsesImports = - root.find(j.CallExpression, { callee: { name: 'require' } }).length === 0; - - // Determine which kind of import/require() we should create based on file contents - let useHasteModules = false; - if (fileUsesImports) { - useHasteModules = - root.find(j.ImportSpecifier).filter(isReactNativeImport).length === 0; - } else { - useHasteModules = - root - .find(j.CallExpression, { callee: { name: 'require' } }) - .filter(isReactNativeRequire).length === 0; - } - - // Create a require statement or an import, based on file convention - let importOrRequireStatement; - if (fileUsesImports) { - const identifier = j.identifier('ViewPropTypes'); - const variable = - useHasteModules === true - ? j.importDefaultSpecifier(identifier) - : j.importSpecifier(identifier); - const source = - useHasteModules === true ? 'ViewPropTypes' : 'react-native'; - - importOrRequireStatement = j.importDeclaration( - [variable], - j.literal(source) - ); - } else { - if (useHasteModules === true) { - importOrRequireStatement = j.template.statement` - const ViewPropTypes = require('ViewPropTypes'); - `; - } else { - importOrRequireStatement = j.template.statement` - const { ViewPropTypes } = require('react-native'); - `; - } - } - - // If the only View reference left is the import/require(), replace it - // Else insert our new import/require() after it - // Add one to avoid counting the import/require() statement itself - const replaceExistingImportOrRequireStatement = - viewReferenceCount <= numMatchedPaths + 1; - - if (fileUsesImports) { - root - .find(j.ImportDeclaration) - .filter(isViewImport) - .forEach(path => { - // Differentiate between destructured and default import - if (path.node.specifiers.length > 1) { - if (useHasteModules) { - // Insert after before removing to avoid an error - j(path).insertAfter(importOrRequireStatement); - - if (replaceExistingImportOrRequireStatement) { - // If this is the last reference to a destructured import, remove it - // We can't replace in this case b'c the target/source is different - path.node.specifiers = path.node.specifiers.filter( - specifier => specifier.local.name !== 'View' - ); - } - } else { - if (replaceExistingImportOrRequireStatement) { - const viewImport = path.node.specifiers.find( - specifier => specifier.local.name === 'View' - ); - - viewImport.local.name = 'ViewPropTypes'; - } else { - path.node.specifiers.push( - j.importSpecifier( - j.identifier('ViewPropTypes'), - j.identifier('ViewPropTypes') - ) - ); - } - } - } else { - if (replaceExistingImportOrRequireStatement) { - j(path).replaceWith(importOrRequireStatement); - } else { - j(path).insertAfter(importOrRequireStatement); - } - } - }); - } else { - root - .find(j.CallExpression, { callee: { name: 'require' } }) - .filter(isViewRequire) - .forEach(path => { - // Differentiate between destructured and default require() - if (path.parent.node.id.type === 'ObjectPattern') { - if (useHasteModules) { - // Insert after before removing to avoid an error - j(path.parent.parent).insertAfter(importOrRequireStatement); - - if (replaceExistingImportOrRequireStatement) { - // If this is the last reference to a destructured import, remove it - // We can't replace in this case b'c the target/source is different - const variableDeclarator = - path.parent.parent.value.declarations[0]; - variableDeclarator.id.properties = variableDeclarator.id.properties.filter( - property => property.value.name !== 'View' - ); - } - } else { - const objectPattern = path.parent; - if (replaceExistingImportOrRequireStatement) { - const property = objectPattern.node.id.properties.find( - property => property.value.name === 'View' - ); - - property.key.name = 'ViewPropTypes'; - } else { - const property = j.property( - 'init', - j.identifier('ViewPropTypes'), - j.identifier('ViewPropTypes') - ); - property.shorthand = true; - - objectPattern.node.id.properties.push(property); - } - } - } else { - if (replaceExistingImportOrRequireStatement) { - j(path.parent.parent).replaceWith(importOrRequireStatement); - } else { - j(path.parent.parent).insertAfter(importOrRequireStatement); - } - } - }); - } - } - - return numMatchedPaths > 0 ? root.toSource(printOptions) : null; -}; diff --git a/codemods/legacy/transforms/__testfixtures__/.eslintrc b/codemods/legacy/transforms/__testfixtures__/.eslintrc deleted file mode 100644 index 4a2e77c..0000000 --- a/codemods/legacy/transforms/__testfixtures__/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ ---- -rules: - no-undef: 0 - no-unused-vars: 0 - no-redeclare: 0 diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-import-dom-from-other-libraries.output.js b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-import-dom-from-other-libraries.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-import.output.js b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-import.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-local-dom-from-other-libraries.output.js b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-local-dom-from-other-libraries.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-require-dom-from-other-libraries.output.js b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-require-dom-from-other-libraries.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-require.output.js b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-require.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/noop-import.output.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/noop-import.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/noop-require.output.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/noop-require.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-anonymous.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-anonymous.input.js deleted file mode 100644 index 22b4ca7..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-anonymous.input.js +++ /dev/null @@ -1,21 +0,0 @@ -var React = require('react'); - -const wrapper = (x) => x; - -const Foo = wrapper(React.createClass({ - render() { - return
wow so anonymous
; - }, -})); - -module.exports = wrapper(React.createClass({ - render() { - return
wow so anonymous
; - }, -})); - -export default wrapper(React.createClass({ - render() { - return
wow so anonymous
; - }, -})); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-anonymous.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-anonymous.output.js deleted file mode 100644 index c90ecbc..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-anonymous.output.js +++ /dev/null @@ -1,21 +0,0 @@ -var React = require('react'); - -const wrapper = (x) => x; - -const Foo = wrapper(class extends React.Component { - render() { - return
wow so anonymous
; - } -}); - -module.exports = wrapper(class extends React.Component { - render() { - return
wow so anonymous
; - } -}); - -export default wrapper(class extends React.Component { - render() { - return
wow so anonymous
; - } -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-anonymous2.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-anonymous2.input.js deleted file mode 100644 index a902474..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-anonymous2.input.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @flow - */ -/* eslint-disable no-use-before-define */ -'use strict'; - -var React = require('React'); - -var CrazyObject = { - foo: { - bar: 123, - }, - method: { - wrapThisGuy: (x) => x, - deep: { - wrapThatGuy: (x) => x, - }, - }, - iDontUnderstand: { - whyYouDoThis: { - butAnyway: { - comp1: React.createClass({ - render() { - return
; - }, - }), - comp2: CrazyObject.method.wrapThatGuy(React.createClass({ - render() { - return
; - }, - })), - waitWhatArrayForReal: [React.createClass({ - render() { - return
; - }, - }), [React.createClass({ - render() { - return

; - }, - }), React.createClass({ - render() { - return ; - }, - })]], - }, - }, - }, -}; - -module.exports = WaltUtils; diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-anonymous2.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-anonymous2.output.js deleted file mode 100644 index 1b7a8fc..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-anonymous2.output.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @flow - */ -/* eslint-disable no-use-before-define */ -'use strict'; - -var React = require('React'); - -var CrazyObject = { - foo: { - bar: 123, - }, - method: { - wrapThisGuy: (x) => x, - deep: { - wrapThatGuy: (x) => x, - }, - }, - iDontUnderstand: { - whyYouDoThis: { - butAnyway: { - comp1: class extends React.Component { - render() { - return

; - } - }, - comp2: CrazyObject.method.wrapThatGuy(class extends React.Component { - render() { - return
; - } - }), - waitWhatArrayForReal: [class extends React.Component { - render() { - return
; - } - }, [class extends React.Component { - render() { - return

; - } - }, class extends React.Component { - render() { - return ; - } - }]], - }, - }, - }, -}; - -module.exports = WaltUtils; diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-create-class-naming.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-create-class-naming.input.js deleted file mode 100644 index 5d6ed33..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-create-class-naming.input.js +++ /dev/null @@ -1,12 +0,0 @@ -// Uses options: -// --create-class-module-name=createReactClass__deprecated -// --create-class-variable-name=createReactClass__deprecated - -const React = require('react'); - -const Component = React.createClass({ - mixins: [{}], - render() { - return

; - } -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-create-class-naming.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-create-class-naming.output.js deleted file mode 100644 index 85e9385..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-create-class-naming.output.js +++ /dev/null @@ -1,16 +0,0 @@ -// Uses options: -// --create-class-module-name=createReactClass__deprecated -// --create-class-variable-name=createReactClass__deprecated - -const React = require('react'); - -const createReactClass__deprecated = require('createReactClass__deprecated'); - -const Component = createReactClass__deprecated({ - displayName: 'Component', - mixins: [{}], - - render() { - return
; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-displayName.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-displayName.input.js deleted file mode 100644 index 61aac9c..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-displayName.input.js +++ /dev/null @@ -1,31 +0,0 @@ -const React = require('React'); - -let A = React.createClass({ - mixins: [], - render() { - return
; - }, -}); - -A = React.createClass({ - mixins: [], - render() { - return
; - }, -}); - -const obj = { - B: React.createClass({ - mixins: [], - render() { - return
; - }, - }), -}; - -export default React.createClass({ - mixins: [], - render() { - return
; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-displayName.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-displayName.output.js deleted file mode 100644 index 1018735..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-displayName.output.js +++ /dev/null @@ -1,41 +0,0 @@ -const React = require('React'); - -const createReactClass = require('create-react-class'); - -let A = createReactClass({ - displayName: 'A', - mixins: [], - - render() { - return
; - }, -}); - -A = createReactClass({ - displayName: 'A', - mixins: [], - - render() { - return
; - }, -}); - -const obj = { - B: createReactClass({ - displayName: 'B', - mixins: [], - - render() { - return
; - }, - }), -}; - -export default createReactClass({ - displayName: 'class-displayName.input', - mixins: [], - - render() { - return
; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-flow1.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-flow1.input.js deleted file mode 100644 index da855cb..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-flow1.input.js +++ /dev/null @@ -1,40 +0,0 @@ -/* @flow */ - -var React = require('react'); - -var Component = React.createClass({ - propTypes: { - optionalArray: React.PropTypes.array, - optionalBool: React.PropTypes.bool, - optionalFunc: React.PropTypes.func, - optionalNumber: React.PropTypes.number, - optionalObject: React.PropTypes.object, - optionalString: React.PropTypes.string, - optionalNode: React.PropTypes.node, - optionalElement: React.PropTypes.element, - optionalMessage: React.PropTypes.instanceOf(Message), - optionalEnum: React.PropTypes.oneOf(['News', 'Photos', 1, true, null, undefined]), - optionalUnion: React.PropTypes.oneOfType([ - React.PropTypes.string, - React.PropTypes.number, - React.PropTypes.instanceOf(Message), - ]), - optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), - optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), - optionalObjectOfRequiredField: React.PropTypes.objectOf(React.PropTypes.number.isRequired), - requiredObjectOfRequiredField: React.PropTypes.objectOf(React.PropTypes.number.isRequired).isRequired, - requiredObjectOfOptionalField: React.PropTypes.objectOf(React.PropTypes.number).isRequired, - optionalObjectWithShape: React.PropTypes.shape({ - color: React.PropTypes.string, - fontSize: React.PropTypes.number.isRequired, - }), - requiredFunc: React.PropTypes.func.isRequired, - requiredAny: React.PropTypes.any.isRequired, - }, - - render: function() { - return ( -
type safety
- ); - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-flow1.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-flow1.output.js deleted file mode 100644 index 7e50622..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-flow1.output.js +++ /dev/null @@ -1,65 +0,0 @@ -/* @flow */ - -var React = require('react'); - -class Component extends React.Component { - props: { - optionalArray?: Array<$FlowFixMe>, - optionalBool?: boolean, - optionalFunc?: Function, - optionalNumber?: number, - optionalObject?: Object, - optionalString?: string, - optionalNode?: $FlowFixMe, - optionalElement?: $FlowFixMe, - optionalMessage?: Message, - optionalEnum?: 'News' | 'Photos' | 1 | true | null | void, - optionalUnion?: string | number | Message, - optionalArrayOf?: Array, - optionalObjectOf?: {[key: string]: number}, - optionalObjectOfRequiredField?: {[key: string]: number}, - requiredObjectOfRequiredField: {[key: string]: number}, - requiredObjectOfOptionalField: {[key: string]: number}, - optionalObjectWithShape?: { - color?: string, - fontSize: number, - }, - requiredFunc: Function, - requiredAny: any, - }; - - static propTypes = { - optionalArray: React.PropTypes.array, - optionalBool: React.PropTypes.bool, - optionalFunc: React.PropTypes.func, - optionalNumber: React.PropTypes.number, - optionalObject: React.PropTypes.object, - optionalString: React.PropTypes.string, - optionalNode: React.PropTypes.node, - optionalElement: React.PropTypes.element, - optionalMessage: React.PropTypes.instanceOf(Message), - optionalEnum: React.PropTypes.oneOf(['News', 'Photos', 1, true, null, undefined]), - optionalUnion: React.PropTypes.oneOfType([ - React.PropTypes.string, - React.PropTypes.number, - React.PropTypes.instanceOf(Message), - ]), - optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), - optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), - optionalObjectOfRequiredField: React.PropTypes.objectOf(React.PropTypes.number.isRequired), - requiredObjectOfRequiredField: React.PropTypes.objectOf(React.PropTypes.number.isRequired).isRequired, - requiredObjectOfOptionalField: React.PropTypes.objectOf(React.PropTypes.number).isRequired, - optionalObjectWithShape: React.PropTypes.shape({ - color: React.PropTypes.string, - fontSize: React.PropTypes.number.isRequired, - }), - requiredFunc: React.PropTypes.func.isRequired, - requiredAny: React.PropTypes.any.isRequired, - }; - - render() { - return ( -
type safety
- ); - } -} diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-flow2.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-flow2.input.js deleted file mode 100644 index 490706c..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-flow2.input.js +++ /dev/null @@ -1,52 +0,0 @@ -/* code taken from https://github.com/reactjs/react-router/blob/master/modules/IndexRoute.js */ -/* @flow */ - -import React from 'react' -import warning from './routerWarning' -import invariant from 'invariant' -import { createRouteFromReactElement } from './RouteUtils' -import { component, components, falsy } from './InternalPropTypes' - -const { func } = React.PropTypes - -/** - * An is used to specify its parent's in - * a JSX route config. - */ -const IndexRoute = React.createClass({ - - statics: { - - createRouteFromReactElement(element, parentRoute) { - /* istanbul ignore else: sanity check */ - if (parentRoute) { - parentRoute.indexRoute = createRouteFromReactElement(element) - } else { - warning( - false, - 'An does not make sense at the root of your route config' - ) - } - } - - }, - - propTypes: { - path: falsy, - component, - components, - getComponent: func, - getComponents: func - }, - - /* istanbul ignore next: sanity check */ - render() { - invariant( - false, - ' elements are for router configuration only and should not be rendered' - ) - } - -}) - -export default IndexRoute diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-flow2.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-flow2.output.js deleted file mode 100644 index 3a6b74d..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-flow2.output.js +++ /dev/null @@ -1,54 +0,0 @@ -/* code taken from https://github.com/reactjs/react-router/blob/master/modules/IndexRoute.js */ -/* @flow */ - -import React from 'react' -import warning from './routerWarning' -import invariant from 'invariant' -import { createRouteFromReactElement } from './RouteUtils' -import { component, components, falsy } from './InternalPropTypes' - -const { func } = React.PropTypes - -/** - * An is used to specify its parent's in - * a JSX route config. - */ -class IndexRoute extends React.Component { - props: { - path?: $FlowFixMe, - component?: $FlowFixMe, - components?: $FlowFixMe, - getComponent?: $FlowFixMe, - getComponents?: $FlowFixMe, - }; - - static createRouteFromReactElement(element, parentRoute) { - /* istanbul ignore else: sanity check */ - if (parentRoute) { - parentRoute.indexRoute = createRouteFromReactElement(element) - } else { - warning( - false, - 'An does not make sense at the root of your route config' - ) - } - } - - static propTypes = { - path: falsy, - component, - components, - getComponent: func, - getComponents: func - }; - - /* istanbul ignore next: sanity check */ - render() { - invariant( - false, - ' elements are for router configuration only and should not be rendered' - ) - } -} - -export default IndexRoute diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-flow3.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-flow3.input.js deleted file mode 100644 index f42dfd4..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-flow3.input.js +++ /dev/null @@ -1,47 +0,0 @@ -/* @flow */ - -var React = require('react'); -var {PropTypes} = React; - -var getPropTypes = () => PropTypes.string; - -var myUnionPropType = PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - PropTypes.instanceOf(Message), -]); - -var spreadMe = { - optionalArray: PropTypes.array, - optionalBool: PropTypes.bool, -}; - -var optionalFuncShortHand = PropTypes.func; - -var Component = React.createClass({ - propTypes: { - ...spreadMe, - optionalFuncShortHand, - optionalNumber: 1 + 1 === 2 ? PropTypes.number : PropTypes.string, - optionalObject: PropTypes.object, - optionalString: getPropTypes(), - optionalNode: PropTypes.node, - optionalElement: PropTypes.element, - optionalMessage: PropTypes.instanceOf(Message), - optionalEnum: PropTypes.oneOf(['News', 'Photos', 1, true, null]), - optionalUnion: myUnionPropType, - optionalArrayOf: PropTypes.arrayOf(PropTypes.number), - optionalObjectOf: PropTypes.objectOf(PropTypes.number), - optionalObjectWithShape: PropTypes.shape({ - color: PropTypes.string, - }), - requiredFunc: PropTypes.func.isRequired, - requiredAny: PropTypes.any.isRequired, - }, - - render: function() { - return ( -
type safety
- ); - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-flow3.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-flow3.output.js deleted file mode 100644 index 932b233..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-flow3.output.js +++ /dev/null @@ -1,64 +0,0 @@ -/* @flow */ - -var React = require('react'); -var {PropTypes} = React; - -var getPropTypes = () => PropTypes.string; - -var myUnionPropType = PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - PropTypes.instanceOf(Message), -]); - -var spreadMe = { - optionalArray: PropTypes.array, - optionalBool: PropTypes.bool, -}; - -var optionalFuncShortHand = PropTypes.func; - -class Component extends React.Component { - props: { - optionalFuncShortHand?: $FlowFixMe, - optionalNumber?: $FlowFixMe, - optionalObject?: Object, - optionalString?: $FlowFixMe, - optionalNode?: $FlowFixMe, - optionalElement?: $FlowFixMe, - optionalMessage?: Message, - optionalEnum?: 'News' | 'Photos' | 1 | true | null, - optionalUnion?: $FlowFixMe, - optionalArrayOf?: Array, - optionalObjectOf?: {[key: string]: number}, - optionalObjectWithShape?: {color?: string}, - requiredFunc: Function, - requiredAny: any, - }; - - static propTypes = { - ...spreadMe, - optionalFuncShortHand, - optionalNumber: 1 + 1 === 2 ? PropTypes.number : PropTypes.string, - optionalObject: PropTypes.object, - optionalString: getPropTypes(), - optionalNode: PropTypes.node, - optionalElement: PropTypes.element, - optionalMessage: PropTypes.instanceOf(Message), - optionalEnum: PropTypes.oneOf(['News', 'Photos', 1, true, null]), - optionalUnion: myUnionPropType, - optionalArrayOf: PropTypes.arrayOf(PropTypes.number), - optionalObjectOf: PropTypes.objectOf(PropTypes.number), - optionalObjectWithShape: PropTypes.shape({ - color: PropTypes.string, - }), - requiredFunc: PropTypes.func.isRequired, - requiredAny: PropTypes.any.isRequired, - }; - - render() { - return ( -
type safety
- ); - } -} diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-flow4.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-flow4.input.js deleted file mode 100644 index 9694eb6..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-flow4.input.js +++ /dev/null @@ -1,44 +0,0 @@ -/* @flow */ - -var React = require('react'); -var {PropTypes} = React; - -var myUnionPropType = PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - PropTypes.instanceOf(Message), -]); - -var spreadMe = { - optionalArray: PropTypes.array, - optionalBool: PropTypes.bool, -}; - -var optionalFuncShortHand = PropTypes.func; - -var Component = React.createClass({ - propTypes: Object.assign({}, { - ...spreadMe, - optionalFuncShortHand, - optionalNumber: PropTypes.number, - optionalObject: PropTypes.object, - }), - - render: function() { - return ( -
type safety
- ); - }, -}); - -var thatPropTypes = {}; - -var Component2 = React.createClass({ - propTypes: thatPropTypes, - - render: function() { - return ( -
type safety
- ); - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-flow4.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-flow4.output.js deleted file mode 100644 index a25c625..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-flow4.output.js +++ /dev/null @@ -1,44 +0,0 @@ -/* @flow */ - -var React = require('react'); -var {PropTypes} = React; - -var myUnionPropType = PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - PropTypes.instanceOf(Message), -]); - -var spreadMe = { - optionalArray: PropTypes.array, - optionalBool: PropTypes.bool, -}; - -var optionalFuncShortHand = PropTypes.func; - -class Component extends React.Component { - static propTypes = Object.assign({}, { - ...spreadMe, - optionalFuncShortHand, - optionalNumber: PropTypes.number, - optionalObject: PropTypes.object, - }); - - render() { - return ( -
type safety
- ); - } -} - -var thatPropTypes = {}; - -class Component2 extends React.Component { - static propTypes = thatPropTypes; - - render() { - return ( -
type safety
- ); - } -} diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-flow5.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-flow5.input.js deleted file mode 100644 index 3abae35..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-flow5.input.js +++ /dev/null @@ -1,40 +0,0 @@ -/* @flow */ - -var React = require('react'); - -type SomeStuff = { // TypeParameter - fetch: () => Promise, -}; - -var Component = React.createClass({ - statics: { - notTyped: true, - nothing: (null: null), // NullTypeAnnotation - numberOrBool: (true: number | boolean), - logger: (x: any): void => { console.log(x); }, - logger2: function(x: any): void { - console.log(x); - }, - }, - - notTyped: true, - foo: (12: number), - bar: ('2000': string), - handleClick: (null: ?(evt: any) => void), - - doStuff: function(x: number, y: boolean): boolean { - return y && (x > 0); - }, - - componentDidMount: function() { - this.handleClick = function(e) { - console.log(e); - }; - }, - - render: function() { - return ( -
{this.foo}
- ); - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-flow5.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-flow5.output.js deleted file mode 100644 index ca00112..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-flow5.output.js +++ /dev/null @@ -1,39 +0,0 @@ -/* @flow */ - -var React = require('react'); - -type SomeStuff
= { // TypeParameter - fetch: () => Promise, -}; - -class Component extends React.Component { - static notTyped = true; - static nothing: null = null; // NullTypeAnnotation - static numberOrBool: number | boolean = true; - static logger = (x: any): void => { console.log(x); }; - - static logger2(x: any): void { - console.log(x); - } - - notTyped = true; - foo: number = 12; - bar: string = '2000'; - handleClick: ?(evt: any) => void = null; - - doStuff = (x: number, y: boolean): boolean => { - return y && (x > 0); - }; - - componentDidMount() { - this.handleClick = function(e) { - console.log(e); - }; - } - - render() { - return ( -
{this.foo}
- ); - } -} diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-flow6.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-flow6.input.js deleted file mode 100644 index b008f81..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-flow6.input.js +++ /dev/null @@ -1,41 +0,0 @@ -/* @flow */ - -var React = require('react'); - -const justNeedKeys = { - a: 12, - b: 23, -}; - -var Component = React.createClass({ - propTypes: { - optionalMessage: React.PropTypes.instanceOf(Message), - optionalMessageOops: React.PropTypes.instanceOf(foo()), - optionalEnum: React.PropTypes.oneOf(Object.keys(justNeedKeys)), - optionalEnumOops: React.PropTypes.oneOf(bar), - optionalUnion: React.PropTypes.oneOfType([ - React.PropTypes.string, - React.PropTypes.number, - React.PropTypes.instanceOf(Message), - ]), - optionalUnionOops: React.PropTypes.oneOfType(foo()), - optionalUnionOops2: React.PropTypes.oneOfType(Bar), - optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), - optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), - optionalObjectWithShape: React.PropTypes.shape({ - color: React.PropTypes.string, - fontSize: foo, - name: bla(), - }), - optionalObjectWithShapeOops: React.PropTypes.shape(foo()), - optionalObjectWithShapeOops2: React.PropTypes.shape(bla), - 'is-literal-cool': React.PropTypes.bool, - 'well-fine': React.PropTypes.number.isRequired, - }, - - render: function() { - return ( -
type safety
- ); - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-flow6.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-flow6.output.js deleted file mode 100644 index 1ac3ff0..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-flow6.output.js +++ /dev/null @@ -1,62 +0,0 @@ -/* @flow */ - -var React = require('react'); - -const justNeedKeys = { - a: 12, - b: 23, -}; - -class Component extends React.Component { - props: { - optionalMessage?: Message, - optionalMessageOops?: $FlowFixMe, - optionalEnum?: $FlowFixMe, - optionalEnumOops?: $FlowFixMe, - optionalUnion?: string | number | Message, - optionalUnionOops?: $FlowFixMe, - optionalUnionOops2?: $FlowFixMe, - optionalArrayOf?: Array, - optionalObjectOf?: {[key: string]: number}, - optionalObjectWithShape?: { - color?: string, - fontSize?: $FlowFixMe, - name?: $FlowFixMe, - }, - optionalObjectWithShapeOops?: $FlowFixMe, - optionalObjectWithShapeOops2?: $FlowFixMe, - 'is-literal-cool'?: boolean, - 'well-fine': number, - }; - - static propTypes = { - optionalMessage: React.PropTypes.instanceOf(Message), - optionalMessageOops: React.PropTypes.instanceOf(foo()), - optionalEnum: React.PropTypes.oneOf(Object.keys(justNeedKeys)), - optionalEnumOops: React.PropTypes.oneOf(bar), - optionalUnion: React.PropTypes.oneOfType([ - React.PropTypes.string, - React.PropTypes.number, - React.PropTypes.instanceOf(Message), - ]), - optionalUnionOops: React.PropTypes.oneOfType(foo()), - optionalUnionOops2: React.PropTypes.oneOfType(Bar), - optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), - optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), - optionalObjectWithShape: React.PropTypes.shape({ - color: React.PropTypes.string, - fontSize: foo, - name: bla(), - }), - optionalObjectWithShapeOops: React.PropTypes.shape(foo()), - optionalObjectWithShapeOops2: React.PropTypes.shape(bla), - 'is-literal-cool': React.PropTypes.bool, - 'well-fine': React.PropTypes.number.isRequired, - }; - - render() { - return ( -
type safety
- ); - } -} diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-flow7.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-flow7.input.js deleted file mode 100644 index b008f81..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-flow7.input.js +++ /dev/null @@ -1,41 +0,0 @@ -/* @flow */ - -var React = require('react'); - -const justNeedKeys = { - a: 12, - b: 23, -}; - -var Component = React.createClass({ - propTypes: { - optionalMessage: React.PropTypes.instanceOf(Message), - optionalMessageOops: React.PropTypes.instanceOf(foo()), - optionalEnum: React.PropTypes.oneOf(Object.keys(justNeedKeys)), - optionalEnumOops: React.PropTypes.oneOf(bar), - optionalUnion: React.PropTypes.oneOfType([ - React.PropTypes.string, - React.PropTypes.number, - React.PropTypes.instanceOf(Message), - ]), - optionalUnionOops: React.PropTypes.oneOfType(foo()), - optionalUnionOops2: React.PropTypes.oneOfType(Bar), - optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), - optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), - optionalObjectWithShape: React.PropTypes.shape({ - color: React.PropTypes.string, - fontSize: foo, - name: bla(), - }), - optionalObjectWithShapeOops: React.PropTypes.shape(foo()), - optionalObjectWithShapeOops2: React.PropTypes.shape(bla), - 'is-literal-cool': React.PropTypes.bool, - 'well-fine': React.PropTypes.number.isRequired, - }, - - render: function() { - return ( -
type safety
- ); - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-flow7.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-flow7.output.js deleted file mode 100644 index a30533b..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-flow7.output.js +++ /dev/null @@ -1,37 +0,0 @@ -/* @flow */ - -var React = require('react'); - -const justNeedKeys = { - a: 12, - b: 23, -}; - -class Component extends React.Component { - props: { - optionalMessage?: Message, - optionalMessageOops?: $FlowFixMe, - optionalEnum?: $FlowFixMe, - optionalEnumOops?: $FlowFixMe, - optionalUnion?: string | number | Message, - optionalUnionOops?: $FlowFixMe, - optionalUnionOops2?: $FlowFixMe, - optionalArrayOf?: Array, - optionalObjectOf?: {[key: string]: number}, - optionalObjectWithShape?: { - color?: string, - fontSize?: $FlowFixMe, - name?: $FlowFixMe, - }, - optionalObjectWithShapeOops?: $FlowFixMe, - optionalObjectWithShapeOops2?: $FlowFixMe, - 'is-literal-cool'?: boolean, - 'well-fine': number, - }; - - render() { - return ( -
type safety
- ); - } -} diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-initial-state.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-initial-state.input.js deleted file mode 100644 index bb18702..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-initial-state.input.js +++ /dev/null @@ -1,335 +0,0 @@ -/* @flow */ - -import React from 'React'; - -type SomeState = {foo: string}; - -// only needs props -var MyComponent = React.createClass({ - getInitialState: function(): {heyoo: number} { - var x = this.props.foo; - return { - heyoo: 23, - }; - }, - - foo: function(): void { - this.setState({heyoo: 24}); - }, -}); - -var ComponentWithBothPropsAndContextAccess = React.createClass({ - contextTypes: { - name: React.PropTypes.string, - }, - - // we actually don't need a constructor here since this will be - // initialized after a proper super(props, context) call. - // in other words, `this` will be ready when it reaches here. - getInitialState: function() { - return { - foo: this.props.foo, - bar: this.context.bar, - }; - }, - - render: function() { - return ( -
{this.context.name}
- ); - }, -}); - -const App = React.createClass({ - getInitialState(): SomeState { - const state = this.calculateState(); // _might_ use `this.context` - return state; - }, - calculateState() { - return { color: this.context.color }; - }, - render() { - return
; - }, -}); - -const App2 = React.createClass({ - getInitialState() { - const state = { - whatever: this.context.whatever, // needs context - }; - return state; - }, - render() { - return
; - }, -}); - -App.contextTypes = { - whatever: React.PropTypes.object, -}; - -var MyComponent2 = React.createClass({ - getInitialState: function() { - var x = this.props.foo.bar.wow.so.deep; - return { - heyoo: 23, - }; - }, - - foo: function(): void { - this.setState({heyoo: 24}); - }, -}); - -const getContextFromInstance = (x) => x.context; // meh - -var MyComponent3 = React.createClass({ - getInitialState: function() { - var x = getContextFromInstance(this); // `this` is referenced alone - return { - heyoo: x, - }; - }, - - foo: function(): void { - this.setState({heyoo: 24}); - }, -}); - -// we are not sure what you'll need from `this`, -// so it's safe to defer `state`'s initialization -var MyComponent4 = React.createClass({ - getInitialState: function() { - return { - heyoo: getContextFromInstance(this), - }; - }, - - foo: function(): void { - this.setState({heyoo: 24}); - }, -}); - -// but only accessing `this.props` and/or `this.context` is safe -var MyComponent5 = React.createClass({ - getInitialState: function() { - return { - heyoo: getContextFromInstance(this.props), - }; - }, - - foo: function(): void { - this.setState({heyoo: 24}); - }, -}); - -// intense control flow testing -var Loader = React.createClass({ - getInitialState() { - if (this.props.stuff) { - return {x: 1}; - } else if (this.props.thing) { - return {x: 2}; - } - switch (this.props.wow) { - case 1: - return this.props.lol ? - {x: 3} : - this.whatever(this.props); - } - for (let i = 0; i < 100; i++) { - if (i === 20) { - return {x: i}; - } - } - - try { - doSomeThingReallyBad(); - } catch (e) { - return {error: e}; - } - - return this.lol(); - }, - - render() { - return null; - }, -}); - -var FunctionDeclarationInGetInitialState = React.createClass({ - getInitialState() { - function func() { - var x = 1; - return x; // dont change me - } - - const foo = () => { - return 120; // dont change me - }; - - var q = function() { - return 100; // dont change me - }; - - return { - x: func(), - y: foo(), - z: q(), - }; - }, - - render() { - return null; - }, -}); - -var DeferStateInitialization = React.createClass({ - getInitialState() { - return {x: this.something}; - }, - - something: 42, - - render() { - return
; - }, -}); - -var helper = () => {}; - -// fallback -var PassGetInitialState = React.createClass({ - getInitialState() { - return this.lol(); - }, - - helper1: function() { - helper(this.getInitialState); - }, - - render() { - return null; - }, -}); - -// fallback -var UseGetInitialState = React.createClass({ - getInitialState() { - return this.lol(); - }, - - helper2() { - this.setState(this.getInitialState()); - }, - - render() { - return null; - }, -}); - -// fallback -var UseArguments = React.createClass({ - helper() { - console.log(arguments); - }, - - render() { - return null; - }, -}); - -// fallback -var ShadowingIssue = React.createClass({ - getInitialState() { - const props = { x: 123 }; - return { x: props.x }; - }, - - render() { - return null; - }, -}); - -// will remove unnecessary bindings -var ShadowingButFine = React.createClass({ - getInitialState() { - const props = this.props; - const context = this.context; - return { x: props.x + context.x }; - }, - - render() { - return null; - }, -}); - -// move type annotations -var WithSimpleType = React.createClass({ - getInitialState(): Object { - return { - x: 12, - y: 13, - z: 14, - }; - }, - - render() { - return null; - }, -}); - -var WithLongType = React.createClass({ - getInitialState(): {name: string, age: number, counter: number} { - return { - name: 'Michael', - age: 23, - count: 6, - }; - }, - - render() { - return null; - }, -}); - -var WithMultiLineType = React.createClass({ - getInitialState(): { - nameLists: Array>, - age?: ?number, - counter?: ?number, - } { - return { - nameLists: [['James']], - count: 1400, - foo: 'bar', - }; - }, - - render() { - return null; - }, -}); - -var WithArrowFunction = React.createClass({ - getInitialState: (): {heyoo: number} => { - return { - heyoo: 23, - }; - }, - - render() { - return null; - }, -}); - -var WithArrowFunctionAndObject = React.createClass({ - getInitialState: (): {heyoo: number} => ({ - heyoo: 23, - }), - - render() { - return null; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-initial-state.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-initial-state.output.js deleted file mode 100644 index ee332e3..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-initial-state.output.js +++ /dev/null @@ -1,362 +0,0 @@ -/* @flow */ - -import React from 'React'; - -import createReactClass from 'create-react-class'; - -type SomeState = {foo: string}; - -// only needs props -class MyComponent extends React.Component { - state: {heyoo: number}; - - constructor(props) { - super(props); - var x = props.foo; - - this.state = { - heyoo: 23, - }; - } - - foo = (): void => { - this.setState({heyoo: 24}); - }; -} - -class ComponentWithBothPropsAndContextAccess extends React.Component { - static contextTypes = { - name: React.PropTypes.string, - }; - - // we actually don't need a constructor here since this will be - // initialized after a proper super(props, context) call. - // in other words, `this` will be ready when it reaches here. - state = { - foo: this.props.foo, - bar: this.context.bar, - }; - - render() { - return ( -
{this.context.name}
- ); - } -} - -class App extends React.Component { - state: SomeState; - - constructor(props, context) { - super(props, context); - const state = this.calculateState(); // _might_ use `this.context` - this.state = state; - } - - calculateState = () => { - return { color: this.context.color }; - }; - - render() { - return
; - } -} - -class App2 extends React.Component { - state: *; - - constructor(props, context) { - super(props, context); - const state = { - whatever: context.whatever, // needs context - }; - this.state = state; - } - - render() { - return
; - } -} - -App.contextTypes = { - whatever: React.PropTypes.object, -}; - -class MyComponent2 extends React.Component { - state: *; - - constructor(props) { - super(props); - var x = props.foo.bar.wow.so.deep; - - this.state = { - heyoo: 23, - }; - } - - foo = (): void => { - this.setState({heyoo: 24}); - }; -} - -const getContextFromInstance = (x) => x.context; // meh - -class MyComponent3 extends React.Component { - state: *; - - constructor(props, context) { - super(props, context); - var x = getContextFromInstance(this); // `this` is referenced alone - - this.state = { - heyoo: x, - }; - } - - foo = (): void => { - this.setState({heyoo: 24}); - }; -} - -// we are not sure what you'll need from `this`, -// so it's safe to defer `state`'s initialization -class MyComponent4 extends React.Component { - foo = (): void => { - this.setState({heyoo: 24}); - }; - - state = { - heyoo: getContextFromInstance(this), - }; -} - -// but only accessing `this.props` and/or `this.context` is safe -class MyComponent5 extends React.Component { - state = { - heyoo: getContextFromInstance(this.props), - }; - - foo = (): void => { - this.setState({heyoo: 24}); - }; -} - -// intense control flow testing -class Loader extends React.Component { - state: *; - - constructor(props, context) { - super(props, context); - if (props.stuff) { - this.state = {x: 1}; - return; - } else if (props.thing) { - this.state = {x: 2}; - return; - } - switch (props.wow) { - case 1: - this.state = props.lol ? - {x: 3} : - this.whatever(props); - - return; - } - for (let i = 0; i < 100; i++) { - if (i === 20) { - this.state = {x: i}; - return; - } - } - - try { - doSomeThingReallyBad(); - } catch (e) { - this.state = {error: e}; - return; - } - - this.state = this.lol(); - } - - render() { - return null; - } -} - -class FunctionDeclarationInGetInitialState extends React.Component { - state: *; - - constructor(props) { - super(props); - function func() { - var x = 1; - return x; // dont change me - } - - const foo = () => { - return 120; // dont change me - }; - - var q = function() { - return 100; // dont change me - }; - - this.state = { - x: func(), - y: foo(), - z: q(), - }; - } - - render() { - return null; - } -} - -class DeferStateInitialization extends React.Component { - something = 42; - state = {x: this.something}; - - render() { - return
; - } -} - -var helper = () => {}; - -// fallback -var PassGetInitialState = createReactClass({ - displayName: 'PassGetInitialState', - - getInitialState() { - return this.lol(); - }, - - helper1: function() { - helper(this.getInitialState); - }, - - render() { - return null; - }, -}); - -// fallback -var UseGetInitialState = createReactClass({ - displayName: 'UseGetInitialState', - - getInitialState() { - return this.lol(); - }, - - helper2() { - this.setState(this.getInitialState()); - }, - - render() { - return null; - }, -}); - -// fallback -var UseArguments = createReactClass({ - displayName: 'UseArguments', - - helper() { - console.log(arguments); - }, - - render() { - return null; - }, -}); - -// fallback -var ShadowingIssue = createReactClass({ - displayName: 'ShadowingIssue', - - getInitialState() { - const props = { x: 123 }; - return { x: props.x }; - }, - - render() { - return null; - }, -}); - -// will remove unnecessary bindings -class ShadowingButFine extends React.Component { - state: *; - - constructor(props, context) { - super(props, context); - this.state = { x: props.x + context.x }; - } - - render() { - return null; - } -} - -// move type annotations -class WithSimpleType extends React.Component { - state: Object = { - x: 12, - y: 13, - z: 14, - }; - - render() { - return null; - } -} - -class WithLongType extends React.Component { - state: {name: string, age: number, counter: number} = { - name: 'Michael', - age: 23, - count: 6, - }; - - render() { - return null; - } -} - -class WithMultiLineType extends React.Component { - state: { - nameLists: Array>, - age?: ?number, - counter?: ?number, - } = { - nameLists: [['James']], - count: 1400, - foo: 'bar', - }; - - render() { - return null; - } -} - -class WithArrowFunction extends React.Component { - state: {heyoo: number} = { - heyoo: 23, - }; - - render() { - return null; - } -} - -class WithArrowFunctionAndObject extends React.Component { - state: {heyoo: number} = { - heyoo: 23, - }; - - render() { - return null; - } -} diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-no-conversion.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-no-conversion.input.js deleted file mode 100644 index d12952a..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-no-conversion.input.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -var React = require('React'); - -var Component = React.createClass({ - render() { - return
; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-no-conversion.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-no-conversion.output.js deleted file mode 100644 index 82768d7..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-no-conversion.output.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -var React = require('React'); - -var createReactClass = require('create-react-class'); - -var Component = createReactClass({ - displayName: 'Component', - - render() { - return
; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-no-display-name.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-no-display-name.input.js deleted file mode 100644 index a2f65e0..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-no-display-name.input.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -var React = require('React'); - -var Component = React.createClass({ - mixins: [{}], - render() { - return
; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-no-display-name.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-no-display-name.output.js deleted file mode 100644 index 1385b8f..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-no-display-name.output.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -var React = require('React'); - -var createReactClass = require('create-react-class'); - -var Component = createReactClass({ - mixins: [{}], - render() { - return
; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-property-field.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-property-field.input.js deleted file mode 100644 index e6ba400..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-property-field.input.js +++ /dev/null @@ -1,28 +0,0 @@ -const React = require('react'); - -const Component1 = React.createClass({ - statics: { - booleanPrim: true, - numberPrim: 12, - stringPrim: 'foo', - nullPrim: null, - undefinedPrim: undefined, - }, - booleanPrim: true, - numberPrim: 12, - stringPrim: 'foo', - nullPrim: null, - undefinedPrim: undefined, - - foobar: function() { - return 123; - }, - - componentDidMount: function() { - console.log('hello'); - }, - - render: function() { - return
; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-property-field.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-property-field.output.js deleted file mode 100644 index 34305a2..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-property-field.output.js +++ /dev/null @@ -1,26 +0,0 @@ -const React = require('react'); - -class Component1 extends React.Component { - static booleanPrim = true; - static numberPrim = 12; - static stringPrim = 'foo'; - static nullPrim = null; - static undefinedPrim = undefined; - booleanPrim = true; - numberPrim = 12; - stringPrim = 'foo'; - nullPrim = null; - undefinedPrim = undefined; - - foobar = () => { - return 123; - }; - - componentDidMount() { - console.log('hello'); - } - - render() { - return
; - } -} diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-prune-react.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-prune-react.input.js deleted file mode 100644 index 478746e..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-prune-react.input.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -import React from 'React'; - -const SomeMixin = { - componentDidMount() { - console.log('did mount'); - }, -}; - -export default React.createClass({ - mixins: [SomeMixin], - render: function() { - return null; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-prune-react.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-prune-react.output.js deleted file mode 100644 index 85ab457..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-prune-react.output.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -import createReactClass from 'create-react-class'; - -const SomeMixin = { - componentDidMount() { - console.log('did mount'); - }, -}; - -export default createReactClass({ - displayName: 'class-prune-react.input', - mixins: [SomeMixin], - - render: function() { - return null; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-prune-react2.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-prune-react2.input.js deleted file mode 100644 index ad3d240..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-prune-react2.input.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -import React from 'React'; - -const SomeMixin = { - componentDidMount() { - console.log('did mount'); - }, -}; - -export default React.createClass({ - mixins: [SomeMixin], - render: function() { - return
; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-prune-react2.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-prune-react2.output.js deleted file mode 100644 index 53548b9..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-prune-react2.output.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -import React from 'React'; - -import createReactClass from 'create-react-class'; - -const SomeMixin = { - componentDidMount() { - console.log('did mount'); - }, -}; - -export default createReactClass({ - displayName: 'class-prune-react2.input', - mixins: [SomeMixin], - - render: function() { - return
; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-prune-react3.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-prune-react3.input.js deleted file mode 100644 index 69fb6b1..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-prune-react3.input.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -import React, {PropTypes} from 'React'; - -const SomeMixin = { - componentDidMount() { - console.log('did mount'); - }, -}; - -export default React.createClass({ - mixins: [SomeMixin], - propTypes: { - foo: PropTypes.string, - }, - render: function() { - return null; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-prune-react3.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-prune-react3.output.js deleted file mode 100644 index 9580454..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-prune-react3.output.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -import {PropTypes} from 'React'; - -import createReactClass from 'create-react-class'; - -const SomeMixin = { - componentDidMount() { - console.log('did mount'); - }, -}; - -export default createReactClass({ - displayName: 'class-prune-react3.input', - mixins: [SomeMixin], - - propTypes: { - foo: PropTypes.string, - }, - - render: function() { - return null; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-prune-react4.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-prune-react4.input.js deleted file mode 100644 index 2f68eb1..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-prune-react4.input.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -import React, {PropTypes} from 'React'; - -const SomeMixin = { - componentDidMount() { - console.log('did mount'); - }, -}; - -export default React.createClass({ - mixins: [SomeMixin], - propTypes: { - foo: PropTypes.string, - }, - render: function() { - return
; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-prune-react4.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-prune-react4.output.js deleted file mode 100644 index 8bb26a9..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-prune-react4.output.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -import React, {PropTypes} from 'React'; - -import createReactClass from 'create-react-class'; - -const SomeMixin = { - componentDidMount() { - console.log('did mount'); - }, -}; - -export default createReactClass({ - displayName: 'class-prune-react4.input', - mixins: [SomeMixin], - - propTypes: { - foo: PropTypes.string, - }, - - render: function() { - return
; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin1.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin1.input.js deleted file mode 100644 index 381a7e7..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin1.input.js +++ /dev/null @@ -1,19 +0,0 @@ -// dont remove me -var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); -var React = require('React'); - -var ComponentWithOnlyPureRenderMixin = React.createClass({ - mixins: [ReactComponentWithPureRenderMixin], - - getInitialState: function() { - return { - counter: this.props.initialNumber + 1, - }; - }, - - render: function() { - return ( -
{this.state.counter}
- ); - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin1.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin1.output.js deleted file mode 100644 index 741db3c..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin1.output.js +++ /dev/null @@ -1,14 +0,0 @@ -// dont remove me -var React = require('React'); - -class ComponentWithOnlyPureRenderMixin extends React.PureComponent { - state = { - counter: this.props.initialNumber + 1, - }; - - render() { - return ( -
{this.state.counter}
- ); - } -} diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin2.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin2.input.js deleted file mode 100644 index 820b1e3..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin2.input.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @typechecks - * @flow - */ -import WhateverYouCallIt from 'react-addons-pure-render-mixin'; -import React from 'React'; -import dontPruneMe from 'foobar'; - -var ComponentWithOnlyPureRenderMixin = React.createClass({ - mixins: [WhateverYouCallIt], - - getInitialState: function() { - return { - counter: this.props.initialNumber + 1, - }; - }, - - render: function() { - dontPruneMe(); - return ( -
{this.state.counter}
- ); - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin2.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin2.output.js deleted file mode 100644 index 559a31e..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin2.output.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @typechecks - * @flow - */ -import React from 'React'; -import dontPruneMe from 'foobar'; - -class ComponentWithOnlyPureRenderMixin extends React.PureComponent { - state = { - counter: this.props.initialNumber + 1, - }; - - render() { - dontPruneMe(); - return ( -
{this.state.counter}
- ); - } -} diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin3.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin3.input.js deleted file mode 100644 index fd7e2e1..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin3.input.js +++ /dev/null @@ -1,20 +0,0 @@ -// for this file we disable the `pure-component` option -// so we should not convert to a plain class -var React = require('React'); -var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); - -var ComponentWithOnlyPureRenderMixin = React.createClass({ - mixins: [ReactComponentWithPureRenderMixin], - - getInitialState: function() { - return { - counter: this.props.initialNumber + 1, - }; - }, - - render: function() { - return ( -
{this.state.counter}
- ); - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin3.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin3.output.js deleted file mode 100644 index d40d339..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin3.output.js +++ /dev/null @@ -1,22 +0,0 @@ -// for this file we disable the `pure-component` option -// so we should not convert to a plain class -var React = require('React'); -var createReactClass = require('create-react-class'); -var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); - -var ComponentWithOnlyPureRenderMixin = createReactClass({ - displayName: 'ComponentWithOnlyPureRenderMixin', - mixins: [ReactComponentWithPureRenderMixin], - - getInitialState: function() { - return { - counter: this.props.initialNumber + 1, - }; - }, - - render: function() { - return ( -
{this.state.counter}
- ); - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin4.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin4.input.js deleted file mode 100644 index a1e7054..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin4.input.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @fbt {"foo": "bar"} - * @flow - * @typechecks - */ - -'use strict'; - -const React = require('React'); -const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); - -/** - * just a description here - */ -const HelloGuys = React.createClass({ - mixins: [ - ReactComponentWithPureRenderMixin, - ], - - propTypes: {}, - - render(): ReactElement { - return ( -
- wassup -
- ); - }, -}); - -module.exports = HelloGuys; diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin4.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin4.output.js deleted file mode 100644 index 5902dfb..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin4.output.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @fbt {"foo": "bar"} - * @flow - * @typechecks - */ - -'use strict'; - -const React = require('React'); - -/** - * just a description here - */ -class HelloGuys extends React.PureComponent { - props: {}; - static propTypes = {}; - - render(): ReactElement { - return ( -
- wassup -
- ); - } -} - -module.exports = HelloGuys; diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin5.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin5.input.js deleted file mode 100644 index b098172..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin5.input.js +++ /dev/null @@ -1,19 +0,0 @@ -// dont remove me -var React = require('React'), - ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); - -var ComponentWithOnlyPureRenderMixin = React.createClass({ - mixins: [ReactComponentWithPureRenderMixin], - - getInitialState: function() { - return { - counter: this.props.initialNumber + 1, - }; - }, - - render: function() { - return ( -
{this.state.counter}
- ); - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin5.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin5.output.js deleted file mode 100644 index 741db3c..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin5.output.js +++ /dev/null @@ -1,14 +0,0 @@ -// dont remove me -var React = require('React'); - -class ComponentWithOnlyPureRenderMixin extends React.PureComponent { - state = { - counter: this.props.initialNumber + 1, - }; - - render() { - return ( -
{this.state.counter}
- ); - } -} diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-test2.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-test2.input.js deleted file mode 100644 index ae54847..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-test2.input.js +++ /dev/null @@ -1,116 +0,0 @@ -'use strict'; - -var React = require('React'); -var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); -var FooBarMixin = require('FooBarMixin'); - -var ComponentWithNonSimpleInitialState = React.createClass({ - statics: { - iDontKnowWhyYouNeedThis: true, // but comment it - foo: 'bar', - dontBindMe: function(count: number): any { - return this; - }, - }, - - getInitialState: function() { - return { - counter: this.props.initialNumber + 1, - }; - }, - - render: function() { - return ( -
{this.state.counter}
- ); - }, -}); - -// Comment -module.exports = React.createClass({ - propTypes: { - foo: React.PropTypes.bool, - }, - - getDefaultProps: function() { - return { - foo: 12, - }; - }, - - getInitialState: function() { // non-simple getInitialState - var data = 'bar'; - return { - bar: data, - }; - }, - - render: function() { - return
; - }, -}); - -var ComponentWithInconvertibleMixins = React.createClass({ - mixins: [ReactComponentWithPureRenderMixin, FooBarMixin], - - getInitialState: function() { - return { - counter: this.props.initialNumber + 1, - }; - }, - - render: function() { - return ( -
{this.state.counter}
- ); - }, -}); - -var listOfInconvertibleMixins = [ReactComponentWithPureRenderMixin, FooBarMixin]; - -var ComponentWithInconvertibleMixins2 = React.createClass({ - mixins: listOfInconvertibleMixins, - - getInitialState: function() { - return { - counter: this.props.initialNumber + 1, - }; - }, - - render: function() { - return ( -
{this.state.counter}
- ); - }, -}); - -// taken from https://facebook.github.io/react/docs/context.html#updating-context -var MediaQuery = React.createClass({ - childContextTypes: { - type: React.PropTypes.string, - }, - - getInitialState: function() { - return {type:'desktop'}; - }, - - getChildContext: function() { - return {type: this.state.type}; - }, - - componentDidMount: function() { - const checkMediaQuery = () => { - const type = window.matchMedia('(min-width: 1025px)').matches ? 'desktop' : 'mobile'; - if (type !== this.state.type) { - this.setState({type}); - } - }; - - window.addEventListener('resize', checkMediaQuery); - checkMediaQuery(); - }, - - render: function() { - return this.props.children; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-test2.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-test2.output.js deleted file mode 100644 index 50ab376..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-test2.output.js +++ /dev/null @@ -1,115 +0,0 @@ -'use strict'; - -var React = require('React'); -var createReactClass = require('create-react-class'); -var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); -var FooBarMixin = require('FooBarMixin'); - -class ComponentWithNonSimpleInitialState extends React.Component { - static iDontKnowWhyYouNeedThis = true; // but comment it - static foo = 'bar'; - - static dontBindMe(count: number): any { - return this; - } - - state = { - counter: this.props.initialNumber + 1, - }; - - render() { - return ( -
{this.state.counter}
- ); - } -} - -// Comment -module.exports = class extends React.Component { - static propTypes = { - foo: React.PropTypes.bool, - }; - - static defaultProps = { - foo: 12, - }; - - constructor(props) { - super(props); - // non-simple getInitialState - var data = 'bar'; - - this.state = { - bar: data, - }; - } - - render() { - return
; - } -}; - -var ComponentWithInconvertibleMixins = createReactClass({ - displayName: 'ComponentWithInconvertibleMixins', - mixins: [ReactComponentWithPureRenderMixin, FooBarMixin], - - getInitialState: function() { - return { - counter: this.props.initialNumber + 1, - }; - }, - - render: function() { - return ( -
{this.state.counter}
- ); - }, -}); - -var listOfInconvertibleMixins = [ReactComponentWithPureRenderMixin, FooBarMixin]; - -var ComponentWithInconvertibleMixins2 = createReactClass({ - displayName: 'ComponentWithInconvertibleMixins2', - mixins: listOfInconvertibleMixins, - - getInitialState: function() { - return { - counter: this.props.initialNumber + 1, - }; - }, - - render: function() { - return ( -
{this.state.counter}
- ); - }, -}); - -// taken from https://facebook.github.io/react/docs/context.html#updating-context -class MediaQuery extends React.Component { - static childContextTypes = { - type: React.PropTypes.string, - }; - - state = {type:'desktop'}; - - getChildContext() { - return {type: this.state.type}; - } - - componentDidMount() { - const checkMediaQuery = () => { - const type = window.matchMedia('(min-width: 1025px)').matches ? 'desktop' : 'mobile'; - if (type !== this.state.type) { - this.setState({type}); - } - }; - - window.addEventListener('resize', checkMediaQuery); - checkMediaQuery(); - } - - render() { - return this.props.children; - } -} diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-top-comment.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-top-comment.input.js deleted file mode 100644 index b01b81c..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-top-comment.input.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @typechecks - * @flow - */ -var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); -var React = require('React'); - -var ComponentWithOnlyPureRenderMixin = React.createClass({ - mixins: [ReactComponentWithPureRenderMixin], - - getInitialState: function() { - return { - counter: this.props.initialNumber + 1, - }; - }, - - render: function() { - return ( -
{this.state.counter}
- ); - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-top-comment.output.js b/codemods/legacy/transforms/__testfixtures__/class/class-top-comment.output.js deleted file mode 100644 index 0da85b9..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class-top-comment.output.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @typechecks - * @flow - */ -var React = require('React'); - -class ComponentWithOnlyPureRenderMixin extends React.PureComponent { - state = { - counter: this.props.initialNumber + 1, - }; - - render() { - return ( -
{this.state.counter}
- ); - } -} diff --git a/codemods/legacy/transforms/__testfixtures__/class/class.input.js b/codemods/legacy/transforms/__testfixtures__/class/class.input.js deleted file mode 100644 index 96a0d9d..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class.input.js +++ /dev/null @@ -1,214 +0,0 @@ -'use strict'; - -var React = require('React'); -var Relay = require('Relay'); - -var Image = require('Image.react'); - -// Class comment -var MyComponent2 = React.createClass({ - getDefaultProps: function(): Object { - return {a: 1}; - }, - foo: function(): void { - const x = (a: Object, b: string): void => {}; // This code cannot be parsed by Babel v5 - pass(this.foo); - this.forceUpdate(); - }, -}); - -var MyComponent3 = React.createClass({ - statics: { - someThing: 10, - funcThatDoesNothing: function(): void {}, - }, - propTypes: { - highlightEntities: React.PropTypes.bool, - linkifyEntities: React.PropTypes.bool, - text: React.PropTypes.shape({ - text: React.PropTypes.string, - ranges: React.PropTypes.array, - }).isRequired, - }, - - getDefaultProps: function() { - unboundFunc(); - return { - linkifyEntities: true, - highlightEntities: false, - }; - }, - - getInitialState: function() { - this.props.foo(); - return { - heyoo: 23, - }; - }, - - // comment here - _renderText: function(text: string): ReactElement { // say something - return ; - }, - - _renderImageRange: function(text: string, range): ReactElement { - var image = range.image; - if (image) { - return ( - - ); - } - return null; - }, - - autobindMe: function() {}, - okBindMe: function(): number { return 12; }, - - // Function comment - _renderRange: function(text: string, range, bla: Promise): ReactElement { - var self = this; - - self.okBindMe(); - call(self.autobindMe); - - var type = rage.type; - var {highlightEntities} = this.props; - - if (type === 'ImageAtRange') { - return this._renderImageRange(text, range); - } - - if (this.props.linkifyEntities) { - text = - - {text} - ; - } else { - text = {text}; - } - - return text; - }, - - /* This is a comment */ - render: function() { - var content = this.props.text; - return ( - - ); - }, -}); - -var MyComponent4 = React.createClass({ - foo: callMeMaybe(), - render: function() {}, -}); - -module.exports = Relay.createContainer(MyComponent, { - queries: { - me: Relay.graphql`this is not graphql`, - }, -}); - -var MyComponent5 = React.createClass({ - getDefaultProps: function() { - return { - thisIs: true, - andThisIs: false, - }; - }, - - statics: {}, - - getInitialState: function() { - return { - todos: [], - }; - }, - - renderTodo: function(): ReactElement { - return ( -
- {this.state.todos.map((item) =>

{item.text}

)} -
- ); - }, - - render: function() { - return ( -
-

TODOs

- {this.renderTodo()} -
- ); - }, -}); - -var GoodName = React.createClass({ - displayName: 'GoodName', - render() { - return
; - }, -}); - -var SingleArgArrowFunction = React.createClass({ - formatInt: function(/*number*/ num) /*string*/ { - return 'foobar'; - }, - render() { - return
; - }, -}); - -var mySpec = {}; -var NotAnObjectLiteral = React.createClass(mySpec); - -var WaitWhat = React.createClass(); - -var HasSpreadArgs = React.createClass({ - _helper: function(...args) { - return args; - }, - _helper2: function(a, b, c, ...args) { - return args.concat(a); - }, - _helper3: function(a: number, ...args: Array) { - return args.concat('' + a); - }, - render() { - return
; - }, -}); - -var HasDefaultArgs = React.createClass({ - _helper: function(foo = 12) { - return foo; - }, - _helper2: function({foo: number = 12, abc}, bar: string = 'hey', ...args: Array) { - return args.concat(foo, bar); - }, - render() { - return
; - }, -}); - -var ManyArgs = React.createClass({ - _helper: function(foo = 12) { - return foo; - }, - _helper2: function({foo: number = 12, abc}, bar: string = 'hey', x: number, y: number, ...args: Array) { - return args.concat(foo, bar); - }, - render() { - return
; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/class.output.js b/codemods/legacy/transforms/__testfixtures__/class/class.output.js deleted file mode 100644 index 61e1065..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/class.output.js +++ /dev/null @@ -1,225 +0,0 @@ -'use strict'; - -var React = require('React'); -var createReactClass = require('create-react-class'); -var Relay = require('Relay'); - -var Image = require('Image.react'); - -// Class comment -class MyComponent2 extends React.Component { - static defaultProps = {a: 1}; - - foo = (): void => { - const x = (a: Object, b: string): void => {}; // This code cannot be parsed by Babel v5 - pass(this.foo); - this.forceUpdate(); - }; -} - -class MyComponent3 extends React.Component { - static someThing = 10; - static funcThatDoesNothing(): void {} - - static propTypes = { - highlightEntities: React.PropTypes.bool, - linkifyEntities: React.PropTypes.bool, - text: React.PropTypes.shape({ - text: React.PropTypes.string, - ranges: React.PropTypes.array, - }).isRequired, - }; - - static defaultProps = function() { - unboundFunc(); - return { - linkifyEntities: true, - highlightEntities: false, - }; - }(); - - constructor(props) { - super(props); - props.foo(); - - this.state = { - heyoo: 23, - }; - } - - // comment here - _renderText = (text: string): ReactElement => { // say something - return ; - }; - - _renderImageRange = (text: string, range): ReactElement => { - var image = range.image; - if (image) { - return ( - - ); - } - return null; - }; - - autobindMe = () => {}; - okBindMe = (): number => { return 12; }; - - // Function comment - _renderRange = (text: string, range, bla: Promise): ReactElement => { - var self = this; - - self.okBindMe(); - call(self.autobindMe); - - var type = rage.type; - var {highlightEntities} = this.props; - - if (type === 'ImageAtRange') { - return this._renderImageRange(text, range); - } - - if (this.props.linkifyEntities) { - text = - - {text} - ; - } else { - text = {text}; - } - - return text; - }; - - /* This is a comment */ - render() { - var content = this.props.text; - return ( - - ); - } -} - -var MyComponent4 = createReactClass({ - displayName: 'MyComponent4', - foo: callMeMaybe(), - render: function() {}, -}); - -module.exports = Relay.createContainer(MyComponent, { - queries: { - me: Relay.graphql`this is not graphql`, - }, -}); - -class MyComponent5 extends React.Component { - static defaultProps = { - thisIs: true, - andThisIs: false, - }; - - state = { - todos: [], - }; - - renderTodo = (): ReactElement => { - return ( -
- {this.state.todos.map((item) =>

{item.text}

)} -
- ); - }; - - render() { - return ( -
-

TODOs

- {this.renderTodo()} -
- ); - } -} - -class GoodName extends React.Component { - static displayName = 'GoodName'; - - render() { - return
; - } -} - -class SingleArgArrowFunction extends React.Component { - formatInt = (/*number*/ num) => /*string*/ { - return 'foobar'; - }; - - render() { - return
; - } -} - -var mySpec = {}; -var NotAnObjectLiteral = createReactClass(mySpec); - -var WaitWhat = createReactClass(); - -class HasSpreadArgs extends React.Component { - _helper = (...args) => { - return args; - }; - - _helper2 = (a, b, c, ...args) => { - return args.concat(a); - }; - - _helper3 = (a: number, ...args: Array) => { - return args.concat('' + a); - }; - - render() { - return
; - } -} - -class HasDefaultArgs extends React.Component { - _helper = (foo = 12) => { - return foo; - }; - - _helper2 = ({foo: number = 12, abc}, bar: string = 'hey', ...args: Array) => { - return args.concat(foo, bar); - }; - - render() { - return
; - } -} - -class ManyArgs extends React.Component { - _helper = (foo = 12) => { - return foo; - }; - - _helper2 = ( - {foo: number = 12, abc}, - bar: string = 'hey', - x: number, - y: number, - ...args: Array - ) => { - return args.concat(foo, bar); - }; - - render() { - return
; - } -} diff --git a/codemods/legacy/transforms/__testfixtures__/class/export-default-class.input.js b/codemods/legacy/transforms/__testfixtures__/class/export-default-class.input.js deleted file mode 100644 index 148843b..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/export-default-class.input.js +++ /dev/null @@ -1,21 +0,0 @@ -/*eslint-disable no-extra-semi*/ - -'use strict'; - -import React from 'React'; - -export default React.createClass({ - getInitialState: function() { - return { - foo: 'bar', - }; - }, - - propTypes: { - foo: React.PropTypes.string, - }, - - render: function() { - return
; - }, -}); diff --git a/codemods/legacy/transforms/__testfixtures__/class/export-default-class.output.js b/codemods/legacy/transforms/__testfixtures__/class/export-default-class.output.js deleted file mode 100644 index 23a7d26..0000000 --- a/codemods/legacy/transforms/__testfixtures__/class/export-default-class.output.js +++ /dev/null @@ -1,19 +0,0 @@ -/*eslint-disable no-extra-semi*/ - -'use strict'; - -import React from 'React'; - -export default class extends React.Component { - static propTypes = { - foo: React.PropTypes.string, - }; - - state = { - foo: 'bar', - }; - - render() { - return
; - } -} diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-react.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-react.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow11.output.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow11.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow3.output.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow3.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/remove-context-provider/no-provider.output.js b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/no-provider.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/no-provider.output.js b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/no-provider.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/standalone-function.output.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/standalone-function.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/variable-within-class-method.output.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/variable-within-class-method.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports-react-not-removed.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports-react-not-removed.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports-variable-used.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports-variable-used.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports.output.js deleted file mode 100644 index 2ebfe7c..0000000 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports.output.js +++ /dev/null @@ -1,5 +0,0 @@ -import { useState } from "react"; - -useState(false); - -
\ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-already-used-named-export.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-already-used-named-export.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-namespace.tsx.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-namespace.tsx.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-already-used-named-export.tsx.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-already-used-named-export.tsx.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/variable-already-used.tsx.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/variable-already-used.tsx.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/variable-already-used.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/variable-already-used.output.js deleted file mode 100644 index e69de29..0000000 diff --git a/codemods/legacy/transforms/__tests__/React-DOM-to-react-dom-factories-test.js b/codemods/legacy/transforms/__tests__/React-DOM-to-react-dom-factories-test.js deleted file mode 100644 index bf9fbaa..0000000 --- a/codemods/legacy/transforms/__tests__/React-DOM-to-react-dom-factories-test.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -const tests = [ - 'react-dom-basic-case', - 'react-dom-deconstructed-import', - 'react-dom-deconstructed-require', - 'react-dom-deconstructed-require-part-two', - 'react-dom-no-change-import', - 'react-dom-no-change-require', - 'react-dom-no-change-import-dom-from-other-libraries', - 'react-dom-no-change-require-dom-from-other-libraries', - 'react-dom-no-change-local-dom-from-other-libraries' -]; - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; - -describe('React-DOM-to-react-dom-factories', () => { - tests.forEach(test => - defineTest( - __dirname, - 'React-DOM-to-react-dom-factories', - null, - `React-DOM-to-react-dom-factories/${ test }` - ) - ); -}); diff --git a/codemods/legacy/transforms/__tests__/ReactNative-View-propTypes-test.js b/codemods/legacy/transforms/__tests__/ReactNative-View-propTypes-test.js deleted file mode 100644 index d1e13f8..0000000 --- a/codemods/legacy/transforms/__tests__/ReactNative-View-propTypes-test.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -jest.mock('../ReactNative-View-propTypes', () => { - return Object.assign(jest.requireActual('../ReactNative-View-propTypes'), { - parser: 'flow' - }); -}); - -const tests = [ - 'default-import-multi-reference', - 'default-import-only-reference', - 'default-require-multi-reference', - 'default-require-only-reference', - 'destructured-import-multi-reference', - 'destructured-import-only-reference', - 'destructured-require-multi-reference', - 'destructured-require-only-reference', - 'import-flow-type-with-require', - 'multiple-replacements', - 'noop-import', - 'noop-require', -]; - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; - -describe('ReactNative-View-propTypes', () => { - tests.forEach(test => - defineTest( - __dirname, - 'ReactNative-View-propTypes', - null, - `ReactNative-View-propTypes/${ test }` - ) - ); -}); diff --git a/codemods/legacy/transforms/__tests__/class-test.js b/codemods/legacy/transforms/__tests__/class-test.js deleted file mode 100644 index e46306c..0000000 --- a/codemods/legacy/transforms/__tests__/class-test.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; - -const pureMixinAlternativeOption = { - 'mixin-module-name': 'ReactComponentWithPureRenderMixin', - 'pure-component': true, -}; - -const enableFlowOption = {flow: true}; - -defineTest(__dirname, 'class', null, 'class/class'); -defineTest(__dirname, 'class', enableFlowOption, 'class/class-anonymous'); -defineTest(__dirname, 'class', enableFlowOption, 'class/class-anonymous2'); -defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class/class-test2'); -defineTest(__dirname, 'class', enableFlowOption, 'class/export-default-class'); -defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class/class-pure-mixin1'); -defineTest(__dirname, 'class', { - ...enableFlowOption, - 'pure-component': true, -}, 'class/class-pure-mixin2'); -defineTest(__dirname, 'class', null, 'class/class-pure-mixin3'); -defineTest(__dirname, 'class', { - ...pureMixinAlternativeOption, - ...enableFlowOption, -}, 'class/class-pure-mixin4'); -defineTest(__dirname, 'class', { - ...pureMixinAlternativeOption, -}, 'class/class-pure-mixin5'); -defineTest(__dirname, 'class', { - ...pureMixinAlternativeOption, - ...enableFlowOption, -}, 'class/class-top-comment'); -defineTest(__dirname, 'class', enableFlowOption, 'class/class-initial-state'); -defineTest(__dirname, 'class', enableFlowOption, 'class/class-property-field'); -defineTest(__dirname, 'class', enableFlowOption, 'class/class-flow1'); -defineTest(__dirname, 'class', enableFlowOption, 'class/class-flow2'); -defineTest(__dirname, 'class', enableFlowOption, 'class/class-flow3'); -defineTest(__dirname, 'class', enableFlowOption, 'class/class-flow4'); -defineTest(__dirname, 'class', enableFlowOption, 'class/class-flow5'); -defineTest(__dirname, 'class', enableFlowOption, 'class/class-flow6'); -defineTest(__dirname, 'class', { - ...enableFlowOption, - 'remove-runtime-proptypes': true, -}, 'class/class-flow7'); -defineTest(__dirname, 'class', null, 'class/class-prune-react'); -defineTest(__dirname, 'class', null, 'class/class-prune-react2'); -defineTest(__dirname, 'class', null, 'class/class-prune-react3'); -defineTest(__dirname, 'class', null, 'class/class-prune-react4'); -defineTest(__dirname, 'class', { - 'create-class-module-name': 'createReactClass__deprecated', - 'create-class-variable-name': 'createReactClass__deprecated', -}, 'class/class-create-class-naming'); -defineTest(__dirname, 'class', null, 'class/class-displayName'); -defineTest(__dirname, 'class', { - 'conversion': false, -}, 'class/class-no-conversion'); -defineTest(__dirname, 'class', { - 'display-name': false, -}, 'class/class-no-display-name'); diff --git a/codemods/legacy/transforms/__tests__/create-element-to-jsx-test.js b/codemods/legacy/transforms/__tests__/create-element-to-jsx-test.js deleted file mode 100644 index b0739c5..0000000 --- a/codemods/legacy/transforms/__tests__/create-element-to-jsx-test.js +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -jest.mock('../create-element-to-jsx', () => { - return Object.assign(jest.requireActual('../create-element-to-jsx'), { - parser: 'flow' - }); -}); - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; -describe('create-element-to-jsx', () => { - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-single-element' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-props' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-props-boolean' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-props-array' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-children-literal' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-children' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-children-map' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-children-mixed-empty-string' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-spread' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-spread-props' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-no-react' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-literal-prop' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-call-as-children' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-react-spread' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-object-assign' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-member-expression-as-prop' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-call-expression-as-prop' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-allow-member-expression' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-gt-lt-entities' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-escaped-string' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-no-props-arg' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-preserve-comments' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-ignore-bad-capitalization' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-arg-spread' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-computed-component' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-deep-nesting' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-literal-spacing' - ); - defineTest( - __dirname, - 'create-element-to-jsx', - null, - 'create-element-to-jsx-element-comment-positioning' - ); - - it('throws when it does not recognize a property type', () => { - const jscodeshift = require('jscodeshift'); - const transform = require('../../transforms/create-element-to-jsx'); - const source = ` - var React = require("react/addons"); - React.createElement("foo", 1) - `; - - expect(() => transform({source}, {jscodeshift}, {})) - .toThrowError('Unexpected attribute of type "Literal"'); - }); -}); diff --git a/codemods/legacy/transforms/__tests__/custom-sort-group.js b/codemods/legacy/transforms/__tests__/custom-sort-group.js deleted file mode 100644 index 13ee78c..0000000 --- a/codemods/legacy/transforms/__tests__/custom-sort-group.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; - -// The test fixtures for this are in their own dir so it can customize eslint with method grouping. -defineTest(__dirname, 'sort-comp', null, 'custom-sort-group/custom-sort-group'); diff --git a/codemods/legacy/transforms/__tests__/custom-sort.js b/codemods/legacy/transforms/__tests__/custom-sort.js deleted file mode 100644 index 0dfbfbe..0000000 --- a/codemods/legacy/transforms/__tests__/custom-sort.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; - -// The test fixtures for this are in their own dir so it can customize eslint. -defineTest(__dirname, 'sort-comp', null, 'custom-sort/custom-sort'); diff --git a/codemods/legacy/transforms/__tests__/error-boundaries.js b/codemods/legacy/transforms/__tests__/error-boundaries.js deleted file mode 100644 index 8ed1c96..0000000 --- a/codemods/legacy/transforms/__tests__/error-boundaries.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -const tests = ['class-component', 'create-class-component']; - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; - -tests.forEach(test => { - defineTest(__dirname, 'error-boundaries', null, `error-boundaries/${test}`); -}); diff --git a/codemods/legacy/transforms/__tests__/findDOMNode-test.js b/codemods/legacy/transforms/__tests__/findDOMNode-test.js deleted file mode 100644 index 16f87eb..0000000 --- a/codemods/legacy/transforms/__tests__/findDOMNode-test.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; -defineTest(__dirname, 'findDOMNode'); diff --git a/codemods/legacy/transforms/__tests__/manual-bind-to-arrow-test.js b/codemods/legacy/transforms/__tests__/manual-bind-to-arrow-test.js deleted file mode 100644 index 30e0827..0000000 --- a/codemods/legacy/transforms/__tests__/manual-bind-to-arrow-test.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -jest.mock('../manual-bind-to-arrow', () => { - return Object.assign(jest.requireActual('../manual-bind-to-arrow'), { - parser: 'flow' - }); -}); - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; - -var TESTS = [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11 -]; - -TESTS.forEach(test => { - defineTest( - __dirname, - 'manual-bind-to-arrow', - {flow: true}, - 'manual-bind-to-arrow/manual-bind-to-arrow' + test - ); -}); diff --git a/codemods/legacy/transforms/__tests__/pure-component-test.js b/codemods/legacy/transforms/__tests__/pure-component-test.js deleted file mode 100644 index 0526d5a..0000000 --- a/codemods/legacy/transforms/__tests__/pure-component-test.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -jest.mock('../pure-component', () => { - return Object.assign(jest.requireActual('../pure-component'), { - parser: 'flow' - }); -}); - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; -defineTest(__dirname, 'pure-component'); -defineTest(__dirname, 'pure-component', { useArrows: true }, 'pure-component2'); -defineTest(__dirname, 'pure-component', { destructuring: true }, 'pure-component-destructuring'); diff --git a/codemods/legacy/transforms/__tests__/pure-render-mixin-test.js b/codemods/legacy/transforms/__tests__/pure-render-mixin-test.js deleted file mode 100644 index 776cf67..0000000 --- a/codemods/legacy/transforms/__tests__/pure-render-mixin-test.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; -defineTest(__dirname, 'pure-render-mixin'); -defineTest(__dirname, 'pure-render-mixin', null, 'pure-render-mixin2'); -defineTest(__dirname, 'pure-render-mixin', null, 'pure-render-mixin3'); -defineTest( - __dirname, - 'pure-render-mixin', - {'mixin-name': 'ReactComponentWithPureRenderMixin'}, - 'pure-render-mixin4' -); diff --git a/codemods/legacy/transforms/__tests__/react-to-react-dom-test.js b/codemods/legacy/transforms/__tests__/react-to-react-dom-test.js deleted file mode 100644 index b00f92c..0000000 --- a/codemods/legacy/transforms/__tests__/react-to-react-dom-test.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -const tests = [ - 'require-dom-base', - 'require-server-base', - 'require-keeps-react', - 'require-indirect', - 'import-dom-base', - 'import-server-base', - 'import-multiple-specifiers', - 'mixed-with-existing-react-dom', - 'import-with-existing-react-dom', - 'import-without-default-specifier', -]; - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; -describe('react-to-react-dom', () => { - tests.forEach(test => - defineTest( - __dirname, - 'react-to-react-dom', - null, - `react-to-react-dom/${ test }` - ) - ); -}); diff --git a/codemods/legacy/transforms/__tests__/remove-context-provider.test.ts b/codemods/legacy/transforms/__tests__/remove-context-provider.test.ts deleted file mode 100644 index b548563..0000000 --- a/codemods/legacy/transforms/__tests__/remove-context-provider.test.ts +++ /dev/null @@ -1,51 +0,0 @@ - -'use strict'; - -const tests = [ - 'with-provider', - 'with-provider-2', - 'no-provider', -]; - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; - -describe('remove-context-provider', () => { - - tests.forEach(test => - defineTest( - __dirname, - 'remove-context-provider', - null, - `remove-context-provider/${ test }` - ) - ); - - - describe('typescript', () => { - - beforeEach(() => { - jest.mock('../remove-context-provider', () => { - return Object.assign( - jest.requireActual('../remove-context-provider'), - { - parser: 'tsx' - } - ); - }); - }); - - afterEach(() => { - jest.resetModules(); - }); - - tests.forEach(test => { - defineTest( - __dirname, - 'remove-context-provider', - null, - `remove-context-provider/typescript/${ test }` - ); - }); - - }); -}); diff --git a/codemods/legacy/transforms/__tests__/remove-forward-ref.test.ts b/codemods/legacy/transforms/__tests__/remove-forward-ref.test.ts deleted file mode 100644 index 000a7f6..0000000 --- a/codemods/legacy/transforms/__tests__/remove-forward-ref.test.ts +++ /dev/null @@ -1,62 +0,0 @@ - -'use strict'; - -const jsTests = [ - 'function-expression', - 'arrow-function-expression', - 'forward-ref-import', - 'forward-ref-import-2', - 'props-identifier', - 'props-object-pattern', - 'callee-is-member-expression' -]; - -const tsTests = [ - 'type-arguments', - 'type-arguments-custom-names', - 'type-arguments-type-literals', - 'props-type-literal' -]; - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; - -describe('remove-forward-ref', () => { - - jsTests.forEach(test => - defineTest( - __dirname, - 'remove-forward-ref', - null, - `remove-forward-ref/${ test }` - ) - ); - - - describe('typescript', () => { - - beforeEach(() => { - jest.mock('../remove-forward-ref', () => { - return Object.assign( - jest.requireActual('../remove-forward-ref'), - { - parser: 'tsx' - } - ); - }); - }); - - afterEach(() => { - jest.resetModules(); - }); - - tsTests.forEach(test => { - defineTest( - __dirname, - 'remove-forward-ref', - null, - `remove-forward-ref/typescript/${ test }` - ); - }); - - }); -}); diff --git a/codemods/legacy/transforms/__tests__/rename-unsafe-lifecycles-test.js b/codemods/legacy/transforms/__tests__/rename-unsafe-lifecycles-test.js deleted file mode 100644 index 2c9f6c6..0000000 --- a/codemods/legacy/transforms/__tests__/rename-unsafe-lifecycles-test.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -const tests = [ - 'arrow-functions', - 'create-react-class', - 'instance-methods', - 'manually-calling-lifecycles', - 'manually-invoked-mixin-methods', - 'one-lifecycle-calls-another', - 'standalone-function', - 'variable-within-class-method', -]; - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; - -describe('rename-unsafe-lifecycles', () => { - describe('flow', () => { - beforeEach(() => { - jest.mock('../rename-unsafe-lifecycles', () => { - return Object.assign( - jest.requireActual('../rename-unsafe-lifecycles'), - { - parser: 'flow' - } - ); - }); - }); - - afterEach(() => { - jest.resetModules(); - }); - - tests.forEach(test => - defineTest( - __dirname, - 'rename-unsafe-lifecycles', - null, - `rename-unsafe-lifecycles/${test}` - ) - ); - }); - - describe('typescript', () => { - beforeEach(() => { - jest.mock('../rename-unsafe-lifecycles', () => { - return Object.assign( - jest.requireActual('../rename-unsafe-lifecycles'), - { - parser: 'tsx' - } - ); - }); - }); - - afterEach(() => { - jest.resetModules(); - }); - - defineTest( - __dirname, - 'rename-unsafe-lifecycles', - null, - 'rename-unsafe-lifecycles/typescript/class.tsx' - ); - }); -}); diff --git a/codemods/legacy/transforms/__tests__/sort-comp-test.js b/codemods/legacy/transforms/__tests__/sort-comp-test.js deleted file mode 100644 index bdeb344..0000000 --- a/codemods/legacy/transforms/__tests__/sort-comp-test.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; -defineTest(__dirname, 'sort-comp'); -defineTest(__dirname, 'sort-comp', null, 'sort-comp2'); -defineTest(__dirname, 'sort-comp', null, 'sort-comp3'); -defineTest(__dirname, 'sort-comp', null, 'sort-comp-pure'); diff --git a/codemods/legacy/transforms/__tests__/update-react-imports-test.js b/codemods/legacy/transforms/__tests__/update-react-imports-test.js deleted file mode 100644 index d9e8ecd..0000000 --- a/codemods/legacy/transforms/__tests__/update-react-imports-test.js +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -const flowOnlyTests = [ - 'flow-default-and-type-specifier-import-react-variable', - 'flow-default-and-type-specifier-import', - 'react-type-not-removed', - 'react-type-default-export', -]; - -const tsOnlyTests = [ - 'preserve-types-namespace', - 'preserve-types-default', -]; - -const tests = [ - 'default-and-multiple-specifiers-import-react-variable', - 'default-and-multiple-specifiers-import', - 'jsx-element', - 'jsx-fragment', - 'leading-comment', - 'react-basic-default-export-jsx-element-react-variable', - 'react-basic-default-export-jsx-element', - 'react-basic-default-export', - 'react-not-removed', - 'variable-already-used', - 'react-jsx-member-expression', - 'react-already-used-named-export', -]; - -const destructureNamedImportTests = [ - 'destructure-named-imports', - 'destructure-named-imports-variable-used', - 'destructure-named-imports-react-not-removed', -]; - -jest.mock('../update-react-imports', () => { - return Object.assign(jest.requireActual('../update-react-imports'), { - parser: 'flow', - }); -}); - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; - -[...tests, ...flowOnlyTests].forEach((test) => { - defineTest( - __dirname, - 'update-react-imports', - null, - `update-react-imports/${test}` - ); -}); - -describe('typescript', () => { - beforeEach(() => { - jest.mock('../update-react-imports', () => { - return Object.assign( - jest.requireActual('../update-react-imports'), - { - parser: 'tsx' - } - ); - }); - }); - - afterEach(() => { - jest.resetModules(); - }); - - [...tests, ...tsOnlyTests].forEach((test) => { - defineTest( - __dirname, - 'update-react-imports', - null, - `update-react-imports/typescript/${test}.tsx` - ); - }); -}); - -destructureNamedImportTests.forEach((test) => { - defineTest( - __dirname, - 'update-react-imports', - {destructureNamespaceImports: true}, - `update-react-imports/${test}` - ); -}); diff --git a/codemods/legacy/transforms/class.js b/codemods/legacy/transforms/class.js deleted file mode 100644 index ff7a212..0000000 --- a/codemods/legacy/transforms/class.js +++ /dev/null @@ -1,1426 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -const { basename, extname, dirname } = require('path'); - -module.exports = (file, api, options) => { - const j = api.jscodeshift; - - require('./utils/array-polyfills'); - const ReactUtils = require('./utils/ReactUtils')(j); - const doesNotUseArguments = require('./utils/doesNotUseArguments')(j); - - const printOptions = options.printOptions || { - quote: 'single', - trailingComma: true, - flowObjectCommas: true, - arrowParensAlways: true, - arrayBracketSpacing: false, - objectCurlySpacing: false - }; - - const root = j(file.source); - - // retain top comments - const { comments: topComments } = root.find(j.Program).get('body', 0).node; - - const AUTOBIND_IGNORE_KEYS = { - componentDidMount: true, - componentDidUpdate: true, - componentWillReceiveProps: true, - componentWillMount: true, - componentWillUpdate: true, - componentWillUnmount: true, - getChildContext: true, - getDefaultProps: true, - getInitialState: true, - render: true, - shouldComponentUpdate: true - }; - - const DEFAULT_PROPS_FIELD = 'getDefaultProps'; - const DEFAULT_PROPS_KEY = 'defaultProps'; - const GET_INITIAL_STATE_FIELD = 'getInitialState'; - - const DEPRECATED_APIS = [ - 'getDOMNode', - 'isMounted', - 'replaceProps', - 'replaceState', - 'setProps' - ]; - - const PURE_MIXIN_MODULE_NAME = - options['mixin-module-name'] || 'react-addons-pure-render-mixin'; - - const CREATE_CLASS_MODULE_NAME = - options['create-class-module-name'] || 'create-react-class'; - - const CREATE_CLASS_VARIABLE_NAME = - options['create-class-variable-name'] || 'createReactClass'; - - const STATIC_KEY = 'statics'; - - const STATIC_KEYS = { - childContextTypes: true, - contextTypes: true, - displayName: true, - propTypes: true - }; - - const MIXIN_KEY = 'mixins'; - - const NO_CONVERSION = options.conversion === false; - - const NO_DISPLAY_NAME = options['display-name'] === false; - - let shouldTransformFlow = false; - - if (options['flow']) { - const programBodyNode = root.find(j.Program).get('body', 0).node; - if (programBodyNode && programBodyNode.comments) { - programBodyNode.comments.forEach(node => { - if (node.value.indexOf('@flow') !== -1) { - shouldTransformFlow = true; - } - }); - } - } - - // --------------------------------------------------------------------------- - // Helpers - const createFindPropFn = prop => property => - property.key && - property.key.type === 'Identifier' && - property.key.name === prop; - - const filterDefaultPropsField = node => - createFindPropFn(DEFAULT_PROPS_FIELD)(node); - - const filterGetInitialStateField = node => - createFindPropFn(GET_INITIAL_STATE_FIELD)(node); - - const findGetInitialState = specPath => - specPath.properties.find(createFindPropFn(GET_INITIAL_STATE_FIELD)); - - const withComments = (to, from) => { - to.comments = from.comments; - return to; - }; - - const isPrimExpression = node => - node.type === 'Literal' || // NOTE this might change in babylon v6 - (node.type === 'Identifier' && node.name === 'undefined'); - - const isFunctionExpression = node => - node.key && - node.key.type === 'Identifier' && - node.value && - node.value.type === 'FunctionExpression'; - - const isPrimProperty = prop => - prop.key && - prop.key.type === 'Identifier' && - prop.value && - isPrimExpression(prop.value); - - const isPrimPropertyWithTypeAnnotation = prop => - prop.key && - prop.key.type === 'Identifier' && - prop.value && - prop.value.type === 'TypeCastExpression' && - isPrimExpression(prop.value.expression); - - const hasSingleReturnStatement = value => - (value.type === 'ArrowFunctionExpression' && - value.body && - value.body.type === 'ObjectExpression') || - ((value.type === 'FunctionExpression' || - value.type === 'ArrowFunctionExpression') && - value.body && - value.body.type === 'BlockStatement' && - value.body.body && - value.body.body.length === 1 && - value.body.body[0].type === 'ReturnStatement' && - value.body.body[0].argument); - - const isInitialStateLiftable = getInitialState => { - if (!getInitialState || !getInitialState.value) { - return true; - } - - return hasSingleReturnStatement(getInitialState.value); - }; - - // --------------------------------------------------------------------------- - // Checks if the module uses mixins or accesses deprecated APIs. - const checkDeprecatedAPICalls = classPath => - DEPRECATED_APIS.reduce( - (acc, name) => - acc + - j(classPath) - .find(j.Identifier, { name }) - .filter(path => { - // Do not consider history.replaceState() deprecated - let correctContext = true; - - if ( - name === 'replaceState' && - path.parentPath && - path.parentPath.value && - path.parentPath.value.object && - path.parentPath.value.object.name && - path.parentPath.value.object.name === 'history' - ) { - correctContext = false; - } - - return correctContext; - }) - .size(), - 0 - ) > 0; - - const hasNoCallsToDeprecatedAPIs = classPath => { - if (checkDeprecatedAPICalls(classPath)) { - console.warn( - file.path + - ': `' + - ReactUtils.directlyGetComponentName(classPath) + - '` ' + - 'was skipped because of deprecated API calls. Remove calls to ' + - DEPRECATED_APIS.join(', ') + - ' in your React component and re-run ' + - 'this script.' - ); - return false; - } - return true; - }; - - const hasNoRefsToAPIsThatWillBeRemoved = classPath => { - const hasInvalidCalls = - j(classPath) - .find(j.MemberExpression, { - object: { type: 'ThisExpression' }, - property: { name: DEFAULT_PROPS_FIELD } - }) - .size() > 0 || - j(classPath) - .find(j.MemberExpression, { - object: { type: 'ThisExpression' }, - property: { name: GET_INITIAL_STATE_FIELD } - }) - .size() > 0; - - if (hasInvalidCalls) { - console.warn( - file.path + - ': `' + - ReactUtils.directlyGetComponentName(classPath) + - '` ' + - 'was skipped because of API calls that will be removed. Remove calls to `' + - DEFAULT_PROPS_FIELD + - '` and/or `' + - GET_INITIAL_STATE_FIELD + - '` in your React component and re-run this script.' - ); - return false; - } - return true; - }; - - const isGetInitialStateConstructorSafe = getInitialState => { - if (!getInitialState) { - return true; - } - - const collection = j(getInitialState); - let result = true; - - const propsVarDeclarationCount = collection - .find(j.VariableDeclarator, { - id: { name: 'props' } - }) - .size(); - - const contextVarDeclarationCount = collection - .find(j.VariableDeclarator, { - id: { name: 'context' } - }) - .size(); - - if ( - propsVarDeclarationCount && - propsVarDeclarationCount !== - collection - .find(j.VariableDeclarator, { - id: { name: 'props' }, - init: { - type: 'MemberExpression', - object: { type: 'ThisExpression' }, - property: { name: 'props' } - } - }) - .size() - ) { - result = false; - } - - if ( - contextVarDeclarationCount && - contextVarDeclarationCount !== - collection - .find(j.VariableDeclarator, { - id: { name: 'context' }, - init: { - type: 'MemberExpression', - object: { type: 'ThisExpression' }, - property: { name: 'context' } - } - }) - .size() - ) { - result = false; - } - - return result; - }; - - const isInitialStateConvertible = classPath => { - const specPath = ReactUtils.directlyGetCreateClassSpec(classPath); - if (!specPath) { - return false; - } - const result = isGetInitialStateConstructorSafe( - findGetInitialState(specPath) - ); - if (!result) { - console.warn( - file.path + - ': `' + - ReactUtils.directlyGetComponentName(classPath) + - '` ' + - 'was skipped because of potential shadowing issues were found in ' + - 'the React component. Rename variable declarations of `props` and/or `context` ' + - 'in your `getInitialState` and re-run this script.' - ); - } - return result; - }; - - const canConvertToClass = classPath => { - const specPath = ReactUtils.directlyGetCreateClassSpec(classPath); - if (!specPath) { - return false; - } - const invalidProperties = specPath.properties.filter( - prop => - !prop.key.name || - (!STATIC_KEYS.hasOwnProperty(prop.key.name) && - STATIC_KEY != prop.key.name && - !filterDefaultPropsField(prop) && - !filterGetInitialStateField(prop) && - !isFunctionExpression(prop) && - !isPrimProperty(prop) && - !isPrimPropertyWithTypeAnnotation(prop) && - MIXIN_KEY != prop.key.name) - ); - - if (invalidProperties.length) { - const invalidText = invalidProperties - .map(prop => (prop.key.name ? prop.key.name : prop.key)) - .join(', '); - console.warn( - file.path + - ': `' + - ReactUtils.directlyGetComponentName(classPath) + - '` ' + - 'was skipped because of invalid field(s) `' + - invalidText + - '` on ' + - 'the React component. Remove any right-hand-side expressions that ' + - 'are not simple, like: `componentWillUpdate: createWillUpdate()` or ' + - '`render: foo ? renderA : renderB`.' - ); - } - return !invalidProperties.length; - }; - - const areMixinsConvertible = (mixinIdentifierNames, classPath) => { - if ( - ReactUtils.directlyHasMixinsField(classPath) && - !ReactUtils.directlyHasSpecificMixins(classPath, mixinIdentifierNames) - ) { - return false; - } - return true; - }; - - // --------------------------------------------------------------------------- - // Collectors - const pickReturnValueOrCreateIIFE = value => { - if (hasSingleReturnStatement(value)) { - if (value.body.type === 'ObjectExpression') { - return value.body; - } else { - return value.body.body[0].argument; - } - } else { - return j.callExpression(value, []); - } - }; - - const createDefaultProps = prop => - withComments( - j.property( - 'init', - j.identifier(DEFAULT_PROPS_KEY), - pickReturnValueOrCreateIIFE(prop.value) - ), - prop - ); - - // Collects `childContextTypes`, `contextTypes`, `displayName`, and `propTypes`; - // simplifies `getDefaultProps` or converts it to an IIFE; - // and collects everything else in the `statics` property object. - const collectStatics = specPath => { - const result = []; - - for (let i = 0; i < specPath.properties.length; i++) { - const property = specPath.properties[i]; - if ( - createFindPropFn('statics')(property) && - property.value && - property.value.properties - ) { - result.push(...property.value.properties); - } else if (createFindPropFn(DEFAULT_PROPS_FIELD)(property)) { - result.push(createDefaultProps(property)); - } else if ( - property.key && - STATIC_KEYS.hasOwnProperty(property.key.name) - ) { - result.push(property); - } - } - - return result; - }; - - const collectNonStaticProperties = specPath => - specPath.properties - .filter( - prop => - !(filterDefaultPropsField(prop) || filterGetInitialStateField(prop)) - ) - .filter( - prop => - !STATIC_KEYS.hasOwnProperty(prop.key.name) && - prop.key.name !== STATIC_KEY - ) - .filter( - prop => - isFunctionExpression(prop) || - isPrimPropertyWithTypeAnnotation(prop) || - isPrimProperty(prop) - ); - - const findRequirePathAndBinding = moduleName => { - let result = null; - const requireCall = root.find(j.VariableDeclarator, { - id: { type: 'Identifier' }, - init: { - callee: { name: 'require' }, - arguments: [{ value: moduleName }] - } - }); - - const importStatement = root.find(j.ImportDeclaration, { - source: { - value: moduleName - } - }); - - if (importStatement.size()) { - importStatement.forEach(path => { - result = { - path, - binding: path.value.specifiers[0].local.name, - type: 'import' - }; - }); - } else if (requireCall.size()) { - requireCall.forEach(path => { - result = { - path, - binding: path.value.id.name, - type: 'require' - }; - }); - } - - return result; - }; - - const pureRenderMixinPathAndBinding = findRequirePathAndBinding( - PURE_MIXIN_MODULE_NAME - ); - - // --------------------------------------------------------------------------- - // Boom! - const createMethodDefinition = fn => - withComments(j.methodDefinition('method', fn.key, fn.value), fn); - - const updatePropsAndContextAccess = getInitialState => { - const collection = j(getInitialState); - - collection - .find(j.MemberExpression, { - object: { - type: 'ThisExpression' - }, - property: { - type: 'Identifier', - name: 'props' - } - }) - .forEach(path => j(path).replaceWith(j.identifier('props'))); - - collection - .find(j.MemberExpression, { - object: { - type: 'ThisExpression' - }, - property: { - type: 'Identifier', - name: 'context' - } - }) - .forEach(path => j(path).replaceWith(j.identifier('context'))); - }; - - const inlineGetInitialState = getInitialState => { - const functionExpressionCollection = j(getInitialState.value); - - // at this point if there exists bindings like `const props = ...`, we - // already know the RHS must be `this.props` (see `isGetInitialStateConstructorSafe`) - // so it's safe to just remove them - functionExpressionCollection - .find(j.VariableDeclarator, { id: { name: 'props' } }) - .forEach(path => j(path).remove()); - - functionExpressionCollection - .find(j.VariableDeclarator, { id: { name: 'context' } }) - .forEach(path => j(path).remove()); - - return functionExpressionCollection - .find(j.ReturnStatement) - .filter(path => { - // filter out inner function declarations here (helper functions, promises, etc.). - const mainBodyCollection = j(getInitialState.value.body); - return ( - mainBodyCollection - .find(j.ArrowFunctionExpression) - .find(j.ReturnStatement, path.value) - .size() === 0 && - mainBodyCollection - .find(j.FunctionDeclaration) - .find(j.ReturnStatement, path.value) - .size() === 0 && - mainBodyCollection - .find(j.FunctionExpression) - .find(j.ReturnStatement, path.value) - .size() === 0 - ); - }) - .forEach(path => { - let shouldInsertReturnAfterAssignment = false; - - // if the return statement is not a direct child of getInitialState's body - if (getInitialState.value.body.body.indexOf(path.value) === -1) { - shouldInsertReturnAfterAssignment = true; - } - - j(path).replaceWith( - j.expressionStatement( - j.assignmentExpression( - '=', - j.memberExpression( - j.thisExpression(), - j.identifier('state'), - false - ), - path.value.argument - ) - ) - ); - - if (shouldInsertReturnAfterAssignment) { - j(path).insertAfter(j.returnStatement(null)); - } - }) - .getAST()[0].value.body.body; - }; - - const convertInitialStateToClassProperty = getInitialState => - withComments( - j.classProperty( - j.identifier('state'), - pickReturnValueOrCreateIIFE(getInitialState.value), - getInitialState.value.returnType, - false - ), - getInitialState - ); - - const createConstructorArgs = hasContextAccess => { - if (hasContextAccess) { - return [j.identifier('props'), j.identifier('context')]; - } - - return [j.identifier('props')]; - }; - - const createConstructor = getInitialState => { - const initialStateAST = j(getInitialState); - let hasContextAccess = false; - - if ( - initialStateAST - .find(j.MemberExpression, { - // has `this.context` access - object: { type: 'ThisExpression' }, - property: { type: 'Identifier', name: 'context' } - }) - .size() || - initialStateAST - .find(j.CallExpression, { - // a direct method call `this.x()` - callee: { - type: 'MemberExpression', - object: { type: 'ThisExpression' } - } - }) - .size() || - initialStateAST - .find(j.MemberExpression, { - // `this` is referenced alone - object: { type: 'ThisExpression' } - }) - .size() !== initialStateAST.find(j.ThisExpression).size() - ) { - hasContextAccess = true; - } - - updatePropsAndContextAccess(getInitialState); - const constructorArgs = createConstructorArgs(hasContextAccess); - - return [ - createMethodDefinition({ - key: j.identifier('constructor'), - value: j.functionExpression( - null, - constructorArgs, - j.blockStatement( - [].concat( - [ - j.expressionStatement( - j.callExpression(j.identifier('super'), constructorArgs) - ) - ], - inlineGetInitialState(getInitialState) - ) - ) - ) - }) - ]; - }; - - const createArrowFunctionExpression = fn => { - const arrowFunc = j.arrowFunctionExpression(fn.params, fn.body, false); - - arrowFunc.returnType = fn.returnType; - arrowFunc.defaults = fn.defaults; - arrowFunc.rest = fn.rest; - arrowFunc.async = fn.async; - arrowFunc.generator = fn.generator; - - return arrowFunc; - }; - - const createArrowProperty = prop => - withComments( - j.classProperty( - j.identifier(prop.key.name), - createArrowFunctionExpression(prop.value), - null, - false - ), - prop - ); - - const createClassProperty = prop => - withComments( - j.classProperty(j.identifier(prop.key.name), prop.value, null, false), - prop - ); - - const createClassPropertyWithType = prop => - withComments( - j.classProperty( - j.identifier(prop.key.name), - prop.value.expression, - prop.value.typeAnnotation, - false - ), - prop - ); - - // --------------------------------------------------------------------------- - // Flow! - - const flowAnyType = j.anyTypeAnnotation(); - const flowFixMeType = j.genericTypeAnnotation( - j.identifier('$FlowFixMe'), - null - ); - - const literalToFlowType = node => { - if (node.type === 'Identifier' && node.name === 'undefined') { - return j.voidTypeAnnotation(); - } - - switch (typeof node.value) { - case 'string': - return j.stringLiteralTypeAnnotation(node.value, node.raw); - case 'number': - return j.numberLiteralTypeAnnotation(node.value, node.raw); - case 'boolean': - return j.booleanLiteralTypeAnnotation(node.value, node.raw); - case 'object': // we already know it's a NullLiteral here - return j.nullLiteralTypeAnnotation(); - default: - // this should never happen - return flowFixMeType; - } - }; - - const propTypeToFlowMapping = { - // prim types - any: flowAnyType, - array: j.genericTypeAnnotation( - j.identifier('Array'), - j.typeParameterInstantiation([flowFixMeType]) - ), - bool: j.booleanTypeAnnotation(), - element: flowFixMeType, // flow does the same for `element` type in `propTypes` - func: j.genericTypeAnnotation(j.identifier('Function'), null), - node: flowFixMeType, // flow does the same for `node` type in `propTypes` - number: j.numberTypeAnnotation(), - object: j.genericTypeAnnotation(j.identifier('Object'), null), - string: j.stringTypeAnnotation(), - - // type classes - arrayOf: type => - j.genericTypeAnnotation( - j.identifier('Array'), - j.typeParameterInstantiation([type]) - ), - instanceOf: type => j.genericTypeAnnotation(type, null), - objectOf: type => - j.objectTypeAnnotation( - [], - [ - j.objectTypeIndexer( - j.identifier('key'), - j.stringTypeAnnotation(), - type - ) - ] - ), - oneOf: typeList => j.unionTypeAnnotation(typeList), - oneOfType: typeList => j.unionTypeAnnotation(typeList), - shape: propList => j.objectTypeAnnotation(propList) - }; - - const propTypeToFlowAnnotation = val => { - let cursor = val; - let isOptional = true; - let typeResult = flowFixMeType; - - if ( - // check `.isRequired` first - cursor.type === 'MemberExpression' && - cursor.property.type === 'Identifier' && - cursor.property.name === 'isRequired' - ) { - isOptional = false; - cursor = cursor.object; - } - - switch (cursor.type) { - case 'CallExpression': { - // type class - const calleeName = - cursor.callee.type === 'MemberExpression' - ? cursor.callee.property.name - : cursor.callee.name; - - const constructor = propTypeToFlowMapping[calleeName]; - if (!constructor) { - // unknown type class - // it's not necessary since `typeResult` defaults to `flowFixMeType`, - // but it's more explicit this way - typeResult = flowFixMeType; - break; - } - - switch (cursor.callee.property.name) { - case 'arrayOf': { - const arg = cursor.arguments[0]; - typeResult = constructor(propTypeToFlowAnnotation(arg)[0]); - break; - } - case 'instanceOf': { - const arg = cursor.arguments[0]; - if (arg.type !== 'Identifier') { - typeResult = flowFixMeType; - break; - } - - typeResult = constructor(arg); - break; - } - case 'objectOf': { - const arg = cursor.arguments[0]; - typeResult = constructor(propTypeToFlowAnnotation(arg)[0]); - break; - } - case 'oneOf': { - const argList = cursor.arguments[0].elements; - if ( - !argList || - !argList.every( - node => - node.type === 'Literal' || - (node.type === 'Identifier' && node.name === 'undefined') - ) - ) { - typeResult = flowFixMeType; - } else { - typeResult = constructor(argList.map(literalToFlowType)); - } - break; - } - case 'oneOfType': { - const argList = cursor.arguments[0].elements; - if (!argList) { - typeResult = flowFixMeType; - } else { - typeResult = constructor( - argList.map(arg => propTypeToFlowAnnotation(arg)[0]) - ); - } - break; - } - case 'shape': { - const rawPropList = cursor.arguments[0].properties; - if (!rawPropList) { - typeResult = flowFixMeType; - break; - } - const flowPropList = []; - rawPropList.forEach(typeProp => { - const keyIsLiteral = typeProp.key.type === 'Literal'; - const name = keyIsLiteral - ? typeProp.key.value - : typeProp.key.name; - - const [valueType, isOptional] = propTypeToFlowAnnotation( - typeProp.value - ); - flowPropList.push( - j.objectTypeProperty( - keyIsLiteral ? j.literal(name) : j.identifier(name), - valueType, - isOptional - ) - ); - }); - - typeResult = constructor(flowPropList); - break; - } - default: { - break; - } - } - break; - } - case 'MemberExpression': { - // prim type - if (cursor.property.type !== 'Identifier') { - // unrecognizable - typeResult = flowFixMeType; - break; - } - - const maybeType = propTypeToFlowMapping[cursor.property.name]; - if (maybeType) { - typeResult = propTypeToFlowMapping[cursor.property.name]; - } else { - // type not found - typeResult = flowFixMeType; - } - - break; - } - default: { - // unrecognizable - break; - } - } - - return [typeResult, isOptional]; - }; - - const createFlowAnnotationsFromPropTypesProperties = prop => { - const typePropertyList = []; - - if (!prop || prop.value.type !== 'ObjectExpression') { - return typePropertyList; - } - - prop.value.properties.forEach(typeProp => { - if (!typeProp.key) { - // stuff like SpreadProperty - return; - } - - const keyIsLiteral = typeProp.key.type === 'Literal'; - const name = keyIsLiteral ? typeProp.key.value : typeProp.key.name; - - const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value); - typePropertyList.push( - j.objectTypeProperty( - keyIsLiteral ? j.literal(name) : j.identifier(name), - valueType, - isOptional - ) - ); - }); - - return j.classProperty( - j.identifier('props'), - null, - j.typeAnnotation(j.objectTypeAnnotation(typePropertyList)), - false - ); - }; - - // to ensure that our property initializers' evaluation order is safe - const repositionStateProperty = ( - initialStateProperty, - propertiesAndMethods - ) => { - const initialStateCollection = j(initialStateProperty); - const thisCount = initialStateCollection.find(j.ThisExpression).size(); - const safeThisMemberCount = - initialStateCollection - .find(j.MemberExpression, { - object: { - type: 'ThisExpression' - }, - property: { - type: 'Identifier', - name: 'props' - } - }) - .size() + - initialStateCollection - .find(j.MemberExpression, { - object: { - type: 'ThisExpression' - }, - property: { - type: 'Identifier', - name: 'context' - } - }) - .size(); - - if (thisCount === safeThisMemberCount) { - return initialStateProperty.concat(propertiesAndMethods); - } - - const result = [].concat(propertiesAndMethods); - let lastPropPosition = result.length - 1; - - while ( - lastPropPosition >= 0 && - result[lastPropPosition].kind === 'method' - ) { - lastPropPosition--; - } - - result.splice(lastPropPosition + 1, 0, initialStateProperty[0]); - return result; - }; - - // if there's no `getInitialState` or the `getInitialState` function is simple - // (i.e., it's just a return statement) then we don't need a constructor. - // we can simply lift `state = {...}` as a property initializer. - // otherwise, create a constructor and inline `this.state = ...`. - // - // when we need to create a constructor, we only put `context` as the - // second parameter when the following things happen in `getInitialState()`: - // 1. there's a `this.context` access, or - // 2. there's a direct method call `this.x()`, or - // 3. `this` is referenced alone - const createESClass = ( - name, - baseClassName, - staticProperties, - getInitialState, - rawProperties, - comments - ) => { - const initialStateProperty = []; - let maybeConstructor = []; - let maybeFlowStateAnnotation = []; // we only need this when we do `this.state = ...` - - if (isInitialStateLiftable(getInitialState)) { - if (getInitialState) { - initialStateProperty.push( - convertInitialStateToClassProperty(getInitialState) - ); - } - } else { - maybeConstructor = createConstructor(getInitialState); - if (shouldTransformFlow) { - let stateType = j.typeAnnotation(j.existsTypeAnnotation()); - - if (getInitialState.value.returnType) { - stateType = getInitialState.value.returnType; - } - - maybeFlowStateAnnotation.push( - j.classProperty(j.identifier('state'), null, stateType, false) - ); - } - } - - const propertiesAndMethods = rawProperties.map(prop => { - if (isPrimPropertyWithTypeAnnotation(prop)) { - return createClassPropertyWithType(prop); - } else if (isPrimProperty(prop)) { - return createClassProperty(prop); - } else if (AUTOBIND_IGNORE_KEYS.hasOwnProperty(prop.key.name)) { - return createMethodDefinition(prop); - } - - return createArrowProperty(prop); - }); - - const flowPropsAnnotation = shouldTransformFlow - ? createFlowAnnotationsFromPropTypesProperties( - staticProperties.find(path => path.key.name === 'propTypes') - ) - : []; - - let finalStaticProperties = staticProperties; - - if (shouldTransformFlow && options['remove-runtime-proptypes']) { - finalStaticProperties = staticProperties.filter( - prop => prop.key.name !== 'propTypes' - ); - } - - return withComments( - j.classDeclaration( - name ? j.identifier(name) : null, - j.classBody( - [].concat( - flowPropsAnnotation, - maybeFlowStateAnnotation, - finalStaticProperties, - maybeConstructor, - repositionStateProperty(initialStateProperty, propertiesAndMethods) - ) - ), - j.memberExpression( - j.identifier('React'), - j.identifier(baseClassName), - false - ) - ), - { comments } - ); - }; - - const createStaticClassProperty = staticProperty => { - if (staticProperty.value.type === 'FunctionExpression') { - return withComments( - j.methodDefinition( - 'method', - j.identifier(staticProperty.key.name), - staticProperty.value, - true - ), - staticProperty - ); - } - - if (staticProperty.value.type === 'TypeCastExpression') { - return withComments( - j.classProperty( - j.identifier(staticProperty.key.name), - staticProperty.value.expression, - staticProperty.value.typeAnnotation, - true - ), - staticProperty - ); - } - - return withComments( - j.classProperty( - j.identifier(staticProperty.key.name), - staticProperty.value, - null, - true - ), - staticProperty - ); - }; - - const createStaticClassProperties = statics => - statics.map(createStaticClassProperty); - - const getComments = classPath => { - if (classPath.value.comments) { - return classPath.value.comments; - } - const declaration = j(classPath).closest(j.VariableDeclaration); - if (declaration.size()) { - return declaration.get().value.comments; - } - return null; - }; - - const findUnusedVariables = (path, varName) => - j(path) - .closestScope() - .find(j.Identifier, { name: varName }) - // Ignore require vars - .filter(identifierPath => identifierPath.value !== path.value.id) - // Ignore import bindings - .filter( - identifierPath => - !( - path.value.type === 'ImportDeclaration' && - path.value.specifiers.some( - specifier => specifier.local === identifierPath.value - ) - ) - ) - // Ignore properties in MemberExpressions - .filter(identifierPath => { - const parent = identifierPath.parent.value; - return !( - j.MemberExpression.check(parent) && - parent.property === identifierPath.value - ); - }); - - const updateToClass = classPath => { - const specPath = ReactUtils.directlyGetCreateClassSpec(classPath); - const name = ReactUtils.directlyGetComponentName(classPath); - const statics = collectStatics(specPath); - const properties = collectNonStaticProperties(specPath); - const comments = getComments(classPath); - - const getInitialState = findGetInitialState(specPath); - - var path = classPath; - - if ( - classPath.parentPath && - classPath.parentPath.value && - classPath.parentPath.value.type === 'VariableDeclarator' - ) { - // the reason that we need to do this awkward dance here is that - // for things like `var Foo = React.createClass({...})`, we need to - // replace the _entire_ VariableDeclaration with - // `class Foo extends React.Component {...}`. - // it looks scary but since we already know it's a VariableDeclarator - // it's actually safe. - // (VariableDeclaration > declarations > VariableDeclarator > CallExpression) - path = classPath.parentPath.parentPath.parentPath; - } - - const staticProperties = createStaticClassProperties(statics); - const baseClassName = - pureRenderMixinPathAndBinding && - ReactUtils.directlyHasSpecificMixins(classPath, [ - pureRenderMixinPathAndBinding.binding - ]) - ? 'PureComponent' - : 'Component'; - - j(path).replaceWith( - createESClass( - name, - baseClassName, - staticProperties, - getInitialState, - properties, - comments - ) - ); - }; - - const addDisplayName = (displayName, specPath) => { - const props = specPath.properties; - let safe = true; - - for (let i = 0; i < props.length; i++) { - const prop = props[i]; - if (prop.key.name === 'displayName') { - safe = false; - break; - } - } - - if (safe) { - props.unshift( - j.objectProperty( - j.identifier('displayName'), - j.stringLiteral(displayName) - ) - ); - } - }; - - const fallbackToCreateClassModule = classPath => { - const comments = getComments(classPath); - const specPath = ReactUtils.directlyGetCreateClassSpec(classPath); - - if (!NO_DISPLAY_NAME) { - if (specPath) { - // Add a displayName property to the spec object - let path = classPath; - let displayName; - while (path && displayName === undefined) { - switch (path.node.type) { - case 'ExportDefaultDeclaration': - displayName = basename(file.path, extname(file.path)); - if (displayName === 'index') { - // ./{module name}/index.js - displayName = basename(dirname(file.path)); - } - break; - case 'VariableDeclarator': - displayName = path.node.id.name; - break; - case 'AssignmentExpression': - displayName = path.node.left.name; - break; - case 'Property': - displayName = path.node.key.name; - break; - case 'Statement': - displayName = null; - break; - } - path = path.parent; - } - if (displayName) { - addDisplayName(displayName, specPath); - } - } - } - - withComments( - j(classPath).replaceWith( - specPath - ? j.callExpression(j.identifier(CREATE_CLASS_VARIABLE_NAME), [ - specPath - ]) - : j.callExpression( - j.identifier(CREATE_CLASS_VARIABLE_NAME), - classPath.value.arguments - ) - ), - { comments } - ); - }; - - if (options['explicit-require'] === false || ReactUtils.hasReact(root)) { - // no mixins found on the classPath -> true - // pure mixin identifier not found -> (has mixins) -> false - // found pure mixin identifier -> - // class mixins is an array and only contains the identifier -> true - // otherwise -> false - const mixinsFilter = classPath => { - if (!ReactUtils.directlyHasMixinsField(classPath)) { - return true; - } else if (options['pure-component'] && pureRenderMixinPathAndBinding) { - const { binding } = pureRenderMixinPathAndBinding; - if (areMixinsConvertible([binding], classPath)) { - return true; - } - } - console.warn( - file.path + - ': `' + - ReactUtils.directlyGetComponentName(classPath) + - '` ' + - 'was skipped because of inconvertible mixins.' - ); - - return false; - }; - - const reinsertTopComments = () => { - root.get().node.comments = topComments; - }; - - let didTransform = false; - let didFallback = false; - - const path = ReactUtils.findAllReactCreateClassCalls(root); - if (NO_CONVERSION) { - path.forEach(childPath => { - fallbackToCreateClassModule(childPath); - }); - didFallback = true; - } else { - // the only time that we can't simply replace the createClass call path - // with a new class is when the parent of that is a variable declaration. - // let's delay it and figure it out later (by looking at `path.parentPath`) - // in `updateToClass`. - path.forEach(childPath => { - if ( - mixinsFilter(childPath) && - hasNoCallsToDeprecatedAPIs(childPath) && - hasNoRefsToAPIsThatWillBeRemoved(childPath) && - doesNotUseArguments(childPath) && - isInitialStateConvertible(childPath) && - canConvertToClass(childPath) - ) { - didTransform = true; - updateToClass(childPath); - } else { - didFallback = true; - fallbackToCreateClassModule(childPath); - } - }); - } - - if (didFallback) { - const reactPathAndBinding = - findRequirePathAndBinding('react') || - findRequirePathAndBinding('React'); - - if (reactPathAndBinding) { - const { path, type } = reactPathAndBinding; - let removePath = null; - let shouldReinsertComment = false; - if (type === 'require') { - const kind = path.parent.value.kind; - j(path.parent).insertAfter( - j.template.statement([ - `${kind} ${CREATE_CLASS_VARIABLE_NAME} = require('${CREATE_CLASS_MODULE_NAME}');` - ]) - ); - const bodyNode = path.parentPath.parentPath.parentPath.value; - const variableDeclarationNode = path.parentPath.parentPath.value; - shouldReinsertComment = - bodyNode.indexOf(variableDeclarationNode) === 0; - removePath = path.parent; - } else { - j(path).insertAfter( - j.template.statement([ - `import ${CREATE_CLASS_VARIABLE_NAME} from '${CREATE_CLASS_MODULE_NAME}';` - ]) - ); - const importDeclarationNode = path.value; - const bodyNode = path.parentPath.value; - removePath = path; - const specifiers = path.value.specifiers; - if (specifiers.length === 1) { - shouldReinsertComment = - bodyNode.indexOf(importDeclarationNode) === 0; - removePath = path; - } else { - const paths = j(path).find(j.ImportDefaultSpecifier); - if (paths.length) { - removePath = j(path) - .find(j.ImportDefaultSpecifier) - .paths()[0]; - } - } - } - - const shouldRemoveReactImport = - removePath && - root.find(j.Identifier).filter(path => path.value.name === 'React') - .length === 1 && - root.find(j.JSXElement).length === 0; - if (shouldRemoveReactImport && removePath) { - j(removePath).remove(); - if (shouldReinsertComment) { - reinsertTopComments(); - } - } - } - } - - if (didTransform) { - // prune removed requires - if (pureRenderMixinPathAndBinding) { - const { binding, path, type } = pureRenderMixinPathAndBinding; - let shouldReinsertComment = false; - if (findUnusedVariables(path, binding).size() === 0) { - var removePath = null; - if (type === 'require') { - const bodyNode = path.parentPath.parentPath.parentPath.value; - const variableDeclarationNode = path.parentPath.parentPath.value; - - if (variableDeclarationNode.declarations.length === 1) { - removePath = path.parentPath.parentPath; - shouldReinsertComment = - bodyNode.indexOf(variableDeclarationNode) === 0; - } else { - removePath = path; - } - } else { - const importDeclarationNode = path.value; - const bodyNode = path.parentPath.value; - - removePath = path; - shouldReinsertComment = - bodyNode.indexOf(importDeclarationNode) === 0; - } - - j(removePath).remove(); - if (shouldReinsertComment) { - reinsertTopComments(); - } - } - } - } - } - return root.toSource(printOptions); -}; - -module.exports.parser = 'flow'; diff --git a/codemods/legacy/transforms/create-element-to-jsx.js b/codemods/legacy/transforms/create-element-to-jsx.js deleted file mode 100644 index 66dbb88..0000000 --- a/codemods/legacy/transforms/create-element-to-jsx.js +++ /dev/null @@ -1,269 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -module.exports = function(file, api, options) { - const j = api.jscodeshift; - const printOptions = options.printOptions || {}; - const root = j(file.source); - const ReactUtils = require('./utils/ReactUtils')(j); - const encodeJSXTextValue = value => - value.replace(//g, '>'); - - const canLiteralBePropString = node => - node.raw.indexOf('\\') === -1 && node.value.indexOf('"') === -1; - - const convertExpressionToJSXAttributes = expression => { - if (!expression) { - return { - attributes: [], - extraComments: [] - }; - } - - const isReactSpread = - expression.type === 'CallExpression' && - expression.callee.type === 'MemberExpression' && - expression.callee.object.name === 'React' && - expression.callee.property.name === '__spread'; - - const isObjectAssign = - expression.type === 'CallExpression' && - expression.callee.type === 'MemberExpression' && - expression.callee.object.name === 'Object' && - expression.callee.property.name === 'assign'; - - const validSpreadTypes = [ - 'Identifier', - 'MemberExpression', - 'CallExpression' - ]; - - if (isReactSpread || isObjectAssign) { - const resultAttributes = []; - const resultExtraComments = expression.comments || []; - const { callee } = expression; - for (const node of [callee, callee.object, callee.property]) { - resultExtraComments.push(...(node.comments || [])); - } - expression.arguments.forEach(expression => { - const { attributes, extraComments } = convertExpressionToJSXAttributes( - expression - ); - resultAttributes.push(...attributes); - resultExtraComments.push(...extraComments); - }); - - return { - attributes: resultAttributes, - extraComments: resultExtraComments - }; - } else if (validSpreadTypes.indexOf(expression.type) != -1) { - return { - attributes: [j.jsxSpreadAttribute(expression)], - extraComments: [] - }; - } else if (expression.type === 'ObjectExpression') { - const attributes = expression.properties.map(property => { - if (property.type === 'SpreadProperty') { - const spreadAttribute = j.jsxSpreadAttribute(property.argument); - spreadAttribute.comments = property.comments; - return spreadAttribute; - } else if (property.type === 'Property') { - const propertyValueType = property.value.type; - - let value; - if ( - propertyValueType === 'Literal' && - typeof property.value.value === 'string' && - canLiteralBePropString(property.value) - ) { - value = j.literal(property.value.value); - value.comments = property.value.comments; - } else { - value = j.jsxExpressionContainer(property.value); - } - - let jsxIdentifier; - if (property.key.type === 'Literal') { - jsxIdentifier = j.jsxIdentifier(property.key.value); - } else { - jsxIdentifier = j.jsxIdentifier(property.key.name); - } - jsxIdentifier.comments = property.key.comments; - - const jsxAttribute = j.jsxAttribute(jsxIdentifier, value); - jsxAttribute.comments = property.comments; - return jsxAttribute; - } - return null; - }); - - return { - attributes, - extraComments: expression.comments || [] - }; - } else if (expression.type === 'Literal' && expression.value === null) { - return { - attributes: [], - extraComments: expression.comments || [] - }; - } else { - throw new Error(`Unexpected attribute of type "${expression.type}"`); - } - }; - - const canConvertToJSXIdentifier = node => - (node.type === 'Literal' && typeof node.value === 'string') || - node.type === 'Identifier' || - (node.type === 'MemberExpression' && - !node.computed && - canConvertToJSXIdentifier(node.object) && - canConvertToJSXIdentifier(node.property)); - - const jsxIdentifierFor = node => { - let identifier; - let comments = node.comments || []; - if (node.type === 'Literal') { - identifier = j.jsxIdentifier(node.value); - } else if (node.type === 'MemberExpression') { - let { - identifier: objectIdentifier, - comments: objectComments - } = jsxIdentifierFor(node.object); - let { - identifier: propertyIdentifier, - comments: propertyComments - } = jsxIdentifierFor(node.property); - identifier = j.jsxMemberExpression(objectIdentifier, propertyIdentifier); - comments.push(...objectComments, ...propertyComments); - } else { - identifier = j.jsxIdentifier(node.name); - } - return { identifier, comments }; - }; - - const isCapitalizationInvalid = node => - (node.type === 'Literal' && !/^[a-z]/.test(node.value)) || - (node.type === 'Identifier' && /^[a-z]/.test(node.name)); - - const convertNodeToJSX = node => { - const comments = node.value.comments || []; - const { callee } = node.value; - for (const calleeNode of [callee, callee.object, callee.property]) { - for (const comment of calleeNode.comments || []) { - comment.leading = true; - comment.trailing = false; - comments.push(comment); - } - } - - const args = node.value.arguments; - - if ( - isCapitalizationInvalid(args[0]) || - !canConvertToJSXIdentifier(args[0]) - ) { - return node.value; - } - - const { - identifier: jsxIdentifier, - comments: identifierComments - } = jsxIdentifierFor(args[0]); - const props = args[1]; - - const { attributes, extraComments } = convertExpressionToJSXAttributes( - props - ); - - for (const comment of [...identifierComments, ...extraComments]) { - comment.leading = false; - comment.trailing = true; - comments.push(comment); - } - - const children = args.slice(2).map((child, index) => { - if ( - child.type === 'Literal' && - typeof child.value === 'string' && - !child.comments && - child.value !== '' && - child.value.trim() === child.value - ) { - return j.jsxText(encodeJSXTextValue(child.value)); - } else if ( - child.type === 'CallExpression' && - child.callee.object && - child.callee.object.name === 'React' && - child.callee.property.name === 'createElement' - ) { - const jsxChild = convertNodeToJSX(node.get('arguments', index + 2)); - if ( - jsxChild.type !== 'JSXElement' || - (jsxChild.comments || []).length > 0 - ) { - return j.jsxExpressionContainer(jsxChild); - } else { - return jsxChild; - } - } else if (child.type === 'SpreadElement') { - return j.jsxExpressionContainer(child.argument); - } else { - return j.jsxExpressionContainer(child); - } - }); - - const openingElement = j.jsxOpeningElement(jsxIdentifier, attributes); - - if (children.length) { - const endIdentifier = Object.assign({}, jsxIdentifier, { comments: [] }); - // Add text newline nodes between elements so recast formats one child per - // line instead of all children on one line. - const paddedChildren = [j.jsxText('\n')]; - for (const child of children) { - paddedChildren.push(child, j.jsxText('\n')); - } - const element = j.jsxElement( - openingElement, - j.jsxClosingElement(endIdentifier), - paddedChildren - ); - element.comments = comments; - return element; - } else { - openingElement.selfClosing = true; - const element = j.jsxElement(openingElement); - element.comments = comments; - return element; - } - }; - - if (options['explicit-require'] === false || ReactUtils.hasReact(root)) { - const mutations = root - .find(j.CallExpression, { - callee: { - object: { - name: 'React' - }, - property: { - name: 'createElement' - } - } - }) - .replaceWith(convertNodeToJSX) - .size(); - - if (mutations) { - return root.toSource(printOptions); - } - } - - return null; -}; diff --git a/codemods/legacy/transforms/error-boundaries.js b/codemods/legacy/transforms/error-boundaries.js deleted file mode 100644 index 86710eb..0000000 --- a/codemods/legacy/transforms/error-boundaries.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = function(file, api, options) { - const j = api.jscodeshift; - - return j(file.source) - .find(j.Identifier) - .forEach(path => { - if (path.node.name === 'unstable_handleError') { - j(path).replaceWith(j.identifier('componentDidCatch')); - } - }) - .toSource(); -}; diff --git a/codemods/legacy/transforms/findDOMNode.js b/codemods/legacy/transforms/findDOMNode.js deleted file mode 100644 index f8665f9..0000000 --- a/codemods/legacy/transforms/findDOMNode.js +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -function getDOMNodeToFindDOMNode(file, api, options) { - const j = api.jscodeshift; - - require('./utils/array-polyfills'); - const ReactUtils = require('./utils/ReactUtils')(j); - - const printOptions = options.printOptions || { - quote: 'single', - trailingComma: true - }; - const root = j(file.source); - - const createReactFindDOMNodeCall = arg => - j.callExpression( - j.memberExpression( - j.identifier('React'), - j.identifier('findDOMNode'), - false - ), - [arg] - ); - - const updateRefCall = (path, refName) => { - j(path) - .find(j.CallExpression, { - callee: { - object: { - type: 'Identifier', - name: refName - }, - property: { - type: 'Identifier', - name: 'getDOMNode' - } - } - }) - .forEach(callPath => - j(callPath).replaceWith( - createReactFindDOMNodeCall(j.identifier(refName)) - ) - ); - }; - - const updateToFindDOMNode = classPath => { - var sum = 0; - - // this.getDOMNode() - sum += j(classPath) - .find(j.CallExpression, { - callee: { - object: { - type: 'ThisExpression' - }, - property: { - type: 'Identifier', - name: 'getDOMNode' - } - } - }) - .forEach(path => - j(path).replaceWith(createReactFindDOMNodeCall(j.thisExpression())) - ) - .size(); - - // this.refs.xxx.getDOMNode() or this.refs.xxx.refs.yyy.getDOMNode() - sum += j(classPath) - .find(j.MemberExpression, { - object: { - type: 'MemberExpression', - object: { - type: 'MemberExpression', - object: { - type: 'ThisExpression' - }, - property: { - type: 'Identifier', - name: 'refs' - } - } - } - }) - .closest(j.CallExpression) - .filter( - path => - path.value.callee.property && - path.value.callee.property.type === 'Identifier' && - path.value.callee.property.name === 'getDOMNode' - ) - .forEach(path => - j(path).replaceWith( - createReactFindDOMNodeCall(path.value.callee.object) - ) - ) - .size(); - - // someVariable.getDOMNode() wherre `someVariable = this.refs.xxx` - sum += j(classPath) - .findVariableDeclarators() - .filter(path => { - const init = path.value.init; - const value = init && init.object; - return ( - value && - value.type === 'MemberExpression' && - value.object && - value.object.type === 'ThisExpression' && - value.property && - value.property.type === 'Identifier' && - value.property.name === 'refs' && - init.property && - init.property.type === 'Identifier' - ); - }) - .forEach(path => - j(path) - .closest(j.FunctionExpression) - .forEach(fnPath => updateRefCall(fnPath, path.value.id.name)) - ) - .size(); - - return sum > 0; - }; - - if (options['explicit-require'] === false || ReactUtils.hasReact(root)) { - const apply = path => path.filter(updateToFindDOMNode); - - const didTransform = - apply(ReactUtils.findReactCreateClass(root)).size() + - apply(ReactUtils.findReactCreateClassModuleExports(root)).size() + - apply(ReactUtils.findReactCreateClassExportDefault(root)).size() > - 0; - - if (didTransform) { - return root.toSource(printOptions); - } - } - - return null; -} - -module.exports = getDOMNodeToFindDOMNode; diff --git a/codemods/legacy/transforms/manual-bind-to-arrow.js b/codemods/legacy/transforms/manual-bind-to-arrow.js deleted file mode 100644 index 7afb09a..0000000 --- a/codemods/legacy/transforms/manual-bind-to-arrow.js +++ /dev/null @@ -1,184 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -/** - * class Component extends React.Component { - * constructor() { this.onClick = this.onClick.bind(this); } - * onClick() { } - * } - * - * --> - * - * class Component extends React.Component { - * onClick = () => { } - * } - */ - -export default function transformer(file, api, options) { - const j = api.jscodeshift; - const doesNotUseArguments = require('./utils/doesNotUseArguments')(j); - - const printOptions = options.printOptions || {}; - var root = j(file.source); - - // Helper functions to transform a method declaration to an arrow function - // By default recast drops comments and jscodeshift doesn't have a way to - // set the return type in the convenience method. Otherwise we would have - // inlined all those. - function withComments(to, from) { - to.comments = from.comments; - return to; - } - - function createArrowFunctionExpression(fn) { - var arrowFunc = j.arrowFunctionExpression(fn.params, fn.body, false); - - arrowFunc.returnType = fn.returnType; - arrowFunc.defaults = fn.defaults; - arrowFunc.rest = fn.rest; - arrowFunc.async = fn.async; - - return arrowFunc; - } - - function createArrowProperty(prop) { - return withComments( - j.classProperty( - j.identifier(prop.key.name), - createArrowFunctionExpression(prop.value), - null, - false - ), - prop - ); - } - - var hasChanged = false; - var transform = root.find(j.AssignmentExpression).forEach(path => { - // Check that the englobing function is constructor - var methodPath = path; - while ( - methodPath && - (methodPath.node.type !== 'MethodDefinition' || - methodPath.node.kind !== 'constructor') - ) { - methodPath = methodPath.parentPath; - } - if (!methodPath) { - return; - } - - // Check that it looks like - // this.method = this.method.bind(this); - // or - // (this: any).method = this.method.bind(this); - // or - // self.method = this.method.bind(this); - if ( - !( - path.node.left.type === 'MemberExpression' && - // this - (path.node.left.object.type === 'ThisExpression' || - // self - (path.node.left.object.type === 'Identifier' && - path.node.left.object.name === 'self') || - // (this: any) - (path.node.left.object.type === 'TypeCastExpression' && - path.node.left.object.expression.type === 'ThisExpression')) && - path.node.left.property.type === 'Identifier' && - path.node.right.type === 'CallExpression' && - path.node.right.callee.type === 'MemberExpression' && - path.node.right.callee.property.type === 'Identifier' && - path.node.right.callee.property.name === 'bind' && - path.node.right.callee.object.type === 'MemberExpression' && - path.node.right.callee.object.property.type === 'Identifier' && - path.node.right.callee.object.object.type === 'ThisExpression' && - path.node.left.property.name === - path.node.right.callee.object.property.name && - true - ) - ) { - return; - } - - // Find the method() declaration and replace it with an arrow function - var methodName = path.node.left.property.name; - - const componentDecl = methodPath.parentPath; - var methods = j(componentDecl) - .find(j.MethodDefinition) - .filter( - path => - path.node.key.type === 'Identifier' && - path.node.key.name === methodName && - doesNotUseArguments(path, file.path) - ); - - // Do not remove the binding if there's no corresponding method to turn - // into an arrow function, or if the method uses `arguments` keyword inside - // it. - if (methods.size() === 0) { - return; - } - methods.replaceWith(path => createArrowProperty(path.node)); - - // Remove the line - // this.method = this.method.bind(this); - j(path.parentPath).remove(); - - var selfCount = j(methodPath) - .find(j.Identifier, { name: 'self' }) - .size(); - if (selfCount === 1) { - // Remove the line - // const self: any = this; - // If self is present somewhere else in the method, then it is - // not safe to do. - j(methodPath) - .find(j.VariableDeclaration) - .filter( - path => - j(path) - .find(j.Identifier, { name: 'self' }) - .size() === 1 - ) - .remove(); - } - - // If we delete everything from the constructor but the super() call, - // then delete the entire constructor. - var canDeleteConstructor = true; - methodPath.node.value.body.body.forEach(node => { - if ( - !node || - (node.type === 'ExpressionStatement' && - node.expression.type === 'CallExpression' && - // babylon parser - (node.expression.callee.type === 'Super' || - // flow parser - (node.expression.callee.type === 'Identifier' && - node.expression.callee.name === 'super'))) - ) { - return; - } - canDeleteConstructor = false; - }); - if (canDeleteConstructor) { - j(methodPath).remove(); - } - - hasChanged++; - }); - - if (hasChanged) { - return transform.toSource(printOptions); - } - return null; -} - -// module.exports.parser = 'flow'; \ No newline at end of file diff --git a/codemods/legacy/transforms/pure-component.js b/codemods/legacy/transforms/pure-component.js deleted file mode 100644 index af538ad..0000000 --- a/codemods/legacy/transforms/pure-component.js +++ /dev/null @@ -1,339 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -module.exports = function(file, api, options) { - const j = api.jscodeshift; - const ReactUtils = require('./utils/ReactUtils')(j); - - const useArrows = options.useArrows || false; - const destructuringEnabled = options.destructuring || false; - const silenceWarnings = options.silenceWarnings || false; - const printOptions = options.printOptions || { - quote: 'single', - trailingComma: true - }; - - const getClassName = path => path.node.id.name; - - const isRenderMethod = node => - node.type == 'MethodDefinition' && - node.key.type == 'Identifier' && - node.key.name == 'render'; - - const isPropsProperty = node => - node.type === 'ClassProperty' && - node.key.type === 'Identifier' && - node.key.name === 'props'; - - const isStaticProperty = node => node.type === 'ClassProperty' && node.static; - - const onlyHasRenderMethod = path => - j(path) - .find(j.MethodDefinition) - .filter(p => !isRenderMethod(p.value)) - .size() === 0; - - const onlyHasSafeClassProperties = path => - j(path) - .find(j.ClassProperty) - .filter(p => !(isPropsProperty(p.value) || isStaticProperty(p.value))) - .size() === 0; - - const hasRefs = path => - j(path) - .find(j.JSXAttribute, { - name: { - type: 'JSXIdentifier', - name: 'ref' - } - }) - .size() > 0; - - const THIS_PROPS = { - object: { - type: 'ThisExpression' - }, - property: { - name: 'props' - } - }; - - const replaceThisProps = path => - j(path) - .find(j.MemberExpression, THIS_PROPS) - .replaceWith(j.identifier('props')); - - const buildIdentifierWithTypeAnnotation = (name, typeAnnotation) => { - const identifier = j.identifier(name); - if (typeAnnotation) { - identifier.typeAnnotation = j.typeAnnotation(typeAnnotation); - } - return identifier; - }; - - const isDuplicateDeclaration = (path, pre) => { - if (path && path.value && path.value.id && path.value.init) { - const initName = pre - ? path.value.init.property && path.value.init.property.name - : path.value.init.name; - return path.value.id.name === initName; - } - return false; - }; - - const needsThisDotProps = path => - path - .find(j.Identifier, { - name: 'props' - }) - .filter(p => p.parentPath.parentPath.value.type !== 'MemberExpression') - .size() > 0; - - const getPropNames = path => { - const propNames = new Set(); - path - .find(j.MemberExpression, { - object: { - property: { - name: 'props' - } - } - }) - .forEach(p => { - propNames.add(p.value.property.name); - }); - return propNames; - }; - - const getDuplicateNames = path => { - const duplicates = new Set(); - path - .find(j.VariableDeclarator) - .filter(p => isDuplicateDeclaration(p, true)) - .forEach(p => { - duplicates.add(p.value.id.name); - }); - return duplicates; - }; - - const getAssignmentNames = path => { - const assignmentNames = new Set(); - path - .find(j.Identifier) - .filter(p => { - if (p.value.type === 'JSXIdentifier') { - return false; - } - if ( - !(p.parentPath.value.object && p.parentPath.value.object.property) - ) { - return true; - } - return p.parentPath.value.object.property.name !== 'props'; - }) - .forEach(p => { - assignmentNames.add(p.value.name); - }); - return assignmentNames; - }; - - const hasAssignmentsThatShadowProps = path => { - const propNames = getPropNames(path); - const assignmentNames = getAssignmentNames(path); - const duplicates = getDuplicateNames(path); - return Array.from(propNames).some( - prop => !duplicates.has(prop) && assignmentNames.has(prop) - ); - }; - - const canDestructure = path => - !needsThisDotProps(path) && !hasAssignmentsThatShadowProps(path); - - const createShorthandProperty = (j, typeAnnotation) => prop => { - const property = j.property('init', j.identifier(prop), j.identifier(prop)); - property.shorthand = true; - if (typeAnnotation) { - typeAnnotation.properties.forEach(t => { - if (t.key.name === prop) { - property.key.typeAnnotation = j.typeAnnotation(t.value); - } - }); - } - return property; - }; - - const destructureProps = (body, typeAnnotation) => { - const toDestructure = body.find(j.MemberExpression, { - object: { - name: 'props' - } - }); - if (toDestructure) { - const propNames = new Set(); - toDestructure.replaceWith(path => { - const propName = path.value.property.name; - propNames.add(propName); - return j.identifier(propName); - }); - if (propNames.size > 0) { - const assignments = body.find(j.VariableDeclarator); - const duplicateAssignments = assignments.filter(a => - isDuplicateDeclaration(a, false) - ); - duplicateAssignments.remove(); - return j.objectPattern( - Array.from(propNames).map(createShorthandProperty(j, typeAnnotation)) - ); - } - } - return false; - }; - - const findPropsTypeAnnotation = body => { - const property = body.find(isPropsProperty); - - return property && property.typeAnnotation.typeAnnotation; - }; - - const isDefaultExport = path => - path.parentPath && path.parentPath.value.type === 'ExportDefaultDeclaration'; - - const safelyDefaultExportDeclaration = (path) => { - const localName = path.value.declarations[0].id.name; - j(path.parent) - .replaceWith(_ => path.value) - .insertAfter( - j.exportDeclaration(true, { type: 'Identifier', name: localName }) - ); - }; - - const build = useArrows => (name, body, typeAnnotation, destructure, hasThisDotProps) => { - const identifier = j.identifier(name); - const propsIdentifier = buildIdentifierWithTypeAnnotation( - 'props', - typeAnnotation - ); - - const propsArg = hasThisDotProps ? [ - (destructure && destructureProps(j(body), typeAnnotation)) || - propsIdentifier - ] : []; - if (useArrows) { - return j.variableDeclaration('const', [ - j.variableDeclarator( - identifier, - j.arrowFunctionExpression(propsArg, body) - ) - ]); - } - return j.functionDeclaration(identifier, propsArg, body); - }; - - const buildPureComponentFunction = build(); - - const buildPureComponentArrowFunction = build(true); - - const buildStatics = (name, properties) => - properties.map(prop => - j.expressionStatement( - j.assignmentExpression( - '=', - j.memberExpression(j.identifier(name), prop.key), - prop.value - ) - ) - ); - - const reportSkipped = path => { - const name = getClassName(path); - const fileName = file.path; - if (!path.value.loc) { - console.warn(`Class "${name}" skipped in ${fileName}`); - return; - } - const { line, column } = path.value.loc.start; - - console.warn(`Class "${name}" skipped in ${fileName} on ${line}:${column}`); - }; - - const f = j(file.source); - - const pureClasses = ReactUtils.findReactES6ClassDeclaration(f).filter( - path => { - const isPure = - onlyHasRenderMethod(path) && - onlyHasSafeClassProperties(path) && - !hasRefs(path); - if (!isPure && !silenceWarnings) { - reportSkipped(path); - } - - return isPure; - } - ); - - if (pureClasses.size() === 0) { - return null; - } - - // Save the names of the deleted pure classes super class - // We need this to prune unused variables at the end. - const parentClassNames = pureClasses.nodes().map(node => node.superClass.name); - - pureClasses.replaceWith(p => { - const name = p.node.id.name; - const renderMethod = p.value.body.body.filter(isRenderMethod)[0]; - const renderBody = renderMethod.value.body; - const propsTypeAnnotation = findPropsTypeAnnotation(p.value.body.body); - const statics = p.value.body.body.filter(isStaticProperty); - const destructure = destructuringEnabled && canDestructure(j(renderMethod)); - - if (destructuringEnabled && !destructure) { - console.warn(`Unable to destructure ${name} props.`); - } - - const hasThisDotProps = j(renderBody).find(j.MemberExpression, THIS_PROPS).length > 0; - replaceThisProps(renderBody); - - if (useArrows) { - return [ - buildPureComponentArrowFunction( - name, - renderBody, - propsTypeAnnotation, - destructure, - hasThisDotProps - ), - ...buildStatics(name, statics) - ]; - } else { - return [ - buildPureComponentFunction( - name, - renderBody, - propsTypeAnnotation, - destructure, - hasThisDotProps - ), - ...buildStatics(name, statics) - ]; - } - }).forEach(p => { - // Check for combining default keyword with const declaration - if (useArrows && isDefaultExport(p)) { - safelyDefaultExportDeclaration(p); - } - }).forEach((p, i) => { - const parentClassName = parentClassNames[i]; - ReactUtils.removeUnusedSuperClassImport(j(p), f, parentClassName); - }); - - return f.toSource(printOptions); -}; diff --git a/codemods/legacy/transforms/pure-render-mixin.js b/codemods/legacy/transforms/pure-render-mixin.js deleted file mode 100644 index 00b2b72..0000000 --- a/codemods/legacy/transforms/pure-render-mixin.js +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -function removePureRenderMixin(file, api, options) { - const j = api.jscodeshift; - - require('./utils/array-polyfills'); - const ReactUtils = require('./utils/ReactUtils')(j); - - const printOptions = options.printOptions || { - quote: 'single', - trailingComma: true - }; - const root = j(file.source); - - const PURE_RENDER_MIXIN = options['mixin-name'] || 'PureRenderMixin'; - const SHOULD_COMPONENT_UPDATE = 'shouldComponentUpdate'; - const NEXT_PROPS = 'nextProps'; - const NEXT_STATE = 'nextState'; - - // --------------------------------------------------------------------------- - // shouldComponentUpdate - const createShouldComponentUpdateFunction = () => - j.functionExpression( - null, - [j.identifier(NEXT_PROPS), j.identifier(NEXT_STATE)], - j.blockStatement([ - j.returnStatement( - j.callExpression( - j.memberExpression( - j.identifier('React'), - j.memberExpression( - j.identifier('addons'), - j.identifier('shallowCompare'), - false - ), - false - ), - [ - j.thisExpression(), - j.identifier(NEXT_PROPS), - j.identifier(NEXT_STATE) - ] - ) - ) - ]) - ); - - const createShouldComponentUpdateProperty = () => - j.property( - 'init', - j.identifier(SHOULD_COMPONENT_UPDATE), - createShouldComponentUpdateFunction() - ); - - const hasShouldComponentUpdate = classPath => - ReactUtils.getReactCreateClassSpec(classPath).properties.every( - property => property.key.name !== SHOULD_COMPONENT_UPDATE - ); - - // --------------------------------------------------------------------------- - // Mixin related code - const isPureRenderMixin = node => - node.type === 'Identifier' && node.name === PURE_RENDER_MIXIN; - - const hasPureRenderMixin = classPath => { - const spec = ReactUtils.getReactCreateClassSpec(classPath); - const mixin = spec && spec.properties.find(ReactUtils.isMixinProperty); - return mixin && mixin.value.elements.some(isPureRenderMixin); - }; - - const removeMixin = elements => - j.property( - 'init', - j.identifier('mixins'), - j.arrayExpression(elements.filter(element => !isPureRenderMixin(element))) - ); - - // --------------------------------------------------------------------------- - // Boom! - const insertShouldComponentUpdate = properties => { - const length = properties.length; - const lastProp = properties[length - 1]; - // I wouldn't dare insert at the bottom if the last function is render - if (lastProp.key.type === 'Identifier' && lastProp.key.name === 'render') { - properties.splice( - length - 1, - 1, - createShouldComponentUpdateProperty(), - lastProp - ); - } else { - properties.push(createShouldComponentUpdateProperty()); - } - return properties; - }; - - const cleanupReactComponent = classPath => { - const spec = ReactUtils.getReactCreateClassSpec(classPath); - const properties = spec.properties - .map(property => { - if (ReactUtils.isMixinProperty(property)) { - const elements = property.value.elements; - return elements.length !== 1 ? removeMixin(elements) : null; - } - return property; - }) - .filter(property => !!property); - - ReactUtils.findReactCreateClassCallExpression(classPath).replaceWith( - ReactUtils.createCreateReactClassCallExpression( - insertShouldComponentUpdate(properties) - ) - ); - }; - - // Remove it if only two or fewer are left: - // var PureRenderMixin = React.addons.PureRenderMixin; - const hasPureRenderIdentifiers = path => - path - .find(j.Identifier, { - name: PURE_RENDER_MIXIN - }) - .size() > 2; - - const deletePureRenderMixin = path => { - if (hasPureRenderIdentifiers(path)) { - return; - } - - const declaration = path - .findVariableDeclarators(PURE_RENDER_MIXIN) - .closest(j.VariableDeclaration); - - if (declaration.size > 1) { - declaration.forEach(p => - j(p).replaceWith( - j.variableDeclaration( - 'var', - p.value.declarations.filter(isPureRenderMixin) - ) - ) - ); - } else { - // Let's assume the variable declaration happens at the top level - const program = declaration.closest(j.Program).get(); - const body = program.value.body; - const index = body.indexOf(declaration.get().value); - if (index !== -1) { - body.splice(index, 1); - } - } - }; - - if (options['explicit-require'] === false || ReactUtils.hasReact(root)) { - const didTransform = - ReactUtils.findReactCreateClass(root) - .filter(hasPureRenderMixin) - .filter(hasShouldComponentUpdate) - .forEach(cleanupReactComponent) - .size() > 0; - - if (didTransform) { - deletePureRenderMixin(root); - return root.toSource(printOptions); - } - } - - return null; -} - -module.exports = removePureRenderMixin; diff --git a/codemods/legacy/transforms/react-to-react-dom.js b/codemods/legacy/transforms/react-to-react-dom.js deleted file mode 100644 index e476adb..0000000 --- a/codemods/legacy/transforms/react-to-react-dom.js +++ /dev/null @@ -1,421 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -var CORE_PROPERTIES = [ - 'Children', - 'Component', - 'createElement', - 'cloneElement', - 'isValidElement', - 'PropTypes', - 'createClass', - 'createFactory', - 'createMixin', - 'DOM', - '__spread' -]; - -var DOM_PROPERTIES = [ - 'findDOMNode', - 'render', - 'unmountComponentAtNode', - 'unstable_batchedUpdates', - 'unstable_renderSubtreeIntoContainer' -]; - -var DOM_SERVER_PROPERTIES = ['renderToString', 'renderToStaticMarkup']; - -function reportError(node, error) { - throw new Error( - `At ${node.loc.start.line}:${node.loc.start.column}: ${error}` - ); -} - -function isRequire(path, moduleName) { - return ( - path.value.type === 'CallExpression' && - path.value.callee.type === 'Identifier' && - path.value.callee.name === 'require' && - path.value.arguments.length === 1 && - path.value.arguments[0].type === 'Literal' && - path.value.arguments[0].value === moduleName - ); -} - -module.exports = function(file, api, options) { - var j = api.jscodeshift; - - var printOptions = options.printOptions || { quote: 'single' }; - var root = j(file.source); - - [ - ['React', 'ReactDOM', 'ReactDOMServer'], - ['react', 'react-dom', 'react-dom/server'] - ].forEach(function(pair) { - var coreModuleName = pair[0]; - var domModuleName = pair[1]; - var domServerModuleName = pair[2]; - - var domAlreadyDeclared = false; - var domServerAlreadyDeclared = false; - - var coreRequireDeclarator; - var coreImportDeclaration; - var coreName = null; - root - .find(j.CallExpression) - .filter(p => isRequire(p, coreModuleName)) - .forEach(p => { - var name, scope; - if (p.parent.value.type === 'VariableDeclarator') { - if (p.parent.value.id.type === 'ObjectPattern') { - var pattern = p.parent.value.id; - var all = pattern.properties.every(function(prop) { - if (prop.key.type === 'Identifier') { - name = prop.key.name; - return CORE_PROPERTIES.indexOf(name) !== -1; - } - return false; - }); - if (all) { - // var {PropTypes} = require('React'); so leave alone - return; - } - } - if (coreRequireDeclarator) { - reportError(p.value, 'Multiple declarations of React'); - } - if (p.parent.value.id.type !== 'Identifier') { - reportError( - p.value, - 'Unexpected destructuring in require of ' + coreModuleName - ); - } - name = p.parent.value.id.name; - scope = p.scope.lookup(name); - if (scope.declares('ReactDOM')) { - console.log('Using existing ReactDOM var in ' + file.path); - domAlreadyDeclared = true; - } - if (scope.declares('ReactDOMServer')) { - console.log('Using existing ReactDOMServer var in ' + file.path); - domServerAlreadyDeclared = true; - } - coreRequireDeclarator = p.parent; - coreName = coreRequireDeclarator.value.id.name; - } else if (p.parent.value.type === 'AssignmentExpression') { - if (p.parent.value.left.type !== 'Identifier') { - reportError( - p.value, - 'Unexpected destructuring in require of ' + coreModuleName - ); - } - name = p.parent.value.left.name; - scope = p.scope.lookup(name); - var reactBindings = scope.getBindings()[name]; - if (reactBindings.length !== 1) { - throw new Error( - 'Unexpected number of bindings: ' + reactBindings.length - ); - } - coreRequireDeclarator = reactBindings[0].parent; - coreName = coreRequireDeclarator.value.id.name; - if ( - coreRequireDeclarator.value.init && - !isRequire(coreRequireDeclarator.get('init'), coreModuleName) - ) { - reportError( - coreRequireDeclarator.value, - 'Unexpected initialization of ' + coreModuleName - ); - } - if (scope.declares('ReactDOM')) { - console.log('Using existing ReactDOM var in ' + file.path); - domAlreadyDeclared = true; - } - if (scope.declares('ReactDOMServer')) { - console.log('Using existing ReactDOMServer var in ' + file.path); - domServerAlreadyDeclared = true; - } - } - }); - root - .find(j.ImportDeclaration, { source: { value: coreModuleName } }) - .forEach(p => { - if (coreImportDeclaration) { - reportError(p.value, 'Multiple declarations of React'); - } - coreImportDeclaration = p; - var defaultSpecifier = p.value.specifiers.find( - sp => sp.type === j.ImportDefaultSpecifier.name - ); - if (defaultSpecifier) { - var name = defaultSpecifier.local.name; - var scope = p.scope.lookup(name); - if (scope.declares('ReactDOM')) { - console.log('Using existing ReactDOM var in ' + file.path); - domAlreadyDeclared = true; - } - if (scope.declares('ReactDOMServer')) { - console.log('Using existing ReactDOMServer var in ' + file.path); - domServerAlreadyDeclared = true; - } - coreName = j(coreImportDeclaration) - .find(j.ImportDefaultSpecifier) - .get().value.local.name; - } - }); - - if (!coreRequireDeclarator && !coreImportDeclaration) { - return; - } - - if ( - !domAlreadyDeclared && - root.find(j.Identifier, { name: 'ReactDOM' }).size() > 0 - ) { - throw new Error( - 'ReactDOM is already defined in a different scope than React' - ); - } - if ( - !domServerAlreadyDeclared && - root.find(j.Identifier, { name: 'ReactDOMServer' }).size() > 0 - ) { - throw new Error( - 'ReactDOMServer is already defined in a different scope than React' - ); - } - - var processed = new Set(); - var requireAssignments = []; - var coreUses = 0; - var domUses = 0; - var domServerUses = 0; - - root.find(j.Identifier, { name: coreName }).forEach(p => { - if (processed.has(p.value)) { - // https://github.com/facebook/jscodeshift/issues/36 - return; - } - processed.add(p.value); - if ( - p.parent.value.type === 'MemberExpression' || - p.parent.value.type === 'QualifiedTypeIdentifier' - ) { - var left; - var right; - if (p.parent.value.type === 'MemberExpression') { - left = p.parent.value.object; - right = p.parent.value.property; - } else { - left = p.parent.value.qualification; - right = p.parent.value.id; - } - if (left === p.value) { - // React.foo (or React[foo]) - if (right.type === 'Identifier') { - var name = right.name; - if (CORE_PROPERTIES.indexOf(name) !== -1) { - coreUses++; - } else if (DOM_PROPERTIES.indexOf(name) !== -1) { - domUses++; - j(p).replaceWith(j.identifier('ReactDOM')); - } else if (DOM_SERVER_PROPERTIES.indexOf(name) !== -1) { - domServerUses++; - j(p).replaceWith(j.identifier('ReactDOMServer')); - } else { - throw new Error('Unknown property React.' + name); - } - } - } else if (right === p.value) { - // foo.React, no need to transform - } else { - throw new Error('unimplemented'); - } - } else if (p.parent.value.type === 'VariableDeclarator') { - if (p.parent.value.id === p.value) { - // var React = ...; - } else if (p.parent.value.init === p.value) { - // var ... = React; - var pattern = p.parent.value.id; - if (pattern.type === 'ObjectPattern') { - // var {PropTypes} = React; - // Most of these cases will just be looking at {PropTypes} so this - // is usually a no-op. - var coreProperties = []; - var domProperties = []; - pattern.properties.forEach(function(prop) { - if (prop.key.type === 'Identifier') { - var key = prop.key.name; - if (CORE_PROPERTIES.indexOf(key) !== -1) { - coreProperties.push(prop); - } else if (DOM_PROPERTIES.indexOf(key) !== -1) { - domProperties.push(prop); - } else { - throw new Error( - 'Unknown property React.' + key + ' while destructuring' - ); - } - } else { - throw new Error('unimplemented'); - } - }); - var domDeclarator = j.variableDeclarator( - j.objectPattern(domProperties), - j.identifier('ReactDOM') - ); - if (coreProperties.length && !domProperties.length) { - // nothing to do - coreUses++; - } else if (domProperties.length && !coreProperties.length) { - domUses++; - j(p.parent).replaceWith(domDeclarator); - } else { - coreUses++; - domUses++; - var decl = j(p).closest(j.VariableDeclaration); - decl.insertAfter( - j.variableDeclaration(decl.get().value.kind, [domDeclarator]) - ); - } - } else { - throw new Error('unimplemented'); - } - } else { - throw new Error('unimplemented'); - } - } else if (p.parent.value.type === 'AssignmentExpression') { - if (p.parent.value.left === p.value) { - if (isRequire(p.parent.get('right'), coreModuleName)) { - requireAssignments.push(p.parent); - } else { - reportError( - p.parent.value, - 'Unexpected assignment to ' + coreModuleName - ); - } - } else { - throw new Error('unimplemented'); - } - } else if (p.parent.value.type === 'ImportDefaultSpecifier') { - // import React from "react"; - } else { - reportError(p.value, 'unimplemented ' + p.parent.value.type); - } - }); - - coreUses += root.find(j.JSXElement).size(); - - function insertRequire(name, path) { - var req = j.callExpression(j.identifier('require'), [j.literal(path)]); - requireAssignments.forEach(function(requireAssignment) { - requireAssignment.parent.insertAfter( - j.expressionStatement( - j.assignmentExpression('=', j.identifier(name), req) - ) - ); - }); - coreRequireDeclarator.parent.insertAfter( - j.variableDeclaration(coreRequireDeclarator.parent.value.kind, [ - j.variableDeclarator( - j.identifier(name), - coreRequireDeclarator.value.init ? req : null - ) - ]) - ); - } - - if (coreRequireDeclarator) { - if (domServerUses > 0 && !domServerAlreadyDeclared) { - insertRequire('ReactDOMServer', domServerModuleName); - } - if (domUses > 0 && !domAlreadyDeclared) { - insertRequire('ReactDOM', domModuleName); - } - if ((domUses > 0 || domServerUses > 0) && coreUses === 0) { - j(coreRequireDeclarator).remove(); - requireAssignments.forEach(r => j(r).remove()); - } - } else { - function findImportPath(name, path) { - return root - .find(j.ImportDeclaration, { source: { value: path } }) - .filter(p => - p.value.specifiers.find( - sp => - sp.type === 'ImportDefaultSpecifier' && sp.local.name === name - ) - ); - } - - function emitImport(name, path, knownProperties, uses) { - const usedProperties = coreImportDeclaration.value.specifiers - .filter(sp => sp.type === j.ImportSpecifier.name) - .filter( - sp => - sp.imported.type === 'Identifier' && - knownProperties.indexOf(sp.imported.name) !== -1 - ) - .map(sp => sp.imported.name); - - const importDeclaration = findImportPath(name, path); - const specifiers = []; - - // if ReactDOM needs to be in scope and it's not declared, or we're going to - // replace its declaration to add import specifiers... - if (uses > 0 && (!domAlreadyDeclared || importDeclaration.length > 0)) { - specifiers.push(j.importDefaultSpecifier(j.identifier(name))); - } - if (usedProperties.length > 0) { - j(coreImportDeclaration) - .find(j.ImportSpecifier) - .filter(p => usedProperties.indexOf(p.value.local.name) !== -1) - .remove(); - specifiers.push( - ...usedProperties.map(prop => j.importSpecifier(j.identifier(prop))) - ); - } - if (specifiers.length > 0) { - if (importDeclaration.length > 0) { - importDeclaration.replaceWith( - j.importDeclaration(specifiers, j.literal(path)) - ); - } else { - coreImportDeclaration.insertAfter( - j.importDeclaration(specifiers, j.literal(path)) - ); - } - } - } - - emitImport('ReactDOM', domModuleName, DOM_PROPERTIES, domUses); - emitImport( - 'ReactDOMServer', - domServerModuleName, - DOM_SERVER_PROPERTIES, - domServerUses - ); - - const coreImportSpecifiers = j(coreImportDeclaration).find( - j.ImportSpecifier - ); - if (coreImportSpecifiers.length === 0 && coreUses === 0) { - j(coreImportDeclaration).remove(); - } else if (coreImportSpecifiers.length > 0 && coreUses === 0) { - j(coreImportDeclaration) - .find(j.ImportDefaultSpecifier) - .remove(); - } - } - }); - - return root.toSource(printOptions); -}; diff --git a/codemods/legacy/transforms/remove-context-provider.ts b/codemods/legacy/transforms/remove-context-provider.ts deleted file mode 100644 index 4aadfbe..0000000 --- a/codemods/legacy/transforms/remove-context-provider.ts +++ /dev/null @@ -1,40 +0,0 @@ -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); - - let isDirty = false; - - 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; - isDirty = true; - } - }); - }); - - return isDirty ? root.toSource() : undefined; -} diff --git a/codemods/legacy/transforms/remove-forward-ref.ts b/codemods/legacy/transforms/remove-forward-ref.ts deleted file mode 100644 index da7ad9d..0000000 --- a/codemods/legacy/transforms/remove-forward-ref.ts +++ /dev/null @@ -1,297 +0,0 @@ -import type { - API, - ArrowFunctionExpression, - CallExpression, - FileInfo, - FunctionExpression, - Identifier, - JSCodeshift, - TSTypeLiteral, - TSTypeReference, -} from 'jscodeshift'; - -// Props & { ref: React.RefObject} -const buildPropsAndRefIntersectionTypeAnnotation = ( - j: JSCodeshift, - propType: TSTypeReference | TSTypeLiteral, - refType: TSTypeReference | TSTypeLiteral | null, -) => - 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 === null ? j.tsUnknownKeyword() : refType, - ]), - }), - ), - }), - ]), - ]), - ); - -// { ref: refName, ...propsName } -const buildRefAndPropsObjectPattern = ( - j: JSCodeshift, - refArgName: string, - propArgName: string, -) => - j.objectPattern([ - j.objectProperty.from({ - shorthand: true, - key: j.identifier('ref'), - value: j.identifier(refArgName), - }), - j.restProperty(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; -}; - -const getForwardRefRenderFunction = ( - j: JSCodeshift, - callExpression: CallExpression, -): FunctionExpression | ArrowFunctionExpression | null => { - const [renderFunction] = callExpression.arguments; - - if ( - !j.FunctionExpression.check(renderFunction) && - !j.ArrowFunctionExpression.check(renderFunction) - ) { - return null; - } - - return renderFunction; -}; - -const isLiteralOrReference = ( - j: JSCodeshift, - type: unknown, -): type is TSTypeReference | TSTypeLiteral => { - return j.TSTypeReference.check(type) || j.TSTypeLiteral.check(type); -}; - -export default function transform(file: FileInfo, api: API) { - const j = api.jscodeshift; - - const root = j(file.source); - - let isDirty = false; - - let reactForwardRefImportLocalName: string | null = null; - let reactDefaultImportName: string | null = null; - - root - .find(j.ImportDeclaration, { - source: { value: 'react' }, - }) - .forEach((path) => { - path.value.specifiers?.forEach((specifier) => { - // named import - if ( - j.ImportSpecifier.check(specifier) && - specifier.imported.name === 'forwardRef' - ) { - reactForwardRefImportLocalName = specifier.local?.name ?? null; - } - - // default and wildcard import - if ( - j.ImportDefaultSpecifier.check(specifier) || - j.ImportNamespaceSpecifier.check(specifier) - ) { - reactDefaultImportName = specifier.local?.name ?? null; - } - }); - }); - - root - .find(j.CallExpression) - .filter((path) => { - const { callee } = path.value; - - if ( - j.Identifier.check(callee) && - callee.name === reactForwardRefImportLocalName - ) { - return true; - } - - if ( - j.MemberExpression.check(callee) && - j.Identifier.check(callee.object) && - callee.object.name === reactDefaultImportName && - j.Identifier.check(callee.property) && - callee.property.name === 'forwardRef' - ) { - return true; - } - - return false; - }) - .replaceWith((callExpressionPath) => { - const originalCallExpression = callExpressionPath.value; - - const renderFunction = getForwardRefRenderFunction( - j, - callExpressionPath.node, - ); - - if (renderFunction === null) { - console.warn('Could not detect render function.'); - - return originalCallExpression; - } - - const [propsArg, refArg] = renderFunction.params; - - if ( - !j.Identifier.check(refArg) || - !(j.Identifier.check(propsArg) || j.ObjectPattern.check(propsArg)) - ) { - console.warn('Could not detect ref or props arguments.'); - - return originalCallExpression; - } - - const refArgTypeReference = getRefTypeFromRefArg(j, refArg); - const refArgName = refArg.name; - - const propsArgTypeReference = propsArg.typeAnnotation?.typeAnnotation; - // remove refArg - renderFunction.params.splice(1, 1); - - // if propsArg is ObjectPattern, add ref as new ObjectProperty - if (j.ObjectPattern.check(propsArg)) { - propsArg.properties.unshift( - j.objectProperty.from({ - shorthand: true, - key: j.identifier('ref'), - value: j.identifier(refArgName), - }), - ); - - isDirty = true; - } - - // if props arg is Identifier, push ref variable declaration to the function body - if (j.Identifier.check(propsArg)) { - renderFunction.params[0] = buildRefAndPropsObjectPattern( - j, - refArg.name, - propsArg.name, - ); - - isDirty = true; - } - - /** - * Transform ts types: render function props and ref are typed - */ - - if ( - isLiteralOrReference(j, propsArgTypeReference) && - renderFunction.params?.[0] && - 'typeAnnotation' in renderFunction.params[0] - ) { - renderFunction.params[0].typeAnnotation = - buildPropsAndRefIntersectionTypeAnnotation( - j, - propsArgTypeReference, - refArgTypeReference, - ); - isDirty = true; - } - - /** - * Transform ts types: forwardRef type arguments are used - */ - - 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) && - renderFunction.params?.[0] && - 'typeAnnotation' in renderFunction.params[0] - ) { - const [refType, propType] = typeParameters.params; - - if ( - j.TSTypeReference.check(refType) && - isLiteralOrReference(j, propType) - ) { - renderFunction.params[0].typeAnnotation = - buildPropsAndRefIntersectionTypeAnnotation(j, propType, refType); - - isDirty = true; - } - } - - return renderFunction; - }); - - /** - * handle import - */ - if (isDirty) { - root - .find(j.ImportDeclaration, { - source: { - value: 'react', - }, - }) - .forEach((importDeclarationPath) => { - const { specifiers, importKind } = importDeclarationPath.node; - - if (importKind !== 'value') { - return; - } - - const specifiersWithoutForwardRef = - specifiers?.filter( - (s) => - !j.ImportSpecifier.check(s) || s.imported.name !== 'forwardRef', - ) ?? []; - - if (specifiersWithoutForwardRef.length === 0) { - j(importDeclarationPath).remove(); - } - - importDeclarationPath.node.specifiers = specifiersWithoutForwardRef; - }); - } - - return isDirty ? root.toSource() : undefined; -} diff --git a/codemods/legacy/transforms/rename-unsafe-lifecycles.js b/codemods/legacy/transforms/rename-unsafe-lifecycles.js deleted file mode 100644 index 09bc78b..0000000 --- a/codemods/legacy/transforms/rename-unsafe-lifecycles.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -const DEPRECATED_APIS = Object.create(null); -DEPRECATED_APIS.componentWillMount = 'UNSAFE_componentWillMount'; -DEPRECATED_APIS.componentWillReceiveProps = 'UNSAFE_componentWillReceiveProps'; -DEPRECATED_APIS.componentWillUpdate = 'UNSAFE_componentWillUpdate'; - -export default (file, api, options) => { - const j = api.jscodeshift; - - const printOptions = options.printOptions || { - quote: 'single', - trailingComma: true - }; - - const root = j(file.source); - - let hasModifications = false; - - const renameDeprecatedApis = path => { - const name = path.node.key.name; - - if (DEPRECATED_APIS[name]) { - path.value.key.name = DEPRECATED_APIS[name]; - hasModifications = true; - } - }; - - const renameDeprecatedCallExpressions = path => { - const name = path.node.property.name; - - if (DEPRECATED_APIS[name]) { - path.node.property.name = DEPRECATED_APIS[name]; - hasModifications = true; - } - }; - - // Class methods - root.find(j.MethodDefinition).forEach(renameDeprecatedApis); - - // Class methods - typescript - root.find(j.ClassMethod).forEach(renameDeprecatedApis); - - // Arrow functions - root.find(j.ClassProperty).forEach(renameDeprecatedApis); - - // createReactClass and mixins - root.find(j.Property).forEach(renameDeprecatedApis); - - // Function calls - root.find(j.MemberExpression).forEach(renameDeprecatedCallExpressions); - - return hasModifications ? root.toSource(printOptions) : null; -}; diff --git a/codemods/legacy/transforms/sort-comp.js b/codemods/legacy/transforms/sort-comp.js deleted file mode 100644 index 6e8adf6..0000000 --- a/codemods/legacy/transforms/sort-comp.js +++ /dev/null @@ -1,239 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -/** - * Reorders React component methods to match the [ESLint](http://eslint.org/) - * [react/sort-comp rule](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md), - * specifically with the [tighter constraints of the Airbnb style guide] - * (https://github.com/airbnb/javascript/blob/7684892951ef663e1c4e62ad57d662e9b2748b9e/\ - * packages/eslint-config-airbnb/rules/react.js#L122-L134), - * - * 'react/sort-comp': [2, { - * 'order': [ - * 'static-methods', - * 'lifecycle', - * '/^on.+$/', - * '/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/', - * 'everything-else', - * '/^render.+$/', - * 'render' - * ] - * }], - */ - -module.exports = function(fileInfo, api, options) { - const j = api.jscodeshift; - - const ReactUtils = require('./utils/ReactUtils')(j); - - const printOptions = options.printOptions || { - quote: 'single', - trailingComma: true - }; - - const methodsOrder = getMethodsOrder(fileInfo, options); // eslint-disable-line no-use-before-define - - const root = j(fileInfo.source); - - const propertyComparator = (a, b) => { - const nameA = a.key.name; - const nameB = b.key.name; - - const indexA = getCorrectIndex(methodsOrder, a); // eslint-disable-line no-use-before-define - const indexB = getCorrectIndex(methodsOrder, b); // eslint-disable-line no-use-before-define - - const sameLocation = indexA === indexB; - - if (sameLocation) { - // compare lexically - return +(nameA > nameB) || +(nameA === nameB) - 1; - } else { - // compare by index - return indexA - indexB; - } - }; - - const sortComponentProperties = classPath => { - const spec = ReactUtils.getReactCreateClassSpec(classPath); - - if (spec) { - spec.properties.sort(propertyComparator); - } - }; - - const sortClassProperties = classPath => { - const spec = ReactUtils.getClassExtendReactSpec(classPath); - - if (spec) { - spec.body.sort(propertyComparator); - } - }; - - if (options['explicit-require'] === false || ReactUtils.hasReact(root)) { - const createClassSortCandidates = ReactUtils.findReactCreateClass(root); - const es6ClassSortCandidates = ReactUtils.findReactES6ClassDeclaration( - root - ); - - if (createClassSortCandidates.size() > 0) { - createClassSortCandidates.forEach(sortComponentProperties); - } - - if (es6ClassSortCandidates.size() > 0) { - es6ClassSortCandidates.forEach(sortClassProperties); - } - - if ( - createClassSortCandidates.size() > 0 || - es6ClassSortCandidates.size() > 0 - ) { - return root.toSource(printOptions); - } - } - - return null; -}; - -// Hard-coded for Airbnb style -const defaultMethodsOrder = [ - 'static-methods', - 'displayName', - 'propTypes', - 'contextTypes', - 'childContextTypes', - 'mixins', - 'statics', - 'defaultProps', - 'constructor', - 'getDefaultProps', - 'state', - 'getInitialState', - 'getChildContext', - 'getDerivedStateFromProps', - 'componentWillMount', - 'UNSAFE_componentWillMount', - 'componentDidMount', - 'componentWillReceiveProps', - 'UNSAFE_componentWillReceiveProps', - 'shouldComponentUpdate', - 'componentWillUpdate', - 'UNSAFE_componentWillUpdate', - 'getSnapshotBeforeUpdate', - 'componentDidUpdate', - 'componentDidCatch', - 'componentWillUnmount', - '/^on.+$/', - '/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/', - 'everything-else', - '/^render.+$/', - 'render' -]; - -// FROM https://github.com/yannickcr/eslint-plugin-react/blob/master/lib/rules/sort-comp.js -const regExpRegExp = /\/(.*)\/([g|y|i|m]*)/; - -function selectorMatches(selector, method) { - const methodName = method.key.name; - - if ( - method.static && - selector === 'static-methods' && - defaultMethodsOrder.indexOf(methodName) === -1 - ) { - return true; - } - - if ( - !method.value && - method.typeAnnotation && - selector === 'type-annotations' - ) { - return true; - } - - if (method.static && selector === 'static-methods') { - return true; - } - - if (selector === methodName) { - return true; - } - - const selectorIsRe = regExpRegExp.test(selector); - - if (selectorIsRe) { - const match = selector.match(regExpRegExp); - const selectorRe = new RegExp(match[1], match[2]); - return selectorRe.test(methodName); - } - - return false; -} - -/** - * Get index of the matching patterns in methods order configuration - * @param {Object} method - * @returns {Number} Index of the method in the method ordering. Return [Infinity] if there is no match. - */ -function getCorrectIndex(methodsOrder, method) { - const everythingElseIndex = methodsOrder.indexOf('everything-else'); - - for (let i = 0; i < methodsOrder.length; i++) { - if (i != everythingElseIndex && selectorMatches(methodsOrder[i], method)) { - return i; - } - } - - if (everythingElseIndex >= 0) { - return everythingElseIndex; - } else { - return Infinity; - } -} - -function getMethodsOrderFromEslint(filePath) { - const CLIEngine = require('eslint').CLIEngine; - const cli = new CLIEngine({ useEslintrc: true }); - try { - const config = cli.getConfigForFile(filePath); - const { rules } = config; - const sortCompRules = rules['react/sort-comp']; - const ruleConfig = sortCompRules && sortCompRules[1]; - if (!ruleConfig) { - return null; - } - - const order = ruleConfig.order; - const groups = ruleConfig.groups || {}; - - let resolvedOrder = []; - for (let i = 0; i < order.length; i++) { - const entry = order[i]; - if (groups[entry]) { - resolvedOrder = resolvedOrder.concat(groups[entry]); - } else { - resolvedOrder.push(entry); - } - } - - return resolvedOrder; - } catch (e) { - // unable to get config for file - } - return null; -} - -function getMethodsOrder(fileInfo, options) { - return ( - options.methodsOrder || - getMethodsOrderFromEslint(fileInfo.path) || - defaultMethodsOrder - ); -} diff --git a/codemods/legacy/transforms/update-react-imports.js b/codemods/legacy/transforms/update-react-imports.js deleted file mode 100644 index 2871541..0000000 --- a/codemods/legacy/transforms/update-react-imports.js +++ /dev/null @@ -1,380 +0,0 @@ -/** - * (c) Facebook, Inc. and its affiliates. Confidential and proprietary. - * - * @format - */ - -'use strict'; - -module.exports = function(file, api, options) { - const j = api.jscodeshift; - const printOptions = options.printOptions || {}; - const root = j(file.source); - const destructureNamespaceImports = options.destructureNamespaceImports; - - // https://github.com/facebook/jscodeshift/blob/master/recipes/retain-first-comment.md - function getFirstNode() { - return root.find(j.Program).get('body', 0).node; - } - - // Save the comments attached to the first node - const firstNode = getFirstNode(); - const { comments } = firstNode; - - function isVariableDeclared(variable) { - return ( - root - .find(j.Identifier, { - name: variable, - }) - .filter( - path => - path.parent.value.type !== 'MemberExpression' && - path.parent.value.type !== 'QualifiedTypeIdentifier' && - path.parent.value.type !== 'JSXMemberExpression', - ) - .size() > 0 - ); - } - - // Get all paths that import from React - const reactImportPaths = root - .find(j.ImportDeclaration, { - type: 'ImportDeclaration', - }) - .filter(path => { - return ( - ( - path.value.source.type === 'Literal' || - path.value.source.type === 'StringLiteral' - ) && ( - path.value.source.value === 'React' || - path.value.source.value === 'react' - ) - ); - }); - - // get all namespace/default React imports - const reactPaths = reactImportPaths.filter(path => { - return ( - path.value.specifiers.length > 0 && - path.value.importKind === 'value' && - path.value.specifiers.some(specifier => specifier.local.name === 'React') - ); - }); - - if (reactPaths.size() > 1) { - throw Error( - 'There should only be one React import. Please remove the duplicate import and try again.', - ); - } - - if (reactPaths.size() === 0) { - return null; - } - - const reactPath = reactPaths.paths()[0]; - // Reuse the node so that we can preserve quoting style. - const reactLiteral = reactPath.value.source; - - const isDefaultImport = reactPath.value.specifiers.some( - specifier => - specifier.type === 'ImportDefaultSpecifier' && - specifier.local.name === 'React', - ); - - // Check to see if we should keep the React import - const isReactImportUsed = - root - .find(j.Identifier, { - name: 'React', - }) - .filter(path => { - return path.parent.parent.value.type !== 'ImportDeclaration'; - }) - .size() > 0; - - // local: imported - const reactIdentifiers = {}; - const reactTypeIdentifiers = {}; - let canDestructureReactVariable = false; - if (isReactImportUsed && (isDefaultImport || destructureNamespaceImports)) { - // Checks to see if the react variable is used itself (rather than used to access its properties) - canDestructureReactVariable = - root - .find(j.Identifier, { - name: 'React', - }) - .filter(path => { - return path.parent.parent.value.type !== 'ImportDeclaration'; - }) - .filter( - path => - !( - path.parent.value.type === 'MemberExpression' && - path.parent.value.object.name === 'React' - ) && - !( - path.parent.value.type === 'QualifiedTypeIdentifier' && - path.parent.value.qualification.name === 'React' - ) && - !( - path.parent.value.type === 'JSXMemberExpression' && - path.parent.value.object.name === 'React' - ), - ) - .size() === 0; - - if (canDestructureReactVariable) { - // Add React identifiers to separate object so we can destructure the imports - // later if we can. If a type variable that we are trying to import has already - // been declared, do not try to destructure imports - // (ex. Element is declared and we are using React.Element) - root - .find(j.QualifiedTypeIdentifier, { - qualification: { - type: 'Identifier', - name: 'React', - }, - }) - .forEach(path => { - const id = path.value.id.name; - if (path.parent.parent.value.type === 'TypeofTypeAnnotation') { - // This is a typeof import so it isn't actually a type - reactIdentifiers[id] = id; - - if (reactTypeIdentifiers[id]) { - canDestructureReactVariable = false; - } - } else { - reactTypeIdentifiers[id] = id; - - if (reactIdentifiers[id]) { - canDestructureReactVariable = false; - } - } - - if (isVariableDeclared(id)) { - canDestructureReactVariable = false; - } - }); - - // Add React identifiers to separate object so we can destructure the imports - // later if we can. If a variable that we are trying to import has already - // been declared, do not try to destructure imports - // (ex. createElement is declared and we are using React.createElement) - root - .find(j.MemberExpression, { - object: { - type: 'Identifier', - name: 'React', - }, - }) - .forEach(path => { - const property = path.value.property.name; - reactIdentifiers[property] = property; - - if (isVariableDeclared(property) || reactTypeIdentifiers[property]) { - canDestructureReactVariable = false; - } - }); - - // Add React identifiers to separate object so we can destructure the imports - // later if we can. If a JSX variable that we are trying to import has already - // been declared, do not try to destructure imports - // (ex. Fragment is declared and we are using React.Fragment) - root - .find(j.JSXMemberExpression, { - object: { - type: 'JSXIdentifier', - name: 'React', - }, - }) - .forEach(path => { - const property = path.value.property.name; - reactIdentifiers[property] = property; - - if (isVariableDeclared(property) || reactTypeIdentifiers[property]) { - canDestructureReactVariable = false; - } - }); - } - } - - if (canDestructureReactVariable) { - // replace react identifiers - root - .find(j.QualifiedTypeIdentifier, { - qualification: { - type: 'Identifier', - name: 'React', - }, - }) - .forEach(path => { - const id = path.value.id.name; - - j(path).replaceWith(j.identifier(id)); - }); - - root - .find(j.MemberExpression, { - object: { - type: 'Identifier', - name: 'React', - }, - }) - .forEach(path => { - const property = path.value.property.name; - - j(path).replaceWith(j.identifier(property)); - }); - - root - .find(j.JSXMemberExpression, { - object: { - type: 'JSXIdentifier', - name: 'React', - }, - }) - .forEach(path => { - const property = path.value.property.name; - - j(path).replaceWith(j.jsxIdentifier(property)); - }); - - // Add exisiting React imports to map - reactImportPaths.forEach(path => { - const specifiers = path.value.specifiers; - for (let i = 0; i < specifiers.length; i++) { - const specifier = specifiers[i]; - // get all type and regular imports that are imported - // from React - if (specifier.type === 'ImportSpecifier') { - if ( - path.value.importKind === 'type' || - specifier.importKind === 'type' - ) { - reactTypeIdentifiers[specifier.local.name] = - specifier.imported.name; - } else { - reactIdentifiers[specifier.local.name] = specifier.imported.name; - } - } - } - }); - - const regularImports = []; - Object.keys(reactIdentifiers).forEach(local => { - const imported = reactIdentifiers[local]; - regularImports.push( - j.importSpecifier(j.identifier(imported), j.identifier(local)), - ); - }); - - const typeImports = []; - Object.keys(reactTypeIdentifiers).forEach(local => { - const imported = reactTypeIdentifiers[local]; - typeImports.push( - j.importSpecifier(j.identifier(imported), j.identifier(local)), - ); - }); - - if (regularImports.length > 0) { - j(reactPath).insertAfter( - j.importDeclaration(regularImports, reactLiteral), - ); - } - if (typeImports.length > 0) { - j(reactPath).insertAfter( - j.importDeclaration(typeImports, reactLiteral, 'type'), - ); - } - - // remove all old react imports - reactImportPaths.forEach(path => { - // This is for import type React from 'react' which shouldn't - // be removed - if ( - path.value.specifiers.some( - specifier => - specifier.type === 'ImportDefaultSpecifier' && - specifier.local.name === 'React' && - (specifier.importKind === 'type' || - path.value.importKind === 'type'), - ) - ) { - j(path).insertAfter( - j.importDeclaration( - [j.importDefaultSpecifier(j.identifier('React'))], - reactLiteral, - 'type', - ), - ); - } - j(path).remove(); - }); - } else { - // Remove the import because it's not being used - // If we should keep the React import, just convert - // default imports to named imports - let isImportRemoved = false; - const specifiers = reactPath.value.specifiers; - for (let i = 0; i < specifiers.length; i++) { - const specifier = specifiers[i]; - if (specifier.type === 'ImportNamespaceSpecifier') { - if (!isReactImportUsed) { - isImportRemoved = true; - j(reactPath).remove(); - } - } else if (specifier.type === 'ImportDefaultSpecifier') { - if (isReactImportUsed) { - j(reactPath).insertAfter( - j.importDeclaration( - [j.importNamespaceSpecifier(j.identifier('React'))], - reactLiteral, - ), - ); - } - - if (specifiers.length > 1) { - const typeImports = []; - const regularImports = []; - for (let x = 0; x < specifiers.length; x++) { - if (specifiers[x].type !== 'ImportDefaultSpecifier') { - if (specifiers[x].importKind === 'type') { - typeImports.push(specifiers[x]); - } else { - regularImports.push(specifiers[x]); - } - } - } - if (regularImports.length > 0) { - j(reactPath).insertAfter( - j.importDeclaration(regularImports, reactLiteral), - ); - } - if (typeImports.length > 0) { - j(reactPath).insertAfter( - j.importDeclaration(typeImports, reactLiteral, 'type'), - ); - } - } - - isImportRemoved = true; - j(reactPath).remove(); - } - } - - if (!isImportRemoved) { - return null; - } - } - - // If the first node has been modified or deleted, reattach the comments - const firstNode2 = getFirstNode(); - if (firstNode2 !== firstNode) { - firstNode2.comments = comments; - } - - return root.toSource(printOptions); -}; diff --git a/codemods/legacy/transforms/utils/ReactUtils.js b/codemods/legacy/transforms/utils/ReactUtils.js deleted file mode 100644 index 2a6a3b1..0000000 --- a/codemods/legacy/transforms/utils/ReactUtils.js +++ /dev/null @@ -1,308 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -module.exports = function(j) { - const REACT_CREATE_CLASS_MEMBER_EXPRESSION = { - type: 'MemberExpression', - object: { - name: 'React', - }, - property: { - name: 'createClass', - }, - }; - - // --------------------------------------------------------------------------- - // Checks if the file requires a certain module - const hasModule = (path, module) => - path - .findVariableDeclarators() - .filter(j.filters.VariableDeclarator.requiresModule(module)) - .size() === 1 || - path - .find(j.ImportDeclaration, { - type: 'ImportDeclaration', - source: { - type: 'Literal', - }, - }) - .filter(declarator => declarator.value.source.value === module) - .size() === 1; - - const hasReact = path => ( - hasModule(path, 'React') || - hasModule(path, 'react') || - hasModule(path, 'react/addons') || - hasModule(path, 'react-native') - ); - - // --------------------------------------------------------------------------- - // Finds all variable declarations that call React.createClass - const findReactCreateClassCallExpression = path => - j(path).find(j.CallExpression, { - callee: REACT_CREATE_CLASS_MEMBER_EXPRESSION, - }); - - const findReactCreateClass = path => - path - .findVariableDeclarators() - .filter(decl => findReactCreateClassCallExpression(decl).size() > 0); - - const findReactCreateClassExportDefault = path => - path.find(j.ExportDeclaration, { - default: true, - declaration: { - type: 'CallExpression', - callee: REACT_CREATE_CLASS_MEMBER_EXPRESSION, - }, - }); - - const findReactCreateClassModuleExports = path => - path - .find(j.AssignmentExpression, { - left: { - type: 'MemberExpression', - object: { - type: 'Identifier', - name: 'module', - }, - property: { - type: 'Identifier', - name: 'exports', - }, - }, - right: { - type: 'CallExpression', - callee: REACT_CREATE_CLASS_MEMBER_EXPRESSION, - }, - }); - - const getReactCreateClassSpec = classPath => { - const {value} = classPath; - const args = (value.init || value.right || value.declaration).arguments; - if (args && args.length) { - const spec = args[0]; - if (spec.type === 'ObjectExpression' && Array.isArray(spec.properties)) { - return spec; - } - } - return null; - }; - - // --------------------------------------------------------------------------- - // Finds alias for React.Component if used as named import. - const findReactComponentNameByParent = (path, parentClassName) => { - const reactImportDeclaration = path - .find(j.ImportDeclaration, { - type: 'ImportDeclaration', - source: { - type: 'Literal', - }, - }) - .filter(importDeclaration => hasReact(path)); - - const componentImportSpecifier = reactImportDeclaration - .find(j.ImportSpecifier, { - type: 'ImportSpecifier', - imported: { - type: 'Identifier', - name: parentClassName, - }, - }).at(0); - - const paths = componentImportSpecifier.paths(); - return paths.length - ? paths[0].value.local.name - : undefined; - }; - - const removeUnusedSuperClassImport = (path, file, superClassName) => { - if (path.find(j.Identifier, { - type: 'Identifier', - name: superClassName - }).length === 0) { - file.find(j.ImportSpecifier, { - type: 'ImportSpecifier', - imported: { - type: 'Identifier', - name: superClassName, - } - }).remove(); - } - }; - - const findReactES6ClassDeclarationByParent = (path, parentClassName) => { - const componentImport = findReactComponentNameByParent(path, parentClassName); - - const selector = componentImport - ? { - superClass: { - type: 'Identifier', - name: componentImport, - }, - } - : { - superClass: { - type: 'MemberExpression', - object: { - type: 'Identifier', - name: 'React', - }, - property: { - type: 'Identifier', - name: parentClassName, - }, - }, - }; - - return path - .find(j.ClassDeclaration, selector); - }; - - // Finds all classes that extend React.Component - const findReactES6ClassDeclaration = path => { - let classDeclarations = findReactES6ClassDeclarationByParent(path, 'Component'); - if (classDeclarations.size() === 0) { - classDeclarations = findReactES6ClassDeclarationByParent(path, 'PureComponent'); - } - return classDeclarations; - }; - - // --------------------------------------------------------------------------- - // Checks if the React class has mixins - const isMixinProperty = property => { - const key = property.key; - const value = property.value; - return ( - key.name === 'mixins' && - value.type === 'ArrayExpression' && - Array.isArray(value.elements) && - value.elements.length - ); - }; - - const hasMixins = classPath => { - const spec = getReactCreateClassSpec(classPath); - return spec && spec.properties.some(isMixinProperty); - }; - - // --------------------------------------------------------------------------- - // Others - const getClassExtendReactSpec = classPath => classPath.value.body; - - const createCreateReactClassCallExpression = properties => - j.callExpression( - j.memberExpression( - j.identifier('React'), - j.identifier('createClass'), - false - ), - [j.objectExpression(properties)] - ); - - const getComponentName = - classPath => classPath.node.id && classPath.node.id.name; - - // --------------------------------------------------------------------------- - // Direct methods! (see explanation below) - const findAllReactCreateClassCalls = path => - path.find(j.CallExpression, { - callee: REACT_CREATE_CLASS_MEMBER_EXPRESSION, - }); - - // Mixin Stuff - const containSameElements = (ls1, ls2) => { - if (ls1.length !== ls2.length) { - return false; - } - - return ( - ls1.reduce((res, x) => res && ls2.indexOf(x) !== -1, true) && - ls2.reduce((res, x) => res && ls1.indexOf(x) !== -1, true) - ); - }; - - const keyNameIsMixins = property => property.key.name === 'mixins'; - - const isSpecificMixinsProperty = (property, mixinIdentifierNames) => { - const key = property.key; - const value = property.value; - - return ( - key.name === 'mixins' && - value.type === 'ArrayExpression' && - Array.isArray(value.elements) && - value.elements.every(elem => elem.type === 'Identifier') && - containSameElements(value.elements.map(elem => elem.name), mixinIdentifierNames) - ); - }; - - // These following methods assume that the argument is - // a `React.createClass` call expression. In other words, - // they should only be used with `findAllReactCreateClassCalls`. - const directlyGetCreateClassSpec = classPath => { - if (!classPath || !classPath.value) { - return null; - } - const args = classPath.value.arguments; - if (args && args.length) { - const spec = args[0]; - if (spec.type === 'ObjectExpression' && Array.isArray(spec.properties)) { - return spec; - } - } - return null; - }; - - const directlyGetComponentName = classPath => { - let result = ''; - if ( - classPath.parentPath.value && - classPath.parentPath.value.type === 'VariableDeclarator' - ) { - result = classPath.parentPath.value.id.name; - } - return result; - }; - - const directlyHasMixinsField = classPath => { - const spec = directlyGetCreateClassSpec(classPath); - return spec && spec.properties.some(keyNameIsMixins); - }; - - const directlyHasSpecificMixins = (classPath, mixinIdentifierNames) => { - const spec = directlyGetCreateClassSpec(classPath); - return spec && spec.properties.some(prop => isSpecificMixinsProperty(prop, mixinIdentifierNames)); - }; - - return { - createCreateReactClassCallExpression, - findReactES6ClassDeclaration, - findReactCreateClass, - findReactCreateClassCallExpression, - findReactCreateClassModuleExports, - findReactCreateClassExportDefault, - getComponentName, - getReactCreateClassSpec, - getClassExtendReactSpec, - hasMixins, - hasModule, - hasReact, - isMixinProperty, - removeUnusedSuperClassImport, - - // "direct" methods - findAllReactCreateClassCalls, - directlyGetComponentName, - directlyGetCreateClassSpec, - directlyHasMixinsField, - directlyHasSpecificMixins, - }; -}; diff --git a/codemods/legacy/transforms/utils/array-polyfills.js b/codemods/legacy/transforms/utils/array-polyfills.js deleted file mode 100644 index e0cbfde..0000000 --- a/codemods/legacy/transforms/utils/array-polyfills.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -/*eslint-disable no-extend-native*/ - -'use strict'; - -function findIndex(predicate, context) { - if (this == null) { - throw new TypeError( - 'Array.prototype.findIndex called on null or undefined' - ); - } - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } - var list = Object(this); - /* eslint-disable no-bitwise */ - var length = list.length >>> 0; - /* eslint-enable no-bitwise */ - for (var i = 0; i < length; i++) { - if (predicate.call(context, list[i], i, list)) { - return i; - } - } - return -1; -} - -if (!Array.prototype.findIndex) { - Array.prototype.findIndex = findIndex; -} - -if (!Array.prototype.find) { - Array.prototype.find = function(predicate, context) { - if (this == null) { - throw new TypeError('Array.prototype.find called on null or undefined'); - } - var index = findIndex.call(this, predicate, context); - return index === -1 ? undefined : this[index]; - }; -} diff --git a/codemods/legacy/transforms/utils/doesNotUseArguments.js b/codemods/legacy/transforms/utils/doesNotUseArguments.js deleted file mode 100644 index fb1c135..0000000 --- a/codemods/legacy/transforms/utils/doesNotUseArguments.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -'use strict'; - -module.exports = function(j) { - const ReactUtils = require('./ReactUtils')(j); - - const doesNotUseArguments = (path, filepath) => { - const hasArguments = - j(path) - .find(j.Identifier, { name: 'arguments' }) - .size() > 0; - - if (hasArguments) { - var warnStr = ''; - - if (filepath) { - warnStr += filepath + ': '; - } - - warnStr += '`' + ReactUtils.directlyGetComponentName(path) + '` ' + - 'was skipped because `arguments` was found in your functions. ' + - 'Arrow functions do not expose an `arguments` object; ' + - 'consider changing to use ES6 spread operator and re-run this script.'; - - console.warn(warnStr); - - return false; - } - return true; - }; - - return doesNotUseArguments; -}; diff --git a/codemods/legacy/tsconfig.json b/codemods/legacy/tsconfig.json deleted file mode 100644 index da0445e..0000000 --- a/codemods/legacy/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "compilerOptions": { - "moduleDetection": "force", - "target": "ES2015" - } -} diff --git a/codemods/manual-bind-to-arrow/README.md b/codemods/manual-bind-to-arrow/README.md new file mode 100644 index 0000000..90f2ffe --- /dev/null +++ b/codemods/manual-bind-to-arrow/README.md @@ -0,0 +1,9 @@ +# manual-bind-to-arrow + +Convert constructor-time `.bind(this)` assignments into arrow class fields. + +## Usage + +```bash +npx codemod @react-new/manual-bind-to-arrow --target +``` diff --git a/codemods/manual-bind-to-arrow/codemod.yaml b/codemods/manual-bind-to-arrow/codemod.yaml new file mode 100644 index 0000000..ca6a8d3 --- /dev/null +++ b/codemods/manual-bind-to-arrow/codemod.yaml @@ -0,0 +1,19 @@ +schema_version: "1.0" + +name: "@react-new/manual-bind-to-arrow" +version: "0.1.1" +description: "Convert this.method = this.method.bind(this) to arrow class property" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + +targets: + languages: ["tsx"] + +keywords: ["React"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/manual-bind-to-arrow/package.json b/codemods/manual-bind-to-arrow/package.json new file mode 100644 index 0000000..9609416 --- /dev/null +++ b/codemods/manual-bind-to-arrow/package.json @@ -0,0 +1,15 @@ +{ + "name": "@react-new/manual-bind-to-arrow", + "version": "0.1.1", + "description": "Convert this.method = this.method.bind(this) to arrow class property", + "type": "module", + "scripts": { + "test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@codemod.com/jssg-types": "latest", + "typescript": "latest", + "@types/node": "latest" + } +} diff --git a/codemods/manual-bind-to-arrow/scripts/codemod.ts b/codemods/manual-bind-to-arrow/scripts/codemod.ts new file mode 100644 index 0000000..66ccf18 --- /dev/null +++ b/codemods/manual-bind-to-arrow/scripts/codemod.ts @@ -0,0 +1,207 @@ +import type { Transform, Edit, SgNode } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { useMetricAtom } from "codemod:metrics"; + +function metricFile(filename: string): string { + const cwd = process.cwd() + "/"; + return filename.startsWith(cwd) ? filename.slice(cwd.length) : filename; +} + + +function methodDefByNameRule(methodName: string) { + const escaped = methodName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return { + kind: "method_definition" as const, + has: { + field: "name", + any: [ + { kind: "property_identifier" as const, regex: `^${escaped}$` }, + { kind: "identifier" as const, regex: `^${escaped}$` }, + ], + }, + }; +} + +const USES_ARGUMENTS_RULE = { + has: { kind: "identifier" as const, regex: "^arguments$" }, +}; + +function getClassBody(node: SgNode): SgNode | null { + const classDecl = node.ancestors().find((a) => + a.kind() === "class_declaration" || a.kind() === "class" + ); + if (!classDecl) return null; + return classDecl.find({ rule: { kind: "class_body" } }); +} + +function memberPropertyName(node: SgNode | null): string | null { + return node?.kind() === "member_expression" ? (node.field("property")?.text() ?? null) : null; +} + +function memberObject(node: SgNode | null): SgNode | null { + return node?.kind() === "member_expression" ? node.field("object") : null; +} + +function isThisLikeObject(node: SgNode | null): boolean { + if (!node) return false; + if (node.text() === "this" || node.text() === "self") return true; + return node.kind() === "parenthesized_expression" && node.find({ rule: { kind: "this" } }) !== null; +} + +function isSuperStatement(node: SgNode): boolean { + const call = node.find({ rule: { kind: "call_expression" } }); + return node.kind() === "expression_statement" && call?.field("function")?.text() === "super"; +} + +function statementForAssignment(assign: SgNode): SgNode | null { + return assign.ancestors().find((a) => a.kind() === "expression_statement") ?? null; +} + +function lineStartIndex(source: string, index: number): number { + let cursor = index; + while (cursor > 0 && source[cursor - 1] !== "\n" && source[cursor - 1] !== "\r") { + cursor--; + } + return cursor; +} + +function statementRemovalEdit(source: string, node: SgNode): Edit { + const startIndex = node.range().start.index; + const lineStart = lineStartIndex(source, startIndex); + const leading = source.slice(lineStart, startIndex); + const removalStart = /^[\t ]*$/.test(leading) ? lineStart : startIndex; + const endIndex = node.range().end.index; + const trailing = source.slice(endIndex).match(/^[\t ]*(?:\r\n|\n|\r)?/)?.[0] ?? ""; + + return { + startPos: removalStart, + endPos: endIndex + trailing.length, + insertedText: "", + }; +} + +function nodeRemovalEdit(source: string, node: SgNode): Edit { + const startIndex = node.range().start.index; + const lineStart = lineStartIndex(source, startIndex); + const leading = source.slice(lineStart, startIndex); + const removalStart = /^[\t ]*$/.test(leading) ? lineStart : startIndex; + const endIndex = node.range().end.index; + const trailing = source.slice(endIndex).match(/^[\t ]*(?:\r\n|\n|\r)?/)?.[0] ?? ""; + + return { + startPos: removalStart, + endPos: endIndex + trailing.length, + insertedText: "", + }; +} + +const transform: Transform = async (root) => { + const rootNode = root.root(); + const source = rootNode.text(); + const edits: Edit[] = []; + const metric = useMetricAtom("manual-bind-to-arrow-transforms"); + + const allAssignments = rootNode.findAll({ rule: { kind: "assignment_expression" } }); + const allBindAssignments = allAssignments.filter((assign) => { + const right = assign.field("right"); + if (!right || right.kind() !== "call_expression") return false; + const callee = right.field("function"); + if (!callee || callee.kind() !== "member_expression") return false; + if (callee.field("property")?.text() !== "bind") return false; + const left = assign.field("left"); + if (!left || left.kind() !== "member_expression") return false; + if (!isThisLikeObject(left.field("object"))) return false; + return true; + }); + const assignmentsInConstructors = allBindAssignments.filter((assign) => { + const ctor = assign.ancestors().find((a) => a.kind() === "method_definition" && (a.field("name") ?? a.field("key"))?.text() === "constructor"); + return !!ctor; + }); + + const assignments = assignmentsInConstructors.filter((assign) => { + const left = assign.field("left"); + const right = assign.field("right"); + if (!left || !right) return false; + const bindCallee = right.field("function"); + if (!bindCallee || bindCallee.kind() !== "member_expression") return false; + const bindObj = bindCallee.field("object"); + if (!bindObj || memberObject(bindObj)?.text() !== "this") return false; + const bindObjProp = memberPropertyName(bindObj); + const leftProp = memberPropertyName(left); + return !!leftProp && !!bindObjProp && leftProp === bindObjProp; + }); + + for (const assign of assignments) { + const left = assign.field("left"); + const leftProp = left?.field("property"); + if (!leftProp) continue; + const methodName = leftProp.text(); + + const classBody = getClassBody(assign); + if (!classBody) continue; + + const methods = classBody.findAll({ rule: methodDefByNameRule(methodName) }); + const methodDef = methods[0]; + if (!methodDef || methodDef.find({ rule: USES_ARGUMENTS_RULE })) continue; + + const params = methodDef.field("parameters"); + const body = methodDef.field("body"); + if (!body) continue; + const stmt = assign.ancestors().find((a) => a.kind() === "expression_statement"); + const constructorNode = assign.ancestors().find((a) => a.kind() === "method_definition" && (a.field("name") ?? a.field("key"))?.text() === "constructor"); + const constructorBody = constructorNode?.field("body"); + const bindAssignmentsInConstructor = constructorNode + ? assignments + .filter((a) => { + const c = a.ancestors().find((anc) => anc.kind() === "method_definition" && (anc.field("name") ?? anc.field("key"))?.text() === "constructor"); + return c?.range().start.index === constructorNode.range().start.index; + }) + .sort((a, b) => a.range().start.index - b.range().start.index) + : []; + const selfIdentifiers = constructorNode?.findAll({ rule: { kind: "identifier", regex: "^self$" } }) ?? []; + const selfDeclaration = constructorNode?.findAll({ rule: { kind: "lexical_declaration" } }).find((decl) => + decl.find({ rule: { kind: "variable_declarator", has: { field: "name", kind: "identifier", regex: "^self$" } } }) !== null + ) ?? null; + const canRemoveSelfDeclaration = + left?.field("object")?.text() === "self" && selfIdentifiers.length <= 2 && !!selfDeclaration; + const removableBindStatements = new Set( + bindAssignmentsInConstructor + .map(statementForAssignment) + .filter((node): node is SgNode => node !== null) + .map((node) => node.range().start.index), + ); + const willBeEmptyConstructor = + !!constructorNode && + !!constructorBody && + constructorBody.children() + .filter((c) => c.isNamed()) + .every((child) => + isSuperStatement(child) || + removableBindStatements.has(child.range().start.index) || + (canRemoveSelfDeclaration && selfDeclaration?.range().start.index === child.range().start.index) + ); + + if (willBeEmptyConstructor && constructorNode) { + edits.push(nodeRemovalEdit(source, constructorNode)); + } else if (stmt) { + edits.push(statementRemovalEdit(source, stmt)); + + if (canRemoveSelfDeclaration && selfDeclaration) { + edits.push(statementRemovalEdit(source, selfDeclaration)); + } + } + + const paramsText = params ? params.text() : "()"; + const returnTypeText = methodDef.field("return_type")?.text() ?? ""; + const bodyText = body.text(); + const arrowReplacement = `${methodName} = ${paramsText}${returnTypeText} => ${bodyText};`; + edits.push(methodDef.replace(arrowReplacement)); + + metric.increment({ file: metricFile(root.filename()) }); + } + + if (edits.length === 0) return null; + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow1.output.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow1/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow1.output.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow1/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow1.input.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow1/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow1.input.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow1/input.tsx diff --git a/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow1/metrics.json b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow1/metrics.json new file mode 100644 index 0000000..4de0031 --- /dev/null +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow1/metrics.json @@ -0,0 +1,10 @@ +{ + "manual-bind-to-arrow-transforms": [ + { + "cardinality": { + "file": "tests/manual-bind-to-arrow1/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow10.output.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow10/expected.tsx similarity index 98% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow10.output.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow10/expected.tsx index 6da7e15..1113e05 100644 --- a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow10.output.js +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow10/expected.tsx @@ -4,4 +4,4 @@ class Component extends React.Component { class Component2 extends React.Component { onClick = () => { }; -} +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow10.input.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow10/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow10.input.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow10/input.tsx diff --git a/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow10/metrics.json b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow10/metrics.json new file mode 100644 index 0000000..6ce9633 --- /dev/null +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow10/metrics.json @@ -0,0 +1,10 @@ +{ + "manual-bind-to-arrow-transforms": [ + { + "cardinality": { + "file": "tests/manual-bind-to-arrow10/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow11.input.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow11/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow11.input.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow11/expected.tsx diff --git a/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow11/input.tsx b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow11/input.tsx new file mode 100644 index 0000000..ddcc601 --- /dev/null +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow11/input.tsx @@ -0,0 +1,12 @@ +class Component extends React.Component { + constructor() { + super(); + this.onClick = this.onClick.bind(this); + } + + onClick() { + if (fn) { + fn.apply(this, Array.prototype.slice.call(arguments, 1)) + } + } +} diff --git a/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow12/expected.tsx b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow12/expected.tsx new file mode 100644 index 0000000..eb69eb6 --- /dev/null +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow12/expected.tsx @@ -0,0 +1,12 @@ +const React = require('react'); + +module.exports = class Router extends React.Component { + constructor(props) { + super(props); + this.state = { hash: window.location.hash }; + } + + updateHash = (event) => { + this.setState({ hash: window.location.hash }); + }; +}; diff --git a/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow12/input.tsx b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow12/input.tsx new file mode 100644 index 0000000..d6e1981 --- /dev/null +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow12/input.tsx @@ -0,0 +1,13 @@ +const React = require('react'); + +module.exports = class Router extends React.Component { + constructor(props) { + super(props); + this.state = { hash: window.location.hash }; + this.updateHash = this.updateHash.bind(this); + } + + updateHash(event) { + this.setState({ hash: window.location.hash }); + } +}; diff --git a/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow12/metrics.json b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow12/metrics.json new file mode 100644 index 0000000..5d4eb91 --- /dev/null +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow12/metrics.json @@ -0,0 +1,10 @@ +{ + "manual-bind-to-arrow-transforms": [ + { + "cardinality": { + "file": "tests/manual-bind-to-arrow12/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow2.output.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow2/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow2.output.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow2/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow2.input.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow2/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow2.input.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow2/input.tsx diff --git a/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow2/metrics.json b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow2/metrics.json new file mode 100644 index 0000000..a426ece --- /dev/null +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow2/metrics.json @@ -0,0 +1,10 @@ +{ + "manual-bind-to-arrow-transforms": [ + { + "cardinality": { + "file": "tests/manual-bind-to-arrow2/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow3.input.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow3/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow3.input.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow3/expected.tsx diff --git a/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow3/input.tsx b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow3/input.tsx new file mode 100644 index 0000000..8687c1f --- /dev/null +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow3/input.tsx @@ -0,0 +1,7 @@ +class Component extends React.Component { + constructor() { + super(); + this.onClick = this.onClick.bind(this); + } + notOnClick() { } +} diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow4.output.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow4/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow4.output.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow4/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow4.input.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow4/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow4.input.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow4/input.tsx diff --git a/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow4/metrics.json b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow4/metrics.json new file mode 100644 index 0000000..574360f --- /dev/null +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow4/metrics.json @@ -0,0 +1,10 @@ +{ + "manual-bind-to-arrow-transforms": [ + { + "cardinality": { + "file": "tests/manual-bind-to-arrow4/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow5.output.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow5/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow5.output.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow5/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow5.input.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow5/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow5.input.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow5/input.tsx diff --git a/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow5/metrics.json b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow5/metrics.json new file mode 100644 index 0000000..3f87734 --- /dev/null +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow5/metrics.json @@ -0,0 +1,10 @@ +{ + "manual-bind-to-arrow-transforms": [ + { + "cardinality": { + "file": "tests/manual-bind-to-arrow5/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow6.output.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow6/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow6.output.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow6/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow6.input.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow6/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow6.input.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow6/input.tsx diff --git a/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow6/metrics.json b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow6/metrics.json new file mode 100644 index 0000000..22e736c --- /dev/null +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow6/metrics.json @@ -0,0 +1,10 @@ +{ + "manual-bind-to-arrow-transforms": [ + { + "cardinality": { + "file": "tests/manual-bind-to-arrow6/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow7.output.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow7/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow7.output.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow7/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow7.input.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow7/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow7.input.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow7/input.tsx diff --git a/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow7/metrics.json b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow7/metrics.json new file mode 100644 index 0000000..475fc7f --- /dev/null +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow7/metrics.json @@ -0,0 +1,10 @@ +{ + "manual-bind-to-arrow-transforms": [ + { + "cardinality": { + "file": "tests/manual-bind-to-arrow7/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow8.output.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow8/expected.tsx similarity index 60% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow8.output.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow8/expected.tsx index a9d9934..6f9abaf 100644 --- a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow8.output.js +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow8/expected.tsx @@ -1,3 +1,3 @@ class Component extends React.Component { - onClick = async () => { }; + onClick = () => { }; } diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow8.input.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow8/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow8.input.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow8/input.tsx diff --git a/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow8/metrics.json b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow8/metrics.json new file mode 100644 index 0000000..b283aae --- /dev/null +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow8/metrics.json @@ -0,0 +1,10 @@ +{ + "manual-bind-to-arrow-transforms": [ + { + "cardinality": { + "file": "tests/manual-bind-to-arrow8/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow9.output.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow9/expected.tsx similarity index 98% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow9.output.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow9/expected.tsx index 7e258e4..19c2ed0 100644 --- a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow9.output.js +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow9/expected.tsx @@ -4,4 +4,4 @@ class Component extends React.Component { class Component2 extends React.Component { didClick = () => { }; -} +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow9.input.js b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow9/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow9.input.js rename to codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow9/input.tsx diff --git a/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow9/metrics.json b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow9/metrics.json new file mode 100644 index 0000000..a615d80 --- /dev/null +++ b/codemods/manual-bind-to-arrow/tests/manual-bind-to-arrow9/metrics.json @@ -0,0 +1,10 @@ +{ + "manual-bind-to-arrow-transforms": [ + { + "cardinality": { + "file": "tests/manual-bind-to-arrow9/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/manual-bind-to-arrow/tsconfig.json b/codemods/manual-bind-to-arrow/tsconfig.json new file mode 100644 index 0000000..ecbb40a --- /dev/null +++ b/codemods/manual-bind-to-arrow/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": [ + "ESNext" + ], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": [ + "@codemod.com/jssg-types", + "node" + ], + "allowImportingTsExtensions": true, + "noEmit": true, + "skipLibCheck": true, + "verbatimModuleSyntax": false, + "strict": true + }, + "exclude": [ + "tests" + ] +} diff --git a/codemods/manual-bind-to-arrow/workflow.yaml b/codemods/manual-bind-to-arrow/workflow.yaml new file mode 100644 index 0000000..8773841 --- /dev/null +++ b/codemods/manual-bind-to-arrow/workflow.yaml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: "Scan tsx files and apply fixes" + js-ast-grep: + js_file: scripts/codemod.ts + language: "tsx" diff --git a/codemods/pure-component/README.md b/codemods/pure-component/README.md new file mode 100644 index 0000000..a024032 --- /dev/null +++ b/codemods/pure-component/README.md @@ -0,0 +1,9 @@ +# pure-component + +Convert simple React classes into pure function components. + +## Usage + +```bash +npx codemod @react-new/pure-component --target +``` diff --git a/codemods/pure-component/codemod.yaml b/codemods/pure-component/codemod.yaml new file mode 100644 index 0000000..73517e9 --- /dev/null +++ b/codemods/pure-component/codemod.yaml @@ -0,0 +1,19 @@ +schema_version: "1.0" + +name: "@react-new/pure-component" +version: "0.1.1" +description: "Convert simple React classes into pure function components" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + +targets: + languages: ["tsx"] + +keywords: ["React", "PureComponent"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/pure-component/package.json b/codemods/pure-component/package.json new file mode 100644 index 0000000..9634bc3 --- /dev/null +++ b/codemods/pure-component/package.json @@ -0,0 +1,15 @@ +{ + "name": "@react-new/pure-component", + "version": "0.1.1", + "description": "Convert simple React classes into pure function components", + "type": "module", + "scripts": { + "test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts && node --test ./scripts/warning-tests.mjs ./scripts/differential-tests.mjs", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@codemod.com/jssg-types": "latest", + "@types/node": "latest", + "typescript": "latest" + } +} diff --git a/codemods/pure-component/scripts/codemod.ts b/codemods/pure-component/scripts/codemod.ts new file mode 100644 index 0000000..c698c29 --- /dev/null +++ b/codemods/pure-component/scripts/codemod.ts @@ -0,0 +1,583 @@ +import type { Transform, Edit, SgNode } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { useMetricAtom } from "codemod:metrics"; + +type ReactImportAliases = { + component: Set; + pureComponent: Set; + removableNamed: Set; +}; + +type ReactSuperclassKind = "Component" | "PureComponent"; + +const REACT_SOURCES = new Set(["react", "React", "react/addons", "react-native"]); + +function truthyParam(value: unknown): boolean { + return value === true || value === "true"; +} + +function metricFile(filename: string): string { + const cwd = process.cwd() + "/"; + return filename.startsWith(cwd) ? filename.slice(cwd.length) : filename; +} + +function namedChildren(node: SgNode | null): SgNode[] { + if (!node) return []; + return node.children().filter((child) => child.isNamed() && child.kind() !== "comment"); +} + +function sourceText(node: SgNode): string | null { + const fragment = node.find({ rule: { kind: "string_fragment" } }); + if (fragment) return fragment.text(); + const text = node.text(); + return text.length >= 2 ? text.slice(1, -1) : null; +} + +function importSource(importStmt: SgNode): SgNode | null { + return importStmt.field("source") ?? importStmt.find({ rule: { kind: "string" } }); +} + +function isReactSource(importStmt: SgNode): boolean { + const source = importSource(importStmt); + const value = source ? sourceText(source) : null; + return value !== null && REACT_SOURCES.has(value); +} + +function reactImportAliases(rootNode: SgNode): ReactImportAliases { + const component = new Set(); + const pureComponent = new Set(); + const removableNamed = new Set(); + + for (const importStmt of rootNode.findAll({ rule: { kind: "import_statement" } })) { + if (!isReactSource(importStmt)) continue; + + for (const specifier of importStmt.findAll({ rule: { kind: "import_specifier" } })) { + const imported = specifier.field("name")?.text(); + const aliasNode = specifier.field("alias"); + const alias = aliasNode?.text() ?? imported; + if (!imported || !alias) continue; + + if (imported === "Component") { + component.add(alias); + } else if (imported === "PureComponent") { + pureComponent.add(alias); + } else { + continue; + } + + // Legacy removes an import specifier by searching for its `imported` name + // in the rest of the file. For `{ Component as C }`, imported=Component but + // the class extends `C`; the search finds no `Component` identifier and the + // specifier gets kept (legacy quirk). Mirror that: only flag as removable + // when the local name equals the imported name. + if (!aliasNode) removableNamed.add(alias); + } + } + + return { component, pureComponent, removableNamed }; +} + +function reactSuperclassKind( + classDecl: SgNode, + aliases: ReactImportAliases, +): ReactSuperclassKind | null { + const heritage = classDecl.find({ rule: { kind: "class_heritage" } }); + if (!heritage) return null; + + const directExtends = heritage.find({ + rule: { any: [{ kind: "member_expression" }, { kind: "identifier" }] }, + }); + if (!directExtends) return null; + + if (directExtends.kind() === "member_expression") { + const object = directExtends.field("object"); + const property = directExtends.field("property")?.text() ?? ""; + if (object?.text() !== "React") return null; + if (property === "Component" || property === "PureComponent") return property; + return null; + } + + const name = directExtends.text(); + if (aliases.component.has(name)) return "Component"; + if (aliases.pureComponent.has(name)) return "PureComponent"; + return null; +} + +function classBodyMembers(classDecl: SgNode): SgNode[] { + const body = classDecl.find({ rule: { kind: "class_body" } }); + return namedChildren(body).filter( + (m) => m.kind() === "method_definition" || m.kind() === "public_field_definition", + ); +} + +function memberName(member: SgNode): string { + return member.field("name")?.text() ?? ""; +} + +function isRenderMethod(member: SgNode): boolean { + return member.kind() === "method_definition" && memberName(member) === "render"; +} + +function isStaticField(member: SgNode): boolean { + if (member.kind() !== "public_field_definition") return false; + return member.children().some((child) => child.kind() === "static" || child.text() === "static"); +} + +function isPropsTypeField(member: SgNode): boolean { + return member.kind() === "public_field_definition" && + !isStaticField(member) && + memberName(member) === "props"; +} + +function onlyHasSafeMembers(classDecl: SgNode): boolean { + const members = classBodyMembers(classDecl); + const methods = members.filter((m) => m.kind() === "method_definition"); + if (methods.length !== 1 || !isRenderMethod(methods[0]!)) return false; + return members.every((m) => isRenderMethod(m) || isPropsTypeField(m) || isStaticField(m)); +} + +function hasRefAttribute(classDecl: SgNode): boolean { + return classDecl.findAll({ rule: { kind: "jsx_attribute" } }) + .some((attr) => namedChildren(attr)[0]?.text() === "ref"); +} + +function renderMethod(classDecl: SgNode): SgNode | null { + return classBodyMembers(classDecl).find(isRenderMethod) ?? null; +} + +function propsField(classDecl: SgNode): SgNode | null { + return classBodyMembers(classDecl).find(isPropsTypeField) ?? null; +} + +function propsTypeAnnotationText(classDecl: SgNode): string { + const annotation = propsField(classDecl)?.find({ rule: { kind: "type_annotation" } }); + return annotation?.text() ?? ""; +} + +function propsObjectType(classDecl: SgNode): SgNode | null { + const annotation = propsField(classDecl)?.find({ rule: { kind: "type_annotation" } }); + const typeNode = namedChildren(annotation ?? null)[0] ?? null; + return typeNode?.kind() === "object_type" ? typeNode : null; +} + +function isThisProps(node: SgNode | null | undefined): boolean { + if (!node || node.kind() !== "member_expression") return false; + const object = node.field("object"); + const property = node.field("property"); + return object?.kind() === "this" && property?.text() === "props"; +} + +function thisPropsExpressions(body: SgNode): SgNode[] { + return body.findAll({ rule: { kind: "member_expression" } }).filter(isThisProps); +} + +function propsAccesses(body: SgNode): SgNode[] { + return body.findAll({ rule: { kind: "member_expression" } }) + .filter((m) => isThisProps(m.field("object"))); +} + +function barePropsIdentifiers(methodDef: SgNode): boolean { + return methodDef.findAll({ + rule: { + any: [ + { kind: "identifier", regex: "^props$" }, + { kind: "property_identifier", regex: "^props$" }, + ], + }, + }).some((identifier) => { + const parent = identifier.parent(); + const grand = parent?.parent(); + if ( + parent?.kind() === "member_expression" && + grand?.kind() === "member_expression" && + grand.field("object")?.id() === parent.id() + ) { + return false; + } + return true; + }); +} + +function duplicateDeclarators(body: SgNode): Map> { + const duplicates = new Map>(); + for (const declarator of body.findAll({ rule: { kind: "variable_declarator" } })) { + const name = declarator.field("name"); + const value = declarator.field("value"); + if (name?.kind() !== "identifier" || value?.kind() !== "member_expression") continue; + const object = value.field("object"); + const property = value.field("property"); + if (!object || !property || !isThisProps(object) || property.text() !== name.text()) continue; + duplicates.set(name.text(), declarator); + } + return duplicates; +} + +function propNamesInOrder(body: SgNode): string[] { + const names: string[] = []; + const seen = new Set(); + for (const access of propsAccesses(body)) { + const property = access.field("property")?.text(); + if (property && !seen.has(property)) { + seen.add(property); + names.push(property); + } + } + return names; +} + +function hasAssignmentNamesShadowing( + body: SgNode, + propNames: string[], + duplicates: Map>, +): boolean { + const propNameSet = new Set(propNames); + const duplicateNames = new Set(duplicates.keys()); + + return body.findAll({ rule: { kind: "identifier" } }).some((identifier) => { + const name = identifier.text(); + if (!propNameSet.has(name) || duplicateNames.has(name)) return false; + const parent = identifier.parent(); + if (!parent) return true; + if (parent.kind() === "member_expression" && parent.field("property")?.id() === identifier.id()) { + return false; + } + const object = parent.kind() === "member_expression" ? parent.field("object") : null; + if (object && isThisProps(object)) return false; + return true; + }); +} + +function canDestructure( + classDecl: SgNode, + methodDef: SgNode, + body: SgNode, +): boolean { + const names = propNamesInOrder(body); + const duplicates = duplicateDeclarators(body); + if (barePropsIdentifiers(methodDef) || hasAssignmentNamesShadowing(body, names, duplicates)) { + return false; + } + // Without an inline object type we can't produce a typed destructure pattern. + // The legacy transform crashes in this case (calls `.properties.forEach` on a + // non-object type); we skip safely and fall back to `props: Annotation`. + const annotationText = propsTypeAnnotationText(classDecl); + if (annotationText === "") return true; + return propsObjectType(classDecl) !== null; +} + +function lineDeletionRange( + node: SgNode, + source: string, +): { start: number; end: number } { + const range = node.range(); + let start = range.start.index; + while (start > 0 && source[start - 1] !== "\n") start--; + let end = range.end.index; + if (end < source.length && source[end] === "\n") end++; + return { start, end }; +} + +function rangeContains( + outer: { start: number; end: number }, + inner: { start: number; end: number }, +): boolean { + return inner.start >= outer.start && inner.end <= outer.end; +} + +function rewriteRenderBody( + renderBody: SgNode, + source: string, + mayDestructure: boolean, + duplicates: Map>, +): string { + const edits: Edit[] = []; + const deletedRanges: Array<{ start: number; end: number }> = []; + + if (mayDestructure) { + for (const declarator of duplicates.values()) { + const stmt = declarator.parent(); + if (!stmt) continue; + if (stmt.kind() !== "lexical_declaration" && stmt.kind() !== "variable_declaration") continue; + const declarators = stmt.findAll({ rule: { kind: "variable_declarator" } }); + if (declarators.length !== 1) continue; + const range = lineDeletionRange(stmt, source); + deletedRanges.push(range); + edits.push({ startPos: range.start, endPos: range.end, insertedText: "" }); + } + } + + const inDeleted = (node: SgNode) => { + const r = node.range(); + const span = { start: r.start.index, end: r.end.index }; + return deletedRanges.some((dr) => rangeContains(dr, span)); + }; + + if (mayDestructure) { + for (const access of propsAccesses(renderBody)) { + if (inDeleted(access)) continue; + const prop = access.field("property")?.text(); + if (prop) edits.push(access.replace(prop)); + } + } else { + for (const me of thisPropsExpressions(renderBody)) { + edits.push(me.replace("props")); + } + } + + return renderBody.commitEdits(edits); +} + +function normalizeBlock(blockText: string, indent: string): string { + const lines = blockText.split("\n"); + if (lines.length <= 2) return blockText; + + const inner = lines.slice(1, -1); + const nonEmpty = inner.filter((line) => line.trim().length > 0); + const commonIndent = nonEmpty.length === 0 + ? 0 + : Math.min(...nonEmpty.map((line) => line.match(/^ */)?.[0].length ?? 0)); + const body = inner + .map((line) => (line.length >= commonIndent ? line.slice(commonIndent) : line)) + .map((line) => (line.trim().length > 0 ? `${indent}${line}` : "")) + .join("\n"); + return `{\n${body}\n}`; +} + +function renderDestructuredParam(names: string[], objectType: SgNode | null): string { + const typed = new Map(); + if (objectType) { + for (const prop of objectType.findAll({ rule: { kind: "property_signature" } })) { + const key = prop.field("name")?.text(); + const type = prop.find({ rule: { kind: "type_annotation" } })?.text().replace(/^:\s*/, ""); + if (key && type) typed.set(key, type); + } + } + + const lines = names.map((name) => + typed.has(name) ? ` ${name}: ${typed.get(name)},` : ` ${name},`, + ); + return `(\n {\n${lines.join("\n")}\n },\n)`; +} + +function renderFunctionLike( + name: string, + bodyText: string, + paramText: string, + useArrows: boolean, +): string { + const body = normalizeBlock(bodyText, " "); + + if (useArrows) { + let left: string; + if (paramText === "") left = "()"; + else if (paramText === "props") left = "props"; + else if (paramText.startsWith("(")) left = paramText; + else left = `(${paramText})`; + return `const ${name} = ${left} => ${body};`; + } + + if (paramText.startsWith("(")) { + return `function ${name}${paramText} ${body}`; + } + return `function ${name}(${paramText}) ${body}`; +} + +function renderStaticAssignments(className: string, classDecl: SgNode): string[] { + return classBodyMembers(classDecl) + .filter(isStaticField) + .map((field) => { + const name = field.field("name")?.text() ?? ""; + const value = field.field("value")?.text() ?? "undefined"; + return `${className}.${name} = ${value};`; + }); +} + +function getClassName(classDecl: SgNode): string | null { + return classDecl.field("name")?.text() ?? null; +} + +function skipWarning(classDecl: SgNode): string { + const name = getClassName(classDecl) ?? "Unknown"; + const filename = classDecl.getRoot().filename(); + const { line, column } = classDecl.range().start; + // Jscodeshift reports line as 1-indexed, column as 0-indexed. Ast-grep is + // 0-indexed on both; +1 on the line matches legacy output exactly. + return `Class "${name}" skipped in ${filename} on ${line + 1}:${column}`; +} + +function enclosingExportStatement(classDecl: SgNode): SgNode | null { + const parent = classDecl.parent(); + return parent?.kind() === "export_statement" ? parent : null; +} + +function isDefaultExportStatement(exportStmt: SgNode): boolean { + return exportStmt.children().some((c) => c.kind() === "default" || c.text() === "default"); +} + +function remainingNamedImportUse( + rootNode: SgNode, + alias: string, + transformedClassIds: Set, +): boolean { + return rootNode + .findAll({ rule: { kind: "identifier", regex: `^${alias}$` } }) + .some((identifier) => { + if (identifier.ancestors().some((a) => a.kind() === "import_statement")) return false; + const classDecl = identifier.ancestors().find((a) => a.kind() === "class_declaration"); + if (!classDecl) return true; + return !transformedClassIds.has(classDecl.id()); + }); +} + +function buildImportSpecifierEdits( + importStmt: SgNode, + aliasesToDrop: Set, + source: string, +): Edit[] { + const named = importStmt.find({ rule: { kind: "named_imports" } }); + if (!named) return []; + + const specifiers = named.findAll({ rule: { kind: "import_specifier" } }); + const toKeep = specifiers.filter((s) => { + const local = s.field("alias")?.text() ?? s.field("name")?.text() ?? ""; + return !aliasesToDrop.has(local); + }); + + if (toKeep.length === specifiers.length) return []; + + const clause = importStmt.find({ rule: { kind: "import_clause" } }); + const defaultImport = clause + ? namedChildren(clause).find((c) => c.kind() === "identifier") + : null; + + if (toKeep.length === 0) { + if (defaultImport) { + return [{ + startPos: clause!.range().start.index, + endPos: clause!.range().end.index, + insertedText: defaultImport.text(), + }]; + } + // Legacy uses ImportSpecifier.remove() which drops only the specifier and + // leaves `import 'source';` as a side-effect import. Match that by + // replacing the import_clause (plus the trailing `from`) with nothing. + const sourceNode = importSource(importStmt); + if (!clause || !sourceNode) { + const range = lineDeletionRange(importStmt, source); + return [{ startPos: range.start, endPos: range.end, insertedText: "" }]; + } + return [{ + startPos: clause.range().start.index, + endPos: sourceNode.range().start.index, + insertedText: "", + }]; + } + + const newSpecifiers = toKeep.map((s) => s.text()).join(", "); + return [{ + startPos: named.range().start.index, + endPos: named.range().end.index, + insertedText: `{ ${newSpecifiers} }`, + }]; +} + +const transform: Transform = async (root, options) => { + const rootNode = root.root(); + const source = rootNode.text(); + const metric = useMetricAtom("pure-component-conversions"); + const useArrows = truthyParam(options.params?.useArrows); + const destructuring = truthyParam(options.params?.destructuring); + const silenceWarnings = truthyParam(options.params?.silenceWarnings); + + const aliases = reactImportAliases(rootNode); + const edits: Edit[] = []; + const transformedClassIds = new Set(); + const reactClasses = rootNode.findAll({ rule: { kind: "class_declaration" } }) + .map((classDecl) => ({ classDecl, kind: reactSuperclassKind(classDecl, aliases) })) + .filter((entry): entry is { classDecl: SgNode; kind: ReactSuperclassKind } => entry.kind !== null); + const targetSuperclassKind = reactClasses.some((entry) => entry.kind === "Component") + ? "Component" + : reactClasses.some((entry) => entry.kind === "PureComponent") + ? "PureComponent" + : null; + + for (const { classDecl, kind } of reactClasses) { + if (targetSuperclassKind === null || kind !== targetSuperclassKind) continue; + + if (!onlyHasSafeMembers(classDecl) || hasRefAttribute(classDecl)) { + if (!silenceWarnings) console.warn(skipWarning(classDecl)); + continue; + } + + const name = getClassName(classDecl); + const render = renderMethod(classDecl); + const renderBody = render?.field("body"); + if (!name || !render || !renderBody) continue; + + const names = propNamesInOrder(renderBody); + const duplicates = duplicateDeclarators(renderBody); + const objectType = propsObjectType(classDecl); + const annotationText = propsTypeAnnotationText(classDecl); + const hasPropsUse = thisPropsExpressions(renderBody).length > 0; + const canDestr = canDestructure(classDecl, render, renderBody); + const mayDestructure = destructuring && hasPropsUse && canDestr; + + if (destructuring && !canDestr) { + console.warn(`Unable to destructure ${name} props.`); + } + + const newBodyText = rewriteRenderBody(renderBody, source, mayDestructure, duplicates); + + let paramText = ""; + if (hasPropsUse) { + if (mayDestructure) { + paramText = renderDestructuredParam(names, objectType); + } else { + paramText = annotationText ? `props${annotationText}` : "props"; + } + } + + let replacement = renderFunctionLike(name, newBodyText, paramText, useArrows); + const statics = renderStaticAssignments(name, classDecl); + if (statics.length > 0) replacement += `\n\n${statics.join("\n")}`; + + const exportStmt = enclosingExportStatement(classDecl); + const isDefault = exportStmt ? isDefaultExportStatement(exportStmt) : false; + const splitDefault = isDefault && useArrows; + + let editStart: number; + let editEnd: number; + if (splitDefault) { + editStart = exportStmt!.range().start.index; + editEnd = exportStmt!.range().end.index; + replacement = `${replacement}\nexport default ${name};`; + } else { + editStart = classDecl.range().start.index; + editEnd = classDecl.range().end.index; + } + + edits.push({ startPos: editStart, endPos: editEnd, insertedText: replacement }); + transformedClassIds.add(classDecl.id()); + } + + if (edits.length === 0) return null; + + const aliasesToDrop = new Set(); + for (const alias of aliases.removableNamed) { + if (!remainingNamedImportUse(rootNode, alias, transformedClassIds)) { + aliasesToDrop.add(alias); + } + } + + if (aliasesToDrop.size > 0) { + for (const importStmt of rootNode.findAll({ rule: { kind: "import_statement" } })) { + if (!isReactSource(importStmt)) continue; + edits.push(...buildImportSpecifierEdits(importStmt, aliasesToDrop, source)); + } + } + + metric.increment({ file: metricFile(root.filename()) }, transformedClassIds.size); + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/pure-component/scripts/differential-tests.mjs b/codemods/pure-component/scripts/differential-tests.mjs new file mode 100644 index 0000000..c1ec390 --- /dev/null +++ b/codemods/pure-component/scripts/differential-tests.mjs @@ -0,0 +1,80 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { spawnSync } from "node:child_process"; + +const __filename = fileURLToPath(import.meta.url); +const packageDir = path.dirname(path.dirname(__filename)); +const codemodFile = path.join(packageDir, "scripts/codemod.ts"); +const testsDir = path.join(packageDir, "tests"); + +function readFixture(name, file) { + return readFileSync(path.join(testsDir, name, file), "utf8").trim(); +} + +function readParams(name) { + const configPath = path.join(testsDir, name, "test.config.json"); + try { + const config = JSON.parse(readFileSync(configPath, "utf8")); + return config.params ?? {}; + } catch { + return {}; + } +} + +function runJssgOutput(input, params = {}) { + const root = mkdtempSync(path.join(tmpdir(), "pure-component-jssg-")); + const caseDir = path.join(root, "case"); + mkdirSync(caseDir); + writeFileSync(path.join(caseDir, "input.tsx"), input, "utf8"); + writeFileSync(path.join(caseDir, "expected.tsx"), input, "utf8"); + if (Object.keys(params).length > 0) { + writeFileSync(path.join(caseDir, "test.config.json"), JSON.stringify({ params }, null, 2)); + } + + try { + const result = spawnSync( + "pnpm", + [ + "dlx", + "codemod@latest", + "jssg", + "test", + "-l", + "tsx", + codemodFile, + root, + "-u", + ], + { cwd: packageDir, encoding: "utf8", timeout: 20000 }, + ); + assert.strictEqual(result.status, 0, `${result.stdout}\n${result.stderr}`); + return readFileSync(path.join(caseDir, "expected.tsx"), "utf8").trim(); + } finally { + rmSync(root, { recursive: true, force: true }); + } +} + +const parityCases = [ + "named-import-component", + "named-import-purecomponent-alias", + "export-default-arrow", + "export-default-function", + "named-export", + "named-export-arrow", + "react-native-named-import", + "mixed-superclasses", +]; + +for (const name of parityCases) { + test(`matches checked-in parity fixture for ${name}`, () => { + const input = readFixture(name, "input.tsx"); + const expected = readFixture(name, "expected.tsx"); + const params = readParams(name); + const jssgOutput = runJssgOutput(input, params); + assert.strictEqual(jssgOutput, expected); + }); +} diff --git a/codemods/pure-component/scripts/warning-tests.mjs b/codemods/pure-component/scripts/warning-tests.mjs new file mode 100644 index 0000000..9940a0f --- /dev/null +++ b/codemods/pure-component/scripts/warning-tests.mjs @@ -0,0 +1,83 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { spawnSync } from "node:child_process"; + +const __filename = fileURLToPath(import.meta.url); +const packageDir = path.dirname(path.dirname(__filename)); +const codemodFile = path.join(packageDir, "scripts/codemod.ts"); + +function runFixtureTest(input, params = {}) { + const root = mkdtempSync(path.join(tmpdir(), "pure-component-warn-")); + const caseDir = path.join(root, "case"); + mkdirSync(caseDir); + writeFileSync(path.join(caseDir, "input.tsx"), input, "utf8"); + writeFileSync(path.join(caseDir, "expected.tsx"), input, "utf8"); + if (Object.keys(params).length > 0) { + writeFileSync(path.join(caseDir, "test.config.json"), JSON.stringify({ params }, null, 2)); + } + + try { + return spawnSync( + "pnpm", + [ + "dlx", + "codemod@latest", + "jssg", + "test", + "-l", + "tsx", + codemodFile, + root, + "--strictness", + "loose", + ], + { + cwd: packageDir, + encoding: "utf8", + timeout: 20000, + }, + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } +} + +test("warns when an impure class is skipped", () => { + const result = runFixtureTest(`import React from 'React'; +class Impure extends React.Component { + componentWillMount() {} + render() { return
; } +} +`); + const combined = `${result.stdout}\n${result.stderr}`; + assert.match(combined, /Class "Impure" skipped in/); +}); + +test("suppresses skip warnings with silenceWarnings", () => { + const result = runFixtureTest(`import React from 'React'; +class Impure extends React.Component { + componentWillMount() {} + render() { return
; } +} +`, { silenceWarnings: true }); + const combined = `${result.stdout}\n${result.stderr}`; + assert.doesNotMatch(combined, /Class "Impure" skipped in/); +}); + +test("warns when destructuring cannot be applied", () => { + const result = runFixtureTest(`import React from 'React'; +function doSomething(props) { return props; } +class UsesThisDotProps extends React.Component { + render() { + doSomething(this.props); + return
; + } +} +`, { destructuring: true }); + const combined = `${result.stdout}\n${result.stderr}`; + assert.match(combined, /Unable to destructure UsesThisDotProps props\./); +}); diff --git a/codemods/legacy/transforms/__testfixtures__/pure-component2.output.js b/codemods/pure-component/tests/arrow/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/pure-component2.output.js rename to codemods/pure-component/tests/arrow/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/pure-component2.input.js b/codemods/pure-component/tests/arrow/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/pure-component2.input.js rename to codemods/pure-component/tests/arrow/input.tsx diff --git a/codemods/pure-component/tests/arrow/metrics.json b/codemods/pure-component/tests/arrow/metrics.json new file mode 100644 index 0000000..66d8789 --- /dev/null +++ b/codemods/pure-component/tests/arrow/metrics.json @@ -0,0 +1,10 @@ +{ + "pure-component-conversions": [ + { + "cardinality": { + "file": "tests/arrow/input.tsx" + }, + "count": 5 + } + ] +} \ No newline at end of file diff --git a/codemods/pure-component/tests/arrow/test.config.json b/codemods/pure-component/tests/arrow/test.config.json new file mode 100644 index 0000000..8646dd5 --- /dev/null +++ b/codemods/pure-component/tests/arrow/test.config.json @@ -0,0 +1,5 @@ +{ + "params": { + "useArrows": true + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/pure-component.output.js b/codemods/pure-component/tests/default/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/pure-component.output.js rename to codemods/pure-component/tests/default/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/pure-component.input.js b/codemods/pure-component/tests/default/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/pure-component.input.js rename to codemods/pure-component/tests/default/input.tsx diff --git a/codemods/pure-component/tests/default/metrics.json b/codemods/pure-component/tests/default/metrics.json new file mode 100644 index 0000000..71a5466 --- /dev/null +++ b/codemods/pure-component/tests/default/metrics.json @@ -0,0 +1,10 @@ +{ + "pure-component-conversions": [ + { + "cardinality": { + "file": "tests/default/input.tsx" + }, + "count": 6 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/pure-component-destructuring.output.js b/codemods/pure-component/tests/destructuring/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/pure-component-destructuring.output.js rename to codemods/pure-component/tests/destructuring/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/pure-component-destructuring.input.js b/codemods/pure-component/tests/destructuring/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/pure-component-destructuring.input.js rename to codemods/pure-component/tests/destructuring/input.tsx diff --git a/codemods/pure-component/tests/destructuring/metrics.json b/codemods/pure-component/tests/destructuring/metrics.json new file mode 100644 index 0000000..4f66453 --- /dev/null +++ b/codemods/pure-component/tests/destructuring/metrics.json @@ -0,0 +1,10 @@ +{ + "pure-component-conversions": [ + { + "cardinality": { + "file": "tests/destructuring/input.tsx" + }, + "count": 6 + } + ] +} \ No newline at end of file diff --git a/codemods/pure-component/tests/destructuring/test.config.json b/codemods/pure-component/tests/destructuring/test.config.json new file mode 100644 index 0000000..dffb75a --- /dev/null +++ b/codemods/pure-component/tests/destructuring/test.config.json @@ -0,0 +1,5 @@ +{ + "params": { + "destructuring": true + } +} diff --git a/codemods/pure-component/tests/export-default-arrow/expected.tsx b/codemods/pure-component/tests/export-default-arrow/expected.tsx new file mode 100644 index 0000000..d8fe639 --- /dev/null +++ b/codemods/pure-component/tests/export-default-arrow/expected.tsx @@ -0,0 +1,4 @@ +import React from 'React'; + +const Pure = () => { return
; }; +export default Pure; diff --git a/codemods/pure-component/tests/export-default-arrow/input.tsx b/codemods/pure-component/tests/export-default-arrow/input.tsx new file mode 100644 index 0000000..0f4bd1f --- /dev/null +++ b/codemods/pure-component/tests/export-default-arrow/input.tsx @@ -0,0 +1,5 @@ +import React from 'React'; + +export default class Pure extends React.Component { + render() { return
; } +} diff --git a/codemods/pure-component/tests/export-default-arrow/metrics.json b/codemods/pure-component/tests/export-default-arrow/metrics.json new file mode 100644 index 0000000..93360c5 --- /dev/null +++ b/codemods/pure-component/tests/export-default-arrow/metrics.json @@ -0,0 +1,10 @@ +{ + "pure-component-conversions": [ + { + "cardinality": { + "file": "tests/export-default-arrow/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/pure-component/tests/export-default-arrow/test.config.json b/codemods/pure-component/tests/export-default-arrow/test.config.json new file mode 100644 index 0000000..8646dd5 --- /dev/null +++ b/codemods/pure-component/tests/export-default-arrow/test.config.json @@ -0,0 +1,5 @@ +{ + "params": { + "useArrows": true + } +} diff --git a/codemods/pure-component/tests/export-default-function/expected.tsx b/codemods/pure-component/tests/export-default-function/expected.tsx new file mode 100644 index 0000000..b87749b --- /dev/null +++ b/codemods/pure-component/tests/export-default-function/expected.tsx @@ -0,0 +1,3 @@ +import React from 'React'; + +export default function Pure() { return
; } diff --git a/codemods/pure-component/tests/export-default-function/input.tsx b/codemods/pure-component/tests/export-default-function/input.tsx new file mode 100644 index 0000000..0f4bd1f --- /dev/null +++ b/codemods/pure-component/tests/export-default-function/input.tsx @@ -0,0 +1,5 @@ +import React from 'React'; + +export default class Pure extends React.Component { + render() { return
; } +} diff --git a/codemods/pure-component/tests/export-default-function/metrics.json b/codemods/pure-component/tests/export-default-function/metrics.json new file mode 100644 index 0000000..5bcf0ec --- /dev/null +++ b/codemods/pure-component/tests/export-default-function/metrics.json @@ -0,0 +1,10 @@ +{ + "pure-component-conversions": [ + { + "cardinality": { + "file": "tests/export-default-function/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/pure-component/tests/mixed-superclasses/expected.tsx b/codemods/pure-component/tests/mixed-superclasses/expected.tsx new file mode 100644 index 0000000..f788443 --- /dev/null +++ b/codemods/pure-component/tests/mixed-superclasses/expected.tsx @@ -0,0 +1,7 @@ +import React, { PureComponent } from 'react'; + +function A(props) { return
; } + +class B extends PureComponent { + render() { return
; } +} diff --git a/codemods/pure-component/tests/mixed-superclasses/input.tsx b/codemods/pure-component/tests/mixed-superclasses/input.tsx new file mode 100644 index 0000000..dcf2b03 --- /dev/null +++ b/codemods/pure-component/tests/mixed-superclasses/input.tsx @@ -0,0 +1,9 @@ +import React, { Component, PureComponent } from 'react'; + +class A extends Component { + render() { return
; } +} + +class B extends PureComponent { + render() { return
; } +} diff --git a/codemods/pure-component/tests/mixed-superclasses/metrics.json b/codemods/pure-component/tests/mixed-superclasses/metrics.json new file mode 100644 index 0000000..b5fbb9c --- /dev/null +++ b/codemods/pure-component/tests/mixed-superclasses/metrics.json @@ -0,0 +1,10 @@ +{ + "pure-component-conversions": [ + { + "cardinality": { + "file": "tests/mixed-superclasses/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/pure-component/tests/named-export-arrow/expected.tsx b/codemods/pure-component/tests/named-export-arrow/expected.tsx new file mode 100644 index 0000000..9254115 --- /dev/null +++ b/codemods/pure-component/tests/named-export-arrow/expected.tsx @@ -0,0 +1,3 @@ +import React from 'react'; + +export const Pure = props => { return
; }; diff --git a/codemods/pure-component/tests/named-export-arrow/input.tsx b/codemods/pure-component/tests/named-export-arrow/input.tsx new file mode 100644 index 0000000..8708ae0 --- /dev/null +++ b/codemods/pure-component/tests/named-export-arrow/input.tsx @@ -0,0 +1,5 @@ +import React, { Component } from 'react'; + +export class Pure extends Component { + render() { return
; } +} diff --git a/codemods/pure-component/tests/named-export-arrow/metrics.json b/codemods/pure-component/tests/named-export-arrow/metrics.json new file mode 100644 index 0000000..2a89fa5 --- /dev/null +++ b/codemods/pure-component/tests/named-export-arrow/metrics.json @@ -0,0 +1,10 @@ +{ + "pure-component-conversions": [ + { + "cardinality": { + "file": "tests/named-export-arrow/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/pure-component/tests/named-export-arrow/test.config.json b/codemods/pure-component/tests/named-export-arrow/test.config.json new file mode 100644 index 0000000..8646dd5 --- /dev/null +++ b/codemods/pure-component/tests/named-export-arrow/test.config.json @@ -0,0 +1,5 @@ +{ + "params": { + "useArrows": true + } +} diff --git a/codemods/pure-component/tests/named-export/expected.tsx b/codemods/pure-component/tests/named-export/expected.tsx new file mode 100644 index 0000000..e234c87 --- /dev/null +++ b/codemods/pure-component/tests/named-export/expected.tsx @@ -0,0 +1,3 @@ +import React from 'react'; + +export function Pure(props) { return
; } diff --git a/codemods/pure-component/tests/named-export/input.tsx b/codemods/pure-component/tests/named-export/input.tsx new file mode 100644 index 0000000..8708ae0 --- /dev/null +++ b/codemods/pure-component/tests/named-export/input.tsx @@ -0,0 +1,5 @@ +import React, { Component } from 'react'; + +export class Pure extends Component { + render() { return
; } +} diff --git a/codemods/pure-component/tests/named-export/metrics.json b/codemods/pure-component/tests/named-export/metrics.json new file mode 100644 index 0000000..6ca6bf0 --- /dev/null +++ b/codemods/pure-component/tests/named-export/metrics.json @@ -0,0 +1,10 @@ +{ + "pure-component-conversions": [ + { + "cardinality": { + "file": "tests/named-export/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/pure-component/tests/named-import-component/expected.tsx b/codemods/pure-component/tests/named-import-component/expected.tsx new file mode 100644 index 0000000..904948f --- /dev/null +++ b/codemods/pure-component/tests/named-import-component/expected.tsx @@ -0,0 +1,3 @@ +import React from 'react'; + +function Pure(props) { return
; } diff --git a/codemods/pure-component/tests/named-import-component/input.tsx b/codemods/pure-component/tests/named-import-component/input.tsx new file mode 100644 index 0000000..2bd8659 --- /dev/null +++ b/codemods/pure-component/tests/named-import-component/input.tsx @@ -0,0 +1,5 @@ +import React, { Component } from 'react'; + +class Pure extends Component { + render() { return
; } +} diff --git a/codemods/pure-component/tests/named-import-component/metrics.json b/codemods/pure-component/tests/named-import-component/metrics.json new file mode 100644 index 0000000..d4d500b --- /dev/null +++ b/codemods/pure-component/tests/named-import-component/metrics.json @@ -0,0 +1,10 @@ +{ + "pure-component-conversions": [ + { + "cardinality": { + "file": "tests/named-import-component/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/pure-component/tests/named-import-purecomponent-alias/expected.tsx b/codemods/pure-component/tests/named-import-purecomponent-alias/expected.tsx new file mode 100644 index 0000000..0cb2bd5 --- /dev/null +++ b/codemods/pure-component/tests/named-import-purecomponent-alias/expected.tsx @@ -0,0 +1,3 @@ +import React, { PureComponent as P } from 'react'; + +function Pure(props) { return
; } diff --git a/codemods/pure-component/tests/named-import-purecomponent-alias/input.tsx b/codemods/pure-component/tests/named-import-purecomponent-alias/input.tsx new file mode 100644 index 0000000..270c814 --- /dev/null +++ b/codemods/pure-component/tests/named-import-purecomponent-alias/input.tsx @@ -0,0 +1,5 @@ +import React, { PureComponent as P } from 'react'; + +class Pure extends P { + render() { return
; } +} diff --git a/codemods/pure-component/tests/named-import-purecomponent-alias/metrics.json b/codemods/pure-component/tests/named-import-purecomponent-alias/metrics.json new file mode 100644 index 0000000..efaa063 --- /dev/null +++ b/codemods/pure-component/tests/named-import-purecomponent-alias/metrics.json @@ -0,0 +1,10 @@ +{ + "pure-component-conversions": [ + { + "cardinality": { + "file": "tests/named-import-purecomponent-alias/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/pure-component/tests/react-native-named-import/expected.tsx b/codemods/pure-component/tests/react-native-named-import/expected.tsx new file mode 100644 index 0000000..6901793 --- /dev/null +++ b/codemods/pure-component/tests/react-native-named-import/expected.tsx @@ -0,0 +1,3 @@ +import 'react-native'; + +function Pure(props) { return
; } diff --git a/codemods/pure-component/tests/react-native-named-import/input.tsx b/codemods/pure-component/tests/react-native-named-import/input.tsx new file mode 100644 index 0000000..56e7352 --- /dev/null +++ b/codemods/pure-component/tests/react-native-named-import/input.tsx @@ -0,0 +1,5 @@ +import { Component } from 'react-native'; + +class Pure extends Component { + render() { return
; } +} diff --git a/codemods/pure-component/tests/react-native-named-import/metrics.json b/codemods/pure-component/tests/react-native-named-import/metrics.json new file mode 100644 index 0000000..419d2d6 --- /dev/null +++ b/codemods/pure-component/tests/react-native-named-import/metrics.json @@ -0,0 +1,10 @@ +{ + "pure-component-conversions": [ + { + "cardinality": { + "file": "tests/react-native-named-import/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-string-ref/tsconfig.json b/codemods/pure-component/tsconfig.json similarity index 100% rename from codemods/jssg/replace-string-ref/tsconfig.json rename to codemods/pure-component/tsconfig.json diff --git a/codemods/pure-component/workflow.yaml b/codemods/pure-component/workflow.yaml new file mode 100644 index 0000000..8773841 --- /dev/null +++ b/codemods/pure-component/workflow.yaml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: "Scan tsx files and apply fixes" + js-ast-grep: + js_file: scripts/codemod.ts + language: "tsx" diff --git a/codemods/pure-render-mixin/README.md b/codemods/pure-render-mixin/README.md new file mode 100644 index 0000000..3f8e5ac --- /dev/null +++ b/codemods/pure-render-mixin/README.md @@ -0,0 +1,14 @@ +# pure-render-mixin + +Replace `PureRenderMixin` with `shouldComponentUpdate` using `React.addons.shallowCompare`. + +## Usage + +```bash +npx codemod @react-new/pure-render-mixin --target +``` + +## Options + +- `mixin-name`: custom mixin identifier to replace. Default: `PureRenderMixin`. +- `explicit-require`: when `false`, run even if no React import/require is present. Default: `true`. diff --git a/codemods/pure-render-mixin/codemod.yaml b/codemods/pure-render-mixin/codemod.yaml new file mode 100644 index 0000000..85ef2df --- /dev/null +++ b/codemods/pure-render-mixin/codemod.yaml @@ -0,0 +1,19 @@ +schema_version: "1.0" + +name: "@react-new/pure-render-mixin" +version: "0.1.1" +description: "Replace PureRenderMixin with shouldComponentUpdate using React.addons.shallowCompare" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + +targets: + languages: ["tsx"] + +keywords: ["React"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/pure-render-mixin/package.json b/codemods/pure-render-mixin/package.json new file mode 100644 index 0000000..e7e6f71 --- /dev/null +++ b/codemods/pure-render-mixin/package.json @@ -0,0 +1,15 @@ +{ + "name": "@react-new/pure-render-mixin", + "version": "0.1.1", + "description": "Replace PureRenderMixin with shouldComponentUpdate using React.addons.shallowCompare", + "type": "module", + "scripts": { + "test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@codemod.com/jssg-types": "latest", + "typescript": "latest", + "@types/node": "latest" + } +} diff --git a/codemods/pure-render-mixin/scripts/codemod.ts b/codemods/pure-render-mixin/scripts/codemod.ts new file mode 100644 index 0000000..bf6180d --- /dev/null +++ b/codemods/pure-render-mixin/scripts/codemod.ts @@ -0,0 +1,244 @@ +import type { Transform, Edit, SgNode } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { useMetricAtom } from "codemod:metrics"; + +const REACT_MODULES = new Set(["React", "react", "react/addons", "react-native"]); + +function metricFile(filename: string): string { + const cwd = process.cwd() + "/"; + return filename.startsWith(cwd) ? filename.slice(cwd.length) : filename; +} + +function stringValue(node: SgNode): string | null { + const fragment = node.find({ rule: { kind: "string_fragment" } }); + if (fragment) return fragment.text(); + const text = node.text(); + return text.length >= 2 ? text.slice(1, -1) : null; +} + +function callArguments(call: SgNode): SgNode[] { + const args = call.field("arguments"); + if (!args) return []; + return args.children().filter((child) => child.isNamed() && child.kind() !== "comment"); +} + +function requireSource(call: SgNode): string | null { + const callee = call.field("function"); + if (!callee || callee.kind() !== "identifier" || callee.text() !== "require") return null; + const firstArg = callArguments(call)[0]; + return firstArg?.kind() === "string" ? stringValue(firstArg) : null; +} + +function importSource(node: SgNode): string | null { + const source = node.field("source") ?? node.find({ rule: { kind: "string" } }); + return source ? stringValue(source) : null; +} + +function hasReact(rootNode: SgNode): boolean { + for (const importNode of rootNode.findAll({ rule: { kind: "import_statement" } })) { + if (REACT_MODULES.has(importSource(importNode) ?? "")) return true; + } + + for (const call of rootNode.findAll({ rule: { kind: "call_expression" } })) { + if (REACT_MODULES.has(requireSource(call) ?? "")) return true; + } + + return false; +} + +function explicitRequireDisabled(value: unknown): boolean { + return value === false || value === "false"; +} + +function isReactCreateClassCall(node: SgNode | null): node is SgNode { + if (!node || node.kind() !== "call_expression") return false; + const callee = node.field("function"); + return callee?.kind() === "member_expression" && + callee.field("object")?.kind() === "identifier" && + callee.field("object")?.text() === "React" && + callee.field("property")?.kind() === "property_identifier" && + callee.field("property")?.text() === "createClass"; +} + +function reactCreateClassDeclarators(rootNode: SgNode): Array<{ + declarator: SgNode; + call: SgNode; +}> { + const result: Array<{ declarator: SgNode; call: SgNode }> = []; + for (const declarator of rootNode.findAll({ rule: { kind: "variable_declarator" } })) { + const value = declarator.field("value"); + if (isReactCreateClassCall(value)) result.push({ declarator, call: value }); + } + return result; +} + + +const DEFAULT_MIXIN_NAME = "PureRenderMixin"; +const SHOULD_COMPONENT_UPDATE = "shouldComponentUpdate"; +const RENDER = "render"; + +function getConfigObject(call: SgNode): SgNode | null { + const args = call.field("arguments"); + if (!args) return null; + return args.find({ rule: { kind: "object" } }); +} + +function pairWithKeyRule(keyName: string) { + const escaped = keyName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return { + kind: "pair" as const, + has: { + field: "key", + any: [ + { kind: "property_identifier" as const, regex: `^${escaped}$` }, + { kind: "identifier" as const, regex: `^${escaped}$` }, + ], + }, + }; +} + +const PAIR_RULE = { rule: { kind: "pair" as const } }; + +function arrayIdentifierNamedRule(name: string) { + const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return { rule: { kind: "identifier" as const, regex: `^${escaped}$` } }; +} + +function getPairKeyName(node: SgNode): string { + return node.field("key")?.text() ?? node.field("name")?.text() ?? ""; +} + +function declarationRemovalRange(varDecl: SgNode, source: string): { start: number; end: number } { + let start = varDecl.range().start.index; + let end = varDecl.range().end.index; + const before = source.slice(0, start); + const after = source.slice(end); + if (after.startsWith("\n\n")) { + end += 1; + } + if (before.endsWith("\n\n") && after.startsWith("\n\n")) { + start -= 1; + } + return { start, end }; +} + +function shouldUpdatePropertyText(): string { + return `${SHOULD_COMPONENT_UPDATE}: function(nextProps, nextState) {\n return React.addons.shallowCompare(this, nextProps, nextState);\n }`; +} + +function renderConfigObject(configObj: SgNode, mixinsPair: SgNode, mixinName: string): string | null { + const mixinsValue = mixinsPair.field("value"); + if (!mixinsValue || mixinsValue.kind() !== "array") return null; + const mixinInArray = mixinsValue.findAll(arrayIdentifierNamedRule(mixinName)); + if (mixinInArray.length === 0) return null; + + const toRemoveSet = new Set(mixinInArray.map((n) => n.range().start.index)); + const kept = mixinsValue.children().filter( + (c) => + c.kind() !== "[" && + c.kind() !== "]" && + c.kind() !== "," && + !toRemoveSet.has(c.range().start.index), + ); + + const pairs = configObj.findAll(PAIR_RULE) + .sort((a, b) => a.range().start.index - b.range().start.index); + const renderedProps: string[] = []; + let renderIndex = -1; + + for (const pair of pairs) { + if (pair.id() === mixinsPair.id()) { + if (kept.length > 0) { + renderedProps.push(`mixins: [${kept.map((e) => e.text()).join(", ")}]`); + } + continue; + } + + if (getPairKeyName(pair) === RENDER) { + renderIndex = renderedProps.length; + } + renderedProps.push(pair.text()); + } + + if (renderedProps.length === 0) return null; + + const shouldUpdateText = shouldUpdatePropertyText(); + if (renderIndex >= 0 && renderIndex === renderedProps.length - 1) { + renderedProps.splice(renderIndex, 0, shouldUpdateText); + } else { + renderedProps.push(shouldUpdateText); + } + + return `{\n ${renderedProps.join(",\n\n ")},\n}`; +} + +const transform: Transform = async (root, options) => { + const rootNode = root.root(); + if (!explicitRequireDisabled(options.params?.["explicit-require"]) && !hasReact(rootNode)) { + return null; + } + + const source = rootNode.text(); + const edits: Edit[] = []; + + const mixinName = (options.params?.["mixin-name"] as string) ?? DEFAULT_MIXIN_NAME; + const metric = useMetricAtom("pure-render-mixin-replacements"); + let transformCount = 0; + + for (const { call } of reactCreateClassDeclarators(rootNode)) { + const configObj = getConfigObject(call); + if (!configObj) continue; + + const mixinsPair = configObj.find({ rule: pairWithKeyRule("mixins") }); + const hasShouldUpdate = configObj.find({ rule: pairWithKeyRule(SHOULD_COMPONENT_UPDATE) }) !== null; + const renderPair = configObj.find({ rule: pairWithKeyRule(RENDER) }); + + if (!mixinsPair || hasShouldUpdate || !renderPair) continue; + + const value = mixinsPair.field("value"); + if (!value || value.kind() !== "array") continue; + const mixinInArray = value.findAll(arrayIdentifierNamedRule(mixinName)); + if (mixinInArray.length === 0) continue; + + const newConfigText = renderConfigObject(configObj, mixinsPair, mixinName); + if (!newConfigText) continue; + edits.push(configObj.replace(newConfigText)); + + transformCount++; + metric.increment({ file: metricFile(root.filename()) }); + } + + if (edits.length > 0 && transformCount > 0) { + const escapedMixin = mixinName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const mixinVarDeclarators = rootNode.findAll({ + rule: { + kind: "variable_declarator", + has: { field: "name", kind: "identifier" as const, regex: `^${escapedMixin}$` }, + }, + }); + const mixinIdentifiers = rootNode.findAll({ + rule: { kind: "identifier" as const, regex: `^${escapedMixin}$` }, + }); + if (mixinIdentifiers.length <= 2) { + for (const decl of mixinVarDeclarators) { + const varDecl = decl.parent(); + if (!varDecl || varDecl.kind() !== "variable_declaration") continue; + const decls = varDecl.findAll({ rule: { kind: "variable_declarator" } }); + if (decls.length !== 1) continue; + const after = source.slice(varDecl.range().end.index, varDecl.range().end.index + 10); + const range = declarationRemovalRange(varDecl, source); + edits.push({ + startPos: range.start, + endPos: range.end, + insertedText: "", + }); + break; + } + } + } + + if (edits.length === 0) return null; + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/pure-render-mixin/tests/identifier-create-class-no-change/expected.tsx b/codemods/pure-render-mixin/tests/identifier-create-class-no-change/expected.tsx new file mode 100644 index 0000000..bd81dcc --- /dev/null +++ b/codemods/pure-render-mixin/tests/identifier-create-class-no-change/expected.tsx @@ -0,0 +1,11 @@ +var React = require('react/addons'); +var PureRenderMixin = React.addons.PureRenderMixin; +var createClass = require('create-react-class'); + +var C = createClass({ + mixins: [PureRenderMixin], + + render: function() { + return
; + }, +}); diff --git a/codemods/pure-render-mixin/tests/identifier-create-class-no-change/input.tsx b/codemods/pure-render-mixin/tests/identifier-create-class-no-change/input.tsx new file mode 100644 index 0000000..bd81dcc --- /dev/null +++ b/codemods/pure-render-mixin/tests/identifier-create-class-no-change/input.tsx @@ -0,0 +1,11 @@ +var React = require('react/addons'); +var PureRenderMixin = React.addons.PureRenderMixin; +var createClass = require('create-react-class'); + +var C = createClass({ + mixins: [PureRenderMixin], + + render: function() { + return
; + }, +}); diff --git a/codemods/pure-render-mixin/tests/no-react-explicit-require/expected.tsx b/codemods/pure-render-mixin/tests/no-react-explicit-require/expected.tsx new file mode 100644 index 0000000..47bd5ed --- /dev/null +++ b/codemods/pure-render-mixin/tests/no-react-explicit-require/expected.tsx @@ -0,0 +1,10 @@ + +var C = React.createClass({ + shouldComponentUpdate: function(nextProps, nextState) { + return React.addons.shallowCompare(this, nextProps, nextState); + }, + + render: function() { + return
; + }, +}); diff --git a/codemods/pure-render-mixin/tests/no-react-explicit-require/input.tsx b/codemods/pure-render-mixin/tests/no-react-explicit-require/input.tsx new file mode 100644 index 0000000..13cd748 --- /dev/null +++ b/codemods/pure-render-mixin/tests/no-react-explicit-require/input.tsx @@ -0,0 +1,9 @@ +var PureRenderMixin = React.addons.PureRenderMixin; + +var C = React.createClass({ + mixins: [PureRenderMixin], + + render: function() { + return
; + }, +}); diff --git a/codemods/pure-render-mixin/tests/no-react-explicit-require/metrics.json b/codemods/pure-render-mixin/tests/no-react-explicit-require/metrics.json new file mode 100644 index 0000000..fd0f9ae --- /dev/null +++ b/codemods/pure-render-mixin/tests/no-react-explicit-require/metrics.json @@ -0,0 +1,10 @@ +{ + "pure-render-mixin-replacements": [ + { + "cardinality": { + "file": "tests/no-react-explicit-require/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/pure-render-mixin/tests/no-react-explicit-require/test.config.json b/codemods/pure-render-mixin/tests/no-react-explicit-require/test.config.json new file mode 100644 index 0000000..4858435 --- /dev/null +++ b/codemods/pure-render-mixin/tests/no-react-explicit-require/test.config.json @@ -0,0 +1,5 @@ +{ + "params": { + "explicit-require": false + } +} diff --git a/codemods/pure-render-mixin/tests/no-react-no-change/expected.tsx b/codemods/pure-render-mixin/tests/no-react-no-change/expected.tsx new file mode 100644 index 0000000..13cd748 --- /dev/null +++ b/codemods/pure-render-mixin/tests/no-react-no-change/expected.tsx @@ -0,0 +1,9 @@ +var PureRenderMixin = React.addons.PureRenderMixin; + +var C = React.createClass({ + mixins: [PureRenderMixin], + + render: function() { + return
; + }, +}); diff --git a/codemods/pure-render-mixin/tests/no-react-no-change/input.tsx b/codemods/pure-render-mixin/tests/no-react-no-change/input.tsx new file mode 100644 index 0000000..13cd748 --- /dev/null +++ b/codemods/pure-render-mixin/tests/no-react-no-change/input.tsx @@ -0,0 +1,9 @@ +var PureRenderMixin = React.addons.PureRenderMixin; + +var C = React.createClass({ + mixins: [PureRenderMixin], + + render: function() { + return
; + }, +}); diff --git a/codemods/legacy/transforms/__testfixtures__/pure-render-mixin.output.js b/codemods/pure-render-mixin/tests/pure-render-mixin/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/pure-render-mixin.output.js rename to codemods/pure-render-mixin/tests/pure-render-mixin/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/pure-render-mixin.input.js b/codemods/pure-render-mixin/tests/pure-render-mixin/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/pure-render-mixin.input.js rename to codemods/pure-render-mixin/tests/pure-render-mixin/input.tsx diff --git a/codemods/pure-render-mixin/tests/pure-render-mixin/metrics.json b/codemods/pure-render-mixin/tests/pure-render-mixin/metrics.json new file mode 100644 index 0000000..9abd990 --- /dev/null +++ b/codemods/pure-render-mixin/tests/pure-render-mixin/metrics.json @@ -0,0 +1,10 @@ +{ + "pure-render-mixin-replacements": [ + { + "cardinality": { + "file": "tests/pure-render-mixin/input.tsx" + }, + "count": 3 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/pure-render-mixin2.output.js b/codemods/pure-render-mixin/tests/pure-render-mixin2/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/pure-render-mixin2.output.js rename to codemods/pure-render-mixin/tests/pure-render-mixin2/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/pure-render-mixin2.input.js b/codemods/pure-render-mixin/tests/pure-render-mixin2/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/pure-render-mixin2.input.js rename to codemods/pure-render-mixin/tests/pure-render-mixin2/input.tsx diff --git a/codemods/pure-render-mixin/tests/pure-render-mixin2/metrics.json b/codemods/pure-render-mixin/tests/pure-render-mixin2/metrics.json new file mode 100644 index 0000000..40a2653 --- /dev/null +++ b/codemods/pure-render-mixin/tests/pure-render-mixin2/metrics.json @@ -0,0 +1,10 @@ +{ + "pure-render-mixin-replacements": [ + { + "cardinality": { + "file": "tests/pure-render-mixin2/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/pure-render-mixin3.output.js b/codemods/pure-render-mixin/tests/pure-render-mixin3/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/pure-render-mixin3.output.js rename to codemods/pure-render-mixin/tests/pure-render-mixin3/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/pure-render-mixin3.input.js b/codemods/pure-render-mixin/tests/pure-render-mixin3/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/pure-render-mixin3.input.js rename to codemods/pure-render-mixin/tests/pure-render-mixin3/input.tsx diff --git a/codemods/pure-render-mixin/tests/pure-render-mixin3/metrics.json b/codemods/pure-render-mixin/tests/pure-render-mixin3/metrics.json new file mode 100644 index 0000000..90c32c0 --- /dev/null +++ b/codemods/pure-render-mixin/tests/pure-render-mixin3/metrics.json @@ -0,0 +1,10 @@ +{ + "pure-render-mixin-replacements": [ + { + "cardinality": { + "file": "tests/pure-render-mixin3/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/pure-render-mixin4.output.js b/codemods/pure-render-mixin/tests/pure-render-mixin4/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/pure-render-mixin4.output.js rename to codemods/pure-render-mixin/tests/pure-render-mixin4/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/pure-render-mixin4.input.js b/codemods/pure-render-mixin/tests/pure-render-mixin4/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/pure-render-mixin4.input.js rename to codemods/pure-render-mixin/tests/pure-render-mixin4/input.tsx diff --git a/codemods/pure-render-mixin/tests/pure-render-mixin4/metrics.json b/codemods/pure-render-mixin/tests/pure-render-mixin4/metrics.json new file mode 100644 index 0000000..2f39039 --- /dev/null +++ b/codemods/pure-render-mixin/tests/pure-render-mixin4/metrics.json @@ -0,0 +1,10 @@ +{ + "pure-render-mixin-replacements": [ + { + "cardinality": { + "file": "tests/pure-render-mixin4/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/pure-render-mixin/tests/pure-render-mixin4/test.config.json b/codemods/pure-render-mixin/tests/pure-render-mixin4/test.config.json new file mode 100644 index 0000000..b9f5c63 --- /dev/null +++ b/codemods/pure-render-mixin/tests/pure-render-mixin4/test.config.json @@ -0,0 +1,5 @@ +{ + "params": { + "mixin-name": "ReactComponentWithPureRenderMixin" + } +} diff --git a/codemods/jssg/replace-use-form-state/tsconfig.json b/codemods/pure-render-mixin/tsconfig.json similarity index 100% rename from codemods/jssg/replace-use-form-state/tsconfig.json rename to codemods/pure-render-mixin/tsconfig.json diff --git a/codemods/pure-render-mixin/workflow.yaml b/codemods/pure-render-mixin/workflow.yaml new file mode 100644 index 0000000..0f8367a --- /dev/null +++ b/codemods/pure-render-mixin/workflow.yaml @@ -0,0 +1,21 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +params: + schema: + mixin-name: + name: "Mixin Name" + description: "Identifier name for the pure render mixin to replace." + type: string + default: "PureRenderMixin" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: "Scan tsx files and apply fixes" + js-ast-grep: + js_file: scripts/codemod.ts + language: "tsx" diff --git a/codemods/jssg/react-19-migration-recipe/README.md b/codemods/react-19-migration-recipe/README.md similarity index 100% rename from codemods/jssg/react-19-migration-recipe/README.md rename to codemods/react-19-migration-recipe/README.md diff --git a/codemods/jssg/react-19-migration-recipe/codemod.yaml b/codemods/react-19-migration-recipe/codemod.yaml similarity index 95% rename from codemods/jssg/react-19-migration-recipe/codemod.yaml rename to codemods/react-19-migration-recipe/codemod.yaml index aff2ebe..672fed5 100644 --- a/codemods/jssg/react-19-migration-recipe/codemod.yaml +++ b/codemods/react-19-migration-recipe/codemod.yaml @@ -1,7 +1,7 @@ schema_version: "1.0" name: "@react-new/react-19-migration-recipe" -version: "0.1.0" +version: "0.1.1" description: "Run all React 19 migration codemods in sequence" author: "Codemod " license: "MIT" diff --git a/codemods/jssg/react-19-migration-recipe/package.json b/codemods/react-19-migration-recipe/package.json similarity index 89% rename from codemods/jssg/react-19-migration-recipe/package.json rename to codemods/react-19-migration-recipe/package.json index 3579557..dff3c27 100644 --- a/codemods/jssg/react-19-migration-recipe/package.json +++ b/codemods/react-19-migration-recipe/package.json @@ -1,6 +1,6 @@ { "name": "@react-new/react-19-migration-recipe", - "version": "0.1.0", + "version": "0.1.1", "description": "Run all React 19 migration codemods in sequence", "type": "module", "private": true, diff --git a/codemods/jssg/react-19-migration-recipe/workflow.yaml b/codemods/react-19-migration-recipe/workflow.yaml similarity index 100% rename from codemods/jssg/react-19-migration-recipe/workflow.yaml rename to codemods/react-19-migration-recipe/workflow.yaml diff --git a/codemods/react-dom-to-react-dom-factories/README.md b/codemods/react-dom-to-react-dom-factories/README.md new file mode 100644 index 0000000..f4eadec --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/README.md @@ -0,0 +1,9 @@ +# react-dom-to-react-dom-factories + +Replace `React.DOM.*(...)` and `DOM.*(...)` factory calls with `createElement(...)`. + +## Usage + +```bash +npx codemod @react-new/react-dom-to-react-dom-factories --target +``` diff --git a/codemods/react-dom-to-react-dom-factories/codemod.yaml b/codemods/react-dom-to-react-dom-factories/codemod.yaml new file mode 100644 index 0000000..5326f06 --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/codemod.yaml @@ -0,0 +1,19 @@ +schema_version: "1.0" + +name: "@react-new/react-dom-to-react-dom-factories" +version: "0.1.1" +description: "Replace React.DOM factories with createElement calls" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + +targets: + languages: ["tsx"] + +keywords: ["React", "DOM"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/react-dom-to-react-dom-factories/package.json b/codemods/react-dom-to-react-dom-factories/package.json new file mode 100644 index 0000000..cbdf42b --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/package.json @@ -0,0 +1,18 @@ +{ + "name": "@react-new/react-dom-to-react-dom-factories", + "version": "0.1.1", + "description": "Replace React.DOM factories with createElement calls", + "type": "module", + "scripts": { + "test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts --strictness loose", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@codemod.com/jssg-types": "latest", + "@types/node": "latest", + "typescript": "latest" + }, + "dependencies": { + "@jssg/utils": "^0.0.2" + } +} diff --git a/codemods/react-dom-to-react-dom-factories/scripts/codemod.ts b/codemods/react-dom-to-react-dom-factories/scripts/codemod.ts new file mode 100644 index 0000000..41d61f9 --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/scripts/codemod.ts @@ -0,0 +1,231 @@ +import type { Transform, Edit, SgNode } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { useMetricAtom } from "codemod:metrics"; +import { getImport } from "@jssg/utils/javascript/imports"; + +function metricFile(filename: string): string { + const cwd = process.cwd() + "/"; + return filename.startsWith(cwd) ? filename.slice(cwd.length) : filename; +} + +function namedChildren(node: SgNode | null): SgNode[] { + if (!node) { + return []; + } + + return node.children().filter((child) => child.isNamed() && child.kind() !== "comment"); +} + +function stringValue(node: SgNode): string { + const evaluated = Function(`"use strict"; return (${node.text()});`)() as unknown; + if (typeof evaluated !== "string") { + throw new Error(`Expected string literal, got "${typeof evaluated}"`); + } + + return evaluated; +} + +function callArguments(call: SgNode): SgNode[] { + return namedChildren(call.field("arguments")); +} + +type InlineEdit = { + start: number; + end: number; + text: string; +}; + +function isReactRequireCall(node: SgNode | null): boolean { + if (!node || node.kind() !== "call_expression") { + return false; + } + + const callee = node.field("function"); + const firstArg = callArguments(node)[0]; + return callee?.kind() === "identifier" && + callee.text() === "require" && + firstArg?.kind() === "string" && + stringValue(firstArg) === "react"; +} + +function isReactIdentifier(node: SgNode | null): boolean { + return node?.kind() === "identifier" && node.text() === "React"; +} + +function isReactDomFactoryMember(callee: SgNode): { factory: string; target: string } | null { + if (callee.kind() !== "member_expression") { + return null; + } + + const property = callee.field("property"); + const object = callee.field("object"); + if (!property || !object || property.kind() !== "property_identifier") { + return null; + } + + if (object.kind() === "identifier" && object.text() === "DOM") { + return { factory: property.text(), target: "createElement" }; + } + + if (object.kind() !== "member_expression") { + return null; + } + + const outerProperty = object.field("property"); + const outerObject = object.field("object"); + if (!outerProperty || !outerObject) { + return null; + } + + if (outerProperty.text() === "DOM" && isReactIdentifier(outerObject)) { + return { factory: property.text(), target: "React.createElement" }; + } + + return null; +} + +function isReactDomDestructurePattern(node: SgNode): boolean { + if (node.kind() !== "object_pattern") { + return false; + } + + const declarator = node.parent(); + if (!declarator || declarator.kind() !== "variable_declarator") { + return false; + } + + const init = declarator.field("value"); + if (!(isReactIdentifier(init) || isReactRequireCall(init))) { + return false; + } + + return node.findAll({ rule: { kind: "shorthand_property_identifier_pattern" } }) + .some((property) => property.text() === "DOM"); +} + +function matchedFactoryCallInfo( + call: SgNode, + hasReactDomBinding: boolean, +): { factory: string; target: string } | null { + const callee = call.field("function"); + if (!callee) { + return null; + } + + const match = isReactDomFactoryMember(callee); + if (!match) { + return null; + } + + if (match.target === "createElement" && !hasReactDomBinding) { + return null; + } + + return match; +} + +function applyInlineEdits(source: string, edits: InlineEdit[]): string { + return [...edits] + .sort((a, b) => b.start - a.start) + .reduce( + (text, edit) => text.slice(0, edit.start) + edit.text + text.slice(edit.end), + source, + ); +} + +function rewriteNodeText(node: SgNode, hasReactDomBinding: boolean): string { + const callMatch = node.kind() === "call_expression" ? matchedFactoryCallInfo(node, hasReactDomBinding) : null; + if (callMatch) { + const argsText = callArguments(node) + .map((arg) => rewriteNodeText(arg, hasReactDomBinding)) + .join(", "); + const prefix = argsText.length > 0 ? ", " : ""; + return `${callMatch.target}('${callMatch.factory}'${prefix}${argsText})`; + } + + const source = node.text(); + const nodeStart = node.range().start.index; + const edits: InlineEdit[] = []; + + for (const child of namedChildren(node)) { + const rewritten = rewriteNodeText(child, hasReactDomBinding); + if (rewritten === child.text()) { + continue; + } + + edits.push({ + start: child.range().start.index - nodeStart, + end: child.range().end.index - nodeStart, + text: rewritten, + }); + } + + return edits.length > 0 ? applyInlineEdits(source, edits) : source; +} + +const transform: Transform = async (root) => { + const rootNode = root.root(); + const edits: Edit[] = []; + const metric = useMetricAtom("react-dom-factory-replacements"); + + const reactDomImport = getImport(rootNode, { type: "named", name: "DOM", from: "react" }); + const hasReactDomPattern = rootNode.findAll({ rule: { kind: "object_pattern" } }) + .some(isReactDomDestructurePattern); + const hasReactDomBinding = reactDomImport !== null || hasReactDomPattern; + + const matchedCalls = rootNode.findAll({ rule: { kind: "call_expression" } }) + .filter((call) => matchedFactoryCallInfo(call, hasReactDomBinding) !== null); + + for (const call of matchedCalls) { + const hasMatchedAncestor = call.ancestors().some((ancestor) => + ancestor.kind() === "call_expression" && + matchedFactoryCallInfo(ancestor, hasReactDomBinding) !== null + ); + if (hasMatchedAncestor) { + continue; + } + + edits.push(call.replace(rewriteNodeText(call, hasReactDomBinding))); + } + + for (const importSpec of rootNode.findAll({ rule: { kind: "import_specifier" } })) { + const imported = importSpec.field("name"); + if (!imported || imported.text() !== "DOM") { + continue; + } + + const importStmt = importSpec.ancestors().find((ancestor) => ancestor.kind() === "import_statement"); + if (!importStmt) { + continue; + } + + const source = importStmt.field("source"); + if (!source || source.kind() !== "string" || stringValue(source) !== "react") { + continue; + } + + edits.push(imported.replace("createElement")); + } + + for (const shorthand of rootNode.findAll({ rule: { kind: "shorthand_property_identifier_pattern" } })) { + if (shorthand.text() !== "DOM") { + continue; + } + + const pattern = shorthand.ancestors().find((ancestor) => ancestor.kind() === "object_pattern"); + if (!pattern || !isReactDomDestructurePattern(pattern)) { + continue; + } + + edits.push(shorthand.replace("createElement")); + } + + if (edits.length === 0) { + return null; + } + + metric.increment({ file: metricFile(root.filename()) }, matchedCalls.length); + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-basic-case.output.js b/codemods/react-dom-to-react-dom-factories/tests/react-dom-basic-case/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-basic-case.output.js rename to codemods/react-dom-to-react-dom-factories/tests/react-dom-basic-case/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-basic-case.input.js b/codemods/react-dom-to-react-dom-factories/tests/react-dom-basic-case/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-basic-case.input.js rename to codemods/react-dom-to-react-dom-factories/tests/react-dom-basic-case/input.tsx diff --git a/codemods/react-dom-to-react-dom-factories/tests/react-dom-basic-case/metrics.json b/codemods/react-dom-to-react-dom-factories/tests/react-dom-basic-case/metrics.json new file mode 100644 index 0000000..6b0f717 --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/tests/react-dom-basic-case/metrics.json @@ -0,0 +1,10 @@ +{ + "react-dom-factory-replacements": [ + { + "cardinality": { + "file": "tests/react-dom-basic-case/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-import.output.js b/codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-import/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-import.output.js rename to codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-import/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-import.input.js b/codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-import/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-import.input.js rename to codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-import/input.tsx diff --git a/codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-import/metrics.json b/codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-import/metrics.json new file mode 100644 index 0000000..a44f433 --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-import/metrics.json @@ -0,0 +1,10 @@ +{ + "react-dom-factory-replacements": [ + { + "cardinality": { + "file": "tests/react-dom-deconstructed-import/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require-part-two.output.js b/codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-require-part-two/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require-part-two.output.js rename to codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-require-part-two/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require-part-two.input.js b/codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-require-part-two/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require-part-two.input.js rename to codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-require-part-two/input.tsx diff --git a/codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-require-part-two/metrics.json b/codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-require-part-two/metrics.json new file mode 100644 index 0000000..8528c73 --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-require-part-two/metrics.json @@ -0,0 +1,10 @@ +{ + "react-dom-factory-replacements": [ + { + "cardinality": { + "file": "tests/react-dom-deconstructed-require-part-two/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require.output.js b/codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-require/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require.output.js rename to codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-require/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require.input.js b/codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-require/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require.input.js rename to codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-require/input.tsx diff --git a/codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-require/metrics.json b/codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-require/metrics.json new file mode 100644 index 0000000..437351b --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/tests/react-dom-deconstructed-require/metrics.json @@ -0,0 +1,10 @@ +{ + "react-dom-factory-replacements": [ + { + "cardinality": { + "file": "tests/react-dom-deconstructed-require/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/react-dom-to-react-dom-factories/tests/react-dom-nested-call-case/expected.tsx b/codemods/react-dom-to-react-dom-factories/tests/react-dom-nested-call-case/expected.tsx new file mode 100644 index 0000000..d733ff1 --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/tests/react-dom-nested-call-case/expected.tsx @@ -0,0 +1,11 @@ +const React = require('react'); + +class Hello extends React.Component { + render() { + return React.createElement( + 'p', + null, + React.createElement('a', { href: '#hello' }, this.props.children) + ); + } +} diff --git a/codemods/react-dom-to-react-dom-factories/tests/react-dom-nested-call-case/input.tsx b/codemods/react-dom-to-react-dom-factories/tests/react-dom-nested-call-case/input.tsx new file mode 100644 index 0000000..c699c1b --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/tests/react-dom-nested-call-case/input.tsx @@ -0,0 +1,7 @@ +const React = require('react'); + +class Hello extends React.Component { + render() { + return React.DOM.p(null, React.DOM.a({ href: '#hello' }, this.props.children)); + } +} diff --git a/codemods/react-dom-to-react-dom-factories/tests/react-dom-nested-call-case/metrics.json b/codemods/react-dom-to-react-dom-factories/tests/react-dom-nested-call-case/metrics.json new file mode 100644 index 0000000..158f193 --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/tests/react-dom-nested-call-case/metrics.json @@ -0,0 +1,10 @@ +{ + "react-dom-factory-replacements": [ + { + "cardinality": { + "file": "tests/react-dom-nested-call-case/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-import-dom-from-other-libraries.input.js b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-import-dom-from-other-libraries/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-import-dom-from-other-libraries.input.js rename to codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-import-dom-from-other-libraries/expected.tsx diff --git a/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-import-dom-from-other-libraries/input.tsx b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-import-dom-from-other-libraries/input.tsx new file mode 100644 index 0000000..b7657cf --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-import-dom-from-other-libraries/input.tsx @@ -0,0 +1,4 @@ +import {DOM} from 'Free'; + +const foo = DOM.div('a', 'b', 'c'); +const bar = Free.DOM.div('a', 'b', 'c'); diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-import.input.js b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-import/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-import.input.js rename to codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-import/expected.tsx diff --git a/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-import/input.tsx b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-import/input.tsx new file mode 100644 index 0000000..3c7cf3f --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-import/input.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +class Hello extends React.Component { + render() { + return React.createElement('div', null, `Hello ${this.props.toWhat}`); + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-local-dom-from-other-libraries.input.js b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-local-dom-from-other-libraries/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-local-dom-from-other-libraries.input.js rename to codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-local-dom-from-other-libraries/expected.tsx diff --git a/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-local-dom-from-other-libraries/input.tsx b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-local-dom-from-other-libraries/input.tsx new file mode 100644 index 0000000..3cf194d --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-local-dom-from-other-libraries/input.tsx @@ -0,0 +1,7 @@ +DOM = 'this is a test!'; + +foo.DOM = {}; + +foo.DOM.div = () => null; + +const bar = foo.DOM.div('a', 'b', 'c'); \ No newline at end of file diff --git a/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-react-destructure-without-dom/expected.tsx b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-react-destructure-without-dom/expected.tsx new file mode 100644 index 0000000..0cdce6e --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-react-destructure-without-dom/expected.tsx @@ -0,0 +1,8 @@ +const React = require('react'); +const { Component } = React; + +class Hello extends Component { + render() { + return DOM.div(null, `Hello ${this.props.toWhat}`); + } +} diff --git a/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-react-destructure-without-dom/input.tsx b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-react-destructure-without-dom/input.tsx new file mode 100644 index 0000000..0cdce6e --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-react-destructure-without-dom/input.tsx @@ -0,0 +1,8 @@ +const React = require('react'); +const { Component } = React; + +class Hello extends Component { + render() { + return DOM.div(null, `Hello ${this.props.toWhat}`); + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-require-dom-from-other-libraries.input.js b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-require-dom-from-other-libraries/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-require-dom-from-other-libraries.input.js rename to codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-require-dom-from-other-libraries/expected.tsx diff --git a/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-require-dom-from-other-libraries/input.tsx b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-require-dom-from-other-libraries/input.tsx new file mode 100644 index 0000000..14a21a0 --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-require-dom-from-other-libraries/input.tsx @@ -0,0 +1,4 @@ +const {DOM} = require('Free'); + +const foo = DOM.div('a', 'b', 'c'); +const bar = Free.DOM.div('a', 'b', 'c'); diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-require.input.js b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-require/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-require.input.js rename to codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-require/expected.tsx diff --git a/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-require/input.tsx b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-require/input.tsx new file mode 100644 index 0000000..fdc8bc6 --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/tests/react-dom-no-change-require/input.tsx @@ -0,0 +1,7 @@ +const React = require('react'); + +class Hello extends React.Component { + render() { + return React.createElement('div', null, `Hello ${this.props.toWhat}`); + } +} diff --git a/codemods/jssg/use-context-hook/tsconfig.json b/codemods/react-dom-to-react-dom-factories/tsconfig.json similarity index 100% rename from codemods/jssg/use-context-hook/tsconfig.json rename to codemods/react-dom-to-react-dom-factories/tsconfig.json diff --git a/codemods/react-dom-to-react-dom-factories/workflow.yaml b/codemods/react-dom-to-react-dom-factories/workflow.yaml new file mode 100644 index 0000000..8773841 --- /dev/null +++ b/codemods/react-dom-to-react-dom-factories/workflow.yaml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: "Scan tsx files and apply fixes" + js-ast-grep: + js_file: scripts/codemod.ts + language: "tsx" diff --git a/codemods/react-native-view-prop-types/README.md b/codemods/react-native-view-prop-types/README.md new file mode 100644 index 0000000..4c111bb --- /dev/null +++ b/codemods/react-native-view-prop-types/README.md @@ -0,0 +1,9 @@ +# react-native-view-prop-types + +Replace `View.propTypes` with `ViewPropTypes`. + +## Usage + +```bash +npx codemod @react-new/react-native-view-prop-types --target +``` diff --git a/codemods/react-native-view-prop-types/codemod.yaml b/codemods/react-native-view-prop-types/codemod.yaml new file mode 100644 index 0000000..78b6a74 --- /dev/null +++ b/codemods/react-native-view-prop-types/codemod.yaml @@ -0,0 +1,19 @@ +schema_version: "1.0" + +name: "@react-new/react-native-view-prop-types" +version: "0.1.1" +description: "Replace View.propTypes with ViewPropTypes" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + +targets: + languages: ["tsx"] + +keywords: ["React Native", "View"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/react-native-view-prop-types/package.json b/codemods/react-native-view-prop-types/package.json new file mode 100644 index 0000000..731bdf6 --- /dev/null +++ b/codemods/react-native-view-prop-types/package.json @@ -0,0 +1,18 @@ +{ + "name": "@react-new/react-native-view-prop-types", + "version": "0.1.1", + "description": "Replace View.propTypes with ViewPropTypes", + "type": "module", + "scripts": { + "test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts --strictness loose", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@codemod.com/jssg-types": "latest", + "@types/node": "latest", + "typescript": "latest" + }, + "dependencies": { + "@jssg/utils": "^0.0.2" + } +} diff --git a/codemods/react-native-view-prop-types/scripts/codemod.ts b/codemods/react-native-view-prop-types/scripts/codemod.ts new file mode 100644 index 0000000..cfd3184 --- /dev/null +++ b/codemods/react-native-view-prop-types/scripts/codemod.ts @@ -0,0 +1,310 @@ +import type { Transform, Edit, SgNode } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { useMetricAtom } from "codemod:metrics"; + +function metricFile(filename: string): string { + const cwd = process.cwd() + "/"; + return filename.startsWith(cwd) ? filename.slice(cwd.length) : filename; +} + +function namedChildren(node: SgNode | null): SgNode[] { + if (!node) { + return []; + } + + return node.children().filter((child) => child.isNamed() && child.kind() !== "comment"); +} + +function callArguments(call: SgNode): SgNode[] { + return namedChildren(call.field("arguments")); +} + +function stringValue(node: SgNode): string { + const evaluated = Function(`"use strict"; return (${node.text()});`)() as unknown; + if (typeof evaluated !== "string") { + throw new Error(`Expected string literal, got "${typeof evaluated}"`); + } + + return evaluated; +} + +function isRequireCall(node: SgNode | null, source: string): boolean { + if (!node || node.kind() !== "call_expression") { + return false; + } + + const callee = node.field("function"); + const firstArg = callArguments(node)[0]; + return callee?.kind() === "identifier" && + callee.text() === "require" && + firstArg?.kind() === "string" && + stringValue(firstArg) === source; +} + +function isViewPropTypesAccess(node: SgNode): boolean { + if (node.kind() !== "member_expression") { + return false; + } + + const object = node.field("object"); + const property = node.field("property"); + return object?.kind() === "identifier" && + object.text() === "View" && + property?.kind() === "property_identifier" && + property.text() === "propTypes"; +} + +function isViewBindingNode(node: SgNode): boolean { + if (node.text() !== "View") { + return false; + } + + if (node.ancestors().some((ancestor) => ancestor.kind() === "import_statement")) { + return false; + } + + const parent = node.parent(); + if (!parent) { + return false; + } + + if (parent.kind() === "member_expression" && + parent.field("object")?.id() === node.id() && + parent.field("property")?.text() === "propTypes") { + return false; + } + + if (parent.kind() === "member_expression" && parent.field("property")?.id() === node.id()) { + return false; + } + + if (parent.kind() === "variable_declarator" && parent.field("name")?.id() === node.id()) { + return false; + } + + if (parent.kind() === "shorthand_property_identifier_pattern") { + const pattern = parent.ancestors().find((ancestor) => ancestor.kind() === "object_pattern"); + if (pattern?.parent()?.kind() === "variable_declarator") { + return false; + } + } + + return true; +} + +function findHasteImportStatement(rootNode: SgNode): SgNode | null { + for (const importStmt of rootNode.findAll({ rule: { kind: "import_statement" } })) { + const source = importStmt.field("source"); + if (source?.kind() === "string" && stringValue(source) === "View") { + return importStmt; + } + } + + return null; +} + +function findHasteRequireStatement(rootNode: SgNode): SgNode | null { + for (const declaration of rootNode.findAll({ + rule: { + any: [ + { pattern: "const View = require('View')" }, + { pattern: "let View = require('View')" }, + { pattern: "var View = require('View')" }, + ], + }, + })) { + if (declaration.kind() === "lexical_declaration" || declaration.kind() === "variable_declaration") { + return declaration; + } + } + + return null; +} + +function findReactNativeImportStatement(rootNode: SgNode): SgNode | null { + for (const importStmt of rootNode.findAll({ rule: { kind: "import_statement" } })) { + const source = importStmt.field("source"); + if (source?.kind() === "string" && stringValue(source) === "react-native") { + const hasView = importStmt.findAll({ rule: { kind: "import_specifier" } }) + .some((specifier) => specifier.field("name")?.text() === "View"); + if (hasView) { + return importStmt; + } + } + } + + return null; +} + +function findReactNativeRequireStatement(rootNode: SgNode): SgNode | null { + for (const declarator of rootNode.findAll({ rule: { kind: "variable_declarator" } })) { + const name = declarator.field("name"); + const value = declarator.field("value"); + if (name?.kind() !== "object_pattern" || !isRequireCall(value, "react-native")) { + continue; + } + + const hasView = name.findAll({ rule: { kind: "shorthand_property_identifier_pattern" } }) + .some((property) => property.text() === "View"); + if (hasView) { + return declarator.ancestors().find((ancestor) => + ancestor.kind() === "lexical_declaration" || ancestor.kind() === "variable_declaration" + ) ?? null; + } + } + + return null; +} + +function viewImportSpecifier(importStmt: SgNode): SgNode | null { + return importStmt.findAll({ rule: { kind: "import_specifier" } }) + .find((specifier) => specifier.field("name")?.text() === "View") ?? null; +} + +function viewRequireProperty(statement: SgNode): SgNode | null { + return statement.findAll({ rule: { kind: "shorthand_property_identifier_pattern" } }) + .find((property) => property.text() === "View") ?? null; +} + +function hasImportSpecifier(importStmt: SgNode, name: string): boolean { + return importStmt.findAll({ rule: { kind: "import_specifier" } }) + .some((specifier) => specifier.field("name")?.text() === name); +} + +function hasRequireProperty(statement: SgNode, name: string): boolean { + return statement.findAll({ rule: { kind: "shorthand_property_identifier_pattern" } }) + .some((property) => property.text() === name); +} + +function hasHasteViewPropTypesImport(rootNode: SgNode): boolean { + return rootNode.findAll({ rule: { kind: "import_statement" } }) + .some((importStmt) => { + const source = importStmt.field("source"); + return source?.kind() === "string" && + stringValue(source) === "ViewPropTypes"; + }); +} + +function hasHasteViewPropTypesRequire(rootNode: SgNode): boolean { + return rootNode.findAll({ + rule: { + any: [ + { pattern: "const ViewPropTypes = require('ViewPropTypes')" }, + { pattern: "let ViewPropTypes = require('ViewPropTypes')" }, + { pattern: "var ViewPropTypes = require('ViewPropTypes')" }, + ], + }, + }).length > 0; +} + +function statementInsertionIndex(source: string, node: SgNode): number { + let index = node.range().end.index; + while (index < source.length && (source[index] === ";" || source[index] === "\r" || source[index] === "\n")) { + index++; + } + return index; +} + +const transform: Transform = async (root) => { + const rootNode = root.root(); + const source = rootNode.text(); + const edits: Edit[] = []; + const metric = useMetricAtom("react-native-view-prop-types-replacements"); + + const hasteImportStatement = findHasteImportStatement(rootNode); + const hasteRequireStatement = findHasteRequireStatement(rootNode); + const reactNativeImportStatement = findReactNativeImportStatement(rootNode); + const reactNativeRequireStatement = findReactNativeRequireStatement(rootNode); + const hasExistingHasteViewPropTypesImport = hasHasteViewPropTypesImport(rootNode); + const hasExistingHasteViewPropTypesRequire = hasHasteViewPropTypesRequire(rootNode); + const hasExistingReactNativeImportViewPropTypes = reactNativeImportStatement + ? hasImportSpecifier(reactNativeImportStatement, "ViewPropTypes") + : false; + const hasExistingReactNativeRequireViewPropTypes = reactNativeRequireStatement + ? hasRequireProperty(reactNativeRequireStatement, "ViewPropTypes") + : false; + + let replacements = 0; + for (const member of rootNode.findAll({ rule: { kind: "member_expression" } })) { + if (!isViewPropTypesAccess(member)) { + continue; + } + + edits.push(member.replace("ViewPropTypes")); + replacements++; + } + + if (replacements === 0) { + return null; + } + + const keepViewBinding = rootNode.findAll({ + rule: { + any: [ + { kind: "identifier" }, + { kind: "property_identifier" }, + ], + }, + }).some(isViewBindingNode); + + if (hasteImportStatement) { + if (hasExistingHasteViewPropTypesImport) { + // Keep the existing import intact; the file already binds ViewPropTypes. + } else if (keepViewBinding) { + edits.push({ + startPos: hasteImportStatement.range().end.index, + endPos: hasteImportStatement.range().end.index, + insertedText: "\nimport ViewPropTypes from 'ViewPropTypes';", + }); + } else { + edits.push(hasteImportStatement.replace("import ViewPropTypes from 'ViewPropTypes';")); + } + } else if (hasteRequireStatement) { + if (hasExistingHasteViewPropTypesRequire) { + // Keep the existing require intact; the file already binds ViewPropTypes. + } else if (keepViewBinding) { + edits.push({ + startPos: statementInsertionIndex(source, hasteRequireStatement), + endPos: statementInsertionIndex(source, hasteRequireStatement), + insertedText: "\nconst ViewPropTypes = require('ViewPropTypes');\n", + }); + } else { + edits.push(hasteRequireStatement.replace("const ViewPropTypes = require('ViewPropTypes');")); + } + } else if (reactNativeImportStatement) { + const specifier = viewImportSpecifier(reactNativeImportStatement); + if (specifier) { + if (hasExistingReactNativeImportViewPropTypes) { + // Keep the existing import intact; the file already binds ViewPropTypes. + } else if (keepViewBinding) { + edits.push({ + startPos: specifier.range().end.index, + endPos: specifier.range().end.index, + insertedText: ", ViewPropTypes", + }); + } else { + edits.push(specifier.replace("ViewPropTypes")); + } + } + } else if (reactNativeRequireStatement) { + const property = viewRequireProperty(reactNativeRequireStatement); + if (property) { + if (hasExistingReactNativeRequireViewPropTypes) { + // Keep the existing require intact; the file already binds ViewPropTypes. + } else if (keepViewBinding) { + edits.push({ + startPos: property.range().end.index, + endPos: property.range().end.index, + insertedText: ", ViewPropTypes", + }); + } else { + edits.push(property.replace("ViewPropTypes")); + } + } + } + + metric.increment({ file: metricFile(root.filename()) }, replacements); + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-multi-reference.output.js b/codemods/react-native-view-prop-types/tests/default-import-multi-reference/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-multi-reference.output.js rename to codemods/react-native-view-prop-types/tests/default-import-multi-reference/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-multi-reference.input.js b/codemods/react-native-view-prop-types/tests/default-import-multi-reference/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-multi-reference.input.js rename to codemods/react-native-view-prop-types/tests/default-import-multi-reference/input.tsx diff --git a/codemods/react-native-view-prop-types/tests/default-import-multi-reference/metrics.json b/codemods/react-native-view-prop-types/tests/default-import-multi-reference/metrics.json new file mode 100644 index 0000000..e59f9f4 --- /dev/null +++ b/codemods/react-native-view-prop-types/tests/default-import-multi-reference/metrics.json @@ -0,0 +1,10 @@ +{ + "react-native-view-prop-types-replacements": [ + { + "cardinality": { + "file": "tests/default-import-multi-reference/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-only-reference.output.js b/codemods/react-native-view-prop-types/tests/default-import-only-reference/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-only-reference.output.js rename to codemods/react-native-view-prop-types/tests/default-import-only-reference/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-only-reference.input.js b/codemods/react-native-view-prop-types/tests/default-import-only-reference/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-only-reference.input.js rename to codemods/react-native-view-prop-types/tests/default-import-only-reference/input.tsx diff --git a/codemods/react-native-view-prop-types/tests/default-import-only-reference/metrics.json b/codemods/react-native-view-prop-types/tests/default-import-only-reference/metrics.json new file mode 100644 index 0000000..f298b7d --- /dev/null +++ b/codemods/react-native-view-prop-types/tests/default-import-only-reference/metrics.json @@ -0,0 +1,10 @@ +{ + "react-native-view-prop-types-replacements": [ + { + "cardinality": { + "file": "tests/default-import-only-reference/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-multi-reference.output.js b/codemods/react-native-view-prop-types/tests/default-require-multi-reference/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-multi-reference.output.js rename to codemods/react-native-view-prop-types/tests/default-require-multi-reference/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-multi-reference.input.js b/codemods/react-native-view-prop-types/tests/default-require-multi-reference/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-multi-reference.input.js rename to codemods/react-native-view-prop-types/tests/default-require-multi-reference/input.tsx diff --git a/codemods/react-native-view-prop-types/tests/default-require-multi-reference/metrics.json b/codemods/react-native-view-prop-types/tests/default-require-multi-reference/metrics.json new file mode 100644 index 0000000..8eebc32 --- /dev/null +++ b/codemods/react-native-view-prop-types/tests/default-require-multi-reference/metrics.json @@ -0,0 +1,10 @@ +{ + "react-native-view-prop-types-replacements": [ + { + "cardinality": { + "file": "tests/default-require-multi-reference/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-only-reference.output.js b/codemods/react-native-view-prop-types/tests/default-require-only-reference/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-only-reference.output.js rename to codemods/react-native-view-prop-types/tests/default-require-only-reference/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-only-reference.input.js b/codemods/react-native-view-prop-types/tests/default-require-only-reference/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-only-reference.input.js rename to codemods/react-native-view-prop-types/tests/default-require-only-reference/input.tsx diff --git a/codemods/react-native-view-prop-types/tests/default-require-only-reference/metrics.json b/codemods/react-native-view-prop-types/tests/default-require-only-reference/metrics.json new file mode 100644 index 0000000..6c4cdbe --- /dev/null +++ b/codemods/react-native-view-prop-types/tests/default-require-only-reference/metrics.json @@ -0,0 +1,10 @@ +{ + "react-native-view-prop-types-replacements": [ + { + "cardinality": { + "file": "tests/default-require-only-reference/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/react-native-view-prop-types/tests/destructured-import-existing-view-prop-types/expected.tsx b/codemods/react-native-view-prop-types/tests/destructured-import-existing-view-prop-types/expected.tsx new file mode 100644 index 0000000..31581a4 --- /dev/null +++ b/codemods/react-native-view-prop-types/tests/destructured-import-existing-view-prop-types/expected.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { View, ViewPropTypes } from 'react-native'; + +function Component() { + return ; +} + +Component.propTypes = { + style: ViewPropTypes ? ViewPropTypes.style : ViewPropTypes.style, +}; + +export default Component; diff --git a/codemods/react-native-view-prop-types/tests/destructured-import-existing-view-prop-types/input.tsx b/codemods/react-native-view-prop-types/tests/destructured-import-existing-view-prop-types/input.tsx new file mode 100644 index 0000000..42ee2cc --- /dev/null +++ b/codemods/react-native-view-prop-types/tests/destructured-import-existing-view-prop-types/input.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { View, ViewPropTypes } from 'react-native'; + +function Component() { + return ; +} + +Component.propTypes = { + style: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, +}; + +export default Component; diff --git a/codemods/react-native-view-prop-types/tests/destructured-import-existing-view-prop-types/metrics.json b/codemods/react-native-view-prop-types/tests/destructured-import-existing-view-prop-types/metrics.json new file mode 100644 index 0000000..3608992 --- /dev/null +++ b/codemods/react-native-view-prop-types/tests/destructured-import-existing-view-prop-types/metrics.json @@ -0,0 +1,10 @@ +{ + "react-native-view-prop-types-replacements": [ + { + "cardinality": { + "file": "tests/destructured-import-existing-view-prop-types/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-multi-reference.output.js b/codemods/react-native-view-prop-types/tests/destructured-import-multi-reference/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-multi-reference.output.js rename to codemods/react-native-view-prop-types/tests/destructured-import-multi-reference/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-multi-reference.input.js b/codemods/react-native-view-prop-types/tests/destructured-import-multi-reference/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-multi-reference.input.js rename to codemods/react-native-view-prop-types/tests/destructured-import-multi-reference/input.tsx diff --git a/codemods/react-native-view-prop-types/tests/destructured-import-multi-reference/metrics.json b/codemods/react-native-view-prop-types/tests/destructured-import-multi-reference/metrics.json new file mode 100644 index 0000000..f9bfb2c --- /dev/null +++ b/codemods/react-native-view-prop-types/tests/destructured-import-multi-reference/metrics.json @@ -0,0 +1,10 @@ +{ + "react-native-view-prop-types-replacements": [ + { + "cardinality": { + "file": "tests/destructured-import-multi-reference/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-only-reference.output.js b/codemods/react-native-view-prop-types/tests/destructured-import-only-reference/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-only-reference.output.js rename to codemods/react-native-view-prop-types/tests/destructured-import-only-reference/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-only-reference.input.js b/codemods/react-native-view-prop-types/tests/destructured-import-only-reference/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-only-reference.input.js rename to codemods/react-native-view-prop-types/tests/destructured-import-only-reference/input.tsx diff --git a/codemods/react-native-view-prop-types/tests/destructured-import-only-reference/metrics.json b/codemods/react-native-view-prop-types/tests/destructured-import-only-reference/metrics.json new file mode 100644 index 0000000..a530459 --- /dev/null +++ b/codemods/react-native-view-prop-types/tests/destructured-import-only-reference/metrics.json @@ -0,0 +1,10 @@ +{ + "react-native-view-prop-types-replacements": [ + { + "cardinality": { + "file": "tests/destructured-import-only-reference/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-multi-reference.output.js b/codemods/react-native-view-prop-types/tests/destructured-require-multi-reference/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-multi-reference.output.js rename to codemods/react-native-view-prop-types/tests/destructured-require-multi-reference/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-multi-reference.input.js b/codemods/react-native-view-prop-types/tests/destructured-require-multi-reference/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-multi-reference.input.js rename to codemods/react-native-view-prop-types/tests/destructured-require-multi-reference/input.tsx diff --git a/codemods/react-native-view-prop-types/tests/destructured-require-multi-reference/metrics.json b/codemods/react-native-view-prop-types/tests/destructured-require-multi-reference/metrics.json new file mode 100644 index 0000000..2b8657f --- /dev/null +++ b/codemods/react-native-view-prop-types/tests/destructured-require-multi-reference/metrics.json @@ -0,0 +1,10 @@ +{ + "react-native-view-prop-types-replacements": [ + { + "cardinality": { + "file": "tests/destructured-require-multi-reference/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-only-reference.output.js b/codemods/react-native-view-prop-types/tests/destructured-require-only-reference/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-only-reference.output.js rename to codemods/react-native-view-prop-types/tests/destructured-require-only-reference/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-only-reference.input.js b/codemods/react-native-view-prop-types/tests/destructured-require-only-reference/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-only-reference.input.js rename to codemods/react-native-view-prop-types/tests/destructured-require-only-reference/input.tsx diff --git a/codemods/react-native-view-prop-types/tests/destructured-require-only-reference/metrics.json b/codemods/react-native-view-prop-types/tests/destructured-require-only-reference/metrics.json new file mode 100644 index 0000000..b7af569 --- /dev/null +++ b/codemods/react-native-view-prop-types/tests/destructured-require-only-reference/metrics.json @@ -0,0 +1,10 @@ +{ + "react-native-view-prop-types-replacements": [ + { + "cardinality": { + "file": "tests/destructured-require-only-reference/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/import-flow-type-with-require.output.js b/codemods/react-native-view-prop-types/tests/import-flow-type-with-require/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/import-flow-type-with-require.output.js rename to codemods/react-native-view-prop-types/tests/import-flow-type-with-require/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/import-flow-type-with-require.input.js b/codemods/react-native-view-prop-types/tests/import-flow-type-with-require/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/import-flow-type-with-require.input.js rename to codemods/react-native-view-prop-types/tests/import-flow-type-with-require/input.tsx diff --git a/codemods/react-native-view-prop-types/tests/import-flow-type-with-require/metrics.json b/codemods/react-native-view-prop-types/tests/import-flow-type-with-require/metrics.json new file mode 100644 index 0000000..dbd793c --- /dev/null +++ b/codemods/react-native-view-prop-types/tests/import-flow-type-with-require/metrics.json @@ -0,0 +1,10 @@ +{ + "react-native-view-prop-types-replacements": [ + { + "cardinality": { + "file": "tests/import-flow-type-with-require/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/multiple-replacements.output.js b/codemods/react-native-view-prop-types/tests/multiple-replacements/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/multiple-replacements.output.js rename to codemods/react-native-view-prop-types/tests/multiple-replacements/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/multiple-replacements.input.js b/codemods/react-native-view-prop-types/tests/multiple-replacements/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/multiple-replacements.input.js rename to codemods/react-native-view-prop-types/tests/multiple-replacements/input.tsx diff --git a/codemods/react-native-view-prop-types/tests/multiple-replacements/metrics.json b/codemods/react-native-view-prop-types/tests/multiple-replacements/metrics.json new file mode 100644 index 0000000..6ad8dcd --- /dev/null +++ b/codemods/react-native-view-prop-types/tests/multiple-replacements/metrics.json @@ -0,0 +1,10 @@ +{ + "react-native-view-prop-types-replacements": [ + { + "cardinality": { + "file": "tests/multiple-replacements/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/noop-import.input.js b/codemods/react-native-view-prop-types/tests/noop-import/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/noop-import.input.js rename to codemods/react-native-view-prop-types/tests/noop-import/expected.tsx diff --git a/codemods/react-native-view-prop-types/tests/noop-import/input.tsx b/codemods/react-native-view-prop-types/tests/noop-import/input.tsx new file mode 100644 index 0000000..e02fc4a --- /dev/null +++ b/codemods/react-native-view-prop-types/tests/noop-import/input.tsx @@ -0,0 +1,6 @@ +import React from 'React'; +import View from 'View'; + +module.exports = function Component() { + return ; +}; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/noop-require.input.js b/codemods/react-native-view-prop-types/tests/noop-require/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/noop-require.input.js rename to codemods/react-native-view-prop-types/tests/noop-require/expected.tsx diff --git a/codemods/react-native-view-prop-types/tests/noop-require/input.tsx b/codemods/react-native-view-prop-types/tests/noop-require/input.tsx new file mode 100644 index 0000000..c1bc51a --- /dev/null +++ b/codemods/react-native-view-prop-types/tests/noop-require/input.tsx @@ -0,0 +1,6 @@ +const React = require('React'); +const View = require('View'); + +module.exports = function Component() { + return ; +}; diff --git a/codemods/react-native-view-prop-types/tsconfig.json b/codemods/react-native-view-prop-types/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/react-native-view-prop-types/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": [ + "ESNext" + ], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": [ + "@codemod.com/jssg-types", + "node" + ], + "allowImportingTsExtensions": true, + "noEmit": true, + "skipLibCheck": true, + "verbatimModuleSyntax": false, + "erasableSyntaxOnly": true, + "strict": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true + }, + "exclude": [ + "tests" + ] +} diff --git a/codemods/react-native-view-prop-types/workflow.yaml b/codemods/react-native-view-prop-types/workflow.yaml new file mode 100644 index 0000000..8773841 --- /dev/null +++ b/codemods/react-native-view-prop-types/workflow.yaml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: "Scan tsx files and apply fixes" + js-ast-grep: + js_file: scripts/codemod.ts + language: "tsx" diff --git a/codemods/jssg/react-proptypes-to-prop-types/README.md b/codemods/react-proptypes-to-prop-types/README.md similarity index 85% rename from codemods/jssg/react-proptypes-to-prop-types/README.md rename to codemods/react-proptypes-to-prop-types/README.md index f2e6b9a..0690b55 100644 --- a/codemods/jssg/react-proptypes-to-prop-types/README.md +++ b/codemods/react-proptypes-to-prop-types/README.md @@ -9,10 +9,3 @@ npx codemod @react-new/react-proptypes-to-prop-types --target ``` To override the `prop-types` module specifier, pass `--param module-name=`. - -## Development - -```bash -pnpm test -pnpm check-types -``` diff --git a/codemods/jssg/react-proptypes-to-prop-types/codemod.yaml b/codemods/react-proptypes-to-prop-types/codemod.yaml similarity index 95% rename from codemods/jssg/react-proptypes-to-prop-types/codemod.yaml rename to codemods/react-proptypes-to-prop-types/codemod.yaml index 20814bf..b4574ff 100644 --- a/codemods/jssg/react-proptypes-to-prop-types/codemod.yaml +++ b/codemods/react-proptypes-to-prop-types/codemod.yaml @@ -1,7 +1,7 @@ schema_version: "1.0" name: "@react-new/react-proptypes-to-prop-types" -version: "0.1.0" +version: "0.1.1" description: "Replace React.PropTypes with prop-types package" author: "Codemod " license: "MIT" diff --git a/codemods/jssg/react-proptypes-to-prop-types/package.json b/codemods/react-proptypes-to-prop-types/package.json similarity index 95% rename from codemods/jssg/react-proptypes-to-prop-types/package.json rename to codemods/react-proptypes-to-prop-types/package.json index 3eb93eb..b6ca9d2 100644 --- a/codemods/jssg/react-proptypes-to-prop-types/package.json +++ b/codemods/react-proptypes-to-prop-types/package.json @@ -1,6 +1,6 @@ { "name": "@react-new/react-proptypes-to-prop-types", - "version": "0.1.0", + "version": "0.1.1", "description": "Replace React.PropTypes with prop-types package", "type": "module", "scripts": { diff --git a/codemods/jssg/react-proptypes-to-prop-types/scripts/codemod.ts b/codemods/react-proptypes-to-prop-types/scripts/codemod.ts similarity index 98% rename from codemods/jssg/react-proptypes-to-prop-types/scripts/codemod.ts rename to codemods/react-proptypes-to-prop-types/scripts/codemod.ts index 6a91929..dd09868 100644 --- a/codemods/jssg/react-proptypes-to-prop-types/scripts/codemod.ts +++ b/codemods/react-proptypes-to-prop-types/scripts/codemod.ts @@ -104,7 +104,11 @@ function usesVarForRequires(rootNode: SgNode): boolean { return rootNode.find({ rule: { kind: "lexical_declaration" } }) === null; } -function firstSortedImportPosition(rootNode: SgNode, moduleName: string): number { +function firstSortedImportPosition( + rootNode: SgNode, + moduleName: string, + source: string, +): number { const imports = rootNode.findAll({ rule: { kind: "import_statement" } }); const lowerModule = moduleName.toLowerCase(); let target: SgNode | null = null; @@ -122,7 +126,8 @@ function firstSortedImportPosition(rootNode: SgNode, moduleName: if (target) return target.range().start.index; const last = imports[imports.length - 1]; - return last ? last.range().end.index : 0; + if (!last) return 0; + return statementRange(last, source).end; } function firstSortedRequirePosition(rootNode: SgNode, moduleName: string): number { @@ -313,7 +318,7 @@ const transform: Transform = async (root, options) => { if (!addedPropTypesImportByReplacement && !hasImportOrRequire(rootNode, moduleName)) { if (usesImportSyntax(rootNode)) { - const pos = firstSortedImportPosition(rootNode, moduleName); + const pos = firstSortedImportPosition(rootNode, moduleName, source); edits.push({ startPos: pos, endPos: pos, diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-import/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/already-migrated-import/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-import/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/already-migrated-import/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-import/input.tsx b/codemods/react-proptypes-to-prop-types/tests/already-migrated-import/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-import/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/already-migrated-import/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-import/metrics.json b/codemods/react-proptypes-to-prop-types/tests/already-migrated-import/metrics.json similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-import/metrics.json rename to codemods/react-proptypes-to-prop-types/tests/already-migrated-import/metrics.json diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/input.tsx b/codemods/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/metrics.json b/codemods/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/metrics.json similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/metrics.json rename to codemods/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/metrics.json diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-import/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/already-migrated-named-import/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-import/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/already-migrated-named-import/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-import/input.tsx b/codemods/react-proptypes-to-prop-types/tests/already-migrated-named-import/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-import/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/already-migrated-named-import/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-import/metrics.json b/codemods/react-proptypes-to-prop-types/tests/already-migrated-named-import/metrics.json similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-import/metrics.json rename to codemods/react-proptypes-to-prop-types/tests/already-migrated-named-import/metrics.json diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-require/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/already-migrated-require/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-require/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/already-migrated-require/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-require/input.tsx b/codemods/react-proptypes-to-prop-types/tests/already-migrated-require/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-require/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/already-migrated-require/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-require/metrics.json b/codemods/react-proptypes-to-prop-types/tests/already-migrated-require/metrics.json similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-require/metrics.json rename to codemods/react-proptypes-to-prop-types/tests/already-migrated-require/metrics.json diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-from-react-var/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/assigned-from-react-var/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/assigned-from-react-var/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/assigned-from-react-var/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-from-react-var/input.tsx b/codemods/react-proptypes-to-prop-types/tests/assigned-from-react-var/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/assigned-from-react-var/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/assigned-from-react-var/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-from-react-var/metrics.json b/codemods/react-proptypes-to-prop-types/tests/assigned-from-react-var/metrics.json similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/assigned-from-react-var/metrics.json rename to codemods/react-proptypes-to-prop-types/tests/assigned-from-react-var/metrics.json diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/input.tsx b/codemods/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/metrics.json b/codemods/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/metrics.json similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/metrics.json rename to codemods/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/metrics.json diff --git a/codemods/react-proptypes-to-prop-types/tests/custom-react-import-insert-newline/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/custom-react-import-insert-newline/expected.tsx new file mode 100644 index 0000000..d5903b5 --- /dev/null +++ b/codemods/react-proptypes-to-prop-types/tests/custom-react-import-insert-newline/expected.tsx @@ -0,0 +1,9 @@ +import {React, ReactDOM} from 'nylas-exports' +import {Menu} from 'nylas-component-kit' + +import PropTypes from 'prop-types'; +export default class DropdownMenu extends React.Component { + static propTypes = { + item: PropTypes.object, + } +} diff --git a/codemods/react-proptypes-to-prop-types/tests/custom-react-import-insert-newline/input.tsx b/codemods/react-proptypes-to-prop-types/tests/custom-react-import-insert-newline/input.tsx new file mode 100644 index 0000000..c9db42c --- /dev/null +++ b/codemods/react-proptypes-to-prop-types/tests/custom-react-import-insert-newline/input.tsx @@ -0,0 +1,8 @@ +import {React, ReactDOM} from 'nylas-exports' +import {Menu} from 'nylas-component-kit' + +export default class DropdownMenu extends React.Component { + static propTypes = { + item: React.PropTypes.object, + } +} diff --git a/codemods/react-proptypes-to-prop-types/tests/custom-react-import-insert-newline/metrics.json b/codemods/react-proptypes-to-prop-types/tests/custom-react-import-insert-newline/metrics.json new file mode 100644 index 0000000..962f9c9 --- /dev/null +++ b/codemods/react-proptypes-to-prop-types/tests/custom-react-import-insert-newline/metrics.json @@ -0,0 +1,10 @@ +{ + "react-proptypes-migrations": [ + { + "cardinality": { + "file": "tests/custom-react-import-insert-newline/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/default-and-named-import/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/default-and-named-import/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/default-and-named-import/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/default-and-named-import/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/default-and-named-import/input.tsx b/codemods/react-proptypes-to-prop-types/tests/default-and-named-import/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/default-and-named-import/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/default-and-named-import/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/default-import/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/default-import/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/default-import/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/default-import/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/default-import/input.tsx b/codemods/react-proptypes-to-prop-types/tests/default-import/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/default-import/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/default-import/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/default-import/metrics.json b/codemods/react-proptypes-to-prop-types/tests/default-import/metrics.json similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/default-import/metrics.json rename to codemods/react-proptypes-to-prop-types/tests/default-import/metrics.json diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/destructured-proptypes-import/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/destructured-proptypes-import/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/destructured-proptypes-import/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/destructured-proptypes-import/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/destructured-proptypes-import/input.tsx b/codemods/react-proptypes-to-prop-types/tests/destructured-proptypes-import/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/destructured-proptypes-import/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/destructured-proptypes-import/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/import-alias/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/import-alias/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/import-alias/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/import-alias/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/import-alias/input.tsx b/codemods/react-proptypes-to-prop-types/tests/import-alias/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/import-alias/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/import-alias/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/import-flow-type-with-require/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/import-flow-type-with-require/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/import-flow-type-with-require/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/import-flow-type-with-require/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/import-flow-type-with-require/input.tsx b/codemods/react-proptypes-to-prop-types/tests/import-flow-type-with-require/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/import-flow-type-with-require/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/import-flow-type-with-require/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require-2/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/mixed-import-and-require-2/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require-2/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/mixed-import-and-require-2/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require-2/input.tsx b/codemods/react-proptypes-to-prop-types/tests/mixed-import-and-require-2/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require-2/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/mixed-import-and-require-2/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/mixed-import-and-require/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/mixed-import-and-require/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require/input.tsx b/codemods/react-proptypes-to-prop-types/tests/mixed-import-and-require/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/mixed-import-and-require/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require/metrics.json b/codemods/react-proptypes-to-prop-types/tests/mixed-import-and-require/metrics.json similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require/metrics.json rename to codemods/react-proptypes-to-prop-types/tests/mixed-import-and-require/metrics.json diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/module-name/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/module-name/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/module-name/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/module-name/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/module-name/input.tsx b/codemods/react-proptypes-to-prop-types/tests/module-name/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/module-name/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/module-name/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/module-name/metrics.json b/codemods/react-proptypes-to-prop-types/tests/module-name/metrics.json similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/module-name/metrics.json rename to codemods/react-proptypes-to-prop-types/tests/module-name/metrics.json diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/module-name/test.config.json b/codemods/react-proptypes-to-prop-types/tests/module-name/test.config.json similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/module-name/test.config.json rename to codemods/react-proptypes-to-prop-types/tests/module-name/test.config.json diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/named-parameters/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/named-parameters/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/named-parameters/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/named-parameters/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/named-parameters/input.tsx b/codemods/react-proptypes-to-prop-types/tests/named-parameters/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/named-parameters/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/named-parameters/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/nested-destructured-proptypes-import/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/nested-destructured-proptypes-import/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/nested-destructured-proptypes-import/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/nested-destructured-proptypes-import/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/nested-destructured-proptypes-import/input.tsx b/codemods/react-proptypes-to-prop-types/tests/nested-destructured-proptypes-import/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/nested-destructured-proptypes-import/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/nested-destructured-proptypes-import/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/no-change-import/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/no-change-import/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/no-change-import/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/no-change-import/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/no-change-import/input.tsx b/codemods/react-proptypes-to-prop-types/tests/no-change-import/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/no-change-import/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/no-change-import/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/no-change-require/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/no-change-require/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/no-change-require/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/no-change-require/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/no-change-require/input.tsx b/codemods/react-proptypes-to-prop-types/tests/no-change-require/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/no-change-require/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/no-change-require/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require-alias/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/require-alias/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/require-alias/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/require-alias/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require-alias/input.tsx b/codemods/react-proptypes-to-prop-types/tests/require-alias/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/require-alias/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/require-alias/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-direct/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/require-destructured-direct/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-direct/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/require-destructured-direct/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-direct/input.tsx b/codemods/react-proptypes-to-prop-types/tests/require-destructured-direct/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-direct/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/require-destructured-direct/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-multi/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/require-destructured-multi/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-multi/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/require-destructured-multi/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-multi/input.tsx b/codemods/react-proptypes-to-prop-types/tests/require-destructured-multi/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-multi/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/require-destructured-multi/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-only/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/require-destructured-only/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-only/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/require-destructured-only/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-only/input.tsx b/codemods/react-proptypes-to-prop-types/tests/require-destructured-only/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-only/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/require-destructured-only/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/require/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/require/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/require/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require/input.tsx b/codemods/react-proptypes-to-prop-types/tests/require/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/require/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/require/input.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require/metrics.json b/codemods/react-proptypes-to-prop-types/tests/require/metrics.json similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/require/metrics.json rename to codemods/react-proptypes-to-prop-types/tests/require/metrics.json diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/with-top-comment/expected.tsx b/codemods/react-proptypes-to-prop-types/tests/with-top-comment/expected.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/with-top-comment/expected.tsx rename to codemods/react-proptypes-to-prop-types/tests/with-top-comment/expected.tsx diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/with-top-comment/input.tsx b/codemods/react-proptypes-to-prop-types/tests/with-top-comment/input.tsx similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/tests/with-top-comment/input.tsx rename to codemods/react-proptypes-to-prop-types/tests/with-top-comment/input.tsx diff --git a/codemods/react-proptypes-to-prop-types/tsconfig.json b/codemods/react-proptypes-to-prop-types/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/react-proptypes-to-prop-types/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": [ + "ESNext" + ], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": [ + "@codemod.com/jssg-types", + "node" + ], + "allowImportingTsExtensions": true, + "noEmit": true, + "skipLibCheck": true, + "verbatimModuleSyntax": false, + "erasableSyntaxOnly": true, + "strict": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true + }, + "exclude": [ + "tests" + ] +} diff --git a/codemods/jssg/react-proptypes-to-prop-types/workflow.yaml b/codemods/react-proptypes-to-prop-types/workflow.yaml similarity index 100% rename from codemods/jssg/react-proptypes-to-prop-types/workflow.yaml rename to codemods/react-proptypes-to-prop-types/workflow.yaml diff --git a/codemods/react-to-react-dom/README.md b/codemods/react-to-react-dom/README.md new file mode 100644 index 0000000..d2bd000 --- /dev/null +++ b/codemods/react-to-react-dom/README.md @@ -0,0 +1,9 @@ +# react-to-react-dom + +Move DOM APIs from `react` to `react-dom` and `react-dom/server`. + +## Usage + +```bash +npx codemod @react-new/react-to-react-dom --target +``` diff --git a/codemods/react-to-react-dom/codemod.yaml b/codemods/react-to-react-dom/codemod.yaml new file mode 100644 index 0000000..9f042e2 --- /dev/null +++ b/codemods/react-to-react-dom/codemod.yaml @@ -0,0 +1,19 @@ +schema_version: "1.0" + +name: "@react-new/react-to-react-dom" +version: "0.1.1" +description: "Move DOM APIs from react to react-dom" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + +targets: + languages: ["tsx"] + +keywords: ["React", "ReactDOM"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/react-to-react-dom/package.json b/codemods/react-to-react-dom/package.json new file mode 100644 index 0000000..c05bd1c --- /dev/null +++ b/codemods/react-to-react-dom/package.json @@ -0,0 +1,15 @@ +{ + "name": "@react-new/react-to-react-dom", + "version": "0.1.1", + "description": "Move DOM APIs from react to react-dom", + "type": "module", + "scripts": { + "test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts --strictness loose && node --test ./scripts/error-tests.mjs", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@codemod.com/jssg-types": "latest", + "@types/node": "latest", + "typescript": "latest" + } +} diff --git a/codemods/react-to-react-dom/scripts/codemod.ts b/codemods/react-to-react-dom/scripts/codemod.ts new file mode 100644 index 0000000..d491ce4 --- /dev/null +++ b/codemods/react-to-react-dom/scripts/codemod.ts @@ -0,0 +1,713 @@ +import type { Transform, Edit, SgNode } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { useMetricAtom } from "codemod:metrics"; + +type NamedSpecifier = { + name: string; + alias?: string; +}; + +type ImportBinding = { + alias: string | null; + named: NamedSpecifier[]; + statements: SgNode[]; + quote: string; +}; + +type RequireBinding = { + alias: string; + statement: SgNode; + kind: "var" | "let" | "const"; +}; + +type AssignmentBinding = { + alias: string; + declaration: SgNode | null; + assignment: SgNode; + kind: "var" | "let" | "const"; +}; + +type ModulePair = { + core: string; + dom: string; + server: string; + domAlias: string; + serverAlias: string; +}; + +type MemberAnalysis = { + edits: Edit[]; + domUses: number; + serverUses: number; + coreUses: number; +}; + +type DestructureAnalysis = { + edits: Edit[]; + domUses: number; + coreUses: number; +}; + +const CORE_PROPERTIES = [ + "Children", + "Component", + "createElement", + "cloneElement", + "isValidElement", + "PropTypes", + "createClass", + "createFactory", + "createMixin", + "DOM", + "__spread", +] as const; + +const DOM_PROPERTIES = [ + "findDOMNode", + "render", + "unmountComponentAtNode", + "unstable_batchedUpdates", + "unstable_renderSubtreeIntoContainer", +] as const; + +const DOM_SERVER_PROPERTIES = [ + "renderToString", + "renderToStaticMarkup", +] as const; + +const CORE_SET = new Set(CORE_PROPERTIES); +const DOM_SET = new Set(DOM_PROPERTIES); +const DOM_SERVER_SET = new Set(DOM_SERVER_PROPERTIES); + +const MODULE_PAIRS: ModulePair[] = [ + { + core: "React", + dom: "ReactDOM", + server: "ReactDOMServer", + domAlias: "ReactDOM", + serverAlias: "ReactDOMServer", + }, + { + core: "react", + dom: "react-dom", + server: "react-dom/server", + domAlias: "ReactDOM", + serverAlias: "ReactDOMServer", + }, +]; + +function metricFile(filename: string): string { + const cwd = process.cwd() + "/"; + return filename.startsWith(cwd) ? filename.slice(cwd.length) : filename; +} + +function namedChildren(node: SgNode | null): SgNode[] { + if (!node) { + return []; + } + + return node.children().filter((child) => child.isNamed() && child.kind() !== "comment"); +} + +function exactRule(kind: "identifier" | "type_identifier" | "property_identifier", name: string) { + return { + kind, + regex: `^${name}$`, + } as const; +} + +function statementRange(node: SgNode, source: string): { start: number; end: number } { + const range = node.range(); + let end = range.end.index; + while (end < source.length && (source[end] === "\n" || source[end] === "\r")) { + end++; + } + return { start: range.start.index, end }; +} + +function sourceText(node: SgNode): string | null { + const fragment = node.find({ rule: { kind: "string_fragment" } }); + if (fragment) { + return fragment.text(); + } + + const text = node.text(); + if (text.length >= 2) { + return text.slice(1, -1); + } + + return null; +} + +function importSource(node: SgNode): string | null { + const source = node.field("source") ?? node.find({ rule: { kind: "string" } }); + return source ? sourceText(source) : null; +} + +function quoteForImport(node: SgNode): string { + const source = node.field("source") ?? node.find({ rule: { kind: "string" } }); + const text = source?.text() ?? "'react'"; + return text.startsWith("\"") ? "\"" : "'"; +} + +function callArguments(call: SgNode): SgNode[] { + return namedChildren(call.field("arguments")); +} + +function isRequireOf(node: SgNode | null, moduleName: string): boolean { + if (!node || node.kind() !== "call_expression") { + return false; + } + + const callee = node.field("function"); + const firstArg = callArguments(node)[0]; + return callee?.kind() === "identifier" && + callee.text() === "require" && + firstArg?.kind() === "string" && + sourceText(firstArg) === moduleName; +} + +function declarationKind(statement: SgNode): "var" | "let" | "const" { + const text = statement.text(); + if (text.startsWith("const ")) return "const"; + if (text.startsWith("let ")) return "let"; + return "var"; +} + +function buildImportLine( + quote: string, + source: string, + defaultAlias: string | null, + named: NamedSpecifier[], +): string { + const parts: string[] = []; + if (defaultAlias) { + parts.push(defaultAlias); + } + if (named.length > 0) { + const rendered = named.map((spec) => + spec.alias && spec.alias !== spec.name ? `${spec.name} as ${spec.alias}` : spec.name + ); + parts.push(`{ ${rendered.join(", ")} }`); + } + return `import ${parts.join(", ")} from ${quote}${source}${quote};`; +} + +function buildRequireLine(kind: "var" | "let" | "const", alias: string, source: string): string { + return `${kind} ${alias} = require('${source}');`; +} + +function buildRequireDeclaration(kind: "var" | "let" | "const", alias: string): string { + return `${kind} ${alias};`; +} + +function buildRequireAssignment(alias: string, source: string): string { + return `${alias} = require('${source}');`; +} + +function uniqueSpecs(specs: NamedSpecifier[]): NamedSpecifier[] { + const seen = new Set(); + const result: NamedSpecifier[] = []; + for (const spec of specs) { + const key = `${spec.name}:${spec.alias ?? ""}`; + if (!seen.has(key)) { + seen.add(key); + result.push(spec); + } + } + return result; +} + +function sortSpecs(specs: NamedSpecifier[]): NamedSpecifier[] { + return [...specs].sort((left, right) => left.name.localeCompare(right.name)); +} + +function findModuleImports(rootNode: SgNode, moduleName: string): ImportBinding { + const statements = rootNode.findAll({ rule: { kind: "import_statement" } }) + .filter((stmt) => importSource(stmt) === moduleName); + + const named: NamedSpecifier[] = []; + let alias: string | null = null; + let quote = "'"; + + for (const stmt of statements) { + quote = quoteForImport(stmt); + const clause = stmt.find({ rule: { kind: "import_clause" } }); + if (!clause) { + continue; + } + + const defaultIdentifier = namedChildren(clause) + .find((child) => child.kind() === "identifier"); + if (defaultIdentifier) { + alias = defaultIdentifier.text(); + } + + for (const spec of stmt.findAll({ rule: { kind: "import_specifier" } })) { + named.push({ + name: spec.field("name")?.text() ?? spec.text(), + alias: spec.field("alias")?.text() ?? undefined, + }); + } + } + + return { + alias, + named: uniqueSpecs(named), + statements, + quote, + }; +} + +function findRequireBinding(rootNode: SgNode, moduleName: string): RequireBinding | null { + for (const declarator of rootNode.findAll({ rule: { kind: "variable_declarator" } })) { + const name = declarator.field("name"); + const value = declarator.field("value"); + if (name?.kind() !== "identifier" || !isRequireOf(value, moduleName)) { + continue; + } + + const statement = declarator.ancestors().find((ancestor) => + ancestor.kind() === "variable_declaration" || ancestor.kind() === "lexical_declaration" + ); + if (!statement) { + continue; + } + + return { + alias: name.text(), + statement, + kind: declarationKind(statement), + }; + } + + return null; +} + +function findAssignmentBinding(rootNode: SgNode, moduleName: string): AssignmentBinding | null { + for (const assignment of rootNode.findAll({ rule: { kind: "assignment_expression" } })) { + const left = assignment.field("left"); + const right = assignment.field("right"); + if (left?.kind() !== "identifier" || !isRequireOf(right, moduleName)) { + continue; + } + + const assignmentStatement = assignment.ancestors().find((ancestor) => ancestor.kind() === "expression_statement"); + if (!assignmentStatement) { + continue; + } + + const alias = left.text(); + const bareDeclaration = rootNode.findAll({ rule: { kind: "variable_declarator" } }) + .find((declarator) => declarator.field("name")?.text() === alias && !declarator.field("value")); + const declaration = bareDeclaration?.ancestors().find((ancestor) => + ancestor.kind() === "variable_declaration" || ancestor.kind() === "lexical_declaration" + ) ?? null; + + return { + alias, + declaration, + assignment: assignmentStatement, + kind: declaration ? declarationKind(declaration) : "var", + }; + } + + return null; +} + +function hasCoreSource(rootNode: SgNode, moduleName: string): boolean { + return findModuleImports(rootNode, moduleName).statements.length > 0 || + findRequireBinding(rootNode, moduleName) !== null || + findAssignmentBinding(rootNode, moduleName) !== null; +} + +function activePair(rootNode: SgNode): ModulePair | null { + for (const pair of MODULE_PAIRS) { + if (hasCoreSource(rootNode, pair.core)) { + return pair; + } + } + + return null; +} + +function hasJSX(rootNode: SgNode): boolean { + return rootNode.findAll({ + rule: { + any: [ + { kind: "jsx_element" }, + { kind: "jsx_self_closing_element" }, + ], + }, + }).length > 0; +} + +function isImportContext(node: SgNode): boolean { + return node.ancestors().some((ancestor) => ancestor.kind() === "import_statement"); +} + +function memberPropertyName(node: SgNode): string | null { + if (node.kind() === "member_expression") { + return node.field("property")?.text() ?? null; + } + + if (node.kind() === "nested_type_identifier") { + const children = namedChildren(node); + return children[1]?.text() ?? null; + } + + return null; +} + +function memberObjectNode(node: SgNode): SgNode | null { + if (node.kind() === "member_expression") { + return node.field("object"); + } + + if (node.kind() === "nested_type_identifier") { + return namedChildren(node)[0] ?? null; + } + + return null; +} + +function isIdentifierNode(node: SgNode | null, name: string): boolean { + return node?.kind() === "identifier" && node.text() === name; +} + +function classifyProperty(name: string): "core" | "dom" | "server" | "unknown" { + if (CORE_SET.has(name)) return "core"; + if (DOM_SET.has(name)) return "dom"; + if (DOM_SERVER_SET.has(name)) return "server"; + return "unknown"; +} + +function analyzeReactMembers( + rootNode: SgNode, + reactAlias: string, + domAlias: string, + serverAlias: string, +): MemberAnalysis { + const edits: Edit[] = []; + let domUses = 0; + let serverUses = 0; + let coreUses = 0; + + const nodes = rootNode.findAll({ + rule: { + any: [ + { kind: "member_expression" }, + { kind: "nested_type_identifier" }, + ], + }, + }); + + for (const node of nodes) { + if (node.kind() === "member_expression" && isImportContext(node)) { + continue; + } + + const object = memberObjectNode(node); + const propertyName = memberPropertyName(node); + if (!isIdentifierNode(object, reactAlias) || !propertyName) { + continue; + } + + const category = classifyProperty(propertyName); + if (category === "unknown") { + throw new Error(`Unknown property React.${propertyName}`); + } + + if (category === "core") { + coreUses++; + continue; + } + + edits.push(object!.replace(category === "dom" ? domAlias : serverAlias)); + if (category === "dom") { + domUses++; + } else { + serverUses++; + } + } + + return { edits, domUses, serverUses, coreUses }; +} + +function destructuredNames(pattern: SgNode): string[] { + return pattern.findAll({ + rule: { + any: [ + { kind: "shorthand_property_identifier_pattern" }, + { kind: "pair_pattern" }, + ], + }, + }).map((property) => { + if (property.kind() === "shorthand_property_identifier_pattern") { + return property.text(); + } + return property.field("key")?.text() ?? property.text(); + }); +} + +function analyzeReactDestructures( + rootNode: SgNode, + reactAlias: string, + domAlias: string, +): DestructureAnalysis { + const edits: Edit[] = []; + let domUses = 0; + let coreUses = 0; + + for (const declarator of rootNode.findAll({ rule: { kind: "variable_declarator" } })) { + const pattern = declarator.field("name"); + const value = declarator.field("value"); + if (pattern?.kind() !== "object_pattern" || !isIdentifierNode(value, reactAlias)) { + continue; + } + + const core: string[] = []; + const dom: string[] = []; + + for (const name of destructuredNames(pattern)) { + const category = classifyProperty(name); + if (category === "unknown" || category === "server") { + throw new Error(`Unknown property React.${name} while destructuring`); + } + if (category === "core") { + core.push(name); + } else { + dom.push(name); + } + } + + if (dom.length === 0) { + coreUses++; + continue; + } + + if (core.length === 0) { + edits.push(value!.replace(domAlias)); + domUses++; + continue; + } + + const statement = declarator.ancestors().find((ancestor) => + ancestor.kind() === "variable_declaration" || ancestor.kind() === "lexical_declaration" + ); + if (!statement) { + continue; + } + + const declarators = namedChildren(statement).filter((child) => child.kind() === "variable_declarator"); + if (declarators.length !== 1) { + throw new Error("Mixed React destructuring with multiple declarators is unsupported"); + } + + const kind = declarationKind(statement); + edits.push(statement.replace( + `${kind} { ${core.join(", ")} } = ${reactAlias};\n${kind} { ${dom.join(", ")} } = ${domAlias};`, + )); + domUses++; + coreUses++; + } + + return { edits, domUses, coreUses }; +} + +function namedFromReact(binding: ImportBinding, names: readonly string[]): NamedSpecifier[] { + const wanted = new Set(names); + return binding.named.filter((spec) => wanted.has(spec.name)); +} + +function withoutNamed(binding: ImportBinding, names: readonly string[]): NamedSpecifier[] { + const removed = new Set(names); + return binding.named.filter((spec) => !removed.has(spec.name)); +} + +function hasIdentifierReference(rootNode: SgNode, name: string): boolean { + return rootNode.findAll({ + rule: { + any: [ + exactRule("identifier", name), + exactRule("type_identifier", name), + exactRule("property_identifier", name), + ], + }, + }).length > 0; +} + +const transform: Transform = async (root) => { + const rootNode = root.root(); + const source = rootNode.text(); + const metric = useMetricAtom("react-to-react-dom-migrations"); + const edits: Edit[] = []; + + const pair = activePair(rootNode); + if (!pair) { + return null; + } + + const reactImport = findModuleImports(rootNode, pair.core); + const reactDomImport = findModuleImports(rootNode, pair.dom); + const reactDomServerImport = findModuleImports(rootNode, pair.server); + const reactRequire = findRequireBinding(rootNode, pair.core); + const reactAssign = findAssignmentBinding(rootNode, pair.core); + const reactDomRequire = findRequireBinding(rootNode, pair.dom); + const reactDomServerRequire = findRequireBinding(rootNode, pair.server); + + const coreDeclarationCount = reactImport.statements.length + (reactRequire ? 1 : 0) + (reactAssign ? 1 : 0); + if (coreDeclarationCount === 0) { + return null; + } + if (coreDeclarationCount > 1) { + throw new Error("Multiple declarations of React"); + } + + const reactAlias = reactImport.alias ?? reactRequire?.alias ?? reactAssign?.alias ?? null; + const domAlias = reactDomImport.alias ?? reactDomRequire?.alias ?? pair.domAlias; + const serverAlias = reactDomServerImport.alias ?? reactDomServerRequire?.alias ?? pair.serverAlias; + + const memberAnalysis = reactAlias + ? analyzeReactMembers(rootNode, reactAlias, domAlias, serverAlias) + : { edits: [], domUses: 0, serverUses: 0, coreUses: 0 }; + const destructureAnalysis = reactAlias + ? analyzeReactDestructures(rootNode, reactAlias, domAlias) + : { edits: [], domUses: 0, coreUses: 0 }; + + const domNamedFromReact = namedFromReact(reactImport, DOM_PROPERTIES); + const serverNamedFromReact = namedFromReact(reactImport, DOM_SERVER_PROPERTIES); + const coreNamedFromReact = withoutNamed(reactImport, [...DOM_PROPERTIES, ...DOM_SERVER_PROPERTIES]); + + const needsDomBinding = + memberAnalysis.domUses > 0 || + destructureAnalysis.domUses > 0 || + domNamedFromReact.length > 0 || + reactDomImport.statements.length > 0; + const needsServerBinding = + memberAnalysis.serverUses > 0 || + serverNamedFromReact.length > 0 || + reactDomServerImport.statements.length > 0; + + if (needsDomBinding && !reactDomImport.alias && !reactDomRequire && hasIdentifierReference(rootNode, domAlias)) { + throw new Error("ReactDOM is already defined in a different scope than React"); + } + if (needsServerBinding && !reactDomServerImport.alias && !reactDomServerRequire && hasIdentifierReference(rootNode, serverAlias)) { + throw new Error("ReactDOMServer is already defined in a different scope than React"); + } + + edits.push(...memberAnalysis.edits, ...destructureAnalysis.edits); + + if (reactImport.statements.length > 0) { + const quote = reactImport.quote; + const keepReactDefault = reactImport.alias !== null && ( + hasJSX(rootNode) || + memberAnalysis.coreUses > 0 || + destructureAnalysis.coreUses > 0 + ); + + const nextReactNamed = sortSpecs(coreNamedFromReact); + const nextDomNamed = sortSpecs(uniqueSpecs([...reactDomImport.named, ...domNamedFromReact])); + const nextServerNamed = sortSpecs(uniqueSpecs([...reactDomServerImport.named, ...serverNamedFromReact])); + + const lines: string[] = []; + if (keepReactDefault || nextReactNamed.length > 0) { + lines.push(buildImportLine(quote, pair.core, keepReactDefault ? reactImport.alias : null, nextReactNamed)); + } + if (!reactDomRequire && needsDomBinding) { + const defaultAlias = reactDomImport.alias ?? (memberAnalysis.domUses > 0 || destructureAnalysis.domUses > 0 ? domAlias : null); + lines.push(buildImportLine(quote, pair.dom, defaultAlias, nextDomNamed)); + } + if (!reactDomServerRequire && needsServerBinding) { + const defaultAlias = reactDomServerImport.alias ?? (memberAnalysis.serverUses > 0 ? serverAlias : null); + lines.push(buildImportLine(quote, pair.server, defaultAlias, nextServerNamed)); + } + + const allImportStatements = [ + ...reactImport.statements, + ...reactDomImport.statements, + ...reactDomServerImport.statements, + ]; + const ranges = allImportStatements.map((stmt) => statementRange(stmt, source)); + const start = Math.min(...ranges.map((range) => range.start)); + const end = Math.max(...ranges.map((range) => range.end)); + edits.push({ + startPos: start, + endPos: end, + insertedText: lines.length > 0 ? `${lines.join("\n")}\n\n` : "", + }); + } + + if (reactRequire) { + const keepReact = hasJSX(rootNode) || memberAnalysis.coreUses > 0 || destructureAnalysis.coreUses > 0; + const inserted: string[] = []; + if (!reactDomRequire && (memberAnalysis.domUses > 0 || destructureAnalysis.domUses > 0)) { + inserted.push(buildRequireLine(reactRequire.kind, domAlias, pair.dom)); + } + if (!reactDomServerRequire && memberAnalysis.serverUses > 0) { + inserted.push(buildRequireLine(reactRequire.kind, serverAlias, pair.server)); + } + + const range = statementRange(reactRequire.statement, source); + if (keepReact) { + if (inserted.length > 0) { + edits.push({ + startPos: range.start, + endPos: range.end, + insertedText: `${reactRequire.statement.text()}\n\n${inserted.join("\n")}\n\n`, + }); + } + } else { + edits.push({ + startPos: range.start, + endPos: range.end, + insertedText: inserted.length > 0 ? `${inserted.join("\n")}\n` : "", + }); + } + } + + if (reactAssign) { + const declarationLines: string[] = []; + const assignmentLines: string[] = []; + + if (!reactDomRequire && (memberAnalysis.domUses > 0 || destructureAnalysis.domUses > 0)) { + declarationLines.push(buildRequireDeclaration(reactAssign.kind, domAlias)); + assignmentLines.push(buildRequireAssignment(domAlias, pair.dom)); + } + if (!reactDomServerRequire && memberAnalysis.serverUses > 0) { + declarationLines.push(buildRequireDeclaration(reactAssign.kind, serverAlias)); + assignmentLines.push(buildRequireAssignment(serverAlias, pair.server)); + } + + if (declarationLines.length > 0 && reactAssign.declaration) { + const declRange = statementRange(reactAssign.declaration, source); + edits.push({ + startPos: declRange.start, + endPos: declRange.end, + insertedText: `${reactAssign.declaration.text()}\n\n${declarationLines.join("\n")}\n\n`, + }); + } + if (assignmentLines.length > 0) { + const assignRange = statementRange(reactAssign.assignment, source); + edits.push({ + startPos: assignRange.start, + endPos: assignRange.end, + insertedText: `${reactAssign.assignment.text()}\n${assignmentLines.join("\n")}\n`, + }); + } + } + + if (edits.length === 0) { + return null; + } + + metric.increment({ + domMembers: String(memberAnalysis.domUses + destructureAnalysis.domUses + domNamedFromReact.length), + serverMembers: String(memberAnalysis.serverUses + serverNamedFromReact.length), + file: metricFile(root.filename()), + }); + + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/react-to-react-dom/scripts/error-tests.mjs b/codemods/react-to-react-dom/scripts/error-tests.mjs new file mode 100644 index 0000000..118f9c7 --- /dev/null +++ b/codemods/react-to-react-dom/scripts/error-tests.mjs @@ -0,0 +1,82 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, writeFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { spawnSync } from "node:child_process"; + +const __filename = fileURLToPath(import.meta.url); +const packageDir = path.dirname(path.dirname(__filename)); +const workflowPath = path.join(packageDir, "workflow.yaml"); + +function runCodemod(input) { + const target = mkdtempSync(path.join(tmpdir(), "react-to-react-dom-")); + const filePath = path.join(target, "input.tsx"); + writeFileSync(filePath, input, "utf8"); + + try { + return spawnSync( + "pnpm", + [ + "dlx", + "codemod@latest", + "workflow", + "run", + "-w", + workflowPath, + "--target", + target, + "--allow-dirty", + ], + { + cwd: packageDir, + encoding: "utf8", + }, + ); + } finally { + rmSync(target, { recursive: true, force: true }); + } +} + +function expectFailure(input, pattern) { + const result = runCodemod(input); + const combined = `${result.stdout}\n${result.stderr}`; + assert.match(combined, pattern); + assert.match(combined, /Failed to execute codemod|InitializationFailed/); +} + +test("throws on conflicting ReactDOM binding", () => { + expectFailure( + "const ReactDOM = 1;\nimport React from 'react';\nReact.render(null);\n", + /ReactDOM is already defined in a different scope than React/, + ); +}); + +test("throws on conflicting ReactDOMServer binding", () => { + expectFailure( + "const ReactDOMServer = 1;\nimport React from 'react';\nReact.renderToString(null);\n", + /ReactDOMServer is already defined in a different scope than React/, + ); +}); + +test("throws on multiple React declarations", () => { + expectFailure( + "import React from 'react';\nimport Foo from 'react';\nReact.render(null);\n", + /Multiple declarations of React/, + ); +}); + +test("throws on unknown React member", () => { + expectFailure( + "import React from 'react';\nconst x = React.Fragment;\n", + /Unknown property React\.Fragment/, + ); +}); + +test("throws on unknown React destructuring", () => { + expectFailure( + "var React;\nReact = require('react');\nlet { Fragment } = React;\n", + /Unknown property React\.Fragment while destructuring/, + ); +}); diff --git a/codemods/react-to-react-dom/tests/import-dom-base-uppercase/expected.tsx b/codemods/react-to-react-dom/tests/import-dom-base-uppercase/expected.tsx new file mode 100644 index 0000000..8ece2af --- /dev/null +++ b/codemods/react-to-react-dom/tests/import-dom-base-uppercase/expected.tsx @@ -0,0 +1,4 @@ +import ReactDOM from 'ReactDOM'; + +ReactDOM.findDOMNode(); +ReactDOM.render(null); diff --git a/codemods/react-to-react-dom/tests/import-dom-base-uppercase/input.tsx b/codemods/react-to-react-dom/tests/import-dom-base-uppercase/input.tsx new file mode 100644 index 0000000..ae38804 --- /dev/null +++ b/codemods/react-to-react-dom/tests/import-dom-base-uppercase/input.tsx @@ -0,0 +1,4 @@ +import React from 'React'; + +React.findDOMNode(); +React.render(null); diff --git a/codemods/react-to-react-dom/tests/import-dom-base-uppercase/metrics.json b/codemods/react-to-react-dom/tests/import-dom-base-uppercase/metrics.json new file mode 100644 index 0000000..be38b7d --- /dev/null +++ b/codemods/react-to-react-dom/tests/import-dom-base-uppercase/metrics.json @@ -0,0 +1,12 @@ +{ + "react-to-react-dom-migrations": [ + { + "cardinality": { + "domMembers": "2", + "file": "tests/import-dom-base-uppercase/input.tsx", + "serverMembers": "0" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-dom-base.output.js b/codemods/react-to-react-dom/tests/import-dom-base/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-dom-base.output.js rename to codemods/react-to-react-dom/tests/import-dom-base/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-dom-base.input.js b/codemods/react-to-react-dom/tests/import-dom-base/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-dom-base.input.js rename to codemods/react-to-react-dom/tests/import-dom-base/input.tsx diff --git a/codemods/react-to-react-dom/tests/import-dom-base/metrics.json b/codemods/react-to-react-dom/tests/import-dom-base/metrics.json new file mode 100644 index 0000000..a191839 --- /dev/null +++ b/codemods/react-to-react-dom/tests/import-dom-base/metrics.json @@ -0,0 +1,12 @@ +{ + "react-to-react-dom-migrations": [ + { + "cardinality": { + "domMembers": "6", + "file": "tests/import-dom-base/input.tsx", + "serverMembers": "0" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/react-to-react-dom/tests/import-dom-type-identifier/expected.tsx b/codemods/react-to-react-dom/tests/import-dom-type-identifier/expected.tsx new file mode 100644 index 0000000..dd1f21f --- /dev/null +++ b/codemods/react-to-react-dom/tests/import-dom-type-identifier/expected.tsx @@ -0,0 +1,5 @@ +import ReactDOM from 'react-dom'; + +type RenderRef = ReactDOM.render; + +ReactDOM.render(null); diff --git a/codemods/react-to-react-dom/tests/import-dom-type-identifier/input.tsx b/codemods/react-to-react-dom/tests/import-dom-type-identifier/input.tsx new file mode 100644 index 0000000..3decd23 --- /dev/null +++ b/codemods/react-to-react-dom/tests/import-dom-type-identifier/input.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +type RenderRef = React.render; + +React.render(null); diff --git a/codemods/react-to-react-dom/tests/import-dom-type-identifier/metrics.json b/codemods/react-to-react-dom/tests/import-dom-type-identifier/metrics.json new file mode 100644 index 0000000..3818519 --- /dev/null +++ b/codemods/react-to-react-dom/tests/import-dom-type-identifier/metrics.json @@ -0,0 +1,12 @@ +{ + "react-to-react-dom-migrations": [ + { + "cardinality": { + "domMembers": "2", + "file": "tests/import-dom-type-identifier/input.tsx", + "serverMembers": "0" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-multiple-specifiers.output.js b/codemods/react-to-react-dom/tests/import-multiple-specifiers/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-multiple-specifiers.output.js rename to codemods/react-to-react-dom/tests/import-multiple-specifiers/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-multiple-specifiers.input.js b/codemods/react-to-react-dom/tests/import-multiple-specifiers/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-multiple-specifiers.input.js rename to codemods/react-to-react-dom/tests/import-multiple-specifiers/input.tsx diff --git a/codemods/react-to-react-dom/tests/import-multiple-specifiers/metrics.json b/codemods/react-to-react-dom/tests/import-multiple-specifiers/metrics.json new file mode 100644 index 0000000..afb685e --- /dev/null +++ b/codemods/react-to-react-dom/tests/import-multiple-specifiers/metrics.json @@ -0,0 +1,12 @@ +{ + "react-to-react-dom-migrations": [ + { + "cardinality": { + "domMembers": "2", + "file": "tests/import-multiple-specifiers/input.tsx", + "serverMembers": "0" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-server-base.output.js b/codemods/react-to-react-dom/tests/import-server-base/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-server-base.output.js rename to codemods/react-to-react-dom/tests/import-server-base/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-server-base.input.js b/codemods/react-to-react-dom/tests/import-server-base/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-server-base.input.js rename to codemods/react-to-react-dom/tests/import-server-base/input.tsx diff --git a/codemods/react-to-react-dom/tests/import-server-base/metrics.json b/codemods/react-to-react-dom/tests/import-server-base/metrics.json new file mode 100644 index 0000000..899310c --- /dev/null +++ b/codemods/react-to-react-dom/tests/import-server-base/metrics.json @@ -0,0 +1,12 @@ +{ + "react-to-react-dom-migrations": [ + { + "cardinality": { + "domMembers": "0", + "file": "tests/import-server-base/input.tsx", + "serverMembers": "2" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-with-existing-react-dom.output.js b/codemods/react-to-react-dom/tests/import-with-existing-react-dom/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-with-existing-react-dom.output.js rename to codemods/react-to-react-dom/tests/import-with-existing-react-dom/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-with-existing-react-dom.input.js b/codemods/react-to-react-dom/tests/import-with-existing-react-dom/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-with-existing-react-dom.input.js rename to codemods/react-to-react-dom/tests/import-with-existing-react-dom/input.tsx diff --git a/codemods/react-to-react-dom/tests/import-with-existing-react-dom/metrics.json b/codemods/react-to-react-dom/tests/import-with-existing-react-dom/metrics.json new file mode 100644 index 0000000..7767d53 --- /dev/null +++ b/codemods/react-to-react-dom/tests/import-with-existing-react-dom/metrics.json @@ -0,0 +1,12 @@ +{ + "react-to-react-dom-migrations": [ + { + "cardinality": { + "domMembers": "2", + "file": "tests/import-with-existing-react-dom/input.tsx", + "serverMembers": "0" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-without-default-specifier.output.js b/codemods/react-to-react-dom/tests/import-without-default-specifier/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-without-default-specifier.output.js rename to codemods/react-to-react-dom/tests/import-without-default-specifier/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-without-default-specifier.input.js b/codemods/react-to-react-dom/tests/import-without-default-specifier/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-without-default-specifier.input.js rename to codemods/react-to-react-dom/tests/import-without-default-specifier/input.tsx diff --git a/codemods/react-to-react-dom/tests/import-without-default-specifier/metrics.json b/codemods/react-to-react-dom/tests/import-without-default-specifier/metrics.json new file mode 100644 index 0000000..289eccc --- /dev/null +++ b/codemods/react-to-react-dom/tests/import-without-default-specifier/metrics.json @@ -0,0 +1,12 @@ +{ + "react-to-react-dom-migrations": [ + { + "cardinality": { + "domMembers": "1", + "file": "tests/import-without-default-specifier/input.tsx", + "serverMembers": "0" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/mixed-with-existing-react-dom.output.js b/codemods/react-to-react-dom/tests/mixed-with-existing-react-dom/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/mixed-with-existing-react-dom.output.js rename to codemods/react-to-react-dom/tests/mixed-with-existing-react-dom/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/mixed-with-existing-react-dom.input.js b/codemods/react-to-react-dom/tests/mixed-with-existing-react-dom/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/mixed-with-existing-react-dom.input.js rename to codemods/react-to-react-dom/tests/mixed-with-existing-react-dom/input.tsx diff --git a/codemods/react-to-react-dom/tests/mixed-with-existing-react-dom/metrics.json b/codemods/react-to-react-dom/tests/mixed-with-existing-react-dom/metrics.json new file mode 100644 index 0000000..e903a51 --- /dev/null +++ b/codemods/react-to-react-dom/tests/mixed-with-existing-react-dom/metrics.json @@ -0,0 +1,12 @@ +{ + "react-to-react-dom-migrations": [ + { + "cardinality": { + "domMembers": "1", + "file": "tests/mixed-with-existing-react-dom/input.tsx", + "serverMembers": "0" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/react-to-react-dom/tests/require-dom-base-uppercase/expected.tsx b/codemods/react-to-react-dom/tests/require-dom-base-uppercase/expected.tsx new file mode 100644 index 0000000..635b207 --- /dev/null +++ b/codemods/react-to-react-dom/tests/require-dom-base-uppercase/expected.tsx @@ -0,0 +1,4 @@ +var ReactDOM = require('ReactDOM'); + +ReactDOM.findDOMNode(); +ReactDOM.render(null); diff --git a/codemods/react-to-react-dom/tests/require-dom-base-uppercase/input.tsx b/codemods/react-to-react-dom/tests/require-dom-base-uppercase/input.tsx new file mode 100644 index 0000000..7f949f8 --- /dev/null +++ b/codemods/react-to-react-dom/tests/require-dom-base-uppercase/input.tsx @@ -0,0 +1,4 @@ +var React = require('React'); + +React.findDOMNode(); +React.render(null); diff --git a/codemods/react-to-react-dom/tests/require-dom-base-uppercase/metrics.json b/codemods/react-to-react-dom/tests/require-dom-base-uppercase/metrics.json new file mode 100644 index 0000000..c46bb29 --- /dev/null +++ b/codemods/react-to-react-dom/tests/require-dom-base-uppercase/metrics.json @@ -0,0 +1,12 @@ +{ + "react-to-react-dom-migrations": [ + { + "cardinality": { + "domMembers": "2", + "file": "tests/require-dom-base-uppercase/input.tsx", + "serverMembers": "0" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-dom-base.output.js b/codemods/react-to-react-dom/tests/require-dom-base/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-dom-base.output.js rename to codemods/react-to-react-dom/tests/require-dom-base/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-dom-base.input.js b/codemods/react-to-react-dom/tests/require-dom-base/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-dom-base.input.js rename to codemods/react-to-react-dom/tests/require-dom-base/input.tsx diff --git a/codemods/react-to-react-dom/tests/require-dom-base/metrics.json b/codemods/react-to-react-dom/tests/require-dom-base/metrics.json new file mode 100644 index 0000000..b8c1387 --- /dev/null +++ b/codemods/react-to-react-dom/tests/require-dom-base/metrics.json @@ -0,0 +1,12 @@ +{ + "react-to-react-dom-migrations": [ + { + "cardinality": { + "domMembers": "6", + "file": "tests/require-dom-base/input.tsx", + "serverMembers": "0" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/react-to-react-dom/tests/require-indirect-mixed/expected.tsx b/codemods/react-to-react-dom/tests/require-indirect-mixed/expected.tsx new file mode 100644 index 0000000..c7e5c74 --- /dev/null +++ b/codemods/react-to-react-dom/tests/require-indirect-mixed/expected.tsx @@ -0,0 +1,12 @@ +var React; + +var ReactDOM; + +React = require('react'); +ReactDOM = require('react-dom'); +let { Component } = React; +let { render } = ReactDOM; + +class Foo extends Component {} + +render(
); diff --git a/codemods/react-to-react-dom/tests/require-indirect-mixed/input.tsx b/codemods/react-to-react-dom/tests/require-indirect-mixed/input.tsx new file mode 100644 index 0000000..7180a6a --- /dev/null +++ b/codemods/react-to-react-dom/tests/require-indirect-mixed/input.tsx @@ -0,0 +1,8 @@ +var React; + +React = require('react'); +let { Component, render } = React; + +class Foo extends Component {} + +render(
); diff --git a/codemods/react-to-react-dom/tests/require-indirect-mixed/metrics.json b/codemods/react-to-react-dom/tests/require-indirect-mixed/metrics.json new file mode 100644 index 0000000..de32ab7 --- /dev/null +++ b/codemods/react-to-react-dom/tests/require-indirect-mixed/metrics.json @@ -0,0 +1,12 @@ +{ + "react-to-react-dom-migrations": [ + { + "cardinality": { + "domMembers": "1", + "file": "tests/require-indirect-mixed/input.tsx", + "serverMembers": "0" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-indirect.output.js b/codemods/react-to-react-dom/tests/require-indirect/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-indirect.output.js rename to codemods/react-to-react-dom/tests/require-indirect/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-indirect.input.js b/codemods/react-to-react-dom/tests/require-indirect/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-indirect.input.js rename to codemods/react-to-react-dom/tests/require-indirect/input.tsx diff --git a/codemods/react-to-react-dom/tests/require-indirect/metrics.json b/codemods/react-to-react-dom/tests/require-indirect/metrics.json new file mode 100644 index 0000000..b9d4a53 --- /dev/null +++ b/codemods/react-to-react-dom/tests/require-indirect/metrics.json @@ -0,0 +1,12 @@ +{ + "react-to-react-dom-migrations": [ + { + "cardinality": { + "domMembers": "1", + "file": "tests/require-indirect/input.tsx", + "serverMembers": "0" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-keeps-react.output.js b/codemods/react-to-react-dom/tests/require-keeps-react/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-keeps-react.output.js rename to codemods/react-to-react-dom/tests/require-keeps-react/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-keeps-react.input.js b/codemods/react-to-react-dom/tests/require-keeps-react/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-keeps-react.input.js rename to codemods/react-to-react-dom/tests/require-keeps-react/input.tsx diff --git a/codemods/react-to-react-dom/tests/require-keeps-react/metrics.json b/codemods/react-to-react-dom/tests/require-keeps-react/metrics.json new file mode 100644 index 0000000..397e979 --- /dev/null +++ b/codemods/react-to-react-dom/tests/require-keeps-react/metrics.json @@ -0,0 +1,12 @@ +{ + "react-to-react-dom-migrations": [ + { + "cardinality": { + "domMembers": "1", + "file": "tests/require-keeps-react/input.tsx", + "serverMembers": "1" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-server-base.output.js b/codemods/react-to-react-dom/tests/require-server-base/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-server-base.output.js rename to codemods/react-to-react-dom/tests/require-server-base/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-server-base.input.js b/codemods/react-to-react-dom/tests/require-server-base/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-server-base.input.js rename to codemods/react-to-react-dom/tests/require-server-base/input.tsx diff --git a/codemods/react-to-react-dom/tests/require-server-base/metrics.json b/codemods/react-to-react-dom/tests/require-server-base/metrics.json new file mode 100644 index 0000000..8ad5612 --- /dev/null +++ b/codemods/react-to-react-dom/tests/require-server-base/metrics.json @@ -0,0 +1,12 @@ +{ + "react-to-react-dom-migrations": [ + { + "cardinality": { + "domMembers": "0", + "file": "tests/require-server-base/input.tsx", + "serverMembers": "2" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/react-to-react-dom/tsconfig.json b/codemods/react-to-react-dom/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/react-to-react-dom/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": [ + "ESNext" + ], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": [ + "@codemod.com/jssg-types", + "node" + ], + "allowImportingTsExtensions": true, + "noEmit": true, + "skipLibCheck": true, + "verbatimModuleSyntax": false, + "erasableSyntaxOnly": true, + "strict": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true + }, + "exclude": [ + "tests" + ] +} diff --git a/codemods/react-to-react-dom/workflow.yaml b/codemods/react-to-react-dom/workflow.yaml new file mode 100644 index 0000000..8773841 --- /dev/null +++ b/codemods/react-to-react-dom/workflow.yaml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: "Scan tsx files and apply fixes" + js-ast-grep: + js_file: scripts/codemod.ts + language: "tsx" diff --git a/codemods/remove-context-provider/README.md b/codemods/remove-context-provider/README.md new file mode 100644 index 0000000..e79cf5c --- /dev/null +++ b/codemods/remove-context-provider/README.md @@ -0,0 +1,9 @@ +# remove-context-provider + +Replace `Context.Provider` JSX tags with `Context`. + +## Usage + +```bash +npx codemod @react-new/remove-context-provider --target +``` diff --git a/codemods/remove-context-provider/codemod.yaml b/codemods/remove-context-provider/codemod.yaml new file mode 100644 index 0000000..155a410 --- /dev/null +++ b/codemods/remove-context-provider/codemod.yaml @@ -0,0 +1,19 @@ +schema_version: "1.0" + +name: "@react-new/remove-context-provider" +version: "0.1.1" +description: "Replace Context.Provider with Context in JSX" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + +targets: + languages: ["tsx"] + +keywords: ["React", "Context"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/remove-context-provider/package.json b/codemods/remove-context-provider/package.json new file mode 100644 index 0000000..9930e8c --- /dev/null +++ b/codemods/remove-context-provider/package.json @@ -0,0 +1,15 @@ +{ + "name": "@react-new/remove-context-provider", + "version": "0.1.1", + "description": "Replace Context.Provider with Context in JSX", + "type": "module", + "scripts": { + "test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@codemod.com/jssg-types": "latest", + "@types/node": "latest", + "typescript": "latest" + } +} diff --git a/codemods/remove-context-provider/scripts/codemod.ts b/codemods/remove-context-provider/scripts/codemod.ts new file mode 100644 index 0000000..4670c2a --- /dev/null +++ b/codemods/remove-context-provider/scripts/codemod.ts @@ -0,0 +1,58 @@ +import type { Transform, Edit, SgNode } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { useMetricAtom } from "codemod:metrics"; + +function metricFile(filename: string): string { + const cwd = process.cwd() + "/"; + return filename.startsWith(cwd) ? filename.slice(cwd.length) : filename; +} + +function isContextProvider(node: SgNode): boolean { + if (node.kind() !== "member_expression") { + return false; + } + + const object = node.field("object"); + const property = node.field("property"); + if (!object || object.kind() !== "identifier" || !property || property.text() !== "Provider") { + return false; + } + + return object.text().toLowerCase().includes("context"); +} + +const transform: Transform = async (root) => { + const rootNode = root.root(); + const edits: Edit[] = []; + const metric = useMetricAtom("remove-context-provider-replacements"); + let replacements = 0; + + for (const member of rootNode.findAll({ rule: { kind: "member_expression" } })) { + const parent = member.parent(); + if (!parent) { + continue; + } + + const isJsxName = parent.kind() === "jsx_opening_element" || parent.kind() === "jsx_closing_element"; + if (!isJsxName || !isContextProvider(member)) { + continue; + } + + const object = member.field("object"); + if (!object) { + continue; + } + + edits.push(member.replace(object.text())); + replacements++; + } + + if (edits.length === 0) { + return null; + } + + metric.increment({ file: metricFile(root.filename()) }, replacements); + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/remove-context-provider/tests/nested-member-no-change/expected.tsx b/codemods/remove-context-provider/tests/nested-member-no-change/expected.tsx new file mode 100644 index 0000000..28d0643 --- /dev/null +++ b/codemods/remove-context-provider/tests/nested-member-no-change/expected.tsx @@ -0,0 +1,7 @@ +function App() { + return ( + + + + ); +} diff --git a/codemods/remove-context-provider/tests/nested-member-no-change/input.tsx b/codemods/remove-context-provider/tests/nested-member-no-change/input.tsx new file mode 100644 index 0000000..28d0643 --- /dev/null +++ b/codemods/remove-context-provider/tests/nested-member-no-change/input.tsx @@ -0,0 +1,7 @@ +function App() { + return ( + + + + ); +} diff --git a/codemods/legacy/transforms/__testfixtures__/remove-context-provider/no-provider.input.js b/codemods/remove-context-provider/tests/no-provider/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-context-provider/no-provider.input.js rename to codemods/remove-context-provider/tests/no-provider/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/remove-context-provider/with-provider-2.output.js b/codemods/remove-context-provider/tests/no-provider/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-context-provider/with-provider-2.output.js rename to codemods/remove-context-provider/tests/no-provider/input.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/no-provider.input.js b/codemods/remove-context-provider/tests/typescript-no-provider/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/no-provider.input.js rename to codemods/remove-context-provider/tests/typescript-no-provider/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider-2.output.js b/codemods/remove-context-provider/tests/typescript-no-provider/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider-2.output.js rename to codemods/remove-context-provider/tests/typescript-no-provider/input.tsx diff --git a/codemods/remove-context-provider/tests/typescript-with-provider-2/expected.tsx b/codemods/remove-context-provider/tests/typescript-with-provider-2/expected.tsx new file mode 100644 index 0000000..357cc7c --- /dev/null +++ b/codemods/remove-context-provider/tests/typescript-with-provider-2/expected.tsx @@ -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/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider-2.input.js b/codemods/remove-context-provider/tests/typescript-with-provider-2/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider-2.input.js rename to codemods/remove-context-provider/tests/typescript-with-provider-2/input.tsx diff --git a/codemods/remove-context-provider/tests/typescript-with-provider-2/metrics.json b/codemods/remove-context-provider/tests/typescript-with-provider-2/metrics.json new file mode 100644 index 0000000..d7e29d4 --- /dev/null +++ b/codemods/remove-context-provider/tests/typescript-with-provider-2/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-context-provider-replacements": [ + { + "cardinality": { + "file": "tests/typescript-with-provider-2/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider.output.js b/codemods/remove-context-provider/tests/typescript-with-provider/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider.output.js rename to codemods/remove-context-provider/tests/typescript-with-provider/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider.input.js b/codemods/remove-context-provider/tests/typescript-with-provider/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider.input.js rename to codemods/remove-context-provider/tests/typescript-with-provider/input.tsx diff --git a/codemods/remove-context-provider/tests/typescript-with-provider/metrics.json b/codemods/remove-context-provider/tests/typescript-with-provider/metrics.json new file mode 100644 index 0000000..8bcb233 --- /dev/null +++ b/codemods/remove-context-provider/tests/typescript-with-provider/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-context-provider-replacements": [ + { + "cardinality": { + "file": "tests/typescript-with-provider/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/remove-context-provider/tests/with-provider-2/expected.tsx b/codemods/remove-context-provider/tests/with-provider-2/expected.tsx new file mode 100644 index 0000000..9ef6573 --- /dev/null +++ b/codemods/remove-context-provider/tests/with-provider-2/expected.tsx @@ -0,0 +1,9 @@ +function App() { + const [theme, setTheme] = useState('light'); + + return ( + + + + ); +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-context-provider/with-provider-2.input.js b/codemods/remove-context-provider/tests/with-provider-2/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-context-provider/with-provider-2.input.js rename to codemods/remove-context-provider/tests/with-provider-2/input.tsx diff --git a/codemods/remove-context-provider/tests/with-provider-2/metrics.json b/codemods/remove-context-provider/tests/with-provider-2/metrics.json new file mode 100644 index 0000000..d0d0389 --- /dev/null +++ b/codemods/remove-context-provider/tests/with-provider-2/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-context-provider-replacements": [ + { + "cardinality": { + "file": "tests/with-provider-2/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-context-provider/with-provider.output.js b/codemods/remove-context-provider/tests/with-provider/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-context-provider/with-provider.output.js rename to codemods/remove-context-provider/tests/with-provider/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/remove-context-provider/with-provider.input.js b/codemods/remove-context-provider/tests/with-provider/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-context-provider/with-provider.input.js rename to codemods/remove-context-provider/tests/with-provider/input.tsx diff --git a/codemods/remove-context-provider/tests/with-provider/metrics.json b/codemods/remove-context-provider/tests/with-provider/metrics.json new file mode 100644 index 0000000..10a3c05 --- /dev/null +++ b/codemods/remove-context-provider/tests/with-provider/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-context-provider-replacements": [ + { + "cardinality": { + "file": "tests/with-provider/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/remove-context-provider/tsconfig.json b/codemods/remove-context-provider/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/remove-context-provider/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": [ + "ESNext" + ], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": [ + "@codemod.com/jssg-types", + "node" + ], + "allowImportingTsExtensions": true, + "noEmit": true, + "skipLibCheck": true, + "verbatimModuleSyntax": false, + "erasableSyntaxOnly": true, + "strict": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true + }, + "exclude": [ + "tests" + ] +} diff --git a/codemods/remove-context-provider/workflow.yaml b/codemods/remove-context-provider/workflow.yaml new file mode 100644 index 0000000..8773841 --- /dev/null +++ b/codemods/remove-context-provider/workflow.yaml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: "Scan tsx files and apply fixes" + js-ast-grep: + js_file: scripts/codemod.ts + language: "tsx" diff --git a/codemods/remove-forward-ref/README.md b/codemods/remove-forward-ref/README.md new file mode 100644 index 0000000..059f71d --- /dev/null +++ b/codemods/remove-forward-ref/README.md @@ -0,0 +1,9 @@ +# remove-forward-ref + +Inline `forwardRef(...)` render functions and pass `ref` through props. + +## Usage + +```bash +npx codemod @react-new/remove-forward-ref --target +``` diff --git a/codemods/remove-forward-ref/codemod.yaml b/codemods/remove-forward-ref/codemod.yaml new file mode 100644 index 0000000..ee2130e --- /dev/null +++ b/codemods/remove-forward-ref/codemod.yaml @@ -0,0 +1,19 @@ +schema_version: "1.0" + +name: "@react-new/remove-forward-ref" +version: "0.1.1" +description: "Inline forwardRef render functions and move ref into props" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + +targets: + languages: ["tsx"] + +keywords: ["React", "forwardRef"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/remove-forward-ref/package.json b/codemods/remove-forward-ref/package.json new file mode 100644 index 0000000..b2f9eda --- /dev/null +++ b/codemods/remove-forward-ref/package.json @@ -0,0 +1,18 @@ +{ + "name": "@react-new/remove-forward-ref", + "version": "0.1.1", + "description": "Inline forwardRef render functions and move ref into props", + "type": "module", + "scripts": { + "test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts --strictness loose", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@codemod.com/jssg-types": "latest", + "@types/node": "latest", + "typescript": "latest" + }, + "dependencies": { + "@jssg/utils": "^0.0.2" + } +} diff --git a/codemods/remove-forward-ref/scripts/codemod.ts b/codemods/remove-forward-ref/scripts/codemod.ts new file mode 100644 index 0000000..1e71588 --- /dev/null +++ b/codemods/remove-forward-ref/scripts/codemod.ts @@ -0,0 +1,293 @@ +import type { Transform, Edit, SgNode } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { useMetricAtom } from "codemod:metrics"; +import { getImport, removeImport } from "@jssg/utils/javascript/imports"; + +function metricFile(filename: string): string { + const cwd = process.cwd() + "/"; + return filename.startsWith(cwd) ? filename.slice(cwd.length) : filename; +} + +function namedChildren(node: SgNode | null): SgNode[] { + if (!node) { + return []; + } + + return node.children().filter((child) => child.isNamed() && child.kind() !== "comment"); +} + +function firstNamedChild(node: SgNode | null): SgNode | null { + return namedChildren(node)[0] ?? null; +} + +function callArguments(call: SgNode): SgNode[] { + return namedChildren(call.field("arguments")); +} + +function functionParameters(fn: SgNode): SgNode[] { + return namedChildren(fn.field("parameters")); +} + +function parameterPattern(param: SgNode): SgNode | null { + return firstNamedChild(param); +} + +function parameterTypeAnnotation(param: SgNode): SgNode | null { + const children = namedChildren(param); + return children.length > 1 ? children[1] ?? null : null; +} + +function legacyLiteralOrReferenceType(node: SgNode | null): SgNode | null { + const directNode = node && ( + node.kind() === "type_identifier" || + node.kind() === "nested_type_identifier" || + node.kind() === "object_type" || + node.kind() === "generic_type" + ) ? node : null; + const typeNode = directNode ?? ( + node && (node.kind() === "type_annotation" || node.kind() === "type_arguments") + ? firstNamedChild(node) + : null + ); + if (!typeNode) { + return null; + } + + return ( + typeNode.kind() === "type_identifier" || + typeNode.kind() === "nested_type_identifier" || + typeNode.kind() === "object_type" || + typeNode.kind() === "generic_type" + ) ? typeNode : null; +} + +function legacyReferenceType(node: SgNode | null): SgNode | null { + const directNode = node && ( + node.kind() === "type_identifier" || + node.kind() === "nested_type_identifier" || + node.kind() === "generic_type" + ) ? node : null; + const typeNode = directNode ?? ( + node && (node.kind() === "type_annotation" || node.kind() === "type_arguments") + ? firstNamedChild(node) + : null + ); + if (!typeNode) { + return null; + } + + return ( + typeNode.kind() === "type_identifier" || + typeNode.kind() === "nested_type_identifier" || + typeNode.kind() === "generic_type" + ) ? typeNode : null; +} + +function stripLeadingColon(text: string): string { + return text.startsWith(":") ? text.slice(1).trim() : text.trim(); +} + +function callTypeArguments(call: SgNode): SgNode[] { + const typeArguments = call.children().find((child) => child.kind() === "type_arguments"); + return namedChildren(typeArguments ?? null); +} + +function refTypeFromForwardedRef(typeAnnotation: SgNode | null): string | null { + const typeNode = legacyReferenceType(typeAnnotation); + if (!typeNode || typeNode.kind() !== "generic_type") { + return null; + } + + const children = namedChildren(typeNode); + const typeNameNode = children[0]; + const genericArgs = children[1]; + if (!typeNameNode || !genericArgs) { + return null; + } + + const typeNameChildren = namedChildren(typeNameNode); + const lastTypeName = typeNameChildren[typeNameChildren.length - 1] ?? typeNameNode; + if (lastTypeName.text() !== "ForwardedRef") { + return null; + } + + const refType = legacyReferenceType(genericArgs); + return refType?.text() ?? null; +} + +function refBindingEntry(refName: string): string { + return refName === "ref" ? "ref" : `ref: ${refName}`; +} + +function objectPatternInnerText(pattern: SgNode): string { + const text = pattern.text().trim(); + return text.startsWith("{") && text.endsWith("}") ? text.slice(1, -1).trim() : text; +} + +function propsPatternText(propsParam: SgNode, refName: string): string | null { + const pattern = parameterPattern(propsParam); + if (!pattern) { + return null; + } + + if (pattern.kind() === "identifier") { + return `{ ${refBindingEntry(refName)}, ...${pattern.text()} }`; + } + + if (pattern.kind() === "object_pattern") { + const inner = objectPatternInnerText(pattern); + return inner.length > 0 + ? `{ ${refBindingEntry(refName)}, ${inner} }` + : `{ ${refBindingEntry(refName)} }`; + } + + return null; +} + +function paramTypeText(propsParam: SgNode, call: SgNode, refParam: SgNode): string | null { + const typeArgs = callTypeArguments(call); + const refTypeArg = typeArgs[0] ?? null; + const propTypeArg = typeArgs[1] ?? null; + const legacyRefTypeArg = legacyReferenceType(refTypeArg); + const legacyPropTypeArg = legacyLiteralOrReferenceType(propTypeArg); + if (legacyRefTypeArg && legacyPropTypeArg) { + return `${legacyPropTypeArg.text()} & { ref: React.RefObject<${legacyRefTypeArg.text()}> }`; + } + + const propsType = legacyLiteralOrReferenceType(parameterTypeAnnotation(propsParam)); + if (!propsType) { + return null; + } + + const refType = refTypeFromForwardedRef(parameterTypeAnnotation(refParam)) ?? "unknown"; + return `${propsType.text()} & { ref: React.RefObject<${refType}> }`; +} + +function bodyText(fn: SgNode): string | null { + return fn.field("body")?.text() ?? null; +} + +function signaturePrefix(fn: SgNode, paramsText: string): string { + const source = fn.text(); + const paramsIndex = source.indexOf(paramsText); + return paramsIndex >= 0 ? source.slice(0, paramsIndex) : ""; +} + +function signatureBetweenParamsAndBody( + fn: SgNode, + paramsText: string, + fnBodyText: string, +): string { + const source = fn.text(); + const paramsIndex = source.indexOf(paramsText); + const bodyIndex = source.lastIndexOf(fnBodyText); + if (paramsIndex < 0 || bodyIndex < 0 || bodyIndex < paramsIndex) { + return ""; + } + + return source.slice(paramsIndex + paramsText.length, bodyIndex); +} + +function buildReplacement(call: SgNode): string | null { + const renderFunction = callArguments(call)[0]; + if (!renderFunction || (renderFunction.kind() !== "function_expression" && renderFunction.kind() !== "arrow_function")) { + return null; + } + + const params = functionParameters(renderFunction); + const propsParam = params[0]; + const refParam = params[1]; + if (!propsParam || !refParam) { + return null; + } + + const refPattern = parameterPattern(refParam); + if (!refPattern || refPattern.kind() !== "identifier") { + return null; + } + + const propsText = propsPatternText(propsParam, refPattern.text()); + const fnBodyText = bodyText(renderFunction); + if (!propsText || !fnBodyText) { + return null; + } + + const annotation = paramTypeText(propsParam, call, refParam); + const finalParam = annotation ? `${propsText}: ${annotation}` : propsText; + const paramsText = renderFunction.field("parameters")?.text() ?? "()"; + const prefix = signaturePrefix(renderFunction, paramsText); + const betweenParamsAndBody = signatureBetweenParamsAndBody(renderFunction, paramsText, fnBodyText); + + return `${prefix}(${finalParam})${betweenParamsAndBody}${fnBodyText}`; +} + +function isForwardRefIdentifierCall(call: SgNode, localName: string | null): boolean { + if (!localName) { + return false; + } + + const callee = call.field("function"); + return callee?.kind() === "identifier" && callee.text() === localName; +} + +function isForwardRefMemberCall(call: SgNode, reactLocalName: string | null): boolean { + if (!reactLocalName) { + return false; + } + + const callee = call.field("function"); + if (!callee || callee.kind() !== "member_expression") { + return false; + } + + const object = callee.field("object"); + const property = callee.field("property"); + return object?.kind() === "identifier" && + object.text() === reactLocalName && + property?.text() === "forwardRef"; +} + +const transform: Transform = async (root) => { + const rootNode = root.root(); + const edits: Edit[] = []; + const metric = useMetricAtom("remove-forward-ref-replacements"); + + const forwardRefImport = getImport(rootNode, { type: "named", name: "forwardRef", from: "react" }); + const reactImport = getImport(rootNode, { type: "default", from: "react" }); + let replacements = 0; + + for (const call of rootNode.findAll({ rule: { kind: "call_expression" } })) { + if (!isForwardRefIdentifierCall(call, forwardRefImport?.alias ?? null) && + !isForwardRefMemberCall(call, reactImport?.alias ?? null)) { + continue; + } + + const replacement = buildReplacement(call); + if (!replacement) { + continue; + } + + edits.push(call.replace(replacement)); + replacements++; + } + + if (replacements === 0) { + return null; + } + + if (forwardRefImport) { + const removeEdit = removeImport(rootNode, { + type: "named", + specifiers: ["forwardRef"], + from: "react", + }); + if (removeEdit) { + edits.push(removeEdit); + } + } + + metric.increment({ file: metricFile(root.filename()) }, replacements); + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/arrow-function-expression.output.js b/codemods/remove-forward-ref/tests/arrow-function-expression/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/arrow-function-expression.output.js rename to codemods/remove-forward-ref/tests/arrow-function-expression/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/arrow-function-expression.input.js b/codemods/remove-forward-ref/tests/arrow-function-expression/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/arrow-function-expression.input.js rename to codemods/remove-forward-ref/tests/arrow-function-expression/input.tsx diff --git a/codemods/remove-forward-ref/tests/arrow-function-expression/metrics.json b/codemods/remove-forward-ref/tests/arrow-function-expression/metrics.json new file mode 100644 index 0000000..0c57c24 --- /dev/null +++ b/codemods/remove-forward-ref/tests/arrow-function-expression/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-forward-ref-replacements": [ + { + "cardinality": { + "file": "tests/arrow-function-expression/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/callee-is-member-expression.output.js b/codemods/remove-forward-ref/tests/callee-is-member-expression/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/callee-is-member-expression.output.js rename to codemods/remove-forward-ref/tests/callee-is-member-expression/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/callee-is-member-expression.input.js b/codemods/remove-forward-ref/tests/callee-is-member-expression/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/callee-is-member-expression.input.js rename to codemods/remove-forward-ref/tests/callee-is-member-expression/input.tsx diff --git a/codemods/remove-forward-ref/tests/callee-is-member-expression/metrics.json b/codemods/remove-forward-ref/tests/callee-is-member-expression/metrics.json new file mode 100644 index 0000000..4f8189b --- /dev/null +++ b/codemods/remove-forward-ref/tests/callee-is-member-expression/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-forward-ref-replacements": [ + { + "cardinality": { + "file": "tests/callee-is-member-expression/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import-2.output.js b/codemods/remove-forward-ref/tests/forward-ref-import-2/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import-2.output.js rename to codemods/remove-forward-ref/tests/forward-ref-import-2/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import-2.input.js b/codemods/remove-forward-ref/tests/forward-ref-import-2/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import-2.input.js rename to codemods/remove-forward-ref/tests/forward-ref-import-2/input.tsx diff --git a/codemods/remove-forward-ref/tests/forward-ref-import-2/metrics.json b/codemods/remove-forward-ref/tests/forward-ref-import-2/metrics.json new file mode 100644 index 0000000..fede801 --- /dev/null +++ b/codemods/remove-forward-ref/tests/forward-ref-import-2/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-forward-ref-replacements": [ + { + "cardinality": { + "file": "tests/forward-ref-import-2/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import.output.js b/codemods/remove-forward-ref/tests/forward-ref-import/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import.output.js rename to codemods/remove-forward-ref/tests/forward-ref-import/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import.input.js b/codemods/remove-forward-ref/tests/forward-ref-import/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import.input.js rename to codemods/remove-forward-ref/tests/forward-ref-import/input.tsx diff --git a/codemods/remove-forward-ref/tests/forward-ref-import/metrics.json b/codemods/remove-forward-ref/tests/forward-ref-import/metrics.json new file mode 100644 index 0000000..24e45e5 --- /dev/null +++ b/codemods/remove-forward-ref/tests/forward-ref-import/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-forward-ref-replacements": [ + { + "cardinality": { + "file": "tests/forward-ref-import/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/remove-forward-ref/tests/function-expression-generics/expected.tsx b/codemods/remove-forward-ref/tests/function-expression-generics/expected.tsx new file mode 100644 index 0000000..fe31cc4 --- /dev/null +++ b/codemods/remove-forward-ref/tests/function-expression-generics/expected.tsx @@ -0,0 +1,12 @@ +type Props = { value?: T }; + +const MyInput = function A( + { + ref, + ...props + }: Props & { + ref: React.RefObject + } +): JSX.Element | null { + return null; +}; diff --git a/codemods/remove-forward-ref/tests/function-expression-generics/input.tsx b/codemods/remove-forward-ref/tests/function-expression-generics/input.tsx new file mode 100644 index 0000000..69a90fb --- /dev/null +++ b/codemods/remove-forward-ref/tests/function-expression-generics/input.tsx @@ -0,0 +1,10 @@ +import { forwardRef } from 'react'; + +type Props = { value?: T }; + +const MyInput = forwardRef(function A( + props: Props, + ref: React.ForwardedRef +): JSX.Element | null { + return null; +}); diff --git a/codemods/remove-forward-ref/tests/function-expression-generics/metrics.json b/codemods/remove-forward-ref/tests/function-expression-generics/metrics.json new file mode 100644 index 0000000..5d4f8c9 --- /dev/null +++ b/codemods/remove-forward-ref/tests/function-expression-generics/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-forward-ref-replacements": [ + { + "cardinality": { + "file": "tests/function-expression-generics/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/function-expression.output.js b/codemods/remove-forward-ref/tests/function-expression/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/function-expression.output.js rename to codemods/remove-forward-ref/tests/function-expression/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/function-expression.input.js b/codemods/remove-forward-ref/tests/function-expression/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/function-expression.input.js rename to codemods/remove-forward-ref/tests/function-expression/input.tsx diff --git a/codemods/remove-forward-ref/tests/function-expression/metrics.json b/codemods/remove-forward-ref/tests/function-expression/metrics.json new file mode 100644 index 0000000..8e42ed8 --- /dev/null +++ b/codemods/remove-forward-ref/tests/function-expression/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-forward-ref-replacements": [ + { + "cardinality": { + "file": "tests/function-expression/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/remove-forward-ref/tests/invalid-props-pattern-no-change/expected.tsx b/codemods/remove-forward-ref/tests/invalid-props-pattern-no-change/expected.tsx new file mode 100644 index 0000000..dc5bb49 --- /dev/null +++ b/codemods/remove-forward-ref/tests/invalid-props-pattern-no-change/expected.tsx @@ -0,0 +1,5 @@ +import { forwardRef } from 'react'; + +const MyComponent = forwardRef(function Component([props], ref) { + return null; +}); diff --git a/codemods/remove-forward-ref/tests/invalid-props-pattern-no-change/input.tsx b/codemods/remove-forward-ref/tests/invalid-props-pattern-no-change/input.tsx new file mode 100644 index 0000000..dc5bb49 --- /dev/null +++ b/codemods/remove-forward-ref/tests/invalid-props-pattern-no-change/input.tsx @@ -0,0 +1,5 @@ +import { forwardRef } from 'react'; + +const MyComponent = forwardRef(function Component([props], ref) { + return null; +}); diff --git a/codemods/remove-forward-ref/tests/invalid-render-arg-no-change/expected.tsx b/codemods/remove-forward-ref/tests/invalid-render-arg-no-change/expected.tsx new file mode 100644 index 0000000..a4303f4 --- /dev/null +++ b/codemods/remove-forward-ref/tests/invalid-render-arg-no-change/expected.tsx @@ -0,0 +1,3 @@ +import { forwardRef } from 'react'; + +const MyComponent = forwardRef(null); diff --git a/codemods/remove-forward-ref/tests/invalid-render-arg-no-change/input.tsx b/codemods/remove-forward-ref/tests/invalid-render-arg-no-change/input.tsx new file mode 100644 index 0000000..a4303f4 --- /dev/null +++ b/codemods/remove-forward-ref/tests/invalid-render-arg-no-change/input.tsx @@ -0,0 +1,3 @@ +import { forwardRef } from 'react'; + +const MyComponent = forwardRef(null); diff --git a/codemods/remove-forward-ref/tests/multiple-react-imports/expected.tsx b/codemods/remove-forward-ref/tests/multiple-react-imports/expected.tsx new file mode 100644 index 0000000..479c98b --- /dev/null +++ b/codemods/remove-forward-ref/tests/multiple-react-imports/expected.tsx @@ -0,0 +1,10 @@ +import { useState } from 'react'; + +const MyComponent = ( + { + ref, + ...props + } +) => { + return null; +}; diff --git a/codemods/remove-forward-ref/tests/multiple-react-imports/input.tsx b/codemods/remove-forward-ref/tests/multiple-react-imports/input.tsx new file mode 100644 index 0000000..5b6e9be --- /dev/null +++ b/codemods/remove-forward-ref/tests/multiple-react-imports/input.tsx @@ -0,0 +1,6 @@ +import { useState } from 'react'; +import { forwardRef } from 'react'; + +const MyComponent = forwardRef((props, ref) => { + return null; +}); diff --git a/codemods/remove-forward-ref/tests/multiple-react-imports/metrics.json b/codemods/remove-forward-ref/tests/multiple-react-imports/metrics.json new file mode 100644 index 0000000..eb74e11 --- /dev/null +++ b/codemods/remove-forward-ref/tests/multiple-react-imports/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-forward-ref-replacements": [ + { + "cardinality": { + "file": "tests/multiple-react-imports/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-identifier.output.js b/codemods/remove-forward-ref/tests/props-identifier/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-identifier.output.js rename to codemods/remove-forward-ref/tests/props-identifier/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-identifier.input.js b/codemods/remove-forward-ref/tests/props-identifier/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-identifier.input.js rename to codemods/remove-forward-ref/tests/props-identifier/input.tsx diff --git a/codemods/remove-forward-ref/tests/props-identifier/metrics.json b/codemods/remove-forward-ref/tests/props-identifier/metrics.json new file mode 100644 index 0000000..31971f0 --- /dev/null +++ b/codemods/remove-forward-ref/tests/props-identifier/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-forward-ref-replacements": [ + { + "cardinality": { + "file": "tests/props-identifier/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-object-pattern.output.js b/codemods/remove-forward-ref/tests/props-object-pattern/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-object-pattern.output.js rename to codemods/remove-forward-ref/tests/props-object-pattern/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-object-pattern.input.js b/codemods/remove-forward-ref/tests/props-object-pattern/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-object-pattern.input.js rename to codemods/remove-forward-ref/tests/props-object-pattern/input.tsx diff --git a/codemods/remove-forward-ref/tests/props-object-pattern/metrics.json b/codemods/remove-forward-ref/tests/props-object-pattern/metrics.json new file mode 100644 index 0000000..c94ce24 --- /dev/null +++ b/codemods/remove-forward-ref/tests/props-object-pattern/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-forward-ref-replacements": [ + { + "cardinality": { + "file": "tests/props-object-pattern/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/props-type-literal.output.js b/codemods/remove-forward-ref/tests/typescript-props-type-literal/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/props-type-literal.output.js rename to codemods/remove-forward-ref/tests/typescript-props-type-literal/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/props-type-literal.input.js b/codemods/remove-forward-ref/tests/typescript-props-type-literal/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/props-type-literal.input.js rename to codemods/remove-forward-ref/tests/typescript-props-type-literal/input.tsx diff --git a/codemods/remove-forward-ref/tests/typescript-props-type-literal/metrics.json b/codemods/remove-forward-ref/tests/typescript-props-type-literal/metrics.json new file mode 100644 index 0000000..7d4d936 --- /dev/null +++ b/codemods/remove-forward-ref/tests/typescript-props-type-literal/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-forward-ref-replacements": [ + { + "cardinality": { + "file": "tests/typescript-props-type-literal/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/remove-forward-ref/tests/typescript-qualified-names/expected.tsx b/codemods/remove-forward-ref/tests/typescript-qualified-names/expected.tsx new file mode 100644 index 0000000..5c8103e --- /dev/null +++ b/codemods/remove-forward-ref/tests/typescript-qualified-names/expected.tsx @@ -0,0 +1,10 @@ +const MyComponent = function Component( + { + ref: myRef, + ...myProps + }: NS.Props & { + ref: React.RefObject + } +) { + return null; +}; diff --git a/codemods/remove-forward-ref/tests/typescript-qualified-names/input.tsx b/codemods/remove-forward-ref/tests/typescript-qualified-names/input.tsx new file mode 100644 index 0000000..833d99c --- /dev/null +++ b/codemods/remove-forward-ref/tests/typescript-qualified-names/input.tsx @@ -0,0 +1,8 @@ +import { forwardRef } from 'react'; + +const MyComponent = forwardRef(function Component( + myProps: NS.Props, + myRef: React.ForwardedRef +) { + return null; +}); diff --git a/codemods/remove-forward-ref/tests/typescript-qualified-names/metrics.json b/codemods/remove-forward-ref/tests/typescript-qualified-names/metrics.json new file mode 100644 index 0000000..228e615 --- /dev/null +++ b/codemods/remove-forward-ref/tests/typescript-qualified-names/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-forward-ref-replacements": [ + { + "cardinality": { + "file": "tests/typescript-qualified-names/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-custom-names.output.js b/codemods/remove-forward-ref/tests/typescript-type-arguments-custom-names/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-custom-names.output.js rename to codemods/remove-forward-ref/tests/typescript-type-arguments-custom-names/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-custom-names.input.js b/codemods/remove-forward-ref/tests/typescript-type-arguments-custom-names/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-custom-names.input.js rename to codemods/remove-forward-ref/tests/typescript-type-arguments-custom-names/input.tsx diff --git a/codemods/remove-forward-ref/tests/typescript-type-arguments-custom-names/metrics.json b/codemods/remove-forward-ref/tests/typescript-type-arguments-custom-names/metrics.json new file mode 100644 index 0000000..11fa3f9 --- /dev/null +++ b/codemods/remove-forward-ref/tests/typescript-type-arguments-custom-names/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-forward-ref-replacements": [ + { + "cardinality": { + "file": "tests/typescript-type-arguments-custom-names/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-type-literals.output.js b/codemods/remove-forward-ref/tests/typescript-type-arguments-type-literals/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-type-literals.output.js rename to codemods/remove-forward-ref/tests/typescript-type-arguments-type-literals/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-type-literals.input.js b/codemods/remove-forward-ref/tests/typescript-type-arguments-type-literals/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-type-literals.input.js rename to codemods/remove-forward-ref/tests/typescript-type-arguments-type-literals/input.tsx diff --git a/codemods/remove-forward-ref/tests/typescript-type-arguments-type-literals/metrics.json b/codemods/remove-forward-ref/tests/typescript-type-arguments-type-literals/metrics.json new file mode 100644 index 0000000..2e70083 --- /dev/null +++ b/codemods/remove-forward-ref/tests/typescript-type-arguments-type-literals/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-forward-ref-replacements": [ + { + "cardinality": { + "file": "tests/typescript-type-arguments-type-literals/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments.output.js b/codemods/remove-forward-ref/tests/typescript-type-arguments/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments.output.js rename to codemods/remove-forward-ref/tests/typescript-type-arguments/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments.input.js b/codemods/remove-forward-ref/tests/typescript-type-arguments/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments.input.js rename to codemods/remove-forward-ref/tests/typescript-type-arguments/input.tsx diff --git a/codemods/remove-forward-ref/tests/typescript-type-arguments/metrics.json b/codemods/remove-forward-ref/tests/typescript-type-arguments/metrics.json new file mode 100644 index 0000000..2ce2d16 --- /dev/null +++ b/codemods/remove-forward-ref/tests/typescript-type-arguments/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-forward-ref-replacements": [ + { + "cardinality": { + "file": "tests/typescript-type-arguments/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/remove-forward-ref/tests/typescript-unsupported-props-union/expected.tsx b/codemods/remove-forward-ref/tests/typescript-unsupported-props-union/expected.tsx new file mode 100644 index 0000000..24c40ee --- /dev/null +++ b/codemods/remove-forward-ref/tests/typescript-unsupported-props-union/expected.tsx @@ -0,0 +1,11 @@ +type A = { a: 1 }; +type B = { b: 1 }; + +const MyComponent = function Component( + { + ref: myRef, + ...myProps + } +) { + return null; +}; diff --git a/codemods/remove-forward-ref/tests/typescript-unsupported-props-union/input.tsx b/codemods/remove-forward-ref/tests/typescript-unsupported-props-union/input.tsx new file mode 100644 index 0000000..2182401 --- /dev/null +++ b/codemods/remove-forward-ref/tests/typescript-unsupported-props-union/input.tsx @@ -0,0 +1,11 @@ +import { forwardRef } from 'react'; + +type A = { a: 1 }; +type B = { b: 1 }; + +const MyComponent = forwardRef(function Component( + myProps: A | B, + myRef: React.ForwardedRef +) { + return null; +}); diff --git a/codemods/remove-forward-ref/tests/typescript-unsupported-props-union/metrics.json b/codemods/remove-forward-ref/tests/typescript-unsupported-props-union/metrics.json new file mode 100644 index 0000000..5d14c6e --- /dev/null +++ b/codemods/remove-forward-ref/tests/typescript-unsupported-props-union/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-forward-ref-replacements": [ + { + "cardinality": { + "file": "tests/typescript-unsupported-props-union/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/remove-forward-ref/tests/typescript-unsupported-type-arguments-union/expected.tsx b/codemods/remove-forward-ref/tests/typescript-unsupported-type-arguments-union/expected.tsx new file mode 100644 index 0000000..6710ae3 --- /dev/null +++ b/codemods/remove-forward-ref/tests/typescript-unsupported-type-arguments-union/expected.tsx @@ -0,0 +1,11 @@ +type A = { a: 1 }; +type B = { b: 1 }; + +const MyComponent = ( + { + ref, + ...props + } +) => { + return null; +}; diff --git a/codemods/remove-forward-ref/tests/typescript-unsupported-type-arguments-union/input.tsx b/codemods/remove-forward-ref/tests/typescript-unsupported-type-arguments-union/input.tsx new file mode 100644 index 0000000..f7d4852 --- /dev/null +++ b/codemods/remove-forward-ref/tests/typescript-unsupported-type-arguments-union/input.tsx @@ -0,0 +1,8 @@ +import { forwardRef } from 'react'; + +type A = { a: 1 }; +type B = { b: 1 }; + +const MyComponent = forwardRef((props, ref) => { + return null; +}); diff --git a/codemods/remove-forward-ref/tests/typescript-unsupported-type-arguments-union/metrics.json b/codemods/remove-forward-ref/tests/typescript-unsupported-type-arguments-union/metrics.json new file mode 100644 index 0000000..772591e --- /dev/null +++ b/codemods/remove-forward-ref/tests/typescript-unsupported-type-arguments-union/metrics.json @@ -0,0 +1,10 @@ +{ + "remove-forward-ref-replacements": [ + { + "cardinality": { + "file": "tests/typescript-unsupported-type-arguments-union/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/remove-forward-ref/tsconfig.json b/codemods/remove-forward-ref/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/remove-forward-ref/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": [ + "ESNext" + ], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": [ + "@codemod.com/jssg-types", + "node" + ], + "allowImportingTsExtensions": true, + "noEmit": true, + "skipLibCheck": true, + "verbatimModuleSyntax": false, + "erasableSyntaxOnly": true, + "strict": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true + }, + "exclude": [ + "tests" + ] +} diff --git a/codemods/remove-forward-ref/workflow.yaml b/codemods/remove-forward-ref/workflow.yaml new file mode 100644 index 0000000..8773841 --- /dev/null +++ b/codemods/remove-forward-ref/workflow.yaml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: "Scan tsx files and apply fixes" + js-ast-grep: + js_file: scripts/codemod.ts + language: "tsx" diff --git a/codemods/jssg/replace-reactdom-render/.gitignore b/codemods/rename-unsafe-lifecycles/.gitignore similarity index 100% rename from codemods/jssg/replace-reactdom-render/.gitignore rename to codemods/rename-unsafe-lifecycles/.gitignore diff --git a/codemods/rename-unsafe-lifecycles/README.md b/codemods/rename-unsafe-lifecycles/README.md new file mode 100644 index 0000000..2b30a6c --- /dev/null +++ b/codemods/rename-unsafe-lifecycles/README.md @@ -0,0 +1,9 @@ +# rename-unsafe-lifecycles + +Rename deprecated React lifecycle names to their `UNSAFE_` equivalents in class components, object literals, and member references. + +## Usage + +```bash +npx codemod @react-new/rename-unsafe-lifecycles --target +``` diff --git a/codemods/rename-unsafe-lifecycles/codemod.yaml b/codemods/rename-unsafe-lifecycles/codemod.yaml new file mode 100644 index 0000000..fc6fc0e --- /dev/null +++ b/codemods/rename-unsafe-lifecycles/codemod.yaml @@ -0,0 +1,20 @@ +schema_version: "1.0" + +name: "@react-new/rename-unsafe-lifecycles" +version: "0.1.1" +description: "Rename deprecated lifecycle methods to UNSAFE_ prefixed versions" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + + +targets: + languages: ["tsx"] + +keywords: ["React"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/rename-unsafe-lifecycles/package.json b/codemods/rename-unsafe-lifecycles/package.json new file mode 100644 index 0000000..b24ee8b --- /dev/null +++ b/codemods/rename-unsafe-lifecycles/package.json @@ -0,0 +1,15 @@ +{ + "name": "@react-new/rename-unsafe-lifecycles", + "version": "0.1.1", + "description": "Rename deprecated lifecycle methods to UNSAFE_ prefixed versions", + "type": "module", + "scripts": { + "test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@codemod.com/jssg-types": "latest", + "typescript": "latest", + "@types/node": "latest" + } +} diff --git a/codemods/rename-unsafe-lifecycles/pnpm-lock.yaml b/codemods/rename-unsafe-lifecycles/pnpm-lock.yaml new file mode 100644 index 0000000..8dfa743 --- /dev/null +++ b/codemods/rename-unsafe-lifecycles/pnpm-lock.yaml @@ -0,0 +1,32 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@codemod.com/jssg-types': + specifier: latest + version: 1.5.0 + typescript: + specifier: latest + version: 5.9.3 + +packages: + + '@codemod.com/jssg-types@1.5.0': + resolution: {integrity: sha512-zChRbxI3hBSGrAHnWlEzOw1FztLWMMiarwcr0Wbk0On4hmv7dVgoUqpIHfxb64mEMKJ5syTIKY3ZNd8DcFQa5w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + +snapshots: + + '@codemod.com/jssg-types@1.5.0': {} + + typescript@5.9.3: {} diff --git a/codemods/rename-unsafe-lifecycles/scripts/codemod.ts b/codemods/rename-unsafe-lifecycles/scripts/codemod.ts new file mode 100644 index 0000000..d65d934 --- /dev/null +++ b/codemods/rename-unsafe-lifecycles/scripts/codemod.ts @@ -0,0 +1,130 @@ +import type { Transform, Edit } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { useMetricAtom } from "codemod:metrics"; + +function metricFile(filename: string): string { + const cwd = process.cwd() + "/"; + return filename.startsWith(cwd) ? filename.slice(cwd.length) : filename; +} + + +const DEPRECATED_LIFECYCLES = { + componentWillMount: "UNSAFE_componentWillMount", + componentWillReceiveProps: "UNSAFE_componentWillReceiveProps", + componentWillUpdate: "UNSAFE_componentWillUpdate", +} as const; + +const transform: Transform = async (root) => { + const rootNode = root.root(); + const edits: Edit[] = []; + + + const transformMetric = useMetricAtom("lifecycle-renames"); + const renameCount: Record = {}; + + for (const [oldName, newName] of Object.entries(DEPRECATED_LIFECYCLES)) { + const methodDefs = rootNode.findAll({ + rule: { + any: [ + { + kind: "method_definition", + has: { + field: "name", + kind: "property_identifier", + regex: `^${oldName}$`, + }, + }, + { + kind: "public_field_definition", + has: { + field: "name", + kind: "property_identifier", + regex: `^${oldName}$`, + }, + }, + ], + }, + }); + + for (const method of methodDefs) { + const nameNode = method.find({ + rule: { + kind: "property_identifier", + regex: `^${oldName}$`, + }, + }); + + if (nameNode) { + edits.push(nameNode.replace(newName)); + renameCount[oldName] = (renameCount[oldName] || 0) + 1; + } + } + + const objectProps = rootNode.findAll({ + rule: { + kind: "pair", + has: { + field: "key", + kind: "property_identifier", + regex: `^${oldName}$`, + }, + }, + }); + + for (const prop of objectProps) { + const keyNode = prop.find({ + rule: { + kind: "property_identifier", + regex: `^${oldName}$`, + }, + }); + + if (keyNode) { + edits.push(keyNode.replace(newName)); + renameCount[oldName] = (renameCount[oldName] || 0) + 1; + } + } + + const memberExprs = rootNode.findAll({ + rule: { + kind: "member_expression", + has: { + field: "property", + kind: "property_identifier", + regex: `^${oldName}$`, + }, + }, + }); + + for (const memberExpr of memberExprs) { + const propertyNode = memberExpr.find({ + rule: { + kind: "property_identifier", + regex: `^${oldName}$`, + }, + }); + + if (propertyNode) { + edits.push(propertyNode.replace(newName)); + renameCount[oldName] = (renameCount[oldName] || 0) + 1; + } + } + } + + if (Object.keys(renameCount).length > 0) { + const lifecycles = Object.keys(renameCount).sort().join(","); + const total = Object.values(renameCount).reduce((a, b) => a + b, 0); + transformMetric.increment({ + lifecycles, + file: metricFile(root.filename()), + }, total); + } + + if (edits.length === 0) { + return null; + } + + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/arrow-functions.output.js b/codemods/rename-unsafe-lifecycles/tests/arrow-functions/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/arrow-functions.output.js rename to codemods/rename-unsafe-lifecycles/tests/arrow-functions/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/arrow-functions.input.js b/codemods/rename-unsafe-lifecycles/tests/arrow-functions/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/arrow-functions.input.js rename to codemods/rename-unsafe-lifecycles/tests/arrow-functions/input.tsx diff --git a/codemods/rename-unsafe-lifecycles/tests/arrow-functions/metrics.json b/codemods/rename-unsafe-lifecycles/tests/arrow-functions/metrics.json new file mode 100644 index 0000000..56a8030 --- /dev/null +++ b/codemods/rename-unsafe-lifecycles/tests/arrow-functions/metrics.json @@ -0,0 +1,11 @@ +{ + "lifecycle-renames": [ + { + "cardinality": { + "file": "tests/arrow-functions/input.tsx", + "lifecycles": "componentWillMount,componentWillReceiveProps,componentWillUpdate" + }, + "count": 3 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/create-react-class.output.js b/codemods/rename-unsafe-lifecycles/tests/create-react-class/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/create-react-class.output.js rename to codemods/rename-unsafe-lifecycles/tests/create-react-class/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/create-react-class.input.js b/codemods/rename-unsafe-lifecycles/tests/create-react-class/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/create-react-class.input.js rename to codemods/rename-unsafe-lifecycles/tests/create-react-class/input.tsx diff --git a/codemods/rename-unsafe-lifecycles/tests/create-react-class/metrics.json b/codemods/rename-unsafe-lifecycles/tests/create-react-class/metrics.json new file mode 100644 index 0000000..68c0180 --- /dev/null +++ b/codemods/rename-unsafe-lifecycles/tests/create-react-class/metrics.json @@ -0,0 +1,11 @@ +{ + "lifecycle-renames": [ + { + "cardinality": { + "file": "tests/create-react-class/input.tsx", + "lifecycles": "componentWillMount,componentWillReceiveProps,componentWillUpdate" + }, + "count": 6 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/instance-methods.output.js b/codemods/rename-unsafe-lifecycles/tests/instance-methods/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/instance-methods.output.js rename to codemods/rename-unsafe-lifecycles/tests/instance-methods/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/instance-methods.input.js b/codemods/rename-unsafe-lifecycles/tests/instance-methods/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/instance-methods.input.js rename to codemods/rename-unsafe-lifecycles/tests/instance-methods/input.tsx diff --git a/codemods/rename-unsafe-lifecycles/tests/instance-methods/metrics.json b/codemods/rename-unsafe-lifecycles/tests/instance-methods/metrics.json new file mode 100644 index 0000000..d59cca0 --- /dev/null +++ b/codemods/rename-unsafe-lifecycles/tests/instance-methods/metrics.json @@ -0,0 +1,11 @@ +{ + "lifecycle-renames": [ + { + "cardinality": { + "file": "tests/instance-methods/input.tsx", + "lifecycles": "componentWillMount,componentWillReceiveProps,componentWillUpdate" + }, + "count": 3 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-calling-lifecycles.output.js b/codemods/rename-unsafe-lifecycles/tests/manually-calling-lifecycles/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-calling-lifecycles.output.js rename to codemods/rename-unsafe-lifecycles/tests/manually-calling-lifecycles/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-calling-lifecycles.input.js b/codemods/rename-unsafe-lifecycles/tests/manually-calling-lifecycles/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-calling-lifecycles.input.js rename to codemods/rename-unsafe-lifecycles/tests/manually-calling-lifecycles/input.tsx diff --git a/codemods/rename-unsafe-lifecycles/tests/manually-calling-lifecycles/metrics.json b/codemods/rename-unsafe-lifecycles/tests/manually-calling-lifecycles/metrics.json new file mode 100644 index 0000000..a91256f --- /dev/null +++ b/codemods/rename-unsafe-lifecycles/tests/manually-calling-lifecycles/metrics.json @@ -0,0 +1,11 @@ +{ + "lifecycle-renames": [ + { + "cardinality": { + "file": "tests/manually-calling-lifecycles/input.tsx", + "lifecycles": "componentWillMount" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-invoked-mixin-methods.output.js b/codemods/rename-unsafe-lifecycles/tests/manually-invoked-mixin-methods/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-invoked-mixin-methods.output.js rename to codemods/rename-unsafe-lifecycles/tests/manually-invoked-mixin-methods/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-invoked-mixin-methods.input.js b/codemods/rename-unsafe-lifecycles/tests/manually-invoked-mixin-methods/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-invoked-mixin-methods.input.js rename to codemods/rename-unsafe-lifecycles/tests/manually-invoked-mixin-methods/input.tsx diff --git a/codemods/rename-unsafe-lifecycles/tests/manually-invoked-mixin-methods/metrics.json b/codemods/rename-unsafe-lifecycles/tests/manually-invoked-mixin-methods/metrics.json new file mode 100644 index 0000000..d83605b --- /dev/null +++ b/codemods/rename-unsafe-lifecycles/tests/manually-invoked-mixin-methods/metrics.json @@ -0,0 +1,11 @@ +{ + "lifecycle-renames": [ + { + "cardinality": { + "file": "tests/manually-invoked-mixin-methods/input.tsx", + "lifecycles": "componentWillMount" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/one-lifecycle-calls-another.output.js b/codemods/rename-unsafe-lifecycles/tests/one-lifecycle-calls-another/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/one-lifecycle-calls-another.output.js rename to codemods/rename-unsafe-lifecycles/tests/one-lifecycle-calls-another/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/one-lifecycle-calls-another.input.js b/codemods/rename-unsafe-lifecycles/tests/one-lifecycle-calls-another/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/one-lifecycle-calls-another.input.js rename to codemods/rename-unsafe-lifecycles/tests/one-lifecycle-calls-another/input.tsx diff --git a/codemods/rename-unsafe-lifecycles/tests/one-lifecycle-calls-another/metrics.json b/codemods/rename-unsafe-lifecycles/tests/one-lifecycle-calls-another/metrics.json new file mode 100644 index 0000000..7997937 --- /dev/null +++ b/codemods/rename-unsafe-lifecycles/tests/one-lifecycle-calls-another/metrics.json @@ -0,0 +1,11 @@ +{ + "lifecycle-renames": [ + { + "cardinality": { + "file": "tests/one-lifecycle-calls-another/input.tsx", + "lifecycles": "componentWillMount,componentWillReceiveProps" + }, + "count": 3 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/standalone-function.input.js b/codemods/rename-unsafe-lifecycles/tests/standalone-function/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/standalone-function.input.js rename to codemods/rename-unsafe-lifecycles/tests/standalone-function/expected.tsx diff --git a/codemods/rename-unsafe-lifecycles/tests/standalone-function/input.tsx b/codemods/rename-unsafe-lifecycles/tests/standalone-function/input.tsx new file mode 100644 index 0000000..c515ccc --- /dev/null +++ b/codemods/rename-unsafe-lifecycles/tests/standalone-function/input.tsx @@ -0,0 +1,9 @@ +function componentWillMount() { + // componentWillMount +} +function componentWillUpdate(nextProps, nextState) { + // componentWillUpdate +} +function componentWillReceiveProps(nextProps) { + // componentWillReceiveProps +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/typescript/class.tsx.output.js b/codemods/rename-unsafe-lifecycles/tests/typescript-class-tsx/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/typescript/class.tsx.output.js rename to codemods/rename-unsafe-lifecycles/tests/typescript-class-tsx/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/typescript/class.tsx.input.js b/codemods/rename-unsafe-lifecycles/tests/typescript-class-tsx/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/typescript/class.tsx.input.js rename to codemods/rename-unsafe-lifecycles/tests/typescript-class-tsx/input.tsx diff --git a/codemods/rename-unsafe-lifecycles/tests/typescript-class-tsx/metrics.json b/codemods/rename-unsafe-lifecycles/tests/typescript-class-tsx/metrics.json new file mode 100644 index 0000000..4119909 --- /dev/null +++ b/codemods/rename-unsafe-lifecycles/tests/typescript-class-tsx/metrics.json @@ -0,0 +1,11 @@ +{ + "lifecycle-renames": [ + { + "cardinality": { + "file": "tests/typescript-class-tsx/input.tsx", + "lifecycles": "componentWillMount,componentWillReceiveProps,componentWillUpdate" + }, + "count": 3 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/variable-within-class-method.input.js b/codemods/rename-unsafe-lifecycles/tests/variable-within-class-method/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/variable-within-class-method.input.js rename to codemods/rename-unsafe-lifecycles/tests/variable-within-class-method/expected.tsx diff --git a/codemods/rename-unsafe-lifecycles/tests/variable-within-class-method/input.tsx b/codemods/rename-unsafe-lifecycles/tests/variable-within-class-method/input.tsx new file mode 100644 index 0000000..eb8979a --- /dev/null +++ b/codemods/rename-unsafe-lifecycles/tests/variable-within-class-method/input.tsx @@ -0,0 +1,7 @@ +class SomeClass { + someMethod() { + const componentWillMount = true; + let componentWillUpdate = 123; + var componentWillReceiveProps = 'abc'; + } +} \ No newline at end of file diff --git a/codemods/rename-unsafe-lifecycles/tsconfig.json b/codemods/rename-unsafe-lifecycles/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/rename-unsafe-lifecycles/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": [ + "ESNext" + ], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": [ + "@codemod.com/jssg-types", + "node" + ], + "allowImportingTsExtensions": true, + "noEmit": true, + "skipLibCheck": true, + "verbatimModuleSyntax": false, + "erasableSyntaxOnly": true, + "strict": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true + }, + "exclude": [ + "tests" + ] +} diff --git a/codemods/rename-unsafe-lifecycles/workflow.yaml b/codemods/rename-unsafe-lifecycles/workflow.yaml new file mode 100644 index 0000000..8773841 --- /dev/null +++ b/codemods/rename-unsafe-lifecycles/workflow.yaml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: "Scan tsx files and apply fixes" + js-ast-grep: + js_file: scripts/codemod.ts + language: "tsx" diff --git a/codemods/jssg/replace-act-import/README.md b/codemods/replace-act-import/README.md similarity index 79% rename from codemods/jssg/replace-act-import/README.md rename to codemods/replace-act-import/README.md index 6406bed..3c4a8fe 100644 --- a/codemods/jssg/replace-act-import/README.md +++ b/codemods/replace-act-import/README.md @@ -7,10 +7,3 @@ Move `act` usage from `react-dom/test-utils` to `react` across named imports, na ```bash npx codemod @react-new/replace-act-import --target ``` - -## Development - -```bash -pnpm test -pnpm check-types -``` diff --git a/codemods/jssg/replace-act-import/codemod.yaml b/codemods/replace-act-import/codemod.yaml similarity index 95% rename from codemods/jssg/replace-act-import/codemod.yaml rename to codemods/replace-act-import/codemod.yaml index 7e3a99e..438096e 100644 --- a/codemods/jssg/replace-act-import/codemod.yaml +++ b/codemods/replace-act-import/codemod.yaml @@ -1,7 +1,7 @@ schema_version: "1.0" name: "@react-new/replace-act-import" -version: "0.1.0" +version: "0.1.1" description: "Migrate act() from react-dom/test-utils to react" author: "Codemod " license: "MIT" diff --git a/codemods/jssg/replace-act-import/package.json b/codemods/replace-act-import/package.json similarity index 95% rename from codemods/jssg/replace-act-import/package.json rename to codemods/replace-act-import/package.json index da11999..9250e12 100644 --- a/codemods/jssg/replace-act-import/package.json +++ b/codemods/replace-act-import/package.json @@ -1,6 +1,6 @@ { "name": "@react-new/replace-act-import", - "version": "0.1.0", + "version": "0.1.1", "description": "Migrate act() from react-dom/test-utils to react", "type": "module", "scripts": { diff --git a/codemods/jssg/replace-act-import/scripts/codemod.ts b/codemods/replace-act-import/scripts/codemod.ts similarity index 100% rename from codemods/jssg/replace-act-import/scripts/codemod.ts rename to codemods/replace-act-import/scripts/codemod.ts diff --git a/codemods/jssg/replace-act-import/tests/default-import/expected.tsx b/codemods/replace-act-import/tests/default-import/expected.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/default-import/expected.tsx rename to codemods/replace-act-import/tests/default-import/expected.tsx diff --git a/codemods/jssg/replace-act-import/tests/default-import/input.tsx b/codemods/replace-act-import/tests/default-import/input.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/default-import/input.tsx rename to codemods/replace-act-import/tests/default-import/input.tsx diff --git a/codemods/jssg/replace-act-import/tests/default-import/metrics.json b/codemods/replace-act-import/tests/default-import/metrics.json similarity index 100% rename from codemods/jssg/replace-act-import/tests/default-import/metrics.json rename to codemods/replace-act-import/tests/default-import/metrics.json diff --git a/codemods/jssg/replace-act-import/tests/multiple-named-imports-with-other-specifier/expected.tsx b/codemods/replace-act-import/tests/multiple-named-imports-with-other-specifier/expected.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/multiple-named-imports-with-other-specifier/expected.tsx rename to codemods/replace-act-import/tests/multiple-named-imports-with-other-specifier/expected.tsx diff --git a/codemods/jssg/replace-act-import/tests/multiple-named-imports-with-other-specifier/input.tsx b/codemods/replace-act-import/tests/multiple-named-imports-with-other-specifier/input.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/multiple-named-imports-with-other-specifier/input.tsx rename to codemods/replace-act-import/tests/multiple-named-imports-with-other-specifier/input.tsx diff --git a/codemods/jssg/replace-act-import/tests/multiple-named-imports-with-other-specifier/metrics.json b/codemods/replace-act-import/tests/multiple-named-imports-with-other-specifier/metrics.json similarity index 100% rename from codemods/jssg/replace-act-import/tests/multiple-named-imports-with-other-specifier/metrics.json rename to codemods/replace-act-import/tests/multiple-named-imports-with-other-specifier/metrics.json diff --git a/codemods/jssg/replace-act-import/tests/multiple-named-imports/expected.tsx b/codemods/replace-act-import/tests/multiple-named-imports/expected.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/multiple-named-imports/expected.tsx rename to codemods/replace-act-import/tests/multiple-named-imports/expected.tsx diff --git a/codemods/jssg/replace-act-import/tests/multiple-named-imports/input.tsx b/codemods/replace-act-import/tests/multiple-named-imports/input.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/multiple-named-imports/input.tsx rename to codemods/replace-act-import/tests/multiple-named-imports/input.tsx diff --git a/codemods/jssg/replace-act-import/tests/multiple-named-imports/metrics.json b/codemods/replace-act-import/tests/multiple-named-imports/metrics.json similarity index 100% rename from codemods/jssg/replace-act-import/tests/multiple-named-imports/metrics.json rename to codemods/replace-act-import/tests/multiple-named-imports/metrics.json diff --git a/codemods/jssg/replace-act-import/tests/named-import-2/expected.tsx b/codemods/replace-act-import/tests/named-import-2/expected.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/named-import-2/expected.tsx rename to codemods/replace-act-import/tests/named-import-2/expected.tsx diff --git a/codemods/jssg/replace-act-import/tests/named-import-2/input.tsx b/codemods/replace-act-import/tests/named-import-2/input.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/named-import-2/input.tsx rename to codemods/replace-act-import/tests/named-import-2/input.tsx diff --git a/codemods/jssg/replace-act-import/tests/named-import-2/metrics.json b/codemods/replace-act-import/tests/named-import-2/metrics.json similarity index 100% rename from codemods/jssg/replace-act-import/tests/named-import-2/metrics.json rename to codemods/replace-act-import/tests/named-import-2/metrics.json diff --git a/codemods/jssg/replace-act-import/tests/named-import/expected.tsx b/codemods/replace-act-import/tests/named-import/expected.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/named-import/expected.tsx rename to codemods/replace-act-import/tests/named-import/expected.tsx diff --git a/codemods/jssg/replace-act-import/tests/named-import/input.tsx b/codemods/replace-act-import/tests/named-import/input.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/named-import/input.tsx rename to codemods/replace-act-import/tests/named-import/input.tsx diff --git a/codemods/jssg/replace-act-import/tests/named-import/metrics.json b/codemods/replace-act-import/tests/named-import/metrics.json similarity index 100% rename from codemods/jssg/replace-act-import/tests/named-import/metrics.json rename to codemods/replace-act-import/tests/named-import/metrics.json diff --git a/codemods/jssg/replace-act-import/tests/named-reexport-no-change/expected.tsx b/codemods/replace-act-import/tests/named-reexport-no-change/expected.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/named-reexport-no-change/expected.tsx rename to codemods/replace-act-import/tests/named-reexport-no-change/expected.tsx diff --git a/codemods/jssg/replace-act-import/tests/named-reexport-no-change/input.tsx b/codemods/replace-act-import/tests/named-reexport-no-change/input.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/named-reexport-no-change/input.tsx rename to codemods/replace-act-import/tests/named-reexport-no-change/input.tsx diff --git a/codemods/jssg/replace-act-import/tests/other-import/expected.tsx b/codemods/replace-act-import/tests/other-import/expected.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/other-import/expected.tsx rename to codemods/replace-act-import/tests/other-import/expected.tsx diff --git a/codemods/jssg/replace-act-import/tests/other-import/input.tsx b/codemods/replace-act-import/tests/other-import/input.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/other-import/input.tsx rename to codemods/replace-act-import/tests/other-import/input.tsx diff --git a/codemods/jssg/replace-act-import/tests/re-export/expected.tsx b/codemods/replace-act-import/tests/re-export/expected.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/re-export/expected.tsx rename to codemods/replace-act-import/tests/re-export/expected.tsx diff --git a/codemods/jssg/replace-act-import/tests/re-export/input.tsx b/codemods/replace-act-import/tests/re-export/input.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/re-export/input.tsx rename to codemods/replace-act-import/tests/re-export/input.tsx diff --git a/codemods/jssg/replace-act-import/tests/re-export/metrics.json b/codemods/replace-act-import/tests/re-export/metrics.json similarity index 100% rename from codemods/jssg/replace-act-import/tests/re-export/metrics.json rename to codemods/replace-act-import/tests/re-export/metrics.json diff --git a/codemods/jssg/replace-act-import/tests/wildcard-import-2/expected.tsx b/codemods/replace-act-import/tests/wildcard-import-2/expected.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/wildcard-import-2/expected.tsx rename to codemods/replace-act-import/tests/wildcard-import-2/expected.tsx diff --git a/codemods/jssg/replace-act-import/tests/wildcard-import-2/input.tsx b/codemods/replace-act-import/tests/wildcard-import-2/input.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/wildcard-import-2/input.tsx rename to codemods/replace-act-import/tests/wildcard-import-2/input.tsx diff --git a/codemods/jssg/replace-act-import/tests/wildcard-import-2/metrics.json b/codemods/replace-act-import/tests/wildcard-import-2/metrics.json similarity index 100% rename from codemods/jssg/replace-act-import/tests/wildcard-import-2/metrics.json rename to codemods/replace-act-import/tests/wildcard-import-2/metrics.json diff --git a/codemods/jssg/replace-act-import/tests/wildcard-import/expected.tsx b/codemods/replace-act-import/tests/wildcard-import/expected.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/wildcard-import/expected.tsx rename to codemods/replace-act-import/tests/wildcard-import/expected.tsx diff --git a/codemods/jssg/replace-act-import/tests/wildcard-import/input.tsx b/codemods/replace-act-import/tests/wildcard-import/input.tsx similarity index 100% rename from codemods/jssg/replace-act-import/tests/wildcard-import/input.tsx rename to codemods/replace-act-import/tests/wildcard-import/input.tsx diff --git a/codemods/jssg/replace-act-import/tests/wildcard-import/metrics.json b/codemods/replace-act-import/tests/wildcard-import/metrics.json similarity index 100% rename from codemods/jssg/replace-act-import/tests/wildcard-import/metrics.json rename to codemods/replace-act-import/tests/wildcard-import/metrics.json diff --git a/codemods/replace-act-import/tsconfig.json b/codemods/replace-act-import/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/replace-act-import/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": [ + "ESNext" + ], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": [ + "@codemod.com/jssg-types", + "node" + ], + "allowImportingTsExtensions": true, + "noEmit": true, + "skipLibCheck": true, + "verbatimModuleSyntax": false, + "erasableSyntaxOnly": true, + "strict": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true + }, + "exclude": [ + "tests" + ] +} diff --git a/codemods/jssg/replace-act-import/workflow.yaml b/codemods/replace-act-import/workflow.yaml similarity index 100% rename from codemods/jssg/replace-act-import/workflow.yaml rename to codemods/replace-act-import/workflow.yaml diff --git a/codemods/jssg/replace-string-ref/.gitignore b/codemods/replace-reactdom-render/.gitignore similarity index 100% rename from codemods/jssg/replace-string-ref/.gitignore rename to codemods/replace-reactdom-render/.gitignore diff --git a/codemods/jssg/replace-reactdom-render/README.md b/codemods/replace-reactdom-render/README.md similarity index 78% rename from codemods/jssg/replace-reactdom-render/README.md rename to codemods/replace-reactdom-render/README.md index 6bfd52f..859b039 100644 --- a/codemods/jssg/replace-reactdom-render/README.md +++ b/codemods/replace-reactdom-render/README.md @@ -7,10 +7,3 @@ Replace `ReactDOM.render` and named `render` imports with `createRoot(...).rende ```bash npx codemod @react-new/replace-reactdom-render --target ``` - -## Development - -```bash -pnpm test -pnpm check-types -``` diff --git a/codemods/jssg/replace-reactdom-render/codemod.yaml b/codemods/replace-reactdom-render/codemod.yaml similarity index 95% rename from codemods/jssg/replace-reactdom-render/codemod.yaml rename to codemods/replace-reactdom-render/codemod.yaml index ce8a58c..dae778b 100644 --- a/codemods/jssg/replace-reactdom-render/codemod.yaml +++ b/codemods/replace-reactdom-render/codemod.yaml @@ -1,7 +1,7 @@ schema_version: "1.0" name: "@react-new/replace-reactdom-render" -version: "0.1.0" +version: "0.1.1" description: "Replace ReactDOM.render with createRoot for React 18" author: "Codemod " license: "MIT" diff --git a/codemods/jssg/replace-reactdom-render/package.json b/codemods/replace-reactdom-render/package.json similarity index 95% rename from codemods/jssg/replace-reactdom-render/package.json rename to codemods/replace-reactdom-render/package.json index 053a994..1aa82ad 100644 --- a/codemods/jssg/replace-reactdom-render/package.json +++ b/codemods/replace-reactdom-render/package.json @@ -1,6 +1,6 @@ { "name": "@react-new/replace-reactdom-render", - "version": "0.1.0", + "version": "0.1.1", "description": "Replace ReactDOM.render with createRoot for React 18", "type": "module", "scripts": { diff --git a/codemods/jssg/replace-reactdom-render/pnpm-lock.yaml b/codemods/replace-reactdom-render/pnpm-lock.yaml similarity index 100% rename from codemods/jssg/replace-reactdom-render/pnpm-lock.yaml rename to codemods/replace-reactdom-render/pnpm-lock.yaml diff --git a/codemods/jssg/replace-reactdom-render/scripts/codemod.ts b/codemods/replace-reactdom-render/scripts/codemod.ts similarity index 84% rename from codemods/jssg/replace-reactdom-render/scripts/codemod.ts rename to codemods/replace-reactdom-render/scripts/codemod.ts index 558c2fc..636a820 100644 --- a/codemods/jssg/replace-reactdom-render/scripts/codemod.ts +++ b/codemods/replace-reactdom-render/scripts/codemod.ts @@ -68,6 +68,30 @@ function findNamedImportNames(rootNode: SgNode, exportedName: st return names; } +function lineStartIndex(source: string, index: number): number { + let cursor = index; + while (cursor > 0 && source[cursor - 1] !== "\n" && source[cursor - 1] !== "\r") { + cursor--; + } + return cursor; +} + +function nearestAncestorOfKind(node: SgNode, kind: string): SgNode | null { + let current = node.parent(); + while (current) { + if (current.kind() === kind) { + return current; + } + current = current.parent(); + } + return null; +} + +function directExpressionStatement(node: SgNode): SgNode | null { + const parent = node.parent(); + return parent?.kind() === "expression_statement" ? parent : null; +} + const transform: Transform = async (root) => { const rootNode = root.root(); const edits: Edit[] = []; @@ -113,7 +137,7 @@ const transform: Transform = async (root) => { const element = argList[0]!; const container = argList[1]!; const callback = argList[2]; - const statement = call.ancestors().find((ancestor) => ancestor.kind() === "expression_statement"); + const statement = directExpressionStatement(call); if (!statement) return; const rootName = nextRootName(); @@ -152,7 +176,7 @@ const transform: Transform = async (root) => { if (argList.length < 1) return; const container = argList[0]!; - const statement = call.ancestors().find((ancestor) => ancestor.kind() === "expression_statement"); + const statement = directExpressionStatement(call); if (!statement) return; const rootName = nextRootName(); @@ -167,6 +191,9 @@ const transform: Transform = async (root) => { }); }; + const matchedRenderCalls: SgNode[] = []; + const matchedUnmountCalls: Array<{ call: SgNode; pattern: string }> = []; + if (reactDomMemberImportNames.size > 0) { const memberRenderCalls = rootNode.findAll({ rule: { @@ -198,7 +225,7 @@ const transform: Transform = async (root) => { const callee = call.field("function"); const objectNode = callee?.field("object"); if (!objectNode || !reactDomMemberImportNames.has(objectNode.text())) continue; - applyRenderReplacement(call, "ReactDOM.render"); + matchedRenderCalls.push(call); } const memberUnmountCalls = rootNode.findAll({ @@ -231,7 +258,7 @@ const transform: Transform = async (root) => { const callee = call.field("function"); const objectNode = callee?.field("object"); if (!objectNode || !reactDomMemberImportNames.has(objectNode.text())) continue; - applyUnmountReplacement(call, "ReactDOM.unmountComponentAtNode"); + matchedUnmountCalls.push({ call, pattern: "ReactDOM.unmountComponentAtNode" }); } } @@ -250,7 +277,7 @@ const transform: Transform = async (root) => { for (const call of renderCalls) { const callee = call.field("function"); if (!callee || !reactDomRenderImportNames.has(callee.text())) continue; - applyRenderReplacement(call, "render"); + matchedRenderCalls.push(call); } } @@ -269,10 +296,25 @@ const transform: Transform = async (root) => { for (const call of unmountCalls) { const callee = call.field("function"); if (!callee || !reactDomUnmountImportNames.has(callee.text())) continue; - applyUnmountReplacement(call, "unmountComponentAtNode"); + matchedUnmountCalls.push({ call, pattern: "unmountComponentAtNode" }); } } + const hasUnsafeRenderUsage = matchedRenderCalls.some((call) => directExpressionStatement(call) === null); + if (hasUnsafeRenderUsage) { + return null; + } + + for (const call of matchedRenderCalls) { + const callee = call.field("function"); + const pattern = callee?.kind() === "identifier" ? "render" : "ReactDOM.render"; + applyRenderReplacement(call, pattern); + } + + for (const { call, pattern } of matchedUnmountCalls) { + applyUnmountReplacement(call, pattern); + } + if (!hasTransformations) { return null; } diff --git a/codemods/jssg/replace-reactdom-render/tests/default/expected.tsx b/codemods/replace-reactdom-render/tests/default/expected.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/default/expected.tsx rename to codemods/replace-reactdom-render/tests/default/expected.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/default/input.tsx b/codemods/replace-reactdom-render/tests/default/input.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/default/input.tsx rename to codemods/replace-reactdom-render/tests/default/input.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/default/metrics.json b/codemods/replace-reactdom-render/tests/default/metrics.json similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/default/metrics.json rename to codemods/replace-reactdom-render/tests/default/metrics.json diff --git a/codemods/jssg/replace-reactdom-render/tests/multiline-jsx/expected.tsx b/codemods/replace-reactdom-render/tests/multiline-jsx/expected.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/multiline-jsx/expected.tsx rename to codemods/replace-reactdom-render/tests/multiline-jsx/expected.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/multiline-jsx/input.tsx b/codemods/replace-reactdom-render/tests/multiline-jsx/input.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/multiline-jsx/input.tsx rename to codemods/replace-reactdom-render/tests/multiline-jsx/input.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/multiline-jsx/metrics.json b/codemods/replace-reactdom-render/tests/multiline-jsx/metrics.json similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/multiline-jsx/metrics.json rename to codemods/replace-reactdom-render/tests/multiline-jsx/metrics.json diff --git a/codemods/jssg/replace-reactdom-render/tests/multiple-member-render-imports/expected.tsx b/codemods/replace-reactdom-render/tests/multiple-member-render-imports/expected.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/multiple-member-render-imports/expected.tsx rename to codemods/replace-reactdom-render/tests/multiple-member-render-imports/expected.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/multiple-member-render-imports/input.tsx b/codemods/replace-reactdom-render/tests/multiple-member-render-imports/input.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/multiple-member-render-imports/input.tsx rename to codemods/replace-reactdom-render/tests/multiple-member-render-imports/input.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/multiple-member-render-imports/metrics.json b/codemods/replace-reactdom-render/tests/multiple-member-render-imports/metrics.json similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/multiple-member-render-imports/metrics.json rename to codemods/replace-reactdom-render/tests/multiple-member-render-imports/metrics.json diff --git a/codemods/jssg/replace-reactdom-render/tests/multiple-named-render-imports/expected.tsx b/codemods/replace-reactdom-render/tests/multiple-named-render-imports/expected.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/multiple-named-render-imports/expected.tsx rename to codemods/replace-reactdom-render/tests/multiple-named-render-imports/expected.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/multiple-named-render-imports/input.tsx b/codemods/replace-reactdom-render/tests/multiple-named-render-imports/input.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/multiple-named-render-imports/input.tsx rename to codemods/replace-reactdom-render/tests/multiple-named-render-imports/input.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/multiple-named-render-imports/metrics.json b/codemods/replace-reactdom-render/tests/multiple-named-render-imports/metrics.json similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/multiple-named-render-imports/metrics.json rename to codemods/replace-reactdom-render/tests/multiple-named-render-imports/metrics.json diff --git a/codemods/replace-reactdom-render/tests/nested-top-level-expression/expected.tsx b/codemods/replace-reactdom-render/tests/nested-top-level-expression/expected.tsx new file mode 100644 index 0000000..586c3ec --- /dev/null +++ b/codemods/replace-reactdom-render/tests/nested-top-level-expression/expected.tsx @@ -0,0 +1,9 @@ +import { createRoot } from "react-dom/client"; +import { render } from "react-dom"; + +describe("suite", () => { + const run = () => { + const root = createRoot(theNode); + root.render(); + }; +}); diff --git a/codemods/replace-reactdom-render/tests/nested-top-level-expression/input.tsx b/codemods/replace-reactdom-render/tests/nested-top-level-expression/input.tsx new file mode 100644 index 0000000..8c13f27 --- /dev/null +++ b/codemods/replace-reactdom-render/tests/nested-top-level-expression/input.tsx @@ -0,0 +1,7 @@ +import { render } from "react-dom"; + +describe("suite", () => { + const run = () => { + render(, theNode); + }; +}); diff --git a/codemods/replace-reactdom-render/tests/nested-top-level-expression/metrics.json b/codemods/replace-reactdom-render/tests/nested-top-level-expression/metrics.json new file mode 100644 index 0000000..df016a9 --- /dev/null +++ b/codemods/replace-reactdom-render/tests/nested-top-level-expression/metrics.json @@ -0,0 +1,11 @@ +{ + "reactdom-render-replacements": [ + { + "cardinality": { + "file": "tests/nested-top-level-expression/input.tsx", + "pattern": "render" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-reactdom-render/tests/nested/expected.tsx b/codemods/replace-reactdom-render/tests/nested/expected.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/nested/expected.tsx rename to codemods/replace-reactdom-render/tests/nested/expected.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/nested/input.tsx b/codemods/replace-reactdom-render/tests/nested/input.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/nested/input.tsx rename to codemods/replace-reactdom-render/tests/nested/input.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/nested/metrics.json b/codemods/replace-reactdom-render/tests/nested/metrics.json similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/nested/metrics.json rename to codemods/replace-reactdom-render/tests/nested/metrics.json diff --git a/codemods/jssg/replace-reactdom-render/tests/render-and-unmount/expected.tsx b/codemods/replace-reactdom-render/tests/render-and-unmount/expected.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/render-and-unmount/expected.tsx rename to codemods/replace-reactdom-render/tests/render-and-unmount/expected.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/render-and-unmount/input.tsx b/codemods/replace-reactdom-render/tests/render-and-unmount/input.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/render-and-unmount/input.tsx rename to codemods/replace-reactdom-render/tests/render-and-unmount/input.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/render-and-unmount/metrics.json b/codemods/replace-reactdom-render/tests/render-and-unmount/metrics.json similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/render-and-unmount/metrics.json rename to codemods/replace-reactdom-render/tests/render-and-unmount/metrics.json diff --git a/codemods/replace-reactdom-render/tests/render-assigned-no-change/expected.tsx b/codemods/replace-reactdom-render/tests/render-assigned-no-change/expected.tsx new file mode 100644 index 0000000..c2e8673 --- /dev/null +++ b/codemods/replace-reactdom-render/tests/render-assigned-no-change/expected.tsx @@ -0,0 +1,8 @@ +import ReactDOM from "react-dom"; + +let renderedNode; + +function mount(container) { + renderedNode = ReactDOM.render(, container); + return renderedNode; +} diff --git a/codemods/replace-reactdom-render/tests/render-assigned-no-change/input.tsx b/codemods/replace-reactdom-render/tests/render-assigned-no-change/input.tsx new file mode 100644 index 0000000..c2e8673 --- /dev/null +++ b/codemods/replace-reactdom-render/tests/render-assigned-no-change/input.tsx @@ -0,0 +1,8 @@ +import ReactDOM from "react-dom"; + +let renderedNode; + +function mount(container) { + renderedNode = ReactDOM.render(, container); + return renderedNode; +} diff --git a/codemods/replace-reactdom-render/tests/render-returned-no-change/expected.tsx b/codemods/replace-reactdom-render/tests/render-returned-no-change/expected.tsx new file mode 100644 index 0000000..2d5e170 --- /dev/null +++ b/codemods/replace-reactdom-render/tests/render-returned-no-change/expected.tsx @@ -0,0 +1,5 @@ +import ReactDOM from "react-dom"; + +function mount(container) { + return ReactDOM.render(, container); +} diff --git a/codemods/replace-reactdom-render/tests/render-returned-no-change/input.tsx b/codemods/replace-reactdom-render/tests/render-returned-no-change/input.tsx new file mode 100644 index 0000000..2d5e170 --- /dev/null +++ b/codemods/replace-reactdom-render/tests/render-returned-no-change/input.tsx @@ -0,0 +1,5 @@ +import ReactDOM from "react-dom"; + +function mount(container) { + return ReactDOM.render(, container); +} diff --git a/codemods/jssg/replace-reactdom-render/tests/render-with-callback/expected.tsx b/codemods/replace-reactdom-render/tests/render-with-callback/expected.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/render-with-callback/expected.tsx rename to codemods/replace-reactdom-render/tests/render-with-callback/expected.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/render-with-callback/input.tsx b/codemods/replace-reactdom-render/tests/render-with-callback/input.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/render-with-callback/input.tsx rename to codemods/replace-reactdom-render/tests/render-with-callback/input.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/render-with-callback/metrics.json b/codemods/replace-reactdom-render/tests/render-with-callback/metrics.json similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/render-with-callback/metrics.json rename to codemods/replace-reactdom-render/tests/render-with-callback/metrics.json diff --git a/codemods/jssg/replace-reactdom-render/tests/unmount-member/expected.tsx b/codemods/replace-reactdom-render/tests/unmount-member/expected.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/unmount-member/expected.tsx rename to codemods/replace-reactdom-render/tests/unmount-member/expected.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/unmount-member/input.tsx b/codemods/replace-reactdom-render/tests/unmount-member/input.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/unmount-member/input.tsx rename to codemods/replace-reactdom-render/tests/unmount-member/input.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/unmount-member/metrics.json b/codemods/replace-reactdom-render/tests/unmount-member/metrics.json similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/unmount-member/metrics.json rename to codemods/replace-reactdom-render/tests/unmount-member/metrics.json diff --git a/codemods/jssg/replace-reactdom-render/tests/unmount-named/expected.tsx b/codemods/replace-reactdom-render/tests/unmount-named/expected.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/unmount-named/expected.tsx rename to codemods/replace-reactdom-render/tests/unmount-named/expected.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/unmount-named/input.tsx b/codemods/replace-reactdom-render/tests/unmount-named/input.tsx similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/unmount-named/input.tsx rename to codemods/replace-reactdom-render/tests/unmount-named/input.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/unmount-named/metrics.json b/codemods/replace-reactdom-render/tests/unmount-named/metrics.json similarity index 100% rename from codemods/jssg/replace-reactdom-render/tests/unmount-named/metrics.json rename to codemods/replace-reactdom-render/tests/unmount-named/metrics.json diff --git a/codemods/replace-reactdom-render/tsconfig.json b/codemods/replace-reactdom-render/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/replace-reactdom-render/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": [ + "ESNext" + ], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": [ + "@codemod.com/jssg-types", + "node" + ], + "allowImportingTsExtensions": true, + "noEmit": true, + "skipLibCheck": true, + "verbatimModuleSyntax": false, + "erasableSyntaxOnly": true, + "strict": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true + }, + "exclude": [ + "tests" + ] +} diff --git a/codemods/replace-reactdom-render/workflow.yaml b/codemods/replace-reactdom-render/workflow.yaml new file mode 100644 index 0000000..8773841 --- /dev/null +++ b/codemods/replace-reactdom-render/workflow.yaml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: "Scan tsx files and apply fixes" + js-ast-grep: + js_file: scripts/codemod.ts + language: "tsx" diff --git a/codemods/jssg/use-context-hook/.gitignore b/codemods/replace-string-ref/.gitignore similarity index 100% rename from codemods/jssg/use-context-hook/.gitignore rename to codemods/replace-string-ref/.gitignore diff --git a/codemods/jssg/replace-string-ref/README.md b/codemods/replace-string-ref/README.md similarity index 78% rename from codemods/jssg/replace-string-ref/README.md rename to codemods/replace-string-ref/README.md index 4a9267d..fa3e33d 100644 --- a/codemods/jssg/replace-string-ref/README.md +++ b/codemods/replace-string-ref/README.md @@ -7,10 +7,3 @@ Replace string refs in React class components with callback refs that assign thr ```bash npx codemod @react-new/replace-string-ref --target ``` - -## Development - -```bash -pnpm test -pnpm check-types -``` diff --git a/codemods/jssg/replace-string-ref/codemod.yaml b/codemods/replace-string-ref/codemod.yaml similarity index 95% rename from codemods/jssg/replace-string-ref/codemod.yaml rename to codemods/replace-string-ref/codemod.yaml index f9fe55c..29e501b 100644 --- a/codemods/jssg/replace-string-ref/codemod.yaml +++ b/codemods/replace-string-ref/codemod.yaml @@ -1,7 +1,7 @@ schema_version: "1.0" name: "@react-new/replace-string-ref" -version: "0.1.0" +version: "0.1.1" description: "Replace string refs with callback refs in React class components" author: "Codemod " license: "MIT" diff --git a/codemods/jssg/replace-string-ref/package.json b/codemods/replace-string-ref/package.json similarity index 95% rename from codemods/jssg/replace-string-ref/package.json rename to codemods/replace-string-ref/package.json index 14fc031..dfb14b6 100644 --- a/codemods/jssg/replace-string-ref/package.json +++ b/codemods/replace-string-ref/package.json @@ -1,6 +1,6 @@ { "name": "@react-new/replace-string-ref", - "version": "0.1.0", + "version": "0.1.1", "description": "Replace string refs with callback refs in React class components", "type": "module", "scripts": { diff --git a/codemods/jssg/replace-string-ref/pnpm-lock.yaml b/codemods/replace-string-ref/pnpm-lock.yaml similarity index 100% rename from codemods/jssg/replace-string-ref/pnpm-lock.yaml rename to codemods/replace-string-ref/pnpm-lock.yaml diff --git a/codemods/jssg/replace-string-ref/scripts/codemod.ts b/codemods/replace-string-ref/scripts/codemod.ts similarity index 100% rename from codemods/jssg/replace-string-ref/scripts/codemod.ts rename to codemods/replace-string-ref/scripts/codemod.ts diff --git a/codemods/jssg/replace-string-ref/tests/class-component-custom-import-names/expected.tsx b/codemods/replace-string-ref/tests/class-component-custom-import-names/expected.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/class-component-custom-import-names/expected.tsx rename to codemods/replace-string-ref/tests/class-component-custom-import-names/expected.tsx diff --git a/codemods/jssg/replace-string-ref/tests/class-component-custom-import-names/input.tsx b/codemods/replace-string-ref/tests/class-component-custom-import-names/input.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/class-component-custom-import-names/input.tsx rename to codemods/replace-string-ref/tests/class-component-custom-import-names/input.tsx diff --git a/codemods/jssg/replace-string-ref/tests/class-component-custom-import-names/metrics.json b/codemods/replace-string-ref/tests/class-component-custom-import-names/metrics.json similarity index 100% rename from codemods/jssg/replace-string-ref/tests/class-component-custom-import-names/metrics.json rename to codemods/replace-string-ref/tests/class-component-custom-import-names/metrics.json diff --git a/codemods/jssg/replace-string-ref/tests/class-component-default-import/expected.tsx b/codemods/replace-string-ref/tests/class-component-default-import/expected.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/class-component-default-import/expected.tsx rename to codemods/replace-string-ref/tests/class-component-default-import/expected.tsx diff --git a/codemods/jssg/replace-string-ref/tests/class-component-default-import/input.tsx b/codemods/replace-string-ref/tests/class-component-default-import/input.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/class-component-default-import/input.tsx rename to codemods/replace-string-ref/tests/class-component-default-import/input.tsx diff --git a/codemods/jssg/replace-string-ref/tests/class-component-default-import/metrics.json b/codemods/replace-string-ref/tests/class-component-default-import/metrics.json similarity index 100% rename from codemods/jssg/replace-string-ref/tests/class-component-default-import/metrics.json rename to codemods/replace-string-ref/tests/class-component-default-import/metrics.json diff --git a/codemods/jssg/replace-string-ref/tests/class-component-named-import/expected.tsx b/codemods/replace-string-ref/tests/class-component-named-import/expected.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/class-component-named-import/expected.tsx rename to codemods/replace-string-ref/tests/class-component-named-import/expected.tsx diff --git a/codemods/jssg/replace-string-ref/tests/class-component-named-import/input.tsx b/codemods/replace-string-ref/tests/class-component-named-import/input.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/class-component-named-import/input.tsx rename to codemods/replace-string-ref/tests/class-component-named-import/input.tsx diff --git a/codemods/jssg/replace-string-ref/tests/class-component-named-import/metrics.json b/codemods/replace-string-ref/tests/class-component-named-import/metrics.json similarity index 100% rename from codemods/jssg/replace-string-ref/tests/class-component-named-import/metrics.json rename to codemods/replace-string-ref/tests/class-component-named-import/metrics.json diff --git a/codemods/jssg/replace-string-ref/tests/export-default-class/expected.tsx b/codemods/replace-string-ref/tests/export-default-class/expected.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/export-default-class/expected.tsx rename to codemods/replace-string-ref/tests/export-default-class/expected.tsx diff --git a/codemods/jssg/replace-string-ref/tests/export-default-class/input.tsx b/codemods/replace-string-ref/tests/export-default-class/input.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/export-default-class/input.tsx rename to codemods/replace-string-ref/tests/export-default-class/input.tsx diff --git a/codemods/jssg/replace-string-ref/tests/export-default-class/metrics.json b/codemods/replace-string-ref/tests/export-default-class/metrics.json similarity index 100% rename from codemods/jssg/replace-string-ref/tests/export-default-class/metrics.json rename to codemods/replace-string-ref/tests/export-default-class/metrics.json diff --git a/codemods/jssg/replace-string-ref/tests/function-component/expected.tsx b/codemods/replace-string-ref/tests/function-component/expected.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/function-component/expected.tsx rename to codemods/replace-string-ref/tests/function-component/expected.tsx diff --git a/codemods/jssg/replace-string-ref/tests/function-component/input.tsx b/codemods/replace-string-ref/tests/function-component/input.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/function-component/input.tsx rename to codemods/replace-string-ref/tests/function-component/input.tsx diff --git a/codemods/jssg/replace-string-ref/tests/member-expression-ref-value-no-change/expected.tsx b/codemods/replace-string-ref/tests/member-expression-ref-value-no-change/expected.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/member-expression-ref-value-no-change/expected.tsx rename to codemods/replace-string-ref/tests/member-expression-ref-value-no-change/expected.tsx diff --git a/codemods/jssg/replace-string-ref/tests/member-expression-ref-value-no-change/input.tsx b/codemods/replace-string-ref/tests/member-expression-ref-value-no-change/input.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/member-expression-ref-value-no-change/input.tsx rename to codemods/replace-string-ref/tests/member-expression-ref-value-no-change/input.tsx diff --git a/codemods/jssg/replace-string-ref/tests/mixin-component-no-change/expected.tsx b/codemods/replace-string-ref/tests/mixin-component-no-change/expected.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/mixin-component-no-change/expected.tsx rename to codemods/replace-string-ref/tests/mixin-component-no-change/expected.tsx diff --git a/codemods/jssg/replace-string-ref/tests/mixin-component-no-change/input.tsx b/codemods/replace-string-ref/tests/mixin-component-no-change/input.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/mixin-component-no-change/input.tsx rename to codemods/replace-string-ref/tests/mixin-component-no-change/input.tsx diff --git a/codemods/jssg/replace-string-ref/tests/multiple-string-refs/expected.tsx b/codemods/replace-string-ref/tests/multiple-string-refs/expected.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/multiple-string-refs/expected.tsx rename to codemods/replace-string-ref/tests/multiple-string-refs/expected.tsx diff --git a/codemods/jssg/replace-string-ref/tests/multiple-string-refs/input.tsx b/codemods/replace-string-ref/tests/multiple-string-refs/input.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/multiple-string-refs/input.tsx rename to codemods/replace-string-ref/tests/multiple-string-refs/input.tsx diff --git a/codemods/jssg/replace-string-ref/tests/multiple-string-refs/metrics.json b/codemods/replace-string-ref/tests/multiple-string-refs/metrics.json similarity index 100% rename from codemods/jssg/replace-string-ref/tests/multiple-string-refs/metrics.json rename to codemods/replace-string-ref/tests/multiple-string-refs/metrics.json diff --git a/codemods/jssg/replace-string-ref/tests/namespace-import/expected.tsx b/codemods/replace-string-ref/tests/namespace-import/expected.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/namespace-import/expected.tsx rename to codemods/replace-string-ref/tests/namespace-import/expected.tsx diff --git a/codemods/jssg/replace-string-ref/tests/namespace-import/input.tsx b/codemods/replace-string-ref/tests/namespace-import/input.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/namespace-import/input.tsx rename to codemods/replace-string-ref/tests/namespace-import/input.tsx diff --git a/codemods/jssg/replace-string-ref/tests/namespace-import/metrics.json b/codemods/replace-string-ref/tests/namespace-import/metrics.json similarity index 100% rename from codemods/jssg/replace-string-ref/tests/namespace-import/metrics.json rename to codemods/replace-string-ref/tests/namespace-import/metrics.json diff --git a/codemods/jssg/replace-string-ref/tests/non-identifier-ref-name/expected.tsx b/codemods/replace-string-ref/tests/non-identifier-ref-name/expected.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/non-identifier-ref-name/expected.tsx rename to codemods/replace-string-ref/tests/non-identifier-ref-name/expected.tsx diff --git a/codemods/jssg/replace-string-ref/tests/non-identifier-ref-name/input.tsx b/codemods/replace-string-ref/tests/non-identifier-ref-name/input.tsx similarity index 100% rename from codemods/jssg/replace-string-ref/tests/non-identifier-ref-name/input.tsx rename to codemods/replace-string-ref/tests/non-identifier-ref-name/input.tsx diff --git a/codemods/jssg/replace-string-ref/tests/non-identifier-ref-name/metrics.json b/codemods/replace-string-ref/tests/non-identifier-ref-name/metrics.json similarity index 100% rename from codemods/jssg/replace-string-ref/tests/non-identifier-ref-name/metrics.json rename to codemods/replace-string-ref/tests/non-identifier-ref-name/metrics.json diff --git a/codemods/replace-string-ref/tsconfig.json b/codemods/replace-string-ref/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/replace-string-ref/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": [ + "ESNext" + ], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": [ + "@codemod.com/jssg-types", + "node" + ], + "allowImportingTsExtensions": true, + "noEmit": true, + "skipLibCheck": true, + "verbatimModuleSyntax": false, + "erasableSyntaxOnly": true, + "strict": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true + }, + "exclude": [ + "tests" + ] +} diff --git a/codemods/replace-string-ref/workflow.yaml b/codemods/replace-string-ref/workflow.yaml new file mode 100644 index 0000000..8773841 --- /dev/null +++ b/codemods/replace-string-ref/workflow.yaml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: "Scan tsx files and apply fixes" + js-ast-grep: + js_file: scripts/codemod.ts + language: "tsx" diff --git a/codemods/jssg/replace-use-form-state/README.md b/codemods/replace-use-form-state/README.md similarity index 77% rename from codemods/jssg/replace-use-form-state/README.md rename to codemods/replace-use-form-state/README.md index 4d2a790..5a43b3a 100644 --- a/codemods/jssg/replace-use-form-state/README.md +++ b/codemods/replace-use-form-state/README.md @@ -7,10 +7,3 @@ Rename `useFormState` to `useActionState` in `react-dom` imports and usages. ```bash npx codemod @react-new/replace-use-form-state --target ``` - -## Development - -```bash -pnpm test -pnpm check-types -``` diff --git a/codemods/jssg/replace-use-form-state/codemod.yaml b/codemods/replace-use-form-state/codemod.yaml similarity index 95% rename from codemods/jssg/replace-use-form-state/codemod.yaml rename to codemods/replace-use-form-state/codemod.yaml index b648f6b..e3e69d1 100644 --- a/codemods/jssg/replace-use-form-state/codemod.yaml +++ b/codemods/replace-use-form-state/codemod.yaml @@ -1,7 +1,7 @@ schema_version: "1.0" name: "@react-new/replace-use-form-state" -version: "0.1.0" +version: "0.1.1" description: "Rename useFormState to useActionState in react-dom imports and usages" author: "Codemod " license: "MIT" diff --git a/codemods/jssg/replace-use-form-state/package.json b/codemods/replace-use-form-state/package.json similarity index 95% rename from codemods/jssg/replace-use-form-state/package.json rename to codemods/replace-use-form-state/package.json index fbcf347..adc6da9 100644 --- a/codemods/jssg/replace-use-form-state/package.json +++ b/codemods/replace-use-form-state/package.json @@ -1,6 +1,6 @@ { "name": "@react-new/replace-use-form-state", - "version": "0.1.0", + "version": "0.1.1", "description": "Rename useFormState to useActionState in react-dom imports and usages", "type": "module", "scripts": { diff --git a/codemods/jssg/replace-use-form-state/scripts/codemod.ts b/codemods/replace-use-form-state/scripts/codemod.ts similarity index 100% rename from codemods/jssg/replace-use-form-state/scripts/codemod.ts rename to codemods/replace-use-form-state/scripts/codemod.ts diff --git a/codemods/jssg/replace-use-form-state/tests/aliased-to-same-name/expected.tsx b/codemods/replace-use-form-state/tests/aliased-to-same-name/expected.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/aliased-to-same-name/expected.tsx rename to codemods/replace-use-form-state/tests/aliased-to-same-name/expected.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/aliased-to-same-name/input.tsx b/codemods/replace-use-form-state/tests/aliased-to-same-name/input.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/aliased-to-same-name/input.tsx rename to codemods/replace-use-form-state/tests/aliased-to-same-name/input.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/aliased-to-same-name/metrics.json b/codemods/replace-use-form-state/tests/aliased-to-same-name/metrics.json similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/aliased-to-same-name/metrics.json rename to codemods/replace-use-form-state/tests/aliased-to-same-name/metrics.json diff --git a/codemods/jssg/replace-use-form-state/tests/default-and-named/expected.tsx b/codemods/replace-use-form-state/tests/default-and-named/expected.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/default-and-named/expected.tsx rename to codemods/replace-use-form-state/tests/default-and-named/expected.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/default-and-named/input.tsx b/codemods/replace-use-form-state/tests/default-and-named/input.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/default-and-named/input.tsx rename to codemods/replace-use-form-state/tests/default-and-named/input.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/default-and-named/metrics.json b/codemods/replace-use-form-state/tests/default-and-named/metrics.json similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/default-and-named/metrics.json rename to codemods/replace-use-form-state/tests/default-and-named/metrics.json diff --git a/codemods/jssg/replace-use-form-state/tests/default-import/expected.tsx b/codemods/replace-use-form-state/tests/default-import/expected.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/default-import/expected.tsx rename to codemods/replace-use-form-state/tests/default-import/expected.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/default-import/input.tsx b/codemods/replace-use-form-state/tests/default-import/input.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/default-import/input.tsx rename to codemods/replace-use-form-state/tests/default-import/input.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/default-import/metrics.json b/codemods/replace-use-form-state/tests/default-import/metrics.json similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/default-import/metrics.json rename to codemods/replace-use-form-state/tests/default-import/metrics.json diff --git a/codemods/jssg/replace-use-form-state/tests/member-access-non-import-no-change/expected.tsx b/codemods/replace-use-form-state/tests/member-access-non-import-no-change/expected.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/member-access-non-import-no-change/expected.tsx rename to codemods/replace-use-form-state/tests/member-access-non-import-no-change/expected.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/member-access-non-import-no-change/input.tsx b/codemods/replace-use-form-state/tests/member-access-non-import-no-change/input.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/member-access-non-import-no-change/input.tsx rename to codemods/replace-use-form-state/tests/member-access-non-import-no-change/input.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/expected.tsx b/codemods/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/expected.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/expected.tsx rename to codemods/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/expected.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/input.tsx b/codemods/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/input.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/input.tsx rename to codemods/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/input.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/metrics.json b/codemods/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/metrics.json similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/metrics.json rename to codemods/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/metrics.json diff --git a/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/expected.tsx b/codemods/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/expected.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/expected.tsx rename to codemods/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/expected.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/input.tsx b/codemods/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/input.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/input.tsx rename to codemods/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/input.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/metrics.json b/codemods/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/metrics.json similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/metrics.json rename to codemods/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/metrics.json diff --git a/codemods/jssg/replace-use-form-state/tests/named-import-2/expected.tsx b/codemods/replace-use-form-state/tests/named-import-2/expected.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/named-import-2/expected.tsx rename to codemods/replace-use-form-state/tests/named-import-2/expected.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/named-import-2/input.tsx b/codemods/replace-use-form-state/tests/named-import-2/input.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/named-import-2/input.tsx rename to codemods/replace-use-form-state/tests/named-import-2/input.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/named-import-2/metrics.json b/codemods/replace-use-form-state/tests/named-import-2/metrics.json similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/named-import-2/metrics.json rename to codemods/replace-use-form-state/tests/named-import-2/metrics.json diff --git a/codemods/jssg/replace-use-form-state/tests/named-import-3/expected.tsx b/codemods/replace-use-form-state/tests/named-import-3/expected.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/named-import-3/expected.tsx rename to codemods/replace-use-form-state/tests/named-import-3/expected.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/named-import-3/input.tsx b/codemods/replace-use-form-state/tests/named-import-3/input.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/named-import-3/input.tsx rename to codemods/replace-use-form-state/tests/named-import-3/input.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/named-import-3/metrics.json b/codemods/replace-use-form-state/tests/named-import-3/metrics.json similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/named-import-3/metrics.json rename to codemods/replace-use-form-state/tests/named-import-3/metrics.json diff --git a/codemods/jssg/replace-use-form-state/tests/named-import/expected.tsx b/codemods/replace-use-form-state/tests/named-import/expected.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/named-import/expected.tsx rename to codemods/replace-use-form-state/tests/named-import/expected.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/named-import/input.tsx b/codemods/replace-use-form-state/tests/named-import/input.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/named-import/input.tsx rename to codemods/replace-use-form-state/tests/named-import/input.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/named-import/metrics.json b/codemods/replace-use-form-state/tests/named-import/metrics.json similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/named-import/metrics.json rename to codemods/replace-use-form-state/tests/named-import/metrics.json diff --git a/codemods/jssg/replace-use-form-state/tests/namespace-and-named/expected.tsx b/codemods/replace-use-form-state/tests/namespace-and-named/expected.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/namespace-and-named/expected.tsx rename to codemods/replace-use-form-state/tests/namespace-and-named/expected.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/namespace-and-named/input.tsx b/codemods/replace-use-form-state/tests/namespace-and-named/input.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/namespace-and-named/input.tsx rename to codemods/replace-use-form-state/tests/namespace-and-named/input.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/namespace-and-named/metrics.json b/codemods/replace-use-form-state/tests/namespace-and-named/metrics.json similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/namespace-and-named/metrics.json rename to codemods/replace-use-form-state/tests/namespace-and-named/metrics.json diff --git a/codemods/jssg/replace-use-form-state/tests/other-import/expected.tsx b/codemods/replace-use-form-state/tests/other-import/expected.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/other-import/expected.tsx rename to codemods/replace-use-form-state/tests/other-import/expected.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/other-import/input.tsx b/codemods/replace-use-form-state/tests/other-import/input.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/other-import/input.tsx rename to codemods/replace-use-form-state/tests/other-import/input.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/shadowed-local-binding/expected.tsx b/codemods/replace-use-form-state/tests/shadowed-local-binding/expected.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/shadowed-local-binding/expected.tsx rename to codemods/replace-use-form-state/tests/shadowed-local-binding/expected.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/shadowed-local-binding/input.tsx b/codemods/replace-use-form-state/tests/shadowed-local-binding/input.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/shadowed-local-binding/input.tsx rename to codemods/replace-use-form-state/tests/shadowed-local-binding/input.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/shadowed-local-binding/metrics.json b/codemods/replace-use-form-state/tests/shadowed-local-binding/metrics.json similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/shadowed-local-binding/metrics.json rename to codemods/replace-use-form-state/tests/shadowed-local-binding/metrics.json diff --git a/codemods/jssg/replace-use-form-state/tests/type-identifier-rename/expected.tsx b/codemods/replace-use-form-state/tests/type-identifier-rename/expected.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/type-identifier-rename/expected.tsx rename to codemods/replace-use-form-state/tests/type-identifier-rename/expected.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/type-identifier-rename/input.tsx b/codemods/replace-use-form-state/tests/type-identifier-rename/input.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/type-identifier-rename/input.tsx rename to codemods/replace-use-form-state/tests/type-identifier-rename/input.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/type-identifier-rename/metrics.json b/codemods/replace-use-form-state/tests/type-identifier-rename/metrics.json similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/type-identifier-rename/metrics.json rename to codemods/replace-use-form-state/tests/type-identifier-rename/metrics.json diff --git a/codemods/jssg/replace-use-form-state/tests/wildcard-import/expected.tsx b/codemods/replace-use-form-state/tests/wildcard-import/expected.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/wildcard-import/expected.tsx rename to codemods/replace-use-form-state/tests/wildcard-import/expected.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/wildcard-import/input.tsx b/codemods/replace-use-form-state/tests/wildcard-import/input.tsx similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/wildcard-import/input.tsx rename to codemods/replace-use-form-state/tests/wildcard-import/input.tsx diff --git a/codemods/jssg/replace-use-form-state/tests/wildcard-import/metrics.json b/codemods/replace-use-form-state/tests/wildcard-import/metrics.json similarity index 100% rename from codemods/jssg/replace-use-form-state/tests/wildcard-import/metrics.json rename to codemods/replace-use-form-state/tests/wildcard-import/metrics.json diff --git a/codemods/replace-use-form-state/tsconfig.json b/codemods/replace-use-form-state/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/replace-use-form-state/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": [ + "ESNext" + ], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": [ + "@codemod.com/jssg-types", + "node" + ], + "allowImportingTsExtensions": true, + "noEmit": true, + "skipLibCheck": true, + "verbatimModuleSyntax": false, + "erasableSyntaxOnly": true, + "strict": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true + }, + "exclude": [ + "tests" + ] +} diff --git a/codemods/jssg/replace-use-form-state/workflow.yaml b/codemods/replace-use-form-state/workflow.yaml similarity index 100% rename from codemods/jssg/replace-use-form-state/workflow.yaml rename to codemods/replace-use-form-state/workflow.yaml diff --git a/codemods/sort-comp/README.md b/codemods/sort-comp/README.md new file mode 100644 index 0000000..57af195 --- /dev/null +++ b/codemods/sort-comp/README.md @@ -0,0 +1,14 @@ +# sort-comp + +Reorder React component members to match the `react/sort-comp` rule across `createClass` components and class components. + +## Usage + +```bash +npx codemod @react-new/sort-comp --target +``` + +## Options + +- `methodsOrder`: ordered list of method groups and names used for sorting. Defaults to the built-in React sort order used by this package. +- `explicit-require`: when `false`, run even if no React import/require is present. Default: `true`. diff --git a/codemods/sort-comp/codemod.yaml b/codemods/sort-comp/codemod.yaml new file mode 100644 index 0000000..b4a706a --- /dev/null +++ b/codemods/sort-comp/codemod.yaml @@ -0,0 +1,20 @@ +schema_version: "1.0" + +name: "@react-new/sort-comp" +version: "0.1.1" +description: "Reorder React component methods to match eslint-plugin-react sort-comp rule" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + +targets: + languages: ["tsx"] + +keywords: ["React"] + +registry: + access: "public" + visibility: "private" + +capabilities: + - fs diff --git a/codemods/sort-comp/package.json b/codemods/sort-comp/package.json new file mode 100644 index 0000000..d251748 --- /dev/null +++ b/codemods/sort-comp/package.json @@ -0,0 +1,15 @@ +{ + "name": "@react-new/sort-comp", + "version": "0.1.1", + "description": "Reorder React component methods to match eslint-plugin-react sort-comp rule", + "type": "module", + "scripts": { + "test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@codemod.com/jssg-types": "latest", + "@types/node": "latest", + "typescript": "latest" + } +} diff --git a/codemods/sort-comp/scripts/codemod.ts b/codemods/sort-comp/scripts/codemod.ts new file mode 100644 index 0000000..0ecab8a --- /dev/null +++ b/codemods/sort-comp/scripts/codemod.ts @@ -0,0 +1,626 @@ +import type { Transform, Edit, SgNode } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { useMetricAtom } from "codemod:metrics"; +import * as fs from "fs"; +import path from "path"; +import { pathToFileURL } from "url"; + +function metricFile(filename: string): string { + const cwd = process.cwd() + "/"; + return filename.startsWith(cwd) ? filename.slice(cwd.length) : filename; +} + +type ReactImportAliases = { + component: Set; + pureComponent: Set; +}; + +type ReactSuperclassKind = "Component" | "PureComponent"; + +const REACT_SOURCES = new Set(["react", "React", "react/addons", "react-native"]); + +function stringValue(node: SgNode): string | null { + const fragment = node.find({ rule: { kind: "string_fragment" } }); + if (fragment) return fragment.text(); + const text = node.text(); + return text.length >= 2 ? text.slice(1, -1) : null; +} + +function callArguments(call: SgNode): SgNode[] { + const args = call.field("arguments"); + if (!args) return []; + return args.children().filter((child) => child.isNamed() && child.kind() !== "comment"); +} + +function requireSource(call: SgNode): string | null { + const callee = call.field("function"); + if (!callee || callee.kind() !== "identifier" || callee.text() !== "require") return null; + const firstArg = callArguments(call)[0]; + return firstArg?.kind() === "string" ? stringValue(firstArg) : null; +} + +function importSource(node: SgNode): string | null { + const source = node.field("source") ?? node.find({ rule: { kind: "string" } }); + return source ? stringValue(source) : null; +} + +function isReactSource(importStmt: SgNode): boolean { + return REACT_SOURCES.has(importSource(importStmt) ?? ""); +} + +function hasReact(rootNode: SgNode): boolean { + for (const importNode of rootNode.findAll({ rule: { kind: "import_statement" } })) { + if (isReactSource(importNode)) return true; + } + + for (const call of rootNode.findAll({ rule: { kind: "call_expression" } })) { + if (REACT_SOURCES.has(requireSource(call) ?? "")) return true; + } + + return false; +} + +function explicitRequireDisabled(value: unknown): boolean { + return value === false || value === "false"; +} + +function reactImportAliases(rootNode: SgNode): ReactImportAliases { + const component = new Set(); + const pureComponent = new Set(); + + for (const importStmt of rootNode.findAll({ rule: { kind: "import_statement" } })) { + if (!isReactSource(importStmt)) continue; + + for (const specifier of importStmt.findAll({ rule: { kind: "import_specifier" } })) { + const imported = specifier.field("name")?.text(); + const alias = specifier.field("alias")?.text() ?? imported; + if (!imported || !alias) continue; + if (imported === "Component") component.add(alias); + if (imported === "PureComponent") pureComponent.add(alias); + } + } + + return { component, pureComponent }; +} + +function reactSuperclassKind( + classDecl: SgNode, + aliases: ReactImportAliases, +): ReactSuperclassKind | null { + const heritage = classDecl.find({ rule: { kind: "class_heritage" } }); + if (!heritage) return null; + + const directExtends = heritage.find({ + rule: { any: [{ kind: "member_expression" }, { kind: "identifier" }] }, + }); + if (!directExtends) return null; + + if (directExtends.kind() === "member_expression") { + const object = directExtends.field("object"); + const property = directExtends.field("property")?.text() ?? ""; + if (object?.text() !== "React") return null; + if (property === "Component" || property === "PureComponent") return property; + return null; + } + + const name = directExtends.text(); + if (aliases.component.has(name)) return "Component"; + if (aliases.pureComponent.has(name)) return "PureComponent"; + return null; +} + +function isReactCreateClassCall(node: SgNode | null): node is SgNode { + if (!node || node.kind() !== "call_expression") return false; + const callee = node.field("function"); + return callee?.kind() === "member_expression" && + callee.field("object")?.kind() === "identifier" && + callee.field("object")?.text() === "React" && + callee.field("property")?.kind() === "property_identifier" && + callee.field("property")?.text() === "createClass"; +} + +function reactCreateClassConfigs(rootNode: SgNode): SgNode[] { + const configs: SgNode[] = []; + for (const declarator of rootNode.findAll({ rule: { kind: "variable_declarator" } })) { + const value = declarator.field("value"); + if (!isReactCreateClassCall(value)) continue; + const args = value.field("arguments"); + const config = args?.children().find((child) => child.kind() === "object"); + if (config) configs.push(config); + } + return configs; +} + +function reactClassBodies(rootNode: SgNode): SgNode[] { + const aliases = reactImportAliases(rootNode); + const reactClasses = rootNode.findAll({ rule: { kind: "class_declaration" } }) + .map((classDecl) => ({ classDecl, kind: reactSuperclassKind(classDecl, aliases) })) + .filter((entry): entry is { classDecl: SgNode; kind: ReactSuperclassKind } => entry.kind !== null); + const targetSuperclassKind = reactClasses.some((entry) => entry.kind === "Component") + ? "Component" + : reactClasses.some((entry) => entry.kind === "PureComponent") + ? "PureComponent" + : null; + + if (!targetSuperclassKind) return []; + return reactClasses + .filter((entry) => entry.kind === targetSuperclassKind) + .map((entry) => entry.classDecl.find({ rule: { kind: "class_body" } })) + .filter((body): body is SgNode => body !== null); +} + + +type MemberChunk = { + node: SgNode; + name: string; + text: string; + start: number; + end: number; + originalIndex: number; +}; + +const DEFAULT_METHODS_ORDER = [ + "static-methods", + "displayName", + "propTypes", + "contextTypes", + "childContextTypes", + "mixins", + "statics", + "defaultProps", + "constructor", + "getDefaultProps", + "state", + "getInitialState", + "getChildContext", + "getDerivedStateFromProps", + "componentWillMount", + "UNSAFE_componentWillMount", + "componentDidMount", + "componentWillReceiveProps", + "UNSAFE_componentWillReceiveProps", + "shouldComponentUpdate", + "componentWillUpdate", + "UNSAFE_componentWillUpdate", + "getSnapshotBeforeUpdate", + "componentDidUpdate", + "componentDidCatch", + "componentWillUnmount", + "/^on.+$/", + "/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/", + "everything-else", + "/^render.+$/", + "render", +]; + +type SortCompRuleConfig = { + order?: string[]; + groups?: Record; +}; + +const ESLINT_CONFIG_FILES = [ + ".eslintrc", + ".eslintrc.json", + ".eslintrc.yaml", + ".eslintrc.yml", + ".eslintrc.js", + ".eslintrc.cjs", + ".eslintrc.mjs", + "package.json", +]; + +function isStringArray(value: unknown): value is string[] { + return Array.isArray(value) && value.every((entry) => typeof entry === "string"); +} + +function stringArrayParam(value: unknown): string[] | null { + if (isStringArray(value)) return value; + if (typeof value !== "string") return null; + try { + const parsed = JSON.parse(value) as unknown; + return isStringArray(parsed) ? parsed : null; + } catch { + return null; + } +} + +function isGroupsMap(value: unknown): value is Record { + return typeof value === "object" && + value !== null && + Object.values(value).every((entry) => isStringArray(entry)); +} + +function normalizeSortCompRuleConfig(value: unknown): SortCompRuleConfig | null { + if (typeof value !== "object" || value === null) return null; + const maybeConfig = value as { order?: unknown; groups?: unknown }; + const config: SortCompRuleConfig = {}; + if (maybeConfig.order !== undefined) { + if (!isStringArray(maybeConfig.order)) return null; + config.order = maybeConfig.order; + } + if (maybeConfig.groups !== undefined) { + if (!isGroupsMap(maybeConfig.groups)) return null; + config.groups = maybeConfig.groups; + } + return config; +} + +async function getMethodsOrderFromEslint(filePath: string): Promise { + let dir = path.dirname(filePath); + while (true) { + for (const configName of ESLINT_CONFIG_FILES) { + const configPath = path.join(dir, configName); + const methodsOrder = await methodsOrderFromConfigFile(configPath, new Set()); + if (methodsOrder) return methodsOrder; + } + const parent = path.dirname(dir); + if (parent === dir) return null; + dir = parent; + } +} + +function resolveMethodsOrder(ruleConfig: SortCompRuleConfig): string[] | null { + if (!ruleConfig.order) return null; + const groups = ruleConfig.groups ?? {}; + const resolvedOrder: string[] = []; + for (const entry of ruleConfig.order) { + const groupEntries = groups[entry]; + if (groupEntries) { + resolvedOrder.push(...groupEntries); + } else { + resolvedOrder.push(entry); + } + } + return resolvedOrder; +} + +function yamlRuleConfig(text: string): SortCompRuleConfig | null { + const lines = text.split("\n"); + const sortCompIndex = lines.findIndex((line) => line.trim() === "react/sort-comp:"); + if (sortCompIndex === -1) return null; + + const ruleConfig: SortCompRuleConfig = {}; + let mode: "order" | "groups" | "group-items" | null = null; + let currentGroup: string | null = null; + + for (let i = sortCompIndex + 1; i < lines.length; i++) { + const line = lines[i]!; + const trimmed = line.trim(); + if (trimmed === "") continue; + const indent = line.match(/^ */)?.[0].length ?? 0; + if (indent <= 2) break; + if (trimmed === "- 0" || trimmed === "- off" || trimmed === "- warn" || trimmed === "- error") continue; + + if (trimmed === "- order:" || trimmed === "order:") { + ruleConfig.order = []; + mode = "order"; + currentGroup = null; + continue; + } + + if (trimmed === "groups:" || trimmed === "- groups:") { + ruleConfig.groups = {}; + mode = "groups"; + currentGroup = null; + continue; + } + + if (mode === "order" && trimmed.startsWith("- ")) { + ruleConfig.order?.push(trimmed.slice(2)); + continue; + } + + if ((mode === "groups" || mode === "group-items") && trimmed.endsWith(":") && !trimmed.startsWith("- ")) { + currentGroup = trimmed.slice(0, -1); + ruleConfig.groups ??= {}; + ruleConfig.groups[currentGroup] = []; + mode = "group-items"; + continue; + } + + if (mode === "group-items" && currentGroup && trimmed.startsWith("- ")) { + ruleConfig.groups?.[currentGroup]?.push(trimmed.slice(2)); + continue; + } + } + + return ruleConfig.order ? ruleConfig : null; +} + +async function loadConfigFile(configPath: string): Promise { + const basename = path.basename(configPath); + if (basename === "package.json") { + try { + const parsed = JSON.parse(fs.readFileSync(configPath, "utf8")) as { eslintConfig?: unknown }; + return parsed.eslintConfig ?? null; + } catch { + return null; + } + } + + if (configPath.endsWith(".js") || configPath.endsWith(".cjs") || configPath.endsWith(".mjs")) { + try { + const imported = await import(pathToFileURL(configPath).href); + return ("default" in imported ? imported.default : imported) as unknown; + } catch { + return null; + } + } + + let text: string; + try { + text = fs.readFileSync(configPath, "utf8"); + } catch { + return null; + } + const trimmed = text.trimStart(); + if (trimmed.startsWith("{")) { + try { + return JSON.parse(text) as unknown; + } catch { + return null; + } + } + + return { rules: { "react/sort-comp": [0, yamlRuleConfig(text)] } }; +} + +function directRuleConfig(config: unknown): SortCompRuleConfig | null { + if (typeof config !== "object" || config === null) return null; + const maybeRules = (config as { rules?: unknown }).rules; + if (typeof maybeRules !== "object" || maybeRules === null) return null; + const sortCompRule = (maybeRules as Record)["react/sort-comp"]; + if (!Array.isArray(sortCompRule) || sortCompRule.length < 2) return null; + return normalizeSortCompRuleConfig(sortCompRule[1]); +} + +function normalizeExtends(value: unknown): string[] { + if (typeof value === "string") return [value]; + return isStringArray(value) ? value : []; +} + +function resolveRelativeExtend(baseDir: string, specifier: string): string | null { + if (!specifier.startsWith(".") && !path.isAbsolute(specifier)) return null; + const candidates = [ + specifier, + `${specifier}.js`, + `${specifier}.cjs`, + `${specifier}.mjs`, + `${specifier}.json`, + `${specifier}.yaml`, + `${specifier}.yml`, + ].map((candidate) => path.resolve(baseDir, candidate)); + for (const candidate of candidates) { + try { + fs.readFileSync(candidate, "utf8"); + return candidate; + } catch { + continue; + } + } + return null; +} + +function tryReadText(filePath: string): string | null { + try { + return fs.readFileSync(filePath, "utf8"); + } catch { + return null; + } +} + +function resolvePackageExtend(baseDir: string, specifier: string): string | null { + if (specifier.startsWith(".") || path.isAbsolute(specifier) || specifier.startsWith("eslint:") || specifier.startsWith("plugin:")) { + return null; + } + + const parts = specifier.split("/"); + const isScoped = specifier.startsWith("@"); + const packagePartLength = isScoped ? 2 : 1; + const packageName = parts.slice(0, packagePartLength).join("/"); + const subpath = parts.slice(packagePartLength).join("/"); + const packageCandidates = (() => { + if (isScoped) { + const [scope, name] = packageName.split("/"); + return name.startsWith("eslint-config-") + ? [packageName] + : [packageName, `${scope}/eslint-config-${name}`]; + } + return packageName.startsWith("eslint-config-") + ? [packageName] + : [packageName, `eslint-config-${packageName}`]; + })(); + + let dir = baseDir; + while (true) { + for (const packageCandidate of packageCandidates) { + const packageRoot = path.join(dir, "node_modules", packageCandidate); + const directCandidate = subpath + ? resolveRelativeExtend(packageRoot, subpath) + : null; + if (directCandidate) return directCandidate; + + const packageJsonPath = path.join(packageRoot, "package.json"); + const packageJsonText = tryReadText(packageJsonPath); + if (packageJsonText) { + try { + const pkg = JSON.parse(packageJsonText) as { main?: string }; + const mainField = typeof pkg.main === "string" ? pkg.main : "index.js"; + const mainCandidate = resolveRelativeExtend(packageRoot, mainField); + if (mainCandidate) return mainCandidate; + } catch { + // ignore malformed package json + } + } + + const indexCandidate = resolveRelativeExtend(packageRoot, "index"); + if (indexCandidate) return indexCandidate; + } + + const parent = path.dirname(dir); + if (parent === dir) return null; + dir = parent; + } +} + +async function methodsOrderFromConfigFile( + configPath: string, + seen: Set, +): Promise { + if (seen.has(configPath)) return null; + seen.add(configPath); + + const config = await loadConfigFile(configPath); + const direct = directRuleConfig(config); + const resolvedDirect = direct ? resolveMethodsOrder(direct) : null; + if (resolvedDirect) return resolvedDirect; + + if (typeof config !== "object" || config === null) return null; + const extendsEntries = normalizeExtends((config as { extends?: unknown }).extends); + for (const entry of extendsEntries) { + const resolvedPath = resolveRelativeExtend(path.dirname(configPath), entry) ?? + resolvePackageExtend(path.dirname(configPath), entry); + if (!resolvedPath) continue; + const resolvedOrder = await methodsOrderFromConfigFile(resolvedPath, seen); + if (resolvedOrder) return resolvedOrder; + } + + return null; +} + +function isTypeAnnotationMember(node: SgNode): boolean { + return node.kind() === "public_field_definition" && + node.field("value") === null && + node.find({ rule: { kind: "type_annotation" } }) !== null; +} + +function selectorMatches(selector: string, name: string, isStatic: boolean): boolean { + if (selector === "static-methods") return isStatic; + if (selector === "type-annotations") return false; + if (selector === name) return true; + if (selector.startsWith("/") && selector.lastIndexOf("/") > 0) { + const lastSlash = selector.lastIndexOf("/"); + const body = selector.slice(1, lastSlash); + const flags = selector.slice(lastSlash + 1); + try { + return new RegExp(body, flags).test(name); + } catch { + return false; + } + } + return false; +} + +function chunkMatchesSelector(selector: string, chunk: MemberChunk): boolean { + if (selector === "type-annotations") return isTypeAnnotationMember(chunk.node); + return selectorMatches(selector, chunk.name, chunk.node.text().startsWith("static ")); +} + +function correctIndex(order: string[], chunk: MemberChunk): number { + const everythingElse = order.indexOf("everything-else"); + for (let i = 0; i < order.length; i++) { + if (i !== everythingElse && chunkMatchesSelector(order[i]!, chunk)) return i; + } + return everythingElse >= 0 ? everythingElse : Number.POSITIVE_INFINITY; +} + +function memberName(node: SgNode): string { + const fieldName = node.field("name")?.text() ?? node.field("key")?.text(); + if (fieldName) return fieldName; + return node.children().find((child) => + child.kind() === "property_identifier" || child.kind() === "identifier" + )?.text() ?? ""; +} + +function significantChildren(container: SgNode): SgNode[] { + return container.children() + .filter((child) => + child.kind() === "comment" || + child.kind() === "pair" || + child.kind() === "method_definition" || + child.kind() === "public_field_definition" + ) + .sort((a, b) => a.range().start.index - b.range().start.index); +} + +function collectChunks(container: SgNode, source: string): MemberChunk[] { + const chunks: MemberChunk[] = []; + let leadingStart: number | null = null; + + for (const child of significantChildren(container)) { + if (child.kind() === "comment") { + if (leadingStart === null) leadingStart = child.range().start.index; + continue; + } + const start = leadingStart ?? child.range().start.index; + const end = child.range().end.index; + chunks.push({ + node: child, + name: memberName(child), + text: source.slice(start, end).trim(), + start, + end, + originalIndex: chunks.length, + }); + leadingStart = null; + } + + return chunks; +} + +function sortChunks(chunks: MemberChunk[], order: string[]): MemberChunk[] { + return [...chunks].sort((a, b) => { + const indexA = correctIndex(order, a); + const indexB = correctIndex(order, b); + if (indexA !== indexB) return indexA - indexB; + const nameCompare = a.name.localeCompare(b.name); + return nameCompare !== 0 ? nameCompare : a.originalIndex - b.originalIndex; + }); +} + +function replacementFor(container: SgNode, chunks: MemberChunk[], order: string[], sep: string): Edit | null { + if (chunks.length <= 1) return null; + const sorted = sortChunks(chunks, order); + const changed = sorted.some((chunk, index) => chunk.originalIndex !== index); + if (!changed) return null; + return { + startPos: chunks[0]!.start, + endPos: chunks[chunks.length - 1]!.end, + insertedText: sorted.map((chunk) => chunk.text).join(sep), + }; +} + +const transform: Transform = async (root, options) => { + const rootNode = root.root(); + if (!explicitRequireDisabled(options.params?.["explicit-require"]) && !hasReact(rootNode)) { + return null; + } + + const source = rootNode.text(); + const edits: Edit[] = []; + const metric = useMetricAtom("sort-comp-reorders"); + const methodsOrder = stringArrayParam(options.params?.methodsOrder) ?? + await getMethodsOrderFromEslint(root.filename()) ?? + DEFAULT_METHODS_ORDER; + + for (const config of reactCreateClassConfigs(rootNode)) { + const edit = replacementFor(config, collectChunks(config, source), methodsOrder, ",\n\n "); + if (edit) { + edits.push(edit); + metric.increment({ file: metricFile(root.filename()), kind: "createClass" }); + } + } + + for (const body of reactClassBodies(rootNode)) { + const edit = replacementFor(body, collectChunks(body, source), methodsOrder, "\n\n "); + if (edit) { + edits.push(edit); + metric.increment({ file: metricFile(root.filename()), kind: "class" }); + } + } + + if (edits.length === 0) return null; + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/legacy/transforms/__testfixtures__/custom-sort-group/.eslintrc b/codemods/sort-comp/tests/custom-sort-group/.eslintrc similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/custom-sort-group/.eslintrc rename to codemods/sort-comp/tests/custom-sort-group/.eslintrc diff --git a/codemods/legacy/transforms/__testfixtures__/custom-sort-group/custom-sort-group.output.js b/codemods/sort-comp/tests/custom-sort-group/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/custom-sort-group/custom-sort-group.output.js rename to codemods/sort-comp/tests/custom-sort-group/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/custom-sort-group/custom-sort-group.input.js b/codemods/sort-comp/tests/custom-sort-group/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/custom-sort-group/custom-sort-group.input.js rename to codemods/sort-comp/tests/custom-sort-group/input.tsx diff --git a/codemods/sort-comp/tests/custom-sort-group/metrics.json b/codemods/sort-comp/tests/custom-sort-group/metrics.json new file mode 100644 index 0000000..ee86a13 --- /dev/null +++ b/codemods/sort-comp/tests/custom-sort-group/metrics.json @@ -0,0 +1,11 @@ +{ + "sort-comp-reorders": [ + { + "cardinality": { + "file": "tests/custom-sort-group/input.tsx", + "kind": "createClass" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/custom-sort/.eslintrc b/codemods/sort-comp/tests/custom-sort/.eslintrc similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/custom-sort/.eslintrc rename to codemods/sort-comp/tests/custom-sort/.eslintrc diff --git a/codemods/legacy/transforms/__testfixtures__/custom-sort/custom-sort.output.js b/codemods/sort-comp/tests/custom-sort/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/custom-sort/custom-sort.output.js rename to codemods/sort-comp/tests/custom-sort/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/custom-sort/custom-sort.input.js b/codemods/sort-comp/tests/custom-sort/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/custom-sort/custom-sort.input.js rename to codemods/sort-comp/tests/custom-sort/input.tsx diff --git a/codemods/sort-comp/tests/custom-sort/metrics.json b/codemods/sort-comp/tests/custom-sort/metrics.json new file mode 100644 index 0000000..96517a4 --- /dev/null +++ b/codemods/sort-comp/tests/custom-sort/metrics.json @@ -0,0 +1,11 @@ +{ + "sort-comp-reorders": [ + { + "cardinality": { + "file": "tests/custom-sort/input.tsx", + "kind": "createClass" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/sort-comp/tests/identifier-create-class-no-change/expected.tsx b/codemods/sort-comp/tests/identifier-create-class-no-change/expected.tsx new file mode 100644 index 0000000..7184b23 --- /dev/null +++ b/codemods/sort-comp/tests/identifier-create-class-no-change/expected.tsx @@ -0,0 +1,9 @@ +var createClass = require('create-react-class'); + +var C = createClass({ + render() { + return
; + }, + + a() {}, +}); diff --git a/codemods/sort-comp/tests/identifier-create-class-no-change/input.tsx b/codemods/sort-comp/tests/identifier-create-class-no-change/input.tsx new file mode 100644 index 0000000..7184b23 --- /dev/null +++ b/codemods/sort-comp/tests/identifier-create-class-no-change/input.tsx @@ -0,0 +1,9 @@ +var createClass = require('create-react-class'); + +var C = createClass({ + render() { + return
; + }, + + a() {}, +}); diff --git a/codemods/sort-comp/tests/methods-order-option/expected.tsx b/codemods/sort-comp/tests/methods-order-option/expected.tsx new file mode 100644 index 0000000..658d96c --- /dev/null +++ b/codemods/sort-comp/tests/methods-order-option/expected.tsx @@ -0,0 +1,11 @@ +var React = require('react/addons'); + +var C = React.createClass({ + componentDidMount() {}, + + z() {}, + + render() { + return
; + }, +}); diff --git a/codemods/sort-comp/tests/methods-order-option/input.tsx b/codemods/sort-comp/tests/methods-order-option/input.tsx new file mode 100644 index 0000000..81a901c --- /dev/null +++ b/codemods/sort-comp/tests/methods-order-option/input.tsx @@ -0,0 +1,11 @@ +var React = require('react/addons'); + +var C = React.createClass({ + render() { + return
; + }, + + z() {}, + + componentDidMount() {}, +}); diff --git a/codemods/sort-comp/tests/methods-order-option/metrics.json b/codemods/sort-comp/tests/methods-order-option/metrics.json new file mode 100644 index 0000000..1723811 --- /dev/null +++ b/codemods/sort-comp/tests/methods-order-option/metrics.json @@ -0,0 +1,11 @@ +{ + "sort-comp-reorders": [ + { + "cardinality": { + "file": "tests/methods-order-option/input.tsx", + "kind": "createClass" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/sort-comp/tests/methods-order-option/test.config.json b/codemods/sort-comp/tests/methods-order-option/test.config.json new file mode 100644 index 0000000..f70c53c --- /dev/null +++ b/codemods/sort-comp/tests/methods-order-option/test.config.json @@ -0,0 +1,5 @@ +{ + "params": { + "methodsOrder": ["componentDidMount", "everything-else", "render"] + } +} diff --git a/codemods/sort-comp/tests/no-react-explicit-require/expected.tsx b/codemods/sort-comp/tests/no-react-explicit-require/expected.tsx new file mode 100644 index 0000000..152ec11 --- /dev/null +++ b/codemods/sort-comp/tests/no-react-explicit-require/expected.tsx @@ -0,0 +1,7 @@ +var C = React.createClass({ + a() {}, + + render() { + return
; + }, +}); diff --git a/codemods/sort-comp/tests/no-react-explicit-require/input.tsx b/codemods/sort-comp/tests/no-react-explicit-require/input.tsx new file mode 100644 index 0000000..d86a4e7 --- /dev/null +++ b/codemods/sort-comp/tests/no-react-explicit-require/input.tsx @@ -0,0 +1,7 @@ +var C = React.createClass({ + render() { + return
; + }, + + a() {}, +}); diff --git a/codemods/sort-comp/tests/no-react-explicit-require/metrics.json b/codemods/sort-comp/tests/no-react-explicit-require/metrics.json new file mode 100644 index 0000000..a24fb9d --- /dev/null +++ b/codemods/sort-comp/tests/no-react-explicit-require/metrics.json @@ -0,0 +1,11 @@ +{ + "sort-comp-reorders": [ + { + "cardinality": { + "file": "tests/no-react-explicit-require/input.tsx", + "kind": "createClass" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/sort-comp/tests/no-react-explicit-require/test.config.json b/codemods/sort-comp/tests/no-react-explicit-require/test.config.json new file mode 100644 index 0000000..4858435 --- /dev/null +++ b/codemods/sort-comp/tests/no-react-explicit-require/test.config.json @@ -0,0 +1,5 @@ +{ + "params": { + "explicit-require": false + } +} diff --git a/codemods/sort-comp/tests/no-react-no-change/expected.tsx b/codemods/sort-comp/tests/no-react-no-change/expected.tsx new file mode 100644 index 0000000..d86a4e7 --- /dev/null +++ b/codemods/sort-comp/tests/no-react-no-change/expected.tsx @@ -0,0 +1,7 @@ +var C = React.createClass({ + render() { + return
; + }, + + a() {}, +}); diff --git a/codemods/sort-comp/tests/no-react-no-change/input.tsx b/codemods/sort-comp/tests/no-react-no-change/input.tsx new file mode 100644 index 0000000..d86a4e7 --- /dev/null +++ b/codemods/sort-comp/tests/no-react-no-change/input.tsx @@ -0,0 +1,7 @@ +var C = React.createClass({ + render() { + return
; + }, + + a() {}, +}); diff --git a/codemods/legacy/transforms/__testfixtures__/sort-comp-pure.output.js b/codemods/sort-comp/tests/sort-comp-pure/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/sort-comp-pure.output.js rename to codemods/sort-comp/tests/sort-comp-pure/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/sort-comp-pure.input.js b/codemods/sort-comp/tests/sort-comp-pure/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/sort-comp-pure.input.js rename to codemods/sort-comp/tests/sort-comp-pure/input.tsx diff --git a/codemods/sort-comp/tests/sort-comp-pure/metrics.json b/codemods/sort-comp/tests/sort-comp-pure/metrics.json new file mode 100644 index 0000000..b40335c --- /dev/null +++ b/codemods/sort-comp/tests/sort-comp-pure/metrics.json @@ -0,0 +1,11 @@ +{ + "sort-comp-reorders": [ + { + "cardinality": { + "file": "tests/sort-comp-pure/input.tsx", + "kind": "class" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/sort-comp.output.js b/codemods/sort-comp/tests/sort-comp/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/sort-comp.output.js rename to codemods/sort-comp/tests/sort-comp/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/sort-comp.input.js b/codemods/sort-comp/tests/sort-comp/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/sort-comp.input.js rename to codemods/sort-comp/tests/sort-comp/input.tsx diff --git a/codemods/sort-comp/tests/sort-comp/metrics.json b/codemods/sort-comp/tests/sort-comp/metrics.json new file mode 100644 index 0000000..9f8d4e0 --- /dev/null +++ b/codemods/sort-comp/tests/sort-comp/metrics.json @@ -0,0 +1,11 @@ +{ + "sort-comp-reorders": [ + { + "cardinality": { + "file": "tests/sort-comp/input.tsx", + "kind": "createClass" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/sort-comp2.output.js b/codemods/sort-comp/tests/sort-comp2/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/sort-comp2.output.js rename to codemods/sort-comp/tests/sort-comp2/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/sort-comp2.input.js b/codemods/sort-comp/tests/sort-comp2/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/sort-comp2.input.js rename to codemods/sort-comp/tests/sort-comp2/input.tsx diff --git a/codemods/sort-comp/tests/sort-comp2/metrics.json b/codemods/sort-comp/tests/sort-comp2/metrics.json new file mode 100644 index 0000000..2d17b75 --- /dev/null +++ b/codemods/sort-comp/tests/sort-comp2/metrics.json @@ -0,0 +1,11 @@ +{ + "sort-comp-reorders": [ + { + "cardinality": { + "file": "tests/sort-comp2/input.tsx", + "kind": "class" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/sort-comp3.output.js b/codemods/sort-comp/tests/sort-comp3/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/sort-comp3.output.js rename to codemods/sort-comp/tests/sort-comp3/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/sort-comp3.input.js b/codemods/sort-comp/tests/sort-comp3/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/sort-comp3.input.js rename to codemods/sort-comp/tests/sort-comp3/input.tsx diff --git a/codemods/sort-comp/tests/sort-comp3/metrics.json b/codemods/sort-comp/tests/sort-comp3/metrics.json new file mode 100644 index 0000000..ea921ea --- /dev/null +++ b/codemods/sort-comp/tests/sort-comp3/metrics.json @@ -0,0 +1,11 @@ +{ + "sort-comp-reorders": [ + { + "cardinality": { + "file": "tests/sort-comp3/input.tsx", + "kind": "class" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/sort-comp/tests/unrelated-component-no-change/expected.tsx b/codemods/sort-comp/tests/unrelated-component-no-change/expected.tsx new file mode 100644 index 0000000..97a64fb --- /dev/null +++ b/codemods/sort-comp/tests/unrelated-component-no-change/expected.tsx @@ -0,0 +1,9 @@ +import { Component } from 'other-lib'; + +class C extends Component { + render() { + return
; + } + + a() {} +} diff --git a/codemods/sort-comp/tests/unrelated-component-no-change/input.tsx b/codemods/sort-comp/tests/unrelated-component-no-change/input.tsx new file mode 100644 index 0000000..97a64fb --- /dev/null +++ b/codemods/sort-comp/tests/unrelated-component-no-change/input.tsx @@ -0,0 +1,9 @@ +import { Component } from 'other-lib'; + +class C extends Component { + render() { + return
; + } + + a() {} +} diff --git a/codemods/sort-comp/tsconfig.json b/codemods/sort-comp/tsconfig.json new file mode 100644 index 0000000..ed30c5e --- /dev/null +++ b/codemods/sort-comp/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": [ + "ESNext" + ], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": [ + "@codemod.com/jssg-types", + "node" + ], + "allowImportingTsExtensions": true, + "noEmit": true, + "skipLibCheck": true, + "verbatimModuleSyntax": false, + "erasableSyntaxOnly": true, + "strict": true, + "strictNullChecks": true + }, + "exclude": [ + "tests" + ] +} diff --git a/codemods/sort-comp/workflow.yaml b/codemods/sort-comp/workflow.yaml new file mode 100644 index 0000000..a80a53d --- /dev/null +++ b/codemods/sort-comp/workflow.yaml @@ -0,0 +1,22 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +params: + schema: + methodsOrder: + name: "Methods Order" + description: "Ordered list of method groups and member names used for sorting." + type: array + items: + type: string + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: "Scan tsx files and apply fixes" + js-ast-grep: + js_file: scripts/codemod.ts + language: "tsx" diff --git a/codemods/update-react-imports/.gitignore b/codemods/update-react-imports/.gitignore new file mode 100644 index 0000000..78174f4 --- /dev/null +++ b/codemods/update-react-imports/.gitignore @@ -0,0 +1,33 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build artifacts +target/ +dist/ +build/ + +# Temporary files +*.tmp +*.temp +.cache/ + +# Environment files +.env +.env.local + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db + +# Package bundles +*.tar.gz +*.tgz \ No newline at end of file diff --git a/codemods/update-react-imports/README.md b/codemods/update-react-imports/README.md new file mode 100644 index 0000000..69d10fb --- /dev/null +++ b/codemods/update-react-imports/README.md @@ -0,0 +1,13 @@ +# update-react-imports + +Remove unnecessary React imports, convert member access to named imports when safe, and keep namespace imports when `React` is used as a value or type namespace. + +## Usage + +```bash +npx codemod @react-new/update-react-imports --target +``` + +## Options + +- `destructureNamespaceImports`: allow namespace imports such as `import * as React` to be destructured into named imports when it is safe to do so. diff --git a/codemods/update-react-imports/codemod.yaml b/codemods/update-react-imports/codemod.yaml new file mode 100644 index 0000000..78e5ccb --- /dev/null +++ b/codemods/update-react-imports/codemod.yaml @@ -0,0 +1,20 @@ +schema_version: "1.0" + +name: "@react-new/update-react-imports" +version: "0.1.1" +description: "Remove unused React default import and convert to named imports" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + + +targets: + languages: ["tsx"] + +keywords: ["React"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/update-react-imports/package.json b/codemods/update-react-imports/package.json new file mode 100644 index 0000000..5df66b0 --- /dev/null +++ b/codemods/update-react-imports/package.json @@ -0,0 +1,18 @@ +{ + "name": "@react-new/update-react-imports", + "version": "0.1.1", + "description": "Remove unused React default import and convert to named imports", + "type": "module", + "scripts": { + "test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@codemod.com/jssg-types": "latest", + "typescript": "latest", + "@types/node": "latest" + }, + "dependencies": { + "@jssg/utils": "^0.0.2" + } +} diff --git a/codemods/jssg/use-context-hook/pnpm-lock.yaml b/codemods/update-react-imports/pnpm-lock.yaml similarity index 100% rename from codemods/jssg/use-context-hook/pnpm-lock.yaml rename to codemods/update-react-imports/pnpm-lock.yaml diff --git a/codemods/update-react-imports/scripts/codemod.ts b/codemods/update-react-imports/scripts/codemod.ts new file mode 100644 index 0000000..d1e58da --- /dev/null +++ b/codemods/update-react-imports/scripts/codemod.ts @@ -0,0 +1,250 @@ +import type { Transform, Edit, SgNode } from "codemod:ast-grep"; +import type TSX from "codemod:ast-grep/langs/tsx"; +import { useMetricAtom } from "codemod:metrics"; + +function metricFile(filename: string): string { + const cwd = process.cwd() + "/"; + return filename.startsWith(cwd) ? filename.slice(cwd.length) : filename; +} + +type Specifier = { name: string; alias?: string; isType: boolean }; + +function isWhitespace(char: string | undefined): boolean { + return char === " " || char === "\t" || char === "\n" || char === "\r"; +} + +function sourceText(node: SgNode): string | null { + const fragment = node.find({ rule: { kind: "string_fragment" } }); + if (fragment) return fragment.text(); + const text = node.text(); + if (text.length >= 2) return text.slice(1, -1); + return null; +} + +function importSource(node: SgNode): string | null { + const source = node.field("source") ?? node.find({ rule: { kind: "string" } }); + return source ? sourceText(source) : null; +} + +function isReactImport(node: SgNode): boolean { + const source = importSource(node); + return source === "react" || source === "React"; +} + +function statementRange(node: SgNode, source: string): { start: number; end: number } { + const range = node.range(); + let end = range.end.index; + while (end < source.length && (source[end] === "\n" || source[end] === "\r")) end++; + return { start: range.start.index, end }; +} + +function formatSpec(spec: Specifier): string { + const name = spec.alias && spec.alias !== spec.name ? `${spec.name} as ${spec.alias}` : spec.name; + return spec.isType ? `type ${name}` : name; +} + +function uniqueSpecs(specs: Specifier[]): Specifier[] { + const seen = new Set(); + const result: Specifier[] = []; + for (const spec of specs) { + const key = `${spec.isType}:${spec.name}:${spec.alias ?? ""}`; + if (!seen.has(key)) { + seen.add(key); + result.push(spec); + } + } + return result; +} + +function splitSpecs(specs: Specifier[]): { regular: Specifier[]; type: Specifier[] } { + return { + regular: uniqueSpecs(specs.filter((s) => !s.isType)), + type: uniqueSpecs(specs.filter((s) => s.isType).map((s) => ({ ...s, isType: false }))), + }; +} + +function importText(specs: Specifier[], quote: string, typeOnly = false): string | null { + if (specs.length === 0) return null; + const body = specs.map(formatSpec).join(", "); + return typeOnly + ? `import type { ${body} } from ${quote}react${quote};\n` + : `import { ${body} } from ${quote}react${quote};\n`; +} + +function namespaceImportText(quote: string): string { + return `import * as React from ${quote}react${quote};\n`; +} + +function quoteFor(node: SgNode): string { + const source = node.field("source") ?? node.find({ rule: { kind: "string" } }); + const text = source?.text() ?? "\"react\""; + return text.startsWith("'") ? "'" : "\""; +} + +function getReactSpecifier(importNode: SgNode): "default" | "namespace" | null { + const clause = importNode.find({ rule: { kind: "import_clause" } }); + if (!clause) return null; + const namespace = clause.find({ rule: { kind: "namespace_import" } }); + if (namespace?.find({ rule: { kind: "identifier", regex: "^React$" } })) return "namespace"; + const firstIdentifier = clause.children().find((c) => c.kind() === "identifier"); + return firstIdentifier?.text() === "React" ? "default" : null; +} + +function namedSpecifiers(importNode: SgNode): Specifier[] { + return importNode.findAll({ rule: { kind: "import_specifier" } }) + .map((spec) => ({ + name: spec.field("name")?.text() ?? spec.text(), + alias: spec.field("alias")?.text() ?? undefined, + isType: spec.text().startsWith("type ") || importNode.text().startsWith("import type"), + })); +} + +function identifierIsInImport(node: SgNode): boolean { + return node.ancestors().some((a) => a.kind() === "import_statement"); +} + +function parentUsesIdentifierAsObject(node: SgNode): SgNode | null { + const parent = node.parent(); + if (!parent) return null; + if ( + parent.kind() === "member_expression" && + parent.field("object")?.id() === node.id() && + parent.field("property") + ) { + return parent; + } + return null; +} + +function isDeclared(rootNode: SgNode, name: string): boolean { + return rootNode.findAll({ rule: { kind: "identifier", regex: `^${name}$` } }) + .some((node) => { + if (identifierIsInImport(node)) return false; + const parent = node.parent(); + if (!parent) return true; + if (parent.kind() === "member_expression" && parent.field("object")?.id() === node.id()) { + return false; + } + if (parent.kind() === "nested_type_identifier" && parent.field("object")?.id() === node.id()) { + return false; + } + return true; + }); +} + +function removeReactImportEdits(imports: SgNode[], source: string): Edit[] { + return imports.map((imp) => { + const { start, end } = statementRange(imp, source); + return { startPos: start, endPos: end, insertedText: "" }; + }); +} + +const transform: Transform = async (root, options) => { + const rootNode = root.root(); + const source = rootNode.text(); + const metric = useMetricAtom("react-import-updates"); + const edits: Edit[] = []; + + const reactImports = rootNode.findAll({ rule: { kind: "import_statement" } }).filter(isReactImport); + const valueReactImports = reactImports.filter((imp) => + !imp.text().startsWith("import type") && getReactSpecifier(imp) !== null + + ); + if (valueReactImports.length === 0) return null; + + const primaryReactImport = valueReactImports[0]!; + const reactSpecifierKind = getReactSpecifier(primaryReactImport)!; + const quote = quoteFor(primaryReactImport); + const allNamedFromReactImports = reactImports.flatMap(namedSpecifiers); + const allExistingNamed = valueReactImports.flatMap(namedSpecifiers); + const reactIdentifiers = rootNode.findAll({ rule: { kind: "identifier", regex: "^React$" } }) + .filter((node) => !identifierIsInImport(node)); + + const memberUsages: SgNode[] = []; + let hasDirectReactUse = false; + let hasTypeNamespaceUse = false; + + for (const identifier of reactIdentifiers) { + const member = parentUsesIdentifierAsObject(identifier); + if (member) { + memberUsages.push(member); + continue; + } + const parent = identifier.parent(); + if (parent?.kind() === "nested_type_identifier" && parent.field("object")?.id() === identifier.id()) { + hasTypeNamespaceUse = true; + continue; + } + hasDirectReactUse = true; + } + + const isReactUsed = reactIdentifiers.length > 0 || hasTypeNamespaceUse; + const canDestructure = isReactUsed && !hasDirectReactUse && !hasTypeNamespaceUse; + const regularSpecs: Specifier[] = []; + const typeSpecs: Specifier[] = []; + const memberReplacementEdits: Edit[] = []; + const existingRegularNames = new Set( + allNamedFromReactImports + .filter((spec) => !spec.isType) + .flatMap((spec) => [spec.name, spec.alias].filter(Boolean) as string[]), + ); + + if (canDestructure) { + for (const member of memberUsages) { + const property = member.field("property"); + if (!property) continue; + const name = property.text(); + if (existingRegularNames.has(name) || isDeclared(rootNode, name)) { + hasDirectReactUse = true; + break; + } + regularSpecs.push({ name, isType: false }); + memberReplacementEdits.push(member.replace(name)); + } + } + + const shouldDestructure = + (reactSpecifierKind === "default" || + options.params?.destructureNamespaceImports === "true") && + canDestructure && + !hasDirectReactUse; + const splitExisting = splitSpecs(allExistingNamed); + regularSpecs.push(...splitExisting.regular); + typeSpecs.push(...splitExisting.type); + + let replacementImports = ""; + if (shouldDestructure) { + edits.push(...memberReplacementEdits); + replacementImports += importText(uniqueSpecs(typeSpecs), quote, true) ?? ""; + replacementImports += importText(uniqueSpecs(regularSpecs), quote) ?? ""; + } else { + const split = splitSpecs(allExistingNamed); + replacementImports += importText(split.type, quote, true) ?? ""; + replacementImports += importText(split.regular, quote) ?? ""; + if (isReactUsed) { + replacementImports += namespaceImportText(quote); + } + } + + const firstImport = valueReactImports[0]!; + edits.push({ + startPos: firstImport.range().start.index, + endPos: firstImport.range().start.index, + insertedText: replacementImports, + }); + edits.push(...removeReactImportEdits(valueReactImports, source)); + + metric.increment({ + action: shouldDestructure + ? "convert-member-to-named" + : isReactUsed + ? "convert-to-namespace" + : "remove-react-import", + file: metricFile(root.filename()), + previous: reactSpecifierKind, + }); + + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import-react-variable.tsx.output.js b/codemods/update-react-imports/tests/default-and-multiple-specifiers-import-react-variable/expected.tsx similarity index 99% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import-react-variable.tsx.output.js rename to codemods/update-react-imports/tests/default-and-multiple-specifiers-import-react-variable/expected.tsx index 41bcef4..0302b77 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import-react-variable.tsx.output.js +++ b/codemods/update-react-imports/tests/default-and-multiple-specifiers-import-react-variable/expected.tsx @@ -1,6 +1,5 @@ import { createElement, useState } from "react"; import * as React from "react"; - React.createElement('div', {});
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import-react-variable.input.js b/codemods/update-react-imports/tests/default-and-multiple-specifiers-import-react-variable/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import-react-variable.input.js rename to codemods/update-react-imports/tests/default-and-multiple-specifiers-import-react-variable/input.tsx diff --git a/codemods/update-react-imports/tests/default-and-multiple-specifiers-import-react-variable/metrics.json b/codemods/update-react-imports/tests/default-and-multiple-specifiers-import-react-variable/metrics.json new file mode 100644 index 0000000..21cc443 --- /dev/null +++ b/codemods/update-react-imports/tests/default-and-multiple-specifiers-import-react-variable/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-to-namespace", + "file": "tests/default-and-multiple-specifiers-import-react-variable/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import.output.js b/codemods/update-react-imports/tests/default-and-multiple-specifiers-import/expected.tsx similarity index 99% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import.output.js rename to codemods/update-react-imports/tests/default-and-multiple-specifiers-import/expected.tsx index ded95b7..83cca58 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import.output.js +++ b/codemods/update-react-imports/tests/default-and-multiple-specifiers-import/expected.tsx @@ -1,4 +1,3 @@ import type { Element } from "react"; import { createElement, useState } from "react"; -
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import.input.js b/codemods/update-react-imports/tests/default-and-multiple-specifiers-import/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import.input.js rename to codemods/update-react-imports/tests/default-and-multiple-specifiers-import/input.tsx diff --git a/codemods/update-react-imports/tests/default-and-multiple-specifiers-import/metrics.json b/codemods/update-react-imports/tests/default-and-multiple-specifiers-import/metrics.json new file mode 100644 index 0000000..bd4da64 --- /dev/null +++ b/codemods/update-react-imports/tests/default-and-multiple-specifiers-import/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "remove-react-import", + "file": "tests/default-and-multiple-specifiers-import/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-not-removed.output.js b/codemods/update-react-imports/tests/destructure-named-imports-react-not-removed/expected.tsx similarity index 99% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/react-not-removed.output.js rename to codemods/update-react-imports/tests/destructure-named-imports-react-not-removed/expected.tsx index 8dc1bf7..9f5bcfc 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-not-removed.output.js +++ b/codemods/update-react-imports/tests/destructure-named-imports-react-not-removed/expected.tsx @@ -1,5 +1,4 @@ import * as React from "react"; - React.createElement('div', {}); Promise.resolve(React); diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports-react-not-removed.input.js b/codemods/update-react-imports/tests/destructure-named-imports-react-not-removed/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports-react-not-removed.input.js rename to codemods/update-react-imports/tests/destructure-named-imports-react-not-removed/input.tsx diff --git a/codemods/update-react-imports/tests/destructure-named-imports-react-not-removed/metrics.json b/codemods/update-react-imports/tests/destructure-named-imports-react-not-removed/metrics.json new file mode 100644 index 0000000..69f37f7 --- /dev/null +++ b/codemods/update-react-imports/tests/destructure-named-imports-react-not-removed/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-to-namespace", + "file": "tests/destructure-named-imports-react-not-removed/input.tsx", + "previous": "namespace" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/update-react-imports/tests/destructure-named-imports-react-not-removed/test.config.json b/codemods/update-react-imports/tests/destructure-named-imports-react-not-removed/test.config.json new file mode 100644 index 0000000..319e5b9 --- /dev/null +++ b/codemods/update-react-imports/tests/destructure-named-imports-react-not-removed/test.config.json @@ -0,0 +1,5 @@ +{ + "params": { + "destructureNamespaceImports": true + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports-variable-used.input.js b/codemods/update-react-imports/tests/destructure-named-imports-variable-used/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports-variable-used.input.js rename to codemods/update-react-imports/tests/destructure-named-imports-variable-used/expected.tsx diff --git a/codemods/update-react-imports/tests/destructure-named-imports-variable-used/input.tsx b/codemods/update-react-imports/tests/destructure-named-imports-variable-used/input.tsx new file mode 100644 index 0000000..ebb2ad9 --- /dev/null +++ b/codemods/update-react-imports/tests/destructure-named-imports-variable-used/input.tsx @@ -0,0 +1,6 @@ +import * as React from "react"; +import { createElement } from "react"; + +React.createElement('div', {}); + +
Hi
; diff --git a/codemods/update-react-imports/tests/destructure-named-imports-variable-used/metrics.json b/codemods/update-react-imports/tests/destructure-named-imports-variable-used/metrics.json new file mode 100644 index 0000000..cc9aca4 --- /dev/null +++ b/codemods/update-react-imports/tests/destructure-named-imports-variable-used/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-to-namespace", + "file": "tests/destructure-named-imports-variable-used/input.tsx", + "previous": "namespace" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/update-react-imports/tests/destructure-named-imports-variable-used/test.config.json b/codemods/update-react-imports/tests/destructure-named-imports-variable-used/test.config.json new file mode 100644 index 0000000..319e5b9 --- /dev/null +++ b/codemods/update-react-imports/tests/destructure-named-imports-variable-used/test.config.json @@ -0,0 +1,5 @@ +{ + "params": { + "destructureNamespaceImports": true + } +} diff --git a/codemods/update-react-imports/tests/destructure-named-imports/expected.tsx b/codemods/update-react-imports/tests/destructure-named-imports/expected.tsx new file mode 100644 index 0000000..6eea2db --- /dev/null +++ b/codemods/update-react-imports/tests/destructure-named-imports/expected.tsx @@ -0,0 +1,4 @@ +import * as React from "react"; +React.useState(false); + +
\ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports.input.js b/codemods/update-react-imports/tests/destructure-named-imports/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports.input.js rename to codemods/update-react-imports/tests/destructure-named-imports/input.tsx diff --git a/codemods/update-react-imports/tests/destructure-named-imports/metrics.json b/codemods/update-react-imports/tests/destructure-named-imports/metrics.json new file mode 100644 index 0000000..8ed6fac --- /dev/null +++ b/codemods/update-react-imports/tests/destructure-named-imports/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-to-namespace", + "file": "tests/destructure-named-imports/input.tsx", + "previous": "namespace" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/update-react-imports/tests/destructure-named-imports/test.config.json b/codemods/update-react-imports/tests/destructure-named-imports/test.config.json new file mode 100644 index 0000000..319e5b9 --- /dev/null +++ b/codemods/update-react-imports/tests/destructure-named-imports/test.config.json @@ -0,0 +1,5 @@ +{ + "params": { + "destructureNamespaceImports": true + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import-react-variable.output.js b/codemods/update-react-imports/tests/flow-default-and-type-specifier-import-react-variable/expected.tsx similarity index 99% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import-react-variable.output.js rename to codemods/update-react-imports/tests/flow-default-and-type-specifier-import-react-variable/expected.tsx index fd12c75..827ccc9 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import-react-variable.output.js +++ b/codemods/update-react-imports/tests/flow-default-and-type-specifier-import-react-variable/expected.tsx @@ -1,6 +1,5 @@ import type { Element, Component } from "react"; import { createElement } from "react"; - createElement('div', {});
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import-react-variable.input.js b/codemods/update-react-imports/tests/flow-default-and-type-specifier-import-react-variable/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import-react-variable.input.js rename to codemods/update-react-imports/tests/flow-default-and-type-specifier-import-react-variable/input.tsx diff --git a/codemods/update-react-imports/tests/flow-default-and-type-specifier-import-react-variable/metrics.json b/codemods/update-react-imports/tests/flow-default-and-type-specifier-import-react-variable/metrics.json new file mode 100644 index 0000000..7270eab --- /dev/null +++ b/codemods/update-react-imports/tests/flow-default-and-type-specifier-import-react-variable/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-member-to-named", + "file": "tests/flow-default-and-type-specifier-import-react-variable/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import.output.js b/codemods/update-react-imports/tests/flow-default-and-type-specifier-import/expected.tsx similarity index 98% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import.output.js rename to codemods/update-react-imports/tests/flow-default-and-type-specifier-import/expected.tsx index 1a4260e..5d45ddc 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import.output.js +++ b/codemods/update-react-imports/tests/flow-default-and-type-specifier-import/expected.tsx @@ -1,3 +1,2 @@ import type { Element } from "react"; -
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import.input.js b/codemods/update-react-imports/tests/flow-default-and-type-specifier-import/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import.input.js rename to codemods/update-react-imports/tests/flow-default-and-type-specifier-import/input.tsx diff --git a/codemods/update-react-imports/tests/flow-default-and-type-specifier-import/metrics.json b/codemods/update-react-imports/tests/flow-default-and-type-specifier-import/metrics.json new file mode 100644 index 0000000..5605f55 --- /dev/null +++ b/codemods/update-react-imports/tests/flow-default-and-type-specifier-import/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "remove-react-import", + "file": "tests/flow-default-and-type-specifier-import/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-element.output.js b/codemods/update-react-imports/tests/jsx-element/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-element.output.js rename to codemods/update-react-imports/tests/jsx-element/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-element.input.js b/codemods/update-react-imports/tests/jsx-element/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-element.input.js rename to codemods/update-react-imports/tests/jsx-element/input.tsx diff --git a/codemods/update-react-imports/tests/jsx-element/metrics.json b/codemods/update-react-imports/tests/jsx-element/metrics.json new file mode 100644 index 0000000..23bca4b --- /dev/null +++ b/codemods/update-react-imports/tests/jsx-element/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "remove-react-import", + "file": "tests/jsx-element/input.tsx", + "previous": "namespace" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-fragment.output.js b/codemods/update-react-imports/tests/jsx-fragment/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-fragment.output.js rename to codemods/update-react-imports/tests/jsx-fragment/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-fragment.input.js b/codemods/update-react-imports/tests/jsx-fragment/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-fragment.input.js rename to codemods/update-react-imports/tests/jsx-fragment/input.tsx diff --git a/codemods/update-react-imports/tests/jsx-fragment/metrics.json b/codemods/update-react-imports/tests/jsx-fragment/metrics.json new file mode 100644 index 0000000..3ea49e2 --- /dev/null +++ b/codemods/update-react-imports/tests/jsx-fragment/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "remove-react-import", + "file": "tests/jsx-fragment/input.tsx", + "previous": "namespace" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/leading-comment.output.js b/codemods/update-react-imports/tests/leading-comment/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/leading-comment.output.js rename to codemods/update-react-imports/tests/leading-comment/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/leading-comment.input.js b/codemods/update-react-imports/tests/leading-comment/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/leading-comment.input.js rename to codemods/update-react-imports/tests/leading-comment/input.tsx diff --git a/codemods/update-react-imports/tests/leading-comment/metrics.json b/codemods/update-react-imports/tests/leading-comment/metrics.json new file mode 100644 index 0000000..f30d41f --- /dev/null +++ b/codemods/update-react-imports/tests/leading-comment/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "remove-react-import", + "file": "tests/leading-comment/input.tsx", + "previous": "namespace" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/update-react-imports/tests/react-already-used-named-export/expected.tsx b/codemods/update-react-imports/tests/react-already-used-named-export/expected.tsx new file mode 100644 index 0000000..491a83a --- /dev/null +++ b/codemods/update-react-imports/tests/react-already-used-named-export/expected.tsx @@ -0,0 +1,2 @@ +import * as React from 'react'; +React.useState(false); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-already-used-named-export.input.js b/codemods/update-react-imports/tests/react-already-used-named-export/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/react-already-used-named-export.input.js rename to codemods/update-react-imports/tests/react-already-used-named-export/input.tsx diff --git a/codemods/update-react-imports/tests/react-already-used-named-export/metrics.json b/codemods/update-react-imports/tests/react-already-used-named-export/metrics.json new file mode 100644 index 0000000..cd15468 --- /dev/null +++ b/codemods/update-react-imports/tests/react-already-used-named-export/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-to-namespace", + "file": "tests/react-already-used-named-export/input.tsx", + "previous": "namespace" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element-react-variable.tsx.output.js b/codemods/update-react-imports/tests/react-basic-default-export-jsx-element-react-variable/expected.tsx similarity index 98% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element-react-variable.tsx.output.js rename to codemods/update-react-imports/tests/react-basic-default-export-jsx-element-react-variable/expected.tsx index 3995e26..4a3728b 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element-react-variable.tsx.output.js +++ b/codemods/update-react-imports/tests/react-basic-default-export-jsx-element-react-variable/expected.tsx @@ -1,5 +1,4 @@ import { createElement } from "react"; - createElement('div', {});
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element-react-variable.input.js b/codemods/update-react-imports/tests/react-basic-default-export-jsx-element-react-variable/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element-react-variable.input.js rename to codemods/update-react-imports/tests/react-basic-default-export-jsx-element-react-variable/input.tsx diff --git a/codemods/update-react-imports/tests/react-basic-default-export-jsx-element-react-variable/metrics.json b/codemods/update-react-imports/tests/react-basic-default-export-jsx-element-react-variable/metrics.json new file mode 100644 index 0000000..e7e8ea0 --- /dev/null +++ b/codemods/update-react-imports/tests/react-basic-default-export-jsx-element-react-variable/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-member-to-named", + "file": "tests/react-basic-default-export-jsx-element-react-variable/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element.output.js b/codemods/update-react-imports/tests/react-basic-default-export-jsx-element/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element.output.js rename to codemods/update-react-imports/tests/react-basic-default-export-jsx-element/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element.input.js b/codemods/update-react-imports/tests/react-basic-default-export-jsx-element/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element.input.js rename to codemods/update-react-imports/tests/react-basic-default-export-jsx-element/input.tsx diff --git a/codemods/update-react-imports/tests/react-basic-default-export-jsx-element/metrics.json b/codemods/update-react-imports/tests/react-basic-default-export-jsx-element/metrics.json new file mode 100644 index 0000000..7754f61 --- /dev/null +++ b/codemods/update-react-imports/tests/react-basic-default-export-jsx-element/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "remove-react-import", + "file": "tests/react-basic-default-export-jsx-element/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export.tsx.output.js b/codemods/update-react-imports/tests/react-basic-default-export/expected.tsx similarity index 98% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export.tsx.output.js rename to codemods/update-react-imports/tests/react-basic-default-export/expected.tsx index c4c96c0..bf4a35e 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export.tsx.output.js +++ b/codemods/update-react-imports/tests/react-basic-default-export/expected.tsx @@ -1,3 +1,2 @@ import { createElement } from "react"; - createElement('div', 'la'); diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export.input.js b/codemods/update-react-imports/tests/react-basic-default-export/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export.input.js rename to codemods/update-react-imports/tests/react-basic-default-export/input.tsx diff --git a/codemods/update-react-imports/tests/react-basic-default-export/metrics.json b/codemods/update-react-imports/tests/react-basic-default-export/metrics.json new file mode 100644 index 0000000..0a7a660 --- /dev/null +++ b/codemods/update-react-imports/tests/react-basic-default-export/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-member-to-named", + "file": "tests/react-basic-default-export/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-jsx-member-expression.output.js b/codemods/update-react-imports/tests/react-jsx-member-expression/expected.tsx similarity index 75% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/react-jsx-member-expression.output.js rename to codemods/update-react-imports/tests/react-jsx-member-expression/expected.tsx index 7627281..dd7360f 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-jsx-member-expression.output.js +++ b/codemods/update-react-imports/tests/react-jsx-member-expression/expected.tsx @@ -1,3 +1,2 @@ import { Fragment, useState } from 'react'; - - + \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-jsx-member-expression.input.js b/codemods/update-react-imports/tests/react-jsx-member-expression/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/react-jsx-member-expression.input.js rename to codemods/update-react-imports/tests/react-jsx-member-expression/input.tsx diff --git a/codemods/update-react-imports/tests/react-jsx-member-expression/metrics.json b/codemods/update-react-imports/tests/react-jsx-member-expression/metrics.json new file mode 100644 index 0000000..e060601 --- /dev/null +++ b/codemods/update-react-imports/tests/react-jsx-member-expression/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-member-to-named", + "file": "tests/react-jsx-member-expression/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-not-removed.tsx.output.js b/codemods/update-react-imports/tests/react-not-removed/expected.tsx similarity index 85% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-not-removed.tsx.output.js rename to codemods/update-react-imports/tests/react-not-removed/expected.tsx index 8dc1bf7..4b9bcea 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-not-removed.tsx.output.js +++ b/codemods/update-react-imports/tests/react-not-removed/expected.tsx @@ -1,7 +1,6 @@ import * as React from "react"; - React.createElement('div', {}); Promise.resolve(React); -
Hi
+
Hi
\ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-not-removed.input.js b/codemods/update-react-imports/tests/react-not-removed/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/react-not-removed.input.js rename to codemods/update-react-imports/tests/react-not-removed/input.tsx diff --git a/codemods/update-react-imports/tests/react-not-removed/metrics.json b/codemods/update-react-imports/tests/react-not-removed/metrics.json new file mode 100644 index 0000000..8ab16c4 --- /dev/null +++ b/codemods/update-react-imports/tests/react-not-removed/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-to-namespace", + "file": "tests/react-not-removed/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-default-export.output.js b/codemods/update-react-imports/tests/react-type-default-export/expected.tsx similarity index 97% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-default-export.output.js rename to codemods/update-react-imports/tests/react-type-default-export/expected.tsx index 441a929..36e2cbd 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-default-export.output.js +++ b/codemods/update-react-imports/tests/react-type-default-export/expected.tsx @@ -1,3 +1,2 @@ import type React from "react"; -
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-default-export.input.js b/codemods/update-react-imports/tests/react-type-default-export/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-default-export.input.js rename to codemods/update-react-imports/tests/react-type-default-export/input.tsx diff --git a/codemods/update-react-imports/tests/react-type-default-export/metrics.json b/codemods/update-react-imports/tests/react-type-default-export/metrics.json new file mode 100644 index 0000000..53bdfa4 --- /dev/null +++ b/codemods/update-react-imports/tests/react-type-default-export/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "remove-react-import", + "file": "tests/react-type-default-export/input.tsx", + "previous": "namespace" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-not-removed.output.js b/codemods/update-react-imports/tests/react-type-not-removed/expected.tsx similarity index 98% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-not-removed.output.js rename to codemods/update-react-imports/tests/react-type-not-removed/expected.tsx index 11e80fe..0dcfc2b 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-not-removed.output.js +++ b/codemods/update-react-imports/tests/react-type-not-removed/expected.tsx @@ -1,3 +1,2 @@ import type React, { Node } from "react"; -
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-not-removed.input.js b/codemods/update-react-imports/tests/react-type-not-removed/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-not-removed.input.js rename to codemods/update-react-imports/tests/react-type-not-removed/input.tsx diff --git a/codemods/update-react-imports/tests/react-type-not-removed/metrics.json b/codemods/update-react-imports/tests/react-type-not-removed/metrics.json new file mode 100644 index 0000000..0218dd9 --- /dev/null +++ b/codemods/update-react-imports/tests/react-type-not-removed/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "remove-react-import", + "file": "tests/react-type-not-removed/input.tsx", + "previous": "namespace" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import-react-variable.output.js b/codemods/update-react-imports/tests/typescript-default-and-multiple-specifiers-import-react-variable-tsx/expected.tsx similarity index 99% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import-react-variable.output.js rename to codemods/update-react-imports/tests/typescript-default-and-multiple-specifiers-import-react-variable-tsx/expected.tsx index 41bcef4..0302b77 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import-react-variable.output.js +++ b/codemods/update-react-imports/tests/typescript-default-and-multiple-specifiers-import-react-variable-tsx/expected.tsx @@ -1,6 +1,5 @@ import { createElement, useState } from "react"; import * as React from "react"; - React.createElement('div', {});
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import-react-variable.tsx.input.js b/codemods/update-react-imports/tests/typescript-default-and-multiple-specifiers-import-react-variable-tsx/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import-react-variable.tsx.input.js rename to codemods/update-react-imports/tests/typescript-default-and-multiple-specifiers-import-react-variable-tsx/input.tsx diff --git a/codemods/update-react-imports/tests/typescript-default-and-multiple-specifiers-import-react-variable-tsx/metrics.json b/codemods/update-react-imports/tests/typescript-default-and-multiple-specifiers-import-react-variable-tsx/metrics.json new file mode 100644 index 0000000..fc22dae --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-default-and-multiple-specifiers-import-react-variable-tsx/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-to-namespace", + "file": "tests/typescript-default-and-multiple-specifiers-import-react-variable-tsx/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import.tsx.output.js b/codemods/update-react-imports/tests/typescript-default-and-multiple-specifiers-import-tsx/expected.tsx similarity index 98% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import.tsx.output.js rename to codemods/update-react-imports/tests/typescript-default-and-multiple-specifiers-import-tsx/expected.tsx index 51ebe95..d617eef 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import.tsx.output.js +++ b/codemods/update-react-imports/tests/typescript-default-and-multiple-specifiers-import-tsx/expected.tsx @@ -1,3 +1,2 @@ import { createElement, useState } from "react"; -
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import.tsx.input.js b/codemods/update-react-imports/tests/typescript-default-and-multiple-specifiers-import-tsx/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import.tsx.input.js rename to codemods/update-react-imports/tests/typescript-default-and-multiple-specifiers-import-tsx/input.tsx diff --git a/codemods/update-react-imports/tests/typescript-default-and-multiple-specifiers-import-tsx/metrics.json b/codemods/update-react-imports/tests/typescript-default-and-multiple-specifiers-import-tsx/metrics.json new file mode 100644 index 0000000..c1b188c --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-default-and-multiple-specifiers-import-tsx/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "remove-react-import", + "file": "tests/typescript-default-and-multiple-specifiers-import-tsx/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-element.tsx.output.js b/codemods/update-react-imports/tests/typescript-jsx-element-tsx/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-element.tsx.output.js rename to codemods/update-react-imports/tests/typescript-jsx-element-tsx/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-element.tsx.input.js b/codemods/update-react-imports/tests/typescript-jsx-element-tsx/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-element.tsx.input.js rename to codemods/update-react-imports/tests/typescript-jsx-element-tsx/input.tsx diff --git a/codemods/update-react-imports/tests/typescript-jsx-element-tsx/metrics.json b/codemods/update-react-imports/tests/typescript-jsx-element-tsx/metrics.json new file mode 100644 index 0000000..5c3bb6b --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-jsx-element-tsx/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "remove-react-import", + "file": "tests/typescript-jsx-element-tsx/input.tsx", + "previous": "namespace" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-fragment.tsx.output.js b/codemods/update-react-imports/tests/typescript-jsx-fragment-tsx/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-fragment.tsx.output.js rename to codemods/update-react-imports/tests/typescript-jsx-fragment-tsx/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-fragment.tsx.input.js b/codemods/update-react-imports/tests/typescript-jsx-fragment-tsx/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-fragment.tsx.input.js rename to codemods/update-react-imports/tests/typescript-jsx-fragment-tsx/input.tsx diff --git a/codemods/update-react-imports/tests/typescript-jsx-fragment-tsx/metrics.json b/codemods/update-react-imports/tests/typescript-jsx-fragment-tsx/metrics.json new file mode 100644 index 0000000..01635eb --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-jsx-fragment-tsx/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "remove-react-import", + "file": "tests/typescript-jsx-fragment-tsx/input.tsx", + "previous": "namespace" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/leading-comment.tsx.output.js b/codemods/update-react-imports/tests/typescript-leading-comment-tsx/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/leading-comment.tsx.output.js rename to codemods/update-react-imports/tests/typescript-leading-comment-tsx/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/leading-comment.tsx.input.js b/codemods/update-react-imports/tests/typescript-leading-comment-tsx/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/leading-comment.tsx.input.js rename to codemods/update-react-imports/tests/typescript-leading-comment-tsx/input.tsx diff --git a/codemods/update-react-imports/tests/typescript-leading-comment-tsx/metrics.json b/codemods/update-react-imports/tests/typescript-leading-comment-tsx/metrics.json new file mode 100644 index 0000000..d8e4624 --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-leading-comment-tsx/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "remove-react-import", + "file": "tests/typescript-leading-comment-tsx/input.tsx", + "previous": "namespace" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-namespace.tsx.input.js b/codemods/update-react-imports/tests/typescript-preserve-types-default-tsx/expected.tsx similarity index 99% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-namespace.tsx.input.js rename to codemods/update-react-imports/tests/typescript-preserve-types-default-tsx/expected.tsx index f27298b..f9f64e1 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-namespace.tsx.input.js +++ b/codemods/update-react-imports/tests/typescript-preserve-types-default-tsx/expected.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; - const App: React.FunctionComponent<{ message: string }> = ({ message }) => (
{message}
); diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-default.tsx.input.js b/codemods/update-react-imports/tests/typescript-preserve-types-default-tsx/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-default.tsx.input.js rename to codemods/update-react-imports/tests/typescript-preserve-types-default-tsx/input.tsx diff --git a/codemods/update-react-imports/tests/typescript-preserve-types-default-tsx/metrics.json b/codemods/update-react-imports/tests/typescript-preserve-types-default-tsx/metrics.json new file mode 100644 index 0000000..6918c08 --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-preserve-types-default-tsx/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-to-namespace", + "file": "tests/typescript-preserve-types-default-tsx/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/update-react-imports/tests/typescript-preserve-types-namespace-tsx/expected.tsx b/codemods/update-react-imports/tests/typescript-preserve-types-namespace-tsx/expected.tsx new file mode 100644 index 0000000..f9f64e1 --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-preserve-types-namespace-tsx/expected.tsx @@ -0,0 +1,4 @@ +import * as React from 'react'; +const App: React.FunctionComponent<{ message: string }> = ({ message }) => ( +
{message}
+); diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-default.tsx.output.js b/codemods/update-react-imports/tests/typescript-preserve-types-namespace-tsx/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-default.tsx.output.js rename to codemods/update-react-imports/tests/typescript-preserve-types-namespace-tsx/input.tsx diff --git a/codemods/update-react-imports/tests/typescript-preserve-types-namespace-tsx/metrics.json b/codemods/update-react-imports/tests/typescript-preserve-types-namespace-tsx/metrics.json new file mode 100644 index 0000000..6f82fd0 --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-preserve-types-namespace-tsx/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-to-namespace", + "file": "tests/typescript-preserve-types-namespace-tsx/input.tsx", + "previous": "namespace" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/update-react-imports/tests/typescript-react-already-used-named-export-tsx/expected.tsx b/codemods/update-react-imports/tests/typescript-react-already-used-named-export-tsx/expected.tsx new file mode 100644 index 0000000..491a83a --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-react-already-used-named-export-tsx/expected.tsx @@ -0,0 +1,2 @@ +import * as React from 'react'; +React.useState(false); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-already-used-named-export.tsx.input.js b/codemods/update-react-imports/tests/typescript-react-already-used-named-export-tsx/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-already-used-named-export.tsx.input.js rename to codemods/update-react-imports/tests/typescript-react-already-used-named-export-tsx/input.tsx diff --git a/codemods/update-react-imports/tests/typescript-react-already-used-named-export-tsx/metrics.json b/codemods/update-react-imports/tests/typescript-react-already-used-named-export-tsx/metrics.json new file mode 100644 index 0000000..f5ea9f4 --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-react-already-used-named-export-tsx/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-to-namespace", + "file": "tests/typescript-react-already-used-named-export-tsx/input.tsx", + "previous": "namespace" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element-react-variable.output.js b/codemods/update-react-imports/tests/typescript-react-basic-default-export-jsx-element-react-variable-tsx/expected.tsx similarity index 98% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element-react-variable.output.js rename to codemods/update-react-imports/tests/typescript-react-basic-default-export-jsx-element-react-variable-tsx/expected.tsx index 3995e26..4a3728b 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element-react-variable.output.js +++ b/codemods/update-react-imports/tests/typescript-react-basic-default-export-jsx-element-react-variable-tsx/expected.tsx @@ -1,5 +1,4 @@ import { createElement } from "react"; - createElement('div', {});
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element-react-variable.tsx.input.js b/codemods/update-react-imports/tests/typescript-react-basic-default-export-jsx-element-react-variable-tsx/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element-react-variable.tsx.input.js rename to codemods/update-react-imports/tests/typescript-react-basic-default-export-jsx-element-react-variable-tsx/input.tsx diff --git a/codemods/update-react-imports/tests/typescript-react-basic-default-export-jsx-element-react-variable-tsx/metrics.json b/codemods/update-react-imports/tests/typescript-react-basic-default-export-jsx-element-react-variable-tsx/metrics.json new file mode 100644 index 0000000..bed66ce --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-react-basic-default-export-jsx-element-react-variable-tsx/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-member-to-named", + "file": "tests/typescript-react-basic-default-export-jsx-element-react-variable-tsx/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element.tsx.output.js b/codemods/update-react-imports/tests/typescript-react-basic-default-export-jsx-element-tsx/expected.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element.tsx.output.js rename to codemods/update-react-imports/tests/typescript-react-basic-default-export-jsx-element-tsx/expected.tsx diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element.tsx.input.js b/codemods/update-react-imports/tests/typescript-react-basic-default-export-jsx-element-tsx/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element.tsx.input.js rename to codemods/update-react-imports/tests/typescript-react-basic-default-export-jsx-element-tsx/input.tsx diff --git a/codemods/update-react-imports/tests/typescript-react-basic-default-export-jsx-element-tsx/metrics.json b/codemods/update-react-imports/tests/typescript-react-basic-default-export-jsx-element-tsx/metrics.json new file mode 100644 index 0000000..5776d05 --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-react-basic-default-export-jsx-element-tsx/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "remove-react-import", + "file": "tests/typescript-react-basic-default-export-jsx-element-tsx/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export.output.js b/codemods/update-react-imports/tests/typescript-react-basic-default-export-tsx/expected.tsx similarity index 98% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export.output.js rename to codemods/update-react-imports/tests/typescript-react-basic-default-export-tsx/expected.tsx index c4c96c0..bf4a35e 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export.output.js +++ b/codemods/update-react-imports/tests/typescript-react-basic-default-export-tsx/expected.tsx @@ -1,3 +1,2 @@ import { createElement } from "react"; - createElement('div', 'la'); diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export.tsx.input.js b/codemods/update-react-imports/tests/typescript-react-basic-default-export-tsx/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export.tsx.input.js rename to codemods/update-react-imports/tests/typescript-react-basic-default-export-tsx/input.tsx diff --git a/codemods/update-react-imports/tests/typescript-react-basic-default-export-tsx/metrics.json b/codemods/update-react-imports/tests/typescript-react-basic-default-export-tsx/metrics.json new file mode 100644 index 0000000..99b4fbe --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-react-basic-default-export-tsx/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-member-to-named", + "file": "tests/typescript-react-basic-default-export-tsx/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-jsx-member-expression.tsx.output.js b/codemods/update-react-imports/tests/typescript-react-jsx-member-expression-tsx/expected.tsx similarity index 75% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-jsx-member-expression.tsx.output.js rename to codemods/update-react-imports/tests/typescript-react-jsx-member-expression-tsx/expected.tsx index 7627281..dd7360f 100644 --- a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-jsx-member-expression.tsx.output.js +++ b/codemods/update-react-imports/tests/typescript-react-jsx-member-expression-tsx/expected.tsx @@ -1,3 +1,2 @@ import { Fragment, useState } from 'react'; - - + \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-jsx-member-expression.tsx.input.js b/codemods/update-react-imports/tests/typescript-react-jsx-member-expression-tsx/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-jsx-member-expression.tsx.input.js rename to codemods/update-react-imports/tests/typescript-react-jsx-member-expression-tsx/input.tsx diff --git a/codemods/update-react-imports/tests/typescript-react-jsx-member-expression-tsx/metrics.json b/codemods/update-react-imports/tests/typescript-react-jsx-member-expression-tsx/metrics.json new file mode 100644 index 0000000..4224bed --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-react-jsx-member-expression-tsx/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-member-to-named", + "file": "tests/typescript-react-jsx-member-expression-tsx/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/update-react-imports/tests/typescript-react-not-removed-tsx/expected.tsx b/codemods/update-react-imports/tests/typescript-react-not-removed-tsx/expected.tsx new file mode 100644 index 0000000..4b9bcea --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-react-not-removed-tsx/expected.tsx @@ -0,0 +1,6 @@ +import * as React from "react"; +React.createElement('div', {}); + +Promise.resolve(React); + +
Hi
\ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-not-removed.tsx.input.js b/codemods/update-react-imports/tests/typescript-react-not-removed-tsx/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-not-removed.tsx.input.js rename to codemods/update-react-imports/tests/typescript-react-not-removed-tsx/input.tsx diff --git a/codemods/update-react-imports/tests/typescript-react-not-removed-tsx/metrics.json b/codemods/update-react-imports/tests/typescript-react-not-removed-tsx/metrics.json new file mode 100644 index 0000000..6acf7b7 --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-react-not-removed-tsx/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-to-namespace", + "file": "tests/typescript-react-not-removed-tsx/input.tsx", + "previous": "default" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/update-react-imports/tests/typescript-variable-already-used-tsx/expected.tsx b/codemods/update-react-imports/tests/typescript-variable-already-used-tsx/expected.tsx new file mode 100644 index 0000000..48abf6f --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-variable-already-used-tsx/expected.tsx @@ -0,0 +1,6 @@ +import * as React from "react"; +React.createElement('div', {}); + +createElement('someFunction'); + +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/variable-already-used.tsx.input.js b/codemods/update-react-imports/tests/typescript-variable-already-used-tsx/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/variable-already-used.tsx.input.js rename to codemods/update-react-imports/tests/typescript-variable-already-used-tsx/input.tsx diff --git a/codemods/update-react-imports/tests/typescript-variable-already-used-tsx/metrics.json b/codemods/update-react-imports/tests/typescript-variable-already-used-tsx/metrics.json new file mode 100644 index 0000000..03439b9 --- /dev/null +++ b/codemods/update-react-imports/tests/typescript-variable-already-used-tsx/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-to-namespace", + "file": "tests/typescript-variable-already-used-tsx/input.tsx", + "previous": "namespace" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/update-react-imports/tests/variable-already-used/expected.tsx b/codemods/update-react-imports/tests/variable-already-used/expected.tsx new file mode 100644 index 0000000..48abf6f --- /dev/null +++ b/codemods/update-react-imports/tests/variable-already-used/expected.tsx @@ -0,0 +1,6 @@ +import * as React from "react"; +React.createElement('div', {}); + +createElement('someFunction'); + +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/variable-already-used.input.js b/codemods/update-react-imports/tests/variable-already-used/input.tsx similarity index 100% rename from codemods/legacy/transforms/__testfixtures__/update-react-imports/variable-already-used.input.js rename to codemods/update-react-imports/tests/variable-already-used/input.tsx diff --git a/codemods/update-react-imports/tests/variable-already-used/metrics.json b/codemods/update-react-imports/tests/variable-already-used/metrics.json new file mode 100644 index 0000000..cb2660e --- /dev/null +++ b/codemods/update-react-imports/tests/variable-already-used/metrics.json @@ -0,0 +1,12 @@ +{ + "react-import-updates": [ + { + "cardinality": { + "action": "convert-to-namespace", + "file": "tests/variable-already-used/input.tsx", + "previous": "namespace" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/update-react-imports/tsconfig.json b/codemods/update-react-imports/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/update-react-imports/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": [ + "ESNext" + ], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": [ + "@codemod.com/jssg-types", + "node" + ], + "allowImportingTsExtensions": true, + "noEmit": true, + "skipLibCheck": true, + "verbatimModuleSyntax": false, + "erasableSyntaxOnly": true, + "strict": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true + }, + "exclude": [ + "tests" + ] +} diff --git a/codemods/update-react-imports/workflow.yaml b/codemods/update-react-imports/workflow.yaml new file mode 100644 index 0000000..f7d8d8c --- /dev/null +++ b/codemods/update-react-imports/workflow.yaml @@ -0,0 +1,21 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +params: + schema: + destructureNamespaceImports: + name: "Destructure Namespace Imports" + description: "Allow namespace React imports to be rewritten to named imports when it is safe." + type: boolean + default: false + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: "Scan tsx files and apply fixes" + js-ast-grep: + js_file: scripts/codemod.ts + language: "tsx" diff --git a/codemods/use-context-hook/.gitignore b/codemods/use-context-hook/.gitignore new file mode 100644 index 0000000..78174f4 --- /dev/null +++ b/codemods/use-context-hook/.gitignore @@ -0,0 +1,33 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build artifacts +target/ +dist/ +build/ + +# Temporary files +*.tmp +*.temp +.cache/ + +# Environment files +.env +.env.local + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db + +# Package bundles +*.tar.gz +*.tgz \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/README.md b/codemods/use-context-hook/README.md similarity index 75% rename from codemods/jssg/use-context-hook/README.md rename to codemods/use-context-hook/README.md index c87cf70..089e12e 100644 --- a/codemods/jssg/use-context-hook/README.md +++ b/codemods/use-context-hook/README.md @@ -7,10 +7,3 @@ Transform `React.useContext()` and `useContext()` calls into `use()`. ```bash npx codemod @react-new/use-context-hook --target ``` - -## Development - -```bash -pnpm test -pnpm check-types -``` diff --git a/codemods/jssg/use-context-hook/codemod.yaml b/codemods/use-context-hook/codemod.yaml similarity index 95% rename from codemods/jssg/use-context-hook/codemod.yaml rename to codemods/use-context-hook/codemod.yaml index 1a161a4..7d14dbb 100644 --- a/codemods/jssg/use-context-hook/codemod.yaml +++ b/codemods/use-context-hook/codemod.yaml @@ -1,7 +1,7 @@ schema_version: "1.0" name: "@react-new/use-context-hook" -version: "0.1.0" +version: "0.1.1" description: "Transform React.useContext() and useContext() to use() hook" author: "Codemod " license: "MIT" diff --git a/codemods/jssg/use-context-hook/package.json b/codemods/use-context-hook/package.json similarity index 95% rename from codemods/jssg/use-context-hook/package.json rename to codemods/use-context-hook/package.json index fb190e8..057ebec 100644 --- a/codemods/jssg/use-context-hook/package.json +++ b/codemods/use-context-hook/package.json @@ -1,6 +1,6 @@ { "name": "@react-new/use-context-hook", - "version": "0.1.0", + "version": "0.1.1", "description": "Transform React.useContext() and useContext() to use() hook", "type": "module", "scripts": { diff --git a/codemods/use-context-hook/pnpm-lock.yaml b/codemods/use-context-hook/pnpm-lock.yaml new file mode 100644 index 0000000..5bad4a8 --- /dev/null +++ b/codemods/use-context-hook/pnpm-lock.yaml @@ -0,0 +1,41 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@jssg/utils': + specifier: ^0.0.2 + version: 0.0.2 + devDependencies: + '@codemod.com/jssg-types': + specifier: latest + version: 1.5.0 + typescript: + specifier: latest + version: 5.9.3 + +packages: + + '@codemod.com/jssg-types@1.5.0': + resolution: {integrity: sha512-zChRbxI3hBSGrAHnWlEzOw1FztLWMMiarwcr0Wbk0On4hmv7dVgoUqpIHfxb64mEMKJ5syTIKY3ZNd8DcFQa5w==} + + '@jssg/utils@0.0.2': + resolution: {integrity: sha512-eCQv5Xs9yfI6OKq2PQ8SyKsxPhdiaJY/XAFJmsZiVbsK5DIBBmGBA95CsyD4JFWXDKkNv6Q7ER/Kbp6/uWzB2w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + +snapshots: + + '@codemod.com/jssg-types@1.5.0': {} + + '@jssg/utils@0.0.2': {} + + typescript@5.9.3: {} diff --git a/codemods/jssg/use-context-hook/scripts/codemod.ts b/codemods/use-context-hook/scripts/codemod.ts similarity index 100% rename from codemods/jssg/use-context-hook/scripts/codemod.ts rename to codemods/use-context-hook/scripts/codemod.ts diff --git a/codemods/jssg/use-context-hook/tests/aliased-use-context-dollar/expected.tsx b/codemods/use-context-hook/tests/aliased-use-context-dollar/expected.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/aliased-use-context-dollar/expected.tsx rename to codemods/use-context-hook/tests/aliased-use-context-dollar/expected.tsx diff --git a/codemods/jssg/use-context-hook/tests/aliased-use-context-dollar/input.tsx b/codemods/use-context-hook/tests/aliased-use-context-dollar/input.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/aliased-use-context-dollar/input.tsx rename to codemods/use-context-hook/tests/aliased-use-context-dollar/input.tsx diff --git a/codemods/jssg/use-context-hook/tests/aliased-use-context-dollar/metrics.json b/codemods/use-context-hook/tests/aliased-use-context-dollar/metrics.json similarity index 100% rename from codemods/jssg/use-context-hook/tests/aliased-use-context-dollar/metrics.json rename to codemods/use-context-hook/tests/aliased-use-context-dollar/metrics.json diff --git a/codemods/jssg/use-context-hook/tests/aliased-use-context/expected.tsx b/codemods/use-context-hook/tests/aliased-use-context/expected.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/aliased-use-context/expected.tsx rename to codemods/use-context-hook/tests/aliased-use-context/expected.tsx diff --git a/codemods/jssg/use-context-hook/tests/aliased-use-context/input.tsx b/codemods/use-context-hook/tests/aliased-use-context/input.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/aliased-use-context/input.tsx rename to codemods/use-context-hook/tests/aliased-use-context/input.tsx diff --git a/codemods/jssg/use-context-hook/tests/aliased-use-context/metrics.json b/codemods/use-context-hook/tests/aliased-use-context/metrics.json similarity index 100% rename from codemods/jssg/use-context-hook/tests/aliased-use-context/metrics.json rename to codemods/use-context-hook/tests/aliased-use-context/metrics.json diff --git a/codemods/jssg/use-context-hook/tests/any-use-context/expected.tsx b/codemods/use-context-hook/tests/any-use-context/expected.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/any-use-context/expected.tsx rename to codemods/use-context-hook/tests/any-use-context/expected.tsx diff --git a/codemods/jssg/use-context-hook/tests/any-use-context/input.tsx b/codemods/use-context-hook/tests/any-use-context/input.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/any-use-context/input.tsx rename to codemods/use-context-hook/tests/any-use-context/input.tsx diff --git a/codemods/jssg/use-context-hook/tests/mixed-import/expected.tsx b/codemods/use-context-hook/tests/mixed-import/expected.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/mixed-import/expected.tsx rename to codemods/use-context-hook/tests/mixed-import/expected.tsx diff --git a/codemods/jssg/use-context-hook/tests/mixed-import/input.tsx b/codemods/use-context-hook/tests/mixed-import/input.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/mixed-import/input.tsx rename to codemods/use-context-hook/tests/mixed-import/input.tsx diff --git a/codemods/jssg/use-context-hook/tests/mixed-import/metrics.json b/codemods/use-context-hook/tests/mixed-import/metrics.json similarity index 100% rename from codemods/jssg/use-context-hook/tests/mixed-import/metrics.json rename to codemods/use-context-hook/tests/mixed-import/metrics.json diff --git a/codemods/jssg/use-context-hook/tests/multiple-use-context-imports/expected.tsx b/codemods/use-context-hook/tests/multiple-use-context-imports/expected.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/multiple-use-context-imports/expected.tsx rename to codemods/use-context-hook/tests/multiple-use-context-imports/expected.tsx diff --git a/codemods/jssg/use-context-hook/tests/multiple-use-context-imports/input.tsx b/codemods/use-context-hook/tests/multiple-use-context-imports/input.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/multiple-use-context-imports/input.tsx rename to codemods/use-context-hook/tests/multiple-use-context-imports/input.tsx diff --git a/codemods/jssg/use-context-hook/tests/multiple-use-context-imports/metrics.json b/codemods/use-context-hook/tests/multiple-use-context-imports/metrics.json similarity index 100% rename from codemods/jssg/use-context-hook/tests/multiple-use-context-imports/metrics.json rename to codemods/use-context-hook/tests/multiple-use-context-imports/metrics.json diff --git a/codemods/jssg/use-context-hook/tests/shadowed-local-binding/expected.tsx b/codemods/use-context-hook/tests/shadowed-local-binding/expected.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/shadowed-local-binding/expected.tsx rename to codemods/use-context-hook/tests/shadowed-local-binding/expected.tsx diff --git a/codemods/jssg/use-context-hook/tests/shadowed-local-binding/input.tsx b/codemods/use-context-hook/tests/shadowed-local-binding/input.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/shadowed-local-binding/input.tsx rename to codemods/use-context-hook/tests/shadowed-local-binding/input.tsx diff --git a/codemods/jssg/use-context-hook/tests/shadowed-local-binding/metrics.json b/codemods/use-context-hook/tests/shadowed-local-binding/metrics.json similarity index 100% rename from codemods/jssg/use-context-hook/tests/shadowed-local-binding/metrics.json rename to codemods/use-context-hook/tests/shadowed-local-binding/metrics.json diff --git a/codemods/jssg/use-context-hook/tests/type-identifier-rename/expected.tsx b/codemods/use-context-hook/tests/type-identifier-rename/expected.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/type-identifier-rename/expected.tsx rename to codemods/use-context-hook/tests/type-identifier-rename/expected.tsx diff --git a/codemods/jssg/use-context-hook/tests/type-identifier-rename/input.tsx b/codemods/use-context-hook/tests/type-identifier-rename/input.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/type-identifier-rename/input.tsx rename to codemods/use-context-hook/tests/type-identifier-rename/input.tsx diff --git a/codemods/jssg/use-context-hook/tests/type-identifier-rename/metrics.json b/codemods/use-context-hook/tests/type-identifier-rename/metrics.json similarity index 100% rename from codemods/jssg/use-context-hook/tests/type-identifier-rename/metrics.json rename to codemods/use-context-hook/tests/type-identifier-rename/metrics.json diff --git a/codemods/jssg/use-context-hook/tests/typescript-any-use-context/expected.tsx b/codemods/use-context-hook/tests/typescript-any-use-context/expected.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/typescript-any-use-context/expected.tsx rename to codemods/use-context-hook/tests/typescript-any-use-context/expected.tsx diff --git a/codemods/jssg/use-context-hook/tests/typescript-any-use-context/input.tsx b/codemods/use-context-hook/tests/typescript-any-use-context/input.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/typescript-any-use-context/input.tsx rename to codemods/use-context-hook/tests/typescript-any-use-context/input.tsx diff --git a/codemods/jssg/use-context-hook/tests/typescript-use-context-2/expected.tsx b/codemods/use-context-hook/tests/typescript-use-context-2/expected.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/typescript-use-context-2/expected.tsx rename to codemods/use-context-hook/tests/typescript-use-context-2/expected.tsx diff --git a/codemods/jssg/use-context-hook/tests/typescript-use-context-2/input.tsx b/codemods/use-context-hook/tests/typescript-use-context-2/input.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/typescript-use-context-2/input.tsx rename to codemods/use-context-hook/tests/typescript-use-context-2/input.tsx diff --git a/codemods/jssg/use-context-hook/tests/typescript-use-context-2/metrics.json b/codemods/use-context-hook/tests/typescript-use-context-2/metrics.json similarity index 100% rename from codemods/jssg/use-context-hook/tests/typescript-use-context-2/metrics.json rename to codemods/use-context-hook/tests/typescript-use-context-2/metrics.json diff --git a/codemods/jssg/use-context-hook/tests/typescript-use-context/expected.tsx b/codemods/use-context-hook/tests/typescript-use-context/expected.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/typescript-use-context/expected.tsx rename to codemods/use-context-hook/tests/typescript-use-context/expected.tsx diff --git a/codemods/jssg/use-context-hook/tests/typescript-use-context/input.tsx b/codemods/use-context-hook/tests/typescript-use-context/input.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/typescript-use-context/input.tsx rename to codemods/use-context-hook/tests/typescript-use-context/input.tsx diff --git a/codemods/jssg/use-context-hook/tests/typescript-use-context/metrics.json b/codemods/use-context-hook/tests/typescript-use-context/metrics.json similarity index 100% rename from codemods/jssg/use-context-hook/tests/typescript-use-context/metrics.json rename to codemods/use-context-hook/tests/typescript-use-context/metrics.json diff --git a/codemods/jssg/use-context-hook/tests/use-context-2/expected.tsx b/codemods/use-context-hook/tests/use-context-2/expected.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/use-context-2/expected.tsx rename to codemods/use-context-hook/tests/use-context-2/expected.tsx diff --git a/codemods/jssg/use-context-hook/tests/use-context-2/input.tsx b/codemods/use-context-hook/tests/use-context-2/input.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/use-context-2/input.tsx rename to codemods/use-context-hook/tests/use-context-2/input.tsx diff --git a/codemods/jssg/use-context-hook/tests/use-context-2/metrics.json b/codemods/use-context-hook/tests/use-context-2/metrics.json similarity index 100% rename from codemods/jssg/use-context-hook/tests/use-context-2/metrics.json rename to codemods/use-context-hook/tests/use-context-2/metrics.json diff --git a/codemods/jssg/use-context-hook/tests/use-context/expected.tsx b/codemods/use-context-hook/tests/use-context/expected.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/use-context/expected.tsx rename to codemods/use-context-hook/tests/use-context/expected.tsx diff --git a/codemods/jssg/use-context-hook/tests/use-context/input.tsx b/codemods/use-context-hook/tests/use-context/input.tsx similarity index 100% rename from codemods/jssg/use-context-hook/tests/use-context/input.tsx rename to codemods/use-context-hook/tests/use-context/input.tsx diff --git a/codemods/jssg/use-context-hook/tests/use-context/metrics.json b/codemods/use-context-hook/tests/use-context/metrics.json similarity index 100% rename from codemods/jssg/use-context-hook/tests/use-context/metrics.json rename to codemods/use-context-hook/tests/use-context/metrics.json diff --git a/codemods/use-context-hook/tsconfig.json b/codemods/use-context-hook/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/use-context-hook/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": [ + "ESNext" + ], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": [ + "@codemod.com/jssg-types", + "node" + ], + "allowImportingTsExtensions": true, + "noEmit": true, + "skipLibCheck": true, + "verbatimModuleSyntax": false, + "erasableSyntaxOnly": true, + "strict": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true + }, + "exclude": [ + "tests" + ] +} diff --git a/codemods/use-context-hook/workflow.yaml b/codemods/use-context-hook/workflow.yaml new file mode 100644 index 0000000..8773841 --- /dev/null +++ b/codemods/use-context-hook/workflow.yaml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: "Scan tsx files and apply fixes" + js-ast-grep: + js_file: scripts/codemod.ts + language: "tsx" diff --git a/package.json b/package.json index 106703f..88ebbd8 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,11 @@ "description": "A collection of codemods to help update React apps", "type": "module", "scripts": { - "lint": "node ./scripts/validate-docs.mjs", - "test": "pnpm run test:active", - "test:active": "pnpm -r --filter \"@react-new/*\" test", - "check-types": "pnpm run check-types:active", - "check-types:active": "pnpm -r --filter \"@react-new/*\" check-types", - "test:legacy": "bash -lc 'cd codemods/legacy && pnpm install --ignore-workspace --frozen-lockfile --force && pnpm run test:ci'", + "test": "pnpm -r --filter \"@react-new/*\" test", + "check-types": "pnpm -r --filter \"@react-new/*\" check-types", "changeset": "changeset", "version-packages": "changeset version && bash scripts/sync-codemod-versions.sh", - "ci": "pnpm run lint && pnpm run test:active && pnpm run check-types:active && pnpm run test:legacy" + "ci": "pnpm run test && pnpm run check-types" }, "keywords": [ "codemod", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 27ccfc0..ff4906f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,9 +19,117 @@ importers: specifier: latest version: 25.6.0 - codemods/jssg/react-19-migration-recipe: {} + codemods/create-element-to-jsx: + devDependencies: + '@codemod.com/jssg-types': + specifier: latest + version: 1.5.2 + '@types/node': + specifier: latest + version: 25.6.0 + typescript: + specifier: latest + version: 6.0.3 + + codemods/error-boundaries: + devDependencies: + '@codemod.com/jssg-types': + specifier: latest + version: 1.5.2 + '@types/node': + specifier: latest + version: 25.6.0 + typescript: + specifier: latest + version: 6.0.3 + + codemods/find-dom-node: + dependencies: + '@jssg/utils': + specifier: ^0.0.2 + version: 0.0.2 + devDependencies: + '@codemod.com/jssg-types': + specifier: latest + version: 1.5.2 + '@types/node': + specifier: latest + version: 25.6.0 + typescript: + specifier: latest + version: 6.0.3 + + codemods/manual-bind-to-arrow: + devDependencies: + '@codemod.com/jssg-types': + specifier: latest + version: 1.5.2 + '@types/node': + specifier: latest + version: 25.6.0 + typescript: + specifier: latest + version: 6.0.3 + + codemods/pure-component: + devDependencies: + '@codemod.com/jssg-types': + specifier: latest + version: 1.5.2 + '@types/node': + specifier: latest + version: 25.6.0 + typescript: + specifier: latest + version: 6.0.3 + + codemods/pure-render-mixin: + devDependencies: + '@codemod.com/jssg-types': + specifier: latest + version: 1.5.2 + '@types/node': + specifier: latest + version: 25.6.0 + typescript: + specifier: latest + version: 6.0.3 + + codemods/react-19-migration-recipe: {} + + codemods/react-dom-to-react-dom-factories: + dependencies: + '@jssg/utils': + specifier: ^0.0.2 + version: 0.0.2 + devDependencies: + '@codemod.com/jssg-types': + specifier: latest + version: 1.5.2 + '@types/node': + specifier: latest + version: 25.6.0 + typescript: + specifier: latest + version: 6.0.3 + + codemods/react-native-view-prop-types: + dependencies: + '@jssg/utils': + specifier: ^0.0.2 + version: 0.0.2 + devDependencies: + '@codemod.com/jssg-types': + specifier: latest + version: 1.5.2 + '@types/node': + specifier: latest + version: 25.6.0 + typescript: + specifier: latest + version: 6.0.3 - codemods/jssg/react-proptypes-to-prop-types: + codemods/react-proptypes-to-prop-types: dependencies: '@jssg/utils': specifier: ^0.0.2 @@ -29,15 +137,39 @@ importers: devDependencies: '@codemod.com/jssg-types': specifier: latest - version: 1.5.1 + version: 1.5.2 '@types/node': specifier: latest version: 25.6.0 typescript: specifier: latest - version: 6.0.2 + version: 6.0.3 - codemods/jssg/replace-act-import: + codemods/react-to-react-dom: + devDependencies: + '@codemod.com/jssg-types': + specifier: latest + version: 1.5.2 + '@types/node': + specifier: latest + version: 25.6.0 + typescript: + specifier: latest + version: 6.0.3 + + codemods/remove-context-provider: + devDependencies: + '@codemod.com/jssg-types': + specifier: latest + version: 1.5.2 + '@types/node': + specifier: latest + version: 25.6.0 + typescript: + specifier: latest + version: 6.0.3 + + codemods/remove-forward-ref: dependencies: '@jssg/utils': specifier: ^0.0.2 @@ -45,15 +177,59 @@ importers: devDependencies: '@codemod.com/jssg-types': specifier: latest - version: 1.5.1 + version: 1.5.2 '@types/node': specifier: latest version: 25.6.0 typescript: specifier: latest - version: 6.0.2 + version: 6.0.3 - codemods/jssg/replace-reactdom-render: + codemods/rename-unsafe-lifecycles: + devDependencies: + '@codemod.com/jssg-types': + specifier: latest + version: 1.5.2 + '@types/node': + specifier: latest + version: 25.6.0 + typescript: + specifier: latest + version: 6.0.3 + + codemods/replace-act-import: + dependencies: + '@jssg/utils': + specifier: ^0.0.2 + version: 0.0.2 + devDependencies: + '@codemod.com/jssg-types': + specifier: latest + version: 1.5.2 + '@types/node': + specifier: latest + version: 25.6.0 + typescript: + specifier: latest + version: 6.0.3 + + codemods/replace-reactdom-render: + dependencies: + '@jssg/utils': + specifier: ^0.0.2 + version: 0.0.2 + devDependencies: + '@codemod.com/jssg-types': + specifier: latest + version: 1.5.2 + '@types/node': + specifier: latest + version: 25.6.0 + typescript: + specifier: latest + version: 6.0.3 + + codemods/replace-string-ref: dependencies: '@jssg/utils': specifier: ^0.0.2 @@ -61,15 +237,15 @@ importers: devDependencies: '@codemod.com/jssg-types': specifier: latest - version: 1.5.1 + version: 1.5.2 '@types/node': specifier: latest version: 25.6.0 typescript: specifier: latest - version: 6.0.2 + version: 6.0.3 - codemods/jssg/replace-string-ref: + codemods/replace-use-form-state: dependencies: '@jssg/utils': specifier: ^0.0.2 @@ -77,15 +253,27 @@ importers: devDependencies: '@codemod.com/jssg-types': specifier: latest - version: 1.5.1 + version: 1.5.2 + '@types/node': + specifier: latest + version: 25.6.0 + typescript: + specifier: latest + version: 6.0.3 + + codemods/sort-comp: + devDependencies: + '@codemod.com/jssg-types': + specifier: latest + version: 1.5.2 '@types/node': specifier: latest version: 25.6.0 typescript: specifier: latest - version: 6.0.2 + version: 6.0.3 - codemods/jssg/replace-use-form-state: + codemods/update-react-imports: dependencies: '@jssg/utils': specifier: ^0.0.2 @@ -93,15 +281,15 @@ importers: devDependencies: '@codemod.com/jssg-types': specifier: latest - version: 1.5.1 + version: 1.5.2 '@types/node': specifier: latest version: 25.6.0 typescript: specifier: latest - version: 6.0.2 + version: 6.0.3 - codemods/jssg/use-context-hook: + codemods/use-context-hook: dependencies: '@jssg/utils': specifier: ^0.0.2 @@ -109,13 +297,13 @@ importers: devDependencies: '@codemod.com/jssg-types': specifier: latest - version: 1.5.1 + version: 1.5.2 '@types/node': specifier: latest version: 25.6.0 typescript: specifier: latest - version: 6.0.2 + version: 6.0.3 packages: @@ -178,8 +366,8 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} - '@codemod.com/jssg-types@1.5.1': - resolution: {integrity: sha512-pCngSoz7hpNiMMkoVi+b4uqy7cA2XuJqEaOx52KYN/25bOdzJ7IqU9DEMukOKnWyFt9iy3UBinayit1P1+9wXA==} + '@codemod.com/jssg-types@1.5.2': + resolution: {integrity: sha512-hZ+CuFL7BzE74VKSOG7xHaxgoRahEoOPdp0N9eg4KAwaYt0gc0YNMI/UvCRZKUTnnRBgTswrwgCbHZMDhjTWoQ==} '@inquirer/external-editor@1.0.3': resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} @@ -490,8 +678,8 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - typescript@6.0.2: - resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true @@ -654,7 +842,7 @@ snapshots: human-id: 4.1.3 prettier: 2.8.8 - '@codemod.com/jssg-types@1.5.1': {} + '@codemod.com/jssg-types@1.5.2': {} '@inquirer/external-editor@1.0.3(@types/node@25.6.0)': dependencies: @@ -930,7 +1118,7 @@ snapshots: dependencies: is-number: 7.0.0 - typescript@6.0.2: {} + typescript@6.0.3: {} undici-types@7.19.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 15e528b..83b59d2 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,2 @@ packages: - - "codemods/jssg/*" + - "codemods/*" diff --git a/scripts/sync-codemod-versions.sh b/scripts/sync-codemod-versions.sh index fd0b363..6a31b31 100755 --- a/scripts/sync-codemod-versions.sh +++ b/scripts/sync-codemod-versions.sh @@ -4,7 +4,7 @@ set -euo pipefail -for pkg_json in codemods/jssg/*/package.json; do +for pkg_json in codemods/*/package.json; do dir="$(dirname "$pkg_json")" codemod_yaml="$dir/codemod.yaml" diff --git a/scripts/tag-and-publish.sh b/scripts/tag-and-publish.sh index 46cdbe8..7b5eb3b 100755 --- a/scripts/tag-and-publish.sh +++ b/scripts/tag-and-publish.sh @@ -7,7 +7,7 @@ set -euo pipefail changed_dirs="[]" -for pkg_json in codemods/jssg/*/package.json; do +for pkg_json in codemods/*/package.json; do dir="$(dirname "$pkg_json")" name="$(node -p "require('./$pkg_json').name")" version="$(node -p "require('./$pkg_json').version")" diff --git a/scripts/validate-docs.mjs b/scripts/validate-docs.mjs deleted file mode 100644 index cc35678..0000000 --- a/scripts/validate-docs.mjs +++ /dev/null @@ -1,33 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; - -const repoRoot = process.cwd(); -const rootReadme = fs.readFileSync(path.join(repoRoot, "README.md"), "utf8"); -const legacyReadme = fs.readFileSync(path.join(repoRoot, "LEGACY.md"), "utf8"); - -const forbiddenRootPatterns = [ - { label: "old registry command claim", regex: /npx codemod react\// }, - { label: "old package-style codemod run claim", regex: /codemod run @react\// }, -]; - -for (const pattern of forbiddenRootPatterns) { - if (pattern.regex.test(rootReadme)) { - throw new Error(`README.md contains forbidden ${pattern.label}.`); - } -} - -if (/Codemod Registry/.test(legacyReadme) && /npx codemod react\//.test(legacyReadme)) { - throw new Error("LEGACY.md must not claim legacy transforms are runnable via Codemod Registry."); -} - -const transformRoot = path.join(repoRoot, "codemods", "jssg"); -for (const name of fs.readdirSync(transformRoot)) { - const readmePath = path.join(transformRoot, name, "README.md"); - const contents = fs.readFileSync(readmePath, "utf8"); - - if (/codemod run @react\//.test(contents) || /npx codemod react\//.test(contents)) { - throw new Error(`${path.relative(repoRoot, readmePath)} contains a forbidden old-scope run claim.`); - } -} - -console.log("Documentation validation passed.");