From 173d5de1f9c43f4e80a11a7711bdcd0d89dc3f70 Mon Sep 17 00:00:00 2001 From: Daniel Schulz Date: Tue, 8 Mar 2022 01:11:21 +0100 Subject: [PATCH] feat(color-mode): mode-to-color-mode-pseudos --- bin/cli.ts | 16 +- .../basic.input.tsx | 55 ++++ .../basic.output.tsx | 83 ++++++ .../__tests__/mode-to-color-mode-pseudos.ts | 20 ++ transforms/mode-to-color-mode-pseudos.ts | 245 ++++++++++++++++++ 5 files changed, 415 insertions(+), 4 deletions(-) create mode 100644 transforms/__testfixtures__/mode-to-color-mode-pseudos/basic.input.tsx create mode 100644 transforms/__testfixtures__/mode-to-color-mode-pseudos/basic.output.tsx create mode 100644 transforms/__tests__/mode-to-color-mode-pseudos.ts create mode 100644 transforms/mode-to-color-mode-pseudos.ts diff --git a/bin/cli.ts b/bin/cli.ts index bb8fec7..80a1106 100644 --- a/bin/cli.ts +++ b/bin/cli.ts @@ -194,13 +194,14 @@ export async function run() { }); } - await updateDependencies(); + // await updateDependencies(); const answer = await askQuestions(cli); const filesBeforeExpansion = cli.input[1] || answer.files; const files = expandFilePathsIfNeeded([filesBeforeExpansion]); const codemods = cli.input[0] || answer.codemods; + console.log(files, codemods); if (!files.length) { log.error(`No files found matching ${files.join(" ")}`); @@ -208,13 +209,20 @@ export async function run() { } // It's important to run this last after all transformations are done - codemods.push("core-to-react"); - for (const codemod of codemods) { + if (typeof codemods === "string") { runTransform({ files, - codemod, + codemod: codemods, flags: cli.flags, }); + } else { + for (const codemod of codemods) { + runTransform({ + files, + codemod, + flags: cli.flags, + }); + } } } diff --git a/transforms/__testfixtures__/mode-to-color-mode-pseudos/basic.input.tsx b/transforms/__testfixtures__/mode-to-color-mode-pseudos/basic.input.tsx new file mode 100644 index 0000000..26ec708 --- /dev/null +++ b/transforms/__testfixtures__/mode-to-color-mode-pseudos/basic.input.tsx @@ -0,0 +1,55 @@ +import { mode } from "@chakra-ui/theme-tools"; +import type { SystemStyleFunction } from "@chakra-ui/theme-tools"; + +const replacesInsideObject: SystemStyleFunction = (props) => { + return { + transform: "translate(25%, 25%)", + borderRadius: "full", + border: "0.2em solid", + borderColor: mode("white", "gray.800")(props), + }; +}; + +const createsVariablesIfOutsideObject: SystemStyleFunction = (props) => { + const borderColor = mode("white", "gray.800")(props); + const bgColor = mode("white", "gray.800")(props); + + return { + borderColor, + bg: bgColor, + }; +}; + +const mergesWithExisting: SystemStyleFunction = (props) => { + const borderColor = mode("white", "gray.800")(props); + + return { + _light: { + color: "red.500", + }, + borderColor, + }; +}; + +const worksWithNestedObjects: SystemStyleFunction = (props) => { + const borderColor = mode("white", "gray.800")(props); + + return { + _focus: { + _light: { + color: "red.500", + }, + borderColor, + }, + }; +}; + +const usesExistingVariable: SystemStyleFunction = (props) => { + const colorDark = "red.500"; + const colorLight = "blue.500"; + const borderColor = mode(colorDark, colorLight)(props); + + return { + borderColor, + }; +}; diff --git a/transforms/__testfixtures__/mode-to-color-mode-pseudos/basic.output.tsx b/transforms/__testfixtures__/mode-to-color-mode-pseudos/basic.output.tsx new file mode 100644 index 0000000..5758782 --- /dev/null +++ b/transforms/__testfixtures__/mode-to-color-mode-pseudos/basic.output.tsx @@ -0,0 +1,83 @@ +import "@chakra-ui/theme-tools"; +import type { SystemStyleFunction } from "@chakra-ui/theme-tools"; + +const replacesInsideObject: SystemStyleFunction = (props) => { + return { + transform: "translate(25%, 25%)", + borderRadius: "full", + border: "0.2em solid", + + _light: { + borderColor: "white" + }, + + _dark: { + borderColor: "gray.800" + } + }; +}; + +const createsVariablesIfOutsideObject: SystemStyleFunction = (props) => { + const borderColorLight = "white"; + const borderColorDark = "gray.800"; + const bgColorDark = "gray.800"; + const bgColorLight = "white"; + + return { + _light: { + borderColor: borderColorLight, + bg: bgColorLight + }, + _dark: { + borderColor: borderColorDark, + bg: bgColorDark + }, + }; +}; + +const mergesWithExisting: SystemStyleFunction = (props) => { + const borderColorLight = "white"; + const borderColorDark = "gray.800"; + + return { + _light: { + color: "red.500", + borderColor: borderColorLight + }, + _dark: { + borderColor: borderColorDark + }, + }; +}; + +const worksWithNestedObjects: SystemStyleFunction = (props) => { + const borderColorLight = "white"; + const borderColorDark = "gray.800"; + + return { + _focus: { + _light: { + color: "red.500", + borderColor: borderColorLight + }, + _dark: { + borderColor: borderColorDark + }, + }, + }; +}; + +const usesExistingVariable: SystemStyleFunction = (props) => { + const colorDark = "red.500"; + const colorLight = "blue.500"; + + return { + _light: { + borderColor: colorDark + }, + + _dark: { + borderColor: colorLight + } + }; +}; diff --git a/transforms/__tests__/mode-to-color-mode-pseudos.ts b/transforms/__tests__/mode-to-color-mode-pseudos.ts new file mode 100644 index 0000000..bba66e5 --- /dev/null +++ b/transforms/__tests__/mode-to-color-mode-pseudos.ts @@ -0,0 +1,20 @@ +/* global jest */ + +jest.autoMockOff(); + +import { defineTest } from "jscodeshift/dist/testUtils"; + +/** + * List of all test fixtures with an input and + * output pair for each. + */ +const fixtures = ["basic"] as const; +const name = "mode-to-color-mode-pseudos"; + +describe(name, () => { + fixtures.forEach((test) => + defineTest(__dirname, name, null, `${name}/${test}`, { + parser: "tsx", + }), + ); +}); diff --git a/transforms/mode-to-color-mode-pseudos.ts b/transforms/mode-to-color-mode-pseudos.ts new file mode 100644 index 0000000..6e32873 --- /dev/null +++ b/transforms/mode-to-color-mode-pseudos.ts @@ -0,0 +1,245 @@ +import { CallExpression, Transform } from "jscodeshift"; +import { prepare } from "../utils/shared"; +import { removeModuleImport } from "../utils/module"; + +type Unpacked = T extends (infer U)[] ? U : T; + +/** + * + * @param file + * @param api + */ +const transfomer: Transform = (file, api) => { + const config = prepare(file, api); + console.log("transform it"); + const { j, root, done } = config; + + /** + * finds the body of the function + * + * @param startNode + */ + const findParentBlock = (startNode) => { + if (startNode.name === "body") { + return startNode; + } else { + return findParentBlock(startNode.parentPath); + } + }; + + /** + * + * creates a new variable from `mode` arguments + * + * + * @param variableValue + * @param propertyName + * @param propertyPath + * @param variableSuffix + */ + const createVariable = ( + variableValue: Unpacked, + propertyName: string, + propertyPath: any, + variableSuffix: string, + ) => { + if (variableValue.type !== "Identifier") { + const newVariable = j.variableDeclaration("const", [ + j.variableDeclarator( + j.identifier(`${propertyName}${variableSuffix}`), + // @ts-ignore + variableValue, + ), + ]); + propertyPath.parentPath.parentPath.insertBefore(newVariable); + return newVariable; + } + return variableValue; + }; + + /** + * create _light / _dark if nessecary in `parentObject` regarding to mode + * + * add property in _light / _dark with `propertyName + * + * + * @param parentObject + * @param mode + * @param propertyName + * @param value + */ + const addProperty = ( + parentObject, + mode: "_light" | "_dark", + propertyName, + value, + ) => { + let propertyValue = value; + // if the property is a variable we use its declaration + // TODO are there more types that can be passed like this? + if (value.type === "VariableDeclaration") { + propertyValue = value.declarations[0].id; + } + + const property = j.objectProperty( + j.identifier(propertyName), + propertyValue, + ); + + let addedToExisting = false; + // try to find a _mode key on parentObject and add to it + parentObject.value.map((val) => { + if (val.key && val.key.name === mode) { + val.value.properties.push(property); + addedToExisting = true; + } + }); + // if we couldnt find a mode code in parentObject create a new one + if (!addedToExisting) { + const objectProperty = j.objectProperty( + j.identifier(mode), + j.objectExpression([property]), + ); + parentObject.push(objectProperty); + } + }; + + // find all calls of `mode` + root + .find(j.CallExpression, { + callee: { + type: "Identifier", + name: "mode", + }, + }) + .forEach((call, idx) => { + // get the arguments of the call + const [light, dark] = call.value.arguments; + + const propertyPath = call.parentPath.parentPath; + switch (propertyPath.value.type) { + /** + * case: + * + * `const color = mode('example.200','example.700')` + * + * => + * + * const colorLight = 'example.200' + * const colorDark = 'example.700' + * + * // Special case 'example.200' is a variable e.g. lightColor already then we don't need to introduce a new one + * + * go up to function body find all object which have `color` as propertyName and replace them with + * + * { + * _light:{ + * color:'example.200' + * } + * _dark:{ + * color:'example.700' + * } + * } + * + */ + case "VariableDeclarator": { + const propertyName = propertyPath.value.id.name; + const lightVariable = createVariable( + light, + propertyName, + propertyPath, + "Light", + ); + const darkVariable = createVariable( + dark, + propertyName, + propertyPath, + "Dark", + ); + + /** + * case 1 propertyName = objectKey + * + * const bg = mode('a','b') + * + * return { + * bg + * } + */ + j(findParentBlock(propertyPath)) + .find(j.ObjectProperty) + .filter((e) => { + return ( + // case {borderColor: variable} + // @ts-ignore + e.value.key.name === propertyName || + // @ts-ignore + e.value.value.name === propertyName + ); + }) + .forEach((objectProperty) => { + addProperty( + objectProperty.parentPath, + "_light", + // @ts-ignore + objectProperty.value.key.name, + lightVariable, + ); + addProperty( + objectProperty.parentPath, + "_dark", + // @ts-ignore + objectProperty.value.key.name, + darkVariable, + ); + objectProperty.prune(); + }); + propertyPath.prune(); + break; + } + + /** + * case + * + * { + * color: mode('example.200','example.700) + * } + * + * which is later used in theme + * + * becomes + * => + * + * { + * _light:{ + * color:'example.200' + * } + * _dark:{ + * color:'example.700' + * } + * } + */ + case "ObjectProperty": { + const propertyName = propertyPath.value.key.name; + const styleDefinition = call.parentPath.parentPath.parentPath; + + propertyPath.prune(); + addProperty(styleDefinition, "_light", propertyName, light); + addProperty(styleDefinition, "_dark", propertyName, dark); + break; + } + } + }); + + removeModuleImport(j, root, { + moduleName: "@chakra-ui/theme-tools", + selector: "mode", + }); + // root + // .find(j.ImportSpecifier, { imported: { name: "mode" } }) + // .forEach((importPath) => importPath.prune()); + + return done(); +}; + +export default transfomer;