├── .gitignore
├── .npmignore
├── .prettierignore
├── .prettierrc
├── .travis.yml
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── CODE_OF_CONDUCT.md
├── LICENSE.md
├── README.md
├── package.json
├── src
├── cli.ts
├── compiler.ts
├── helpers
│ ├── build-prop-type-interface.ts
│ └── index.ts
├── index.ts
├── transforms
│ ├── collapse-intersection-interfaces-transform.ts
│ ├── react-js-make-props-and-state-transform.ts
│ ├── react-move-prop-types-to-class-transform.ts
│ ├── react-remove-prop-types-assignment-transform.ts
│ ├── react-remove-prop-types-import.ts
│ ├── react-remove-static-prop-types-member-transform.ts
│ └── react-stateless-function-make-props-transform.ts
└── untyped-modules.d.ts
├── test
├── collapse-intersection-interfaces-transform
│ ├── advanced
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── empty-empty
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── multiple
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── repeated
│ │ ├── input.tsx
│ │ └── output.tsx
│ └── simple
│ │ ├── input.tsx
│ │ └── output.tsx
├── end-to-end
│ ├── basic
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── initial-state-and-proprypes-and-set-state
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── initial-state-and-proprypes
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── multiple-components
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── non-react
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── stateless-arrow-function
│ │ ├── input.tsx
│ │ └── output.tsx
│ └── stateless-function
│ │ ├── input.tsx
│ │ └── output.tsx
├── react-js-make-props-and-state-transform
│ ├── multiple-components
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── non-react
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── propless-stateless
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── set-state-advanced
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── set-state-only
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── state-in-class-member
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── state-in-constructor
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── static-proptypes-getter-simple
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── static-proptypes-many-props
│ │ ├── input.tsx
│ │ └── output.tsx
│ └── static-proptypes-simple
│ │ ├── input.tsx
│ │ └── output.tsx
├── react-move-prop-types-to-class-transform
│ ├── multiple-components
│ │ ├── input.tsx
│ │ └── output.tsx
│ └── simple
│ │ ├── input.tsx
│ │ └── output.tsx
├── react-remove-prop-types-assignment-transform
│ ├── functional-components
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── multiple
│ │ ├── input.tsx
│ │ └── output.tsx
│ └── simple
│ │ ├── input.tsx
│ │ └── output.tsx
├── react-remove-prop-types-import
│ ├── from-prop-types
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── from-react-multi-named-import
│ │ ├── input.tsx
│ │ └── output.tsx
│ └── from-react-simple
│ │ ├── input.tsx
│ │ └── output.tsx
├── react-remove-static-prop-types-member-transform
│ ├── getter
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── multiple-components
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── multiple
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── other-static-members
│ │ ├── input.tsx
│ │ └── output.tsx
│ └── simple
│ │ ├── input.tsx
│ │ └── output.tsx
├── react-stateless-function-make-props-transform
│ ├── empty-prop
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── multiple-components
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── stateless-arrow-function
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── stateless-function-many-props
│ │ ├── input.tsx
│ │ └── output.tsx
│ ├── stateless-function
│ │ ├── input.tsx
│ │ └── output.tsx
│ └── stateless-propless
│ │ ├── input.tsx
│ │ └── output.tsx
└── transformers.test.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .DS_Store
4 | coverage/
5 | *.log
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | src
3 | test
4 | .vscode
5 | package-lock.json
6 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | test/
2 | dist/
3 | !test/transformers.test.ts
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "printWidth": 120,
4 | "tabWidth": 4,
5 | "trailingComma": "all"
6 | }
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js: stable
3 | cache: yarn
4 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "args": [],
6 | "cwd": "${workspaceRoot}",
7 | "env": {
8 | "NODE_ENV": "test"
9 | },
10 | "console": "internalConsole",
11 | "name": "Run Tests",
12 | "outFiles": ["${workspaceRoot}/dist"],
13 | "preLaunchTask": "tsc",
14 | "program": "${workspaceRoot}/node_modules/.bin/jest",
15 | "request": "launch",
16 | "runtimeArgs": [],
17 | "runtimeExecutable": null,
18 | "sourceMaps": true,
19 | "stopOnEntry": false,
20 | "type": "node"
21 | },
22 | {
23 | "name": "Attach",
24 | "type": "node",
25 | "request": "attach",
26 | "port": 5858
27 | }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "**/.git": true,
4 | "**/.svn": true,
5 | "**/.hg": true,
6 | "**/CVS": true,
7 | "**/.DS_Store": true,
8 | "**/node_modules": true,
9 | "**/dist": true
10 | },
11 | "search.exclude": {
12 | "**/node_modules": true,
13 | "**/dist": true
14 | },
15 | "typescript.tsdk": "node_modules/typescript/lib"
16 | }
17 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "0.1.0",
5 | "command": "tsc",
6 | "isShellCommand": true,
7 | "args": ["-p", "."],
8 | "showOutput": "silent",
9 | "problemMatcher": "$tsc"
10 | }
11 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | This project is governed by [Lyft's code of conduct](https://github.com/lyft/code-of-conduct). All contributors and participants agree to abide by its terms.
2 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2014 Lyft, Inc.
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React JavaScript to TypeScript Transform
2 |
3 | [](https://travis-ci.org/lyft/react-javascript-to-typescript-transform)
4 |
5 | Transforms React code written in JavaScript to TypeScript.
6 |
7 | [**🖥 Download the VSCode Extension**](https://marketplace.visualstudio.com/items?itemName=mohsen1.react-javascript-to-typescript-transform-vscode)
8 |
9 | ## Features:
10 |
11 | * Proxies `PropTypes` to `React.Component` generic type and removes PropTypes
12 | * Provides state typing for `React.Component` based on initial state and `setState()` calls in the component
13 | * Hoist large interfaces for props and state out of `React.Component
` into declared types
14 | * Convert functional components with `PropTypes` property to TypeScript and uses propTypes to generate function type declaration
15 |
16 | ## Example
17 |
18 | **input**
19 |
20 | ```jsx
21 | class MyComponent extends React.Component {
22 | static propTypes = {
23 | prop1: React.PropTypes.string.isRequired,
24 | prop2: React.PropTypes.number,
25 | };
26 | constructor() {
27 | super();
28 | this.state = { foo: 1, bar: 'str' };
29 | }
30 | render() {
31 | return (
32 |
33 | {this.state.foo}, {this.state.bar}, {this.state.baz}
34 |
35 | );
36 | }
37 | onClick() {
38 | this.setState({ baz: 3 });
39 | }
40 | }
41 | ```
42 |
43 | **output**
44 |
45 | ```tsx
46 | type MyComponentProps = {
47 | prop1: string;
48 | prop2?: number;
49 | };
50 |
51 | type MyComponentState = {
52 | foo: number;
53 | bar: string;
54 | baz: number;
55 | };
56 |
57 | class MyComponent extends React.Component {
58 | constructor() {
59 | super();
60 | this.state = { foo: 1, bar: 'str' };
61 | }
62 | render() {
63 | return (
64 |
65 | {this.state.foo}, {this.state.bar}, {this.state.baz}
66 |
67 | );
68 | }
69 | onClick() {
70 | this.setState({ baz: 3 });
71 | }
72 | }
73 | ```
74 |
75 | ## Usage
76 |
77 | ### CLI
78 |
79 | ```
80 | npm install -g react-js-to-ts
81 | ```
82 |
83 | ```
84 | react-js-to-ts my-react-js-file.js
85 | ```
86 |
87 | ### VSCode plugin
88 |
89 | details
90 | [Download from VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=mohsen1.react-javascript-to-typescript-transform-vscode#overview)
91 |
92 | ## Development
93 |
94 | ### Tests
95 |
96 | Tests are organized in `test` folder. For each transform there is a folder that contains folders for each test case. Each test case has `input.tsx` and `output.tsx`.
97 |
98 | ```
99 | npm test
100 | ```
101 |
102 | #### Watch mode
103 |
104 | Pass `-w` to `npm test`
105 |
106 | ```
107 | npm test -- -w
108 | ```
109 |
110 | #### Only a single test case
111 |
112 | Pass `-t` with transform name and case name space separated to `npm test`
113 |
114 | ```
115 | npm test -- -t "react-js-make-props-and-state-transform propless-stateless"
116 | ```
117 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-js-to-ts",
3 | "version": "1.4.0",
4 | "description": "Convert React code from JavaScript to TypeScript",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "pretest": "npm run build",
8 | "test": "jest",
9 | "coverage": "jest --coverage",
10 | "posttest": "npm run lint",
11 | "prelint": "npm run clean",
12 | "lint": "tslint --project tsconfig.json --format codeFrame --exclude test/**/*.tsx",
13 | "prepublish": "npm run build",
14 | "clean": "rm -rf dist",
15 | "prebuild": "npm run clean",
16 | "build": "tsc --pretty",
17 | "precommit": "lint-staged",
18 | "prettier": "prettier --write *.{js,json,css,md,ts,tsx}"
19 | },
20 | "jest": {
21 | "transform": {
22 | ".ts": "/node_modules/ts-jest/preprocessor.js"
23 | },
24 | "testRegex": "(/tests/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
25 | "moduleFileExtensions": ["ts", "js"]
26 | },
27 | "lint-staged": {
28 | "*.{js,json,css,md,ts,tsx}": ["node_modules/.bin/prettier --write", "git add"]
29 | },
30 | "bin": "dist/cli.js",
31 | "author": "Mohsen Azimi ",
32 | "license": "Apache-2.0",
33 | "dependencies": {
34 | "chalk": "^2.4.1",
35 | "commander": "^2.15.1",
36 | "detect-indent": "^5.0.0",
37 | "glob": "^7.1.2",
38 | "lodash": "^4.17.10",
39 | "prettier": "^1.12.1",
40 | "typescript": "2.8.3"
41 | },
42 | "devDependencies": {
43 | "@types/chalk": "^2.2.0",
44 | "@types/commander": "^2.9.1",
45 | "@types/detect-indent": "^5.0.0",
46 | "@types/glob": "^5.0.35",
47 | "@types/jest": "^22.2.3",
48 | "@types/lodash": "^4.14.109",
49 | "@types/node": "^10.1.2",
50 | "@types/prettier": "^1.12.2",
51 | "@types/react": "^16.3.14",
52 | "dedent": "^0.7.0",
53 | "husky": "^0.14.3",
54 | "jest": "^22.4.4",
55 | "lint-staged": "^7.1.1",
56 | "ts-jest": "^22.4.6",
57 | "ts-node": "^6.0.3",
58 | "tslint": "^5.10.0"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/cli.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import * as program from 'commander';
4 | import * as glob from 'glob';
5 | import * as fs from 'fs';
6 | import * as path from 'path';
7 | import * as prettier from 'prettier';
8 |
9 | import { run } from '.';
10 | import { CompilationOptions } from './compiler';
11 |
12 | function resolveGlobs(globPatterns: string[]): string[] {
13 | const files: string[] = [];
14 | function addFile(file: string) {
15 | file = path.resolve(file);
16 | if (files.indexOf(file) === -1) {
17 | files.push(file);
18 | }
19 | }
20 | globPatterns.forEach(pattern => {
21 | if (/[{}*?+\[\]]/.test(pattern)) {
22 | // Smells like globs
23 | glob.sync(pattern, {}).forEach(file => {
24 | addFile(file);
25 | });
26 | } else {
27 | addFile(pattern);
28 | }
29 | });
30 | return files;
31 | }
32 |
33 | program
34 | .version('1.0.0')
35 | .option('--arrow-parens ', 'Include parentheses around a sole arrow function parameter.', 'avoid')
36 | .option('--no-bracket-spacing', 'Do not print spaces between brackets.', false)
37 | .option('--jsx-bracket-same-line', 'Put > on the last line instead of at a new line.', false)
38 | .option('--print-width ', 'The line length where Prettier will try wrap.', 80)
39 | .option('--prose-wrap How to wrap prose. (markdown)', 'preserve')
40 | .option('--no-semi', 'Do not print semicolons, except at the beginning of lines which may need them', false)
41 | .option('--single-quote', 'Use single quotes instead of double quotes.', false)
42 | .option('--tab-width ', 'Number of spaces per indentation level.', 2)
43 | .option('--trailing-comma ', 'Print trailing commas wherever possible when multi-line.', 'none')
44 | .option('--use-tabs', 'Indent with tabs instead of spaces.', false)
45 | .option('--ignore-prettier-errors', 'Ignore (but warn about) errors in Prettier', false)
46 | .option('--keep-original-files', 'Keep original files', false)
47 | .option('--keep-temporary-files', 'Keep temporary files', false)
48 | .usage('[options] ')
49 | .command('* [glob/filename...]')
50 | .action((globPatterns: string[]) => {
51 | const prettierOptions: prettier.Options = {
52 | arrowParens: program.arrowParens,
53 | bracketSpacing: !program.noBracketSpacing,
54 | jsxBracketSameLine: !!program.jsxBracketSameLine,
55 | printWidth: parseInt(program.printWidth, 10),
56 | proseWrap: program.proseWrap,
57 | semi: !program.noSemi,
58 | singleQuote: !!program.singleQuote,
59 | tabWidth: parseInt(program.tabWidth, 10),
60 | trailingComma: program.trailingComma,
61 | useTabs: !!program.useTabs,
62 | };
63 | const compilationOptions: CompilationOptions = {
64 | ignorePrettierErrors: !!program.ignorePrettierErrors,
65 | };
66 | const files = resolveGlobs(globPatterns);
67 | if (!files.length) {
68 | throw new Error('Nothing to do. You must provide file names or glob patterns to transform.');
69 | }
70 | let errors = false;
71 | for (const filePath of files) {
72 | console.log(`Transforming ${filePath}...`);
73 | const newPath = filePath.replace(/\.jsx?$/, '.tsx');
74 | const temporaryPath = filePath.replace(/\.jsx?$/, `_js2ts_${+new Date()}.tsx`);
75 | try {
76 | fs.copyFileSync(filePath, temporaryPath);
77 | const result = run(temporaryPath, prettierOptions, compilationOptions);
78 | fs.writeFileSync(newPath, result);
79 | if (!program.keepOriginalFiles) {
80 | fs.unlinkSync(filePath);
81 | }
82 | } catch (error) {
83 | console.warn(`Failed to convert ${filePath}`);
84 | console.warn(error);
85 | errors = true;
86 | }
87 | if (!program.keepTemporaryFiles) {
88 | if (fs.existsSync(temporaryPath)) {
89 | fs.unlinkSync(temporaryPath);
90 | }
91 | }
92 | }
93 | if (errors) {
94 | process.exit(1);
95 | }
96 | });
97 |
98 | program.parse(process.argv);
99 |
--------------------------------------------------------------------------------
/src/compiler.ts:
--------------------------------------------------------------------------------
1 | import * as os from 'os';
2 | import * as fs from 'fs';
3 | import * as ts from 'typescript';
4 | import chalk from 'chalk';
5 | import * as _ from 'lodash';
6 | import * as prettier from 'prettier';
7 | import * as detectIndent from 'detect-indent';
8 |
9 | import { TransformFactoryFactory } from '.';
10 |
11 | export interface CompilationOptions {
12 | ignorePrettierErrors: boolean;
13 | }
14 |
15 | const DEFAULT_COMPILATION_OPTIONS: CompilationOptions = {
16 | ignorePrettierErrors: false,
17 | };
18 |
19 | export { DEFAULT_COMPILATION_OPTIONS };
20 |
21 | /**
22 | * Compile and return result TypeScript
23 | * @param filePath Path to file to compile
24 | */
25 | export function compile(
26 | filePath: string,
27 | factoryFactories: TransformFactoryFactory[],
28 | incomingPrettierOptions: prettier.Options = {},
29 | compilationOptions: CompilationOptions = DEFAULT_COMPILATION_OPTIONS,
30 | ) {
31 | const compilerOptions: ts.CompilerOptions = {
32 | target: ts.ScriptTarget.ES2017,
33 | module: ts.ModuleKind.ES2015,
34 | };
35 |
36 | const program = ts.createProgram([filePath], compilerOptions);
37 | // `program.getSourceFiles()` will include those imported files,
38 | // like: `import * as a from './file-a'`.
39 | // We should only transform current file.
40 | const sourceFiles = program.getSourceFiles().filter(sf => sf.fileName === filePath);
41 | const typeChecker = program.getTypeChecker();
42 |
43 | const result = ts.transform(
44 | sourceFiles,
45 | factoryFactories.map(factoryFactory => factoryFactory(typeChecker), compilerOptions),
46 | );
47 |
48 | if (result.diagnostics && result.diagnostics.length) {
49 | console.log(
50 | chalk.yellow(`
51 | ======================= Diagnostics for ${filePath} =======================
52 | `),
53 | );
54 | for (const diag of result.diagnostics) {
55 | if (diag.file && diag.start) {
56 | const pos = diag.file.getLineAndCharacterOfPosition(diag.start);
57 | console.log(`(${pos.line}, ${pos.character}) ${diag.messageText}`);
58 | }
59 | }
60 | }
61 |
62 | const printer = ts.createPrinter();
63 |
64 | // TODO: fix the index 0 access... What if program have multiple source files?
65 | const printed = printer.printNode(ts.EmitHint.SourceFile, result.transformed[0], sourceFiles[0]);
66 |
67 | const inputSource = fs.readFileSync(filePath, 'utf-8');
68 | const prettierOptions = getPrettierOptions(filePath, inputSource, incomingPrettierOptions);
69 |
70 | try {
71 | return prettier.format(printed, prettierOptions);
72 | } catch (prettierError) {
73 | if (compilationOptions.ignorePrettierErrors) {
74 | console.warn(`Prettier failed for ${filePath} (ignorePrettierErrors is on):`);
75 | console.warn(prettierError);
76 | return printed;
77 | }
78 | throw prettierError;
79 | }
80 | }
81 |
82 | /**
83 | * Get Prettier options based on style of a JavaScript
84 | * @param filePath Path to source file
85 | * @param source Body of a JavaScript
86 | * @param options Existing prettier option
87 | */
88 | export function getPrettierOptions(filePath: string, source: string, options: prettier.Options): prettier.Options {
89 | const resolvedOptions = prettier.resolveConfig.sync(filePath);
90 | if (resolvedOptions) {
91 | _.defaults(resolvedOptions, options);
92 | return resolvedOptions;
93 | }
94 | const { amount: indentAmount, type: indentType } = detectIndent(source);
95 | const sourceWidth = getCodeWidth(source, 80);
96 | const semi = getUseOfSemi(source);
97 | const quotations = getQuotation(source);
98 |
99 | _.defaults(Object.assign({}, options), {
100 | tabWidth: indentAmount,
101 | useTabs: indentType && indentType === 'tab',
102 | printWidth: sourceWidth,
103 | semi,
104 | singleQuote: quotations === 'single',
105 | });
106 |
107 | return options;
108 | }
109 |
110 | /**
111 | * Given body of a source file, return its code width
112 | * @param source
113 | */
114 | function getCodeWidth(source: string, defaultWidth: number): number {
115 | return source.split(os.EOL).reduce((result, line) => Math.max(result, line.length), defaultWidth);
116 | }
117 |
118 | /**
119 | * Detect if a source file is using semicolon
120 | * @todo: use an actual parser. This is not a proper implementation
121 | * @param source
122 | * @return true if code is using semicolons
123 | */
124 | function getUseOfSemi(source: string): boolean {
125 | return source.indexOf(';') !== -1;
126 | }
127 |
128 | /**
129 | * Detect if a source file is using single quotes or double quotes
130 | * @todo use an actual parser. This is not a proper implementation
131 | * @param source
132 | */
133 | function getQuotation(source: string): 'single' | 'double' {
134 | const numberOfSingleQuotes = (source.match(/\'/g) || []).length;
135 | const numberOfDoubleQuotes = (source.match(/\"/g) || []).length;
136 | if (numberOfSingleQuotes > numberOfDoubleQuotes) {
137 | return 'single';
138 | }
139 | return 'double';
140 | }
141 |
--------------------------------------------------------------------------------
/src/helpers/build-prop-type-interface.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 |
3 | /**
4 | * Build props interface from propTypes object
5 | * @example
6 | * {
7 | * foo: React.PropTypes.string.isRequired
8 | * }
9 | *
10 | * becomes
11 | * {
12 | * foo: string;
13 | * }
14 | * @param objectLiteral
15 | */
16 | export function buildInterfaceFromPropTypeObjectLiteral(objectLiteral: ts.ObjectLiteralExpression) {
17 | const members = objectLiteral.properties
18 | // We only need to process PropertyAssignment:
19 | // {
20 | // a: 123 // PropertyAssignment
21 | // }
22 | //
23 | // filter out:
24 | // {
25 | // a() {}, // MethodDeclaration
26 | // b, // ShorthandPropertyAssignment
27 | // ...c, // SpreadAssignment
28 | // get d() {}, // AccessorDeclaration
29 | // }
30 | .filter(ts.isPropertyAssignment)
31 | // Ignore children, React types have it
32 | .filter(property => property.name.getText() !== 'children')
33 | .map(propertyAssignment => {
34 | const name = propertyAssignment.name.getText();
35 | const initializer = propertyAssignment.initializer;
36 | const isRequired = isPropTypeRequired(initializer);
37 | const typeExpression = isRequired
38 | ? // We have guaranteed the type in `isPropTypeRequired()`
39 | (initializer as ts.PropertyAccessExpression).expression
40 | : initializer;
41 | const typeValue = getTypeFromReactPropTypeExpression(typeExpression);
42 |
43 | return ts.createPropertySignature(
44 | [],
45 | name,
46 | isRequired ? undefined : ts.createToken(ts.SyntaxKind.QuestionToken),
47 | typeValue,
48 | undefined,
49 | );
50 | });
51 |
52 | return ts.createTypeLiteralNode(members);
53 | }
54 |
55 | /**
56 | * Turns React.PropTypes.* into TypeScript type value
57 | *
58 | * @param node React propTypes value
59 | */
60 | function getTypeFromReactPropTypeExpression(node: ts.Expression): ts.TypeNode {
61 | let result = null;
62 | if (ts.isPropertyAccessExpression(node)) {
63 | /**
64 | * PropTypes.array,
65 | * PropTypes.bool,
66 | * PropTypes.func,
67 | * PropTypes.number,
68 | * PropTypes.object,
69 | * PropTypes.string,
70 | * PropTypes.symbol, (ignore)
71 | * PropTypes.node,
72 | * PropTypes.element,
73 | * PropTypes.any,
74 | */
75 | const text = node.getText().replace(/React\.PropTypes\./, '');
76 |
77 | if (/string/.test(text)) {
78 | result = ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
79 | } else if (/any/.test(text)) {
80 | result = ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
81 | } else if (/array/.test(text)) {
82 | result = ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
83 | } else if (/bool/.test(text)) {
84 | result = ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
85 | } else if (/number/.test(text)) {
86 | result = ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
87 | } else if (/object/.test(text)) {
88 | result = ts.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
89 | } else if (/node/.test(text)) {
90 | result = ts.createTypeReferenceNode('React.ReactNode', []);
91 | } else if (/element/.test(text)) {
92 | result = ts.createTypeReferenceNode('JSX.Element', []);
93 | } else if (/func/.test(text)) {
94 | const arrayOfAny = ts.createParameter(
95 | [],
96 | [],
97 | ts.createToken(ts.SyntaxKind.DotDotDotToken),
98 | 'args',
99 | undefined,
100 | ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)),
101 | undefined,
102 | );
103 | result = ts.createFunctionTypeNode([], [arrayOfAny], ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
104 | }
105 | } else if (ts.isCallExpression(node)) {
106 | /**
107 | * PropTypes.instanceOf(), (ignore)
108 | * PropTypes.oneOf(), // only support oneOf([1, 2]), oneOf(['a', 'b'])
109 | * PropTypes.oneOfType(),
110 | * PropTypes.arrayOf(),
111 | * PropTypes.objectOf(),
112 | * PropTypes.shape(),
113 | */
114 | const text = node.expression.getText();
115 | if (/oneOf$/.test(text)) {
116 | const argument = node.arguments[0];
117 | if (ts.isArrayLiteralExpression(argument)) {
118 | if (argument.elements.every(elm => ts.isStringLiteral(elm) || ts.isNumericLiteral(elm))) {
119 | result = ts.createUnionTypeNode(
120 | (argument.elements as ts.NodeArray).map(elm =>
121 | ts.createLiteralTypeNode(elm),
122 | ),
123 | );
124 | }
125 | }
126 | } else if (/oneOfType$/.test(text)) {
127 | const argument = node.arguments[0];
128 | if (ts.isArrayLiteralExpression(argument)) {
129 | result = ts.createUnionOrIntersectionTypeNode(
130 | ts.SyntaxKind.UnionType,
131 | argument.elements.map(elm => getTypeFromReactPropTypeExpression(elm)),
132 | );
133 | }
134 | } else if (/arrayOf$/.test(text)) {
135 | const argument = node.arguments[0];
136 | if (argument) {
137 | result = ts.createArrayTypeNode(getTypeFromReactPropTypeExpression(argument));
138 | }
139 | } else if (/objectOf$/.test(text)) {
140 | const argument = node.arguments[0];
141 | if (argument) {
142 | result = ts.createTypeLiteralNode([
143 | ts.createIndexSignature(
144 | undefined,
145 | undefined,
146 | [
147 | ts.createParameter(
148 | undefined,
149 | undefined,
150 | undefined,
151 | 'key',
152 | undefined,
153 | ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
154 | ),
155 | ],
156 | getTypeFromReactPropTypeExpression(argument),
157 | ),
158 | ]);
159 | }
160 | } else if (/shape$/.test(text)) {
161 | const argument = node.arguments[0];
162 | if (ts.isObjectLiteralExpression(argument)) {
163 | return buildInterfaceFromPropTypeObjectLiteral(argument);
164 | }
165 | }
166 | }
167 |
168 | /**
169 | * customProp,
170 | * anything others
171 | */
172 | if (result === null) {
173 | result = ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
174 | }
175 |
176 | return result;
177 | }
178 |
179 | /**
180 | * Decide if node is required
181 | * @param node React propTypes member node
182 | */
183 | function isPropTypeRequired(node: ts.Expression) {
184 | if (!ts.isPropertyAccessExpression(node)) return false;
185 |
186 | const text = node.getText().replace(/React\.PropTypes\./, '');
187 | return /\.isRequired/.test(text);
188 | }
189 |
--------------------------------------------------------------------------------
/src/helpers/index.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import * as _ from 'lodash';
3 |
4 | export * from './build-prop-type-interface';
5 |
6 | /**
7 | * If a class declaration a react class?
8 | * @param classDeclaration
9 | * @param typeChecker
10 | */
11 | export function isReactComponent(classDeclaration: ts.ClassDeclaration, typeChecker: ts.TypeChecker): boolean {
12 | // Only classes that extend React.Component
13 | if (!classDeclaration.heritageClauses) {
14 | return false;
15 | }
16 | if (classDeclaration.heritageClauses.length !== 1) {
17 | return false;
18 | }
19 |
20 | const firstHeritageClauses = classDeclaration.heritageClauses[0];
21 |
22 | if (firstHeritageClauses.token !== ts.SyntaxKind.ExtendsKeyword) {
23 | return false;
24 | }
25 |
26 | const expressionWithTypeArguments = firstHeritageClauses.types[0];
27 |
28 | if (!expressionWithTypeArguments) {
29 | return false;
30 | }
31 |
32 | // Try type checker and fallback to node text
33 | const type = typeChecker.getTypeAtLocation(expressionWithTypeArguments);
34 | let typeSymbol = type && type.symbol && type.symbol.name;
35 | if (!typeSymbol) {
36 | typeSymbol = expressionWithTypeArguments.expression.getText();
37 | }
38 |
39 | if (!/React\.Component|Component/.test(typeSymbol)) {
40 | return false;
41 | }
42 |
43 | return true;
44 | }
45 |
46 | /**
47 | * Determine if a ts.HeritageClause is React HeritageClause
48 | *
49 | * @example `extends React.Component<{}, {}>` is a React HeritageClause
50 | *
51 | * @todo: this is lazy. Use the typeChecker instead
52 | * @param clause
53 | */
54 | export function isReactHeritageClause(clause: ts.HeritageClause) {
55 | return (
56 | clause.token === ts.SyntaxKind.ExtendsKeyword &&
57 | clause.types.length === 1 &&
58 | ts.isExpressionWithTypeArguments(clause.types[0]) &&
59 | /Component/.test(clause.types[0].expression.getText())
60 | );
61 | }
62 |
63 | /**
64 | * Return true if a statement is a React propType assignment statement
65 | * @example
66 | * SomeComponent.propTypes = { foo: React.PropTypes.string };
67 | * @param statement
68 | */
69 | export function isReactPropTypeAssignmentStatement(statement: ts.Statement): statement is ts.ExpressionStatement {
70 | return (
71 | ts.isExpressionStatement(statement) &&
72 | ts.isBinaryExpression(statement.expression) &&
73 | statement.expression.operatorToken.kind === ts.SyntaxKind.FirstAssignment &&
74 | ts.isPropertyAccessExpression(statement.expression.left) &&
75 | /\.propTypes$|\.propTypes\..+$/.test(statement.expression.left.getText())
76 | );
77 | }
78 |
79 | /**
80 | * Does class member have a "static" member?
81 | * @param classMember
82 | */
83 | export function hasStaticModifier(classMember: ts.ClassElement) {
84 | if (!classMember.modifiers) {
85 | return false;
86 | }
87 | const staticModifier = _.find(classMember.modifiers, modifier => {
88 | return modifier.kind == ts.SyntaxKind.StaticKeyword;
89 | });
90 | return staticModifier !== undefined;
91 | }
92 |
93 | /**
94 | * Is class member a React "propTypes" member?
95 | * @param classMember
96 | * @param sourceFile
97 | */
98 | export function isPropTypesMember(classMember: ts.ClassElement, sourceFile: ts.SourceFile) {
99 | try {
100 | const name =
101 | classMember.name !== undefined && ts.isIdentifier(classMember.name) ? classMember.name.escapedText : null;
102 | return name === 'propTypes';
103 | } catch (e) {
104 | return false;
105 | }
106 | }
107 |
108 | /**
109 | * Get component name off of a propType assignment statement
110 | * @param propTypeAssignment
111 | * @param sourceFile
112 | */
113 | export function getComponentName(propTypeAssignment: ts.Statement, sourceFile: ts.SourceFile) {
114 | const text = propTypeAssignment.getText(sourceFile);
115 | return text.substr(0, text.indexOf('.'));
116 | }
117 |
118 | /**
119 | * Convert react stateless function to arrow function
120 | * @example
121 | * Before:
122 | * function Hello(message) {
123 | * return {message}
124 | * }
125 | *
126 | * After:
127 | * const Hello = message => {
128 | * return {message}
129 | * }
130 | */
131 | export function convertReactStatelessFunctionToArrowFunction(
132 | statelessFunc: ts.FunctionDeclaration | ts.VariableStatement,
133 | ) {
134 | if (ts.isVariableStatement(statelessFunc)) return statelessFunc;
135 |
136 | const funcName = statelessFunc.name || 'Component';
137 | const funcBody = statelessFunc.body || ts.createBlock([]);
138 |
139 | const initializer = ts.createArrowFunction(
140 | undefined,
141 | undefined,
142 | statelessFunc.parameters,
143 | undefined,
144 | undefined,
145 | funcBody,
146 | );
147 |
148 | return ts.createVariableStatement(
149 | statelessFunc.modifiers,
150 | ts.createVariableDeclarationList(
151 | [ts.createVariableDeclaration(funcName, undefined, initializer)],
152 | ts.NodeFlags.Const,
153 | ),
154 | );
155 | }
156 |
157 | /**
158 | * Insert an item in middle of an array after a specific item
159 | * @param collection
160 | * @param afterItem
161 | * @param newItem
162 | */
163 | export function insertAfter(collection: ArrayLike, afterItem: T, newItem: T) {
164 | const index = _.indexOf(collection, afterItem) + 1;
165 |
166 | return _.slice(collection, 0, index)
167 | .concat(newItem)
168 | .concat(_.slice(collection, index));
169 | }
170 |
171 | /**
172 | * Insert an item in middle of an array before a specific item
173 | * @param collection
174 | * @param beforeItem
175 | * @param newItem
176 | */
177 | export function insertBefore(collection: ArrayLike, beforeItem: T, newItems: T | T[]) {
178 | const index = _.indexOf(collection, beforeItem);
179 |
180 | return _.slice(collection, 0, index)
181 | .concat(newItems)
182 | .concat(_.slice(collection, index));
183 | }
184 |
185 | /**
186 | * Replace an item in a collection with another item
187 | * @param collection
188 | * @param item
189 | * @param newItem
190 | */
191 | export function replaceItem(collection: ArrayLike, item: T, newItem: T) {
192 | const index = _.indexOf(collection, item);
193 | return _.slice(collection, 0, index)
194 | .concat(newItem)
195 | .concat(_.slice(collection, index + 1));
196 | }
197 |
198 | /**
199 | * Remove an item from a collection
200 | * @param collection
201 | * @param item
202 | * @param newItem
203 | */
204 | export function removeItem(collection: ArrayLike, item: T) {
205 | const index = _.indexOf(collection, item);
206 | return _.slice(collection, 0, index).concat(_.slice(collection, index + 1));
207 | }
208 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import * as prettier from 'prettier';
3 |
4 | import { compile, CompilationOptions, DEFAULT_COMPILATION_OPTIONS } from './compiler';
5 | import { reactJSMakePropsAndStateInterfaceTransformFactoryFactory } from './transforms/react-js-make-props-and-state-transform';
6 | import { reactRemovePropTypesAssignmentTransformFactoryFactory } from './transforms/react-remove-prop-types-assignment-transform';
7 | import { reactMovePropTypesToClassTransformFactoryFactory } from './transforms/react-move-prop-types-to-class-transform';
8 | import { collapseIntersectionInterfacesTransformFactoryFactory } from './transforms/collapse-intersection-interfaces-transform';
9 | import { reactRemoveStaticPropTypesMemberTransformFactoryFactory } from './transforms/react-remove-static-prop-types-member-transform';
10 | import { reactStatelessFunctionMakePropsTransformFactoryFactory } from './transforms/react-stateless-function-make-props-transform';
11 | import { reactRemovePropTypesImportTransformFactoryFactory } from './transforms/react-remove-prop-types-import';
12 |
13 | export {
14 | reactMovePropTypesToClassTransformFactoryFactory,
15 | reactJSMakePropsAndStateInterfaceTransformFactoryFactory,
16 | reactStatelessFunctionMakePropsTransformFactoryFactory,
17 | collapseIntersectionInterfacesTransformFactoryFactory,
18 | reactRemovePropTypesAssignmentTransformFactoryFactory,
19 | reactRemoveStaticPropTypesMemberTransformFactoryFactory,
20 | reactRemovePropTypesImportTransformFactoryFactory,
21 | compile,
22 | };
23 |
24 | export const allTransforms = [
25 | reactMovePropTypesToClassTransformFactoryFactory,
26 | reactJSMakePropsAndStateInterfaceTransformFactoryFactory,
27 | reactStatelessFunctionMakePropsTransformFactoryFactory,
28 | collapseIntersectionInterfacesTransformFactoryFactory,
29 | reactRemovePropTypesAssignmentTransformFactoryFactory,
30 | reactRemoveStaticPropTypesMemberTransformFactoryFactory,
31 | reactRemovePropTypesImportTransformFactoryFactory,
32 | ];
33 |
34 | export type TransformFactoryFactory = (typeChecker: ts.TypeChecker) => ts.TransformerFactory;
35 |
36 | /**
37 | * Run React JavaScript to TypeScript transform for file at `filePath`
38 | * @param filePath
39 | */
40 | export function run(
41 | filePath: string,
42 | prettierOptions: prettier.Options = {},
43 | compilationOptions: CompilationOptions = DEFAULT_COMPILATION_OPTIONS,
44 | ): string {
45 | return compile(filePath, allTransforms, prettierOptions, compilationOptions);
46 | }
47 |
--------------------------------------------------------------------------------
/src/transforms/collapse-intersection-interfaces-transform.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import * as _ from 'lodash';
3 |
4 | import * as helpers from '../helpers';
5 |
6 | /**
7 | * Collapse unnecessary intersections between type literals
8 | *
9 | * @example
10 | * Before:
11 | * type Foo = {foo: string;} & {bar: number;}
12 | *
13 | * After
14 | * type Foo = {foo: string; bar: number;}
15 | */
16 | export function collapseIntersectionInterfacesTransformFactoryFactory(
17 | typeChecker: ts.TypeChecker,
18 | ): ts.TransformerFactory {
19 | return function collapseIntersectionInterfacesTransformFactory(context: ts.TransformationContext) {
20 | return function collapseIntersectionInterfacesTransform(sourceFile: ts.SourceFile) {
21 | const visited = ts.visitEachChild(sourceFile, visitor, context);
22 | ts.addEmitHelpers(visited, context.readEmitHelpers());
23 |
24 | return visited;
25 |
26 | function visitor(node: ts.Node) {
27 | if (ts.isTypeAliasDeclaration(node)) {
28 | return visitTypeAliasDeclaration(node);
29 | }
30 |
31 | return node;
32 | }
33 |
34 | function visitTypeAliasDeclaration(node: ts.TypeAliasDeclaration) {
35 | if (ts.isIntersectionTypeNode(node.type)) {
36 | return ts.createTypeAliasDeclaration(
37 | [],
38 | [],
39 | node.name.text,
40 | [],
41 | visitIntersectionTypeNode(node.type),
42 | );
43 | }
44 |
45 | return node;
46 | }
47 |
48 | function visitIntersectionTypeNode(node: ts.IntersectionTypeNode) {
49 | // Only intersection of type literals can be colapsed.
50 | // We are currently ignoring intersections such as `{foo: string} & {bar: string} & TypeRef`
51 | // TODO: handle mix of type references and multiple literal types
52 | if (!node.types.every(typeNode => ts.isTypeLiteralNode(typeNode))) {
53 | return node;
54 | }
55 |
56 | // We need cast `node.type.types` to `ts.NodeArray`
57 | // because TypeScript can't figure out `node.type.types.every(ts.isTypeLiteralNode)`
58 | const types = node.types as ts.NodeArray;
59 |
60 | // Build a map of member names to all of types found in intersectioning type literals
61 | // For instance {foo: string, bar: number} & { foo: number } will result in a map like this:
62 | // Map {
63 | // 'foo' => Set { 'string', 'number' },
64 | // 'bar' => Set { 'number' }
65 | // }
66 | const membersMap = new Map>();
67 |
68 | // A sepecial member of type literal nodes is index signitures which don't have a name
69 | // We use this symbol to track it in our members map
70 | const INDEX_SIGNITUTRE_MEMBER = Symbol('Index signiture member');
71 |
72 | // Keep a reference of first index signiture member parameters. (ignore rest)
73 | let indexMemberParameter: ts.NodeArray | null = null;
74 |
75 | // Iterate through all of type literal nodes members and add them to the members map
76 | types.forEach(typeNode => {
77 | typeNode.members.forEach(member => {
78 | if (ts.isIndexSignatureDeclaration(member)) {
79 | if (member.type !== undefined) {
80 | if (membersMap.has(INDEX_SIGNITUTRE_MEMBER)) {
81 | membersMap.get(INDEX_SIGNITUTRE_MEMBER)!.add(member.type);
82 | } else {
83 | indexMemberParameter = member.parameters;
84 | membersMap.set(INDEX_SIGNITUTRE_MEMBER, new Set([member.type]));
85 | }
86 | }
87 | } else if (ts.isPropertySignature(member)) {
88 | if (member.type !== undefined) {
89 | let memberName = member.name.getText(sourceFile);
90 |
91 | // For unknown reasons, member.name.getText() is returning nothing in some cases
92 | // This is probably because previous transformers did something with the AST that
93 | // index of text string of member identifier is lost
94 | // TODO: investigate
95 | if (!memberName) {
96 | memberName = (member.name as any).escapedText;
97 | }
98 |
99 | if (membersMap.has(memberName)) {
100 | membersMap.get(memberName)!.add(member.type);
101 | } else {
102 | membersMap.set(memberName, new Set([member.type]));
103 | }
104 | }
105 | }
106 | });
107 | });
108 |
109 | // Result type literal members list
110 | const finalMembers: Array = [];
111 |
112 | // Put together the map into a type literal that has member per each map entery and type of that
113 | // member is a union of all types in vlues for that member name in members map
114 | // if a member has only one type, create a simple type literal for it
115 | for (const [name, types] of membersMap.entries()) {
116 | if (typeof name === 'symbol') {
117 | continue;
118 | }
119 | // if for this name there is only one type found use the first type, otherwise make a union of all types
120 | let resultType = types.size === 1 ? Array.from(types)[0] : createUnionType(Array.from(types));
121 |
122 | finalMembers.push(ts.createPropertySignature([], name, undefined, resultType, undefined));
123 | }
124 |
125 | // Handle index signiture member
126 | if (membersMap.has(INDEX_SIGNITUTRE_MEMBER)) {
127 | const indexTypes = Array.from(membersMap.get(INDEX_SIGNITUTRE_MEMBER)!);
128 | let indexType = indexTypes[0];
129 | if (indexTypes.length > 1) {
130 | indexType = createUnionType(indexTypes);
131 | }
132 | const indexSigniture = ts.createIndexSignature([], [], indexMemberParameter!, indexType);
133 | finalMembers.push(indexSigniture);
134 | }
135 |
136 | // Generate one single type literal node
137 | return ts.createTypeLiteralNode(finalMembers);
138 | }
139 |
140 | /**
141 | * Create a union type from multiple type nodes
142 | * @param types
143 | */
144 | function createUnionType(types: ts.TypeNode[]) {
145 | // first dedupe literal types
146 | // TODO: this only works if all types are primitive types like string or number
147 | const uniqueTypes = _.uniqBy(types, type => type.kind);
148 | return ts.createUnionOrIntersectionTypeNode(ts.SyntaxKind.UnionType, uniqueTypes);
149 | }
150 | };
151 | };
152 | }
153 |
--------------------------------------------------------------------------------
/src/transforms/react-js-make-props-and-state-transform.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import * as _ from 'lodash';
3 | import * as helpers from '../helpers';
4 |
5 | export type Factory = ts.TransformerFactory;
6 |
7 | /**
8 | * Get transform for transforming React code originally written in JS which does not have
9 | * props and state generic types
10 | * This transform will remove React component static "propTypes" member during transform
11 | */
12 | export function reactJSMakePropsAndStateInterfaceTransformFactoryFactory(typeChecker: ts.TypeChecker): Factory {
13 | return function reactJSMakePropsAndStateInterfaceTransformFactory(context: ts.TransformationContext) {
14 | return function reactJSMakePropsAndStateInterfaceTransform(sourceFile: ts.SourceFile) {
15 | const visited = visitSourceFile(sourceFile, typeChecker);
16 | ts.addEmitHelpers(visited, context.readEmitHelpers());
17 |
18 | return visited;
19 | };
20 | };
21 | }
22 |
23 | function visitSourceFile(sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker) {
24 | let newSourceFile = sourceFile;
25 | for (const statement of sourceFile.statements) {
26 | if (ts.isClassDeclaration(statement) && helpers.isReactComponent(statement, typeChecker)) {
27 | newSourceFile = visitReactClassDeclaration(statement, newSourceFile, typeChecker);
28 | }
29 | }
30 |
31 | return newSourceFile;
32 | }
33 |
34 | function visitReactClassDeclaration(
35 | classDeclaration: ts.ClassDeclaration,
36 | sourceFile: ts.SourceFile,
37 | typeChecker: ts.TypeChecker,
38 | ) {
39 | if (!classDeclaration.heritageClauses || !classDeclaration.heritageClauses.length) {
40 | return sourceFile;
41 | }
42 | const className = classDeclaration && classDeclaration.name && classDeclaration.name.getText(sourceFile);
43 | const propType = getPropsTypeOfReactComponentClass(classDeclaration, sourceFile);
44 | const stateType = getStateTypeOfReactComponentClass(classDeclaration, typeChecker);
45 | const shouldMakePropTypeDeclaration = propType.members.length > 0;
46 | const shouldMakeStateTypeDeclaration = !isStateTypeMemberEmpty(stateType);
47 | const propTypeName = `${className}Props`;
48 | const stateTypeName = `${className}State`;
49 | const propTypeDeclaration = ts.createTypeAliasDeclaration([], [], propTypeName, [], propType);
50 | const stateTypeDeclaration = ts.createTypeAliasDeclaration([], [], stateTypeName, [], stateType);
51 | const propTypeRef = ts.createTypeReferenceNode(propTypeName, []);
52 | const stateTypeRef = ts.createTypeReferenceNode(stateTypeName, []);
53 |
54 | const newClassDeclaration = getNewReactClassDeclaration(
55 | classDeclaration,
56 | shouldMakePropTypeDeclaration ? propTypeRef : propType,
57 | shouldMakeStateTypeDeclaration ? stateTypeRef : stateType,
58 | );
59 |
60 | const allTypeDeclarations = [];
61 | if (shouldMakePropTypeDeclaration) allTypeDeclarations.push(propTypeDeclaration);
62 | if (shouldMakeStateTypeDeclaration) allTypeDeclarations.push(stateTypeDeclaration);
63 |
64 | let statements = helpers.insertBefore(sourceFile.statements, classDeclaration, allTypeDeclarations);
65 | statements = helpers.replaceItem(statements, classDeclaration, newClassDeclaration);
66 | return ts.updateSourceFileNode(sourceFile, statements);
67 | }
68 |
69 | function getNewReactClassDeclaration(
70 | classDeclaration: ts.ClassDeclaration,
71 | propTypeRef: ts.TypeNode,
72 | stateTypeRef: ts.TypeNode,
73 | ) {
74 | if (!classDeclaration.heritageClauses || !classDeclaration.heritageClauses.length) {
75 | return classDeclaration;
76 | }
77 |
78 | const firstHeritageClause = classDeclaration.heritageClauses[0];
79 |
80 | const newFirstHeritageClauseTypes = helpers.replaceItem(
81 | firstHeritageClause.types,
82 | firstHeritageClause.types[0],
83 | ts.updateExpressionWithTypeArguments(
84 | firstHeritageClause.types[0],
85 | [propTypeRef, stateTypeRef],
86 | firstHeritageClause.types[0].expression,
87 | ),
88 | );
89 |
90 | const newHeritageClauses = helpers.replaceItem(
91 | classDeclaration.heritageClauses,
92 | firstHeritageClause,
93 | ts.updateHeritageClause(firstHeritageClause, newFirstHeritageClauseTypes),
94 | );
95 |
96 | return ts.updateClassDeclaration(
97 | classDeclaration,
98 | classDeclaration.decorators,
99 | classDeclaration.modifiers,
100 | classDeclaration.name,
101 | classDeclaration.typeParameters,
102 | newHeritageClauses,
103 | classDeclaration.members,
104 | );
105 | }
106 |
107 | function getPropsTypeOfReactComponentClass(
108 | classDeclaration: ts.ClassDeclaration,
109 | sourceFile: ts.SourceFile,
110 | ): ts.TypeLiteralNode {
111 | const staticPropTypesMember = _.find(classDeclaration.members, member => {
112 | return (
113 | ts.isPropertyDeclaration(member) &&
114 | helpers.hasStaticModifier(member) &&
115 | helpers.isPropTypesMember(member, sourceFile)
116 | );
117 | });
118 |
119 | if (
120 | staticPropTypesMember !== undefined &&
121 | ts.isPropertyDeclaration(staticPropTypesMember) && // check to satisfy type checker
122 | staticPropTypesMember.initializer &&
123 | ts.isObjectLiteralExpression(staticPropTypesMember.initializer)
124 | ) {
125 | return helpers.buildInterfaceFromPropTypeObjectLiteral(staticPropTypesMember.initializer);
126 | }
127 |
128 | const staticPropTypesGetterMember = _.find(classDeclaration.members, member => {
129 | return (
130 | ts.isGetAccessorDeclaration(member) &&
131 | helpers.hasStaticModifier(member) &&
132 | helpers.isPropTypesMember(member, sourceFile)
133 | );
134 | });
135 |
136 | if (
137 | staticPropTypesGetterMember !== undefined &&
138 | ts.isGetAccessorDeclaration(staticPropTypesGetterMember) // check to satisfy typechecker
139 | ) {
140 | const returnStatement = _.find(staticPropTypesGetterMember.body!.statements, statement =>
141 | ts.isReturnStatement(statement),
142 | );
143 | if (
144 | returnStatement !== undefined &&
145 | ts.isReturnStatement(returnStatement) && // check to satisfy typechecker
146 | returnStatement.expression &&
147 | ts.isObjectLiteralExpression(returnStatement.expression)
148 | ) {
149 | return helpers.buildInterfaceFromPropTypeObjectLiteral(returnStatement.expression);
150 | }
151 | }
152 |
153 | return ts.createTypeLiteralNode([]);
154 | }
155 |
156 | function getStateTypeOfReactComponentClass(
157 | classDeclaration: ts.ClassDeclaration,
158 | typeChecker: ts.TypeChecker,
159 | ): ts.TypeNode {
160 | const initialState = getInitialStateFromClassDeclaration(classDeclaration, typeChecker);
161 | const initialStateIsVoid = initialState.kind === ts.SyntaxKind.VoidKeyword;
162 | const collectedStateTypes = getStateLookingForSetStateCalls(classDeclaration, typeChecker);
163 | if (!collectedStateTypes.length && initialStateIsVoid) {
164 | return ts.createTypeLiteralNode([]);
165 | }
166 | if (!initialStateIsVoid) {
167 | collectedStateTypes.push(initialState);
168 | }
169 |
170 | return ts.createUnionOrIntersectionTypeNode(ts.SyntaxKind.IntersectionType, collectedStateTypes);
171 | }
172 |
173 | /**
174 | * Get initial state of a React component looking for state value initially set
175 | * @param classDeclaration
176 | * @param typeChecker
177 | */
178 | function getInitialStateFromClassDeclaration(
179 | classDeclaration: ts.ClassDeclaration,
180 | typeChecker: ts.TypeChecker,
181 | ): ts.TypeNode {
182 | // initial state class member
183 |
184 | const initialStateMember = _.find(classDeclaration.members, member => {
185 | try {
186 | return ts.isPropertyDeclaration(member) && member.name && member.name.getText() === 'state';
187 | } catch (e) {
188 | return false;
189 | }
190 | });
191 |
192 | if (initialStateMember && ts.isPropertyDeclaration(initialStateMember) && initialStateMember.initializer) {
193 | const type = typeChecker.getTypeAtLocation(initialStateMember.initializer)!;
194 |
195 | return typeChecker.typeToTypeNode(type);
196 | }
197 |
198 | // Initial state in constructor
199 | const constructor = _.find(classDeclaration.members, member => member.kind === ts.SyntaxKind.Constructor) as
200 | | ts.ConstructorDeclaration
201 | | undefined;
202 |
203 | if (constructor && constructor.body) {
204 | for (const statement of constructor.body.statements) {
205 | if (
206 | ts.isExpressionStatement(statement) &&
207 | ts.isBinaryExpression(statement.expression) &&
208 | statement.expression.left.getText() === 'this.state'
209 | ) {
210 | return typeChecker.typeToTypeNode(typeChecker.getTypeAtLocation(statement.expression.right));
211 | }
212 | }
213 | }
214 |
215 | // No initial state, fall back to void
216 | return ts.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
217 | }
218 |
219 | /**
220 | * Look for setState() function calls to collect the state interface in a React class component
221 | * @param classDeclaration
222 | * @param typeChecker
223 | */
224 | function getStateLookingForSetStateCalls(
225 | classDeclaration: ts.ClassDeclaration,
226 | typeChecker: ts.TypeChecker,
227 | ): ts.TypeNode[] {
228 | const typeNodes: ts.TypeNode[] = [];
229 | for (const member of classDeclaration.members) {
230 | if (member && ts.isMethodDeclaration(member) && member.body) {
231 | lookForSetState(member.body);
232 | }
233 | }
234 |
235 | return typeNodes;
236 |
237 | function lookForSetState(node: ts.Node) {
238 | ts.forEachChild(node, lookForSetState);
239 | if (
240 | ts.isExpressionStatement(node) &&
241 | ts.isCallExpression(node.expression) &&
242 | node.expression.expression.getText().match(/setState/)
243 | ) {
244 | const type = typeChecker.getTypeAtLocation(node.expression.arguments[0]);
245 | typeNodes.push(typeChecker.typeToTypeNode(type));
246 | }
247 | }
248 | }
249 |
250 | function isStateTypeMemberEmpty(stateType: ts.TypeNode): boolean {
251 | // Only need to handle TypeLiteralNode & IntersectionTypeNode
252 | if (ts.isTypeLiteralNode(stateType)) {
253 | return stateType.members.length === 0;
254 | }
255 |
256 | if (!ts.isIntersectionTypeNode(stateType)) {
257 | return true;
258 | }
259 |
260 | return stateType.types.every(isStateTypeMemberEmpty);
261 | }
262 |
--------------------------------------------------------------------------------
/src/transforms/react-move-prop-types-to-class-transform.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import * as _ from 'lodash';
3 |
4 | import * as helpers from '../helpers';
5 |
6 | export type Factory = ts.TransformerFactory;
7 |
8 | /**
9 | * Move Component.propTypes statements into class as a static member of the class
10 | * if React component is defined using a class
11 | *
12 | * Note: This transform assumes React component declaration and propTypes assignment statement
13 | * are both on root of the source file
14 | *
15 | * @example
16 | * Before:
17 | * class SomeComponent extends React.Component<{foo: number;}, {bar: string;}> {}
18 | * SomeComponent.propTypes = { foo: React.PropTypes.string }
19 | *
20 | * After
21 | * class SomeComponent extends React.Component<{foo: number;}, {bar: string;}> {
22 | * static propTypes = { foo: React.PropTypes.string }
23 | * }
24 | *
25 | * @todo
26 | * This is not supporting multiple statements for a single class yet
27 | * ```
28 | * class SomeComponent extends React.Component<{foo: number;}, {bar: string;}> {}
29 | * SomeComponent.propTypes = { foo: React.PropTypes.string }
30 | * SomeComponent.propTypes.bar = React.PropTypes.number;
31 | * ```
32 | */
33 | export function reactMovePropTypesToClassTransformFactoryFactory(typeChecker: ts.TypeChecker): Factory {
34 | return function reactMovePropTypesToClassTransformFactory(context: ts.TransformationContext) {
35 | return function reactMovePropTypesToClassTransform(sourceFile: ts.SourceFile) {
36 | const visited = visitSourceFile(sourceFile, typeChecker);
37 | ts.addEmitHelpers(visited, context.readEmitHelpers());
38 | return visited;
39 | };
40 | };
41 | }
42 |
43 | /**
44 | * Make the move from propType statement to static member
45 | * @param sourceFile
46 | * @param typeChecker
47 | */
48 | function visitSourceFile(sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker) {
49 | let statements = sourceFile.statements;
50 |
51 | // Look for propType assignment statements
52 | const propTypeAssignments = statements.filter(statement =>
53 | helpers.isReactPropTypeAssignmentStatement(statement),
54 | ) as ts.ExpressionStatement[];
55 |
56 | for (const propTypeAssignment of propTypeAssignments) {
57 | // Look for the class declarations with the same name
58 | const componentName = helpers.getComponentName(propTypeAssignment, sourceFile);
59 |
60 | const classStatement = (_.find(
61 | statements,
62 | statement =>
63 | ts.isClassDeclaration(statement) &&
64 | statement.name !== undefined &&
65 | statement.name.getText(sourceFile) === componentName,
66 | ) as {}) as ts.ClassDeclaration; // Type weirdness
67 |
68 | // && helpers.isBinaryExpression(propTypeAssignment.expression) is redundant to satisfy the type checker
69 | if (classStatement && ts.isBinaryExpression(propTypeAssignment.expression)) {
70 | const newClassStatement = addStaticMemberToClass(
71 | classStatement,
72 | 'propTypes',
73 | propTypeAssignment.expression.right,
74 | );
75 | statements = ts.createNodeArray(helpers.replaceItem(statements, classStatement, newClassStatement));
76 | }
77 | }
78 |
79 | return ts.updateSourceFileNode(sourceFile, statements);
80 | }
81 |
82 | /**
83 | * Insert a new static member into a class
84 | * @param classDeclaration
85 | * @param name
86 | * @param value
87 | */
88 | function addStaticMemberToClass(classDeclaration: ts.ClassDeclaration, name: string, value: ts.Expression) {
89 | const staticModifier = ts.createToken(ts.SyntaxKind.StaticKeyword);
90 | const propertyDeclaration = ts.createProperty([], [staticModifier], name, undefined, undefined, value);
91 | return ts.updateClassDeclaration(
92 | classDeclaration,
93 | classDeclaration.decorators,
94 | classDeclaration.modifiers,
95 | classDeclaration.name,
96 | classDeclaration.typeParameters,
97 | ts.createNodeArray(classDeclaration.heritageClauses),
98 | ts.createNodeArray([propertyDeclaration, ...classDeclaration.members]),
99 | );
100 | }
101 |
--------------------------------------------------------------------------------
/src/transforms/react-remove-prop-types-assignment-transform.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 |
3 | import * as helpers from '../helpers';
4 |
5 | export type Factory = ts.TransformerFactory;
6 |
7 | /**
8 | * Remove Component.propTypes statements
9 | *
10 | * @example
11 | * Before:
12 | * class SomeComponent extends React.Component<{foo: number;}, {bar: string;}> {}
13 | * SomeComponent.propTypes = { foo: React.PropTypes.string }
14 | *
15 | * After
16 | * class SomeComponent extends React.Component<{foo: number;}, {bar: string;}> {}
17 | */
18 | export function reactRemovePropTypesAssignmentTransformFactoryFactory(typeChecker: ts.TypeChecker): Factory {
19 | return function reactRemovePropTypesAssignmentTransformFactory(context: ts.TransformationContext) {
20 | return function reactRemovePropTypesAssignmentTransform(sourceFile: ts.SourceFile) {
21 | const visited = ts.updateSourceFileNode(
22 | sourceFile,
23 | sourceFile.statements.filter(s => !helpers.isReactPropTypeAssignmentStatement(s)),
24 | );
25 | ts.addEmitHelpers(visited, context.readEmitHelpers());
26 | return visited;
27 | };
28 | };
29 | }
30 |
--------------------------------------------------------------------------------
/src/transforms/react-remove-prop-types-import.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import * as _ from 'lodash';
3 |
4 | import * as helpers from '../helpers';
5 |
6 | export type Factory = ts.TransformerFactory;
7 |
8 | /**
9 | * Remove `import PropTypes from 'prop-types'` or
10 | * `import { PropTypes } from 'react'`
11 | *
12 | * @example
13 | * Before:
14 | * import PropTypes from 'prop-types'
15 | * import React, { PropTypes } from 'react'
16 | *
17 | * After:
18 | * import React from 'react'
19 | */
20 | export function reactRemovePropTypesImportTransformFactoryFactory(typeChecker: ts.TypeChecker): Factory {
21 | return function reactRemovePropTypesImportTransformFactory(context: ts.TransformationContext) {
22 | return function reactRemovePropTypesImportTransform(sourceFile: ts.SourceFile) {
23 | const visited = ts.updateSourceFileNode(
24 | sourceFile,
25 | sourceFile.statements
26 | .filter(s => {
27 | return !(
28 | ts.isImportDeclaration(s) &&
29 | ts.isStringLiteral(s.moduleSpecifier) &&
30 | s.moduleSpecifier.text === 'prop-types'
31 | );
32 | })
33 | .map(updateReactImportIfNeeded),
34 | );
35 | ts.addEmitHelpers(visited, context.readEmitHelpers());
36 | return visited;
37 | };
38 | };
39 | }
40 |
41 | function updateReactImportIfNeeded(statement: ts.Statement) {
42 | if (
43 | !ts.isImportDeclaration(statement) ||
44 | !ts.isStringLiteral(statement.moduleSpecifier) ||
45 | statement.moduleSpecifier.text !== 'react' ||
46 | !statement.importClause ||
47 | !statement.importClause.namedBindings ||
48 | !ts.isNamedImports(statement.importClause.namedBindings)
49 | ) {
50 | return statement;
51 | }
52 |
53 | const namedBindings = statement.importClause.namedBindings;
54 | const newNamedBindingElements = namedBindings.elements.filter(elm => elm.name.text !== 'PropTypes');
55 |
56 | if (newNamedBindingElements.length === namedBindings.elements.length) {
57 | // Means it has no 'PropTypes' named import
58 | return statement;
59 | }
60 |
61 | const newImportClause = ts.updateImportClause(
62 | statement.importClause,
63 | statement.importClause.name,
64 | newNamedBindingElements.length === 0
65 | ? undefined
66 | : ts.updateNamedImports(namedBindings, newNamedBindingElements),
67 | );
68 |
69 | return ts.updateImportDeclaration(
70 | statement,
71 | statement.decorators,
72 | statement.modifiers,
73 | newImportClause,
74 | statement.moduleSpecifier,
75 | );
76 | }
77 |
--------------------------------------------------------------------------------
/src/transforms/react-remove-static-prop-types-member-transform.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 |
3 | import * as helpers from '../helpers';
4 |
5 | export type Factory = ts.TransformerFactory;
6 |
7 | /**
8 | * Remove static propTypes
9 | *
10 | * @example
11 | * Before:
12 | * class SomeComponent extends React.Component<{foo: number;}, {bar: string;}> {
13 | * static propTypes = {
14 | * foo: React.PropTypes.number.isRequired,
15 | * }
16 | * }
17 | *
18 | * After:
19 | * class SomeComponent extends React.Component<{foo: number;}, {bar: string;}> {}
20 | */
21 | export function reactRemoveStaticPropTypesMemberTransformFactoryFactory(typeChecker: ts.TypeChecker): Factory {
22 | return function reactRemoveStaticPropTypesMemberTransformFactory(context: ts.TransformationContext) {
23 | return function reactRemoveStaticPropTypesMemberTransform(sourceFile: ts.SourceFile) {
24 | const visited = ts.visitEachChild(sourceFile, visitor, context);
25 | ts.addEmitHelpers(visited, context.readEmitHelpers());
26 | return visited;
27 |
28 | function visitor(node: ts.Node) {
29 | if (ts.isClassDeclaration(node) && helpers.isReactComponent(node, typeChecker)) {
30 | return ts.updateClassDeclaration(
31 | node,
32 | node.decorators,
33 | node.modifiers,
34 | node.name,
35 | node.typeParameters,
36 | ts.createNodeArray(node.heritageClauses),
37 | node.members.filter(member => {
38 | if (
39 | ts.isPropertyDeclaration(member) &&
40 | helpers.hasStaticModifier(member) &&
41 | helpers.isPropTypesMember(member, sourceFile)
42 | ) {
43 | return false;
44 | }
45 |
46 | // propTypes getter
47 | if (
48 | ts.isGetAccessorDeclaration(member) &&
49 | helpers.hasStaticModifier(member) &&
50 | helpers.isPropTypesMember(member, sourceFile)
51 | ) {
52 | return false;
53 | }
54 | return true;
55 | }),
56 | );
57 | }
58 | return node;
59 | }
60 | };
61 | };
62 | }
63 |
--------------------------------------------------------------------------------
/src/transforms/react-stateless-function-make-props-transform.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import * as _ from 'lodash';
3 |
4 | import * as helpers from '../helpers';
5 |
6 | export type Factory = ts.TransformerFactory;
7 |
8 | /**
9 | * Transform react stateless components
10 | *
11 | * @example
12 | * Before:
13 | * const Hello = ({ message }) => {
14 | * return hello {message}
15 | * }
16 | * // Or:
17 | * // const Hello = ({ message }) => hello {message}
18 | *
19 | * Hello.propTypes = {
20 | * message: React.PropTypes.string,
21 | * }
22 | *
23 | * After:
24 | * Type HelloProps = {
25 | * message: string;
26 | * }
27 | *
28 | * const Hello: React.SFC = ({ message }) => {
29 | * return hello {message}
30 | * }
31 | *
32 | * Hello.propTypes = {
33 | * message: React.PropTypes.string,
34 | * }
35 | */
36 | export function reactStatelessFunctionMakePropsTransformFactoryFactory(typeChecker: ts.TypeChecker): Factory {
37 | return function reactStatelessFunctionMakePropsTransformFactory(context: ts.TransformationContext) {
38 | return function reactStatelessFunctionMakePropsTransform(sourceFile: ts.SourceFile) {
39 | const visited = visitSourceFile(sourceFile, typeChecker);
40 | ts.addEmitHelpers(visited, context.readEmitHelpers());
41 | return visited;
42 | };
43 | };
44 | }
45 |
46 | function visitSourceFile(sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker) {
47 | // Look for propType assignment statements
48 | const propTypeAssignments = sourceFile.statements.filter(statement =>
49 | helpers.isReactPropTypeAssignmentStatement(statement),
50 | ) as ts.ExpressionStatement[];
51 |
52 | let newSourceFile = sourceFile;
53 | for (const propTypeAssignment of propTypeAssignments) {
54 | const componentName = helpers.getComponentName(propTypeAssignment, newSourceFile);
55 |
56 | const funcComponent = (_.find(newSourceFile.statements, s => {
57 | return (
58 | (ts.isFunctionDeclaration(s) && s.name !== undefined && s.name.getText() === componentName) ||
59 | (ts.isVariableStatement(s) && s.declarationList.declarations[0].name.getText() === componentName)
60 | );
61 | }) as {}) as ts.FunctionDeclaration | ts.VariableStatement; // Type weirdness
62 |
63 | if (funcComponent) {
64 | newSourceFile = visitReactStatelessComponent(funcComponent, propTypeAssignment, newSourceFile);
65 | }
66 | }
67 |
68 | return newSourceFile;
69 | }
70 |
71 | function visitReactStatelessComponent(
72 | component: ts.FunctionDeclaration | ts.VariableStatement,
73 | propTypesExpressionStatement: ts.ExpressionStatement,
74 | sourceFile: ts.SourceFile,
75 | ) {
76 | let arrowFuncComponent = helpers.convertReactStatelessFunctionToArrowFunction(component);
77 | let componentName = arrowFuncComponent.declarationList.declarations[0].name.getText();
78 | let componentInitializer = arrowFuncComponent.declarationList.declarations[0].initializer;
79 |
80 | const propType = getPropTypesFromTypeAssignment(propTypesExpressionStatement);
81 | const shouldMakePropTypeDeclaration = propType.members.length > 0;
82 | const propTypeName = `${componentName}Props`;
83 | const propTypeDeclaration = ts.createTypeAliasDeclaration([], [], propTypeName, [], propType);
84 | const propTypeRef = ts.createTypeReferenceNode(propTypeName, []);
85 |
86 | let componentType = ts.createTypeReferenceNode(ts.createQualifiedName(ts.createIdentifier('React'), 'SFC'), [
87 | shouldMakePropTypeDeclaration ? propTypeRef : propType,
88 | ]);
89 |
90 | // replace component with ts stateless component
91 | const typedComponent = ts.createVariableStatement(
92 | arrowFuncComponent.modifiers,
93 | ts.createVariableDeclarationList(
94 | [ts.createVariableDeclaration(componentName, componentType, componentInitializer)],
95 | arrowFuncComponent.declarationList.flags,
96 | ),
97 | );
98 |
99 | let statements = shouldMakePropTypeDeclaration
100 | ? helpers.insertBefore(sourceFile.statements, component, [propTypeDeclaration])
101 | : sourceFile.statements;
102 |
103 | statements = helpers.replaceItem(statements, component, typedComponent);
104 | return ts.updateSourceFileNode(sourceFile, statements);
105 | }
106 |
107 | function getPropTypesFromTypeAssignment(propTypesExpressionStatement: ts.ExpressionStatement) {
108 | if (
109 | propTypesExpressionStatement !== undefined &&
110 | ts.isBinaryExpression(propTypesExpressionStatement.expression) &&
111 | ts.isObjectLiteralExpression(propTypesExpressionStatement.expression.right)
112 | ) {
113 | return helpers.buildInterfaceFromPropTypeObjectLiteral(propTypesExpressionStatement.expression.right);
114 | }
115 |
116 | return ts.createTypeLiteralNode([]);
117 | }
118 |
--------------------------------------------------------------------------------
/src/untyped-modules.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'dedent';
2 |
--------------------------------------------------------------------------------
/test/collapse-intersection-interfaces-transform/advanced/input.tsx:
--------------------------------------------------------------------------------
1 | type Foo = {
2 | foo: string;
3 | stuff: boolean;
4 | other: () => void;
5 | } & {
6 | bar: number;
7 | [key: string]: number;
8 | };
9 |
--------------------------------------------------------------------------------
/test/collapse-intersection-interfaces-transform/advanced/output.tsx:
--------------------------------------------------------------------------------
1 | type Foo = {
2 | foo: string,
3 | stuff: boolean,
4 | other: () => void,
5 | bar: number,
6 | [key: string]: number,
7 | };
8 |
--------------------------------------------------------------------------------
/test/collapse-intersection-interfaces-transform/empty-empty/input.tsx:
--------------------------------------------------------------------------------
1 | type Foo = {} & {};
--------------------------------------------------------------------------------
/test/collapse-intersection-interfaces-transform/empty-empty/output.tsx:
--------------------------------------------------------------------------------
1 | type Foo = {};
2 |
--------------------------------------------------------------------------------
/test/collapse-intersection-interfaces-transform/multiple/input.tsx:
--------------------------------------------------------------------------------
1 | type Foo = {foo: string} & {bar: number};
2 |
3 | type Bar = {foo: number} & {bar: string};
4 |
--------------------------------------------------------------------------------
/test/collapse-intersection-interfaces-transform/multiple/output.tsx:
--------------------------------------------------------------------------------
1 | type Foo = {
2 | foo: string,
3 | bar: number,
4 | };
5 | type Bar = {
6 | foo: number,
7 | bar: string,
8 | };
9 |
--------------------------------------------------------------------------------
/test/collapse-intersection-interfaces-transform/repeated/input.tsx:
--------------------------------------------------------------------------------
1 | type A = { foo: string; } & { foo: string; };
2 |
3 | type B = { foo: string; bar: number; } & { foo: number; bar: number; }
4 |
5 | type C = { foo: string; bar: number; } & { foo: number; bar: number; } & { foo: string; }
6 |
--------------------------------------------------------------------------------
/test/collapse-intersection-interfaces-transform/repeated/output.tsx:
--------------------------------------------------------------------------------
1 | type A = {
2 | foo: string,
3 | };
4 |
5 | type B = {
6 | foo: string | number,
7 | bar: number,
8 | };
9 |
10 | type C = {
11 | foo: string | number,
12 | bar: number,
13 | };
14 |
--------------------------------------------------------------------------------
/test/collapse-intersection-interfaces-transform/simple/input.tsx:
--------------------------------------------------------------------------------
1 | type Foo = {foo: string} & {bar: number;};
--------------------------------------------------------------------------------
/test/collapse-intersection-interfaces-transform/simple/output.tsx:
--------------------------------------------------------------------------------
1 | type Foo = {
2 | foo: string,
3 | bar: number,
4 | };
5 |
--------------------------------------------------------------------------------
/test/end-to-end/basic/input.tsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import * as React from 'react';
3 |
4 | export default class MyComponent extends React.Component {
5 | render() {
6 | return
;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/test/end-to-end/basic/output.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | export default class MyComponent extends React.Component<{}, {}> {
3 | render() {
4 | return
;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/test/end-to-end/initial-state-and-proprypes-and-set-state/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default class MyComponent extends React.Component {
4 | state = {foo: 1, bar: 'str'};
5 | render() {
6 | return
;
7 | }
8 | otherFn() {
9 | this.setState({dynamicState: 42});
10 | }
11 | }
12 |
13 | MyComponent.propTypes = {
14 | baz: React.PropTypes.string.isRequired,
15 | }
16 |
--------------------------------------------------------------------------------
/test/end-to-end/initial-state-and-proprypes-and-set-state/output.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | type MyComponentProps = {
3 | baz: string,
4 | };
5 | type MyComponentState = {
6 | dynamicState: number,
7 | foo: number,
8 | bar: string,
9 | };
10 | export default class MyComponent extends React.Component {
11 | state = { foo: 1, bar: 'str' };
12 | render() {
13 | return
;
14 | }
15 | otherFn() {
16 | this.setState({ dynamicState: 42 });
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/test/end-to-end/initial-state-and-proprypes/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default class MyComponent extends React.Component {
4 | state = {foo: 1, bar: 'str'};
5 | render() {
6 | return
;
7 | }
8 | }
9 |
10 | MyComponent.propTypes = {
11 | baz: React.PropTypes.string.isRequired;
12 | }
13 |
--------------------------------------------------------------------------------
/test/end-to-end/initial-state-and-proprypes/output.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | type MyComponentProps = {
3 | baz: string,
4 | };
5 | type MyComponentState = {
6 | foo: number,
7 | bar: string,
8 | };
9 | export default class MyComponent extends React.Component {
10 | state = { foo: 1, bar: 'str' };
11 | render() {
12 | return
;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/test/end-to-end/multiple-components/input.tsx:
--------------------------------------------------------------------------------
1 | const Hello = ({ message }) => {
2 | return hello {message}
3 | };
4 |
5 | const Hey = ({ name }) => {
6 | return hey, {name}
7 | }
8 |
9 | Hey.propTypes = {
10 | message: React.PropTypes.string,
11 | }
12 |
13 | Hello.propTypes = {
14 | message: React.PropTypes.string,
15 | }
16 |
17 | export default class MyComponent extends React.Component {
18 | render() {
19 | return ;
20 | }
21 |
22 | onclick() {
23 | this.setState({foo: 1, bar: 2})
24 | }
25 | }
26 |
27 | export class AnotherComponent extends React.Component {
28 | static propTypes = {
29 | foo: React.PropTypes.string.isRequired,
30 | };
31 | render() {
32 | return
;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/test/end-to-end/multiple-components/output.tsx:
--------------------------------------------------------------------------------
1 | type HelloProps = {
2 | message?: string,
3 | };
4 | const Hello: React.SFC = ({ message }) => {
5 | return hello {message}
;
6 | };
7 | type HeyProps = {
8 | message?: string,
9 | };
10 | const Hey: React.SFC = ({ name }) => {
11 | return hey, {name}
;
12 | };
13 | type MyComponentState = {
14 | foo: number,
15 | bar: number,
16 | };
17 | export default class MyComponent extends React.Component<{}, MyComponentState> {
18 | render() {
19 | return ;
20 | }
21 | onclick() {
22 | this.setState({ foo: 1, bar: 2 });
23 | }
24 | }
25 | type AnotherComponentProps = {
26 | foo: string,
27 | };
28 | export class AnotherComponent extends React.Component {
29 | render() {
30 | return
;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/test/end-to-end/non-react/input.tsx:
--------------------------------------------------------------------------------
1 | class Foo {
2 | render() {
3 | return '100'
4 | }
5 | }
6 |
7 | class Bar extends Foo {
8 | baz() {
9 |
10 | }
11 | }
--------------------------------------------------------------------------------
/test/end-to-end/non-react/output.tsx:
--------------------------------------------------------------------------------
1 | class Foo {
2 | render() {
3 | return '100';
4 | }
5 | }
6 | class Bar extends Foo {
7 | baz() {}
8 | }
9 |
--------------------------------------------------------------------------------
/test/end-to-end/stateless-arrow-function/input.tsx:
--------------------------------------------------------------------------------
1 | const Hello = ({ message }) => {
2 | return hello {message}
3 | };
4 |
5 | Hello.propTypes = {
6 | message: React.PropTypes.string,
7 | }
8 |
--------------------------------------------------------------------------------
/test/end-to-end/stateless-arrow-function/output.tsx:
--------------------------------------------------------------------------------
1 | type HelloProps = {
2 | message?: string,
3 | };
4 | const Hello: React.SFC = ({ message }) => {
5 | return hello {message}
;
6 | };
7 |
--------------------------------------------------------------------------------
/test/end-to-end/stateless-function/input.tsx:
--------------------------------------------------------------------------------
1 | export function Hello({ message }) {
2 | return hello {message}
3 | }
4 |
5 | Hello.propTypes = {
6 | message: React.PropTypes.string,
7 | }
8 |
--------------------------------------------------------------------------------
/test/end-to-end/stateless-function/output.tsx:
--------------------------------------------------------------------------------
1 | type HelloProps = {
2 | message?: string,
3 | };
4 | export const Hello: React.SFC = ({ message }) => {
5 | return hello {message}
;
6 | };
7 |
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/multiple-components/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default class MyComponent extends React.Component {
4 | render() {
5 | return ;
6 | }
7 |
8 | onclick() {
9 | this.setState({foo: 1, bar: 2})
10 | }
11 | }
12 |
13 | export class AnotherComponent extends React.Component {
14 | static propTypes = {
15 | foo: React.PropTypes.string.isRequired,
16 | };
17 | render() {
18 | return
;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/multiple-components/output.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | type MyComponentState = { foo: number, bar: number };
3 | export default class MyComponent extends React.Component<{}, MyComponentState> {
4 | render() {
5 | return ;
6 | }
7 | onclick() {
8 | this.setState({ foo: 1, bar: 2 });
9 | }
10 | }
11 | type AnotherComponentProps = {
12 | foo: string,
13 | };
14 | export class AnotherComponent extends React.Component {
15 | static propTypes = {
16 | foo: React.PropTypes.string.isRequired,
17 | };
18 | render() {
19 | return
;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/non-react/input.tsx:
--------------------------------------------------------------------------------
1 | interface IFoo {
2 |
3 | }
4 | class Foo {
5 |
6 | }
7 |
8 | class Bar extends Foo {
9 |
10 | }
11 |
12 | class Foo2 implements IFoo {
13 |
14 | }
15 |
16 | class Bar2 extends Foo implements IFoo {
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/non-react/output.tsx:
--------------------------------------------------------------------------------
1 | interface IFoo {}
2 | class Foo {}
3 | class Bar extends Foo {}
4 | class Foo2 implements IFoo {}
5 | class Bar2 extends Foo implements IFoo {}
6 |
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/propless-stateless/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default class MyComponent extends React.Component {
4 | render() {
5 | return
;
6 | }
7 | }
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/propless-stateless/output.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | export default class MyComponent extends React.Component<{}, {}> {
3 | render() {
4 | return
;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/set-state-advanced/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default class MyComponent extends React.Component {
4 | render() {
5 | return ;
6 | }
7 |
8 | onclick() {
9 | if (Math.random() > 0.5) {
10 | this.setState({foo: 1, bar: 2})
11 | }
12 | this.otherMethod()
13 | }
14 |
15 | otherMethod() {
16 | for (const foo of [1,2,3]) {
17 | if (foo > 2) {
18 | this.setState({baz: foo})
19 | }
20 | }
21 | }
22 |
23 | addLargeObjectToState() {
24 | this.setState({
25 | something: {
26 | big: 123,
27 | here: 'string',
28 | of: [{a: 1}, {a: 2}]
29 | }
30 | })
31 | }
32 | }
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/set-state-advanced/output.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | type MyComponentState = { foo: number, bar: number } & { baz: number } & {
3 | something: { big: number, here: string, of: { a: number }[] },
4 | };
5 | export default class MyComponent extends React.Component<{}, MyComponentState> {
6 | render() {
7 | return ;
8 | }
9 | onclick() {
10 | if (Math.random() > 0.5) {
11 | this.setState({ foo: 1, bar: 2 });
12 | }
13 | this.otherMethod();
14 | }
15 | otherMethod() {
16 | for (const foo of [1, 2, 3]) {
17 | if (foo > 2) {
18 | this.setState({ baz: foo });
19 | }
20 | }
21 | }
22 | addLargeObjectToState() {
23 | this.setState({
24 | something: {
25 | big: 123,
26 | here: 'string',
27 | of: [{ a: 1 }, { a: 2 }],
28 | },
29 | });
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/set-state-only/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default class MyComponent extends React.Component {
4 | render() {
5 | return ;
6 | }
7 |
8 | onclick() {
9 | this.setState({foo: 1, bar: 2})
10 | }
11 | }
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/set-state-only/output.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | type MyComponentState = { foo: number, bar: number };
3 | export default class MyComponent extends React.Component<{}, MyComponentState> {
4 | render() {
5 | return ;
6 | }
7 | onclick() {
8 | this.setState({ foo: 1, bar: 2 });
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/state-in-class-member/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default class MyComponent extends React.Component {
4 | state = { foo: 1 }
5 | render() {
6 | return
;
7 | }
8 | }
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/state-in-class-member/output.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | type MyComponentState = { foo: number };
3 | export default class MyComponent extends React.Component<{}, MyComponentState> {
4 | state = { foo: 1 };
5 | render() {
6 | return
;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/state-in-constructor/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default class MyComponent extends React.Component {
4 | constructor(props, context) {
5 | super(props, context);
6 | this.state = { foo: 1 }
7 | }
8 | render() {
9 | return
;
10 | }
11 | }
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/state-in-constructor/output.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | type MyComponentState = { foo: number };
3 | export default class MyComponent extends React.Component<{}, MyComponentState> {
4 | constructor(props, context) {
5 | super(props, context);
6 | this.state = { foo: 1 };
7 | }
8 | render() {
9 | return
;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/static-proptypes-getter-simple/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default class MyComponent extends React.Component {
4 | static get propTypes() {
5 | return {
6 | foo: React.PropTypes.string.isRequired,
7 | };
8 | }
9 | render() {
10 | return
;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/static-proptypes-getter-simple/output.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | type MyComponentProps = {
3 | foo: string,
4 | };
5 | export default class MyComponent extends React.Component {
6 | static get propTypes() {
7 | return {
8 | foo: React.PropTypes.string.isRequired,
9 | };
10 | }
11 | render() {
12 | return
;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/static-proptypes-many-props/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default class MyComponent extends React.Component {
4 | static propTypes = {
5 | children: React.PropTypes.node,
6 | any: React.PropTypes.any,
7 | array: React.PropTypes.array,
8 | bool: React.PropTypes.bool,
9 | func: React.PropTypes.func,
10 | number: React.PropTypes.number,
11 | object: React.PropTypes.object,
12 | string: React.PropTypes.string,
13 | node: React.PropTypes.node,
14 | element: React.PropTypes.element,
15 | oneOf: React.PropTypes.oneOf(['a', 'b', 'c']),
16 | oneOfType: React.PropTypes.oneOfType([
17 | React.PropTypes.string,
18 | React.PropTypes.number,
19 | ]),
20 | arrayOf: React.PropTypes.arrayOf(React.PropTypes.string),
21 | objectOf: React.PropTypes.objectOf(React.PropTypes.string),
22 | shape: React.PropTypes.shape({
23 | color: React.PropTypes.string,
24 | fontSize: React.PropTypes.number,
25 | }),
26 | anyRequired: React.PropTypes.any.isRequired,
27 | arrayRequired: React.PropTypes.array.isRequired,
28 | boolRequired: React.PropTypes.bool.isRequired,
29 | funcRequired: React.PropTypes.func.isRequired,
30 | numberRequired: React.PropTypes.number.isRequired,
31 | objectRequired: React.PropTypes.object.isRequired,
32 | stringRequired: React.PropTypes.string.isRequired,
33 | nodeRequired: React.PropTypes.node.isRequired,
34 | elementRequired: React.PropTypes.element.isRequired,
35 | oneOfRequired: React.PropTypes.oneOf(['a', 'b', 'c']).isRequired,
36 | oneOfTypeRequired: React.PropTypes.oneOfType([
37 | React.PropTypes.string,
38 | React.PropTypes.number,
39 | ]).isRequired,
40 | arrayOfRequired: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
41 | objectOfRequired: React.PropTypes.objectOf(React.PropTypes.string).isRequired,
42 | shapeRequired: React.PropTypes.shape({
43 | color: React.PropTypes.string,
44 | fontSize: React.PropTypes.number.isRequired,
45 | }).isRequired,
46 | };
47 | render() {
48 | return
;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/static-proptypes-many-props/output.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | type MyComponentProps = {
3 | any?: any,
4 | array?: any[],
5 | bool?: boolean,
6 | func?: (...args: any[]) => any,
7 | number?: number,
8 | object?: object,
9 | string?: string,
10 | node?: React.ReactNode,
11 | element?: JSX.Element,
12 | oneOf?: 'a' | 'b' | 'c',
13 | oneOfType?: string | number,
14 | arrayOf?: string[],
15 | objectOf?: {
16 | [key: string]: string,
17 | },
18 | shape?: {
19 | color?: string,
20 | fontSize?: number,
21 | },
22 | anyRequired: any,
23 | arrayRequired: any[],
24 | boolRequired: boolean,
25 | funcRequired: (...args: any[]) => any,
26 | numberRequired: number,
27 | objectRequired: object,
28 | stringRequired: string,
29 | nodeRequired: React.ReactNode,
30 | elementRequired: JSX.Element,
31 | oneOfRequired: 'a' | 'b' | 'c',
32 | oneOfTypeRequired: string | number,
33 | arrayOfRequired: string[],
34 | objectOfRequired: {
35 | [key: string]: string,
36 | },
37 | shapeRequired: {
38 | color?: string,
39 | fontSize: number,
40 | },
41 | };
42 | export default class MyComponent extends React.Component {
43 | static propTypes = {
44 | children: React.PropTypes.node,
45 | any: React.PropTypes.any,
46 | array: React.PropTypes.array,
47 | bool: React.PropTypes.bool,
48 | func: React.PropTypes.func,
49 | number: React.PropTypes.number,
50 | object: React.PropTypes.object,
51 | string: React.PropTypes.string,
52 | node: React.PropTypes.node,
53 | element: React.PropTypes.element,
54 | oneOf: React.PropTypes.oneOf(['a', 'b', 'c']),
55 | oneOfType: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]),
56 | arrayOf: React.PropTypes.arrayOf(React.PropTypes.string),
57 | objectOf: React.PropTypes.objectOf(React.PropTypes.string),
58 | shape: React.PropTypes.shape({
59 | color: React.PropTypes.string,
60 | fontSize: React.PropTypes.number,
61 | }),
62 | anyRequired: React.PropTypes.any.isRequired,
63 | arrayRequired: React.PropTypes.array.isRequired,
64 | boolRequired: React.PropTypes.bool.isRequired,
65 | funcRequired: React.PropTypes.func.isRequired,
66 | numberRequired: React.PropTypes.number.isRequired,
67 | objectRequired: React.PropTypes.object.isRequired,
68 | stringRequired: React.PropTypes.string.isRequired,
69 | nodeRequired: React.PropTypes.node.isRequired,
70 | elementRequired: React.PropTypes.element.isRequired,
71 | oneOfRequired: React.PropTypes.oneOf(['a', 'b', 'c']).isRequired,
72 | oneOfTypeRequired: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]).isRequired,
73 | arrayOfRequired: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
74 | objectOfRequired: React.PropTypes.objectOf(React.PropTypes.string).isRequired,
75 | shapeRequired: React.PropTypes.shape({
76 | color: React.PropTypes.string,
77 | fontSize: React.PropTypes.number.isRequired,
78 | }).isRequired,
79 | };
80 | render() {
81 | return
;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/static-proptypes-simple/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default class MyComponent extends React.Component {
4 | static propTypes = {
5 | foo: React.PropTypes.string.isRequired,
6 | };
7 | render() {
8 | return
;
9 | }
10 | }
--------------------------------------------------------------------------------
/test/react-js-make-props-and-state-transform/static-proptypes-simple/output.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | type MyComponentProps = {
3 | foo: string,
4 | };
5 | export default class MyComponent extends React.Component {
6 | static propTypes = {
7 | foo: React.PropTypes.string.isRequired,
8 | };
9 | render() {
10 | return
;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/react-move-prop-types-to-class-transform/multiple-components/input.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<{
2 | foo: number;
3 | }, {
4 | bar: string;
5 | }> {
6 | render() {
7 | return null;
8 | }
9 | }
10 | SomeComponent.propTypes = { foo: React.PropTypes.string };
11 |
12 | class AnotherComponent extends React.Component<{
13 | baz: number;
14 | }> {
15 | render() {
16 | return null;
17 | }
18 | }
19 | AnotherComponent.propTypes = { baz: React.PropTypes.string };
20 |
--------------------------------------------------------------------------------
/test/react-move-prop-types-to-class-transform/multiple-components/output.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<
2 | {
3 | foo: number,
4 | },
5 | {
6 | bar: string,
7 | },
8 | > {
9 | static propTypes = { foo: React.PropTypes.string };
10 | render() {
11 | return null;
12 | }
13 | }
14 | SomeComponent.propTypes = { foo: React.PropTypes.string };
15 | class AnotherComponent extends React.Component<{
16 | baz: number,
17 | }> {
18 | static propTypes = { baz: React.PropTypes.string };
19 | render() {
20 | return null;
21 | }
22 | }
23 | AnotherComponent.propTypes = { baz: React.PropTypes.string };
24 |
--------------------------------------------------------------------------------
/test/react-move-prop-types-to-class-transform/simple/input.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<{
2 | foo: number;
3 | }, {
4 | bar: string;
5 | }> {
6 | render() {
7 | return null;
8 | }
9 | }
10 | SomeComponent.propTypes = { foo: React.PropTypes.string };
--------------------------------------------------------------------------------
/test/react-move-prop-types-to-class-transform/simple/output.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<
2 | {
3 | foo: number,
4 | },
5 | {
6 | bar: string,
7 | },
8 | > {
9 | static propTypes = { foo: React.PropTypes.string };
10 | render() {
11 | return null;
12 | }
13 | }
14 | SomeComponent.propTypes = { foo: React.PropTypes.string };
15 |
--------------------------------------------------------------------------------
/test/react-remove-prop-types-assignment-transform/functional-components/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | function Foo() {
4 |
5 | }
6 |
7 | Foo.propTypes = {bar: React.PropTypes.string.isRequired}
--------------------------------------------------------------------------------
/test/react-remove-prop-types-assignment-transform/functional-components/output.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | function Foo() {}
3 |
--------------------------------------------------------------------------------
/test/react-remove-prop-types-assignment-transform/multiple/input.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<{
2 | foo: string;
3 | baz: string;
4 | }, {
5 | bar: string;
6 | }> {
7 | }
8 | SomeComponent.propTypes = { foo: React.PropTypes.string };
9 | SomeComponent.propTypes.baz = React.PropTypes.string.isRequired;
10 |
11 |
12 | class AnotherComponent extends React.Component<{
13 | lol: number;
14 | }> {
15 | }
16 | AnotherComponent.propTypes = { lol: React.PropTypes.number };
17 |
--------------------------------------------------------------------------------
/test/react-remove-prop-types-assignment-transform/multiple/output.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<
2 | {
3 | foo: string,
4 | baz: string,
5 | },
6 | {
7 | bar: string,
8 | },
9 | > {}
10 | class AnotherComponent extends React.Component<{
11 | lol: number,
12 | }> {}
13 |
--------------------------------------------------------------------------------
/test/react-remove-prop-types-assignment-transform/simple/input.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<{
2 | foo: number;
3 | }, {
4 | bar: string;
5 | }> {
6 | }
7 | SomeComponent.propTypes = { foo: React.PropTypes.string };
--------------------------------------------------------------------------------
/test/react-remove-prop-types-assignment-transform/simple/output.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<
2 | {
3 | foo: number,
4 | },
5 | {
6 | bar: string,
7 | },
8 | > {}
9 |
--------------------------------------------------------------------------------
/test/react-remove-prop-types-import/from-prop-types/input.tsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 | import React from 'react'
3 |
4 | export const Hello = ({ message }) => {
5 | return hello {message}
6 | }
7 |
--------------------------------------------------------------------------------
/test/react-remove-prop-types-import/from-prop-types/output.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | export const Hello = ({ message }) => {
3 | return hello {message}
;
4 | };
5 |
--------------------------------------------------------------------------------
/test/react-remove-prop-types-import/from-react-multi-named-import/input.tsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes, Component } from 'react';
2 |
3 | export class MyComponent extends Component {
4 | render() {
5 | return hello
;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/test/react-remove-prop-types-import/from-react-multi-named-import/output.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | export class MyComponent extends Component {
3 | render() {
4 | return hello
;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/test/react-remove-prop-types-import/from-react-simple/input.tsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 |
3 | export const Hello = ({ message }) => {
4 | return hello {message}
5 | }
6 |
--------------------------------------------------------------------------------
/test/react-remove-prop-types-import/from-react-simple/output.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | export const Hello = ({ message }) => {
3 | return hello {message}
;
4 | };
5 |
--------------------------------------------------------------------------------
/test/react-remove-static-prop-types-member-transform/getter/input.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<{
2 | foo: number;
3 | }, {
4 | bar: string;
5 | }> {
6 | static propTypes = { foo: React.PropTypes.string };
7 | }
--------------------------------------------------------------------------------
/test/react-remove-static-prop-types-member-transform/getter/output.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<
2 | {
3 | foo: number,
4 | },
5 | {
6 | bar: string,
7 | },
8 | > {}
9 |
--------------------------------------------------------------------------------
/test/react-remove-static-prop-types-member-transform/multiple-components/input.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<{
2 | foo: number;
3 | }, {
4 | bar: string;
5 | }> {
6 | static propTypes = {
7 | foo: React.PropTypes.string,
8 | baz: React.PropTypes.string.isRequired,
9 | };
10 | }
11 |
12 | class AnotherComponent extends React.Component<{
13 | foo: number;
14 | }, {
15 | bar: string;
16 | }> {
17 | static propTypes = {
18 | baz: React.PropTypes.string.isRequired,
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/test/react-remove-static-prop-types-member-transform/multiple-components/output.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<
2 | {
3 | foo: number,
4 | },
5 | {
6 | bar: string,
7 | },
8 | > {}
9 | class AnotherComponent extends React.Component<
10 | {
11 | foo: number,
12 | },
13 | {
14 | bar: string,
15 | },
16 | > {}
17 |
--------------------------------------------------------------------------------
/test/react-remove-static-prop-types-member-transform/multiple/input.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<{
2 | foo: number;
3 | }, {
4 | bar: string;
5 | }> {
6 | static propTypes = {
7 | foo: React.PropTypes.string,
8 | baz: React.PropTypes.string.isRequired,
9 | };
10 | }
--------------------------------------------------------------------------------
/test/react-remove-static-prop-types-member-transform/multiple/output.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<
2 | {
3 | foo: number,
4 | },
5 | {
6 | bar: string,
7 | },
8 | > {}
9 |
--------------------------------------------------------------------------------
/test/react-remove-static-prop-types-member-transform/other-static-members/input.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<{
2 | foo: number;
3 | }, {
4 | bar: string;
5 | }> {
6 | static propTypes = { foo: React.PropTypes.string };
7 | static defaultProps = { foo: 'bar' };
8 | }
9 |
--------------------------------------------------------------------------------
/test/react-remove-static-prop-types-member-transform/other-static-members/output.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<
2 | {
3 | foo: number,
4 | },
5 | {
6 | bar: string,
7 | },
8 | > {
9 | static defaultProps = { foo: 'bar' };
10 | }
11 |
--------------------------------------------------------------------------------
/test/react-remove-static-prop-types-member-transform/simple/input.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<{
2 | foo: number;
3 | }, {
4 | bar: string;
5 | }> {
6 | static get propTypes() {
7 | return { foo: React.PropTypes.string };
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/react-remove-static-prop-types-member-transform/simple/output.tsx:
--------------------------------------------------------------------------------
1 | class SomeComponent extends React.Component<
2 | {
3 | foo: number,
4 | },
5 | {
6 | bar: string,
7 | },
8 | > {}
9 |
--------------------------------------------------------------------------------
/test/react-stateless-function-make-props-transform/empty-prop/input.tsx:
--------------------------------------------------------------------------------
1 | function Hello() {
2 | return
3 | }
4 |
5 | Hello.propTypes = {
6 | };
7 |
--------------------------------------------------------------------------------
/test/react-stateless-function-make-props-transform/empty-prop/output.tsx:
--------------------------------------------------------------------------------
1 | const Hello: React.SFC<{}> = () => {
2 | return
;
3 | };
4 | Hello.propTypes = {};
5 |
--------------------------------------------------------------------------------
/test/react-stateless-function-make-props-transform/multiple-components/input.tsx:
--------------------------------------------------------------------------------
1 | const Hello = ({ message }) => {
2 | return hello {message}
3 | };
4 |
5 | function Hey({ name }) {
6 | return hey, {name}
7 | }
8 |
9 | Hey.propTypes = {
10 | name: React.PropTypes.string.isRequired,
11 | }
12 |
13 | Hello.propTypes = {
14 | message: React.PropTypes.string,
15 | }
16 |
--------------------------------------------------------------------------------
/test/react-stateless-function-make-props-transform/multiple-components/output.tsx:
--------------------------------------------------------------------------------
1 | type HelloProps = {
2 | message?: string,
3 | };
4 | const Hello: React.SFC = ({ message }) => {
5 | return hello {message}
;
6 | };
7 | type HeyProps = {
8 | name: string,
9 | };
10 | const Hey: React.SFC = ({ name }) => {
11 | return hey, {name}
;
12 | };
13 | Hey.propTypes = {
14 | name: React.PropTypes.string.isRequired,
15 | };
16 | Hello.propTypes = {
17 | message: React.PropTypes.string,
18 | };
19 |
--------------------------------------------------------------------------------
/test/react-stateless-function-make-props-transform/stateless-arrow-function/input.tsx:
--------------------------------------------------------------------------------
1 | const Hello = ({ message }) => {
2 | return hello {message}
3 | };
4 |
5 | Hello.propTypes = {
6 | message: React.PropTypes.string,
7 | }
8 |
--------------------------------------------------------------------------------
/test/react-stateless-function-make-props-transform/stateless-arrow-function/output.tsx:
--------------------------------------------------------------------------------
1 | type HelloProps = {
2 | message?: string,
3 | };
4 | const Hello: React.SFC = ({ message }) => {
5 | return hello {message}
;
6 | };
7 | Hello.propTypes = {
8 | message: React.PropTypes.string,
9 | };
10 |
--------------------------------------------------------------------------------
/test/react-stateless-function-make-props-transform/stateless-function-many-props/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | function MyComponent() {
3 | return
4 | }
5 |
6 | MyComponent.propTypes = {
7 | children: React.PropTypes.node,
8 | any: React.PropTypes.any,
9 | array: React.PropTypes.array,
10 | bool: React.PropTypes.bool,
11 | func: React.PropTypes.func,
12 | number: React.PropTypes.number,
13 | object: React.PropTypes.object,
14 | string: React.PropTypes.string,
15 | node: React.PropTypes.node,
16 | element: React.PropTypes.element,
17 | oneOf: React.PropTypes.oneOf(['a', 'b', 'c']),
18 | oneOfType: React.PropTypes.oneOfType([
19 | React.PropTypes.string,
20 | React.PropTypes.number,
21 | ]),
22 | arrayOf: React.PropTypes.arrayOf(React.PropTypes.string),
23 | objectOf: React.PropTypes.objectOf(React.PropTypes.string),
24 | shape: React.PropTypes.shape({
25 | color: React.PropTypes.string,
26 | fontSize: React.PropTypes.number,
27 | }),
28 | anyRequired: React.PropTypes.any.isRequired,
29 | arrayRequired: React.PropTypes.array.isRequired,
30 | boolRequired: React.PropTypes.bool.isRequired,
31 | funcRequired: React.PropTypes.func.isRequired,
32 | numberRequired: React.PropTypes.number.isRequired,
33 | objectRequired: React.PropTypes.object.isRequired,
34 | stringRequired: React.PropTypes.string.isRequired,
35 | nodeRequired: React.PropTypes.node.isRequired,
36 | elementRequired: React.PropTypes.element.isRequired,
37 | oneOfRequired: React.PropTypes.oneOf(['a', 'b', 'c']).isRequired,
38 | oneOfTypeRequired: React.PropTypes.oneOfType([
39 | React.PropTypes.string,
40 | React.PropTypes.number,
41 | ]).isRequired,
42 | arrayOfRequired: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
43 | objectOfRequired: React.PropTypes.objectOf(React.PropTypes.string).isRequired,
44 | shapeRequired: React.PropTypes.shape({
45 | color: React.PropTypes.string,
46 | fontSize: React.PropTypes.number.isRequired,
47 | }).isRequired,
48 | };
49 |
--------------------------------------------------------------------------------
/test/react-stateless-function-make-props-transform/stateless-function-many-props/output.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | type MyComponentProps = {
3 | any?: any,
4 | array?: any[],
5 | bool?: boolean,
6 | func?: (...args: any[]) => any,
7 | number?: number,
8 | object?: object,
9 | string?: string,
10 | node?: React.ReactNode,
11 | element?: JSX.Element,
12 | oneOf?: 'a' | 'b' | 'c',
13 | oneOfType?: string | number,
14 | arrayOf?: string[],
15 | objectOf?: {
16 | [key: string]: string,
17 | },
18 | shape?: {
19 | color?: string,
20 | fontSize?: number,
21 | },
22 | anyRequired: any,
23 | arrayRequired: any[],
24 | boolRequired: boolean,
25 | funcRequired: (...args: any[]) => any,
26 | numberRequired: number,
27 | objectRequired: object,
28 | stringRequired: string,
29 | nodeRequired: React.ReactNode,
30 | elementRequired: JSX.Element,
31 | oneOfRequired: 'a' | 'b' | 'c',
32 | oneOfTypeRequired: string | number,
33 | arrayOfRequired: string[],
34 | objectOfRequired: {
35 | [key: string]: string,
36 | },
37 | shapeRequired: {
38 | color?: string,
39 | fontSize: number,
40 | },
41 | };
42 | const MyComponent: React.SFC = () => {
43 | return
;
44 | };
45 | MyComponent.propTypes = {
46 | children: React.PropTypes.node,
47 | any: React.PropTypes.any,
48 | array: React.PropTypes.array,
49 | bool: React.PropTypes.bool,
50 | func: React.PropTypes.func,
51 | number: React.PropTypes.number,
52 | object: React.PropTypes.object,
53 | string: React.PropTypes.string,
54 | node: React.PropTypes.node,
55 | element: React.PropTypes.element,
56 | oneOf: React.PropTypes.oneOf(['a', 'b', 'c']),
57 | oneOfType: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]),
58 | arrayOf: React.PropTypes.arrayOf(React.PropTypes.string),
59 | objectOf: React.PropTypes.objectOf(React.PropTypes.string),
60 | shape: React.PropTypes.shape({
61 | color: React.PropTypes.string,
62 | fontSize: React.PropTypes.number,
63 | }),
64 | anyRequired: React.PropTypes.any.isRequired,
65 | arrayRequired: React.PropTypes.array.isRequired,
66 | boolRequired: React.PropTypes.bool.isRequired,
67 | funcRequired: React.PropTypes.func.isRequired,
68 | numberRequired: React.PropTypes.number.isRequired,
69 | objectRequired: React.PropTypes.object.isRequired,
70 | stringRequired: React.PropTypes.string.isRequired,
71 | nodeRequired: React.PropTypes.node.isRequired,
72 | elementRequired: React.PropTypes.element.isRequired,
73 | oneOfRequired: React.PropTypes.oneOf(['a', 'b', 'c']).isRequired,
74 | oneOfTypeRequired: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]).isRequired,
75 | arrayOfRequired: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
76 | objectOfRequired: React.PropTypes.objectOf(React.PropTypes.string).isRequired,
77 | shapeRequired: React.PropTypes.shape({
78 | color: React.PropTypes.string,
79 | fontSize: React.PropTypes.number.isRequired,
80 | }).isRequired,
81 | };
82 |
--------------------------------------------------------------------------------
/test/react-stateless-function-make-props-transform/stateless-function/input.tsx:
--------------------------------------------------------------------------------
1 | function Hello({ message }) {
2 | return hello {message}
;
3 | }
4 |
5 | Hello.propTypes = {
6 | message: React.PropTypes.string,
7 | };
8 |
--------------------------------------------------------------------------------
/test/react-stateless-function-make-props-transform/stateless-function/output.tsx:
--------------------------------------------------------------------------------
1 | type HelloProps = {
2 | message?: string,
3 | };
4 | const Hello: React.SFC = ({ message }) => {
5 | return hello {message}
;
6 | };
7 | Hello.propTypes = {
8 | message: React.PropTypes.string,
9 | };
10 |
--------------------------------------------------------------------------------
/test/react-stateless-function-make-props-transform/stateless-propless/input.tsx:
--------------------------------------------------------------------------------
1 | const Hello1 = ({ message }) => {
2 | return hello {message}
;
3 | };
4 | function Hello2({ message }) {
5 | return hello {message}
;
6 | }
7 |
--------------------------------------------------------------------------------
/test/react-stateless-function-make-props-transform/stateless-propless/output.tsx:
--------------------------------------------------------------------------------
1 | const Hello1 = ({ message }) => {
2 | return hello {message}
;
3 | };
4 | function Hello2({ message }) {
5 | return hello {message}
;
6 | }
7 |
--------------------------------------------------------------------------------
/test/transformers.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @file
3 | * Test runner
4 | */
5 |
6 | import * as path from 'path';
7 | import * as fs from 'fs';
8 | import * as ts from 'typescript';
9 | import * as chalk from 'chalk';
10 | import * as _ from 'lodash';
11 |
12 | import {
13 | reactJSMakePropsAndStateInterfaceTransformFactoryFactory,
14 | reactStatelessFunctionMakePropsTransformFactoryFactory,
15 | reactMovePropTypesToClassTransformFactoryFactory,
16 | reactRemovePropTypesAssignmentTransformFactoryFactory,
17 | reactRemoveStaticPropTypesMemberTransformFactoryFactory,
18 | collapseIntersectionInterfacesTransformFactoryFactory,
19 | reactRemovePropTypesImportTransformFactoryFactory,
20 | allTransforms,
21 | compile,
22 | TransformFactoryFactory
23 | } from '../src';
24 |
25 |
26 | /** Map between a transform and its test folder */
27 | const transformToFolderMap: [string, TransformFactoryFactory[]][] = [
28 | ['react-js-make-props-and-state-transform', [reactJSMakePropsAndStateInterfaceTransformFactoryFactory]],
29 | ['react-stateless-function-make-props-transform', [reactStatelessFunctionMakePropsTransformFactoryFactory]],
30 | ['react-remove-static-prop-types-member-transform', [reactRemoveStaticPropTypesMemberTransformFactoryFactory]],
31 | ['react-remove-prop-types-assignment-transform', [reactRemovePropTypesAssignmentTransformFactoryFactory]],
32 | ['collapse-intersection-interfaces-transform', [collapseIntersectionInterfacesTransformFactoryFactory]],
33 | ['react-move-prop-types-to-class-transform', [reactMovePropTypesToClassTransformFactoryFactory]],
34 | ['react-remove-prop-types-import', [reactRemovePropTypesImportTransformFactoryFactory]],
35 | ['end-to-end', allTransforms],
36 | ];
37 |
38 | const isJestUpdateSnapshotEnabled = !!_.intersection(process.argv, ['-u', '--updateSnapshot']).length;
39 |
40 | for (const [testFolderName, getFactory] of transformToFolderMap) {
41 | describe(testFolderName.replace(/\-/g, ' ').replace('transform', ''), () => {
42 | for (const folderName of fs.readdirSync(path.join(__dirname, testFolderName))) {
43 | const folder = path.join(path.join(__dirname, testFolderName, folderName));
44 | if (fs.statSync(folder).isDirectory()) {
45 | it(`${testFolderName} ${folderName}`, async () => {
46 | const inputPath = path.join(folder, 'input.tsx');
47 | const outputPath = path.join(folder, 'output.tsx');
48 | const result = compile(inputPath, getFactory, {
49 | singleQuote: true,
50 | printWidth: 120,
51 | tabWidth: 4,
52 | trailingComma: 'all',
53 | });
54 | if (isJestUpdateSnapshotEnabled) {
55 | await writeFile(outputPath, result);
56 | }
57 | const output = await readFile(outputPath);
58 | expect(stripEmptyLines(result)).toEqual(stripEmptyLines(output));
59 | });
60 | }
61 | }
62 | });
63 | }
64 |
65 | /**
66 | * Remove extra empty lines
67 | * @param s A file text
68 | */
69 | function stripEmptyLines(s: string) {
70 | const newLineRegex = /\n|\r\n/;
71 | return s.split(newLineRegex).filter((l) => l.trim()).join('\n');
72 | }
73 |
74 | /**
75 | * Read a string file
76 | * @param pathToFile Path to a string file
77 | */
78 | function readFile(pathToFile: string) {
79 | return new Promise((resolve, reject) => {
80 | fs.readFile(pathToFile, (error, buffer) => {
81 | if (error) { return reject(error); }
82 | resolve(buffer.toString());
83 | });
84 | });
85 | }
86 |
87 | /**
88 | * Read a file
89 | * @param pathToFile Path to a string file
90 | * @param contents Contents of the file
91 | */
92 | function writeFile(pathToFile: string, contents: string) {
93 | return new Promise((resolve, reject) => {
94 | fs.writeFile(pathToFile, contents, (error) => {
95 | if (error) { return reject(error); }
96 | resolve();
97 | });
98 | });
99 | }
100 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "target": "es5",
5 | "declaration": true,
6 | "moduleResolution": "node",
7 | "module": "commonjs",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "downlevelIteration": true,
11 | "sourceMap": true,
12 | "outDir": "dist",
13 | "sourceRoot": "../src",
14 | "lib": ["dom", "es2015"]
15 | },
16 | "exclude": ["node_modules", "test", "dist"],
17 | "types": ["node", "jest"]
18 | }
19 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "lintOptions": {
3 | "typeCheck": true
4 | },
5 | "defaultSeverity": "warning",
6 | "rules": {
7 | "quotemark": [true, "single", "avoid-escape"],
8 | "max-line-length": [false],
9 | "indent": ["error", 4],
10 | "only-arrow-functions": [false],
11 | // Disabled rules
12 | "typedef": [false]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------