Skip to content

Commit

Permalink
Implement codemod transform which replaces enum usage with string lit…
Browse files Browse the repository at this point in the history
…eral (channel-io#1639)
  • Loading branch information
Dogdriip authored Sep 27, 2023
1 parent cca0db2 commit 28fa9cc
Show file tree
Hide file tree
Showing 11 changed files with 302 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/unlucky-crabs-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@channel.io/bezier-codemod": minor
---

Implement codemod transform which replaces enum usage with string literal
36 changes: 36 additions & 0 deletions packages/bezier-codemod/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,39 @@ import { Button, Icon, IconSize } from '@channel.io/bezier-react'

import Foo from './foo'
```

### Enum Member to String Literal

**`enum-member-to-string-literal`**

Replace deprecated enum usage to string literal.

For example:

```tsx
import { ProgressBar, ProgressBarSize, ProgressBarVariant } from '@channel.io/bezier-react'

export default () => (
<ProgressBar
width='100%'
size={ProgressBarSize.M}
variant={ProgressBarVariant.GreenAlt}
value={uploadProgressPercentage / 100}
/>
)
```

Transforms into:

```tsx
import { ProgressBar } from '@channel.io/bezier-react'

export default () => (
<ProgressBar
width='100%'
size='m'
variant='green-alt'
value={uploadProgressPercentage / 100}
/>
)
```
3 changes: 3 additions & 0 deletions packages/bezier-codemod/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from 'ink'

import project from './project.js'
import enumMemberToStringLiteral from './transforms/enum-member-to-string-literal.js'
import iconNameToBezierIcon from './transforms/icon-name-to-bezier-icon.js'
import iconsToBezierIcons from './transforms/icons-to-bezier-icons.js'

Expand All @@ -31,6 +32,7 @@ enum Step {
enum Option {
IconsToBezierIcons = 'icons-to-bezier-icons',
IconNameToBezierIcon = 'icon-name-to-bezier-icon',
EnumMemberToStringLiteral = 'enum-member-to-string-literal',
Exit = 'Exit',
}

Expand All @@ -39,6 +41,7 @@ type TransformName = Exclude<Option, Option.Exit>
const transformMap = {
[Option.IconsToBezierIcons]: iconsToBezierIcons,
[Option.IconNameToBezierIcon]: iconNameToBezierIcon,
[Option.EnumMemberToStringLiteral]: enumMemberToStringLiteral,
}

const options = (Object.keys(transformMap) as Option[]).map((transformName) => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
Node,
type SourceFile,
SyntaxKind,
} from 'ts-morph'

type EnumTransforms = Record<string, Record<string, string>>

function transformEnumMemberToStringLiteral(sourceFile: SourceFile, enumTransforms: EnumTransforms) {
const transformedEnumNames: string[] = []

sourceFile.forEachDescendant((node) => {
if (node.isKind(SyntaxKind.PropertyAccessExpression)) {
const firstIdentifier = node.getFirstChildByKind(SyntaxKind.Identifier)
const lastIdentifier = node.getLastChildByKind(SyntaxKind.Identifier)

if (firstIdentifier && lastIdentifier) {
const declarationSymbol = firstIdentifier.getSymbol()
const memberSymbol = lastIdentifier.getSymbol()
const memberValueDeclaration = memberSymbol?.getValueDeclaration()

if (Node.isEnumMember(memberValueDeclaration)) {
const enumName = declarationSymbol?.getName()
const enumMember = memberSymbol?.getName()

if (enumName && enumMember) {
const newEnumMemberValue = enumTransforms[enumName][enumMember]
const ancestor = node.getFirstAncestor()
if (ancestor?.isKind(SyntaxKind.JsxExpression)) {
ancestor.replaceWithText(`'${newEnumMemberValue}'`)
} else {
node.replaceWithText(`'${newEnumMemberValue}'`)
}

transformedEnumNames.push(enumName)
}
}
}
}
})

if (transformedEnumNames.length > 0) {
sourceFile.fixUnusedIdentifiers()
return true
}

return undefined
}

function enumMemberToStringLiteral(sourceFile: SourceFile): true | void {
const enumTransforms: EnumTransforms = {
ProgressBarSize: {
M: 'm',
S: 's',
},
ProgressBarVariant: {
Green: 'green',
GreenAlt: 'green-alt',
Monochrome: 'monochrome',
},
}
return transformEnumMemberToStringLiteral(sourceFile, enumTransforms)
}

export default enumMemberToStringLiteral
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import fs from 'fs'
import path from 'path'

import project from '../../../project.js'
import enumMemberToStringLiteral from '../../enum-member-to-string-literal.js'

describe('enumMemberToStringLiteral', () => {
it('should transform enum members to string literals correctly', () => {
const inputPath = path.join(__dirname, 'fixtures', 'input1.tsx')
const outputPath = path.join(__dirname, 'fixtures', 'output1.tsx')

const inputCode = fs.readFileSync(inputPath, 'utf-8')
const outputCode = fs.readFileSync(outputPath, 'utf-8')
const sourceFile = project.createSourceFile('test.tsx', inputCode, { overwrite: true })

const isMigrated = enumMemberToStringLiteral(sourceFile)

expect(sourceFile.getFullText()).toBe(outputCode)
expect(isMigrated).toBe(true)
})

it('should transform enum members to string literals correctly with type imports', () => {
const inputPath = path.join(__dirname, 'fixtures', 'input2.tsx')
const outputPath = path.join(__dirname, 'fixtures', 'output2.tsx')

const inputCode = fs.readFileSync(inputPath, 'utf-8')
const outputCode = fs.readFileSync(outputPath, 'utf-8')
const sourceFile = project.createSourceFile('test.tsx', inputCode, { overwrite: true })

const isMigrated = enumMemberToStringLiteral(sourceFile)

expect(sourceFile.getFullText()).toBe(outputCode)
expect(isMigrated).toBe(true)
})

it('should transform enum members to string literals correctly with mixed imports', () => {
const inputPath = path.join(__dirname, 'fixtures', 'input3.tsx')
const outputPath = path.join(__dirname, 'fixtures', 'output3.tsx')

const inputCode = fs.readFileSync(inputPath, 'utf-8')
const outputCode = fs.readFileSync(outputPath, 'utf-8')
const sourceFile = project.createSourceFile('test.tsx', inputCode, { overwrite: true })

const isMigrated = enumMemberToStringLiteral(sourceFile)

expect(sourceFile.getFullText()).toBe(outputCode)
expect(isMigrated).toBe(true)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react'
import { ProgressBar, ProgressBarSize, ProgressBarVariant } from '@channel.io/bezier-react'

export default function UploadProgress({
uploadProgressPercentage,
}: {
uploadProgressPercentage: number
}) {
return (
<ProgressBar
width='100%'
size={ProgressBarSize.M}
variant={ProgressBarVariant.GreenAlt}
value={uploadProgressPercentage / 100}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react'
import { ProgressBar, type ProgressBarSize, ProgressBarVariant } from '@channel.io/bezier-react'

export default function UploadProgress({
uploadProgressPercentage,
size,
}: {
uploadProgressPercentage: number
size: ProgressBarSize
}) {
return (
<ProgressBar
width='100%'
size={size}
variant={ProgressBarVariant.GreenAlt}
value={uploadProgressPercentage / 100}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react'
import { ProgressBar, ProgressBarSize, ProgressBarVariant } from '@channel.io/bezier-react'

export default function UploadProgress({
uploadProgressPercentage,
variant,
}: {
uploadProgressPercentage: number
variant: ProgressBarVariant
}) {
if (variant === ProgressBarVariant.Monochrome) {
return (
<>
<p>Monochrome ProgressBar</p>
<ProgressBar
width='100%'
size={ProgressBarSize.M}
variant={ProgressBarVariant.Monochrome}
value={uploadProgressPercentage / 100}
/>
</>
)
}

return (
<>
<p>Colored ProgressBar</p>
<ProgressBar
width='100%'
size={ProgressBarSize.M}
variant={variant}
value={uploadProgressPercentage / 100}
/>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react'
import { ProgressBar } from '@channel.io/bezier-react'

export default function UploadProgress({
uploadProgressPercentage,
}: {
uploadProgressPercentage: number
}) {
return (
<ProgressBar
width='100%'
size='m'
variant='green-alt'
value={uploadProgressPercentage / 100}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react'
import { ProgressBar, type ProgressBarSize } from '@channel.io/bezier-react'

export default function UploadProgress({
uploadProgressPercentage,
size,
}: {
uploadProgressPercentage: number
size: ProgressBarSize
}) {
return (
<ProgressBar
width='100%'
size={size}
variant='green-alt'
value={uploadProgressPercentage / 100}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react'
import { ProgressBar, ProgressBarVariant } from '@channel.io/bezier-react'

export default function UploadProgress({
uploadProgressPercentage,
variant,
}: {
uploadProgressPercentage: number
variant: ProgressBarVariant
}) {
if (variant === 'monochrome') {
return (
<>
<p>Monochrome ProgressBar</p>
<ProgressBar
width='100%'
size='m'
variant='monochrome'
value={uploadProgressPercentage / 100}
/>
</>
)
}

return (
<>
<p>Colored ProgressBar</p>
<ProgressBar
width='100%'
size='m'
variant={variant}
value={uploadProgressPercentage / 100}
/>
</>
)
}

0 comments on commit 28fa9cc

Please sign in to comment.