Skip to content

Commit

Permalink
[CCUBE-1558][MAHI]Migrate colour and layout codemod scripts with its …
Browse files Browse the repository at this point in the history
…test cases
  • Loading branch information
mahidhar-reddy09 committed Oct 3, 2024
1 parent 3e4177e commit 6fd5075
Show file tree
Hide file tree
Showing 8 changed files with 441 additions and 0 deletions.
59 changes: 59 additions & 0 deletions codemods/migrate-colour/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
export const lifesgMapping = {
Secondary: "primary-40",
PrimaryDark: "primary-40",
Primary: "primary-50",
"Accent.Light[1]": "primary-60",
"Accent.Light[2]": "primary-70",
"Accent.Light[3]": "primary-80",
"Accent.Light[4]": "primary-90",
"Accent.Light[5]": "primary-95",
"Accent.Light[6]": "primary-100",
"Accent.Dark[1]": "secondary-40",
"Accent.Dark[2]": "secondary-50",
"Accent.Dark[3]": "secondary-60",
};

export const bookingSgMapping = {
PrimaryDark: "primary-40",
Primary: "primary-50",
"Brand[1]": "brand-50",
"Brand[2]": "brand-60",
"Brand[3]": "brand-70",
"Brand[4]": "brand-80",
"Brand[5]": "brand-90",
"Brand[6]": "brand-95",
"Accent.Light[1]": "primary-60",
"Accent.Dark[1]": "secondary-40",
"Accent.Dark[2]": "secondary-60",
"Accent.Dark[3]": "secondary-70",
"Neutral[1]": "neutral-20",
"Neutral[2]": "neutral-30",
"Neutral[3]": "neutral-50",
"Neutral[4]": "neutral-60",
"Neutral[5]": "neutral-90",
"Neutral[6]": "neutral-95",
"Neutral[7]": "neutral-100",
"Blue[1]": "information-40",
"Blue[2]": "information-50",
"Blue[3]": "information-70",
};

export const mylegacyMapping = {
PrimaryDark: "primary-40",
Primary: "primary-50",
"Accent.Light[1]": "primary-60",
"Accent.Light[2]": "primary-80",
"Accent.Light[3]": "primary-90",
"Accent.Light[4]": "primary-95",
"Accent.Light[5]": "primary-100",
"Accent.Light[6]": "primary-100",
};

export const ccubeMapping = {
"Brand[2]": "brand-50",
"Brand[1]": "brand-60",
Primary: "primary-50",
Secondary: "secondary-50",
};

export const rbsMapping = {};
138 changes: 138 additions & 0 deletions codemods/migrate-colour/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { API, FileInfo, JSCodeshift } from "jscodeshift";
import {
bookingSgMapping,
ccubeMapping,
lifesgMapping,
mylegacyMapping,
rbsMapping,
} from "./data";

const IMPORT_PATHS = {
V2_COLOR: "@lifesg/react-design-system/v2_color",
THEME: "@lifesg/react-design-system/theme",
};

const IMPORT_SPECIFIERS = {
V2_COLOR: "V2_Color",
COLOUR: "Colour",
};

const MEMBER_EXPRESSION_PROPERTIES = {
PRIMITIVE: "Primitive",
};

const COLOR_MAPPINGS = {
lifesg: lifesgMapping,
bookingsg: bookingSgMapping,
mylegacy: mylegacyMapping,
ccube: ccubeMapping,
rbs: rbsMapping,
};

export default function transformer(file: FileInfo, api: API, options: any) {
const j: JSCodeshift = api.jscodeshift;
const source = j(file.source);

let isv2ColorImport = false;

// Determine which Colour mapping to use
const color_mapping =
COLOR_MAPPINGS[options.mapping] || COLOR_MAPPINGS.lifesg;

//Update Colour usage post mapping
const replaceWithColorPrimitive = (path: any, new_color_value: string) => {
path.replace(
j.memberExpression(
j.memberExpression(
j.identifier(IMPORT_SPECIFIERS.COLOUR),
j.identifier(MEMBER_EXPRESSION_PROPERTIES.PRIMITIVE)
),
j.literal(new_color_value)
)
);
};

source.find(j.ImportDeclaration).forEach((path) => {
const import_path = path.node.source.value;

if (import_path === IMPORT_PATHS.V2_COLOR) {
isv2ColorImport = true;

// Update imports
if (path.node.specifiers && path.node.specifiers.length > 0) {
path.node.specifiers.forEach((specifier) => {
if (j.ImportSpecifier.check(specifier)) {
if (
specifier.imported.name ===
IMPORT_SPECIFIERS.V2_COLOR
) {
specifier.imported.name = IMPORT_SPECIFIERS.COLOUR;
if (
specifier.local &&
specifier.local.name ===
IMPORT_SPECIFIERS.V2_COLOR
) {
specifier.local.name = IMPORT_SPECIFIERS.COLOUR;
}
}
}
});

path.node.source.value = IMPORT_PATHS.THEME;
}
}
});

if (isv2ColorImport) {
// Update all instances of 'V2_Color' to 'Colour'
source
.find(j.Identifier, { name: IMPORT_SPECIFIERS.V2_COLOR })
.forEach((path) => {
path.node.name = IMPORT_SPECIFIERS.COLOUR;
});

// Map V2 Color usage to V3 Colour
source.find(j.MemberExpression).forEach((path) => {
let currentPath = path.node;
const propertyNameParts: string[] = [];
let index: any = null;
let startsWithColour = false;

while (j.MemberExpression.check(currentPath)) {
const property = currentPath.property;
const object = currentPath.object;

if (j.Literal.check(property)) {
index = property.value;
} else if (j.Identifier.check(property)) {
propertyNameParts.unshift(property.name);
}

if (j.MemberExpression.check(object)) {
currentPath = object;
} else if (j.Identifier.check(object)) {
if (object.name === IMPORT_SPECIFIERS.COLOUR) {
startsWithColour = true;
}
break;
} else {
break;
}
}

if (startsWithColour) {
let property_name = propertyNameParts.join(".");
if (index !== null) {
property_name += `[${index}]`;
}

const newColorValue = color_mapping[property_name];
if (newColorValue) {
replaceWithColorPrimitive(path, newColorValue);
}
}
});
}

return source.toSource();
}
5 changes: 5 additions & 0 deletions codemods/migrate-layout/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const propMapping = {
desktopCols: "xlCols",
tabletCols: "lgCols",
mobileCols: "smCols",
};
103 changes: 103 additions & 0 deletions codemods/migrate-layout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { API, FileInfo, JSCodeshift } from "jscodeshift";
import { propMapping } from "./data";

