From 166d41a24dfe90ccf46925ad4659526108bc81c4 Mon Sep 17 00:00:00 2001 From: dengfuping Date: Tue, 24 Oct 2023 20:17:18 +0800 Subject: [PATCH] feat(codemod): Add style-to-token transform --- packages/codemod/README.md | 99 ++++++++++++++++ packages/codemod/bin/cli.js | 1 + .../style-to-token/class-component.input.js | 20 ++++ .../style-to-token/class-component.output.js | 20 ++++ .../function-component.input.js | 13 +++ .../function-component.output.js | 14 +++ .../style-to-token/static.input.js | 27 +++++ .../style-to-token/static.output.js | 28 +++++ .../__tests__/style-to-token.test.ts | 10 ++ packages/codemod/transforms/style-to-token.js | 110 ++++++++++++++++++ packages/codemod/transforms/utils/index.js | 2 +- 11 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 packages/codemod/transforms/__testfixtures__/style-to-token/class-component.input.js create mode 100644 packages/codemod/transforms/__testfixtures__/style-to-token/class-component.output.js create mode 100644 packages/codemod/transforms/__testfixtures__/style-to-token/function-component.input.js create mode 100644 packages/codemod/transforms/__testfixtures__/style-to-token/function-component.output.js create mode 100644 packages/codemod/transforms/__testfixtures__/style-to-token/static.input.js create mode 100644 packages/codemod/transforms/__testfixtures__/style-to-token/static.output.js create mode 100644 packages/codemod/transforms/__tests__/style-to-token.test.ts create mode 100644 packages/codemod/transforms/style-to-token.js diff --git a/packages/codemod/README.md b/packages/codemod/README.md index 16d5efc11..e4db204c6 100644 --- a/packages/codemod/README.md +++ b/packages/codemod/README.md @@ -133,3 +133,102 @@ import utils and hooks from `@alipay/ob-util` to `@oceanbase/util`. Additionally export default Demo; ``` + +### `style-to-token` + +transform fixed css style to antd v5 design token. + +- React function components: + +```diff + import React from 'react'; +- import { Alert, Button } from '@oceanbase/design'; ++ import { Alert, Button, theme } from '@oceanbase/design'; + + const Demo = () => { ++ const { token } = theme.useToken(); + return ( +-
+- +- +-
++ (
++ ++ ++
) + ); + }; + +export default Demo; +``` + +- React class components: + +```diff + import React from 'react'; +- import { Alert, Button } from '@oceanbase/design'; ++ import { Alert, Button, token } from '@oceanbase/design'; + + class Demo extends React.PureComponent { + constructor(props) { + super(props); + this.state = {}; + } + + render() { + return ( +-
+- +- +-
++ (
++ ++ ++
) + ); + } + } + + export default Demo; +``` + +- Static file (not react components): + +```diff ++ import { token } from '@oceanbase/design'; + const colorMap = { +- info: '#1890ff', +- success: '#52c41a', +- warning: '#faad14', +- error: '#ff4D4F', ++ info: token.colorInfo, ++ success: token.colorSuccess, ++ warning: token.colorWarning, ++ error: token.colorError, + }; + + function getColorList() { + return [ + { + type: 'info', +- color: '#1890ff', ++ color: token.colorInfo, + }, + { + type: 'success', +- color: '#52c41a', ++ color: token.colorSuccess, + }, + { + type: 'warning', +- color: '#faad14', ++ color: token.colorWarning, + }, + { + type: 'error', +- color: '#ff4D4F', ++ color: token.colorError, + } + ]; + } +``` diff --git a/packages/codemod/bin/cli.js b/packages/codemod/bin/cli.js index 399d2bb91..21b732718 100644 --- a/packages/codemod/bin/cli.js +++ b/packages/codemod/bin/cli.js @@ -30,6 +30,7 @@ const transformers = [ 'obui-to-oceanbase-design-and-ui', 'obutil-to-oceanbase-util', 'page-container-to-oceanbase-ui', + 'style-to-token', ]; const dependencyProperties = [ diff --git a/packages/codemod/transforms/__testfixtures__/style-to-token/class-component.input.js b/packages/codemod/transforms/__testfixtures__/style-to-token/class-component.input.js new file mode 100644 index 000000000..f696fb88d --- /dev/null +++ b/packages/codemod/transforms/__testfixtures__/style-to-token/class-component.input.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { Alert, Button } from '@oceanbase/design'; + +class Demo extends React.PureComponent { + constructor(props) { + super(props); + this.state = {}; + } + + render() { + return ( +
+ + +
+ ); + } +} + +export default Demo; diff --git a/packages/codemod/transforms/__testfixtures__/style-to-token/class-component.output.js b/packages/codemod/transforms/__testfixtures__/style-to-token/class-component.output.js new file mode 100644 index 000000000..64f5d3ae9 --- /dev/null +++ b/packages/codemod/transforms/__testfixtures__/style-to-token/class-component.output.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { Alert, Button, token } from '@oceanbase/design'; + +class Demo extends React.PureComponent { + constructor(props) { + super(props); + this.state = {}; + } + + render() { + return ( + (
+ + +
) + ); + } +} + +export default Demo; diff --git a/packages/codemod/transforms/__testfixtures__/style-to-token/function-component.input.js b/packages/codemod/transforms/__testfixtures__/style-to-token/function-component.input.js new file mode 100644 index 000000000..f3ccf3613 --- /dev/null +++ b/packages/codemod/transforms/__testfixtures__/style-to-token/function-component.input.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { Alert, Button } from '@oceanbase/design'; + +const Demo = () => { + return ( +
+ + +
+ ); +}; + +export default Demo; diff --git a/packages/codemod/transforms/__testfixtures__/style-to-token/function-component.output.js b/packages/codemod/transforms/__testfixtures__/style-to-token/function-component.output.js new file mode 100644 index 000000000..b660aafc2 --- /dev/null +++ b/packages/codemod/transforms/__testfixtures__/style-to-token/function-component.output.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { Alert, Button, theme } from '@oceanbase/design'; + +const Demo = () => { + const { token } = theme.useToken(); + return ( + (
+ + +
) + ); +}; + +export default Demo; diff --git a/packages/codemod/transforms/__testfixtures__/style-to-token/static.input.js b/packages/codemod/transforms/__testfixtures__/style-to-token/static.input.js new file mode 100644 index 000000000..88f0ee732 --- /dev/null +++ b/packages/codemod/transforms/__testfixtures__/style-to-token/static.input.js @@ -0,0 +1,27 @@ +const colorMap = { + info: '#1890ff', + success: '#52c41a', + warning: '#faad14', + error: '#ff4D4F', +}; + +function getColorList() { + return [ + { + type: 'info', + color: '#1890ff', + }, + { + type: 'success', + color: '#52c41a', + }, + { + type: 'warning', + color: '#faad14', + }, + { + type: 'error', + color: '#ff4D4F', + } + ]; +} diff --git a/packages/codemod/transforms/__testfixtures__/style-to-token/static.output.js b/packages/codemod/transforms/__testfixtures__/style-to-token/static.output.js new file mode 100644 index 000000000..cbde84094 --- /dev/null +++ b/packages/codemod/transforms/__testfixtures__/style-to-token/static.output.js @@ -0,0 +1,28 @@ +import { token } from '@oceanbase/design'; +const colorMap = { + info: token.colorInfo, + success: token.colorSuccess, + warning: token.colorWarning, + error: token.colorError, +}; + +function getColorList() { + return [ + { + type: 'info', + color: token.colorInfo, + }, + { + type: 'success', + color: token.colorSuccess, + }, + { + type: 'warning', + color: token.colorWarning, + }, + { + type: 'error', + color: token.colorError, + } + ]; +} diff --git a/packages/codemod/transforms/__tests__/style-to-token.test.ts b/packages/codemod/transforms/__tests__/style-to-token.test.ts new file mode 100644 index 000000000..351bbdb25 --- /dev/null +++ b/packages/codemod/transforms/__tests__/style-to-token.test.ts @@ -0,0 +1,10 @@ +import { defineTest } from 'jscodeshift/src/testUtils'; + +const testUnit = 'style-to-token'; +const tests = ['function-component', 'class-component', 'static']; + +describe(testUnit, () => { + tests.forEach(test => + defineTest(__dirname, testUnit, {}, `${testUnit}/${test}`, { parser: 'babylon' }) + ); +}); diff --git a/packages/codemod/transforms/style-to-token.js b/packages/codemod/transforms/style-to-token.js new file mode 100644 index 000000000..c3fef5d7d --- /dev/null +++ b/packages/codemod/transforms/style-to-token.js @@ -0,0 +1,110 @@ +const { toLower } = require('lodash'); +const { addSubmoduleImport } = require('./utils'); +const { printOptions } = require('./utils/config'); + +const TOKEN_MAP = { + // antd fixed style => antd v5 token + '#f0f2f5': 'colorBgLayout', + '#fafafa': 'colorBgLayout', + '#fff': 'colorBgContainer', + '#ffffff': 'colorBgContainer', + '#1890ff': 'colorInfo', + '#52c41a': 'colorSuccess', + '#73d13d': 'colorSuccess', + '#faad14': 'colorWarning', + '#ff4d4f': 'colorError', + '#F5222D': 'colorError', + '#F8636B': 'colorError', + '#d9d9d9': 'colorBorder', + '#bfbfbf': 'colorBorder', + 'rgba(0,0,0,0.85)': 'colorText', + 'rgba(0,0,0,0.65)': 'colorTextSecondary', + 'rgba(0,0,0,0.45)': 'colorTextTertiary', + 'rgba(0,0,0,0.25)': 'colorTextQuaternary', + 'rgba(0,0,0,0.2)': 'colorFillQuaternary', + 'rgba(0,0,0,0.04)': 'colorBgLayout', +}; + +function trimAll(str) { + return str?.replace(/(\s)*/g, ''); +} + +function formatValue(value) { + return trimAll(toLower(value)); +} + +function importComponent(j, root, options) { + let hasChanged = false; + + const stringList = root.find(j.StringLiteral, { + value: value => TOKEN_MAP[formatValue(value)], + }); + if (stringList.length > 0) { + // replace inline style to token + stringList.replaceWith(path => { + hasChanged = true; + const stringValue = path.value.value; + const mapToken = TOKEN_MAP[formatValue(stringValue)]; + return j.identifier(`token.${mapToken}`); + }); + + root.find(j.BlockStatement).forEach(path => { + const includeToken = + j(path).find(j.Identifier, { + name: name => name?.includes('token.'), + }).length > 0; + if (includeToken) { + const includeJsxElementList = j(path).find(j.JSXElement).length > 0; + const parentType = path.parentPath.value?.type; + // React function component + if (includeJsxElementList && parentType !== 'ClassMethod') { + const importString = `const { token } = theme.useToken()`; + path.get('body').value.unshift(j.expressionStatement(j.identifier(importString))); + // import theme from @oceanbase/design + addSubmoduleImport(j, root, { + moduleName: '@oceanbase/design', + importedName: 'theme', + importKind: 'value', + }); + } else { + // React class component and static file (not react component) + // import token from @oceanbase/design + addSubmoduleImport(j, root, { + moduleName: '@oceanbase/design', + importedName: 'token', + importKind: 'value', + }); + } + } + + // const name = path.value.declarations[0].id.name; + // if (/^[A-Z]/.test(name)) { + // const init = path.value.declarations[0].init; + // const initCode = generate(path.value).code; + // if ( + // init && + // initCode.includes('token.') && + // // avoid duplicate statement + // !initCode.includes('useToken()') && + // init.type === 'ArrowFunctionExpression' + // ) { + // const codeBody = init.body; + // const importStyleString = `const { token } = theme.useToken();`; + // codeBody.body.unshift(j.blockStatement(importStyleString).program.body[0]); + // } + // } + }); + } + + return hasChanged; +} + +module.exports = (file, api, options) => { + const j = api.jscodeshift; + const root = j(file.source); + + let hasChanged = false; + hasChanged = importComponent(j, root, options) || hasChanged; + + return hasChanged ? root.toSource(options.printOptions || printOptions) : null; +}; diff --git a/packages/codemod/transforms/utils/index.js b/packages/codemod/transforms/utils/index.js index 5f12c0eaf..c970c2cd5 100644 --- a/packages/codemod/transforms/utils/index.js +++ b/packages/codemod/transforms/utils/index.js @@ -151,7 +151,7 @@ function addModuleImport(j, root, { pkgName, importSpecifier, importKind, before if (before) { insertImportBefore(j, root, { importStatement, importKind, beforeModule: before }); - } else if (after) { + } else { insertImportAfter(j, root, { importStatement, importKind, afterModule: after }); }