diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..6b45e55 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,11 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in the readme](https://github.com/changesets/changesets/blob/main/packages/changesets-cli/README.md) + +It is used by the repository to track versioning and release codemods. + +## Adding a changeset + +Run `pnpm changeset` to create a new changeset for your changes. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..43b13d3 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [], + "privatePackages": { + "version": false, + "tag": false + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d6b15d2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: CI + +on: + push: + pull_request: + +jobs: + docs-and-lint: + 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 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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4709a47 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,70 @@ +name: Release + +on: + push: + branches: + - main + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +permissions: + id-token: write + contents: write + pull-requests: write + +jobs: + release: + name: Release + runs-on: ubuntu-latest + outputs: + published: ${{ steps.changesets.outputs.published }} + changed_dirs: ${{ steps.tag.outputs.changed_dirs }} + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - name: Create Release PR or Publish + id: changesets + uses: changesets/action@v1 + with: + version: pnpm version-packages + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Tag released versions + if: steps.changesets.outputs.hasChangesets == 'false' + id: tag + run: bash scripts/tag-and-publish.sh + + publish: + name: Publish ${{ matrix.dir }} + needs: release + if: needs.release.outputs.changed_dirs != '[]' && needs.release.outputs.changed_dirs != '' + runs-on: ubuntu-latest + strategy: + matrix: + dir: ${{ fromJson(needs.release.outputs.changed_dirs) }} + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - name: Publish codemod + uses: codemod/publish-action@v1 + with: + path: ${{ matrix.dir }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0e6af29 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,60 @@ +# Contributing + +Thanks for your interest in contributing to react-codemod! + +## Development 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. + +## Adding a changeset + +This repo uses [Changesets](https://github.com/changesets/changesets) for versioning and releases. Every PR that changes a codemod must include a changeset. + +```bash +pnpm changeset +``` + +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. + +This creates a markdown file in `.changeset/` that should be committed with your PR. + +## Release workflow + +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. + +## Adding a new codemod + +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 +``` + +Use an existing codemod as a reference when creating a new one. diff --git a/LEGACY.md b/LEGACY.md new file mode 100644 index 0000000..730dcb0 --- /dev/null +++ b/LEGACY.md @@ -0,0 +1,32 @@ +# 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 new file mode 100644 index 0000000..328e542 --- /dev/null +++ b/PARITY_STATUS.md @@ -0,0 +1,39 @@ +# Parity Status + +Last updated: 2026-04-15 + +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. + +## JSSG Codemods + +| Codemod | Original Source | Status | Notes | +| --- | --- | --- | --- | +| `replace-use-form-state` | `replace-use-form-state.ts` | `Certified` | Full fixture coverage plus multi-import regression tests are green. Collection-wide replacement on matching `react-dom` imports fixes a real parity gap. | +| `replace-act-import` | `replace-act-import.ts` | `Certified` | Full fixture coverage plus multi-import regression tests. Mixed test-utils partial-removal path is a safe extension. | +| `replace-string-ref` | `replace-string-ref.ts` | `Certified` | Full fixture coverage plus namespace/default-export/multi-ref tests are green. Direct-superclass guard restores intended behavior. | +| `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/): + +- `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 new file mode 100644 index 0000000..0f21006 --- /dev/null +++ b/README.md @@ -0,0 +1,129 @@ +# React Codemods + +This repository contains a collection of codemods to help update React apps. + +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 +``` + +- `` — 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. + +## Available Codemods + +All React codemods are also available in the [Codemod Registry](https://go.codemod.com/react-codemods). + +#### `react-19-migration-recipe` + +Runs all React 19 migration codemods in sequence. + +```bash +npx codemod @react-new/react-19-migration-recipe --target +``` + +See [`react-19-migration-recipe`](./codemods/jssg/react-19-migration-recipe/) for details. + +#### `use-context-hook` + +Replaces usages of `React.useContext(...)` with `React.use(...)`. + +```bash +npx codemod @react-new/use-context-hook --target +``` + +See [`use-context-hook`](./codemods/jssg/use-context-hook/) for details. + +#### `replace-act-import` + +Updates `act` import path from `react-dom/test-utils` to `react`. + +```bash +npx codemod @react-new/replace-act-import --target +``` + +See [`replace-act-import`](./codemods/jssg/replace-act-import/) for details. + +#### `replace-string-ref` + +Replaces deprecated string refs with callback refs. + +```bash +npx codemod @react-new/replace-string-ref --target +``` + +See [`replace-string-ref`](./codemods/jssg/replace-string-ref/) for details. + +#### `replace-use-form-state` + +Replaces usages of `useFormState()` with `useActionState()`. + +```bash +npx codemod @react-new/replace-use-form-state --target +``` + +See [`replace-use-form-state`](./codemods/jssg/replace-use-form-state/) for details. + +#### `replace-reactdom-render` + +Replaces usages of `ReactDOM.render()` with `createRoot(node).render()`. + +```bash +npx codemod @react-new/replace-reactdom-render --target +``` + +See [`replace-reactdom-render`](./codemods/jssg/replace-reactdom-render/) for details. + +#### `react-proptypes-to-prop-types` + +Replaces `React.PropTypes` references with the `prop-types` package and adds the appropriate import statement. + +```bash +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. + +## Development + +```bash +pnpm install +pnpm run ci +``` + +Run tests: + +```bash +pnpm test +``` + +Run type checking: + +```bash +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). diff --git a/TESTING_PLAN.md b/TESTING_PLAN.md new file mode 100644 index 0000000..962d1e5 --- /dev/null +++ b/TESTING_PLAN.md @@ -0,0 +1,207 @@ +# Side-by-Side Codemod Testing Plan + +> Testing `@react-new/*` JSSG codemods vs `reactjs/react-codemod` jscodeshift counterparts on real-world open-source repos. + +## 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. + +**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. + +--- + +## Recommended Test Repos + +### Tier 1 — Multi-Pattern Repos (highest value) + +| Repo | Stars | React Version | Patterns Found | Language | +|------|-------|---------------|----------------|----------| +| **[youzan/zent](https://github.com/youzan/zent)** | 2.2k | 17.0.x (dev), ^17 (peer) | `ReactDOM.render` (~398), `react-dom/test-utils` (~17), `useContext` | TypeScript 65% | +| **[salesforce/design-system-react](https://github.com/salesforce/design-system-react)** | ~1k | ^17.0.2 (dev), >=16.8 (peer) | `ReactDOM.render` (~315), `useContext` | JavaScript | +| **[atlassian/react-beautiful-dnd](https://github.com/atlassian/react-beautiful-dnd)** | 33k | 16.13.1 (dev), ^16.8.5‖^17‖^18 (peer) | `react-dom/test-utils` (multiple), `ReactDOM.render` | JS + Flow | +| **[calcom/cal.com](https://github.com/calcom/cal.com)** (redirects to [`calcom/cal.diy`](https://github.com/calcom/cal.diy)) | 41.4k | 18.2.0 in `apps/web`, `^18 || ^19` across workspace packages | `useContext` (~47 raw refs, 30 actual codemod hits), `react-dom/test-utils` (1) | TypeScript | + +### Tier 2 — Single-Pattern Repos (well-known, good signal) + +| Repo | React Version | Primary Pattern | Match Count | +|------|---------------|-----------------|-------------| +| **[MetaMask/metamask-extension](https://github.com/MetaMask/metamask-extension)** | 13.1k stars, React ^16.12.0 | `react-dom/test-utils` | ~18 | +| **[rsuite/rsuite](https://github.com/rsuite/rsuite)** | React ^19.0.0 (dev), >=18 (peer) | `ReactDOM.render` | ~776 | +| **[OnsenUI/OnsenUI](https://github.com/OnsenUI/OnsenUI)** (react-onsenui) | React ^18 (peer) | `react-dom/test-utils` | ~37 | + +### Tier 3 — Legacy Pattern Repos (older code, needed for PropTypes + string refs) + +| Repo | React Version | Patterns Found | Notes | +|------|---------------|----------------|-------| +| **[nylas/nylas-mail](https://github.com/nylas/nylas-mail)** | 24.8k stars, React ~15.x (Electron) | `React.PropTypes` (~193), `this.refs.` (~41) | Archived 2017, JS + CoffeeScript | +| **[azat-co/react-quickly](https://github.com/azat-co/react-quickly)** | React ~15.x | `React.PropTypes` (~155), `this.refs.` (~120) | Book examples, highest string ref count | +| **[cockpit-project/cockpit](https://github.com/cockpit-project/cockpit)** | 11k stars, varies | `this.refs.` (~54) | Active project, Linux admin tool | + +--- + +## Per-Codemod Breakdown + +### 1. `replace-reactdom-render` — ReactDOM.render → createRoot + +**Pattern:** `ReactDOM.render(element, container)` → `createRoot(container).render(element)` + +| Repo | Matches | Notes | +|------|---------|-------| +| rsuite/rsuite | ~776 | Largest count, but already on React 19 dev | +| alibaba-fusion/next | ~657 | Alibaba component library | +| kingdee/kdesign | ~482 | React component design system | +| **youzan/zent** | ~398 | ★ React 17, TypeScript, ideal target | +| **salesforce/design-system-react** | ~315 | ★ React 17, enterprise library | +| nfl/react-helmet | ~100+ | Widely used, small scope | + +**Recommended test repo:** `youzan/zent` (React 17 + TypeScript + high match count) + +**Legacy counterpart:** `npx react-codemod react/19/replace-reactdom-render` + +--- + +### 2. `replace-act-import` — react-dom/test-utils → react + +**Pattern:** `import { act } from 'react-dom/test-utils'` → `import { act } from 'react'` + +| Repo | Matches | Notes | +|------|---------|-------| +| **OnsenUI/OnsenUI** | ~37 | ★ Highest count | +| Foundry376/Mailspring | ~23 | Electron email client | +| **MetaMask/metamask-extension** | ~18 | ★ Very well-known, React 16 | +| youzan/zent | ~17 | Also has ReactDOM.render | +| atlassian/react-beautiful-dnd | multiple | Popular DnD library | +| **calcom/cal.com** | 1 | Modern React 18/19 monorepo, verified on tag `v6.2.0` | + +**Recommended test repos:** `MetaMask/metamask-extension` (brand recognition) + `OnsenUI/OnsenUI` (highest count) + `calcom/cal.com` (modern React 18/19 spot-check) + +**Legacy counterpart:** `npx react-codemod react/19/replace-act-import` + +--- + +### 3. `react-proptypes-to-prop-types` — React.PropTypes → prop-types package + +**Pattern:** `React.PropTypes.xxx` → `import PropTypes from 'prop-types'; PropTypes.xxx` + +| Repo | Matches | Notes | +|------|---------|-------| +| jianliaoim/talk-os | ~253 | CoffeeScript + React, older | +| **nylas/nylas-mail** | ~193 | ★ Archived, also has string refs | +| **azat-co/react-quickly** | ~155 | ★ Book examples, also has string refs | +| yhat/rodeo | ~125 | Data science IDE, older | + +**Note:** This pattern only exists in React ≤15.x codebases. All recommended repos are older/archived. This is expected — React.PropTypes was removed in React 16. + +**Recommended test repo:** `nylas/nylas-mail` (well-known + has 2 patterns) + +**Legacy counterpart:** `npx react-codemod React-PropTypes-to-prop-types` + +--- + +### 4. `replace-string-ref` — ref="string" → callback ref + +**Pattern:** `ref="myRef"` in JSX inside class components → `ref={(ref) => { this.myRef = ref; }}` + +| Repo | Matches | Notes | +|------|---------|-------| +| **azat-co/react-quickly** | ~120 | ★ Highest count, also has PropTypes | +| cockpit-project/cockpit | ~54 | Active Linux admin tool | +| nicehash/whistle | ~52 | Network debugging tool | +| bitshares/bitshares-ui | ~43 | Crypto exchange UI | +| **nylas/nylas-mail** | ~41 | Also has PropTypes | +| BoostIO/BoostNote-Legacy | many | Note-taking app | +| atom/atom | ~36 | Archived text editor | + +**Note:** Like PropTypes, string refs only exist in older codebases (React ≤15.x, deprecated from 16). + +**Recommended test repo:** `azat-co/react-quickly` (highest count + PropTypes combo) + +**Legacy counterpart:** `npx react-codemod react/19/replace-string-ref` + +--- + +### 5. `use-context-hook` — useContext → use + +**Pattern:** `useContext(SomeContext)` / `React.useContext(SomeContext)` → `use(SomeContext)` + +This pattern is **ubiquitous** — virtually every React application uses `useContext`. Any of the Tier 1 repos will work. + +**Additional verified modern repo:** `calcom/cal.com` (redirects to `calcom/cal.diy`) at tag `v6.2.0` yields 30 transformable files with the current local workflow. + +**Recommended test repos:** `youzan/zent` + `salesforce/design-system-react` + `calcom/cal.com` (modern React 18/19 monorepo) + +**Legacy counterpart:** `npx react-codemod react/19/use-context-hook` + +--- + +### 6. `replace-use-form-state` — useFormState → useActionState + +**Pattern:** `import { useFormState } from 'react-dom'` → `import { useActionState } from 'react'` + +**⚠️ ZERO real-world usage found.** + +Searched grep.app for `useFormState` combined with `react-dom` across all languages. Zero matches. The only `useFormState` in the wild comes from `react-hook-form` (completely different API). + +This API was renamed to `useActionState` before it saw meaningful public adoption. Testing should rely on **synthetic fixtures only** (which are already in our test suite). + +--- + +## Recommended Testing Matrix + +| Repo | render | act-import | PropTypes | string-ref | useContext | useFormState | +|------|--------|------------|-----------|------------|------------|--------------| +| youzan/zent | ✅ ~398 | ✅ ~17 | — | — | ✅ | — | +| salesforce/design-system-react | ✅ ~315 | — | — | — | ✅ | — | +| calcom/cal.com (`v6.2.0`) | — | ✅ 1 | — | — | ✅ 30 transformable files | — | +| MetaMask/metamask-extension | — | ✅ ~18 | — | — | ✅ | — | +| nylas/nylas-mail | — | — | ✅ ~193 | ✅ ~41 | — | — | +| azat-co/react-quickly | — | — | ✅ ~155 | ✅ ~120 | — | — | +| *(synthetic fixtures)* | — | — | — | — | — | ✅ | + +**Minimum repos to cover all 5 viable codemods: 3** (zent + MetaMask + nylas-mail) + +For modern React 18/19 coverage, `calcom/cal.com` is a strong fourth repo even though it is not required for minimum pattern coverage. + +--- + +## Test Execution Plan + +### For each codemod × repo pair: + +1. **Clone the repo** at a recent tagged release +2. **Run the JSSG codemod** (`@react-new/`): + ```bash + npx codemod@latest react-new/ --target + ``` +3. **Capture the diff** → `results/jssg//.diff` +4. **Reset the repo** (`git checkout .`) +5. **Run the legacy jscodeshift codemod** (from `reactjs/react-codemod`): + ```bash + npx react-codemod + ``` +6. **Capture the diff** → `results/legacy//.diff` +7. **Compare** the two diffs: + - Files transformed (count + list) + - Transformation correctness + - Edge cases handled differently + - Performance (execution time) +8. **Document** findings per codemod + +### Metrics to capture: +- **Coverage**: How many files / instances were transformed +- **Correctness**: Do both produce valid, equivalent code +- **Edge cases**: Where do they diverge +- **Performance**: Wall-clock execution time +- **Errors**: Any crashes or partial transformations + +--- + +## Notes + +- Match counts are approximate (from grep.app searches, may include comments/docs) +- Some repos are archived/unmaintained — that's fine for testing, the code patterns are what matter +- `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 diff --git a/TESTING_REPORT.md b/TESTING_REPORT.md new file mode 100644 index 0000000..87889bf --- /dev/null +++ b/TESTING_REPORT.md @@ -0,0 +1,297 @@ +# JSSG vs jscodeshift — Side-by-Side Testing Report + +> **Goal**: Determine whether the new JSSG (ast-grep) codemods regress any intentional behavior compared to the legacy jscodeshift codemods from `reactjs/react-codemod`. + +## Test Environment + +| 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) | +| **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`) | + +--- + +## Summary + +| 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-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. + +--- + +## Detailed Findings + +### 1. `replace-reactdom-render` — **Perfect Parity** (Fixed) + +**Repo**: youzan/zent (`packages/zent/src/`) + +Both codemods now produce equivalent output across all 4 files: + +| File | JSSG | Legacy | +|------|:----:|:------:| +| `dialog/open.tsx` | ✅ | ✅ | +| `preview-image/previewImage.tsx` | ✅ | ✅ | +| `notify/Notify.tsx` | ✅ | ✅ | +| `notice/Container.tsx` | ✅ | ✅ | + +**What was fixed**: +- Added `ReactDOM.unmountComponentAtNode(container)` → `createRoot(container).unmount()` pattern (member + named import) +- Fixed indentation bug caused by UTF-8 byte offset vs JS string index mismatch in files with non-ASCII characters (e.g. Chinese comments) +- Added multi-line JSX formatting: elements spanning multiple lines are placed on a new line inside `render()` with correct re-indentation + +Example transformation (Notify.tsx): +```diff +- ReactDOM.unmountComponentAtNode(container); ++ const root2 = createRoot(container); ++ root2.unmount(); +``` + +```diff +- ReactDOM.render( +- , +- container +- ); ++ const root1 = createRoot(container); ++ root1.render( ++ ++ ); +``` + +#### Recommendation + +No action needed — regressions resolved. + +--- + +### 2. `replace-act-import` — **JSSG Outperforms** + +**Repo**: atlassian/react-beautiful-dnd (`test/`) + +| Metric | JSSG | Legacy | +|--------|:----:|:------:| +| Files transformed | **6** | 1 | + +Both codemods produce the **same correct transformation** per file: + +```diff +-import { act } from 'react-dom/test-utils'; ++import { act } from "react"; +``` + +But the legacy codemod only picks up **1 of 6 files** (`touch-sensor/click-blocking.spec.js`), while JSSG correctly finds and transforms all 6. The legacy codemod appears to have a file-matching or traversal limitation that causes it to miss files in nested test directories. + +#### Additional spot-check: `calcom/cal.com` -> `calcom/cal.diy` (`v6.2.0`, commit `1c193cc`) + +As of April 17, 2026, GitHub redirects `calcom/cal.com` to `calcom/cal.diy`. On tag `v6.2.0`, both the local JSSG workflow and the legacy registry codemod transform the same single file: + +| Metric | JSSG | Legacy | +|--------|:----:|:------:| +| Files transformed | 1 | 1 | +| Diff comparison | **Byte-identical** | — | + +File transformed: `packages/ui/components/form/color-picker/colorpicker.test.tsx` + +```diff +-import { act } from "react-dom/test-utils"; ++import { act } from "react"; +``` + +#### Recommendation + +No action needed — JSSG is strictly better here. + +--- + +### 3. `use-context-hook` — **Perfect Parity on zent; JSSG Outperforms on cal.com** + +**Repo**: youzan/zent (`packages/zent/src/`) + +| Metric | JSSG | Legacy | +|--------|:----:|:------:| +| Files transformed | 47 | 47 | +| Insertions | 104 | 104 | +| Deletions | 104 | 104 | +| Diff comparison | **Byte-identical** | — | + +Running `diff` on the two saved diffs produced **empty output**. Every file, every line, every character is identical between the two codemods. This is the gold standard result. + +Transformation pattern (applied consistently across all 47 files): + +```diff +-import { useContext } from 'react'; ++import { use } from 'react'; + ... +-const value = useContext(SomeContext); ++const value = use(SomeContext); +``` + +#### Additional spot-check: `calcom/cal.com` -> `calcom/cal.diy` (`v6.2.0`, commit `1c193cc`) + +For this add-on validation, JSSG was run from the current local workflow in this repo and legacy was run from the published `react/19/use-context-hook` package. + +| Metric | JSSG | Legacy | +|--------|:----:|:------:| +| Files transformed | **30** | 29 | +| Insertions | **58** | 55 | +| Deletions | **58** | 55 | +| Common-file diff comparison | **28 files, byte-identical** | — | + +The overlapping 28 file diffs are byte-identical. The difference comes from three file-targeting decisions: + +- JSSG-only: `apps/web/modules/users/components/UserTable/BulkActions/MassAssignAttributes.tsx` +- JSSG-only: `packages/features/embed/lib/hooks/useEmbedDialogCtx.tsx` +- Legacy-only: `apps/web/modules/notifications/components/WebPushContext.tsx` + +The two JSSG-only files contain real `useContext(...)` call sites: + +```diff +- const context = useContext(AttributesContext); ++ const context = use(AttributesContext); +``` + +```diff +- const context = useContext(EmbedDialogContext); ++ const context = use(EmbedDialogContext); +``` + +The legacy-only file does **not** contain a `useContext(...)` call; it only had an unused `useContext` import, which legacy rewrote to `use` anyway: + +```diff +-import { createContext, useContext, useEffect, useMemo, useState } from "react"; ++import { createContext, use, useEffect, useMemo, useState } from "react"; +``` + +#### Recommendation + +No action needed — zent remains byte-identical parity, and cal.com shows a safe modern-repo improvement. + +--- + +### 4. `replace-string-ref` — **JSSG Outperforms** + +**Repo**: azat-co/react-quickly (full repo) + +| Metric | JSSG | Legacy | +|--------|:----:|:------:| +| Files transformed | **5** | 0 | +| String refs replaced | **9** | 0 | + +The legacy codemod reports _"No changes were made during the codemod run"_ despite the repo containing numerous string refs in `.jsx` files. The legacy jscodeshift transform appears to have a **file extension filtering issue** — all target files are `.jsx`, and the legacy codemod seems to skip non-`.js`/`.ts` files. + +JSSG correctly transforms all instances: + +```diff +- ++ { this.refs.emailAddress = ref; }} type="text" /> +``` + +Files transformed by JSSG: `ch07/email/jsx/content.jsx`, `ch11/homework/jsx/content.jsx`, `ch12/email/email-webpack/jsx/content.jsx` (×2 refs), `ch12/email/email-webpack/source/content.jsx` (×2 refs), `ch17/message-board/source/app.jsx`. + +#### Recommendation + +No action needed — JSSG is strictly better here. + +--- + +### 5. `replace-use-form-state` — **Perfect Parity** (Fixed) + +**Repo**: synthetic fixture (no real-world repos use `useFormState` from `react-dom`) + +**Input**: +```tsx +import { useFormState } from "react-dom"; + +function Form() { + const [state, formAction] = useFormState(action, initialState); + return
{state}
; +} +``` + +| Aspect | JSSG Output | Legacy Output | +|--------|-------------|---------------| +| Rename | `useFormState` → `useActionState` ✅ | `useFormState` → `useActionState` ✅ | +| Import source | `import { useActionState } from "react"` ✅ | `import { useActionState } from "react"` ✅ | + +**What was fixed**: +- Named imports: now rewrites the import source from `"react-dom"` to `"react"` (or splits when other specifiers remain) +- Member access (`ReactDOM.useFormState`): now replaces the entire expression with `useActionState` and adds a `react` import +- Handles quote style preservation, aliases, default imports + named splits, and multiple react-dom import statements + +Example (mixed imports): +```diff +-import { createPortal, useFormState } from "react-dom"; +-import * as ReactDOM from "react-dom"; ++import { createPortal } from "react-dom"; ++import { useActionState } from "react"; ++import * as ReactDOM from "react-dom"; + ... +- const [state, formAction] = useFormState(increment, 0); ++ const [state, formAction] = useActionState(increment, 0); +- const otherState = ReactDOM.useFormState(increment, 0); ++ const otherState = useActionState(increment, 0); +``` + +#### Recommendation + +No action needed — regressions resolved. + +--- + +### 6. `react-proptypes-to-prop-types` — **No Legacy Comparison** + +**Repo**: azat-co/react-quickly (`ch13/`) + +The legacy counterpart (`React-PropTypes-to-prop-types`) is **not published on the Codemod Registry**, so no direct comparison is possible. + +The JSSG codemod works correctly on the 2 files tested: + +```diff ++const PropTypes = require('prop-types'); + ... +-React.PropTypes.object.isRequired ++PropTypes.object.isRequired +``` + +It intelligently uses `require()` syntax matching the file's existing module system (CommonJS). + +#### Recommendation + +Consider publishing the legacy transform to the registry to enable future comparison, or treat the JSSG version as the canonical implementation. + +--- + +## Action Items + +### Resolved Regressions + +All regressions found during initial testing have been fixed and retested. + +| # | Codemod | Issue | Resolution | +|---|---------|-------|------------| +| 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 | + +### 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-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 | diff --git a/codemods/jssg/react-19-migration-recipe/README.md b/codemods/jssg/react-19-migration-recipe/README.md new file mode 100644 index 0000000..fecdd48 --- /dev/null +++ b/codemods/jssg/react-19-migration-recipe/README.md @@ -0,0 +1,17 @@ +# react-19-migration-recipe + +Run all React 19 migration codemods in sequence. + +## Usage + +```bash +npx codemod @react-new/react-19-migration-recipe --target +``` + +This recipe applies the following codemods: + +1. **replace-reactdom-render** — replace `ReactDOM.render` with `createRoot(...).render(...)` +2. **replace-string-ref** — replace deprecated string refs with callback refs +3. **replace-act-import** — move `act` from `react-dom/test-utils` to `react` +4. **replace-use-form-state** — rename `useFormState` to `useActionState` +5. **use-context-hook** — replace `useContext` with `use` diff --git a/codemods/jssg/react-19-migration-recipe/codemod.yaml b/codemods/jssg/react-19-migration-recipe/codemod.yaml new file mode 100644 index 0000000..aff2ebe --- /dev/null +++ b/codemods/jssg/react-19-migration-recipe/codemod.yaml @@ -0,0 +1,19 @@ +schema_version: "1.0" + +name: "@react-new/react-19-migration-recipe" +version: "0.1.0" +description: "Run all React 19 migration codemods in sequence" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + +targets: + languages: ["tsx"] + +keywords: ["React", "migration", "recipe"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/jssg/react-19-migration-recipe/package.json b/codemods/jssg/react-19-migration-recipe/package.json new file mode 100644 index 0000000..3579557 --- /dev/null +++ b/codemods/jssg/react-19-migration-recipe/package.json @@ -0,0 +1,8 @@ +{ + "name": "@react-new/react-19-migration-recipe", + "version": "0.1.0", + "description": "Run all React 19 migration codemods in sequence", + "type": "module", + "private": true, + "license": "MIT" +} diff --git a/codemods/jssg/react-19-migration-recipe/workflow.yaml b/codemods/jssg/react-19-migration-recipe/workflow.yaml new file mode 100644 index 0000000..0d48a99 --- /dev/null +++ b/codemods/jssg/react-19-migration-recipe/workflow.yaml @@ -0,0 +1,28 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: react-19-migration + name: React 19 Migration + type: automatic + steps: + - name: "Replace ReactDOM.render with createRoot" + codemod: + source: "@react-new/replace-reactdom-render" + + - name: "Replace string refs with callback refs" + codemod: + source: "@react-new/replace-string-ref" + + - name: "Migrate act from react-dom/test-utils to react" + codemod: + source: "@react-new/replace-act-import" + + - name: "Replace useFormState with useActionState" + codemod: + source: "@react-new/replace-use-form-state" + + - name: "Replace useContext with use" + codemod: + source: "@react-new/use-context-hook" diff --git a/codemods/jssg/react-proptypes-to-prop-types/README.md b/codemods/jssg/react-proptypes-to-prop-types/README.md new file mode 100644 index 0000000..f2e6b9a --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/README.md @@ -0,0 +1,18 @@ +# react-proptypes-to-prop-types + +Replace legacy React `PropTypes` usage with the `prop-types` package across imports, requires, aliases, and destructuring patterns. + +## Usage + +```bash +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/jssg/react-proptypes-to-prop-types/codemod.yaml new file mode 100644 index 0000000..20814bf --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/codemod.yaml @@ -0,0 +1,19 @@ +schema_version: "1.0" + +name: "@react-new/react-proptypes-to-prop-types" +version: "0.1.0" +description: "Replace React.PropTypes with prop-types package" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + +targets: + languages: ["tsx"] + +keywords: ["React"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/jssg/react-proptypes-to-prop-types/package.json b/codemods/jssg/react-proptypes-to-prop-types/package.json new file mode 100644 index 0000000..3eb93eb --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/package.json @@ -0,0 +1,18 @@ +{ + "name": "@react-new/react-proptypes-to-prop-types", + "version": "0.1.0", + "description": "Replace React.PropTypes with prop-types package", + "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/react-proptypes-to-prop-types/scripts/codemod.ts b/codemods/jssg/react-proptypes-to-prop-types/scripts/codemod.ts new file mode 100644 index 0000000..6a91929 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/scripts/codemod.ts @@ -0,0 +1,337 @@ +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 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 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 statementRangeWithLeadingLineComments( + node: SgNode, + source: string, +): { start: number; end: number } { + const range = statementRange(node, source); + let start = range.start; + + while (start > 0) { + const lineEnd = start; + let lineStart = source.lastIndexOf("\n", lineEnd - 2); + lineStart = lineStart === -1 ? 0 : lineStart + 1; + const line = source.slice(lineStart, lineEnd).trim(); + if (line === "" || line.startsWith("//")) { + start = lineStart; + continue; + } + break; + } + + return { start, end: range.end }; +} + +function removeNodeWithComma(node: SgNode, source: string): Edit { + let start = node.range().start.index; + let end = node.range().end.index; + + let i = end; + while (isWhitespace(source[i])) i++; + if (source[i] === ",") { + end = i + 1; + while (isWhitespace(source[end])) end++; + } else { + let j = start - 1; + while (isWhitespace(source[j])) j--; + if (source[j] === ",") start = j; + } + + return { startPos: start, endPos: end, insertedText: "" }; +} + +function nearestStatement(node: SgNode): SgNode | null { + return node.ancestors().find((a) => + a.kind() === "lexical_declaration" || + a.kind() === "variable_declaration" || + a.kind() === "import_statement" + ) ?? null; +} + +function isRequireCall(node: SgNode | null, moduleNames: Set): boolean { + if (!node || node.kind() !== "call_expression") return false; + if (node.field("function")?.text() !== "require") return false; + const arg = node.field("arguments")?.find({ rule: { kind: "string" } }); + const source = arg ? sourceText(arg) : null; + return !!source && moduleNames.has(source); +} + +function importSource(node: SgNode): string | null { + const source = node.field("source") ?? node.find({ rule: { kind: "string" } }); + return source ? sourceText(source) : null; +} + +function hasImportOrRequire(rootNode: SgNode, moduleName: string): boolean { + const modules = new Set([moduleName]); + return rootNode.findAll({ rule: { kind: "import_statement" } }) + .some((node) => importSource(node) === moduleName) || + rootNode.findAll({ rule: { kind: "call_expression" } }) + .some((node) => isRequireCall(node, modules)); +} + +function usesImportSyntax(rootNode: SgNode): boolean { + return rootNode.findAll({ rule: { kind: "import_statement" } }) + .some((node) => !node.text().startsWith("import type")); +} + +function usesVarForRequires(rootNode: SgNode): boolean { + return rootNode.find({ rule: { kind: "lexical_declaration" } }) === null; +} + +function firstSortedImportPosition(rootNode: SgNode, moduleName: string): number { + const imports = rootNode.findAll({ rule: { kind: "import_statement" } }); + const lowerModule = moduleName.toLowerCase(); + let target: SgNode | null = null; + let targetName = ""; + + for (const imp of imports) { + const source = importSource(imp); + if (!source) continue; + const lowerSource = source.toLowerCase(); + if (lowerSource > lowerModule && (!target || lowerSource < targetName)) { + target = imp; + targetName = lowerSource; + } + } + + if (target) return target.range().start.index; + const last = imports[imports.length - 1]; + return last ? last.range().end.index : 0; +} + +function firstSortedRequirePosition(rootNode: SgNode, moduleName: string): number { + const lowerModule = moduleName.toLowerCase(); + let target: SgNode | null = null; + let targetName = ""; + + for (const call of rootNode.findAll({ rule: { kind: "call_expression" } })) { + if (call.field("function")?.text() !== "require") continue; + const arg = call.field("arguments")?.find({ rule: { kind: "string" } }); + const source = arg ? sourceText(arg) : null; + if (!source) continue; + const lowerSource = source.toLowerCase(); + if (lowerSource > lowerModule && (!target || lowerSource < targetName)) { + const stmt = nearestStatement(call); + if (stmt) { + target = stmt; + targetName = lowerSource; + } + } + } + + if (target) return target.range().start.index; + return 0; +} + +function propTypesBindingFromModule(rootNode: SgNode, moduleName: string): string | null { + for (const imp of rootNode.findAll({ rule: { kind: "import_statement" } })) { + if (importSource(imp) !== moduleName) continue; + const clause = imp.find({ rule: { kind: "import_clause" } }); + const defaultName = clause?.children().find((c) => c.kind() === "identifier"); + if (defaultName) return defaultName.text(); + const named = imp.findAll({ rule: { kind: "import_specifier" } }) + .find((spec) => spec.field("name")?.text() === "PropTypes"); + if (named && !named.field("alias")) return "PropTypes"; + } + + for (const decl of rootNode.findAll({ rule: { kind: "variable_declarator" } })) { + const value = decl.field("value"); + if (!isRequireCall(value, new Set([moduleName]))) continue; + const name = decl.field("name"); + if (name?.kind() === "identifier") return name.text(); + } + + return null; +} + +function identifierNamesInPattern(pattern: SgNode): string[] { + return pattern.children() + .filter((child) => + child.kind() === "shorthand_property_identifier_pattern" || + child.kind() === "pair_pattern" + ) + .map((child) => { + const alias = child.field("value"); + if (alias?.kind() === "identifier") return alias.text(); + return child.field("key")?.text() ?? child.text(); + }); +} + +function removeNamedImportsAfterDefault(namedImports: SgNode, source: string): Edit { + let start = namedImports.range().start.index; + let i = start - 1; + while (isWhitespace(source[i])) i--; + if (source[i] === ",") start = i; + return { startPos: start, endPos: namedImports.range().end.index, insertedText: "" }; +} + +const transform: Transform = async (root, options) => { + const rootNode = root.root(); + const source = rootNode.text(); + const edits: Edit[] = []; + const metric = useMetricAtom("react-proptypes-migrations"); + const moduleName = (options.params?.["module-name"] as string | undefined) ?? "prop-types"; + const reactModules = new Set(["react", "React"]); + const reactNames = new Set(["React"]); + let localPropTypesName = propTypesBindingFromModule(rootNode, moduleName) ?? "PropTypes"; + let addedPropTypesImportByReplacement = false; + let changed = false; + + for (const imp of rootNode.findAll({ rule: { kind: "import_statement" } })) { + const sourceName = importSource(imp); + if (sourceName && reactModules.has(sourceName)) { + const clause = imp.find({ rule: { kind: "import_clause" } }); + const defaultName = clause?.children().find((c) => c.kind() === "identifier"); + if (defaultName) reactNames.add(defaultName.text()); + } + } + + for (const decl of rootNode.findAll({ rule: { kind: "variable_declarator" } })) { + const value = decl.field("value"); + const name = decl.field("name"); + if (name?.kind() === "identifier" && isRequireCall(value, reactModules)) { + reactNames.add(name.text()); + } + } + + for (const spec of rootNode.findAll({ rule: { kind: "import_specifier" } })) { + const parentImport = spec.ancestors().find((a) => a.kind() === "import_statement"); + if (!parentImport || !reactModules.has(importSource(parentImport) ?? "")) continue; + if (spec.field("name")?.text() !== "PropTypes") continue; + localPropTypesName = spec.field("alias")?.text() ?? "PropTypes"; + const namedImports = spec.ancestors().find((a) => a.kind() === "named_imports"); + const specifierCount = namedImports?.findAll({ rule: { kind: "import_specifier" } }).length ?? 0; + const importClause = parentImport.find({ rule: { kind: "import_clause" } }); + const hasDefault = importClause?.children().some((c) => c.kind() === "identifier") ?? false; + if (specifierCount === 1 && !hasDefault) { + if (!hasImportOrRequire(rootNode, moduleName)) { + edits.push(parentImport.replace(`import ${localPropTypesName} from '${moduleName}';`)); + addedPropTypesImportByReplacement = true; + } else { + const { start, end } = statementRange(parentImport, source); + edits.push({ startPos: start, endPos: end, insertedText: "" }); + } + } else if (specifierCount === 1 && hasDefault && namedImports) { + edits.push(removeNamedImportsAfterDefault(namedImports, source)); + } else { + edits.push(removeNodeWithComma(spec, source)); + } + changed = true; + } + + for (const pattern of rootNode.findAll({ rule: { kind: "object_pattern" } })) { + const decl = pattern.ancestors().find((a) => a.kind() === "variable_declarator"); + const statement = decl ? nearestStatement(decl) : null; + const value = decl?.field("value"); + if (!decl || !statement) continue; + const destructuresReact = + (value?.kind() === "identifier" && reactNames.has(value.text())) || + isRequireCall(value ?? null, reactModules); + if (!destructuresReact) continue; + + const props = pattern.children().filter((child) => + child.kind() === "shorthand_property_identifier_pattern" || + child.kind() === "pair_pattern" + ); + const propTypesProp = props.find((prop) => + prop.text() === "PropTypes" || prop.field("key")?.text() === "PropTypes" + ); + if (!propTypesProp) continue; + + const nestedPattern = propTypesProp.field("value"); + if (nestedPattern?.kind() === "object_pattern") { + const childNames = identifierNamesInPattern(nestedPattern); + if (childNames.length > 0) { + edits.push({ + startPos: statement.range().start.index, + endPos: statement.range().start.index, + insertedText: `const { ${childNames.join(", ")} } = ${localPropTypesName};\n`, + }); + } + } else { + const alias = propTypesProp.field("value"); + if (alias?.kind() === "identifier") localPropTypesName = alias.text(); + } + + if (props.length === 1) { + const { start, end } = statementRange(statement, source); + edits.push({ startPos: start, endPos: end, insertedText: "" }); + } else { + edits.push(removeNodeWithComma(propTypesProp, source)); + } + changed = true; + } + + for (const member of rootNode.findAll({ rule: { kind: "member_expression" } })) { + const object = member.field("object"); + const property = member.field("property"); + if (!object || !property || property.text() !== "PropTypes") continue; + if (object.kind() !== "identifier" || !reactNames.has(object.text())) continue; + + const declarator = member.ancestors().find((a) => a.kind() === "variable_declarator"); + const name = declarator?.field("name"); + if (declarator && name?.kind() === "identifier" && name.text() === localPropTypesName) { + const statement = nearestStatement(declarator); + if (statement) { + const { start, end } = statementRangeWithLeadingLineComments(statement, source); + edits.push({ startPos: start, endPos: end, insertedText: "" }); + } + } else { + edits.push(member.replace(localPropTypesName)); + } + changed = true; + metric.increment({ file: metricFile(root.filename()) }); + } + + if (!changed) return null; + + if (!addedPropTypesImportByReplacement && !hasImportOrRequire(rootNode, moduleName)) { + if (usesImportSyntax(rootNode)) { + const pos = firstSortedImportPosition(rootNode, moduleName); + edits.push({ + startPos: pos, + endPos: pos, + insertedText: `import ${localPropTypesName} from '${moduleName}';\n`, + + }); + } else { + const pos = firstSortedRequirePosition(rootNode, moduleName); + const keyword = usesVarForRequires(rootNode) ? "var" : "const"; + edits.push({ + startPos: pos, + endPos: pos, + insertedText: `${keyword} ${localPropTypesName} = require('${moduleName}');\n`, + }); + } + } + + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-import/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-import/expected.tsx new file mode 100644 index 0000000..1ab2a24 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-import/expected.tsx @@ -0,0 +1,5 @@ +import PropTypes from 'prop-types'; +import React from 'react'; + +const object = PropTypes.object; +const string = PropTypes.string; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-import/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-import/input.tsx new file mode 100644 index 0000000..36366cc --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-import/input.tsx @@ -0,0 +1,5 @@ +import PropTypes from 'prop-types'; +import React from 'react'; + +const object = PropTypes.object; +const string = React.PropTypes.string; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-import/metrics.json b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-import/metrics.json new file mode 100644 index 0000000..f6d3564 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-import/metrics.json @@ -0,0 +1,10 @@ +{ + "react-proptypes-migrations": [ + { + "cardinality": { + "file": "tests/already-migrated-import/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/expected.tsx new file mode 100644 index 0000000..61771ed --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/expected.tsx @@ -0,0 +1,5 @@ +import { PropTypes as PT } from 'prop-types'; +import React from 'react'; + +const object = PropTypes.object; +const string = PropTypes.string; diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/input.tsx new file mode 100644 index 0000000..53b00e1 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/input.tsx @@ -0,0 +1,5 @@ +import { PropTypes as PT } from 'prop-types'; +import React from 'react'; + +const object = PropTypes.object; +const string = React.PropTypes.string; diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/metrics.json b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/metrics.json new file mode 100644 index 0000000..ff7379a --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-as-import/metrics.json @@ -0,0 +1,10 @@ +{ + "react-proptypes-migrations": [ + { + "cardinality": { + "file": "tests/already-migrated-named-as-import/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-import/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-import/expected.tsx new file mode 100644 index 0000000..d6a9a96 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-import/expected.tsx @@ -0,0 +1,5 @@ +import { PropTypes } from 'prop-types'; +import React from 'react'; + +const object = PropTypes.object; +const string = PropTypes.string; diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-import/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-import/input.tsx new file mode 100644 index 0000000..2cd97a1 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-import/input.tsx @@ -0,0 +1,5 @@ +import { PropTypes } from 'prop-types'; +import React from 'react'; + +const object = PropTypes.object; +const string = React.PropTypes.string; diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-import/metrics.json b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-import/metrics.json new file mode 100644 index 0000000..5cb2111 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-named-import/metrics.json @@ -0,0 +1,10 @@ +{ + "react-proptypes-migrations": [ + { + "cardinality": { + "file": "tests/already-migrated-named-import/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-require/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-require/expected.tsx new file mode 100644 index 0000000..9726ce1 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-require/expected.tsx @@ -0,0 +1,5 @@ +const PropTypes = require('prop-types'); +const React = require('React'); + +const object = PropTypes.object; +const string = PropTypes.string; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-require/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-require/input.tsx new file mode 100644 index 0000000..f4eb188 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-require/input.tsx @@ -0,0 +1,5 @@ +const PropTypes = require('prop-types'); +const React = require('React'); + +const object = PropTypes.object; +const string = React.PropTypes.string; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-require/metrics.json b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-require/metrics.json new file mode 100644 index 0000000..3eda7ec --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/already-migrated-require/metrics.json @@ -0,0 +1,10 @@ +{ + "react-proptypes-migrations": [ + { + "cardinality": { + "file": "tests/already-migrated-require/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-from-react-var/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-from-react-var/expected.tsx new file mode 100644 index 0000000..7071006 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-from-react-var/expected.tsx @@ -0,0 +1,5 @@ +const PropTypes = require('prop-types'); +const React = require('react'); +// This should remain, modified +const string = PropTypes.string; +const bool = PropTypes.bool; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-from-react-var/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-from-react-var/input.tsx new file mode 100644 index 0000000..03ff11b --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-from-react-var/input.tsx @@ -0,0 +1,9 @@ +const React = require('react'); + +// These are no longer needed after the transform +const PropTypes = React.PropTypes; +const {PropTypes} = React; + +// This should remain, modified +const string = React.PropTypes.string; +const bool = PropTypes.bool; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-from-react-var/metrics.json b/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-from-react-var/metrics.json new file mode 100644 index 0000000..fe7592c --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-from-react-var/metrics.json @@ -0,0 +1,10 @@ +{ + "react-proptypes-migrations": [ + { + "cardinality": { + "file": "tests/assigned-from-react-var/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/expected.tsx new file mode 100644 index 0000000..24e0ce0 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/expected.tsx @@ -0,0 +1,6 @@ +const PropTypes = require('prop-types'); +const React = require('react'); + +const ReactPropTypes = PropTypes; +const foo = ReactPropTypes.string; +const bar = PropTypes.string; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/input.tsx new file mode 100644 index 0000000..55bdf39 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/input.tsx @@ -0,0 +1,7 @@ +const React = require('react'); + +const ReactPropTypes = React.PropTypes; +const PropTypes = React.PropTypes; + +const foo = ReactPropTypes.string; +const bar = PropTypes.string; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/metrics.json b/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/metrics.json new file mode 100644 index 0000000..45447b2 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/assigned-to-var-with-different-name/metrics.json @@ -0,0 +1,10 @@ +{ + "react-proptypes-migrations": [ + { + "cardinality": { + "file": "tests/assigned-to-var-with-different-name/input.tsx" + }, + "count": 2 + } + ] +} \ 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/jssg/react-proptypes-to-prop-types/tests/default-and-named-import/expected.tsx new file mode 100644 index 0000000..ba61a76 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/default-and-named-import/expected.tsx @@ -0,0 +1,18 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; + +class ClassComponent extends Component { + static propTypes = { + text: PropTypes.string.isRequired, + }; + render() { + return
{this.props.text}
; + } +} + +function FunctionalComponent (props) { + return
{props.text}
; +} +FunctionalComponent.propTypes = { + text: PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/default-and-named-import/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/default-and-named-import/input.tsx new file mode 100644 index 0000000..466c1e2 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/default-and-named-import/input.tsx @@ -0,0 +1,17 @@ +import React, { Component, PropTypes } from 'react'; + +class ClassComponent extends Component { + static propTypes = { + text: PropTypes.string.isRequired, + }; + render() { + return
{this.props.text}
; + } +} + +function FunctionalComponent (props) { + return
{props.text}
; +} +FunctionalComponent.propTypes = { + text: PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/default-import/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/default-import/expected.tsx new file mode 100644 index 0000000..15e1bf3 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/default-import/expected.tsx @@ -0,0 +1,22 @@ +import Abc from 'abc'; +import PropTypes from 'prop-types'; +import React from 'React'; +import Xzy from 'xyz'; + +class ClassComponent extends React.Component { + static propTypes = { + text: PropTypes.string.isRequired, + }; + render() { + const { text, ...rest } = this.props; + return {text}; + } +} + +function FunctionalComponent (props) { + const { text, ...rest } = props; + return {text}; +} +FunctionalComponent.propTypes = { + text: PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/default-import/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/default-import/input.tsx new file mode 100644 index 0000000..0ffc10a --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/default-import/input.tsx @@ -0,0 +1,21 @@ +import Abc from 'abc'; +import React from 'React'; +import Xzy from 'xyz'; + +class ClassComponent extends React.Component { + static propTypes = { + text: React.PropTypes.string.isRequired, + }; + render() { + const { text, ...rest } = this.props; + return {text}; + } +} + +function FunctionalComponent (props) { + const { text, ...rest } = props; + return {text}; +} +FunctionalComponent.propTypes = { + text: React.PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/default-import/metrics.json b/codemods/jssg/react-proptypes-to-prop-types/tests/default-import/metrics.json new file mode 100644 index 0000000..8af3ca6 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/default-import/metrics.json @@ -0,0 +1,10 @@ +{ + "react-proptypes-migrations": [ + { + "cardinality": { + "file": "tests/default-import/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/destructured-proptypes-import/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/destructured-proptypes-import/expected.tsx new file mode 100644 index 0000000..75133a7 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/destructured-proptypes-import/expected.tsx @@ -0,0 +1,5 @@ +import PropTypes from 'prop-types'; + +export default PropTypes.shape({ + id: PropTypes.string, +}) diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/destructured-proptypes-import/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/destructured-proptypes-import/input.tsx new file mode 100644 index 0000000..48b1fa7 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/destructured-proptypes-import/input.tsx @@ -0,0 +1,5 @@ +import { PropTypes } from 'react'; + +export default PropTypes.shape({ + id: PropTypes.string, +}) diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/import-alias/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/import-alias/expected.tsx new file mode 100644 index 0000000..317f7e5 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/import-alias/expected.tsx @@ -0,0 +1,8 @@ +import PT from 'prop-types'; +import React, { Component } from 'react'; + +class ClassComponent extends Component { + static propTypes = { + foo: PT.string.isRequired, + } +} diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/import-alias/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/import-alias/input.tsx new file mode 100644 index 0000000..61ec4d1 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/import-alias/input.tsx @@ -0,0 +1,7 @@ +import React, { Component, PropTypes as PT } from 'react'; + +class ClassComponent extends Component { + static propTypes = { + foo: PT.string.isRequired, + } +} diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/import-flow-type-with-require/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/import-flow-type-with-require/expected.tsx new file mode 100644 index 0000000..9c5701c --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/import-flow-type-with-require/expected.tsx @@ -0,0 +1,4 @@ +const PropTypes = require('prop-types'); +const React = require('React'); + +import type {SomeType} from 'SomeModule'; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/import-flow-type-with-require/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/import-flow-type-with-require/input.tsx new file mode 100644 index 0000000..03286f9 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/import-flow-type-with-require/input.tsx @@ -0,0 +1,5 @@ +const React = require('React'); + +const {PropTypes} = React; + +import type {SomeType} from 'SomeModule'; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require-2/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require-2/expected.tsx new file mode 100644 index 0000000..3845188 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require-2/expected.tsx @@ -0,0 +1,14 @@ +'use strict'; + +import type { Foo } from 'Foo'; + +const PropTypes = require('prop-types'); +const React = require('React'); + +class Component extends React.PureComponent { + render() { + return
; + } +} + +module.exports = Component; diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require-2/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require-2/input.tsx new file mode 100644 index 0000000..053b76b --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require-2/input.tsx @@ -0,0 +1,15 @@ +'use strict'; + +import type { Foo } from 'Foo'; + +const React = require('React'); + +const {PropTypes} = React; + +class Component extends React.PureComponent { + render() { + return
; + } +} + +module.exports = Component; diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require/expected.tsx new file mode 100644 index 0000000..8787e82 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require/expected.tsx @@ -0,0 +1,10 @@ +import PropTypes from 'prop-types'; +import React from 'react'; + +require('styles.css'); + +class MyClass extends React.Component {} + +MyClass.propTypes = { + foo: PropTypes.string +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require/input.tsx new file mode 100644 index 0000000..fba7ee3 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require/input.tsx @@ -0,0 +1,9 @@ +import React from 'react'; + +require('styles.css'); + +class MyClass extends React.Component {} + +MyClass.propTypes = { + foo: React.PropTypes.string +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require/metrics.json b/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require/metrics.json new file mode 100644 index 0000000..7402b04 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/mixed-import-and-require/metrics.json @@ -0,0 +1,10 @@ +{ + "react-proptypes-migrations": [ + { + "cardinality": { + "file": "tests/mixed-import-and-require/input.tsx" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/module-name/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/module-name/expected.tsx new file mode 100644 index 0000000..e78e65e --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/module-name/expected.tsx @@ -0,0 +1,18 @@ +const PropTypes = require('PropTypes'); +const React = require('React'); + +class ClassComponent extends React.Component { + static propTypes = { + text: PropTypes.string.isRequired, + }; + render() { + return
{this.props.text}
; + } +} + +function FunctionalComponent (props) { + return
{props.text}
; +} +FunctionalComponent.propTypes = { + text: PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/module-name/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/module-name/input.tsx new file mode 100644 index 0000000..b0bd1ef --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/module-name/input.tsx @@ -0,0 +1,17 @@ +const React = require('React'); + +class ClassComponent extends React.Component { + static propTypes = { + text: React.PropTypes.string.isRequired, + }; + render() { + return
{this.props.text}
; + } +} + +function FunctionalComponent (props) { + return
{props.text}
; +} +FunctionalComponent.propTypes = { + text: React.PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/module-name/metrics.json b/codemods/jssg/react-proptypes-to-prop-types/tests/module-name/metrics.json new file mode 100644 index 0000000..1d5d4ab --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/module-name/metrics.json @@ -0,0 +1,10 @@ +{ + "react-proptypes-migrations": [ + { + "cardinality": { + "file": "tests/module-name/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/module-name/test.config.json b/codemods/jssg/react-proptypes-to-prop-types/tests/module-name/test.config.json new file mode 100644 index 0000000..79a5de0 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/module-name/test.config.json @@ -0,0 +1,5 @@ +{ + "params": { + "module-name": "PropTypes" + } +} diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/named-parameters/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/named-parameters/expected.tsx new file mode 100644 index 0000000..7e1fd2f --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/named-parameters/expected.tsx @@ -0,0 +1,21 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import classnames from 'classnames'; + +const propTypes = { + children: PropTypes.node, + className: PropTypes.string, +}; + +const Accordion = ({ children, className }) => { + const classNames = classnames('bx--accordion', className); + return ( +
    + {children} +
+ ); +}; + +Accordion.propTypes = propTypes; + +export default Accordion; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/named-parameters/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/named-parameters/input.tsx new file mode 100644 index 0000000..d316f50 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/named-parameters/input.tsx @@ -0,0 +1,20 @@ +import React, { PropTypes } from 'react'; +import classnames from 'classnames'; + +const propTypes = { + children: PropTypes.node, + className: PropTypes.string, +}; + +const Accordion = ({ children, className }) => { + const classNames = classnames('bx--accordion', className); + return ( +
    + {children} +
+ ); +}; + +Accordion.propTypes = propTypes; + +export default Accordion; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/nested-destructured-proptypes-import/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/nested-destructured-proptypes-import/expected.tsx new file mode 100644 index 0000000..bc39982 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/nested-destructured-proptypes-import/expected.tsx @@ -0,0 +1,7 @@ +import PropTypes from 'prop-types'; +import React from 'react'; + +const { shape, bool, number, string } = PropTypes; +const { + Component, + } = React; diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/nested-destructured-proptypes-import/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/nested-destructured-proptypes-import/input.tsx new file mode 100644 index 0000000..27d7543 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/nested-destructured-proptypes-import/input.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const { + Component, + PropTypes: { + shape, + bool, + number, + string, + }, +} = React; diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/no-change-import/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/no-change-import/expected.tsx new file mode 100644 index 0000000..0ed892a --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/no-change-import/expected.tsx @@ -0,0 +1,18 @@ +import PropTypes from 'prop-types'; +import React from 'React'; + +class ClassComponent extends React.Component { + static propTypes = { + text: PropTypes.string.isRequired, + }; + render() { + return
{this.props.text}
; + } +} + +function FunctionalComponent (props) { + return
{props.text}
; +} +FunctionalComponent.propTypes = { + text: PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/no-change-import/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/no-change-import/input.tsx new file mode 100644 index 0000000..0ed892a --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/no-change-import/input.tsx @@ -0,0 +1,18 @@ +import PropTypes from 'prop-types'; +import React from 'React'; + +class ClassComponent extends React.Component { + static propTypes = { + text: PropTypes.string.isRequired, + }; + render() { + return
{this.props.text}
; + } +} + +function FunctionalComponent (props) { + return
{props.text}
; +} +FunctionalComponent.propTypes = { + text: PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/no-change-require/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/no-change-require/expected.tsx new file mode 100644 index 0000000..463e982 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/no-change-require/expected.tsx @@ -0,0 +1,18 @@ +const PropTypes = require('prop-types'); +const React = require('React'); + +class ClassComponent extends React.Component { + static propTypes = { + text: PropTypes.string.isRequired, + }; + render() { + return
{this.props.text}
; + } +} + +function FunctionalComponent (props) { + return
{props.text}
; +} +FunctionalComponent.propTypes = { + text: PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/no-change-require/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/no-change-require/input.tsx new file mode 100644 index 0000000..463e982 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/no-change-require/input.tsx @@ -0,0 +1,18 @@ +const PropTypes = require('prop-types'); +const React = require('React'); + +class ClassComponent extends React.Component { + static propTypes = { + text: PropTypes.string.isRequired, + }; + render() { + return
{this.props.text}
; + } +} + +function FunctionalComponent (props) { + return
{props.text}
; +} +FunctionalComponent.propTypes = { + text: PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require-alias/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/require-alias/expected.tsx new file mode 100644 index 0000000..0a7e190 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/require-alias/expected.tsx @@ -0,0 +1,9 @@ +const PT = require('prop-types'); +const React = require('react'); +const { Component } = React; + +class ClassComponent extends Component { + static propTypes = { + foo: PT.string.isRequired, + } +} diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require-alias/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/require-alias/input.tsx new file mode 100644 index 0000000..3d4f7b3 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/require-alias/input.tsx @@ -0,0 +1,8 @@ +const React = require('react'); +const { Component, PropTypes: PT } = React; + +class ClassComponent extends Component { + static propTypes = { + foo: PT.string.isRequired, + } +} diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-direct/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-direct/expected.tsx new file mode 100644 index 0000000..b8672d9 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-direct/expected.tsx @@ -0,0 +1,8 @@ +const PropTypes = require('prop-types'); +const React = require('react'); +function Foo(props) { + return
{props.text}
; +} +Foo.propTypes = { + text: PropTypes.string.isRequired, +}; diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-direct/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-direct/input.tsx new file mode 100644 index 0000000..4fde2f4 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-direct/input.tsx @@ -0,0 +1,9 @@ +const React = require('react'); +const { PropTypes } = require('react'); + +function Foo(props) { + return
{props.text}
; +} +Foo.propTypes = { + text: PropTypes.string.isRequired, +}; diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-multi/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-multi/expected.tsx new file mode 100644 index 0000000..5491a87 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-multi/expected.tsx @@ -0,0 +1,19 @@ +const PropTypes = require('prop-types'); +const React = require('react'); +const { Component } = React; + +class ClassComponent extends Component { + static propTypes = { + text: PropTypes.string.isRequired, + }; + render() { + return
{this.props.text}
; + } +} + +function FunctionalComponent (props) { + return
{props.text}
; +} +FunctionalComponent.propTypes = { + text: PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-multi/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-multi/input.tsx new file mode 100644 index 0000000..2db70ad --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-multi/input.tsx @@ -0,0 +1,18 @@ +const React = require('react'); +const { Component, PropTypes } = React; + +class ClassComponent extends Component { + static propTypes = { + text: PropTypes.string.isRequired, + }; + render() { + return
{this.props.text}
; + } +} + +function FunctionalComponent (props) { + return
{props.text}
; +} +FunctionalComponent.propTypes = { + text: PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-only/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-only/expected.tsx new file mode 100644 index 0000000..3d1d184 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-only/expected.tsx @@ -0,0 +1,20 @@ +const Abc = require('abc'); +const PropTypes = require('prop-types'); +const React = require('react'); +const Xyz = require('xyz'); + +class ClassComponent extends React.Component { + static propTypes = { + text: PropTypes.string.isRequired, + }; + render() { + return {this.props.text}; + } +} + +function FunctionalComponent (props) { + return {props.text}; +} +FunctionalComponent.propTypes = { + text: PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-only/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-only/input.tsx new file mode 100644 index 0000000..3bf0664 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/require-destructured-only/input.tsx @@ -0,0 +1,20 @@ +const Abc = require('abc'); +const React = require('react'); +const { PropTypes } = React; +const Xyz = require('xyz'); + +class ClassComponent extends React.Component { + static propTypes = { + text: PropTypes.string.isRequired, + }; + render() { + return {this.props.text}; + } +} + +function FunctionalComponent (props) { + return {props.text}; +} +FunctionalComponent.propTypes = { + text: PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/require/expected.tsx new file mode 100644 index 0000000..463e982 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/require/expected.tsx @@ -0,0 +1,18 @@ +const PropTypes = require('prop-types'); +const React = require('React'); + +class ClassComponent extends React.Component { + static propTypes = { + text: PropTypes.string.isRequired, + }; + render() { + return
{this.props.text}
; + } +} + +function FunctionalComponent (props) { + return
{props.text}
; +} +FunctionalComponent.propTypes = { + text: PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/require/input.tsx new file mode 100644 index 0000000..b0bd1ef --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/require/input.tsx @@ -0,0 +1,17 @@ +const React = require('React'); + +class ClassComponent extends React.Component { + static propTypes = { + text: React.PropTypes.string.isRequired, + }; + render() { + return
{this.props.text}
; + } +} + +function FunctionalComponent (props) { + return
{props.text}
; +} +FunctionalComponent.propTypes = { + text: React.PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/require/metrics.json b/codemods/jssg/react-proptypes-to-prop-types/tests/require/metrics.json new file mode 100644 index 0000000..e73a0dd --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/require/metrics.json @@ -0,0 +1,10 @@ +{ + "react-proptypes-migrations": [ + { + "cardinality": { + "file": "tests/require/input.tsx" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/with-top-comment/expected.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/with-top-comment/expected.tsx new file mode 100644 index 0000000..8ab3354 --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/with-top-comment/expected.tsx @@ -0,0 +1,12 @@ +/** + * This is some multiline comment + */ +import PropTypes from 'prop-types'; +import React from 'react'; + +function FunctionalComponent (props) { + return
{props.text}
; +} +FunctionalComponent.propTypes = { + text: PropTypes.string.isRequired, +}; diff --git a/codemods/jssg/react-proptypes-to-prop-types/tests/with-top-comment/input.tsx b/codemods/jssg/react-proptypes-to-prop-types/tests/with-top-comment/input.tsx new file mode 100644 index 0000000..a1adccf --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/tests/with-top-comment/input.tsx @@ -0,0 +1,11 @@ +/** + * This is some multiline comment + */ +import React, { PropTypes } from 'react'; + +function FunctionalComponent (props) { + return
{props.text}
; +} +FunctionalComponent.propTypes = { + text: PropTypes.string.isRequired, +}; diff --git a/codemods/jssg/react-proptypes-to-prop-types/tsconfig.json b/codemods/jssg/react-proptypes-to-prop-types/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/jssg/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/jssg/react-proptypes-to-prop-types/workflow.yaml new file mode 100644 index 0000000..c39db7c --- /dev/null +++ b/codemods/jssg/react-proptypes-to-prop-types/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: + module-name: + name: "Module Name" + description: "Module specifier to use for PropTypes imports and requires." + type: string + default: "prop-types" + +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/jssg/replace-act-import/README.md new file mode 100644 index 0000000..6406bed --- /dev/null +++ b/codemods/jssg/replace-act-import/README.md @@ -0,0 +1,16 @@ +# replace-act-import + +Move `act` usage from `react-dom/test-utils` to `react` across named imports, namespace imports, and re-exports. + +## Usage + +```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/jssg/replace-act-import/codemod.yaml new file mode 100644 index 0000000..7e3a99e --- /dev/null +++ b/codemods/jssg/replace-act-import/codemod.yaml @@ -0,0 +1,19 @@ +schema_version: "1.0" + +name: "@react-new/replace-act-import" +version: "0.1.0" +description: "Migrate act() from react-dom/test-utils to react" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + +targets: + languages: ["tsx"] + +keywords: ["React"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/jssg/replace-act-import/package.json b/codemods/jssg/replace-act-import/package.json new file mode 100644 index 0000000..da11999 --- /dev/null +++ b/codemods/jssg/replace-act-import/package.json @@ -0,0 +1,18 @@ +{ + "name": "@react-new/replace-act-import", + "version": "0.1.0", + "description": "Migrate act() from react-dom/test-utils to react", + "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/replace-act-import/scripts/codemod.ts b/codemods/jssg/replace-act-import/scripts/codemod.ts new file mode 100644 index 0000000..cfb5346 --- /dev/null +++ b/codemods/jssg/replace-act-import/scripts/codemod.ts @@ -0,0 +1,284 @@ +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; +} + +const TEST_UTILS_MODULE = "react-dom/test-utils"; +const REACT_MODULE = "react"; + +type TestUtilsImport = { name: string; type: "default" | "namespace" }; +type NamedActImport = { importNode: SgNode; specifier: SgNode; alias: string | null }; + +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 isWhitespace(char: string | undefined): boolean { + return char === " " || char === "\t" || char === "\n" || char === "\r"; +} + +function removeNodeWithComma(node: SgNode, source: string): Edit { + let start = node.range().start.index; + let end = node.range().end.index; + + let i = end; + while (isWhitespace(source[i])) i++; + if (source[i] === ",") { + end = i + 1; + while (isWhitespace(source[end])) end++; + } else { + let j = start - 1; + while (isWhitespace(source[j])) j--; + if (source[j] === ",") start = j; + } + + return { startPos: start, endPos: end, insertedText: "" }; +} + +function namedImportText(alias: string | null): string { + return alias && alias !== "act" ? `act as ${alias}` : "act"; +} + +function countImportSpecifiers(importNode: SgNode): number { + return importNode.findAll({ rule: { kind: "import_specifier" } }).length; +} + +function findExistingReactNamedImport(rootNode: SgNode): SgNode | null { + return rootNode.findAll({ rule: { kind: "import_statement" } }).find((imp) => + importSource(imp) === REACT_MODULE && imp.find({ rule: { kind: "named_imports" } }) !== null + ) ?? null; +} + +function findNamedActImports(rootNode: SgNode): NamedActImport[] { + const imports: NamedActImport[] = []; + + for (const importNode of rootNode.findAll({ rule: { kind: "import_statement" } })) { + if (importSource(importNode) !== TEST_UTILS_MODULE) continue; + + for (const specifier of importNode.findAll({ rule: { kind: "import_specifier" } })) { + if (specifier.field("name")?.text() !== "act") continue; + imports.push({ + importNode, + specifier, + alias: specifier.field("alias")?.text() ?? null, + }); + } + } + + return imports; +} + +function getTestUtilsImport(rootNode: SgNode): (TestUtilsImport & { importNode?: SgNode }) | null { + const defaultImp = getImport(rootNode, { type: "default", from: TEST_UTILS_MODULE }); + if (defaultImp) { + const importNode = defaultImp.node.ancestors().find((a) => a.kind() === "import_statement"); + return { + name: defaultImp.alias, + type: defaultImp.isNamespace ? "namespace" : "default", + importNode: importNode ?? undefined, + }; + } + + const importDecls = rootNode.findAll({ + rule: { + kind: "import_statement", + has: { + all: [ + { kind: "string", regex: "react-dom/test-utils" }, + { kind: "namespace_import" }, + ], + }, + }, + }); + + for (const imp of importDecls) { + const namespaceImport = imp.find({ rule: { kind: "namespace_import" } }); + if (namespaceImport) { + const ident = namespaceImport.field("name") ?? namespaceImport.find({ rule: { kind: "identifier" } }); + if (ident) return { name: ident.text(), type: "namespace", importNode: imp }; + } + } + return null; +} + +const transform: Transform = async (root) => { + const rootNode = root.root(); + const source = rootNode.text(); + const edits: Edit[] = []; + const metric = useMetricAtom("replace-act-import-migrations"); + + const testUtilsImport = getTestUtilsImport(rootNode); + const namedActImports = findNamedActImports(rootNode); + + if (testUtilsImport) { + const actMemberCalls = rootNode.findAll({ + rule: { + kind: "member_expression", + has: { + kind: "property_identifier", + regex: "^act$", + }, + }, + }); + + if (actMemberCalls.length > 0) { + const reactDefault = getImport(rootNode, { type: "default", from: REACT_MODULE }); + const reactNamespace = rootNode.find({ + rule: { + kind: "import_statement", + has: { + all: [ + { kind: "string", regex: "^react$" }, + { kind: "namespace_import" }, + ], + }, + }, + }); + const nsIdent = reactNamespace?.find({ rule: { kind: "namespace_import" } })?.field("name"); + const reactName = reactDefault?.alias ?? nsIdent?.text() ?? "React"; + + if (testUtilsImport.importNode) { + if (reactDefault || reactNamespace) { + const removeEdit = removeImport(rootNode, { + type: testUtilsImport.type, + from: TEST_UTILS_MODULE, + }); + if (removeEdit) edits.push(removeEdit); + } else { + const newImportText = + testUtilsImport.type === "namespace" + ? 'import * as React from "react";' + : 'import React from "react";'; + edits.push(testUtilsImport.importNode.replace(newImportText)); + } + } + + for (const member of actMemberCalls) { + const objNode = member.field("object"); + if (objNode && objNode.text() === testUtilsImport.name) { + edits.push(objNode.replace(reactName)); + metric.increment({ file: metricFile(root.filename()), pattern: "member-call" }); + } + } + } + } + + if (namedActImports.length > 0) { + const existingReactNamedImport = findExistingReactNamedImport(rootNode); + const existingReactActSpecifiers = new Set(); + if (existingReactNamedImport) { + for (const specifier of existingReactNamedImport.findAll({ rule: { kind: "import_specifier" } })) { + if (specifier.field("name")?.text() !== "act") continue; + existingReactActSpecifiers.add(namedImportText(specifier.field("alias")?.text() ?? null)); + } + } + + const wantedActSpecifiers = Array.from( + new Set(namedActImports.map((entry) => namedImportText(entry.alias))), + ); + const specifiersToAdd = wantedActSpecifiers.filter((text) => !existingReactActSpecifiers.has(text)); + + let replacementImportId: number | null = null; + if (existingReactNamedImport) { + if (specifiersToAdd.length > 0) { + const namedImports = existingReactNamedImport.find({ rule: { kind: "named_imports" } }); + if (namedImports) { + const insertPos = namedImports.range().end.index - 1; + edits.push({ + startPos: insertPos, + endPos: insertPos, + insertedText: `, ${specifiersToAdd.join(", ")}`, + }); + } + } + } else if (specifiersToAdd.length > 0) { + const firstNamedActImport = namedActImports[0]!.importNode; + const firstImportActCount = namedActImports.filter((entry) => + entry.importNode.id() === firstNamedActImport.id(), + ).length; + const replacementText = `import { ${specifiersToAdd.join(", ")} } from "react";`; + + if (countImportSpecifiers(firstNamedActImport) === firstImportActCount) { + edits.push(firstNamedActImport.replace(replacementText)); + replacementImportId = firstNamedActImport.id(); + } else { + edits.push({ + startPos: firstNamedActImport.range().start.index, + endPos: firstNamedActImport.range().start.index, + insertedText: `${replacementText}\n`, + }); + } + } + + const importsById = new Map; specifiers: SgNode[] }>(); + for (const entry of namedActImports) { + const current = importsById.get(entry.importNode.id()); + if (current) { + current.specifiers.push(entry.specifier); + } else { + importsById.set(entry.importNode.id(), { + importNode: entry.importNode, + specifiers: [entry.specifier], + }); + } + } + + for (const [importId, group] of importsById) { + if (replacementImportId === importId) continue; + + if (countImportSpecifiers(group.importNode) === group.specifiers.length) { + edits.push(group.importNode.replace("")); + } else { + for (const specifier of group.specifiers) { + edits.push(removeNodeWithComma(specifier, source)); + } + } + } + + metric.increment({ file: metricFile(root.filename()), pattern: "named-import" }); + } + + const exportDecls = rootNode.findAll({ + rule: { + kind: "export_statement", + has: { + kind: "string", + regex: "react-dom/test-utils", + }, + }, + }); + + for (const exp of exportDecls) { + const hasExportAll = + exp.has({ rule: { kind: "namespace_export" } }) || + exp.children().some((c) => c.text() === "*"); + if (hasExportAll) { + const insertPos = exp.range().end.index; + edits.push({ + startPos: insertPos, + endPos: insertPos, + insertedText: "\nexport { act } from \"react\";", + }); + metric.increment({ file: metricFile(root.filename()), pattern: "re-export" }); + } + } + + if (edits.length === 0) return null; + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/replace-act-import/tests/default-import/expected.tsx b/codemods/jssg/replace-act-import/tests/default-import/expected.tsx similarity index 100% rename from replace-act-import/tests/default-import/expected.tsx rename to codemods/jssg/replace-act-import/tests/default-import/expected.tsx diff --git a/replace-act-import/tests/default-import/input.tsx b/codemods/jssg/replace-act-import/tests/default-import/input.tsx similarity index 100% rename from replace-act-import/tests/default-import/input.tsx rename to codemods/jssg/replace-act-import/tests/default-import/input.tsx diff --git a/replace-act-import/tests/default-import/metrics.json b/codemods/jssg/replace-act-import/tests/default-import/metrics.json similarity index 100% rename from replace-act-import/tests/default-import/metrics.json rename to codemods/jssg/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/jssg/replace-act-import/tests/multiple-named-imports-with-other-specifier/expected.tsx new file mode 100644 index 0000000..7f9f63a --- /dev/null +++ b/codemods/jssg/replace-act-import/tests/multiple-named-imports-with-other-specifier/expected.tsx @@ -0,0 +1,7 @@ +import { act, act as act2 } from "react"; +import { render } from "react-dom/test-utils"; + + +render(); +act(); +act2(); diff --git a/codemods/jssg/replace-act-import/tests/multiple-named-imports-with-other-specifier/input.tsx b/codemods/jssg/replace-act-import/tests/multiple-named-imports-with-other-specifier/input.tsx new file mode 100644 index 0000000..b5a65bb --- /dev/null +++ b/codemods/jssg/replace-act-import/tests/multiple-named-imports-with-other-specifier/input.tsx @@ -0,0 +1,6 @@ +import { render, act } from "react-dom/test-utils"; +import { act as act2 } from "react-dom/test-utils"; + +render(); +act(); +act2(); diff --git a/codemods/jssg/replace-act-import/tests/multiple-named-imports-with-other-specifier/metrics.json b/codemods/jssg/replace-act-import/tests/multiple-named-imports-with-other-specifier/metrics.json new file mode 100644 index 0000000..1fbe435 --- /dev/null +++ b/codemods/jssg/replace-act-import/tests/multiple-named-imports-with-other-specifier/metrics.json @@ -0,0 +1,11 @@ +{ + "replace-act-import-migrations": [ + { + "cardinality": { + "file": "tests/multiple-named-imports-with-other-specifier/input.tsx", + "pattern": "named-import" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-act-import/tests/multiple-named-imports/expected.tsx b/codemods/jssg/replace-act-import/tests/multiple-named-imports/expected.tsx new file mode 100644 index 0000000..dec384a --- /dev/null +++ b/codemods/jssg/replace-act-import/tests/multiple-named-imports/expected.tsx @@ -0,0 +1,6 @@ +import { FC , act, act as act2} from "react"; + + + +act(); +act2(); diff --git a/codemods/jssg/replace-act-import/tests/multiple-named-imports/input.tsx b/codemods/jssg/replace-act-import/tests/multiple-named-imports/input.tsx new file mode 100644 index 0000000..3b9408c --- /dev/null +++ b/codemods/jssg/replace-act-import/tests/multiple-named-imports/input.tsx @@ -0,0 +1,6 @@ +import { FC } from "react"; +import { act } from "react-dom/test-utils"; +import { act as act2 } from "react-dom/test-utils"; + +act(); +act2(); diff --git a/codemods/jssg/replace-act-import/tests/multiple-named-imports/metrics.json b/codemods/jssg/replace-act-import/tests/multiple-named-imports/metrics.json new file mode 100644 index 0000000..a727e22 --- /dev/null +++ b/codemods/jssg/replace-act-import/tests/multiple-named-imports/metrics.json @@ -0,0 +1,11 @@ +{ + "replace-act-import-migrations": [ + { + "cardinality": { + "file": "tests/multiple-named-imports/input.tsx", + "pattern": "named-import" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-act-import/tests/named-import-2/expected.tsx b/codemods/jssg/replace-act-import/tests/named-import-2/expected.tsx new file mode 100644 index 0000000..6a1ffe1 --- /dev/null +++ b/codemods/jssg/replace-act-import/tests/named-import-2/expected.tsx @@ -0,0 +1,4 @@ +import { FC , act} from "react"; + + +act(); diff --git a/replace-act-import/tests/named-import-2/input.tsx b/codemods/jssg/replace-act-import/tests/named-import-2/input.tsx similarity index 100% rename from replace-act-import/tests/named-import-2/input.tsx rename to codemods/jssg/replace-act-import/tests/named-import-2/input.tsx diff --git a/replace-act-import/tests/named-import-2/metrics.json b/codemods/jssg/replace-act-import/tests/named-import-2/metrics.json similarity index 100% rename from replace-act-import/tests/named-import-2/metrics.json rename to codemods/jssg/replace-act-import/tests/named-import-2/metrics.json diff --git a/codemods/jssg/replace-act-import/tests/named-import/expected.tsx b/codemods/jssg/replace-act-import/tests/named-import/expected.tsx new file mode 100644 index 0000000..44ab638 --- /dev/null +++ b/codemods/jssg/replace-act-import/tests/named-import/expected.tsx @@ -0,0 +1,2 @@ +import { act } from "react"; +act(); diff --git a/replace-act-import/tests/named-import/input.tsx b/codemods/jssg/replace-act-import/tests/named-import/input.tsx similarity index 100% rename from replace-act-import/tests/named-import/input.tsx rename to codemods/jssg/replace-act-import/tests/named-import/input.tsx diff --git a/replace-act-import/tests/named-import/metrics.json b/codemods/jssg/replace-act-import/tests/named-import/metrics.json similarity index 100% rename from replace-act-import/tests/named-import/metrics.json rename to codemods/jssg/replace-act-import/tests/named-import/metrics.json diff --git a/replace-act-import/tests/export-named-act/input.tsx b/codemods/jssg/replace-act-import/tests/named-reexport-no-change/expected.tsx similarity index 100% rename from replace-act-import/tests/export-named-act/input.tsx rename to codemods/jssg/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/jssg/replace-act-import/tests/named-reexport-no-change/input.tsx new file mode 100644 index 0000000..2430a1e --- /dev/null +++ b/codemods/jssg/replace-act-import/tests/named-reexport-no-change/input.tsx @@ -0,0 +1 @@ +export { act } from "react-dom/test-utils"; diff --git a/replace-act-import/tests/other-import/expected.tsx b/codemods/jssg/replace-act-import/tests/other-import/expected.tsx similarity index 100% rename from replace-act-import/tests/other-import/expected.tsx rename to codemods/jssg/replace-act-import/tests/other-import/expected.tsx diff --git a/replace-act-import/tests/other-import/input.tsx b/codemods/jssg/replace-act-import/tests/other-import/input.tsx similarity index 100% rename from replace-act-import/tests/other-import/input.tsx rename to codemods/jssg/replace-act-import/tests/other-import/input.tsx diff --git a/replace-act-import/tests/re-export/expected.tsx b/codemods/jssg/replace-act-import/tests/re-export/expected.tsx similarity index 100% rename from replace-act-import/tests/re-export/expected.tsx rename to codemods/jssg/replace-act-import/tests/re-export/expected.tsx diff --git a/replace-act-import/tests/re-export/input.tsx b/codemods/jssg/replace-act-import/tests/re-export/input.tsx similarity index 100% rename from replace-act-import/tests/re-export/input.tsx rename to codemods/jssg/replace-act-import/tests/re-export/input.tsx diff --git a/replace-act-import/tests/re-export/metrics.json b/codemods/jssg/replace-act-import/tests/re-export/metrics.json similarity index 100% rename from replace-act-import/tests/re-export/metrics.json rename to codemods/jssg/replace-act-import/tests/re-export/metrics.json diff --git a/codemods/jssg/replace-act-import/tests/wildcard-import-2/expected.tsx b/codemods/jssg/replace-act-import/tests/wildcard-import-2/expected.tsx new file mode 100644 index 0000000..fc21df2 --- /dev/null +++ b/codemods/jssg/replace-act-import/tests/wildcard-import-2/expected.tsx @@ -0,0 +1,3 @@ +import * as React from "react"; + +React.act(); diff --git a/replace-act-import/tests/wildcard-import-2/input.tsx b/codemods/jssg/replace-act-import/tests/wildcard-import-2/input.tsx similarity index 100% rename from replace-act-import/tests/wildcard-import-2/input.tsx rename to codemods/jssg/replace-act-import/tests/wildcard-import-2/input.tsx diff --git a/replace-act-import/tests/wildcard-import-2/metrics.json b/codemods/jssg/replace-act-import/tests/wildcard-import-2/metrics.json similarity index 100% rename from replace-act-import/tests/wildcard-import-2/metrics.json rename to codemods/jssg/replace-act-import/tests/wildcard-import-2/metrics.json diff --git a/codemods/jssg/replace-act-import/tests/wildcard-import/expected.tsx b/codemods/jssg/replace-act-import/tests/wildcard-import/expected.tsx new file mode 100644 index 0000000..4acf1aa --- /dev/null +++ b/codemods/jssg/replace-act-import/tests/wildcard-import/expected.tsx @@ -0,0 +1,2 @@ +import * as React from "react"; +React.act(); diff --git a/replace-act-import/tests/wildcard-import/input.tsx b/codemods/jssg/replace-act-import/tests/wildcard-import/input.tsx similarity index 100% rename from replace-act-import/tests/wildcard-import/input.tsx rename to codemods/jssg/replace-act-import/tests/wildcard-import/input.tsx diff --git a/replace-act-import/tests/wildcard-import/metrics.json b/codemods/jssg/replace-act-import/tests/wildcard-import/metrics.json similarity index 100% rename from replace-act-import/tests/wildcard-import/metrics.json rename to codemods/jssg/replace-act-import/tests/wildcard-import/metrics.json diff --git a/codemods/jssg/replace-act-import/tsconfig.json b/codemods/jssg/replace-act-import/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/jssg/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/jssg/replace-act-import/workflow.yaml new file mode 100644 index 0000000..4db52cd --- /dev/null +++ b/codemods/jssg/replace-act-import/workflow.yaml @@ -0,0 +1,34 @@ +# 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: "Migrate act from react-dom/test-utils to react" + js-ast-grep: + js_file: scripts/codemod.ts + language: "tsx" + # Test-oriented globs . + include: + - "**/*.test.ts" + - "**/*.test.tsx" + - "**/*.spec.ts" + - "**/*.spec.tsx" + - "**/__tests__/**/*.ts" + - "**/__tests__/**/*.tsx" + - "**/tests/**/*.ts" + - "**/tests/**/*.tsx" + exclude: + - "**/node_modules/**" + - "**/dist/**" + - "**/build/**" + - "**/.next/**" + - "**/out/**" + - "**/coverage/**" + - "**/.git/**" + # JSSG golden outputs (avoid touching when running workflow from package root) + - "**/tests/**/expected.ts" + - "**/tests/**/expected.tsx" diff --git a/rename-unsafe-lifecycles/.gitignore b/codemods/jssg/replace-reactdom-render/.gitignore similarity index 100% rename from rename-unsafe-lifecycles/.gitignore rename to codemods/jssg/replace-reactdom-render/.gitignore diff --git a/codemods/jssg/replace-reactdom-render/README.md b/codemods/jssg/replace-reactdom-render/README.md new file mode 100644 index 0000000..6bfd52f --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/README.md @@ -0,0 +1,16 @@ +# replace-reactdom-render + +Replace `ReactDOM.render` and named `render` imports with `createRoot(...).render(...)`. + +## Usage + +```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/jssg/replace-reactdom-render/codemod.yaml new file mode 100644 index 0000000..ce8a58c --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/codemod.yaml @@ -0,0 +1,20 @@ +schema_version: "1.0" + +name: "@react-new/replace-reactdom-render" +version: "0.1.0" +description: "Replace ReactDOM.render with createRoot for React 18" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + + +targets: + languages: ["tsx"] + +keywords: ["React"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/jssg/replace-reactdom-render/package.json b/codemods/jssg/replace-reactdom-render/package.json new file mode 100644 index 0000000..053a994 --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/package.json @@ -0,0 +1,18 @@ +{ + "name": "@react-new/replace-reactdom-render", + "version": "0.1.0", + "description": "Replace ReactDOM.render with createRoot for React 18", + "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/replace-reactdom-render/pnpm-lock.yaml b/codemods/jssg/replace-reactdom-render/pnpm-lock.yaml similarity index 100% rename from replace-reactdom-render/pnpm-lock.yaml rename to codemods/jssg/replace-reactdom-render/pnpm-lock.yaml diff --git a/codemods/jssg/replace-reactdom-render/scripts/codemod.ts b/codemods/jssg/replace-reactdom-render/scripts/codemod.ts new file mode 100644 index 0000000..558c2fc --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/scripts/codemod.ts @@ -0,0 +1,296 @@ +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 reindentText(text: string, fromCol: number, toCol: number): string { + if (fromCol === toCol) return text; + const lines = text.split("\n"); + const delta = toCol - fromCol; + return lines + .map((line, i) => { + if (i === 0) return line; + if (delta > 0) return " ".repeat(delta) + line; + const strip = Math.min(-delta, line.length - line.trimStart().length); + return line.slice(strip); + }) + .join("\n"); +} + +function metricFile(filename: string): string { + const cwd = process.cwd() + "/"; + return filename.startsWith(cwd) ? filename.slice(cwd.length) : filename; +} + +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(node: SgNode): string | null { + const source = node.field("source") ?? node.find({ rule: { kind: "string" } }); + return source ? sourceText(source) : null; +} + +function findReactDomMemberImportNames(rootNode: SgNode): Set { + const names = new Set(); + + for (const imp of rootNode.findAll({ rule: { kind: "import_statement" } })) { + if (importSource(imp) !== "react-dom") continue; + + const importClause = imp.find({ rule: { kind: "import_clause" } }); + const defaultIdentifier = importClause?.children().find((child) => child.kind() === "identifier"); + if (defaultIdentifier) names.add(defaultIdentifier.text()); + + const namespaceImport = imp.find({ rule: { kind: "namespace_import" } }); + const namespaceName = + namespaceImport?.field("name") ?? namespaceImport?.find({ rule: { kind: "identifier" } }); + if (namespaceName) names.add(namespaceName.text()); + } + + return names; +} + +function findNamedImportNames(rootNode: SgNode, exportedName: string): Set { + const names = new Set(); + + for (const importNode of rootNode.findAll({ rule: { kind: "import_statement" } })) { + if (importSource(importNode) !== "react-dom") continue; + + for (const specifier of importNode.findAll({ rule: { kind: "import_specifier" } })) { + if (specifier.field("name")?.text() !== exportedName) continue; + names.add(specifier.field("alias")?.text() ?? exportedName); + } + } + + return names; +} + +const transform: Transform = async (root) => { + const rootNode = root.root(); + const edits: Edit[] = []; + + const transformMetric = useMetricAtom("reactdom-render-replacements"); + + const reactDomMemberImportNames = findReactDomMemberImportNames(rootNode); + const reactDomRenderImportNames = findNamedImportNames(rootNode, "render"); + const reactDomUnmountImportNames = findNamedImportNames(rootNode, "unmountComponentAtNode"); + + if (reactDomMemberImportNames.size === 0 && reactDomRenderImportNames.size === 0 && reactDomUnmountImportNames.size === 0) { + return null; + } + + let hasTransformations = false; + let rootIndex = 0; + + const nextRootName = (): string => { + const name = rootIndex === 0 ? "root" : `root${rootIndex}`; + rootIndex += 1; + return name; + }; + + const sourceCode = rootNode.text(); + const sourceLines = sourceCode.split("\n"); + + const getIndent = (statement: SgNode): string => { + const line = sourceLines[statement.range().start.line] ?? ""; + const match = line.match(/^(\s*)/); + return match?.[1] ?? ""; + }; + + const applyRenderReplacement = (call: SgNode, pattern: string): void => { + const args = call.field("arguments"); + if (!args) return; + + const argList = args.children().filter((child) => + child.kind() !== "(" && child.kind() !== ")" && child.kind() !== "," + ); + + if (argList.length < 2) return; + + const element = argList[0]!; + const container = argList[1]!; + const callback = argList[2]; + const statement = call.ancestors().find((ancestor) => ancestor.kind() === "expression_statement"); + if (!statement) return; + + const rootName = nextRootName(); + const indent = getIndent(statement); + const elementText = element.text(); + + let renderCall: string; + if (elementText.includes("\n")) { + const elementStartLine = sourceLines[element.range().start.line] ?? ""; + const originalCol = elementStartLine.length - elementStartLine.trimStart().length; + const desiredCol = indent.length + 2; + const reindented = reindentText(elementText, originalCol, desiredCol); + renderCall = `${rootName}.render(\n${" ".repeat(desiredCol)}${reindented}\n${indent})`; + } else { + renderCall = `${rootName}.render(${elementText})`; + } + + const callbackCall = callback ? `\n${indent}(${callback.text()})();` : ""; + const replacement = `const ${rootName} = createRoot(${container.text()});\n${indent}${renderCall};${callbackCall}`; + edits.push(statement.replace(replacement)); + hasTransformations = true; + transformMetric.increment({ + pattern, + file: metricFile(root.filename()), + }); + }; + + const applyUnmountReplacement = (call: SgNode, pattern: string): void => { + const args = call.field("arguments"); + if (!args) return; + + const argList = args.children().filter((child) => + child.kind() !== "(" && child.kind() !== ")" && child.kind() !== "," + ); + + if (argList.length < 1) return; + + const container = argList[0]!; + const statement = call.ancestors().find((ancestor) => ancestor.kind() === "expression_statement"); + if (!statement) return; + + const rootName = nextRootName(); + const indent = getIndent(statement); + + const replacement = `const ${rootName} = createRoot(${container.text()});\n${indent}${rootName}.unmount();`; + edits.push(statement.replace(replacement)); + hasTransformations = true; + transformMetric.increment({ + pattern, + file: metricFile(root.filename()), + }); + }; + + if (reactDomMemberImportNames.size > 0) { + const memberRenderCalls = rootNode.findAll({ + rule: { + kind: "call_expression", + has: { + field: "function", + kind: "member_expression", + all: [ + { + has: { + field: "object", + kind: "identifier", + regex: ".*", + }, + }, + { + has: { + field: "property", + kind: "property_identifier", + regex: "^render$", + }, + }, + ], + }, + }, + }); + + for (const call of memberRenderCalls) { + const callee = call.field("function"); + const objectNode = callee?.field("object"); + if (!objectNode || !reactDomMemberImportNames.has(objectNode.text())) continue; + applyRenderReplacement(call, "ReactDOM.render"); + } + + const memberUnmountCalls = rootNode.findAll({ + rule: { + kind: "call_expression", + has: { + field: "function", + kind: "member_expression", + all: [ + { + has: { + field: "object", + kind: "identifier", + regex: ".*", + }, + }, + { + has: { + field: "property", + kind: "property_identifier", + regex: "^unmountComponentAtNode$", + }, + }, + ], + }, + }, + }); + + for (const call of memberUnmountCalls) { + const callee = call.field("function"); + const objectNode = callee?.field("object"); + if (!objectNode || !reactDomMemberImportNames.has(objectNode.text())) continue; + applyUnmountReplacement(call, "ReactDOM.unmountComponentAtNode"); + } + } + + if (reactDomRenderImportNames.size > 0) { + const renderCalls = rootNode.findAll({ + rule: { + kind: "call_expression", + has: { + field: "function", + kind: "identifier", + regex: ".*", + }, + }, + }); + + for (const call of renderCalls) { + const callee = call.field("function"); + if (!callee || !reactDomRenderImportNames.has(callee.text())) continue; + applyRenderReplacement(call, "render"); + } + } + + if (reactDomUnmountImportNames.size > 0) { + const unmountCalls = rootNode.findAll({ + rule: { + kind: "call_expression", + has: { + field: "function", + kind: "identifier", + regex: ".*", + }, + }, + }); + + for (const call of unmountCalls) { + const callee = call.field("function"); + if (!callee || !reactDomUnmountImportNames.has(callee.text())) continue; + applyUnmountReplacement(call, "unmountComponentAtNode"); + } + } + + if (!hasTransformations) { + return null; + } + + const hasCreateRootImport = getImport(rootNode, { + type: "named", + name: "createRoot", + from: "react-dom/client", + }); + if (!hasCreateRootImport) { + edits.push({ + startPos: 0, + endPos: 0, + insertedText: 'import { createRoot } from "react-dom/client";\n', + }); + } + + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/jssg/replace-reactdom-render/tests/default/expected.tsx b/codemods/jssg/replace-reactdom-render/tests/default/expected.tsx new file mode 100644 index 0000000..7a3e571 --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/default/expected.tsx @@ -0,0 +1,6 @@ +import { createRoot } from "react-dom/client"; +import ReactDom from "react-dom"; +import Component from "Component"; + +const root = createRoot(document.getElementById("app")); +root.render(); diff --git a/replace-reactdom-render/tests/original-default/input.tsx b/codemods/jssg/replace-reactdom-render/tests/default/input.tsx similarity index 100% rename from replace-reactdom-render/tests/original-default/input.tsx rename to codemods/jssg/replace-reactdom-render/tests/default/input.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/default/metrics.json b/codemods/jssg/replace-reactdom-render/tests/default/metrics.json new file mode 100644 index 0000000..95d32e3 --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/default/metrics.json @@ -0,0 +1,11 @@ +{ + "reactdom-render-replacements": [ + { + "cardinality": { + "file": "tests/default/input.tsx", + "pattern": "ReactDOM.render" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-reactdom-render/tests/multiline-jsx/expected.tsx b/codemods/jssg/replace-reactdom-render/tests/multiline-jsx/expected.tsx new file mode 100644 index 0000000..f0f2aa4 --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/multiline-jsx/expected.tsx @@ -0,0 +1,13 @@ +import { createRoot } from "react-dom/client"; +import * as ReactDOM from "react-dom"; + +function show(container) { + const root = createRoot(container); + root.render( + + ); +} diff --git a/codemods/jssg/replace-reactdom-render/tests/multiline-jsx/input.tsx b/codemods/jssg/replace-reactdom-render/tests/multiline-jsx/input.tsx new file mode 100644 index 0000000..c831c91 --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/multiline-jsx/input.tsx @@ -0,0 +1,12 @@ +import * as ReactDOM from "react-dom"; + +function show(container) { + ReactDOM.render( + , + container + ); +} diff --git a/codemods/jssg/replace-reactdom-render/tests/multiline-jsx/metrics.json b/codemods/jssg/replace-reactdom-render/tests/multiline-jsx/metrics.json new file mode 100644 index 0000000..6ee0fdc --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/multiline-jsx/metrics.json @@ -0,0 +1,11 @@ +{ + "reactdom-render-replacements": [ + { + "cardinality": { + "file": "tests/multiline-jsx/input.tsx", + "pattern": "ReactDOM.render" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-reactdom-render/tests/multiple-member-render-imports/expected.tsx b/codemods/jssg/replace-reactdom-render/tests/multiple-member-render-imports/expected.tsx new file mode 100644 index 0000000..b9416e9 --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/multiple-member-render-imports/expected.tsx @@ -0,0 +1,8 @@ +import { createRoot } from "react-dom/client"; +import ReactDOM from "react-dom"; +import * as DOM from "react-dom"; + +const root = createRoot(node1); +root.render(); +const root1 = createRoot(node2); +root1.render(); diff --git a/codemods/jssg/replace-reactdom-render/tests/multiple-member-render-imports/input.tsx b/codemods/jssg/replace-reactdom-render/tests/multiple-member-render-imports/input.tsx new file mode 100644 index 0000000..b100d69 --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/multiple-member-render-imports/input.tsx @@ -0,0 +1,5 @@ +import ReactDOM from "react-dom"; +import * as DOM from "react-dom"; + +ReactDOM.render(, node1); +DOM.render(, node2); diff --git a/codemods/jssg/replace-reactdom-render/tests/multiple-member-render-imports/metrics.json b/codemods/jssg/replace-reactdom-render/tests/multiple-member-render-imports/metrics.json new file mode 100644 index 0000000..abafe95 --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/multiple-member-render-imports/metrics.json @@ -0,0 +1,11 @@ +{ + "reactdom-render-replacements": [ + { + "cardinality": { + "file": "tests/multiple-member-render-imports/input.tsx", + "pattern": "ReactDOM.render" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-reactdom-render/tests/multiple-named-render-imports/expected.tsx b/codemods/jssg/replace-reactdom-render/tests/multiple-named-render-imports/expected.tsx new file mode 100644 index 0000000..4a7b7a9 --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/multiple-named-render-imports/expected.tsx @@ -0,0 +1,8 @@ +import { createRoot } from "react-dom/client"; +import { render } from "react-dom"; +import { render as render2 } from "react-dom"; + +const root = createRoot(node1); +root.render(); +const root1 = createRoot(node2); +root1.render(); diff --git a/codemods/jssg/replace-reactdom-render/tests/multiple-named-render-imports/input.tsx b/codemods/jssg/replace-reactdom-render/tests/multiple-named-render-imports/input.tsx new file mode 100644 index 0000000..2ee1dad --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/multiple-named-render-imports/input.tsx @@ -0,0 +1,5 @@ +import { render } from "react-dom"; +import { render as render2 } from "react-dom"; + +render(, node1); +render2(, node2); diff --git a/codemods/jssg/replace-reactdom-render/tests/multiple-named-render-imports/metrics.json b/codemods/jssg/replace-reactdom-render/tests/multiple-named-render-imports/metrics.json new file mode 100644 index 0000000..49c0961 --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/multiple-named-render-imports/metrics.json @@ -0,0 +1,11 @@ +{ + "reactdom-render-replacements": [ + { + "cardinality": { + "file": "tests/multiple-named-render-imports/input.tsx", + "pattern": "render" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-reactdom-render/tests/nested/expected.tsx b/codemods/jssg/replace-reactdom-render/tests/nested/expected.tsx new file mode 100644 index 0000000..4f659df --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/nested/expected.tsx @@ -0,0 +1,9 @@ +import { createRoot } from "react-dom/client"; +import { render } from "react-dom"; + +const fn = () => { + if (true) { + const root = createRoot(theNode); + root.render(); + } +}; diff --git a/replace-reactdom-render/tests/original-nested/input.tsx b/codemods/jssg/replace-reactdom-render/tests/nested/input.tsx similarity index 100% rename from replace-reactdom-render/tests/original-nested/input.tsx rename to codemods/jssg/replace-reactdom-render/tests/nested/input.tsx diff --git a/codemods/jssg/replace-reactdom-render/tests/nested/metrics.json b/codemods/jssg/replace-reactdom-render/tests/nested/metrics.json new file mode 100644 index 0000000..4d372e2 --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/nested/metrics.json @@ -0,0 +1,11 @@ +{ + "reactdom-render-replacements": [ + { + "cardinality": { + "file": "tests/nested/input.tsx", + "pattern": "render" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-reactdom-render/tests/render-and-unmount/expected.tsx b/codemods/jssg/replace-reactdom-render/tests/render-and-unmount/expected.tsx new file mode 100644 index 0000000..a1cde91 --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/render-and-unmount/expected.tsx @@ -0,0 +1,12 @@ +import { createRoot } from "react-dom/client"; +import * as ReactDOM from "react-dom"; + +function mount(container) { + const root = createRoot(container); + root.render(); +} + +function cleanup(container) { + const root1 = createRoot(container); + root1.unmount(); +} diff --git a/codemods/jssg/replace-reactdom-render/tests/render-and-unmount/input.tsx b/codemods/jssg/replace-reactdom-render/tests/render-and-unmount/input.tsx new file mode 100644 index 0000000..de47b7e --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/render-and-unmount/input.tsx @@ -0,0 +1,9 @@ +import * as ReactDOM from "react-dom"; + +function mount(container) { + ReactDOM.render(, container); +} + +function cleanup(container) { + ReactDOM.unmountComponentAtNode(container); +} diff --git a/codemods/jssg/replace-reactdom-render/tests/render-and-unmount/metrics.json b/codemods/jssg/replace-reactdom-render/tests/render-and-unmount/metrics.json new file mode 100644 index 0000000..707622b --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/render-and-unmount/metrics.json @@ -0,0 +1,18 @@ +{ + "reactdom-render-replacements": [ + { + "cardinality": { + "file": "tests/render-and-unmount/input.tsx", + "pattern": "ReactDOM.render" + }, + "count": 1 + }, + { + "cardinality": { + "file": "tests/render-and-unmount/input.tsx", + "pattern": "ReactDOM.unmountComponentAtNode" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-reactdom-render/tests/render-with-callback/expected.tsx b/codemods/jssg/replace-reactdom-render/tests/render-with-callback/expected.tsx new file mode 100644 index 0000000..78b9abf --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/render-with-callback/expected.tsx @@ -0,0 +1,8 @@ +import { createRoot } from "react-dom/client"; +import ReactDOM from "react-dom"; + +function mount(container) { + const root = createRoot(container); + root.render(); + (onReady)(); +} diff --git a/codemods/jssg/replace-reactdom-render/tests/render-with-callback/input.tsx b/codemods/jssg/replace-reactdom-render/tests/render-with-callback/input.tsx new file mode 100644 index 0000000..aa80c80 --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/render-with-callback/input.tsx @@ -0,0 +1,5 @@ +import ReactDOM from "react-dom"; + +function mount(container) { + ReactDOM.render(, container, onReady); +} diff --git a/codemods/jssg/replace-reactdom-render/tests/render-with-callback/metrics.json b/codemods/jssg/replace-reactdom-render/tests/render-with-callback/metrics.json new file mode 100644 index 0000000..884e40c --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/render-with-callback/metrics.json @@ -0,0 +1,11 @@ +{ + "reactdom-render-replacements": [ + { + "cardinality": { + "file": "tests/render-with-callback/input.tsx", + "pattern": "ReactDOM.render" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-reactdom-render/tests/unmount-member/expected.tsx b/codemods/jssg/replace-reactdom-render/tests/unmount-member/expected.tsx new file mode 100644 index 0000000..b32b914 --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/unmount-member/expected.tsx @@ -0,0 +1,7 @@ +import { createRoot } from "react-dom/client"; +import * as ReactDOM from "react-dom"; + +function cleanup(container) { + const root = createRoot(container); + root.unmount(); +} diff --git a/codemods/jssg/replace-reactdom-render/tests/unmount-member/input.tsx b/codemods/jssg/replace-reactdom-render/tests/unmount-member/input.tsx new file mode 100644 index 0000000..95ce2fb --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/unmount-member/input.tsx @@ -0,0 +1,5 @@ +import * as ReactDOM from "react-dom"; + +function cleanup(container) { + ReactDOM.unmountComponentAtNode(container); +} diff --git a/codemods/jssg/replace-reactdom-render/tests/unmount-member/metrics.json b/codemods/jssg/replace-reactdom-render/tests/unmount-member/metrics.json new file mode 100644 index 0000000..7cf3c4e --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/unmount-member/metrics.json @@ -0,0 +1,11 @@ +{ + "reactdom-render-replacements": [ + { + "cardinality": { + "file": "tests/unmount-member/input.tsx", + "pattern": "ReactDOM.unmountComponentAtNode" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-reactdom-render/tests/unmount-named/expected.tsx b/codemods/jssg/replace-reactdom-render/tests/unmount-named/expected.tsx new file mode 100644 index 0000000..82a0078 --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/unmount-named/expected.tsx @@ -0,0 +1,7 @@ +import { createRoot } from "react-dom/client"; +import { unmountComponentAtNode } from "react-dom"; + +function cleanup(container) { + const root = createRoot(container); + root.unmount(); +} diff --git a/codemods/jssg/replace-reactdom-render/tests/unmount-named/input.tsx b/codemods/jssg/replace-reactdom-render/tests/unmount-named/input.tsx new file mode 100644 index 0000000..083346c --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/unmount-named/input.tsx @@ -0,0 +1,5 @@ +import { unmountComponentAtNode } from "react-dom"; + +function cleanup(container) { + unmountComponentAtNode(container); +} diff --git a/codemods/jssg/replace-reactdom-render/tests/unmount-named/metrics.json b/codemods/jssg/replace-reactdom-render/tests/unmount-named/metrics.json new file mode 100644 index 0000000..28c99ab --- /dev/null +++ b/codemods/jssg/replace-reactdom-render/tests/unmount-named/metrics.json @@ -0,0 +1,11 @@ +{ + "reactdom-render-replacements": [ + { + "cardinality": { + "file": "tests/unmount-named/input.tsx", + "pattern": "unmountComponentAtNode" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-reactdom-render/tsconfig.json b/codemods/jssg/replace-reactdom-render/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/jssg/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/error-boundaries/workflow.yaml b/codemods/jssg/replace-reactdom-render/workflow.yaml similarity index 100% rename from error-boundaries/workflow.yaml rename to codemods/jssg/replace-reactdom-render/workflow.yaml diff --git a/replace-reactdom-render/.gitignore b/codemods/jssg/replace-string-ref/.gitignore similarity index 100% rename from replace-reactdom-render/.gitignore rename to codemods/jssg/replace-string-ref/.gitignore diff --git a/codemods/jssg/replace-string-ref/README.md b/codemods/jssg/replace-string-ref/README.md new file mode 100644 index 0000000..4a9267d --- /dev/null +++ b/codemods/jssg/replace-string-ref/README.md @@ -0,0 +1,16 @@ +# replace-string-ref + +Replace string refs in React class components with callback refs that assign through `this.refs`. + +## Usage + +```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/jssg/replace-string-ref/codemod.yaml new file mode 100644 index 0000000..f9fe55c --- /dev/null +++ b/codemods/jssg/replace-string-ref/codemod.yaml @@ -0,0 +1,20 @@ +schema_version: "1.0" + +name: "@react-new/replace-string-ref" +version: "0.1.0" +description: "Replace string refs with callback refs in React class components" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + + +targets: + languages: ["tsx"] + +keywords: ["React"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/jssg/replace-string-ref/package.json b/codemods/jssg/replace-string-ref/package.json new file mode 100644 index 0000000..14fc031 --- /dev/null +++ b/codemods/jssg/replace-string-ref/package.json @@ -0,0 +1,18 @@ +{ + "name": "@react-new/replace-string-ref", + "version": "0.1.0", + "description": "Replace string refs with callback refs in React class components", + "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/replace-string-ref/pnpm-lock.yaml b/codemods/jssg/replace-string-ref/pnpm-lock.yaml similarity index 100% rename from replace-string-ref/pnpm-lock.yaml rename to codemods/jssg/replace-string-ref/pnpm-lock.yaml diff --git a/codemods/jssg/replace-string-ref/scripts/codemod.ts b/codemods/jssg/replace-string-ref/scripts/codemod.ts new file mode 100644 index 0000000..66885df --- /dev/null +++ b/codemods/jssg/replace-string-ref/scripts/codemod.ts @@ -0,0 +1,168 @@ +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 isInsideReactClassComponent( + node: SgNode, + reactName: string, + componentNamesList: string[], +): boolean { + const classDecl = node.ancestors().find((a) => a.kind() === "class_declaration"); + if (!classDecl) return false; + + const heritage = classDecl.children().find((c) => c.kind() === "class_heritage"); + if (!heritage) return false; + + const extendsClause = heritage.children().find((child) => child.kind() === "extends_clause"); + const directSuperclass = extendsClause?.children().find( + (child) => child.isNamed() && child.kind() !== "type_arguments", + ); + if (!directSuperclass) return false; + + if (directSuperclass.kind() === "member_expression") { + const object = directSuperclass.field("object"); + const property = directSuperclass.field("property"); + return !!( + object && + property && + object.text() === reactName && + (property.text() === "Component" || property.text() === "PureComponent") + ); + } + + if (directSuperclass.kind() === "identifier") { + return componentNamesList.includes(directSuperclass.text()); + } + + return false; +} + +function isValidIdentifierName(name: string): boolean { + return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name); +} + +function callbackRefText(refName: string): string { + const assignmentTarget = isValidIdentifierName(refName) + ? `this.refs.${refName}` + : `this.refs[${JSON.stringify(refName)}]`; + return `ref={(ref) => { + ${assignmentTarget} = ref; + }}`; +} + +function namedReturnExpression(returnStmt: SgNode): SgNode | null { + return returnStmt.children().find((child) => child.isNamed() && child.kind() !== "comment") ?? null; +} + +function isFragmentElement(node: SgNode): boolean { + if (node.kind() !== "jsx_element") return false; + const opening = node.children().find((child) => child.kind() === "jsx_opening_element"); + if (!opening) return false; + const tagName = opening.children().find((child) => + child.kind() === "identifier" || child.kind() === "property_identifier" + ); + return !tagName; +} + +function shouldWrapReturnExpression(node: SgNode | null): node is SgNode { + if (!node) return false; + if (node.kind() === "jsx_self_closing_element") return true; + if (node.kind() === "jsx_element") return !isFragmentElement(node); + return false; +} + +const transform: Transform = async (root) => { + const rootNode = root.root(); + const edits: Edit[] = []; + + const transformMetric = useMetricAtom("string-ref-replacements"); + const refNames: string[] = []; + const wrappedJsxStarts = new Set(); + + const reactDefault = getImport(rootNode, { type: "default", from: "react" }); + const componentImport = getImport(rootNode, { type: "named", name: "Component", from: "react" }); + const pureImport = getImport(rootNode, { type: "named", name: "PureComponent", from: "react" }); + const reactName = reactDefault?.alias ?? "React"; + const componentNames = new Set(); + if (componentImport) componentNames.add(componentImport.alias); + if (pureImport) componentNames.add(pureImport.alias); + const componentNamesList = Array.from(componentNames); + + const stringRefs = rootNode.findAll({ + rule: { + kind: "jsx_attribute", + all: [ + { has: { kind: "property_identifier", regex: "^ref$" } }, + { has: { kind: "string" } }, + ], + }, + }); + + for (const refAttr of stringRefs) { + if (!isInsideReactClassComponent(refAttr, reactName, componentNamesList)) continue; + const stringValue = refAttr.find({ + rule: { + kind: "string", + }, + }); + + if (!stringValue) continue; + + const stringFragment = stringValue.find({ + rule: { + kind: "string_fragment", + }, + }); + + if (!stringFragment) continue; + + const refName = stringFragment.text(); + const callbackRef = callbackRefText(refName); + + edits.push(refAttr.replace(callbackRef)); + const returnStmt = refAttr.ancestors().find((a) => a.kind() === "return_statement"); + const returnExpr = returnStmt ? namedReturnExpression(returnStmt) : null; + if ( + returnExpr && + returnStmt && + shouldWrapReturnExpression(returnExpr) && + returnExpr.parent()?.kind() !== "parenthesized_expression" && + !wrappedJsxStarts.has(returnExpr.range().start.index) + ) { + wrappedJsxStarts.add(returnExpr.range().start.index); + edits.push({ + startPos: returnExpr.range().start.index, + endPos: returnExpr.range().start.index, + insertedText: "(", + }); + edits.push({ + startPos: returnExpr.range().end.index, + endPos: returnExpr.range().end.index, + insertedText: ")", + }); + } + refNames.push(refName); + } + + if (refNames.length > 0) { + transformMetric.increment({ + refs: refNames.sort().join(","), + file: metricFile(root.filename()), + }, refNames.length); + } + + if (edits.length === 0) { + return null; + } + + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/jssg/replace-string-ref/tests/class-component-custom-import-names/expected.tsx b/codemods/jssg/replace-string-ref/tests/class-component-custom-import-names/expected.tsx new file mode 100644 index 0000000..70193fe --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/class-component-custom-import-names/expected.tsx @@ -0,0 +1,17 @@ +import React1, { PureComponent as PureComponent1 } from "react"; + +class C extends React1.Component { + render() { + return (
{ + this.refs.refName = ref; + }} />); + } +} + +class C1 extends PureComponent1 { + render() { + return (
{ + this.refs.refName = ref; + }} />); + } +} diff --git a/replace-string-ref/tests/original-class-component-custom-import-names/input.tsx b/codemods/jssg/replace-string-ref/tests/class-component-custom-import-names/input.tsx similarity index 100% rename from replace-string-ref/tests/original-class-component-custom-import-names/input.tsx rename to codemods/jssg/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/jssg/replace-string-ref/tests/class-component-custom-import-names/metrics.json new file mode 100644 index 0000000..9be800e --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/class-component-custom-import-names/metrics.json @@ -0,0 +1,11 @@ +{ + "string-ref-replacements": [ + { + "cardinality": { + "file": "tests/class-component-custom-import-names/input.tsx", + "refs": "refName,refName" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-string-ref/tests/class-component-default-import/expected.tsx b/codemods/jssg/replace-string-ref/tests/class-component-default-import/expected.tsx new file mode 100644 index 0000000..90367dd --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/class-component-default-import/expected.tsx @@ -0,0 +1,17 @@ +import React from "react"; + +class C extends React.Component { + render() { + return (
{ + this.refs.refName = ref; + }} />); + } +} + +class C1 extends React.PureComponent { + render() { + return (
{ + this.refs.refName = ref; + }} />); + } +} diff --git a/replace-string-ref/tests/original-class-component-default-import/input.tsx b/codemods/jssg/replace-string-ref/tests/class-component-default-import/input.tsx similarity index 100% rename from replace-string-ref/tests/original-class-component-default-import/input.tsx rename to codemods/jssg/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/jssg/replace-string-ref/tests/class-component-default-import/metrics.json new file mode 100644 index 0000000..a7db8e3 --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/class-component-default-import/metrics.json @@ -0,0 +1,11 @@ +{ + "string-ref-replacements": [ + { + "cardinality": { + "file": "tests/class-component-default-import/input.tsx", + "refs": "refName,refName" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-string-ref/tests/class-component-named-import/expected.tsx b/codemods/jssg/replace-string-ref/tests/class-component-named-import/expected.tsx new file mode 100644 index 0000000..cac99e6 --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/class-component-named-import/expected.tsx @@ -0,0 +1,17 @@ +import { Component, PureComponent } from "react"; + +class C extends Component { + render() { + return (
{ + this.refs.refName = ref; + }} />); + } +} + +class C1 extends PureComponent { + render() { + return (
{ + this.refs.refName = ref; + }} />); + } +} diff --git a/replace-string-ref/tests/original-class-component-named-import/input.tsx b/codemods/jssg/replace-string-ref/tests/class-component-named-import/input.tsx similarity index 100% rename from replace-string-ref/tests/original-class-component-named-import/input.tsx rename to codemods/jssg/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/jssg/replace-string-ref/tests/class-component-named-import/metrics.json new file mode 100644 index 0000000..1ca0b5f --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/class-component-named-import/metrics.json @@ -0,0 +1,11 @@ +{ + "string-ref-replacements": [ + { + "cardinality": { + "file": "tests/class-component-named-import/input.tsx", + "refs": "refName,refName" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-string-ref/tests/export-default-class/expected.tsx b/codemods/jssg/replace-string-ref/tests/export-default-class/expected.tsx new file mode 100644 index 0000000..3ee5887 --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/export-default-class/expected.tsx @@ -0,0 +1,4 @@ +import React from "react"; +export default class C extends React.Component { render() { return (
{ + this.refs.refName = ref; + }} />); } } diff --git a/codemods/jssg/replace-string-ref/tests/export-default-class/input.tsx b/codemods/jssg/replace-string-ref/tests/export-default-class/input.tsx new file mode 100644 index 0000000..bdc33e9 --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/export-default-class/input.tsx @@ -0,0 +1,2 @@ +import React from "react"; +export default class C extends React.Component { render() { return
; } } diff --git a/codemods/jssg/replace-string-ref/tests/export-default-class/metrics.json b/codemods/jssg/replace-string-ref/tests/export-default-class/metrics.json new file mode 100644 index 0000000..8998681 --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/export-default-class/metrics.json @@ -0,0 +1,11 @@ +{ + "string-ref-replacements": [ + { + "cardinality": { + "file": "tests/export-default-class/input.tsx", + "refs": "refName" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/replace-string-ref/tests/original-function-component/expected.tsx b/codemods/jssg/replace-string-ref/tests/function-component/expected.tsx similarity index 100% rename from replace-string-ref/tests/original-function-component/expected.tsx rename to codemods/jssg/replace-string-ref/tests/function-component/expected.tsx diff --git a/replace-string-ref/tests/original-function-component/input.tsx b/codemods/jssg/replace-string-ref/tests/function-component/input.tsx similarity index 100% rename from replace-string-ref/tests/original-function-component/input.tsx rename to codemods/jssg/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/jssg/replace-string-ref/tests/member-expression-ref-value-no-change/expected.tsx new file mode 100644 index 0000000..fee1ffe --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/member-expression-ref-value-no-change/expected.tsx @@ -0,0 +1,2 @@ +import React from "react"; +class C extends React.Component { render(){ return
; } } diff --git a/codemods/jssg/replace-string-ref/tests/member-expression-ref-value-no-change/input.tsx b/codemods/jssg/replace-string-ref/tests/member-expression-ref-value-no-change/input.tsx new file mode 100644 index 0000000..fee1ffe --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/member-expression-ref-value-no-change/input.tsx @@ -0,0 +1,2 @@ +import React from "react"; +class C extends React.Component { render(){ return
; } } diff --git a/codemods/jssg/replace-string-ref/tests/mixin-component-no-change/expected.tsx b/codemods/jssg/replace-string-ref/tests/mixin-component-no-change/expected.tsx new file mode 100644 index 0000000..7eda311 --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/mixin-component-no-change/expected.tsx @@ -0,0 +1,2 @@ +import { Component } from "react"; +class C extends mixin(Component) { render(){ return
; } } diff --git a/codemods/jssg/replace-string-ref/tests/mixin-component-no-change/input.tsx b/codemods/jssg/replace-string-ref/tests/mixin-component-no-change/input.tsx new file mode 100644 index 0000000..7eda311 --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/mixin-component-no-change/input.tsx @@ -0,0 +1,2 @@ +import { Component } from "react"; +class C extends mixin(Component) { render(){ return
; } } diff --git a/codemods/jssg/replace-string-ref/tests/multiple-string-refs/expected.tsx b/codemods/jssg/replace-string-ref/tests/multiple-string-refs/expected.tsx new file mode 100644 index 0000000..68c052a --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/multiple-string-refs/expected.tsx @@ -0,0 +1,6 @@ +import React from "react"; +class C extends React.Component { render() { return <>
{ + this.refs.a = ref; + }} /> { + this.refs.b = ref; + }} />; } } diff --git a/codemods/jssg/replace-string-ref/tests/multiple-string-refs/input.tsx b/codemods/jssg/replace-string-ref/tests/multiple-string-refs/input.tsx new file mode 100644 index 0000000..16a4334 --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/multiple-string-refs/input.tsx @@ -0,0 +1,2 @@ +import React from "react"; +class C extends React.Component { render() { return <>
; } } diff --git a/codemods/jssg/replace-string-ref/tests/multiple-string-refs/metrics.json b/codemods/jssg/replace-string-ref/tests/multiple-string-refs/metrics.json new file mode 100644 index 0000000..d2db766 --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/multiple-string-refs/metrics.json @@ -0,0 +1,11 @@ +{ + "string-ref-replacements": [ + { + "cardinality": { + "file": "tests/multiple-string-refs/input.tsx", + "refs": "a,b" + }, + "count": 2 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-string-ref/tests/namespace-import/expected.tsx b/codemods/jssg/replace-string-ref/tests/namespace-import/expected.tsx new file mode 100644 index 0000000..69b90a7 --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/namespace-import/expected.tsx @@ -0,0 +1,4 @@ +import * as React1 from "react"; +class C extends React1.Component { render() { return (
{ + this.refs.refName = ref; + }} />); } } diff --git a/codemods/jssg/replace-string-ref/tests/namespace-import/input.tsx b/codemods/jssg/replace-string-ref/tests/namespace-import/input.tsx new file mode 100644 index 0000000..9bca446 --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/namespace-import/input.tsx @@ -0,0 +1,2 @@ +import * as React1 from "react"; +class C extends React1.Component { render() { return
; } } diff --git a/codemods/jssg/replace-string-ref/tests/namespace-import/metrics.json b/codemods/jssg/replace-string-ref/tests/namespace-import/metrics.json new file mode 100644 index 0000000..8034886 --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/namespace-import/metrics.json @@ -0,0 +1,11 @@ +{ + "string-ref-replacements": [ + { + "cardinality": { + "file": "tests/namespace-import/input.tsx", + "refs": "refName" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-string-ref/tests/non-identifier-ref-name/expected.tsx b/codemods/jssg/replace-string-ref/tests/non-identifier-ref-name/expected.tsx new file mode 100644 index 0000000..e31278e --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/non-identifier-ref-name/expected.tsx @@ -0,0 +1,4 @@ +import React from "react"; +class C extends React.Component { render(){ return (
{ + this.refs["foo-bar"] = ref; + }} />); } } diff --git a/codemods/jssg/replace-string-ref/tests/non-identifier-ref-name/input.tsx b/codemods/jssg/replace-string-ref/tests/non-identifier-ref-name/input.tsx new file mode 100644 index 0000000..218c61c --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/non-identifier-ref-name/input.tsx @@ -0,0 +1,2 @@ +import React from "react"; +class C extends React.Component { render(){ return
; } } diff --git a/codemods/jssg/replace-string-ref/tests/non-identifier-ref-name/metrics.json b/codemods/jssg/replace-string-ref/tests/non-identifier-ref-name/metrics.json new file mode 100644 index 0000000..d373883 --- /dev/null +++ b/codemods/jssg/replace-string-ref/tests/non-identifier-ref-name/metrics.json @@ -0,0 +1,11 @@ +{ + "string-ref-replacements": [ + { + "cardinality": { + "file": "tests/non-identifier-ref-name/input.tsx", + "refs": "foo-bar" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-string-ref/tsconfig.json b/codemods/jssg/replace-string-ref/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/jssg/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/find-dom-node/workflow.yaml b/codemods/jssg/replace-string-ref/workflow.yaml similarity index 100% rename from find-dom-node/workflow.yaml rename to codemods/jssg/replace-string-ref/workflow.yaml diff --git a/codemods/jssg/replace-use-form-state/README.md b/codemods/jssg/replace-use-form-state/README.md new file mode 100644 index 0000000..4d2a790 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/README.md @@ -0,0 +1,16 @@ +# replace-use-form-state + +Rename `useFormState` to `useActionState` in `react-dom` imports and usages. + +## Usage + +```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/jssg/replace-use-form-state/codemod.yaml new file mode 100644 index 0000000..b648f6b --- /dev/null +++ b/codemods/jssg/replace-use-form-state/codemod.yaml @@ -0,0 +1,19 @@ +schema_version: "1.0" + +name: "@react-new/replace-use-form-state" +version: "0.1.0" +description: "Rename useFormState to useActionState in react-dom imports and usages" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + +targets: + languages: ["tsx"] + +keywords: ["React"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/jssg/replace-use-form-state/package.json b/codemods/jssg/replace-use-form-state/package.json new file mode 100644 index 0000000..fbcf347 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/package.json @@ -0,0 +1,18 @@ +{ + "name": "@react-new/replace-use-form-state", + "version": "0.1.0", + "description": "Rename useFormState to useActionState in react-dom imports and usages", + "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/replace-use-form-state/scripts/codemod.ts b/codemods/jssg/replace-use-form-state/scripts/codemod.ts new file mode 100644 index 0000000..3b90d31 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/scripts/codemod.ts @@ -0,0 +1,210 @@ +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; +} + + +const REACT_DOM_MODULE = "react-dom"; + +type NamedUseFormStateImport = { importNode: SgNode; specifier: SgNode; localName: string }; + +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(node: SgNode): string | null { + const source = node.field("source") ?? node.find({ rule: { kind: "string" } }); + return source ? sourceText(source) : null; +} + +function findReactDOMMemberImportNames(rootNode: SgNode): Set { + const names = new Set(); + + for (const imp of rootNode.findAll({ rule: { kind: "import_statement" } })) { + if (importSource(imp) !== REACT_DOM_MODULE) continue; + + const importClause = imp.find({ rule: { kind: "import_clause" } }); + const defaultIdentifier = importClause?.children().find((child) => child.kind() === "identifier"); + if (defaultIdentifier) names.add(defaultIdentifier.text()); + + const namespaceImport = imp.find({ rule: { kind: "namespace_import" } }); + const namespaceName = + namespaceImport?.field("name") ?? namespaceImport?.find({ rule: { kind: "identifier" } }); + if (namespaceName) names.add(namespaceName.text()); + } + + return names; +} + +function findNamedUseFormStateImports(rootNode: SgNode): NamedUseFormStateImport[] { + const imports: NamedUseFormStateImport[] = []; + + for (const importNode of rootNode.findAll({ rule: { kind: "import_statement" } })) { + if (importSource(importNode) !== REACT_DOM_MODULE) continue; + + for (const specifier of importNode.findAll({ rule: { kind: "import_specifier" } })) { + if (specifier.field("name")?.text() !== "useFormState") continue; + imports.push({ + importNode, + specifier, + localName: specifier.field("alias")?.text() ?? specifier.field("name")?.text() ?? "useFormState", + }); + } + } + + return imports; +} + +const REACT_MODULE = "react"; + +const transform: Transform = async (root) => { + const rootNode = root.root(); + const edits: Edit[] = []; + const metric = useMetricAtom("replace-use-form-state-migrations"); + + const reactDOMMemberImportNames = findReactDOMMemberImportNames(rootNode); + const namedUseFormStateImports = findNamedUseFormStateImports(rootNode); + + let needsReactImport = false; + + if (reactDOMMemberImportNames.size > 0) { + const memberCalls = rootNode.findAll({ + rule: { + kind: "member_expression", + has: { + kind: "property_identifier", + regex: "^useFormState$", + }, + }, + }); + + for (const member of memberCalls) { + const objNode = member.field("object"); + if (objNode && reactDOMMemberImportNames.has(objNode.text())) { + // Replace the entire member expression (ReactDOM.useFormState) with just useActionState + edits.push(member.replace("useActionState")); + needsReactImport = true; + metric.increment({ file: metricFile(root.filename()), pattern: "member-call" }); + } + } + } + + if (namedUseFormStateImports.length > 0) { + // Track import statements we've already edited to avoid duplicate edits + const handledImportRanges = new Set(); + + for (const entry of namedUseFormStateImports) { + const rangeKey = `${entry.importNode.range().start.index}-${entry.importNode.range().end.index}`; + if (!handledImportRanges.has(rangeKey)) { + handledImportRanges.add(rangeKey); + + // Count other specifiers in this import (non-useFormState) + const allSpecifiers = entry.importNode.findAll({ rule: { kind: "import_specifier" } }); + const otherSpecifiers = allSpecifiers.filter( + (s) => s.field("name")?.text() !== "useFormState" + ); + const importClause = entry.importNode.find({ rule: { kind: "import_clause" } }); + const defaultIdentifier = importClause?.children().find((child) => child.kind() === "identifier"); + + // Detect the quote style from the original import source + const sourceNode = entry.importNode.field("source") ?? entry.importNode.find({ rule: { kind: "string" } }); + const quoteChar = sourceNode?.text().startsWith("'") ? "'" : '"'; + + const reactImportSpecifier = entry.localName !== "useFormState" + ? `useActionState as ${entry.localName}` + : "useActionState"; + + if (otherSpecifiers.length === 0 && !defaultIdentifier) { + // useFormState is the only specifier — replace entire import with react import + edits.push(entry.importNode.replace( + `import { ${reactImportSpecifier} } from ${quoteChar}react${quoteChar};` + )); + } else if (defaultIdentifier && otherSpecifiers.length === 0) { + // Has default import + useFormState only — remove named imports part, add react import + // e.g., import ReactDOM, { useFormState } from 'react-dom' + // → import ReactDOM from 'react-dom'; + // import { useActionState } from 'react'; + const defaultName = defaultIdentifier.text(); + edits.push(entry.importNode.replace( + `import ${defaultName} from ${quoteChar}${REACT_DOM_MODULE}${quoteChar};\nimport { ${reactImportSpecifier} } from ${quoteChar}react${quoteChar};` + )); + } else { + // Other specifiers remain — rebuild import without useFormState, add react import + const otherSpecTexts = otherSpecifiers.map((s) => { + const name = s.field("name")?.text(); + const alias = s.field("alias")?.text(); + return alias && alias !== name ? `${name} as ${alias}` : name; + }); + const prefix = defaultIdentifier ? `${defaultIdentifier.text()}, ` : ""; + edits.push(entry.importNode.replace( + `import ${prefix}{ ${otherSpecTexts.join(", ")} } from ${quoteChar}${REACT_DOM_MODULE}${quoteChar};\nimport { ${reactImportSpecifier} } from ${quoteChar}react${quoteChar};` + )); + } + } + + needsReactImport = true; + + if (entry.localName === "useFormState") { + // Rename all usage sites from useFormState to useActionState + const renameTargets = rootNode.findAll({ + rule: { + any: [ + { kind: "identifier", regex: "^useFormState$" }, + { kind: "type_identifier", regex: "^useFormState$" }, + ], + }, + }); + + for (const node of renameTargets) { + if (node.ancestors().some((ancestor) => ancestor.kind() === "import_statement")) continue; + edits.push(node.replace("useActionState")); + } + } + } + + metric.increment({ file: metricFile(root.filename()), pattern: "named-import" }); + } + + if (edits.length === 0) return null; + + // For member access cases (ReactDOM.useFormState), add import { useActionState } from "react" + // Named import cases are already handled above (import replacement includes the react import) + if (needsReactImport && namedUseFormStateImports.length === 0) { + const hasReactImport = getImport(rootNode, { + type: "named", + name: "useActionState", + from: REACT_MODULE, + }); + if (!hasReactImport) { + // Find last import statement to insert after + const allImports = rootNode.findAll({ rule: { kind: "import_statement" } }); + const lastImport = allImports[allImports.length - 1]; + if (lastImport) { + const endPos = lastImport.range().end.index; + edits.push({ + startPos: endPos, + endPos: endPos, + insertedText: '\nimport { useActionState } from "react";', + }); + } else { + edits.push({ + startPos: 0, + endPos: 0, + insertedText: 'import { useActionState } from "react";\n', + }); + } + } + } + + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/jssg/replace-use-form-state/tests/aliased-to-same-name/expected.tsx b/codemods/jssg/replace-use-form-state/tests/aliased-to-same-name/expected.tsx new file mode 100644 index 0000000..968de3c --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/aliased-to-same-name/expected.tsx @@ -0,0 +1,2 @@ +import { useActionState } from 'react'; +function C(){ return useActionState(a,0); } diff --git a/codemods/jssg/replace-use-form-state/tests/aliased-to-same-name/input.tsx b/codemods/jssg/replace-use-form-state/tests/aliased-to-same-name/input.tsx new file mode 100644 index 0000000..1701827 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/aliased-to-same-name/input.tsx @@ -0,0 +1,2 @@ +import { useFormState as useFormState } from 'react-dom'; +function C(){ return useFormState(a,0); } diff --git a/codemods/jssg/replace-use-form-state/tests/aliased-to-same-name/metrics.json b/codemods/jssg/replace-use-form-state/tests/aliased-to-same-name/metrics.json new file mode 100644 index 0000000..d06ff7d --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/aliased-to-same-name/metrics.json @@ -0,0 +1,11 @@ +{ + "replace-use-form-state-migrations": [ + { + "cardinality": { + "file": "tests/aliased-to-same-name/input.tsx", + "pattern": "named-import" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-use-form-state/tests/default-and-named/expected.tsx b/codemods/jssg/replace-use-form-state/tests/default-and-named/expected.tsx new file mode 100644 index 0000000..81949bc --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/default-and-named/expected.tsx @@ -0,0 +1,3 @@ +import ReactDOM from 'react-dom'; +import { useActionState } from 'react'; +function C(){ const x = useActionState(a,0); return useActionState(b,1); } diff --git a/codemods/jssg/replace-use-form-state/tests/default-and-named/input.tsx b/codemods/jssg/replace-use-form-state/tests/default-and-named/input.tsx new file mode 100644 index 0000000..b1247ed --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/default-and-named/input.tsx @@ -0,0 +1,2 @@ +import ReactDOM, { useFormState } from 'react-dom'; +function C(){ const x = useFormState(a,0); return ReactDOM.useFormState(b,1); } diff --git a/codemods/jssg/replace-use-form-state/tests/default-and-named/metrics.json b/codemods/jssg/replace-use-form-state/tests/default-and-named/metrics.json new file mode 100644 index 0000000..fc8f667 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/default-and-named/metrics.json @@ -0,0 +1,18 @@ +{ + "replace-use-form-state-migrations": [ + { + "cardinality": { + "file": "tests/default-and-named/input.tsx", + "pattern": "member-call" + }, + "count": 1 + }, + { + "cardinality": { + "file": "tests/default-and-named/input.tsx", + "pattern": "named-import" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-use-form-state/tests/default-import/expected.tsx b/codemods/jssg/replace-use-form-state/tests/default-import/expected.tsx new file mode 100644 index 0000000..c359d05 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/default-import/expected.tsx @@ -0,0 +1,7 @@ +import ReactDOM from "react-dom"; +import { useActionState } from "react"; + +function StatefulForm({}) { + const [state, formAction] = useActionState(increment, 0); + return
; +} diff --git a/replace-use-form-state/tests/default-import/input.tsx b/codemods/jssg/replace-use-form-state/tests/default-import/input.tsx similarity index 100% rename from replace-use-form-state/tests/default-import/input.tsx rename to codemods/jssg/replace-use-form-state/tests/default-import/input.tsx diff --git a/replace-use-form-state/tests/default-import/metrics.json b/codemods/jssg/replace-use-form-state/tests/default-import/metrics.json similarity index 100% rename from replace-use-form-state/tests/default-import/metrics.json rename to codemods/jssg/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/jssg/replace-use-form-state/tests/member-access-non-import-no-change/expected.tsx new file mode 100644 index 0000000..30ae5b3 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/member-access-non-import-no-change/expected.tsx @@ -0,0 +1,2 @@ +const ReactDOM = other; +function C(){ return ReactDOM.useFormState(a,0); } diff --git a/codemods/jssg/replace-use-form-state/tests/member-access-non-import-no-change/input.tsx b/codemods/jssg/replace-use-form-state/tests/member-access-non-import-no-change/input.tsx new file mode 100644 index 0000000..30ae5b3 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/member-access-non-import-no-change/input.tsx @@ -0,0 +1,2 @@ +const ReactDOM = other; +function C(){ return ReactDOM.useFormState(a,0); } diff --git a/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/expected.tsx b/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/expected.tsx new file mode 100644 index 0000000..2303201 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/expected.tsx @@ -0,0 +1,3 @@ +import { useActionState } from 'react'; +import { useActionState as ufs } from 'react'; +function C(){ useActionState(a,0); return ufs(b,1); } diff --git a/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/input.tsx b/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/input.tsx new file mode 100644 index 0000000..1363096 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/input.tsx @@ -0,0 +1,3 @@ +import { useFormState } from 'react-dom'; +import { useFormState as ufs } from 'react-dom'; +function C(){ useFormState(a,0); return ufs(b,1); } diff --git a/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/metrics.json b/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/metrics.json new file mode 100644 index 0000000..30814d4 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-both-have-hook/metrics.json @@ -0,0 +1,11 @@ +{ + "replace-use-form-state-migrations": [ + { + "cardinality": { + "file": "tests/multiple-react-dom-imports-both-have-hook/input.tsx", + "pattern": "named-import" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/expected.tsx b/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/expected.tsx new file mode 100644 index 0000000..2842302 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/expected.tsx @@ -0,0 +1,3 @@ +import { createPortal } from 'react-dom'; +import { useActionState } from 'react'; +function C(){ return useActionState(a,0); } diff --git a/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/input.tsx b/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/input.tsx new file mode 100644 index 0000000..c3611b0 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/input.tsx @@ -0,0 +1,3 @@ +import { createPortal } from 'react-dom'; +import { useFormState } from 'react-dom'; +function C(){ return useFormState(a,0); } diff --git a/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/metrics.json b/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/metrics.json new file mode 100644 index 0000000..3b7e6ea --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/multiple-react-dom-imports-second-has-hook/metrics.json @@ -0,0 +1,11 @@ +{ + "replace-use-form-state-migrations": [ + { + "cardinality": { + "file": "tests/multiple-react-dom-imports-second-has-hook/input.tsx", + "pattern": "named-import" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-use-form-state/tests/named-import-2/expected.tsx b/codemods/jssg/replace-use-form-state/tests/named-import-2/expected.tsx new file mode 100644 index 0000000..e24620d --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/named-import-2/expected.tsx @@ -0,0 +1,9 @@ +import { createPortal } from "react-dom"; +import { useActionState } from "react"; + +function StatefulForm({}) { + const [state, formAction] = useActionState(increment, 0); + + createPortal(); + return
; +} diff --git a/replace-use-form-state/tests/named-import-2/input.tsx b/codemods/jssg/replace-use-form-state/tests/named-import-2/input.tsx similarity index 100% rename from replace-use-form-state/tests/named-import-2/input.tsx rename to codemods/jssg/replace-use-form-state/tests/named-import-2/input.tsx diff --git a/replace-use-form-state/tests/named-import-2/metrics.json b/codemods/jssg/replace-use-form-state/tests/named-import-2/metrics.json similarity index 100% rename from replace-use-form-state/tests/named-import-2/metrics.json rename to codemods/jssg/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/jssg/replace-use-form-state/tests/named-import-3/expected.tsx new file mode 100644 index 0000000..3915af5 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/named-import-3/expected.tsx @@ -0,0 +1,9 @@ +import { createPortal } from "react-dom"; +import { useActionState as UFS } from "react"; + +function StatefulForm({}) { + const [state, formAction] = UFS(increment, 0); + + createPortal(); + return
; +} diff --git a/replace-use-form-state/tests/named-import-3/input.tsx b/codemods/jssg/replace-use-form-state/tests/named-import-3/input.tsx similarity index 100% rename from replace-use-form-state/tests/named-import-3/input.tsx rename to codemods/jssg/replace-use-form-state/tests/named-import-3/input.tsx diff --git a/replace-use-form-state/tests/named-import-3/metrics.json b/codemods/jssg/replace-use-form-state/tests/named-import-3/metrics.json similarity index 100% rename from replace-use-form-state/tests/named-import-3/metrics.json rename to codemods/jssg/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/jssg/replace-use-form-state/tests/named-import/expected.tsx new file mode 100644 index 0000000..02ae2e2 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/named-import/expected.tsx @@ -0,0 +1,15 @@ +import { useActionState } from "react"; + +async function increment(previousState, formData) { + return previousState + 1; +} + +function StatefulForm({}) { + const [state, formAction] = useActionState(increment, 0); + return ( +
+ {state} + +
+ ); +} diff --git a/replace-use-form-state/tests/named-import/input.tsx b/codemods/jssg/replace-use-form-state/tests/named-import/input.tsx similarity index 100% rename from replace-use-form-state/tests/named-import/input.tsx rename to codemods/jssg/replace-use-form-state/tests/named-import/input.tsx diff --git a/replace-use-form-state/tests/named-import/metrics.json b/codemods/jssg/replace-use-form-state/tests/named-import/metrics.json similarity index 100% rename from replace-use-form-state/tests/named-import/metrics.json rename to codemods/jssg/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/jssg/replace-use-form-state/tests/namespace-and-named/expected.tsx new file mode 100644 index 0000000..dc100c8 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/namespace-and-named/expected.tsx @@ -0,0 +1,3 @@ +import * as ReactDOM from 'react-dom'; +import { useActionState as useFormStateAlias } from 'react'; +function C(){ const x = useFormStateAlias(a,0); return useActionState(b,1); } diff --git a/codemods/jssg/replace-use-form-state/tests/namespace-and-named/input.tsx b/codemods/jssg/replace-use-form-state/tests/namespace-and-named/input.tsx new file mode 100644 index 0000000..537efc5 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/namespace-and-named/input.tsx @@ -0,0 +1,3 @@ +import * as ReactDOM from 'react-dom'; +import { useFormState as useFormStateAlias } from 'react-dom'; +function C(){ const x = useFormStateAlias(a,0); return ReactDOM.useFormState(b,1); } diff --git a/codemods/jssg/replace-use-form-state/tests/namespace-and-named/metrics.json b/codemods/jssg/replace-use-form-state/tests/namespace-and-named/metrics.json new file mode 100644 index 0000000..507ddec --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/namespace-and-named/metrics.json @@ -0,0 +1,18 @@ +{ + "replace-use-form-state-migrations": [ + { + "cardinality": { + "file": "tests/namespace-and-named/input.tsx", + "pattern": "member-call" + }, + "count": 1 + }, + { + "cardinality": { + "file": "tests/namespace-and-named/input.tsx", + "pattern": "named-import" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/replace-use-form-state/tests/other-import/expected.tsx b/codemods/jssg/replace-use-form-state/tests/other-import/expected.tsx similarity index 100% rename from replace-use-form-state/tests/other-import/expected.tsx rename to codemods/jssg/replace-use-form-state/tests/other-import/expected.tsx diff --git a/replace-use-form-state/tests/other-import/input.tsx b/codemods/jssg/replace-use-form-state/tests/other-import/input.tsx similarity index 100% rename from replace-use-form-state/tests/other-import/input.tsx rename to codemods/jssg/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/jssg/replace-use-form-state/tests/shadowed-local-binding/expected.tsx new file mode 100644 index 0000000..fa7078c --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/shadowed-local-binding/expected.tsx @@ -0,0 +1,2 @@ +import { useActionState } from 'react'; +function C(){ const useActionState = make(); return useActionState(a,0); } diff --git a/codemods/jssg/replace-use-form-state/tests/shadowed-local-binding/input.tsx b/codemods/jssg/replace-use-form-state/tests/shadowed-local-binding/input.tsx new file mode 100644 index 0000000..98f5afe --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/shadowed-local-binding/input.tsx @@ -0,0 +1,2 @@ +import { useFormState } from 'react-dom'; +function C(){ const useFormState = make(); return useFormState(a,0); } diff --git a/codemods/jssg/replace-use-form-state/tests/shadowed-local-binding/metrics.json b/codemods/jssg/replace-use-form-state/tests/shadowed-local-binding/metrics.json new file mode 100644 index 0000000..54c7b7e --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/shadowed-local-binding/metrics.json @@ -0,0 +1,11 @@ +{ + "replace-use-form-state-migrations": [ + { + "cardinality": { + "file": "tests/shadowed-local-binding/input.tsx", + "pattern": "named-import" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-use-form-state/tests/type-identifier-rename/expected.tsx b/codemods/jssg/replace-use-form-state/tests/type-identifier-rename/expected.tsx new file mode 100644 index 0000000..d15e457 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/type-identifier-rename/expected.tsx @@ -0,0 +1,3 @@ +import { useActionState } from 'react'; +type useActionState = number; +function C(){ return useActionState(a,0); } diff --git a/codemods/jssg/replace-use-form-state/tests/type-identifier-rename/input.tsx b/codemods/jssg/replace-use-form-state/tests/type-identifier-rename/input.tsx new file mode 100644 index 0000000..1724eee --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/type-identifier-rename/input.tsx @@ -0,0 +1,3 @@ +import { useFormState } from 'react-dom'; +type useFormState = number; +function C(){ return useFormState(a,0); } diff --git a/codemods/jssg/replace-use-form-state/tests/type-identifier-rename/metrics.json b/codemods/jssg/replace-use-form-state/tests/type-identifier-rename/metrics.json new file mode 100644 index 0000000..adca29c --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/type-identifier-rename/metrics.json @@ -0,0 +1,11 @@ +{ + "replace-use-form-state-migrations": [ + { + "cardinality": { + "file": "tests/type-identifier-rename/input.tsx", + "pattern": "named-import" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/replace-use-form-state/tests/wildcard-import/expected.tsx b/codemods/jssg/replace-use-form-state/tests/wildcard-import/expected.tsx new file mode 100644 index 0000000..78187b0 --- /dev/null +++ b/codemods/jssg/replace-use-form-state/tests/wildcard-import/expected.tsx @@ -0,0 +1,7 @@ +import * as ReactDOM from "react-dom"; +import { useActionState } from "react"; + +function StatefulForm({}) { + const [state, formAction] = useActionState(increment, 0); + return
; +} diff --git a/replace-use-form-state/tests/wildcard-import/input.tsx b/codemods/jssg/replace-use-form-state/tests/wildcard-import/input.tsx similarity index 100% rename from replace-use-form-state/tests/wildcard-import/input.tsx rename to codemods/jssg/replace-use-form-state/tests/wildcard-import/input.tsx diff --git a/replace-use-form-state/tests/wildcard-import/metrics.json b/codemods/jssg/replace-use-form-state/tests/wildcard-import/metrics.json similarity index 100% rename from replace-use-form-state/tests/wildcard-import/metrics.json rename to codemods/jssg/replace-use-form-state/tests/wildcard-import/metrics.json diff --git a/codemods/jssg/replace-use-form-state/tsconfig.json b/codemods/jssg/replace-use-form-state/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/jssg/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/replace-use-form-state/workflow.yaml b/codemods/jssg/replace-use-form-state/workflow.yaml similarity index 100% rename from replace-use-form-state/workflow.yaml rename to codemods/jssg/replace-use-form-state/workflow.yaml diff --git a/replace-string-ref/.gitignore b/codemods/jssg/use-context-hook/.gitignore similarity index 100% rename from replace-string-ref/.gitignore rename to codemods/jssg/use-context-hook/.gitignore diff --git a/codemods/jssg/use-context-hook/README.md b/codemods/jssg/use-context-hook/README.md new file mode 100644 index 0000000..c87cf70 --- /dev/null +++ b/codemods/jssg/use-context-hook/README.md @@ -0,0 +1,16 @@ +# use-context-hook + +Transform `React.useContext()` and `useContext()` calls into `use()`. + +## Usage + +```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/jssg/use-context-hook/codemod.yaml new file mode 100644 index 0000000..1a161a4 --- /dev/null +++ b/codemods/jssg/use-context-hook/codemod.yaml @@ -0,0 +1,20 @@ +schema_version: "1.0" + +name: "@react-new/use-context-hook" +version: "0.1.0" +description: "Transform React.useContext() and useContext() to use() hook" +author: "Codemod " +license: "MIT" +workflow: "workflow.yaml" + + +targets: + languages: ["tsx"] + +keywords: ["React"] + +registry: + access: "public" + visibility: "private" + +capabilities: [] diff --git a/codemods/jssg/use-context-hook/package.json b/codemods/jssg/use-context-hook/package.json new file mode 100644 index 0000000..fb190e8 --- /dev/null +++ b/codemods/jssg/use-context-hook/package.json @@ -0,0 +1,18 @@ +{ + "name": "@react-new/use-context-hook", + "version": "0.1.0", + "description": "Transform React.useContext() and useContext() to use() hook", + "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/update-react-imports/pnpm-lock.yaml b/codemods/jssg/use-context-hook/pnpm-lock.yaml similarity index 100% rename from update-react-imports/pnpm-lock.yaml rename to codemods/jssg/use-context-hook/pnpm-lock.yaml diff --git a/codemods/jssg/use-context-hook/scripts/codemod.ts b/codemods/jssg/use-context-hook/scripts/codemod.ts new file mode 100644 index 0000000..1bde92d --- /dev/null +++ b/codemods/jssg/use-context-hook/scripts/codemod.ts @@ -0,0 +1,201 @@ +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 escapeRegex(text: string): string { + return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +type NamedUseContextImport = { specifier: SgNode; localName: string }; + +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(node: SgNode): string | null { + const source = node.field("source") ?? node.find({ rule: { kind: "string" } }); + return source ? sourceText(source) : null; +} + +function findReactMemberImportNames(rootNode: SgNode): Set { + const names = new Set(); + + for (const imp of rootNode.findAll({ rule: { kind: "import_statement" } })) { + if (importSource(imp) !== "react") continue; + + const importClause = imp.find({ rule: { kind: "import_clause" } }); + const defaultIdentifier = importClause?.children().find((child) => child.kind() === "identifier"); + if (defaultIdentifier) names.add(defaultIdentifier.text()); + + const namespaceImport = imp.find({ rule: { kind: "namespace_import" } }); + const namespaceName = + namespaceImport?.field("name") ?? namespaceImport?.find({ rule: { kind: "identifier" } }); + if (namespaceName) names.add(namespaceName.text()); + } + + return names; +} + +function findNamedUseContextImports(rootNode: SgNode): NamedUseContextImport[] { + const imports: NamedUseContextImport[] = []; + + for (const importNode of rootNode.findAll({ rule: { kind: "import_statement" } })) { + if (importSource(importNode) !== "react") continue; + + for (const specifier of importNode.findAll({ rule: { kind: "import_specifier" } })) { + if (specifier.field("name")?.text() !== "useContext") continue; + imports.push({ + specifier, + localName: specifier.field("alias")?.text() ?? "useContext", + }); + } + } + + return imports; +} + +const transform: Transform = async (root) => { + const rootNode = root.root(); + const edits: Edit[] = []; + + const transformMetric = useMetricAtom("use-context-transformations"); + + const reactMemberImportNames = findReactMemberImportNames(rootNode); + const namedUseContextImports = findNamedUseContextImports(rootNode); + + const shouldSkipLegacyIdentifierRename = (node: SgNode): boolean => { + const parent = node.parent(); + if (!parent) return false; + if (node.ancestors().some((ancestor) => ancestor.kind() === "import_statement")) return true; + if ( + parent.kind() === "member_expression" && + parent.field("property")?.id() === node.id() + ) { + return true; + } + return false; + }; + + const reactMemberCalls = rootNode.findAll({ + rule: { + kind: "call_expression", + has: { + field: "function", + kind: "member_expression", + all: [ + { + has: { + field: "object", + kind: "identifier", + regex: ".*", + }, + }, + { + has: { + field: "property", + kind: "property_identifier", + regex: "^useContext$", + }, + }, + ], + }, + }, + }); + + let reactMemberCallCount = 0; + for (const call of reactMemberCalls) { + const functionNode = call.field("function"); + const objectNode = functionNode?.field("object"); + if (!objectNode || !reactMemberImportNames.has(objectNode.text())) continue; + + const propertyNode = functionNode?.field("property"); + if (!propertyNode) continue; + + edits.push(propertyNode.replace("use")); + reactMemberCallCount += 1; + } + + if (reactMemberCallCount > 0) { + transformMetric.increment({ + pattern: "React.useContext", + file: metricFile(root.filename()), + }, reactMemberCallCount); + } + + const activeLocalNames = new Map(); + for (const localName of new Set(namedUseContextImports.map((entry) => entry.localName))) { + const useContextCalls = rootNode.findAll({ + rule: { + kind: "call_expression", + has: { + field: "function", + kind: "identifier", + regex: `^${escapeRegex(localName)}$`, + }, + }, + }); + + if (useContextCalls.length > 0) { + activeLocalNames.set(localName, useContextCalls.length); + } + } + + if (activeLocalNames.has("useContext")) { + const renameTargets = rootNode.findAll({ + rule: { + any: [ + { kind: "identifier", regex: "^useContext$" }, + { kind: "type_identifier", regex: "^useContext$" }, + ], + }, + }); + + for (const node of renameTargets) { + if (shouldSkipLegacyIdentifierRename(node)) continue; + edits.push(node.replace("use")); + } + } + + for (const entry of namedUseContextImports) { + if (!activeLocalNames.has(entry.localName)) continue; + const replacementText = entry.localName === "useContext" ? "use" : `use as ${entry.localName}`; + edits.push(entry.specifier.replace(replacementText)); + } + + const unaliasedCallCount = activeLocalNames.get("useContext") ?? 0; + if (unaliasedCallCount > 0) { + transformMetric.increment({ + pattern: "useContext", + file: metricFile(root.filename()), + }, unaliasedCallCount); + } + + let aliasedCallCount = 0; + for (const [localName, count] of activeLocalNames) { + if (localName === "useContext") continue; + aliasedCallCount += count; + } + + if (aliasedCallCount > 0) { + transformMetric.increment({ + pattern: "useContext (aliased)", + file: metricFile(root.filename()), + }, aliasedCallCount); + } + + if (edits.length === 0) { + return null; + } + + return rootNode.commitEdits(edits); +}; + +export default transform; diff --git a/codemods/jssg/use-context-hook/tests/aliased-use-context-dollar/expected.tsx b/codemods/jssg/use-context-hook/tests/aliased-use-context-dollar/expected.tsx new file mode 100644 index 0000000..8142bc3 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/aliased-use-context-dollar/expected.tsx @@ -0,0 +1,3 @@ +import { use as $ctx } from "react"; + +const value = $ctx(ThemeContext); diff --git a/codemods/jssg/use-context-hook/tests/aliased-use-context-dollar/input.tsx b/codemods/jssg/use-context-hook/tests/aliased-use-context-dollar/input.tsx new file mode 100644 index 0000000..4f5e735 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/aliased-use-context-dollar/input.tsx @@ -0,0 +1,3 @@ +import { useContext as $ctx } from "react"; + +const value = $ctx(ThemeContext); diff --git a/codemods/jssg/use-context-hook/tests/aliased-use-context-dollar/metrics.json b/codemods/jssg/use-context-hook/tests/aliased-use-context-dollar/metrics.json new file mode 100644 index 0000000..79abe9f --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/aliased-use-context-dollar/metrics.json @@ -0,0 +1,11 @@ +{ + "use-context-transformations": [ + { + "cardinality": { + "file": "tests/aliased-use-context-dollar/input.tsx", + "pattern": "useContext (aliased)" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/aliased-use-context/expected.tsx b/codemods/jssg/use-context-hook/tests/aliased-use-context/expected.tsx new file mode 100644 index 0000000..f5c66fd --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/aliased-use-context/expected.tsx @@ -0,0 +1,2 @@ +import { use as uc } from "react"; +const x = uc(ThemeContext); diff --git a/codemods/jssg/use-context-hook/tests/aliased-use-context/input.tsx b/codemods/jssg/use-context-hook/tests/aliased-use-context/input.tsx new file mode 100644 index 0000000..2290c34 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/aliased-use-context/input.tsx @@ -0,0 +1,2 @@ +import { useContext as uc } from "react"; +const x = uc(ThemeContext); diff --git a/codemods/jssg/use-context-hook/tests/aliased-use-context/metrics.json b/codemods/jssg/use-context-hook/tests/aliased-use-context/metrics.json new file mode 100644 index 0000000..50ae81a --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/aliased-use-context/metrics.json @@ -0,0 +1,11 @@ +{ + "use-context-transformations": [ + { + "cardinality": { + "file": "tests/aliased-use-context/input.tsx", + "pattern": "useContext (aliased)" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/any-use-context/expected.tsx b/codemods/jssg/use-context-hook/tests/any-use-context/expected.tsx new file mode 100644 index 0000000..75bfeb5 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/any-use-context/expected.tsx @@ -0,0 +1 @@ +const theme = trpc.useContext(); \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/any-use-context/input.tsx b/codemods/jssg/use-context-hook/tests/any-use-context/input.tsx new file mode 100644 index 0000000..75bfeb5 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/any-use-context/input.tsx @@ -0,0 +1 @@ +const theme = trpc.useContext(); \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/mixed-import/expected.tsx b/codemods/jssg/use-context-hook/tests/mixed-import/expected.tsx new file mode 100644 index 0000000..7901080 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/mixed-import/expected.tsx @@ -0,0 +1,4 @@ +import React, { use } from "react"; +import ThemeContext from "./ThemeContext"; + +const theme = use(ThemeContext); \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/mixed-import/input.tsx b/codemods/jssg/use-context-hook/tests/mixed-import/input.tsx new file mode 100644 index 0000000..fb91bfd --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/mixed-import/input.tsx @@ -0,0 +1,4 @@ +import React, { useContext } from "react"; +import ThemeContext from "./ThemeContext"; + +const theme = useContext(ThemeContext); \ No newline at end of file diff --git a/use-context-hook/tests/mixed-import/metrics.json b/codemods/jssg/use-context-hook/tests/mixed-import/metrics.json similarity index 100% rename from use-context-hook/tests/mixed-import/metrics.json rename to codemods/jssg/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/jssg/use-context-hook/tests/multiple-use-context-imports/expected.tsx new file mode 100644 index 0000000..7526834 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/multiple-use-context-imports/expected.tsx @@ -0,0 +1,4 @@ +import { use } from "react"; +import { use as uc } from "react"; +const a = use(C1); +const b = uc(C2); diff --git a/codemods/jssg/use-context-hook/tests/multiple-use-context-imports/input.tsx b/codemods/jssg/use-context-hook/tests/multiple-use-context-imports/input.tsx new file mode 100644 index 0000000..17cd21a --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/multiple-use-context-imports/input.tsx @@ -0,0 +1,4 @@ +import { useContext } from "react"; +import { useContext as uc } from "react"; +const a = useContext(C1); +const b = uc(C2); diff --git a/codemods/jssg/use-context-hook/tests/multiple-use-context-imports/metrics.json b/codemods/jssg/use-context-hook/tests/multiple-use-context-imports/metrics.json new file mode 100644 index 0000000..4d18366 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/multiple-use-context-imports/metrics.json @@ -0,0 +1,18 @@ +{ + "use-context-transformations": [ + { + "cardinality": { + "file": "tests/multiple-use-context-imports/input.tsx", + "pattern": "useContext" + }, + "count": 1 + }, + { + "cardinality": { + "file": "tests/multiple-use-context-imports/input.tsx", + "pattern": "useContext (aliased)" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/shadowed-local-binding/expected.tsx b/codemods/jssg/use-context-hook/tests/shadowed-local-binding/expected.tsx new file mode 100644 index 0000000..254ae32 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/shadowed-local-binding/expected.tsx @@ -0,0 +1,2 @@ +import { use } from "react"; +function f(use){ return use(Ctx); } diff --git a/codemods/jssg/use-context-hook/tests/shadowed-local-binding/input.tsx b/codemods/jssg/use-context-hook/tests/shadowed-local-binding/input.tsx new file mode 100644 index 0000000..e010190 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/shadowed-local-binding/input.tsx @@ -0,0 +1,2 @@ +import { useContext } from "react"; +function f(useContext){ return useContext(Ctx); } diff --git a/codemods/jssg/use-context-hook/tests/shadowed-local-binding/metrics.json b/codemods/jssg/use-context-hook/tests/shadowed-local-binding/metrics.json new file mode 100644 index 0000000..b71459b --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/shadowed-local-binding/metrics.json @@ -0,0 +1,11 @@ +{ + "use-context-transformations": [ + { + "cardinality": { + "file": "tests/shadowed-local-binding/input.tsx", + "pattern": "useContext" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/type-identifier-rename/expected.tsx b/codemods/jssg/use-context-hook/tests/type-identifier-rename/expected.tsx new file mode 100644 index 0000000..76b6176 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/type-identifier-rename/expected.tsx @@ -0,0 +1,3 @@ +import { use } from "react"; +type use = number; +const x = use(Ctx); diff --git a/codemods/jssg/use-context-hook/tests/type-identifier-rename/input.tsx b/codemods/jssg/use-context-hook/tests/type-identifier-rename/input.tsx new file mode 100644 index 0000000..0a7b35b --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/type-identifier-rename/input.tsx @@ -0,0 +1,3 @@ +import { useContext } from "react"; +type useContext = number; +const x = useContext(Ctx); diff --git a/codemods/jssg/use-context-hook/tests/type-identifier-rename/metrics.json b/codemods/jssg/use-context-hook/tests/type-identifier-rename/metrics.json new file mode 100644 index 0000000..ef8bf7f --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/type-identifier-rename/metrics.json @@ -0,0 +1,11 @@ +{ + "use-context-transformations": [ + { + "cardinality": { + "file": "tests/type-identifier-rename/input.tsx", + "pattern": "useContext" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/typescript-any-use-context/expected.tsx b/codemods/jssg/use-context-hook/tests/typescript-any-use-context/expected.tsx new file mode 100644 index 0000000..88397ec --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/typescript-any-use-context/expected.tsx @@ -0,0 +1,8 @@ +function Component({ + appUrl, +}: { + appUrl: string; +}) { + const theme = trpc.useContext(); + return
; +}; \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/typescript-any-use-context/input.tsx b/codemods/jssg/use-context-hook/tests/typescript-any-use-context/input.tsx new file mode 100644 index 0000000..88397ec --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/typescript-any-use-context/input.tsx @@ -0,0 +1,8 @@ +function Component({ + appUrl, +}: { + appUrl: string; +}) { + const theme = trpc.useContext(); + return
; +}; \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/typescript-use-context-2/expected.tsx b/codemods/jssg/use-context-hook/tests/typescript-use-context-2/expected.tsx new file mode 100644 index 0000000..a873ec9 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/typescript-use-context-2/expected.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import ThemeContext from "./ThemeContext"; + +function Component({ + appUrl, +}: { + appUrl: string; +}) { + const theme = React.use(ThemeContext); + return
; +}; \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/typescript-use-context-2/input.tsx b/codemods/jssg/use-context-hook/tests/typescript-use-context-2/input.tsx new file mode 100644 index 0000000..062dab5 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/typescript-use-context-2/input.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import ThemeContext from "./ThemeContext"; + +function Component({ + appUrl, +}: { + appUrl: string; +}) { + const theme = React.useContext(ThemeContext); + return
; +}; \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/typescript-use-context-2/metrics.json b/codemods/jssg/use-context-hook/tests/typescript-use-context-2/metrics.json new file mode 100644 index 0000000..c1aaf10 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/typescript-use-context-2/metrics.json @@ -0,0 +1,11 @@ +{ + "use-context-transformations": [ + { + "cardinality": { + "file": "tests/typescript-use-context-2/input.tsx", + "pattern": "React.useContext" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/typescript-use-context/expected.tsx b/codemods/jssg/use-context-hook/tests/typescript-use-context/expected.tsx new file mode 100644 index 0000000..d44d7ae --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/typescript-use-context/expected.tsx @@ -0,0 +1,11 @@ +import { use } from "react"; +import ThemeContext from "./ThemeContext"; + +function Component({ + appUrl, +}: { + appUrl: string; +}) { + const theme = use(ThemeContext); + return
; +}; \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/typescript-use-context/input.tsx b/codemods/jssg/use-context-hook/tests/typescript-use-context/input.tsx new file mode 100644 index 0000000..7464058 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/typescript-use-context/input.tsx @@ -0,0 +1,11 @@ +import { useContext } from "react"; +import ThemeContext from "./ThemeContext"; + +function Component({ + appUrl, +}: { + appUrl: string; +}) { + const theme = useContext(ThemeContext); + return
; +}; \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/typescript-use-context/metrics.json b/codemods/jssg/use-context-hook/tests/typescript-use-context/metrics.json new file mode 100644 index 0000000..98a6998 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/typescript-use-context/metrics.json @@ -0,0 +1,11 @@ +{ + "use-context-transformations": [ + { + "cardinality": { + "file": "tests/typescript-use-context/input.tsx", + "pattern": "useContext" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/use-context-2/expected.tsx b/codemods/jssg/use-context-hook/tests/use-context-2/expected.tsx new file mode 100644 index 0000000..f7f504f --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/use-context-2/expected.tsx @@ -0,0 +1,4 @@ +import React from "react"; +import ThemeContext from "./ThemeContext"; + +const theme = React.use(ThemeContext); \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/use-context-2/input.tsx b/codemods/jssg/use-context-hook/tests/use-context-2/input.tsx new file mode 100644 index 0000000..67718d9 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/use-context-2/input.tsx @@ -0,0 +1,4 @@ +import React from "react"; +import ThemeContext from "./ThemeContext"; + +const theme = React.useContext(ThemeContext); \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/use-context-2/metrics.json b/codemods/jssg/use-context-hook/tests/use-context-2/metrics.json new file mode 100644 index 0000000..5e0a448 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/use-context-2/metrics.json @@ -0,0 +1,11 @@ +{ + "use-context-transformations": [ + { + "cardinality": { + "file": "tests/use-context-2/input.tsx", + "pattern": "React.useContext" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/use-context/expected.tsx b/codemods/jssg/use-context-hook/tests/use-context/expected.tsx new file mode 100644 index 0000000..e251b57 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/use-context/expected.tsx @@ -0,0 +1,4 @@ +import { use } from "react"; +import ThemeContext from "./ThemeContext"; + +const theme = use(ThemeContext); \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/use-context/input.tsx b/codemods/jssg/use-context-hook/tests/use-context/input.tsx new file mode 100644 index 0000000..979a8e4 --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/use-context/input.tsx @@ -0,0 +1,4 @@ +import { useContext } from "react"; +import ThemeContext from "./ThemeContext"; + +const theme = useContext(ThemeContext); \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tests/use-context/metrics.json b/codemods/jssg/use-context-hook/tests/use-context/metrics.json new file mode 100644 index 0000000..06a884c --- /dev/null +++ b/codemods/jssg/use-context-hook/tests/use-context/metrics.json @@ -0,0 +1,11 @@ +{ + "use-context-transformations": [ + { + "cardinality": { + "file": "tests/use-context/input.tsx", + "pattern": "useContext" + }, + "count": 1 + } + ] +} \ No newline at end of file diff --git a/codemods/jssg/use-context-hook/tsconfig.json b/codemods/jssg/use-context-hook/tsconfig.json new file mode 100644 index 0000000..decec57 --- /dev/null +++ b/codemods/jssg/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/manual-bind-to-arrow/workflow.yaml b/codemods/jssg/use-context-hook/workflow.yaml similarity index 100% rename from manual-bind-to-arrow/workflow.yaml rename to codemods/jssg/use-context-hook/workflow.yaml diff --git a/codemods/legacy/.babelrc b/codemods/legacy/.babelrc new file mode 100644 index 0000000..5295ecd --- /dev/null +++ b/codemods/legacy/.babelrc @@ -0,0 +1,4 @@ +{ + "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 new file mode 100644 index 0000000..09957ab --- /dev/null +++ b/codemods/legacy/jest/require-actual.d.ts @@ -0,0 +1,5 @@ +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 new file mode 100644 index 0000000..81a0e55 --- /dev/null +++ b/codemods/legacy/jest/setup-require-actual.js @@ -0,0 +1,6 @@ +Object.defineProperty(Function.prototype, "requireActual", { + value: jest.requireActual, + configurable: true, + writable: true, +}); + diff --git a/codemods/legacy/package.json b/codemods/legacy/package.json new file mode 100644 index 0000000..09252c9 --- /dev/null +++ b/codemods/legacy/package.json @@ -0,0 +1,42 @@ +{ + "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 new file mode 100644 index 0000000..72e1c10 --- /dev/null +++ b/codemods/legacy/pnpm-lock.yaml @@ -0,0 +1,6904 @@ +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 new file mode 100644 index 0000000..55a8b4a --- /dev/null +++ b/codemods/legacy/transforms/React-DOM-to-react-dom-factories.js @@ -0,0 +1,177 @@ +/** + * 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 new file mode 100644 index 0000000..70ec81a --- /dev/null +++ b/codemods/legacy/transforms/ReactNative-View-propTypes.js @@ -0,0 +1,218 @@ +/** + * 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 new file mode 100644 index 0000000..4a2e77c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/.eslintrc @@ -0,0 +1,5 @@ +--- +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-basic-case.input.js b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-basic-case.input.js new file mode 100644 index 0000000..7543d34 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-basic-case.input.js @@ -0,0 +1,7 @@ +const React = require('react'); + +class Hello extends React.Component { + render() { + return React.DOM.div(null, `Hello ${this.props.toWhat}`); + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-basic-case.output.js b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-basic-case.output.js new file mode 100644 index 0000000..fdc8bc6 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-basic-case.output.js @@ -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/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-import.input.js b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-import.input.js new file mode 100644 index 0000000..32a4b2c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-import.input.js @@ -0,0 +1,11 @@ +import ReactDOM from 'ReactDOM'; +import { + Component, + DOM +} from '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-deconstructed-import.output.js b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-import.output.js new file mode 100644 index 0000000..9be5131 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-import.output.js @@ -0,0 +1,11 @@ +import ReactDOM from 'ReactDOM'; +import { + Component, + createElement +} from 'react'; + +class Hello extends Component { + render() { + return createElement('div', null, `Hello ${this.props.toWhat}`); + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require-part-two.input.js b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require-part-two.input.js new file mode 100644 index 0000000..dd4bec3 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require-part-two.input.js @@ -0,0 +1,11 @@ +const ReactDOM = require('ReactDOM'); +const { + Component, + DOM +} = require('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-deconstructed-require-part-two.output.js b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require-part-two.output.js new file mode 100644 index 0000000..b5c30c2 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require-part-two.output.js @@ -0,0 +1,11 @@ +const ReactDOM = require('ReactDOM'); +const { + Component, + createElement +} = require('react'); + +class Hello extends Component { + render() { + return createElement('div', null, `Hello ${this.props.toWhat}`); + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require.input.js b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require.input.js new file mode 100644 index 0000000..92706fc --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require.input.js @@ -0,0 +1,12 @@ +const React = require('react'); +const ReactDOM = require('ReactDOM'); +const { + Component, + DOM +} = 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-deconstructed-require.output.js b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require.output.js new file mode 100644 index 0000000..a8ba771 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-deconstructed-require.output.js @@ -0,0 +1,12 @@ +const React = require('react'); +const ReactDOM = require('ReactDOM'); +const { + Component, + createElement +} = React; + +class Hello extends Component { + render() { + return createElement('div', null, `Hello ${this.props.toWhat}`); + } +} 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/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-import-dom-from-other-libraries.input.js new file mode 100644 index 0000000..b7657cf --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-import-dom-from-other-libraries.input.js @@ -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-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 new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-import.input.js b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-import.input.js new file mode 100644 index 0000000..3c7cf3f --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-import.input.js @@ -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-import.output.js b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-import.output.js new file mode 100644 index 0000000..e69de29 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/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-local-dom-from-other-libraries.input.js new file mode 100644 index 0000000..3cf194d --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-local-dom-from-other-libraries.input.js @@ -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/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 new file mode 100644 index 0000000..e69de29 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/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-require-dom-from-other-libraries.input.js new file mode 100644 index 0000000..14a21a0 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-require-dom-from-other-libraries.input.js @@ -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-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 new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-require.input.js b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-require.input.js new file mode 100644 index 0000000..fdc8bc6 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-require.input.js @@ -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/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 new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-multi-reference.input.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-multi-reference.input.js new file mode 100644 index 0000000..fde8f85 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-multi-reference.input.js @@ -0,0 +1,10 @@ +import { PropTypes } from 'react'; +import View from 'View'; + +function Component() { + return ; +} + +Component.propTypes = View.propTypes; + +module.exports = Component; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-multi-reference.output.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-multi-reference.output.js new file mode 100644 index 0000000..fefcf5b --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-multi-reference.output.js @@ -0,0 +1,12 @@ +import { PropTypes } from 'react'; +import View from 'View'; + +import ViewPropTypes from 'ViewPropTypes'; + +function Component() { + return ; +} + +Component.propTypes = ViewPropTypes; + +module.exports = Component; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-only-reference.input.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-only-reference.input.js new file mode 100644 index 0000000..d38b46e --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-only-reference.input.js @@ -0,0 +1,11 @@ +import { PropTypes } from 'react'; +import Text from 'Text'; +import View from 'View'; + +function Component() { + return text; +} + +Component.propTypes = View.propTypes; + +module.exports = Component; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-only-reference.output.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-only-reference.output.js new file mode 100644 index 0000000..0cd4562 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-import-only-reference.output.js @@ -0,0 +1,11 @@ +import { PropTypes } from 'react'; +import Text from 'Text'; +import ViewPropTypes from 'ViewPropTypes'; + +function Component() { + return text; +} + +Component.propTypes = ViewPropTypes; + +module.exports = Component; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-multi-reference.input.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-multi-reference.input.js new file mode 100644 index 0000000..cdb7389 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-multi-reference.input.js @@ -0,0 +1,19 @@ +var React = require('React'); +var View = require('View'); + +var PropTypes = React.PropTypes; + +class ASTrackView extends React.Component { + static propTypes = { + style: View.propTypes.style, + track: PropTypes.object.isRequired, + }; + + render() { + return ( + + {this.props.track} + + ); + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-multi-reference.output.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-multi-reference.output.js new file mode 100644 index 0000000..1dc25be --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-multi-reference.output.js @@ -0,0 +1,21 @@ +var React = require('React'); +var View = require('View'); + +const ViewPropTypes = require('ViewPropTypes'); + +var PropTypes = React.PropTypes; + +class ASTrackView extends React.Component { + static propTypes = { + style: ViewPropTypes.style, + track: PropTypes.object.isRequired, + }; + + render() { + return ( + + {this.props.track} + + ); + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-only-reference.input.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-only-reference.input.js new file mode 100644 index 0000000..47e3d95 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-only-reference.input.js @@ -0,0 +1,13 @@ +const Animated = require('Animated'); +const React = require('React'); +const View = require('View'); + +function MyComponent() { + return React.createElement(Animated.View); +} + +MyComponent.propTypes = { + style: View.propTypes.style +}; + +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-only-reference.output.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-only-reference.output.js new file mode 100644 index 0000000..e078e4f --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/default-require-only-reference.output.js @@ -0,0 +1,13 @@ +const Animated = require('Animated'); +const React = require('React'); +const ViewPropTypes = require('ViewPropTypes'); + +function MyComponent() { + return React.createElement(Animated.View); +} + +MyComponent.propTypes = { + style: ViewPropTypes.style +}; + +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-multi-reference.input.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-multi-reference.input.js new file mode 100644 index 0000000..9d9b18f --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-multi-reference.input.js @@ -0,0 +1,12 @@ +import { PropTypes } from 'react'; +import { requireNativeComponent, View } from 'react-native'; + +const Foo = requireNativeComponent('Foo'); + +function MyComponent(props) { + return ; +} + +MyComponent.propTypes = View.propTypes; + +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-multi-reference.output.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-multi-reference.output.js new file mode 100644 index 0000000..02e1c4c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-multi-reference.output.js @@ -0,0 +1,12 @@ +import { PropTypes } from 'react'; +import { requireNativeComponent, View, ViewPropTypes } from 'react-native'; + +const Foo = requireNativeComponent('Foo'); + +function MyComponent(props) { + return ; +} + +MyComponent.propTypes = ViewPropTypes; + +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-only-reference.input.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-only-reference.input.js new file mode 100644 index 0000000..ee44a7a --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-only-reference.input.js @@ -0,0 +1,12 @@ +import { PropTypes } from 'react'; +import { requireNativeComponent, View } from 'react-native'; + +const Foo = requireNativeComponent('Foo'); + +function MyComponent(props) { + return ; +} + +MyComponent.propTypes = View.propTypes; + +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-only-reference.output.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-only-reference.output.js new file mode 100644 index 0000000..852adb8 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-import-only-reference.output.js @@ -0,0 +1,12 @@ +import { PropTypes } from 'react'; +import { requireNativeComponent, ViewPropTypes } from 'react-native'; + +const Foo = requireNativeComponent('Foo'); + +function MyComponent(props) { + return ; +} + +MyComponent.propTypes = ViewPropTypes; + +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-multi-reference.input.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-multi-reference.input.js new file mode 100644 index 0000000..44fc9fe --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-multi-reference.input.js @@ -0,0 +1,12 @@ +const { PropTypes } = require('react'); +const { requireNativeComponent, View } = require('react-native'); + +const Foo = requireNativeComponent('Foo'); + +function MyComponent(props) { + return ; +} + +MyComponent.propTypes = View.propTypes; + +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-multi-reference.output.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-multi-reference.output.js new file mode 100644 index 0000000..82bc166 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-multi-reference.output.js @@ -0,0 +1,16 @@ +const { PropTypes } = require('react'); +const { + requireNativeComponent, + View, + ViewPropTypes +} = require('react-native'); + +const Foo = requireNativeComponent('Foo'); + +function MyComponent(props) { + return ; +} + +MyComponent.propTypes = ViewPropTypes; + +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-only-reference.input.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-only-reference.input.js new file mode 100644 index 0000000..2cad73a --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-only-reference.input.js @@ -0,0 +1,10 @@ +const PropTypes = require('react'); +const { Animated, View } = require('react-native'); + +function MyComponent(props) { + return ; +} + +MyComponent.propTypes = View.propTypes; + +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-only-reference.output.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-only-reference.output.js new file mode 100644 index 0000000..f0f2214 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/destructured-require-only-reference.output.js @@ -0,0 +1,10 @@ +const PropTypes = require('react'); +const { Animated, ViewPropTypes } = require('react-native'); + +function MyComponent(props) { + return ; +} + +MyComponent.propTypes = ViewPropTypes; + +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/import-flow-type-with-require.input.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/import-flow-type-with-require.input.js new file mode 100644 index 0000000..f734f33 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/import-flow-type-with-require.input.js @@ -0,0 +1,15 @@ +const Animated = require('Animated'); +const React = require('React'); +const View = require('View'); + +import type { Foo } from 'Foo'; + +function MyComponent(): Foo { + return React.createElement(Animated.View); +} + +MyComponent.propTypes = { + style: View.propTypes.style +}; + +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/import-flow-type-with-require.output.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/import-flow-type-with-require.output.js new file mode 100644 index 0000000..2264c11 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/import-flow-type-with-require.output.js @@ -0,0 +1,15 @@ +const Animated = require('Animated'); +const React = require('React'); +const ViewPropTypes = require('ViewPropTypes'); + +import type { Foo } from 'Foo'; + +function MyComponent(): Foo { + return React.createElement(Animated.View); +} + +MyComponent.propTypes = { + style: ViewPropTypes.style +}; + +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/multiple-replacements.input.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/multiple-replacements.input.js new file mode 100644 index 0000000..fcef6a3 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/multiple-replacements.input.js @@ -0,0 +1,29 @@ +const React = require('React'); +const ScrollView = require('ScrollView'); +const View = require('View'); + +const PropTypes = React.PropTypes; + +class AdsManagerAbstractRow extends React.Component { + static propTypes = { + containerStyle: View.propTypes.style, + style: View.propTypes.style, + }; + + render() { + let child = null; + if (this.props.child) { + child = ( + + {this.props.child} + + ); + } + + return ( + + {child} + + ); + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/multiple-replacements.output.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/multiple-replacements.output.js new file mode 100644 index 0000000..2da7197 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/multiple-replacements.output.js @@ -0,0 +1,31 @@ +const React = require('React'); +const ScrollView = require('ScrollView'); +const View = require('View'); + +const ViewPropTypes = require('ViewPropTypes'); + +const PropTypes = React.PropTypes; + +class AdsManagerAbstractRow extends React.Component { + static propTypes = { + containerStyle: ViewPropTypes.style, + style: ViewPropTypes.style, + }; + + render() { + let child = null; + if (this.props.child) { + child = ( + + {this.props.child} + + ); + } + + return ( + + {child} + + ); + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/noop-import.input.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/noop-import.input.js new file mode 100644 index 0000000..e02fc4a --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/noop-import.input.js @@ -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-import.output.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/noop-import.output.js new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/noop-require.input.js b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/noop-require.input.js new file mode 100644 index 0000000..c1bc51a --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/ReactNative-View-propTypes/noop-require.input.js @@ -0,0 +1,6 @@ +const React = require('React'); +const View = require('View'); + +module.exports = function Component() { + return ; +}; 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 new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/class/class-anonymous.input.js b/codemods/legacy/transforms/__testfixtures__/class/class-anonymous.input.js new file mode 100644 index 0000000..22b4ca7 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-anonymous.input.js @@ -0,0 +1,21 @@ +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 new file mode 100644 index 0000000..c90ecbc --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-anonymous.output.js @@ -0,0 +1,21 @@ +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 new file mode 100644 index 0000000..a902474 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-anonymous2.input.js @@ -0,0 +1,50 @@ +/** + * @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 new file mode 100644 index 0000000..1b7a8fc --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-anonymous2.output.js @@ -0,0 +1,50 @@ +/** + * @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 new file mode 100644 index 0000000..5d6ed33 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-create-class-naming.input.js @@ -0,0 +1,12 @@ +// 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 new file mode 100644 index 0000000..85e9385 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-create-class-naming.output.js @@ -0,0 +1,16 @@ +// 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 new file mode 100644 index 0000000..61aac9c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-displayName.input.js @@ -0,0 +1,31 @@ +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 new file mode 100644 index 0000000..1018735 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-displayName.output.js @@ -0,0 +1,41 @@ +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 new file mode 100644 index 0000000..da855cb --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-flow1.input.js @@ -0,0 +1,40 @@ +/* @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 new file mode 100644 index 0000000..7e50622 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-flow1.output.js @@ -0,0 +1,65 @@ +/* @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 new file mode 100644 index 0000000..490706c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-flow2.input.js @@ -0,0 +1,52 @@ +/* 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 new file mode 100644 index 0000000..3a6b74d --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-flow2.output.js @@ -0,0 +1,54 @@ +/* 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 new file mode 100644 index 0000000..f42dfd4 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-flow3.input.js @@ -0,0 +1,47 @@ +/* @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 new file mode 100644 index 0000000..932b233 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-flow3.output.js @@ -0,0 +1,64 @@ +/* @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 new file mode 100644 index 0000000..9694eb6 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-flow4.input.js @@ -0,0 +1,44 @@ +/* @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 new file mode 100644 index 0000000..a25c625 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-flow4.output.js @@ -0,0 +1,44 @@ +/* @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 new file mode 100644 index 0000000..3abae35 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-flow5.input.js @@ -0,0 +1,40 @@ +/* @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 new file mode 100644 index 0000000..ca00112 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-flow5.output.js @@ -0,0 +1,39 @@ +/* @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 new file mode 100644 index 0000000..b008f81 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-flow6.input.js @@ -0,0 +1,41 @@ +/* @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 new file mode 100644 index 0000000..1ac3ff0 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-flow6.output.js @@ -0,0 +1,62 @@ +/* @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 new file mode 100644 index 0000000..b008f81 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-flow7.input.js @@ -0,0 +1,41 @@ +/* @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 new file mode 100644 index 0000000..a30533b --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-flow7.output.js @@ -0,0 +1,37 @@ +/* @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 new file mode 100644 index 0000000..bb18702 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-initial-state.input.js @@ -0,0 +1,335 @@ +/* @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 new file mode 100644 index 0000000..ee332e3 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-initial-state.output.js @@ -0,0 +1,362 @@ +/* @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 new file mode 100644 index 0000000..d12952a --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-no-conversion.input.js @@ -0,0 +1,9 @@ +'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 new file mode 100644 index 0000000..82768d7 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-no-conversion.output.js @@ -0,0 +1,13 @@ +'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 new file mode 100644 index 0000000..a2f65e0 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-no-display-name.input.js @@ -0,0 +1,10 @@ +'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 new file mode 100644 index 0000000..1385b8f --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-no-display-name.output.js @@ -0,0 +1,12 @@ +'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 new file mode 100644 index 0000000..e6ba400 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-property-field.input.js @@ -0,0 +1,28 @@ +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 new file mode 100644 index 0000000..34305a2 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-property-field.output.js @@ -0,0 +1,26 @@ +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 new file mode 100644 index 0000000..478746e --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-prune-react.input.js @@ -0,0 +1,16 @@ +'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 new file mode 100644 index 0000000..85ab457 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-prune-react.output.js @@ -0,0 +1,18 @@ +'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 new file mode 100644 index 0000000..ad3d240 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-prune-react2.input.js @@ -0,0 +1,16 @@ +'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 new file mode 100644 index 0000000..53548b9 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-prune-react2.output.js @@ -0,0 +1,20 @@ +'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 new file mode 100644 index 0000000..69fb6b1 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-prune-react3.input.js @@ -0,0 +1,19 @@ +'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 new file mode 100644 index 0000000..9580454 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-prune-react3.output.js @@ -0,0 +1,24 @@ +'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 new file mode 100644 index 0000000..2f68eb1 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-prune-react4.input.js @@ -0,0 +1,19 @@ +'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 new file mode 100644 index 0000000..8bb26a9 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-prune-react4.output.js @@ -0,0 +1,24 @@ +'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 new file mode 100644 index 0000000..381a7e7 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin1.input.js @@ -0,0 +1,19 @@ +// 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 new file mode 100644 index 0000000..741db3c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin1.output.js @@ -0,0 +1,14 @@ +// 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 new file mode 100644 index 0000000..820b1e3 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin2.input.js @@ -0,0 +1,29 @@ +/** + * 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 new file mode 100644 index 0000000..559a31e --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin2.output.js @@ -0,0 +1,24 @@ +/** + * 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 new file mode 100644 index 0000000..fd7e2e1 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin3.input.js @@ -0,0 +1,20 @@ +// 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 new file mode 100644 index 0000000..d40d339 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin3.output.js @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 0000000..a1e7054 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin4.input.js @@ -0,0 +1,36 @@ +/** + * 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 new file mode 100644 index 0000000..5902dfb --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin4.output.js @@ -0,0 +1,32 @@ +/** + * 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 new file mode 100644 index 0000000..b098172 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin5.input.js @@ -0,0 +1,19 @@ +// 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 new file mode 100644 index 0000000..741db3c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-pure-mixin5.output.js @@ -0,0 +1,14 @@ +// 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 new file mode 100644 index 0000000..ae54847 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-test2.input.js @@ -0,0 +1,116 @@ +'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 new file mode 100644 index 0000000..50ab376 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-test2.output.js @@ -0,0 +1,115 @@ +'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 new file mode 100644 index 0000000..b01b81c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-top-comment.input.js @@ -0,0 +1,27 @@ +/** + * 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 new file mode 100644 index 0000000..0da85b9 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class-top-comment.output.js @@ -0,0 +1,22 @@ +/** + * 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 new file mode 100644 index 0000000..96a0d9d --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class.input.js @@ -0,0 +1,214 @@ +'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 new file mode 100644 index 0000000..61e1065 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/class.output.js @@ -0,0 +1,225 @@ +'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 new file mode 100644 index 0000000..148843b --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/export-default-class.input.js @@ -0,0 +1,21 @@ +/*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 new file mode 100644 index 0000000..23a7d26 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/class/export-default-class.output.js @@ -0,0 +1,19 @@ +/*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-allow-member-expression.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-allow-member-expression.input.js new file mode 100644 index 0000000..2a78a80 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-allow-member-expression.input.js @@ -0,0 +1,3 @@ +var React = require('react/addons'); + +React.createElement(React.addons.CSSTransitionGroup, null, ''); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-allow-member-expression.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-allow-member-expression.output.js new file mode 100644 index 0000000..bfa842b --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-allow-member-expression.output.js @@ -0,0 +1,5 @@ +var React = require('react/addons'); + + + {''} +; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-arg-spread.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-arg-spread.input.js new file mode 100644 index 0000000..c5f23f2 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-arg-spread.input.js @@ -0,0 +1,4 @@ +var React = require('React'); + +React.createElement(Foo, null, ...children); +React.createElement(Foo, null, firstChild, ...otherChildren); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-arg-spread.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-arg-spread.output.js new file mode 100644 index 0000000..75b2136 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-arg-spread.output.js @@ -0,0 +1,9 @@ +var React = require('React'); + + + {children} +; + + {firstChild} + {otherChildren} +; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-as-children.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-as-children.input.js new file mode 100644 index 0000000..73dae68 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-as-children.input.js @@ -0,0 +1,3 @@ +var React = require('react/addons'); + +React.createElement('div', {}, foo()); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-as-children.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-as-children.output.js new file mode 100644 index 0000000..79e558a --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-as-children.output.js @@ -0,0 +1,5 @@ +var React = require('react/addons'); + +
+ {foo()} +
; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-expression-as-prop.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-expression-as-prop.input.js new file mode 100644 index 0000000..eb81962 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-expression-as-prop.input.js @@ -0,0 +1,3 @@ +var React = require('react/addons'); + +React.createElement('div', getProps(), 'foo'); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-expression-as-prop.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-expression-as-prop.output.js new file mode 100644 index 0000000..f9cb036 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-call-expression-as-prop.output.js @@ -0,0 +1,5 @@ +var React = require('react/addons'); + +
+ foo +
; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-literal.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-literal.input.js new file mode 100644 index 0000000..c8500fd --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-literal.input.js @@ -0,0 +1,3 @@ +var React = require('React'); + +React.createElement('div', null, 'foo'); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-literal.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-literal.output.js new file mode 100644 index 0000000..92639f8 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-literal.output.js @@ -0,0 +1,5 @@ +var React = require('React'); + +
+ foo +
; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-map.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-map.input.js new file mode 100644 index 0000000..ae04dc9 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-map.input.js @@ -0,0 +1,7 @@ +var React = require('React'); + +React.createElement( + 'div', + null, + foo.map(function() {}) +); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-map.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-map.output.js new file mode 100644 index 0000000..90817f2 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-map.output.js @@ -0,0 +1,5 @@ +var React = require('React'); + +
+ {foo.map(function() {})} +
; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-mixed-empty-string.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-mixed-empty-string.input.js new file mode 100644 index 0000000..0e521fe --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-mixed-empty-string.input.js @@ -0,0 +1,11 @@ +var React = require('react'); + +a = 'foo'; + +React.createElement( + 'div', + null, + a, + ' ', + a +); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-mixed-empty-string.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-mixed-empty-string.output.js new file mode 100644 index 0000000..08c1f61 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children-mixed-empty-string.output.js @@ -0,0 +1,9 @@ +var React = require('react'); + +a = 'foo'; + +
+ {a} + {' '} + {a} +
; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children.input.js new file mode 100644 index 0000000..5d32fa9 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children.input.js @@ -0,0 +1,12 @@ +var React = require('React'); + +var a = React.createElement( + Foo, + null, + React.createElement('div', { foo: 'bar' }), + React.createElement( + 'span', + null, + 'blah' + ) +); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children.output.js new file mode 100644 index 0000000..425289c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-children.output.js @@ -0,0 +1,8 @@ +var React = require('React'); + +var a = +
+ + blah + +; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-computed-component.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-computed-component.input.js new file mode 100644 index 0000000..2d91b7c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-computed-component.input.js @@ -0,0 +1,5 @@ +var React = require('React'); + +React.createElement(a[b]); +React.createElement(a[b.c]); +React.createElement(f()); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-computed-component.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-computed-component.output.js new file mode 100644 index 0000000..2d91b7c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-computed-component.output.js @@ -0,0 +1,5 @@ +var React = require('React'); + +React.createElement(a[b]); +React.createElement(a[b.c]); +React.createElement(f()); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-deep-nesting.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-deep-nesting.input.js new file mode 100644 index 0000000..99a9a79 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-deep-nesting.input.js @@ -0,0 +1,21 @@ +var React = require('react/addons'); + +React.createElement( + 'div', + {}, + React.createElement( + 'span', + {}, + React.createElement( + 'button', + {}, + React.createElement( + 'a', + {href: 'https://www.google.com'}, + React.createElement('audio') + ) + ), + React.createElement('br'), + React.createElement('img') + ) +); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-deep-nesting.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-deep-nesting.output.js new file mode 100644 index 0000000..f555aee --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-deep-nesting.output.js @@ -0,0 +1,13 @@ +var React = require('react/addons'); + +
+ + +
+ +
+
; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-element-comment-positioning.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-element-comment-positioning.input.js new file mode 100644 index 0000000..a7431ee --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-element-comment-positioning.input.js @@ -0,0 +1,8 @@ +var React = require('react/addons'); + +function render() { + return React.createElement( + 'div', // Note that this element needs to be a div. + {foo: 'bar'} + ); +} diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-element-comment-positioning.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-element-comment-positioning.output.js new file mode 100644 index 0000000..a28e49d --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-element-comment-positioning.output.js @@ -0,0 +1,8 @@ +var React = require('react/addons'); + +function render() { + return ( + // Note that this element needs to be a div. +
+ ); +} diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-escaped-string.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-escaped-string.input.js new file mode 100644 index 0000000..1413547 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-escaped-string.input.js @@ -0,0 +1,7 @@ +var React = require('React'); + +React.createElement(Foo, {bar: 'abc'}); +React.createElement(Foo, {bar: 'a\nbc'}); +React.createElement(Foo, {bar: 'ab\tc'}); +React.createElement(Foo, {bar: 'ab"c'}); +React.createElement(Foo, {bar: "ab'c"}); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-escaped-string.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-escaped-string.output.js new file mode 100644 index 0000000..94e9ebb --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-escaped-string.output.js @@ -0,0 +1,7 @@ +var React = require('React'); + +; +; +; +; +; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-gt-lt-entities.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-gt-lt-entities.input.js new file mode 100644 index 0000000..aec09d2 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-gt-lt-entities.input.js @@ -0,0 +1,3 @@ +var React = require('React'); + +React.createElement('div', null, '\x3C\x3E'); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-gt-lt-entities.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-gt-lt-entities.output.js new file mode 100644 index 0000000..acde1b8 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-gt-lt-entities.output.js @@ -0,0 +1,5 @@ +var React = require('React'); + +
+ <> +
; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-ignore-bad-capitalization.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-ignore-bad-capitalization.input.js new file mode 100644 index 0000000..1e1a8a8 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-ignore-bad-capitalization.input.js @@ -0,0 +1,10 @@ +var React = require('React'); + +React.createElement(Foo); +React.createElement(foo); +React.createElement('Foo'); +React.createElement('foo'); +React.createElement(_foo); +React.createElement('_foo'); +React.createElement(foo.bar); +React.createElement(Foo, null, React.createElement(foo)); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-ignore-bad-capitalization.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-ignore-bad-capitalization.output.js new file mode 100644 index 0000000..5a036f7 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-ignore-bad-capitalization.output.js @@ -0,0 +1,12 @@ +var React = require('React'); + +; +React.createElement(foo); +React.createElement('Foo'); +; +<_foo />; +React.createElement('_foo'); +; + + {React.createElement(foo)} +; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-prop.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-prop.input.js new file mode 100644 index 0000000..6b74002 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-prop.input.js @@ -0,0 +1,3 @@ +var React = require('React'); + +React.createElement(Foo, {'foo': 'bar'}); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-prop.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-prop.output.js new file mode 100644 index 0000000..12408c0 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-prop.output.js @@ -0,0 +1,3 @@ +var React = require('React'); + +; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-spacing.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-spacing.input.js new file mode 100644 index 0000000..1787b2f --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-spacing.input.js @@ -0,0 +1,15 @@ +var React = require('react/addons'); + +const recipient = 'world'; +React.createElement( + 'span', + {}, + 'Hello ', + recipient +); +React.createElement( + 'span', + {}, + 'Water', + recipient +); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-spacing.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-spacing.output.js new file mode 100644 index 0000000..51ad38b --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-literal-spacing.output.js @@ -0,0 +1,11 @@ +var React = require('react/addons'); + +const recipient = 'world'; + + {'Hello '} + {recipient} +; + + Water + {recipient} +; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-member-expression-as-prop.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-member-expression-as-prop.input.js new file mode 100644 index 0000000..419c71c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-member-expression-as-prop.input.js @@ -0,0 +1,3 @@ +var React = require('react/addons'); + +React.createElement('div', this.props, 'foo'); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-member-expression-as-prop.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-member-expression-as-prop.output.js new file mode 100644 index 0000000..ed7e1c0 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-member-expression-as-prop.output.js @@ -0,0 +1,5 @@ +var React = require('react/addons'); + +
+ foo +
; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-props-arg.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-props-arg.input.js new file mode 100644 index 0000000..6d918af --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-props-arg.input.js @@ -0,0 +1,4 @@ +var React = require('React'); + +var a = React.createElement(Foo); +var b = React.createElement('div'); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-props-arg.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-props-arg.output.js new file mode 100644 index 0000000..d68821d --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-props-arg.output.js @@ -0,0 +1,4 @@ +var React = require('React'); + +var a = ; +var b =
; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-react.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-react.input.js new file mode 100644 index 0000000..aa81e70 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-no-react.input.js @@ -0,0 +1,3 @@ +var React = require('foo'); + +React.createElement(Foo, 'la'); 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 new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-object-assign.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-object-assign.input.js new file mode 100644 index 0000000..0dc500b --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-object-assign.input.js @@ -0,0 +1,7 @@ +var React = require('react/addons'); + +React.createElement(Foo, Object.assign({ + 'foo': 'bar', +}, props, { + 'bar': 'foo', +})); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-object-assign.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-object-assign.output.js new file mode 100644 index 0000000..d71e1fe --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-object-assign.output.js @@ -0,0 +1,3 @@ +var React = require('react/addons'); + +; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-preserve-comments.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-preserve-comments.input.js new file mode 100644 index 0000000..2e161ff --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-preserve-comments.input.js @@ -0,0 +1,38 @@ +var React = require('react'); + +const render = () => { + return /*1*/React/*2*/./*3*/createElement/*4*/( + /*5*/'div'/*6*/,/*7*/ + { + /*8*/className/*9*/: /*10*/'foo'/*11*/,/*12*/ + /*13*/onClick/*14*/:/*15*/ this.handleClick/*16*/, //17 + }/*18*/, + /*19*/React.createElement(/*20*/TodoList/*21*/./*22*/Item/*23*/)/*24*/, //25 + React.createElement( + 'span', + /*26*/getProps()/*27*/ + ), + React.createElement('input', /*28*/null/*29*/) + ); +}; + +const render2 = () => { + return React.createElement( + 'div', { + className: 'foo', // Prop comment. + }, + 'hello' // Child string comment. + ); +}; + +const render3 = () => { + return React.createElement( + 'div', + null, + React.createElement('span') // Child element comment. + ); +}; + +const render4 = () => { + return React.createElement(Foo, {/* No props to see here! */}); +}; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-preserve-comments.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-preserve-comments.output.js new file mode 100644 index 0000000..28ca5ff --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-preserve-comments.output.js @@ -0,0 +1,41 @@ +var React = require('react'); + +const render = () => { + return ( + /*1*//*4*//*2*//*3*/
+ {/*19*///25 + /*24*//*20*//*23*//*21*//*22*//*20*//*23*//*21*//*22*//*21*//*22*/} + + { /*28*//*29*/} +
+ /*5*//*6*//*7*//*18*/ + ); +}; + +const render2 = () => { + return ( +
+ {// Child string comment. + 'hello'} +
+ ); +}; + +const render3 = () => { + return ( +
+ {// Child element comment. + } +
+ ); +}; + +const render4 = () => { + return /* No props to see here! */; +}; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-array.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-array.input.js new file mode 100644 index 0000000..c70ff04 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-array.input.js @@ -0,0 +1,8 @@ +var React = require('react'); + +var foo = React.createElement( + 'div', + { + array: [], + } +); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-array.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-array.output.js new file mode 100644 index 0000000..06cfec7 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-array.output.js @@ -0,0 +1,3 @@ +var React = require('react'); + +var foo =
; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-boolean.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-boolean.input.js new file mode 100644 index 0000000..5c6c75d --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-boolean.input.js @@ -0,0 +1,4 @@ +var React = require('React'); + +var a = React.createElement('div', { a: true }); +var a = React.createElement('div', { a: 4 }); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-boolean.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-boolean.output.js new file mode 100644 index 0000000..edc58ea --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props-boolean.output.js @@ -0,0 +1,4 @@ +var React = require('React'); + +var a =
; +var a =
; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props.input.js new file mode 100644 index 0000000..b8230e8 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props.input.js @@ -0,0 +1,6 @@ +var React = require('React'); + +function foo() { + var a = React.createElement(Foo, { foo: 'bar', bar: this.state.baz }); + var b = React.createElement('div', { foo: 'bar', bar: this.state.baz }); +} diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props.output.js new file mode 100644 index 0000000..97b6d5b --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-props.output.js @@ -0,0 +1,6 @@ +var React = require('React'); + +function foo() { + var a = ; + var b =
; +} diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-react-spread.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-react-spread.input.js new file mode 100644 index 0000000..1a04287 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-react-spread.input.js @@ -0,0 +1,7 @@ +var React = require('react/addons'); + +React.createElement(Foo, React.__spread({ + 'foo': 'bar', +}, props, { + 'bar': 'foo', +})); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-react-spread.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-react-spread.output.js new file mode 100644 index 0000000..d71e1fe --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-react-spread.output.js @@ -0,0 +1,3 @@ +var React = require('react/addons'); + +; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-single-element.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-single-element.input.js new file mode 100644 index 0000000..5b0a961 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-single-element.input.js @@ -0,0 +1,4 @@ +var React = require('React'); + +var a = React.createElement(Foo, null); +var b = React.createElement('div', null); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-single-element.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-single-element.output.js new file mode 100644 index 0000000..d68821d --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-single-element.output.js @@ -0,0 +1,4 @@ +var React = require('React'); + +var a = ; +var b =
; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread-props.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread-props.input.js new file mode 100644 index 0000000..e43ee8b --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread-props.input.js @@ -0,0 +1,3 @@ +var React = require('React'); + +React.createElement(Constructor, props); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread-props.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread-props.output.js new file mode 100644 index 0000000..3ccfe6c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread-props.output.js @@ -0,0 +1,3 @@ +var React = require('React'); + +; diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread.input.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread.input.js new file mode 100644 index 0000000..87a7393 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread.input.js @@ -0,0 +1,3 @@ +var React = require('React'); + +React.createElement(Foo, {foo: 'bar', ...someObject}); diff --git a/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread.output.js b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread.output.js new file mode 100644 index 0000000..bb9c69c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/create-element-to-jsx-spread.output.js @@ -0,0 +1,3 @@ +var React = require('React'); + +; diff --git a/codemods/legacy/transforms/__testfixtures__/custom-sort-group/.eslintrc b/codemods/legacy/transforms/__testfixtures__/custom-sort-group/.eslintrc new file mode 100644 index 0000000..5996239 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/custom-sort-group/.eslintrc @@ -0,0 +1,19 @@ +--- +parser: babel-eslint +plugins: + - react +rules: + no-undef: 0 + no-unused-vars: 0 + react/sort-comp: + - 0 + - order: + - lifecycle + - everything-else + - rendering + groups: + lifecycle: + - componentDidMount + rendering: + - "/^render.+$/" + - render diff --git a/codemods/legacy/transforms/__testfixtures__/custom-sort-group/custom-sort-group.input.js b/codemods/legacy/transforms/__testfixtures__/custom-sort-group/custom-sort-group.input.js new file mode 100644 index 0000000..d405432 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/custom-sort-group/custom-sort-group.input.js @@ -0,0 +1,24 @@ +var React = require('react/addons'); + +// comment above createClass +var MyComponent = React.createClass({ + render: function() { + return renderChild(); + }, + + mixins: [PureRenderMixin], + + // comment on componentDidMount + componentDidMount() {}, + + myOwnMethod(foo) { + // comment within method + }, + + renderChild: function() { + return
; + }, +}); + +/* comment at end */ +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/custom-sort-group/custom-sort-group.output.js b/codemods/legacy/transforms/__testfixtures__/custom-sort-group/custom-sort-group.output.js new file mode 100644 index 0000000..9147794 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/custom-sort-group/custom-sort-group.output.js @@ -0,0 +1,24 @@ +var React = require('react/addons'); + +// comment above createClass +var MyComponent = React.createClass({ + // comment on componentDidMount + componentDidMount() {}, + + mixins: [PureRenderMixin], + + myOwnMethod(foo) { + // comment within method + }, + + renderChild: function() { + return
; + }, + + render: function() { + return renderChild(); + }, +}); + +/* comment at end */ +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/custom-sort/.eslintrc b/codemods/legacy/transforms/__testfixtures__/custom-sort/.eslintrc new file mode 100644 index 0000000..ab6e1bd --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/custom-sort/.eslintrc @@ -0,0 +1,12 @@ +--- +parser: babel-eslint +plugins: + - react +rules: + no-undef: 0 + no-unused-vars: 0 + react/sort-comp: + - 0 + - order: + - everything-else + - render diff --git a/codemods/legacy/transforms/__testfixtures__/custom-sort/custom-sort.input.js b/codemods/legacy/transforms/__testfixtures__/custom-sort/custom-sort.input.js new file mode 100644 index 0000000..ad0764d --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/custom-sort/custom-sort.input.js @@ -0,0 +1,21 @@ +var React = require('react/addons'); + +// comment above createClass +var MyComponent = React.createClass({ + render: function() { + return
; + }, + + mixins: [PureRenderMixin], + + // comment on componentDidMount + componentDidMount() {}, + + myOwnMethod(foo) { + // comment within method + }, + +}); + +/* comment at end */ +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/custom-sort/custom-sort.output.js b/codemods/legacy/transforms/__testfixtures__/custom-sort/custom-sort.output.js new file mode 100644 index 0000000..7822c3f --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/custom-sort/custom-sort.output.js @@ -0,0 +1,21 @@ +var React = require('react/addons'); + +// comment above createClass +var MyComponent = React.createClass({ + // comment on componentDidMount + componentDidMount() {}, + + mixins: [PureRenderMixin], + + myOwnMethod(foo) { + // comment within method + }, + + render: function() { + return
; + }, + +}); + +/* comment at end */ +module.exports = MyComponent; diff --git a/error-boundaries/tests/original-class-component/input.tsx b/codemods/legacy/transforms/__testfixtures__/error-boundaries/class-component.input.js similarity index 100% rename from error-boundaries/tests/original-class-component/input.tsx rename to codemods/legacy/transforms/__testfixtures__/error-boundaries/class-component.input.js diff --git a/error-boundaries/tests/original-class-component/expected.tsx b/codemods/legacy/transforms/__testfixtures__/error-boundaries/class-component.output.js similarity index 100% rename from error-boundaries/tests/original-class-component/expected.tsx rename to codemods/legacy/transforms/__testfixtures__/error-boundaries/class-component.output.js diff --git a/error-boundaries/tests/original-create-class/input.tsx b/codemods/legacy/transforms/__testfixtures__/error-boundaries/create-class-component.input.js similarity index 100% rename from error-boundaries/tests/original-create-class/input.tsx rename to codemods/legacy/transforms/__testfixtures__/error-boundaries/create-class-component.input.js diff --git a/error-boundaries/tests/original-create-class/expected.tsx b/codemods/legacy/transforms/__testfixtures__/error-boundaries/create-class-component.output.js similarity index 100% rename from error-boundaries/tests/original-create-class/expected.tsx rename to codemods/legacy/transforms/__testfixtures__/error-boundaries/create-class-component.output.js diff --git a/codemods/legacy/transforms/__testfixtures__/findDOMNode.input.js b/codemods/legacy/transforms/__testfixtures__/findDOMNode.input.js new file mode 100644 index 0000000..ef5bc1b --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/findDOMNode.input.js @@ -0,0 +1,34 @@ +'use strict'; + +var React = require('React'); + +var Composer = React.createClass({ + componentWillReceiveProps: function(nextProps) { + this.getDOMNode(); + return foo(this.refs.input.getDOMNode()); + }, + + foo: function() { + var ref = 'foo'; + var element = this.refs[ref]; + var domNode = element.getDOMNode(); + }, + + bar: function() { + var thing = this.refs.foo; + thing.getDOMNode(); + }, + + foobar: function() { + passThisOn(this.refs.main.refs.list.getDOMNode()); + }, +}); + +var SomeDialog = React.createClass({ + render: function() { + call(this.refs.SomeThing); + return ( +
+ ); + }, +}); diff --git a/codemods/legacy/transforms/__testfixtures__/findDOMNode.output.js b/codemods/legacy/transforms/__testfixtures__/findDOMNode.output.js new file mode 100644 index 0000000..275ac90 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/findDOMNode.output.js @@ -0,0 +1,34 @@ +'use strict'; + +var React = require('React'); + +var Composer = React.createClass({ + componentWillReceiveProps: function(nextProps) { + React.findDOMNode(this); + return foo(React.findDOMNode(this.refs.input)); + }, + + foo: function() { + var ref = 'foo'; + var element = this.refs[ref]; + var domNode = React.findDOMNode(element); + }, + + bar: function() { + var thing = this.refs.foo; + React.findDOMNode(thing); + }, + + foobar: function() { + passThisOn(React.findDOMNode(this.refs.main.refs.list)); + }, +}); + +var SomeDialog = React.createClass({ + render: function() { + call(this.refs.SomeThing); + return ( +
+ ); + }, +}); diff --git a/manual-bind-to-arrow/tests/simple-bind/input.tsx b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow1.input.js similarity index 100% rename from manual-bind-to-arrow/tests/simple-bind/input.tsx rename to codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow1.input.js diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow1.output.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow1.output.js new file mode 100644 index 0000000..6f9abaf --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow1.output.js @@ -0,0 +1,3 @@ +class Component extends React.Component { + onClick = () => { }; +} diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow10.input.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow10.input.js new file mode 100644 index 0000000..d01be8a --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow10.input.js @@ -0,0 +1,15 @@ +class Component extends React.Component { + constructor() { + super(); + this.onClick = this.onClick.bind(this); + } + onClick() { } +} + +class Component2 extends React.Component { + constructor() { + super(); + this.onClick = this.onClick.bind(this); + } + onClick() { } +} \ 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/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow10.output.js new file mode 100644 index 0000000..6da7e15 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow10.output.js @@ -0,0 +1,7 @@ +class Component extends React.Component { + onClick = () => { }; +} + +class Component2 extends React.Component { + onClick = () => { }; +} diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow11.input.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow11.input.js new file mode 100644 index 0000000..ddcc601 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow11.input.js @@ -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/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 new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow2.input.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow2.input.js new file mode 100644 index 0000000..434b3bc --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow2.input.js @@ -0,0 +1,19 @@ +class SomeName extends React.Component { + _onChange: Function; + + constructor(props: Object) { + super(props); + this._onChange = this._onChange.bind(this); + if (!this.props.something.somePreference) { + this._onChange(SomeConstantsOptions.SOME_CONSTANT); + } + } + + /* Some comment */ + _onChange(value: String): void { + Dispatcher.handleViewAction({ + type: SomeConstantsOptions.SOME_CONSTANT, + payload: value, + }); + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow2.output.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow2.output.js new file mode 100644 index 0000000..e56e9c9 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow2.output.js @@ -0,0 +1,18 @@ +class SomeName extends React.Component { + _onChange: Function; + + constructor(props: Object) { + super(props); + if (!this.props.something.somePreference) { + this._onChange(SomeConstantsOptions.SOME_CONSTANT); + } + } + + /* Some comment */ + _onChange = (value: String): void => { + Dispatcher.handleViewAction({ + type: SomeConstantsOptions.SOME_CONSTANT, + payload: value, + }); + }; +} diff --git a/manual-bind-to-arrow/tests/bind-no-matching-method/expected.tsx b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow3.input.js similarity index 100% rename from manual-bind-to-arrow/tests/bind-no-matching-method/expected.tsx rename to codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow3.input.js 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 new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow4.input.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow4.input.js new file mode 100644 index 0000000..65d0ef6 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow4.input.js @@ -0,0 +1,7 @@ +class Component extends React.Component { + constructor() { + super(); + (this: any).onClick = this.onClick.bind(this); + } + onClick() { } +} diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow4.output.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow4.output.js new file mode 100644 index 0000000..6f9abaf --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow4.output.js @@ -0,0 +1,3 @@ +class Component extends React.Component { + onClick = () => { }; +} diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow5.input.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow5.input.js new file mode 100644 index 0000000..34e18c2 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow5.input.js @@ -0,0 +1,8 @@ +class Component extends React.Component { + constructor() { + super(); + const self: any = this; + self.onClick = this.onClick.bind(this); + } + onClick() { } +} diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow5.output.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow5.output.js new file mode 100644 index 0000000..6f9abaf --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow5.output.js @@ -0,0 +1,3 @@ +class Component extends React.Component { + onClick = () => { }; +} diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow6.input.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow6.input.js new file mode 100644 index 0000000..44c5135 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow6.input.js @@ -0,0 +1,10 @@ +class Component extends React.Component { + constructor() { + super(); + const self: any = this; + self._onMapResize = debounceCore(this._onMapResize.bind(this), 100); + self.onClick = this.onClick.bind(this); + } + onClick() { } + onMapResize() { } +} diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow6.output.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow6.output.js new file mode 100644 index 0000000..2737b87 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow6.output.js @@ -0,0 +1,9 @@ +class Component extends React.Component { + constructor() { + super(); + const self: any = this; + self._onMapResize = debounceCore(this._onMapResize.bind(this), 100); + } + onClick = () => { }; + onMapResize() { } +} diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow7.input.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow7.input.js new file mode 100644 index 0000000..44af15d --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow7.input.js @@ -0,0 +1,10 @@ +class SomeClass { + constructor() { + (this: any)._isLoaded = false; + (this: any).isLoaded = this.isLoaded.bind(this); + } + + isLoaded(): boolean { + return this._isLoaded; + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow7.output.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow7.output.js new file mode 100644 index 0000000..7c16f80 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow7.output.js @@ -0,0 +1,9 @@ +class SomeClass { + constructor() { + (this: any)._isLoaded = false; + } + + isLoaded = (): boolean => { + return this._isLoaded; + }; +} diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow8.input.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow8.input.js new file mode 100644 index 0000000..d72504b --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow8.input.js @@ -0,0 +1,7 @@ +class Component extends React.Component { + constructor() { + super(); + this.onClick = this.onClick.bind(this); + } + async onClick() { } +} diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow8.output.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow8.output.js new file mode 100644 index 0000000..a9d9934 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow8.output.js @@ -0,0 +1,3 @@ +class Component extends React.Component { + onClick = async () => { }; +} diff --git a/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow9.input.js b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow9.input.js new file mode 100644 index 0000000..3122cab --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow9.input.js @@ -0,0 +1,15 @@ +class Component extends React.Component { + constructor() { + super(); + this.onClick = this.onClick.bind(this); + } + onClick() { } +} + +class Component2 extends React.Component { + constructor() { + super(); + this.didClick = this.didClick.bind(this); + } + didClick() { } +} \ 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/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow9.output.js new file mode 100644 index 0000000..7e258e4 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/manual-bind-to-arrow/manual-bind-to-arrow9.output.js @@ -0,0 +1,7 @@ +class Component extends React.Component { + onClick = () => { }; +} + +class Component2 extends React.Component { + didClick = () => { }; +} diff --git a/codemods/legacy/transforms/__testfixtures__/pure-component-destructuring.input.js b/codemods/legacy/transforms/__testfixtures__/pure-component-destructuring.input.js new file mode 100644 index 0000000..c9875e8 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/pure-component-destructuring.input.js @@ -0,0 +1,51 @@ +'use strict'; + +var React = require('React'); + +const shadow = 'shadow'; + +function doSomething(props) { return props; } + +class ShouldDestsructure extends React.Component { + render() { + return
; + } +} + +class ShouldDestructureAndRemoveDuplicateDeclaration extends React.Component { + render() { + const fizz = { buzz: 'buzz' }; + const bar = this.props.bar; + const baz = this.props.bizzaz; + const buzz = fizz.buzz; + return
; + } +} + +class UsesThisDotProps extends React.Component { + render() { + doSomething(this.props); + return
; + } +} + +class DestructuresThisDotProps extends React.Component { + // would be nice to destructure in this case + render() { + const { bar } = this.props; + return
; + } +} + +class HasShadowProps extends React.Component { + render() { + return
; + } +} + +class PureWithTypes extends React.Component { + props: { foo: string }; + render() { + return
; + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/pure-component-destructuring.output.js b/codemods/legacy/transforms/__testfixtures__/pure-component-destructuring.output.js new file mode 100644 index 0000000..714b15c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/pure-component-destructuring.output.js @@ -0,0 +1,50 @@ +'use strict'; + +var React = require('React'); + +const shadow = 'shadow'; + +function doSomething(props) { return props; } + +function ShouldDestsructure( + { + foo, + }, +) { + return
; +} + +function ShouldDestructureAndRemoveDuplicateDeclaration( + { + bar, + bizzaz, + foo, + }, +) { + const fizz = { buzz: 'buzz' }; + const baz = bizzaz; + const buzz = fizz.buzz; + return
; +} + +function UsesThisDotProps(props) { + doSomething(props); + return
; +} + +function DestructuresThisDotProps(props) { + const { bar } = props; + return
; +} + +function HasShadowProps(props) { + return
; +} + +function PureWithTypes( + { + foo: string, + }, +) { + return
; +} diff --git a/codemods/legacy/transforms/__testfixtures__/pure-component.input.js b/codemods/legacy/transforms/__testfixtures__/pure-component.input.js new file mode 100644 index 0000000..8ee7f80 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/pure-component.input.js @@ -0,0 +1,86 @@ +'use strict'; + +var React = require('React'); + +function render() { + return
; +} + +class Pure extends React.Component { + render() { + return
; + } +} + +class Impure extends React.Component { + componentWillMount() { + // such impure + } + render() { + return
; + } +} + +class ImpureWithRef extends React.Component { + render() { + return ( +
+ +
+ ); + } +} + +class PureWithoutProps extends React.Component { + render() { + return
; + } +} + +class PureWithTypes extends React.Component { + props: { foo: string }; + render() { + return
; + } +} + +type Props = { foo: string }; + +class PureWithTypes2 extends React.Component { + props: Props; + render() { + return
; + } +} + +class ImpureClassProperty extends React.Component { + state = { foo: 2 }; + render() { + return
; + } +} + +class ImpureClassPropertyWithTypes extends React.Component { + state: { x: string }; + render() { + return
; + } +} + +class PureWithPropTypes extends React.Component { + static propTypes = { foo: React.PropTypes.string }; + static foo = 'bar'; + render() { + return
{this.props.foo}
; + } +} + +class PureWithPropTypes2 extends React.Component { + props: { foo: string }; + static propTypes = { foo: React.PropTypes.string }; + render() { + return
{this.props.foo}
; + } +} + +var A = props =>
; diff --git a/codemods/legacy/transforms/__testfixtures__/pure-component.output.js b/codemods/legacy/transforms/__testfixtures__/pure-component.output.js new file mode 100644 index 0000000..3418d2c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/pure-component.output.js @@ -0,0 +1,73 @@ +'use strict'; + +var React = require('React'); + +function render() { + return
; +} + +function Pure(props) { + return
; +} + +class Impure extends React.Component { + componentWillMount() { + // such impure + } + render() { + return
; + } +} + +class ImpureWithRef extends React.Component { + render() { + return ( +
+ +
+ ); + } +} + +function PureWithoutProps() { + return
; +} + +function PureWithTypes(props: { foo: string }) { + return
; +} + +type Props = { foo: string }; + +function PureWithTypes2(props: Props) { + return
; +} + +class ImpureClassProperty extends React.Component { + state = { foo: 2 }; + render() { + return
; + } +} + +class ImpureClassPropertyWithTypes extends React.Component { + state: { x: string }; + render() { + return
; + } +} + +function PureWithPropTypes(props) { + return
{props.foo}
; +} + +PureWithPropTypes.propTypes = { foo: React.PropTypes.string }; +PureWithPropTypes.foo = 'bar'; + +function PureWithPropTypes2(props: { foo: string }) { + return
{props.foo}
; +} + +PureWithPropTypes2.propTypes = { foo: React.PropTypes.string }; + +var A = props =>
; diff --git a/codemods/legacy/transforms/__testfixtures__/pure-component2.input.js b/codemods/legacy/transforms/__testfixtures__/pure-component2.input.js new file mode 100644 index 0000000..32775f3 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/pure-component2.input.js @@ -0,0 +1,53 @@ +'use strict'; + +var React = require('React'); + +function render() { + return
; +} + +class Pure extends React.Component { + render() { + return
; + } +} + +class Impure extends React.Component { + componentWillMount() { + // such impure + } + render() { + return
; + } +} + +class PureWithoutProps extends React.Component { + render() { + return
; + } +} + +class PureWithTypes extends React.Component { + props: { foo: string }; + render() { + return
; + } +} + +type Props = { foo: string }; + +class PureWithTypes2 extends React.Component { + props: Props; + render() { + return
; + } +} + +class PureWithPropTypes extends React.Component { + static propTypes = { foo: React.PropTypes.string }; + render() { + return
{this.props.foo}
; + } +} + +var A = props =>
; diff --git a/codemods/legacy/transforms/__testfixtures__/pure-component2.output.js b/codemods/legacy/transforms/__testfixtures__/pure-component2.output.js new file mode 100644 index 0000000..a155191 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/pure-component2.output.js @@ -0,0 +1,42 @@ +'use strict'; + +var React = require('React'); + +function render() { + return
; +} + +const Pure = props => { + return
; +}; + +class Impure extends React.Component { + componentWillMount() { + // such impure + } + render() { + return
; + } +} + +const PureWithoutProps = () => { + return
; +}; + +const PureWithTypes = (props: { foo: string }) => { + return
; +}; + +type Props = { foo: string }; + +const PureWithTypes2 = (props: Props) => { + return
; +}; + +const PureWithPropTypes = props => { + return
{props.foo}
; +}; + +PureWithPropTypes.propTypes = { foo: React.PropTypes.string }; + +var A = props =>
; diff --git a/pure-render-mixin/tests/original-mixed-mixins/input.tsx b/codemods/legacy/transforms/__testfixtures__/pure-render-mixin.input.js similarity index 100% rename from pure-render-mixin/tests/original-mixed-mixins/input.tsx rename to codemods/legacy/transforms/__testfixtures__/pure-render-mixin.input.js diff --git a/codemods/legacy/transforms/__testfixtures__/pure-render-mixin.output.js b/codemods/legacy/transforms/__testfixtures__/pure-render-mixin.output.js new file mode 100644 index 0000000..7d7ac3d --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/pure-render-mixin.output.js @@ -0,0 +1,55 @@ +var React = require('react/addons'); + +var PureRenderMixin = React.addons.PureRenderMixin; + +var MyComponent = React.createClass({ + shouldComponentUpdate: function(nextProps, nextState) { + return React.addons.shallowCompare(this, nextProps, nextState); + }, + + render: function() { + return
; + }, +}); + +var MyMixedComponent = React.createClass({ + mixins: [SomeOtherMixin], + + shouldComponentUpdate: function(nextProps, nextState) { + return React.addons.shallowCompare(this, nextProps, nextState); + }, + + render: function() { + return
; + }, +}); + +var MyFooComponent = React.createClass({ + mixins: [SomeOtherMixin], + + render: function() { + return
; + }, + + foo: function() { + + }, + + shouldComponentUpdate: function(nextProps, nextState) { + return React.addons.shallowCompare(this, nextProps, nextState); + }, +}); + +var MyStupidComponent = React.createClass({ + mixins: [PureRenderMixin], + + shouldComponentUpdate: function() { + return !!'wtf is this doing here?'; + }, + + render: function() { + return
; + }, +}); + +module.exports = MyComponent; diff --git a/pure-render-mixin/tests/original-single/input.tsx b/codemods/legacy/transforms/__testfixtures__/pure-render-mixin2.input.js similarity index 100% rename from pure-render-mixin/tests/original-single/input.tsx rename to codemods/legacy/transforms/__testfixtures__/pure-render-mixin2.input.js diff --git a/codemods/legacy/transforms/__testfixtures__/pure-render-mixin2.output.js b/codemods/legacy/transforms/__testfixtures__/pure-render-mixin2.output.js new file mode 100644 index 0000000..26425d8 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/pure-render-mixin2.output.js @@ -0,0 +1,13 @@ +var React = require('react/addons'); + +var MyComponent = React.createClass({ + shouldComponentUpdate: function(nextProps, nextState) { + return React.addons.shallowCompare(this, nextProps, nextState); + }, + + render: function() { + return
; + }, +}); + +module.exports = MyComponent; diff --git a/pure-render-mixin/tests/original-fixture3-removes-var/input.tsx b/codemods/legacy/transforms/__testfixtures__/pure-render-mixin3.input.js similarity index 100% rename from pure-render-mixin/tests/original-fixture3-removes-var/input.tsx rename to codemods/legacy/transforms/__testfixtures__/pure-render-mixin3.input.js diff --git a/codemods/legacy/transforms/__testfixtures__/pure-render-mixin3.output.js b/codemods/legacy/transforms/__testfixtures__/pure-render-mixin3.output.js new file mode 100644 index 0000000..cd97493 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/pure-render-mixin3.output.js @@ -0,0 +1,15 @@ +var React = require('react/addons'); + +var Foo = 'Foo'; + +var MyComponent = React.createClass({ + shouldComponentUpdate: function(nextProps, nextState) { + return React.addons.shallowCompare(this, nextProps, nextState); + }, + + render: function() { + return
; + }, +}); + +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/pure-render-mixin4.input.js b/codemods/legacy/transforms/__testfixtures__/pure-render-mixin4.input.js new file mode 100644 index 0000000..2fe0945 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/pure-render-mixin4.input.js @@ -0,0 +1,12 @@ +var React = require('React'); +var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); + +var MyComponent = React.createClass({ + mixins: [ReactComponentWithPureRenderMixin], + + render: function() { + return
; + }, +}); + +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/pure-render-mixin4.output.js b/codemods/legacy/transforms/__testfixtures__/pure-render-mixin4.output.js new file mode 100644 index 0000000..3e3854e --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/pure-render-mixin4.output.js @@ -0,0 +1,13 @@ +var React = require('React'); + +var MyComponent = React.createClass({ + shouldComponentUpdate: function(nextProps, nextState) { + return React.addons.shallowCompare(this, nextProps, nextState); + }, + + render: function() { + return
; + }, +}); + +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-dom-base.input.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-dom-base.input.js new file mode 100644 index 0000000..0191c3d --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-dom-base.input.js @@ -0,0 +1,8 @@ +import React from 'react'; + +React.findDOMNode(); +React.findDOMNode(this.refs.node); +React.render(null); +React.unmountComponentAtNode(node); +React.unstable_batchedUpdates(() => {}); +React.unstable_renderSubtreeIntoContainer(null); diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-dom-base.output.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-dom-base.output.js new file mode 100644 index 0000000..87190bf --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-dom-base.output.js @@ -0,0 +1,8 @@ +import ReactDOM from 'react-dom'; + +ReactDOM.findDOMNode(); +ReactDOM.findDOMNode(this.refs.node); +ReactDOM.render(null); +ReactDOM.unmountComponentAtNode(node); +ReactDOM.unstable_batchedUpdates(() => {}); +ReactDOM.unstable_renderSubtreeIntoContainer(null); diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-multiple-specifiers.input.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-multiple-specifiers.input.js new file mode 100644 index 0000000..d6c950e --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-multiple-specifiers.input.js @@ -0,0 +1,5 @@ +import React, { PropTypes, render, findDOMNode } from 'react'; + +render(
); +findDOMNode(this.refs.node); +PropTypes.number diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-multiple-specifiers.output.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-multiple-specifiers.output.js new file mode 100644 index 0000000..d68800f --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-multiple-specifiers.output.js @@ -0,0 +1,7 @@ +import React, { PropTypes } from 'react'; + +import { render, findDOMNode } from 'react-dom'; + +render(
); +findDOMNode(this.refs.node); +PropTypes.number diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-server-base.input.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-server-base.input.js new file mode 100644 index 0000000..4191bf4 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-server-base.input.js @@ -0,0 +1,4 @@ +import React from 'react'; + +React.renderToString(); +React.renderToStaticMarkup(); diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-server-base.output.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-server-base.output.js new file mode 100644 index 0000000..4369aad --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-server-base.output.js @@ -0,0 +1,4 @@ +import ReactDOMServer from 'react-dom/server'; + +ReactDOMServer.renderToString(); +ReactDOMServer.renderToStaticMarkup(); diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-with-existing-react-dom.input.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-with-existing-react-dom.input.js new file mode 100644 index 0000000..bcbe45f --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-with-existing-react-dom.input.js @@ -0,0 +1,5 @@ +import React, { PropTypes, findDOMNode } from 'react'; +import ReactDOM from 'react-dom'; + +React.render(
); +findDOMNode(this.refs.node); diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-with-existing-react-dom.output.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-with-existing-react-dom.output.js new file mode 100644 index 0000000..30a3027 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-with-existing-react-dom.output.js @@ -0,0 +1,6 @@ +import React, { PropTypes } from 'react'; +import ReactDOM, { findDOMNode } from 'react-dom'; + +ReactDOM.render(
); +findDOMNode(this.refs.node); + diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-without-default-specifier.input.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-without-default-specifier.input.js new file mode 100644 index 0000000..33c92e0 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-without-default-specifier.input.js @@ -0,0 +1,3 @@ +import { findDOMNode } from 'react'; + +findDOMNode(this.refs.node); diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-without-default-specifier.output.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-without-default-specifier.output.js new file mode 100644 index 0000000..95febc5 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/import-without-default-specifier.output.js @@ -0,0 +1,3 @@ +import { findDOMNode } from 'react-dom'; + +findDOMNode(this.refs.node); diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/mixed-with-existing-react-dom.input.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/mixed-with-existing-react-dom.input.js new file mode 100644 index 0000000..fd563e9 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/mixed-with-existing-react-dom.input.js @@ -0,0 +1,3 @@ +import React from 'react'; +var ReactDOM = require('react-dom'); +React.findDOMNode(); diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/mixed-with-existing-react-dom.output.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/mixed-with-existing-react-dom.output.js new file mode 100644 index 0000000..efd1e01 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/mixed-with-existing-react-dom.output.js @@ -0,0 +1,2 @@ +var ReactDOM = require('react-dom'); +ReactDOM.findDOMNode(); diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-dom-base.input.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-dom-base.input.js new file mode 100644 index 0000000..cf3fe29 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-dom-base.input.js @@ -0,0 +1,8 @@ +var React = require('react'); + +React.findDOMNode(); +React.findDOMNode(this.refs.node); +React.render(null); +React.unmountComponentAtNode(node); +React.unstable_batchedUpdates(() => {}); +React.unstable_renderSubtreeIntoContainer(null); diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-dom-base.output.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-dom-base.output.js new file mode 100644 index 0000000..8ae07bc --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-dom-base.output.js @@ -0,0 +1,8 @@ +var ReactDOM = require('react-dom'); + +ReactDOM.findDOMNode(); +ReactDOM.findDOMNode(this.refs.node); +ReactDOM.render(null); +ReactDOM.unmountComponentAtNode(node); +ReactDOM.unstable_batchedUpdates(() => {}); +ReactDOM.unstable_renderSubtreeIntoContainer(null); diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-indirect.input.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-indirect.input.js new file mode 100644 index 0000000..de1f202 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-indirect.input.js @@ -0,0 +1,6 @@ +var React; + +React = require('react'); +let { render } = React; + +render(
); diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-indirect.output.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-indirect.output.js new file mode 100644 index 0000000..fcbf2e9 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-indirect.output.js @@ -0,0 +1,11 @@ +var React; + +var ReactDOM; + +React = require('react'); +ReactDOM = require('react-dom'); +let { + render +} = ReactDOM; + +render(
); diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-keeps-react.input.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-keeps-react.input.js new file mode 100644 index 0000000..c756d06 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-keeps-react.input.js @@ -0,0 +1,4 @@ +var React = require('react'); + +React.render(
); +React.renderToString(); diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-keeps-react.output.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-keeps-react.output.js new file mode 100644 index 0000000..0f35a74 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-keeps-react.output.js @@ -0,0 +1,7 @@ +var React = require('react'); + +var ReactDOM = require('react-dom'); +var ReactDOMServer = require('react-dom/server'); + +ReactDOM.render(
); +ReactDOMServer.renderToString(); diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-server-base.input.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-server-base.input.js new file mode 100644 index 0000000..6b7ded6 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-server-base.input.js @@ -0,0 +1,4 @@ +var React = require('react'); + +React.renderToString(); +React.renderToStaticMarkup(); diff --git a/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-server-base.output.js b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-server-base.output.js new file mode 100644 index 0000000..63f42ae --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/react-to-react-dom/require-server-base.output.js @@ -0,0 +1,4 @@ +var ReactDOMServer = require('react-dom/server'); + +ReactDOMServer.renderToString(); +ReactDOMServer.renderToStaticMarkup(); diff --git a/codemods/legacy/transforms/__testfixtures__/remove-context-provider/no-provider.input.js b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/no-provider.input.js new file mode 100644 index 0000000..9ef6573 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/no-provider.input.js @@ -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/no-provider.output.js b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/no-provider.output.js new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/no-provider.input.js b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/no-provider.input.js new file mode 100644 index 0000000..357cc7c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/no-provider.input.js @@ -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/no-provider.output.js b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/no-provider.output.js new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider-2.input.js b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider-2.input.js new file mode 100644 index 0000000..b8d7261 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider-2.input.js @@ -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.output.js b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider-2.output.js new file mode 100644 index 0000000..357cc7c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider-2.output.js @@ -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.input.js b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider.input.js new file mode 100644 index 0000000..9787205 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider.input.js @@ -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.output.js b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider.output.js new file mode 100644 index 0000000..f1d4569 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/typescript/with-provider.output.js @@ -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/with-provider-2.input.js b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/with-provider-2.input.js new file mode 100644 index 0000000..18cb6e6 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/with-provider-2.input.js @@ -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.output.js b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/with-provider-2.output.js new file mode 100644 index 0000000..9ef6573 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/with-provider-2.output.js @@ -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.input.js b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/with-provider.input.js new file mode 100644 index 0000000..a066cc8 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/with-provider.input.js @@ -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.output.js b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/with-provider.output.js new file mode 100644 index 0000000..84f317b --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-context-provider/with-provider.output.js @@ -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-forward-ref/arrow-function-expression.input.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/arrow-function-expression.input.js new file mode 100644 index 0000000..88af3bd --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/arrow-function-expression.input.js @@ -0,0 +1,5 @@ +import { forwardRef } from 'react'; + +const MyInput = forwardRef((props, ref) => { + return null; +}); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/arrow-function-expression.output.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/arrow-function-expression.output.js new file mode 100644 index 0000000..53cb416 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/arrow-function-expression.output.js @@ -0,0 +1,8 @@ +const MyInput = ( + { + ref, + ...props + } +) => { + return null; +}; \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/callee-is-member-expression.input.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/callee-is-member-expression.input.js new file mode 100644 index 0000000..5f3f314 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/callee-is-member-expression.input.js @@ -0,0 +1,5 @@ +import * as React1 from 'react'; + +const MyInput = React1.forwardRef((props, ref) => { + return null; +}); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/callee-is-member-expression.output.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/callee-is-member-expression.output.js new file mode 100644 index 0000000..05547f5 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/callee-is-member-expression.output.js @@ -0,0 +1,10 @@ +import * as React1 from 'react'; + +const MyInput = ( + { + ref, + ...props + } +) => { + return null; +}; \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import-2.input.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import-2.input.js new file mode 100644 index 0000000..e6675dc --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import-2.input.js @@ -0,0 +1,5 @@ +import { forwardRef, useState } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + return null; +}); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import-2.output.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import-2.output.js new file mode 100644 index 0000000..6c2fb56 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import-2.output.js @@ -0,0 +1,10 @@ +import { useState } from 'react'; + +const MyInput = function MyInput( + { + ref, + ...props + } +) { + return null; +}; \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import.input.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import.input.js new file mode 100644 index 0000000..6623c96 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import.input.js @@ -0,0 +1,5 @@ +import { forwardRef } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + return null; +}); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import.output.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import.output.js new file mode 100644 index 0000000..033a974 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/forward-ref-import.output.js @@ -0,0 +1,8 @@ +const MyInput = function MyInput( + { + ref, + ...props + } +) { + return null; +}; \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/function-expression.input.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/function-expression.input.js new file mode 100644 index 0000000..0466217 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/function-expression.input.js @@ -0,0 +1,5 @@ +import { forwardRef } from 'react'; + +const MyInput = forwardRef(function A(props, ref) { + return null; +}); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/function-expression.output.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/function-expression.output.js new file mode 100644 index 0000000..cf65821 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/function-expression.output.js @@ -0,0 +1,8 @@ +const MyInput = function A( + { + ref, + ...props + } +) { + return null; +}; \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-identifier.input.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-identifier.input.js new file mode 100644 index 0000000..bb87e8c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-identifier.input.js @@ -0,0 +1,5 @@ +import { forwardRef } from 'react'; + +const MyInput = forwardRef(function MyInput(props, ref) { + return +}); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-identifier.output.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-identifier.output.js new file mode 100644 index 0000000..297637a --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-identifier.output.js @@ -0,0 +1,8 @@ +const MyInput = function MyInput( + { + ref, + ...props + } +) { + return +}; \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-object-pattern.input.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-object-pattern.input.js new file mode 100644 index 0000000..99b9510 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-object-pattern.input.js @@ -0,0 +1,5 @@ +import { forwardRef } from 'react'; + +const MyInput = forwardRef(function MyInput({ onChange }, ref) { + return +}); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-object-pattern.output.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-object-pattern.output.js new file mode 100644 index 0000000..651d524 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/props-object-pattern.output.js @@ -0,0 +1,8 @@ +const MyInput = function MyInput( + { + ref, + onChange + } +) { + return +}; \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/props-type-literal.input.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/props-type-literal.input.js new file mode 100644 index 0000000..c242b87 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/props-type-literal.input.js @@ -0,0 +1,7 @@ +import { forwardRef } from 'react'; +const MyComponent = forwardRef(function Component( + myProps: { a: 1 }, + myRef +) { + return null; +}); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/props-type-literal.output.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/props-type-literal.output.js new file mode 100644 index 0000000..56fa6f5 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/props-type-literal.output.js @@ -0,0 +1,10 @@ +const MyComponent = function Component( + { + ref: myRef, + ...myProps + }: { a: 1 } & { + ref: React.RefObject + } +) { + return null; +}; \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-custom-names.input.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-custom-names.input.js new file mode 100644 index 0000000..fd523ba --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-custom-names.input.js @@ -0,0 +1,8 @@ +import { forwardRef } from 'react'; + +const MyComponent = forwardRef(function Component( + myProps: Props, + myRef: React.ForwardedRef +) { + return null; +}); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-custom-names.output.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-custom-names.output.js new file mode 100644 index 0000000..e868776 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-custom-names.output.js @@ -0,0 +1,10 @@ +const MyComponent = function Component( + { + ref: myRef, + ...myProps + }: Props & { + ref: React.RefObject + } +) { + return null; +}; \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-type-literals.input.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-type-literals.input.js new file mode 100644 index 0000000..f4f4ac0 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-type-literals.input.js @@ -0,0 +1,5 @@ +import { forwardRef } from 'react'; + +const MyInput = forwardRef((props, ref) => { + return null; +}); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-type-literals.output.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-type-literals.output.js new file mode 100644 index 0000000..f569dfc --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments-type-literals.output.js @@ -0,0 +1,10 @@ +const MyInput = ( + { + ref, + ...props + }: { a: string } & { + ref: React.RefObject + } +) => { + return null; +}; \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments.input.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments.input.js new file mode 100644 index 0000000..2a045b6 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments.input.js @@ -0,0 +1,6 @@ +import { forwardRef } from 'react'; +type Props = { a: 1 }; + +const MyInput = forwardRef((props, ref) => { + return null; +}); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments.output.js b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments.output.js new file mode 100644 index 0000000..23e1c16 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/remove-forward-ref/typescript/type-arguments.output.js @@ -0,0 +1,12 @@ +type Props = { a: 1 }; + +const MyInput = ( + { + ref, + ...props + }: Props & { + ref: React.RefObject + } +) => { + return null; +}; \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/arrow-functions.input.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/arrow-functions.input.js new file mode 100644 index 0000000..501cd6c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/arrow-functions.input.js @@ -0,0 +1,14 @@ +const React = require('React'); + +class Component extends React.Component { + componentWillMount = logger('componentWillMount'); + componentDidMount = logger('componentDidMount'); + componentWillReceiveProps = logger('componentWillReceiveProps'); + shouldComponentUpdate = logger('shouldComponentUpdate'); + componentWillUpdate = logger('componentWillUpdate'); + componentDidUpdate = logger('componentDidUpdate'); + componentWillUnmount = logger('componentWillUnmount'); + render() { + return null; + } +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/arrow-functions.output.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/arrow-functions.output.js new file mode 100644 index 0000000..8087f10 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/arrow-functions.output.js @@ -0,0 +1,14 @@ +const React = require('React'); + +class Component extends React.Component { + UNSAFE_componentWillMount = logger('componentWillMount'); + componentDidMount = logger('componentDidMount'); + UNSAFE_componentWillReceiveProps = logger('componentWillReceiveProps'); + shouldComponentUpdate = logger('shouldComponentUpdate'); + UNSAFE_componentWillUpdate = logger('componentWillUpdate'); + componentDidUpdate = logger('componentDidUpdate'); + componentWillUnmount = logger('componentWillUnmount'); + render() { + return null; + } +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/create-react-class.input.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/create-react-class.input.js new file mode 100644 index 0000000..5708b2d --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/create-react-class.input.js @@ -0,0 +1,48 @@ +const createReactClass = require('create-react-class'); + +const MyComponent = createReactClass({ + displayName: 'MyComponent', + mixins: [ + { + componentWillMount() { + // componentWillMount + }, + componentDidMount() { + // componentDidMount + }, + componentWillUpdate(nextProps, nextState) { + // componentWillUpdate + }, + componentDidUpdate(prevProps, prevState) { + // componentDidUpdate + }, + componentWillReceiveProps(nextProps) { + // componentWillReceiveProps + }, + componentWillUnmount() { + // componentWillUnmount + }, + }, + ], + componentWillMount() { + // componentWillMount + }, + componentDidMount() { + // componentDidMount + }, + componentWillUpdate(nextProps, nextState) { + // componentWillUpdate + }, + componentDidUpdate(prevProps, prevState) { + // componentDidUpdate + }, + componentWillReceiveProps(nextProps) { + // componentWillReceiveProps + }, + componentWillUnmount() { + // componentWillUnmount + }, + render() { + // render + }, +}); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/create-react-class.output.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/create-react-class.output.js new file mode 100644 index 0000000..f9eab80 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/create-react-class.output.js @@ -0,0 +1,48 @@ +const createReactClass = require('create-react-class'); + +const MyComponent = createReactClass({ + displayName: 'MyComponent', + mixins: [ + { + UNSAFE_componentWillMount() { + // componentWillMount + }, + componentDidMount() { + // componentDidMount + }, + UNSAFE_componentWillUpdate(nextProps, nextState) { + // componentWillUpdate + }, + componentDidUpdate(prevProps, prevState) { + // componentDidUpdate + }, + UNSAFE_componentWillReceiveProps(nextProps) { + // componentWillReceiveProps + }, + componentWillUnmount() { + // componentWillUnmount + }, + }, + ], + UNSAFE_componentWillMount() { + // componentWillMount + }, + componentDidMount() { + // componentDidMount + }, + UNSAFE_componentWillUpdate(nextProps, nextState) { + // componentWillUpdate + }, + componentDidUpdate(prevProps, prevState) { + // componentDidUpdate + }, + UNSAFE_componentWillReceiveProps(nextProps) { + // componentWillReceiveProps + }, + componentWillUnmount() { + // componentWillUnmount + }, + render() { + // render + }, +}); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/instance-methods.input.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/instance-methods.input.js new file mode 100644 index 0000000..619bf50 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/instance-methods.input.js @@ -0,0 +1,25 @@ +const React = require('React'); + +class ClassComponent extends React.Component { + componentWillMount() { + // componentWillMount + } + componentDidMount() { + // componentDidMount + } + componentWillUpdate(nextProps, nextState) { + // componentWillUpdate + } + componentDidUpdate(prevProps, prevState) { + // componentDidUpdate + } + componentWillReceiveProps(nextProps) { + // componentWillReceiveProps + } + componentWillUnmount() { + // componentWillUnmount + } + render() { + // render + } +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/instance-methods.output.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/instance-methods.output.js new file mode 100644 index 0000000..610364c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/instance-methods.output.js @@ -0,0 +1,25 @@ +const React = require('React'); + +class ClassComponent extends React.Component { + UNSAFE_componentWillMount() { + // componentWillMount + } + componentDidMount() { + // componentDidMount + } + UNSAFE_componentWillUpdate(nextProps, nextState) { + // componentWillUpdate + } + componentDidUpdate(prevProps, prevState) { + // componentDidUpdate + } + UNSAFE_componentWillReceiveProps(nextProps) { + // componentWillReceiveProps + } + componentWillUnmount() { + // componentWillUnmount + } + render() { + // render + } +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-calling-lifecycles.input.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-calling-lifecycles.input.js new file mode 100644 index 0000000..d4d7835 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-calling-lifecycles.input.js @@ -0,0 +1,3 @@ +const instance = new Component(); +instance.componentWillMount(); +instance.componentDidMount(); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-calling-lifecycles.output.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-calling-lifecycles.output.js new file mode 100644 index 0000000..f17b3e7 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-calling-lifecycles.output.js @@ -0,0 +1,3 @@ +const instance = new Component(); +instance.UNSAFE_componentWillMount(); +instance.componentDidMount(); \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-invoked-mixin-methods.input.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-invoked-mixin-methods.input.js new file mode 100644 index 0000000..a746d81 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-invoked-mixin-methods.input.js @@ -0,0 +1,20 @@ +const React = require('react'); +const SomeOtherComponent = require('SomeOtherComponent'); + +class PMTAuthenticationFailureDialog extends React.Component { + componentDidMount(): void { + SomeOtherComponent.mixin.componentDidMount.apply(this); + } + + componentWillMount(): void { + SomeOtherComponent.mixin.componentWillMount.apply(this); + } + + componentWillUnmount(): void { + SomeOtherComponent.mixin.componentWillUnmount.apply(this); + } + + render() { + return null; + } +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-invoked-mixin-methods.output.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-invoked-mixin-methods.output.js new file mode 100644 index 0000000..4af350b --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/manually-invoked-mixin-methods.output.js @@ -0,0 +1,20 @@ +const React = require('react'); +const SomeOtherComponent = require('SomeOtherComponent'); + +class PMTAuthenticationFailureDialog extends React.Component { + componentDidMount(): void { + SomeOtherComponent.mixin.componentDidMount.apply(this); + } + + UNSAFE_componentWillMount(): void { + SomeOtherComponent.mixin.UNSAFE_componentWillMount.apply(this); + } + + componentWillUnmount(): void { + SomeOtherComponent.mixin.componentWillUnmount.apply(this); + } + + render() { + return null; + } +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/one-lifecycle-calls-another.input.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/one-lifecycle-calls-another.input.js new file mode 100644 index 0000000..b4b12dd --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/one-lifecycle-calls-another.input.js @@ -0,0 +1,11 @@ +const React = require('React'); + +class Component extends React.Component { + componentWillMount() { + this.componentWillReceiveProps(this.props); + } + componentWillReceiveProps() {} + render() { + return null; + } +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/one-lifecycle-calls-another.output.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/one-lifecycle-calls-another.output.js new file mode 100644 index 0000000..29c5734 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/one-lifecycle-calls-another.output.js @@ -0,0 +1,11 @@ +const React = require('React'); + +class Component extends React.Component { + UNSAFE_componentWillMount() { + this.UNSAFE_componentWillReceiveProps(this.props); + } + UNSAFE_componentWillReceiveProps() {} + render() { + return null; + } +} \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/standalone-function.input.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/standalone-function.input.js new file mode 100644 index 0000000..c515ccc --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/standalone-function.input.js @@ -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/standalone-function.output.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/standalone-function.output.js new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/typescript/class.tsx.input.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/typescript/class.tsx.input.js new file mode 100644 index 0000000..5afdeed --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/typescript/class.tsx.input.js @@ -0,0 +1,14 @@ +import React from "react"; + +class Component extends React.Component { + public componentWillMount() {} + public componentDidMount() {} + public componentWillReceiveProps() {} + public shouldComponentUpdate() {} + public componentWillUpdate() {} + public componentDidUpdate() {} + public componentWillUnmount() {} + public render() { + return null; + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/typescript/class.tsx.output.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/typescript/class.tsx.output.js new file mode 100644 index 0000000..13f1501 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/typescript/class.tsx.output.js @@ -0,0 +1,14 @@ +import React from "react"; + +class Component extends React.Component { + public UNSAFE_componentWillMount() {} + public componentDidMount() {} + public UNSAFE_componentWillReceiveProps() {} + public shouldComponentUpdate() {} + public UNSAFE_componentWillUpdate() {} + public componentDidUpdate() {} + public componentWillUnmount() {} + public render() { + return null; + } +} diff --git a/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/variable-within-class-method.input.js b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/variable-within-class-method.input.js new file mode 100644 index 0000000..eb8979a --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/rename-unsafe-lifecycles/variable-within-class-method.input.js @@ -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/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 new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/sort-comp-pure.input.js b/codemods/legacy/transforms/__testfixtures__/sort-comp-pure.input.js new file mode 100644 index 0000000..963c4da --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/sort-comp-pure.input.js @@ -0,0 +1,43 @@ +import React, { PureComponent } from 'react/addons'; + +const propTypes = {}; + +// comment above class +class MyPureComponent extends PureComponent { + // comment at top of createClass + // this will be attached to first method + + render() { + return
; + } + + // comment on componentDidMount + componentDidMount() { + } + + static someStaticThing() { + // should bundle with other statics + } + + renderFoo() { + // other render* function + } + + renderBar() { + // should come before renderFoo + } + + static aStaticThing() { + // should come first + } + + myOwnMethod(foo) { + // comment within method + } + +} + +MyPureComponent.propTypes = propTypes; + +/* comment at end */ +module.exports = MyPureComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/sort-comp-pure.output.js b/codemods/legacy/transforms/__testfixtures__/sort-comp-pure.output.js new file mode 100644 index 0000000..7b01fba --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/sort-comp-pure.output.js @@ -0,0 +1,43 @@ +import React, { PureComponent } from 'react/addons'; + +const propTypes = {}; + +// comment above class +class MyPureComponent extends PureComponent { + static aStaticThing() { + // should come first + } + + static someStaticThing() { + // should bundle with other statics + } + + // comment on componentDidMount + componentDidMount() { + } + + myOwnMethod(foo) { + // comment within method + } + + renderBar() { + // should come before renderFoo + } + + renderFoo() { + // other render* function + } + + // comment at top of createClass + // this will be attached to first method + + render() { + return
; + } + +} + +MyPureComponent.propTypes = propTypes; + +/* comment at end */ +module.exports = MyPureComponent; diff --git a/sort-comp/tests/original-createClass/input.tsx b/codemods/legacy/transforms/__testfixtures__/sort-comp.input.js similarity index 100% rename from sort-comp/tests/original-createClass/input.tsx rename to codemods/legacy/transforms/__testfixtures__/sort-comp.input.js diff --git a/codemods/legacy/transforms/__testfixtures__/sort-comp.output.js b/codemods/legacy/transforms/__testfixtures__/sort-comp.output.js new file mode 100644 index 0000000..7cbc621 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/sort-comp.output.js @@ -0,0 +1,37 @@ +var React = require('react/addons'); + +// comment above createClass +var MyComponent = React.createClass({ + propTypes: { + foo: bar, // comment on prop + }, + + mixins: [PureRenderMixin], + + // comment on componentDidMount + componentDidMount() { + }, + + myOwnMethod(foo) { + // comment within method + }, + + renderBar() { + // should come before renderFoo + }, + + renderFoo() { + // other render* function + }, + + // comment at top of createClass + // this will be attached to first method + + render: function() { + return
; + }, + +}); + +/* comment at end */ +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/sort-comp2.input.js b/codemods/legacy/transforms/__testfixtures__/sort-comp2.input.js new file mode 100644 index 0000000..5d14a14 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/sort-comp2.input.js @@ -0,0 +1,43 @@ +var React = require('react/addons'); + +const propTypes = {}; + +// comment above class +class MyComponent extends React.Component { + // comment at top of createClass + // this will be attached to first method + + render() { + return
; + } + + // comment on componentDidMount + componentDidMount() { + } + + static someStaticThing() { + // should bundle with other statics + } + + renderFoo() { + // other render* function + } + + renderBar() { + // should come before renderFoo + } + + static aStaticThing() { + // should come first + } + + myOwnMethod(foo) { + // comment within method + } + +} + +MyComponent.propTypes = propTypes; + +/* comment at end */ +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/sort-comp2.output.js b/codemods/legacy/transforms/__testfixtures__/sort-comp2.output.js new file mode 100644 index 0000000..71052ad --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/sort-comp2.output.js @@ -0,0 +1,43 @@ +var React = require('react/addons'); + +const propTypes = {}; + +// comment above class +class MyComponent extends React.Component { + static aStaticThing() { + // should come first + } + + static someStaticThing() { + // should bundle with other statics + } + + // comment on componentDidMount + componentDidMount() { + } + + myOwnMethod(foo) { + // comment within method + } + + renderBar() { + // should come before renderFoo + } + + renderFoo() { + // other render* function + } + + // comment at top of createClass + // this will be attached to first method + + render() { + return
; + } + +} + +MyComponent.propTypes = propTypes; + +/* comment at end */ +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/sort-comp3.input.js b/codemods/legacy/transforms/__testfixtures__/sort-comp3.input.js new file mode 100644 index 0000000..76dcb63 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/sort-comp3.input.js @@ -0,0 +1,43 @@ +import React, { Component } from 'react/addons'; + +const propTypes = {}; + +// comment above class +class MyComponent extends Component { + // comment at top of createClass + // this will be attached to first method + + render() { + return
; + } + + // comment on componentDidMount + componentDidMount() { + } + + static someStaticThing() { + // should bundle with other statics + } + + renderFoo() { + // other render* function + } + + renderBar() { + // should come before renderFoo + } + + static aStaticThing() { + // should come first + } + + myOwnMethod(foo) { + // comment within method + } + +} + +MyComponent.propTypes = propTypes; + +/* comment at end */ +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/sort-comp3.output.js b/codemods/legacy/transforms/__testfixtures__/sort-comp3.output.js new file mode 100644 index 0000000..a5bd4ca --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/sort-comp3.output.js @@ -0,0 +1,43 @@ +import React, { Component } from 'react/addons'; + +const propTypes = {}; + +// comment above class +class MyComponent extends Component { + static aStaticThing() { + // should come first + } + + static someStaticThing() { + // should bundle with other statics + } + + // comment on componentDidMount + componentDidMount() { + } + + myOwnMethod(foo) { + // comment within method + } + + renderBar() { + // should come before renderFoo + } + + renderFoo() { + // other render* function + } + + // comment at top of createClass + // this will be attached to first method + + render() { + return
; + } + +} + +MyComponent.propTypes = propTypes; + +/* comment at end */ +module.exports = MyComponent; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import-react-variable.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import-react-variable.input.js new file mode 100644 index 0000000..b4427d5 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import-react-variable.input.js @@ -0,0 +1,5 @@ +import React, { createElement, useState } from "react"; + +React.createElement('div', {}); + +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import-react-variable.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import-react-variable.output.js new file mode 100644 index 0000000..41bcef4 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import-react-variable.output.js @@ -0,0 +1,6 @@ +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.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import.input.js new file mode 100644 index 0000000..4b0f90d --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import.input.js @@ -0,0 +1,3 @@ +import React, { type Element, createElement, useState } from "react"; + +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import.output.js new file mode 100644 index 0000000..ded95b7 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/default-and-multiple-specifiers-import.output.js @@ -0,0 +1,4 @@ +import type { Element } from "react"; +import { createElement, useState } from "react"; + +
Hi
; diff --git a/update-react-imports/tests/original-react-not-removed/expected.tsx b/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports-react-not-removed.input.js similarity index 100% rename from update-react-imports/tests/original-react-not-removed/expected.tsx rename to codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports-react-not-removed.input.js 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 new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports-variable-used.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports-variable-used.input.js new file mode 100644 index 0000000..ebb2ad9 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports-variable-used.input.js @@ -0,0 +1,6 @@ +import * as React from "react"; +import { createElement } from "react"; + +React.createElement('div', {}); + +
Hi
; 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 new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports.input.js new file mode 100644 index 0000000..1d6e228 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports.input.js @@ -0,0 +1,5 @@ +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.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports.output.js new file mode 100644 index 0000000..2ebfe7c --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/destructure-named-imports.output.js @@ -0,0 +1,5 @@ +import { useState } from "react"; + +useState(false); + +
\ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import-react-variable.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import-react-variable.input.js new file mode 100644 index 0000000..37bcba8 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import-react-variable.input.js @@ -0,0 +1,5 @@ +import React, { type Element, type Component } from "react"; + +React.createElement('div', {}); + +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import-react-variable.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import-react-variable.output.js new file mode 100644 index 0000000..fd12c75 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import-react-variable.output.js @@ -0,0 +1,6 @@ +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.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import.input.js new file mode 100644 index 0000000..b0e8765 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import.input.js @@ -0,0 +1,3 @@ +import React, { type Element } from "react"; + +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import.output.js new file mode 100644 index 0000000..1a4260e --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/flow-default-and-type-specifier-import.output.js @@ -0,0 +1,3 @@ +import type { Element } from "react"; + +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-element.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-element.input.js new file mode 100644 index 0000000..0d90028 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-element.input.js @@ -0,0 +1,3 @@ +import * as React from "react"; + +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-element.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-element.output.js new file mode 100644 index 0000000..8d6e7b5 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-element.output.js @@ -0,0 +1 @@ +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-fragment.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-fragment.input.js new file mode 100644 index 0000000..6a8c34d --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-fragment.input.js @@ -0,0 +1,3 @@ +import * as React from "react"; + +<>; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-fragment.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-fragment.output.js new file mode 100644 index 0000000..ef5e491 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/jsx-fragment.output.js @@ -0,0 +1 @@ +<>; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/leading-comment.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/leading-comment.input.js new file mode 100644 index 0000000..0ace368 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/leading-comment.input.js @@ -0,0 +1,7 @@ +/** + * Hello world. + */ + +import * as React from "react"; + +
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/leading-comment.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/leading-comment.output.js new file mode 100644 index 0000000..f739df6 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/leading-comment.output.js @@ -0,0 +1,5 @@ +/** + * Hello world. + */ + +
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-already-used-named-export.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-already-used-named-export.input.js new file mode 100644 index 0000000..33f3568 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-already-used-named-export.input.js @@ -0,0 +1,3 @@ +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.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-already-used-named-export.output.js new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element-react-variable.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element-react-variable.input.js new file mode 100644 index 0000000..ec844c2 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element-react-variable.input.js @@ -0,0 +1,5 @@ +import React from "react"; + +React.createElement('div', {}); + +
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element-react-variable.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element-react-variable.output.js new file mode 100644 index 0000000..3995e26 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element-react-variable.output.js @@ -0,0 +1,5 @@ +import { createElement } from "react"; + +createElement('div', {}); + +
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element.input.js new file mode 100644 index 0000000..16c8617 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element.input.js @@ -0,0 +1,3 @@ +import React from "react"; + +
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element.output.js new file mode 100644 index 0000000..f3b72ee --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export-jsx-element.output.js @@ -0,0 +1 @@ +
; diff --git a/update-react-imports/tests/original-react-basic-default-export/input.tsx b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export.input.js similarity index 100% rename from update-react-imports/tests/original-react-basic-default-export/input.tsx rename to codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export.input.js diff --git a/update-react-imports/tests/original-react-basic-default-export/expected.tsx b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export.output.js similarity index 100% rename from update-react-imports/tests/original-react-basic-default-export/expected.tsx rename to codemods/legacy/transforms/__testfixtures__/update-react-imports/react-basic-default-export.output.js diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-jsx-member-expression.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-jsx-member-expression.input.js new file mode 100644 index 0000000..5d34788 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-jsx-member-expression.input.js @@ -0,0 +1,3 @@ +import React, { useState } from 'react'; + + \ No newline at end of file diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-jsx-member-expression.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-jsx-member-expression.output.js new file mode 100644 index 0000000..7627281 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-jsx-member-expression.output.js @@ -0,0 +1,3 @@ +import { Fragment, useState } from 'react'; + + diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-not-removed.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-not-removed.input.js new file mode 100644 index 0000000..73b0dae --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-not-removed.input.js @@ -0,0 +1,7 @@ +import 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/react-not-removed.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-not-removed.output.js new file mode 100644 index 0000000..8dc1bf7 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-not-removed.output.js @@ -0,0 +1,7 @@ +import * as React from "react"; + +React.createElement('div', {}); + +Promise.resolve(React); + +
Hi
diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-default-export.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-default-export.input.js new file mode 100644 index 0000000..65515a7 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-default-export.input.js @@ -0,0 +1,4 @@ +import type React from "react"; +import * as React from "react"; + +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-default-export.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-default-export.output.js new file mode 100644 index 0000000..441a929 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-default-export.output.js @@ -0,0 +1,3 @@ +import type React from "react"; + +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-not-removed.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-not-removed.input.js new file mode 100644 index 0000000..3c71fbf --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-not-removed.input.js @@ -0,0 +1,4 @@ +import type React, { Node } from "react"; +import * as React from "react"; + +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-not-removed.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-not-removed.output.js new file mode 100644 index 0000000..11e80fe --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/react-type-not-removed.output.js @@ -0,0 +1,3 @@ +import type React, { Node } from "react"; + +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import-react-variable.tsx.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import-react-variable.tsx.input.js new file mode 100644 index 0000000..b4427d5 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import-react-variable.tsx.input.js @@ -0,0 +1,5 @@ +import React, { createElement, useState } 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.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import-react-variable.tsx.output.js new file mode 100644 index 0000000..41bcef4 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import-react-variable.tsx.output.js @@ -0,0 +1,6 @@ +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.tsx.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import.tsx.input.js new file mode 100644 index 0000000..e2b936a --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import.tsx.input.js @@ -0,0 +1,3 @@ +import React, { createElement, useState } from "react"; + +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import.tsx.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import.tsx.output.js new file mode 100644 index 0000000..51ebe95 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/default-and-multiple-specifiers-import.tsx.output.js @@ -0,0 +1,3 @@ +import { createElement, useState } from "react"; + +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-element.tsx.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-element.tsx.input.js new file mode 100644 index 0000000..0d90028 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-element.tsx.input.js @@ -0,0 +1,3 @@ +import * as React from "react"; + +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-element.tsx.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-element.tsx.output.js new file mode 100644 index 0000000..8d6e7b5 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-element.tsx.output.js @@ -0,0 +1 @@ +
Hi
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-fragment.tsx.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-fragment.tsx.input.js new file mode 100644 index 0000000..6a8c34d --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-fragment.tsx.input.js @@ -0,0 +1,3 @@ +import * as React from "react"; + +<>; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-fragment.tsx.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-fragment.tsx.output.js new file mode 100644 index 0000000..ef5e491 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/jsx-fragment.tsx.output.js @@ -0,0 +1 @@ +<>; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/leading-comment.tsx.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/leading-comment.tsx.input.js new file mode 100644 index 0000000..0ace368 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/leading-comment.tsx.input.js @@ -0,0 +1,7 @@ +/** + * Hello world. + */ + +import * as React from "react"; + +
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/leading-comment.tsx.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/leading-comment.tsx.output.js new file mode 100644 index 0000000..f739df6 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/leading-comment.tsx.output.js @@ -0,0 +1,5 @@ +/** + * Hello world. + */ + +
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-default.tsx.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-default.tsx.input.js new file mode 100644 index 0000000..2f8f600 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-default.tsx.input.js @@ -0,0 +1,5 @@ +import 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/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-default.tsx.output.js new file mode 100644 index 0000000..f27298b --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-default.tsx.output.js @@ -0,0 +1,5 @@ +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-namespace.tsx.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-namespace.tsx.input.js new file mode 100644 index 0000000..f27298b --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-namespace.tsx.input.js @@ -0,0 +1,5 @@ +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-namespace.tsx.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/preserve-types-namespace.tsx.output.js new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-already-used-named-export.tsx.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-already-used-named-export.tsx.input.js new file mode 100644 index 0000000..33f3568 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-already-used-named-export.tsx.input.js @@ -0,0 +1,3 @@ +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.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-already-used-named-export.tsx.output.js new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element-react-variable.tsx.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element-react-variable.tsx.input.js new file mode 100644 index 0000000..ec844c2 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element-react-variable.tsx.input.js @@ -0,0 +1,5 @@ +import React from "react"; + +React.createElement('div', {}); + +
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element-react-variable.tsx.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element-react-variable.tsx.output.js new file mode 100644 index 0000000..3995e26 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element-react-variable.tsx.output.js @@ -0,0 +1,5 @@ +import { createElement } from "react"; + +createElement('div', {}); + +
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element.tsx.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element.tsx.input.js new file mode 100644 index 0000000..16c8617 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element.tsx.input.js @@ -0,0 +1,3 @@ +import React from "react"; + +
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element.tsx.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element.tsx.output.js new file mode 100644 index 0000000..f3b72ee --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export-jsx-element.tsx.output.js @@ -0,0 +1 @@ +
; diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export.tsx.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export.tsx.input.js new file mode 100644 index 0000000..37f5d41 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export.tsx.input.js @@ -0,0 +1,3 @@ +import React from "react"; + +React.createElement('div', 'la'); diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export.tsx.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export.tsx.output.js new file mode 100644 index 0000000..c4c96c0 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-basic-default-export.tsx.output.js @@ -0,0 +1,3 @@ +import { createElement } from "react"; + +createElement('div', 'la'); diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-jsx-member-expression.tsx.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-jsx-member-expression.tsx.input.js new file mode 100644 index 0000000..5d34788 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-jsx-member-expression.tsx.input.js @@ -0,0 +1,3 @@ +import React, { 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.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-jsx-member-expression.tsx.output.js new file mode 100644 index 0000000..7627281 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-jsx-member-expression.tsx.output.js @@ -0,0 +1,3 @@ +import { Fragment, useState } from 'react'; + + diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-not-removed.tsx.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-not-removed.tsx.input.js new file mode 100644 index 0000000..73b0dae --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-not-removed.tsx.input.js @@ -0,0 +1,7 @@ +import 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.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-not-removed.tsx.output.js new file mode 100644 index 0000000..8dc1bf7 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/react-not-removed.tsx.output.js @@ -0,0 +1,7 @@ +import * as React from "react"; + +React.createElement('div', {}); + +Promise.resolve(React); + +
Hi
diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/variable-already-used.tsx.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/variable-already-used.tsx.input.js new file mode 100644 index 0000000..c2d7852 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/variable-already-used.tsx.input.js @@ -0,0 +1,7 @@ +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.output.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/typescript/variable-already-used.tsx.output.js new file mode 100644 index 0000000..e69de29 diff --git a/codemods/legacy/transforms/__testfixtures__/update-react-imports/variable-already-used.input.js b/codemods/legacy/transforms/__testfixtures__/update-react-imports/variable-already-used.input.js new file mode 100644 index 0000000..c2d7852 --- /dev/null +++ b/codemods/legacy/transforms/__testfixtures__/update-react-imports/variable-already-used.input.js @@ -0,0 +1,7 @@ +import * as React from "react"; + +React.createElement('div', {}); + +createElement('someFunction'); + +
Hi
; 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 new file mode 100644 index 0000000..e69de29 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 new file mode 100644 index 0000000..bf9fbaa --- /dev/null +++ b/codemods/legacy/transforms/__tests__/React-DOM-to-react-dom-factories-test.js @@ -0,0 +1,34 @@ +/** + * 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 new file mode 100644 index 0000000..d1e13f8 --- /dev/null +++ b/codemods/legacy/transforms/__tests__/ReactNative-View-propTypes-test.js @@ -0,0 +1,43 @@ +/** + * 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 new file mode 100644 index 0000000..e46306c --- /dev/null +++ b/codemods/legacy/transforms/__tests__/class-test.js @@ -0,0 +1,68 @@ +/** + * 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 new file mode 100644 index 0000000..b0739c5 --- /dev/null +++ b/codemods/legacy/transforms/__tests__/create-element-to-jsx-test.js @@ -0,0 +1,199 @@ +/** + * 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 new file mode 100644 index 0000000..13ee78c --- /dev/null +++ b/codemods/legacy/transforms/__tests__/custom-sort-group.js @@ -0,0 +1,14 @@ +/** + * 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 new file mode 100644 index 0000000..0dfbfbe --- /dev/null +++ b/codemods/legacy/transforms/__tests__/custom-sort.js @@ -0,0 +1,14 @@ +/** + * 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 new file mode 100644 index 0000000..8ed1c96 --- /dev/null +++ b/codemods/legacy/transforms/__tests__/error-boundaries.js @@ -0,0 +1,17 @@ +/** + * 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 new file mode 100644 index 0000000..16f87eb --- /dev/null +++ b/codemods/legacy/transforms/__tests__/findDOMNode-test.js @@ -0,0 +1,12 @@ +/** + * 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 new file mode 100644 index 0000000..30e0827 --- /dev/null +++ b/codemods/legacy/transforms/__tests__/manual-bind-to-arrow-test.js @@ -0,0 +1,40 @@ +/** + * 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 new file mode 100644 index 0000000..0526d5a --- /dev/null +++ b/codemods/legacy/transforms/__tests__/pure-component-test.js @@ -0,0 +1,20 @@ +/** + * 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 new file mode 100644 index 0000000..776cf67 --- /dev/null +++ b/codemods/legacy/transforms/__tests__/pure-render-mixin-test.js @@ -0,0 +1,20 @@ +/** + * 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 new file mode 100644 index 0000000..b00f92c --- /dev/null +++ b/codemods/legacy/transforms/__tests__/react-to-react-dom-test.js @@ -0,0 +1,34 @@ +/** + * 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 new file mode 100644 index 0000000..b548563 --- /dev/null +++ b/codemods/legacy/transforms/__tests__/remove-context-provider.test.ts @@ -0,0 +1,51 @@ + +'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 new file mode 100644 index 0000000..000a7f6 --- /dev/null +++ b/codemods/legacy/transforms/__tests__/remove-forward-ref.test.ts @@ -0,0 +1,62 @@ + +'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 new file mode 100644 index 0000000..2c9f6c6 --- /dev/null +++ b/codemods/legacy/transforms/__tests__/rename-unsafe-lifecycles-test.js @@ -0,0 +1,74 @@ +/** + * 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 new file mode 100644 index 0000000..bdeb344 --- /dev/null +++ b/codemods/legacy/transforms/__tests__/sort-comp-test.js @@ -0,0 +1,15 @@ +/** + * 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 new file mode 100644 index 0000000..d9e8ecd --- /dev/null +++ b/codemods/legacy/transforms/__tests__/update-react-imports-test.js @@ -0,0 +1,94 @@ +/** + * 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 new file mode 100644 index 0000000..ff7a212 --- /dev/null +++ b/codemods/legacy/transforms/class.js @@ -0,0 +1,1426 @@ +/** + * 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 new file mode 100644 index 0000000..66dbb88 --- /dev/null +++ b/codemods/legacy/transforms/create-element-to-jsx.js @@ -0,0 +1,269 @@ +/** + * 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 new file mode 100644 index 0000000..86710eb --- /dev/null +++ b/codemods/legacy/transforms/error-boundaries.js @@ -0,0 +1,12 @@ +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 new file mode 100644 index 0000000..f8665f9 --- /dev/null +++ b/codemods/legacy/transforms/findDOMNode.js @@ -0,0 +1,151 @@ +/** + * 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 new file mode 100644 index 0000000..7afb09a --- /dev/null +++ b/codemods/legacy/transforms/manual-bind-to-arrow.js @@ -0,0 +1,184 @@ +/** + * 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 new file mode 100644 index 0000000..af538ad --- /dev/null +++ b/codemods/legacy/transforms/pure-component.js @@ -0,0 +1,339 @@ +/** + * 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 new file mode 100644 index 0000000..00b2b72 --- /dev/null +++ b/codemods/legacy/transforms/pure-render-mixin.js @@ -0,0 +1,179 @@ +/** + * 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 new file mode 100644 index 0000000..e476adb --- /dev/null +++ b/codemods/legacy/transforms/react-to-react-dom.js @@ -0,0 +1,421 @@ +/** + * 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 new file mode 100644 index 0000000..4aadfbe --- /dev/null +++ b/codemods/legacy/transforms/remove-context-provider.ts @@ -0,0 +1,40 @@ +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 new file mode 100644 index 0000000..da7ad9d --- /dev/null +++ b/codemods/legacy/transforms/remove-forward-ref.ts @@ -0,0 +1,297 @@ +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 new file mode 100644 index 0000000..09bc78b --- /dev/null +++ b/codemods/legacy/transforms/rename-unsafe-lifecycles.js @@ -0,0 +1,62 @@ +/** + * 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 new file mode 100644 index 0000000..6e8adf6 --- /dev/null +++ b/codemods/legacy/transforms/sort-comp.js @@ -0,0 +1,239 @@ +/** + * 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 new file mode 100644 index 0000000..2871541 --- /dev/null +++ b/codemods/legacy/transforms/update-react-imports.js @@ -0,0 +1,380 @@ +/** + * (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 new file mode 100644 index 0000000..2a6a3b1 --- /dev/null +++ b/codemods/legacy/transforms/utils/ReactUtils.js @@ -0,0 +1,308 @@ +/** + * 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 new file mode 100644 index 0000000..e0cbfde --- /dev/null +++ b/codemods/legacy/transforms/utils/array-polyfills.js @@ -0,0 +1,46 @@ +/** + * 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 new file mode 100644 index 0000000..fb1c135 --- /dev/null +++ b/codemods/legacy/transforms/utils/doesNotUseArguments.js @@ -0,0 +1,40 @@ +/** + * 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 new file mode 100644 index 0000000..da0445e --- /dev/null +++ b/codemods/legacy/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "moduleDetection": "force", + "target": "ES2015" + } +} diff --git a/error-boundaries/codemod.yaml b/error-boundaries/codemod.yaml deleted file mode 100644 index 8756d6d..0000000 --- a/error-boundaries/codemod.yaml +++ /dev/null @@ -1,19 +0,0 @@ -schema_version: "1.0" - -name: "@react-codemods/error-boundaries" -version: "0.1.0" -description: "Rename unstable_handleError to componentDidCatch" -author: "React Codemods" -license: "MIT" -workflow: "workflow.yaml" - -targets: - languages: ["tsx"] - -keywords: ["transformation", "migration", "error-boundary"] - -registry: - access: "public" - visibility: "public" - -capabilities: [] diff --git a/error-boundaries/package.json b/error-boundaries/package.json deleted file mode 100644 index 02f1bfb..0000000 --- a/error-boundaries/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@react-codemods/error-boundaries", - "version": "0.1.0", - "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" - } -} diff --git a/error-boundaries/scripts/codemod.ts b/error-boundaries/scripts/codemod.ts deleted file mode 100644 index c5a03d8..0000000 --- a/error-boundaries/scripts/codemod.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type { Transform, Edit, SgNode } from "codemod:ast-grep"; -import type TSX from "codemod:ast-grep/langs/tsx"; -import { useMetricAtom } from "codemod:metrics"; - -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: root.filename(), - }, - edits.length, - ); - } - - if (edits.length === 0) { - return null; - } - - return rootNode.commitEdits(edits); -}; - -export default transform; diff --git a/error-boundaries/tests/arrow-assignment/expected.tsx b/error-boundaries/tests/arrow-assignment/expected.tsx deleted file mode 100644 index 32a7b9f..0000000 --- a/error-boundaries/tests/arrow-assignment/expected.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -class Boundary extends React.Component { - componentDidCatch = (error: Error, errorInfo: React.ErrorInfo) => { - this.setState({ error }); - }; - - render() { - return this.props.children; - } -} diff --git a/error-boundaries/tests/arrow-assignment/input.tsx b/error-boundaries/tests/arrow-assignment/input.tsx deleted file mode 100644 index abfbf69..0000000 --- a/error-boundaries/tests/arrow-assignment/input.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -class Boundary extends React.Component { - unstable_handleError = (error: Error, errorInfo: React.ErrorInfo) => { - this.setState({ error }); - }; - - render() { - return this.props.children; - } -} diff --git a/error-boundaries/tests/arrow-assignment/metrics.json b/error-boundaries/tests/arrow-assignment/metrics.json deleted file mode 100644 index 6104550..0000000 --- a/error-boundaries/tests/arrow-assignment/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "error-boundary-renames": [ - { - "cardinality": { - "file": "tests/arrow-assignment/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/error-boundaries/tests/export-default-class/expected.tsx b/error-boundaries/tests/export-default-class/expected.tsx deleted file mode 100644 index 52ae77c..0000000 --- a/error-boundaries/tests/export-default-class/expected.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; - -export default class AppErrorBoundary extends React.Component { - state = { hasError: false }; - - componentDidCatch(error, errorInfo) { - this.setState({ hasError: true }); - } - - render() { - return this.props.children; - } -} diff --git a/error-boundaries/tests/export-default-class/input.tsx b/error-boundaries/tests/export-default-class/input.tsx deleted file mode 100644 index f195793..0000000 --- a/error-boundaries/tests/export-default-class/input.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; - -export default class AppErrorBoundary extends React.Component { - state = { hasError: false }; - - unstable_handleError(error, errorInfo) { - this.setState({ hasError: true }); - } - - render() { - return this.props.children; - } -} diff --git a/error-boundaries/tests/export-default-class/metrics.json b/error-boundaries/tests/export-default-class/metrics.json deleted file mode 100644 index 571d898..0000000 --- a/error-boundaries/tests/export-default-class/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "error-boundary-renames": [ - { - "cardinality": { - "file": "tests/export-default-class/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/error-boundaries/tests/mixed-methods/expected.tsx b/error-boundaries/tests/mixed-methods/expected.tsx deleted file mode 100644 index 080e170..0000000 --- a/error-boundaries/tests/mixed-methods/expected.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from "react"; - -class ErrorBoundary extends React.Component { - constructor(props) { - super(props); - this.state = { hasError: false }; - } - - componentDidCatch(error, errorInfo) { - this.setState({ hasError: true }); - console.log(error, errorInfo); - } - - componentDidMount() { - console.log("mounted"); - } - - render() { - if (this.state.hasError) { - return

Something went wrong.

; - } - return this.props.children; - } -} - -export default ErrorBoundary; diff --git a/error-boundaries/tests/mixed-methods/input.tsx b/error-boundaries/tests/mixed-methods/input.tsx deleted file mode 100644 index 2b8215b..0000000 --- a/error-boundaries/tests/mixed-methods/input.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from "react"; - -class ErrorBoundary extends React.Component { - constructor(props) { - super(props); - this.state = { hasError: false }; - } - - unstable_handleError(error, errorInfo) { - this.setState({ hasError: true }); - console.log(error, errorInfo); - } - - componentDidMount() { - console.log("mounted"); - } - - render() { - if (this.state.hasError) { - return

Something went wrong.

; - } - return this.props.children; - } -} - -export default ErrorBoundary; diff --git a/error-boundaries/tests/mixed-methods/metrics.json b/error-boundaries/tests/mixed-methods/metrics.json deleted file mode 100644 index c73ce75..0000000 --- a/error-boundaries/tests/mixed-methods/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "error-boundary-renames": [ - { - "cardinality": { - "file": "tests/mixed-methods/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/error-boundaries/tests/no-unstable-handleError/expected.tsx b/error-boundaries/tests/no-unstable-handleError/expected.tsx deleted file mode 100644 index 3e9309c..0000000 --- a/error-boundaries/tests/no-unstable-handleError/expected.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -export class MyComponent extends React.Component { - componentDidCatch(error, errorInfo) { - console.log(error, errorInfo); - } - - render() { - return
Hello
; - } -} diff --git a/error-boundaries/tests/no-unstable-handleError/input.tsx b/error-boundaries/tests/no-unstable-handleError/input.tsx deleted file mode 100644 index 3e9309c..0000000 --- a/error-boundaries/tests/no-unstable-handleError/input.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -export class MyComponent extends React.Component { - componentDidCatch(error, errorInfo) { - console.log(error, errorInfo); - } - - render() { - return
Hello
; - } -} diff --git a/error-boundaries/tests/object-literal-method/expected.tsx b/error-boundaries/tests/object-literal-method/expected.tsx deleted file mode 100644 index 18e6f00..0000000 --- a/error-boundaries/tests/object-literal-method/expected.tsx +++ /dev/null @@ -1,8 +0,0 @@ -const config = { - unstable_handleError(err: Error) { - report(err); - }, - onRetry() {}, -}; - -export const ErrorHandler = () => null; diff --git a/error-boundaries/tests/object-literal-method/input.tsx b/error-boundaries/tests/object-literal-method/input.tsx deleted file mode 100644 index 18e6f00..0000000 --- a/error-boundaries/tests/object-literal-method/input.tsx +++ /dev/null @@ -1,8 +0,0 @@ -const config = { - unstable_handleError(err: Error) { - report(err); - }, - onRetry() {}, -}; - -export const ErrorHandler = () => null; diff --git a/error-boundaries/tests/original-class-component/metrics.json b/error-boundaries/tests/original-class-component/metrics.json deleted file mode 100644 index 08958b3..0000000 --- a/error-boundaries/tests/original-class-component/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "error-boundary-renames": [ - { - "cardinality": { - "file": "tests/original-class-component/input.tsx" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/error-boundaries/tests/original-create-class/metrics.json b/error-boundaries/tests/original-create-class/metrics.json deleted file mode 100644 index 70c49ee..0000000 --- a/error-boundaries/tests/original-create-class/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "error-boundary-renames": [ - { - "cardinality": { - "file": "tests/original-create-class/input.tsx" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/error-boundaries/tests/real-world-dashboard-error-boundary/expected.tsx b/error-boundaries/tests/real-world-dashboard-error-boundary/expected.tsx deleted file mode 100644 index 9f63c62..0000000 --- a/error-boundaries/tests/real-world-dashboard-error-boundary/expected.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from "react"; - -export class DashboardErrorBoundary extends React.Component { - state = { hasError: false }; - - componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { - this.setState({ hasError: true }); - logErrorToService(error, errorInfo.componentStack); - } - - render() { - if (this.state.hasError) { - return
Something went wrong.
; - } - return this.props.children; - } -} - -function logErrorToService(_err: Error, _stack?: string) {} diff --git a/error-boundaries/tests/real-world-dashboard-error-boundary/input.tsx b/error-boundaries/tests/real-world-dashboard-error-boundary/input.tsx deleted file mode 100644 index d514b03..0000000 --- a/error-boundaries/tests/real-world-dashboard-error-boundary/input.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from "react"; - -export class DashboardErrorBoundary extends React.Component { - state = { hasError: false }; - - unstable_handleError(error: Error, errorInfo: React.ErrorInfo) { - this.setState({ hasError: true }); - logErrorToService(error, errorInfo.componentStack); - } - - render() { - if (this.state.hasError) { - return
Something went wrong.
; - } - return this.props.children; - } -} - -function logErrorToService(_err: Error, _stack?: string) {} diff --git a/error-boundaries/tests/real-world-dashboard-error-boundary/metrics.json b/error-boundaries/tests/real-world-dashboard-error-boundary/metrics.json deleted file mode 100644 index f386676..0000000 --- a/error-boundaries/tests/real-world-dashboard-error-boundary/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "error-boundary-renames": [ - { - "cardinality": { - "file": "tests/real-world-dashboard-error-boundary/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/error-boundaries/tests/similar-name-no-op/expected.tsx b/error-boundaries/tests/similar-name-no-op/expected.tsx deleted file mode 100644 index 336384f..0000000 --- a/error-boundaries/tests/similar-name-no-op/expected.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; - -// Should NOT transform - different identifier -class Component extends React.Component { - unstable_handleErrorX() {} - handleError() {} - render() { - return null; - } -} diff --git a/error-boundaries/tests/similar-name-no-op/input.tsx b/error-boundaries/tests/similar-name-no-op/input.tsx deleted file mode 100644 index 336384f..0000000 --- a/error-boundaries/tests/similar-name-no-op/input.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; - -// Should NOT transform - different identifier -class Component extends React.Component { - unstable_handleErrorX() {} - handleError() {} - render() { - return null; - } -} diff --git a/error-boundaries/tests/single-method/expected.tsx b/error-boundaries/tests/single-method/expected.tsx deleted file mode 100644 index 8962fc4..0000000 --- a/error-boundaries/tests/single-method/expected.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react"; - -class ErrorBoundary extends React.Component { - componentDidCatch(error) { - logError(error); - } - render() { - return this.props.children; - } -} - -export default ErrorBoundary; diff --git a/error-boundaries/tests/single-method/input.tsx b/error-boundaries/tests/single-method/input.tsx deleted file mode 100644 index 326fd48..0000000 --- a/error-boundaries/tests/single-method/input.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react"; - -class ErrorBoundary extends React.Component { - unstable_handleError(error) { - logError(error); - } - render() { - return this.props.children; - } -} - -export default ErrorBoundary; diff --git a/error-boundaries/tests/single-method/metrics.json b/error-boundaries/tests/single-method/metrics.json deleted file mode 100644 index ffd4308..0000000 --- a/error-boundaries/tests/single-method/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "error-boundary-renames": [ - { - "cardinality": { - "file": "tests/single-method/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/error-boundaries/tests/three-classes/expected.tsx b/error-boundaries/tests/three-classes/expected.tsx deleted file mode 100644 index df79ec7..0000000 --- a/error-boundaries/tests/three-classes/expected.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; - -class A extends React.Component { - componentDidCatch() {} - render() { - return null; - } -} - -class B extends React.Component { - componentDidCatch() {} - render() { - return null; - } -} - -class C extends React.Component { - componentDidCatch() {} - render() { - return null; - } -} diff --git a/error-boundaries/tests/three-classes/input.tsx b/error-boundaries/tests/three-classes/input.tsx deleted file mode 100644 index 8133110..0000000 --- a/error-boundaries/tests/three-classes/input.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; - -class A extends React.Component { - unstable_handleError() {} - render() { - return null; - } -} - -class B extends React.Component { - unstable_handleError() {} - render() { - return null; - } -} - -class C extends React.Component { - unstable_handleError() {} - render() { - return null; - } -} diff --git a/error-boundaries/tests/three-classes/metrics.json b/error-boundaries/tests/three-classes/metrics.json deleted file mode 100644 index 4f7b001..0000000 --- a/error-boundaries/tests/three-classes/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "error-boundary-renames": [ - { - "cardinality": { - "file": "tests/three-classes/input.tsx" - }, - "count": 3 - } - ] -} \ No newline at end of file diff --git a/error-boundaries/tests/typescript-method/expected.tsx b/error-boundaries/tests/typescript-method/expected.tsx deleted file mode 100644 index 85e2fb1..0000000 --- a/error-boundaries/tests/typescript-method/expected.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from "react"; - -interface Props { - children: React.ReactNode; -} - -export class TypedErrorBoundary extends React.Component { - componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { - console.error(error, errorInfo); - } - - render() { - return this.props.children; - } -} diff --git a/error-boundaries/tests/typescript-method/input.tsx b/error-boundaries/tests/typescript-method/input.tsx deleted file mode 100644 index f248169..0000000 --- a/error-boundaries/tests/typescript-method/input.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from "react"; - -interface Props { - children: React.ReactNode; -} - -export class TypedErrorBoundary extends React.Component { - unstable_handleError(error: Error, errorInfo: React.ErrorInfo): void { - console.error(error, errorInfo); - } - - render() { - return this.props.children; - } -} diff --git a/error-boundaries/tests/typescript-method/metrics.json b/error-boundaries/tests/typescript-method/metrics.json deleted file mode 100644 index d9a41ea..0000000 --- a/error-boundaries/tests/typescript-method/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "error-boundary-renames": [ - { - "cardinality": { - "file": "tests/typescript-method/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/error-boundaries/tsconfig.json b/error-boundaries/tsconfig.json deleted file mode 100644 index 469fc5a..0000000 --- a/error-boundaries/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", - "types": ["@codemod.com/jssg-types"], - "allowImportingTsExtensions": true, - "noEmit": true, - "verbatimModuleSyntax": true, - "erasableSyntaxOnly": true, - "strict": true, - "strictNullChecks": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true - }, - "exclude": ["tests"] -} diff --git a/find-dom-node/codemod.yaml b/find-dom-node/codemod.yaml deleted file mode 100644 index 374db93..0000000 --- a/find-dom-node/codemod.yaml +++ /dev/null @@ -1,19 +0,0 @@ -schema_version: "1.0" - -name: "@react-codemods/find-dom-node" -version: "0.1.0" -description: "Replace getDOMNode() with React.findDOMNode()" -author: "React Codemods" -license: "MIT" -workflow: "workflow.yaml" - -targets: - languages: ["tsx"] - -keywords: ["transformation", "migration", "findDOMNode"] - -registry: - access: "public" - visibility: "public" - -capabilities: [] diff --git a/find-dom-node/package.json b/find-dom-node/package.json deleted file mode 100644 index 8fed40c..0000000 --- a/find-dom-node/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@react-codemods/find-dom-node", - "version": "0.1.0", - "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" - }, - "dependencies": { - "@jssg/utils": "^0.0.2" - } -} diff --git a/find-dom-node/scripts/codemod.ts b/find-dom-node/scripts/codemod.ts deleted file mode 100644 index 6f9e28f..0000000 --- a/find-dom-node/scripts/codemod.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Transform, Edit } 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"; - -const transform: Transform = async (root) => { - const rootNode = root.root(); - const edits: Edit[] = []; - - const findDOMNodeMetric = useMetricAtom("find-dom-node-replacements"); - - const reactDefaultImport = getImport(rootNode, { type: "default", from: "react" }); - const reactName = reactDefaultImport?.alias ?? "React"; - - const getDOMNodeCalls = rootNode.findAll({ - rule: { - kind: "call_expression", - has: { - field: "function", - kind: "member_expression", - all: [ - { has: { field: "property", kind: "property_identifier", regex: "^getDOMNode$" } }, - ], - }, - }, - }); - - for (const call of getDOMNodeCalls) { - const callee = call.field("function"); - if (!callee) continue; - - const objectNode = callee.field("object"); - if (!objectNode) continue; - - const replacement = `${reactName}.findDOMNode(${objectNode.text()})`; - edits.push(call.replace(replacement)); - - findDOMNodeMetric.increment({ - file: root.filename(), - }); - } - - if (edits.length === 0) return null; - - return rootNode.commitEdits(edits); -}; - -export default transform; diff --git a/find-dom-node/tests/comment-and-string-unchanged/expected.tsx b/find-dom-node/tests/comment-and-string-unchanged/expected.tsx deleted file mode 100644 index 9e29801..0000000 --- a/find-dom-node/tests/comment-and-string-unchanged/expected.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react"; - -// Migrate getDOMNode() to React.findDOMNode() when you run the codemod. -const LEGACY_API = "getDOMNode"; - -class Component extends React.Component { - componentDidMount() { - // We use this.refs.div for something else, no getDOMNode here - this.refs.div.classList.add("mounted"); - } - - render() { - return
; - } -} - -export default Component; diff --git a/find-dom-node/tests/comment-and-string-unchanged/input.tsx b/find-dom-node/tests/comment-and-string-unchanged/input.tsx deleted file mode 100644 index 9e29801..0000000 --- a/find-dom-node/tests/comment-and-string-unchanged/input.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react"; - -// Migrate getDOMNode() to React.findDOMNode() when you run the codemod. -const LEGACY_API = "getDOMNode"; - -class Component extends React.Component { - componentDidMount() { - // We use this.refs.div for something else, no getDOMNode here - this.refs.div.classList.add("mounted"); - } - - render() { - return
; - } -} - -export default Component; diff --git a/find-dom-node/tests/enzyme-wrapper-style/expected.tsx b/find-dom-node/tests/enzyme-wrapper-style/expected.tsx deleted file mode 100644 index 3b04af4..0000000 --- a/find-dom-node/tests/enzyme-wrapper-style/expected.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react"; -import { mount } from "enzyme"; -import MyComponent from "./MyComponent"; - -describe("MyComponent", () => { - it("focuses the input on mount", () => { - const wrapper = mount(); - const inputNode = React.findDOMNode(wrapper.find("input")); - expect(inputNode).toBe(document.activeElement); - }); - - it("calls getDOMNode on wrapper for measurement", () => { - const wrapper = mount(); - const domNode = React.findDOMNode(wrapper); - expect(domNode).toBeInstanceOf(HTMLElement); - }); -}); diff --git a/find-dom-node/tests/enzyme-wrapper-style/input.tsx b/find-dom-node/tests/enzyme-wrapper-style/input.tsx deleted file mode 100644 index ccff91d..0000000 --- a/find-dom-node/tests/enzyme-wrapper-style/input.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react"; -import { mount } from "enzyme"; -import MyComponent from "./MyComponent"; - -describe("MyComponent", () => { - it("focuses the input on mount", () => { - const wrapper = mount(); - const inputNode = wrapper.find("input").getDOMNode(); - expect(inputNode).toBe(document.activeElement); - }); - - it("calls getDOMNode on wrapper for measurement", () => { - const wrapper = mount(); - const domNode = wrapper.getDOMNode(); - expect(domNode).toBeInstanceOf(HTMLElement); - }); -}); diff --git a/find-dom-node/tests/enzyme-wrapper-style/metrics.json b/find-dom-node/tests/enzyme-wrapper-style/metrics.json deleted file mode 100644 index f5bd3ae..0000000 --- a/find-dom-node/tests/enzyme-wrapper-style/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "find-dom-node-replacements": [ - { - "cardinality": { - "file": "tests/enzyme-wrapper-style/input.tsx" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/find-dom-node/tests/inside-another-call/expected.tsx b/find-dom-node/tests/inside-another-call/expected.tsx deleted file mode 100644 index c4c19c8..0000000 --- a/find-dom-node/tests/inside-another-call/expected.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from "react"; - -class SearchInput extends React.Component { - componentDidMount() { - // Real pattern: focus the input after mount or scroll it into view - const el = React.findDOMNode(this.refs.input); - if (el && typeof el.focus === "function") { - el.focus(); - } - } - - handleSubmit = () => { - const node = React.findDOMNode(this); - if (node) { - node.scrollIntoView({ behavior: "smooth" }); - } - }; - - render() { - return ; - } -} - -export default SearchInput; diff --git a/find-dom-node/tests/inside-another-call/input.tsx b/find-dom-node/tests/inside-another-call/input.tsx deleted file mode 100644 index ccc3bd7..0000000 --- a/find-dom-node/tests/inside-another-call/input.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from "react"; - -class SearchInput extends React.Component { - componentDidMount() { - // Real pattern: focus the input after mount or scroll it into view - const el = this.refs.input.getDOMNode(); - if (el && typeof el.focus === "function") { - el.focus(); - } - } - - handleSubmit = () => { - const node = this.getDOMNode(); - if (node) { - node.scrollIntoView({ behavior: "smooth" }); - } - }; - - render() { - return ; - } -} - -export default SearchInput; diff --git a/find-dom-node/tests/inside-another-call/metrics.json b/find-dom-node/tests/inside-another-call/metrics.json deleted file mode 100644 index e98fe22..0000000 --- a/find-dom-node/tests/inside-another-call/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "find-dom-node-replacements": [ - { - "cardinality": { - "file": "tests/inside-another-call/input.tsx" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/find-dom-node/tests/multiple-calls/expected.tsx b/find-dom-node/tests/multiple-calls/expected.tsx deleted file mode 100644 index 7c9ce4b..0000000 --- a/find-dom-node/tests/multiple-calls/expected.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; - -class Component extends React.Component { - handleClick = () => { - const a = React.findDOMNode(this); - const b = React.findDOMNode(this.refs.foo); - a.classList.add("x"); - b.classList.add("y"); - }; - render() { - return
; - } -} diff --git a/find-dom-node/tests/multiple-calls/input.tsx b/find-dom-node/tests/multiple-calls/input.tsx deleted file mode 100644 index c04d12a..0000000 --- a/find-dom-node/tests/multiple-calls/input.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; - -class Component extends React.Component { - handleClick = () => { - const a = this.getDOMNode(); - const b = this.refs.foo.getDOMNode(); - a.classList.add("x"); - b.classList.add("y"); - }; - render() { - return
; - } -} diff --git a/find-dom-node/tests/multiple-calls/metrics.json b/find-dom-node/tests/multiple-calls/metrics.json deleted file mode 100644 index f6c3891..0000000 --- a/find-dom-node/tests/multiple-calls/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "find-dom-node-replacements": [ - { - "cardinality": { - "file": "tests/multiple-calls/input.tsx" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/find-dom-node/tests/nested-refs/expected.tsx b/find-dom-node/tests/nested-refs/expected.tsx deleted file mode 100644 index 381c718..0000000 --- a/find-dom-node/tests/nested-refs/expected.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; - -interface Props { - onMeasure?: (node: Element | null) => void; -} - -class NestedLayout extends React.Component { - componentDidMount() { - // Legacy pattern: nested refs for list inside a main container - const listNode = React.findDOMNode(this.refs.main.refs.list); - this.props.onMeasure?.(listNode as Element); - } - - render() { - return ( -
-
-
    -
  • Item 1
  • -
  • Item 2
  • -
-
-
- ); - } -} - -export default NestedLayout; diff --git a/find-dom-node/tests/nested-refs/input.tsx b/find-dom-node/tests/nested-refs/input.tsx deleted file mode 100644 index b05676d..0000000 --- a/find-dom-node/tests/nested-refs/input.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; - -interface Props { - onMeasure?: (node: Element | null) => void; -} - -class NestedLayout extends React.Component { - componentDidMount() { - // Legacy pattern: nested refs for list inside a main container - const listNode = this.refs.main.refs.list.getDOMNode(); - this.props.onMeasure?.(listNode as Element); - } - - render() { - return ( -
-
-
    -
  • Item 1
  • -
  • Item 2
  • -
-
-
- ); - } -} - -export default NestedLayout; diff --git a/find-dom-node/tests/nested-refs/metrics.json b/find-dom-node/tests/nested-refs/metrics.json deleted file mode 100644 index efe452d..0000000 --- a/find-dom-node/tests/nested-refs/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "find-dom-node-replacements": [ - { - "cardinality": { - "file": "tests/nested-refs/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/find-dom-node/tests/no-getDOMNode/expected.tsx b/find-dom-node/tests/no-getDOMNode/expected.tsx deleted file mode 100644 index dd2968d..0000000 --- a/find-dom-node/tests/no-getDOMNode/expected.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; - -class Component extends React.Component { - componentDidMount() { - this.refs.div.focus(); - } - render() { - return
; - } -} diff --git a/find-dom-node/tests/no-getDOMNode/input.tsx b/find-dom-node/tests/no-getDOMNode/input.tsx deleted file mode 100644 index dd2968d..0000000 --- a/find-dom-node/tests/no-getDOMNode/input.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; - -class Component extends React.Component { - componentDidMount() { - this.refs.div.focus(); - } - render() { - return
; - } -} diff --git a/find-dom-node/tests/react-import-alias/expected.tsx b/find-dom-node/tests/react-import-alias/expected.tsx deleted file mode 100644 index 9d18394..0000000 --- a/find-dom-node/tests/react-import-alias/expected.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import R from "react"; - -class LegacyComponent extends R.Component { - componentDidMount() { - const el = R.findDOMNode(this); - if (el) (el as HTMLElement).focus(); - } - - render() { - return
; - } -} - -export default LegacyComponent; diff --git a/find-dom-node/tests/react-import-alias/input.tsx b/find-dom-node/tests/react-import-alias/input.tsx deleted file mode 100644 index a21afa1..0000000 --- a/find-dom-node/tests/react-import-alias/input.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import R from "react"; - -class LegacyComponent extends R.Component { - componentDidMount() { - const el = this.getDOMNode(); - if (el) (el as HTMLElement).focus(); - } - - render() { - return
; - } -} - -export default LegacyComponent; diff --git a/find-dom-node/tests/react-import-alias/metrics.json b/find-dom-node/tests/react-import-alias/metrics.json deleted file mode 100644 index 8599294..0000000 --- a/find-dom-node/tests/react-import-alias/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "find-dom-node-replacements": [ - { - "cardinality": { - "file": "tests/react-import-alias/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/find-dom-node/tests/real-world-autosize-input/expected.tsx b/find-dom-node/tests/real-world-autosize-input/expected.tsx deleted file mode 100644 index 81357c0..0000000 --- a/find-dom-node/tests/real-world-autosize-input/expected.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from "react"; - -class AutosizeInput extends React.Component { - componentDidMount() { - const el = React.findDOMNode(this.refs.input); - if (el && typeof el.focus === "function") { - el.focus(); - } - this.copyStyles(); - } - - copyStyles() { - const node = React.findDOMNode(this); - if (node && this.props.styles) { - const target = node as HTMLElement; - Object.assign(target.style, this.props.styles); - } - } - - render() { - return ; - } -} - -export default AutosizeInput; diff --git a/find-dom-node/tests/real-world-autosize-input/input.tsx b/find-dom-node/tests/real-world-autosize-input/input.tsx deleted file mode 100644 index 808cefc..0000000 --- a/find-dom-node/tests/real-world-autosize-input/input.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from "react"; - -class AutosizeInput extends React.Component { - componentDidMount() { - const el = this.refs.input.getDOMNode(); - if (el && typeof el.focus === "function") { - el.focus(); - } - this.copyStyles(); - } - - copyStyles() { - const node = this.getDOMNode(); - if (node && this.props.styles) { - const target = node as HTMLElement; - Object.assign(target.style, this.props.styles); - } - } - - render() { - return ; - } -} - -export default AutosizeInput; diff --git a/find-dom-node/tests/real-world-autosize-input/metrics.json b/find-dom-node/tests/real-world-autosize-input/metrics.json deleted file mode 100644 index 239a1c4..0000000 --- a/find-dom-node/tests/real-world-autosize-input/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "find-dom-node-replacements": [ - { - "cardinality": { - "file": "tests/real-world-autosize-input/input.tsx" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/find-dom-node/tests/refs-getDOMNode/expected.tsx b/find-dom-node/tests/refs-getDOMNode/expected.tsx deleted file mode 100644 index f6b70b9..0000000 --- a/find-dom-node/tests/refs-getDOMNode/expected.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from "react"; - -class Form extends React.Component { - submit() { - const input = React.findDOMNode(this.refs.emailInput); - input.focus(); - } - render() { - return ( -
- -
- ); - } -} diff --git a/find-dom-node/tests/refs-getDOMNode/input.tsx b/find-dom-node/tests/refs-getDOMNode/input.tsx deleted file mode 100644 index c99cdd4..0000000 --- a/find-dom-node/tests/refs-getDOMNode/input.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from "react"; - -class Form extends React.Component { - submit() { - const input = this.refs.emailInput.getDOMNode(); - input.focus(); - } - render() { - return ( -
- -
- ); - } -} diff --git a/find-dom-node/tests/refs-getDOMNode/metrics.json b/find-dom-node/tests/refs-getDOMNode/metrics.json deleted file mode 100644 index 0ab6312..0000000 --- a/find-dom-node/tests/refs-getDOMNode/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "find-dom-node-replacements": [ - { - "cardinality": { - "file": "tests/refs-getDOMNode/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/find-dom-node/tests/this-getDOMNode/expected.tsx b/find-dom-node/tests/this-getDOMNode/expected.tsx deleted file mode 100644 index 5f32477..0000000 --- a/find-dom-node/tests/this-getDOMNode/expected.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -class MyComponent extends React.Component { - componentDidMount() { - const node = React.findDOMNode(this); - node.focus(); - } - render() { - return
; - } -} diff --git a/find-dom-node/tests/this-getDOMNode/input.tsx b/find-dom-node/tests/this-getDOMNode/input.tsx deleted file mode 100644 index 1223434..0000000 --- a/find-dom-node/tests/this-getDOMNode/input.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -class MyComponent extends React.Component { - componentDidMount() { - const node = this.getDOMNode(); - node.focus(); - } - render() { - return
; - } -} diff --git a/find-dom-node/tests/this-getDOMNode/metrics.json b/find-dom-node/tests/this-getDOMNode/metrics.json deleted file mode 100644 index ec124fe..0000000 --- a/find-dom-node/tests/this-getDOMNode/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "find-dom-node-replacements": [ - { - "cardinality": { - "file": "tests/this-getDOMNode/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/find-dom-node/tests/two-components-same-file/expected.tsx b/find-dom-node/tests/two-components-same-file/expected.tsx deleted file mode 100644 index 8c409d5..0000000 --- a/find-dom-node/tests/two-components-same-file/expected.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; - -class Modal extends React.Component { - componentDidMount() { - const node = React.findDOMNode(this); - if (node) { - (node as HTMLElement).setAttribute("aria-hidden", "false"); - } - } - - render() { - return
{this.props.children}
; - } -} - -class Tooltip extends React.Component<{ targetRef?: React.ReactInstance }> { - position() { - const tipNode = React.findDOMNode(this.refs.tip); - const target = this.props.targetRef; - // ... positioning logic using tipNode - return tipNode; - } - - render() { - return
; - } -} - -export { Modal, Tooltip }; diff --git a/find-dom-node/tests/two-components-same-file/input.tsx b/find-dom-node/tests/two-components-same-file/input.tsx deleted file mode 100644 index 8825f9f..0000000 --- a/find-dom-node/tests/two-components-same-file/input.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; - -class Modal extends React.Component { - componentDidMount() { - const node = this.getDOMNode(); - if (node) { - (node as HTMLElement).setAttribute("aria-hidden", "false"); - } - } - - render() { - return
{this.props.children}
; - } -} - -class Tooltip extends React.Component<{ targetRef?: React.ReactInstance }> { - position() { - const tipNode = this.refs.tip.getDOMNode(); - const target = this.props.targetRef; - // ... positioning logic using tipNode - return tipNode; - } - - render() { - return
; - } -} - -export { Modal, Tooltip }; diff --git a/find-dom-node/tests/two-components-same-file/metrics.json b/find-dom-node/tests/two-components-same-file/metrics.json deleted file mode 100644 index 17b1a38..0000000 --- a/find-dom-node/tests/two-components-same-file/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "find-dom-node-replacements": [ - { - "cardinality": { - "file": "tests/two-components-same-file/input.tsx" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/find-dom-node/tests/variable-holding-ref/expected.tsx b/find-dom-node/tests/variable-holding-ref/expected.tsx deleted file mode 100644 index c5c6294..0000000 --- a/find-dom-node/tests/variable-holding-ref/expected.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from "react"; - -class FormWithRefs extends React.Component { - submitForm = () => { - const emailInput = this.refs.email; - const emailNode = React.findDOMNode(emailInput); - if (emailNode && (emailNode as HTMLInputElement).value.trim() === "") { - (emailNode as HTMLInputElement).focus(); - return; - } - // ... submit logic - }; - - render() { - return ( -
- - -
- ); - } -} - -export default FormWithRefs; diff --git a/find-dom-node/tests/variable-holding-ref/input.tsx b/find-dom-node/tests/variable-holding-ref/input.tsx deleted file mode 100644 index 9261aac..0000000 --- a/find-dom-node/tests/variable-holding-ref/input.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from "react"; - -class FormWithRefs extends React.Component { - submitForm = () => { - const emailInput = this.refs.email; - const emailNode = emailInput.getDOMNode(); - if (emailNode && (emailNode as HTMLInputElement).value.trim() === "") { - (emailNode as HTMLInputElement).focus(); - return; - } - // ... submit logic - }; - - render() { - return ( -
- - -
- ); - } -} - -export default FormWithRefs; diff --git a/find-dom-node/tests/variable-holding-ref/metrics.json b/find-dom-node/tests/variable-holding-ref/metrics.json deleted file mode 100644 index 8d1681b..0000000 --- a/find-dom-node/tests/variable-holding-ref/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "find-dom-node-replacements": [ - { - "cardinality": { - "file": "tests/variable-holding-ref/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/find-dom-node/tsconfig.json b/find-dom-node/tsconfig.json deleted file mode 100644 index 469fc5a..0000000 --- a/find-dom-node/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", - "types": ["@codemod.com/jssg-types"], - "allowImportingTsExtensions": true, - "noEmit": true, - "verbatimModuleSyntax": true, - "erasableSyntaxOnly": true, - "strict": true, - "strictNullChecks": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true - }, - "exclude": ["tests"] -} diff --git a/manual-bind-to-arrow/codemod.yaml b/manual-bind-to-arrow/codemod.yaml deleted file mode 100644 index bdd8cae..0000000 --- a/manual-bind-to-arrow/codemod.yaml +++ /dev/null @@ -1,19 +0,0 @@ -schema_version: "1.0" - -name: "@react-codemods/manual-bind-to-arrow" -version: "0.1.0" -description: "Convert this.method = this.method.bind(this) to arrow class property" -author: "React Codemods" -license: "MIT" -workflow: "workflow.yaml" - -targets: - languages: ["tsx"] - -keywords: ["transformation", "bind", "arrow", "react"] - -registry: - access: "public" - visibility: "public" - -capabilities: [] diff --git a/manual-bind-to-arrow/package.json b/manual-bind-to-arrow/package.json deleted file mode 100644 index 2d5daf1..0000000 --- a/manual-bind-to-arrow/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@react-codemods/manual-bind-to-arrow", - "version": "0.1.0", - "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" - } -} diff --git a/manual-bind-to-arrow/scripts/codemod.ts b/manual-bind-to-arrow/scripts/codemod.ts deleted file mode 100644 index ded1d41..0000000 --- a/manual-bind-to-arrow/scripts/codemod.ts +++ /dev/null @@ -1,132 +0,0 @@ -import type { Transform, Edit, SgNode } from "codemod:ast-grep"; -import type TSX from "codemod:ast-grep/langs/tsx"; -import { useMetricAtom } from "codemod:metrics"; - -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"); - if (!classDecl) return null; - return classDecl.find({ rule: { kind: "class_body" } }); -} - -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 (left.field("object")?.text() !== "this") 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"); - const bindObjProp = bindObj?.kind() === "member_expression" ? bindObj.field("property") : null; - const leftProp = left.field("property"); - return leftProp && bindObjProp && leftProp.text() === bindObjProp.text(); - }); - - 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 stmtCount = constructorBody?.children().filter((c) => c.kind() === "expression_statement" || c.kind() === "lexical_declaration" || c.kind() === "variable_declaration").length ?? 0; - 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 isLastBindInConstructor = - bindAssignmentsInConstructor.length > 0 && - bindAssignmentsInConstructor[bindAssignmentsInConstructor.length - 1]!.range().start.index === assign.range().start.index; - const willBeEmptyConstructor = - stmtCount === 1 + bindAssignmentsInConstructor.length && isLastBindInConstructor && !!constructorNode; - - if (stmt) { - const stmtEnd = stmt.range().end.index; - const after = source.slice(stmtEnd, stmtEnd + 10); - const trailing = after.match(/^\s*\n?/)?.[0] ?? "\n"; - edits.push({ - startPos: stmt.range().start.index, - endPos: stmtEnd + trailing.length, - insertedText: "", - }); - } - - if (willBeEmptyConstructor && constructorNode) { - const ctorEnd = constructorNode.range().end.index; - const afterCtor = source.slice(ctorEnd, ctorEnd + 10); - const ctorTrailing = afterCtor.match(/^\s*\n?/)?.[0] ?? "\n"; - edits.push({ - startPos: constructorNode.range().start.index, - endPos: ctorEnd + ctorTrailing.length, - insertedText: "", - }); - } - - const paramsText = params ? params.text() : "()"; - const bodyText = body.text(); - const arrowReplacement = `${methodName} = ${paramsText} => ${bodyText}`; - edits.push(methodDef.replace(arrowReplacement)); - - metric.increment({ file: root.filename() }); - } - - if (edits.length === 0) return null; - return rootNode.commitEdits(edits); -}; - -export default transform; diff --git a/manual-bind-to-arrow/tests/bind-no-matching-method/input.tsx b/manual-bind-to-arrow/tests/bind-no-matching-method/input.tsx deleted file mode 100644 index 8687c1f..0000000 --- a/manual-bind-to-arrow/tests/bind-no-matching-method/input.tsx +++ /dev/null @@ -1,7 +0,0 @@ -class Component extends React.Component { - constructor() { - super(); - this.onClick = this.onClick.bind(this); - } - notOnClick() { } -} diff --git a/manual-bind-to-arrow/tests/bind-outside-constructor-no-op/expected.tsx b/manual-bind-to-arrow/tests/bind-outside-constructor-no-op/expected.tsx deleted file mode 100644 index a4cb0c2..0000000 --- a/manual-bind-to-arrow/tests/bind-outside-constructor-no-op/expected.tsx +++ /dev/null @@ -1,13 +0,0 @@ -class BadExample extends React.Component { - componentDidMount() { - this.handleClick = this.handleClick.bind(this); - } - - handleClick() { - this.setState({ clicked: true }); - } - - render() { - return ; - } -} diff --git a/manual-bind-to-arrow/tests/bind-outside-constructor-no-op/input.tsx b/manual-bind-to-arrow/tests/bind-outside-constructor-no-op/input.tsx deleted file mode 100644 index a4cb0c2..0000000 --- a/manual-bind-to-arrow/tests/bind-outside-constructor-no-op/input.tsx +++ /dev/null @@ -1,13 +0,0 @@ -class BadExample extends React.Component { - componentDidMount() { - this.handleClick = this.handleClick.bind(this); - } - - handleClick() { - this.setState({ clicked: true }); - } - - render() { - return ; - } -} diff --git a/manual-bind-to-arrow/tests/bind-with-other-constructor-code/expected.tsx b/manual-bind-to-arrow/tests/bind-with-other-constructor-code/expected.tsx deleted file mode 100644 index 6d68949..0000000 --- a/manual-bind-to-arrow/tests/bind-with-other-constructor-code/expected.tsx +++ /dev/null @@ -1,14 +0,0 @@ -class DataTable extends React.Component { - constructor(props) { - super(props); - this.state = { sortKey: null }; - } - - handleSort = (column) => { - this.setState({ sortKey: column }); - } - - render() { - return ; - } -} diff --git a/manual-bind-to-arrow/tests/bind-with-other-constructor-code/input.tsx b/manual-bind-to-arrow/tests/bind-with-other-constructor-code/input.tsx deleted file mode 100644 index 8227c22..0000000 --- a/manual-bind-to-arrow/tests/bind-with-other-constructor-code/input.tsx +++ /dev/null @@ -1,15 +0,0 @@ -class DataTable extends React.Component { - constructor(props) { - super(props); - this.state = { sortKey: null }; - this.handleSort = this.handleSort.bind(this); - } - - handleSort(column) { - this.setState({ sortKey: column }); - } - - render() { - return
; - } -} diff --git a/manual-bind-to-arrow/tests/bind-with-other-constructor-code/metrics.json b/manual-bind-to-arrow/tests/bind-with-other-constructor-code/metrics.json deleted file mode 100644 index c919dca..0000000 --- a/manual-bind-to-arrow/tests/bind-with-other-constructor-code/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "manual-bind-to-arrow-transforms": [ - { - "cardinality": { - "file": "tests/bind-with-other-constructor-code/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/manual-bind-to-arrow/tests/constructor-with-props/expected.tsx b/manual-bind-to-arrow/tests/constructor-with-props/expected.tsx deleted file mode 100644 index 9bea035..0000000 --- a/manual-bind-to-arrow/tests/constructor-with-props/expected.tsx +++ /dev/null @@ -1,13 +0,0 @@ -class Pagination extends React.Component { - onPageChange = (page) => { - this.props.onChange(page); - } - - render() { - return ( -
- -
- ); - } -} diff --git a/manual-bind-to-arrow/tests/constructor-with-props/input.tsx b/manual-bind-to-arrow/tests/constructor-with-props/input.tsx deleted file mode 100644 index b65a227..0000000 --- a/manual-bind-to-arrow/tests/constructor-with-props/input.tsx +++ /dev/null @@ -1,18 +0,0 @@ -class Pagination extends React.Component { - constructor(props) { - super(props); - this.onPageChange = this.onPageChange.bind(this); - } - - onPageChange(page) { - this.props.onChange(page); - } - - render() { - return ( -
- -
- ); - } -} diff --git a/manual-bind-to-arrow/tests/constructor-with-props/metrics.json b/manual-bind-to-arrow/tests/constructor-with-props/metrics.json deleted file mode 100644 index 208f39d..0000000 --- a/manual-bind-to-arrow/tests/constructor-with-props/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "manual-bind-to-arrow-transforms": [ - { - "cardinality": { - "file": "tests/constructor-with-props/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/manual-bind-to-arrow/tests/no-bind/expected.tsx b/manual-bind-to-arrow/tests/no-bind/expected.tsx deleted file mode 100644 index d185609..0000000 --- a/manual-bind-to-arrow/tests/no-bind/expected.tsx +++ /dev/null @@ -1,6 +0,0 @@ -class Component extends React.Component { - render() { - return
; - } - handleClick() {} -} diff --git a/manual-bind-to-arrow/tests/no-bind/input.tsx b/manual-bind-to-arrow/tests/no-bind/input.tsx deleted file mode 100644 index d185609..0000000 --- a/manual-bind-to-arrow/tests/no-bind/input.tsx +++ /dev/null @@ -1,6 +0,0 @@ -class Component extends React.Component { - render() { - return
; - } - handleClick() {} -} diff --git a/manual-bind-to-arrow/tests/real-world-dropdown/expected.tsx b/manual-bind-to-arrow/tests/real-world-dropdown/expected.tsx deleted file mode 100644 index 696661e..0000000 --- a/manual-bind-to-arrow/tests/real-world-dropdown/expected.tsx +++ /dev/null @@ -1,28 +0,0 @@ -class Dropdown extends React.Component { - componentDidMount() { - document.addEventListener('click', this.handleOutsideClick); - } - - componentWillUnmount() { - document.removeEventListener('click', this.handleOutsideClick); - } - - toggle = () => { - this.setState({ open: !this.state.open }); - } - - handleOutsideClick = (e) => { - if (this.refs.dropdown && !this.refs.dropdown.contains(e.target)) { - this.setState({ open: false }); - } - } - - render() { - return ( -
- - {this.state.open &&
    {this.props.children}
} -
- ); - } -} diff --git a/manual-bind-to-arrow/tests/real-world-dropdown/input.tsx b/manual-bind-to-arrow/tests/real-world-dropdown/input.tsx deleted file mode 100644 index 22b4c8a..0000000 --- a/manual-bind-to-arrow/tests/real-world-dropdown/input.tsx +++ /dev/null @@ -1,34 +0,0 @@ -class Dropdown extends React.Component { - constructor() { - super(); - this.toggle = this.toggle.bind(this); - this.handleOutsideClick = this.handleOutsideClick.bind(this); - } - - componentDidMount() { - document.addEventListener('click', this.handleOutsideClick); - } - - componentWillUnmount() { - document.removeEventListener('click', this.handleOutsideClick); - } - - toggle() { - this.setState({ open: !this.state.open }); - } - - handleOutsideClick(e) { - if (this.refs.dropdown && !this.refs.dropdown.contains(e.target)) { - this.setState({ open: false }); - } - } - - render() { - return ( -
- - {this.state.open &&
    {this.props.children}
} -
- ); - } -} diff --git a/manual-bind-to-arrow/tests/real-world-dropdown/metrics.json b/manual-bind-to-arrow/tests/real-world-dropdown/metrics.json deleted file mode 100644 index 72328ee..0000000 --- a/manual-bind-to-arrow/tests/real-world-dropdown/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "manual-bind-to-arrow-transforms": [ - { - "cardinality": { - "file": "tests/real-world-dropdown/input.tsx" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/manual-bind-to-arrow/tests/real-world-form-two-binds/expected.tsx b/manual-bind-to-arrow/tests/real-world-form-two-binds/expected.tsx deleted file mode 100644 index 138b037..0000000 --- a/manual-bind-to-arrow/tests/real-world-form-two-binds/expected.tsx +++ /dev/null @@ -1,20 +0,0 @@ -class ContactForm extends React.Component { - handleSubmit = (e) => { - e.preventDefault(); - this.props.onSubmit(this.state); - } - - handleChange = (e) => { - this.setState({ [e.target.name]: e.target.value }); - } - - render() { - return ( -
- - - - - ); - } -} diff --git a/manual-bind-to-arrow/tests/real-world-form-two-binds/input.tsx b/manual-bind-to-arrow/tests/real-world-form-two-binds/input.tsx deleted file mode 100644 index a3e9059..0000000 --- a/manual-bind-to-arrow/tests/real-world-form-two-binds/input.tsx +++ /dev/null @@ -1,26 +0,0 @@ -class ContactForm extends React.Component { - constructor(props) { - super(props); - this.handleSubmit = this.handleSubmit.bind(this); - this.handleChange = this.handleChange.bind(this); - } - - handleSubmit(e) { - e.preventDefault(); - this.props.onSubmit(this.state); - } - - handleChange(e) { - this.setState({ [e.target.name]: e.target.value }); - } - - render() { - return ( -
- - - - - ); - } -} diff --git a/manual-bind-to-arrow/tests/real-world-form-two-binds/metrics.json b/manual-bind-to-arrow/tests/real-world-form-two-binds/metrics.json deleted file mode 100644 index 3dd8706..0000000 --- a/manual-bind-to-arrow/tests/real-world-form-two-binds/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "manual-bind-to-arrow-transforms": [ - { - "cardinality": { - "file": "tests/real-world-form-two-binds/input.tsx" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/manual-bind-to-arrow/tests/real-world-todo-item/expected.tsx b/manual-bind-to-arrow/tests/real-world-todo-item/expected.tsx deleted file mode 100644 index 8e632b2..0000000 --- a/manual-bind-to-arrow/tests/real-world-todo-item/expected.tsx +++ /dev/null @@ -1,20 +0,0 @@ -class TodoItem extends React.Component { - handleToggle = () => { - this.props.onToggle(this.props.id); - } - - handleDelete = () => { - this.props.onDelete(this.props.id); - } - - render() { - const { completed, text } = this.props; - return ( -
  • - - {text} - -
  • - ); - } -} diff --git a/manual-bind-to-arrow/tests/real-world-todo-item/input.tsx b/manual-bind-to-arrow/tests/real-world-todo-item/input.tsx deleted file mode 100644 index 4f63382..0000000 --- a/manual-bind-to-arrow/tests/real-world-todo-item/input.tsx +++ /dev/null @@ -1,26 +0,0 @@ -class TodoItem extends React.Component { - constructor(props) { - super(props); - this.handleToggle = this.handleToggle.bind(this); - this.handleDelete = this.handleDelete.bind(this); - } - - handleToggle() { - this.props.onToggle(this.props.id); - } - - handleDelete() { - this.props.onDelete(this.props.id); - } - - render() { - const { completed, text } = this.props; - return ( -
  • - - {text} - -
  • - ); - } -} diff --git a/manual-bind-to-arrow/tests/real-world-todo-item/metrics.json b/manual-bind-to-arrow/tests/real-world-todo-item/metrics.json deleted file mode 100644 index 3e73cb1..0000000 --- a/manual-bind-to-arrow/tests/real-world-todo-item/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "manual-bind-to-arrow-transforms": [ - { - "cardinality": { - "file": "tests/real-world-todo-item/input.tsx" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/manual-bind-to-arrow/tests/simple-bind/expected.tsx b/manual-bind-to-arrow/tests/simple-bind/expected.tsx deleted file mode 100644 index 168d91d..0000000 --- a/manual-bind-to-arrow/tests/simple-bind/expected.tsx +++ /dev/null @@ -1,3 +0,0 @@ -class Component extends React.Component { - onClick = () => { } -} diff --git a/manual-bind-to-arrow/tests/simple-bind/metrics.json b/manual-bind-to-arrow/tests/simple-bind/metrics.json deleted file mode 100644 index 00745db..0000000 --- a/manual-bind-to-arrow/tests/simple-bind/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "manual-bind-to-arrow-transforms": [ - { - "cardinality": { - "file": "tests/simple-bind/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/manual-bind-to-arrow/tsconfig.json b/manual-bind-to-arrow/tsconfig.json deleted file mode 100644 index 9b4220a..0000000 --- a/manual-bind-to-arrow/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", - "types": ["@codemod.com/jssg-types"], - "allowImportingTsExtensions": true, - "noEmit": true, - "verbatimModuleSyntax": true, - "strict": true - }, - "exclude": ["tests"] -} diff --git a/package.json b/package.json index 1d606ce..106703f 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,19 @@ { - "name": "jssg-react-codemods", + "name": "react-codemod", "version": "0.1.0", "private": true, - "description": "React codemods built with JSSG (JavaScript ast-grep)", + "description": "A collection of codemods to help update React apps", "type": "module", "scripts": { - "test": "pnpm -r test", - "test:use-context-hook": "pnpm --filter @react-codemods/use-context-hook test", - "test:update-react-imports": "pnpm --filter @react-codemods/update-react-imports test", - "test:replace-reactdom-render": "pnpm --filter @react-codemods/replace-reactdom-render test", - "test:replace-string-ref": "pnpm --filter @react-codemods/replace-string-ref test", - "test:rename-unsafe-lifecycles": "pnpm --filter @react-codemods/rename-unsafe-lifecycles test", - "test:error-boundaries": "pnpm --filter @react-codemods/error-boundaries test", - "test:replace-act-import": "pnpm --filter @react-codemods/replace-act-import test", - "test:replace-use-form-state": "pnpm --filter @react-codemods/replace-use-form-state test", - "test:find-dom-node": "pnpm --filter @react-codemods/find-dom-node test", - "test:react-proptypes-to-prop-types": "pnpm --filter @react-codemods/react-proptypes-to-prop-types test", - "test:pure-render-mixin": "pnpm --filter @react-codemods/pure-render-mixin test", - "test:sort-comp": "pnpm --filter @react-codemods/sort-comp test", - "test:manual-bind-to-arrow": "pnpm --filter @react-codemods/manual-bind-to-arrow test", - "check-types": "pnpm -r check-types" + "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'", + "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" }, "keywords": [ "codemod", @@ -33,6 +27,10 @@ "url": "https://github.com/codemod/jssg-react-codemods.git" }, "packageManager": "pnpm@9.14.2", + "devDependencies": { + "@changesets/cli": "^2.30.0", + "@types/node": "latest" + }, "dependencies": { "@jssg/utils": "^0.0.2" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ad23d7..27ccfc0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,17 +11,17 @@ importers: '@jssg/utils': specifier: ^0.0.2 version: 0.0.2 - - error-boundaries: devDependencies: - '@codemod.com/jssg-types': + '@changesets/cli': + specifier: ^2.30.0 + version: 2.30.0(@types/node@25.6.0) + '@types/node': specifier: latest - version: 1.5.0 - typescript: - specifier: latest - version: 5.9.3 + version: 25.6.0 + + codemods/jssg/react-19-migration-recipe: {} - find-dom-node: + codemods/jssg/react-proptypes-to-prop-types: dependencies: '@jssg/utils': specifier: ^0.0.2 @@ -29,30 +29,15 @@ importers: devDependencies: '@codemod.com/jssg-types': specifier: latest - version: 1.5.0 - typescript: - specifier: latest - version: 5.9.3 - - manual-bind-to-arrow: - devDependencies: - '@codemod.com/jssg-types': - specifier: latest - version: 1.5.0 - typescript: - specifier: latest - version: 5.9.3 - - pure-render-mixin: - devDependencies: - '@codemod.com/jssg-types': + version: 1.5.1 + '@types/node': specifier: latest - version: 1.5.0 + version: 25.6.0 typescript: specifier: latest - version: 5.9.3 + version: 6.0.2 - react-proptypes-to-prop-types: + codemods/jssg/replace-act-import: dependencies: '@jssg/utils': specifier: ^0.0.2 @@ -60,21 +45,15 @@ importers: devDependencies: '@codemod.com/jssg-types': specifier: latest - version: 1.5.0 - typescript: + version: 1.5.1 + '@types/node': specifier: latest - version: 5.9.3 - - rename-unsafe-lifecycles: - devDependencies: - '@codemod.com/jssg-types': - specifier: latest - version: 1.5.0 + version: 25.6.0 typescript: specifier: latest - version: 5.9.3 + version: 6.0.2 - replace-act-import: + codemods/jssg/replace-reactdom-render: dependencies: '@jssg/utils': specifier: ^0.0.2 @@ -82,25 +61,15 @@ importers: devDependencies: '@codemod.com/jssg-types': specifier: latest - version: 1.5.0 - typescript: - specifier: latest - version: 5.9.3 - - replace-reactdom-render: - dependencies: - '@jssg/utils': - specifier: ^0.0.2 - version: 0.0.2 - devDependencies: - '@codemod.com/jssg-types': + version: 1.5.1 + '@types/node': specifier: latest - version: 1.5.0 + version: 25.6.0 typescript: specifier: latest - version: 5.9.3 + version: 6.0.2 - replace-string-ref: + codemods/jssg/replace-string-ref: dependencies: '@jssg/utils': specifier: ^0.0.2 @@ -108,12 +77,15 @@ importers: devDependencies: '@codemod.com/jssg-types': specifier: latest - version: 1.5.0 + version: 1.5.1 + '@types/node': + specifier: latest + version: 25.6.0 typescript: specifier: latest - version: 5.9.3 + version: 6.0.2 - replace-use-form-state: + codemods/jssg/replace-use-form-state: dependencies: '@jssg/utils': specifier: ^0.0.2 @@ -121,21 +93,15 @@ importers: devDependencies: '@codemod.com/jssg-types': specifier: latest - version: 1.5.0 - typescript: + version: 1.5.1 + '@types/node': specifier: latest - version: 5.9.3 - - sort-comp: - devDependencies: - '@codemod.com/jssg-types': - specifier: latest - version: 1.5.0 + version: 25.6.0 typescript: specifier: latest - version: 5.9.3 + version: 6.0.2 - update-react-imports: + codemods/jssg/use-context-hook: dependencies: '@jssg/utils': specifier: ^0.0.2 @@ -143,41 +109,833 @@ importers: devDependencies: '@codemod.com/jssg-types': specifier: latest - version: 1.5.0 - typescript: - specifier: latest - version: 5.9.3 - - use-context-hook: - dependencies: - '@jssg/utils': - specifier: ^0.0.2 - version: 0.0.2 - devDependencies: - '@codemod.com/jssg-types': + version: 1.5.1 + '@types/node': specifier: latest - version: 1.5.0 + version: 25.6.0 typescript: specifier: latest - version: 5.9.3 + version: 6.0.2 packages: - '@codemod.com/jssg-types@1.5.0': - resolution: {integrity: sha512-zChRbxI3hBSGrAHnWlEzOw1FztLWMMiarwcr0Wbk0On4hmv7dVgoUqpIHfxb64mEMKJ5syTIKY3ZNd8DcFQa5w==} + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@changesets/apply-release-plan@7.1.0': + resolution: {integrity: sha512-yq8ML3YS7koKQ/9bk1PqO0HMzApIFNwjlwCnwFEXMzNe8NpzeeYYKCmnhWJGkN8g7E51MnWaSbqRcTcdIxUgnQ==} + + '@changesets/assemble-release-plan@6.0.9': + resolution: {integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==} + + '@changesets/changelog-git@0.2.1': + resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} + + '@changesets/cli@2.30.0': + resolution: {integrity: sha512-5D3Nk2JPqMI1wK25pEymeWRSlSMdo5QOGlyfrKg0AOufrUcjEE3RQgaCpHoBiM31CSNrtSgdJ0U6zL1rLDDfBA==} + hasBin: true + + '@changesets/config@3.1.3': + resolution: {integrity: sha512-vnXjcey8YgBn2L1OPWd3ORs0bGC4LoYcK/ubpgvzNVr53JXV5GiTVj7fWdMRsoKUH7hhhMAQnsJUqLr21EncNw==} + + '@changesets/errors@0.2.0': + resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} + + '@changesets/get-dependents-graph@2.1.3': + resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==} + + '@changesets/get-release-plan@4.0.15': + resolution: {integrity: sha512-Q04ZaRPuEVZtA+auOYgFaVQQSA98dXiVe/yFaZfY7hoSmQICHGvP0TF4u3EDNHWmmCS4ekA/XSpKlSM2PyTS2g==} + + '@changesets/get-version-range-type@0.4.0': + resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} + + '@changesets/git@3.0.4': + resolution: {integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==} + + '@changesets/logger@0.1.1': + resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} + + '@changesets/parse@0.4.3': + resolution: {integrity: sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A==} + + '@changesets/pre@2.0.2': + resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} + + '@changesets/read@0.6.7': + resolution: {integrity: sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA==} + + '@changesets/should-skip-package@0.1.2': + resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} + + '@changesets/types@4.1.0': + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + + '@changesets/types@6.1.0': + resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==} + + '@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==} + + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true '@jssg/utils@0.0.2': resolution: {integrity: sha512-eCQv5Xs9yfI6OKq2PQ8SyKsxPhdiaJY/XAFJmsZiVbsK5DIBBmGBA95CsyD4JFWXDKkNv6Q7ER/Kbp6/uWzB2w==} - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + '@manypkg/find-root@1.1.0': + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + + '@manypkg/get-packages@1.1.3': + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + + '@types/node@25.6.0': + resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + better-path-resolve@1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + extendable-error@0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + human-id@4.1.3: + resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} + hasBin: true + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-subdir@1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + outdent@0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + + p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-manager-detector@0.2.11: + resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + read-yaml-file@1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + spawndamnit@3.0.1: + resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + 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'} + + term-size@2.2.1: + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} + engines: {node: '>=8'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + typescript@6.0.2: + resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==} engines: {node: '>=14.17'} hasBin: true + undici-types@7.19.2: + resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + snapshots: - '@codemod.com/jssg-types@1.5.0': {} + '@babel/runtime@7.29.2': {} + + '@changesets/apply-release-plan@7.1.0': + dependencies: + '@changesets/config': 3.1.3 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.8.8 + resolve-from: 5.0.0 + semver: 7.7.4 + + '@changesets/assemble-release-plan@6.0.9': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + semver: 7.7.4 + + '@changesets/changelog-git@0.2.1': + dependencies: + '@changesets/types': 6.1.0 + + '@changesets/cli@2.30.0(@types/node@25.6.0)': + dependencies: + '@changesets/apply-release-plan': 7.1.0 + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/changelog-git': 0.2.1 + '@changesets/config': 3.1.3 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/get-release-plan': 4.0.15 + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.7 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@changesets/write': 0.4.0 + '@inquirer/external-editor': 1.0.3(@types/node@25.6.0) + '@manypkg/get-packages': 1.1.3 + ansi-colors: 4.1.3 + enquirer: 2.4.1 + fs-extra: 7.0.1 + mri: 1.2.0 + package-manager-detector: 0.2.11 + picocolors: 1.1.1 + resolve-from: 5.0.0 + semver: 7.7.4 + spawndamnit: 3.0.1 + term-size: 2.2.1 + transitivePeerDependencies: + - '@types/node' + + '@changesets/config@3.1.3': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/logger': 0.1.1 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.8 + + '@changesets/errors@0.2.0': + dependencies: + extendable-error: 0.1.7 + + '@changesets/get-dependents-graph@2.1.3': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + picocolors: 1.1.1 + semver: 7.7.4 + + '@changesets/get-release-plan@4.0.15': + dependencies: + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/config': 3.1.3 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.7 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/get-version-range-type@0.4.0': {} + + '@changesets/git@3.0.4': + dependencies: + '@changesets/errors': 0.2.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.8 + spawndamnit: 3.0.1 + + '@changesets/logger@0.1.1': + dependencies: + picocolors: 1.1.1 + + '@changesets/parse@0.4.3': + dependencies: + '@changesets/types': 6.1.0 + js-yaml: 4.1.1 + + '@changesets/pre@2.0.2': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + + '@changesets/read@0.6.7': + dependencies: + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/parse': 0.4.3 + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + p-filter: 2.1.0 + picocolors: 1.1.1 + + '@changesets/should-skip-package@0.1.2': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/types@4.1.0': {} + + '@changesets/types@6.1.0': {} + + '@changesets/write@0.4.0': + dependencies: + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + human-id: 4.1.3 + prettier: 2.8.8 + + '@codemod.com/jssg-types@1.5.1': {} + + '@inquirer/external-editor@1.0.3(@types/node@25.6.0)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 25.6.0 '@jssg/utils@0.0.2': {} - typescript@5.9.3: {} + '@manypkg/find-root@1.1.0': + dependencies: + '@babel/runtime': 7.29.2 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + + '@manypkg/get-packages@1.1.3': + dependencies: + '@babel/runtime': 7.29.2 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@types/node@12.20.55': {} + + '@types/node@25.6.0': + dependencies: + undici-types: 7.19.2 + + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-union@2.1.0: {} + + better-path-resolve@1.0.0: + dependencies: + is-windows: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + chardet@2.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + detect-indent@6.1.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + + esprima@4.0.1: {} + + extendable-error@0.1.7: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + graceful-fs@4.2.11: {} + + human-id@4.1.3: {} + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-subdir@1.2.0: + dependencies: + better-path-resolve: 1.0.0 + + is-windows@1.0.2: {} + + isexe@2.0.0: {} + + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + lodash.startcase@4.4.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mri@1.2.0: {} + + outdent@0.5.0: {} + + p-filter@2.1.0: + dependencies: + p-map: 2.1.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-map@2.1.0: {} + + p-try@2.2.0: {} + + package-manager-detector@0.2.11: + dependencies: + quansync: 0.2.11 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + pify@4.0.1: {} + + prettier@2.8.8: {} + + quansync@0.2.11: {} + + queue-microtask@1.2.3: {} + + read-yaml-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.2 + pify: 4.0.1 + strip-bom: 3.0.0 + + resolve-from@5.0.0: {} + + reusify@1.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safer-buffer@2.1.2: {} + + semver@7.7.4: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + spawndamnit@3.0.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + sprintf-js@1.0.3: {} + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-bom@3.0.0: {} + + term-size@2.2.1: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + typescript@6.0.2: {} + + undici-types@7.19.2: {} + + universalify@0.1.2: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d415562..15e528b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,14 +1,2 @@ packages: - - "replace-act-import" - - "replace-use-form-state" - - "use-context-hook" - - "update-react-imports" - - "replace-reactdom-render" - - "replace-string-ref" - - "rename-unsafe-lifecycles" - - "manual-bind-to-arrow" - - "sort-comp" - - "pure-render-mixin" - - "react-proptypes-to-prop-types" - - "error-boundaries" - - "find-dom-node" + - "codemods/jssg/*" diff --git a/pure-render-mixin/codemod.yaml b/pure-render-mixin/codemod.yaml deleted file mode 100644 index 9ef2cd5..0000000 --- a/pure-render-mixin/codemod.yaml +++ /dev/null @@ -1,19 +0,0 @@ -schema_version: "1.0" - -name: "@react-codemods/pure-render-mixin" -version: "0.1.0" -description: "Replace PureRenderMixin with shouldComponentUpdate using React.addons.shallowCompare" -author: "React Codemods" -license: "MIT" -workflow: "workflow.yaml" - -targets: - languages: ["tsx"] - -keywords: ["transformation", "migration", "pure-render-mixin", "shouldComponentUpdate"] - -registry: - access: "public" - visibility: "public" - -capabilities: [] diff --git a/pure-render-mixin/package.json b/pure-render-mixin/package.json deleted file mode 100644 index 558ee4b..0000000 --- a/pure-render-mixin/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@react-codemods/pure-render-mixin", - "version": "0.1.0", - "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" - } -} diff --git a/pure-render-mixin/scripts/codemod.ts b/pure-render-mixin/scripts/codemod.ts deleted file mode 100644 index 717baf0..0000000 --- a/pure-render-mixin/scripts/codemod.ts +++ /dev/null @@ -1,162 +0,0 @@ -import type { Transform, Edit, SgNode } from "codemod:ast-grep"; -import type TSX from "codemod:ast-grep/langs/tsx"; -import { useMetricAtom } from "codemod:metrics"; - -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}$` } }; -} - -const transform: Transform = async (root, options) => { - const rootNode = root.root(); - 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; - - const createClassCalls = rootNode.findAll({ - rule: { - kind: "call_expression", - has: { - field: "function", - any: [ - { - kind: "member_expression", - all: [ - { has: { field: "object", kind: "identifier", regex: "^React$" } }, - { has: { field: "property", kind: "property_identifier", regex: "^createClass$" } }, - ], - }, - { kind: "identifier", regex: "^createClass$" }, - ], - }, - }, - }); - - for (const call of createClassCalls) { - 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 toRemoveSet = new Set(mixinInArray.map((n) => n.range().start.index)); - const kept = value.children().filter( - (c) => - c.kind() !== "[" && - c.kind() !== "]" && - c.kind() !== "," && - !toRemoveSet.has(c.range().start.index), - ); - const newArrayText = kept.length === 0 ? "[]" : "[" + kept.map((e) => e.text()).join(", ") + "]"; - - const allPairs = configObj.findAll(PAIR_RULE); - allPairs.sort((a, b) => a.range().start.index - b.range().start.index); - const mixinsIdx = allPairs.findIndex((p) => p.range().start.index === mixinsPair.range().start.index); - const nextPair = mixinsIdx >= 0 && mixinsIdx < allPairs.length - 1 ? allPairs[mixinsIdx + 1]! : null; - const prevPair = mixinsIdx > 0 ? allPairs[mixinsIdx - 1]! : null; - - if (newArrayText === "[]") { - const removeStart = nextPair - ? mixinsPair.range().start.index - : prevPair - ? prevPair.range().end.index - : mixinsPair.range().start.index; - const removeEnd = nextPair - ? nextPair.range().start.index - : mixinsPair.range().end.index; - edits.push({ - startPos: removeStart, - endPos: removeEnd, - insertedText: "", - }); - } else { - const newMixinsText = `mixins: ${newArrayText}`; - edits.push(mixinsPair.replace(newMixinsText)); - } - - const renderStart = renderPair.range().start.index; - const beforeRender = source.slice(Math.max(0, renderStart - 30), renderStart); - const indentMatch = beforeRender.match(/\n(\s*)$/); - const indent = indentMatch ? indentMatch[1] : " "; - const shouldUpdateBlock = `${indent}${SHOULD_COMPONENT_UPDATE}: function(nextProps, nextState) {\n${indent} return React.addons.shallowCompare(this, nextProps, nextState);\n${indent}},\n${indent}`; - edits.push({ - startPos: renderStart, - endPos: renderStart, - insertedText: shouldUpdateBlock, - }); - - transformCount++; - metric.increment({ file: 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 trailing = after.match(/^(\s*\n?)/)?.[1] ?? "\n"; - edits.push({ - startPos: varDecl.range().start.index, - endPos: varDecl.range().end.index + trailing.length, - insertedText: "", - }); - break; - } - } - } - - if (edits.length === 0) return null; - return rootNode.commitEdits(edits); -}; - -export default transform; diff --git a/pure-render-mixin/tests/create-class-direct-call/expected.tsx b/pure-render-mixin/tests/create-class-direct-call/expected.tsx deleted file mode 100644 index c913438..0000000 --- a/pure-render-mixin/tests/create-class-direct-call/expected.tsx +++ /dev/null @@ -1,17 +0,0 @@ -var React = require('react/addons'); -var createClass = React.createClass; - -var Sidebar = createClass({ - - shouldComponentUpdate: function(nextProps, nextState) { - - return React.addons.shallowCompare(this, nextProps, nextState); - - }, - - render: function() { - return ; - }, -}); - -module.exports = Sidebar; diff --git a/pure-render-mixin/tests/create-class-direct-call/input.tsx b/pure-render-mixin/tests/create-class-direct-call/input.tsx deleted file mode 100644 index 619b84a..0000000 --- a/pure-render-mixin/tests/create-class-direct-call/input.tsx +++ /dev/null @@ -1,13 +0,0 @@ -var React = require('react/addons'); -var PureRenderMixin = React.addons.PureRenderMixin; -var createClass = React.createClass; - -var Sidebar = createClass({ - mixins: [PureRenderMixin], - - render: function() { - return ; - }, -}); - -module.exports = Sidebar; diff --git a/pure-render-mixin/tests/create-class-direct-call/metrics.json b/pure-render-mixin/tests/create-class-direct-call/metrics.json deleted file mode 100644 index e9a1503..0000000 --- a/pure-render-mixin/tests/create-class-direct-call/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "pure-render-mixin-replacements": [ - { - "cardinality": { - "file": "tests/create-class-direct-call/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/pure-render-mixin/tests/no-pure-render-mixin/expected.tsx b/pure-render-mixin/tests/no-pure-render-mixin/expected.tsx deleted file mode 100644 index 1e98a17..0000000 --- a/pure-render-mixin/tests/no-pure-render-mixin/expected.tsx +++ /dev/null @@ -1,11 +0,0 @@ -var React = require('react'); - -var MyComponent = React.createClass({ - mixins: [SomeOtherMixin], - - render: function() { - return
    ; - }, -}); - -module.exports = MyComponent; diff --git a/pure-render-mixin/tests/no-pure-render-mixin/input.tsx b/pure-render-mixin/tests/no-pure-render-mixin/input.tsx deleted file mode 100644 index 1e98a17..0000000 --- a/pure-render-mixin/tests/no-pure-render-mixin/input.tsx +++ /dev/null @@ -1,11 +0,0 @@ -var React = require('react'); - -var MyComponent = React.createClass({ - mixins: [SomeOtherMixin], - - render: function() { - return
    ; - }, -}); - -module.exports = MyComponent; diff --git a/pure-render-mixin/tests/original-fixture3-removes-var/expected.tsx b/pure-render-mixin/tests/original-fixture3-removes-var/expected.tsx deleted file mode 100644 index 659d0a9..0000000 --- a/pure-render-mixin/tests/original-fixture3-removes-var/expected.tsx +++ /dev/null @@ -1,17 +0,0 @@ -var React = require('react/addons'); - -var Foo = 'Foo'; -var MyComponent = React.createClass({ - - shouldComponentUpdate: function(nextProps, nextState) { - - return React.addons.shallowCompare(this, nextProps, nextState); - - }, - - render: function() { - return
    ; - }, -}); - -module.exports = MyComponent; diff --git a/pure-render-mixin/tests/original-fixture3-removes-var/metrics.json b/pure-render-mixin/tests/original-fixture3-removes-var/metrics.json deleted file mode 100644 index 97a421d..0000000 --- a/pure-render-mixin/tests/original-fixture3-removes-var/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "pure-render-mixin-replacements": [ - { - "cardinality": { - "file": "tests/original-fixture3-removes-var/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/pure-render-mixin/tests/original-mixed-mixins/expected.tsx b/pure-render-mixin/tests/original-mixed-mixins/expected.tsx deleted file mode 100644 index a608307..0000000 --- a/pure-render-mixin/tests/original-mixed-mixins/expected.tsx +++ /dev/null @@ -1,64 +0,0 @@ -var React = require('react/addons'); - -var PureRenderMixin = React.addons.PureRenderMixin; - -var MyComponent = React.createClass({ - - shouldComponentUpdate: function(nextProps, nextState) { - - return React.addons.shallowCompare(this, nextProps, nextState); - - }, - - render: function() { - return
    ; - }, -}); - -var MyMixedComponent = React.createClass({ - mixins: [SomeOtherMixin], - - - shouldComponentUpdate: function(nextProps, nextState) { - - return React.addons.shallowCompare(this, nextProps, nextState); - - }, - - render: function() { - return
    ; - }, -}); - -var MyFooComponent = React.createClass({ - mixins: [SomeOtherMixin], - - - shouldComponentUpdate: function(nextProps, nextState) { - - return React.addons.shallowCompare(this, nextProps, nextState); - - }, - - render: function() { - return
    ; - }, - - foo: function() { - - }, -}); - -var MyStupidComponent = React.createClass({ - mixins: [PureRenderMixin], - - shouldComponentUpdate: function() { - return !!'wtf is this doing here?'; - }, - - render: function() { - return
    ; - }, -}); - -module.exports = MyComponent; diff --git a/pure-render-mixin/tests/original-mixed-mixins/metrics.json b/pure-render-mixin/tests/original-mixed-mixins/metrics.json deleted file mode 100644 index cb8ca5d..0000000 --- a/pure-render-mixin/tests/original-mixed-mixins/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "pure-render-mixin-replacements": [ - { - "cardinality": { - "file": "tests/original-mixed-mixins/input.tsx" - }, - "count": 3 - } - ] -} \ No newline at end of file diff --git a/pure-render-mixin/tests/original-single/expected.tsx b/pure-render-mixin/tests/original-single/expected.tsx deleted file mode 100644 index 88bd5aa..0000000 --- a/pure-render-mixin/tests/original-single/expected.tsx +++ /dev/null @@ -1,16 +0,0 @@ -var React = require('react/addons'); - -var MyComponent = React.createClass({ - - shouldComponentUpdate: function(nextProps, nextState) { - - return React.addons.shallowCompare(this, nextProps, nextState); - - }, - - render: function() { - return
    ; - }, -}); - -module.exports = MyComponent; diff --git a/pure-render-mixin/tests/original-single/metrics.json b/pure-render-mixin/tests/original-single/metrics.json deleted file mode 100644 index 2014f7e..0000000 --- a/pure-render-mixin/tests/original-single/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "pure-render-mixin-replacements": [ - { - "cardinality": { - "file": "tests/original-single/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/pure-render-mixin/tests/real-world-form/expected.tsx b/pure-render-mixin/tests/real-world-form/expected.tsx deleted file mode 100644 index 0c7106d..0000000 --- a/pure-render-mixin/tests/real-world-form/expected.tsx +++ /dev/null @@ -1,29 +0,0 @@ -var React = require("react/addons"); -var Form = React.createClass({ - getInitialState: function() { - return { values: {} }; - }, - - handleSubmit: function(e) { - e.preventDefault(); - this.props.onSubmit(this.state.values); - }, - - - shouldComponentUpdate: function(nextProps, nextState) { - - return React.addons.shallowCompare(this, nextProps, nextState); - - }, - - render: function() { - return ( -
    - - - - ); - }, -}); - -module.exports = Form; diff --git a/pure-render-mixin/tests/real-world-form/input.tsx b/pure-render-mixin/tests/real-world-form/input.tsx deleted file mode 100644 index 0f38fa4..0000000 --- a/pure-render-mixin/tests/real-world-form/input.tsx +++ /dev/null @@ -1,26 +0,0 @@ -var React = require("react/addons"); -var PureRenderMixin = React.addons.PureRenderMixin; - -var Form = React.createClass({ - mixins: [PureRenderMixin], - - getInitialState: function() { - return { values: {} }; - }, - - handleSubmit: function(e) { - e.preventDefault(); - this.props.onSubmit(this.state.values); - }, - - render: function() { - return ( -
    - - - - ); - }, -}); - -module.exports = Form; diff --git a/pure-render-mixin/tests/real-world-form/metrics.json b/pure-render-mixin/tests/real-world-form/metrics.json deleted file mode 100644 index b885487..0000000 --- a/pure-render-mixin/tests/real-world-form/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "pure-render-mixin-replacements": [ - { - "cardinality": { - "file": "tests/real-world-form/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/pure-render-mixin/tests/real-world-list-item/expected.tsx b/pure-render-mixin/tests/real-world-list-item/expected.tsx deleted file mode 100644 index 0b3ff2a..0000000 --- a/pure-render-mixin/tests/real-world-list-item/expected.tsx +++ /dev/null @@ -1,33 +0,0 @@ -var React = require('react/addons'); -var TodoItem = React.createClass({ - displayName: 'TodoItem', - - propTypes: { - id: React.PropTypes.string.isRequired, - title: React.PropTypes.string, - completed: React.PropTypes.bool, - onToggle: React.PropTypes.func, - }, - - handleClick: function() { - this.props.onToggle(this.props.id); - }, - - - shouldComponentUpdate: function(nextProps, nextState) { - - return React.addons.shallowCompare(this, nextProps, nextState); - - }, - - render: function() { - var className = this.props.completed ? 'todo-item completed' : 'todo-item'; - return ( -
  • - {this.props.title} -
  • - ); - }, -}); - -module.exports = TodoItem; diff --git a/pure-render-mixin/tests/real-world-list-item/input.tsx b/pure-render-mixin/tests/real-world-list-item/input.tsx deleted file mode 100644 index 4ae280f..0000000 --- a/pure-render-mixin/tests/real-world-list-item/input.tsx +++ /dev/null @@ -1,30 +0,0 @@ -var React = require('react/addons'); -var PureRenderMixin = React.addons.PureRenderMixin; - -var TodoItem = React.createClass({ - displayName: 'TodoItem', - - propTypes: { - id: React.PropTypes.string.isRequired, - title: React.PropTypes.string, - completed: React.PropTypes.bool, - onToggle: React.PropTypes.func, - }, - - mixins: [PureRenderMixin], - - handleClick: function() { - this.props.onToggle(this.props.id); - }, - - render: function() { - var className = this.props.completed ? 'todo-item completed' : 'todo-item'; - return ( -
  • - {this.props.title} -
  • - ); - }, -}); - -module.exports = TodoItem; diff --git a/pure-render-mixin/tests/real-world-list-item/metrics.json b/pure-render-mixin/tests/real-world-list-item/metrics.json deleted file mode 100644 index 3a33f3f..0000000 --- a/pure-render-mixin/tests/real-world-list-item/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "pure-render-mixin-replacements": [ - { - "cardinality": { - "file": "tests/real-world-list-item/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/pure-render-mixin/tests/real-world-modal-with-other-mixin/expected.tsx b/pure-render-mixin/tests/real-world-modal-with-other-mixin/expected.tsx deleted file mode 100644 index 1c27cb5..0000000 --- a/pure-render-mixin/tests/real-world-modal-with-other-mixin/expected.tsx +++ /dev/null @@ -1,37 +0,0 @@ -var React = require('react/addons'); -var EventListenerMixin = require('react-event-listener-mixin'); - -var Modal = React.createClass({ - mixins: [EventListenerMixin], - - componentDidMount: function() { - this.addEventListener(document, 'keydown', this.handleKeyDown); - }, - - componentWillUnmount: function() { - this.removeEventListener(document, 'keydown', this.handleKeyDown); - }, - - handleKeyDown: function(e) { - if (e.key === 'Escape') this.props.onClose(); - }, - - - shouldComponentUpdate: function(nextProps, nextState) { - - return React.addons.shallowCompare(this, nextProps, nextState); - - }, - - render: function() { - return ( -
    -
    - {this.props.children} -
    -
    - ); - }, -}); - -module.exports = Modal; diff --git a/pure-render-mixin/tests/real-world-modal-with-other-mixin/input.tsx b/pure-render-mixin/tests/real-world-modal-with-other-mixin/input.tsx deleted file mode 100644 index 31b1da4..0000000 --- a/pure-render-mixin/tests/real-world-modal-with-other-mixin/input.tsx +++ /dev/null @@ -1,31 +0,0 @@ -var React = require('react/addons'); -var PureRenderMixin = React.addons.PureRenderMixin; -var EventListenerMixin = require('react-event-listener-mixin'); - -var Modal = React.createClass({ - mixins: [PureRenderMixin, EventListenerMixin], - - componentDidMount: function() { - this.addEventListener(document, 'keydown', this.handleKeyDown); - }, - - componentWillUnmount: function() { - this.removeEventListener(document, 'keydown', this.handleKeyDown); - }, - - handleKeyDown: function(e) { - if (e.key === 'Escape') this.props.onClose(); - }, - - render: function() { - return ( -
    -
    - {this.props.children} -
    -
    - ); - }, -}); - -module.exports = Modal; diff --git a/pure-render-mixin/tests/real-world-modal-with-other-mixin/metrics.json b/pure-render-mixin/tests/real-world-modal-with-other-mixin/metrics.json deleted file mode 100644 index b6b3fff..0000000 --- a/pure-render-mixin/tests/real-world-modal-with-other-mixin/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "pure-render-mixin-replacements": [ - { - "cardinality": { - "file": "tests/real-world-modal-with-other-mixin/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/pure-render-mixin/tests/two-components-one-with-mixin/expected.tsx b/pure-render-mixin/tests/two-components-one-with-mixin/expected.tsx deleted file mode 100644 index 49d51b3..0000000 --- a/pure-render-mixin/tests/two-components-one-with-mixin/expected.tsx +++ /dev/null @@ -1,21 +0,0 @@ -var React = require('react/addons'); -var PlainComponent = React.createClass({ - render: function() { - return
    Plain
    ; - }, -}); - -var OptimizedComponent = React.createClass({ - - shouldComponentUpdate: function(nextProps, nextState) { - - return React.addons.shallowCompare(this, nextProps, nextState); - - }, - - render: function() { - return
    Optimized
    ; - }, -}); - -module.exports = { PlainComponent, OptimizedComponent }; diff --git a/pure-render-mixin/tests/two-components-one-with-mixin/input.tsx b/pure-render-mixin/tests/two-components-one-with-mixin/input.tsx deleted file mode 100644 index cc7145f..0000000 --- a/pure-render-mixin/tests/two-components-one-with-mixin/input.tsx +++ /dev/null @@ -1,18 +0,0 @@ -var React = require('react/addons'); -var PureRenderMixin = React.addons.PureRenderMixin; - -var PlainComponent = React.createClass({ - render: function() { - return
    Plain
    ; - }, -}); - -var OptimizedComponent = React.createClass({ - mixins: [PureRenderMixin], - - render: function() { - return
    Optimized
    ; - }, -}); - -module.exports = { PlainComponent, OptimizedComponent }; diff --git a/pure-render-mixin/tests/two-components-one-with-mixin/metrics.json b/pure-render-mixin/tests/two-components-one-with-mixin/metrics.json deleted file mode 100644 index 40a58eb..0000000 --- a/pure-render-mixin/tests/two-components-one-with-mixin/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "pure-render-mixin-replacements": [ - { - "cardinality": { - "file": "tests/two-components-one-with-mixin/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/pure-render-mixin/tsconfig.json b/pure-render-mixin/tsconfig.json deleted file mode 100644 index 469fc5a..0000000 --- a/pure-render-mixin/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", - "types": ["@codemod.com/jssg-types"], - "allowImportingTsExtensions": true, - "noEmit": true, - "verbatimModuleSyntax": true, - "erasableSyntaxOnly": true, - "strict": true, - "strictNullChecks": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true - }, - "exclude": ["tests"] -} diff --git a/pure-render-mixin/workflow.yaml b/pure-render-mixin/workflow.yaml deleted file mode 100644 index 8773841..0000000 --- a/pure-render-mixin/workflow.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# 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/react-proptypes-to-prop-types/codemod.yaml b/react-proptypes-to-prop-types/codemod.yaml deleted file mode 100644 index fc93ba1..0000000 --- a/react-proptypes-to-prop-types/codemod.yaml +++ /dev/null @@ -1,19 +0,0 @@ -schema_version: "1.0" - -name: "@react-codemods/react-proptypes-to-prop-types" -version: "0.1.0" -description: "Replace React.PropTypes with prop-types package" -author: "React Codemods" -license: "MIT" -workflow: "workflow.yaml" - -targets: - languages: ["tsx"] - -keywords: ["transformation", "migration", "prop-types"] - -registry: - access: "public" - visibility: "public" - -capabilities: [] diff --git a/react-proptypes-to-prop-types/package.json b/react-proptypes-to-prop-types/package.json deleted file mode 100644 index 2bfac08..0000000 --- a/react-proptypes-to-prop-types/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@react-codemods/react-proptypes-to-prop-types", - "version": "0.1.0", - "description": "Replace React.PropTypes with prop-types package", - "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" - }, - "dependencies": { - "@jssg/utils": "^0.0.2" - } -} diff --git a/react-proptypes-to-prop-types/scripts/codemod.ts b/react-proptypes-to-prop-types/scripts/codemod.ts deleted file mode 100644 index d44a06a..0000000 --- a/react-proptypes-to-prop-types/scripts/codemod.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { Transform, Edit } 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"; - -const transform: Transform = async (root) => { - const rootNode = root.root(); - const edits: Edit[] = []; - - const propTypesMetric = useMetricAtom("react-proptypes-migrations"); - - const reactDefaultImport = getImport(rootNode, { type: "default", from: "react" }); - const reactName = reactDefaultImport?.alias ?? "React"; - - const propTypesImport = getImport(rootNode, { type: "default", from: "prop-types" }); - const propTypesName = propTypesImport?.alias ?? "PropTypes"; - - // Find member_expressions: React.PropTypes (object=React, property=PropTypes) - const reactPropTypesRefs = rootNode.findAll({ - rule: { - kind: "member_expression", - all: [ - { has: { field: "object", kind: "identifier", regex: `^${reactName}$` } }, - { has: { field: "property", kind: "property_identifier", regex: "^PropTypes$" } }, - ], - }, - }); - - for (const ref of reactPropTypesRefs) { - edits.push(ref.replace(propTypesName)); - propTypesMetric.increment({ - file: root.filename(), - }); - } - - if (edits.length === 0) return null; - - // Add prop-types import if not present (insert after first import) - if (!propTypesImport) { - const firstImport = rootNode.find({ - rule: { kind: "import_statement" }, - }); - if (firstImport) { - const importLine = `import ${propTypesName} from "prop-types";\n`; - const insertPos = firstImport.range().start.index; - edits.push({ startPos: insertPos, endPos: insertPos, insertedText: importLine }); - } - } - - return rootNode.commitEdits(edits); -}; - -export default transform; diff --git a/react-proptypes-to-prop-types/tests/array-and-object/expected.tsx b/react-proptypes-to-prop-types/tests/array-and-object/expected.tsx deleted file mode 100644 index b855699..0000000 --- a/react-proptypes-to-prop-types/tests/array-and-object/expected.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import PropTypes from "prop-types"; -import React from "react"; - -Component.propTypes = { - items: PropTypes.array, - config: PropTypes.object, - id: PropTypes.oneOf([1, 2]).isRequired, -}; - -function Component() { - return null; -} diff --git a/react-proptypes-to-prop-types/tests/array-and-object/input.tsx b/react-proptypes-to-prop-types/tests/array-and-object/input.tsx deleted file mode 100644 index e301210..0000000 --- a/react-proptypes-to-prop-types/tests/array-and-object/input.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -Component.propTypes = { - items: React.PropTypes.array, - config: React.PropTypes.object, - id: React.PropTypes.oneOf([1, 2]).isRequired, -}; - -function Component() { - return null; -} diff --git a/react-proptypes-to-prop-types/tests/array-and-object/metrics.json b/react-proptypes-to-prop-types/tests/array-and-object/metrics.json deleted file mode 100644 index 409488c..0000000 --- a/react-proptypes-to-prop-types/tests/array-and-object/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "react-proptypes-migrations": [ - { - "cardinality": { - "file": "tests/array-and-object/input.tsx" - }, - "count": 3 - } - ] -} \ No newline at end of file diff --git a/react-proptypes-to-prop-types/tests/basic-prop-types/expected.tsx b/react-proptypes-to-prop-types/tests/basic-prop-types/expected.tsx deleted file mode 100644 index 019448e..0000000 --- a/react-proptypes-to-prop-types/tests/basic-prop-types/expected.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; - -const MyComponent = ({ name }: { name: string }) => ( -
    {name}
    -); - -MyComponent.propTypes = { - name: PropTypes.string, -}; - -export default MyComponent; diff --git a/react-proptypes-to-prop-types/tests/basic-prop-types/input.tsx b/react-proptypes-to-prop-types/tests/basic-prop-types/input.tsx deleted file mode 100644 index f23d333..0000000 --- a/react-proptypes-to-prop-types/tests/basic-prop-types/input.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; - -const MyComponent = ({ name }: { name: string }) => ( -
    {name}
    -); - -MyComponent.propTypes = { - name: React.PropTypes.string, -}; - -export default MyComponent; diff --git a/react-proptypes-to-prop-types/tests/basic-prop-types/metrics.json b/react-proptypes-to-prop-types/tests/basic-prop-types/metrics.json deleted file mode 100644 index 59c45c1..0000000 --- a/react-proptypes-to-prop-types/tests/basic-prop-types/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "react-proptypes-migrations": [ - { - "cardinality": { - "file": "tests/basic-prop-types/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/react-proptypes-to-prop-types/tests/multiple-types/expected.tsx b/react-proptypes-to-prop-types/tests/multiple-types/expected.tsx deleted file mode 100644 index a61570b..0000000 --- a/react-proptypes-to-prop-types/tests/multiple-types/expected.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import PropTypes from "prop-types"; -import React from "react"; - -function Button({ label, disabled }: { label: string; disabled?: boolean }) { - return ; -} - -Button.propTypes = { - label: PropTypes.string.isRequired, - disabled: PropTypes.bool, -}; - -export default Button; diff --git a/react-proptypes-to-prop-types/tests/multiple-types/input.tsx b/react-proptypes-to-prop-types/tests/multiple-types/input.tsx deleted file mode 100644 index cf3cbc7..0000000 --- a/react-proptypes-to-prop-types/tests/multiple-types/input.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react"; - -function Button({ label, disabled }: { label: string; disabled?: boolean }) { - return ; -} - -Button.propTypes = { - label: React.PropTypes.string.isRequired, - disabled: React.PropTypes.bool, -}; - -export default Button; diff --git a/react-proptypes-to-prop-types/tests/multiple-types/metrics.json b/react-proptypes-to-prop-types/tests/multiple-types/metrics.json deleted file mode 100644 index 50d7232..0000000 --- a/react-proptypes-to-prop-types/tests/multiple-types/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "react-proptypes-migrations": [ - { - "cardinality": { - "file": "tests/multiple-types/input.tsx" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/react-proptypes-to-prop-types/tests/no-react-proptypes/expected.tsx b/react-proptypes-to-prop-types/tests/no-react-proptypes/expected.tsx deleted file mode 100644 index 01042b1..0000000 --- a/react-proptypes-to-prop-types/tests/no-react-proptypes/expected.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import PropTypes from "prop-types"; - -const Component = () => null; -Component.propTypes = { id: PropTypes.string }; -export default Component; diff --git a/react-proptypes-to-prop-types/tests/no-react-proptypes/input.tsx b/react-proptypes-to-prop-types/tests/no-react-proptypes/input.tsx deleted file mode 100644 index 01042b1..0000000 --- a/react-proptypes-to-prop-types/tests/no-react-proptypes/input.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import PropTypes from "prop-types"; - -const Component = () => null; -Component.propTypes = { id: PropTypes.string }; -export default Component; diff --git a/react-proptypes-to-prop-types/tsconfig.json b/react-proptypes-to-prop-types/tsconfig.json deleted file mode 100644 index 469fc5a..0000000 --- a/react-proptypes-to-prop-types/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", - "types": ["@codemod.com/jssg-types"], - "allowImportingTsExtensions": true, - "noEmit": true, - "verbatimModuleSyntax": true, - "erasableSyntaxOnly": true, - "strict": true, - "strictNullChecks": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true - }, - "exclude": ["tests"] -} diff --git a/react-proptypes-to-prop-types/workflow.yaml b/react-proptypes-to-prop-types/workflow.yaml deleted file mode 100644 index 8773841..0000000 --- a/react-proptypes-to-prop-types/workflow.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# 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/rename-unsafe-lifecycles/README.md b/rename-unsafe-lifecycles/README.md deleted file mode 100644 index d290bda..0000000 --- a/rename-unsafe-lifecycles/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# @react-codemods/rename-unsafe-lifecycles - -Rename deprecated lifecycle methods to UNSAFE_ prefixed versions - -## Installation - -```bash -# Install from registry -codemod run @react-codemods/rename-unsafe-lifecycles - -# Or run locally -codemod run -w workflow.yaml -``` - -## Usage - -This codemod transforms tsx code by: - -- Converting `var` declarations to `const`/`let` -- Removing debug statements -- Modernizing syntax patterns - -## Development - -```bash -# Test the transformation -npm test - -# Validate the workflow -codemod validate -w workflow.yaml - -# Publish to registry -codemod login -codemod publish -``` - -## License -MIT diff --git a/rename-unsafe-lifecycles/codemod.yaml b/rename-unsafe-lifecycles/codemod.yaml deleted file mode 100644 index 1893f91..0000000 --- a/rename-unsafe-lifecycles/codemod.yaml +++ /dev/null @@ -1,20 +0,0 @@ -schema_version: "1.0" - -name: "@react-codemods/rename-unsafe-lifecycles" -version: "0.1.0" -description: "Rename deprecated lifecycle methods to UNSAFE_ prefixed versions" -author: "React Codemods" -license: "MIT" -workflow: "workflow.yaml" - - -targets: - languages: ["tsx"] - -keywords: ["transformation", "migration"] - -registry: - access: "public" - visibility: "public" - -capabilities: [] diff --git a/rename-unsafe-lifecycles/package.json b/rename-unsafe-lifecycles/package.json deleted file mode 100644 index c41c9a3..0000000 --- a/rename-unsafe-lifecycles/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@react-codemods/rename-unsafe-lifecycles", - "version": "0.1.0", - "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" - } -} diff --git a/rename-unsafe-lifecycles/pnpm-lock.yaml b/rename-unsafe-lifecycles/pnpm-lock.yaml deleted file mode 100644 index 8dfa743..0000000 --- a/rename-unsafe-lifecycles/pnpm-lock.yaml +++ /dev/null @@ -1,32 +0,0 @@ -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/rename-unsafe-lifecycles/scripts/codemod.ts b/rename-unsafe-lifecycles/scripts/codemod.ts deleted file mode 100644 index 98b727f..0000000 --- a/rename-unsafe-lifecycles/scripts/codemod.ts +++ /dev/null @@ -1,124 +0,0 @@ -import type { Transform, Edit } from "codemod:ast-grep"; -import type TSX from "codemod:ast-grep/langs/tsx"; -import { useMetricAtom } from "codemod:metrics"; - -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: root.filename(), - }, total); - } - - if (edits.length === 0) { - return null; - } - - return rootNode.commitEdits(edits); -}; - -export default transform; diff --git a/rename-unsafe-lifecycles/tests/arrow-functions/expected.tsx b/rename-unsafe-lifecycles/tests/arrow-functions/expected.tsx deleted file mode 100644 index 857671c..0000000 --- a/rename-unsafe-lifecycles/tests/arrow-functions/expected.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - UNSAFE_componentWillMount = () => { - console.log("mounting"); - }; - - render() { - return
    Hello
    ; - } -} - -export default MyComponent; diff --git a/rename-unsafe-lifecycles/tests/arrow-functions/input.tsx b/rename-unsafe-lifecycles/tests/arrow-functions/input.tsx deleted file mode 100644 index e8ade10..0000000 --- a/rename-unsafe-lifecycles/tests/arrow-functions/input.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - componentWillMount = () => { - console.log("mounting"); - }; - - render() { - return
    Hello
    ; - } -} - -export default MyComponent; diff --git a/rename-unsafe-lifecycles/tests/arrow-functions/metrics.json b/rename-unsafe-lifecycles/tests/arrow-functions/metrics.json deleted file mode 100644 index acc1a00..0000000 --- a/rename-unsafe-lifecycles/tests/arrow-functions/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "lifecycle-renames": [ - { - "cardinality": { - "file": "tests/arrow-functions/input.tsx", - "lifecycles": "componentWillMount" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/rename-unsafe-lifecycles/tests/class-methods/expected.tsx b/rename-unsafe-lifecycles/tests/class-methods/expected.tsx deleted file mode 100644 index 8481d72..0000000 --- a/rename-unsafe-lifecycles/tests/class-methods/expected.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - UNSAFE_componentWillMount() { - console.log("mounting"); - } - - UNSAFE_componentWillReceiveProps(nextProps) { - console.log("receiving props", nextProps); - } - - UNSAFE_componentWillUpdate(nextProps, nextState) { - console.log("updating", nextProps, nextState); - } - - componentDidMount() { - console.log("mounted"); - } - - render() { - return
    Hello
    ; - } -} - -export default MyComponent; diff --git a/rename-unsafe-lifecycles/tests/class-methods/input.tsx b/rename-unsafe-lifecycles/tests/class-methods/input.tsx deleted file mode 100644 index 89e873a..0000000 --- a/rename-unsafe-lifecycles/tests/class-methods/input.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - componentWillMount() { - console.log("mounting"); - } - - componentWillReceiveProps(nextProps) { - console.log("receiving props", nextProps); - } - - componentWillUpdate(nextProps, nextState) { - console.log("updating", nextProps, nextState); - } - - componentDidMount() { - console.log("mounted"); - } - - render() { - return
    Hello
    ; - } -} - -export default MyComponent; diff --git a/rename-unsafe-lifecycles/tests/class-methods/metrics.json b/rename-unsafe-lifecycles/tests/class-methods/metrics.json deleted file mode 100644 index 3c9ca7a..0000000 --- a/rename-unsafe-lifecycles/tests/class-methods/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "lifecycle-renames": [ - { - "cardinality": { - "file": "tests/class-methods/input.tsx", - "lifecycles": "componentWillMount,componentWillReceiveProps,componentWillUpdate" - }, - "count": 3 - } - ] -} \ No newline at end of file diff --git a/rename-unsafe-lifecycles/tests/componentWillUpdate-only/expected.tsx b/rename-unsafe-lifecycles/tests/componentWillUpdate-only/expected.tsx deleted file mode 100644 index 1afcf95..0000000 --- a/rename-unsafe-lifecycles/tests/componentWillUpdate-only/expected.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - UNSAFE_componentWillUpdate(nextProps: unknown, nextState: unknown) { - console.log("will update", nextProps, nextState); - } - - render() { - return
    Hello
    ; - } -} - -export default MyComponent; diff --git a/rename-unsafe-lifecycles/tests/componentWillUpdate-only/input.tsx b/rename-unsafe-lifecycles/tests/componentWillUpdate-only/input.tsx deleted file mode 100644 index 2a363ac..0000000 --- a/rename-unsafe-lifecycles/tests/componentWillUpdate-only/input.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - componentWillUpdate(nextProps: unknown, nextState: unknown) { - console.log("will update", nextProps, nextState); - } - - render() { - return
    Hello
    ; - } -} - -export default MyComponent; diff --git a/rename-unsafe-lifecycles/tests/componentWillUpdate-only/metrics.json b/rename-unsafe-lifecycles/tests/componentWillUpdate-only/metrics.json deleted file mode 100644 index e506b2a..0000000 --- a/rename-unsafe-lifecycles/tests/componentWillUpdate-only/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "lifecycle-renames": [ - { - "cardinality": { - "file": "tests/componentWillUpdate-only/input.tsx", - "lifecycles": "componentWillUpdate" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/rename-unsafe-lifecycles/tests/member-expression-call/expected.tsx b/rename-unsafe-lifecycles/tests/member-expression-call/expected.tsx deleted file mode 100644 index 25538a5..0000000 --- a/rename-unsafe-lifecycles/tests/member-expression-call/expected.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - UNSAFE_componentWillMount() { - console.log("mounting"); - } - - componentDidMount() { - this.UNSAFE_componentWillMount(); - } - - render() { - return
    Hello
    ; - } -} - -export default MyComponent; diff --git a/rename-unsafe-lifecycles/tests/member-expression-call/input.tsx b/rename-unsafe-lifecycles/tests/member-expression-call/input.tsx deleted file mode 100644 index 37b8883..0000000 --- a/rename-unsafe-lifecycles/tests/member-expression-call/input.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - componentWillMount() { - console.log("mounting"); - } - - componentDidMount() { - this.componentWillMount(); - } - - render() { - return
    Hello
    ; - } -} - -export default MyComponent; diff --git a/rename-unsafe-lifecycles/tests/member-expression-call/metrics.json b/rename-unsafe-lifecycles/tests/member-expression-call/metrics.json deleted file mode 100644 index 12f8955..0000000 --- a/rename-unsafe-lifecycles/tests/member-expression-call/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "lifecycle-renames": [ - { - "cardinality": { - "file": "tests/member-expression-call/input.tsx", - "lifecycles": "componentWillMount" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/rename-unsafe-lifecycles/tests/no-deprecated-lifecycles/expected.tsx b/rename-unsafe-lifecycles/tests/no-deprecated-lifecycles/expected.tsx deleted file mode 100644 index 129b901..0000000 --- a/rename-unsafe-lifecycles/tests/no-deprecated-lifecycles/expected.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - componentDidMount() { - console.log("mounted"); - } - - componentDidUpdate(prevProps: unknown) { - console.log("updated", prevProps); - } - - render() { - return
    Hello
    ; - } -} - -export default MyComponent; diff --git a/rename-unsafe-lifecycles/tests/no-deprecated-lifecycles/input.tsx b/rename-unsafe-lifecycles/tests/no-deprecated-lifecycles/input.tsx deleted file mode 100644 index 129b901..0000000 --- a/rename-unsafe-lifecycles/tests/no-deprecated-lifecycles/input.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - componentDidMount() { - console.log("mounted"); - } - - componentDidUpdate(prevProps: unknown) { - console.log("updated", prevProps); - } - - render() { - return
    Hello
    ; - } -} - -export default MyComponent; diff --git a/rename-unsafe-lifecycles/tests/object-properties/expected.tsx b/rename-unsafe-lifecycles/tests/object-properties/expected.tsx deleted file mode 100644 index 45d7dbf..0000000 --- a/rename-unsafe-lifecycles/tests/object-properties/expected.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import createReactClass from "create-react-class"; - -const MyComponent = createReactClass({ - UNSAFE_componentWillMount: function() { - console.log("mounting"); - }, - - UNSAFE_componentWillReceiveProps: function(nextProps) { - console.log("receiving props", nextProps); - }, - - render: function() { - return
    Hello
    ; - } -}); - -export default MyComponent; diff --git a/rename-unsafe-lifecycles/tests/object-properties/input.tsx b/rename-unsafe-lifecycles/tests/object-properties/input.tsx deleted file mode 100644 index 37ce039..0000000 --- a/rename-unsafe-lifecycles/tests/object-properties/input.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import createReactClass from "create-react-class"; - -const MyComponent = createReactClass({ - componentWillMount: function() { - console.log("mounting"); - }, - - componentWillReceiveProps: function(nextProps) { - console.log("receiving props", nextProps); - }, - - render: function() { - return
    Hello
    ; - } -}); - -export default MyComponent; diff --git a/rename-unsafe-lifecycles/tests/object-properties/metrics.json b/rename-unsafe-lifecycles/tests/object-properties/metrics.json deleted file mode 100644 index 74deee0..0000000 --- a/rename-unsafe-lifecycles/tests/object-properties/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "lifecycle-renames": [ - { - "cardinality": { - "file": "tests/object-properties/input.tsx", - "lifecycles": "componentWillMount,componentWillReceiveProps" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/rename-unsafe-lifecycles/tests/original-arrow-functions/expected.tsx b/rename-unsafe-lifecycles/tests/original-arrow-functions/expected.tsx deleted file mode 100644 index d87c71a..0000000 --- a/rename-unsafe-lifecycles/tests/original-arrow-functions/expected.tsx +++ /dev/null @@ -1,14 +0,0 @@ -const React = require('React'); - -class Component extends React.Component { - UNSAFE_componentWillMount = logger('componentWillMount'); - componentDidMount = logger('componentDidMount'); - UNSAFE_componentWillReceiveProps = logger('componentWillReceiveProps'); - shouldComponentUpdate = logger('shouldComponentUpdate'); - UNSAFE_componentWillUpdate = logger('componentWillUpdate'); - componentDidUpdate = logger('componentDidUpdate'); - componentWillUnmount = logger('componentWillUnmount'); - render() { - return null; - } -} diff --git a/rename-unsafe-lifecycles/tests/original-arrow-functions/input.tsx b/rename-unsafe-lifecycles/tests/original-arrow-functions/input.tsx deleted file mode 100644 index 7bf3094..0000000 --- a/rename-unsafe-lifecycles/tests/original-arrow-functions/input.tsx +++ /dev/null @@ -1,14 +0,0 @@ -const React = require('React'); - -class Component extends React.Component { - componentWillMount = logger('componentWillMount'); - componentDidMount = logger('componentDidMount'); - componentWillReceiveProps = logger('componentWillReceiveProps'); - shouldComponentUpdate = logger('shouldComponentUpdate'); - componentWillUpdate = logger('componentWillUpdate'); - componentDidUpdate = logger('componentDidUpdate'); - componentWillUnmount = logger('componentWillUnmount'); - render() { - return null; - } -} diff --git a/rename-unsafe-lifecycles/tests/original-arrow-functions/metrics.json b/rename-unsafe-lifecycles/tests/original-arrow-functions/metrics.json deleted file mode 100644 index 7cf8ed9..0000000 --- a/rename-unsafe-lifecycles/tests/original-arrow-functions/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "lifecycle-renames": [ - { - "cardinality": { - "file": "tests/original-arrow-functions/input.tsx", - "lifecycles": "componentWillMount,componentWillReceiveProps,componentWillUpdate" - }, - "count": 3 - } - ] -} \ No newline at end of file diff --git a/rename-unsafe-lifecycles/tests/original-create-react-class/expected.tsx b/rename-unsafe-lifecycles/tests/original-create-react-class/expected.tsx deleted file mode 100644 index 8949876..0000000 --- a/rename-unsafe-lifecycles/tests/original-create-react-class/expected.tsx +++ /dev/null @@ -1,48 +0,0 @@ -const createReactClass = require('create-react-class'); - -const MyComponent = createReactClass({ - displayName: 'MyComponent', - mixins: [ - { - UNSAFE_componentWillMount() { - // componentWillMount - }, - componentDidMount() { - // componentDidMount - }, - UNSAFE_componentWillUpdate(nextProps, nextState) { - // componentWillUpdate - }, - componentDidUpdate(prevProps, prevState) { - // componentDidUpdate - }, - UNSAFE_componentWillReceiveProps(nextProps) { - // componentWillReceiveProps - }, - componentWillUnmount() { - // componentWillUnmount - }, - }, - ], - UNSAFE_componentWillMount() { - // componentWillMount - }, - componentDidMount() { - // componentDidMount - }, - UNSAFE_componentWillUpdate(nextProps, nextState) { - // componentWillUpdate - }, - componentDidUpdate(prevProps, prevState) { - // componentDidUpdate - }, - UNSAFE_componentWillReceiveProps(nextProps) { - // componentWillReceiveProps - }, - componentWillUnmount() { - // componentWillUnmount - }, - render() { - // render - }, -}); diff --git a/rename-unsafe-lifecycles/tests/original-create-react-class/input.tsx b/rename-unsafe-lifecycles/tests/original-create-react-class/input.tsx deleted file mode 100644 index e1a661f..0000000 --- a/rename-unsafe-lifecycles/tests/original-create-react-class/input.tsx +++ /dev/null @@ -1,48 +0,0 @@ -const createReactClass = require('create-react-class'); - -const MyComponent = createReactClass({ - displayName: 'MyComponent', - mixins: [ - { - componentWillMount() { - // componentWillMount - }, - componentDidMount() { - // componentDidMount - }, - componentWillUpdate(nextProps, nextState) { - // componentWillUpdate - }, - componentDidUpdate(prevProps, prevState) { - // componentDidUpdate - }, - componentWillReceiveProps(nextProps) { - // componentWillReceiveProps - }, - componentWillUnmount() { - // componentWillUnmount - }, - }, - ], - componentWillMount() { - // componentWillMount - }, - componentDidMount() { - // componentDidMount - }, - componentWillUpdate(nextProps, nextState) { - // componentWillUpdate - }, - componentDidUpdate(prevProps, prevState) { - // componentDidUpdate - }, - componentWillReceiveProps(nextProps) { - // componentWillReceiveProps - }, - componentWillUnmount() { - // componentWillUnmount - }, - render() { - // render - }, -}); diff --git a/rename-unsafe-lifecycles/tests/original-create-react-class/metrics.json b/rename-unsafe-lifecycles/tests/original-create-react-class/metrics.json deleted file mode 100644 index c31d44c..0000000 --- a/rename-unsafe-lifecycles/tests/original-create-react-class/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "lifecycle-renames": [ - { - "cardinality": { - "file": "tests/original-create-react-class/input.tsx", - "lifecycles": "componentWillMount,componentWillReceiveProps,componentWillUpdate" - }, - "count": 6 - } - ] -} \ No newline at end of file diff --git a/rename-unsafe-lifecycles/tests/original-instance-methods/expected.tsx b/rename-unsafe-lifecycles/tests/original-instance-methods/expected.tsx deleted file mode 100644 index 78915c9..0000000 --- a/rename-unsafe-lifecycles/tests/original-instance-methods/expected.tsx +++ /dev/null @@ -1,25 +0,0 @@ -const React = require('React'); - -class ClassComponent extends React.Component { - UNSAFE_componentWillMount() { - // componentWillMount - } - componentDidMount() { - // componentDidMount - } - UNSAFE_componentWillUpdate(nextProps, nextState) { - // componentWillUpdate - } - componentDidUpdate(prevProps, prevState) { - // componentDidUpdate - } - UNSAFE_componentWillReceiveProps(nextProps) { - // componentWillReceiveProps - } - componentWillUnmount() { - // componentWillUnmount - } - render() { - // render - } -} diff --git a/rename-unsafe-lifecycles/tests/original-instance-methods/input.tsx b/rename-unsafe-lifecycles/tests/original-instance-methods/input.tsx deleted file mode 100644 index 28ae3de..0000000 --- a/rename-unsafe-lifecycles/tests/original-instance-methods/input.tsx +++ /dev/null @@ -1,25 +0,0 @@ -const React = require('React'); - -class ClassComponent extends React.Component { - componentWillMount() { - // componentWillMount - } - componentDidMount() { - // componentDidMount - } - componentWillUpdate(nextProps, nextState) { - // componentWillUpdate - } - componentDidUpdate(prevProps, prevState) { - // componentDidUpdate - } - componentWillReceiveProps(nextProps) { - // componentWillReceiveProps - } - componentWillUnmount() { - // componentWillUnmount - } - render() { - // render - } -} diff --git a/rename-unsafe-lifecycles/tests/original-instance-methods/metrics.json b/rename-unsafe-lifecycles/tests/original-instance-methods/metrics.json deleted file mode 100644 index 10cc361..0000000 --- a/rename-unsafe-lifecycles/tests/original-instance-methods/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "lifecycle-renames": [ - { - "cardinality": { - "file": "tests/original-instance-methods/input.tsx", - "lifecycles": "componentWillMount,componentWillReceiveProps,componentWillUpdate" - }, - "count": 3 - } - ] -} \ No newline at end of file diff --git a/rename-unsafe-lifecycles/tsconfig.json b/rename-unsafe-lifecycles/tsconfig.json deleted file mode 100644 index 469fc5a..0000000 --- a/rename-unsafe-lifecycles/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", - "types": ["@codemod.com/jssg-types"], - "allowImportingTsExtensions": true, - "noEmit": true, - "verbatimModuleSyntax": true, - "erasableSyntaxOnly": true, - "strict": true, - "strictNullChecks": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true - }, - "exclude": ["tests"] -} diff --git a/rename-unsafe-lifecycles/workflow.yaml b/rename-unsafe-lifecycles/workflow.yaml deleted file mode 100644 index 8773841..0000000 --- a/rename-unsafe-lifecycles/workflow.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# 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/replace-act-import/codemod.yaml b/replace-act-import/codemod.yaml deleted file mode 100644 index 77d19ec..0000000 --- a/replace-act-import/codemod.yaml +++ /dev/null @@ -1,19 +0,0 @@ -schema_version: "1.0" - -name: "@react-codemods/replace-act-import" -version: "0.1.0" -description: "Migrate act() from react-dom/test-utils to react" -author: "React Codemods" -license: "MIT" -workflow: "workflow.yaml" - -targets: - languages: ["tsx"] - -keywords: ["transformation", "migration", "react", "act"] - -registry: - access: "public" - visibility: "public" - -capabilities: [] diff --git a/replace-act-import/package.json b/replace-act-import/package.json deleted file mode 100644 index 5403fc2..0000000 --- a/replace-act-import/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@react-codemods/replace-act-import", - "version": "0.1.0", - "description": "Migrate act() from react-dom/test-utils to react", - "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" - }, - "dependencies": { - "@jssg/utils": "^0.0.2" - } -} diff --git a/replace-act-import/scripts/codemod.ts b/replace-act-import/scripts/codemod.ts deleted file mode 100644 index 46022ec..0000000 --- a/replace-act-import/scripts/codemod.ts +++ /dev/null @@ -1,160 +0,0 @@ -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, addImport, removeImport } from "@jssg/utils/javascript/imports"; - -const TEST_UTILS_MODULE = "react-dom/test-utils"; -const REACT_MODULE = "react"; - -type TestUtilsImport = { name: string; type: "default" | "namespace" }; - -function getTestUtilsImport(rootNode: SgNode): (TestUtilsImport & { importNode?: SgNode }) | null { - const defaultImp = getImport(rootNode, { type: "default", from: TEST_UTILS_MODULE }); - if (defaultImp) { - const importNode = defaultImp.node.ancestors().find((a) => a.kind() === "import_statement"); - return { name: defaultImp.alias, type: "default", importNode: importNode ?? undefined }; - } - - const importDecls = rootNode.findAll({ - rule: { - kind: "import_statement", - has: { - all: [ - { kind: "string", regex: "react-dom/test-utils" }, - { kind: "namespace_import" }, - ], - }, - }, - }); - - for (const imp of importDecls) { - const namespaceImport = imp.find({ rule: { kind: "namespace_import" } }); - if (namespaceImport) { - const ident = namespaceImport.field("name") ?? namespaceImport.find({ rule: { kind: "identifier" } }); - if (ident) return { name: ident.text(), type: "namespace", importNode: imp }; - } - } - return null; -} - -const transform: Transform = async (root) => { - const rootNode = root.root(); - const edits: Edit[] = []; - const metric = useMetricAtom("replace-act-import-migrations"); - - const testUtilsImport = getTestUtilsImport(rootNode); - const actNamedImport = getImport(rootNode, { type: "named", name: "act", from: TEST_UTILS_MODULE }); - - if (testUtilsImport) { - const actMemberCalls = rootNode.findAll({ - rule: { - kind: "member_expression", - has: { - kind: "property_identifier", - regex: "^act$", - }, - }, - }); - - if (actMemberCalls.length > 0) { - const reactDefault = getImport(rootNode, { type: "default", from: REACT_MODULE }); - const reactNamespace = rootNode.find({ - rule: { - kind: "import_statement", - has: { - all: [ - { kind: "string", regex: "^react$" }, - { kind: "namespace_import" }, - ], - }, - }, - }); - const nsIdent = reactNamespace?.find({ rule: { kind: "namespace_import" } })?.field("name"); - const reactName = reactDefault?.alias ?? nsIdent?.text() ?? "React"; - - if (testUtilsImport.importNode) { - if (reactDefault || reactNamespace) { - const removeEdit = removeImport(rootNode, { - type: testUtilsImport.type, - from: TEST_UTILS_MODULE, - }); - if (removeEdit) edits.push(removeEdit); - } else { - const newImportText = - testUtilsImport.type === "namespace" - ? 'import * as React from "react";' - : 'import React from "react";'; - edits.push(testUtilsImport.importNode.replace(newImportText)); - } - } - - for (const member of actMemberCalls) { - const objNode = member.field("object"); - if (objNode && objNode.text() === testUtilsImport.name) { - edits.push(objNode.replace(reactName)); - metric.increment({ file: root.filename(), pattern: "member-call" }); - } - } - } - } - - if (actNamedImport && !actNamedImport.isNamespace) { - const addEdit = addImport(rootNode, { - type: "named", - specifiers: [{ name: "act", alias: actNamedImport.alias }], - from: REACT_MODULE, - }); - if (addEdit) edits.push(addEdit); - - const removeEdit = removeImport(rootNode, { - type: "named", - specifiers: ["act"], - from: TEST_UTILS_MODULE, - }); - if (removeEdit) edits.push(removeEdit); - metric.increment({ file: root.filename(), pattern: "named-import" }); - } - - const exportDecls = rootNode.findAll({ - rule: { - kind: "export_statement", - has: { - kind: "string", - regex: "react-dom/test-utils", - }, - }, - }); - - for (const exp of exportDecls) { - const hasExportAll = - exp.has({ rule: { kind: "namespace_export" } }) || - exp.children().some((c) => c.text() === "*"); - const hasNamedAct = exp.find({ - rule: { - kind: "export_specifier", - has: { kind: "identifier", regex: "^act$" }, - }, - }); - - if (hasNamedAct) { - const stringNode = exp.find({ rule: { kind: "string", regex: "react-dom/test-utils" } }); - if (stringNode) { - edits.push(stringNode.replace('"react"')); - metric.increment({ file: root.filename(), pattern: "re-export" }); - } - } else if (hasExportAll) { - const insertPos = exp.range().end.index; - edits.push({ - startPos: insertPos, - endPos: insertPos, - insertedText: "\nexport { act } from \"react\";", - }); - metric.increment({ file: root.filename(), pattern: "re-export" }); - } - } - - if (edits.length === 0) return null; - return rootNode.commitEdits(edits); -}; - -export default transform; diff --git a/replace-act-import/tests/act-in-async/expected.tsx b/replace-act-import/tests/act-in-async/expected.tsx deleted file mode 100644 index b1d1f91..0000000 --- a/replace-act-import/tests/act-in-async/expected.tsx +++ /dev/null @@ -1,6 +0,0 @@ - -it("handles async", async () => { - await act(async () => { - await doSomething(); - }); -}); diff --git a/replace-act-import/tests/act-in-async/input.tsx b/replace-act-import/tests/act-in-async/input.tsx deleted file mode 100644 index 2d255d1..0000000 --- a/replace-act-import/tests/act-in-async/input.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { act } from "react-dom/test-utils"; - -it("handles async", async () => { - await act(async () => { - await doSomething(); - }); -}); diff --git a/replace-act-import/tests/act-in-async/metrics.json b/replace-act-import/tests/act-in-async/metrics.json deleted file mode 100644 index 2680f9c..0000000 --- a/replace-act-import/tests/act-in-async/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "replace-act-import-migrations": [ - { - "cardinality": { - "file": "tests/act-in-async/input.tsx", - "pattern": "named-import" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/replace-act-import/tests/export-named-act/expected.tsx b/replace-act-import/tests/export-named-act/expected.tsx deleted file mode 100644 index ce8f78b..0000000 --- a/replace-act-import/tests/export-named-act/expected.tsx +++ /dev/null @@ -1 +0,0 @@ -export { act } from "react"; diff --git a/replace-act-import/tests/export-named-act/metrics.json b/replace-act-import/tests/export-named-act/metrics.json deleted file mode 100644 index d087f81..0000000 --- a/replace-act-import/tests/export-named-act/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "replace-act-import-migrations": [ - { - "cardinality": { - "file": "tests/export-named-act/input.tsx", - "pattern": "re-export" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/replace-act-import/tests/multiple-act-calls/expected.tsx b/replace-act-import/tests/multiple-act-calls/expected.tsx deleted file mode 100644 index 00fb549..0000000 --- a/replace-act-import/tests/multiple-act-calls/expected.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; - -test("example", async () => { - React.act(() => { - render(); - }); - React.act(() => { - fireEvent.click(button); - }); -}); diff --git a/replace-act-import/tests/multiple-act-calls/input.tsx b/replace-act-import/tests/multiple-act-calls/input.tsx deleted file mode 100644 index ad3e95e..0000000 --- a/replace-act-import/tests/multiple-act-calls/input.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import ReactTestUtils from "react-dom/test-utils"; - -test("example", async () => { - ReactTestUtils.act(() => { - render(); - }); - ReactTestUtils.act(() => { - fireEvent.click(button); - }); -}); diff --git a/replace-act-import/tests/multiple-act-calls/metrics.json b/replace-act-import/tests/multiple-act-calls/metrics.json deleted file mode 100644 index 6de8d82..0000000 --- a/replace-act-import/tests/multiple-act-calls/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "replace-act-import-migrations": [ - { - "cardinality": { - "file": "tests/multiple-act-calls/input.tsx", - "pattern": "member-call" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/replace-act-import/tests/named-import-2/expected.tsx b/replace-act-import/tests/named-import-2/expected.tsx deleted file mode 100644 index 75b5c0c..0000000 --- a/replace-act-import/tests/named-import-2/expected.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { FC , act} from "react"; - -act(); diff --git a/replace-act-import/tests/named-import/expected.tsx b/replace-act-import/tests/named-import/expected.tsx deleted file mode 100644 index a49755f..0000000 --- a/replace-act-import/tests/named-import/expected.tsx +++ /dev/null @@ -1 +0,0 @@ -act(); diff --git a/replace-act-import/tests/react-testing-library-style/expected.tsx b/replace-act-import/tests/react-testing-library-style/expected.tsx deleted file mode 100644 index ff0e5d5..0000000 --- a/replace-act-import/tests/react-testing-library-style/expected.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from "react"; -import { renderHook } from "@testing-library/react"; -import { useCounter } from "../useCounter"; - -describe("useCounter", () => { - it("increments when dispatch is called", async () => { - const { result } = renderHook(() => useCounter(0)); - - await React.act(() => { - result.current.increment(); - }); - - expect(result.current.count).toBe(1); - }); -}); diff --git a/replace-act-import/tests/react-testing-library-style/input.tsx b/replace-act-import/tests/react-testing-library-style/input.tsx deleted file mode 100644 index 941b6c7..0000000 --- a/replace-act-import/tests/react-testing-library-style/input.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import * as ReactTestUtils from "react-dom/test-utils"; -import { renderHook } from "@testing-library/react"; -import { useCounter } from "../useCounter"; - -describe("useCounter", () => { - it("increments when dispatch is called", async () => { - const { result } = renderHook(() => useCounter(0)); - - await ReactTestUtils.act(() => { - result.current.increment(); - }); - - expect(result.current.count).toBe(1); - }); -}); diff --git a/replace-act-import/tests/react-testing-library-style/metrics.json b/replace-act-import/tests/react-testing-library-style/metrics.json deleted file mode 100644 index 0a1b02a..0000000 --- a/replace-act-import/tests/react-testing-library-style/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "replace-act-import-migrations": [ - { - "cardinality": { - "file": "tests/react-testing-library-style/input.tsx", - "pattern": "member-call" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/replace-act-import/tests/real-world-test-file/expected.tsx b/replace-act-import/tests/real-world-test-file/expected.tsx deleted file mode 100644 index 9899718..0000000 --- a/replace-act-import/tests/real-world-test-file/expected.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import { render, screen, fireEvent } from "@testing-library/react"; -import { UserProfile } from "./UserProfile"; - -describe("UserProfile", () => { - it("updates name when edited", async () => { - render(); - - const input = screen.getByRole("textbox", { name: /name/i }); - fireEvent.change(input, { target: { value: "Jane Doe" } }); - - await React.act(async () => { - fireEvent.click(screen.getByRole("button", { name: /save/i })); - }); - - expect(screen.getByText("Jane Doe")).toBeInTheDocument(); - }); - - it("shows loading state during save", async () => { - render(); - - await React.act(async () => { - fireEvent.click(screen.getByRole("button", { name: /save/i })); - }); - - expect(screen.getByText(/saving/i)).toHaveBeenVisible(); - }); -}); diff --git a/replace-act-import/tests/real-world-test-file/input.tsx b/replace-act-import/tests/real-world-test-file/input.tsx deleted file mode 100644 index e383c96..0000000 --- a/replace-act-import/tests/real-world-test-file/input.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; -import { render, screen, fireEvent } from "@testing-library/react"; -import ReactTestUtils from "react-dom/test-utils"; -import { UserProfile } from "./UserProfile"; - -describe("UserProfile", () => { - it("updates name when edited", async () => { - render(); - - const input = screen.getByRole("textbox", { name: /name/i }); - fireEvent.change(input, { target: { value: "Jane Doe" } }); - - await ReactTestUtils.act(async () => { - fireEvent.click(screen.getByRole("button", { name: /save/i })); - }); - - expect(screen.getByText("Jane Doe")).toBeInTheDocument(); - }); - - it("shows loading state during save", async () => { - render(); - - await ReactTestUtils.act(async () => { - fireEvent.click(screen.getByRole("button", { name: /save/i })); - }); - - expect(screen.getByText(/saving/i)).toHaveBeenVisible(); - }); -}); diff --git a/replace-act-import/tests/real-world-test-file/metrics.json b/replace-act-import/tests/real-world-test-file/metrics.json deleted file mode 100644 index 566bf4d..0000000 --- a/replace-act-import/tests/real-world-test-file/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "replace-act-import-migrations": [ - { - "cardinality": { - "file": "tests/real-world-test-file/input.tsx", - "pattern": "member-call" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/replace-act-import/tests/wildcard-import-2/expected.tsx b/replace-act-import/tests/wildcard-import-2/expected.tsx deleted file mode 100644 index 75678ea..0000000 --- a/replace-act-import/tests/wildcard-import-2/expected.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import * as React from "react"; -import * as ReactTestUtils from "react-dom/test-utils"; - -React.act(); diff --git a/replace-act-import/tests/wildcard-import/expected.tsx b/replace-act-import/tests/wildcard-import/expected.tsx deleted file mode 100644 index 3e51ec4..0000000 --- a/replace-act-import/tests/wildcard-import/expected.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import React from "react"; -React.act(); diff --git a/replace-act-import/tsconfig.json b/replace-act-import/tsconfig.json deleted file mode 100644 index 469fc5a..0000000 --- a/replace-act-import/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", - "types": ["@codemod.com/jssg-types"], - "allowImportingTsExtensions": true, - "noEmit": true, - "verbatimModuleSyntax": true, - "erasableSyntaxOnly": true, - "strict": true, - "strictNullChecks": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true - }, - "exclude": ["tests"] -} diff --git a/replace-act-import/workflow.yaml b/replace-act-import/workflow.yaml deleted file mode 100644 index 363f8cc..0000000 --- a/replace-act-import/workflow.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# 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: "Migrate act from react-dom/test-utils to react" - js-ast-grep: - js_file: scripts/codemod.ts - language: "tsx" diff --git a/replace-reactdom-render/README.md b/replace-reactdom-render/README.md deleted file mode 100644 index cfcf5c3..0000000 --- a/replace-reactdom-render/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# @react-codemods/replace-reactdom-render - -Replace ReactDOM.render with createRoot for React 18 - -## Installation - -```bash -# Install from registry -codemod run @react-codemods/replace-reactdom-render - -# Or run locally -codemod run -w workflow.yaml -``` - -## Usage - -This codemod transforms tsx code by: - -- Converting `var` declarations to `const`/`let` -- Removing debug statements -- Modernizing syntax patterns - -## Development - -```bash -# Test the transformation -npm test - -# Validate the workflow -codemod validate -w workflow.yaml - -# Publish to registry -codemod login -codemod publish -``` - -## License -MIT diff --git a/replace-reactdom-render/codemod.yaml b/replace-reactdom-render/codemod.yaml deleted file mode 100644 index f766db4..0000000 --- a/replace-reactdom-render/codemod.yaml +++ /dev/null @@ -1,20 +0,0 @@ -schema_version: "1.0" - -name: "@react-codemods/replace-reactdom-render" -version: "0.1.0" -description: "Replace ReactDOM.render with createRoot for React 18" -author: "React Codemods" -license: "MIT" -workflow: "workflow.yaml" - - -targets: - languages: ["tsx"] - -keywords: ["transformation", "migration"] - -registry: - access: "public" - visibility: "public" - -capabilities: [] diff --git a/replace-reactdom-render/package.json b/replace-reactdom-render/package.json deleted file mode 100644 index 2fb19f2..0000000 --- a/replace-reactdom-render/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@react-codemods/replace-reactdom-render", - "version": "0.1.0", - "description": "Replace ReactDOM.render with createRoot for React 18", - "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" - }, - "dependencies": { - "@jssg/utils": "^0.0.2" - } -} diff --git a/replace-reactdom-render/scripts/codemod.ts b/replace-reactdom-render/scripts/codemod.ts deleted file mode 100644 index 2dddbf0..0000000 --- a/replace-reactdom-render/scripts/codemod.ts +++ /dev/null @@ -1,171 +0,0 @@ -import type { Transform, Edit } from "codemod:ast-grep"; -import type TSX from "codemod:ast-grep/langs/tsx"; -import { useMetricAtom } from "codemod:metrics"; -import { getImport, addImport, removeImport } from "@jssg/utils/javascript/imports"; - - -const transform: Transform = async (root) => { - const rootNode = root.root(); - const edits: Edit[] = []; - - const transformMetric = useMetricAtom("reactdom-render-replacements"); - - const reactDomDefaultImport = getImport(rootNode, { type: "default", from: "react-dom" }); - const reactDomRenderImport = getImport(rootNode, { type: "named", name: "render", from: "react-dom" }); - - if (!reactDomDefaultImport && !reactDomRenderImport) { - return null; - } - - const reactDomName = reactDomDefaultImport?.alias || "ReactDOM"; - const renderName = reactDomRenderImport?.alias || "render"; - - let hasTransformations = false; - let rootIndex = 0; - - const nextRootName = (): string => { - const name = rootIndex === 0 ? "root" : `root${rootIndex}`; - rootIndex += 1; - return name; - }; - - if (reactDomDefaultImport) { - const memberRenderCalls = rootNode.findAll({ - rule: { - kind: "call_expression", - has: { - field: "function", - kind: "member_expression", - all: [ - { - has: { - field: "object", - kind: "identifier", - regex: `^${reactDomName}$`, - }, - }, - { - has: { - field: "property", - kind: "property_identifier", - regex: "^render$", - }, - }, - ], - }, - }, - }); - - for (const call of memberRenderCalls) { - const args = call.field("arguments"); - if (!args) continue; - - const argList = args.children().filter((child) => - child.kind() !== "(" && child.kind() !== ")" && child.kind() !== "," - ); - - if (argList.length >= 2) { - const element = argList[0]; - const container = argList[1]; - - const statement = call.ancestors().find((ancestor) => - ancestor.kind() === "expression_statement" - ); - - if (statement) { - const rootName = nextRootName(); - const sourceCode = rootNode.text(); - const stmtStart = statement.range().start.index; - let indent = ""; - for (let i = stmtStart - 1; i >= 0; i--) { - const char = sourceCode[i]; - if (char === '\n') break; - if (char === ' ' || char === '\t') { - indent = char + indent; - } else { - indent = ""; - } - } - const replacement = `${indent}const ${rootName} = createRoot(${container.text()});\n${indent}${rootName}.render(${element.text()});`; - edits.push(statement.replace(replacement)); - hasTransformations = true; - transformMetric.increment({ - pattern: "ReactDOM.render", - file: root.filename(), - }); - } - } - } - } - - if (reactDomRenderImport) { - const renderCalls = rootNode.findAll({ - rule: { - kind: "call_expression", - has: { - field: "function", - kind: "identifier", - regex: `^${renderName}$`, - }, - }, - }); - - for (const call of renderCalls) { - const args = call.field("arguments"); - if (!args) continue; - - const argList = args.children().filter((child) => - child.kind() !== "(" && child.kind() !== ")" && child.kind() !== "," - ); - - if (argList.length >= 2) { - const element = argList[0]; - const container = argList[1]; - - const statement = call.ancestors().find((ancestor) => - ancestor.kind() === "expression_statement" - ); - - if (statement) { - const rootName = nextRootName(); - const sourceCode = rootNode.text(); - const stmtStart = statement.range().start.index; - let indent = ""; - for (let i = stmtStart - 1; i >= 0; i--) { - const char = sourceCode[i]; - if (char === '\n') break; - if (char === ' ' || char === '\t') { - indent = char + indent; - } else { - indent = ""; - } - } - const replacement = `${indent}const ${rootName} = createRoot(${container.text()});\n${indent}${rootName}.render(${element.text()});`; - edits.push(statement.replace(replacement)); - hasTransformations = true; - transformMetric.increment({ - pattern: "render", - file: root.filename(), - }); - } - } - } - } - - if (!hasTransformations) { - return null; - } - - const addEdit = addImport(rootNode, { - type: "named", - specifiers: [{ name: "createRoot" }], - from: "react-dom/client", - }); - if (addEdit) { - edits.push(addEdit); - } - - return rootNode.commitEdits(edits); -}; - -export default transform; diff --git a/replace-reactdom-render/tests/multiple-render-calls/expected.tsx b/replace-reactdom-render/tests/multiple-render-calls/expected.tsx deleted file mode 100644 index 427d49a..0000000 --- a/replace-reactdom-render/tests/multiple-render-calls/expected.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import ReactDOM from "react-dom"; -import App from "./App"; -import AdminApp from "./AdminApp";import { createRoot } from 'react-dom/client'; - - -const root = createRoot(document.getElementById("root")); -root.render(); -const root1 = createRoot(document.getElementById("admin-root")); -root1.render(); diff --git a/replace-reactdom-render/tests/multiple-render-calls/input.tsx b/replace-reactdom-render/tests/multiple-render-calls/input.tsx deleted file mode 100644 index 5c0ad19..0000000 --- a/replace-reactdom-render/tests/multiple-render-calls/input.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import ReactDOM from "react-dom"; -import App from "./App"; -import AdminApp from "./AdminApp"; - -ReactDOM.render(, document.getElementById("root")); -ReactDOM.render(, document.getElementById("admin-root")); diff --git a/replace-reactdom-render/tests/multiple-render-calls/metrics.json b/replace-reactdom-render/tests/multiple-render-calls/metrics.json deleted file mode 100644 index 3a36166..0000000 --- a/replace-reactdom-render/tests/multiple-render-calls/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "reactdom-render-replacements": [ - { - "cardinality": { - "file": "tests/multiple-render-calls/input.tsx", - "pattern": "ReactDOM.render" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/replace-reactdom-render/tests/named-import/expected.tsx b/replace-reactdom-render/tests/named-import/expected.tsx deleted file mode 100644 index c1fe685..0000000 --- a/replace-reactdom-render/tests/named-import/expected.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { render } from "react-dom"; -import App from "./App";import { createRoot } from 'react-dom/client'; - - -const root = createRoot(document.getElementById("root")); -root.render(); diff --git a/replace-reactdom-render/tests/named-import/input.tsx b/replace-reactdom-render/tests/named-import/input.tsx deleted file mode 100644 index 6c1cee3..0000000 --- a/replace-reactdom-render/tests/named-import/input.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import { render } from "react-dom"; -import App from "./App"; - -render(, document.getElementById("root")); diff --git a/replace-reactdom-render/tests/named-import/metrics.json b/replace-reactdom-render/tests/named-import/metrics.json deleted file mode 100644 index 49282a5..0000000 --- a/replace-reactdom-render/tests/named-import/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "reactdom-render-replacements": [ - { - "cardinality": { - "file": "tests/named-import/input.tsx", - "pattern": "render" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/replace-reactdom-render/tests/no-reactdom-import/expected.tsx b/replace-reactdom-render/tests/no-reactdom-import/expected.tsx deleted file mode 100644 index d188567..0000000 --- a/replace-reactdom-render/tests/no-reactdom-import/expected.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import App from "./App"; - -// Already using createRoot - no react-dom render -const root = document.getElementById("root"); -if (root) { - console.log("Mount app to root"); -} - -export {}; diff --git a/replace-reactdom-render/tests/no-reactdom-import/input.tsx b/replace-reactdom-render/tests/no-reactdom-import/input.tsx deleted file mode 100644 index d188567..0000000 --- a/replace-reactdom-render/tests/no-reactdom-import/input.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import App from "./App"; - -// Already using createRoot - no react-dom render -const root = document.getElementById("root"); -if (root) { - console.log("Mount app to root"); -} - -export {}; diff --git a/replace-reactdom-render/tests/original-default/expected.tsx b/replace-reactdom-render/tests/original-default/expected.tsx deleted file mode 100644 index cb093d3..0000000 --- a/replace-reactdom-render/tests/original-default/expected.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import ReactDom from "react-dom"; -import Component from "Component";import { createRoot } from 'react-dom/client'; - - -const root = createRoot(document.getElementById("app")); -root.render(); diff --git a/replace-reactdom-render/tests/original-default/metrics.json b/replace-reactdom-render/tests/original-default/metrics.json deleted file mode 100644 index 77ef1b0..0000000 --- a/replace-reactdom-render/tests/original-default/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "reactdom-render-replacements": [ - { - "cardinality": { - "file": "tests/original-default/input.tsx", - "pattern": "ReactDOM.render" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/replace-reactdom-render/tests/original-nested/expected.tsx b/replace-reactdom-render/tests/original-nested/expected.tsx deleted file mode 100644 index fc06116..0000000 --- a/replace-reactdom-render/tests/original-nested/expected.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { render } from "react-dom";import { createRoot } from 'react-dom/client'; - - -const fn = () => { - if (true) { - const root = createRoot(theNode); - root.render(); - } -}; diff --git a/replace-reactdom-render/tests/original-nested/metrics.json b/replace-reactdom-render/tests/original-nested/metrics.json deleted file mode 100644 index 44a8e83..0000000 --- a/replace-reactdom-render/tests/original-nested/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "reactdom-render-replacements": [ - { - "cardinality": { - "file": "tests/original-nested/input.tsx", - "pattern": "render" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/replace-reactdom-render/tests/reactdom-member/expected.tsx b/replace-reactdom-render/tests/reactdom-member/expected.tsx deleted file mode 100644 index e02df57..0000000 --- a/replace-reactdom-render/tests/reactdom-member/expected.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import ReactDOM from "react-dom"; -import App from "./App";import { createRoot } from 'react-dom/client'; - - -const root = createRoot(document.getElementById("root")); -root.render(); diff --git a/replace-reactdom-render/tests/reactdom-member/input.tsx b/replace-reactdom-render/tests/reactdom-member/input.tsx deleted file mode 100644 index ce92b1c..0000000 --- a/replace-reactdom-render/tests/reactdom-member/input.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import ReactDOM from "react-dom"; -import App from "./App"; - -ReactDOM.render(, document.getElementById("root")); diff --git a/replace-reactdom-render/tests/reactdom-member/metrics.json b/replace-reactdom-render/tests/reactdom-member/metrics.json deleted file mode 100644 index 2dc28f7..0000000 --- a/replace-reactdom-render/tests/reactdom-member/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "reactdom-render-replacements": [ - { - "cardinality": { - "file": "tests/reactdom-member/input.tsx", - "pattern": "ReactDOM.render" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/replace-reactdom-render/tsconfig.json b/replace-reactdom-render/tsconfig.json deleted file mode 100644 index 469fc5a..0000000 --- a/replace-reactdom-render/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", - "types": ["@codemod.com/jssg-types"], - "allowImportingTsExtensions": true, - "noEmit": true, - "verbatimModuleSyntax": true, - "erasableSyntaxOnly": true, - "strict": true, - "strictNullChecks": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true - }, - "exclude": ["tests"] -} diff --git a/replace-reactdom-render/workflow.yaml b/replace-reactdom-render/workflow.yaml deleted file mode 100644 index 8773841..0000000 --- a/replace-reactdom-render/workflow.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# 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/replace-string-ref/README.md b/replace-string-ref/README.md deleted file mode 100644 index 92f9593..0000000 --- a/replace-string-ref/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# @react-codemods/replace-string-ref - -Replace string refs with callback refs in React class components - -## Installation - -```bash -# Install from registry -codemod run @react-codemods/replace-string-ref - -# Or run locally -codemod run -w workflow.yaml -``` - -## Usage - -This codemod transforms tsx code by: - -- Converting `var` declarations to `const`/`let` -- Removing debug statements -- Modernizing syntax patterns - -## Development - -```bash -# Test the transformation -npm test - -# Validate the workflow -codemod validate -w workflow.yaml - -# Publish to registry -codemod login -codemod publish -``` - -## License -MIT diff --git a/replace-string-ref/codemod.yaml b/replace-string-ref/codemod.yaml deleted file mode 100644 index 1b89cdc..0000000 --- a/replace-string-ref/codemod.yaml +++ /dev/null @@ -1,20 +0,0 @@ -schema_version: "1.0" - -name: "@react-codemods/replace-string-ref" -version: "0.1.0" -description: "Replace string refs with callback refs in React class components" -author: "React Codemods" -license: "MIT" -workflow: "workflow.yaml" - - -targets: - languages: ["tsx"] - -keywords: ["transformation", "migration"] - -registry: - access: "public" - visibility: "public" - -capabilities: [] diff --git a/replace-string-ref/package.json b/replace-string-ref/package.json deleted file mode 100644 index 015cc8d..0000000 --- a/replace-string-ref/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@react-codemods/replace-string-ref", - "version": "0.1.0", - "description": "Replace string refs with callback refs in React class components", - "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" - }, - "dependencies": { - "@jssg/utils": "^0.0.2" - } -} diff --git a/replace-string-ref/scripts/codemod.ts b/replace-string-ref/scripts/codemod.ts deleted file mode 100644 index 260ab64..0000000 --- a/replace-string-ref/scripts/codemod.ts +++ /dev/null @@ -1,120 +0,0 @@ -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 isInsideReactClassComponent( - node: SgNode, - reactName: string, - componentNamesList: string[], -): boolean { - const classDecl = node.ancestors().find((a) => a.kind() === "class_declaration"); - if (!classDecl) return false; - - const heritage = classDecl.children().find((c) => c.kind() === "class_heritage"); - if (!heritage) return false; - - // Check for React.Component or React.PureComponent - const memberExpressions = heritage.findAll({ - rule: { kind: "member_expression" }, - }); - - for (const memberExpr of memberExpressions) { - const object = memberExpr.field("object"); - const property = memberExpr.field("property"); - - if (object && property) { - const objectText = object.text(); - const propertyText = property.text(); - - if (objectText === reactName && (propertyText === "Component" || propertyText === "PureComponent")) { - return true; - } - } - } - - // Check for named imports like Component or PureComponent - if (componentNamesList.length > 0) { - const identifiers = heritage.findAll({ - rule: { kind: "identifier" }, - }); - - for (const id of identifiers) { - if (componentNamesList.includes(id.text())) { - return true; - } - } - } - - return false; -} - -const transform: Transform = async (root) => { - const rootNode = root.root(); - const edits: Edit[] = []; - - const transformMetric = useMetricAtom("string-ref-replacements"); - const refNames: string[] = []; - - const reactDefault = getImport(rootNode, { type: "default", from: "react" }); - const componentImport = getImport(rootNode, { type: "named", name: "Component", from: "react" }); - const pureImport = getImport(rootNode, { type: "named", name: "PureComponent", from: "react" }); - const reactName = reactDefault?.alias ?? "React"; - const componentNames = new Set(); - if (componentImport) componentNames.add(componentImport.alias); - if (pureImport) componentNames.add(pureImport.alias); - const componentNamesList = Array.from(componentNames); - - const stringRefs = rootNode.findAll({ - rule: { - kind: "jsx_attribute", - all: [ - { has: { kind: "property_identifier", regex: "^ref$" } }, - { has: { kind: "string" } }, - ], - }, - }); - - for (const refAttr of stringRefs) { - if (!isInsideReactClassComponent(refAttr, reactName, componentNamesList)) continue; - const stringValue = refAttr.find({ - rule: { - kind: "string", - }, - }); - - if (!stringValue) continue; - - const stringFragment = stringValue.find({ - rule: { - kind: "string_fragment", - }, - }); - - if (!stringFragment) continue; - - const refName = stringFragment.text(); - - const callbackRef = `ref={(ref) => { - this.refs.${refName} = ref; - }}`; - - edits.push(refAttr.replace(callbackRef)); - refNames.push(refName); - } - - if (refNames.length > 0) { - transformMetric.increment({ - refs: refNames.sort().join(","), - file: root.filename(), - }, refNames.length); - } - - if (edits.length === 0) { - return null; - } - - return rootNode.commitEdits(edits); -}; - -export default transform; diff --git a/replace-string-ref/tests/basic-string-ref/expected.tsx b/replace-string-ref/tests/basic-string-ref/expected.tsx deleted file mode 100644 index 73aef79..0000000 --- a/replace-string-ref/tests/basic-string-ref/expected.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - render() { - return { - this.refs.myInput = ref; - }} />; - } -} - -export default MyComponent; diff --git a/replace-string-ref/tests/basic-string-ref/input.tsx b/replace-string-ref/tests/basic-string-ref/input.tsx deleted file mode 100644 index ad73be3..0000000 --- a/replace-string-ref/tests/basic-string-ref/input.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - render() { - return ; - } -} - -export default MyComponent; diff --git a/replace-string-ref/tests/basic-string-ref/metrics.json b/replace-string-ref/tests/basic-string-ref/metrics.json deleted file mode 100644 index 82c3ed9..0000000 --- a/replace-string-ref/tests/basic-string-ref/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "string-ref-replacements": [ - { - "cardinality": { - "file": "tests/basic-string-ref/input.tsx", - "refs": "myInput" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/replace-string-ref/tests/callback-ref-no-op/expected.tsx b/replace-string-ref/tests/callback-ref-no-op/expected.tsx deleted file mode 100644 index f6a4118..0000000 --- a/replace-string-ref/tests/callback-ref-no-op/expected.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - inputRef = React.createRef(); - - render() { - return ; - } -} - -export default MyComponent; diff --git a/replace-string-ref/tests/callback-ref-no-op/input.tsx b/replace-string-ref/tests/callback-ref-no-op/input.tsx deleted file mode 100644 index f6a4118..0000000 --- a/replace-string-ref/tests/callback-ref-no-op/input.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - inputRef = React.createRef(); - - render() { - return ; - } -} - -export default MyComponent; diff --git a/replace-string-ref/tests/expression-ref-no-op/expected.tsx b/replace-string-ref/tests/expression-ref-no-op/expected.tsx deleted file mode 100644 index 1a52b25..0000000 --- a/replace-string-ref/tests/expression-ref-no-op/expected.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - render() { - const refName = "myInput"; - return ; - } -} - -export default MyComponent; diff --git a/replace-string-ref/tests/expression-ref-no-op/input.tsx b/replace-string-ref/tests/expression-ref-no-op/input.tsx deleted file mode 100644 index 1a52b25..0000000 --- a/replace-string-ref/tests/expression-ref-no-op/input.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - render() { - const refName = "myInput"; - return ; - } -} - -export default MyComponent; diff --git a/replace-string-ref/tests/multiple-refs/expected.tsx b/replace-string-ref/tests/multiple-refs/expected.tsx deleted file mode 100644 index 0e27c65..0000000 --- a/replace-string-ref/tests/multiple-refs/expected.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; - -class MyComponent extends React.Component { - render() { - return ( -
    - { - this.refs.input1 = ref; - }} /> - { - this.refs.input2 = ref; - }} /> - -
    - ); - } -} - -export default MyComponent; diff --git a/replace-string-ref/tests/multiple-refs/input.tsx b/replace-string-ref/tests/multiple-refs/input.tsx deleted file mode 100644 index 7a7c605..0000000 --- a/replace-string-ref/tests/multiple-refs/input.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from "react"; - -class MyComponent extends React.Component { - render() { - return ( -
    - - - -
    - ); - } -} - -export default MyComponent; diff --git a/replace-string-ref/tests/multiple-refs/metrics.json b/replace-string-ref/tests/multiple-refs/metrics.json deleted file mode 100644 index 7b2b9a6..0000000 --- a/replace-string-ref/tests/multiple-refs/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "string-ref-replacements": [ - { - "cardinality": { - "file": "tests/multiple-refs/input.tsx", - "refs": "input1,input2,submitBtn" - }, - "count": 3 - } - ] -} \ No newline at end of file diff --git a/replace-string-ref/tests/original-class-component-custom-import-names/expected.tsx b/replace-string-ref/tests/original-class-component-custom-import-names/expected.tsx deleted file mode 100644 index 9b5faed..0000000 --- a/replace-string-ref/tests/original-class-component-custom-import-names/expected.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React1, { PureComponent as PureComponent1 } from "react"; - -class C extends React1.Component { - render() { - return
    { - this.refs.refName = ref; - }} />; - } -} - -class C1 extends PureComponent1 { - render() { - return
    { - this.refs.refName = ref; - }} />; - } -} diff --git a/replace-string-ref/tests/original-class-component-custom-import-names/metrics.json b/replace-string-ref/tests/original-class-component-custom-import-names/metrics.json deleted file mode 100644 index 979d65e..0000000 --- a/replace-string-ref/tests/original-class-component-custom-import-names/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "string-ref-replacements": [ - { - "cardinality": { - "file": "tests/original-class-component-custom-import-names/input.tsx", - "refs": "refName,refName" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/replace-string-ref/tests/original-class-component-default-import/expected.tsx b/replace-string-ref/tests/original-class-component-default-import/expected.tsx deleted file mode 100644 index e653027..0000000 --- a/replace-string-ref/tests/original-class-component-default-import/expected.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react"; - -class C extends React.Component { - render() { - return
    { - this.refs.refName = ref; - }} />; - } -} - -class C1 extends React.PureComponent { - render() { - return
    { - this.refs.refName = ref; - }} />; - } -} diff --git a/replace-string-ref/tests/original-class-component-default-import/metrics.json b/replace-string-ref/tests/original-class-component-default-import/metrics.json deleted file mode 100644 index 13a1bca..0000000 --- a/replace-string-ref/tests/original-class-component-default-import/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "string-ref-replacements": [ - { - "cardinality": { - "file": "tests/original-class-component-default-import/input.tsx", - "refs": "refName,refName" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/replace-string-ref/tests/original-class-component-named-import/expected.tsx b/replace-string-ref/tests/original-class-component-named-import/expected.tsx deleted file mode 100644 index ab6052a..0000000 --- a/replace-string-ref/tests/original-class-component-named-import/expected.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Component, PureComponent } from "react"; - -class C extends Component { - render() { - return
    { - this.refs.refName = ref; - }} />; - } -} - -class C1 extends PureComponent { - render() { - return
    { - this.refs.refName = ref; - }} />; - } -} diff --git a/replace-string-ref/tests/original-class-component-named-import/metrics.json b/replace-string-ref/tests/original-class-component-named-import/metrics.json deleted file mode 100644 index 13664cb..0000000 --- a/replace-string-ref/tests/original-class-component-named-import/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "string-ref-replacements": [ - { - "cardinality": { - "file": "tests/original-class-component-named-import/input.tsx", - "refs": "refName,refName" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/replace-string-ref/tests/single-ref-in-fragment/expected.tsx b/replace-string-ref/tests/single-ref-in-fragment/expected.tsx deleted file mode 100644 index be414e2..0000000 --- a/replace-string-ref/tests/single-ref-in-fragment/expected.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - render() { - return ( - <> -
    Header
    - { - this.refs.searchInput = ref; - }} /> - - ); - } -} - -export default MyComponent; diff --git a/replace-string-ref/tests/single-ref-in-fragment/input.tsx b/replace-string-ref/tests/single-ref-in-fragment/input.tsx deleted file mode 100644 index cdbc365..0000000 --- a/replace-string-ref/tests/single-ref-in-fragment/input.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React, { Component } from "react"; - -class MyComponent extends Component { - render() { - return ( - <> -
    Header
    - - - ); - } -} - -export default MyComponent; diff --git a/replace-string-ref/tests/single-ref-in-fragment/metrics.json b/replace-string-ref/tests/single-ref-in-fragment/metrics.json deleted file mode 100644 index e2b2088..0000000 --- a/replace-string-ref/tests/single-ref-in-fragment/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "string-ref-replacements": [ - { - "cardinality": { - "file": "tests/single-ref-in-fragment/input.tsx", - "refs": "searchInput" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/replace-string-ref/tsconfig.json b/replace-string-ref/tsconfig.json deleted file mode 100644 index 469fc5a..0000000 --- a/replace-string-ref/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", - "types": ["@codemod.com/jssg-types"], - "allowImportingTsExtensions": true, - "noEmit": true, - "verbatimModuleSyntax": true, - "erasableSyntaxOnly": true, - "strict": true, - "strictNullChecks": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true - }, - "exclude": ["tests"] -} diff --git a/replace-string-ref/workflow.yaml b/replace-string-ref/workflow.yaml deleted file mode 100644 index 8773841..0000000 --- a/replace-string-ref/workflow.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# 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/replace-use-form-state/codemod.yaml b/replace-use-form-state/codemod.yaml deleted file mode 100644 index 23dc871..0000000 --- a/replace-use-form-state/codemod.yaml +++ /dev/null @@ -1,19 +0,0 @@ -schema_version: "1.0" - -name: "@react-codemods/replace-use-form-state" -version: "0.1.0" -description: "Rename useFormState to useActionState in react-dom imports and usages" -author: "React Codemods" -license: "MIT" -workflow: "workflow.yaml" - -targets: - languages: ["tsx"] - -keywords: ["transformation", "migration", "react", "useFormState", "useActionState"] - -registry: - access: "public" - visibility: "public" - -capabilities: [] diff --git a/replace-use-form-state/package.json b/replace-use-form-state/package.json deleted file mode 100644 index 94f3c83..0000000 --- a/replace-use-form-state/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@react-codemods/replace-use-form-state", - "version": "0.1.0", - "description": "Rename useFormState to useActionState in react-dom imports and usages", - "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" - }, - "dependencies": { - "@jssg/utils": "^0.0.2" - } -} diff --git a/replace-use-form-state/scripts/codemod.ts b/replace-use-form-state/scripts/codemod.ts deleted file mode 100644 index b43815d..0000000 --- a/replace-use-form-state/scripts/codemod.ts +++ /dev/null @@ -1,113 +0,0 @@ -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"; - -const REACT_DOM_MODULE = "react-dom"; - -type ReactDOMImport = { name: string; type: "default" | "namespace" }; - -function getReactDOMImport(rootNode: SgNode): ReactDOMImport | null { - const defaultImp = getImport(rootNode, { type: "default", from: REACT_DOM_MODULE }); - if (defaultImp) return { name: defaultImp.alias, type: "default" }; - - const importDecls = rootNode.findAll({ - rule: { - kind: "import_statement", - has: { - all: [ - { kind: "string", regex: "^react-dom$" }, - { kind: "namespace_import" }, - ], - }, - }, - }); - - for (const imp of importDecls) { - const namespaceImport = imp.find({ rule: { kind: "namespace_import" } }); - if (namespaceImport) { - const ident = namespaceImport.field("name") ?? namespaceImport.find({ rule: { kind: "identifier" } }); - if (ident) return { name: ident.text(), type: "namespace" }; - } - } - return null; -} - -const transform: Transform = async (root) => { - const rootNode = root.root(); - const edits: Edit[] = []; - const metric = useMetricAtom("replace-use-form-state-migrations"); - - const reactDOMImport = getReactDOMImport(rootNode); - const useFormStateNamedImport = getImport(rootNode, { - type: "named", - name: "useFormState", - from: REACT_DOM_MODULE, - }); - - if (reactDOMImport) { - const memberCalls = rootNode.findAll({ - rule: { - kind: "member_expression", - has: { - kind: "property_identifier", - regex: "^useFormState$", - }, - }, - }); - - for (const member of memberCalls) { - const objNode = member.field("object"); - if (objNode && objNode.text() === reactDOMImport.name) { - const propNode = member.field("property"); - if (propNode) { - edits.push(propNode.replace("useActionState")); - metric.increment({ file: root.filename(), pattern: "member-call" }); - } - } - } - } - - if (useFormStateNamedImport && !useFormStateNamedImport.isNamespace) { - const importNode = useFormStateNamedImport.node.ancestors().find( - (a) => a.kind() === "import_statement" - ); - if (importNode) { - const specifiers = importNode.findAll({ - rule: { - kind: "import_specifier", - has: { - kind: "identifier", - regex: "^useFormState$", - }, - }, - }); - for (const spec of specifiers) { - const nameNode = spec.field("name"); - if (nameNode && nameNode.text() === "useFormState") { - edits.push(nameNode.replace("useActionState")); - } - } - } - - const localName = useFormStateNamedImport.alias; - if (localName === "useFormState") { - const refs = useFormStateNamedImport.node.references(); - for (const fileRef of refs) { - if (fileRef.root.filename() !== root.filename()) continue; - for (const node of fileRef.nodes) { - const inImport = node.ancestors().some((a) => a.kind() === "import_statement"); - if (!inImport) { - edits.push(node.replace("useActionState")); - } - } - } - } - metric.increment({ file: root.filename(), pattern: "named-import" }); - } - - if (edits.length === 0) return null; - return rootNode.commitEdits(edits); -}; - -export default transform; diff --git a/replace-use-form-state/tests/default-import/expected.tsx b/replace-use-form-state/tests/default-import/expected.tsx deleted file mode 100644 index 6fc2765..0000000 --- a/replace-use-form-state/tests/default-import/expected.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import ReactDOM from "react-dom"; - -function StatefulForm({}) { - const [state, formAction] = ReactDOM.useActionState(increment, 0); - return
    ; -} diff --git a/replace-use-form-state/tests/different-package-no-op/expected.tsx b/replace-use-form-state/tests/different-package-no-op/expected.tsx deleted file mode 100644 index 80c66fb..0000000 --- a/replace-use-form-state/tests/different-package-no-op/expected.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { useFormState } from "fake-form-library"; - -function Form() { - const form = useFormState({ initial: {} }); - return
    {form.fields}; -} diff --git a/replace-use-form-state/tests/different-package-no-op/input.tsx b/replace-use-form-state/tests/different-package-no-op/input.tsx deleted file mode 100644 index 80c66fb..0000000 --- a/replace-use-form-state/tests/different-package-no-op/input.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { useFormState } from "fake-form-library"; - -function Form() { - const form = useFormState({ initial: {} }); - return
    {form.fields}; -} diff --git a/replace-use-form-state/tests/local-variable-shadow-no-op/expected.tsx b/replace-use-form-state/tests/local-variable-shadow-no-op/expected.tsx deleted file mode 100644 index 77ba700..0000000 --- a/replace-use-form-state/tests/local-variable-shadow-no-op/expected.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useActionState } from "react-dom"; - -function Form() { - const [state, formAction] = useActionState(submit, null); - - function handleClick() { - const useFormState = "local variable - should NOT be replaced"; - console.log(useFormState); - } - - return ( -
    - - - - ); -} - -async function submit(_prev: string | null, _data: FormData) { - return "ok"; -} diff --git a/replace-use-form-state/tests/local-variable-shadow-no-op/input.tsx b/replace-use-form-state/tests/local-variable-shadow-no-op/input.tsx deleted file mode 100644 index 689d853..0000000 --- a/replace-use-form-state/tests/local-variable-shadow-no-op/input.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useFormState } from "react-dom"; - -function Form() { - const [state, formAction] = useFormState(submit, null); - - function handleClick() { - const useFormState = "local variable - should NOT be replaced"; - console.log(useFormState); - } - - return ( -
    - - - - ); -} - -async function submit(_prev: string | null, _data: FormData) { - return "ok"; -} diff --git a/replace-use-form-state/tests/local-variable-shadow-no-op/metrics.json b/replace-use-form-state/tests/local-variable-shadow-no-op/metrics.json deleted file mode 100644 index d504896..0000000 --- a/replace-use-form-state/tests/local-variable-shadow-no-op/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "replace-use-form-state-migrations": [ - { - "cardinality": { - "file": "tests/local-variable-shadow-no-op/input.tsx", - "pattern": "named-import" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/replace-use-form-state/tests/multiple-usages/expected.tsx b/replace-use-form-state/tests/multiple-usages/expected.tsx deleted file mode 100644 index 9e760fb..0000000 --- a/replace-use-form-state/tests/multiple-usages/expected.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { useActionState } from "react-dom"; - -function Form1() { - const [a, setA] = useActionState(fn, null); - return
    {a}; -} - -function Form2() { - const [b, setB] = useActionState(fn, null); - return
    {b}; -} diff --git a/replace-use-form-state/tests/multiple-usages/input.tsx b/replace-use-form-state/tests/multiple-usages/input.tsx deleted file mode 100644 index 0823603..0000000 --- a/replace-use-form-state/tests/multiple-usages/input.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { useFormState } from "react-dom"; - -function Form1() { - const [a, setA] = useFormState(fn, null); - return
    {a}; -} - -function Form2() { - const [b, setB] = useFormState(fn, null); - return
    {b}; -} diff --git a/replace-use-form-state/tests/multiple-usages/metrics.json b/replace-use-form-state/tests/multiple-usages/metrics.json deleted file mode 100644 index b5c1c62..0000000 --- a/replace-use-form-state/tests/multiple-usages/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "replace-use-form-state-migrations": [ - { - "cardinality": { - "file": "tests/multiple-usages/input.tsx", - "pattern": "named-import" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/replace-use-form-state/tests/named-import-2/expected.tsx b/replace-use-form-state/tests/named-import-2/expected.tsx deleted file mode 100644 index 1fbd00d..0000000 --- a/replace-use-form-state/tests/named-import-2/expected.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { createPortal, useActionState } from "react-dom"; - -function StatefulForm({}) { - const [state, formAction] = useActionState(increment, 0); - - createPortal(); - return
    ; -} diff --git a/replace-use-form-state/tests/named-import-3/expected.tsx b/replace-use-form-state/tests/named-import-3/expected.tsx deleted file mode 100644 index c36827d..0000000 --- a/replace-use-form-state/tests/named-import-3/expected.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { useActionState as UFS, createPortal } from "react-dom"; - -function StatefulForm({}) { - const [state, formAction] = UFS(increment, 0); - - createPortal(); - return
    ; -} diff --git a/replace-use-form-state/tests/named-import/expected.tsx b/replace-use-form-state/tests/named-import/expected.tsx deleted file mode 100644 index 7d57506..0000000 --- a/replace-use-form-state/tests/named-import/expected.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { useActionState } from "react-dom"; - -async function increment(previousState, formData) { - return previousState + 1; -} - -function StatefulForm({}) { - const [state, formAction] = useActionState(increment, 0); - return ( -
    - {state} - - - ); -} diff --git a/replace-use-form-state/tests/real-world-form-component/expected.tsx b/replace-use-form-state/tests/real-world-form-component/expected.tsx deleted file mode 100644 index 96ace59..0000000 --- a/replace-use-form-state/tests/real-world-form-component/expected.tsx +++ /dev/null @@ -1,40 +0,0 @@ -"use client"; - -import { useActionState } from "react-dom"; -import { useFormStatus } from "react-dom"; - -async function createPost(_prev: { error?: string } | null, formData: FormData) { - const title = formData.get("title") as string; - if (!title?.trim()) { - return { error: "Title is required" }; - } - await fetch("/api/posts", { - method: "POST", - body: JSON.stringify({ title }), - }); - return { error: undefined }; -} - -function SubmitButton() { - const { pending } = useFormStatus(); - return ( - - ); -} - -export function CreatePostForm() { - const [state, formAction] = useActionState(createPost, null); - - return ( -
    -
    - - -
    - {state?.error &&

    {state.error}

    } - - - ); -} diff --git a/replace-use-form-state/tests/real-world-form-component/input.tsx b/replace-use-form-state/tests/real-world-form-component/input.tsx deleted file mode 100644 index 40a4832..0000000 --- a/replace-use-form-state/tests/real-world-form-component/input.tsx +++ /dev/null @@ -1,40 +0,0 @@ -"use client"; - -import { useFormState } from "react-dom"; -import { useFormStatus } from "react-dom"; - -async function createPost(_prev: { error?: string } | null, formData: FormData) { - const title = formData.get("title") as string; - if (!title?.trim()) { - return { error: "Title is required" }; - } - await fetch("/api/posts", { - method: "POST", - body: JSON.stringify({ title }), - }); - return { error: undefined }; -} - -function SubmitButton() { - const { pending } = useFormStatus(); - return ( - - ); -} - -export function CreatePostForm() { - const [state, formAction] = useFormState(createPost, null); - - return ( -
    -
    - - -
    - {state?.error &&

    {state.error}

    } - - - ); -} diff --git a/replace-use-form-state/tests/real-world-form-component/metrics.json b/replace-use-form-state/tests/real-world-form-component/metrics.json deleted file mode 100644 index 2b872d4..0000000 --- a/replace-use-form-state/tests/real-world-form-component/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "replace-use-form-state-migrations": [ - { - "cardinality": { - "file": "tests/real-world-form-component/input.tsx", - "pattern": "named-import" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/replace-use-form-state/tests/wildcard-import/expected.tsx b/replace-use-form-state/tests/wildcard-import/expected.tsx deleted file mode 100644 index cfcf7dc..0000000 --- a/replace-use-form-state/tests/wildcard-import/expected.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import * as ReactDOM from "react-dom"; - -function StatefulForm({}) { - const [state, formAction] = ReactDOM.useActionState(increment, 0); - return
    ; -} diff --git a/replace-use-form-state/tsconfig.json b/replace-use-form-state/tsconfig.json deleted file mode 100644 index 469fc5a..0000000 --- a/replace-use-form-state/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", - "types": ["@codemod.com/jssg-types"], - "allowImportingTsExtensions": true, - "noEmit": true, - "verbatimModuleSyntax": true, - "erasableSyntaxOnly": true, - "strict": true, - "strictNullChecks": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true - }, - "exclude": ["tests"] -} diff --git a/scripts/sync-codemod-versions.sh b/scripts/sync-codemod-versions.sh new file mode 100755 index 0000000..fd0b363 --- /dev/null +++ b/scripts/sync-codemod-versions.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Syncs the version field in each codemod's codemod.yaml with its package.json. +# Run after `changeset version` to keep both files in sync. + +set -euo pipefail + +for pkg_json in codemods/jssg/*/package.json; do + dir="$(dirname "$pkg_json")" + codemod_yaml="$dir/codemod.yaml" + + if [ ! -f "$codemod_yaml" ]; then + continue + fi + + version="$(node -p "require('./$pkg_json').version")" + # Replace the version line in codemod.yaml + sed -i'' -e "s/^version: .*/version: \"$version\"/" "$codemod_yaml" + + echo "Synced $codemod_yaml to version $version" +done diff --git a/scripts/tag-and-publish.sh b/scripts/tag-and-publish.sh new file mode 100755 index 0000000..46cdbe8 --- /dev/null +++ b/scripts/tag-and-publish.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Creates git tags for each codemod whose version was bumped by changesets. +# Outputs the list of changed codemod directories for the publish job. +# Tags follow the pattern: @v + +set -euo pipefail + +changed_dirs="[]" + +for pkg_json in codemods/jssg/*/package.json; do + dir="$(dirname "$pkg_json")" + name="$(node -p "require('./$pkg_json').name")" + version="$(node -p "require('./$pkg_json').version")" + tag="${name}@v${version}" + + if git rev-parse "$tag" >/dev/null 2>&1; then + echo "Tag $tag already exists, skipping" + continue + fi + + echo "Creating tag $tag" + git tag "$tag" + changed_dirs="$(echo "$changed_dirs" | node -p "JSON.stringify([...JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')), \"$dir\"])")" +done + +git push --tags + +echo "changed_dirs=$changed_dirs" >> "$GITHUB_OUTPUT" diff --git a/scripts/validate-docs.mjs b/scripts/validate-docs.mjs new file mode 100644 index 0000000..cc35678 --- /dev/null +++ b/scripts/validate-docs.mjs @@ -0,0 +1,33 @@ +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."); diff --git a/sort-comp/codemod.yaml b/sort-comp/codemod.yaml deleted file mode 100644 index 1931761..0000000 --- a/sort-comp/codemod.yaml +++ /dev/null @@ -1,19 +0,0 @@ -schema_version: "1.0" - -name: "@react-codemods/sort-comp" -version: "0.1.0" -description: "Reorder React component methods to match eslint-plugin-react sort-comp rule" -author: "React Codemods" -license: "MIT" -workflow: "workflow.yaml" - -targets: - languages: ["tsx"] - -keywords: ["transformation", "sort", "react", "sort-comp"] - -registry: - access: "public" - visibility: "public" - -capabilities: [] diff --git a/sort-comp/package.json b/sort-comp/package.json deleted file mode 100644 index b12be0d..0000000 --- a/sort-comp/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@react-codemods/sort-comp", - "version": "0.1.0", - "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", - "typescript": "latest" - } -} diff --git a/sort-comp/scripts/codemod.ts b/sort-comp/scripts/codemod.ts deleted file mode 100644 index 96aafc2..0000000 --- a/sort-comp/scripts/codemod.ts +++ /dev/null @@ -1,139 +0,0 @@ -import type { Transform, Edit, SgNode } from "codemod:ast-grep"; -import type TSX from "codemod:ast-grep/langs/tsx"; -import { useMetricAtom } from "codemod:metrics"; - -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", -]; - -const REGEXP_REGEXP = /^\/(.*)\/([gimuy]*)$/; - -function selectorMatches(selector: string, methodName: string): boolean { - if (selector === methodName) return true; - if (selector === "static-methods") return false; - const reMatch = selector.match(REGEXP_REGEXP); - if (reMatch) { - try { - const re = new RegExp(reMatch[1], reMatch[2]); - return re.test(methodName); - } catch { - return false; - } - } - return false; -} - -function getCorrectIndex(methodsOrder: string[], methodName: string): number { - const everythingElseIndex = methodsOrder.indexOf("everything-else"); - for (let i = 0; i < methodsOrder.length; i++) { - if (i !== everythingElseIndex && selectorMatches(methodsOrder[i], methodName)) { - return i; - } - } - return everythingElseIndex >= 0 ? everythingElseIndex : Number.POSITIVE_INFINITY; -} - -function getPairKeyName(node: SgNode): string { - if (node.kind() === "method_definition") { - const name = node.field("name"); - return name ? name.text() : ""; - } - const key = node.field("key"); - if (!key) return ""; - return key.text(); -} - -const transform: Transform = async (root, options) => { - const rootNode = root.root(); - const source = rootNode.text(); - const edits: Edit[] = []; - const methodsOrder = (options.params?.["methodsOrder"] as string[]) ?? DEFAULT_METHODS_ORDER; - const metric = useMetricAtom("sort-comp-reorders"); - - const createClassCalls = rootNode.findAll({ - rule: { - kind: "call_expression", - has: { - field: "function", - any: [ - { - kind: "member_expression", - all: [ - { has: { field: "object", kind: "identifier", regex: "^React$" } }, - { has: { field: "property", kind: "property_identifier", regex: "^createClass$" } }, - ], - }, - { kind: "identifier", regex: "^createClass$" }, - ], - }, - }, - }); - - 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 || configObj.kind() !== "object") continue; - - const pairs = configObj.children().filter( - (c) => c.kind() === "pair" || c.kind() === "method_definition", - ); - if (pairs.length <= 1) continue; - - const sorted = [...pairs].sort((a, b) => { - const nameA = getPairKeyName(a); - const nameB = getPairKeyName(b); - const idxA = getCorrectIndex(methodsOrder, nameA); - const idxB = getCorrectIndex(methodsOrder, nameB); - if (idxA !== idxB) return idxA - idxB; - return nameA.localeCompare(nameB); - }); - - const first = pairs[0]!; - const last = pairs[pairs.length - 1]!; - const start = first.range().start.index; - const end = last.range().end.index; - const sep = ",\n\n "; - const newContent = sorted.map((p) => p.text()).join(sep); - edits.push({ startPos: start, endPos: end, insertedText: newContent }); - metric.increment({ file: root.filename() }); - } - - if (edits.length === 0) return null; - return rootNode.commitEdits(edits); -}; - -export default transform; diff --git a/sort-comp/tests/lifecycle-order/expected.tsx b/sort-comp/tests/lifecycle-order/expected.tsx deleted file mode 100644 index a11c5c4..0000000 --- a/sort-comp/tests/lifecycle-order/expected.tsx +++ /dev/null @@ -1,25 +0,0 @@ -var React = require('react'); - -var LifecycleDemo = React.createClass({ - getInitialState: function() { - return {}; - }, - - componentWillMount: function() { - console.log('will mount'); - }, - - componentDidMount: function() { - console.log('mounted'); - }, - - componentWillUnmount: function() { - console.log('unmounting'); - }, - - render: function() { - return
    ; - }, -}); - -module.exports = LifecycleDemo; diff --git a/sort-comp/tests/lifecycle-order/input.tsx b/sort-comp/tests/lifecycle-order/input.tsx deleted file mode 100644 index 3ac938d..0000000 --- a/sort-comp/tests/lifecycle-order/input.tsx +++ /dev/null @@ -1,25 +0,0 @@ -var React = require('react'); - -var LifecycleDemo = React.createClass({ - render: function() { - return
    ; - }, - - componentDidMount: function() { - console.log('mounted'); - }, - - componentWillUnmount: function() { - console.log('unmounting'); - }, - - componentWillMount: function() { - console.log('will mount'); - }, - - getInitialState: function() { - return {}; - }, -}); - -module.exports = LifecycleDemo; diff --git a/sort-comp/tests/lifecycle-order/metrics.json b/sort-comp/tests/lifecycle-order/metrics.json deleted file mode 100644 index 8636725..0000000 --- a/sort-comp/tests/lifecycle-order/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sort-comp-reorders": [ - { - "cardinality": { - "file": "tests/lifecycle-order/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/sort-comp/tests/no-reorder-needed/expected.tsx b/sort-comp/tests/no-reorder-needed/expected.tsx deleted file mode 100644 index 78a5e48..0000000 --- a/sort-comp/tests/no-reorder-needed/expected.tsx +++ /dev/null @@ -1,13 +0,0 @@ -var React = require('react'); - -var C = React.createClass({ - displayName: 'C', - - propTypes: {}, - - render: function() { - return
    ; - }, -}); - -module.exports = C; diff --git a/sort-comp/tests/no-reorder-needed/input.tsx b/sort-comp/tests/no-reorder-needed/input.tsx deleted file mode 100644 index a321e98..0000000 --- a/sort-comp/tests/no-reorder-needed/input.tsx +++ /dev/null @@ -1,11 +0,0 @@ -var React = require('react'); - -var C = React.createClass({ - displayName: 'C', - propTypes: {}, - render: function() { - return
    ; - }, -}); - -module.exports = C; diff --git a/sort-comp/tests/no-reorder-needed/metrics.json b/sort-comp/tests/no-reorder-needed/metrics.json deleted file mode 100644 index 914f1ab..0000000 --- a/sort-comp/tests/no-reorder-needed/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sort-comp-reorders": [ - { - "cardinality": { - "file": "tests/no-reorder-needed/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/sort-comp/tests/original-createClass/expected.tsx b/sort-comp/tests/original-createClass/expected.tsx deleted file mode 100644 index e2b2210..0000000 --- a/sort-comp/tests/original-createClass/expected.tsx +++ /dev/null @@ -1,36 +0,0 @@ -var React = require('react/addons'); - -// comment above createClass -var MyComponent = React.createClass({ - // comment at top of createClass - // this will be attached to first method - - propTypes: { - foo: bar, // comment on prop - }, - - mixins: [PureRenderMixin], - - componentDidMount() { - }, - - myOwnMethod(foo) { - // comment within method - }, - - renderBar() { - // should come before renderFoo - }, - - renderFoo() { - // other render* function - }, - - render: function() { - return
    ; - }, - -}); - -/* comment at end */ -module.exports = MyComponent; diff --git a/sort-comp/tests/original-createClass/metrics.json b/sort-comp/tests/original-createClass/metrics.json deleted file mode 100644 index 76ffc6e..0000000 --- a/sort-comp/tests/original-createClass/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sort-comp-reorders": [ - { - "cardinality": { - "file": "tests/original-createClass/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/sort-comp/tests/real-world-container/expected.tsx b/sort-comp/tests/real-world-container/expected.tsx deleted file mode 100644 index 3222c4b..0000000 --- a/sort-comp/tests/real-world-container/expected.tsx +++ /dev/null @@ -1,37 +0,0 @@ -var React = require('react'); - -var DataContainer = React.createClass({ - propTypes: { - url: React.PropTypes.string.isRequired, - children: React.PropTypes.node, - }, - - defaultProps: { - pollInterval: 5000, - }, - - getInitialState: function() { - return { loading: true }; - }, - - componentDidMount: function() { - this.fetchData(); - }, - - fetchData: function() { - var self = this; - fetch(this.props.url).then(function(r) { return r.json(); }).then(function(data) { - self.setState({ loading: false, data: data }); - }); - }, - - render: function() { - return ( -
    - {this.state.loading ? Loading... : this.props.children} -
    - ); - }, -}); - -module.exports = DataContainer; diff --git a/sort-comp/tests/real-world-container/input.tsx b/sort-comp/tests/real-world-container/input.tsx deleted file mode 100644 index f1e6eb3..0000000 --- a/sort-comp/tests/real-world-container/input.tsx +++ /dev/null @@ -1,37 +0,0 @@ -var React = require('react'); - -var DataContainer = React.createClass({ - render: function() { - return ( -
    - {this.state.loading ? Loading... : this.props.children} -
    - ); - }, - - getInitialState: function() { - return { loading: true }; - }, - - defaultProps: { - pollInterval: 5000, - }, - - componentDidMount: function() { - this.fetchData(); - }, - - fetchData: function() { - var self = this; - fetch(this.props.url).then(function(r) { return r.json(); }).then(function(data) { - self.setState({ loading: false, data: data }); - }); - }, - - propTypes: { - url: React.PropTypes.string.isRequired, - children: React.PropTypes.node, - }, -}); - -module.exports = DataContainer; diff --git a/sort-comp/tests/real-world-container/metrics.json b/sort-comp/tests/real-world-container/metrics.json deleted file mode 100644 index de9c9b2..0000000 --- a/sort-comp/tests/real-world-container/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sort-comp-reorders": [ - { - "cardinality": { - "file": "tests/real-world-container/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/sort-comp/tests/real-world-search-input/expected.tsx b/sort-comp/tests/real-world-search-input/expected.tsx deleted file mode 100644 index 10cfe33..0000000 --- a/sort-comp/tests/real-world-search-input/expected.tsx +++ /dev/null @@ -1,42 +0,0 @@ -var React = require('react'); - -var SearchInput = React.createClass({ - propTypes: { - placeholder: React.PropTypes.string, - onSearch: React.PropTypes.func, - }, - - getInitialState: function() { - return { value: '' }; - }, - - componentDidMount: function() { - this.inputRef.focus(); - }, - - handleChange: function(e) { - this.setState({ value: e.target.value }); - }, - - handleClear: function() { - this.setState({ value: '' }); - }, - - render: function() { - return ( -
    - { this.inputRef = el; }} - value={this.state.value} - onChange={this.handleChange} - placeholder={this.props.placeholder} - /> - {this.state.value && ( - - )} -
    - ); - }, -}); - -module.exports = SearchInput; diff --git a/sort-comp/tests/real-world-search-input/input.tsx b/sort-comp/tests/real-world-search-input/input.tsx deleted file mode 100644 index 9add27a..0000000 --- a/sort-comp/tests/real-world-search-input/input.tsx +++ /dev/null @@ -1,42 +0,0 @@ -var React = require('react'); - -var SearchInput = React.createClass({ - handleClear: function() { - this.setState({ value: '' }); - }, - - handleChange: function(e) { - this.setState({ value: e.target.value }); - }, - - getInitialState: function() { - return { value: '' }; - }, - - componentDidMount: function() { - this.inputRef.focus(); - }, - - propTypes: { - placeholder: React.PropTypes.string, - onSearch: React.PropTypes.func, - }, - - render: function() { - return ( -
    - { this.inputRef = el; }} - value={this.state.value} - onChange={this.handleChange} - placeholder={this.props.placeholder} - /> - {this.state.value && ( - - )} -
    - ); - }, -}); - -module.exports = SearchInput; diff --git a/sort-comp/tests/real-world-search-input/metrics.json b/sort-comp/tests/real-world-search-input/metrics.json deleted file mode 100644 index 410d069..0000000 --- a/sort-comp/tests/real-world-search-input/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sort-comp-reorders": [ - { - "cardinality": { - "file": "tests/real-world-search-input/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/sort-comp/tests/real-world-settings-panel/expected.tsx b/sort-comp/tests/real-world-settings-panel/expected.tsx deleted file mode 100644 index 80aa311..0000000 --- a/sort-comp/tests/real-world-settings-panel/expected.tsx +++ /dev/null @@ -1,26 +0,0 @@ -var React = require('react'); - -var SettingsPanel = React.createClass({ - propTypes: { - children: React.PropTypes.node, - }, - - getInitialState: function() { - return { expanded: false }; - }, - - toggle: function() { - this.setState({ expanded: !this.state.expanded }); - }, - - render: function() { - return ( -
    - - {this.state.expanded &&
    {this.props.children}
    } -
    - ); - }, -}); - -module.exports = SettingsPanel; diff --git a/sort-comp/tests/real-world-settings-panel/input.tsx b/sort-comp/tests/real-world-settings-panel/input.tsx deleted file mode 100644 index fb5e5e5..0000000 --- a/sort-comp/tests/real-world-settings-panel/input.tsx +++ /dev/null @@ -1,26 +0,0 @@ -var React = require('react'); - -var SettingsPanel = React.createClass({ - getInitialState: function() { - return { expanded: false }; - }, - - toggle: function() { - this.setState({ expanded: !this.state.expanded }); - }, - - render: function() { - return ( -
    - - {this.state.expanded &&
    {this.props.children}
    } -
    - ); - }, - - propTypes: { - children: React.PropTypes.node, - }, -}); - -module.exports = SettingsPanel; diff --git a/sort-comp/tests/real-world-settings-panel/metrics.json b/sort-comp/tests/real-world-settings-panel/metrics.json deleted file mode 100644 index 96ea420..0000000 --- a/sort-comp/tests/real-world-settings-panel/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sort-comp-reorders": [ - { - "cardinality": { - "file": "tests/real-world-settings-panel/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/sort-comp/tests/real-world-tabbed-panel/expected.tsx b/sort-comp/tests/real-world-tabbed-panel/expected.tsx deleted file mode 100644 index 3eebd22..0000000 --- a/sort-comp/tests/real-world-tabbed-panel/expected.tsx +++ /dev/null @@ -1,29 +0,0 @@ -var React = require('react'); - -var TabbedPanel = React.createClass({ - propTypes: { - tabs: React.PropTypes.array.isRequired, - }, - - statics: { - defaultActiveIndex: 0, - }, - - getInitialState: function() { - return { activeTab: null }; - }, - - getActiveContent: function() { - return this.props.tabs.find(function(t) { return t.id === this.state.activeTab; }.bind(this)); - }, - - selectTab: function(tabId) { - this.setState({ activeTab: tabId }); - }, - - render: function() { - return
    {this.getActiveContent()}
    ; - }, -}); - -module.exports = TabbedPanel; diff --git a/sort-comp/tests/real-world-tabbed-panel/input.tsx b/sort-comp/tests/real-world-tabbed-panel/input.tsx deleted file mode 100644 index 2503103..0000000 --- a/sort-comp/tests/real-world-tabbed-panel/input.tsx +++ /dev/null @@ -1,29 +0,0 @@ -var React = require('react'); - -var TabbedPanel = React.createClass({ - selectTab: function(tabId) { - this.setState({ activeTab: tabId }); - }, - - getActiveContent: function() { - return this.props.tabs.find(function(t) { return t.id === this.state.activeTab; }.bind(this)); - }, - - getInitialState: function() { - return { activeTab: null }; - }, - - statics: { - defaultActiveIndex: 0, - }, - - propTypes: { - tabs: React.PropTypes.array.isRequired, - }, - - render: function() { - return
    {this.getActiveContent()}
    ; - }, -}); - -module.exports = TabbedPanel; diff --git a/sort-comp/tests/real-world-tabbed-panel/metrics.json b/sort-comp/tests/real-world-tabbed-panel/metrics.json deleted file mode 100644 index 163e5e9..0000000 --- a/sort-comp/tests/real-world-tabbed-panel/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sort-comp-reorders": [ - { - "cardinality": { - "file": "tests/real-world-tabbed-panel/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/sort-comp/tests/render-helpers-order/expected.tsx b/sort-comp/tests/render-helpers-order/expected.tsx deleted file mode 100644 index 3ecb2eb..0000000 --- a/sort-comp/tests/render-helpers-order/expected.tsx +++ /dev/null @@ -1,26 +0,0 @@ -var React = require('react'); - -var CardList = React.createClass({ - propTypes: { - items: React.PropTypes.array, - }, - - renderCard: function(item) { - return
    {item.name}
    ; - }, - - renderHeader: function() { - return

    Header

    ; - }, - - render: function() { - return ( -
    - {this.renderHeader()} - {this.props.items.map(this.renderCard)} -
    - ); - }, -}); - -module.exports = CardList; diff --git a/sort-comp/tests/render-helpers-order/input.tsx b/sort-comp/tests/render-helpers-order/input.tsx deleted file mode 100644 index b0d4f8a..0000000 --- a/sort-comp/tests/render-helpers-order/input.tsx +++ /dev/null @@ -1,26 +0,0 @@ -var React = require('react'); - -var CardList = React.createClass({ - renderHeader: function() { - return

    Header

    ; - }, - - renderCard: function(item) { - return
    {item.name}
    ; - }, - - propTypes: { - items: React.PropTypes.array, - }, - - render: function() { - return ( -
    - {this.renderHeader()} - {this.props.items.map(this.renderCard)} -
    - ); - }, -}); - -module.exports = CardList; diff --git a/sort-comp/tests/render-helpers-order/metrics.json b/sort-comp/tests/render-helpers-order/metrics.json deleted file mode 100644 index 2867315..0000000 --- a/sort-comp/tests/render-helpers-order/metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sort-comp-reorders": [ - { - "cardinality": { - "file": "tests/render-helpers-order/input.tsx" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/sort-comp/tsconfig.json b/sort-comp/tsconfig.json deleted file mode 100644 index 321c0d7..0000000 --- a/sort-comp/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", - "types": ["@codemod.com/jssg-types"], - "allowImportingTsExtensions": true, - "noEmit": true, - "verbatimModuleSyntax": true, - "erasableSyntaxOnly": true, - "strict": true, - "strictNullChecks": true - }, - "exclude": ["tests"] -} diff --git a/sort-comp/workflow.yaml b/sort-comp/workflow.yaml deleted file mode 100644 index 8773841..0000000 --- a/sort-comp/workflow.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# 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/update-react-imports/.gitignore b/update-react-imports/.gitignore deleted file mode 100644 index 78174f4..0000000 --- a/update-react-imports/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -# 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/update-react-imports/README.md b/update-react-imports/README.md deleted file mode 100644 index c10ade6..0000000 --- a/update-react-imports/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# @react-codemods/update-react-imports - -Remove unused React default import and convert to named imports - -## Installation - -```bash -# Install from registry -codemod run @react-codemods/update-react-imports - -# Or run locally -codemod run -w workflow.yaml -``` - -## Usage - -This codemod transforms tsx code by: - -- Converting `var` declarations to `const`/`let` -- Removing debug statements -- Modernizing syntax patterns - -## Development - -```bash -# Test the transformation -npm test - -# Validate the workflow -codemod validate -w workflow.yaml - -# Publish to registry -codemod login -codemod publish -``` - -## License -MIT diff --git a/update-react-imports/codemod.yaml b/update-react-imports/codemod.yaml deleted file mode 100644 index a4626a6..0000000 --- a/update-react-imports/codemod.yaml +++ /dev/null @@ -1,20 +0,0 @@ -schema_version: "1.0" - -name: "@react-codemods/update-react-imports" -version: "0.1.0" -description: "Remove unused React default import and convert to named imports" -author: "React Codemods" -license: "MIT" -workflow: "workflow.yaml" - - -targets: - languages: ["tsx"] - -keywords: ["transformation", "migration"] - -registry: - access: "public" - visibility: "public" - -capabilities: [] diff --git a/update-react-imports/package.json b/update-react-imports/package.json deleted file mode 100644 index 6b56ed8..0000000 --- a/update-react-imports/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@react-codemods/update-react-imports", - "version": "0.1.0", - "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" - }, - "dependencies": { - "@jssg/utils": "^0.0.2" - } -} diff --git a/update-react-imports/scripts/codemod.ts b/update-react-imports/scripts/codemod.ts deleted file mode 100644 index 1c69c71..0000000 --- a/update-react-imports/scripts/codemod.ts +++ /dev/null @@ -1,171 +0,0 @@ -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"; - -const transform: Transform = async (root) => { - const rootNode = root.root(); - const edits: Edit[] = []; - - const transformMetric = useMetricAtom("react-import-updates"); - - const reactDefaultImport = getImport(rootNode, { type: "default", from: "react" }); - - if (!reactDefaultImport) { - return null; - } - - const reactName = reactDefaultImport.alias; - - const reactMemberUsages = new Map[]>(); - - const memberExpressions = rootNode.findAll({ - rule: { - kind: "member_expression", - has: { - field: "object", - kind: "identifier", - regex: `^${reactName}$`, - }, - }, - }); - - for (const memberExpr of memberExpressions) { - const property = memberExpr.find({ - rule: { - kind: "property_identifier", - }, - }); - - if (property) { - const propName = property.text(); - if (!reactMemberUsages.has(propName)) { - reactMemberUsages.set(propName, []); - } - reactMemberUsages.get(propName)!.push(memberExpr); - } - } - - - const reactAsValue = rootNode.findAll({ - rule: { - kind: "identifier", - regex: `^${reactName}$`, - }, - }).filter((node) => { - const parent = node.parent(); - if (!parent) return true; - - if (parent.kind() === "member_expression") { - const objectField = parent.field("object"); - if (objectField && objectField.id() === node.id()) { - return false; - } - } - - const ancestors = node.ancestors(); - for (const ancestor of ancestors) { - if (ancestor.kind() === "import_statement") { - return false; - } - } - - return true; - }); - - const isReactUsedAsValue = reactAsValue.length > 0; - - if (isReactUsedAsValue) { - const importStatement = reactDefaultImport.node.ancestors().find( - (a) => a.kind() === "import_statement", - ); - if (importStatement) { - const source = importStatement.field("source"); - const sourceText = source?.text() ?? '"react"'; - const namedImports = importStatement.find({ - rule: { kind: "named_imports" }, - }); - const namespaceLine = `import * as ${reactName} from ${sourceText};`; - if (namedImports) { - const namedLine = `import ${namedImports.text()} from ${sourceText};`; - edits.push(importStatement.replace(`${namespaceLine}\n${namedLine}`)); - } else { - edits.push(importStatement.replace(namespaceLine)); - } - } - - transformMetric.increment({ - action: "convert-to-namespace", - reason: "react-used-as-value", - }); - } else if (reactMemberUsages.size > 0) { - const namedImportsToAdd: string[] = []; - let totalConversions = 0; - - for (const [memberName, usages] of reactMemberUsages) { - namedImportsToAdd.push(memberName); - - for (const usage of usages) { - edits.push(usage.replace(memberName)); - } - - totalConversions += usages.length; - } - - const namedList = [...new Set(namedImportsToAdd)].sort().join(", "); - const newImportText = `import { ${namedList} } from "react";`; - const importStatement = reactDefaultImport.node.ancestors().find( - (a) => a.kind() === "import_statement" - ); - if (importStatement) { - edits.push(importStatement.replace(newImportText)); - } - - transformMetric.increment({ - action: "convert-member-to-named", - members: namedList, - }, totalConversions); - } else { - const importStatement = reactDefaultImport.node.ancestors().find( - (a) => a.kind() === "import_statement", - ); - if (!importStatement) { - return null; - } - - const namedImports = importStatement.find({ - rule: { kind: "named_imports" }, - }); - - if (namedImports) { - const source = importStatement.field("source"); - const sourceText = source?.text() ?? '"react"'; - const newImportText = `import ${namedImports.text()} from ${sourceText};`; - edits.push(importStatement.replace(newImportText)); - transformMetric.increment({ - action: "remove-default-import", - reason: "only-jsx-usage", - }); - } else { - const removeEdit = removeImport(rootNode, { - type: "default", - from: "react", - }); - if (removeEdit) { - edits.push(removeEdit); - transformMetric.increment({ - action: "remove-default-import", - reason: "only-jsx-usage", - }); - } - } - } - - if (edits.length === 0) { - return null; - } - - return rootNode.commitEdits(edits); -}; - -export default transform; diff --git a/update-react-imports/tests/combined-default-named-import/expected.tsx b/update-react-imports/tests/combined-default-named-import/expected.tsx deleted file mode 100644 index e68c487..0000000 --- a/update-react-imports/tests/combined-default-named-import/expected.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { useState } from "react"; - -export function Counter() { - const [n, setN] = useState(0); - return ( - - ); -} diff --git a/update-react-imports/tests/combined-default-named-import/input.tsx b/update-react-imports/tests/combined-default-named-import/input.tsx deleted file mode 100644 index 832160b..0000000 --- a/update-react-imports/tests/combined-default-named-import/input.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React, { useState } from "react"; - -export function Counter() { - const [n, setN] = useState(0); - return ( - - ); -} diff --git a/update-react-imports/tests/combined-default-named-import/metrics.json b/update-react-imports/tests/combined-default-named-import/metrics.json deleted file mode 100644 index 44c71ae..0000000 --- a/update-react-imports/tests/combined-default-named-import/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "react-import-updates": [ - { - "cardinality": { - "action": "remove-default-import", - "reason": "only-jsx-usage" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/update-react-imports/tests/fragment-only/expected.tsx b/update-react-imports/tests/fragment-only/expected.tsx deleted file mode 100644 index b826e27..0000000 --- a/update-react-imports/tests/fragment-only/expected.tsx +++ /dev/null @@ -1,11 +0,0 @@ - -function MyComponent() { - return ( - <> -
    Hello
    - World - - ); -} - -export default MyComponent; diff --git a/update-react-imports/tests/fragment-only/input.tsx b/update-react-imports/tests/fragment-only/input.tsx deleted file mode 100644 index 4c40584..0000000 --- a/update-react-imports/tests/fragment-only/input.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react"; - -function MyComponent() { - return ( - <> -
    Hello
    - World - - ); -} - -export default MyComponent; diff --git a/update-react-imports/tests/fragment-only/metrics.json b/update-react-imports/tests/fragment-only/metrics.json deleted file mode 100644 index 44c71ae..0000000 --- a/update-react-imports/tests/fragment-only/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "react-import-updates": [ - { - "cardinality": { - "action": "remove-default-import", - "reason": "only-jsx-usage" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/update-react-imports/tests/jsx-only/expected.tsx b/update-react-imports/tests/jsx-only/expected.tsx deleted file mode 100644 index c45e4ae..0000000 --- a/update-react-imports/tests/jsx-only/expected.tsx +++ /dev/null @@ -1,6 +0,0 @@ - -function MyComponent() { - return
    Hello World
    ; -} - -export default MyComponent; diff --git a/update-react-imports/tests/jsx-only/input.tsx b/update-react-imports/tests/jsx-only/input.tsx deleted file mode 100644 index 0a80c61..0000000 --- a/update-react-imports/tests/jsx-only/input.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -function MyComponent() { - return
    Hello World
    ; -} - -export default MyComponent; diff --git a/update-react-imports/tests/jsx-only/metrics.json b/update-react-imports/tests/jsx-only/metrics.json deleted file mode 100644 index 44c71ae..0000000 --- a/update-react-imports/tests/jsx-only/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "react-import-updates": [ - { - "cardinality": { - "action": "remove-default-import", - "reason": "only-jsx-usage" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/update-react-imports/tests/member-expression/expected.tsx b/update-react-imports/tests/member-expression/expected.tsx deleted file mode 100644 index e62d6d3..0000000 --- a/update-react-imports/tests/member-expression/expected.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { createElement } from "react"; - -createElement('div', {}); - -function MyComponent() { - return
    Hello
    ; -} diff --git a/update-react-imports/tests/member-expression/input.tsx b/update-react-imports/tests/member-expression/input.tsx deleted file mode 100644 index f1b7846..0000000 --- a/update-react-imports/tests/member-expression/input.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -React.createElement('div', {}); - -function MyComponent() { - return
    Hello
    ; -} diff --git a/update-react-imports/tests/member-expression/metrics.json b/update-react-imports/tests/member-expression/metrics.json deleted file mode 100644 index 645bf79..0000000 --- a/update-react-imports/tests/member-expression/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "react-import-updates": [ - { - "cardinality": { - "action": "convert-member-to-named", - "members": "createElement" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/update-react-imports/tests/mixed-usage/expected.tsx b/update-react-imports/tests/mixed-usage/expected.tsx deleted file mode 100644 index b0e0ac9..0000000 --- a/update-react-imports/tests/mixed-usage/expected.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, createElement } from "react"; - -class MyComponent extends Component { - render() { - return createElement('div', {}, 'Hello'); - } -} - -export default MyComponent; diff --git a/update-react-imports/tests/mixed-usage/input.tsx b/update-react-imports/tests/mixed-usage/input.tsx deleted file mode 100644 index 5b80cf6..0000000 --- a/update-react-imports/tests/mixed-usage/input.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; - -class MyComponent extends React.Component { - render() { - return React.createElement('div', {}, 'Hello'); - } -} - -export default MyComponent; diff --git a/update-react-imports/tests/mixed-usage/metrics.json b/update-react-imports/tests/mixed-usage/metrics.json deleted file mode 100644 index 3ef48e9..0000000 --- a/update-react-imports/tests/mixed-usage/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "react-import-updates": [ - { - "cardinality": { - "action": "convert-member-to-named", - "members": "Component, createElement" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/update-react-imports/tests/no-react-import/expected.tsx b/update-react-imports/tests/no-react-import/expected.tsx deleted file mode 100644 index a8baecb..0000000 --- a/update-react-imports/tests/no-react-import/expected.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { useState } from "react"; - -function MyComponent() { - const [count, setCount] = useState(0); - return
    {count}
    ; -} - -export default MyComponent; diff --git a/update-react-imports/tests/no-react-import/input.tsx b/update-react-imports/tests/no-react-import/input.tsx deleted file mode 100644 index a8baecb..0000000 --- a/update-react-imports/tests/no-react-import/input.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { useState } from "react"; - -function MyComponent() { - const [count, setCount] = useState(0); - return
    {count}
    ; -} - -export default MyComponent; diff --git a/update-react-imports/tests/original-react-basic-default-export/metrics.json b/update-react-imports/tests/original-react-basic-default-export/metrics.json deleted file mode 100644 index 645bf79..0000000 --- a/update-react-imports/tests/original-react-basic-default-export/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "react-import-updates": [ - { - "cardinality": { - "action": "convert-member-to-named", - "members": "createElement" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/update-react-imports/tests/original-react-not-removed/input.tsx b/update-react-imports/tests/original-react-not-removed/input.tsx deleted file mode 100644 index 1b5b27c..0000000 --- a/update-react-imports/tests/original-react-not-removed/input.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -React.createElement('div', {}); - -Promise.resolve(React); - -
    Hi
    diff --git a/update-react-imports/tests/original-react-not-removed/metrics.json b/update-react-imports/tests/original-react-not-removed/metrics.json deleted file mode 100644 index ae6435e..0000000 --- a/update-react-imports/tests/original-react-not-removed/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "react-import-updates": [ - { - "cardinality": { - "action": "convert-to-namespace", - "reason": "react-used-as-value" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/update-react-imports/tests/react-as-value/expected.tsx b/update-react-imports/tests/react-as-value/expected.tsx deleted file mode 100644 index 949811d..0000000 --- a/update-react-imports/tests/react-as-value/expected.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import * as React from "react"; - -React.createElement('div', {}); - -Promise.resolve(React); - -function MyComponent() { - return
    Hi
    ; -} diff --git a/update-react-imports/tests/react-as-value/input.tsx b/update-react-imports/tests/react-as-value/input.tsx deleted file mode 100644 index 8a75314..0000000 --- a/update-react-imports/tests/react-as-value/input.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; - -React.createElement('div', {}); - -Promise.resolve(React); - -function MyComponent() { - return
    Hi
    ; -} diff --git a/update-react-imports/tests/react-as-value/metrics.json b/update-react-imports/tests/react-as-value/metrics.json deleted file mode 100644 index ae6435e..0000000 --- a/update-react-imports/tests/react-as-value/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "react-import-updates": [ - { - "cardinality": { - "action": "convert-to-namespace", - "reason": "react-used-as-value" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/update-react-imports/tests/react-fragment-member/expected.tsx b/update-react-imports/tests/react-fragment-member/expected.tsx deleted file mode 100644 index e51ae5c..0000000 --- a/update-react-imports/tests/react-fragment-member/expected.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { Fragment } from "react"; - -function MyComponent() { - return ( - -
    Hello
    -
    - ); -} - -export default MyComponent; diff --git a/update-react-imports/tests/react-fragment-member/input.tsx b/update-react-imports/tests/react-fragment-member/input.tsx deleted file mode 100644 index 67e335c..0000000 --- a/update-react-imports/tests/react-fragment-member/input.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -function MyComponent() { - return ( - -
    Hello
    -
    - ); -} - -export default MyComponent; diff --git a/update-react-imports/tests/react-fragment-member/metrics.json b/update-react-imports/tests/react-fragment-member/metrics.json deleted file mode 100644 index ded3440..0000000 --- a/update-react-imports/tests/react-fragment-member/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "react-import-updates": [ - { - "cardinality": { - "action": "convert-member-to-named", - "members": "Fragment" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/update-react-imports/tsconfig.json b/update-react-imports/tsconfig.json deleted file mode 100644 index 469fc5a..0000000 --- a/update-react-imports/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", - "types": ["@codemod.com/jssg-types"], - "allowImportingTsExtensions": true, - "noEmit": true, - "verbatimModuleSyntax": true, - "erasableSyntaxOnly": true, - "strict": true, - "strictNullChecks": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true - }, - "exclude": ["tests"] -} diff --git a/update-react-imports/workflow.yaml b/update-react-imports/workflow.yaml deleted file mode 100644 index 8773841..0000000 --- a/update-react-imports/workflow.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# 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/use-context-hook/.gitignore b/use-context-hook/.gitignore deleted file mode 100644 index 78174f4..0000000 --- a/use-context-hook/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -# 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/use-context-hook/README.md b/use-context-hook/README.md deleted file mode 100644 index 706eeda..0000000 --- a/use-context-hook/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# @react-codemods/use-context-hook - -Transform React.useContext() and useContext() to use() hook - -## Installation - -```bash -# Install from registry -codemod run @react-codemods/use-context-hook - -# Or run locally -codemod run -w workflow.yaml -``` - -## Usage - -This codemod transforms tsx code by: - -- Converting `var` declarations to `const`/`let` -- Removing debug statements -- Modernizing syntax patterns - -## Development - -```bash -# Test the transformation -npm test - -# Validate the workflow -codemod validate -w workflow.yaml - -# Publish to registry -codemod login -codemod publish -``` - -## License -MIT diff --git a/use-context-hook/codemod.yaml b/use-context-hook/codemod.yaml deleted file mode 100644 index 930358f..0000000 --- a/use-context-hook/codemod.yaml +++ /dev/null @@ -1,20 +0,0 @@ -schema_version: "1.0" - -name: "@react-codemods/use-context-hook" -version: "0.1.0" -description: "Transform React.useContext() and useContext() to use() hook" -author: "React Codemods" -license: "MIT" -workflow: "workflow.yaml" - - -targets: - languages: ["tsx"] - -keywords: ["transformation", "migration"] - -registry: - access: "public" - visibility: "public" - -capabilities: [] diff --git a/use-context-hook/package.json b/use-context-hook/package.json deleted file mode 100644 index 405844f..0000000 --- a/use-context-hook/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@react-codemods/use-context-hook", - "version": "0.1.0", - "description": "Transform React.useContext() and useContext() to use() hook", - "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" - }, - "dependencies": { - "@jssg/utils": "^0.0.2" - } -} diff --git a/use-context-hook/pnpm-lock.yaml b/use-context-hook/pnpm-lock.yaml deleted file mode 100644 index 5bad4a8..0000000 --- a/use-context-hook/pnpm-lock.yaml +++ /dev/null @@ -1,41 +0,0 @@ -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/use-context-hook/scripts/codemod.ts b/use-context-hook/scripts/codemod.ts deleted file mode 100644 index f1cd222..0000000 --- a/use-context-hook/scripts/codemod.ts +++ /dev/null @@ -1,137 +0,0 @@ -import type { Transform, Edit } from "codemod:ast-grep"; -import type TSX from "codemod:ast-grep/langs/tsx"; -import { useMetricAtom } from "codemod:metrics"; -import { getImport, addImport, removeImport } from "@jssg/utils/javascript/imports"; - -const transform: Transform = async (root) => { - const rootNode = root.root(); - const edits: Edit[] = []; - - const transformMetric = useMetricAtom("use-context-transformations"); - - const reactDefaultImport = getImport(rootNode, { type: "default", from: "react" }); - const reactDefaultName = reactDefaultImport?.alias || "React"; - - const useContextImport = getImport(rootNode, { type: "named", name: "useContext", from: "react" }); - const useContextLocalName = useContextImport?.alias; - - let needsUseNamedImport = false; - - const reactMemberCalls = rootNode.findAll({ - rule: { - kind: "call_expression", - has: { - field: "function", - kind: "member_expression", - all: [ - { - has: { - field: "object", - kind: "identifier", - regex: `^${reactDefaultName}$`, - }, - }, - { - has: { - field: "property", - kind: "property_identifier", - regex: "^useContext$", - }, - }, - ], - }, - }, - }); - - for (const call of reactMemberCalls) { - const propertyNode = call.find({ - rule: { - kind: "property_identifier", - regex: "^useContext$", - }, - }); - - if (propertyNode) { - edits.push(propertyNode.replace("use")); - transformMetric.increment({ - pattern: "React.useContext", - file: root.filename() - }); - } - } - - if (useContextLocalName) { - const useContextCalls = rootNode.findAll({ - rule: { - kind: "call_expression", - has: { - field: "function", - kind: "identifier", - regex: `^${useContextLocalName}$`, - }, - }, - }); - - const hasUseContextCalls = useContextCalls.length > 0; - - if (hasUseContextCalls) { - const isAliased = useContextImport && useContextImport.alias !== "useContext"; - - if (!isAliased) { - for (const call of useContextCalls) { - const callee = call.find({ - rule: { - kind: "identifier", - regex: `^${useContextLocalName}$`, - }, - }); - - if (callee) { - edits.push(callee.replace("use")); - transformMetric.increment({ - pattern: "useContext", - file: root.filename() - }); - } - } - } else { - transformMetric.increment({ - pattern: "useContext (aliased)", - file: root.filename() - }, useContextCalls.length); - } - - needsUseNamedImport = true; - } - } - - if (needsUseNamedImport && useContextImport) { - const importStatement = useContextImport.node.ancestors().find( - (a) => a.kind() === "import_statement" - ); - - if (importStatement) { - const importText = importStatement.text(); - let newImportText: string; - - if (useContextImport.alias !== "useContext") { - newImportText = importText.replace( - new RegExp(`\\buseContext\\s+as\\s+${useContextImport.alias}\\b`), - `use as ${useContextImport.alias}` - ); - } else { - newImportText = importText.replace(/\buseContext\b/, "use"); - } - - edits.push(importStatement.replace(newImportText)); - } - } - - if (edits.length === 0) { - return null; - } - - return rootNode.commitEdits(edits); -}; - -export default transform; diff --git a/use-context-hook/tests/aliased-useContext/expected.tsx b/use-context-hook/tests/aliased-useContext/expected.tsx deleted file mode 100644 index 36abebb..0000000 --- a/use-context-hook/tests/aliased-useContext/expected.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { use as useCtx } from "react"; -import ThemeContext from "./ThemeContext"; - -function MyComponent() { - const theme = useCtx(ThemeContext); - return
    {theme.name}
    ; -} - -export default MyComponent; diff --git a/use-context-hook/tests/aliased-useContext/input.tsx b/use-context-hook/tests/aliased-useContext/input.tsx deleted file mode 100644 index cff772e..0000000 --- a/use-context-hook/tests/aliased-useContext/input.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { useContext as useCtx } from "react"; -import ThemeContext from "./ThemeContext"; - -function MyComponent() { - const theme = useCtx(ThemeContext); - return
    {theme.name}
    ; -} - -export default MyComponent; diff --git a/use-context-hook/tests/aliased-useContext/metrics.json b/use-context-hook/tests/aliased-useContext/metrics.json deleted file mode 100644 index ce77a02..0000000 --- a/use-context-hook/tests/aliased-useContext/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "use-context-transformations": [ - { - "cardinality": { - "file": "tests/aliased-useContext/input.tsx", - "pattern": "useContext (aliased)" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/use-context-hook/tests/already-use/expected.tsx b/use-context-hook/tests/already-use/expected.tsx deleted file mode 100644 index 96f875b..0000000 --- a/use-context-hook/tests/already-use/expected.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { use } from "react"; -import ThemeContext from "./ThemeContext"; - -function MyComponent() { - const theme = use(ThemeContext); - - return
    {theme.color}
    ; -} diff --git a/use-context-hook/tests/already-use/input.tsx b/use-context-hook/tests/already-use/input.tsx deleted file mode 100644 index 96f875b..0000000 --- a/use-context-hook/tests/already-use/input.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { use } from "react"; -import ThemeContext from "./ThemeContext"; - -function MyComponent() { - const theme = use(ThemeContext); - - return
    {theme.color}
    ; -} diff --git a/use-context-hook/tests/basic-named-import/expected.tsx b/use-context-hook/tests/basic-named-import/expected.tsx deleted file mode 100644 index 656749d..0000000 --- a/use-context-hook/tests/basic-named-import/expected.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { use } from "react"; -import ThemeContext from "./ThemeContext"; - -function MyComponent() { - const theme = use(ThemeContext); - const user = use(UserContext); - - return
    {theme.color}
    ; -} diff --git a/use-context-hook/tests/basic-named-import/input.tsx b/use-context-hook/tests/basic-named-import/input.tsx deleted file mode 100644 index 25477f4..0000000 --- a/use-context-hook/tests/basic-named-import/input.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { useContext } from "react"; -import ThemeContext from "./ThemeContext"; - -function MyComponent() { - const theme = useContext(ThemeContext); - const user = useContext(UserContext); - - return
    {theme.color}
    ; -} diff --git a/use-context-hook/tests/basic-named-import/metrics.json b/use-context-hook/tests/basic-named-import/metrics.json deleted file mode 100644 index 5d003fa..0000000 --- a/use-context-hook/tests/basic-named-import/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "use-context-transformations": [ - { - "cardinality": { - "file": "tests/basic-named-import/input.tsx", - "pattern": "useContext" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/use-context-hook/tests/mixed-import/expected.tsx b/use-context-hook/tests/mixed-import/expected.tsx deleted file mode 100644 index cdbbe93..0000000 --- a/use-context-hook/tests/mixed-import/expected.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useState, use, useEffect } from "react"; -import ThemeContext from "./ThemeContext"; - -function MyComponent() { - const [count, setCount] = useState(0); - const theme = use(ThemeContext); - - useEffect(() => { - console.log(theme); - }, [theme]); - - return
    {count}
    ; -} diff --git a/use-context-hook/tests/mixed-import/input.tsx b/use-context-hook/tests/mixed-import/input.tsx deleted file mode 100644 index ab3e414..0000000 --- a/use-context-hook/tests/mixed-import/input.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useState, useContext, useEffect } from "react"; -import ThemeContext from "./ThemeContext"; - -function MyComponent() { - const [count, setCount] = useState(0); - const theme = useContext(ThemeContext); - - useEffect(() => { - console.log(theme); - }, [theme]); - - return
    {count}
    ; -} diff --git a/use-context-hook/tests/no-context/expected.tsx b/use-context-hook/tests/no-context/expected.tsx deleted file mode 100644 index bf75a9c..0000000 --- a/use-context-hook/tests/no-context/expected.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { useState } from "react"; - -function MyComponent() { - const [count, setCount] = useState(0); - - return
    {count}
    ; -} diff --git a/use-context-hook/tests/no-context/input.tsx b/use-context-hook/tests/no-context/input.tsx deleted file mode 100644 index bf75a9c..0000000 --- a/use-context-hook/tests/no-context/input.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { useState } from "react"; - -function MyComponent() { - const [count, setCount] = useState(0); - - return
    {count}
    ; -} diff --git a/use-context-hook/tests/original-any-use-context/expected.tsx b/use-context-hook/tests/original-any-use-context/expected.tsx deleted file mode 100644 index 8603179..0000000 --- a/use-context-hook/tests/original-any-use-context/expected.tsx +++ /dev/null @@ -1 +0,0 @@ -const theme = trpc.useContext(); diff --git a/use-context-hook/tests/original-any-use-context/input.tsx b/use-context-hook/tests/original-any-use-context/input.tsx deleted file mode 100644 index 8603179..0000000 --- a/use-context-hook/tests/original-any-use-context/input.tsx +++ /dev/null @@ -1 +0,0 @@ -const theme = trpc.useContext(); diff --git a/use-context-hook/tests/original-mixed-import/expected.tsx b/use-context-hook/tests/original-mixed-import/expected.tsx deleted file mode 100644 index 6eeb2ba..0000000 --- a/use-context-hook/tests/original-mixed-import/expected.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import React, { use } from "react"; -import ThemeContext from "./ThemeContext"; - -const theme = use(ThemeContext); diff --git a/use-context-hook/tests/original-mixed-import/input.tsx b/use-context-hook/tests/original-mixed-import/input.tsx deleted file mode 100644 index 0049e9b..0000000 --- a/use-context-hook/tests/original-mixed-import/input.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import React, { useContext } from "react"; -import ThemeContext from "./ThemeContext"; - -const theme = useContext(ThemeContext); diff --git a/use-context-hook/tests/original-mixed-import/metrics.json b/use-context-hook/tests/original-mixed-import/metrics.json deleted file mode 100644 index 22eacfd..0000000 --- a/use-context-hook/tests/original-mixed-import/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "use-context-transformations": [ - { - "cardinality": { - "file": "tests/original-mixed-import/input.tsx", - "pattern": "useContext" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/use-context-hook/tests/original-ts-use-context/expected.tsx b/use-context-hook/tests/original-ts-use-context/expected.tsx deleted file mode 100644 index 6ce9e77..0000000 --- a/use-context-hook/tests/original-ts-use-context/expected.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { use } from "react"; -import ThemeContext from "./ThemeContext"; - -function Component({ - appUrl, -}: { - appUrl: string; -}) { - const theme = use(ThemeContext); - return
    ; -}; diff --git a/use-context-hook/tests/original-ts-use-context/input.tsx b/use-context-hook/tests/original-ts-use-context/input.tsx deleted file mode 100644 index 4e28896..0000000 --- a/use-context-hook/tests/original-ts-use-context/input.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { useContext } from "react"; -import ThemeContext from "./ThemeContext"; - -function Component({ - appUrl, -}: { - appUrl: string; -}) { - const theme = useContext(ThemeContext); - return
    ; -}; diff --git a/use-context-hook/tests/original-ts-use-context/metrics.json b/use-context-hook/tests/original-ts-use-context/metrics.json deleted file mode 100644 index 0298e77..0000000 --- a/use-context-hook/tests/original-ts-use-context/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "use-context-transformations": [ - { - "cardinality": { - "file": "tests/original-ts-use-context/input.tsx", - "pattern": "useContext" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/use-context-hook/tests/original-use-context-2/expected.tsx b/use-context-hook/tests/original-use-context-2/expected.tsx deleted file mode 100644 index a126e89..0000000 --- a/use-context-hook/tests/original-use-context-2/expected.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import React from "react"; -import ThemeContext from "./ThemeContext"; - -const theme = React.use(ThemeContext); diff --git a/use-context-hook/tests/original-use-context-2/input.tsx b/use-context-hook/tests/original-use-context-2/input.tsx deleted file mode 100644 index 5aa2564..0000000 --- a/use-context-hook/tests/original-use-context-2/input.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import React from "react"; -import ThemeContext from "./ThemeContext"; - -const theme = React.useContext(ThemeContext); diff --git a/use-context-hook/tests/original-use-context-2/metrics.json b/use-context-hook/tests/original-use-context-2/metrics.json deleted file mode 100644 index eeb8696..0000000 --- a/use-context-hook/tests/original-use-context-2/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "use-context-transformations": [ - { - "cardinality": { - "file": "tests/original-use-context-2/input.tsx", - "pattern": "React.useContext" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/use-context-hook/tests/original-use-context/expected.tsx b/use-context-hook/tests/original-use-context/expected.tsx deleted file mode 100644 index bb7281c..0000000 --- a/use-context-hook/tests/original-use-context/expected.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import { use } from "react"; -import ThemeContext from "./ThemeContext"; - -const theme = use(ThemeContext); diff --git a/use-context-hook/tests/original-use-context/input.tsx b/use-context-hook/tests/original-use-context/input.tsx deleted file mode 100644 index c206438..0000000 --- a/use-context-hook/tests/original-use-context/input.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import { useContext } from "react"; -import ThemeContext from "./ThemeContext"; - -const theme = useContext(ThemeContext); diff --git a/use-context-hook/tests/original-use-context/metrics.json b/use-context-hook/tests/original-use-context/metrics.json deleted file mode 100644 index f2a8df1..0000000 --- a/use-context-hook/tests/original-use-context/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "use-context-transformations": [ - { - "cardinality": { - "file": "tests/original-use-context/input.tsx", - "pattern": "useContext" - }, - "count": 1 - } - ] -} \ No newline at end of file diff --git a/use-context-hook/tests/react-member-expression/expected.tsx b/use-context-hook/tests/react-member-expression/expected.tsx deleted file mode 100644 index 9982c7c..0000000 --- a/use-context-hook/tests/react-member-expression/expected.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; -import ThemeContext from "./ThemeContext"; - -function MyComponent() { - const theme = React.use(ThemeContext); - const user = React.use(UserContext); - - return
    {theme.color}
    ; -} diff --git a/use-context-hook/tests/react-member-expression/input.tsx b/use-context-hook/tests/react-member-expression/input.tsx deleted file mode 100644 index 8c1a117..0000000 --- a/use-context-hook/tests/react-member-expression/input.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; -import ThemeContext from "./ThemeContext"; - -function MyComponent() { - const theme = React.useContext(ThemeContext); - const user = React.useContext(UserContext); - - return
    {theme.color}
    ; -} diff --git a/use-context-hook/tests/react-member-expression/metrics.json b/use-context-hook/tests/react-member-expression/metrics.json deleted file mode 100644 index 7489301..0000000 --- a/use-context-hook/tests/react-member-expression/metrics.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "use-context-transformations": [ - { - "cardinality": { - "file": "tests/react-member-expression/input.tsx", - "pattern": "React.useContext" - }, - "count": 2 - } - ] -} \ No newline at end of file diff --git a/use-context-hook/tests/useContext-not-from-react/expected.tsx b/use-context-hook/tests/useContext-not-from-react/expected.tsx deleted file mode 100644 index 7ec397a..0000000 --- a/use-context-hook/tests/useContext-not-from-react/expected.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { useContext } from "some-other-lib"; -import ThemeContext from "./ThemeContext"; - -function MyComponent() { - const theme = useContext(ThemeContext); - return
    {theme.name}
    ; -} - -export default MyComponent; diff --git a/use-context-hook/tests/useContext-not-from-react/input.tsx b/use-context-hook/tests/useContext-not-from-react/input.tsx deleted file mode 100644 index 7ec397a..0000000 --- a/use-context-hook/tests/useContext-not-from-react/input.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { useContext } from "some-other-lib"; -import ThemeContext from "./ThemeContext"; - -function MyComponent() { - const theme = useContext(ThemeContext); - return
    {theme.name}
    ; -} - -export default MyComponent; diff --git a/use-context-hook/tsconfig.json b/use-context-hook/tsconfig.json deleted file mode 100644 index 469fc5a..0000000 --- a/use-context-hook/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", - "types": ["@codemod.com/jssg-types"], - "allowImportingTsExtensions": true, - "noEmit": true, - "verbatimModuleSyntax": true, - "erasableSyntaxOnly": true, - "strict": true, - "strictNullChecks": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true - }, - "exclude": ["tests"] -} diff --git a/use-context-hook/workflow.yaml b/use-context-hook/workflow.yaml deleted file mode 100644 index 8773841..0000000 --- a/use-context-hook/workflow.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# 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"