Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add codemod for z-index interpolation #1845

Merged
merged 10 commits into from
Dec 22, 2023
5 changes: 5 additions & 0 deletions .changeset/slimy-lobsters-sparkle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@channel.io/bezier-codemod": minor
---

Add codemod for z-index interpolation and enum
31 changes: 31 additions & 0 deletions packages/bezier-codemod/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'styled-component 내에서 interpolation 으로 사용될 때' 에 대한 내용도 포함되면 좋을 거 같아요

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`**
Expand Down
5 changes: 4 additions & 1 deletion packages/bezier-codemod/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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',
}

Expand All @@ -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) => ({
Expand Down
40 changes: 40 additions & 0 deletions packages/bezier-codemod/src/shared/enum.ts
Original file line number Diff line number Diff line change
@@ -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<Name, Record<Member, Value>>

export const transformEnumMemberToStringLiteral = (sourceFile: SourceFile, enumTransforms: EnumTransformMap) => {
const transformedEnumNames: string[] = []

Object
.keys(enumTransforms)
.forEach((enumName) => {
if (hasNamedImportInImportDeclaration(sourceFile, enumName, '@channel.io/bezier-react')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

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
}
Original file line number Diff line number Diff line change
@@ -1,65 +1,24 @@
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()
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 {
Copy link
Collaborator Author

@yangwooseong yangwooseong Dec 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추후에 이 함수에 v2 prefix 를 붙이고 변환해야하는 enum 을 모두 포함해야 합니다. (#1815)

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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Overlay, ZIndex } from '@channel.io/bezier-react'

export function SelectionOverlay () {
return (
<Overlay
container={{ style: { zIndex: ZIndex.Overlay } }}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Overlay } from '@channel.io/bezier-react'

export function SelectionOverlay () {
return (
<Overlay
container={{ style: { zIndex: 'var(--z-index-overlay)' } }}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ZIndex } from 'some-library'

export const OVERLAY_POSITION1 = {
zIndex: ZIndex.Modal,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ZIndex } from 'some-library'

export const OVERLAY_POSITION1 = {
zIndex: ZIndex.Modal,
}
Original file line number Diff line number Diff line change
@@ -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,
}
Original file line number Diff line number Diff line change
@@ -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)',
}
Original file line number Diff line number Diff line change
@@ -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}
`
Original file line number Diff line number Diff line change
@@ -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);
`
Original file line number Diff line number Diff line change
@@ -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)
})
})
Original file line number Diff line number Diff line change
@@ -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, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 이걸 사용할 수 있겠군요... 👍

Copy link
Collaborator Author

@yangwooseong yangwooseong Dec 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그런데 다시보니 이 함수는 bezier-react 에서 import 할 때를 체크하고 있지는 않아서 사용처에서 동일한 이름의 enum 을 직접 선언해서 사용하면 사이드 이펙트를 발생시키네요. 홈페이지에서 그렇게 사용하는 부분이 있는 것으로 알고 있어서, bezier-react 에서 타켓 enum 을 import 하고 있는지 방어 로직을 추가하겠습니다

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
14 changes: 14 additions & 0 deletions packages/bezier-codemod/src/utils/enum.ts
Original file line number Diff line number Diff line change
@@ -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}'`)
}
}
Loading