Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ lint-staged.config.js
jest.config.js
jest.config.react18.js
babel.config.js
codemods/__testfixtures__/
plopfile.mjs
release.config.js
coverage/
Expand Down
90 changes: 90 additions & 0 deletions codemods/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Text Variants codemod

Migrates the breaking Text, Heading, and Display APIs to the consolidated Text component and its
named variants.

## Run

First, run a strict dry run. It exits with an error if parsing fails or manual work remains.

```bash
npx jscodeshift@17.3.0 \
--dry \
--run-in-band \
--verbose=0 \
--fail-on-error \
--fail-on-manual=true \
--transform ./node_modules/@doist/reactist/codemods/text-variants.ts \
--extensions js,jsx,ts,tsx \
--parser tsx \
src
```

The dry-run summary groups every manual migration by reason. Remove
`--fail-on-manual=true` when you want to inspect the automatic changes before you resolve the
remaining cases.

Apply the transform after you review the dry run:

```bash
npx jscodeshift@17.3.0 \
--transform ./node_modules/@doist/reactist/codemods/text-variants.ts \
--extensions js,jsx,ts,tsx \
--parser tsx \
src
```

## Mappings

| Legacy Text size | Regular or omitted | Semibold | Bold |
| ---------------- | ------------------ | ----------- | ----------- |
| subtitle | subheader-2 | subheader-1 | subheader-1 |
| body or omitted | body-3 | body-2 | body-1 |
| copy | callout-2 | callout-1 | callout-1 |
| caption | caption-3 | caption-2 | caption-1 |

Bold subtitle and copy text use the nearest named variant and change from 700 to 600 weight. Other
Text mappings preserve size and weight.

| Legacy Heading metrics | Text variant |
| ---------------------- | ------------ |
| 32px/700 | header-1 |
| 24px/700 | header-2 |
| 20px/700 | header-3 |
| 16px/700 or 16px/600 | subheader-1 |
| 16px/400 | subheader-2 |
| 14px/700 | body-1 |
| 14px/600 | body-2 |
| 14px/400 | body-3 |
| 12px/700 | caption-1 |
| 12px/600 | caption-2 |
| 12px/400 | caption-3 |

The 24px/700 mapping changes to 26px/700. The 16px/700 mapping changes to 16px/600. Other Heading
mappings preserve size and weight. The transform preserves the original semantic heading level
with `render={<hN />}` when the variant does not render that element by default.

Existing named Heading variants map directly from `heading-1` through `heading-4` to `header-1`
through `header-4`. Display variants keep their `display-1` through `display-5` names. Heading and
Display imports with only direct, fully migrated JSX uses merge into one Text import. Aliased,
commented, indirect, or unresolved bindings keep their local names.

Literal conditional expressions also migrate when every branch has an exact mapping. This includes
an `undefined` branch, which uses the legacy default. The transform does not trace variables or
infer values across files.

Static Text `as` values migrate to `render`. Props for the rendered element move into that element.
Text styling props, `key`, and `ref` stay on Text. Dynamic targets, existing `render` props, and prop
spreads remain manual.

## Manual migrations

The transform changes only documented mappings. Unsupported size/weight combinations, non-finite
dynamic expressions, duplicate props, and prop spreads remain unchanged. Each unresolved use gets
a nearby TODO(reactist-codemod) comment, and the command prints its file and line. Direct Heading JSX
uses still migrate when the same import has indirect references. The indirect statements get TODOs
for manual migration. Namespace references and removed Heading and Display types also get TODOs.
References such as `TextProps['size']` and `Pick<TextProps, 'size' | 'weight'>` get precise TODOs
because the replacement type depends on the consumer API.

The transform supports imports from `@doist/reactist`. Deep imports are outside its scope.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as React from 'react'

import {
/* display import */ Display,
Display as Hero,
/* heading import */ Heading,
Heading as Title,
Text,
} from '@doist/reactist'

