-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[CCUBE-1554][MAHI]Codemod scripts for migrating text and mediaquery a…
…nd their test cases
- Loading branch information
1 parent
5252585
commit ef01d96
Showing
8 changed files
with
413 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
export const mediaQueryMap = { | ||
MaxWidth: { | ||
mobileS: "xxs", | ||
mobileM: "xs", | ||
mobileL: "sm", | ||
tablet: "lg", | ||
desktopM: "xl", | ||
desktopL: "xl", | ||
desktop4k: "xl", | ||
}, | ||
MinWidth: { | ||
mobileS: "xs", | ||
mobileM: "sm", | ||
mobileL: "md", | ||
tablet: "xl", | ||
desktopM: "xxl", | ||
desktopL: "xxl", | ||
desktop4k: "xxl", | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { API, FileInfo, JSCodeshift } from "jscodeshift"; | ||
import { mediaQueryMap } from "./data"; | ||
|
||
export default function transformer(file: FileInfo, api: API, options: any) { | ||
const j: JSCodeshift = api.jscodeshift; | ||
const source = j(file.source); | ||
|
||
let isV2MediaQueryImport = false; | ||
let isV2MediaWidthsUsed = false; | ||
|
||
source.find(j.ImportDeclaration).forEach((path) => { | ||
const importPath = path.node.source.value; | ||
|
||
if (importPath === "@lifesg/react-design-system/v2_media") { | ||
isV2MediaQueryImport = true; | ||
|
||
// loop over specifiers to rename V2_MediaQuery | ||
if (path.node.specifiers && path.node.specifiers.length > 0) { | ||
path.node.specifiers.forEach((specifier) => { | ||
if (j.ImportSpecifier.check(specifier)) { | ||
if (specifier.imported.name === "V2_MediaQuery") { | ||
specifier.imported.name = "MediaQuery"; | ||
if ( | ||
specifier.local && | ||
specifier.local.name === "V2_MediaQuery" | ||
) { | ||
specifier.local.name = "MediaQuery"; | ||
} | ||
} else if ( | ||
specifier.imported.name === "V2_MediaWidths" | ||
) { | ||
isV2MediaWidthsUsed = true; | ||
} | ||
} | ||
}); | ||
|
||
// change the import source path | ||
path.node.source.value = "@lifesg/react-design-system/theme"; | ||
} | ||
} | ||
}); | ||
|
||
if (isV2MediaWidthsUsed) { | ||
let hasLoggedV2MediaWidthsWarning = false; | ||
|
||
source | ||
.find(j.Identifier, { name: "V2_MediaWidths" }) | ||
.forEach((path) => { | ||
if (!hasLoggedV2MediaWidthsWarning) { | ||
console.error( | ||
`\x1b[31mDeprecated usage detected: V2_MediaWidths is deprecated and needs adjustment. File: ${file.path}\x1b[0m` | ||
); | ||
hasLoggedV2MediaWidthsWarning = true; | ||
} | ||
}); | ||
} | ||
|
||
if (isV2MediaQueryImport) { | ||
// change all instances of V2_MediaQuery | ||
source.find(j.Identifier, { name: "V2_MediaQuery" }).forEach((path) => { | ||
path.node.name = "MediaQuery"; | ||
}); | ||
|
||
// for nested member expression | ||
source.find(j.MemberExpression).forEach((path) => { | ||
const object = path.node.object; | ||
const property = path.node.property; | ||
|
||
// checking the obj | ||
if ( | ||
j.MemberExpression.check(object) && // ensure obj is a MemberExpression | ||
j.Identifier.check(object.object) && | ||
object.object.name === "MediaQuery" && // check if MediaQuery | ||
j.Identifier.check(object.property) && // max,min width | ||
(object.property.name === "MaxWidth" || | ||
object.property.name === "MinWidth") && | ||
j.Identifier.check(property) | ||
) { | ||
const queryType = object.property.name; | ||
const mediaKey = property.name; | ||
|
||
// check if the mediaKey exists in the mediaQueryMap | ||
if ( | ||
mediaQueryMap[queryType] && | ||
mediaQueryMap[queryType][mediaKey] | ||
) { | ||
const newMediaKey = mediaQueryMap[queryType][mediaKey]; | ||
property.name = newMediaKey; | ||
} | ||
} | ||
}); | ||
} | ||
|
||
return source.toSource(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
export const textComponentMap = { | ||
D1: "HeaderXXL", | ||
D2: "HeaderXL", | ||
D3: "HeaderMD", | ||
D4: "HeaderSM", | ||
H1: "HeaderLG", | ||
H2: "HeaderMD", | ||
H3: "HeaderSM", | ||
H4: "HeaderXS", | ||
H5: "HeaderXS", | ||
H6: "HeaderXS", | ||
DBody: "HeaderSM", | ||
Body: "BodyBL", | ||
BodySmall: "BodyLG", | ||
XSmall: "LinkSM", | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { API, FileInfo, JSCodeshift } from "jscodeshift"; | ||
import { textComponentMap } from "./data"; | ||
|
||
export default function transformer(file: FileInfo, api: API, options: any) { | ||
const j: JSCodeshift = api.jscodeshift; | ||
const source = j(file.source); | ||
|
||
let isLifesgImport = false; | ||
|
||
// Check import declarations | ||
source.find(j.ImportDeclaration).forEach((path) => { | ||
const importPath = path.node.source.value; | ||
|
||
// If the import is from lifesg ds , set to true | ||
if (importPath === "@lifesg/react-design-system/v2_text") { | ||
isLifesgImport = true; | ||
|
||
// Check if specifiers exist and iterate over them | ||
if (path.node.specifiers && path.node.specifiers.length > 0) { | ||
path.node.specifiers.forEach((specifier) => { | ||
if ( | ||
j.ImportSpecifier.check(specifier) && | ||
specifier.imported.name === "V2_Text" | ||
) { | ||
specifier.imported.name = "Typography"; | ||
if ( | ||
specifier.local && | ||
specifier.local.name === "V2_Text" | ||
) { | ||
specifier.local.name = "Typography"; | ||
} | ||
|
||
// Update the import path | ||
path.node.source.value = | ||
"@lifesg/react-design-system/typography"; | ||
} | ||
}); | ||
} | ||
} | ||
}); | ||
|
||
// change if import from @lifesg/react-design-system | ||
if (isLifesgImport) { | ||
// update JSX and identifiers | ||
source.find(j.JSXMemberExpression).forEach((path) => { | ||
const { object, property } = path.node; | ||
|
||
// Change V2_text to typography | ||
if (j.JSXIdentifier.check(object) && object.name === "V2_Text") { | ||
object.name = "Typography"; | ||
|
||
// Map properties (e.g., Body -> BodyBL) | ||
if (textComponentMap[property.name]) { | ||
property.name = textComponentMap[property.name]; | ||
} | ||
} | ||
}); | ||
} | ||
|
||
return source.toSource(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
export const inputCode = ` | ||
import { V2_MediaQuery, V2_MediaWidths } from "@lifesg/react-design-system/v2_media"; | ||
const StyledContainer = styled.div<StyleProps>\` | ||
flex-grow: 1; | ||
margin: 0 auto; | ||
position: relative; | ||
width: auto; | ||
height: auto; | ||
\${(props) => { | ||
if (props.stretch) { | ||
return css\` | ||
padding: 0 3rem; | ||
\`; | ||
} else { | ||
return css\` | ||
padding: 0 0.75rem; | ||
/* Max width restrictions */ | ||
max-width: 1320px; | ||
\${V2_MediaQuery.MaxWidth.desktopM} { | ||
max-width: 1140px; | ||
} | ||
\`; | ||
} | ||
}} | ||
\${V2_MediaQuery.MaxWidth.tablet} { | ||
max-width: 720px; | ||
} | ||
\${V2_MediaQuery.MaxWidth.mobileL} { | ||
width: 100%; | ||
padding: 0; | ||
max-width: unset; | ||
} | ||
@media (max-width: \${V2_MediaWidths.mobileL}px) { | ||
padding: 0; | ||
max-width: 100%; | ||
} | ||
@media (max-width: \${V2_MediaWidths.mobileL}px) { | ||
padding: 0; | ||
max-width: 100%; | ||
} | ||
\`; | ||
`; | ||
|
||
export const expectedOutputCode = ` | ||
import { MediaQuery, V2_MediaWidths } from "@lifesg/react-design-system/theme"; | ||
const StyledContainer = styled.div<StyleProps>\` | ||
flex-grow: 1; | ||
margin: 0 auto; | ||
position: relative; | ||
width: auto; | ||
height: auto; | ||
\${(props) => { | ||
if (props.stretch) { | ||
return css\` | ||
padding: 0 3rem; | ||
\`; | ||
} else { | ||
return css\` | ||
padding: 0 0.75rem; | ||
/* Max width restrictions */ | ||
max-width: 1320px; | ||
\${MediaQuery.MaxWidth.xl} { | ||
max-width: 1140px; | ||
} | ||
\`; | ||
} | ||
}} | ||
\${MediaQuery.MaxWidth.lg} { | ||
max-width: 720px; | ||
} | ||
\${MediaQuery.MaxWidth.sm} { | ||
width: 100%; | ||
padding: 0; | ||
max-width: unset; | ||
} | ||
@media (max-width: \${V2_MediaWidths.mobileL}px) { | ||
padding: 0; | ||
max-width: 100%; | ||
} | ||
@media (max-width: \${V2_MediaWidths.mobileL}px) { | ||
padding: 0; | ||
max-width: 100%; | ||
} | ||
\`; | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { execSync } from "child_process"; | ||
import * as fs from "fs"; | ||
import * as path from "path"; | ||
import { expectedOutputCode, inputCode } from "./test-data"; | ||
|
||
describe("Codemod Transformer for V2_Layout to Layout", () => { | ||
const inputPath = path.join(__dirname, "input.tsx"); | ||
const outputPath = path.join(__dirname, "output.tsx"); | ||
|
||
beforeAll(() => { | ||
// Create sample input file for testing | ||
jest.resetAllMocks(); | ||
fs.writeFileSync(inputPath, inputCode); | ||
}); | ||
|
||
afterAll(() => { | ||
// Delete the files created for testing (comment this out to view files) | ||
fs.unlinkSync(inputPath); | ||
fs.unlinkSync(outputPath); | ||
}); | ||
|
||
it("should transform V2_Layout components to Layout components and map props correctly", () => { | ||
fs.copyFileSync(inputPath, outputPath); | ||
|
||
// Execute the jscodeshift command for the codemod | ||
execSync( | ||
`jscodeshift --parser=tsx -t ./codemods/migrate-mediaquery ${outputPath}` | ||
); | ||
|
||
// Check the transformed code | ||
const transformedCode = fs.readFileSync(outputPath, "utf8"); | ||
|
||
// Compare the transformed code with the expected output | ||
expect(transformedCode.trim()).toEqual(expectedOutputCode.trim()); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
export const inputCode = ` | ||
import { V2_Text } from "@lifesg/react-design-system/v2_text"; | ||
const ExampleComponent = () => ( | ||
<div> | ||
<V2_Text.Body weight="bold">This is body text</V2_Text.Body> | ||
<V2_Text.H1>This is a heading</V2_Text.H1> | ||
<V2_Text.BodySmall>This is smaller body text</V2_Text.BodySmall> | ||
<V2_Text.Body paragraph={true}> | ||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. | ||
Morbi euismod quam eget ex tincidunt dapibus. Donec vitae | ||
leo vehicula, fermentum urna vitae, gravida ex. | ||
</V2_Text.Body> | ||
<V2_Text.Body paragraph={true}> | ||
Aenean imperdiet faucibus velit, eu maximus libero facilisis | ||
ut. Donec nulla nisi, fermentum eget lorem at, feugiat | ||
ultricies ex. Aliquam volutpat nibh non suscipit rhoncus. | ||
</V2_Text.Body> | ||
</div> | ||
); | ||
export default ExampleComponent; | ||
`; | ||
|
||
export const expectedOutputCode = ` | ||
import { Typography } from "@lifesg/react-design-system/typography"; | ||
const ExampleComponent = () => ( | ||
<div> | ||
<Typography.BodyBL weight="bold">This is body text</Typography.BodyBL> | ||
<Typography.HeaderLG>This is a heading</Typography.HeaderLG> | ||
<Typography.BodyLG>This is smaller body text</Typography.BodyLG> | ||
<Typography.BodyBL paragraph={true}> | ||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. | ||
Morbi euismod quam eget ex tincidunt dapibus. Donec vitae | ||
leo vehicula, fermentum urna vitae, gravida ex. | ||
</Typography.BodyBL> | ||
<Typography.BodyBL paragraph={true}> | ||
Aenean imperdiet faucibus velit, eu maximus libero facilisis | ||
ut. Donec nulla nisi, fermentum eget lorem at, feugiat | ||
ultricies ex. Aliquam volutpat nibh non suscipit rhoncus. | ||
</Typography.BodyBL> | ||
</div> | ||
); | ||
export default ExampleComponent; | ||
`; |
Oops, something went wrong.