diff --git a/.changeset/slimy-lobsters-sparkle.md b/.changeset/slimy-lobsters-sparkle.md new file mode 100644 index 0000000000..08606e260b --- /dev/null +++ b/.changeset/slimy-lobsters-sparkle.md @@ -0,0 +1,5 @@ +--- +"@channel.io/bezier-codemod": minor +--- + +Add codemod for z-index interpolation and enum diff --git a/packages/bezier-codemod/README.md b/packages/bezier-codemod/README.md index c737065242..084e5086e7 100644 --- a/packages/bezier-codemod/README.md +++ b/packages/bezier-codemod/README.md @@ -203,6 +203,37 @@ const Wrapper = styled.div` `; ``` +**`v2-z-index-interpolation-to-css-variable`** + +Replace z-index interpolation to css variable +For example: + +```tsx +import { ZIndex, styled } from "@channel.io/bezier-react"; + +const OVERLAY_POSITION = { + zIndex: ZIndex.Modal, +}; + +const Wrapper = styled.div` + z-index: ${ZIndex.Hide}; +`; +``` + +Transforms into: + +```tsx +import { styled } from "@channel.io/bezier-react"; + +const OVERLAY_POSITION = { + zIndex: "var(--z-index-modal)", +}; + +const Wrapper = styled.div` + z-index: var(--z-index-hidden); +`; +``` + ### Styled from @channel.io/bezier-react to styled-components **`v2-styled-to-styled-components`** diff --git a/packages/bezier-codemod/src/App.tsx b/packages/bezier-codemod/src/App.tsx index 32aa10b56e..16b7b2282b 100644 --- a/packages/bezier-codemod/src/App.tsx +++ b/packages/bezier-codemod/src/App.tsx @@ -31,6 +31,7 @@ import styledToStyledComponents from './transforms/v2-import-styled-from-styled- import inputInterpolationToCssVariable from './transforms/v2-input-interpolation-to-css-variable/transform.js' import removeAlphaFromAlphaStack from './transforms/v2-remove-alpha-from-alpha-stack/transform.js' import typographyInterpolationToCssVariable from './transforms/v2-typography-interpolation-to-css-variable/transform.js' +import zIndexInterpolationToCssVariable from './transforms/v2-z-index-interpolation-to-css-variable/transform.js' enum Step { SelectTransformer, @@ -52,7 +53,8 @@ enum Option { V2InputInterpolationToCssVariable = 'v2-input-interpolation-to-css-variable', V2TypographyInterpolationToCssVariable = 'v2-typography-interpolation-to-css-variable', V2StyledToStyledComponents = 'v2-styled-to-styled-components', - V2RemoveAlphaFromAlphaStack = 'remove-alpha-from-alpha-stack', + V2RemoveAlphaFromAlphaStack = 'v2-remove-alpha-from-alpha-stack', + V2ZIndexInterpolationToCssVariable = 'v2-z-index-interpolation-to-css-variable', Exit = 'Exit', } @@ -72,6 +74,7 @@ const transformMap = { [Option.V2TypographyInterpolationToCssVariable]: typographyInterpolationToCssVariable, [Option.V2StyledToStyledComponents]: styledToStyledComponents, [Option.V2RemoveAlphaFromAlphaStack]: removeAlphaFromAlphaStack, + [Option.V2ZIndexInterpolationToCssVariable]: zIndexInterpolationToCssVariable, } const options = (Object.keys(transformMap) as Option[]).map((transformName) => ({ diff --git a/packages/bezier-codemod/src/shared/enum.ts b/packages/bezier-codemod/src/shared/enum.ts new file mode 100644 index 0000000000..f50acb0692 --- /dev/null +++ b/packages/bezier-codemod/src/shared/enum.ts @@ -0,0 +1,40 @@ +import { + type SourceFile, + SyntaxKind, +} from 'ts-morph' + +import { renameEnumMember } from '../utils/enum.js' +import { hasNamedImportInImportDeclaration } from '../utils/import.js' + +type Name = string +type Member = string +type Value = string +export type EnumTransformMap = Record> + +export const transformEnumMemberToStringLiteral = (sourceFile: SourceFile, enumTransforms: EnumTransformMap) => { + const transformedEnumNames: string[] = [] + + Object + .keys(enumTransforms) + .forEach((enumName) => { + if (hasNamedImportInImportDeclaration(sourceFile, enumName, '@channel.io/bezier-react')) { + sourceFile + .getDescendantsOfKind(SyntaxKind.PropertyAccessExpression) + .filter((node) => node.getFirstChildByKind(SyntaxKind.Identifier)?.getText() === enumName) + .forEach((node) => { + const enumValue = node.getLastChildByKind(SyntaxKind.Identifier)?.getText() + if (enumValue) { + renameEnumMember(node, enumTransforms[enumName][enumValue]) + transformedEnumNames.push(enumName) + } + }) + } + }) + + if (transformedEnumNames.length > 0) { + sourceFile.fixUnusedIdentifiers() + return true + } + + return undefined +} diff --git a/packages/bezier-codemod/src/transforms/enum-member-to-string-literal/transform.ts b/packages/bezier-codemod/src/transforms/enum-member-to-string-literal/transform.ts index d962097225..3d90a06a38 100644 --- a/packages/bezier-codemod/src/transforms/enum-member-to-string-literal/transform.ts +++ b/packages/bezier-codemod/src/transforms/enum-member-to-string-literal/transform.ts @@ -1,65 +1,24 @@ -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() +import { type SourceFile } from 'ts-morph' - 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 +import { + type EnumTransformMap, + transformEnumMemberToStringLiteral, +} from '../../shared/enum.js' + +const ENUM_TRANSFORM_MAP: EnumTransformMap = { + ProgressBarSize: { + M: 'm', + S: 's', + }, + ProgressBarVariant: { + Green: 'green', + GreenAlt: 'green-alt', + Monochrome: 'monochrome', + }, } 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) + return transformEnumMemberToStringLiteral(sourceFile, ENUM_TRANSFORM_MAP) } export default enumMemberToStringLiteral diff --git a/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum-as-prop.input.tsx b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum-as-prop.input.tsx new file mode 100644 index 0000000000..68411866b9 --- /dev/null +++ b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum-as-prop.input.tsx @@ -0,0 +1,9 @@ +import { Overlay, ZIndex } from '@channel.io/bezier-react' + +export function SelectionOverlay () { + return ( + + ) +} \ No newline at end of file diff --git a/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum-as-prop.output.tsx b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum-as-prop.output.tsx new file mode 100644 index 0000000000..38277f8952 --- /dev/null +++ b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum-as-prop.output.tsx @@ -0,0 +1,9 @@ +import { Overlay } from '@channel.io/bezier-react' + +export function SelectionOverlay () { + return ( + + ) +} \ No newline at end of file diff --git a/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum-not-from-bezier-react.input.tsx b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum-not-from-bezier-react.input.tsx new file mode 100644 index 0000000000..80c21c7c36 --- /dev/null +++ b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum-not-from-bezier-react.input.tsx @@ -0,0 +1,5 @@ +import { ZIndex } from 'some-library' + +export const OVERLAY_POSITION1 = { + zIndex: ZIndex.Modal, +} diff --git a/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum-not-from-bezier-react.output.tsx b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum-not-from-bezier-react.output.tsx new file mode 100644 index 0000000000..80c21c7c36 --- /dev/null +++ b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum-not-from-bezier-react.output.tsx @@ -0,0 +1,5 @@ +import { ZIndex } from 'some-library' + +export const OVERLAY_POSITION1 = { + zIndex: ZIndex.Modal, +} diff --git a/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum.input.tsx b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum.input.tsx new file mode 100644 index 0000000000..45ff45aaab --- /dev/null +++ b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum.input.tsx @@ -0,0 +1,17 @@ +import { ZIndex } from "@channel.io/bezier-react" + +export const OVERLAY_POSITION1 = { + zIndex: ZIndex.Modal, +} + +export const OVERLAY_POSITION2 = { + zIndex: ZIndex.Float, +} + +export const OVERLAY_POSITION3 = { + zIndex: ZIndex.Important, +} + +export const OVERLAY_POSITION4 = { + zIndex: ZIndex.Hide, +} diff --git a/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum.output.tsx b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum.output.tsx new file mode 100644 index 0000000000..1ccb0e9e55 --- /dev/null +++ b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-enum.output.tsx @@ -0,0 +1,16 @@ + +export const OVERLAY_POSITION1 = { + zIndex: 'var(--z-index-modal)', +} + +export const OVERLAY_POSITION2 = { + zIndex: 'var(--z-index-float)', +} + +export const OVERLAY_POSITION3 = { + zIndex: 'var(--z-index-important)', +} + +export const OVERLAY_POSITION4 = { + zIndex: 'var(--z-index-hidden)', +} diff --git a/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-interpolation-in-styled-component.input.tsx b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-interpolation-in-styled-component.input.tsx new file mode 100644 index 0000000000..06387c43a4 --- /dev/null +++ b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-interpolation-in-styled-component.input.tsx @@ -0,0 +1,21 @@ +import { ZIndex, styled } from "@channel.io/bezier-react"; + +const Wrapper = styled.div` + z-index: ${ZIndex.Hide} +` + +const Wrapper = styled.div` + z-index: ${ZIndex.Base}; +` + +const Wrapper = styled.div` + z-index: ${ZIndex.Float}; +` + +const Wrapper = styled.div` + z-index: ${ZIndex.Tooltip} +` + +const Wrapper = styled.div` + z-index: ${ZIndex.Important} +` diff --git a/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-interpolation-in-styled-component.output.tsx b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-interpolation-in-styled-component.output.tsx new file mode 100644 index 0000000000..598105c81f --- /dev/null +++ b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/fixtures/z-index-interpolation-in-styled-component.output.tsx @@ -0,0 +1,21 @@ +import { styled } from "@channel.io/bezier-react"; + +const Wrapper = styled.div` + z-index: var(--z-index-hidden); +` + +const Wrapper = styled.div` + z-index: var(--z-index-base); +` + +const Wrapper = styled.div` + z-index: var(--z-index-float); +` + +const Wrapper = styled.div` + z-index: var(--z-index-tooltip); +` + +const Wrapper = styled.div` + z-index: var(--z-index-important); +` diff --git a/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/transform.test.ts b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/transform.test.ts new file mode 100644 index 0000000000..591bb256a6 --- /dev/null +++ b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/transform.test.ts @@ -0,0 +1,21 @@ +import { testTransformFunction } from '../../utils/test.js' + +import interpolationTransform from './transform.js' + +describe('z-index interpolation transform', () => { + it('should transform z-index interpolation to css variable in styled-component', () => { + testTransformFunction(__dirname, 'z-index-interpolation-in-styled-component', interpolationTransform) + }) + + it('should transform z-index enum to css variable', () => { + testTransformFunction(__dirname, 'z-index-enum', interpolationTransform) + }) + + it('should transform z-index enum to css variable when used as prop', () => { + testTransformFunction(__dirname, 'z-index-enum-as-prop', interpolationTransform) + }) + + it('should not transform z-index enum if it is not imported from bezier-react', () => { + testTransformFunction(__dirname, 'z-index-enum-not-from-bezier-react', interpolationTransform) + }) +}) diff --git a/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/transform.ts b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/transform.ts new file mode 100644 index 0000000000..bae4982eb0 --- /dev/null +++ b/packages/bezier-codemod/src/transforms/v2-z-index-interpolation-to-css-variable/transform.ts @@ -0,0 +1,44 @@ +/* eslint-disable no-template-curly-in-string */ +import { type SourceFile } from 'ts-morph' + +import { transformEnumMemberToStringLiteral } from '../../shared/enum.js' +import { interpolationTransform } from '../../shared/interpolation.js' +import { removeUnusedNamedImport } from '../../utils/import.js' + +const cssVariableByInterpolation = { + 'ZIndex.Hide': 'var(--z-index-hidden);', + 'ZIndex.Auto': 'var(--z-index-auto);', + 'ZIndex.Base': 'var(--z-index-base);', + 'ZIndex.Float': 'var(--z-index-float);', + 'ZIndex.Overlay': 'var(--z-index-overlay);', + 'ZIndex.Modal': 'var(--z-index-modal);', + 'ZIndex.Toast': 'var(--z-index-toast);', + 'ZIndex.Tooltip': 'var(--z-index-tooltip);', + 'ZIndex.Important': 'var(--z-index-important);', +} + +const replaceZIndexInterpolation = (sourceFile: SourceFile) => { + const oldSourceFileText = sourceFile.getText() + + interpolationTransform(sourceFile, cssVariableByInterpolation) + transformEnumMemberToStringLiteral(sourceFile, { + ZIndex: { + Hide: 'var(--z-index-hidden)', + Base: 'var(--z-index-base)', + Float: 'var(--z-index-float)', + Overlay: 'var(--z-index-overlay)', + Modal: 'var(--z-index-modal)', + Toast: 'var(--z-index-toast)', + Tooltip: 'var(--z-index-tooltip)', + Important: 'var(--z-index-important)', + }, + }) + + const isChanged = sourceFile.getText() !== oldSourceFileText + if (isChanged) { + removeUnusedNamedImport(sourceFile) + } + return isChanged +} + +export default replaceZIndexInterpolation diff --git a/packages/bezier-codemod/src/utils/enum.ts b/packages/bezier-codemod/src/utils/enum.ts new file mode 100644 index 0000000000..c34076b8d3 --- /dev/null +++ b/packages/bezier-codemod/src/utils/enum.ts @@ -0,0 +1,14 @@ +import { + type PropertyAccessExpression, + SyntaxKind, +} from 'ts-morph' + +export const renameEnumMember = (node: PropertyAccessExpression, to: string) => { + const ancestor = node.getFirstAncestor() + + if (ancestor?.isKind(SyntaxKind.JsxExpression)) { + ancestor.replaceWithText(`'${to}'`) + } else { + node.replaceWithText(`'${to}'`) + } +} diff --git a/packages/bezier-codemod/src/utils/import.ts b/packages/bezier-codemod/src/utils/import.ts index 2059bdcefa..d5a3aec54e 100644 --- a/packages/bezier-codemod/src/utils/import.ts +++ b/packages/bezier-codemod/src/utils/import.ts @@ -17,6 +17,14 @@ export const removeImportDeclarationWithoutImport = (sourceFile: SourceFile) => }) } +export const hasNamedImportInImportDeclaration = (sourceFile: SourceFile, namedImport: string, moduleName: string) => { + const importDeclaration = getImportDeclaration(sourceFile, moduleName) + return importDeclaration + ?.getNamedImports() + .map((node) => node.getText()) + .includes(namedImport) +} + export const getNamedImport = (sourceFile: SourceFile, namedImport: string) => sourceFile .getImportDeclarations()