Skip to content

Commit

Permalink
feat: update (#40)
Browse files Browse the repository at this point in the history
* feat: update

* feat: update
  • Loading branch information
vagusX authored Jan 5, 2020
1 parent 29fb9fe commit 9e0b2fb
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 40 deletions.
2 changes: 2 additions & 0 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const transformersDir = path.join(__dirname, '../transforms');
const babylonConfig = path.join(__dirname, './babylon.config.json');

const transformers = [
// TODO: 考虑大多数项目并没有直接使用新版本的 `@antd-design/icons`
// 该项 codemod script 考虑动态加载
'v4-Icon-Outlined',
'v3-Icon-to-v4-Icon',
'v3-Modal-method-with-icon-to-v4',
Expand Down
4 changes: 4 additions & 0 deletions transforms/__testfixtures__/v3-Icon-to-v4-Icon/misc.input.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ ReactDOM.render(
</Timeline>,
mountNode,
);

const DynamicIcon = props => {
<Icon type={props.type} theme={props.theme} />
}
5 changes: 5 additions & 0 deletions transforms/__testfixtures__/v3-Icon-to-v4-Icon/misc.output.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ClockCircleOutlined, SmileTwoTone } from '@ant-design/icons';
import { Icon as LegacyIcon } from '@ant-design/compatible';
import { Result, Timeline } from 'antd';

ReactDOM.render(
Expand All @@ -21,3 +22,7 @@ ReactDOM.render(
</Timeline>,
mountNode,
);

const DynamicIcon = props => {
<LegacyIcon type={props.type} theme={props.theme} />
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,11 @@ function warning() {
content: 'some messages...some messages...',
});
}

function warningWithProps(props) {
Modal.warning({
title: 'This is a warning message',
icon: props.icon,
content: 'some messages...some messages...',
});
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AntDesignOutlined, MinusOutlined, PlusOutlined, QuestionOutlined } from '@ant-design/icons';
import { Icon as LegacyIcon } from '@ant-design/compatible';
import { Modal } from 'antd';

function showConfirm() {
Expand Down Expand Up @@ -51,3 +52,11 @@ function warning() {
content: 'some messages...some messages...',
});
}

