├── .gitattributes
├── .eslintignore
├── packages
├── react-pure-to-class
│ ├── .npmignore
│ ├── __testfixtures__
│ │ ├── typescript.input.tsx
│ │ ├── arrow-functions.input.js
│ │ ├── regular-functions.input.js
│ │ ├── typescript.output.tsx
│ │ ├── arrow-functions.output.js
│ │ └── regular-functions.output.js
│ ├── package.json
│ ├── cli.js
│ ├── __tests__
│ │ └── pure-to-class-test.js
│ ├── README.md
│ └── pure-to-class.js
└── react-pure-to-class-vscode
│ ├── icon.png
│ ├── example.gif
│ ├── extension.js
│ ├── webpack.config.js
│ ├── .vscode
│ └── launch.json
│ ├── README.md
│ ├── package.json
│ └── init.js
├── .gitignore
├── README.md
├── .editorconfig
├── .eslintrc
└── package.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | __testfixtures__
2 | node_modules
3 | __bundle.js
4 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class/.npmignore:
--------------------------------------------------------------------------------
1 | __testfixtures__
2 | __tests__
3 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class-vscode/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/angryobject/react-pure-to-class/HEAD/packages/react-pure-to-class-vscode/icon.png
--------------------------------------------------------------------------------
/packages/react-pure-to-class-vscode/example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/angryobject/react-pure-to-class/HEAD/packages/react-pure-to-class-vscode/example.gif
--------------------------------------------------------------------------------
/packages/react-pure-to-class-vscode/extension.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const vscode = require('vscode');
4 | const { init } = require('./__bundle');
5 |
6 | module.exports = init(vscode);
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | Thumbs.db
3 |
4 | *.swp
5 | .idea
6 | ./.vscode
7 |
8 | npm-debug.log
9 | lerna-debug.log
10 | yarn-error.log
11 |
12 | .babel_cache
13 | node_modules
14 | build
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | A [jscodeshift](https://github.com/facebook/jscodeshift) transformer to create react class component from pure functional component.
2 |
3 | This repo uses [yarn workspaces](https://yarnpkg.com/lang/en/docs/workspaces/) feature, it contains the transformer itself and a vscode extension.
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # change these settings to your own preference
11 | indent_style = space
12 | indent_size = 2
13 |
14 | # we recommend you to keep these unchanged
15 | end_of_line = lf
16 | charset = utf-8
17 | trim_trailing_whitespace = true
18 | insert_final_newline = true
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": [
4 | "eslint:recommended",
5 | "plugin:react/recommended"
6 | ],
7 | "plugins": [
8 | "react",
9 | "prettier"
10 | ],
11 | "env": {
12 | "node": true,
13 | "es6": true,
14 | "jest": true
15 | },
16 | "settings": {
17 | "react": {
18 | "version": "16.0"
19 | }
20 | },
21 | "rules": {
22 | "prettier/prettier": [
23 | "error",
24 | {
25 | "trailingComma": "es5",
26 | "singleQuote": true
27 | }
28 | ]
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class-vscode/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 |
3 | module.exports = {
4 | mode: 'production',
5 |
6 | context: __dirname,
7 |
8 | target: 'node',
9 |
10 | entry: {
11 | index: './init.js',
12 | },
13 |
14 | output: {
15 | path: __dirname,
16 | filename: '__bundle.js',
17 | library: 'init',
18 | libraryTarget: 'commonjs',
19 | },
20 |
21 | plugins: [
22 | new webpack.DefinePlugin({
23 | 'process.env': {
24 | NODE_ENV: JSON.stringify('production'),
25 | },
26 | }),
27 | ],
28 |
29 | stats: 'none',
30 | };
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "workspaces": [
4 | "packages/*"
5 | ],
6 | "scripts": {
7 | "lint": "eslint packages",
8 | "test": "jest",
9 | "precommit": "npm test && npm run lint",
10 | "publish-lib": "cd packages/react-pure-to-class && npm publish",
11 | "publish-ext": "cd packages/react-pure-to-class-vscode && vsce publish"
12 | },
13 | "devDependencies": {
14 | "babel-eslint": "^10.0.1",
15 | "eslint": "^5.14.1",
16 | "eslint-plugin-prettier": "^3.0.1",
17 | "eslint-plugin-react": "^7.12.4",
18 | "husky": "^1.3.1",
19 | "jest": "^24.1.0",
20 | "prettier": "^1.16.4"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class/__testfixtures__/typescript.input.tsx:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import * as React from 'react';
3 |
4 | interface IProps {
5 | message: string;
6 | children: React.Element;
7 | }
8 |
9 | type OtherProps = {};
10 |
11 | const MyComponent1 = (props: IProps) => {
12 | return
{props.message}
;
13 | };
14 |
15 | function MyComponent2({
16 | message = 'foobar',
17 | }: IProps & OtherProps) {
18 | return {message}
;
19 | }
20 |
21 | const MyComponent3 = (p: IProps & OtherProps) =>
22 | p.children ? {p.children}
: null;
23 |
24 | function MyComponent4(p: IProps) {
25 | return p.children ? {p.children}
: null;
26 | }
27 |
28 | const NonReact = v => v.x + v.y;
29 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-pure-to-class",
3 | "description": "A transformer to convert pure component to class component",
4 | "version": "1.1.6",
5 | "main": "pure-to-class.js",
6 | "license": "MIT",
7 | "bin": "./cli.js",
8 | "peerDependencies": {
9 | "jscodeshift": "^0.4.0"
10 | },
11 | "devDependencies": {
12 | "jscodeshift": "^0.6.3"
13 | },
14 | "author": {
15 | "name": "Max Shishkin"
16 | },
17 | "homepage": "https://github.com/angryobject/react-pure-to-class",
18 | "keywords": [
19 | "codemod",
20 | "jscodeshift",
21 | "react"
22 | ],
23 | "repository": {
24 | "type": "git",
25 | "url": "https://github.com/angryobject/react-pure-to-class"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const stdin = process.openStdin();
4 | const jscodeshift = require('jscodeshift');
5 | const getParser = require('jscodeshift/src/getParser');
6 | const pureToClass = require('./pure-to-class');
7 | const parser = getParser(process.argv[2] || 'babel');
8 |
9 | let input = '';
10 |
11 | stdin.on('data', function(chunk) {
12 | input += chunk;
13 | });
14 |
15 | stdin.on('end', function() {
16 | let output;
17 |
18 | try {
19 | output = pureToClass(
20 | { source: input },
21 | {
22 | jscodeshift,
23 | stats: () => {},
24 | },
25 | {
26 | parser,
27 | }
28 | );
29 | } finally {
30 | console.log(output || input); // eslint-disable-line no-console
31 | }
32 | });
33 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class/__testfixtures__/arrow-functions.input.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const MyComponent = props => {
4 | return {props.message}
;
5 | };
6 |
7 | const MyComponent = (props) => {props.message}
;
8 |
9 | const MyComponent = p => p.children ? {p.children}
: null;
10 |
11 | export const MyComponent = p => p.children ? {p.children}
: null;
12 |
13 | module.exports = (props) => {
14 | return {props.message}
;
15 | };
16 |
17 | export default props => {
18 | return {props.message}
;
19 | };
20 |
21 | const NonReact = v => v.x + v.y;
22 |
23 | const StillReact = () => {
24 | const a = bla-bla
;
25 | return 2 + 2;
26 | };
27 |
28 | export const AnotherMyComponent = ({ items }) => (
29 | {items.map(item => - {item}
)}
30 |
)
31 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class-vscode/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that launches the extension inside a new window
2 | {
3 | "version": "0.1.0",
4 | "configurations": [
5 | {
6 | "name": "Launch Extension",
7 | "type": "extensionHost",
8 | "request": "launch",
9 | "runtimeExecutable": "${execPath}",
10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
11 | "stopOnEntry": false
12 | },
13 | {
14 | "name": "Launch Tests",
15 | "type": "extensionHost",
16 | "request": "launch",
17 | "runtimeExecutable": "${execPath}",
18 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/test" ],
19 | "stopOnEntry": false
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class/__testfixtures__/regular-functions.input.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function MyComponent(props) {
4 | return {props.message}
;
5 | };
6 |
7 | module.exports = function MyComponent(props) {
8 | return {props.message}
;
9 | };
10 |
11 | export default function MyComponent(props) {
12 | return {props.message}
;
13 | };
14 |
15 | function MyComponent() {
16 | return bla-bla
;
17 | }
18 |
19 | function MyComponent(p) {
20 | return p.children ? {children}
: null;
21 | }
22 |
23 | const MyComponent = function() {
24 | return bla-bla
;
25 | }
26 |
27 | const MyComponent = function MyFunction() {
28 | return bla-bla
;
29 | }
30 |
31 | function NonReact(v) {
32 | return v.x + v.y;
33 | }
34 |
35 | function StillReact() {
36 | const a = bla-bla
;
37 | return 2 + 2;
38 | }
39 |
40 | function MyComponent({ items }) {
41 | return (
42 | {items.map(item => - {item}
)}
43 |
);
44 | }
45 |
46 | function MyComponent({ items }) {
47 | const lis = items.map(item => {item});
48 |
49 | return ();
50 | }
51 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class/__testfixtures__/typescript.output.tsx:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import * as React from 'react';
3 |
4 | interface IProps {
5 | message: string;
6 | children: React.Element;
7 | }
8 |
9 | type OtherProps = {};
10 |
11 | class MyComponent1 extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | }
15 |
16 | render() {
17 | const {
18 | props,
19 | } = this;
20 |
21 | return {props.message}
;
22 | }
23 | }
24 |
25 | class MyComponent2 extends React.Component {
26 | constructor(props) {
27 | super(props);
28 | }
29 |
30 | render() {
31 | const {
32 | message = 'foobar',
33 | } = this.props;
34 |
35 | return {message}
;
36 | }
37 | }
38 |
39 | class MyComponent3 extends React.Component {
40 | constructor(props) {
41 | super(props);
42 | }
43 |
44 | render() {
45 | const p = this.props;
46 | return p.children ? {p.children}
: null;
47 | }
48 | }
49 |
50 | class MyComponent4 extends React.Component {
51 | constructor(props) {
52 | super(props);
53 | }
54 |
55 | render() {
56 | const p = this.props;
57 | return p.children ? {p.children}
: null;
58 | }
59 | }
60 |
61 | class NonReact extends React.Component {
62 | constructor(props) {
63 | super(props);
64 | }
65 |
66 | render() {
67 | const v = this.props;
68 | return v.x + v.y;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class-vscode/README.md:
--------------------------------------------------------------------------------
1 | Replaces pure functional react components with class components. Works both for JavaScript and TypeScript.
2 |
3 | Select a block of code, choose `React Pure To Class` from the Command Palette.
4 |
5 | 
6 |
7 | Turns this:
8 |
9 | ```javascript
10 | function MyComponent(props) {
11 | return (
12 |
13 | {props.children}
14 |
15 | );
16 | }
17 | ```
18 |
19 | into this:
20 |
21 | ```javascript
22 | class MyComponent extends React.Component {
23 | constructor(props) {
24 | super(props);
25 | }
26 |
27 | render() {
28 | const {
29 | props,
30 | } = this;
31 |
32 | return (
33 |
34 | {props.children}
35 |
36 | );
37 | }
38 | }
39 | ```
40 |
41 | It makes some assumptions about functions that can be transformed:
42 |
43 | * function should has zero or one argument (i.e. `props`, though the name me be different)
44 | * the argument, if present, should be an identifier (`foo => {..}`) or object pattern(`({ foo }) => {...}`). This means array patterns (`([foo]) => {...}`) and default function parameters (`(foo = defaultFoo) => {...}`) don't work. `props` is always an object and default props are handled differently in React
45 | * the functions should not appear inside other functions, be property of an objects or method of a class
46 |
47 | Extension options:
48 |
49 | `reactPureToClass.reactComponent` - string, where to find base react component, defaults to `React.Component`.
50 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class-vscode/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "react-pure-to-class-vscode",
4 | "displayName": "React Pure To Class",
5 | "description": "Convert pure react components to class components",
6 | "version": "1.1.8",
7 | "publisher": "angryobject",
8 | "license": "MIT",
9 | "author": {
10 | "name": "Max Shishkin"
11 | },
12 | "homepage": "https://github.com/angryobject/react-pure-to-class",
13 | "keywords": [
14 | "codemod",
15 | "jscodeshift",
16 | "react",
17 | "vscode"
18 | ],
19 | "main": "./extension.js",
20 | "engines": {
21 | "vscode": "^1.12.0"
22 | },
23 | "categories": [
24 | "Formatters",
25 | "Other"
26 | ],
27 | "activationEvents": [
28 | "onCommand:extension.reactPureToClass"
29 | ],
30 | "contributes": {
31 | "commands": [
32 | {
33 | "command": "extension.reactPureToClass",
34 | "title": "React Pure To Class"
35 | }
36 | ],
37 | "configuration": {
38 | "type": "object",
39 | "title": "React Pure To Class",
40 | "properties": {
41 | "reactPureToClass.reactComponent": {
42 | "type": "string",
43 | "default": "React.Component",
44 | "description": "Where to find base React component"
45 | }
46 | }
47 | }
48 | },
49 | "scripts": {
50 | "watch": "webpack --watch",
51 | "build": "webpack"
52 | },
53 | "devDependencies": {
54 | "jscodeshift": "^0.6.3",
55 | "react-pure-to-class": "^1.1.5",
56 | "vscode": "^1.1.29",
57 | "webpack": "^4.29.5",
58 | "webpack-cli": "^3.2.3"
59 | },
60 | "repository": {
61 | "type": "git",
62 | "url": "https://github.com/angryobject/react-pure-to-class"
63 | },
64 | "icon": "icon.png"
65 | }
66 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class/__tests__/pure-to-class-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest;
4 |
5 | defineTest(__dirname, 'pure-to-class', null, 'regular-functions');
6 | defineTest(__dirname, 'pure-to-class', null, 'arrow-functions');
7 |
8 | const fs = require('fs');
9 | const jscodeshift = require('jscodeshift');
10 | const getParser = require('jscodeshift/src/getParser');
11 | const pureToClass = require('../pure-to-class');
12 |
13 | const api = {
14 | jscodeshift,
15 | stats: () => {},
16 | };
17 |
18 | const getFixture = name =>
19 | fs.readFileSync(__dirname + `/../__testfixtures__/${name}`, 'utf-8');
20 |
21 | const transform = (source, options = {}) =>
22 | pureToClass({ source }, api, options);
23 |
24 | test('invalid transforms', () => {
25 | expect(transform('function C(arg1, arg2) {return }')).toBe(
26 | 'function C(arg1, arg2) {return }'
27 | );
28 |
29 | expect(transform('function C(arg1 = defaultArg) {return }')).toBe(
30 | 'function C(arg1 = defaultArg) {return }'
31 | );
32 |
33 | expect(
34 | transform('const obj = {c(arg1 = defaultArg) {return }}')
35 | ).toBe('const obj = {c(arg1 = defaultArg) {return }}');
36 |
37 | expect(transform('class C { someFn(props) {return }}')).toBe(
38 | 'class C { someFn(props) {return }}'
39 | );
40 |
41 | expect(transform('function someFn(a, b) {return (props) => }')).toBe(
42 | 'function someFn(a, b) {return (props) => }'
43 | );
44 | });
45 |
46 | test('typescript transforms', () => {
47 | const inputTS = getFixture('typescript.input.tsx');
48 | const outputTS = getFixture('typescript.output.tsx');
49 |
50 | expect(transform(inputTS, { parser: getParser('tsx') })).toBe(outputTS);
51 | });
52 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class/README.md:
--------------------------------------------------------------------------------
1 | A `jscodeshift` transformer to create react class component from pure functional component. Works both for JavaScript and TypeScript.
2 |
3 | Turns this:
4 |
5 | ```javascript
6 | function MyComponent(props) {
7 | return {props.message}
;
8 | };
9 | ```
10 |
11 | into this:
12 |
13 | ```javascript
14 | class MyComponent extends React.Component {
15 | constructor(props) {
16 | super(props);
17 | }
18 |
19 | render() {
20 | const {
21 | props,
22 | } = this;
23 |
24 | return {props.message}
;
25 | }
26 | }
27 | ```
28 |
29 | It makes some assumptions about functions that can be transformed:
30 |
31 | * function should has zero or one argument (i.e. `props`, though the name me be different)
32 | * the argument, if present, should be an identifier (`foo => {..}`) or object pattern(`({ foo }) => {...}`). This means array patterns (`([foo]) => {...}`) and default function parameters (`(foo = defaultFoo) => {...}`) don't work. `props` is always an object and default props are handled differently in React
33 | * the functions should not appear inside other functions, be property of an objects or method of a class
34 |
35 | See [jscodeshift](https://github.com/facebook/jscodeshift) for more info on transformations.
36 |
37 | Basic manual usage in node (you probably don't need it):
38 |
39 | ```javascript
40 | const jscodeshift = require('jscodeshift');
41 | const pureToClass = require('react-pure-to-class');
42 |
43 | const options = {
44 | reactComponent: 'React.Component',
45 | printOptions: {
46 | quote: 'single',
47 | trailingComma: true,
48 | },
49 | };
50 |
51 | const source = ''; // your source code here;
52 |
53 | const transformedSource = pureToClass(
54 | { source },
55 | { jscodeshift },
56 | options // or empty object for defaults
57 | );
58 | ```
59 |
60 | It also works as a cli, which may be useful in vim to transform selected code, like so:
61 |
62 | ```
63 | :'<,'>!react-pure-to-class
64 | ```
65 |
66 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class-vscode/init.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const jscodeshift = require('jscodeshift');
4 | const getParser = require('jscodeshift/src/getParser');
5 | const pureToClass = require('react-pure-to-class');
6 |
7 | const tsFiles = ['typescript', 'typescriptreact'];
8 | const supportedFiles = ['javascript', 'javascriptreact', ...tsFiles];
9 |
10 | module.exports = vscode => ({
11 | activate(context) {
12 | const disposable = vscode.commands.registerCommand(
13 | 'extension.reactPureToClass',
14 | () => {
15 | const editor = vscode.window.activeTextEditor;
16 | const config = vscode.workspace.getConfiguration('reactPureToClass');
17 |
18 | if (!editor) {
19 | return;
20 | }
21 |
22 | const doc = editor.document;
23 |
24 | if (!supportedFiles.includes(doc.languageId)) {
25 | vscode.window.showInformationMessage(
26 | 'Only available for javascript/typescript/react file types'
27 | );
28 | return;
29 | }
30 |
31 | const parser = tsFiles.includes(doc.languageId) ? 'tsx' : 'babel';
32 |
33 | const selection = editor.selection;
34 | const text = doc.getText(selection);
35 |
36 | let output;
37 |
38 | try {
39 | output = pureToClass(
40 | { source: text },
41 | {
42 | jscodeshift,
43 | stats: () => {},
44 | },
45 | {
46 | parser: getParser(parser),
47 | reactComponent: config.reactComponent,
48 | }
49 | );
50 | } catch (e) {
51 | vscode.window.showInformationMessage(
52 | 'Something went wrong (probably bad selection)'
53 | );
54 | return;
55 | }
56 |
57 | if (output === text) {
58 | vscode.window.showInformationMessage('Nothing to transform');
59 | return;
60 | }
61 |
62 | editor.edit(function(builder) {
63 | builder.replace(selection, output);
64 | });
65 | }
66 | );
67 |
68 | context.subscriptions.push(disposable);
69 | },
70 | });
71 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class/__testfixtures__/arrow-functions.output.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class MyComponent extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | }
7 |
8 | render() {
9 | const {
10 | props,
11 | } = this;
12 |
13 | return {props.message}
;
14 | }
15 | }
16 |
17 | class MyComponent extends React.Component {
18 | constructor(props) {
19 | super(props);
20 | }
21 |
22 | render() {
23 | const {
24 | props,
25 | } = this;
26 |
27 | return {props.message}
;
28 | }
29 | }
30 |
31 | class MyComponent extends React.Component {
32 | constructor(props) {
33 | super(props);
34 | }
35 |
36 | render() {
37 | const p = this.props;
38 | return p.children ? {p.children}
: null;
39 | }
40 | }
41 |
42 | export class MyComponent extends React.Component {
43 | constructor(props) {
44 | super(props);
45 | }
46 |
47 | render() {
48 | const p = this.props;
49 | return p.children ? {p.children}
: null;
50 | }
51 | }
52 |
53 | module.exports = class extends React.Component {
54 | constructor(props) {
55 | super(props);
56 | }
57 |
58 | render() {
59 | const {
60 | props,
61 | } = this;
62 |
63 | return {props.message}
;
64 | }
65 | };
66 |
67 | export default class extends React.Component {
68 | constructor(props) {
69 | super(props);
70 | }
71 |
72 | render() {
73 | const {
74 | props,
75 | } = this;
76 |
77 | return {props.message}
;
78 | }
79 | }
80 |
81 | class NonReact extends React.Component {
82 | constructor(props) {
83 | super(props);
84 | }
85 |
86 | render() {
87 | const v = this.props;
88 | return v.x + v.y;
89 | }
90 | }
91 |
92 | class StillReact extends React.Component {
93 | constructor(props) {
94 | super(props);
95 | }
96 |
97 | render() {
98 | const a = bla-bla
;
99 | return 2 + 2;
100 | }
101 | }
102 |
103 | export class AnotherMyComponent extends React.Component {
104 | constructor(props) {
105 | super(props);
106 | }
107 |
108 | render() {
109 | const { items } = this.props;
110 |
111 | return (
112 |
113 | {items.map(item => - {item}
)}
114 |
115 | );
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class/__testfixtures__/regular-functions.output.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class MyComponent extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | }
7 |
8 | render() {
9 | const {
10 | props,
11 | } = this;
12 |
13 | return {props.message}
;
14 | }
15 | }
16 |
17 | module.exports = class MyComponent extends React.Component {
18 | constructor(props) {
19 | super(props);
20 | }
21 |
22 | render() {
23 | const {
24 | props,
25 | } = this;
26 |
27 | return {props.message}
;
28 | }
29 | };
30 |
31 | export default class MyComponent extends React.Component {
32 | constructor(props) {
33 | super(props);
34 | }
35 |
36 | render() {
37 | const {
38 | props,
39 | } = this;
40 |
41 | return {props.message}
;
42 | }
43 | }
44 |
45 | class MyComponent extends React.Component {
46 | constructor(props) {
47 | super(props);
48 | }
49 |
50 | render() {
51 | return bla-bla
;
52 | }
53 | }
54 |
55 | class MyComponent extends React.Component {
56 | constructor(props) {
57 | super(props);
58 | }
59 |
60 | render() {
61 | const p = this.props;
62 | return p.children ? {children}
: null;
63 | }
64 | }
65 |
66 | class MyComponent extends React.Component {
67 | constructor(props) {
68 | super(props);
69 | }
70 |
71 | render() {
72 | return bla-bla
;
73 | }
74 | }
75 |
76 | const MyComponent = class MyFunction extends React.Component {
77 | constructor(props) {
78 | super(props);
79 | }
80 |
81 | render() {
82 | return bla-bla
;
83 | }
84 | }
85 |
86 | class NonReact extends React.Component {
87 | constructor(props) {
88 | super(props);
89 | }
90 |
91 | render() {
92 | const v = this.props;
93 | return v.x + v.y;
94 | }
95 | }
96 |
97 | class StillReact extends React.Component {
98 | constructor(props) {
99 | super(props);
100 | }
101 |
102 | render() {
103 | const a = bla-bla
;
104 | return 2 + 2;
105 | }
106 | }
107 |
108 | class MyComponent extends React.Component {
109 | constructor(props) {
110 | super(props);
111 | }
112 |
113 | render() {
114 | const { items } = this.props;
115 | return (
116 | {items.map(item => - {item}
)}
117 |
);
118 | }
119 | }
120 |
121 | class MyComponent extends React.Component {
122 | constructor(props) {
123 | super(props);
124 | }
125 |
126 | render() {
127 | const { items } = this.props;
128 | const lis = items.map(item => {item});
129 |
130 | return ();
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/packages/react-pure-to-class/pure-to-class.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(file, api, options) {
4 | const j = api.jscodeshift;
5 |
6 | const reactComponent = options.reactComponent || 'React.Component';
7 |
8 | const printOptions = options.printOptions || {
9 | quote: 'single',
10 | trailingComma: true,
11 | };
12 |
13 | const areValidArguments = args => {
14 | const hasOneArgumentMax = args.length <= 1;
15 | const argumentIsIdentifierOrObjectPattern =
16 | !args[0] || j.Identifier.check(args[0]) || j.ObjectPattern.check(args[0]);
17 |
18 | return hasOneArgumentMax && argumentIsIdentifierOrObjectPattern;
19 | };
20 |
21 | const isInsideJSXOrFunctionOrObjectOrClass = path => {
22 | const jp = j(path);
23 |
24 | return (
25 | jp.closest(j.JSXElement).size() ||
26 | jp.closest(j.FunctionDeclaration).size() ||
27 | jp.closest(j.FunctionExpression).size() ||
28 | jp.closest(j.ArrowFunctionExpression).size() ||
29 | jp.closest(j.ObjectExpression).size() ||
30 | jp.closest(j.ClassBody).size()
31 | );
32 | };
33 |
34 | const canBeReplaced = path => {
35 | const hasValidArguments = areValidArguments(path.value.params);
36 | return hasValidArguments && !isInsideJSXOrFunctionOrObjectOrClass(path);
37 | };
38 |
39 | const createBodyWithReturn = body =>
40 | j.BlockStatement.check(body)
41 | ? body
42 | : j.blockStatement([j.returnStatement(body)]);
43 |
44 | const createPropsDecl = param => {
45 | if (j.ObjectPattern.check(param) || param.name !== 'props') {
46 | return j.variableDeclaration('const', [
47 | j.variableDeclarator(
48 | param,
49 | j.memberExpression(j.thisExpression(), j.identifier('props'))
50 | ),
51 | ]);
52 | }
53 |
54 | const props = j.property('init', j.identifier('props'), param);
55 | props.shorthand = true;
56 |
57 | return j.variableDeclaration('const', [
58 | j.variableDeclarator(j.objectPattern([props]), j.thisExpression()),
59 | ]);
60 | };
61 |
62 | const createConstructor = () =>
63 | j.methodDefinition(
64 | 'constructor',
65 | j.identifier('constructor'),
66 | j.functionExpression(
67 | null,
68 | [j.identifier('props')],
69 | j.blockStatement([
70 | j.expressionStatement(
71 | j.callExpression(j.super(), [j.identifier('props')])
72 | ),
73 | ])
74 | )
75 | );
76 |
77 | const createRenderMethod = body =>
78 | j.methodDefinition(
79 | 'method',
80 | j.identifier('render'),
81 | j.functionExpression(null, [], body)
82 | );
83 |
84 | const createClassComponent = (name, renderBody, propsType) => {
85 | const cls = j.classDeclaration(
86 | name ? j.identifier(name) : null,
87 | j.classBody([createConstructor(), createRenderMethod(renderBody)])
88 | );
89 |
90 | cls.superClass = j.template.expression([reactComponent]);
91 |
92 | if (propsType) {
93 | cls.superTypeParameters = j.tsTypeParameterInstantiation([
94 | propsType.typeAnnotation,
95 | ]);
96 | }
97 |
98 | return cls;
99 | };
100 |
101 | const replaceWithClass = collection =>
102 | collection
103 | .map(path => {
104 | const grandParent = path.parent.parent;
105 | const hasOwnName = path.value.id && path.value.id.name;
106 |
107 | if (
108 | !hasOwnName &&
109 | j.VariableDeclaration.check(grandParent.value) &&
110 | grandParent.value.declarations.length === 1
111 | ) {
112 | return grandParent;
113 | }
114 |
115 | return path;
116 | })
117 | .replaceWith(path => {
118 | const isVarDecl = j.VariableDeclaration.check(path.value);
119 | const fn = isVarDecl ? path.value.declarations[0].init : path.value;
120 |
121 | const name = isVarDecl
122 | ? path.value.declarations[0].id.name
123 | : fn.id && fn.id.name;
124 |
125 | const props = fn.params[0];
126 | const propsType = props && fn.params[0].typeAnnotation;
127 | const body = createBodyWithReturn(fn.body);
128 |
129 | if (props) {
130 | delete props.typeAnnotation;
131 | body.body.unshift(createPropsDecl(props));
132 | }
133 |
134 | return createClassComponent(name, body, propsType);
135 | });
136 |
137 | const root = j(file.source, {
138 | parser: options.parser,
139 | });
140 |
141 | [
142 | root.find(j.FunctionDeclaration).filter(canBeReplaced),
143 | root.find(j.FunctionExpression).filter(canBeReplaced),
144 | root.find(j.ArrowFunctionExpression).filter(canBeReplaced),
145 | ].forEach(replaceWithClass);
146 |
147 | return root.toSource(printOptions);
148 | };
149 |
--------------------------------------------------------------------------------