export function ConsolidatedComponents() {
return (
<>
<Heading level={1} variant="heading-1">
Header 1
</Heading>
<Heading level={2} variant="heading-2">
Header 2
</Heading>
<Heading level="3" variant="heading-3">
Header 3
</Heading>
<Heading level={6} variant="heading-4">
Header 4
</Heading>
<Title variant="heading-2" render={<button type="button" />}>
Custom header
</Title>
<Display variant="display-1">Display 1</Display>
<Display variant="display-2">Display 2</Display>
<Display variant="display-3">Display 3</Display>
<Display variant="display-4">Display 4</Display>
<Hero variant="display-5" render={<h1 />}>
Display 5
</Hero>
<Text variant="body-1">Body</Text>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react'

import {
/* display import */ Text as Display,
Text as Hero,
/* heading import */ Text as Heading,
Text as Title,
Text,
} from '@doist/reactist'

export function ConsolidatedComponents() {
return (
<>
<Heading variant="header-1">Header 1</Heading>
<Heading variant="header-2">Header 2</Heading>
<Heading variant="header-3">Header 3</Heading>
<Heading variant="header-4" render={<h6 />}>
Header 4
</Heading>
<Title variant="header-2" render={<button type="button" />}>
Custom header
</Title>
<Display variant="display-1">Display 1</Display>
<Display variant="display-2">Display 2</Display>
<Display variant="display-3">Display 3</Display>
<Display variant="display-4">Display 4</Display>
<Hero variant="display-5" render={<h1 />}>
Display 5
</Hero>
<Text variant="body-1">Body</Text>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Heading, Heading as Title } from '@doist/reactist'

type Labels = {
Heading: string
Title: string
}

const labels: Labels = {
Heading: 'Heading',
Title: 'Title',
}

const memberLabels = [labels.Heading, labels.Title]
const { Heading: headingLabel, Title: titleLabel } = labels
const UI = { Heading: 'div', Title: 'span' }

export function HeadingNameKeys() {
return (
<>
<Heading level={1} size="largest">
{headingLabel}
</Heading>
<Title variant="heading-2">{titleLabel}</Title>
<UI.Heading />
<UI.Title />
<div Heading="Heading" Title="Title" />
{memberLabels.join(', ')}
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Text, Text as Title } from '@doist/reactist'

type Labels = {
Heading: string
Title: string
}

const labels: Labels = {
Heading: 'Heading',
Title: 'Title',
}

const memberLabels = [labels.Heading, labels.Title]
const { Heading: headingLabel, Title: titleLabel } = labels
const UI = { Heading: 'div', Title: 'span' }

export function HeadingNameKeys() {
return (
<>
<Text variant="header-1">{headingLabel}</Text>
<Title variant="header-2" render={<h1 />}>
{titleLabel}
</Title>
<UI.Heading />
<UI.Title />
<div Heading="Heading" Title="Title" />
{memberLabels.join(', ')}
</>
)
}
53 changes: 53 additions & 0 deletions codemods/__testfixtures__/text-variants-heading.input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as React from 'react'

import { Heading, Heading as Title } from '@doist/reactist'

export function ExactHeadings() {
return (
<>
<Heading level={1} size="largest">
Large
</Heading>
<Heading level={1}>Current default</Heading>
<Title level={2} size="larger">
Visual 20
</Title>
<Heading level={4} size="largest" weight="regular">
Visual 20
</Heading>
<Heading level={1} size="larger">
Header 2 rendered as h1
</Heading>
<Heading level={2} size="largest">
Header 2 rendered as h2
</Heading>
<Heading level={2}>Subheader 1 from bold</Heading>
<Heading level={3} size="larger" weight="medium">
Subheader 1 from medium
</Heading>
<Heading level={2} weight="light">
Subheader 2
</Heading>
<Heading level={3}>Body 1</Heading>
<Heading level={4} weight="medium">
Body 2
</Heading>
<Heading level={5}>Body 1 rendered as h5</Heading>
<Heading level={6} size="larger">
Subheader 1 rendered as h6
</Heading>
<Heading level={2} size="smaller" weight="light">
Body 3
</Heading>
<Heading level={3} size="smaller">
Caption 1
</Heading>
<Heading level={3} size="smaller" weight="medium">
Caption 2
</Heading>
<Heading level={3} size="smaller" weight="light">
Caption 3
</Heading>
</>
)
}
57 changes: 57 additions & 0 deletions codemods/__testfixtures__/text-variants-heading.output.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as React from 'react'

import { Text, Text as Title } from '@doist/reactist'

export function ExactHeadings() {
return (
<>
<Text variant="header-1">Large</Text>
<Text variant="header-3" render={<h1 />}>
Current default
</Text>
<Title variant="header-3" render={<h2 />}>
Visual 20
</Title>
<Text variant="header-3" render={<h4 />}>
Visual 20
</Text>
<Text variant="header-2" render={<h1 />}>
Header 2 rendered as h1
</Text>
<Text variant="header-2">Header 2 rendered as h2</Text>
<Text variant="subheader-1" render={<h2 />}>
Subheader 1 from bold
</Text>
<Text variant="subheader-1" render={<h3 />}>
Subheader 1 from medium
</Text>
<Text variant="subheader-2" render={<h2 />}>
Subheader 2
</Text>
<Text variant="body-1" render={<h3 />}>
Body 1
</Text>
<Text variant="body-2" render={<h4 />}>
Body 2
</Text>
<Text variant="body-1" render={<h5 />}>
Body 1 rendered as h5
</Text>
<Text variant="subheader-1" render={<h6 />}>
Subheader 1 rendered as h6
</Text>
<Text variant="body-3" render={<h2 />}>
Body 3
</Text>
<Text variant="caption-1" render={<h3 />}>
Caption 1
</Text>
<Text variant="caption-2" render={<h3 />}>
Caption 2
</Text>
<Text variant="caption-3" render={<h3 />}>
Caption 3
</Text>
</>
)
}
14 changes: 14 additions & 0 deletions codemods/__testfixtures__/text-variants-idempotence.input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Text as Heading } from '@doist/reactist'

export function HeadingVariants() {
return (
<>
<Heading variant="header-1" render={<h1 />}>
Page title
</Heading>
<Heading variant="header-3" render={<h4 />}>
Prominent subsection
</Heading>
</>
)
}
14 changes: 14 additions & 0 deletions codemods/__testfixtures__/text-variants-idempotence.output.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Text as Heading } from '@doist/reactist'

export function HeadingVariants() {
return (
<>
<Heading variant="header-1" render={<h1 />}>
Page title
</Heading>
<Heading variant="header-3" render={<h4 />}>
Prominent subsection
</Heading>
</>
)
}
21 changes: 21 additions & 0 deletions codemods/__testfixtures__/text-variants-indirect-heading.input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as React from 'react'

import { Heading, Text } from '@doist/reactist'

const Title = Heading
const title = React.createElement(Heading, { level: 1, size: 'largest' }, 'Created title')

export function IndirectHeading() {
return (
<>
<Heading level={1} size="largest">
Direct title
</Heading>
<Title level={1} size="largest">
Aliased title
</Title>
{title}
<Text>Body</Text>
</>
)
}
Loading
Loading