├── .eslintignore ├── .gitignore ├── .travis.yml ├── transforms ├── __tests__ │ ├── GregorianCalendar-to-moment-test.js │ ├── Popover-overlay-to-content-test.js │ ├── getFieldProps-to-getFieldDecorator-test.js │ └── time-related-value-to-moment-test.js ├── __testfixtures__ │ ├── time-related-value-to-moment.with-moment.input.js │ ├── time-related-value-to-moment.with-moment.output.js │ ├── Popover-overlay-to-content.input.js │ ├── Popover-overlay-to-content.output.js │ ├── time-related-value-to-moment.input.js │ ├── time-related-value-to-moment.output.js │ ├── getFieldProps-to-getFieldDecorator.input.js │ ├── getFieldProps-to-getFieldDecorator.output.js │ ├── GregorianCalendar-to-moment.output.js │ └── GregorianCalendar-to-moment.input.js ├── Popover-overlay-to-content.js ├── utils.js ├── getJscodeshiftExtension.js ├── getFieldProps-to-getFieldDecorator.js ├── time-related-value-to-moment.js └── GregorianCalendar-to-moment.js ├── .eslintrc ├── package.json ├── LICENSE └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | /transforms/__testfixtures__ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /npm-debug.log 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "7" 5 | -------------------------------------------------------------------------------- /transforms/__tests__/GregorianCalendar-to-moment-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.autoMockOff(); // eslint-disable-line no-undef 4 | 5 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 6 | defineTest(__dirname, 'GregorianCalendar-to-moment'); 7 | -------------------------------------------------------------------------------- /transforms/__tests__/Popover-overlay-to-content-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.autoMockOff(); // eslint-disable-line no-undef 4 | 5 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 6 | defineTest(__dirname, 'Popover-overlay-to-content'); 7 | -------------------------------------------------------------------------------- /transforms/__tests__/getFieldProps-to-getFieldDecorator-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.autoMockOff(); // eslint-disable-line no-undef 4 | 5 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 6 | defineTest(__dirname, 'getFieldProps-to-getFieldDecorator'); 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint-config-egg"], 3 | "parser": "babel-eslint", 4 | "parserOptions": { 5 | "sourceType": "module", 6 | "ecmaVersion": 6, 7 | "ecmaFeatures": { 8 | "jsx": true, 9 | "experimentalObjectRestSpread": true 10 | } 11 | }, 12 | "rules": { 13 | "strict": 0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/time-related-value-to-moment.with-moment.input.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable quotes */ 2 | import moment from 'moment'; // eslint-disable-line no-unused-vars 3 | function Test(props) { // eslint-disable-line no-unused-vars 4 | return ( 5 |
6 | 7 | 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /transforms/__tests__/time-related-value-to-moment-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.autoMockOff(); // eslint-disable-line no-undef 4 | 5 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 6 | defineTest(__dirname, 'time-related-value-to-moment'); 7 | defineTest(__dirname, 'time-related-value-to-moment', null, 'time-related-value-to-moment.with-moment'); 8 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/time-related-value-to-moment.with-moment.output.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable quotes */ 2 | import moment from 'moment'; // eslint-disable-line no-unused-vars 3 | function Test(props) { // eslint-disable-line no-unused-vars 4 | return ( 5 |
6 | 7 | 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/Popover-overlay-to-content.input.js: -------------------------------------------------------------------------------- 1 | function Test() { // eslint-disable-line no-unused-vars 2 | return ( 3 | 6 | Content 7 | 8 | } 9 | title="Title" 10 | > 11 | Trigger 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/Popover-overlay-to-content.output.js: -------------------------------------------------------------------------------- 1 | function Test() { // eslint-disable-line no-unused-vars 2 | return ( 3 | 6 | Content 7 | 8 | } 9 | title="Title" 10 | > 11 | Trigger 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /transforms/Popover-overlay-to-content.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getExtension = require('./getJscodeshiftExtension'); 4 | 5 | module.exports = function(file, api) { 6 | const j = api.jscodeshift; 7 | j.registerMethods(getExtension(j)); 8 | const ast = j(file.source); 9 | 10 | ast.find(j.JSXOpeningElement, { 11 | name: { 12 | type: 'JSXIdentifier', 13 | name: 'Popover', 14 | }, 15 | }).map(nodePath => nodePath.get('attributes')) 16 | .children(j.JSXAttribute, { 17 | name: { 18 | type: 'JSXIdentifier', 19 | name: 'overlay', 20 | }, 21 | }) 22 | .map(nodePath => nodePath.get('name')) 23 | .replaceWith(j.jsxIdentifier('content')); 24 | 25 | return ast.toSource(); 26 | }; 27 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/time-related-value-to-moment.input.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable quotes */ 2 | function Test(props) { // eslint-disable-line no-unused-vars 3 | const time = '00:00:00'; 4 | const dateFormat = 'yyyy-MM-dd HH:mm:ss'; 5 | return ( 6 |
7 | {/* all the time-related components should workd */} 8 | 9 | 10 | 11 | 12 | 13 | {/* should handle `defaultValue` and `format`, too */} 14 | 18 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "antd-codemod", 3 | "description": "antd codemod scripts.", 4 | "scripts": { 5 | "lint": "eslint ./transforms", 6 | "eslint-fix": "eslint --fix ./transforms", 7 | "test": "jest" 8 | }, 9 | "jest": { 10 | "testEnvironment": "node" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/ant-design/antd-codemod.git" 15 | }, 16 | "keywords": [ 17 | "antd", 18 | "codemod", 19 | "scripts", 20 | "update", 21 | "jscodeshift" 22 | ], 23 | "author": "Benjy Cui", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/ant-design/antd-codemod/issues" 27 | }, 28 | "homepage": "https://github.com/ant-design/antd-codemod#readme", 29 | "devDependencies": { 30 | "babel-eslint": "^7.1.1", 31 | "eslint": "^3.10.2", 32 | "eslint-config-egg": "^3.2.0", 33 | "jest": "^17.0.3", 34 | "jscodeshift": "^0.3.30" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/time-related-value-to-moment.output.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | /* eslint-disable quotes */ 3 | function Test(props) { // eslint-disable-line no-unused-vars 4 | const time = '00:00:00'; 5 | const dateFormat = "YYYY-MM-DD HH:mm:ss"; 6 | return ( 7 |
8 | {/* all the time-related components should workd */} 9 | 10 | 11 | 12 | 13 | 14 | {/* should handle `defaultValue` and `format`, too */} 15 | 19 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /transforms/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.isTimeRelatedComponent = function isTimeRelatedComponent(componentName) { 4 | return [ 5 | 'DatePicker', 'TimePicker', 6 | 'Calendar', 'MonthPicker', 7 | // 'RangePicker' ignore RangePicker now 8 | ].indexOf(componentName) > -1; 9 | }; 10 | 11 | exports.getNameFieldValue = function getNameFieldValue(nodePath) { 12 | return nodePath.get('name').get('name').value; 13 | }; 14 | 15 | const hasOwn = 16 | Object.prototype.hasOwnProperty.call.bind(Object.prototype.hasOwnProperty); 17 | 18 | function isNode(value) { 19 | return typeof value === 'object' && value; 20 | } 21 | 22 | exports.matchNode = function matchNode(haystack, needle) { 23 | if (typeof needle === 'function') { 24 | return needle(haystack); 25 | } 26 | if (isNode(needle) && isNode(haystack)) { 27 | return Object.keys(needle).every(function(property) { 28 | return ( 29 | hasOwn(haystack, property) && 30 | matchNode(haystack[property], needle[property]) 31 | ); 32 | }); 33 | } 34 | return haystack === needle; 35 | }; 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/getFieldProps-to-getFieldDecorator.input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form } from 'antd'; 3 | 4 | const NormalForm = Form.create()(React.createClass({ // eslint-disable-line no-unused-vars 5 | render() { 6 | const { getFieldProps } = this.props.form; 7 | const nameField = ( 8 | 11 | ); 12 | return ( 13 |
14 | {nameField} 15 | 16 |
17 | ); 18 | }, 19 | })); 20 | 21 | function StatelessForm(props) { // eslint-disable-line no-unused-vars 22 | const getFieldProps = props.form.getFieldProps; 23 | 24 | const nameProps = getFieldProps('name', { 25 | rules: [{ required: true, message: 'Please input your name!' }], 26 | }); 27 | const nameField = ; 28 | 29 | const passwordProps = getFieldProps('password'); 30 | return ( 31 |
32 | {nameField} 33 | 34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/getFieldProps-to-getFieldDecorator.output.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form } from 'antd'; 3 | 4 | const NormalForm = Form.create()(React.createClass({ // eslint-disable-line no-unused-vars 5 | render() { 6 | const { getFieldDecorator } = this.props.form; 7 | const nameField = ( 8 | getFieldDecorator('name', { 9 | rules: [{ required: true, message: 'Please input your name!' }], 10 | })() 11 | ); 12 | return ( 13 |
14 | {nameField} 15 | {getFieldDecorator('password')()} 16 |
17 | ); 18 | }, 19 | })); 20 | 21 | function StatelessForm(props) { // eslint-disable-line no-unused-vars 22 | const getFieldDecorator = props.form.getFieldDecorator; 23 | 24 | const nameDecorator = getFieldDecorator('name', { 25 | rules: [{ required: true, message: 'Please input your name!' }], 26 | }); 27 | const nameField = nameDecorator(); 28 | 29 | const passwordDecorator = getFieldDecorator('password'); 30 | return ( 31 |
32 | {nameField} 33 | {passwordDecorator()} 34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/GregorianCalendar-to-moment.output.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const handleChange = function handleChange(date) { 4 | console.log(date.valueOf()); 5 | }; 6 | 7 | const NormalForm = React.createClass({ // eslint-disable-line no-unused-vars 8 | disabledDate(date) { 9 | console.log(date.valueOf()); 10 | }, 11 | disabledTime: date => { 12 | console.log(date.valueOf()); 13 | }, 14 | render() { 15 | return ( 16 |
17 | 22 | 23 | ); 24 | }, 25 | }); 26 | 27 | function handlePanelChange(date) { 28 | console.log(date.valueOf()); 29 | } 30 | 31 | class ClassForm extends React.Component { // eslint-disable-line no-unused-vars 32 | dateCellRender(date) { 33 | return
{date.date()}
; 34 | } 35 | 36 | monthCellRender = date => { 37 | return
{date.month()}
; 38 | } 39 | 40 | render() { 41 | return ( 42 |
43 | 48 | 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/GregorianCalendar-to-moment.input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const handleChange = function handleChange(date) { 4 | console.log(date.getTime()); 5 | }; 6 | 7 | const NormalForm = React.createClass({ // eslint-disable-line no-unused-vars 8 | disabledDate(date) { 9 | console.log(date.getTime()); 10 | }, 11 | disabledTime: date => { 12 | console.log(date.getTime()); 13 | }, 14 | render() { 15 | return ( 16 |
17 | 22 | 23 | ); 24 | }, 25 | }); 26 | 27 | function handlePanelChange(date) { 28 | console.log(date.getTime()); 29 | } 30 | 31 | class ClassForm extends React.Component { // eslint-disable-line no-unused-vars 32 | dateCellRender(date) { 33 | return
{date.getDayOfMonth()}
; 34 | } 35 | 36 | monthCellRender = date => { 37 | return
{date.getMonth()}
; 38 | } 39 | 40 | render() { 41 | return ( 42 |
43 | 48 | 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /transforms/getJscodeshiftExtension.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let extended = false; 4 | 5 | module.exports = function getExtension(j) { 6 | if (extended) { 7 | return {}; 8 | } 9 | extended = true; 10 | return { 11 | // works like `find`, but only get direct nodePaths 12 | children(type, filter) { 13 | return this.map(parentPath => { 14 | const collection = j(parentPath).find(type, filter) 15 | .filter(childPath => childPath.parentPath === parentPath); 16 | return collection.paths(); 17 | }); 18 | }, 19 | siblings(type, filter) { 20 | return this.map(selfPath => { 21 | const collection = j(selfPath.parentPath).children(type, filter); 22 | return collection.paths(); 23 | }); 24 | }, 25 | getFunctionDeclaration(nameGetter) { 26 | /* eslint-disable array-callback-return */ 27 | return this.map(varPath => { 28 | const currentScope = varPath.scope; 29 | if (!currentScope) { 30 | return; 31 | } 32 | const functionName = nameGetter(varPath); 33 | if (!functionName) { 34 | return; 35 | } 36 | const targetScope = currentScope.lookup(functionName); 37 | if (!targetScope) { 38 | return; 39 | } 40 | const bindings = targetScope.getBindings()[functionName]; 41 | if (!bindings) { 42 | return; 43 | } 44 | const decl = j(bindings).closest(j.FunctionDeclaration); 45 | if (decl.length === 1) { 46 | return decl.paths()[0]; 47 | } 48 | }); 49 | /* eslint-enable array-callback-return */ 50 | }, 51 | }; 52 | }; 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # antd-codemod 2 | 3 | [![](https://img.shields.io/travis/ant-design/antd-codemod.svg?style=flat-square)](https://travis-ci.org/ant-design/antd-codemod) 4 | [![Dependency Status](https://david-dm.org/ant-design/antd-codemod.svg?style=flat-square)](https://david-dm.org/ant-design/antd-codemod) 5 | 6 | This repository contains a collection of codemod scripts based for use with [JSCodeshift](https://github.com/facebook/jscodeshift) that help update `antd` APIs. 7 | 8 | ## Setup & Run 9 | 10 | * `npm install -g jscodeshift` 11 | * `git clone https://github.com/ant-design/antd-codemod.git` or download a zip file from `https://github.com/ant-design/antd-codemod/archive/master.zip` 12 | * Run `npm install` in the antd-codemod directory 13 | * `jscodeshift -t ` 14 | * Use the `-d` option for a dry-run and use `-p` to print the output for comparison 15 | 16 | ## Included Scripts 17 | 18 | ### 1.x-2.x 19 | 20 | #### `getFieldProps-to-getFieldDecorator` 21 | 22 | Replace deprecated `getFieldProps` with newer `getFieldDecorator`: 23 | 24 | ```diff 25 | - 26 | + {getFieldDecorator('userName', { ... })( 27 | + 28 | + )} 29 | ``` 30 | 31 | #### `Popover-overlay-to-content` 32 | 33 | `Popover[overlay]` is removed, so we need to replace it with `Popover[content]`: 34 | 35 | ```diff 36 | - 37 | + 38 | ``` 39 | 40 | #### `time-related-value-to-moment` 41 | 42 | Update `value` `defaultValue` and `format` of `DatePicker` `TimePicker` `Calendar` `MonthPicker`(not support `RangePicker` now): 43 | 44 | ```diff 45 | + import moment from 'moment'; 46 | 47 | 54 | ``` 55 | 56 | #### `GergorianCalendar-to-moment` 57 | 58 | Update GregorianCalendar's APIs to moment's APIs. 59 | 60 | ```diff 61 | function disabledDate(date) { 62 | - console.log(date.getTime()); 63 | + console.log(date.valueOf()); 64 | } 65 | ``` 66 | 67 | ## License 68 | 69 | MIT 70 | -------------------------------------------------------------------------------- /transforms/getFieldProps-to-getFieldDecorator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getFieldPropsIdentifier = { 4 | type: 'Identifier', 5 | name: 'getFieldProps', 6 | }; 7 | const getFieldPropsCallExpression = { 8 | callee: getFieldPropsIdentifier, 9 | }; 10 | 11 | function propsToDecorator(name) { 12 | return name.replace('Props', 'Decorator'); 13 | } 14 | 15 | function processGetFieldPropsInJSX(j, getFieldPropsNodes) { 16 | // wrap form controls with `getFieldDecorator(...)` 17 | const getFieldPropsInJSXNodes = getFieldPropsNodes 18 | .filter(nodePath => j.JSXSpreadAttribute.check(nodePath.parentPath.value)); 19 | getFieldPropsInJSXNodes.closest(j.JSXElement) 20 | .replaceWith(nodePath => { 21 | const getFieldPropsNode = j(nodePath) 22 | .find(j.CallExpression, getFieldPropsCallExpression).nodes()[0]; 23 | const callExpression = j.callExpression( 24 | j.callExpression(getFieldPropsNode.callee, getFieldPropsNode.arguments), 25 | [ nodePath.value ] 26 | ); 27 | if (j.JSXElement.check(nodePath.parentPath.parentPath.value)) { 28 | return j.jsxExpressionContainer(callExpression); 29 | } 30 | return callExpression; 31 | }); 32 | // remove `getFieldProps` in JSX 33 | getFieldPropsInJSXNodes.closest(j.JSXSpreadAttribute).remove(); 34 | } 35 | 36 | function processGetFieldPropsWithTmp(j, getFieldPropsNodes) { 37 | const tmpPropsIdentifierNodes = getFieldPropsNodes 38 | .filter(nodePath => j.VariableDeclarator.check(nodePath.parentPath.value)) 39 | .closest(j.VariableDeclarator) 40 | .map(nodePath => nodePath.get('id')); 41 | // replace `` with `nameDecorator()` 42 | const spreadPropsInJSXNodes = tmpPropsIdentifierNodes.map(nodePath => { 43 | return j(nodePath).closestScope() 44 | .find(j.JSXSpreadAttribute, { 45 | argument: { 46 | type: 'Identifier', 47 | name: nodePath.value.name, 48 | }, 49 | }) 50 | .get(); 51 | }); 52 | spreadPropsInJSXNodes.forEach(spreadProps => { 53 | const relativeJSXElement = j(spreadProps).closest(j.JSXElement); 54 | relativeJSXElement.replaceWith(nodePath => { 55 | const callExpression = j.callExpression( 56 | j.identifier(propsToDecorator(spreadProps.value.argument.name)), 57 | relativeJSXElement.nodes() 58 | ); 59 | if (j.JSXElement.check(nodePath.parentPath.parentPath.value)) { 60 | return j.jsxExpressionContainer(callExpression); 61 | } 62 | return callExpression; 63 | }); 64 | }); 65 | spreadPropsInJSXNodes.remove(); 66 | // replace `const nameProps = getFieldProps(...)` with `const nameDecorator = getFieldDecorator` 67 | tmpPropsIdentifierNodes.replaceWith(nodePath => j.identifier(propsToDecorator(nodePath.value.name))); 68 | } 69 | 70 | module.exports = function(file, api) { 71 | const j = api.jscodeshift; 72 | const ast = j(file.source); 73 | 74 | const getFieldPropsNodes = ast.find(j.CallExpression, getFieldPropsCallExpression); 75 | processGetFieldPropsInJSX(j, getFieldPropsNodes); 76 | processGetFieldPropsWithTmp(j, getFieldPropsNodes); 77 | 78 | // rename `getFieldProps` to `getFieldDecorator` 79 | ast.find(j.Identifier, getFieldPropsIdentifier) 80 | .replaceWith(j.identifier('getFieldDecorator')); 81 | 82 | return ast.toSource(); 83 | }; 84 | -------------------------------------------------------------------------------- /transforms/time-related-value-to-moment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getExtension = require('./getJscodeshiftExtension'); 4 | const utils = require('./utils'); 5 | 6 | function oldFormatToNewFormat(format) { 7 | return format.split('').map(c => { 8 | if (c === 'y') { 9 | return 'Y'; 10 | } else if (c === 'd') { 11 | return 'D'; 12 | } 13 | return c; 14 | }).join(''); 15 | } 16 | 17 | function getDefaultFormat(componentName) { 18 | if (componentName === 'MonthPicker') { 19 | return 'YYYY-MM'; 20 | } else if (componentName === 'TimePicker') { 21 | return 'HH:mm:ss'; 22 | } 23 | return 'YYYY-MM-DD'; 24 | } 25 | 26 | function getComponentNameFromAttr(j, attr) { 27 | return j(attr).closest(j.JSXOpeningElement).nodes()[0].name.name; 28 | } 29 | 30 | module.exports = function(file, api) { 31 | const j = api.jscodeshift; 32 | j.registerMethods(getExtension(j)); 33 | const ast = j(file.source); 34 | const template = j.template; 35 | 36 | const timeRelatedOpeningElementsAttrs = ast.find(j.JSXOpeningElement) 37 | .filter(nodePath => utils.isTimeRelatedComponent(utils.getNameFieldValue(nodePath))) 38 | .map(nodePath => nodePath.get('attributes')); 39 | 40 | // update `format` to moment format 41 | timeRelatedOpeningElementsAttrs 42 | .children(j.JSXAttribute, { 43 | name: { 44 | type: 'JSXIdentifier', 45 | name: 'format', 46 | }, 47 | }) 48 | .forEach(formatAttr => { 49 | const $formatAttr = j(formatAttr); 50 | const $formatLiteral = $formatAttr.find(j.Literal); 51 | if ($formatLiteral.size() > 0) { 52 | $formatLiteral.replaceWith(nodePath => j.literal(oldFormatToNewFormat(nodePath.value.value))); 53 | return; 54 | } 55 | $formatAttr.getVariableDeclarators(nodePath => { 56 | const formatVariable = nodePath.value.value.expression; 57 | return formatVariable.name; 58 | }).forEach(declarator => { 59 | j(declarator.get('init')).replaceWith(nodePath => j.literal(oldFormatToNewFormat(nodePath.value.value))); 60 | }); 61 | }); 62 | 63 | let hadUsedMoment = false; 64 | // update `value` || `defaultValue` to moment object 65 | timeRelatedOpeningElementsAttrs 66 | .children(j.JSXAttribute, nodePath => { 67 | const attrName = nodePath.name.name; 68 | return attrName === 'value' || attrName === 'defaultValue'; 69 | }) 70 | .forEach(attrToModify => { 71 | const $attrToModify = j(attrToModify); 72 | const formatAttr = $attrToModify.siblings(j.JSXAttribute, { 73 | name: { 74 | type: 'JSXIdentifier', 75 | name: 'format', 76 | }, 77 | }); 78 | const formatAttrPath = formatAttr.nodes()[0]; 79 | const formatValue = formatAttr.find(j.Literal).nodes()[0] || 80 | formatAttrPath && formatAttrPath.value.expression || 81 | j.identifier(JSON.stringify(getDefaultFormat(getComponentNameFromAttr(j, attrToModify)))); 82 | 83 | j(attrToModify.get('value')) 84 | .forEach(nodePath => { 85 | const $valueToBeReplaced = j.JSXExpressionContainer.check(nodePath.value) && 86 | j.ArrayExpression.check(nodePath.value.expression) ? 87 | j(nodePath.get('expression').get('elements')) : j(nodePath); 88 | 89 | $valueToBeReplaced.replaceWith(({ value }) => { 90 | function wrapInMoment(extractedValue) { 91 | hadUsedMoment = true; 92 | const attrValue = j.Literal.check(extractedValue) ? extractedValue : extractedValue.expression; 93 | return j.callExpression(j.identifier('moment'), [ attrValue, formatValue ]); 94 | } 95 | return j.jsxExpressionContainer(wrapInMoment(value)); 96 | }); 97 | }); 98 | }); 99 | 100 | if (hadUsedMoment) { 101 | // make sure that `import moment from 'moment'` exist 102 | const importMomentStatement = ast.find(j.ImportDeclaration, { 103 | source: { 104 | type: 'Literal', 105 | value: 'moment', 106 | }, 107 | }); 108 | if (importMomentStatement.size() === 0) { 109 | const statement = template.statement`import moment from 'moment';\n`; 110 | ast.find(j.Program).get('body').get(0) 111 | .insertBefore(statement); 112 | } 113 | } 114 | 115 | return ast.toSource(); 116 | }; 117 | -------------------------------------------------------------------------------- /transforms/GregorianCalendar-to-moment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getExtension = require('./getJscodeshiftExtension'); 4 | const utils = require('./utils'); 5 | 6 | function isCallbackWithDateObject(attributeName) { 7 | return [ 8 | 'disabledDate', 'disabledTime', 'onChange', 9 | 'dateCellRender', 'monthCellRender', 'onPanelChange', 10 | ].indexOf(attributeName) > -1; 11 | } 12 | 13 | const apisMap = { 14 | getTime: 'valueOf', 15 | getDayOfMonth: 'date', 16 | getMonth: 'month', 17 | }; 18 | function getMomentAPI(gregorianCalendarAPI) { 19 | return apisMap[gregorianCalendarAPI] || gregorianCalendarAPI; 20 | } 21 | 22 | function getMethodName(expressionPath) { 23 | // this.xxx 24 | const name = expressionPath.get('property').get('name').value; 25 | if (!name) { 26 | // this.xxx.bind(this) 27 | return expressionPath 28 | .get('callee') 29 | .get('object') 30 | .get('property') 31 | .get('name').value; 32 | } 33 | return name; 34 | } 35 | 36 | module.exports = function(file, api) { 37 | const j = api.jscodeshift; 38 | j.registerMethods(getExtension(j)); 39 | const ast = j(file.source); 40 | 41 | function replaceGregorianCalendarAPIToMoment(functionExpressionPath) { 42 | const firstArgument = functionExpressionPath.get('params').value[0]; 43 | if (!firstArgument) { 44 | return; 45 | } 46 | const firstArgumentName = firstArgument.name; 47 | j(functionExpressionPath.get('body')) 48 | .find(j.CallExpression, { 49 | callee: { 50 | type: 'MemberExpression', 51 | object: { 52 | name: firstArgumentName, 53 | }, 54 | }, 55 | }) 56 | .map(nodePath => nodePath.get('callee').get('property')) 57 | .replaceWith(nodePath => j.identifier(getMomentAPI(nodePath.get('name').value))); 58 | } 59 | 60 | const callbackExpression = ast.find(j.JSXOpeningElement) 61 | .filter(nodePath => utils.isTimeRelatedComponent(utils.getNameFieldValue(nodePath))) 62 | .map(nodePath => nodePath.get('attributes')) 63 | .children(j.JSXAttribute) 64 | .filter(nodePath => isCallbackWithDateObject(utils.getNameFieldValue(nodePath))) 65 | .map(nodePath => nodePath.get('value').get('expression')); 66 | 67 | callbackExpression.filter(nodePath => j.Identifier.check(nodePath.value)) 68 | .getVariableDeclarators(nodePath => nodePath.get('name').value) 69 | .map(nodePath => nodePath.get('init')) 70 | .forEach(replaceGregorianCalendarAPIToMoment); 71 | callbackExpression.filter(nodePath => j.Identifier.check(nodePath.value)) 72 | .getFunctionDeclaration(nodePath => nodePath.get('name').value) 73 | .forEach(replaceGregorianCalendarAPIToMoment); 74 | 75 | function isFromThis(nodePath) { 76 | // this.xxx 77 | return j.MemberExpression.check(nodePath.value) && 78 | j.ThisExpression.check(nodePath.get('object').value) || 79 | // this.xxx.bind(this) 80 | j.CallExpression.check(nodePath.value) && 81 | utils.matchNode(nodePath.value, { 82 | callee: { 83 | type: 'MemberExpression', 84 | object: { 85 | type: 'MemberExpression', 86 | object: { type: 'ThisExpression' }, 87 | }, 88 | property: { name: 'bind' }, 89 | }, 90 | arguments: [{ type: 'ThisExpression' }], 91 | }) && nodePath.value.arguments.length === 1; 92 | } 93 | const callbackFromThis = callbackExpression.filter(isFromThis); 94 | callbackFromThis 95 | .map(expressionPath => { 96 | return j(expressionPath) 97 | .closest(j.CallExpression, { 98 | callee: { 99 | type: 'MemberExpression', 100 | object: { 101 | name: 'React', 102 | }, 103 | property: { 104 | name: 'createClass', 105 | }, 106 | }, 107 | }) 108 | .map(nodePath => nodePath.get('arguments')) 109 | .children(j.ObjectExpression) 110 | .map(nodePath => nodePath.get('properties')) 111 | .children(j.Property) 112 | .filter(propertyPath => { 113 | return propertyPath.get('key').get('name').value === 114 | getMethodName(expressionPath); 115 | }) 116 | .map(nodePath => nodePath.get('value')) 117 | .paths(); 118 | }) 119 | .forEach(replaceGregorianCalendarAPIToMoment); 120 | callbackFromThis 121 | .map(expressionPath => { 122 | const classDefinition = j(expressionPath) 123 | .closest(j.ClassDeclaration) 124 | .map(nodePath => nodePath.get('body').get('body')); 125 | const methodsDefinition = classDefinition.children(j.MethodDefinition).paths() 126 | .concat(classDefinition.children(j.ClassProperty).filter(nodePath => { 127 | const node = nodePath.get('value').value; 128 | return j.ArrowFunctionExpression.check(node) || 129 | j.FunctionExpression.check(node); 130 | }).paths()); 131 | return methodsDefinition.filter(propertyPath => { 132 | return propertyPath.get('key').get('name').value === 133 | getMethodName(expressionPath); 134 | }).map(nodePath => nodePath.get('value')); 135 | }) 136 | .forEach(replaceGregorianCalendarAPIToMoment); 137 | 138 | return ast.toSource(); 139 | }; 140 | --------------------------------------------------------------------------------