function warningWithProps(props) {
Modal.warning({
title: 'This is a warning message',
icon: <LegacyIcon type={props.icon} />,
content: 'some messages...some messages...',
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,11 @@ ReactDOM.render(
</div>,
mountNode,
);

const CustomAvatar = (props) => {
return (
<Badge count={1}>
<Avatar shape="square" icon={props.icon} />
</Badge>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { UserFilled, UserOutlined } from '@ant-design/icons';
import { Icon as LegacyIcon } from '@ant-design/compatible';
import { Avatar, Badge } from 'antd';

ReactDOM.render(
Expand All @@ -21,3 +22,11 @@ ReactDOM.render(
</div>,
mountNode,
);

const CustomAvatar = (props) => {
return (
<Badge count={1}>
<Avatar shape="square" icon={<LegacyIcon type={props.icon} />} />
</Badge>
);
}
4 changes: 2 additions & 2 deletions transforms/utils/icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ function getV4IconComponentName(type, theme) {
);
}

function createIconJSXElement(j, iconLocalName) {
function createIconJSXElement(j, iconLocalName, attrs = []) {
const openingElement = j.jsxOpeningElement(
j.jsxIdentifier(iconLocalName),
[],
attrs,
);
openingElement.selfClosing = true;
return j.jsxElement(openingElement);
Expand Down
9 changes: 9 additions & 0 deletions transforms/utils/summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,17 @@ function cleanup() {
fs.unlinkSync(summaryFilePath);
}

function addIconRelatedMsg(file, location, source) {
return appendLine(
`${file.path} - ${location.line}:${location.column}`,
source,
'Contains an invalid icon, please check it at https://ant.design/components/icon',
);
}

module.exports = {
start,
appendLine,
output,
addIconRelatedMsg,
};
81 changes: 63 additions & 18 deletions transforms/v3-Modal-method-with-icon-to-v4.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,37 @@ module.exports = (file, api, options) => {
const root = j(file.source);
const antdPkgNames = parseStrToArray(options.antdPkgNames || 'antd');

function importV4SpecificIcon(j, iconName, before) {
const iconJSXElement = createIconJSXElement(j, iconName);

addSubmoduleImport(j, root, {
moduleName: '@ant-design/icons',
importedName: iconName,
before,
});

return iconJSXElement;
}

function importLegacyIcon(j, iconProperty, before) {
// handle it with `@ant-design/compatible`
const typeAttr = j.jsxAttribute(
j.jsxIdentifier('type'),
j.jsxExpressionContainer(iconProperty.value),
);
const iconJSXElement = createIconJSXElement(j, 'LegacyIcon', [typeAttr]);

// add @ant-design/compatible imports
addSubmoduleImport(j, root, {
moduleName: '@ant-design/compatible',
importedName: 'Icon',
localName: 'LegacyIcon',
before,
});

return iconJSXElement;
}

// rename old Model.method() calls with `icon#string` argument
function renameV3ModalMethodCalls(j, root) {
let hasChanged = false;
Expand Down Expand Up @@ -52,37 +83,51 @@ module.exports = (file, api, options) => {
!nodePath.node.arguments[0] ||
nodePath.node.arguments[0].type !== 'ObjectExpression'
) {
// FIXME: need log?
// but we cannot know it contains `icon` property
return;
}

const args = nodePath.node.arguments[0];
const iconProperty = args.properties.find(
property =>
property.key.type === 'Identifier' &&
property.key.name === 'icon' &&
property.value.type === 'StringLiteral',
property.key.name === 'icon',
);

if (!iconProperty) {
// v3-Icon-to-v4-Icon should handle with JSXElement
if (!iconProperty || iconProperty.value.type === 'JSXElement') {
return;
}

const v3IconName = iconProperty.value.value;
const v4IconComponentName = getV4IconComponentName(v3IconName);
if (v4IconComponentName) {
const iconJSXElement = createIconJSXElement(
j,
v4IconComponentName,
);
iconProperty.value = iconJSXElement;

addSubmoduleImport(j, root, {
moduleName: '@ant-design/icons',
importedName: v4IconComponentName,
before: antdPkgName,
});
hasChanged = true;
hasChanged = true;

if (iconProperty.value.type === 'StringLiteral') {
const v3IconName = iconProperty.value.value;
const v4IconComponentName = getV4IconComponentName(v3IconName);
if (v4IconComponentName) {
const jsxElement = importV4SpecificIcon(
j,
v4IconComponentName,
antdPkgName,
);
iconProperty.value = jsxElement;
return;
} else {
// FIXME: use parent jsxElement
const location = nodePath.node.loc.start;
const message =
'Contains an invalid icon, please check it at https://ant.design/components/icon';
summary.appendLine(
`${file.path} - ${location.line}:${location.column}`,
j(nodePath).toSource(),
message,
);
}
}

const jsxElement = importLegacyIcon(j, iconProperty, antdPkgName);
iconProperty.value = jsxElement;
});
});

Expand Down
79 changes: 59 additions & 20 deletions transforms/v3-component-with-string-icon-props-to-v4.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const {
addSubmoduleImport,
} = require('./utils');
const { printOptions } = require('./utils/config');
const { addIconRelatedMsg } = require('./utils/summary');
const {
createIconJSXElement,
getV4IconComponentName,
Expand Down Expand Up @@ -38,31 +39,69 @@ module.exports = (file, api, options) => {
type: 'JSXIdentifier',
name: 'icon',
},
value: {
type: 'StringLiteral',
},
})
.find(j.Literal)
.forEach(path => {
// TODO: 是否考虑将非 Literal 的值增加 warning 的 log
const v4IconComponentName = getV4IconComponentName(
path.value.value,
.filter(nodePath => {
return (
nodePath.node.type === 'StringLiteral' ||
nodePath.node.type !== 'JSXExpressionContainer'
);
if (v4IconComponentName) {
const iconJSXElement = createIconJSXElement(
j,
v4IconComponentName,
})
.forEach(path => {
hasChanged = true;

const iconProperty = path.value;

// v3-Icon-to-v4-Icon should handle with JSXElement
if (
iconProperty.value.type === 'JSXExpressionContainer' &&
iconProperty.value.expression.type === 'JSXElement'
) {
return;
}

if (iconProperty.value.type === 'StringLiteral') {
const v4IconComponentName = getV4IconComponentName(
iconProperty.value.value,
);
// we need a brace to wrap a jsxElement to pass Icon prop
path.parent.node.value = j.jsxExpressionContainer(iconJSXElement);
if (v4IconComponentName) {
const iconJSXElement = createIconJSXElement(
j,
v4IconComponentName,
);
// we need a brace to wrap a jsxElement to pass Icon prop
iconProperty.value = j.jsxExpressionContainer(iconJSXElement);

addSubmoduleImport(j, root, {
moduleName: '@ant-design/icons',
importedName: v4IconComponentName,
before: antdPkgName,
});
return;
} else {
const location = path.node.loc.start;

addSubmoduleImport(j, root, {
moduleName: '@ant-design/icons',
importedName: v4IconComponentName,
before: antdPkgName,
});
hasChanged = true;
addIconRelatedMsg(file, location, j(nodePath).toSource());
}
}

// handle it with `@ant-design/compatible`
const typeAttr = j.jsxAttribute(
j.jsxIdentifier('type'),
iconProperty.value,
);
const iconJSXElement = createIconJSXElement(j, 'LegacyIcon', [
typeAttr,
]);
// we need a brace to wrap a jsxElement to pass Icon prop
iconProperty.value = j.jsxExpressionContainer(iconJSXElement);

// add @ant-design/compatible imports
addSubmoduleImport(j, root, {
moduleName: '@ant-design/compatible',
importedName: 'Icon',
localName: 'LegacyIcon',
before: antdPkgName,
});
});
});

Expand Down

0 comments on commit 9e0b2fb

Please sign in to comment.