Skip to content

Commit

Permalink
[CCUBE-1554][MAHI]Codemod scripts for migrating text and mediaquery a…
Browse files Browse the repository at this point in the history
…nd their test cases
  • Loading branch information
mahidhar-reddy09 committed Sep 23, 2024
1 parent 5252585 commit ef01d96
Show file tree
Hide file tree
Showing 8 changed files with 413 additions and 0 deletions.
20 changes: 20 additions & 0 deletions codemods/migrate-mediaquery/data.ts
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",
},
};
95 changes: 95 additions & 0 deletions codemods/migrate-mediaquery/index.ts
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();
}
16 changes: 16 additions & 0 deletions codemods/migrate-text/data.ts
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",
};
61 changes: 61 additions & 0 deletions codemods/migrate-text/index.ts
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();
}
95 changes: 95 additions & 0 deletions tests/codemod/migrate-mediaQuery/test-data.ts
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%;
}
\`;
`;
36 changes: 36 additions & 0 deletions tests/codemod/migrate-mediaQuery/transformer.spec.tsx
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());
});
});
54 changes: 54 additions & 0 deletions tests/codemod/migrate-text/test-data.ts
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;
`;
Loading

0 comments on commit ef01d96

Please sign in to comment.