From 28fa9cc9405aa54fd7d1556c5666176b6cc18577 Mon Sep 17 00:00:00 2001 From: Aru Hyunseung Jeon Date: Wed, 27 Sep 2023 14:31:52 +0900 Subject: [PATCH] Implement codemod transform which replaces enum usage with string literal (#1639) --- .changeset/unlucky-crabs-mate.md | 5 ++ packages/bezier-codemod/README.md | 36 ++++++++++ packages/bezier-codemod/src/App.tsx | 3 + .../enum-member-to-string-literal.ts | 65 +++++++++++++++++++ .../enum-member-to-string-literal.test.ts | 49 ++++++++++++++ .../fixtures/input1.tsx | 17 +++++ .../fixtures/input2.tsx | 19 ++++++ .../fixtures/input3.tsx | 36 ++++++++++ .../fixtures/output1.tsx | 17 +++++ .../fixtures/output2.tsx | 19 ++++++ .../fixtures/output3.tsx | 36 ++++++++++ 11 files changed, 302 insertions(+) create mode 100644 .changeset/unlucky-crabs-mate.md create mode 100644 packages/bezier-codemod/src/transforms/enum-member-to-string-literal.ts create mode 100644 packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/enum-member-to-string-literal.test.ts create mode 100644 packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/input1.tsx create mode 100644 packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/input2.tsx create mode 100644 packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/input3.tsx create mode 100644 packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/output1.tsx create mode 100644 packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/output2.tsx create mode 100644 packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/output3.tsx diff --git a/.changeset/unlucky-crabs-mate.md b/.changeset/unlucky-crabs-mate.md new file mode 100644 index 0000000000..4ce6f4daf2 --- /dev/null +++ b/.changeset/unlucky-crabs-mate.md @@ -0,0 +1,5 @@ +--- +"@channel.io/bezier-codemod": minor +--- + +Implement codemod transform which replaces enum usage with string literal diff --git a/packages/bezier-codemod/README.md b/packages/bezier-codemod/README.md index 2c97e44752..31cb2d9203 100644 --- a/packages/bezier-codemod/README.md +++ b/packages/bezier-codemod/README.md @@ -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 () => ( + +) +``` + +Transforms into: + +```tsx +import { ProgressBar } from '@channel.io/bezier-react' + +export default () => ( + +) +``` diff --git a/packages/bezier-codemod/src/App.tsx b/packages/bezier-codemod/src/App.tsx index b240f9cc85..0467dd949f 100644 --- a/packages/bezier-codemod/src/App.tsx +++ b/packages/bezier-codemod/src/App.tsx @@ -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' @@ -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', } @@ -39,6 +41,7 @@ type TransformName = Exclude const transformMap = { [Option.IconsToBezierIcons]: iconsToBezierIcons, [Option.IconNameToBezierIcon]: iconNameToBezierIcon, + [Option.EnumMemberToStringLiteral]: enumMemberToStringLiteral, } const options = (Object.keys(transformMap) as Option[]).map((transformName) => ({ diff --git a/packages/bezier-codemod/src/transforms/enum-member-to-string-literal.ts b/packages/bezier-codemod/src/transforms/enum-member-to-string-literal.ts new file mode 100644 index 0000000000..d962097225 --- /dev/null +++ b/packages/bezier-codemod/src/transforms/enum-member-to-string-literal.ts @@ -0,0 +1,65 @@ +import { + Node, + type SourceFile, + SyntaxKind, +} from 'ts-morph' + +type EnumTransforms = Record> + +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 diff --git a/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/enum-member-to-string-literal.test.ts b/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/enum-member-to-string-literal.test.ts new file mode 100644 index 0000000000..b23cfc930c --- /dev/null +++ b/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/enum-member-to-string-literal.test.ts @@ -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) + }) +}) diff --git a/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/input1.tsx b/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/input1.tsx new file mode 100644 index 0000000000..2703c38629 --- /dev/null +++ b/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/input1.tsx @@ -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 ( + + ) +} diff --git a/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/input2.tsx b/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/input2.tsx new file mode 100644 index 0000000000..2f38b32eb5 --- /dev/null +++ b/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/input2.tsx @@ -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 ( + + ) +} diff --git a/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/input3.tsx b/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/input3.tsx new file mode 100644 index 0000000000..10ff873e94 --- /dev/null +++ b/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/input3.tsx @@ -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 ( + <> +

Monochrome ProgressBar

+ + + ) + } + + return ( + <> +

Colored ProgressBar

+ + + ) +} diff --git a/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/output1.tsx b/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/output1.tsx new file mode 100644 index 0000000000..bcd2ba57f8 --- /dev/null +++ b/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/output1.tsx @@ -0,0 +1,17 @@ +import React from 'react' +import { ProgressBar } from '@channel.io/bezier-react' + +export default function UploadProgress({ + uploadProgressPercentage, +}: { + uploadProgressPercentage: number +}) { + return ( + + ) +} diff --git a/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/output2.tsx b/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/output2.tsx new file mode 100644 index 0000000000..af2b17023e --- /dev/null +++ b/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/output2.tsx @@ -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 ( + + ) +} diff --git a/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/output3.tsx b/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/output3.tsx new file mode 100644 index 0000000000..41bd08dd8e --- /dev/null +++ b/packages/bezier-codemod/src/transforms/tests/enum-member-to-string-literal/fixtures/output3.tsx @@ -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 ( + <> +

Monochrome ProgressBar

+ + + ) + } + + return ( + <> +

Colored ProgressBar

+ + + ) +}