const IMPORT_PATHS = {
V2_LAYOUT: "@lifesg/react-design-system/v2_layout",
LAYOUT: "@lifesg/react-design-system/layout",
};

const IMPORT_SPECIFIERS = {
V2_LAYOUT: "V2_Layout",
LAYOUT: "Layout",
};

export default function transformer(file: FileInfo, api: API, options: any) {
const j: JSCodeshift = api.jscodeshift;
const source = j(file.source);

let isLifesgImport = false;

source.find(j.ImportDeclaration).forEach((path) => {
const importPath = path.node.source.value;

if (importPath === IMPORT_PATHS.V2_LAYOUT) {
isLifesgImport = true;

// Update import path
if (path.node.specifiers && path.node.specifiers.length > 0) {
path.node.specifiers.forEach((specifier) => {
if (
j.ImportSpecifier.check(specifier) &&
specifier.imported.name === IMPORT_SPECIFIERS.V2_LAYOUT
) {
specifier.imported.name = IMPORT_SPECIFIERS.LAYOUT;
if (
specifier.local &&
specifier.local.name === IMPORT_SPECIFIERS.V2_LAYOUT
) {
specifier.local.name = IMPORT_SPECIFIERS.LAYOUT;
}

path.node.source.value = IMPORT_PATHS.LAYOUT;
}
});
}
}
});

if (isLifesgImport) {
// Update V2_Layout to Layout
source.find(j.JSXMemberExpression).forEach((path) => {
const { object } = path.node;

if (
j.JSXIdentifier.check(object) &&
object.name === IMPORT_SPECIFIERS.V2_LAYOUT
) {
object.name = IMPORT_SPECIFIERS.LAYOUT;
}
});

// Update Layout props to its V3 Mapped version
source.find(j.JSXOpeningElement).forEach((path) => {
const { name, attributes } = path.node;

if (
j.JSXMemberExpression.check(name) &&
j.JSXIdentifier.check(name.object) &&
name.object.name === IMPORT_SPECIFIERS.LAYOUT
) {
if (attributes && attributes.length > 0) {
attributes.forEach((attribute) => {
if (
j.JSXAttribute.check(attribute) &&
j.JSXIdentifier.check(attribute.name)
) {
const oldPropName = attribute.name.name;
const newPropName = propMapping[oldPropName];

if (newPropName) {
attribute.name.name = newPropName;
}
}
});
}
}
});

source.find(j.JSXElement).forEach((path) => {
const openingElement = path.node.openingElement;
const { name } = openingElement;
if (
j.JSXMemberExpression.check(name) &&
j.JSXIdentifier.check(name.object)
) {
if (name.object.name === IMPORT_SPECIFIERS.V2_LAYOUT) {
name.object.name = IMPORT_SPECIFIERS.LAYOUT;
}
}
});
}

return source.toSource();
}
19 changes: 19 additions & 0 deletions tests/codemod/migrate-color/test-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const inputCode = `
import { V2_Color } from '@lifesg/react-design-system/v2_color';
const styles = css\`
color: \${V2_Color.Primary};
border-color: \${V2_Color.Accent.Dark[3]};
text-shadow: 1px 1px \${V2_Color.Accent.Light[1]};
\`;
`;

export const expectedOutputCode = `
import { Colour } from "@lifesg/react-design-system/theme";
const styles = css\`
color: \${Colour.Primitive["primary-50"]};
border-color: \${Colour.Primitive["secondary-60"]};
text-shadow: 1px 1px \${Colour.Primitive["primary-60"]};
\`;
`;
36 changes: 36 additions & 0 deletions tests/codemod/migrate-color/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_Color to Colour", () => {
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_Color tokens to Colour tokens and map theme correctly", () => {
fs.copyFileSync(inputPath, outputPath);

// Execute the jscodeshift command for the codemod
execSync(
`jscodeshift --parser=tsx --verbose=2 -t ./codemods/migrate-colour --mapping=lifesg ${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());
});
});
Loading

0 comments on commit 6fd5075

Please sign in to comment.