├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── lerna.json ├── package.json ├── packages ├── react-refactor-cli │ ├── .babelrc │ ├── bin │ │ └── react-refactor.js │ ├── package.json │ ├── src │ │ ├── argv.js │ │ └── command.js │ ├── test │ │ ├── __fixture__ │ │ │ └── Func.jsx │ │ └── command.spec.js │ └── yarn.lock ├── react-refactor-gui │ ├── .env │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── App.js │ │ ├── CodeEditor.jsx │ │ ├── Content.jsx │ │ ├── Footer.jsx │ │ ├── GithubRibbon.css │ │ ├── GithubRibbon.jsx │ │ ├── Header.jsx │ │ ├── Introduction.jsx │ │ ├── defaultSource.js │ │ ├── index.css │ │ └── index.js │ └── yarn.lock └── react-refactor │ ├── .babelrc │ ├── package.json │ ├── src │ ├── classToFunctional.js │ ├── errors.js │ ├── functionalToClass.js │ ├── index.js │ ├── parser.js │ ├── stringUtils.js │ └── treeUtils.js │ ├── test │ ├── __fixtures__ │ │ ├── ClassComp.jsx.txt │ │ ├── FuncComp.jsx.txt │ │ ├── example.jsx.txt │ │ └── refactoredExample.jsx.txt │ ├── __snapshots__ │ │ ├── classToFunctional.spec.js.snap │ │ ├── functionalToClass.spec.js.snap │ │ ├── index.spec.js.snap │ │ └── parser.spec.js.snap │ ├── classToFunctional.spec.js │ ├── functionalToClass.spec.js │ ├── index.spec.js │ ├── parser.spec.js │ ├── stringUtils.spec.js │ ├── testUtils.js │ └── treeUtils.spec.js │ └── yarn.lock └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | coverage 4 | *.log 5 | build 6 | *.lerna_backup 7 | packages/*/README.md 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | os: 4 | - linux 5 | 6 | node_js: 7 | - "6" 8 | - "8" 9 | - "lts/*" 10 | 11 | before_script: 12 | - "yarn run bootstrap" 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Christian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-refactor 2 | [![Build Status](https://travis-ci.org/chrvadala/react-refactor.svg?branch=master)](https://travis-ci.org/chrvadala/react-refactor) 3 | [![Beerpay](https://beerpay.io/chrvadala/react-refactor/badge.svg?style=beer)](https://beerpay.io/chrvadala/react-refactor) 4 | 5 | How many times have you converted a **React Class component** to a **React Functional component** and vice-versa? It’s a boring task, and we know... "*developers don’t like boring tasks*". 6 | Thanks to **React Refactor** you can convert any React component from and to Class component. 7 | 8 | It's made with Babel Babylon and thanks to string replacing it’s able to instantly convert your component to the opposite kind of component that you provided. 9 | 10 | **React Refactor** is available in three different packages: Library, CLI, Web Interface 11 | 12 | ## Library 13 | [![npm](https://img.shields.io/npm/v/react-refactor.svg?maxAge=2592000?style=plastic)](https://www.npmjs.com/package/react-refactor) 14 | [![Downloads](https://img.shields.io/npm/dm/react-refactor.svg)](https://www.npmjs.com/package/react-refactor) 15 | 16 | The package *react-refactor* offers methods to programmatically convert a component. You can use it to make new useful utilities that integrate this ability. 17 | ````js 18 | const {execRefactor} = require('react-refactor') 19 | let {output} = execRefactor(source) 20 | ```` 21 | 22 | 23 | ## CLI 24 | [![npm](https://img.shields.io/npm/v/react-refactor-cli.svg?maxAge=2592000?style=plastic)](https://www.npmjs.com/package/react-refactor-cli) 25 | [![Downloads](https://img.shields.io/npm/dm/react-refactor-cli.svg)](https://www.npmjs.com/package/react-refactor-cli) 26 | 27 | You can globally install the package **react-refactor-cli** and use it to convert your component on the fly. 28 | ```` 29 | $ yarn global add react-refactor-cli 30 | $ react-refactor [--output ] 31 | ```` 32 | 33 | ## Web interface 34 | You can avoid installing anything and convert your component through the web interface available at [https://chrvadala.github.io/react-refactor/](https://chrvadala.github.io/react-refactor/) 35 | 36 | ## Changelog 37 | - **v0.0** - Preview version 38 | - **v1.0** - First stable version 39 | 40 | ## Run tests 41 | ```` 42 | yarn install 43 | yarn run bootstrap 44 | yarn build 45 | yarn test 46 | yarn run clean 47 | ```` 48 | 49 | ## Contributors 50 | - [chrvadala](https://github.com/chrvadala) (author) 51 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.5.1", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "version": "1.0.0" 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chrvadala/react-refactor", 3 | "private": true, 4 | "devDependencies": { 5 | "cpy-cli": "^2.0.0", 6 | "gh-pages": "^1.2.0", 7 | "lerna": "^3.0.0-rc.0", 8 | "npm-run-all": "^4.1.3" 9 | }, 10 | "scripts": { 11 | "bootstrap": "lerna bootstrap", 12 | "publish": "lerna publish", 13 | "build": "lerna run build", 14 | "test": "lerna --ignore react-refactor-gui run test", 15 | "deploy": "lerna --scope react-refactor-gui run deploy", 16 | "clean": "lerna run clean && lerna clean --yes" 17 | }, 18 | "license": "MIT", 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/chrvadala/react-refactor.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/chrvadala/react-refactor/issues" 25 | }, 26 | "homepage": "https://github.com/chrvadala/react-refactor#readme", 27 | "jest": { 28 | "testEnvironment": "node" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/react-refactor-cli/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-object-rest-spread" 4 | ], 5 | "presets": [ 6 | [ 7 | "es2015" 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-refactor-cli/bin/react-refactor.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | //version check 4 | const updateNotifier = require('update-notifier'); 5 | const pkg = require('../package.json'); 6 | updateNotifier({pkg}).notify(); 7 | 8 | //command 9 | const argv = require('../build/argv').default 10 | const command = require('../build/command').default 11 | command(argv._[0], argv) 12 | -------------------------------------------------------------------------------- /packages/react-refactor-cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-refactor-cli", 3 | "description": "Convert your React Class Component to Functional Component and vice-versa", 4 | "bin": { 5 | "react-refactor": "./bin/react-refactor.js", 6 | "react-refactor-cli": "./bin/react-refactor.js" 7 | }, 8 | "devDependencies": { 9 | "babel-cli": "^6.26.0", 10 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 11 | "babel-preset-es2015": "^6.24.1", 12 | "del-cli": "^1.1.0", 13 | "jest": "^22.1.4" 14 | }, 15 | "scripts": { 16 | "test": "jest", 17 | "build": "babel src --out-dir build", 18 | "clean": "del build README.md", 19 | "cp-readme": "cpy ../../README.md .", 20 | "prepare": "npm-run-all build cp-readme" 21 | }, 22 | "engines": { 23 | "node": ">=6.0.0" 24 | }, 25 | "version": "1.0.0", 26 | "main": "index.js", 27 | "license": "MIT", 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/chrvadala/react-refactor.git" 31 | }, 32 | "bugs": { 33 | "url": "https://github.com/chrvadala/react-refactor/issues" 34 | }, 35 | "homepage": "https://chrvadala.github.io/react-refactor", 36 | "dependencies": { 37 | "react-refactor": "^1.0.0", 38 | "update-notifier": "^2.3.0", 39 | "yargs": "^11.0.0" 40 | }, 41 | "jest": { 42 | "testEnvironment": "node" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/react-refactor-cli/src/argv.js: -------------------------------------------------------------------------------- 1 | const yargs = require('yargs') 2 | .usage('Usage: $0 [args] ') 3 | .help('help') 4 | .alias('help', 'h') 5 | .version() 6 | 7 | //INTO ROADMAP 8 | // .option('clipboard', { 9 | // alias: 'c', 10 | // describe: 'Copy output on clipboard', 11 | // type: 'boolean' 12 | // }) 13 | 14 | .option('output', { 15 | alias: 'o', 16 | describe: 'Save output on file', 17 | type: 'string' 18 | }) 19 | 20 | const argv = yargs.argv 21 | 22 | if (!argv._[0]) { 23 | yargs.showHelp(); 24 | process.exit(-1) 25 | } 26 | 27 | export default argv 28 | -------------------------------------------------------------------------------- /packages/react-refactor-cli/src/command.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | const write = message => console.log(message) 3 | 4 | import {execRefactor} from 'react-refactor' 5 | 6 | export default createCommand(write, fs); 7 | 8 | export function createCommand(write, fs) { 9 | return function command(file, options) { 10 | const { 11 | output = null, 12 | } = options; 13 | 14 | let source = fs.readFileSync(file, 'utf8') 15 | let result = execRefactor(source) 16 | 17 | if (output) { 18 | fs.writeFileSync(output, result.output, 'utf8') 19 | write('Output saved on file') 20 | return 21 | } 22 | 23 | write(result.output) 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /packages/react-refactor-cli/test/__fixture__/Func.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | function Func(props) { 5 | return ( 6 |
7 | ciao 8 |
9 | ); 10 | } 11 | 12 | Func.propTypes = {}; 13 | Func.defaultProps = {}; 14 | 15 | 16 | export default Func; 17 | -------------------------------------------------------------------------------- /packages/react-refactor-cli/test/command.spec.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import fs from 'fs' 3 | import {createCommand} from '../src/command' 4 | 5 | const testfile = path.join(__dirname, './__fixture__/Func.jsx') 6 | 7 | describe('command', () => { 8 | 9 | it('should generate output on console', () => { 10 | const writeMock = jest.fn() 11 | const fsMock = { 12 | readFileSync: jest.fn(fs.readFileSync), 13 | writeFileSync: jest.fn(), 14 | } 15 | 16 | const command = createCommand(writeMock, fsMock) 17 | const options = {} 18 | 19 | command(testfile, options) 20 | 21 | expect(fsMock.readFileSync).toBeCalledWith(testfile, 'utf8'); 22 | expect(fsMock.writeFileSync).not.toBeCalled(); 23 | expect(writeMock).toBeCalled(); 24 | expect(writeMock.mock.calls[0][0]).toMatch(/class *Func/g) 25 | }) 26 | 27 | it('should generate output on file', () => { 28 | const writeMock = jest.fn() 29 | const fsMock = { 30 | readFileSync: jest.fn(fs.readFileSync), 31 | writeFileSync: jest.fn(), 32 | } 33 | 34 | const command = createCommand(writeMock, fsMock) 35 | const options = { 36 | output: path.join(__dirname, './__fixture__/Output.jsx') 37 | } 38 | 39 | command(testfile, options) 40 | 41 | expect(fsMock.readFileSync).toBeCalledWith(testfile, 'utf8'); 42 | expect(fsMock.writeFileSync).toBeCalled(); 43 | expect(fsMock.writeFileSync.mock.calls[0][1]).toMatch(/class *Func/g) 44 | expect(writeMock).toBeCalledWith('Output saved on file'); 45 | }) 46 | }) 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /packages/react-refactor-gui/.env: -------------------------------------------------------------------------------- 1 | PUBLIC_URL=./ 2 | -------------------------------------------------------------------------------- /packages/react-refactor-gui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-refactor-gui", 3 | "version": "1.0.0", 4 | "description": "Convert your React Class Component to Functional Component and vice-versa", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "react-scripts start", 8 | "build": "react-scripts build", 9 | "clean": "del build README.md", 10 | "cp-readme": "cpy ../../README.md .", 11 | "prepare": "npm-run-all build cp-readme", 12 | "deploy": "gh-pages -m 'Updates gh-pages' -d ./build" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/chrvadala/react-refactor.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/chrvadala/react-refactor/issues" 20 | }, 21 | "homepage": "https://chrvadala.github.io/react-refactor", 22 | "devDependencies": { 23 | "del-cli": "^1.1.0", 24 | "react": "^16.2.0", 25 | "react-ace": "^5.9.0", 26 | "react-dom": "^16.2.0", 27 | "react-refactor": "^1.0.0", 28 | "react-scripts": "^1.1.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/react-refactor-gui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrvadala/react-refactor/e5e57e633e585e3c7eed74963599a37047cc29c9/packages/react-refactor-gui/public/favicon.ico -------------------------------------------------------------------------------- /packages/react-refactor-gui/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 23 | React Refactor 24 | 25 | 26 | 27 | 34 | 35 | 36 | 39 |
40 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /packages/react-refactor-gui/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React Refactor", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /packages/react-refactor-gui/src/App.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import Content from "./Content"; 3 | import Header from "./Header"; 4 | import Introduction from "./Introduction"; 5 | import GithubRibbon from "./GithubRibbon"; 6 | import Footer from "./Footer"; 7 | 8 | const S_App = { 9 | height: '100%', 10 | display: 'flex', 11 | flexDirection: 'column', 12 | alignItems: 'stretch', 13 | alignContent: 'flex-end', 14 | } 15 | 16 | class App extends Component { 17 | render() { 18 | return [ 19 | , 20 |
21 |
22 | 23 | 24 |
25 |
26 | ] 27 | } 28 | } 29 | 30 | export default App; 31 | -------------------------------------------------------------------------------- /packages/react-refactor-gui/src/CodeEditor.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import AceEditor from 'react-ace'; 5 | 6 | import 'brace/mode/jsx'; 7 | import 'brace/theme/tomorrow'; 8 | 9 | class CodeEditor extends React.PureComponent { 10 | render() { 11 | return ( 12 | this.props.onChange(v)} 16 | value={this.props.value} 17 | editorProps={{$blockScrolling: true}} 18 | style={{width: '100%', height: '100%'}} 19 | readOnly={!this.props.onChange} 20 | /> 21 | ); 22 | } 23 | } 24 | 25 | CodeEditor.propTypes = { 26 | value: PropTypes.string.isRequired, 27 | onChange: PropTypes.func, 28 | }; 29 | CodeEditor.defaultProps = {}; 30 | 31 | 32 | export default CodeEditor; 33 | -------------------------------------------------------------------------------- /packages/react-refactor-gui/src/Content.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CodeEditor from "./CodeEditor"; 3 | import * as ReactRefactor from 'react-refactor' 4 | import defaultSource from "./defaultSource"; 5 | 6 | const S_CONTENT = { 7 | display: 'flex', 8 | alignContent: 'stretch', 9 | justifyContent: 'center', 10 | padding: "0 10px", 11 | alignItems: "stretch", 12 | height: "100%", 13 | } 14 | 15 | const S_COL = { 16 | width: '50%', 17 | border: "1px solid #2085c1", 18 | margin: 10, 19 | } 20 | 21 | class Content extends React.Component { 22 | constructor(props) { 23 | super(props) 24 | this.state = { 25 | code: defaultSource, 26 | refactoredCode: this.update(defaultSource, true) 27 | } 28 | this.update = this.update.bind(this) 29 | } 30 | 31 | update(source, skipStateUpdate = false) { 32 | let output; 33 | try { 34 | ({output} = ReactRefactor.execRefactor(source)) 35 | } catch (err) { 36 | this.setState({code: source, refactoredCode: '// Source file can\'t be refactored'}) 37 | console.warn(err) 38 | return; 39 | } 40 | if (!skipStateUpdate) this.setState({code: source, refactoredCode: output}) 41 | return output; 42 | } 43 | 44 | render() { 45 | return ( 46 |
47 |
48 | 49 |
50 |
51 | 52 |
53 |
54 | ); 55 | } 56 | } 57 | 58 | Content.propTypes = {}; 59 | Content.defaultProps = {}; 60 | 61 | 62 | export default Content; 63 | -------------------------------------------------------------------------------- /packages/react-refactor-gui/src/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const S_FOOTER = { 4 | display: 'flex', 5 | justifyContent: "space-around", 6 | 7 | background: '#efefef', 8 | borderTop: "1px solid #ddd", 9 | fontSize: '0.9em', 10 | padding: 2, 11 | color: "#555", 12 | height: 25, 13 | textAlign: 'center', 14 | } 15 | 16 | function Footer(props) { 17 | return ( 18 |
19 |
20 | Made with by chrvadala 22 |
23 | 24 |
Install CLI tool npm i -g react-refactor-cli
25 |
26 | ); 27 | } 28 | 29 | Footer.propTypes = {}; 30 | Footer.defaultProps = {}; 31 | 32 | 33 | export default Footer; 34 | -------------------------------------------------------------------------------- /packages/react-refactor-gui/src/GithubRibbon.css: -------------------------------------------------------------------------------- 1 | .github-corner:hover .octo-arm { 2 | animation: octocat-wave 560ms ease-in-out 3 | } 4 | 5 | @keyframes octocat-wave { 6 | 0%, 100% { 7 | transform: rotate(0) 8 | } 9 | 20%, 60% { 10 | transform: rotate(-25deg) 11 | } 12 | 40%, 80% { 13 | transform: rotate(10deg) 14 | } 15 | } 16 | 17 | @media (max-width: 500px) { 18 | .github-corner:hover .octo-arm { 19 | animation: none 20 | } 21 | 22 | .github-corner .octo-arm { 23 | animation: octocat-wave 560ms ease-in-out 24 | } 25 | } 26 | 27 | .github-corner svg { 28 | fill: #64CEAA; 29 | color: #fff; 30 | position: absolute; 31 | top: 0; 32 | border: 0; 33 | right: 0; 34 | } 35 | -------------------------------------------------------------------------------- /packages/react-refactor-gui/src/GithubRibbon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import "./GithubRibbon.css" 4 | 5 | function GithubRibbon({url}) { 6 | return ( 7 | 8 | 17 | 18 | 19 | ); 20 | } 21 | 22 | GithubRibbon.propTypes = { 23 | url: PropTypes.string.isRequired, 24 | }; 25 | GithubRibbon.defaultProps = {}; 26 | 27 | 28 | export default GithubRibbon; 29 | -------------------------------------------------------------------------------- /packages/react-refactor-gui/src/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const S_HEADER = { 4 | backgroundColor: '#2a2d33', 5 | } 6 | 7 | const S_TITLE = { 8 | padding: "10px 20px", 9 | color: '#fff', 10 | fontSize: '1.3em', 11 | margin: 0, 12 | fontWeight: "bold", 13 | } 14 | 15 | const S_SUBTITLE = { 16 | fontSize: "0.8em", 17 | fontWeight: 100, 18 | } 19 | 20 | function Header(props) { 21 | return ( 22 |
23 |

24 | React Refactor - Convert your React Class Component to Functional Component and vice-versa 25 |

26 |
27 | ); 28 | } 29 | 30 | export default Header; 31 | -------------------------------------------------------------------------------- /packages/react-refactor-gui/src/Introduction.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const S_INTRODUCTION = { 4 | margin: "10px 20px", 5 | } 6 | 7 | const S_DESC = { 8 | margin: 0, 9 | fontSize: "0.9em", 10 | fontWeight: 100, 11 | lineHeight: "1.5em", 12 | } 13 | 14 | function Introduction(props) { 15 | return ( 16 |
17 |

18 | How many times have you converted a React Class component to a React Functional component and vice-versa? It’s a boring task, and we know... "developers don’t like boring tasks".
19 | Thanks to React Refactor you can convert any React component from and to Class component.

20 | Paste your code into left panel 21 |

22 |
23 | ); 24 | } 25 | 26 | Introduction.propTypes = {}; 27 | Introduction.defaultProps = {}; 28 | 29 | 30 | export default Introduction; 31 | -------------------------------------------------------------------------------- /packages/react-refactor-gui/src/defaultSource.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | function Welcome(props) { 3 | return

Hello, {props.name}

; 4 | } 5 | 6 | class Welcome extends React.Component { 7 | render() { 8 | return

Hello, {this.props.name}

; 9 | } 10 | } 11 | 12 | `.trim() 13 | -------------------------------------------------------------------------------- /packages/react-refactor-gui/src/index.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 0; 3 | font-family: Roboto, cursive; 4 | background: #f3f5f7; 5 | } 6 | 7 | html, body, #root{ 8 | height: 100%; 9 | width: 100%; 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-refactor-gui/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import "./index.css" 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | -------------------------------------------------------------------------------- /packages/react-refactor/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-object-rest-spread" 4 | ], 5 | "presets": [ 6 | [ 7 | "es2015" 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-refactor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-refactor", 3 | "version": "1.0.0", 4 | "main": "build/index.js", 5 | "module": "build/index.js", 6 | "license": "MIT", 7 | "description": "Convert your React Class Component to Functional Component and vice-versa", 8 | "devDependencies": { 9 | "babel-cli": "^6.26.0", 10 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 11 | "babel-preset-es2015": "^6.24.1", 12 | "del-cli": "^1.1.0", 13 | "jest": "^22.1.4" 14 | }, 15 | "scripts": { 16 | "test": "jest", 17 | "build": "babel src --out-dir build", 18 | "clean": "del build README.md", 19 | "cp-readme": "cpy ../../README.md .", 20 | "prepare": "npm-run-all build cp-readme" 21 | }, 22 | "dependencies": { 23 | "babylon": "^6.18.0", 24 | "js-beautify": "^1.7.5", 25 | "traverse": "^0.6.6" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/chrvadala/react-refactor.git" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/chrvadala/react-refactor/issues" 33 | }, 34 | "homepage": "https://chrvadala.github.io/react-refactor", 35 | "jest": { 36 | "testEnvironment": "node" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/react-refactor/src/classToFunctional.js: -------------------------------------------------------------------------------- 1 | import {get, getOrThrow} from'./treeUtils' 2 | import traverse from 'traverse' 3 | import {NotAReactComponent} from "./errors" 4 | import {insert, remove} from "./stringUtils" 5 | 6 | const THIS_REPLACER = 'self' 7 | const SUPER_COMPONENTS = ['Component', 'PureComponent'] 8 | 9 | export function classToFunctional(source, classDeclaration) { 10 | 11 | //check if is a React component 12 | let isReactComponent; 13 | switch (get(classDeclaration, ['superClass', 'type'])) { 14 | case 'MemberExpression': 15 | let superObject = get(classDeclaration, ['superClass', 'object', 'name']) 16 | let superProperty = get(classDeclaration, ['superClass', 'property', 'name']) 17 | 18 | isReactComponent = superObject 19 | && superProperty 20 | && superObject === "React" 21 | && SUPER_COMPONENTS.includes(superProperty) 22 | break; 23 | 24 | case 'Identifier': 25 | let superIdentifier = get(classDeclaration, ['superClass', 'name']) 26 | isReactComponent = superIdentifier 27 | && SUPER_COMPONENTS.includes(superIdentifier) 28 | break 29 | 30 | default: 31 | isReactComponent = false 32 | } 33 | if (!isReactComponent) throw NotAReactComponent; 34 | 35 | 36 | //detect component info 37 | let className = getOrThrow(classDeclaration, ['id', 'name'], 'ReactComponent') 38 | let classStart = getOrThrow(classDeclaration, ['start']) 39 | let classEnd = getOrThrow(classDeclaration, ['end']) 40 | let classBody = getOrThrow(classDeclaration, ['body']) 41 | 42 | 43 | //detect render method 44 | let renderMethod; 45 | getOrThrow(classBody, ['body']).forEach(node => { 46 | if (get(node, ['type']) !== 'ClassMethod') return 47 | if (get(node, ['key', 'name']) !== 'render') return 48 | renderMethod = node 49 | }) 50 | if (!renderMethod) throw NotAReactComponent; 51 | let renderStart = getOrThrow(renderMethod, ['body', 'start']) 52 | let renderEnd = getOrThrow(renderMethod, ['body', 'end']) 53 | 54 | 55 | //generates patch to convert it from class to func 56 | let patch = [] 57 | patch.push(insert(classStart, `function ${className}(props)`)) 58 | patch.push(remove(classStart, renderStart)) 59 | patch.push(insert(renderStart + 1, `\n\tlet ${THIS_REPLACER} = {props};\n`)) 60 | traverse(classDeclaration).map(node => { 61 | if (get(node, ['type']) !== 'ThisExpression') return 62 | patch.push(insert(node.start, THIS_REPLACER)) 63 | patch.push(remove(node.start, node.end)) 64 | }) 65 | patch.push(remove(renderEnd, classEnd)) 66 | 67 | //patch 68 | return patch 69 | } 70 | -------------------------------------------------------------------------------- /packages/react-refactor/src/errors.js: -------------------------------------------------------------------------------- 1 | export const NotAReactComponent = new Error('This isn\'t a React component') 2 | export const NotAProgram = new Error('This isn\'t a Program script') 3 | -------------------------------------------------------------------------------- /packages/react-refactor/src/functionalToClass.js: -------------------------------------------------------------------------------- 1 | import traverse from "traverse" 2 | import {NotAReactComponent} from "./errors" 3 | import {getOrThrow} from './treeUtils' 4 | import {insert, remove} from "./stringUtils" 5 | 6 | export function functionalToClass(source, functionalDeclaration) { 7 | 8 | //check if is a React component (has at least 1 comp) 9 | let isReactComponent = false; 10 | traverse(functionalDeclaration) 11 | .forEach(function () { 12 | let {node, path} = this 13 | let tail = path[path.length - 1] 14 | if (tail !== 'type') return 15 | if (node !== 'JSXElement') return 16 | isReactComponent = true; 17 | }) 18 | if (!isReactComponent) throw NotAReactComponent 19 | 20 | //detect component info 21 | let functionalName = getOrThrow(functionalDeclaration, ['id', 'name'], 'ReactComponent') 22 | let functionalStart = getOrThrow(functionalDeclaration, ['start']) 23 | let functionalEnd = getOrThrow(functionalDeclaration, ['end']) 24 | 25 | let hasParams = getOrThrow(functionalDeclaration, ['params']).length > 0; 26 | let paramsCode; 27 | if(hasParams) { 28 | let paramsStart = getOrThrow(functionalDeclaration, ['params', 0, 'start']) 29 | let paramsEnd = getOrThrow(functionalDeclaration, ['params', 0, 'end']) 30 | paramsCode = source.substring(paramsStart, paramsEnd) 31 | } 32 | 33 | let renderStart = getOrThrow(functionalDeclaration, ['body', 'start']) 34 | let renderEnd = getOrThrow(functionalDeclaration, ['body', 'end']) 35 | 36 | let patch = [] 37 | patch.push(insert(functionalStart, `class ${functionalName} extends React.Component {`)) 38 | patch.push(insert(functionalStart, `render()`)) 39 | patch.push(remove(functionalStart, renderStart)) 40 | if(hasParams) patch.push(insert(renderStart + 1, `\nlet ${paramsCode} = this.props;`)) 41 | patch.push(insert(renderEnd, `}`)) 42 | 43 | return patch 44 | } 45 | 46 | -------------------------------------------------------------------------------- /packages/react-refactor/src/index.js: -------------------------------------------------------------------------------- 1 | import {patchString} from "./stringUtils" 2 | import {parse, beautify} from "./parser" 3 | import {classToFunctional} from "./classToFunctional" 4 | import {functionalToClass} from "./functionalToClass" 5 | import {getOrThrow, get} from "./treeUtils" 6 | import {NotAProgram} from './errors' 7 | 8 | export function execRefactor(source) { 9 | let programDeclaration = parse(source) 10 | let type = getOrThrow(programDeclaration, ['type']) 11 | let body = getOrThrow(programDeclaration, ['body']) 12 | 13 | if (type !== 'Program') throw NotAProgram 14 | 15 | let patch = [] 16 | let skipped = [] 17 | 18 | body.forEach(block => { 19 | let type = get(block, ['type']) 20 | if (!type) return 21 | 22 | switch (type) { 23 | case 'ExportDefaultDeclaration': 24 | case 'ExportNamedDeclaration': 25 | block = get(block, ['declaration'], block) 26 | type = get(block, ['type']) 27 | } 28 | 29 | let start = get(block, ['start']), end = get(block, ['end']); 30 | 31 | switch (type) { 32 | case 'FunctionDeclaration': 33 | try { 34 | let curPatch = functionalToClass(source, block) 35 | patch = patch.concat(curPatch) 36 | } catch (err) { 37 | skipped.push({type: 'FunctionDeclaration', start, end, message: err.message, stack: err}) 38 | } 39 | break; 40 | 41 | 42 | case 'ClassDeclaration': 43 | try { 44 | let curPatch = classToFunctional(source, block) 45 | patch = patch.concat(curPatch) 46 | } catch (err) { 47 | skipped.push({type: 'FunctionDeclaration', start, end, message: err.message, stack: err}) 48 | } 49 | break; 50 | } 51 | }); 52 | 53 | let output = patchString(source, patch) 54 | 55 | try { 56 | output = beautify(output) 57 | } catch (err) { 58 | skipped.push({type: 'Beautify'}) 59 | } 60 | 61 | return {patch, skipped, output} 62 | } 63 | -------------------------------------------------------------------------------- /packages/react-refactor/src/parser.js: -------------------------------------------------------------------------------- 1 | import {parse as astParser} from 'babylon' 2 | import {js_beautify as codeBeautify} from "js-beautify" 3 | 4 | export function parse(code) { 5 | const parserOpt = { 6 | sourceType: 'module', 7 | plugins: [ 8 | "jsx", 9 | "objectRestSpread", 10 | ] 11 | } 12 | return astParser(code, parserOpt).program 13 | } 14 | 15 | export function beautify(code) { 16 | const beautifyOpt = { 17 | indent_size: 2, e4x: true 18 | } 19 | return codeBeautify(code, beautifyOpt) 20 | } 21 | -------------------------------------------------------------------------------- /packages/react-refactor/src/stringUtils.js: -------------------------------------------------------------------------------- 1 | export const INSERT = 'insert'; 2 | export const REMOVE = 'remove'; 3 | 4 | export const insert = (start, payload) => ({operation: INSERT, start, payload}) 5 | export const remove = (start, end) => ({operation: REMOVE, start, end}) 6 | 7 | const StartGTEndError = new Error('Start offset should be greater than end offset') 8 | const StartGTLengthError = new Error('start offset should be lower than the size of the string') 9 | const PatchesUnsortedError = new Error('Patches should be provided sorted by start offset') 10 | const UnsupportedOperationError = new Error('Unsupported operation') 11 | 12 | export function patchString(string, patch) { 13 | let cur = 0; 14 | let out = ''; 15 | 16 | for (let i = 0; i < patch.length; i++) { 17 | let {operation, start, end, payload} = patch[i]; 18 | 19 | //check constraints 20 | if (end && (start > end)) throw new StartGTEndError() 21 | if (start > string.length) throw new StartGTLengthError() 22 | if (cur > start) throw new PatchesUnsortedError() 23 | 24 | //move on cursor 25 | out += string.substring(cur, start); 26 | cur = start; 27 | 28 | //perform operation 29 | switch (operation) { 30 | case INSERT: 31 | out += payload; 32 | break; 33 | 34 | case REMOVE: 35 | cur = end; 36 | break; 37 | 38 | default: 39 | throw new UnsupportedOperationError() 40 | } 41 | } 42 | 43 | //copy remained part 44 | out += string.substring(cur); 45 | 46 | return out 47 | } 48 | 49 | -------------------------------------------------------------------------------- /packages/react-refactor/src/treeUtils.js: -------------------------------------------------------------------------------- 1 | const traverse = require('traverse') 2 | 3 | export function getOrThrow(object, path) { 4 | let wrapped = traverse(object) 5 | if(wrapped.has(path)) return wrapped.get(path) 6 | throw new Error(`${path.join(',')} not found`) 7 | } 8 | 9 | export function get(object, path, defaultValue = undefined) { 10 | let wrapped = traverse(object) 11 | if(wrapped.has(path)) return wrapped.get(path) 12 | return defaultValue 13 | } 14 | -------------------------------------------------------------------------------- /packages/react-refactor/test/__fixtures__/ClassComp.jsx.txt: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class ClassComp extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | 9 | render() { 10 | return ( 11 |
12 | 13 |
14 | ); 15 | } 16 | } 17 | 18 | ClassComp.propTypes = {}; 19 | ClassComp.defaultProps = {}; 20 | 21 | 22 | export default ClassComp; 23 | -------------------------------------------------------------------------------- /packages/react-refactor/test/__fixtures__/FuncComp.jsx.txt: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | function FuncComp(props) { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | 12 | FuncComp.propTypes = {}; 13 | FuncComp.defaultProps = {}; 14 | 15 | 16 | export default FuncComp; 17 | -------------------------------------------------------------------------------- /packages/react-refactor/test/__fixtures__/example.jsx.txt: -------------------------------------------------------------------------------- 1 | function funcComp(props) { 2 | return
ciao
3 | } 4 | 5 | export default function funcCompExport(props) { 6 | return
ciao
7 | } 8 | 9 | class ClassComp extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | } 13 | 14 | doSomething() { 15 | //doSomething() 16 | } 17 | 18 | render() { 19 | let {def} = this.props 20 | let {props: {ghi}} = this 21 | return ( 22 |
{this.props.abc}
23 | ); 24 | } 25 | } 26 | 27 | export class ClassCompExport extends React.Component { 28 | constructor(props) { 29 | super(props); 30 | } 31 | 32 | doSomething() { 33 | //doSomething() 34 | } 35 | 36 | render() { 37 | let {def} = this.props 38 | let {props: {ghi}} = this 39 | return ( 40 |
{this.props.abc}
41 | ); 42 | } 43 | } 44 | 45 | function simpleFunc(){ 46 | return 'ciao' 47 | } 48 | 49 | class simpleClass{ 50 | do(){ 51 | return 'ciao' 52 | } 53 | } 54 | 55 | export {funcComp} 56 | export {ClassComp} 57 | -------------------------------------------------------------------------------- /packages/react-refactor/test/__fixtures__/refactoredExample.jsx.txt: -------------------------------------------------------------------------------- 1 | class funcComp extends React.Component { 2 | render() { 3 | let props = this.props; 4 | return
ciao
5 | } 6 | } 7 | 8 | export default class funcCompExport extends React.Component { 9 | render() { 10 | let props = this.props; 11 | return
ciao
12 | } 13 | } 14 | 15 | function ClassComp(props) { 16 | let self = {props}; 17 | 18 | let {def} = self.props 19 | let {props: {ghi}} = self 20 | return ( 21 |
{self.props.abc}
22 | ); 23 | } 24 | 25 | export function ClassCompExport(props) { 26 | let self = {props}; 27 | 28 | let {def} = self.props 29 | let {props: {ghi}} = self 30 | return ( 31 |
{self.props.abc}
32 | ); 33 | } 34 | 35 | function simpleFunc(){ 36 | return 'ciao' 37 | } 38 | 39 | class simpleClass{ 40 | do(){ 41 | return 'ciao' 42 | } 43 | } 44 | 45 | export {funcComp} 46 | export {ClassComp} 47 | -------------------------------------------------------------------------------- /packages/react-refactor/test/__snapshots__/classToFunctional.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`classToFunctional should convert class to functional comp 1`] = ` 4 | Array [ 5 | Object { 6 | "operation": "insert", 7 | "payload": "function ClassComp(props)", 8 | "start": 0, 9 | }, 10 | Object { 11 | "end": 139, 12 | "operation": "remove", 13 | "start": 0, 14 | }, 15 | Object { 16 | "operation": "insert", 17 | "payload": " 18 | let self = {props}; 19 | ", 20 | "start": 140, 21 | }, 22 | Object { 23 | "operation": "insert", 24 | "payload": "self", 25 | "start": 157, 26 | }, 27 | Object { 28 | "end": 161, 29 | "operation": "remove", 30 | "start": 157, 31 | }, 32 | Object { 33 | "operation": "insert", 34 | "payload": "self", 35 | "start": 193, 36 | }, 37 | Object { 38 | "end": 197, 39 | "operation": "remove", 40 | "start": 193, 41 | }, 42 | Object { 43 | "operation": "insert", 44 | "payload": "self", 45 | "start": 223, 46 | }, 47 | Object { 48 | "end": 227, 49 | "operation": "remove", 50 | "start": 223, 51 | }, 52 | Object { 53 | "end": 257, 54 | "operation": "remove", 55 | "start": 255, 56 | }, 57 | ] 58 | `; 59 | 60 | exports[`classToFunctional should supports different superclass should support Component 1`] = ` 61 | Array [ 62 | Object { 63 | "operation": "insert", 64 | "payload": "function ClassComp(props)", 65 | "start": 0, 66 | }, 67 | Object { 68 | "end": 133, 69 | "operation": "remove", 70 | "start": 0, 71 | }, 72 | Object { 73 | "operation": "insert", 74 | "payload": " 75 | let self = {props}; 76 | ", 77 | "start": 134, 78 | }, 79 | Object { 80 | "operation": "insert", 81 | "payload": "self", 82 | "start": 151, 83 | }, 84 | Object { 85 | "end": 155, 86 | "operation": "remove", 87 | "start": 151, 88 | }, 89 | Object { 90 | "operation": "insert", 91 | "payload": "self", 92 | "start": 187, 93 | }, 94 | Object { 95 | "end": 191, 96 | "operation": "remove", 97 | "start": 187, 98 | }, 99 | Object { 100 | "operation": "insert", 101 | "payload": "self", 102 | "start": 217, 103 | }, 104 | Object { 105 | "end": 221, 106 | "operation": "remove", 107 | "start": 217, 108 | }, 109 | Object { 110 | "end": 251, 111 | "operation": "remove", 112 | "start": 249, 113 | }, 114 | ] 115 | `; 116 | 117 | exports[`classToFunctional should supports different superclass should support PureComponent 1`] = ` 118 | Array [ 119 | Object { 120 | "operation": "insert", 121 | "payload": "function ClassComp(props)", 122 | "start": 0, 123 | }, 124 | Object { 125 | "end": 137, 126 | "operation": "remove", 127 | "start": 0, 128 | }, 129 | Object { 130 | "operation": "insert", 131 | "payload": " 132 | let self = {props}; 133 | ", 134 | "start": 138, 135 | }, 136 | Object { 137 | "operation": "insert", 138 | "payload": "self", 139 | "start": 155, 140 | }, 141 | Object { 142 | "end": 159, 143 | "operation": "remove", 144 | "start": 155, 145 | }, 146 | Object { 147 | "operation": "insert", 148 | "payload": "self", 149 | "start": 191, 150 | }, 151 | Object { 152 | "end": 195, 153 | "operation": "remove", 154 | "start": 191, 155 | }, 156 | Object { 157 | "operation": "insert", 158 | "payload": "self", 159 | "start": 221, 160 | }, 161 | Object { 162 | "end": 225, 163 | "operation": "remove", 164 | "start": 221, 165 | }, 166 | Object { 167 | "end": 255, 168 | "operation": "remove", 169 | "start": 253, 170 | }, 171 | ] 172 | `; 173 | 174 | exports[`classToFunctional should supports different superclass should support React.PureComponent 1`] = ` 175 | Array [ 176 | Object { 177 | "operation": "insert", 178 | "payload": "function ClassComp(props)", 179 | "start": 0, 180 | }, 181 | Object { 182 | "end": 143, 183 | "operation": "remove", 184 | "start": 0, 185 | }, 186 | Object { 187 | "operation": "insert", 188 | "payload": " 189 | let self = {props}; 190 | ", 191 | "start": 144, 192 | }, 193 | Object { 194 | "operation": "insert", 195 | "payload": "self", 196 | "start": 161, 197 | }, 198 | Object { 199 | "end": 165, 200 | "operation": "remove", 201 | "start": 161, 202 | }, 203 | Object { 204 | "operation": "insert", 205 | "payload": "self", 206 | "start": 197, 207 | }, 208 | Object { 209 | "end": 201, 210 | "operation": "remove", 211 | "start": 197, 212 | }, 213 | Object { 214 | "operation": "insert", 215 | "payload": "self", 216 | "start": 227, 217 | }, 218 | Object { 219 | "end": 231, 220 | "operation": "remove", 221 | "start": 227, 222 | }, 223 | Object { 224 | "end": 261, 225 | "operation": "remove", 226 | "start": 259, 227 | }, 228 | ] 229 | `; 230 | -------------------------------------------------------------------------------- /packages/react-refactor/test/__snapshots__/functionalToClass.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`functionalToClass should convert functional to class comp 1`] = ` 4 | Array [ 5 | Object { 6 | "operation": "insert", 7 | "payload": "class FunctionalComp extends React.Component {", 8 | "start": 1, 9 | }, 10 | Object { 11 | "operation": "insert", 12 | "payload": "render()", 13 | "start": 1, 14 | }, 15 | Object { 16 | "end": 31, 17 | "operation": "remove", 18 | "start": 1, 19 | }, 20 | Object { 21 | "operation": "insert", 22 | "payload": " 23 | let props = this.props;", 24 | "start": 32, 25 | }, 26 | Object { 27 | "operation": "insert", 28 | "payload": "}", 29 | "start": 77, 30 | }, 31 | ] 32 | `; 33 | 34 | exports[`functionalToClass should convert functional to class comp with destructuring 1`] = ` 35 | Array [ 36 | Object { 37 | "operation": "insert", 38 | "payload": "class FunctionalComp extends React.Component {", 39 | "start": 1, 40 | }, 41 | Object { 42 | "operation": "insert", 43 | "payload": "render()", 44 | "start": 1, 45 | }, 46 | Object { 47 | "end": 36, 48 | "operation": "remove", 49 | "start": 1, 50 | }, 51 | Object { 52 | "operation": "insert", 53 | "payload": " 54 | let {abc, cde} = this.props;", 55 | "start": 37, 56 | }, 57 | Object { 58 | "operation": "insert", 59 | "payload": "}", 60 | "start": 76, 61 | }, 62 | ] 63 | `; 64 | 65 | exports[`functionalToClass should convert functional to class comp without props 1`] = ` 66 | Array [ 67 | Object { 68 | "operation": "insert", 69 | "payload": "class FunctionalComp extends React.Component {", 70 | "start": 1, 71 | }, 72 | Object { 73 | "operation": "insert", 74 | "payload": "render()", 75 | "start": 1, 76 | }, 77 | Object { 78 | "end": 26, 79 | "operation": "remove", 80 | "start": 1, 81 | }, 82 | Object { 83 | "operation": "insert", 84 | "payload": "}", 85 | "start": 70, 86 | }, 87 | ] 88 | `; 89 | -------------------------------------------------------------------------------- /packages/react-refactor/test/__snapshots__/index.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`refactor should convert 1`] = ` 4 | Array [ 5 | Object { 6 | "operation": "insert", 7 | "payload": "class funcComp extends React.Component {", 8 | "start": 0, 9 | }, 10 | Object { 11 | "operation": "insert", 12 | "payload": "render()", 13 | "start": 0, 14 | }, 15 | Object { 16 | "end": 25, 17 | "operation": "remove", 18 | "start": 0, 19 | }, 20 | Object { 21 | "operation": "insert", 22 | "payload": " 23 | let props = this.props;", 24 | "start": 26, 25 | }, 26 | Object { 27 | "operation": "insert", 28 | "payload": "}", 29 | "start": 53, 30 | }, 31 | Object { 32 | "operation": "insert", 33 | "payload": "class funcCompExport extends React.Component {", 34 | "start": 70, 35 | }, 36 | Object { 37 | "operation": "insert", 38 | "payload": "render()", 39 | "start": 70, 40 | }, 41 | Object { 42 | "end": 101, 43 | "operation": "remove", 44 | "start": 70, 45 | }, 46 | Object { 47 | "operation": "insert", 48 | "payload": " 49 | let props = this.props;", 50 | "start": 102, 51 | }, 52 | Object { 53 | "operation": "insert", 54 | "payload": "}", 55 | "start": 129, 56 | }, 57 | Object { 58 | "operation": "insert", 59 | "payload": "function ClassComp(props)", 60 | "start": 131, 61 | }, 62 | Object { 63 | "end": 273, 64 | "operation": "remove", 65 | "start": 131, 66 | }, 67 | Object { 68 | "operation": "insert", 69 | "payload": " 70 | let self = {props}; 71 | ", 72 | "start": 274, 73 | }, 74 | Object { 75 | "operation": "insert", 76 | "payload": "self", 77 | "start": 291, 78 | }, 79 | Object { 80 | "end": 295, 81 | "operation": "remove", 82 | "start": 291, 83 | }, 84 | Object { 85 | "operation": "insert", 86 | "payload": "self", 87 | "start": 327, 88 | }, 89 | Object { 90 | "end": 331, 91 | "operation": "remove", 92 | "start": 327, 93 | }, 94 | Object { 95 | "operation": "insert", 96 | "payload": "self", 97 | "start": 357, 98 | }, 99 | Object { 100 | "end": 361, 101 | "operation": "remove", 102 | "start": 357, 103 | }, 104 | Object { 105 | "end": 391, 106 | "operation": "remove", 107 | "start": 389, 108 | }, 109 | Object { 110 | "operation": "insert", 111 | "payload": "function ClassCompExport(props)", 112 | "start": 400, 113 | }, 114 | Object { 115 | "end": 548, 116 | "operation": "remove", 117 | "start": 400, 118 | }, 119 | Object { 120 | "operation": "insert", 121 | "payload": " 122 | let self = {props}; 123 | ", 124 | "start": 549, 125 | }, 126 | Object { 127 | "operation": "insert", 128 | "payload": "self", 129 | "start": 566, 130 | }, 131 | Object { 132 | "end": 570, 133 | "operation": "remove", 134 | "start": 566, 135 | }, 136 | Object { 137 | "operation": "insert", 138 | "payload": "self", 139 | "start": 602, 140 | }, 141 | Object { 142 | "end": 606, 143 | "operation": "remove", 144 | "start": 602, 145 | }, 146 | Object { 147 | "operation": "insert", 148 | "payload": "self", 149 | "start": 632, 150 | }, 151 | Object { 152 | "end": 636, 153 | "operation": "remove", 154 | "start": 632, 155 | }, 156 | Object { 157 | "end": 666, 158 | "operation": "remove", 159 | "start": 664, 160 | }, 161 | ] 162 | `; 163 | 164 | exports[`refactor should convert 2`] = ` 165 | Array [ 166 | Object { 167 | "end": 708, 168 | "message": "This isn't a React component", 169 | "stack": [Error: This isn't a React component], 170 | "start": 668, 171 | "type": "FunctionDeclaration", 172 | }, 173 | Object { 174 | "end": 760, 175 | "message": "This isn't a React component", 176 | "stack": [Error: This isn't a React component], 177 | "start": 710, 178 | "type": "FunctionDeclaration", 179 | }, 180 | ] 181 | `; 182 | -------------------------------------------------------------------------------- /packages/react-refactor/test/__snapshots__/parser.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`parser should parse Class file 1`] = ` 4 | Node { 5 | "body": Array [ 6 | Node { 7 | "end": 26, 8 | "loc": SourceLocation { 9 | "end": Position { 10 | "column": 26, 11 | "line": 1, 12 | }, 13 | "start": Position { 14 | "column": 0, 15 | "line": 1, 16 | }, 17 | }, 18 | "source": Node { 19 | "end": 25, 20 | "extra": Object { 21 | "raw": "'react'", 22 | "rawValue": "react", 23 | }, 24 | "loc": SourceLocation { 25 | "end": Position { 26 | "column": 25, 27 | "line": 1, 28 | }, 29 | "start": Position { 30 | "column": 18, 31 | "line": 1, 32 | }, 33 | }, 34 | "start": 18, 35 | "type": "StringLiteral", 36 | "value": "react", 37 | }, 38 | "specifiers": Array [ 39 | Node { 40 | "end": 12, 41 | "loc": SourceLocation { 42 | "end": Position { 43 | "column": 12, 44 | "line": 1, 45 | }, 46 | "start": Position { 47 | "column": 7, 48 | "line": 1, 49 | }, 50 | }, 51 | "local": Node { 52 | "end": 12, 53 | "loc": SourceLocation { 54 | "end": Position { 55 | "column": 12, 56 | "line": 1, 57 | }, 58 | "identifierName": "React", 59 | "start": Position { 60 | "column": 7, 61 | "line": 1, 62 | }, 63 | }, 64 | "name": "React", 65 | "start": 7, 66 | "type": "Identifier", 67 | }, 68 | "start": 7, 69 | "type": "ImportDefaultSpecifier", 70 | }, 71 | ], 72 | "start": 0, 73 | "type": "ImportDeclaration", 74 | }, 75 | Node { 76 | "end": 62, 77 | "loc": SourceLocation { 78 | "end": Position { 79 | "column": 35, 80 | "line": 2, 81 | }, 82 | "start": Position { 83 | "column": 0, 84 | "line": 2, 85 | }, 86 | }, 87 | "source": Node { 88 | "end": 61, 89 | "extra": Object { 90 | "raw": "'prop-types'", 91 | "rawValue": "prop-types", 92 | }, 93 | "loc": SourceLocation { 94 | "end": Position { 95 | "column": 34, 96 | "line": 2, 97 | }, 98 | "start": Position { 99 | "column": 22, 100 | "line": 2, 101 | }, 102 | }, 103 | "start": 49, 104 | "type": "StringLiteral", 105 | "value": "prop-types", 106 | }, 107 | "specifiers": Array [ 108 | Node { 109 | "end": 43, 110 | "loc": SourceLocation { 111 | "end": Position { 112 | "column": 16, 113 | "line": 2, 114 | }, 115 | "start": Position { 116 | "column": 7, 117 | "line": 2, 118 | }, 119 | }, 120 | "local": Node { 121 | "end": 43, 122 | "loc": SourceLocation { 123 | "end": Position { 124 | "column": 16, 125 | "line": 2, 126 | }, 127 | "identifierName": "PropTypes", 128 | "start": Position { 129 | "column": 7, 130 | "line": 2, 131 | }, 132 | }, 133 | "name": "PropTypes", 134 | "start": 34, 135 | "type": "Identifier", 136 | }, 137 | "start": 34, 138 | "type": "ImportDefaultSpecifier", 139 | }, 140 | ], 141 | "start": 27, 142 | "type": "ImportDeclaration", 143 | }, 144 | Node { 145 | "body": Node { 146 | "body": Array [ 147 | Node { 148 | "async": false, 149 | "body": Node { 150 | "body": Array [ 151 | Node { 152 | "end": 146, 153 | "expression": Node { 154 | "arguments": Array [ 155 | Node { 156 | "end": 144, 157 | "loc": SourceLocation { 158 | "end": Position { 159 | "column": 15, 160 | "line": 6, 161 | }, 162 | "identifierName": "props", 163 | "start": Position { 164 | "column": 10, 165 | "line": 6, 166 | }, 167 | }, 168 | "name": "props", 169 | "start": 139, 170 | "type": "Identifier", 171 | }, 172 | ], 173 | "callee": Node { 174 | "end": 138, 175 | "loc": SourceLocation { 176 | "end": Position { 177 | "column": 9, 178 | "line": 6, 179 | }, 180 | "start": Position { 181 | "column": 4, 182 | "line": 6, 183 | }, 184 | }, 185 | "start": 133, 186 | "type": "Super", 187 | }, 188 | "end": 145, 189 | "loc": SourceLocation { 190 | "end": Position { 191 | "column": 16, 192 | "line": 6, 193 | }, 194 | "start": Position { 195 | "column": 4, 196 | "line": 6, 197 | }, 198 | }, 199 | "start": 133, 200 | "type": "CallExpression", 201 | }, 202 | "loc": SourceLocation { 203 | "end": Position { 204 | "column": 17, 205 | "line": 6, 206 | }, 207 | "start": Position { 208 | "column": 4, 209 | "line": 6, 210 | }, 211 | }, 212 | "start": 133, 213 | "type": "ExpressionStatement", 214 | }, 215 | ], 216 | "directives": Array [], 217 | "end": 150, 218 | "loc": SourceLocation { 219 | "end": Position { 220 | "column": 3, 221 | "line": 7, 222 | }, 223 | "start": Position { 224 | "column": 21, 225 | "line": 5, 226 | }, 227 | }, 228 | "start": 127, 229 | "type": "BlockStatement", 230 | }, 231 | "computed": false, 232 | "end": 150, 233 | "expression": false, 234 | "generator": false, 235 | "id": null, 236 | "key": Node { 237 | "end": 119, 238 | "loc": SourceLocation { 239 | "end": Position { 240 | "column": 13, 241 | "line": 5, 242 | }, 243 | "identifierName": "constructor", 244 | "start": Position { 245 | "column": 2, 246 | "line": 5, 247 | }, 248 | }, 249 | "name": "constructor", 250 | "start": 108, 251 | "type": "Identifier", 252 | }, 253 | "kind": "constructor", 254 | "loc": SourceLocation { 255 | "end": Position { 256 | "column": 3, 257 | "line": 7, 258 | }, 259 | "start": Position { 260 | "column": 2, 261 | "line": 5, 262 | }, 263 | }, 264 | "params": Array [ 265 | Node { 266 | "end": 125, 267 | "loc": SourceLocation { 268 | "end": Position { 269 | "column": 19, 270 | "line": 5, 271 | }, 272 | "identifierName": "props", 273 | "start": Position { 274 | "column": 14, 275 | "line": 5, 276 | }, 277 | }, 278 | "name": "props", 279 | "start": 120, 280 | "type": "Identifier", 281 | }, 282 | ], 283 | "start": 108, 284 | "static": false, 285 | "type": "ClassMethod", 286 | }, 287 | Node { 288 | "async": false, 289 | "body": Node { 290 | "body": Array [ 291 | Node { 292 | "argument": Node { 293 | "children": Array [ 294 | Node { 295 | "end": 197, 296 | "extra": null, 297 | "loc": SourceLocation { 298 | "end": Position { 299 | "column": 6, 300 | "line": 13, 301 | }, 302 | "start": Position { 303 | "column": 11, 304 | "line": 11, 305 | }, 306 | }, 307 | "start": 189, 308 | "type": "JSXText", 309 | "value": " 310 | 311 | ", 312 | }, 313 | ], 314 | "closingElement": Node { 315 | "end": 203, 316 | "loc": SourceLocation { 317 | "end": Position { 318 | "column": 12, 319 | "line": 13, 320 | }, 321 | "start": Position { 322 | "column": 6, 323 | "line": 13, 324 | }, 325 | }, 326 | "name": Node { 327 | "end": 202, 328 | "loc": SourceLocation { 329 | "end": Position { 330 | "column": 11, 331 | "line": 13, 332 | }, 333 | "start": Position { 334 | "column": 8, 335 | "line": 13, 336 | }, 337 | }, 338 | "name": "div", 339 | "start": 199, 340 | "type": "JSXIdentifier", 341 | }, 342 | "start": 197, 343 | "type": "JSXClosingElement", 344 | }, 345 | "end": 203, 346 | "extra": Object { 347 | "parenStart": 176, 348 | "parenthesized": true, 349 | }, 350 | "loc": SourceLocation { 351 | "end": Position { 352 | "column": 12, 353 | "line": 13, 354 | }, 355 | "start": Position { 356 | "column": 6, 357 | "line": 11, 358 | }, 359 | }, 360 | "openingElement": Node { 361 | "attributes": Array [], 362 | "end": 189, 363 | "loc": SourceLocation { 364 | "end": Position { 365 | "column": 11, 366 | "line": 11, 367 | }, 368 | "start": Position { 369 | "column": 6, 370 | "line": 11, 371 | }, 372 | }, 373 | "name": Node { 374 | "end": 188, 375 | "loc": SourceLocation { 376 | "end": Position { 377 | "column": 10, 378 | "line": 11, 379 | }, 380 | "start": Position { 381 | "column": 7, 382 | "line": 11, 383 | }, 384 | }, 385 | "name": "div", 386 | "start": 185, 387 | "type": "JSXIdentifier", 388 | }, 389 | "selfClosing": false, 390 | "start": 184, 391 | "type": "JSXOpeningElement", 392 | }, 393 | "start": 184, 394 | "type": "JSXElement", 395 | }, 396 | "end": 210, 397 | "loc": SourceLocation { 398 | "end": Position { 399 | "column": 6, 400 | "line": 14, 401 | }, 402 | "start": Position { 403 | "column": 4, 404 | "line": 10, 405 | }, 406 | }, 407 | "start": 169, 408 | "type": "ReturnStatement", 409 | }, 410 | ], 411 | "directives": Array [], 412 | "end": 214, 413 | "loc": SourceLocation { 414 | "end": Position { 415 | "column": 3, 416 | "line": 15, 417 | }, 418 | "start": Position { 419 | "column": 11, 420 | "line": 9, 421 | }, 422 | }, 423 | "start": 163, 424 | "type": "BlockStatement", 425 | }, 426 | "computed": false, 427 | "end": 214, 428 | "expression": false, 429 | "generator": false, 430 | "id": null, 431 | "key": Node { 432 | "end": 160, 433 | "loc": SourceLocation { 434 | "end": Position { 435 | "column": 8, 436 | "line": 9, 437 | }, 438 | "identifierName": "render", 439 | "start": Position { 440 | "column": 2, 441 | "line": 9, 442 | }, 443 | }, 444 | "name": "render", 445 | "start": 154, 446 | "type": "Identifier", 447 | }, 448 | "kind": "method", 449 | "loc": SourceLocation { 450 | "end": Position { 451 | "column": 3, 452 | "line": 15, 453 | }, 454 | "start": Position { 455 | "column": 2, 456 | "line": 9, 457 | }, 458 | }, 459 | "params": Array [], 460 | "start": 154, 461 | "static": false, 462 | "type": "ClassMethod", 463 | }, 464 | ], 465 | "end": 216, 466 | "loc": SourceLocation { 467 | "end": Position { 468 | "column": 1, 469 | "line": 16, 470 | }, 471 | "start": Position { 472 | "column": 40, 473 | "line": 4, 474 | }, 475 | }, 476 | "start": 104, 477 | "type": "ClassBody", 478 | }, 479 | "end": 216, 480 | "id": Node { 481 | "end": 79, 482 | "loc": SourceLocation { 483 | "end": Position { 484 | "column": 15, 485 | "line": 4, 486 | }, 487 | "identifierName": "ClassComp", 488 | "start": Position { 489 | "column": 6, 490 | "line": 4, 491 | }, 492 | }, 493 | "name": "ClassComp", 494 | "start": 70, 495 | "type": "Identifier", 496 | }, 497 | "loc": SourceLocation { 498 | "end": Position { 499 | "column": 1, 500 | "line": 16, 501 | }, 502 | "start": Position { 503 | "column": 0, 504 | "line": 4, 505 | }, 506 | }, 507 | "start": 64, 508 | "superClass": Node { 509 | "computed": false, 510 | "end": 103, 511 | "loc": SourceLocation { 512 | "end": Position { 513 | "column": 39, 514 | "line": 4, 515 | }, 516 | "start": Position { 517 | "column": 24, 518 | "line": 4, 519 | }, 520 | }, 521 | "object": Node { 522 | "end": 93, 523 | "loc": SourceLocation { 524 | "end": Position { 525 | "column": 29, 526 | "line": 4, 527 | }, 528 | "identifierName": "React", 529 | "start": Position { 530 | "column": 24, 531 | "line": 4, 532 | }, 533 | }, 534 | "name": "React", 535 | "start": 88, 536 | "type": "Identifier", 537 | }, 538 | "property": Node { 539 | "end": 103, 540 | "loc": SourceLocation { 541 | "end": Position { 542 | "column": 39, 543 | "line": 4, 544 | }, 545 | "identifierName": "Component", 546 | "start": Position { 547 | "column": 30, 548 | "line": 4, 549 | }, 550 | }, 551 | "name": "Component", 552 | "start": 94, 553 | "type": "Identifier", 554 | }, 555 | "start": 88, 556 | "type": "MemberExpression", 557 | }, 558 | "type": "ClassDeclaration", 559 | }, 560 | Node { 561 | "end": 243, 562 | "expression": Node { 563 | "end": 242, 564 | "left": Node { 565 | "computed": false, 566 | "end": 237, 567 | "loc": SourceLocation { 568 | "end": Position { 569 | "column": 19, 570 | "line": 18, 571 | }, 572 | "start": Position { 573 | "column": 0, 574 | "line": 18, 575 | }, 576 | }, 577 | "object": Node { 578 | "end": 227, 579 | "loc": SourceLocation { 580 | "end": Position { 581 | "column": 9, 582 | "line": 18, 583 | }, 584 | "identifierName": "ClassComp", 585 | "start": Position { 586 | "column": 0, 587 | "line": 18, 588 | }, 589 | }, 590 | "name": "ClassComp", 591 | "start": 218, 592 | "type": "Identifier", 593 | }, 594 | "property": Node { 595 | "end": 237, 596 | "loc": SourceLocation { 597 | "end": Position { 598 | "column": 19, 599 | "line": 18, 600 | }, 601 | "identifierName": "propTypes", 602 | "start": Position { 603 | "column": 10, 604 | "line": 18, 605 | }, 606 | }, 607 | "name": "propTypes", 608 | "start": 228, 609 | "type": "Identifier", 610 | }, 611 | "start": 218, 612 | "type": "MemberExpression", 613 | }, 614 | "loc": SourceLocation { 615 | "end": Position { 616 | "column": 24, 617 | "line": 18, 618 | }, 619 | "start": Position { 620 | "column": 0, 621 | "line": 18, 622 | }, 623 | }, 624 | "operator": "=", 625 | "right": Node { 626 | "end": 242, 627 | "loc": SourceLocation { 628 | "end": Position { 629 | "column": 24, 630 | "line": 18, 631 | }, 632 | "start": Position { 633 | "column": 22, 634 | "line": 18, 635 | }, 636 | }, 637 | "properties": Array [], 638 | "start": 240, 639 | "type": "ObjectExpression", 640 | }, 641 | "start": 218, 642 | "type": "AssignmentExpression", 643 | }, 644 | "loc": SourceLocation { 645 | "end": Position { 646 | "column": 25, 647 | "line": 18, 648 | }, 649 | "start": Position { 650 | "column": 0, 651 | "line": 18, 652 | }, 653 | }, 654 | "start": 218, 655 | "type": "ExpressionStatement", 656 | }, 657 | Node { 658 | "end": 272, 659 | "expression": Node { 660 | "end": 271, 661 | "left": Node { 662 | "computed": false, 663 | "end": 266, 664 | "loc": SourceLocation { 665 | "end": Position { 666 | "column": 22, 667 | "line": 19, 668 | }, 669 | "start": Position { 670 | "column": 0, 671 | "line": 19, 672 | }, 673 | }, 674 | "object": Node { 675 | "end": 253, 676 | "loc": SourceLocation { 677 | "end": Position { 678 | "column": 9, 679 | "line": 19, 680 | }, 681 | "identifierName": "ClassComp", 682 | "start": Position { 683 | "column": 0, 684 | "line": 19, 685 | }, 686 | }, 687 | "name": "ClassComp", 688 | "start": 244, 689 | "type": "Identifier", 690 | }, 691 | "property": Node { 692 | "end": 266, 693 | "loc": SourceLocation { 694 | "end": Position { 695 | "column": 22, 696 | "line": 19, 697 | }, 698 | "identifierName": "defaultProps", 699 | "start": Position { 700 | "column": 10, 701 | "line": 19, 702 | }, 703 | }, 704 | "name": "defaultProps", 705 | "start": 254, 706 | "type": "Identifier", 707 | }, 708 | "start": 244, 709 | "type": "MemberExpression", 710 | }, 711 | "loc": SourceLocation { 712 | "end": Position { 713 | "column": 27, 714 | "line": 19, 715 | }, 716 | "start": Position { 717 | "column": 0, 718 | "line": 19, 719 | }, 720 | }, 721 | "operator": "=", 722 | "right": Node { 723 | "end": 271, 724 | "loc": SourceLocation { 725 | "end": Position { 726 | "column": 27, 727 | "line": 19, 728 | }, 729 | "start": Position { 730 | "column": 25, 731 | "line": 19, 732 | }, 733 | }, 734 | "properties": Array [], 735 | "start": 269, 736 | "type": "ObjectExpression", 737 | }, 738 | "start": 244, 739 | "type": "AssignmentExpression", 740 | }, 741 | "loc": SourceLocation { 742 | "end": Position { 743 | "column": 28, 744 | "line": 19, 745 | }, 746 | "start": Position { 747 | "column": 0, 748 | "line": 19, 749 | }, 750 | }, 751 | "start": 244, 752 | "type": "ExpressionStatement", 753 | }, 754 | Node { 755 | "declaration": Node { 756 | "end": 299, 757 | "loc": SourceLocation { 758 | "end": Position { 759 | "column": 24, 760 | "line": 22, 761 | }, 762 | "identifierName": "ClassComp", 763 | "start": Position { 764 | "column": 15, 765 | "line": 22, 766 | }, 767 | }, 768 | "name": "ClassComp", 769 | "start": 290, 770 | "type": "Identifier", 771 | }, 772 | "end": 300, 773 | "loc": SourceLocation { 774 | "end": Position { 775 | "column": 25, 776 | "line": 22, 777 | }, 778 | "start": Position { 779 | "column": 0, 780 | "line": 22, 781 | }, 782 | }, 783 | "start": 275, 784 | "type": "ExportDefaultDeclaration", 785 | }, 786 | ], 787 | "directives": Array [], 788 | "end": 301, 789 | "loc": SourceLocation { 790 | "end": Position { 791 | "column": 0, 792 | "line": 23, 793 | }, 794 | "start": Position { 795 | "column": 0, 796 | "line": 1, 797 | }, 798 | }, 799 | "sourceType": "module", 800 | "start": 0, 801 | "type": "Program", 802 | } 803 | `; 804 | 805 | exports[`parser should parse Func file 1`] = ` 806 | Node { 807 | "body": Array [ 808 | Node { 809 | "end": 26, 810 | "loc": SourceLocation { 811 | "end": Position { 812 | "column": 26, 813 | "line": 1, 814 | }, 815 | "start": Position { 816 | "column": 0, 817 | "line": 1, 818 | }, 819 | }, 820 | "source": Node { 821 | "end": 25, 822 | "extra": Object { 823 | "raw": "'react'", 824 | "rawValue": "react", 825 | }, 826 | "loc": SourceLocation { 827 | "end": Position { 828 | "column": 25, 829 | "line": 1, 830 | }, 831 | "start": Position { 832 | "column": 18, 833 | "line": 1, 834 | }, 835 | }, 836 | "start": 18, 837 | "type": "StringLiteral", 838 | "value": "react", 839 | }, 840 | "specifiers": Array [ 841 | Node { 842 | "end": 12, 843 | "loc": SourceLocation { 844 | "end": Position { 845 | "column": 12, 846 | "line": 1, 847 | }, 848 | "start": Position { 849 | "column": 7, 850 | "line": 1, 851 | }, 852 | }, 853 | "local": Node { 854 | "end": 12, 855 | "loc": SourceLocation { 856 | "end": Position { 857 | "column": 12, 858 | "line": 1, 859 | }, 860 | "identifierName": "React", 861 | "start": Position { 862 | "column": 7, 863 | "line": 1, 864 | }, 865 | }, 866 | "name": "React", 867 | "start": 7, 868 | "type": "Identifier", 869 | }, 870 | "start": 7, 871 | "type": "ImportDefaultSpecifier", 872 | }, 873 | ], 874 | "start": 0, 875 | "type": "ImportDeclaration", 876 | }, 877 | Node { 878 | "end": 62, 879 | "loc": SourceLocation { 880 | "end": Position { 881 | "column": 35, 882 | "line": 2, 883 | }, 884 | "start": Position { 885 | "column": 0, 886 | "line": 2, 887 | }, 888 | }, 889 | "source": Node { 890 | "end": 61, 891 | "extra": Object { 892 | "raw": "'prop-types'", 893 | "rawValue": "prop-types", 894 | }, 895 | "loc": SourceLocation { 896 | "end": Position { 897 | "column": 34, 898 | "line": 2, 899 | }, 900 | "start": Position { 901 | "column": 22, 902 | "line": 2, 903 | }, 904 | }, 905 | "start": 49, 906 | "type": "StringLiteral", 907 | "value": "prop-types", 908 | }, 909 | "specifiers": Array [ 910 | Node { 911 | "end": 43, 912 | "loc": SourceLocation { 913 | "end": Position { 914 | "column": 16, 915 | "line": 2, 916 | }, 917 | "start": Position { 918 | "column": 7, 919 | "line": 2, 920 | }, 921 | }, 922 | "local": Node { 923 | "end": 43, 924 | "loc": SourceLocation { 925 | "end": Position { 926 | "column": 16, 927 | "line": 2, 928 | }, 929 | "identifierName": "PropTypes", 930 | "start": Position { 931 | "column": 7, 932 | "line": 2, 933 | }, 934 | }, 935 | "name": "PropTypes", 936 | "start": 34, 937 | "type": "Identifier", 938 | }, 939 | "start": 34, 940 | "type": "ImportDefaultSpecifier", 941 | }, 942 | ], 943 | "start": 27, 944 | "type": "ImportDeclaration", 945 | }, 946 | Node { 947 | "async": false, 948 | "body": Node { 949 | "body": Array [ 950 | Node { 951 | "argument": Node { 952 | "children": Array [ 953 | Node { 954 | "end": 117, 955 | "extra": null, 956 | "loc": SourceLocation { 957 | "end": Position { 958 | "column": 4, 959 | "line": 8, 960 | }, 961 | "start": Position { 962 | "column": 9, 963 | "line": 6, 964 | }, 965 | }, 966 | "start": 111, 967 | "type": "JSXText", 968 | "value": " 969 | 970 | ", 971 | }, 972 | ], 973 | "closingElement": Node { 974 | "end": 123, 975 | "loc": SourceLocation { 976 | "end": Position { 977 | "column": 10, 978 | "line": 8, 979 | }, 980 | "start": Position { 981 | "column": 4, 982 | "line": 8, 983 | }, 984 | }, 985 | "name": Node { 986 | "end": 122, 987 | "loc": SourceLocation { 988 | "end": Position { 989 | "column": 9, 990 | "line": 8, 991 | }, 992 | "start": Position { 993 | "column": 6, 994 | "line": 8, 995 | }, 996 | }, 997 | "name": "div", 998 | "start": 119, 999 | "type": "JSXIdentifier", 1000 | }, 1001 | "start": 117, 1002 | "type": "JSXClosingElement", 1003 | }, 1004 | "end": 123, 1005 | "extra": Object { 1006 | "parenStart": 100, 1007 | "parenthesized": true, 1008 | }, 1009 | "loc": SourceLocation { 1010 | "end": Position { 1011 | "column": 10, 1012 | "line": 8, 1013 | }, 1014 | "start": Position { 1015 | "column": 4, 1016 | "line": 6, 1017 | }, 1018 | }, 1019 | "openingElement": Node { 1020 | "attributes": Array [], 1021 | "end": 111, 1022 | "loc": SourceLocation { 1023 | "end": Position { 1024 | "column": 9, 1025 | "line": 6, 1026 | }, 1027 | "start": Position { 1028 | "column": 4, 1029 | "line": 6, 1030 | }, 1031 | }, 1032 | "name": Node { 1033 | "end": 110, 1034 | "loc": SourceLocation { 1035 | "end": Position { 1036 | "column": 8, 1037 | "line": 6, 1038 | }, 1039 | "start": Position { 1040 | "column": 5, 1041 | "line": 6, 1042 | }, 1043 | }, 1044 | "name": "div", 1045 | "start": 107, 1046 | "type": "JSXIdentifier", 1047 | }, 1048 | "selfClosing": false, 1049 | "start": 106, 1050 | "type": "JSXOpeningElement", 1051 | }, 1052 | "start": 106, 1053 | "type": "JSXElement", 1054 | }, 1055 | "end": 128, 1056 | "loc": SourceLocation { 1057 | "end": Position { 1058 | "column": 4, 1059 | "line": 9, 1060 | }, 1061 | "start": Position { 1062 | "column": 2, 1063 | "line": 5, 1064 | }, 1065 | }, 1066 | "start": 93, 1067 | "type": "ReturnStatement", 1068 | }, 1069 | ], 1070 | "directives": Array [], 1071 | "end": 130, 1072 | "loc": SourceLocation { 1073 | "end": Position { 1074 | "column": 1, 1075 | "line": 10, 1076 | }, 1077 | "start": Position { 1078 | "column": 25, 1079 | "line": 4, 1080 | }, 1081 | }, 1082 | "start": 89, 1083 | "type": "BlockStatement", 1084 | }, 1085 | "end": 130, 1086 | "expression": false, 1087 | "generator": false, 1088 | "id": Node { 1089 | "end": 81, 1090 | "loc": SourceLocation { 1091 | "end": Position { 1092 | "column": 17, 1093 | "line": 4, 1094 | }, 1095 | "identifierName": "FuncComp", 1096 | "start": Position { 1097 | "column": 9, 1098 | "line": 4, 1099 | }, 1100 | }, 1101 | "name": "FuncComp", 1102 | "start": 73, 1103 | "type": "Identifier", 1104 | }, 1105 | "loc": SourceLocation { 1106 | "end": Position { 1107 | "column": 1, 1108 | "line": 10, 1109 | }, 1110 | "start": Position { 1111 | "column": 0, 1112 | "line": 4, 1113 | }, 1114 | }, 1115 | "params": Array [ 1116 | Node { 1117 | "end": 87, 1118 | "loc": SourceLocation { 1119 | "end": Position { 1120 | "column": 23, 1121 | "line": 4, 1122 | }, 1123 | "identifierName": "props", 1124 | "start": Position { 1125 | "column": 18, 1126 | "line": 4, 1127 | }, 1128 | }, 1129 | "name": "props", 1130 | "start": 82, 1131 | "type": "Identifier", 1132 | }, 1133 | ], 1134 | "start": 64, 1135 | "type": "FunctionDeclaration", 1136 | }, 1137 | Node { 1138 | "end": 156, 1139 | "expression": Node { 1140 | "end": 155, 1141 | "left": Node { 1142 | "computed": false, 1143 | "end": 150, 1144 | "loc": SourceLocation { 1145 | "end": Position { 1146 | "column": 18, 1147 | "line": 12, 1148 | }, 1149 | "start": Position { 1150 | "column": 0, 1151 | "line": 12, 1152 | }, 1153 | }, 1154 | "object": Node { 1155 | "end": 140, 1156 | "loc": SourceLocation { 1157 | "end": Position { 1158 | "column": 8, 1159 | "line": 12, 1160 | }, 1161 | "identifierName": "FuncComp", 1162 | "start": Position { 1163 | "column": 0, 1164 | "line": 12, 1165 | }, 1166 | }, 1167 | "name": "FuncComp", 1168 | "start": 132, 1169 | "type": "Identifier", 1170 | }, 1171 | "property": Node { 1172 | "end": 150, 1173 | "loc": SourceLocation { 1174 | "end": Position { 1175 | "column": 18, 1176 | "line": 12, 1177 | }, 1178 | "identifierName": "propTypes", 1179 | "start": Position { 1180 | "column": 9, 1181 | "line": 12, 1182 | }, 1183 | }, 1184 | "name": "propTypes", 1185 | "start": 141, 1186 | "type": "Identifier", 1187 | }, 1188 | "start": 132, 1189 | "type": "MemberExpression", 1190 | }, 1191 | "loc": SourceLocation { 1192 | "end": Position { 1193 | "column": 23, 1194 | "line": 12, 1195 | }, 1196 | "start": Position { 1197 | "column": 0, 1198 | "line": 12, 1199 | }, 1200 | }, 1201 | "operator": "=", 1202 | "right": Node { 1203 | "end": 155, 1204 | "loc": SourceLocation { 1205 | "end": Position { 1206 | "column": 23, 1207 | "line": 12, 1208 | }, 1209 | "start": Position { 1210 | "column": 21, 1211 | "line": 12, 1212 | }, 1213 | }, 1214 | "properties": Array [], 1215 | "start": 153, 1216 | "type": "ObjectExpression", 1217 | }, 1218 | "start": 132, 1219 | "type": "AssignmentExpression", 1220 | }, 1221 | "loc": SourceLocation { 1222 | "end": Position { 1223 | "column": 24, 1224 | "line": 12, 1225 | }, 1226 | "start": Position { 1227 | "column": 0, 1228 | "line": 12, 1229 | }, 1230 | }, 1231 | "start": 132, 1232 | "type": "ExpressionStatement", 1233 | }, 1234 | Node { 1235 | "end": 184, 1236 | "expression": Node { 1237 | "end": 183, 1238 | "left": Node { 1239 | "computed": false, 1240 | "end": 178, 1241 | "loc": SourceLocation { 1242 | "end": Position { 1243 | "column": 21, 1244 | "line": 13, 1245 | }, 1246 | "start": Position { 1247 | "column": 0, 1248 | "line": 13, 1249 | }, 1250 | }, 1251 | "object": Node { 1252 | "end": 165, 1253 | "loc": SourceLocation { 1254 | "end": Position { 1255 | "column": 8, 1256 | "line": 13, 1257 | }, 1258 | "identifierName": "FuncComp", 1259 | "start": Position { 1260 | "column": 0, 1261 | "line": 13, 1262 | }, 1263 | }, 1264 | "name": "FuncComp", 1265 | "start": 157, 1266 | "type": "Identifier", 1267 | }, 1268 | "property": Node { 1269 | "end": 178, 1270 | "loc": SourceLocation { 1271 | "end": Position { 1272 | "column": 21, 1273 | "line": 13, 1274 | }, 1275 | "identifierName": "defaultProps", 1276 | "start": Position { 1277 | "column": 9, 1278 | "line": 13, 1279 | }, 1280 | }, 1281 | "name": "defaultProps", 1282 | "start": 166, 1283 | "type": "Identifier", 1284 | }, 1285 | "start": 157, 1286 | "type": "MemberExpression", 1287 | }, 1288 | "loc": SourceLocation { 1289 | "end": Position { 1290 | "column": 26, 1291 | "line": 13, 1292 | }, 1293 | "start": Position { 1294 | "column": 0, 1295 | "line": 13, 1296 | }, 1297 | }, 1298 | "operator": "=", 1299 | "right": Node { 1300 | "end": 183, 1301 | "loc": SourceLocation { 1302 | "end": Position { 1303 | "column": 26, 1304 | "line": 13, 1305 | }, 1306 | "start": Position { 1307 | "column": 24, 1308 | "line": 13, 1309 | }, 1310 | }, 1311 | "properties": Array [], 1312 | "start": 181, 1313 | "type": "ObjectExpression", 1314 | }, 1315 | "start": 157, 1316 | "type": "AssignmentExpression", 1317 | }, 1318 | "loc": SourceLocation { 1319 | "end": Position { 1320 | "column": 27, 1321 | "line": 13, 1322 | }, 1323 | "start": Position { 1324 | "column": 0, 1325 | "line": 13, 1326 | }, 1327 | }, 1328 | "start": 157, 1329 | "type": "ExpressionStatement", 1330 | }, 1331 | Node { 1332 | "declaration": Node { 1333 | "end": 210, 1334 | "loc": SourceLocation { 1335 | "end": Position { 1336 | "column": 23, 1337 | "line": 16, 1338 | }, 1339 | "identifierName": "FuncComp", 1340 | "start": Position { 1341 | "column": 15, 1342 | "line": 16, 1343 | }, 1344 | }, 1345 | "name": "FuncComp", 1346 | "start": 202, 1347 | "type": "Identifier", 1348 | }, 1349 | "end": 211, 1350 | "loc": SourceLocation { 1351 | "end": Position { 1352 | "column": 24, 1353 | "line": 16, 1354 | }, 1355 | "start": Position { 1356 | "column": 0, 1357 | "line": 16, 1358 | }, 1359 | }, 1360 | "start": 187, 1361 | "type": "ExportDefaultDeclaration", 1362 | }, 1363 | ], 1364 | "directives": Array [], 1365 | "end": 212, 1366 | "loc": SourceLocation { 1367 | "end": Position { 1368 | "column": 0, 1369 | "line": 17, 1370 | }, 1371 | "start": Position { 1372 | "column": 0, 1373 | "line": 1, 1374 | }, 1375 | }, 1376 | "sourceType": "module", 1377 | "start": 0, 1378 | "type": "Program", 1379 | } 1380 | `; 1381 | -------------------------------------------------------------------------------- /packages/react-refactor/test/classToFunctional.spec.js: -------------------------------------------------------------------------------- 1 | const {NotAReactComponent} = require("../src/errors"); 2 | const {patchString} = require("../src/stringUtils"); 3 | const {removeSpaces} = require("./testUtils"); 4 | const {classToFunctional} = require('../src/classToFunctional') 5 | const {parse} = require('../src/parser') 6 | 7 | const classTemplate = ` 8 | class ClassComp extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | } 12 | doSomething(){ 13 | //doSomething() 14 | } 15 | render() { 16 | let {def} = this.props 17 | let {props: {ghi}} = this 18 | return ( 19 |
{this.props.abc}
20 | ); 21 | } 22 | } 23 | `.trim() 24 | 25 | const functionalTemplate = ` 26 | function ClassComp(props){ 27 | let self = {props}; 28 | 29 | let {def} = self.props 30 | let {props: {ghi}} = self 31 | return ( 32 |
{self.props.abc}
33 | ); 34 | } 35 | ` 36 | 37 | const genericClassTemplate = ` 38 | class ClassComp extends React.Component { 39 | constructor(props) { 40 | super(props); 41 | } 42 | doSomething(){ 43 | //doSomething() 44 | } 45 | } 46 | `.trim() 47 | 48 | describe('classToFunctional', () => { 49 | it('should convert class to functional comp', () => { 50 | let classComp = parse(classTemplate) 51 | let patch = classToFunctional(classTemplate, classComp.body[0]) 52 | expect(Array.isArray(patch)).toBe(true) 53 | expect(patch).toMatchSnapshot() 54 | let output = patchString(classTemplate, patch) 55 | expect(removeSpaces(output)).toBe(removeSpaces(functionalTemplate)) 56 | }) 57 | it('should throw exception', () => { 58 | let classComp = parse(genericClassTemplate) 59 | expect(() => { 60 | classToFunctional(classTemplate, classComp.body[0]) 61 | }).toThrow(NotAReactComponent) 62 | }) 63 | 64 | describe('should supports different superclass', () => { 65 | [ 66 | 'React.PureComponent', 67 | 'PureComponent', 68 | 'Component', 69 | ].forEach(superClass => { 70 | it(`should support ${superClass}`, () => { 71 | let classCompVariant = classTemplate.replace('React.Component', superClass) 72 | let functionalCompVariant = functionalTemplate.replace('React.Component', superClass) 73 | let classComp = parse(classCompVariant) 74 | let patch = classToFunctional(classTemplate, classComp.body[0]) 75 | expect(Array.isArray(patch)).toBe(true) 76 | expect(patch).toMatchSnapshot() 77 | let output = patchString(classCompVariant, patch) 78 | expect(removeSpaces(output)).toBe(removeSpaces(functionalCompVariant)) 79 | }) 80 | }) 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /packages/react-refactor/test/functionalToClass.spec.js: -------------------------------------------------------------------------------- 1 | const {NotAReactComponent} = require("../src/errors"); 2 | const {patchString} = require("../src/stringUtils"); 3 | const {removeSpaces} = require("./testUtils"); 4 | const {parse} = require('../src/parser') 5 | const {functionalToClass} = require('../src/functionalToClass') 6 | 7 | const functionalTemplate1 = ` 8 | function FunctionalComp(props){ 9 | return ( 10 |
{props.abc}
11 | ); 12 | } 13 | ` 14 | const classTemplate1 = ` 15 | class FunctionalComp extends React.Component { 16 | render() { 17 | let props = this.props; 18 | return ( 19 |
{props.abc}
20 | ); 21 | } 22 | } 23 | `.trim() 24 | 25 | const functionalTemplate2 = ` 26 | function FunctionalComp({abc, cde}){ 27 | return ( 28 |
{abc}
29 | ); 30 | } 31 | ` 32 | const classTemplate2 = ` 33 | class FunctionalComp extends React.Component { 34 | render() { 35 | let {abc, cde} = this.props; 36 | return ( 37 |
{abc}
38 | ); 39 | } 40 | } 41 | `.trim() 42 | 43 | const functionalTemplate3 = ` 44 | function FunctionalComp(){ 45 | return ( 46 |
blablabla
47 | ); 48 | } 49 | ` 50 | const classTemplate3 = ` 51 | class FunctionalComp extends React.Component { 52 | render() { 53 | return ( 54 |
blablabla
55 | ); 56 | } 57 | } 58 | `.trim() 59 | 60 | const genericFunction = ` 61 | function testFunc(props){ 62 | return 'Saluti from Rome!' 63 | } 64 | ` 65 | 66 | 67 | describe('functionalToClass', () => { 68 | it('should convert functional to class comp', () => { 69 | let classComp = parse(functionalTemplate1) 70 | let patch = functionalToClass(functionalTemplate1, classComp.body[0]) 71 | expect(Array.isArray(patch)).toBe(true) 72 | expect(patch).toMatchSnapshot() 73 | let output = patchString(functionalTemplate1, patch) 74 | expect(removeSpaces(output)).toBe(removeSpaces(classTemplate1)) 75 | }) 76 | 77 | it('should convert functional to class comp with destructuring', () => { 78 | let classComp = parse(functionalTemplate2) 79 | let patch = functionalToClass(functionalTemplate2, classComp.body[0]) 80 | expect(Array.isArray(patch)).toBe(true) 81 | expect(patch).toMatchSnapshot() 82 | let output = patchString(functionalTemplate2, patch) 83 | expect(removeSpaces(output)).toBe(removeSpaces(classTemplate2)) 84 | }) 85 | 86 | it('should convert functional to class comp without props', () => { 87 | let classComp = parse(functionalTemplate3) 88 | let patch = functionalToClass(functionalTemplate3, classComp.body[0]) 89 | expect(Array.isArray(patch)).toBe(true) 90 | expect(patch).toMatchSnapshot() 91 | let output = patchString(functionalTemplate3, patch) 92 | expect(removeSpaces(output)).toBe(removeSpaces(classTemplate3)) 93 | }) 94 | 95 | it('should throw', () => { 96 | let parsedFunc = parse(genericFunction) 97 | expect(() => { 98 | functionalToClass(genericFunction, parsedFunc.body[0]) 99 | }).toThrow(NotAReactComponent) 100 | }) 101 | }) 102 | -------------------------------------------------------------------------------- /packages/react-refactor/test/index.spec.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs") 3 | const {removeSpaces} = require("./testUtils"); 4 | const {patchString} = require("../src/stringUtils"); 5 | const {execRefactor} = require("../src/index"); 6 | const {parseFile} = require('../src/parser') 7 | const fixture = filename => path.join(__dirname, '__fixtures__', filename) 8 | 9 | let example, refactoredExample; 10 | 11 | beforeAll(done => { 12 | fs.readFile(fixture('example.jsx.txt'), 'utf8', (err, data) => { 13 | if (err) throw new Error() 14 | example = data 15 | done() 16 | }) 17 | }) 18 | 19 | beforeAll(done => { 20 | fs.readFile(fixture('refactoredExample.jsx.txt'), 'utf8', (err, data) => { 21 | if (err) throw new Error() 22 | refactoredExample = data 23 | done() 24 | }) 25 | }) 26 | 27 | 28 | describe('refactor', () => { 29 | it.only('should convert', () => { 30 | let {patch, skipped, output} = execRefactor(example) 31 | 32 | expect(Array.isArray(patch)).toBe(true) 33 | expect(Array.isArray(skipped)).toBe(true) 34 | expect(patch).toMatchSnapshot() 35 | expect(skipped).toMatchSnapshot() 36 | 37 | expect(removeSpaces(output)) 38 | .toBe(removeSpaces(refactoredExample)) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /packages/react-refactor/test/parser.spec.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require('fs') 3 | const {parse} = require('../src/parser') 4 | const fixture = filename => path.join(__dirname, '__fixtures__', filename) 5 | 6 | 7 | let classComp; 8 | beforeAll(done => { 9 | fs.readFile(fixture('ClassComp.jsx.txt'), 'utf8', (err, data) => { 10 | if (err) throw new Error() 11 | classComp = data 12 | done() 13 | }) 14 | }) 15 | 16 | let funcComp; 17 | beforeAll(done => { 18 | fs.readFile(fixture('FuncComp.jsx.txt'), 'utf8', (err, data) => { 19 | if (err) throw new Error() 20 | funcComp = data 21 | done() 22 | }) 23 | }) 24 | 25 | describe('parser', () => { 26 | it('should parse Class file', () => { 27 | let parsedClass = parse(classComp) 28 | expect(parsedClass).toMatchSnapshot() 29 | }) 30 | it('should parse Func file', () => { 31 | let parsedFunc = parse(funcComp) 32 | expect(parsedFunc).toMatchSnapshot() 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /packages/react-refactor/test/stringUtils.spec.js: -------------------------------------------------------------------------------- 1 | const {patchString, INSERT, REMOVE, insert, remove} = require('../src/stringUtils'); 2 | const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 3 | 4 | describe('patchString', () => { 5 | it('should insert a new part', () => { 6 | const patch = [ 7 | {operation: INSERT, start: 5, payload: 'xxabcdxx'} 8 | ]; 9 | expect(patchString(alphabet, patch)).toBe('ABCDExxabcdxxFGHIJKLMNOPQRSTUVWXYZ') 10 | }); 11 | 12 | it('should remove a part', () => { 13 | const patch = [ 14 | {operation: REMOVE, start: 5, end: 10} 15 | ]; 16 | expect(patchString(alphabet, patch)).toBe('ABCDEKLMNOPQRSTUVWXYZ') 17 | }); 18 | 19 | it('should apply differents patches (ir)', () => { 20 | const patch = [ 21 | insert(0, 'xxabcdxx'), 22 | remove(2, 5), //CDE 23 | ]; 24 | expect(patchString(alphabet, patch)).toBe('xxabcdxxABFGHIJKLMNOPQRSTUVWXYZ') 25 | }) 26 | 27 | it('should apply differents patches (irir)', () => { 28 | const patch = [ 29 | insert(0, 'xxabcdxx'), 30 | remove(2, 5), //CDE 31 | insert(5, 'yyefghyy'), 32 | remove(5, 9), //EFGH 33 | ]; 34 | 35 | expect(patchString(alphabet, patch)).toBe('xxabcdxxAByyefghyyJKLMNOPQRSTUVWXYZ') 36 | }) 37 | 38 | it('should apply differents patches (rrirri)', () => { 39 | let patch = [] 40 | 41 | //rem AB 42 | patch.push(remove(0, 2)) 43 | expect(patchString(alphabet, patch)).toBe('CDEFGHIJKLMNOPQRSTUVWXYZ') 44 | 45 | //rem FG 46 | patch.push(remove(5, 7)) 47 | expect(patchString(alphabet, patch)).toBe('CDEHIJKLMNOPQRSTUVWXYZ') 48 | 49 | //ins yy123yy 50 | patch.push(insert(11, 'yy123yy')) 51 | expect(patchString(alphabet, patch)).toBe('CDEHIJKyy123yyLMNOPQRSTUVWXYZ') 52 | 53 | //rem L 54 | patch.push(remove(11, 12)) 55 | expect(patchString(alphabet, patch)).toBe('CDEHIJKyy123yyMNOPQRSTUVWXYZ') 56 | 57 | //rem Z 58 | patch.push(remove(25, 26)) 59 | expect(patchString(alphabet, patch)).toBe('CDEHIJKyy123yyMNOPQRSTUVWXY') 60 | 61 | //ins xx456xx 62 | patch.push(insert(26, 'xx456xx')) 63 | expect(patchString(alphabet, patch)).toBe('CDEHIJKyy123yyMNOPQRSTUVWXYxx456xx') 64 | }) 65 | 66 | it('should throw expection', () => { 67 | expect(() => { 68 | patchString(alphabet, [{start: 20, end: 5}]) 69 | }).toThrowError('StartGTEndError') 70 | 71 | expect(() => { 72 | patchString(alphabet, [{start: 50}]) 73 | }).toThrowError('StartGTLengthError') 74 | 75 | expect(() => { 76 | patchString(alphabet, [insert(20, 'hei'), insert(19, 'ciao')]) 77 | }).toThrowError('PatchesUnsortedError') 78 | 79 | expect(() => { 80 | patchString(alphabet, [{operation: 'ciao', start: 20}]) 81 | }).toThrowError('UnsupportedOperationError') 82 | }) 83 | }); 84 | -------------------------------------------------------------------------------- /packages/react-refactor/test/testUtils.js: -------------------------------------------------------------------------------- 1 | function removeSpaces(string){ 2 | return string.replace(/\s/g, '') 3 | } 4 | 5 | module.exports = { 6 | removeSpaces 7 | } 8 | -------------------------------------------------------------------------------- /packages/react-refactor/test/treeUtils.spec.js: -------------------------------------------------------------------------------- 1 | const {getOrThrow, get} = require('../src/treeUtils') 2 | 3 | const obj = { 4 | a: 10, 5 | z: 0, 6 | b: { 7 | ba: 20, 8 | bb: { 9 | bba: 30, 10 | bbb: { 11 | bbba: 40 12 | } 13 | } 14 | } 15 | } 16 | 17 | describe('getOrThrow', () => { 18 | it('should get a data', () => { 19 | expect(getOrThrow(obj, ['z'])).toBe(0) 20 | expect(getOrThrow(obj, ['a'])).toBe(10) 21 | expect(getOrThrow(obj, ['b', 'ba'])).toBe(20) 22 | expect(getOrThrow(obj, ['b', 'bb', 'bba'])).toBe(30) 23 | expect(getOrThrow(obj, ['b', 'bb', 'bbb', 'bbba'])).toBe(40) 24 | }) 25 | 26 | it('should throw an exception', () => { 27 | expect(() => getOrThrow(obj, ['c'])).toThrow() 28 | expect(() => getOrThrow(obj, ['b', 'c'])).toThrow() 29 | expect(() => getOrThrow(obj, ['b', 'bb', 'c'])).toThrow() 30 | expect(() => getOrThrow(obj, ['b', 'bb', 'bba', 'c'])).toThrow() 31 | }) 32 | }) 33 | 34 | describe('get', () => { 35 | it('should get a data', () => { 36 | expect(get(obj, ['z'])).toBe(0) 37 | expect(get(obj, ['a'])).toBe(10) 38 | expect(get(obj, ['b', 'ba'])).toBe(20) 39 | expect(get(obj, ['b', 'bb', 'bba'])).toBe(30) 40 | expect(get(obj, ['b', 'bb', 'bbb', 'bbba'])).toBe(40) 41 | }) 42 | 43 | it('should return undefined', () => { 44 | expect(get(obj, ['c'])).toBeFalsy() 45 | expect(get(obj, ['b', 'c'])).toBeFalsy() 46 | expect(get(obj, ['b', 'bb', 'c'])).toBeFalsy() 47 | expect(get(obj, ['b', 'bb', 'bba', 'c'])).toBeFalsy() 48 | }) 49 | 50 | it('should return defaultValue', () => { 51 | const defVal = '__DEFAULT__' 52 | expect(get(obj, ['c'], defVal)).toBe(defVal) 53 | expect(get(obj, ['b', 'c'], defVal)).toBe(defVal) 54 | expect(get(obj, ['b', 'bb', 'c'], defVal)).toBe(defVal) 55 | expect(get(obj, ['b', 'bb', 'bba', 'c'], defVal)).toBe(defVal) 56 | }) 57 | }) 58 | --------------------------------------------------------------------------------