-
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-1558][MAHI]Migrate colour and layout codemod scripts with its …
…test cases
- Loading branch information
1 parent
3e4177e
commit 6fd5075
Showing
8 changed files
with
441 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,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 = {}; |
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,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(); | ||
} |
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,5 @@ | ||
export const propMapping = { | ||
desktopCols: "xlCols", | ||
tabletCols: "lgCols", | ||
mobileCols: "smCols", | ||
}; |
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,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(); | ||
} |
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,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"]}; | ||
\`; | ||
`; |
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_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()); | ||
}); | ||
}); |
Oops, something went wrong.