├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── dependabot.yml └── workflows │ ├── dependency-review.yml │ ├── ossar.yml │ └── test.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .prettierignore ├── .prettierrc.js ├── .vscode ├── launch.json └── launch.json-new ├── LICENSE.md ├── README.md ├── README.zh-CN.md ├── bin ├── antd-codemod.js ├── babylon.config.json ├── cli.js ├── codemod.ignore └── upgrade-list.json ├── jest.config.js ├── less-transforms ├── __tests__ │ ├── fixtures │ │ ├── input.less │ │ └── output.less │ └── index.test.js ├── index.js ├── postcss-plugin-remove-antd-less.js └── transform.js ├── package.json ├── transforms ├── __testfixtures__ │ ├── .eslintrc.js │ ├── v5-props-changed-migration │ │ ├── basic.input.js │ │ ├── basic.output.js │ │ ├── compound.input.js │ │ └── compound.output.js │ ├── v5-remove-style-import │ │ ├── basic.input.js │ │ └── basic.output.js │ ├── v5-removed-component-migration │ │ ├── alias-import.input.js │ │ ├── alias-import.output.js │ │ ├── basic.input.js │ │ ├── basic.output.js │ │ ├── empty.input.js │ │ ├── empty.output.js │ │ ├── imported.input.js │ │ └── imported.output.js │ └── v5-removed-static-method-migration │ │ ├── message.input.js │ │ ├── message.output.js │ │ ├── notification.input.js │ │ └── notification.output.js ├── __tests__ │ ├── v5-props-changed-migration.test.ts │ ├── v5-remove-style-import.test.ts │ ├── v5-removed-component-migration.test.ts │ └── v5-removed-static-method-migration.test.ts ├── utils │ ├── ast.js │ ├── compatible-icon-utils.js │ ├── config.js │ ├── index.js │ └── marker.js ├── v5-props-changed-migration.js ├── v5-remove-style-import.js ├── v5-removed-component-migration.js └── v5-removed-static-method-migration.js └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*.{js,css,md}] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | __testfixtures__/ 2 | __tests__/ -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: [require.resolve('@umijs/fabric/dist/eslint')], 5 | rules: { 6 | 'no-template-curly-in-string': 0, 7 | 'prefer-promise-reject-errors': 0, 8 | 'react/no-array-index-key': 0, 9 | 'react/sort-comp': 0, 10 | '@typescript-eslint/no-explicit-any': 0, 11 | 'no-return-await': 0, 12 | 'consistent-return': 0, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 4 | # 5 | # Source repository: https://github.com/actions/dependency-review-action 6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 7 | name: 'Dependency Review' 8 | on: [pull_request] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | dependency-review: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: 'Checkout Repository' 18 | uses: actions/checkout@v3 19 | - name: 'Dependency Review' 20 | uses: actions/dependency-review-action@v2 21 | -------------------------------------------------------------------------------- /.github/workflows/ossar.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | # This workflow integrates a collection of open source static analysis tools 7 | # with GitHub code scanning. For documentation, or to provide feedback, visit 8 | # https://github.com/github/ossar-action 9 | name: OSSAR 10 | 11 | on: 12 | push: 13 | branches: [ "main" ] 14 | pull_request: 15 | # The branches below must be a subset of the branches above 16 | branches: [ "main" ] 17 | schedule: 18 | - cron: '24 4 * * 0' 19 | 20 | permissions: 21 | contents: read 22 | 23 | jobs: 24 | OSSAR-Scan: 25 | # OSSAR runs on windows-latest. 26 | # ubuntu-latest and macos-latest support coming soon 27 | permissions: 28 | contents: read # for actions/checkout to fetch code 29 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 30 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 31 | runs-on: windows-latest 32 | 33 | steps: 34 | - name: Checkout repository 35 | uses: actions/checkout@v3 36 | 37 | # Ensure a compatible version of dotnet is installed. 38 | # The [Microsoft Security Code Analysis CLI](https://aka.ms/mscadocs) is built with dotnet v3.1.201. 39 | # A version greater than or equal to v3.1.201 of dotnet must be installed on the agent in order to run this action. 40 | # GitHub hosted runners already have a compatible version of dotnet installed and this step may be skipped. 41 | # For self-hosted runners, ensure dotnet version 3.1.201 or later is installed by including this action: 42 | # - name: Install .NET 43 | # uses: actions/setup-dotnet@v2 44 | # with: 45 | # dotnet-version: '3.1.x' 46 | 47 | # Run open source static analysis tools 48 | - name: Run OSSAR 49 | uses: github/ossar-action@v1 50 | id: ossar 51 | 52 | # Upload results to the Security tab 53 | - name: Upload OSSAR results 54 | uses: github/codeql-action/upload-sarif@v2 55 | with: 56 | sarif_file: ${{ steps.ossar.outputs.sarifFile }} 57 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: ['main'] 9 | pull_request: 10 | branches: ['main'] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [14.x, 16.x, 18.x] 19 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | cache: 'npm' 28 | - run: npm i 29 | - run: npm test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .storybook 2 | *.iml 3 | *.log 4 | .idea/ 5 | .ipr 6 | .iws 7 | *~ 8 | ~* 9 | *.diff 10 | *.patch 11 | *.bak 12 | .DS_Store 13 | Thumbs.db 14 | .project 15 | .*proj 16 | .svn/ 17 | *.swp 18 | *.swo 19 | *.pyc 20 | *.pyo 21 | .build 22 | node_modules 23 | .cache 24 | assets/**/*.css 25 | build 26 | lib 27 | es 28 | yarn.lock 29 | package-lock.json 30 | coverage/ 31 | .doc 32 | 33 | cli_fixtures/ 34 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.diff 2 | *.log 3 | *.patch 4 | *.bak 5 | .DS_Store 6 | .github 7 | .vscode 8 | node_modules 9 | .eslint* 10 | .prettier* 11 | __testfixtures__ 12 | __tests__ 13 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .storybook 2 | node_modules 3 | lib 4 | es 5 | .cache 6 | package.json 7 | package-lock.json 8 | public 9 | .site 10 | _site 11 | .umi 12 | .doc 13 | __testfixtures__ 14 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require('@umijs/fabric'); 2 | 3 | module.exports = { 4 | ...fabric.prettier, 5 | }; 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Jest Current File", 8 | "program": "${workspaceFolder}/node_modules/.bin/jest", 9 | "args": ["${relativeFile}"], 10 | "console": "integratedTerminal", 11 | "internalConsoleOptions": "neverOpen", 12 | "disableOptimisticBPs": true, 13 | "windows": { 14 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 15 | } 16 | }, 17 | { 18 | "type": "node", 19 | "request": "launch", 20 | "name": "Jest All", 21 | "program": "${workspaceFolder}/node_modules/.bin/jest", 22 | "args": ["--runInBand"], 23 | "console": "integratedTerminal", 24 | "internalConsoleOptions": "neverOpen", 25 | "disableOptimisticBPs": true, 26 | "windows": { 27 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 28 | } 29 | }, 30 | { 31 | "type": "node", 32 | "request": "launch", 33 | "name": "Launch cli", 34 | "env": { 35 | "NODE_ENV": "local" 36 | }, 37 | "program": "${workspaceFolder}/bin/antd-codemod.js", 38 | "cwd": "${workspaceFolder}/es", 39 | "args": ["run", "--dir=*.js", "--parser=babylon", "--style"], 40 | "skipFiles": ["/**"] 41 | }, 42 | { 43 | "name": "Launch Program", 44 | "args": ["${relativeFile}"], 45 | "request": "launch", 46 | "skipFiles": ["/**"], 47 | "type": "node" 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /.vscode/launch.json-new: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Debug Transform", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceRoot}/node_modules/.bin/jscodeshift", 15 | "stopOnEntry": false, 16 | "args": [ 17 | "--dry", 18 | "--print", 19 | "-t", 20 | "${input:transformFile}", 21 | "--parser", 22 | "${input:parser}", 23 | "--run-in-band", 24 | "${file}" 25 | ], 26 | "preLaunchTask": null, 27 | "runtimeExecutable": null, 28 | "runtimeArgs": [ 29 | "--nolazy" 30 | ], 31 | "console": "internalConsole", 32 | "sourceMaps": true, 33 | "outFiles": [] 34 | }, 35 | { 36 | "name": "Debug All JSCodeshift Jest Tests", 37 | "type": "node", 38 | "request": "launch", 39 | "runtimeArgs": [ 40 | "--inspect-brk", 41 | "${workspaceRoot}/node_modules/jest/bin/jest.js", 42 | "--runInBand", 43 | "--testPathPattern=${fileBasenameNoExtension}" 44 | ], 45 | "console": "integratedTerminal", 46 | "internalConsoleOptions": "neverOpen" 47 | } 48 | ], 49 | "inputs": [ 50 | { 51 | "type": "pickString", 52 | "id": "parser", 53 | "description": "jscodeshift parser", 54 | "options": [ 55 | "babel", 56 | "babylon", 57 | "flow", 58 | "ts", 59 | "tsx", 60 | ], 61 | "default": "babel" 62 | }, 63 | { 64 | "type": "promptString", 65 | "id": "transformFile", 66 | "description": "jscodeshift transform file", 67 | "default": "transform.js" 68 | } 69 | ] 70 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright (c) 2015-present Ant UED, https://xtech.antfin.com/ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | English | [简体中文](./README.zh-CN.md) 2 | 3 | # Ant Design v5 Codemod 4 | 5 | A collection of codemod scripts that help upgrade antd v5 using [jscodeshift](https://github.com/facebook/jscodeshift) and [postcss](https://github.com/postcss/postcss).(Inspired by [react-codemod](https://github.com/reactjs/react-codemod)) 6 | 7 | [![NPM version](https://img.shields.io/npm/v/@ant-design/codemod-v5.svg?style=flat)](https://npmjs.org/package/@ant-design/codemod-v5) [![NPM downloads](http://img.shields.io/npm/dm/@ant-design/codemod-v5.svg?style=flat)](https://npmjs.org/package/@ant-design/codemod-v5) [![Github Action](https://github.com/ant-design/codemod-v5/actions/workflows/test.yml/badge.svg)](https://github.com/ant-design/codemod-v5/actions/workflows/test.yml) 8 | 9 | ## Usage 10 | 11 | Before run codemod scripts, you'd better make sure to commit your local git changes firstly. 12 | 13 | ```shell 14 | # Run directly through npx 15 | npx -p @ant-design/codemod-v5 antd5-codemod src 16 | 17 | # Or run directly through pnpm 18 | pnpm --package=@ant-design/codemod-v5 dlx antd5-codemod src 19 | ``` 20 | 21 | ## Codemod scripts introduction 22 | 23 | #### `v5-removed-component-migration` 24 | 25 | Replace import for removed component in v5. 26 | 27 | - Change `Comment` import from `@ant-design/compatible`. 28 | - Change `PageHeader` import from `@ant-design/pro-layout`. 29 | - Use `BackTop` from `FloatButton.BackTop`. 30 | 31 | ```diff 32 | - import { Avatar, BackTop, Comment, PageHeader } from 'antd'; 33 | + import { Comment } from '@ant-design/compatible'; 34 | + import { PageHeader } from '@ant-design/pro-layout'; 35 | + import { Avatar, FloatButton } from 'antd'; 36 | 37 | ReactDOM.render( ( 38 |
39 | null} 42 | title="Title" 43 | subTitle="This is a subtitle" 44 | /> 45 | Han Solo} 48 | avatar={} 49 | content={ 50 |

51 | We supply a series of design principles, practical patterns and high quality design 52 | resources (Sketch and Axure), to help people create their product prototypes beautifully 53 | and efficiently. 54 |

55 | } 56 | datetime={ 57 | 8 hours ago 58 | } 59 | /> 60 | - 61 | + 62 |
63 | ); 64 | ``` 65 | 66 | #### `v5-props-changed-migration` 67 | 68 | Change props usage from v4 to v5. 69 | 70 | ```diff 71 | import { Tag, Modal, Slider } from 'antd'; 72 | 73 | const App = () => { 74 | const [visible, setVisible] = useState(false); 75 | 76 | return ( 77 | <> 78 | - 81 | + {visible ? : null} 82 | 86 | - 87 | + 88 | 89 | ); 90 | }; 91 | ``` 92 | 93 | #### `v5-removed-static-method-migration` 94 | 95 | * Replace `message.warn` with `message.warning`. 96 | * Replace `notification.close` with `notification.destroy`. 97 | 98 | ```diff 99 | import { message, notification } from 'antd'; 100 | 101 | const App = () => { 102 | const [messageApi, contextHolder] = message.useMessage(); 103 | const onClick1 = () => { 104 | - message.warn(); 105 | + message.warning(); 106 | } 107 | const onClick2 = () => { 108 | - messageApi.warn(); 109 | + messageApi.warning(); 110 | }; 111 | 112 | const [notificationApi] = notification.useNotification(); 113 | const onClick3 = () => { 114 | - notification.close(); 115 | + notification.destroy(); 116 | } 117 | const onClick4 = () => { 118 | - notificationApi.close(); 119 | + notificationApi.destroy(); 120 | }; 121 | 122 | return <>{contextHolder}; 123 | }; 124 | ``` 125 | 126 | #### `v5-remove-style-import` 127 | 128 | Comment out the style file import from antd (in js file). 129 | 130 | ```diff 131 | - import 'antd/es/auto-complete/style'; 132 | - import 'antd/lib/button/style/index.less'; 133 | - import 'antd/dist/antd.compact.min.css'; 134 | + // import 'antd/es/auto-complete/style'; 135 | + // import 'antd/lib/button/style/index.less'; 136 | + // import 'antd/dist/antd.compact.min.css'; 137 | ``` 138 | 139 | #### `Remove Antd Less` in less file 140 | 141 | Comment out the style file import from antd in less file. 142 | 143 | ```diff 144 | - @import (reference) '~antd/dist/antd.less'; 145 | - @import '~antd/es/button/style/index.less'; 146 | + /* @import (reference) '~antd/dist/antd.less'; */ 147 | + /* @import '~antd/es/button/style/index.less'; */ 148 | @import './styles.less'; 149 | 150 | body { 151 | font-size: 14px; 152 | } 153 | ``` 154 | 155 | ## Development 156 | 157 | ```bash 158 | npm run release 159 | npm publish 160 | ``` 161 | 162 | ## License 163 | 164 | MIT 165 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | [English](./README.md) | 简体中文 2 | 3 | # Ant Design 5 Codemod 4 | 5 | 一组帮助你升级到 antd 5 的 codemod 脚本集合,基于 [jscodeshift](https://github.com/facebook/jscodeshift) 和 and [postcss](https://github.com/postcss/postcss) 构建。(受 [react-codemod](https://github.com/reactjs/react-codemod) 启发) 6 | 7 | [![NPM version](https://img.shields.io/npm/v/@ant-design/codemod-v5.svg?style=flat)](https://npmjs.org/package/@ant-design/codemod-v5) [![NPM downloads](http://img.shields.io/npm/dm/@ant-design/codemod-v5.svg?style=flat)](https://npmjs.org/package/@ant-design/codemod-v5) [![Github Action](https://github.com/ant-design/codemod-v5/actions/workflows/test.yml/badge.svg)](https://github.com/ant-design/codemod-v5/actions/workflows/test.yml) 8 | 9 | ## 使用 10 | 11 | 在运行 codemod 脚本前,请先提交你的本地代码修改。 12 | 13 | ```shell 14 | # 使用 npx 直接运行 15 | npx -p @ant-design/codemod-v5 antd5-codemod src 16 | # 或者使用 pnpm 直接运行 17 | pnpm --package=@ant-design/codemod-v5 dlx antd5-codemod src 18 | ``` 19 | 20 | ## Codemod 脚本包括: 21 | 22 | #### `v5-removed-component-migration` 23 | 24 | Replace import for removed component in v5. 25 | 26 | - 将 `Comment` 改为从 `@ant-design/compatible` import. 27 | - 将 `PageHeader` 改为从 `@ant-design/pro-layout` import. 28 | - 从 `FloatButton.BackTop` 组件中导入并使用 `BackTop`. 29 | 30 | ```diff 31 | - import { Avatar, BackTop, Comment, PageHeader } from 'antd'; 32 | + import { Comment } from '@ant-design/compatible'; 33 | + import { PageHeader } from '@ant-design/pro-layout'; 34 | + import { Avatar, FloatButton } from 'antd'; 35 | 36 | ReactDOM.render( ( 37 |
38 | null} 41 | title="Title" 42 | subTitle="This is a subtitle" 43 | /> 44 | Han Solo} 47 | avatar={} 48 | content={ 49 |

50 | We supply a series of design principles, practical patterns and high quality design 51 | resources (Sketch and Axure), to help people create their product prototypes beautifully 52 | and efficiently. 53 |

54 | } 55 | datetime={ 56 | 8 hours ago 57 | } 58 | /> 59 | - 60 | + 61 |
62 | ); 63 | ``` 64 | 65 | #### `v5-props-changed-migration` 66 | 67 | 将 v4 中部分 props 用法迁移到 v5 版本. 68 | 69 | ```diff 70 | import { Tag, Modal, Slider } from 'antd'; 71 | 72 | const App = () => { 73 | const [visible, setVisible] = useState(false); 74 | 75 | return ( 76 | <> 77 | - 80 | + {visible ? : null} 81 | 85 | - 86 | + 87 | 88 | ); 89 | }; 90 | ``` 91 | 92 | #### `v5-removed-static-method-migration` 93 | 94 | * 替换 `message.warn` 为 `message.warning`。 95 | * 替换 `notification.close` 为 `notification.destroy`。 96 | 97 | ```diff 98 | import { message, notification } from 'antd'; 99 | 100 | const App = () => { 101 | const [messageApi, contextHolder] = message.useMessage(); 102 | const onClick1 = () => { 103 | - message.warn(); 104 | + message.warning(); 105 | } 106 | const onClick2 = () => { 107 | - messageApi.warn(); 108 | + messageApi.warning(); 109 | }; 110 | 111 | const [notificationApi] = notification.useNotification(); 112 | const onClick3 = () => { 113 | - notification.close(); 114 | + notification.destroy(); 115 | } 116 | const onClick4 = () => { 117 | - notificationApi.close(); 118 | + notificationApi.destroy(); 119 | }; 120 | 121 | return <>{contextHolder}; 122 | }; 123 | ``` 124 | 125 | #### `v5-remove-style-import` 126 | 127 | 注释掉 js 文件中的 antd 样式文件导入。 128 | 129 | ```diff 130 | - import 'antd/es/auto-complete/style'; 131 | - import 'antd/lib/button/style/index.less'; 132 | - import 'antd/dist/antd.compact.min.css'; 133 | + // import 'antd/es/auto-complete/style'; 134 | + // import 'antd/lib/button/style/index.less'; 135 | + // import 'antd/dist/antd.compact.min.css'; 136 | ``` 137 | 138 | #### `Remove Antd Less` in less file 139 | 140 | 注释掉 less 文件中的 antd 样式文件导入。 141 | 142 | ```diff 143 | - @import (reference) '~antd/dist/antd.less'; 144 | - @import '~antd/es/button/style/index.less'; 145 | + /* @import (reference) '~antd/dist/antd.less'; */ 146 | + /* @import '~antd/es/button/style/index.less'; */ 147 | @import './styles.less'; 148 | 149 | body { 150 | font-size: 14px; 151 | } 152 | ``` 153 | 154 | ## Development 155 | 156 | ```bash 157 | npm run release 158 | npm publish 159 | ``` 160 | 161 | ## License 162 | 163 | MIT 164 | -------------------------------------------------------------------------------- /bin/antd-codemod.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('./cli').bootstrap(); 4 | -------------------------------------------------------------------------------- /bin/babylon.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceType": "module", 3 | "allowImportExportEverywhere": true, 4 | "allowReturnOutsideFunction": true, 5 | "startLine": 1, 6 | "tokens": true, 7 | "plugins": [ 8 | "jsx", 9 | "asyncGenerators", 10 | "bigInt", 11 | "classProperties", 12 | "classPrivateProperties", 13 | "classPrivateMethods", 14 | [ 15 | "decorators", 16 | { 17 | "decoratorsBeforeExport": true 18 | } 19 | ], 20 | "doExpressions", 21 | "dynamicImport", 22 | "exportDefaultFrom", 23 | "exportExtensions", 24 | "exportNamespaceFrom", 25 | "functionBind", 26 | "functionSent", 27 | "importMeta", 28 | "logicalAssignment", 29 | "nullishCoalescingOperator", 30 | "numericSeparator", 31 | "objectRestSpread", 32 | "optionalCatchBinding", 33 | "optionalChaining", 34 | [ 35 | "pipelineOperator", 36 | { 37 | "proposal": "minimal" 38 | } 39 | ], 40 | "throwExpressions", 41 | "typescript" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const os = require('os'); 6 | 7 | const _ = require('lodash'); 8 | const chalk = require('chalk'); 9 | const isGitClean = require('is-git-clean'); 10 | const updateCheck = require('update-check'); 11 | const findUp = require('find-up'); 12 | const semver = require('semver'); 13 | const { run: jscodeshift } = require('jscodeshift/src/Runner'); 14 | 15 | const pkg = require('../package.json'); 16 | const pkgUpgradeList = require('./upgrade-list'); 17 | const { getDependencies } = require('../transforms/utils/marker'); 18 | const transformLess = require('../less-transforms'); 19 | 20 | // jscodeshift codemod scripts dir 21 | const transformersDir = path.join(__dirname, '../transforms'); 22 | 23 | // jscodeshift bin#--ignore-config 24 | const ignoreConfig = path.join(__dirname, './codemod.ignore'); 25 | 26 | const transformers = [ 27 | 'v5-props-changed-migration', 28 | 'v5-removed-component-migration', 29 | 'v5-remove-style-import', 30 | ]; 31 | 32 | const dependencyProperties = [ 33 | 'dependencies', 34 | 'devDependencies', 35 | 'clientDependencies', 36 | 'isomorphicDependencies', 37 | 'buildDependencies', 38 | ]; 39 | 40 | async function ensureGitClean() { 41 | let clean = false; 42 | try { 43 | clean = await isGitClean(); 44 | } catch (err) { 45 | if ( 46 | err && 47 | err.stderr && 48 | err.stderr.toLowerCase().includes('not a git repository') 49 | ) { 50 | clean = true; 51 | } 52 | } 53 | 54 | if (!clean) { 55 | console.log(chalk.yellow('Sorry that there are still some git changes')); 56 | console.log('\n you must commit or stash them firstly'); 57 | process.exit(1); 58 | } 59 | } 60 | 61 | async function checkUpdates() { 62 | let update; 63 | try { 64 | update = await updateCheck(pkg); 65 | } catch (err) { 66 | console.log(chalk.yellow(`Failed to check for updates: ${err}`)); 67 | } 68 | 69 | if (update) { 70 | console.log( 71 | chalk.blue(`Latest version is ${update.latest}. Please update firstly`), 72 | ); 73 | process.exit(1); 74 | } 75 | } 76 | 77 | function getMaxWorkers(options = {}) { 78 | // limit usage for cpus 79 | return options.cpus || Math.max(2, Math.ceil(os.cpus().length / 3)); 80 | } 81 | 82 | function getRunnerArgs( 83 | transformerPath, 84 | parser = 'babylon', // use babylon as default parser 85 | options = {}, 86 | ) { 87 | const args = { 88 | verbose: 2, 89 | // limit usage for cpus 90 | cpus: getMaxWorkers(options), 91 | // https://github.com/facebook/jscodeshift/blob/master/src/Runner.js#L255 92 | // https://github.com/facebook/jscodeshift/blob/master/src/Worker.js#L50 93 | babel: false, 94 | parser, 95 | // override default babylon parser config to enable `decorator-legacy` 96 | // https://github.com/facebook/jscodeshift/blob/master/parser/babylon.js 97 | parserConfig: require('./babylon.config.json'), 98 | extensions: ['tsx', 'ts', 'jsx', 'js'].join(','), 99 | transform: transformerPath, 100 | ignorePattern: '**/node_modules', 101 | ignoreConfig, 102 | args: ['antd', '@alipay/bigfish/antd'], 103 | }; 104 | 105 | return args; 106 | } 107 | 108 | async function run(filePath, args = {}) { 109 | for (const transformer of transformers) { 110 | await transform(transformer, 'babylon', filePath, args); 111 | } 112 | 113 | await lessTransform(filePath, args); 114 | } 115 | 116 | async function lessTransform(filePath, options) { 117 | const maxWorkers = getMaxWorkers(options); 118 | const dir = path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath); 119 | // less part 120 | // `@antd/xxxx` | `~@antd/xxxx` 121 | return await transformLess(dir, { maxWorkers }); 122 | } 123 | 124 | async function transform(transformer, parser, filePath, options) { 125 | console.log(chalk.bgGreen.bold('Transform'), transformer); 126 | const transformerPath = path.join(transformersDir, `${transformer}.js`); 127 | 128 | // pass closet .gitignore to jscodeshift as extra `--ignore-file` option 129 | // const gitignorePath = await findGitIgnore(filePath); 130 | 131 | const args = getRunnerArgs(transformerPath, parser, { 132 | ...options, 133 | // gitignore: gitignorePath, 134 | }); 135 | 136 | try { 137 | if (process.env.NODE_ENV === 'local') { 138 | console.log(`Running jscodeshift with: ${JSON.stringify(args)}`); 139 | } 140 | 141 | // js part 142 | await jscodeshift(transformerPath, [ filePath ], args); 143 | } catch (err) { 144 | console.error(err); 145 | if (process.env.NODE_ENV === 'local') { 146 | const errorLogFile = path.join(__dirname, './error.log'); 147 | fs.appendFileSync(errorLogFile, err); 148 | fs.appendFileSync(errorLogFile, '\n'); 149 | } 150 | } 151 | } 152 | 153 | async function upgradeDetect(targetDir, needProLayout, needCompatible) { 154 | const result = []; 155 | const cwd = path.join(process.cwd(), targetDir); 156 | const { readPackageUp } = await import('read-pkg-up'); 157 | const closetPkgJson = await readPackageUp({ cwd }); 158 | 159 | let pkgJsonPath; 160 | if (!closetPkgJson) { 161 | pkgJsonPath = "we didn't find your package.json"; 162 | // unknown dependency property 163 | result.push(['install', 'antd', pkgUpgradeList.antd]); 164 | if (needProLayout) { 165 | result.push([ 166 | 'install', 167 | '@ant-design/pro-layout', 168 | pkgUpgradeList['@ant-design/pro-layout'].version, 169 | ]); 170 | } 171 | 172 | if (needCompatible) { 173 | result.push([ 174 | 'install', 175 | '@ant-design/compatible', 176 | pkgUpgradeList['@ant-design/compatible'].version, 177 | ]); 178 | } 179 | } else { 180 | const { packageJson } = closetPkgJson; 181 | pkgJsonPath = closetPkgJson.path; 182 | 183 | // dependencies must be installed or upgraded with correct version 184 | const mustInstallOrUpgradeDeps = ['antd']; 185 | if (needProLayout) { 186 | mustInstallOrUpgradeDeps.push('@ant-design/pro-layout'); 187 | } 188 | if (needCompatible) { 189 | mustInstallOrUpgradeDeps.push('@ant-design/compatible'); 190 | } 191 | 192 | // handle mustInstallOrUpgradeDeps 193 | mustInstallOrUpgradeDeps.forEach(depName => { 194 | let hasDependency = false; 195 | const expectVersion = pkgUpgradeList[depName].version; 196 | // const upgradePkgDescription = pkgUpgradeList[depName].description; 197 | dependencyProperties.forEach(property => { 198 | const versionRange = _.get(packageJson, `${property}.${depName}`); 199 | // mark dependency installment state 200 | hasDependency = hasDependency || !!versionRange; 201 | // no dependency or improper version dependency 202 | if (versionRange && !semver.satisfies(semver.minVersion(versionRange), expectVersion)) { 203 | result.push(['update', depName, expectVersion, property]); 204 | } 205 | }); 206 | if (!hasDependency) { 207 | // unknown dependency property 208 | result.push(['install', depName, pkgUpgradeList[depName].version]); 209 | } 210 | }); 211 | 212 | // dependencies must be upgraded to correct version 213 | const mustUpgradeDeps = _.without( 214 | Object.keys(pkgUpgradeList), 215 | ...mustInstallOrUpgradeDeps, 216 | ); 217 | mustUpgradeDeps.forEach(depName => { 218 | dependencyProperties.forEach(property => { 219 | const expectVersion = pkgUpgradeList[depName].version; 220 | const versionRange = _.get(packageJson, `${property}.${depName}`); 221 | /** 222 | * we may have dependencies in `package.json` 223 | * make sure that they can `work well` with `antd5` 224 | * so we check dependency's version here 225 | */ 226 | if (versionRange && !semver.satisfies(semver.minVersion(versionRange), expectVersion)) { 227 | result.push(['update', depName, expectVersion, property]); 228 | } 229 | }); 230 | }); 231 | } 232 | 233 | if (!result.length) { 234 | console.log(chalk.green('Checking passed')); 235 | return; 236 | } 237 | 238 | console.log( 239 | chalk.yellow( 240 | "It's recommended to install or upgrade these dependencies to ensure working well with antd v5\n", 241 | ), 242 | ); 243 | console.log(`> package.json file: ${pkgJsonPath} \n`); 244 | const dependencies = result.map( 245 | ([operateType, depName, expectVersion, dependencyProperty]) => 246 | [ 247 | _.capitalize(operateType), 248 | `${depName}${expectVersion}`, 249 | dependencyProperty ? `in ${dependencyProperty}` : '', 250 | ].join(' '), 251 | ); 252 | 253 | console.log(dependencies.map(n => `* ${n}`).join('\n')); 254 | } 255 | 256 | async function findGitIgnore(targetDir) { 257 | const cwd = path.join(process.cwd(), targetDir); 258 | return await findUp('.gitignore', { cwd }); 259 | } 260 | 261 | /** 262 | * options 263 | * --force // force skip git checking (dangerously) 264 | * --cpus=1 // specify cpus cores to use 265 | */ 266 | 267 | async function bootstrap() { 268 | const dir = process.argv[2]; 269 | // eslint-disable-next-line global-require 270 | const args = require('yargs-parser')(process.argv.slice(3)); 271 | if (process.env.NODE_ENV !== 'local') { 272 | // check for updates 273 | await checkUpdates(); 274 | // check for git status 275 | if (!args.force) { 276 | await ensureGitClean(); 277 | } else { 278 | console.log( 279 | Array(3) 280 | .fill(1) 281 | .map(() => 282 | chalk.yellow( 283 | 'WARNING: You are trying to skip git status checking, please be careful', 284 | ), 285 | ) 286 | .join('\n'), 287 | ); 288 | } 289 | } 290 | 291 | // check for `path` 292 | if (!dir || !fs.existsSync(dir)) { 293 | console.log(chalk.yellow('Invalid dir:', dir, ', please pass a valid dir')); 294 | process.exit(1); 295 | } 296 | 297 | await run(dir, args); 298 | 299 | try { 300 | console.log('----------- antd5 dependencies alert -----------\n'); 301 | const depsList = await getDependencies(); 302 | await upgradeDetect( 303 | dir, 304 | depsList.includes('@ant-design/pro-layout'), 305 | depsList.includes('@ant-design/compatible'), 306 | ); 307 | } catch (err) { 308 | console.log('skip summary due to', err); 309 | } finally { 310 | console.log( 311 | `\n----------- Thanks for using @ant-design/codemod ${pkg.version} -----------`, 312 | ); 313 | } 314 | } 315 | 316 | module.exports = { 317 | bootstrap, 318 | ensureGitClean, 319 | transform, 320 | run, 321 | getRunnerArgs, 322 | checkUpdates, 323 | }; 324 | -------------------------------------------------------------------------------- /bin/codemod.ignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.css 3 | *.json 4 | *.less 5 | *.sass 6 | *.scss 7 | .umi -------------------------------------------------------------------------------- /bin/upgrade-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "antd": { 3 | "version": "^5.0.0" 4 | }, 5 | "@ant-design/icons": { 6 | "version": "^4.0.0" 7 | }, 8 | "@ant-design/compatible": { 9 | "version": "^5.0.0" 10 | }, 11 | "@ant-design/pro-layout": { 12 | "version": "^7.0.0" 13 | }, 14 | "react": { 15 | "version": "^16.9.0" 16 | }, 17 | "react-dom": { 18 | "version": "^16.9.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | snapshotSerializers: [require.resolve('enzyme-to-json/serializer')], 3 | transform: { '\\.ts$': ['ts-jest'] }, 4 | }; 5 | -------------------------------------------------------------------------------- /less-transforms/__tests__/fixtures/input.less: -------------------------------------------------------------------------------- 1 | @import (reference) '~antd/dist/antd.less'; 2 | @import '~antd/es/button/style/index.less'; 3 | @import '@ant-design/compatible/assets/index.css'; 4 | @import (reference) '~antd/es/style/themes/index'; 5 | @import './styles.less'; 6 | 7 | body { 8 | font-size: 14px; 9 | } 10 | -------------------------------------------------------------------------------- /less-transforms/__tests__/fixtures/output.less: -------------------------------------------------------------------------------- 1 | /* @import (reference) '~antd/dist/antd.less'; */ 2 | /* @import '~antd/es/button/style/index.less'; */ 3 | /* @import '@ant-design/compatible/assets/index.css'; */ 4 | /* @import (reference) '~antd/es/style/themes/index'; */ 5 | @import './styles.less'; 6 | 7 | body { 8 | font-size: 14px; 9 | } 10 | -------------------------------------------------------------------------------- /less-transforms/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const { transform } = require('../transform'); 5 | 6 | const fixturesDir = path.join(__dirname, './fixtures'); 7 | const inputLess = path.join(fixturesDir, 'input.less'); 8 | const outputLess = path.join(fixturesDir, 'output.less'); 9 | 10 | it('less transform', async () => { 11 | const content = await fs.promises.readFile(inputLess, 'utf8'); 12 | const output = await transform(content, { from: inputLess }); 13 | expect(output).toBe(await fs.promises.readFile(outputLess, 'utf8')); 14 | }); 15 | -------------------------------------------------------------------------------- /less-transforms/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { promisify } = require('util'); 3 | const glob = require('glob'); 4 | const { performance } = require('perf_hooks'); 5 | 6 | const globAsync = promisify(glob); 7 | 8 | const { transformFile } = require('./transform'); 9 | 10 | module.exports = async (dir, options = {}) => { 11 | const { ignore = ['**/node_modules/**', '**/dist/**'], maxWorkers = 2 } = options; 12 | const start = performance.now(); 13 | 14 | const files = await globAsync(`**/*.less`, { 15 | ignore, 16 | cwd: path.isAbsolute(dir) ? dir : path.join(process.cwd(), dir), 17 | }); 18 | 19 | const { default: ora } = await import('ora'); 20 | 21 | const transformers = files.map(file => async () => { 22 | const spinner = ora({ 23 | symbol: chalk.bgGreen(' Transform '), 24 | text: file, 25 | }).start(); 26 | const changed = await transformFile(path.resolve(dir, file)); 27 | spinner.stopAndPersist({ 28 | symbol: changed ? chalk.bgGreen(' OK ') : chalk.bgYellow(' SKIP '), 29 | }); 30 | return changed; 31 | }); 32 | 33 | const { default: pAll } = await import('p-all'); 34 | const { default: chalk } = await import('chalk'); 35 | 36 | console.log('\n'); 37 | console.log('%s remove-antd-less-import', chalk.bgGreen(' Transform ')); 38 | console.log(`Processing ${files.length} files...`); 39 | 40 | // All done. 41 | // Results: 42 | // 0 errors 43 | // 0 unmodified 44 | // 33 skipped 45 | // 0 ok 46 | // Time elapsed: 0.384seconds 47 | 48 | const result = await pAll(transformers, { concurrency: maxWorkers }); 49 | console.log('All done.'); 50 | console.log('Result:'); 51 | const changedFileCount = result.filter(Boolean).length; 52 | console.log(chalk.yellow(`${result.length - changedFileCount} skipped`)); 53 | console.log(chalk.green(`${changedFileCount} ok`)); 54 | console.log(`Time elapsed: ${((performance.now() - start) / 1000).toFixed(3)}seconds`); 55 | console.log('\n'); 56 | }; 57 | -------------------------------------------------------------------------------- /less-transforms/postcss-plugin-remove-antd-less.js: -------------------------------------------------------------------------------- 1 | const testAntdImportRegex = /(?:\(.+\)\s+)?(?:"|').*(~?antd\/.+(\.less)?|~?@ant-design\/compatible\/assets\/index\.css)(?:"|');?$/; 2 | 3 | module.exports = (opts = {}) => { 4 | return { 5 | postcssPlugin: 'postcss-plugin-remove-antd-less', 6 | AtRule: { 7 | import(atRule, { postcss }) { 8 | // TODO: handle forked antd 9 | const params = atRule.params.replace(atRule.options || '', ''); 10 | if (testAntdImportRegex.test(params)) { 11 | const commentedRule = postcss.comment({ 12 | text: `${atRule.toString()};`, 13 | }); 14 | atRule.replaceWith(commentedRule); 15 | } 16 | }, 17 | }, 18 | }; 19 | }; 20 | 21 | module.exports.postcss = true; 22 | -------------------------------------------------------------------------------- /less-transforms/transform.js: -------------------------------------------------------------------------------- 1 | const postcss = require('postcss'); 2 | const lessSyntax = require('postcss-less'); 3 | const fs = require('fs'); 4 | 5 | const removeImport = require('./postcss-plugin-remove-antd-less'); 6 | 7 | exports.transform = async (content, opts = {}) => { 8 | const result = await postcss([removeImport]).process(content, { 9 | syntax: lessSyntax, 10 | ...opts, 11 | }); 12 | return result.toString(); 13 | }; 14 | 15 | exports.transformFile = async filename => { 16 | const content = await fs.promises.readFile(filename, 'utf8'); 17 | 18 | const result = await exports.transform(content, { from: filename }); 19 | 20 | const changed = result.length !== content.length; 21 | if (changed) { 22 | await fs.promises.writeFile(filename, result, 'utf8'); 23 | } 24 | return changed; 25 | }; 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ant-design/codemod-v5", 3 | "version": "1.1.5", 4 | "description": "Codemod for ant design v5 upgrade", 5 | "bin": { 6 | "antd5-codemod": "./bin/antd-codemod.js" 7 | }, 8 | "keywords": [ 9 | "ant design", 10 | "codemod", 11 | "antd" 12 | ], 13 | "homepage": "https://github.com/ant-design/codemod-v5", 14 | "repository": { 15 | "type": "git", 16 | "url": "git@github.com:ant-design/codemod-v5.git" 17 | }, 18 | "bugs": { 19 | "url": "http://github.com/ant-design/codemod-v5/issues" 20 | }, 21 | "license": "MIT", 22 | "engines": { 23 | "node": ">=8.0.0" 24 | }, 25 | "publishConfig": { 26 | "registry": "https://registry.npmjs.org", 27 | "access": "public" 28 | }, 29 | "scripts": { 30 | "release": "np --yolo --no-publish", 31 | "lint": "eslint transforms/ --ext .ts,.tsx,.jsx,.js,.md", 32 | "prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"", 33 | "test": "jest" 34 | }, 35 | "dependencies": { 36 | "chalk": "^3.0.0", 37 | "find-up": "^4.1.0", 38 | "glob": "^8.0.3", 39 | "is-git-clean": "^1.1.0", 40 | "jscodeshift": "^0.14.0", 41 | "lodash": "^4.17.15", 42 | "ora": "^6.1.2", 43 | "p-all": "^4.0.0", 44 | "postcss": "^8.4.19", 45 | "postcss-less": "^6.0.0", 46 | "read-pkg-up": "^9.1.0", 47 | "semver": "^7.1.3", 48 | "update-check": "^1.5.3", 49 | "yargs-parser": "^21.1.1" 50 | }, 51 | "devDependencies": { 52 | "@types/jest": "^29.2.3", 53 | "@types/jscodeshift": "^0.11.5", 54 | "@umijs/fabric": "^3.0.0", 55 | "enzyme": "^3.0.0", 56 | "enzyme-to-json": "^3.4.0", 57 | "eslint": "^7.1.0", 58 | "husky": "^4.3.0", 59 | "jest": "^29.3.1", 60 | "lint-staged": "^13.0.4", 61 | "np": "^7.0.0", 62 | "prettier": "^2.8.0", 63 | "ts-jest": "^29.0.3", 64 | "typescript": "^4.0.3" 65 | }, 66 | "husky": { 67 | "hooks": { 68 | "pre-commit": "lint-staged" 69 | } 70 | }, 71 | "lint-staged": { 72 | "src/**/*.{ts,tsx,js,jsx,json,md}": [ 73 | "prettier --write" 74 | ] 75 | }, 76 | "cnpm": { 77 | "mode": "npm" 78 | }, 79 | "tnpm": { 80 | "mode": "npm" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'no-undef': 0, 4 | 'no-unused-vars': 0, 5 | 'no-redeclare': 0, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-props-changed-migration/basic.input.js: -------------------------------------------------------------------------------- 1 | import { Drawer, Tag, Modal, Slider } from 'antd'; 2 | 3 | const App = () => { 4 | const [visible, setVisible] = useState(false); 5 | 6 | return ( 7 | <> 8 | 11 | 14 | 17 | 18 | 19 | ); 20 | }; 21 | 22 | const App1 = () => { 23 | const [visible, setVisible] = useState(false); 24 | 25 | return ( 26 | 29 | ); 30 | }; 31 | 32 | const App2 = () => { 33 | return ( 34 | 37 | ); 38 | }; 39 | 40 | const App3 = () => { 41 | return ( 42 | 48 |
Some contents...
49 |
Some contents...
50 |
51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-props-changed-migration/basic.output.js: -------------------------------------------------------------------------------- 1 | import { Drawer, Tag, Modal, Slider } from 'antd'; 2 | 3 | const App = () => { 4 | const [visible, setVisible] = useState(false); 5 | 6 | return (<> 7 | {visible ? : null} 8 | 9 | 12 | 17 | ); 18 | }; 19 | 20 | const App1 = () => { 21 | const [visible, setVisible] = useState(false); 22 | 23 | return visible ? () : null; 24 | }; 25 | 26 | const App2 = () => { 27 | return (); 28 | }; 29 | 30 | const App3 = () => { 31 | return ( 32 | ( 38 |
Some contents...
39 |
Some contents...
40 |
) 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-props-changed-migration/compound.input.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DatePicker } from 'antd'; 3 | 4 | const { RangePicker } = DatePicker; 5 | 6 | const RangePicker1 = DatePicker.RangePicker; 7 | const RangePicker2 = RangePicker1; 8 | const RangePicker3 = RangePicker1; 9 | const RangePicker4 = RangePicker2; 10 | 11 | const RangePicker5 = RangePicker; 12 | const RangePicker6 = RangePicker5; 13 | 14 | const Comp = () => { 15 | return ( 16 | <> 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-props-changed-migration/compound.output.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DatePicker } from 'antd'; 3 | 4 | const { RangePicker } = DatePicker; 5 | 6 | const RangePicker1 = DatePicker.RangePicker; 7 | const RangePicker2 = RangePicker1; 8 | const RangePicker3 = RangePicker1; 9 | const RangePicker4 = RangePicker2; 10 | 11 | const RangePicker5 = RangePicker; 12 | const RangePicker6 = RangePicker5; 13 | 14 | const Comp = () => { 15 | return (<> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-remove-style-import/basic.input.js: -------------------------------------------------------------------------------- 1 | import 'antd/es/auto-complete/style'; 2 | import 'antd/lib/button/style/index.less'; 3 | import 'antd/dist/antd.compact.min.css'; 4 | import '@ant-design/compatible/assets/index.css'; 5 | 6 | const a = 1; 7 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-remove-style-import/basic.output.js: -------------------------------------------------------------------------------- 1 | // import 'antd/es/auto-complete/style'; 2 | 3 | 4 | // import 'antd/lib/button/style/index.less'; 5 | 6 | 7 | // import 'antd/dist/antd.compact.min.css'; 8 | 9 | 10 | // import '@ant-design/compatible/assets/index.css'; 11 | 12 | 13 | const a = 1; 14 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-removed-component-migration/alias-import.input.js: -------------------------------------------------------------------------------- 1 | import { Comment as AntdComment, Avatar, Tooltip, PageHeader, BackTop as Back } from 'antd'; 2 | import { DislikeFilled, DislikeOutlined, LikeFilled, LikeOutlined } from '@ant-design/icons'; 3 | import React, { createElement, useState } from 'react'; 4 | const App = () => { 5 | const [likes, setLikes] = useState(0); 6 | const [dislikes, setDislikes] = useState(0); 7 | const [action, setAction] = useState(null); 8 | const like = () => { 9 | setLikes(1); 10 | setDislikes(0); 11 | setAction('liked'); 12 | }; 13 | const dislike = () => { 14 | setLikes(0); 15 | setDislikes(1); 16 | setAction('disliked'); 17 | }; 18 | const actions = [ 19 | 20 | 21 | {createElement(action === 'liked' ? LikeFilled : LikeOutlined)} 22 | {likes} 23 | 24 | , 25 | 26 | 27 | {React.createElement(action === 'disliked' ? DislikeFilled : DislikeOutlined)} 28 | {dislikes} 29 | 30 | , 31 | Reply to, 32 | ]; 33 | return ( 34 | <> 35 | Han Solo} 38 | avatar={} 39 | content={ 40 |

41 | We supply a series of design principles, practical patterns and high quality design 42 | resources (Sketch and Axure), to help people create their product prototypes beautifully 43 | and efficiently. 44 |

45 | } 46 | datetime={ 47 | 48 | 8 hours ago 49 | 50 | } 51 | /> 52 | null} 55 | title="Title" 56 | subTitle="This is a subtitle" 57 | /> 58 | 59 | 60 | ); 61 | }; 62 | 63 | export default App; 64 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-removed-component-migration/alias-import.output.js: -------------------------------------------------------------------------------- 1 | import { Comment as AntdComment } from '@ant-design/compatible'; 2 | import { PageHeader } from '@ant-design/pro-layout'; 3 | import { Avatar, FloatButton, Tooltip } from 'antd'; 4 | import { DislikeFilled, DislikeOutlined, LikeFilled, LikeOutlined } from '@ant-design/icons'; 5 | import React, { createElement, useState } from 'react'; 6 | const App = () => { 7 | const [likes, setLikes] = useState(0); 8 | const [dislikes, setDislikes] = useState(0); 9 | const [action, setAction] = useState(null); 10 | const like = () => { 11 | setLikes(1); 12 | setDislikes(0); 13 | setAction('liked'); 14 | }; 15 | const dislike = () => { 16 | setLikes(0); 17 | setDislikes(1); 18 | setAction('disliked'); 19 | }; 20 | const actions = [ 21 | 22 | 23 | {createElement(action === 'liked' ? LikeFilled : LikeOutlined)} 24 | {likes} 25 | 26 | , 27 | 28 | 29 | {React.createElement(action === 'disliked' ? DislikeFilled : DislikeOutlined)} 30 | {dislikes} 31 | 32 | , 33 | Reply to, 34 | ]; 35 | return (<> 36 | Han Solo} 39 | avatar={} 40 | content={ 41 |

42 | We supply a series of design principles, practical patterns and high quality design 43 | resources (Sketch and Axure), to help people create their product prototypes beautifully 44 | and efficiently. 45 |

46 | } 47 | datetime={ 48 | 49 | 8 hours ago 50 | 51 | } 52 | /> 53 | null} 56 | title="Title" 57 | subTitle="This is a subtitle" 58 | /> 59 | 60 | ); 61 | }; 62 | 63 | export default App; 64 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-removed-component-migration/basic.input.js: -------------------------------------------------------------------------------- 1 | import { Comment, PageHeader, BackTop } from 'antd'; 2 | import { DislikeFilled, DislikeOutlined, LikeFilled, LikeOutlined } from '@ant-design/icons'; 3 | import React, { createElement, useState } from 'react'; 4 | const App = () => { 5 | const [likes, setLikes] = useState(0); 6 | const [dislikes, setDislikes] = useState(0); 7 | const [action, setAction] = useState(null); 8 | const like = () => { 9 | setLikes(1); 10 | setDislikes(0); 11 | setAction('liked'); 12 | }; 13 | const dislike = () => { 14 | setLikes(0); 15 | setDislikes(1); 16 | setAction('disliked'); 17 | }; 18 | const actions = [ 19 | 20 | 21 | {createElement(action === 'liked' ? LikeFilled : LikeOutlined)} 22 | {likes} 23 | 24 | , 25 | 26 | 27 | {React.createElement(action === 'disliked' ? DislikeFilled : DislikeOutlined)} 28 | {dislikes} 29 | 30 | , 31 | Reply to, 32 | ]; 33 | return ( 34 | <> 35 | Han Solo} 38 | avatar={} 39 | content={ 40 |

41 | We supply a series of design principles, practical patterns and high quality design 42 | resources (Sketch and Axure), to help people create their product prototypes beautifully 43 | and efficiently. 44 |

45 | } 46 | datetime={ 47 | 48 | 8 hours ago 49 | 50 | } 51 | /> 52 | null} 55 | title="Title" 56 | subTitle="This is a subtitle" 57 | /> 58 | 59 | 60 | ); 61 | }; 62 | 63 | export default App; 64 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-removed-component-migration/basic.output.js: -------------------------------------------------------------------------------- 1 | import { Comment } from '@ant-design/compatible'; 2 | import { PageHeader } from '@ant-design/pro-layout'; 3 | import { FloatButton } from 'antd'; 4 | import { DislikeFilled, DislikeOutlined, LikeFilled, LikeOutlined } from '@ant-design/icons'; 5 | import React, { createElement, useState } from 'react'; 6 | const App = () => { 7 | const [likes, setLikes] = useState(0); 8 | const [dislikes, setDislikes] = useState(0); 9 | const [action, setAction] = useState(null); 10 | const like = () => { 11 | setLikes(1); 12 | setDislikes(0); 13 | setAction('liked'); 14 | }; 15 | const dislike = () => { 16 | setLikes(0); 17 | setDislikes(1); 18 | setAction('disliked'); 19 | }; 20 | const actions = [ 21 | 22 | 23 | {createElement(action === 'liked' ? LikeFilled : LikeOutlined)} 24 | {likes} 25 | 26 | , 27 | 28 | 29 | {React.createElement(action === 'disliked' ? DislikeFilled : DislikeOutlined)} 30 | {dislikes} 31 | 32 | , 33 | Reply to, 34 | ]; 35 | return (<> 36 | Han Solo} 39 | avatar={} 40 | content={ 41 |

42 | We supply a series of design principles, practical patterns and high quality design 43 | resources (Sketch and Axure), to help people create their product prototypes beautifully 44 | and efficiently. 45 |

46 | } 47 | datetime={ 48 | 49 | 8 hours ago 50 | 51 | } 52 | /> 53 | null} 56 | title="Title" 57 | subTitle="This is a subtitle" 58 | /> 59 | 60 | ); 61 | }; 62 | 63 | export default App; 64 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-removed-component-migration/empty.input.js: -------------------------------------------------------------------------------- 1 | import { Comment, PageHeader } from 'antd'; 2 | import { DislikeFilled, DislikeOutlined, LikeFilled, LikeOutlined } from '@ant-design/icons'; 3 | import React, { createElement, useState } from 'react'; 4 | const App = () => { 5 | const [likes, setLikes] = useState(0); 6 | const [dislikes, setDislikes] = useState(0); 7 | const [action, setAction] = useState(null); 8 | const like = () => { 9 | setLikes(1); 10 | setDislikes(0); 11 | setAction('liked'); 12 | }; 13 | const dislike = () => { 14 | setLikes(0); 15 | setDislikes(1); 16 | setAction('disliked'); 17 | }; 18 | const actions = [ 19 | 20 | 21 | {createElement(action === 'liked' ? LikeFilled : LikeOutlined)} 22 | {likes} 23 | 24 | , 25 | 26 | 27 | {React.createElement(action === 'disliked' ? DislikeFilled : DislikeOutlined)} 28 | {dislikes} 29 | 30 | , 31 | Reply to, 32 | ]; 33 | return ( 34 | <> 35 | Han Solo} 38 | avatar={} 39 | content={ 40 |

41 | We supply a series of design principles, practical patterns and high quality design 42 | resources (Sketch and Axure), to help people create their product prototypes beautifully 43 | and efficiently. 44 |

45 | } 46 | datetime={ 47 | 48 | 8 hours ago 49 | 50 | } 51 | /> 52 | null} 55 | title="Title" 56 | subTitle="This is a subtitle" 57 | /> 58 | 59 | 60 | ); 61 | }; 62 | 63 | export default App; 64 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-removed-component-migration/empty.output.js: -------------------------------------------------------------------------------- 1 | import { Comment } from '@ant-design/compatible'; 2 | import { PageHeader } from '@ant-design/pro-layout'; 3 | import { DislikeFilled, DislikeOutlined, LikeFilled, LikeOutlined } from '@ant-design/icons'; 4 | import React, { createElement, useState } from 'react'; 5 | const App = () => { 6 | const [likes, setLikes] = useState(0); 7 | const [dislikes, setDislikes] = useState(0); 8 | const [action, setAction] = useState(null); 9 | const like = () => { 10 | setLikes(1); 11 | setDislikes(0); 12 | setAction('liked'); 13 | }; 14 | const dislike = () => { 15 | setLikes(0); 16 | setDislikes(1); 17 | setAction('disliked'); 18 | }; 19 | const actions = [ 20 | 21 | 22 | {createElement(action === 'liked' ? LikeFilled : LikeOutlined)} 23 | {likes} 24 | 25 | , 26 | 27 | 28 | {React.createElement(action === 'disliked' ? DislikeFilled : DislikeOutlined)} 29 | {dislikes} 30 | 31 | , 32 | Reply to, 33 | ]; 34 | return ( 35 | <> 36 | Han Solo} 39 | avatar={} 40 | content={ 41 |

42 | We supply a series of design principles, practical patterns and high quality design 43 | resources (Sketch and Axure), to help people create their product prototypes beautifully 44 | and efficiently. 45 |

46 | } 47 | datetime={ 48 | 49 | 8 hours ago 50 | 51 | } 52 | /> 53 | null} 56 | title="Title" 57 | subTitle="This is a subtitle" 58 | /> 59 | 60 | 61 | ); 62 | }; 63 | 64 | export default App; 65 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-removed-component-migration/imported.input.js: -------------------------------------------------------------------------------- 1 | import { BackTop, FloatButton as FButton } from 'antd'; 2 | import React from 'react'; 3 | const App = () => { 4 | return ( 5 | 6 | ); 7 | }; 8 | 9 | export default App; 10 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-removed-component-migration/imported.output.js: -------------------------------------------------------------------------------- 1 | import { FloatButton as FButton } from 'antd'; 2 | import React from 'react'; 3 | const App = () => { 4 | return (); 5 | }; 6 | 7 | export default App; 8 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-removed-static-method-migration/message.input.js: -------------------------------------------------------------------------------- 1 | import { message } from 'antd'; 2 | 3 | const App = () => { 4 | const [messageApi, contextHolder] = message.useMessage(); 5 | const msg = message.useMessage(); 6 | const [msgApi] = msg; 7 | const onClick1 = () => { 8 | message.warn(); 9 | } 10 | const onClick2 = () => { 11 | messageApi.warn(); 12 | }; 13 | const onClick3 = () => { 14 | msg[0].warn(); 15 | }; 16 | const onClick4 = () => { 17 | msgApi.warn(); 18 | }; 19 | return <>{contextHolder}; 20 | }; 21 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-removed-static-method-migration/message.output.js: -------------------------------------------------------------------------------- 1 | import { message } from 'antd'; 2 | 3 | const App = () => { 4 | const [messageApi, contextHolder] = message.useMessage(); 5 | const msg = message.useMessage(); 6 | const [msgApi] = msg; 7 | const onClick1 = () => { 8 | message.warning(); 9 | } 10 | const onClick2 = () => { 11 | messageApi.warning(); 12 | }; 13 | const onClick3 = () => { 14 | msg[0].warning(); 15 | }; 16 | const onClick4 = () => { 17 | msgApi.warning(); 18 | }; 19 | return <>{contextHolder}; 20 | }; 21 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-removed-static-method-migration/notification.input.js: -------------------------------------------------------------------------------- 1 | import { notification } from 'antd'; 2 | 3 | const App = () => { 4 | const [notificationApi, contextHolder] = notification.useNotification(); 5 | const notice = notification.useNotification(); 6 | const [noticeApi] = notice; 7 | const onClick1 = () => { 8 | notification.close(); 9 | } 10 | const onClick2 = () => { 11 | notificationApi.close(); 12 | }; 13 | const onClick3 = () => { 14 | notice[0].close(); 15 | }; 16 | const onClick4 = () => { 17 | noticeApi.close(); 18 | }; 19 | return <>{contextHolder}; 20 | }; 21 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v5-removed-static-method-migration/notification.output.js: -------------------------------------------------------------------------------- 1 | import { notification } from 'antd'; 2 | 3 | const App = () => { 4 | const [notificationApi, contextHolder] = notification.useNotification(); 5 | const notice = notification.useNotification(); 6 | const [noticeApi] = notice; 7 | const onClick1 = () => { 8 | notification.destroy(); 9 | } 10 | const onClick2 = () => { 11 | notificationApi.destroy(); 12 | }; 13 | const onClick3 = () => { 14 | notice[0].destroy(); 15 | }; 16 | const onClick4 = () => { 17 | noticeApi.destroy(); 18 | }; 19 | return <>{contextHolder}; 20 | }; 21 | -------------------------------------------------------------------------------- /transforms/__tests__/v5-props-changed-migration.test.ts: -------------------------------------------------------------------------------- 1 | import { defineTest } from 'jscodeshift/src/testUtils'; 2 | 3 | const tests = [ 4 | 'basic', 5 | 'compound', 6 | ]; 7 | 8 | const testUnit = 'v5-props-changed-migration'; 9 | 10 | describe(testUnit, () => { 11 | tests.forEach(test => 12 | defineTest( 13 | __dirname, 14 | testUnit, 15 | { antdPkgNames: ['antd', '@forked/antd'].join(',') }, 16 | `${testUnit}/${test}`, 17 | { parser: 'babylon' }, 18 | ), 19 | ); 20 | }); 21 | -------------------------------------------------------------------------------- /transforms/__tests__/v5-remove-style-import.test.ts: -------------------------------------------------------------------------------- 1 | import { defineTest } from 'jscodeshift/src/testUtils'; 2 | 3 | const tests = ['basic']; 4 | 5 | const testUnit = 'v5-remove-style-import'; 6 | 7 | describe(testUnit, () => { 8 | tests.forEach(test => 9 | defineTest( 10 | __dirname, 11 | testUnit, 12 | { antdPkgNames: ['antd', '@forked/antd'].join(',') }, 13 | `${testUnit}/${test}`, 14 | { parser: 'babylon' }, 15 | ), 16 | ); 17 | }); 18 | -------------------------------------------------------------------------------- /transforms/__tests__/v5-removed-component-migration.test.ts: -------------------------------------------------------------------------------- 1 | import { defineTest } from 'jscodeshift/src/testUtils'; 2 | 3 | const testUnit = 'v5-removed-component-migration'; 4 | const tests = ['basic', 'alias-import', 'empty', 'imported']; 5 | 6 | describe(testUnit, () => { 7 | tests.forEach(test => 8 | defineTest( 9 | __dirname, 10 | testUnit, 11 | { antdPkgNames: ['antd', '@forked/antd'].join(',') }, 12 | `${testUnit}/${test}`, 13 | { parser: 'babylon' }, 14 | ), 15 | ); 16 | }); 17 | -------------------------------------------------------------------------------- /transforms/__tests__/v5-removed-static-method-migration.test.ts: -------------------------------------------------------------------------------- 1 | import { defineTest } from 'jscodeshift/src/testUtils'; 2 | 3 | const testUnit = 'v5-removed-static-method-migration'; 4 | const tests = ['message', 'notification']; 5 | 6 | describe(testUnit, () => { 7 | tests.forEach(test => 8 | defineTest( 9 | __dirname, 10 | testUnit, 11 | { antdPkgNames: ['antd', '@forked/antd'].join(',') }, 12 | `${testUnit}/${test}`, 13 | { parser: 'babylon' }, 14 | ), 15 | ); 16 | }); 17 | -------------------------------------------------------------------------------- /transforms/utils/ast.js: -------------------------------------------------------------------------------- 1 | exports.createObjectProperty = function(j, key, value) { 2 | return j.property('init', j.identifier(key), value); 3 | }; 4 | 5 | exports.createObjectExpression = function(j, obj) { 6 | return j.objectExpression( 7 | Object.entries(obj).map(([key, val]) => 8 | exports.createObjectProperty(j, key, val), 9 | ), 10 | ); 11 | }; 12 | 13 | exports.getJSXAttributeValue = function(j, nodePath) { 14 | // 15 | let value = nodePath.value.value; 16 | // 17 | // 取出来 JSXExpressionContainer 其中值部分 18 | if (nodePath.value?.value?.type === 'JSXExpressionContainer') { 19 | value = nodePath.value.value.expression; 20 | } 21 | return value; 22 | }; 23 | 24 | exports.findAllAssignedNames = function findAllAssignedNames(root, j, localAssignedName, localAssignedNames = []) { 25 | const collection = root.find(j.VariableDeclarator, { 26 | init: { 27 | type: 'Identifier', 28 | name: localAssignedName, 29 | }, 30 | }); 31 | 32 | if (collection.length > 0) { 33 | collection.forEach(nodePath => { 34 | localAssignedNames.push(nodePath.node.id.name); 35 | findAllAssignedNames(root, j, nodePath.node.id.name, localAssignedNames); 36 | }); 37 | } 38 | return localAssignedNames; 39 | } 40 | -------------------------------------------------------------------------------- /transforms/utils/compatible-icon-utils.js: -------------------------------------------------------------------------------- 1 | const { camelCase, upperFirst } = require('lodash'); 2 | 3 | // copied from https://github.com/ant-design/compatible/blob/master/src/icon/utils.ts 4 | // "translate" these code to commonjs to avoid `babel` and `regenerator-runtime` 5 | 6 | const themeMap = { 7 | filled: 'filled', 8 | outlined: 'outlined', // default theme 9 | twoTone: 'twoTone', 10 | }; 11 | 12 | // moved from https://github.com/ant-design/ant-design/blob/master/components/icon/utils.ts 13 | const fillTester = /-fill$/; 14 | const outlineTester = /-o$/; 15 | const twoToneTester = /-twotone$/; 16 | 17 | function withThemeSuffix(type, theme) { 18 | const result = upperFirst(camelCase(type)); 19 | const realTheme = upperFirst(themeMap[theme]); 20 | 21 | if (theme !== 'outlined' && !realTheme) { 22 | console.warn(`This icon '${type}' has unknown theme '${theme}'`); 23 | } 24 | 25 | return result + realTheme; 26 | } 27 | 28 | function removeTypeTheme(type) { 29 | return type 30 | .replace(fillTester, '') 31 | .replace(outlineTester, '') 32 | .replace(twoToneTester, ''); 33 | } 34 | 35 | // For alias or compatibility 36 | function alias(type) { 37 | let newType = type; 38 | switch (type) { 39 | case 'cross': 40 | newType = 'close'; 41 | break; 42 | // https://github.com/ant-design/ant-design/issues/13007 43 | case 'interation': 44 | newType = 'interaction'; 45 | break; 46 | // https://github.com/ant-design/ant-design/issues/16810 47 | case 'canlendar': 48 | newType = 'calendar'; 49 | break; 50 | // https://github.com/ant-design/ant-design/issues/17448 51 | case 'colum-height': 52 | newType = 'column-height'; 53 | break; 54 | default: 55 | } 56 | 57 | if (type !== newType) { 58 | console.warn( 59 | `Icon '${type}' was a typo and is now deprecated, please use '${newType}' instead.`, 60 | ); 61 | } 62 | return newType; 63 | } 64 | 65 | module.exports = { 66 | withThemeSuffix, 67 | removeTypeTheme, 68 | alias, 69 | }; 70 | -------------------------------------------------------------------------------- /transforms/utils/config.js: -------------------------------------------------------------------------------- 1 | // https://github.com/benjamn/recast/blob/master/lib/options.ts 2 | const printOptions = { 3 | quote: 'single', 4 | }; 5 | 6 | module.exports = { 7 | printOptions, 8 | }; 9 | -------------------------------------------------------------------------------- /transforms/utils/index.js: -------------------------------------------------------------------------------- 1 | // some utils extract from https://github.com/reactjs/react-codemod 2 | function insertImportAfter(j, root, { importStatement, afterModule }) { 3 | const firstAfterModuleImport = root 4 | .find(j.ImportDeclaration, { 5 | source: { value: afterModule }, 6 | }) 7 | .at(0); 8 | 9 | if (firstAfterModuleImport.paths()[0]) { 10 | firstAfterModuleImport.insertAfter(importStatement); 11 | } else { 12 | // 保留首行的注释 13 | // https://github.com/facebook/jscodeshift/blob/master/recipes/retain-first-comment.md 14 | const firstNode = getFirstNode(j); 15 | const { comments } = firstNode; 16 | if (comments) { 17 | delete firstNode.comments; 18 | importStatement.comments = comments; 19 | } 20 | 21 | // insert `import` at body(0) 22 | root.get().node.program.body.unshift(importStatement); 23 | } 24 | } 25 | 26 | function insertImportBefore(j, root, { importStatement, beforeModule }) { 27 | const firstBeforeModuleImport = root 28 | .find(j.ImportDeclaration, { 29 | source: { value: beforeModule }, 30 | }) 31 | .at(0); 32 | 33 | const firstNode = getFirstNode(j, root); 34 | if ( 35 | (firstBeforeModuleImport.paths()[0] && 36 | firstBeforeModuleImport.paths()[0].node === firstNode) || 37 | !firstBeforeModuleImport 38 | ) { 39 | // 保留首行的注释 40 | // https://github.com/facebook/jscodeshift/blob/master/recipes/retain-first-comment.md 41 | const { comments } = firstNode; 42 | if (comments) { 43 | delete firstNode.comments; 44 | importStatement.comments = comments; 45 | } 46 | } 47 | 48 | if (firstBeforeModuleImport) { 49 | firstBeforeModuleImport.insertBefore(importStatement); 50 | } else { 51 | // insert `import` at body(0) 52 | root.get().node.program.body.unshift(importStatement); 53 | } 54 | } 55 | 56 | function hasSubmoduleImport(j, root, moduleName, submoduleName) { 57 | const collections = root 58 | .find(j.ImportDeclaration, { 59 | source: { 60 | value: moduleName, 61 | }, 62 | }) 63 | .find(j.ImportSpecifier, { 64 | imported: { 65 | name: submoduleName, 66 | }, 67 | }); 68 | 69 | const targetImport = collections.paths(); 70 | 71 | // local 默认设置为 null 72 | return ( 73 | targetImport[0]?.value?.local?.name || targetImport[0]?.value?.imported.name 74 | ); 75 | } 76 | 77 | function hasModuleImport(j, root, moduleName) { 78 | return ( 79 | root.find(j.ImportDeclaration, { 80 | source: { value: moduleName }, 81 | }).length > 0 82 | ); 83 | } 84 | 85 | function hasModuleDefaultImport(j, root, pkgName, localModuleName) { 86 | let found = false; 87 | root 88 | .find(j.ImportDeclaration, { 89 | source: { value: pkgName }, 90 | }) 91 | .forEach(nodePath => { 92 | const defaultImport = nodePath.node.specifiers.filter( 93 | n => 94 | n.type === 'ImportDefaultSpecifier' && 95 | n.local.name === localModuleName, 96 | ); 97 | if (defaultImport.length) { 98 | found = true; 99 | } 100 | }); 101 | return found; 102 | } 103 | 104 | function removeEmptyModuleImport(j, root, moduleName) { 105 | root 106 | .find(j.ImportDeclaration) 107 | .filter( 108 | path => 109 | path.node.specifiers.length === 0 && 110 | path.node.source.value === moduleName, 111 | ) 112 | .replaceWith(); 113 | } 114 | 115 | // Program uses var keywords 116 | function useVar(j, root) { 117 | return root.find(j.VariableDeclaration, { kind: 'const' }).length === 0; 118 | } 119 | 120 | function getFirstNode(j, root) { 121 | return root.find(j.Program).get('body', 0).node; 122 | } 123 | 124 | function addModuleImport(j, root, { pkgName, importSpecifier, before }) { 125 | // if has module imported, just import new submodule from existed 126 | // else just create a new import 127 | if (hasModuleImport(j, root, pkgName)) { 128 | root 129 | .find(j.ImportDeclaration, { 130 | source: { value: pkgName }, 131 | }) 132 | .at(0) 133 | .replaceWith(({ node }) => { 134 | const mergedImportSpecifiers = node.specifiers 135 | .concat(importSpecifier) 136 | .sort((a, b) => { 137 | if (a.type === 'ImportDefaultSpecifier') { 138 | return -1; 139 | } 140 | 141 | if (b.type === 'ImportDefaultSpecifier') { 142 | return 1; 143 | } 144 | 145 | return a.imported.name.localeCompare(b.imported.name); 146 | }); 147 | return j.importDeclaration(mergedImportSpecifiers, j.literal(pkgName)); 148 | }); 149 | return true; 150 | } 151 | 152 | const importStatement = j.importDeclaration( 153 | [importSpecifier], 154 | j.literal(pkgName), 155 | ); 156 | 157 | insertImportBefore(j, root, { importStatement, beforeModule: before }); 158 | return true; 159 | } 160 | 161 | // add default import before one module 162 | function addModuleDefaultImport(j, root, { moduleName, localName, before }) { 163 | if (hasModuleDefaultImport(j, root, moduleName, localName)) { 164 | return; 165 | } 166 | 167 | // DefaultImportSpecifier 168 | const importSpecifier = j.importDefaultSpecifier(j.identifier(localName)); 169 | 170 | if ( 171 | addModuleImport(j, root, { pkgName: moduleName, importSpecifier, before }) 172 | ) { 173 | return; 174 | } 175 | 176 | throw new Error(`No ${moduleName} import found!`); 177 | } 178 | 179 | // add submodule import before one module 180 | function addSubmoduleImport( 181 | j, 182 | root, 183 | { moduleName, importedName, localName, before }, 184 | ) { 185 | const importedLocalName = hasSubmoduleImport( 186 | j, 187 | root, 188 | moduleName, 189 | importedName, 190 | ); 191 | if (importedLocalName) { 192 | return importedLocalName; 193 | } 194 | 195 | const importSpecifier = j.importSpecifier( 196 | j.identifier(importedName), 197 | localName ? j.identifier(localName) : null, 198 | ); 199 | 200 | if ( 201 | addModuleImport(j, root, { pkgName: moduleName, importSpecifier, before }) 202 | ) { 203 | return localName || importedName; 204 | } 205 | 206 | throw new Error(`No ${moduleName} import found!`); 207 | } 208 | 209 | // add style import after one module 210 | function addStyleModuleImport(j, root, { moduleName, after }) { 211 | if (hasModuleImport(j, root, moduleName)) { 212 | return; 213 | } 214 | 215 | const importStatement = j.importDeclaration([], j.literal(moduleName)); 216 | insertImportAfter(j, root, { importStatement, afterModule: after }); 217 | } 218 | 219 | function parseStrToArray(antdPkgNames) { 220 | return (antdPkgNames || '') 221 | .split(',') 222 | .filter(n => n) 223 | .map(n => n.trim()); 224 | } 225 | 226 | module.exports = { 227 | parseStrToArray, 228 | addModuleDefaultImport, 229 | addStyleModuleImport, 230 | addSubmoduleImport, 231 | removeEmptyModuleImport, 232 | useVar, 233 | }; 234 | -------------------------------------------------------------------------------- /transforms/utils/marker.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const _ = require('lodash'); 4 | 5 | const markerPath = path.join( 6 | process.env.NODE_ENV === 'local' ? process.cwd() : require('os').tmpdir(), 7 | './antd5-codemod-marker.log', 8 | ); 9 | 10 | const newline = '\n'; 11 | 12 | function ensureFile() { 13 | return fs.openSync(markerPath, 'w'); 14 | } 15 | 16 | async function cleanup() { 17 | return await fs.promises.unlink(markerPath); 18 | } 19 | 20 | function markDependency(depName) { 21 | ensureFile(); 22 | return fs.appendFileSync( 23 | markerPath, 24 | depName + newline, 25 | 'utf8' 26 | ); 27 | } 28 | 29 | async function getDependencies() { 30 | ensureFile(); 31 | const content = await fs.promises.readFile(markerPath, 'utf8'); 32 | await cleanup(); 33 | return _.uniq((content || '').split(newline)); 34 | } 35 | 36 | module.exports = { 37 | markDependency, 38 | getDependencies, 39 | }; 40 | -------------------------------------------------------------------------------- /transforms/v5-props-changed-migration.js: -------------------------------------------------------------------------------- 1 | const { printOptions } = require('./utils/config'); 2 | const { parseStrToArray } = require('./utils'); 3 | const { 4 | createObjectProperty, 5 | createObjectExpression, 6 | getJSXAttributeValue, 7 | findAllAssignedNames, 8 | } = require('./utils/ast'); 9 | 10 | const changedComponentPropsMap = { 11 | AutoComplete: { 12 | dropdownClassName: { 13 | action: 'rename', 14 | replacer: 'popupClassName', 15 | }, 16 | }, 17 | Cascader: { 18 | dropdownClassName: { 19 | action: 'rename', 20 | replacer: 'popupClassName', 21 | }, 22 | }, 23 | Select: { 24 | dropdownClassName: { 25 | action: 'rename', 26 | replacer: 'popupClassName', 27 | }, 28 | }, 29 | TreeSelect: { 30 | dropdownClassName: { 31 | action: 'rename', 32 | replacer: 'popupClassName', 33 | }, 34 | }, 35 | // 处理 compound components: TimePicker.RangePicker 36 | TimePicker: { 37 | dropdownClassName: { 38 | action: 'rename', 39 | replacer: 'popupClassName', 40 | }, 41 | }, 42 | 'TimePicker.RangePicker': { 43 | dropdownClassName: { 44 | action: 'rename', 45 | replacer: 'popupClassName', 46 | }, 47 | }, 48 | // 处理 compound components: DatePicker.RangePicker 49 | DatePicker: { 50 | dropdownClassName: { 51 | action: 'rename', 52 | replacer: 'popupClassName', 53 | }, 54 | }, 55 | 'DatePicker.RangePicker': { 56 | dropdownClassName: { 57 | action: 'rename', 58 | replacer: 'popupClassName', 59 | }, 60 | }, 61 | Mentions: { 62 | dropdownClassName: { 63 | action: 'rename', 64 | replacer: 'popupClassName', 65 | }, 66 | }, 67 | Drawer: { 68 | visible: { 69 | action: 'rename', 70 | replacer: 'open', 71 | }, 72 | className: { 73 | action: 'rename', 74 | replacer: 'rootClassName', 75 | }, 76 | style: { 77 | action: 'rename', 78 | replacer: 'rootStyle', 79 | }, 80 | }, 81 | Modal: { 82 | visible: { 83 | action: 'rename', 84 | replacer: 'open', 85 | }, 86 | }, 87 | Dropdown: { 88 | visible: { 89 | action: 'rename', 90 | replacer: 'open', 91 | }, 92 | }, 93 | Tooltip: { 94 | visible: { 95 | action: 'rename', 96 | replacer: 'open', 97 | }, 98 | }, 99 | Tag: { 100 | visible: { 101 | action: 'remove', 102 | }, 103 | }, 104 | Slider: { 105 | tipFormatter: { 106 | action: 'rename', 107 | replacer: 'tooltip.formatter', 108 | }, 109 | tooltipPlacement: { 110 | action: 'rename', 111 | replacer: 'tooltip.placement', 112 | }, 113 | tooltipVisible: { 114 | action: 'rename', 115 | replacer: 'tooltip.open', 116 | }, 117 | }, 118 | Table: { 119 | filterDropdownVisible: { 120 | action: 'rename', 121 | replacer: 'filterDropdownOpen', 122 | }, 123 | }, 124 | }; 125 | 126 | module.exports = (file, api, options) => { 127 | const j = api.jscodeshift; 128 | const root = j(file.source); 129 | const antdPkgNames = parseStrToArray(options.antdPkgNames || 'antd'); 130 | 131 | // [ [DatePicker], [DatePicker, RangePicker] ] 132 | const componentTuple = Object.keys(changedComponentPropsMap).map(component => 133 | component.split('.'), 134 | ); 135 | 136 | function handlePropsTransform(collection, componentConfig) { 137 | let hasChanged = false; 138 | Object.keys(componentConfig).forEach(propName => { 139 | collection 140 | .find(j.JSXAttribute, { 141 | name: { 142 | type: 'JSXIdentifier', 143 | name: propName, 144 | }, 145 | }) 146 | .filter(nodePath => { 147 | // 只找第一层的 JSXElement 的 Attributes 148 | return j.JSXOpeningElement.check(nodePath.parent.node) 149 | && collection.paths().includes(nodePath.parent.parent); 150 | }) 151 | .forEach(nodePath => { 152 | const { action, replacer } = componentConfig[propName]; 153 | if (action === 'rename' && replacer) { 154 | // -> 155 | if (replacer.includes('.')) { 156 | const value = getJSXAttributeValue(j, nodePath); 157 | 158 | // delete origin `props` 159 | nodePath.parent.node.attributes = nodePath.parent.node.attributes.filter( 160 | attr => attr.name.name !== propName, 161 | ); 162 | 163 | hasChanged = true; 164 | 165 | const [propKey, propSubKey] = replacer.split('.'); 166 | // 检测是否已存在对应的 property 没有则创建一个新的 167 | // 获取 `Tag` 的 props 168 | let existedPropKeyAttr = j(nodePath.parent.parent).find( 169 | j.JSXAttribute, 170 | { 171 | name: { 172 | type: 'JSXIdentifier', 173 | name: propKey, 174 | }, 175 | }, 176 | ); 177 | if (existedPropKeyAttr.length === 0) { 178 | const newPropKeyAttr = j.jsxAttribute( 179 | j.jsxIdentifier(propKey), 180 | j.jsxExpressionContainer( 181 | createObjectExpression(j, { 182 | [propSubKey]: value, 183 | }), 184 | ), 185 | ); 186 | 187 | // 给对应 property 新增值 188 | nodePath.parent.node.attributes.push(newPropKeyAttr); 189 | } else { 190 | existedPropKeyAttr 191 | .paths()[0] 192 | .value.value.expression.properties.push( 193 | createObjectProperty(j, propSubKey, value), 194 | ); 195 | } 196 | } else { 197 | // -> 198 | nodePath.node.name = replacer; 199 | hasChanged = true; 200 | } 201 | } 202 | 203 | if (action === 'remove') { 204 | // 205 | let value = nodePath.value.value; 206 | // 207 | // 取出来 JSXExpressionContainer 其中值部分 208 | if (nodePath.value?.value?.type === 'JSXExpressionContainer') { 209 | value = nodePath.value.value.expression; 210 | } 211 | 212 | // delete origin `props` 213 | nodePath.parent.node.attributes = nodePath.parent.node.attributes.filter( 214 | attr => attr.name.name !== propName, 215 | ); 216 | 217 | hasChanged = true; 218 | 219 | // 220 | // 这种情况直接去掉 visible 即可 221 | 222 | // 有 value 再创建条件语句,没有 value 则默认为 true 223 | // create a conditional expression 224 | const conditionalExpression = value 225 | ? j.conditionalExpression( 226 | value, 227 | // Component Usage JSXElement `` 228 | nodePath.parent.parent.node, 229 | j.nullLiteral(), 230 | ) 231 | : null; 232 | 233 | // <> 234 | //
235 | // return (); 236 | // <- transform to -> 237 | // <>{vi && } 238 | //
{vi && }
239 | // return (vi && ) 240 | 241 | if (conditionalExpression) { 242 | // vi(JSXIdentifier) -> `<`(JSXOpeningElement) -> (JSXElement) 243 | // -> `<>`(JSXFragment) | `
`(JSXElement) 244 | if ( 245 | ['JSXElement', 'JSXFragment'].includes( 246 | nodePath.parent.parent.parent.node.type, 247 | ) 248 | ) { 249 | const index = nodePath.parent.parent.parent.node.children.findIndex( 250 | n => n === nodePath.parent.parent.node, 251 | ); 252 | if (index > -1) { 253 | nodePath.parent.parent.parent.node.children.splice( 254 | index, 255 | 1, 256 | // add `{}` 257 | j.jsxExpressionContainer(conditionalExpression), 258 | ); 259 | } 260 | } else if ( 261 | ['ReturnStatement'].includes( 262 | nodePath.parent.parent.parent.node.type, 263 | ) 264 | ) { 265 | nodePath.parent.parent.parent.node.argument = conditionalExpression; 266 | } 267 | } 268 | 269 | // 将 jsx element 转为一个条件表达式,并使用小括号包住 270 | // 最后再检查是不是有一个大的 {} JSXExpressionContainer 包住 271 | } 272 | }); 273 | }); 274 | 275 | return hasChanged; 276 | } 277 | 278 | // import deprecated components from '@ant-design/compatible' 279 | function handlePropsChanged(j, root) { 280 | let hasChanged = false; 281 | 282 | // import { Tag, Mention } from 'antd'; 283 | // import { Form, Mention } from '@forked/antd'; 284 | root 285 | .find(j.Identifier) 286 | .filter( 287 | path => 288 | componentTuple.map(n => n[0]).includes(path.node.name) && 289 | path.parent.node.type === 'ImportSpecifier' && 290 | antdPkgNames.includes(path.parent.parent.node.source.value), 291 | ) 292 | .forEach(path => { 293 | // import { Tag } from 'antd' 294 | // import { Tag as Tag1 } from 'antd' 295 | const importedComponentName = path.parent.node.imported.name; 296 | const localComponentName = path.parent.node.local.name; 297 | 298 | const componentConfig = changedComponentPropsMap[importedComponentName]; 299 | 300 | const nonCompoundComponent = root.findJSXElements(localComponentName); 301 | // 处理非 compound component 部分 302 | if (handlePropsTransform(nonCompoundComponent, componentConfig)) { 303 | hasChanged = true; 304 | } 305 | 306 | // 处理 compound component 部分 307 | const compoundComponentTuple = componentTuple.find( 308 | n => n[0] === importedComponentName && n[1], 309 | ); 310 | const [, compoundComponentName] = compoundComponentTuple || []; 311 | if (compoundComponentName) { 312 | // 313 | // JSXMemberExpression 314 | const compoundComponent = root.find(j.JSXElement, { 315 | openingElement: { 316 | name: { 317 | type: 'JSXMemberExpression', 318 | object: { 319 | type: 'JSXIdentifier', 320 | name: localComponentName, 321 | }, 322 | property: { 323 | type: 'JSXIdentifier', 324 | name: compoundComponentName, 325 | }, 326 | }, 327 | }, 328 | }); 329 | if (handlePropsTransform(compoundComponent, componentConfig)) { 330 | hasChanged = true; 331 | } 332 | 333 | // const { RangePicker } = DatePicker; 334 | root 335 | .find(j.VariableDeclarator, { 336 | init: { 337 | type: 'Identifier', 338 | name: localComponentName, 339 | }, 340 | }) 341 | .forEach(path => { 342 | const localComponentNames = path.node.id.properties 343 | .filter(n => n.key.name === compoundComponentName) 344 | // 优先处理解构场景 345 | // key.name: `const { RangePicker } = DatePicker;` 346 | // value.name: `const { RangePicker: RP1 } = DatePicker;` 347 | .map(n => n.value?.name || n.key.name); 348 | localComponentNames.forEach(compoundComponentName => { 349 | // 处理反复 reassign 的场景 350 | const localAssignedNames = findAllAssignedNames( 351 | root, j, 352 | compoundComponentName, 353 | [compoundComponentName], 354 | ); 355 | 356 | localAssignedNames.forEach(componentName => { 357 | const compoundComponent = root.findJSXElements(componentName); 358 | if (handlePropsTransform(compoundComponent, componentConfig)) { 359 | hasChanged = true; 360 | } 361 | }); 362 | }); 363 | }); 364 | 365 | // const RangerPicker1 = DatePicker.RangePicker; 366 | // const RangerPicker2 = RangerPicker1; 367 | root 368 | .find(j.VariableDeclarator, { 369 | init: { 370 | type: 'MemberExpression', 371 | object: { 372 | type: 'Identifier', 373 | name: localComponentName, 374 | }, 375 | property: { 376 | type: 'Identifier', 377 | name: compoundComponentName, 378 | }, 379 | }, 380 | }) 381 | .forEach(path => { 382 | const localAssignedName = path.node.id.name; 383 | 384 | // 处理反复 reassign 的场景 385 | const localAssignedNames = findAllAssignedNames( 386 | root, j, 387 | localAssignedName, 388 | [localAssignedName], 389 | ); 390 | 391 | localAssignedNames.forEach(componentName => { 392 | const compoundComponent = root.findJSXElements(componentName); 393 | if (handlePropsTransform(compoundComponent, componentConfig)) { 394 | hasChanged = true; 395 | } 396 | }); 397 | }); 398 | } 399 | }); 400 | 401 | return hasChanged; 402 | } 403 | 404 | // step1. import deprecated components from '@ant-design/compatible' 405 | // step2. cleanup antd import if empty 406 | let hasChanged = false; 407 | hasChanged = handlePropsChanged(j, root) || hasChanged; 408 | 409 | return hasChanged 410 | ? root.toSource(options.printOptions || printOptions) 411 | : null; 412 | }; 413 | -------------------------------------------------------------------------------- /transforms/v5-remove-style-import.js: -------------------------------------------------------------------------------- 1 | const { printOptions } = require('./utils/config'); 2 | const { 3 | parseStrToArray, 4 | } = require('./utils'); 5 | 6 | // handle forked antd 7 | const commentOutStyleImport = [ 8 | // antd/es/auto-complete/style 9 | /(es|lib)\/.+\/style.*/, 10 | // antd/dist/antd.compact.min.css 11 | /dist\/.+\.(less|css)/, 12 | ]; 13 | 14 | module.exports = (file, api, options) => { 15 | const j = api.jscodeshift; 16 | const root = j(file.source); 17 | const antdPkgNames = parseStrToArray(options.antdPkgNames || 'antd'); 18 | 19 | // import 'antd/es/auto-complete/style'; 20 | // import 'antd/dist/antd.compact.min.css'; 21 | function removeStyleImport(j, root) { 22 | let hasChanged = false; 23 | 24 | const regexList = antdPkgNames.map(antdPkg => { 25 | return new RegExp( 26 | [antdPkg, `(${commentOutStyleImport.map(re => re.source).join('|')})`].join('/'), 27 | ); 28 | }).concat( 29 | // import '@ant-design/compatible/assets/index.css'; 30 | new RegExp('@ant-design/compatible/assets/index\\.css'), 31 | ); 32 | 33 | // import { Comment, PageHeader } from 'antd'; 34 | // import { Comment, PageHeader } from '@forked/antd'; 35 | root 36 | .find(j.ImportDeclaration) 37 | .filter( 38 | path => 39 | path.node.source.type === 'StringLiteral' && 40 | regexList.some(re => re.test(path.node.source.value)), 41 | ) 42 | .forEach(path => { 43 | hasChanged = true; 44 | j(path).replaceWith(path => { 45 | // 不加空行会导致无法执行 root.toSource() 46 | const empty = j.emptyStatement(); 47 | // add indent 48 | empty.comments = [j.commentLine(' ' + j(path.node).toSource())]; 49 | return empty; 50 | }); 51 | }); 52 | 53 | return hasChanged; 54 | } 55 | 56 | // step1. import deprecated components from '@ant-design/compatible' 57 | // step2. cleanup antd import if empty 58 | let hasChanged = false; 59 | hasChanged = removeStyleImport(j, root) || hasChanged; 60 | 61 | return hasChanged ? root.toSource(options.printOptions || printOptions) : null; 62 | }; 63 | -------------------------------------------------------------------------------- /transforms/v5-removed-component-migration.js: -------------------------------------------------------------------------------- 1 | const { 2 | addSubmoduleImport, 3 | removeEmptyModuleImport, 4 | parseStrToArray, 5 | } = require('./utils'); 6 | const { printOptions } = require('./utils/config'); 7 | const { markDependency } = require('./utils/marker'); 8 | 9 | const removedComponentConfig = { 10 | Comment: { 11 | importSource: '@ant-design/compatible', 12 | }, 13 | PageHeader: { 14 | importSource: '@ant-design/pro-layout', 15 | }, 16 | BackTop: { 17 | importSource: 'antd', 18 | rename: 'FloatButton.BackTop', 19 | }, 20 | }; 21 | 22 | module.exports = (file, api, options) => { 23 | const j = api.jscodeshift; 24 | const root = j(file.source); 25 | const antdPkgNames = parseStrToArray(options.antdPkgNames || 'antd'); 26 | 27 | // import { Comment } from '@ant-design/compatible' 28 | // import { PageHeader } from '@ant-design/pro-components' 29 | function importDeprecatedComponent(j, root) { 30 | let hasChanged = false; 31 | 32 | // import { Comment, PageHeader } from 'antd'; 33 | // import { Comment, PageHeader } from '@forked/antd'; 34 | const componentNameList = Object.keys(removedComponentConfig); 35 | 36 | root 37 | .find(j.Identifier) 38 | .filter( 39 | path => 40 | componentNameList.includes(path.node.name) && 41 | path.parent.node.type === 'ImportSpecifier' && 42 | antdPkgNames.includes(path.parent.parent.node.source.value), 43 | ) 44 | .forEach(path => { 45 | hasChanged = true; 46 | const importedComponentName = path.parent.node.imported.name; 47 | const antdPkgName = path.parent.parent.node.source.value; 48 | 49 | // remove old imports 50 | const importDeclaration = path.parent.parent.node; 51 | importDeclaration.specifiers = importDeclaration.specifiers.filter( 52 | specifier => 53 | !specifier.imported || 54 | specifier.imported.name !== importedComponentName, 55 | ); 56 | 57 | // add new import from '@ant-design/compatible' 58 | const localComponentName = path.parent.node.local.name; 59 | const importConfig = removedComponentConfig[importedComponentName]; 60 | if (importConfig.rename) { 61 | if (importConfig.rename.includes('.')) { 62 | // `FloatButton.BackTop` 63 | const [ 64 | newComponentName, 65 | compoundComponentName, 66 | ] = importConfig.rename.split('.'); 67 | // import part 68 | const importedLocalName = addSubmoduleImport(j, root, { 69 | moduleName: importConfig.importSource, 70 | importedName: newComponentName, 71 | before: antdPkgName, 72 | }); 73 | // rename part 74 | root 75 | .find(j.JSXElement, { 76 | openingElement: { 77 | name: { name: localComponentName }, 78 | }, 79 | }) 80 | .forEach(path => { 81 | path.node.openingElement.name = j.jsxMemberExpression( 82 | j.jsxIdentifier(importedLocalName), 83 | j.jsxIdentifier(compoundComponentName), 84 | ); 85 | }); 86 | } 87 | } else { 88 | addSubmoduleImport(j, root, { 89 | moduleName: importConfig.importSource, 90 | importedName: importedComponentName, 91 | localName: localComponentName, 92 | before: antdPkgName, 93 | }); 94 | markDependency(importConfig.importSource); 95 | } 96 | }); 97 | 98 | return hasChanged; 99 | } 100 | 101 | // step1. import deprecated components from '@ant-design/compatible' 102 | // step2. cleanup antd import if empty 103 | let hasChanged = false; 104 | hasChanged = importDeprecatedComponent(j, root) || hasChanged; 105 | 106 | if (hasChanged) { 107 | antdPkgNames.forEach(antdPkgName => { 108 | removeEmptyModuleImport(j, root, antdPkgName); 109 | }); 110 | } 111 | 112 | return hasChanged 113 | ? root.toSource(options.printOptions || printOptions) 114 | : null; 115 | }; 116 | -------------------------------------------------------------------------------- /transforms/v5-removed-static-method-migration.js: -------------------------------------------------------------------------------- 1 | const { parseStrToArray } = require('./utils'); 2 | const { printOptions } = require('./utils/config'); 3 | const { findAllAssignedNames } = require('./utils/ast'); 4 | 5 | const changedApiMap = { 6 | message: { 7 | hook: 'useMessage', 8 | replace: { 9 | 'warn': 'warning', 10 | }, 11 | }, 12 | notification: { 13 | hook: 'useNotification', 14 | replace: { 15 | 'close': 'destroy', 16 | }, 17 | }, 18 | }; 19 | 20 | module.exports = (file, api, options) => { 21 | const j = api.jscodeshift; 22 | const root = j(file.source); 23 | const antdPkgNames = parseStrToArray(options.antdPkgNames || 'antd'); 24 | 25 | function replaceDeconstructionAssignCall(msgApiName, importedName) { 26 | const changedApiConfig = changedApiMap[importedName]?.replace; 27 | if (changedApiConfig) { 28 | Object.entries(changedApiConfig).forEach(([oldName, newName]) => { 29 | // msgApi.warn => msgApi.warning 30 | root.find(j.CallExpression, { 31 | callee: { 32 | type: 'MemberExpression', 33 | object: { 34 | type: 'Identifier', 35 | name: msgApiName, 36 | }, 37 | property: { 38 | type: 'Identifier', 39 | name: oldName, 40 | }, 41 | }, 42 | }).forEach(path => { 43 | path.node.callee.property.name = newName; 44 | }); 45 | }); 46 | } 47 | } 48 | 49 | function replaceCallWithIndexZero(aliasedApiName, importedName) { 50 | const changedApiConfig = changedApiMap[importedName]?.replace; 51 | if (changedApiConfig) { 52 | Object.entries(changedApiConfig).forEach(([_, newName]) => { 53 | // msgNamespace[0].warn => msgNamespace[0].warning 54 | root.find(j.CallExpression, { 55 | callee: { 56 | type: 'MemberExpression', 57 | object: { 58 | type: 'MemberExpression', 59 | object: { 60 | type: 'Identifier', 61 | name: aliasedApiName, 62 | }, 63 | property: { 64 | type: 'NumericLiteral', 65 | value: 0, 66 | }, 67 | }, 68 | }, 69 | }).forEach(path => { 70 | path.node.callee.property.name = newName; 71 | }); 72 | }); 73 | } 74 | } 75 | 76 | function replaceMessageCall(path, importedName) { 77 | // const [messageApi, contextHolder] = messageAlias; 78 | if (path.node.id.type === 'ArrayPattern') { 79 | const msgApiName = path.node.id.elements[0].name; 80 | // 处理反复 reassign 的场景 81 | const localAssignedNames = findAllAssignedNames( 82 | root, j, 83 | msgApiName, 84 | [msgApiName], 85 | ); 86 | 87 | localAssignedNames.forEach(apiName => { 88 | replaceDeconstructionAssignCall(apiName, importedName); 89 | hasChanged = true; 90 | }); 91 | } else if (path.node.id.type === 'Identifier') { 92 | // const msg = msg; 93 | // 处理反复 reassign 的场景 94 | const msgName = path.node.id.name; 95 | const localAssignedNames = findAllAssignedNames( 96 | root, j, 97 | msgName, 98 | [msgName], 99 | ); 100 | 101 | localAssignedNames.forEach(apiName => { 102 | // const [api] = msg; 103 | root.find(j.VariableDeclarator, { 104 | init: { 105 | type: 'Identifier', 106 | name: apiName, 107 | }, 108 | }).forEach(path => { 109 | replaceMessageCall(path, importedName); 110 | hasChanged = true; 111 | }); 112 | 113 | replaceCallWithIndexZero(apiName, importedName); 114 | }); 115 | } 116 | } 117 | 118 | // rename old Message.warn() calls to Message.warning() 119 | function renameMessageWarnMethodCalls(j, root) { 120 | let hasChanged = false; 121 | root 122 | .find(j.Identifier) 123 | .filter( 124 | path => 125 | Object.keys(changedApiMap).includes(path.node.name) && 126 | path.parent.node.type === 'ImportSpecifier' && 127 | antdPkgNames.includes(path.parent.parent.node.source.value), 128 | ) 129 | .forEach(path => { 130 | const importedName = path.parent.node.imported.name; 131 | const localComponentName = path.parent.node.local.name; 132 | // message.warn -> message.warning 133 | replaceDeconstructionAssignCall(localComponentName, importedName); 134 | hasChanged = true; 135 | 136 | // useMessage called() 137 | // const [messageApi, contextHolder] = message.useMessage(); 138 | // const msg = message.useMessage(); 139 | const hook = changedApiMap[importedName]?.hook; 140 | if (hook) { 141 | root.find(j.VariableDeclarator, { 142 | init: { 143 | type: 'CallExpression', 144 | callee: { 145 | type: 'MemberExpression', 146 | property: { 147 | type: 'Identifier', 148 | name: hook, 149 | }, 150 | }, 151 | }, 152 | }).forEach(path => { 153 | replaceMessageCall(path, importedName); 154 | }); 155 | } 156 | }); 157 | 158 | return hasChanged; 159 | } 160 | 161 | // step1. // rename old Model.method() calls 162 | // step2. cleanup antd import if empty 163 | let hasChanged = false; 164 | hasChanged = renameMessageWarnMethodCalls(j, root) || hasChanged; 165 | 166 | return hasChanged 167 | ? root.toSource(options.printOptions || printOptions) 168 | : null; 169 | }; 170 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, 5 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | "allowJs": true /* Allow javascript files to be compiled. */, 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | "declaration": true /* Generates corresponding '.d.ts' file. */, 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "lib" /* Redirect output structure to the directory. */, 15 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": true /* Enable all strict type-checking options. */, 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 29 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 30 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 31 | 32 | /* Additional Checks */ 33 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 34 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 35 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 36 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 37 | 38 | /* Module Resolution Options */ 39 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 40 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 42 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 43 | // "typeRoots": [], /* List of folders to include type definitions from. */ 44 | // "types": [], /* Type declaration files to be included in compilation. */ 45 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, 46 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 48 | 49 | /* Source Map Options */ 50 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 51 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 52 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 53 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 54 | 55 | /* Experimental Options */ 56 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 57 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 58 | 59 | /* Advanced Options */ 60 | // "declarationDir": "lib" /* Output directory for generated declaration files. */ 61 | }, 62 | "include": ["./transforms", "less-transforms"], 63 | "exclude": ["less-transforms/__tests__", "__testfixtures__"] 64 | } 65 | --------------------------------------------------------------------------------