├── .babelrc ├── .editorconfig ├── .github ├── stale.yml └── workflows │ └── build.yml ├── .gitignore ├── .npmrc ├── .vscode ├── settings.json └── tasks.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── cli.js ├── package.json ├── src ├── analyzer.ts ├── deprecated.ts ├── generator.ts ├── index.ts ├── types.ts └── typings.ts ├── tests ├── babylon-plugin.d.ts ├── babylon-plugin.jsx ├── cli-test.ts ├── component-without-proptypes.d.ts ├── component-without-proptypes.jsx ├── const-as-proptypes.d.ts ├── const-as-proptypes.jsx ├── error-reporting-test.ts ├── es6-class.d.ts ├── es6-class.jsx ├── es7-class-babeled-to-es6.d.ts ├── es7-class-babeled-to-es6.js ├── es7-class-babeled.js ├── es7-class-separate-export.d.ts ├── es7-class-separate-export.jsx ├── es7-class-top-level-module.d.ts ├── es7-class.d.ts ├── es7-class.jsx ├── generator-test.ts ├── import-react-component.d.ts ├── import-react-component.jsx ├── instance-of-proptype-names.d.ts ├── instance-of-proptype-names.jsx ├── multiple-components-dot-notation-default.d.ts ├── multiple-components-dot-notation-default.jsx ├── multiple-components-dot-notation.d.ts ├── multiple-components-dot-notation.jsx ├── multiple-components-object-default.d.ts ├── multiple-components-object-default.jsx ├── multiple-components-object-unnamed-default.d.ts ├── multiple-components-object-unnamed-default.jsx ├── multiple-components-object.d.ts ├── multiple-components-object.jsx ├── named-export-specifiers.d.ts ├── named-export-specifiers.jsx ├── parse-prop-types-test.ts ├── parsing-test.ts ├── preact-definition.d.ts ├── preact-definition.jsx ├── prop-types-default-import.jsx ├── prop-types.d.ts ├── prop-types.jsx ├── pure-component.d.ts ├── pure-component.jsx ├── reference-as-proptypes.d.ts ├── reference-as-proptypes.jsx ├── references-in-proptypes.d.ts ├── references-in-proptypes.jsx ├── stateless-default-export.d.ts ├── stateless-default-export.jsx ├── stateless-export-as-default.d.ts ├── stateless-export-as-default.js ├── stateless.d.ts ├── stateless.jsx ├── unnamed-default-export.d.ts └── unnamed-default-export.jsx ├── tsconfig.json ├── tslint.json ├── typings ├── astq.d.ts ├── babylon.d.ts └── get-stdin.d.ts ├── wallaby.conf.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 2 4 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 30 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Comment to post when marking an issue as stale. Set to `false` to disable 10 | markComment: > 11 | This issue has been automatically marked as stale because it has not had 12 | recent activity. It will be closed if no further activity occurs. Thank you 13 | for your contributions. 14 | # Comment to post when closing a stale issue. Set to `false` to disable 15 | closeComment: false 16 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build and test 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [12.x, 14.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - name: Get yarn cache directory path 24 | id: yarn-cache-dir-path 25 | run: echo "::set-output name=dir::$(yarn cache dir)" 26 | - uses: actions/cache@v3 27 | id: yarn-cache 28 | with: 29 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 30 | key: ${{ runner.os }}-${{ matrix.node-version }}-yarn-${{ hashFiles('**/yarn.lock') }} 31 | restore-keys: | 32 | ${{ runner.os }}-yarn- 33 | - run: yarn 34 | - run: yarn linter 35 | - run: yarn test 36 | - run: yarn coverage 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /index.js 2 | /index.d.ts 3 | /node_modules 4 | *.js.map 5 | /tests/*-test.js 6 | coverage/ 7 | .nyc_output/ 8 | npm-debug.log 9 | dist/ 10 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact = true 2 | 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib/", 3 | "files.exclude": { 4 | "**/.git": true, 5 | "**/.svn": true, 6 | "**/.hg": true, 7 | "**/.DS_Store": true 8 | }, 9 | "search.exclude": { 10 | "**/dist": true, 11 | "**/coverage": true, 12 | "**/node_modules": true, 13 | "**/bower_components": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "build:watch", 9 | "group": { 10 | "kind": "build", 11 | "isDefault": true 12 | }, 13 | "isBackground": true, 14 | "presentation": { 15 | "echo": true, 16 | "reveal": "silent", 17 | "focus": false, 18 | "panel": "shared" 19 | }, 20 | "problemMatcher": ["$tsc-watch", "$tslint5"] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [3.1.1](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v3.1.0...v3.1.1) (2022-06-07) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * upgrade astq ([db5a2f9](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/db5a2f9ddae22b9974c8b46c2645f97484ae82c1)) 11 | 12 | ## [3.1.0](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v3.0.1...v3.1.0) (2021-11-12) 13 | 14 | 15 | ### Features 16 | 17 | * create definitions for components exported as object with component properties ([38603a0](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/38603a05f7d20dcde74201b171194bc4a94e4552)) 18 | * create intersection types for components with dot notation component members ([40bccb0](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/40bccb0f631d88ce1e2dfc51e4110a37df182b99)) 19 | 20 | ### [3.0.1](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v3.0.0...v3.0.1) (2021-04-07) 21 | 22 | ### Bug Fixes 23 | 24 | - handle nested string literal props ([46edf00](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/46edf00dc24c5e8dfd3dde44bf52fc2dd48950ff)) 25 | 26 | ## [3.0.0](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v2.0.0...v3.0.0) (2021-03-22) 27 | 28 | ### ⚠ BREAKING CHANGES 29 | 30 | - Drop support for node 8 31 | 32 | ### Features 33 | 34 | - parse files which uses optional chaining feature ([3432de8](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/3432de8a5d479572ff3fcd308e3e2ffb0bed0fce)) 35 | 36 | ### Bug Fixes 37 | 38 | - **deps:** update dependency dts-dom to v3.4.0 ([d87a65e](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/d87a65ecbe844ad78845abbf40afecd2d130a5b8)) 39 | - **deps:** update dependency dts-dom to v3.5.0 ([1c87f5c](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/1c87f5ca6d1c1737db794818ac4802df248db534)) 40 | - **deps:** update dependency dts-dom to v3.6.0 ([4aa8889](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/4aa8889383fa36d51f3e39e12f9e877fdb933f93)) 41 | - **deps:** update dependency meow to v6.0.1 ([8967eb7](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/8967eb74d9e373204da2f5a668bb02f1150ead6b)) 42 | - **deps:** update dependency meow to v6.1.0 ([8ce686d](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/8ce686d9ac95a710f912d8292fee2c437a7f6bcc)) 43 | - correctly import pascalcase function ([686bd9f](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/686bd9fb328934cd65bca62eca003cb6592e180f)) 44 | - **deps:** update dependency pascal-case to v3 ([ca6fecc](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/ca6fecc1fdfb09278aff0d12c0fa5c55e5ed8773)) 45 | - correct usage of execa ([ccb4699](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/ccb469976184ee939d30641bd4d9817327785eeb)) 46 | - **deps:** update dependency chalk to v3 ([963d6fc](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/963d6fc83bca1a05b2d76f2fa0fc05a2f09c5590)) 47 | - **deps:** update dependency dts-dom to v3.3.0 ([49135b1](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/49135b1ac5b94b24446048f6acd1f3460a6debcd)) 48 | - **deps:** update dependency meow to v6 ([435ae07](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/435ae078fdca10cac301335b0862a095959d7ac2)) 49 | - **deps:** update dependency strip-ansi to v6 ([fd7e788](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/fd7e788361126a6945697e443e9309f1bec5e87c)) 50 | 51 | - drop support for node 8 ([ca0a130](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/ca0a130d7814da3897118b57a301b81a271ccf9a)) 52 | 53 | ## [2.0.0](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v1.2.0...v2.0.0) (2019-05-17) 54 | 55 | ### Bug Fixes 56 | 57 | - **deps:** remove stubbed strip-ansi typings, use default import ([75f59b6](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/75f59b6)) 58 | - **deps:** update dependency chalk to v2.4.2 ([dd31c7e](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/dd31c7e)) 59 | - **deps:** update dependency dts-dom to v3.1.1 ([5c20f13](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/5c20f13)) 60 | - **deps:** update dependency dts-dom to v3.2.0 ([e4ad59c](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/e4ad59c)) 61 | - **deps:** update dependency get-stdin to v7 ([61fb667](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/61fb667)) 62 | - **deps:** update dependency strip-ansi to v5 ([a321237](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/a321237)) 63 | - **deps:** update dependency strip-ansi to v5.1.0 ([7d6aee4](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/7d6aee4)) 64 | - **deps:** update dependency strip-ansi to v5.2.0 ([5360d26](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/5360d26)) 65 | - remove unused variable ([70dace6](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/70dace6)) 66 | 67 | ### chore 68 | 69 | - drop support for node6 ([032a8b1](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/032a8b1)) 70 | 71 | ### BREAKING CHANGES 72 | 73 | - drop support for node6 74 | 75 | 76 | 77 | # [1.2.0](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v1.1.0...v1.2.0) (2018-09-26) 78 | 79 | ### Features 80 | 81 | - allow users to set additional babylon plugins ([9798c08](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/9798c08)) 82 | 83 | 84 | 85 | # [1.1.0](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v1.0.0...v1.1.0) (2018-08-16) 86 | 87 | ### Bug Fixes 88 | 89 | - **deps:** update dependency astq to v2.2.10 ([a9c27c7](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/a9c27c7)) 90 | - **deps:** update dependency astq to v2.3.1 ([e9d58aa](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/e9d58aa)) 91 | - **deps:** update dependency astq to v2.3.3 ([c6a5f97](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/c6a5f97)) 92 | - **deps:** update dependency astq to v2.3.4 ([3d077de](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/3d077de)) 93 | - **deps:** update dependency astq to v2.3.6 ([0064a64](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/0064a64)) 94 | - **deps:** update dependency babel-generator to v6.26.1 ([ff30c67](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/ff30c67)) 95 | - **deps:** update dependency babylon to v7.0.0-beta.47 ([ab95114](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/ab95114)) 96 | - **deps:** update dependency chalk to v2.4.1 ([e4980fc](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/e4980fc)) 97 | - **deps:** update dependency dts-dom to v3.1.0 ([1d79303](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/1d79303)) 98 | 99 | ### Features 100 | 101 | - allow custom eol characters ([3e41b31](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/3e41b31)), closes [#609](https://github.com/KnisterPeter/react-to-typescript-definitions/issues/609) 102 | 103 | 104 | 105 | # [1.0.0](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v0.28.2...v1.0.0) (2018-04-26) 106 | 107 | ### Bug Fixes 108 | 109 | - **deps:** update dependency babylon to v7.0.0-beta.41 ([9b5736d](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/9b5736d)) 110 | - **deps:** update dependency babylon to v7.0.0-beta.42 ([48902cc](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/48902cc)) 111 | - **deps:** update dependency babylon to v7.0.0-beta.44 ([b77411e](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/b77411e)) 112 | - **deps:** update dependency babylon to v7.0.0-beta.46 ([a7939dd](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/a7939dd)) 113 | - **deps:** update dependency dts-dom to ^3.0.0 ([56b1012](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/56b1012)) 114 | - **deps:** update dependency meow to v5 ([d48b698](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/d48b698)) 115 | 116 | ### Chores 117 | 118 | - drop test on node4 ([db3b960](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/db3b960)) 119 | 120 | ### BREAKING CHANGES 121 | 122 | - drop node4 support 123 | 124 | 125 | 126 | ## [0.28.2](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v0.28.1...v0.28.2) (2018-03-14) 127 | 128 | ### Bug Fixes 129 | 130 | - **deps:** update dependency babylon to v7.0.0-beta.40 ([75bd5f6](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/75bd5f6)) 131 | - **deps:** update dependency get-stdin to ^6.0.0 ([aa579ea](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/aa579ea)) 132 | - for SFCs export a const and not a type ([173eeb7](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/173eeb7)) 133 | 134 | 135 | 136 | ## [0.28.1](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v0.28.0...v0.28.1) (2018-02-23) 137 | 138 | 139 | 140 | # [0.28.0](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v0.27.1...v0.28.0) (2018-02-07) 141 | 142 | ### Bug Fixes 143 | 144 | - avoid any for oneOf arrays with identifiers ([f947a23](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/f947a23)) 145 | 146 | ### Features 147 | 148 | - support generating types for PureComponents ([7343af1](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/7343af1)) 149 | 150 | 151 | 152 | ## [0.27.1](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v0.27.0...v0.27.1) (2018-01-23) 153 | 154 | ### Bug Fixes 155 | 156 | - **package:** update dts-dom to version 1.0.0 ([7a7cd1b](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/7a7cd1b)) 157 | - for SFCs use triple slash directive instead of react import ([9b1096d](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/9b1096d)), closes [#469](https://github.com/KnisterPeter/react-to-typescript-definitions/issues/469) [#471](https://github.com/KnisterPeter/react-to-typescript-definitions/issues/471) 158 | 159 | 160 | 161 | # [0.27.0](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v0.25.1...v0.27.0) (2018-01-20) 162 | 163 | ### Bug Fixes 164 | 165 | - add parentheses around function in union by updating dts-dom ([baaa644](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/baaa644)) 166 | - add react import to SFC typings ([2f53d2e](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/2f53d2e)) 167 | 168 | ### Features 169 | 170 | - support named exports as default ([67064b8](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/67064b8)) 171 | 172 | 173 | 174 | # [0.26.0](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v0.25.1...v0.26.0) (2018-01-19) 175 | 176 | ### Bug Fixes 177 | 178 | - add react import to SFC typings ([2f53d2e](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/2f53d2e)) 179 | 180 | ### Features 181 | 182 | - support named exports as default ([67064b8](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/67064b8)) 183 | 184 | 185 | 186 | ## [0.25.1](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v0.25.0...v0.25.1) (2018-01-16) 187 | 188 | ### Bug Fixes 189 | 190 | - remove `typeof` from instanceOf and symbol prop types ([e3d0467](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/e3d0467)) 191 | 192 | 193 | 194 | # [0.25.0](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v0.24.1...v0.25.0) (2018-01-15) 195 | 196 | ### Features 197 | 198 | - support any name for namespace import of `prop-types` ([fe26b1e](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/fe26b1e)) 199 | - support named imports from `prop-types` ([c7df614](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/c7df614)) 200 | 201 | 202 | 203 | ## [0.24.1](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v0.24.0...v0.24.1) (2018-01-12) 204 | 205 | ### Bug Fixes 206 | 207 | - **npm:** add "types" entry to package.json ([b71c964](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/b71c964)) 208 | 209 | 210 | 211 | # [0.24.0](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v0.23.0...v0.24.0) (2017-12-12) 212 | 213 | ### Bug Fixes 214 | 215 | - **package:** update babylon to version 7.0.0-beta.34 ([7d1dee1](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/7d1dee1)), closes [#448](https://github.com/KnisterPeter/react-to-typescript-definitions/issues/448) 216 | - **package:** update meow to version 4.0.0 ([f7768a4](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/f7768a4)) 217 | 218 | ### Features 219 | 220 | - add file option to cli ([f7ae784](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/f7ae784)), closes [#451](https://github.com/KnisterPeter/react-to-typescript-definitions/issues/451) 221 | 222 | 223 | 224 | # [0.23.0](https://github.com/KnisterPeter/react-to-typescript-definitions/compare/v0.22.0...v0.23.0) (2017-11-22) 225 | 226 | ### Bug Fixes 227 | 228 | - **package:** update babylon to version 6.17.0 ([17a6d25](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/17a6d25)) 229 | - **package:** update babylon to version 6.17.1 ([d6742f9](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/d6742f9)) 230 | - **package:** update babylon to version 7.0.0-beta.31 ([fc60b2c](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/fc60b2c)) 231 | - **package:** update babylon to version 7.0.0-beta.32 ([6d79609](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/6d79609)) 232 | - **package:** update babylon to version 7.0.0-beta.9 ([91bdfc3](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/91bdfc3)) 233 | - **package:** update chalk to version 2.0.1 ([85d37ff](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/85d37ff)), closes [#408](https://github.com/KnisterPeter/react-to-typescript-definitions/issues/408) 234 | - **package:** update chalk to version 2.1.0 ([e11c8d6](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/e11c8d6)) 235 | - **package:** update strip-ansi to version 4.0.0 ([a138ce7](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/a138ce7)) 236 | 237 | ### Features 238 | 239 | - support prop-types repository ([f5f7337](https://github.com/KnisterPeter/react-to-typescript-definitions/commit/f5f7337)), closes [#439](https://github.com/KnisterPeter/react-to-typescript-definitions/issues/439) 240 | 241 | 242 | 243 | # [0.22.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.21.1...v0.22.0) (2017-04-18) 244 | 245 | ### Features 246 | 247 | - resolve referenced propTypes ([da9863b](https://github.com/knisterpeter/react-to-typescript-definitions/commit/da9863b)) 248 | 249 | 250 | 251 | ## [0.21.1](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.21.0...v0.21.1) (2017-04-16) 252 | 253 | ### Bug Fixes 254 | 255 | - **package:** update astq to version 2.0.1 ([fa1178b](https://github.com/knisterpeter/react-to-typescript-definitions/commit/fa1178b)) 256 | - **package:** update babel-generator to version 6.24.1 ([8887bb5](https://github.com/knisterpeter/react-to-typescript-definitions/commit/8887bb5)) 257 | - handle named export specifiers ([beced3b](https://github.com/knisterpeter/react-to-typescript-definitions/commit/beced3b)), closes [#368](https://github.com/knisterpeter/react-to-typescript-definitions/issues/368) 258 | 259 | 260 | 261 | # [0.21.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.20.0...v0.21.0) (2017-04-03) 262 | 263 | ### Bug Fixes 264 | 265 | - **package:** update dts-dom to version 0.1.17 ([03e7ef6](https://github.com/knisterpeter/react-to-typescript-definitions/commit/03e7ef6)) 266 | - handle unnamed default export ([a0be64e](https://github.com/knisterpeter/react-to-typescript-definitions/commit/a0be64e)), closes [#350](https://github.com/knisterpeter/react-to-typescript-definitions/issues/350) 267 | - **package:** update dts-dom to version 0.1.18 ([1405400](https://github.com/knisterpeter/react-to-typescript-definitions/commit/1405400)) 268 | 269 | ### Features 270 | 271 | - support output for react-like libraries ([0ed6ffc](https://github.com/knisterpeter/react-to-typescript-definitions/commit/0ed6ffc)), closes [#351](https://github.com/knisterpeter/react-to-typescript-definitions/issues/351) 272 | 273 | 274 | 275 | # [0.20.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.19.1...v0.20.0) (2017-03-21) 276 | 277 | ### Bug Fixes 278 | 279 | - **package:** update babel-generator to version 6.24.0 ([20b8840](https://github.com/knisterpeter/react-to-typescript-definitions/commit/20b8840)) 280 | - **package:** update dts-dom to version 0.1.16 ([2b0cf25](https://github.com/knisterpeter/react-to-typescript-definitions/commit/2b0cf25)) 281 | - **package:** update pascal-case to version 2.0.1 ([fe8ad84](https://github.com/knisterpeter/react-to-typescript-definitions/commit/fe8ad84)) 282 | 283 | ### Features 284 | 285 | - handle array props with shapes in extractComplexTypes ([efb0fa8](https://github.com/knisterpeter/react-to-typescript-definitions/commit/efb0fa8)) 286 | 287 | 288 | 289 | ## [0.19.1](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.19.0...v0.19.1) (2017-03-02) 290 | 291 | ### Bug Fixes 292 | 293 | - fix and improve error output ([c3d79b6](https://github.com/knisterpeter/react-to-typescript-definitions/commit/c3d79b6)) 294 | 295 | 296 | 297 | # [0.19.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.18.1...v0.19.0) (2017-03-01) 298 | 299 | ### Bug Fixes 300 | 301 | - **package:** update babylon to version 6.16.0 ([24e3da1](https://github.com/knisterpeter/react-to-typescript-definitions/commit/24e3da1)) 302 | - **package:** update babylon to version 6.16.1 ([c2064f0](https://github.com/knisterpeter/react-to-typescript-definitions/commit/c2064f0)) 303 | - **package:** update dts-dom to version 0.1.13 ([2630ab8](https://github.com/knisterpeter/react-to-typescript-definitions/commit/2630ab8)) 304 | - **package:** update dts-dom to version 0.1.14 (#295) ([859c134](https://github.com/knisterpeter/react-to-typescript-definitions/commit/859c134)) 305 | - **package:** update dts-dom to version 0.1.15 ([75d5775](https://github.com/knisterpeter/react-to-typescript-definitions/commit/75d5775)) 306 | 307 | ### Features 308 | 309 | - report error context and source excerpt ([251a46a](https://github.com/knisterpeter/react-to-typescript-definitions/commit/251a46a)) 310 | - retain error information ([e24f500](https://github.com/knisterpeter/react-to-typescript-definitions/commit/e24f500)) 311 | 312 | 313 | 314 | ## [0.18.1](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.18.0...v0.18.1) (2017-01-27) 315 | 316 | ### Bug Fixes 317 | 318 | - replace all newlines ([7a7153b](https://github.com/knisterpeter/react-to-typescript-definitions/commit/7a7153b)) 319 | - **typings:** handle one more type of es6 exports (#287) ([3d3cfad](https://github.com/knisterpeter/react-to-typescript-definitions/commit/3d3cfad)) 320 | 321 | 322 | 323 | # [0.18.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.17.1...v0.18.0) (2017-01-11) 324 | 325 | ### Bug Fixes 326 | 327 | - **package:** update babylon to version 6.15.0 (#269) ([8bb7da2](https://github.com/knisterpeter/react-to-typescript-definitions/commit/8bb7da2)) 328 | 329 | ### Features 330 | 331 | - export complex types as type aliases or interfaces (#272) ([95215cd](https://github.com/knisterpeter/react-to-typescript-definitions/commit/95215cd)) 332 | 333 | 334 | 335 | ## [0.17.1](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.17.0...v0.17.1) (2017-01-09) 336 | 337 | ### Bug Fixes 338 | 339 | - handle references for shapes correctly (#262) ([cc4b6f9](https://github.com/knisterpeter/react-to-typescript-definitions/commit/cc4b6f9)) 340 | 341 | 342 | 343 | # [0.17.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.16.2...v0.17.0) (2016-11-22) 344 | 345 | ### Features 346 | 347 | - resolve references in oneOf propTypes (#236) ([4e85be2](https://github.com/knisterpeter/react-to-typescript-definitions/commit/4e85be2)), closes [#236](https://github.com/knisterpeter/react-to-typescript-definitions/issues/236) 348 | 349 | 350 | 351 | ## [0.16.2](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.16.1...v0.16.2) (2016-11-07) 352 | 353 | ### Bug Fixes 354 | 355 | - enum-types should fallback to any ([0fa1fa7](https://github.com/knisterpeter/react-to-typescript-definitions/commit/0fa1fa7)) 356 | 357 | 358 | 359 | ## [0.16.1](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.16.0...v0.16.1) (2016-11-07) 360 | 361 | ### Bug Fixes 362 | 363 | - do not fail in case of inference errors (#223) ([078b97d](https://github.com/knisterpeter/react-to-typescript-definitions/commit/078b97d)) 364 | 365 | 366 | 367 | # [0.16.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.15.0...v0.16.0) (2016-11-04) 368 | 369 | ### Features 370 | 371 | - add partial support for onOf propType (#218) ([d6d7d1e](https://github.com/knisterpeter/react-to-typescript-definitions/commit/d6d7d1e)) 372 | - add support for symbol propType ([c2a2542](https://github.com/knisterpeter/react-to-typescript-definitions/commit/c2a2542)) 373 | - implement shape propType (#219) ([6876062](https://github.com/knisterpeter/react-to-typescript-definitions/commit/6876062)) 374 | 375 | 376 | 377 | # [0.15.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.14.1...v0.15.0) (2016-11-03) 378 | 379 | ### Features 380 | 381 | - write multiple outputs per file (#211) ([f6f63ae](https://github.com/knisterpeter/react-to-typescript-definitions/commit/f6f63ae)) 382 | 383 | 384 | 385 | ## [0.14.1](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.14.0...v0.14.1) (2016-11-01) 386 | 387 | ### Bug Fixes 388 | 389 | - allow empty or non-export input files (#213) ([1dca59c](https://github.com/knisterpeter/react-to-typescript-definitions/commit/1dca59c)), closes [#212](https://github.com/knisterpeter/react-to-typescript-definitions/issues/212) 390 | 391 | 392 | 393 | # [0.14.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.13.0...v0.14.0) (2016-10-31) 394 | 395 | ### Features 396 | 397 | - add support for stateless function components (#204) ([c7a988b](https://github.com/knisterpeter/react-to-typescript-definitions/commit/c7a988b)) 398 | 399 | 400 | 401 | # [0.13.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.12.0...v0.13.0) (2016-10-20) 402 | 403 | ### Features 404 | 405 | - **parsing:** Add ability to parse babeled files (#121) ([a19c80c](https://github.com/knisterpeter/react-to-typescript-definitions/commit/a19c80c)) 406 | 407 | 408 | 409 | # [0.12.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.11.1...v0.12.0) (2016-05-25) 410 | 411 | ### Features 412 | 413 | - Adds option create write typings to top-level (#116) ([a4cb090](https://github.com/knisterpeter/react-to-typescript-definitions/commit/a4cb090)) 414 | 415 | 416 | 417 | ## [0.11.1](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.11.0...v0.11.1) (2016-05-06) 418 | 419 | 420 | 421 | # [0.11.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.10.0...v0.11.0) (2016-04-09) 422 | 423 | ### Bug Fixes 424 | 425 | - **parser:** configure babel to be as permissive as possible ([586b5ec](https://github.com/knisterpeter/react-to-typescript-definitions/commit/586b5ec)) 426 | 427 | 428 | 429 | # [0.10.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.9.0...v0.10.0) (2016-02-01) 430 | 431 | ### Features 432 | 433 | - Added support for ref attributes ([d6f5b46](https://github.com/knisterpeter/react-to-typescript-definitions/commit/d6f5b46)) 434 | 435 | 436 | 437 | # [0.9.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.8.0...v0.9.0) (2016-01-29) 438 | 439 | 440 | 441 | # [0.8.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.7.0...v0.8.0) (2016-01-21) 442 | 443 | ### Features 444 | 445 | - Exported component interfaces ([511767d](https://github.com/knisterpeter/react-to-typescript-definitions/commit/511767d)) 446 | 447 | 448 | 449 | # [0.7.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.6.0...v0.7.0) (2015-12-31) 450 | 451 | ### Features 452 | 453 | - Support es6 class syntax ([8b27145](https://github.com/knisterpeter/react-to-typescript-definitions/commit/8b27145)) 454 | 455 | 456 | 457 | # [0.6.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.5.0...v0.6.0) (2015-12-21) 458 | 459 | ### Features 460 | 461 | - Added instanceOf proptypes ([c548b7b](https://github.com/knisterpeter/react-to-typescript-definitions/commit/c548b7b)) 462 | - Added jsdoc to d.ts files ([1a40858](https://github.com/knisterpeter/react-to-typescript-definitions/commit/1a40858)) 463 | 464 | 465 | 466 | # [0.5.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.4.0...v0.5.0) (2015-12-21) 467 | 468 | ### Features 469 | 470 | - Added arrayOf type props ([faa9e0b](https://github.com/knisterpeter/react-to-typescript-definitions/commit/faa9e0b)) 471 | - Added required props ([f5d8cf7](https://github.com/knisterpeter/react-to-typescript-definitions/commit/f5d8cf7)) 472 | - Added union proptypes ([033a159](https://github.com/knisterpeter/react-to-typescript-definitions/commit/033a159)) 473 | 474 | 475 | 476 | # [0.4.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.3.2...v0.4.0) (2015-12-19) 477 | 478 | ### Features 479 | 480 | - Added react key to generated props ([a54511e](https://github.com/knisterpeter/react-to-typescript-definitions/commit/a54511e)) 481 | 482 | ### Performance Improvements 483 | 484 | - Tests are written in typescript ([2b04aa6](https://github.com/knisterpeter/react-to-typescript-definitions/commit/2b04aa6)) 485 | 486 | 487 | 488 | ## [0.3.2](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.3.1...v0.3.2) (2015-12-15) 489 | 490 | ### Performance Improvements 491 | 492 | - Reduced indention depth ([d981864](https://github.com/knisterpeter/react-to-typescript-definitions/commit/d981864)) 493 | 494 | 495 | 496 | ## [0.3.1](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.3.0...v0.3.1) (2015-12-10) 497 | 498 | ### Bug Fixes 499 | 500 | - Terminate node if no input from stdin ([61734ca](https://github.com/knisterpeter/react-to-typescript-definitions/commit/61734ca)) 501 | 502 | 503 | 504 | # [0.3.0](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.2.1...v0.3.0) (2015-12-10) 505 | 506 | ### Bug Fixes 507 | 508 | - **CHANGELOG:** Fixed changelog urls ([5aa784d](https://github.com/knisterpeter/react-to-typescript-definitions/commit/5aa784d)) 509 | 510 | ### Features 511 | 512 | - **types:** Added explicit any type ([d06dc20](https://github.com/knisterpeter/react-to-typescript-definitions/commit/d06dc20)) 513 | - **types:** Added more optional types ([5ab5736](https://github.com/knisterpeter/react-to-typescript-definitions/commit/5ab5736)) 514 | - Implemented pipes ([2c4b42a](https://github.com/knisterpeter/react-to-typescript-definitions/commit/2c4b42a)) 515 | 516 | 517 | 518 | ## [0.2.1](https://github.com/knisterpeter/react-to-typescript-definitions/compare/v0.2.0...v0.2.1) (2015-12-02) 519 | 520 | ### Bug Fixes 521 | 522 | - **main:** Fixed mail file attribute ([c694f3e](https://github.com/knisterpeter/react-to-typescript-definitions/commit/c694f3e)) 523 | - **npm:** Fixed publishing ([28c64d8](https://github.com/knisterpeter/react-to-typescript-definitions/commit/28c64d8)) 524 | 525 | 526 | 527 | # 0.2.0 (2015-12-02) 528 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Markus Wolf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-to-typescript-definitions 2 | 3 | [![GitHub license][license-image]][license-link] 4 | [![npm][npm-image]][npm-link] 5 | [![Travis][ci-image]][ci-link] 6 | [![codecov](https://codecov.io/gh/KnisterPeter/react-to-typescript-definitions/branch/master/graph/badge.svg)](https://codecov.io/gh/KnisterPeter/react-to-typescript-definitions)[![Commitizen friendly][commitizen-image]][commitizen-link] 7 | [![Standard Version][standard-version-image]][standard-version-link] 8 | [![renovate badge](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://renovateapp.com/) 9 | 10 | Create typescript definitions files (d.ts) from react components. 11 | 12 | ## Features 13 | 14 | - ES6 and ES7 class syntax 15 | - Most PropTypes 16 | - any, array, bool, func, number, object, string, node, element, oneOfType, arrayOf, symbol, shape 17 | - Partial support for oneOf PropType 18 | - required PropTypes 19 | - instanceOf PropTypes (when using API and giving a resolve function) 20 | - jsdoc 21 | 22 | ## Usage 23 | 24 | ### Installation 25 | 26 | Install as npm package: 27 | 28 | ```sh 29 | npm install react-to-typescript-definitions --save-dev 30 | ``` 31 | 32 | or 33 | 34 | ```sh 35 | npm install -g react-to-typescript-definitions 36 | ``` 37 | 38 | ### CLI 39 | 40 | ```sh 41 | Usage 42 | $ react2dts [--module-name | --top-level-module] 43 | 44 | react2dts reads from stdin to process a file. 45 | 46 | Options 47 | --module-name, --name name of the module to create 48 | --top-level-module if the created module should live in top-level 49 | 50 | Examples 51 | $ cat |react2dts --module-name module-name 52 | 53 | $ cat |react2dts --top-level-module 54 | ``` 55 | 56 | ### API 57 | 58 | Functions: 59 | 60 | ```js 61 | /** 62 | * Returns the typescript definition for the given file. 63 | * 64 | * @param name The name of the generated module 65 | * @param path The path to the file to parse 66 | * @param options The options to use 67 | * @return The type definition as string 68 | */ 69 | function generateFromFile(name, path, options) 70 | ``` 71 | 72 | ```js 73 | /** 74 | * Returns the typescript definition for the given source. 75 | * 76 | * @param name The name of the generated module 77 | * @param code The code to parse 78 | * @param options The options to use 79 | * @return The type definition as string 80 | */ 81 | function generateFromSource(name, code, options) 82 | ``` 83 | 84 | ```js 85 | /** 86 | * Returns the typescript definition for the given babylon AST object. 87 | * 88 | * @param name The name of the generated module 89 | * @param ast The babylon ASt to parse 90 | * @param options The options to use 91 | * @return The type definition as string 92 | */ 93 | function generateFromAst(name, ast, options) 94 | ``` 95 | 96 | Options: 97 | 98 | - instanceOfResolver 99 | A function which gets a type name (as string) and should return the path 100 | to the file defining the type or undefined if the type is not resolvable. 101 | This function is required to generate instanceOf PropTypes. 102 | 103 | [license-image]: https://img.shields.io/github/license/KnisterPeter/react-to-typescript-definitions.svg 104 | [license-link]: https://github.com/KnisterPeter/react-to-typescript-definitions 105 | [npm-image]: https://img.shields.io/npm/v/react-to-typescript-definitions.svg 106 | [npm-link]: https://www.npmjs.com/package/react-to-typescript-definitions 107 | [ci-image]: https://img.shields.io/travis/KnisterPeter/react-to-typescript-definitions.svg 108 | [ci-link]: https://travis-ci.org/KnisterPeter/react-to-typescript-definitions 109 | [commitizen-image]: https://img.shields.io/badge/commitizen-friendly-brightgreen.svg 110 | [commitizen-link]: http://commitizen.github.io/cz-cli/ 111 | [standard-version-image]: https://img.shields.io/badge/release-standard%20version-brightgreen.svg 112 | [standard-version-link]: https://github.com/conventional-changelog/standard-version 113 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var react2dts = require('./dist/src/index'); 3 | var meow = require('meow'); 4 | 5 | const cli = meow(` 6 | Usage 7 | $ react2dts [--module-name | --top-level-module] [--react-import ] [--file ] 8 | 9 | react2dts reads from stdin to process a file. 10 | 11 | Options 12 | --module-name, --name name of the module to create 13 | --top-level-module if the created module should live in top-level 14 | --react-import name of the react-like library to import (default to react) 15 | --file the file to process instead of reading from stdin 16 | 17 | Examples 18 | $ cat |react2dts --module-name module-name 19 | 20 | $ cat |react2dts --top-level-module 21 | 22 | $ react2dts --top-level-module --file 23 | `, { 24 | flags: { 25 | 'module-name': { 26 | type: 'string', 27 | alias: 'name' 28 | }, 29 | 'top-level-module': { 30 | type: 'string' 31 | }, 32 | 'react-import': { 33 | type: 'string', 34 | default: 'react' 35 | }, 36 | 'file': { 37 | type: 'string' 38 | } 39 | } 40 | }); 41 | if (Object.keys(cli.flags).length === 0) { 42 | cli.showHelp(1); 43 | } 44 | 45 | react2dts.cli(cli.flags); 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-to-typescript-definitions", 3 | "version": "3.1.1", 4 | "description": "Create typescript definitions files (d.ts) from react components", 5 | "main": "dist/src/index.js", 6 | "types": "dist/src/index.d.ts", 7 | "bin": { 8 | "react2dts": "cli.js" 9 | }, 10 | "files": [ 11 | "dist", 12 | "cli.js", 13 | "index.d.ts" 14 | ], 15 | "scripts": { 16 | "fmt": "prettier --write '**/*.{json,md}' 'src/**/*.ts' 'tests/**/*-test.ts'", 17 | "linter": "tslint --project ./tsconfig.json", 18 | "start": "npm test", 19 | "clean": "rimraf dist", 20 | "prebuild": "npm run clean", 21 | "build": "tsc --sourceMap", 22 | "build:watch": "npm run build -- --watch", 23 | "build:inline": "tsc --inlineSourceMap", 24 | "pretest": "npm run clean && npm run build:inline", 25 | "test": "nyc ava", 26 | "coverage": "nyc report --reporter=lcov && codecov", 27 | "prerelease": "npm test && npm run build", 28 | "release": "standard-version", 29 | "postrelease": "git push --follow-tags origin master && npm publish" 30 | }, 31 | "author": { 32 | "name": "Markus Wolf", 33 | "email": "knister.peter@shadowrun-clan.de" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "KnisterPeter/react-to-typescript-definitions" 38 | }, 39 | "license": "MIT", 40 | "devDependencies": { 41 | "@knisterpeter/standard-tslint": "1.7.2", 42 | "@types/babel-generator": "6.25.7", 43 | "@types/diff": "5.0.7", 44 | "@types/execa": "0.9.0", 45 | "@types/node": "20.8.10", 46 | "@types/prop-types": "15.7.9", 47 | "@types/react": "18.2.36", 48 | "ava": "4.3.3", 49 | "babel-core": "6.26.3", 50 | "babel-preset-es2015": "6.24.1", 51 | "babel-register": "6.26.0", 52 | "chokidar-cli": "3.0.0", 53 | "codecov": "3.8.3", 54 | "coveralls": "3.1.1", 55 | "diff": "5.1.0", 56 | "execa": "5.1.1", 57 | "nyc": "15.1.0", 58 | "prettier": "2.8.8", 59 | "prop-types": "15.8.1", 60 | "react": "18.2.0", 61 | "rimraf": "3.0.2", 62 | "standard-version": "9.5.0", 63 | "tslint": "6.1.3", 64 | "typescript": "4.9.5" 65 | }, 66 | "dependencies": { 67 | "astq": "2.7.9", 68 | "babel-generator": "6.26.1", 69 | "babylon": "7.0.0-beta.47", 70 | "chalk": "4.1.2", 71 | "dts-dom": "3.6.0", 72 | "get-stdin": "8.0.0", 73 | "meow": "8.1.2", 74 | "pascal-case": "3.1.2", 75 | "strip-ansi": "6.0.1" 76 | }, 77 | "ava": { 78 | "files": [ 79 | "dist/tests/**/*-test.js" 80 | ], 81 | "source": [ 82 | "dist/src/**/*.js" 83 | ] 84 | }, 85 | "nyc": { 86 | "all": true, 87 | "cache": true, 88 | "exclude": [ 89 | "wallaby.conf.js", 90 | "node_modules", 91 | "coverage", 92 | "dist/tests", 93 | "tests", 94 | "dist/src/deprecated.js", 95 | "dist/src/analyzer.js" 96 | ] 97 | }, 98 | "prettier": { 99 | "singleQuote": true 100 | }, 101 | "renovate": { 102 | "lockFileMaintenance": { 103 | "enabled": true, 104 | "automerge": true 105 | }, 106 | "packageRules": [ 107 | { 108 | "depTypeList": [ 109 | "devDependencies" 110 | ], 111 | "updateTypes": [ 112 | "minor", 113 | "patch" 114 | ], 115 | "automerge": true 116 | }, 117 | { 118 | "packagePatterns": [ 119 | "^@types/" 120 | ], 121 | "automerge": true 122 | } 123 | ] 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/analyzer.ts: -------------------------------------------------------------------------------- 1 | import ASTQ from 'astq'; 2 | 3 | import { IPropTypes, IProp } from './deprecated'; 4 | import { InstanceOfResolver } from './index'; 5 | 6 | type IASTNode = ASTNode; 7 | interface ASTNode { 8 | type: string; 9 | loc: object; 10 | [name: string]: any; 11 | value?: any; 12 | key?: any; 13 | expression?: any; 14 | id?: any; 15 | body?: any; 16 | } 17 | 18 | const defaultInstanceOfResolver: InstanceOfResolver = ( 19 | _name: string 20 | ): undefined => undefined; 21 | 22 | export function parsePropTypes( 23 | node: any, 24 | instanceOfResolver?: InstanceOfResolver 25 | ): IPropTypes { 26 | const astq = new ASTQ(); 27 | astq.adapter('mozast', true); 28 | return astq 29 | .query(node, `/ObjectProperty`) 30 | .reduce((propTypes: IPropTypes, propertyNode: IASTNode) => { 31 | const prop: IProp = getTypeFromPropType( 32 | propertyNode.value, 33 | instanceOfResolver 34 | ); 35 | prop.documentation = getOptionalDocumentation(propertyNode); 36 | propTypes[propertyNode.key.name] = prop; 37 | return propTypes; 38 | }, {}); 39 | } 40 | 41 | function getOptionalDocumentation(propertyNode: any): string { 42 | return ( 43 | ((propertyNode.leadingComments || []) as any[]).filter( 44 | (comment) => comment.type === 'CommentBlock' 45 | )[0] || {} 46 | ).value; 47 | } 48 | 49 | /** 50 | * @internal 51 | */ 52 | // tslint:disable:next-line cyclomatic-complexity 53 | export function getTypeFromPropType( 54 | node: IASTNode, 55 | instanceOfResolver = defaultInstanceOfResolver 56 | ): IProp { 57 | const result: IProp = { 58 | type: 'any', 59 | optional: true, 60 | }; 61 | if (isNode(node)) { 62 | const { isRequired, type } = isRequiredPropType(node, instanceOfResolver); 63 | result.optional = !isRequired; 64 | switch (type.name) { 65 | case 'any': 66 | result.type = 'any'; 67 | break; 68 | case 'array': 69 | result.type = (type.arrayType || 'any') + '[]'; 70 | break; 71 | case 'bool': 72 | result.type = 'boolean'; 73 | break; 74 | case 'func': 75 | result.type = '(...args: any[]) => any'; 76 | break; 77 | case 'number': 78 | result.type = 'number'; 79 | break; 80 | case 'object': 81 | result.type = 'Object'; 82 | break; 83 | case 'string': 84 | result.type = 'string'; 85 | break; 86 | case 'node': 87 | result.type = 'React.ReactNode'; 88 | break; 89 | case 'element': 90 | result.type = 'React.ReactElement'; 91 | break; 92 | case 'union': 93 | result.type = type.types 94 | .map((unionType: string) => unionType) 95 | .join('|'); 96 | break; 97 | case 'instanceOf': 98 | if (type.importPath) { 99 | result.type = type.type; 100 | result.importPath = type.importPath; 101 | } else { 102 | result.type = 'any'; 103 | } 104 | break; 105 | } 106 | } 107 | return result; 108 | } 109 | 110 | function isNode(obj: IASTNode): boolean { 111 | return ( 112 | obj && typeof obj.type !== 'undefined' && typeof obj.loc !== 'undefined' 113 | ); 114 | } 115 | 116 | function getReactPropTypeFromExpression( 117 | node: any, 118 | instanceOfResolver: InstanceOfResolver 119 | ): any { 120 | if ( 121 | node.type === 'MemberExpression' && 122 | node.object.type === 'MemberExpression' && 123 | node.object.object.name === 'React' && 124 | node.object.property.name === 'PropTypes' 125 | ) { 126 | return node.property; 127 | } else if (node.type === 'CallExpression') { 128 | const callType = getReactPropTypeFromExpression( 129 | node.callee, 130 | instanceOfResolver 131 | ); 132 | switch (callType.name) { 133 | case 'instanceOf': 134 | return { 135 | name: 'instanceOf', 136 | type: node.arguments[0].name, 137 | importPath: instanceOfResolver(node.arguments[0].name), 138 | }; 139 | case 'arrayOf': 140 | const arrayType = getTypeFromPropType( 141 | node.arguments[0], 142 | instanceOfResolver 143 | ); 144 | return { 145 | name: 'array', 146 | arrayType: arrayType.type, 147 | }; 148 | case 'oneOfType': 149 | const unionTypes = node.arguments[0].elements.map((element: IASTNode) => 150 | getTypeFromPropType(element, instanceOfResolver) 151 | ); 152 | return { 153 | name: 'union', 154 | types: unionTypes.map((type: any) => type.type), 155 | }; 156 | } 157 | } 158 | return 'undefined'; 159 | } 160 | 161 | function isRequiredPropType( 162 | node: any, 163 | instanceOfResolver: InstanceOfResolver 164 | ): any { 165 | const isRequired = 166 | node.type === 'MemberExpression' && node.property.name === 'isRequired'; 167 | return { 168 | isRequired, 169 | type: getReactPropTypeFromExpression( 170 | isRequired ? node.object : node, 171 | instanceOfResolver 172 | ), 173 | }; 174 | } 175 | -------------------------------------------------------------------------------- /src/deprecated.ts: -------------------------------------------------------------------------------- 1 | import ASTQ from 'astq'; 2 | import { parsePropTypes } from './analyzer'; 3 | import { Generator } from './generator'; 4 | import { InstanceOfResolver, IOptions } from './index'; 5 | 6 | export enum ExportType { 7 | default, 8 | named, 9 | } 10 | 11 | // tslint:disable:next-line interface-name 12 | export interface IProp { 13 | type: string; 14 | optional: boolean; 15 | importPath?: string; 16 | documentation?: string; 17 | } 18 | 19 | // tslint:disable:next-line interface-name 20 | export interface IPropTypes { 21 | [name: string]: IProp; 22 | } 23 | 24 | export function generateTypings( 25 | moduleName: string | null, 26 | ast: any, 27 | options: IOptions 28 | ): string { 29 | const parsingResult = parseAst(ast, options.instanceOfResolver); 30 | return deprecatedGenerator( 31 | // tslint:disable-next-line: deprecation 32 | options.generator as Generator, 33 | moduleName, 34 | parsingResult 35 | ); 36 | } 37 | 38 | function deprecatedGenerator( 39 | generator: Generator, 40 | moduleName: string | null, 41 | { exportType, classname, propTypes }: IParsingResult 42 | ): string { 43 | const componentName = classname || 'Anonymous'; 44 | const generateTypings = () => { 45 | generator.import('* as React', 'react'); 46 | if (propTypes) { 47 | Object.keys(propTypes).forEach((propName) => { 48 | const prop = propTypes[propName]; 49 | if (prop.importPath) { 50 | generator.import(prop.type, prop.importPath); 51 | } 52 | }); 53 | } 54 | generator.nl(); 55 | generator.props(componentName, propTypes); 56 | generator.nl(); 57 | generator.exportDeclaration(exportType, () => { 58 | generator.class(componentName, !!propTypes); 59 | }); 60 | }; 61 | 62 | if (moduleName === null) { 63 | generateTypings(); 64 | } else { 65 | generator.declareModule(moduleName, generateTypings); 66 | } 67 | return generator.toString(); 68 | } 69 | 70 | /** 71 | * @internal 72 | */ 73 | export interface IParsingResult { 74 | exportType: ExportType; 75 | classname: string | undefined; 76 | functionname: string | undefined; 77 | propTypes: IPropTypes; 78 | } 79 | 80 | // tslint:disable:next-line cyclomatic-complexity 81 | function parseAst( 82 | ast: any, 83 | instanceOfResolver?: InstanceOfResolver 84 | ): IParsingResult { 85 | let exportType: ExportType | undefined; 86 | let functionname: string | undefined; 87 | let propTypes: IPropTypes | undefined; 88 | 89 | let classname = getClassName(ast); 90 | if (classname) { 91 | propTypes = getEs7StyleClassPropTypes(ast, classname, instanceOfResolver); 92 | exportType = getClassExportType(ast, classname); 93 | } 94 | if (!propTypes) { 95 | const componentName = getComponentNameByPropTypeAssignment(ast); 96 | if (componentName) { 97 | const astq = new ASTQ(); 98 | const exportTypeNodes = astq.query( 99 | ast, 100 | ` 101 | //ExportNamedDeclaration // VariableDeclarator[ 102 | /:id Identifier[@name=='${componentName}'] && 103 | /:init ArrowFunctionExpression // JSXElement 104 | ], 105 | //ExportNamedDeclaration // FunctionDeclaration[/:id Identifier[@name == '${componentName}']] // JSXElement, 106 | //ExportDefaultDeclaration // AssignmentExpression[/:left Identifier[@name == '${componentName}']] 107 | // ArrowFunctionExpression // JSXElement, 108 | //ExportDefaultDeclaration // FunctionDeclaration[/:id Identifier[@name == '${componentName}']] // JSXElement 109 | ` 110 | ); 111 | if (exportTypeNodes.length > 0) { 112 | functionname = componentName; 113 | exportType = ExportType.named; 114 | } 115 | propTypes = getPropTypesFromAssignment( 116 | ast, 117 | componentName, 118 | instanceOfResolver 119 | ); 120 | } 121 | if (!exportType) { 122 | const astq = new ASTQ(); 123 | const commonJsExports = astq.query( 124 | ast, 125 | ` 126 | // AssignmentExpression[ 127 | /:left MemberExpression[ 128 | /:object Identifier[@name == 'exports'] && 129 | /:property Identifier[@name == 'default'] 130 | ] && 131 | /:right Identifier[@name == '${componentName}'] 132 | ] 133 | ` 134 | ); 135 | if (commonJsExports.length > 0) { 136 | classname = componentName; 137 | exportType = ExportType.default; 138 | } 139 | } 140 | } 141 | 142 | if (exportType === undefined) { 143 | throw new Error('No exported component found'); 144 | } 145 | return { 146 | exportType, 147 | classname, 148 | functionname, 149 | propTypes: propTypes || {}, 150 | }; 151 | } 152 | 153 | function getClassName(ast: any): string | undefined { 154 | const astq = new ASTQ(); 155 | astq.adapter('mozast', true); 156 | const classDeclarationNodes = astq.query( 157 | ast, 158 | ` 159 | //ClassDeclaration[ 160 | /:id Identifier[@name] 161 | ] 162 | ` 163 | ); 164 | if (classDeclarationNodes.length > 0) { 165 | return classDeclarationNodes[0].id.name; 166 | } 167 | return undefined; 168 | } 169 | 170 | function getEs7StyleClassPropTypes( 171 | ast: any, 172 | classname: string, 173 | instanceOfResolver?: InstanceOfResolver 174 | ): IPropTypes | undefined { 175 | const astq = new ASTQ(); 176 | const propTypesNodes = astq.query( 177 | ast, 178 | ` 179 | //ClassDeclaration[/:id Identifier[@name == '${classname}']] 180 | //ClassProperty[/:key Identifier[@name == 'propTypes']] 181 | ` 182 | ); 183 | if (propTypesNodes.length > 0) { 184 | return parsePropTypes(propTypesNodes[0].value, instanceOfResolver); 185 | } 186 | return undefined; 187 | } 188 | 189 | function getClassExportType( 190 | ast: any, 191 | classname: string 192 | ): ExportType | undefined { 193 | const astq = new ASTQ(); 194 | astq.adapter('mozast', true); 195 | const exportTypeNodes = astq.query( 196 | ast, 197 | ` 198 | //ExportNamedDeclaration [ 199 | /ClassDeclaration [ /:id Identifier[@name=='${classname}'] ] 200 | ], 201 | //ExportDefaultDeclaration [ 202 | /ClassDeclaration [ /:id Identifier[@name=='${classname}'] ] 203 | ] 204 | ` 205 | ); 206 | if (exportTypeNodes.length > 0) { 207 | return exportTypeNodes[0].type === 'ExportDefaultDeclaration' 208 | ? ExportType.default 209 | : ExportType.named; 210 | } 211 | return undefined; 212 | } 213 | 214 | function getComponentNameByPropTypeAssignment(ast: any): string | undefined { 215 | const astq = new ASTQ(); 216 | const componentNames = astq.query( 217 | ast, 218 | ` 219 | //AssignmentExpression 220 | /:left MemberExpression[ 221 | /:object Identifier && 222 | /:property Identifier[@name == 'propTypes'] 223 | ] 224 | ` 225 | ); 226 | if (componentNames.length > 0) { 227 | return componentNames[0].object.name; 228 | } 229 | return undefined; 230 | } 231 | 232 | function getPropTypesFromAssignment( 233 | ast: any, 234 | componentName: string, 235 | instanceOfResolver?: InstanceOfResolver 236 | ): IPropTypes | undefined { 237 | const astq = new ASTQ(); 238 | const propTypesNodes = astq.query( 239 | ast, 240 | ` 241 | //AssignmentExpression[ 242 | /:left MemberExpression[ 243 | /:object Identifier[@name == '${componentName}'] && 244 | /:property Identifier[@name == 'propTypes'] 245 | ] 246 | ] /:right * 247 | ` 248 | ); 249 | if (propTypesNodes.length > 0) { 250 | return parsePropTypes(propTypesNodes[0], instanceOfResolver); 251 | } 252 | return undefined; 253 | } 254 | -------------------------------------------------------------------------------- /src/generator.ts: -------------------------------------------------------------------------------- 1 | import { IPropTypes, ExportType } from './deprecated'; 2 | 3 | export class Generator { 4 | private static readonly NL = '\n'; 5 | 6 | private indentLevel = 0; 7 | 8 | private code = ''; 9 | 10 | private indent(): void { 11 | let result = ''; 12 | const n = this.indentLevel; 13 | for (let i = 0; i < n; i++) { 14 | result += '\t'; 15 | } 16 | this.code += result; 17 | } 18 | 19 | public nl(): void { 20 | this.code += Generator.NL; 21 | } 22 | 23 | public declareModule(name: string, fn: () => void): void { 24 | this.indent(); 25 | this.code += `declare module '${name}' {`; 26 | this.nl(); 27 | this.indentLevel++; 28 | fn(); 29 | this.indentLevel--; 30 | this.indent(); 31 | this.code += '}'; 32 | this.nl(); 33 | } 34 | 35 | public import(decl: string, from: string, fn?: () => void): void { 36 | this.indent(); 37 | this.code += `import ${decl} from '${from}';`; 38 | this.nl(); 39 | if (fn) { 40 | fn(); 41 | } 42 | } 43 | 44 | public props(name: string, props: IPropTypes, fn?: () => void): void { 45 | this.interface(`${name}Props`, () => { 46 | Object.keys(props).forEach((propName) => { 47 | const prop = props[propName]; 48 | this.prop(propName, prop.type, prop.optional, prop.documentation); 49 | }); 50 | }); 51 | if (fn) { 52 | fn(); 53 | } 54 | } 55 | 56 | public prop( 57 | name: string, 58 | type: string, 59 | optional: boolean, 60 | documentation?: string 61 | ): void { 62 | this.indent(); 63 | if (documentation) { 64 | this.comment(documentation); 65 | } 66 | this.code += `${name}${optional ? '?' : ''}: ${type};`; 67 | this.nl(); 68 | } 69 | 70 | public comment(comment: string): void { 71 | this.code += '/*'; 72 | const lines = (comment || '').replace(/\t/g, '').split(/\n/g); 73 | lines.forEach((line, index) => { 74 | this.code += line; 75 | if (index < lines.length - 1) { 76 | this.nl(); 77 | this.indent(); 78 | } 79 | }); 80 | this.code += '*/'; 81 | this.nl(); 82 | this.indent(); 83 | } 84 | 85 | public interface(name: string, fn: () => void): void { 86 | this.indent(); 87 | this.code += `export interface ${name} {`; 88 | this.nl(); 89 | this.indentLevel++; 90 | fn(); 91 | this.indentLevel--; 92 | this.indent(); 93 | this.code += '}'; 94 | this.nl(); 95 | } 96 | 97 | public exportDeclaration(exportType: ExportType, fn: () => void): void { 98 | this.indent(); 99 | this.code += 'export '; 100 | if (exportType === ExportType.default) { 101 | this.code += 'default '; 102 | } 103 | fn(); 104 | } 105 | 106 | public class(name: string, props: boolean, fn?: () => void): void { 107 | this.code += `class ${name} extends React.Component<${ 108 | props ? `${name}Props` : 'any' 109 | }, any> {`; 110 | this.nl(); 111 | this.indentLevel++; 112 | if (fn) { 113 | fn(); 114 | } 115 | this.indentLevel--; 116 | this.indent(); 117 | this.code += '}'; 118 | this.nl(); 119 | } 120 | 121 | public toString(): string { 122 | return this.code; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as babylon from 'babylon'; 2 | import * as fs from 'fs'; 3 | import getStdin from 'get-stdin'; 4 | import { generateTypings } from './deprecated'; 5 | import { Generator } from './generator'; 6 | import { createTypings } from './typings'; 7 | 8 | export interface InstanceOfResolver { 9 | (name: string): string | undefined; 10 | } 11 | 12 | // the IOptions is for backward compatibility 13 | export type IOptions = Options; 14 | export interface Options { 15 | /** 16 | * Resolves type names to import paths. 17 | * 18 | * @return Path to given name if resolveable, undefined otherwise 19 | */ 20 | instanceOfResolver?: InstanceOfResolver; 21 | 22 | /** 23 | * The Generator generating .d.ts code with. 24 | * 25 | * This option is deprecated with 0.13 and is not supported anymore. 26 | * any new feature will not work with the deprecated Generator interface. 27 | * @deprecated 28 | */ 29 | generator?: Generator; 30 | 31 | /** 32 | * Could be given if the generator is started with an AST. 33 | * 34 | * This is helpful to create better messages in case of errors/warnings. 35 | * 36 | * @type {string} 37 | * @memberOf Options 38 | */ 39 | source?: string; 40 | 41 | /** 42 | * Could be given to show filename in error/warning messages. 43 | * 44 | * @type {string} 45 | * @memberOf Options 46 | */ 47 | filename?: string; 48 | 49 | /** 50 | * EOL character. This would be changed to whatever is liked to 51 | * terminate lines. Defaults to '\r\n' 52 | * 53 | * @type {string} 54 | * @memberOf Options 55 | */ 56 | eol?: string; 57 | 58 | /** 59 | * babylon plugins. Allow users to set additional plugins. 60 | * 61 | * @type {string[]} 62 | * @memberOf Options 63 | */ 64 | babylonPlugins?: string[]; 65 | } 66 | 67 | export function cli(options: any): void { 68 | const processInput = (code: string) => { 69 | const result = generateFromSource( 70 | options.topLevelModule ? null : options.moduleName, 71 | code, 72 | {}, 73 | options.reactImport 74 | ); 75 | process.stdout.write(result); 76 | }; 77 | if (options.file) { 78 | fs.readFile(options.file, (err, data) => { 79 | if (err) { 80 | throw err; 81 | } 82 | processInput(data.toString()); 83 | }); 84 | } else { 85 | getStdin().then(processInput); 86 | } 87 | } 88 | 89 | export function generateFromFile( 90 | moduleName: string | null, 91 | path: string, 92 | options: IOptions = {}, 93 | reactImport = 'react' 94 | ): string { 95 | if (!options.filename) { 96 | options.filename = path; 97 | } 98 | return generateFromSource( 99 | moduleName, 100 | fs.readFileSync(path).toString(), 101 | options, 102 | reactImport 103 | ); 104 | } 105 | 106 | export function generateFromSource( 107 | moduleName: string | null, 108 | code: string, 109 | options: IOptions = {}, 110 | reactImport = 'react' 111 | ): string { 112 | const additionalBabylonPlugins = Array.isArray(options.babylonPlugins) 113 | ? options.babylonPlugins 114 | : []; 115 | const ast = babylon.parse(code, { 116 | sourceType: 'module', 117 | allowReturnOutsideFunction: true, 118 | allowImportExportEverywhere: true, 119 | allowSuperOutsideMethod: true, 120 | plugins: [ 121 | 'jsx', 122 | 'flow', 123 | 'asyncFunctions', 124 | 'classConstructorCall', 125 | 'doExpressions', 126 | 'trailingFunctionCommas', 127 | 'objectRestSpread', 128 | 'decorators', 129 | 'classProperties', 130 | 'exportExtensions', 131 | 'exponentiationOperator', 132 | 'asyncGenerators', 133 | 'functionBind', 134 | 'functionSent', 135 | 'optionalChaining', 136 | ...additionalBabylonPlugins, 137 | ], 138 | }); 139 | if (!options.source) { 140 | options.source = code; 141 | } 142 | return generateFromAst(moduleName, ast, options, reactImport); 143 | } 144 | 145 | export function generateFromAst( 146 | moduleName: string | null, 147 | ast: any, 148 | options: IOptions = {}, 149 | reactImport = 'react' 150 | ): string { 151 | // tslint:disable-next-line:deprecation 152 | if (options.generator) { 153 | return generateTypings(moduleName, ast, options); 154 | } 155 | return createTypings(moduleName, ast, options, reactImport); 156 | } 157 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import astToCode from 'babel-generator'; 2 | import chalk from 'chalk'; 3 | import * as dom from 'dts-dom'; 4 | import { IOptions } from './index'; 5 | import { 6 | AstQuery, 7 | ImportedPropTypes, 8 | propTypeQueryExpression, 9 | } from './typings'; 10 | 11 | export interface TypeDeclaration { 12 | type: any; 13 | optional: boolean; 14 | } 15 | 16 | function getTypeDeclaration(type: any, optional: boolean): TypeDeclaration { 17 | return { 18 | type, 19 | optional, 20 | }; 21 | } 22 | 23 | export function get( 24 | ast: AstQuery, 25 | propertyAst: any, 26 | importedPropTypes: ImportedPropTypes, 27 | options: IOptions 28 | ): TypeDeclaration { 29 | try { 30 | const simpleType = getSimpleType(ast, propertyAst, importedPropTypes); 31 | if (simpleType) { 32 | return simpleType; 33 | } 34 | const complexType = getComplexType( 35 | ast, 36 | propertyAst, 37 | importedPropTypes, 38 | options 39 | ); 40 | if (complexType) { 41 | return complexType; 42 | } 43 | } catch (e) { 44 | if (e.loc) { 45 | printErrorWithContext(e, ast.ast, options); 46 | } else { 47 | console.error('Failed to infer PropType; Fallback to any'); 48 | console.error(e.stack); 49 | } 50 | } 51 | return { 52 | type: 'any', 53 | optional: true, 54 | }; 55 | } 56 | 57 | // tslint:disable:next-line cyclomatic-complexity 58 | function getSimpleType( 59 | ast: AstQuery, 60 | propertyAst: any, 61 | importedPropTypes: ImportedPropTypes 62 | ): TypeDeclaration | undefined { 63 | const [required, simpleTypeName] = getSimpleTypeName( 64 | ast, 65 | propertyAst, 66 | importedPropTypes 67 | ); 68 | switch (simpleTypeName) { 69 | case 'any': 70 | return getTypeDeclaration('any', !required); 71 | case 'array': 72 | return getTypeDeclaration(dom.create.array('any'), !required); 73 | case 'bool': 74 | return getTypeDeclaration('boolean', !required); 75 | case 'func': 76 | return getTypeDeclaration( 77 | dom.create.functionType( 78 | [ 79 | dom.create.parameter( 80 | 'args', 81 | dom.create.array('any'), 82 | dom.ParameterFlags.Rest 83 | ), 84 | ], 85 | 'any' 86 | ), 87 | !required 88 | ); 89 | case 'number': 90 | return getTypeDeclaration('number', !required); 91 | case 'object': 92 | return getTypeDeclaration( 93 | dom.create.namedTypeReference('Object'), 94 | !required 95 | ); 96 | case 'string': 97 | return getTypeDeclaration('string', !required); 98 | case 'node': 99 | return getTypeDeclaration( 100 | dom.create.namedTypeReference('React.ReactNode'), 101 | !required 102 | ); 103 | case 'element': 104 | return getTypeDeclaration( 105 | dom.create.namedTypeReference('React.ReactElement'), 106 | !required 107 | ); 108 | case 'symbol': 109 | return getTypeDeclaration( 110 | dom.create.namedTypeReference('Symbol'), 111 | !required 112 | ); 113 | } 114 | return undefined; 115 | } 116 | 117 | function getComplexType( 118 | ast: AstQuery, 119 | propertyAst: any, 120 | importedPropTypes: ImportedPropTypes, 121 | options: IOptions 122 | ): TypeDeclaration | undefined { 123 | const [required, complexTypeName, typeAst] = getComplexTypeName( 124 | ast, 125 | propertyAst, 126 | importedPropTypes 127 | ); 128 | switch (complexTypeName) { 129 | case 'instanceOf': 130 | return getTypeDeclaration( 131 | dom.create.namedTypeReference(typeAst.arguments[0].name), 132 | !required 133 | ); 134 | case 'oneOfType': 135 | const typeDecls = typeAst.arguments[0].elements.map((subtree: any) => 136 | get(ast, subtree, importedPropTypes, options) 137 | ) as TypeDeclaration[]; 138 | return getTypeDeclaration( 139 | dom.create.union(typeDecls.map((type) => type.type)), 140 | !required 141 | ); 142 | case 'arrayOf': 143 | const typeDecl = get( 144 | ast, 145 | typeAst.arguments[0], 146 | importedPropTypes, 147 | options 148 | ); 149 | return getTypeDeclaration(dom.create.array(typeDecl.type), !required); 150 | case 'oneOf': 151 | // tslint:disable:next-line comment-format 152 | // FIXME: This should better be a real enum 153 | const enumEntries = getEnumValues(ast, typeAst.arguments[0]); 154 | return getTypeDeclaration( 155 | dom.create.union(enumEntries as dom.Type[]), 156 | !required 157 | ); 158 | case 'shape': 159 | const entries = getShapeProperties(ast, typeAst.arguments[0]).map( 160 | (entry: any) => { 161 | const typeDecl = get(ast, entry.value, importedPropTypes, options); 162 | return dom.create.property( 163 | entry.key.type === 'StringLiteral' 164 | ? `${entry.key.value}` 165 | : entry.key.name, 166 | typeDecl.type, 167 | typeDecl.optional 168 | ? dom.DeclarationFlags.Optional 169 | : dom.DeclarationFlags.None 170 | ); 171 | } 172 | ); 173 | return getTypeDeclaration(dom.create.objectType(entries), !required); 174 | } 175 | return undefined; 176 | } 177 | 178 | function isRequired(ast: AstQuery, propertyAst: any): [boolean, any] { 179 | const required = ast.querySubtree( 180 | propertyAst, 181 | ` 182 | MemberExpression /:property Identifier[@name == 'isRequired'] 183 | ` 184 | ); 185 | if (required.length > 0) { 186 | return [true, propertyAst.object]; 187 | } 188 | return [false, propertyAst]; 189 | } 190 | 191 | function getSimpleTypeName( 192 | ast: AstQuery, 193 | propertyAst: any, 194 | importedPropTypes: ImportedPropTypes 195 | ): [boolean, string | undefined] { 196 | const { propTypesName, propTypes } = importedPropTypes; 197 | const [required, typeAst] = isRequired(ast, propertyAst); 198 | 199 | if (!propTypesName && typeAst.type === 'Identifier') { 200 | const propType = propTypes.find( 201 | ({ localName }) => localName === typeAst.name 202 | ); 203 | return [required, propType ? propType.importedName : undefined]; 204 | } 205 | 206 | const res = ast.querySubtree( 207 | typeAst, 208 | ` 209 | MemberExpression[ 210 | (${propTypeQueryExpression(propTypesName)}) 211 | && 212 | /:property Identifier 213 | ] 214 | /:property Identifier 215 | ` 216 | ); 217 | 218 | return [required, res.length > 0 ? res[0].name : undefined]; 219 | } 220 | 221 | function getComplexTypeName( 222 | ast: AstQuery, 223 | propertyAst: any, 224 | importedPropTypes: ImportedPropTypes 225 | ): [boolean, string | undefined, any] { 226 | const [required, typeAst] = isRequired(ast, propertyAst); 227 | if (typeAst.type === 'CallExpression') { 228 | const [, simpleTypeName] = getSimpleTypeName( 229 | ast, 230 | typeAst.callee, 231 | importedPropTypes 232 | ); 233 | return [required, simpleTypeName, typeAst]; 234 | } 235 | return [required, undefined, typeAst]; 236 | } 237 | 238 | function getEnumValues(ast: AstQuery, oneOfTypes: any): any[] { 239 | oneOfTypes = resolveIdentifier(ast, oneOfTypes); 240 | 241 | if (!oneOfTypes.elements) { 242 | throwWithLocation('Failed to lookup enum values', oneOfTypes); 243 | } 244 | 245 | return (oneOfTypes.elements as any[]).map((element: any) => { 246 | element = resolveIdentifier(ast, element); 247 | if (element.type === 'StringLiteral') { 248 | return dom.type.stringLiteral(element.value); 249 | } 250 | if (element.type === 'NumericLiteral') { 251 | return dom.type.numberLiteral(element.value); 252 | } 253 | return 'any'; 254 | }); 255 | } 256 | 257 | function getShapeProperties(ast: AstQuery, input: any): any[] { 258 | input = resolveIdentifier(ast, input); 259 | 260 | if (!input.properties) { 261 | throwWithLocation('Failed to lookup shape properties', input); 262 | } 263 | 264 | return input.properties; 265 | } 266 | 267 | function resolveIdentifier(ast: AstQuery, input: any): any { 268 | if (input.type !== 'Identifier') { 269 | return input; 270 | } 271 | 272 | const res = ast.query(` 273 | //VariableDeclarator[ 274 | /:id Identifier[@name == '${input.name}'] 275 | ] 276 | /:init * 277 | `); 278 | if (!res[0]) { 279 | throwWithLocation('Failed to lookup identifier', input); 280 | } 281 | return res[0]; 282 | } 283 | 284 | function throwWithLocation(message: string, ast: any): never { 285 | const error = new Error(message); 286 | (error as any).loc = ast.loc; 287 | (error as any).start = ast.start; 288 | (error as any).end = ast.end; 289 | throw error; 290 | } 291 | 292 | function printErrorWithContext(e: any, ast: any, options: IOptions): void { 293 | console.error(`${options.filename || ''} ${e.message}`); 294 | const src = options.source || astToCode(ast.ast).code; 295 | // console.log(src.substring(e.start, e.end)); 296 | const lines = src.split('\n'); 297 | const errorLine = lines[e.loc.start.line - 1]; 298 | 299 | console.error(`Line ${e.loc.start.line - 1}: ${lines[e.loc.start.line - 2]}`); 300 | // tslint:disable-next-line prefer-template 301 | console.error( 302 | `Line ${e.loc.start.line}: ${errorLine.substring( 303 | 0, 304 | e.loc.start.column 305 | )}${chalk.red( 306 | errorLine.substring(e.loc.start.column, e.loc.end.column) 307 | )}${errorLine.substring(e.loc.end.column)}` 308 | ); 309 | console.error(`Line ${e.loc.start.line + 1}: ${lines[e.loc.start.line]}`); 310 | console.error(); 311 | } 312 | -------------------------------------------------------------------------------- /src/typings.ts: -------------------------------------------------------------------------------- 1 | import ASTQ from 'astq'; 2 | import * as dom from 'dts-dom'; 3 | import { pascalCase } from 'pascal-case'; 4 | import { InstanceOfResolver, IOptions } from './index'; 5 | import * as types from './types'; 6 | 7 | export interface AstQuery { 8 | ast: any; 9 | query(query: string): any[]; 10 | querySubtree(subtree: any, query: string): any[]; 11 | } 12 | 13 | export interface ImportedPropType { 14 | importedName: string; 15 | localName: string; 16 | } 17 | 18 | export interface ImportedPropTypes { 19 | propTypesName: string | undefined; 20 | propTypes: ImportedPropType[]; 21 | } 22 | 23 | export function createTypings( 24 | moduleName: string | null, 25 | programAst: any, 26 | options: IOptions, 27 | reactImport: string 28 | ): string { 29 | // #609: configure eol character 30 | dom.config.outputEol = options.eol || '\r\n'; 31 | 32 | const astq = new ASTQ(); 33 | astq.adapter('mozast', true); 34 | const ast = { 35 | ast: programAst, 36 | query(query: string): any[] { 37 | return astq.query(programAst, query); 38 | }, 39 | querySubtree(subtree: any, query: string): any[] { 40 | return astq.query(subtree, query); 41 | }, 42 | }; 43 | const reactComponentName = getReactComponentName(ast); 44 | const importedPropTypes: ImportedPropTypes = { 45 | propTypesName: getPropTypesName(ast), 46 | propTypes: getImportedPropTypes(ast), 47 | }; 48 | const importedTypes = getInstanceOfPropTypes(ast, importedPropTypes); 49 | const importStatements = getImportStatements( 50 | ast, 51 | importedTypes, 52 | options.instanceOfResolver 53 | ); 54 | const componentNames = getUniqueNames([ 55 | ...getComponentNamesByPropTypeAssignment(ast), 56 | ...getComponentNamesByStaticPropTypeAttribute(ast), 57 | ...getComponentNamesByJsxInBody(ast), 58 | ]); 59 | const tripleSlashDirectives: dom.TripleSlashDirective[] = []; 60 | const m = dom.create.module(moduleName || 'moduleName'); 61 | 62 | m.members.push(dom.create.importAll('React', reactImport)); 63 | 64 | if (importStatements.length > 0) { 65 | importStatements.forEach((importStatement) => { 66 | if (importStatement.name === undefined) { 67 | m.members.push( 68 | dom.create.importDefault(importStatement.local, importStatement.path) 69 | ); 70 | } else { 71 | throw new Error('Named imports are currently unsupported'); 72 | } 73 | }); 74 | } 75 | const alreadyDefined: string[] = []; 76 | 77 | const componentDots = getComponentDotProperties(ast, componentNames); 78 | componentNames.forEach((componentName) => { 79 | const exportType = getComponentExportType(ast, componentName); 80 | const propTypes = getPropTypes(ast, componentName); 81 | const intersection = getIntersection(componentDots, componentName); 82 | if (exportType || componentDots.length) { 83 | alreadyDefined.push(componentName); 84 | createExportedTypes( 85 | m, 86 | ast, 87 | componentName, 88 | reactComponentName, 89 | propTypes, 90 | importedPropTypes, 91 | exportType, 92 | intersection, 93 | options 94 | ); 95 | } 96 | }); 97 | 98 | // top level object variables 99 | const componentObject = getComponentNamesByObject(ast, componentNames); 100 | 101 | componentObject.forEach(({ name, properties }) => { 102 | const obj = dom.create.objectType([]); 103 | let hasType; 104 | 105 | Object.keys(properties).forEach((k) => { 106 | const { key, value } = properties[k]; 107 | // if a property matches an existing component 108 | // add it to the object definition 109 | if (value.type === 'Identifier' && componentNames.includes(value.name)) { 110 | const exportType = 111 | name === '_default' 112 | ? undefined 113 | : getComponentExportType(ast, value.name); 114 | const propTypes = getPropTypes(ast, value.name); 115 | const intersection = getIntersection(componentDots, name); 116 | 117 | // if it was exported individually, it will already have been typed earlier 118 | if (!alreadyDefined.includes(value.name)) { 119 | createExportedTypes( 120 | m, 121 | ast, 122 | value.name, 123 | reactComponentName, 124 | propTypes, 125 | importedPropTypes, 126 | exportType, 127 | intersection, 128 | options 129 | ); 130 | } 131 | 132 | if (propTypes) { 133 | hasType = true; 134 | const type1 = dom.create.namedTypeReference(value.name); 135 | const typeBase = dom.create.typeof(type1); 136 | const b = dom.create.property(key.name, typeBase); 137 | obj.members.push(b); 138 | } 139 | } 140 | }); 141 | if (hasType) { 142 | const exportType = getComponentExportType(ast, name); 143 | 144 | const objConst = dom.create.const(name, obj); 145 | m.members.push(objConst); 146 | 147 | if ( 148 | exportType === dom.DeclarationFlags.ExportDefault || 149 | name === '_default' 150 | ) { 151 | m.members.push(dom.create.exportDefault(name)); 152 | } else { 153 | objConst.flags = exportType; 154 | } 155 | } 156 | }); 157 | 158 | if (moduleName === null) { 159 | return m.members.map((member) => dom.emit(member)).join(''); 160 | } else { 161 | return dom.emit(m, { tripleSlashDirectives }); 162 | } 163 | } 164 | function getIntersection( 165 | componentDots: ComponentProperties[], 166 | componentName: string 167 | ): string | null { 168 | const intersection = componentDots.find((v) => v.name === componentName); 169 | if (intersection) { 170 | const types = intersection.properties.map( 171 | (prop: ComponentProperties['properties'][0]) => { 172 | return `\t\t${prop.key}: typeof ${prop.value};`; 173 | } 174 | ); 175 | 176 | return ` & { 177 | ${types.join('\n')} 178 | }`; 179 | } 180 | return null; 181 | } 182 | 183 | function createExportedTypes( 184 | m: dom.ModuleDeclaration, 185 | ast: AstQuery, 186 | componentName: string, 187 | reactComponentName: string | undefined, 188 | propTypes: any, 189 | importedPropTypes: ImportedPropTypes, 190 | exportType: dom.DeclarationFlags | undefined, 191 | intersection: any, 192 | options: IOptions 193 | ): void { 194 | const classComponent = isClassComponent( 195 | ast, 196 | componentName, 197 | reactComponentName 198 | ); 199 | 200 | const interf = dom.create.interface(`${componentName}Props`); 201 | interf.flags = dom.DeclarationFlags.Export; 202 | if (propTypes) { 203 | createPropTypeTypings(interf, ast, propTypes, importedPropTypes, options); 204 | extractComplexTypes(m, interf, componentName); 205 | } 206 | 207 | if (propTypes || classComponent) { 208 | m.members.push(interf); 209 | } 210 | if (classComponent) { 211 | createClassOrExportedClass( 212 | m, 213 | componentName, 214 | reactComponentName, 215 | exportType, 216 | interf 217 | ); 218 | } else { 219 | createFunctionalOrExportedFunctionalComponent( 220 | m, 221 | componentName, 222 | propTypes, 223 | exportType!, 224 | intersection, 225 | interf 226 | ); 227 | } 228 | } 229 | function createClassOrExportedClass( 230 | m: dom.ModuleDeclaration, 231 | componentName: string, 232 | reactComponentName: string | undefined, 233 | exportType: dom.DeclarationFlags | undefined, 234 | interf: dom.InterfaceDeclaration 235 | ): void { 236 | if (exportType) { 237 | createExportedClassComponent( 238 | m, 239 | componentName, 240 | reactComponentName, 241 | exportType, 242 | interf 243 | ); 244 | } else { 245 | createClassComponent(m, componentName, reactComponentName, interf); 246 | } 247 | } 248 | function createFunctionalOrExportedFunctionalComponent( 249 | m: dom.ModuleDeclaration, 250 | componentName: string, 251 | propTypes: any, 252 | exportType: dom.DeclarationFlags | undefined, 253 | intersection: any, 254 | interf: dom.InterfaceDeclaration 255 | ): void { 256 | if (exportType) { 257 | createExportedFunctionalComponent( 258 | m, 259 | componentName, 260 | propTypes, 261 | exportType, 262 | intersection, 263 | interf 264 | ); 265 | } else { 266 | createFunctionalComponent( 267 | m, 268 | componentName, 269 | propTypes, 270 | intersection, 271 | interf 272 | ); 273 | } 274 | } 275 | function createClassComponent( 276 | m: dom.ModuleDeclaration, 277 | componentName: string, 278 | reactComponentName: string | undefined, 279 | interf: dom.InterfaceDeclaration 280 | ): dom.ClassDeclaration { 281 | const classDecl = dom.create.class(componentName); 282 | classDecl.baseType = dom.create.interface( 283 | `React.${reactComponentName || 'Component'}<${interf.name}, any>` 284 | ); 285 | classDecl.members.push( 286 | dom.create.method( 287 | 'render', 288 | [], 289 | dom.create.namedTypeReference('JSX.Element') 290 | ) 291 | ); 292 | m.members.push(classDecl); 293 | return classDecl; 294 | } 295 | 296 | function createExportedClassComponent( 297 | m: dom.ModuleDeclaration, 298 | componentName: string, 299 | reactComponentName: string | undefined, 300 | exportType: dom.DeclarationFlags, 301 | interf: dom.InterfaceDeclaration 302 | ): void { 303 | const classDecl = createClassComponent( 304 | m, 305 | componentName, 306 | reactComponentName, 307 | interf 308 | ); 309 | classDecl.flags = exportType; 310 | } 311 | 312 | function createFunctionalComponent( 313 | m: dom.ModuleDeclaration, 314 | componentName: string, 315 | propTypes: any, 316 | intersection: any, 317 | interf: dom.InterfaceDeclaration 318 | ): dom.ConstDeclaration { 319 | const typeDecl = dom.create.namedTypeReference( 320 | `React.FC${propTypes ? `<${interf.name}>` : ''}${ 321 | intersection ? intersection : '' 322 | }` 323 | ); 324 | const constDecl = dom.create.const(componentName, typeDecl); 325 | m.members.push(constDecl); 326 | 327 | return constDecl; 328 | } 329 | 330 | function createExportedFunctionalComponent( 331 | m: dom.ModuleDeclaration, 332 | componentName: string, 333 | propTypes: any, 334 | exportType: dom.DeclarationFlags, 335 | intersection: any, 336 | interf: dom.InterfaceDeclaration 337 | ): void { 338 | const constDecl = createFunctionalComponent( 339 | m, 340 | componentName, 341 | propTypes, 342 | intersection, 343 | interf 344 | ); 345 | if (exportType === dom.DeclarationFlags.ExportDefault) { 346 | m.members.push(dom.create.exportDefault(componentName)); 347 | } else { 348 | constDecl.flags = exportType; 349 | } 350 | } 351 | 352 | function createPropTypeTypings( 353 | interf: dom.InterfaceDeclaration, 354 | ast: AstQuery, 355 | propTypes: any, 356 | importedPropTypes: ImportedPropTypes, 357 | options: IOptions 358 | ): void { 359 | const res = ast.querySubtree( 360 | propTypes, 361 | ` 362 | / ObjectProperty 363 | ` 364 | ); 365 | res.forEach((propertyAst) => { 366 | const typeDecl = types.get( 367 | ast, 368 | propertyAst.value, 369 | importedPropTypes, 370 | options 371 | ); 372 | const property = dom.create.property( 373 | propertyAst.key.name || propertyAst.key.value, 374 | typeDecl.type, 375 | typeDecl.optional ? dom.DeclarationFlags.Optional : 0 376 | ); 377 | if ( 378 | propertyAst.leadingComments && 379 | propertyAst.leadingComments[0].type === 'CommentBlock' 380 | ) { 381 | const trimLines = (): ((line: string) => boolean) => { 382 | return (line: string) => Boolean(line); 383 | }; 384 | property.jsDocComment = (propertyAst.leadingComments[0].value as string) 385 | .split('\n') 386 | .map((line) => line.trim()) 387 | .map((line) => line.replace(/^\*\*?/, '')) 388 | .map((line) => line.trim()) 389 | .filter(trimLines()) 390 | .reverse() 391 | .filter(trimLines()) 392 | .reverse() 393 | .join('\n'); 394 | } 395 | interf.members.push(property); 396 | }); 397 | } 398 | 399 | function extractComplexTypes( 400 | m: dom.ModuleDeclaration, 401 | interf: dom.InterfaceDeclaration, 402 | componentName: string 403 | ): void { 404 | interf.members.forEach((member) => { 405 | if (member.kind === 'property' && isExtractableType(member.type)) { 406 | const name = `${componentName}${pascalCase(member.name)}`; 407 | const extractedMember = createModuleMember(name, member.type); 408 | if (extractedMember) { 409 | extractedMember.flags = dom.DeclarationFlags.Export; 410 | m.members.push(extractedMember); 411 | member.type = createTypeReference(name, member.type); 412 | } 413 | } 414 | }); 415 | } 416 | 417 | type ExtractableType = 418 | | dom.UnionType 419 | | dom.IntersectionType 420 | | dom.ObjectType 421 | | dom.ArrayTypeReference; 422 | 423 | function isExtractableType(type: dom.Type): type is ExtractableType { 424 | if (typeof type === 'object') { 425 | return ['union', 'intersection', 'object', 'array'].indexOf(type.kind) > -1; 426 | } 427 | return false; 428 | } 429 | 430 | function createModuleMember( 431 | name: string, 432 | type: ExtractableType 433 | ): dom.ModuleMember | undefined { 434 | switch (type.kind) { 435 | case 'intersection': 436 | case 'union': 437 | return dom.create.alias(name, type); 438 | case 'object': 439 | const interf = dom.create.interface(name); 440 | interf.members = type.members; 441 | return interf; 442 | case 'array': 443 | return isExtractableType(type.type) 444 | ? createModuleMember(name, type.type) 445 | : undefined; 446 | } 447 | } 448 | 449 | function createTypeReference( 450 | name: string, 451 | type: ExtractableType 452 | ): dom.TypeReference { 453 | const namedTypeReference = dom.create.namedTypeReference(name); 454 | if (type.kind === 'array') { 455 | return dom.create.array(namedTypeReference); 456 | } else { 457 | return namedTypeReference; 458 | } 459 | } 460 | 461 | function getUniqueNames(input: string[]): string[] { 462 | return Object.keys( 463 | input.reduce((all: any, name: string) => { 464 | all[name] = true; 465 | return all; 466 | }, {}) 467 | ); 468 | } 469 | 470 | export function propTypeQueryExpression( 471 | propTypesName: string | undefined 472 | ): string { 473 | return ` 474 | '${propTypesName}' == 'undefined' 475 | ? 476 | /:object MemberExpression[ 477 | /:property Identifier[@name == 'PropTypes'] 478 | ] 479 | : 480 | /:object Identifier[@name == '${propTypesName}'] 481 | `; 482 | } 483 | 484 | function getReactComponentName(ast: AstQuery): string | undefined { 485 | const res = ast.query(` 486 | // ImportDeclaration[ 487 | /:source StringLiteral[@value == 'react'] 488 | ] 489 | /:specifiers *[ 490 | / Identifier[@name == 'Component'] || / Identifier[@name == 'PureComponent'] 491 | ] 492 | /:local Identifier 493 | `); 494 | if (res.length > 0) { 495 | return res[0].name; 496 | } 497 | return undefined; 498 | } 499 | 500 | function getPropTypesName(ast: AstQuery): string | undefined { 501 | let res = ast.query(` 502 | // ImportDeclaration[ 503 | /:source StringLiteral[@value == 'react'] 504 | ] 505 | /:specifiers *[ 506 | / Identifier[@name == 'PropTypes'] 507 | ] 508 | /:local Identifier 509 | `); 510 | if (res.length > 0) { 511 | return res[0].name; 512 | } 513 | res = ast.query(` 514 | // ImportDeclaration[ 515 | /:source StringLiteral[@value == 'prop-types'] 516 | ] 517 | /:specifiers *[ 518 | ImportNamespaceSpecifier || / Identifier[@name == 'PropTypes'] 519 | ] 520 | /:local Identifier 521 | `); 522 | if (res.length > 0) { 523 | return res[0].name; 524 | } 525 | return undefined; 526 | } 527 | 528 | function getImportedPropTypes(ast: AstQuery): ImportedPropType[] { 529 | return ast 530 | .query( 531 | ` 532 | // ImportDeclaration[ 533 | /:source StringLiteral[@value == 'prop-types'] 534 | ] 535 | /:specifiers ImportSpecifier 536 | ` 537 | ) 538 | .map(({ imported, local }) => ({ 539 | importedName: imported.name, 540 | localName: local.name, 541 | })); 542 | } 543 | 544 | function getInstanceOfPropTypes( 545 | ast: AstQuery, 546 | importedPropTypes: ImportedPropTypes 547 | ): string[] { 548 | const { propTypesName, propTypes } = importedPropTypes; 549 | const instanceOfPropType = propTypes.find( 550 | ({ importedName }) => importedName === 'instanceOf' 551 | ); 552 | const localInstanceOfName = instanceOfPropType 553 | ? instanceOfPropType.localName 554 | : undefined; 555 | 556 | const res = ast.query(` 557 | // CallExpression[ 558 | /:callee MemberExpression[ 559 | (${propTypeQueryExpression(propTypesName)}) 560 | && 561 | /:property Identifier[@name == 'instanceOf'] 562 | ] 563 | || 564 | /:callee Identifier[@name == '${localInstanceOfName}'] 565 | ] 566 | /:arguments * 567 | `); 568 | 569 | return res.map((identifer) => identifer.name); 570 | } 571 | 572 | interface ImportStatement { 573 | name: string | undefined; 574 | local: string; 575 | path: string; 576 | } 577 | function getImportStatements( 578 | ast: AstQuery, 579 | typeNames: string[], 580 | instanceOfResolver: InstanceOfResolver | undefined 581 | ): ImportStatement[] { 582 | return typeNames 583 | .map((name) => { 584 | const res = ast.query(` 585 | // ImportDeclaration[ 586 | /:specifiers * /:local Identifier[@name == '${name}'] 587 | ] 588 | `); 589 | return { 590 | name: 591 | res.length > 0 && res[0].specifiers[0].imported 592 | ? res[0].specifiers[0].imported.name 593 | : undefined, 594 | local: name, 595 | path: res.length > 0 ? res[0].source.value : undefined, 596 | }; 597 | }) 598 | .map((importStatement) => { 599 | if (importStatement && instanceOfResolver) { 600 | const resolvedPath = importStatement.name 601 | ? instanceOfResolver(importStatement.name) 602 | : instanceOfResolver(importStatement.local); 603 | if (resolvedPath) { 604 | importStatement.path = resolvedPath; 605 | } 606 | } 607 | return importStatement; 608 | }) 609 | .filter((importStatement) => Boolean(importStatement.path)); 610 | } 611 | 612 | function getComponentNamesByPropTypeAssignment(ast: AstQuery): string[] { 613 | const res = ast.query(` 614 | //AssignmentExpression 615 | /:left MemberExpression[ 616 | /:object Identifier && 617 | /:property Identifier[@name == 'propTypes'] 618 | ] 619 | `); 620 | if (res.length > 0) { 621 | return res.map((match) => match.object.name); 622 | } 623 | return []; 624 | } 625 | 626 | function getComponentNamesByStaticPropTypeAttribute(ast: AstQuery): string[] { 627 | const res = ast.query(` 628 | //ClassDeclaration[ 629 | /:body * //ClassProperty /:key Identifier[@name == 'propTypes'] 630 | ] 631 | `); 632 | if (res.length > 0) { 633 | return res.map((match) => (match.id ? match.id.name : '')); 634 | } 635 | return []; 636 | } 637 | 638 | function getComponentNamesByJsxInBody(ast: AstQuery): string[] { 639 | const res = ast.query(` 640 | // ClassDeclaration[ 641 | /:body * //JSXElement 642 | ], 643 | // FunctionDeclaration[ 644 | /:body * //JSXElement 645 | ], 646 | // VariableDeclarator[ 647 | /:init ArrowFunctionExpression 648 | // JSXElement 649 | ] 650 | `); 651 | if (res.length > 0) { 652 | return res.map((match) => (match.id ? match.id.name : '')); 653 | } 654 | return []; 655 | } 656 | type ComponentProperties = { 657 | name: string; 658 | properties: { 659 | key: any; 660 | value: any; 661 | type?: any; 662 | }[]; 663 | }; 664 | 665 | function getComponentNamesByObject( 666 | ast: AstQuery, 667 | componentNames: string[] 668 | ): ComponentProperties[] { 669 | let arr: ComponentProperties[] = []; 670 | componentNames.forEach((name) => { 671 | const res = ast.query(` 672 | /:program * 673 | / VariableDeclaration 674 | / VariableDeclarator[ 675 | /:init ObjectExpression 676 | // ObjectProperty 677 | /:value Identifier[@name == '${name}'] 678 | ], 679 | /:program * 680 | / ExportNamedDeclaration 681 | // VariableDeclarator[ 682 | /:init ObjectExpression 683 | // ObjectProperty 684 | /:value Identifier[@name == '${name}'] 685 | ], 686 | /:program * 687 | / ExportDefaultDeclaration [ 688 | // ObjectProperty 689 | /:value Identifier[@name == '${name}'] 690 | ] /:declaration ObjectExpression 691 | `); 692 | 693 | if (res.length > 0) { 694 | const matches: ComponentProperties[] = []; 695 | // this accounts for export const X = {...} and export default {...} 696 | // we need to give the default exported object a name hence '_default' 697 | res.forEach((match) => { 698 | if ( 699 | arr.findIndex( 700 | (val) => 701 | val.name === match.id?.name || 702 | (val.name === '_default' && !match.id?.name) 703 | ) === -1 704 | ) { 705 | matches.push({ 706 | name: match.id?.name || '_default', 707 | properties: match.init?.properties || match.properties, 708 | }); 709 | } 710 | }); 711 | 712 | arr = [...arr, ...matches]; 713 | } 714 | }); 715 | return arr; 716 | } 717 | 718 | function getComponentDotProperties( 719 | ast: AstQuery, 720 | componentNames: string[] 721 | ): ComponentProperties[] { 722 | let arr: ComponentProperties[] = []; 723 | componentNames.forEach((name) => { 724 | const res = ast.query(` 725 | /:program * 726 | // AssignmentExpression[ 727 | /:left MemberExpression[ 728 | /:object Identifier[@name == '${name}'] 729 | ] 730 | && 731 | /:right Identifier 732 | ] 733 | `); 734 | if (res.length > 0) { 735 | const properties: ComponentProperties['properties'] = []; 736 | res.forEach((match) => { 737 | if (!componentNames.includes(match.right?.name)) { 738 | return; 739 | } 740 | properties.push({ 741 | key: match.left?.property?.name, 742 | value: match.right?.name, 743 | }); 744 | }); 745 | if (properties.length > 0) { 746 | arr = [ 747 | ...arr, 748 | { 749 | name, 750 | properties, 751 | }, 752 | ]; 753 | } 754 | } 755 | }); 756 | return arr; 757 | } 758 | 759 | function getPropTypes(ast: AstQuery, componentName: string): any | undefined { 760 | const propTypes = 761 | getPropTypesFromAssignment(ast, componentName) || 762 | getPropTypesFromStaticAttribute(ast, componentName); 763 | 764 | const referencedComponentName = getReferencedPropTypesComponentName( 765 | ast, 766 | propTypes 767 | ); 768 | if (referencedComponentName) { 769 | return getPropTypes(ast, referencedComponentName); 770 | } 771 | 772 | if (propTypes) { 773 | const referencedVariable = ast.query(` 774 | //VariableDeclarator[ 775 | /:id Identifier[@name == '${propTypes.name}'] 776 | ] 777 | /:init * 778 | `); 779 | 780 | if (referencedVariable && referencedVariable.length) { 781 | return referencedVariable[0]; 782 | } 783 | } 784 | 785 | return propTypes; 786 | } 787 | 788 | function getPropTypesFromAssignment( 789 | ast: AstQuery, 790 | componentName: string 791 | ): any | undefined { 792 | const res = ast.query(` 793 | //AssignmentExpression[ 794 | /:left MemberExpression[ 795 | /:object Identifier[@name == '${componentName}'] && 796 | /:property Identifier[@name == 'propTypes'] 797 | ] 798 | ] /:right * 799 | `); 800 | if (res.length > 0) { 801 | return res[0]; 802 | } 803 | return undefined; 804 | } 805 | 806 | function getPropTypesFromStaticAttribute( 807 | ast: AstQuery, 808 | componentName: string 809 | ): any | undefined { 810 | if (componentName === '') { 811 | const res = ast.query(` 812 | //ClassDeclaration 813 | /:body * 814 | //ClassProperty[ 815 | /:key Identifier[@name == 'propTypes'] 816 | ] 817 | /:value* 818 | `); 819 | if (res.length > 0 && !res[0].id) { 820 | return res[0]; 821 | } 822 | } 823 | const res = ast.query(` 824 | //ClassDeclaration[ 825 | /:id Identifier[@name == '${componentName}'] 826 | ] 827 | /:body * 828 | //ClassProperty[ 829 | /:key Identifier[@name == 'propTypes'] 830 | ] 831 | /:value* 832 | `); 833 | if (res.length > 0) { 834 | return res[0]; 835 | } 836 | return undefined; 837 | } 838 | 839 | function getReferencedPropTypesComponentName( 840 | ast: AstQuery, 841 | propTypes: any | undefined 842 | ): string | undefined { 843 | if (propTypes) { 844 | const propTypesReference = ast.querySubtree( 845 | propTypes, 846 | ` 847 | MemberExpression [ 848 | /:property Identifier[@name == 'propTypes'] 849 | ] /:object Identifier 850 | ` 851 | ); 852 | if (propTypesReference.length > 0) { 853 | return propTypesReference[0].name; 854 | } 855 | } 856 | return undefined; 857 | } 858 | 859 | function getComponentExportType( 860 | ast: AstQuery, 861 | componentName: string 862 | ): dom.DeclarationFlags | undefined { 863 | if (isDefaultExport(ast, componentName)) { 864 | return dom.DeclarationFlags.ExportDefault; 865 | } 866 | 867 | if (isNamedExport(ast, componentName)) { 868 | return dom.DeclarationFlags.Export; 869 | } 870 | 871 | return undefined; 872 | } 873 | 874 | function isDefaultExport(ast: AstQuery, componentName: string): boolean { 875 | return ( 876 | isUnnamedDefaultExport(ast, componentName) || 877 | isNamedDefaultExport(ast, componentName) || 878 | isNamedExportAsDefault(ast, componentName) 879 | ); 880 | } 881 | 882 | function isUnnamedDefaultExport(ast: AstQuery, componentName: string): boolean { 883 | if (componentName !== '') { 884 | return false; 885 | } 886 | 887 | const res = ast.query(` 888 | // ExportDefaultDeclaration[ 889 | // ClassDeclaration 890 | || 891 | // FunctionDeclaration 892 | ] 893 | `); 894 | 895 | return res.length > 0 && !res[0].id; 896 | } 897 | 898 | function isNamedDefaultExport(ast: AstQuery, componentName: string): boolean { 899 | const res = ast.query(` 900 | // ExportDefaultDeclaration[ 901 | // ClassDeclaration 902 | /:id Identifier[@name == '${componentName}'] 903 | || 904 | // FunctionDeclaration 905 | /:id Identifier[@name == '${componentName}'] 906 | || 907 | // VariableDeclaration 908 | / VariableDeclarator 909 | /:id Identifier[@name == '${componentName}'] 910 | || 911 | /Identifier[@name == '${componentName}'] 912 | ] 913 | , 914 | // AssignmentExpression[ 915 | /:left MemberExpression[ 916 | /:object Identifier[@name == 'exports'] 917 | && 918 | /:property Identifier[@name == 'default'] 919 | ] 920 | && 921 | /:right Identifier[@name == '${componentName}'] 922 | ] 923 | `); 924 | 925 | return res.length > 0; 926 | } 927 | 928 | function isNamedExportAsDefault(ast: AstQuery, componentName: string): boolean { 929 | const res = ast.query(` 930 | // ExportNamedDeclaration[ 931 | // ExportSpecifier [ 932 | /:local Identifier[@name == '${componentName}'] && 933 | /:exported Identifier[@name == 'default'] 934 | ] 935 | ] 936 | `); 937 | 938 | return res.length > 0; 939 | } 940 | 941 | function isNamedExport(ast: AstQuery, componentName: string): boolean { 942 | const res = ast.query(` 943 | // ExportNamedDeclaration[ 944 | // ClassDeclaration 945 | /:id Identifier[@name == '${componentName}'] 946 | || 947 | // FunctionDeclaration 948 | /:id Identifier[@name == '${componentName}'] 949 | || 950 | // VariableDeclaration 951 | / VariableDeclarator 952 | /:id Identifier[@name == '${componentName}'] 953 | || 954 | // ExportSpecifier 955 | /:exported Identifier[@name == '${componentName}'] 956 | ] 957 | `); 958 | 959 | return res.length > 0; 960 | } 961 | 962 | function isClassComponent( 963 | ast: AstQuery, 964 | componentName: string, 965 | reactComponentName: string | undefined 966 | ): boolean { 967 | if (componentName === '') { 968 | const res = ast.query(` 969 | // ClassDeclaration 970 | `); 971 | if (res.length > 0 && !res[0].id) { 972 | return true; 973 | } 974 | } 975 | const res = ast.query(` 976 | // ClassDeclaration 977 | /:id Identifier[@name == '${componentName}'] 978 | , 979 | // VariableDeclaration 980 | / VariableDeclarator[ 981 | /:id Identifier[@name == '${componentName}'] 982 | && 983 | /:init CallExpression[ 984 | '${reactComponentName}' == 'undefined' 985 | ? 986 | /:arguments MemberExpression[ 987 | /:object Identifier[@name == 'React'] && 988 | /:property Identifier[@name == 'Component'] 989 | ] 990 | : 991 | /:arguments Identifier[@name == '${reactComponentName}'] 992 | ] 993 | ] 994 | `); 995 | if (res.length > 0) { 996 | return true; 997 | } 998 | return false; 999 | } 1000 | -------------------------------------------------------------------------------- /tests/babylon-plugin.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'Component' { 2 | import * as React from 'react'; 3 | 4 | export interface ComponentProps { 5 | } 6 | 7 | export default class Component extends React.Component { 8 | render(): JSX.Element; 9 | 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /tests/babylon-plugin.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export default class Component extends React.PureComponent { 4 | 5 | render() { 6 | return

{ 7 | import('./fake-dialog-box.js') 8 | .then(dialogBox => { 9 | dialogBox.open(); 10 | }) 11 | .catch(error => { 12 | /* Error handling */ 13 | }) 14 | }} />; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/cli-test.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-implicit-dependencies 2 | import test from 'ava'; 3 | import execa from 'execa'; 4 | import * as fs from 'fs'; 5 | 6 | function normalize(input: string): string { 7 | return input.replace(/\s+/g, ' ').replace(/ => /g, '=>'); 8 | } 9 | 10 | test('cli should read from stdin', async (t) => { 11 | const expected = fs 12 | .readFileSync('./tests/import-react-component.d.ts') 13 | .toString(); 14 | 15 | const result = await execa( 16 | `cat ./tests/import-react-component.jsx |${process.argv[0]} ./cli.js --module-name component`, 17 | { shell: true } 18 | ); 19 | 20 | t.is(normalize(result.stdout), normalize(expected)); 21 | }); 22 | 23 | test('cli should read from file', async (t) => { 24 | const expected = fs 25 | .readFileSync('./tests/import-react-component.d.ts') 26 | .toString(); 27 | 28 | const result = await execa( 29 | `${process.argv[0]} ./cli.js --module-name component --file ./tests/import-react-component.jsx`, 30 | { shell: true } 31 | ); 32 | 33 | t.is(normalize(result.stdout), normalize(expected)); 34 | }); 35 | -------------------------------------------------------------------------------- /tests/component-without-proptypes.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | export interface TestProps { 5 | } 6 | 7 | export default class Test extends React.Component { 8 | render(): JSX.Element; 9 | } 10 | 11 | export const test: React.FC; 12 | } 13 | -------------------------------------------------------------------------------- /tests/component-without-proptypes.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export default class Test extends React.Component { 4 | render() { 5 | return ( 6 |

7 | ) 8 | } 9 | } 10 | 11 | export function test() { 12 | return ( 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /tests/const-as-proptypes.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | export type ButtonButtonSize = "sm" | "md" | "lg"; 5 | 6 | export interface ButtonProps { 7 | buttonSize?: ButtonButtonSize; 8 | } 9 | 10 | const Button: React.FC; 11 | 12 | export default Button; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /tests/const-as-proptypes.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const buttonPropTypes = { 5 | buttonSize: PropTypes.oneOf(['sm', 'md', 'lg']) 6 | }; 7 | 8 | export default function Button({ buttonSize }) { 9 | return ( 10 | 11 | ); 12 | } 13 | 14 | Button.propTypes = buttonPropTypes; 15 | -------------------------------------------------------------------------------- /tests/error-reporting-test.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-implicit-dependencies 2 | import test, { ExecutionContext } from 'ava'; 3 | import stripAnsi from 'strip-ansi'; 4 | import * as react2dts from '../src/index'; 5 | 6 | type Args = { args: any[] }; 7 | const originalConsoleError = console.error; 8 | 9 | test.beforeEach((t: ExecutionContext) => { 10 | console.error = function (...args: any[]): void { 11 | if (!t.context.args) { 12 | t.context.args = []; 13 | } 14 | t.context.args.push(args); 15 | }; 16 | }); 17 | 18 | test.afterEach(() => { 19 | console.error = originalConsoleError; 20 | }); 21 | 22 | test.serial( 23 | 'In case of error during shape type inference (direct reference) the error information should be retained', 24 | (t: ExecutionContext) => { 25 | react2dts.generateFromSource( 26 | null, 27 | ` 28 | import React from 'react'; 29 | 30 | export class Component extends React.Component { 31 | static propTypes = { 32 | someShape: React.PropTypes.shape(shape) 33 | }; 34 | } 35 | ` 36 | ); 37 | const args = t.context.args.reduce( 38 | (akku: any[], args: any[]) => [...akku, ...args], 39 | [] 40 | ); 41 | t.is( 42 | stripAnsi(args[2]), 43 | 'Line 6: someShape: React.PropTypes.shape(shape)' 44 | ); 45 | } 46 | ); 47 | 48 | test.serial( 49 | 'In case of error during enum type inference the error information should be retained', 50 | (t: ExecutionContext) => { 51 | react2dts.generateFromSource( 52 | null, 53 | ` 54 | import React from 'react'; 55 | 56 | export class Component extends React.Component { 57 | static propTypes = { 58 | list: React.PropTypes.oneOf(list) 59 | }; 60 | } 61 | ` 62 | ); 63 | const args = t.context.args.reduce( 64 | (akku: any[], args: any[]) => [...akku, ...args], 65 | [] 66 | ); 67 | t.is( 68 | stripAnsi(args[2]), 69 | 'Line 6: list: React.PropTypes.oneOf(list)' 70 | ); 71 | } 72 | ); 73 | 74 | test.serial( 75 | 'In case of error during enum value creation inference the error information should be retained', 76 | (t: ExecutionContext) => { 77 | react2dts.generateFromSource( 78 | null, 79 | ` 80 | import React from 'react'; 81 | 82 | export class Component extends React.Component { 83 | static propTypes = { 84 | list: React.PropTypes.oneOf(Object.keys(object)) 85 | }; 86 | } 87 | ` 88 | ); 89 | const args = t.context.args.reduce( 90 | (akku: any[], args: any[]) => [...akku, ...args], 91 | [] 92 | ); 93 | t.is( 94 | stripAnsi(args[2]), 95 | 'Line 6: list: React.PropTypes.oneOf(Object.keys(object))' 96 | ); 97 | } 98 | ); 99 | 100 | test.serial( 101 | 'In case of error during shape type inference (indirect reference) the error information should be retained', 102 | (t: ExecutionContext) => { 103 | react2dts.generateFromSource( 104 | null, 105 | ` 106 | import React from 'react'; 107 | 108 | export class Component extends React.Component { 109 | static propTypes = { 110 | shape: React.PropTypes.shape(some.shape) 111 | }; 112 | } 113 | ` 114 | ); 115 | const args = t.context.args.reduce( 116 | (akku: any[], args: any[]) => [...akku, ...args], 117 | [] 118 | ); 119 | t.is( 120 | stripAnsi(args[2]), 121 | 'Line 6: shape: React.PropTypes.shape(some.shape)' 122 | ); 123 | } 124 | ); 125 | -------------------------------------------------------------------------------- /tests/es6-class.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | import Message from './path/to/Message'; 5 | 6 | export type ComponentOptionalEnum = "News" | "Photos" | 1 | 2; 7 | 8 | export type ComponentOptionalUnion = string | number; 9 | 10 | export interface ComponentOptionalObjectWithShape { 11 | color?: string; 12 | fontSize?: number; 13 | "aria-label"?: string; 14 | } 15 | 16 | export type ComponentRequiredUnion = ((...args: any[])=>any) | boolean; 17 | 18 | export interface ComponentRequiredArrayOfObjectsWithShape { 19 | color?: string; 20 | fontSize?: number; 21 | } 22 | 23 | export interface ComponentDeeplyNested { 24 | arrayInDeeplyNested?: { 25 | foo?: number; 26 | }[]; 27 | } 28 | 29 | export interface ComponentProps { 30 | /** 31 | * This is a jsdoc comment for optionalAny. 32 | */ 33 | optionalAny?: any; 34 | optionalArray?: any[]; 35 | optionalBool?: boolean; 36 | optionalFunc?: (...args: any[]) => any; 37 | optionalNumber?: number; 38 | optionalObject?: Object; 39 | optionalString?: string; 40 | optionalNode?: React.ReactNode; 41 | optionalElement?: React.ReactElement; 42 | optionalMessage?: Message; 43 | optionalEnum?: ComponentOptionalEnum; 44 | optionalUnion?: ComponentOptionalUnion; 45 | optionalArrayOf?: number[]; 46 | optionalObjectWithShape?: ComponentOptionalObjectWithShape; 47 | requiredFunc: (...args: any[]) => any; 48 | requiredAny: any; 49 | requiredUnion: ComponentRequiredUnion; 50 | requiredArrayOf: string[]; 51 | requiredArrayOfObjectsWithShape: ComponentRequiredArrayOfObjectsWithShape[]; 52 | deeplyNested: ComponentDeeplyNested[]; 53 | requiredSymbol: Symbol; 54 | } 55 | 56 | export class Component extends React.Component { 57 | render(): JSX.Element; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/es6-class.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Message from './path/to/Message'; 3 | 4 | export class Component extends React.Component { 5 | 6 | render() { 7 | return ( 8 |
9 | ); 10 | } 11 | } 12 | Component.propTypes = { 13 | /** 14 | * This is a jsdoc comment for optionalAny. 15 | */ 16 | optionalAny: React.PropTypes.any, 17 | optionalArray: React.PropTypes.array, 18 | optionalBool: React.PropTypes.bool, 19 | optionalFunc: React.PropTypes.func, 20 | optionalNumber: React.PropTypes.number, 21 | optionalObject: React.PropTypes.object, 22 | optionalString: React.PropTypes.string, 23 | optionalNode: React.PropTypes.node, 24 | optionalElement: React.PropTypes.element, 25 | optionalMessage: React.PropTypes.instanceOf(Message), 26 | optionalEnum: React.PropTypes.oneOf(['News', 'Photos', 1, 2]), 27 | optionalUnion: React.PropTypes.oneOfType([ 28 | React.PropTypes.string, 29 | React.PropTypes.number 30 | ]), 31 | optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), 32 | //optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), 33 | optionalObjectWithShape: React.PropTypes.shape({ 34 | color: React.PropTypes.string, 35 | fontSize: React.PropTypes.number, 36 | 'aria-label': React.PropTypes.string, 37 | }), 38 | requiredFunc: React.PropTypes.func.isRequired, 39 | requiredAny: React.PropTypes.any.isRequired, 40 | requiredUnion: React.PropTypes.oneOfType([ 41 | React.PropTypes.func, 42 | React.PropTypes.bool 43 | ]).isRequired, 44 | requiredArrayOf: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, 45 | requiredArrayOfObjectsWithShape: React.PropTypes.arrayOf(React.PropTypes.shape({ 46 | color: React.PropTypes.string, 47 | fontSize: React.PropTypes.number 48 | })).isRequired, 49 | deeplyNested: React.PropTypes.arrayOf(React.PropTypes.shape({ 50 | arrayInDeeplyNested: React.PropTypes.arrayOf(React.PropTypes.shape({ 51 | foo: React.PropTypes.number 52 | })) 53 | })).isRequired, 54 | requiredSymbol: React.PropTypes.symbol.isRequired, 55 | }; 56 | -------------------------------------------------------------------------------- /tests/es7-class-babeled-to-es6.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | import Message from './path/to/Message'; 5 | 6 | export type MyComponentOptionalEnum = "News" | "Photos" | 1 | 2; 7 | 8 | export type MyComponentOptionalUnion = string | number; 9 | 10 | export interface MyComponentOptionalObjectWithShape { 11 | color?: string; 12 | fontSize?: number; 13 | } 14 | 15 | export type MyComponentRequiredUnion = any[] | boolean; 16 | 17 | export interface MyComponentRequiredArrayOfObjectsWithShape { 18 | color?: string; 19 | fontSize?: number; 20 | } 21 | 22 | export interface MyComponentDeeplyNested { 23 | arrayInDeeplyNested?: { 24 | foo?: number; 25 | }[]; 26 | } 27 | 28 | export interface MyComponentProps { 29 | /** 30 | * This is a jsdoc comment for optionalAny. 31 | */ 32 | optionalAny?: any; 33 | optionalArray?: any[]; 34 | optionalBool?: boolean; 35 | optionalFunc?: (...args: any[]) => any; 36 | optionalNumber?: number; 37 | optionalObject?: Object; 38 | optionalString?: string; 39 | optionalNode?: React.ReactNode; 40 | optionalElement?: React.ReactElement; 41 | optionalMessage?: Message; 42 | optionalEnum?: MyComponentOptionalEnum; 43 | optionalUnion?: MyComponentOptionalUnion; 44 | optionalArrayOf?: number[]; 45 | optionalObjectWithShape?: MyComponentOptionalObjectWithShape; 46 | requiredFunc: (...args: any[]) => any; 47 | requiredAny: any; 48 | requiredUnion: MyComponentRequiredUnion; 49 | requiredArrayOf: string[]; 50 | requiredArrayOfObjectsWithShape: MyComponentRequiredArrayOfObjectsWithShape[]; 51 | deeplyNested: MyComponentDeeplyNested[]; 52 | requiredSymbol: Symbol; 53 | } 54 | 55 | export class MyComponent extends React.Component { 56 | render(): JSX.Element; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/es7-class-babeled-to-es6.js: -------------------------------------------------------------------------------- 1 | import { Component, createElement } from 'react'; 2 | import { any, array, arrayOf, bool, element, func, instanceOf, node, number, object, oneOf, oneOfType, shape, string, symbol } from 'prop-types'; 3 | import Message from './path/to/Message'; 4 | 5 | class MyComponent extends Component { 6 | 7 | render() { 8 | return createElement('div', null); 9 | } 10 | } 11 | MyComponent.propTypes = { 12 | /** 13 | * This is a jsdoc comment for optionalAny. 14 | */ 15 | optionalAny: any, 16 | optionalArray: array, 17 | optionalBool: bool, 18 | optionalFunc: func, 19 | optionalNumber: number, 20 | optionalObject: object, 21 | optionalString: string, 22 | optionalNode: node, 23 | optionalElement: element, 24 | optionalMessage: instanceOf(Message), 25 | optionalEnum: oneOf(['News', 'Photos', 1, 2]), 26 | optionalUnion: oneOfType([string, number]), 27 | optionalArrayOf: arrayOf(number), 28 | optionalObjectWithShape: shape({ 29 | color: string, 30 | fontSize: number 31 | }), 32 | requiredFunc: func.isRequired, 33 | requiredAny: any.isRequired, 34 | requiredUnion: oneOfType([array, bool]).isRequired, 35 | requiredArrayOf: arrayOf(string).isRequired, 36 | requiredArrayOfObjectsWithShape: arrayOf(shape({ 37 | color: string, 38 | fontSize: number 39 | })).isRequired, 40 | deeplyNested: arrayOf(shape({ 41 | arrayInDeeplyNested: arrayOf(shape({ 42 | foo: number 43 | })) 44 | })).isRequired, 45 | requiredSymbol: symbol.isRequired 46 | }; 47 | 48 | export { MyComponent }; 49 | -------------------------------------------------------------------------------- /tests/es7-class-babeled.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _react = require('react'); 10 | 11 | var React = _interopRequireWildcard(_react); 12 | 13 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 14 | 15 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 16 | 17 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 18 | 19 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 20 | 21 | var Component = function (_React$Component) { 22 | _inherits(Component, _React$Component); 23 | 24 | function Component() { 25 | _classCallCheck(this, Component); 26 | 27 | return _possibleConstructorReturn(this, Object.getPrototypeOf(Component).apply(this, arguments)); 28 | } 29 | 30 | _createClass(Component, [{ 31 | key: 'render', 32 | value: function render() { 33 | return React.createElement('div', null); 34 | } 35 | }]); 36 | 37 | return Component; 38 | }(React.Component); 39 | 40 | Component.propTypes = { 41 | /** 42 | * This is a jsdoc comment for optionalAny. 43 | */ 44 | optionalAny: React.PropTypes.any, 45 | optionalArray: React.PropTypes.array, 46 | optionalBool: React.PropTypes.bool, 47 | optionalFunc: React.PropTypes.func, 48 | optionalNumber: React.PropTypes.number, 49 | optionalObject: React.PropTypes.object, 50 | optionalString: React.PropTypes.string, 51 | optionalNode: React.PropTypes.node, 52 | optionalElement: React.PropTypes.element, 53 | optionalMessage: React.PropTypes.instanceOf(Message), 54 | //optionalEnum: React.PropTypes.oneOf(['News', 'Photos']), 55 | optionalUnion: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]), 56 | optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), 57 | //optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), 58 | //optionalObjectWithShape: React.PropTypes.shape({ 59 | // color: React.PropTypes.string, 60 | // fontSize: React.PropTypes.number 61 | //}), 62 | requiredFunc: React.PropTypes.func.isRequired, 63 | requiredAny: React.PropTypes.any.isRequired, 64 | requiredUnion: React.PropTypes.oneOfType([React.PropTypes.array, React.PropTypes.bool]).isRequired, 65 | requiredArrayOf: React.PropTypes.arrayOf(React.PropTypes.string).isRequired 66 | }; 67 | exports.default = Component; 68 | 69 | -------------------------------------------------------------------------------- /tests/es7-class-separate-export.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | export interface ComponentProps { 5 | optionalAny?: any; 6 | } 7 | 8 | export default class Component extends React.Component { 9 | render(): JSX.Element; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/es7-class-separate-export.jsx: -------------------------------------------------------------------------------- 1 | import {Component, PropTypes} from 'react'; 2 | 3 | class Component extends Component { 4 | 5 | static propTypes = { 6 | optionalAny: PropTypes.any, 7 | }; 8 | 9 | render() { 10 | return ( 11 |
12 | ); 13 | } 14 | } 15 | 16 | export default Component; 17 | -------------------------------------------------------------------------------- /tests/es7-class-top-level-module.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Message from './path/to/Message'; 3 | 4 | export type ComponentOptionalUnion = string | number; 5 | 6 | export type ComponentRequiredUnion = any[] | boolean; 7 | 8 | export interface ComponentProps { 9 | /** 10 | * This is a jsdoc comment for optionalAny. 11 | */ 12 | optionalAny?: any; 13 | optionalArray?: any[]; 14 | optionalBool?: boolean; 15 | optionalFunc?: (...args: any[]) => any; 16 | optionalNumber?: number; 17 | optionalObject?: Object; 18 | optionalString?: string; 19 | optionalNode?: React.ReactNode; 20 | optionalElement?: React.ReactElement; 21 | optionalMessage?: Message; 22 | optionalUnion?: ComponentOptionalUnion; 23 | optionalArrayOf?: number[]; 24 | requiredFunc: (...args: any[]) => any; 25 | requiredAny: any; 26 | requiredUnion: ComponentRequiredUnion; 27 | requiredArrayOf: string[]; 28 | } 29 | 30 | export default class Component extends React.Component { 31 | render(): JSX.Element; 32 | } 33 | -------------------------------------------------------------------------------- /tests/es7-class.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | import Message from './path/to/Message'; 4 | 5 | export type ComponentOptionalUnion = string | number; 6 | 7 | export type ComponentRequiredUnion = any[] | boolean; 8 | 9 | export interface ComponentProps { 10 | /** 11 | * This is a jsdoc comment for optionalAny. 12 | */ 13 | optionalAny?: any; 14 | optionalArray?: any[]; 15 | optionalBool?: boolean; 16 | optionalFunc?: (...args: any[]) => any; 17 | optionalNumber?: number; 18 | optionalObject?: Object; 19 | optionalString?: string; 20 | optionalNode?: React.ReactNode; 21 | optionalElement?: React.ReactElement; 22 | optionalMessage?: Message; 23 | optionalUnion?: ComponentOptionalUnion; 24 | optionalArrayOf?: number[]; 25 | requiredFunc: (...args: any[]) => any; 26 | requiredAny: any; 27 | requiredUnion: ComponentRequiredUnion; 28 | requiredArrayOf: string[]; 29 | } 30 | 31 | export default class Component extends React.Component { 32 | render(): JSX.Element; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/es7-class.jsx: -------------------------------------------------------------------------------- 1 | import {Component, PropTypes} from 'react'; 2 | import Message from './path/to/Message'; 3 | 4 | export default class Component extends Component { 5 | 6 | static propTypes = { 7 | /** 8 | * This is a jsdoc comment for optionalAny. 9 | */ 10 | optionalAny: PropTypes.any, 11 | optionalArray: PropTypes.array, 12 | optionalBool: PropTypes.bool, 13 | optionalFunc: PropTypes.func, 14 | optionalNumber: PropTypes.number, 15 | optionalObject: PropTypes.object, 16 | optionalString: PropTypes.string, 17 | optionalNode: PropTypes.node, 18 | optionalElement: PropTypes.element, 19 | optionalMessage: PropTypes.instanceOf(Message), 20 | //optionalEnum: PropTypes.oneOf(['News', 'Photos']), 21 | optionalUnion: PropTypes.oneOfType([ 22 | PropTypes.string, 23 | PropTypes.number 24 | ]), 25 | optionalArrayOf: PropTypes.arrayOf(PropTypes.number), 26 | //optionalObjectOf: PropTypes.objectOf(PropTypes.number), 27 | //optionalObjectWithShape: PropTypes.shape({ 28 | // color: PropTypes.string, 29 | // fontSize: PropTypes.number 30 | //}), 31 | requiredFunc: PropTypes.func.isRequired, 32 | requiredAny: PropTypes.any.isRequired, 33 | requiredUnion: PropTypes.oneOfType([ 34 | PropTypes.array, 35 | PropTypes.bool 36 | ]).isRequired, 37 | requiredArrayOf: PropTypes.arrayOf(PropTypes.string).isRequired, 38 | }; 39 | 40 | render() { 41 | return ( 42 |
43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/generator-test.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-implicit-dependencies 2 | import test from 'ava'; 3 | 4 | import { Generator } from '../src/generator'; 5 | import { generateFromSource } from '../src/index'; 6 | 7 | function setup(): Generator { 8 | return new Generator(); 9 | } 10 | 11 | test('The Generator should write a module declaration', (t) => { 12 | const generator = setup(); 13 | generator.declareModule('name', () => { 14 | // 15 | }); 16 | t.is(generator.toString(), "declare module 'name' {\n}\n"); 17 | }); 18 | test('The Generator should write an import statement', (t) => { 19 | const generator = setup(); 20 | generator.import('decls', 'from'); 21 | t.is(generator.toString(), "import decls from 'from';\n"); 22 | }); 23 | test('The Generator should write a required property', (t) => { 24 | const generator = setup(); 25 | generator.prop('name', 'type', false); 26 | t.is(generator.toString(), 'name: type;\n'); 27 | }); 28 | test('The Generator should write an optional property', (t) => { 29 | const generator = setup(); 30 | generator.prop('name', 'type', true); 31 | t.is(generator.toString(), 'name?: type;\n'); 32 | }); 33 | test('The Generator should write a property interface', (t) => { 34 | const generator = setup(); 35 | generator.props('Name', { prop: { type: 'type', optional: true } }); 36 | t.is( 37 | generator.toString(), 38 | 'export interface NameProps {\n\tprop?: type;\n}\n' 39 | ); 40 | }); 41 | test('The Generator should write a class with props declaration', (t) => { 42 | const generator = setup(); 43 | generator.class('Name', true); 44 | t.is( 45 | generator.toString(), 46 | 'class Name extends React.Component {\n}\n' 47 | ); 48 | }); 49 | test('The Generator should write a class without props declaration', (t) => { 50 | const generator = setup(); 51 | generator.class('Name', false); 52 | t.is( 53 | generator.toString(), 54 | 'class Name extends React.Component {\n}\n' 55 | ); 56 | }); 57 | test('The Generator should write an indented block comment', (t) => { 58 | const generator = setup(); 59 | generator.comment('* yada\n\t\t\t\tyada\n '); 60 | t.is(generator.toString(), '/** yada\nyada\n */\n'); 61 | }); 62 | test('The Generator should write an export default declaration', (t) => { 63 | const generator = setup(); 64 | generator.exportDeclaration(0, () => undefined); 65 | t.is(generator.toString(), 'export default '); 66 | }); 67 | test('The Generator should write a named export declaration', (t) => { 68 | const generator = setup(); 69 | generator.exportDeclaration(1, () => undefined); 70 | t.is(generator.toString(), 'export '); 71 | }); 72 | 73 | test('Generating typings with given custom generator should delare a module if name given', (t) => { 74 | const generator = setup(); 75 | let name: string | undefined; 76 | generator.declareModule = (moduleName) => { 77 | name = moduleName; 78 | }; 79 | 80 | const source = ` 81 | export class Test {} 82 | `; 83 | generateFromSource('module', source, { generator }); 84 | 85 | t.is(name, 'module'); 86 | }); 87 | 88 | test('Generating typings with given custom generator should import react', (t) => { 89 | const generator = setup(); 90 | let decl: string | undefined; 91 | let from: string | undefined; 92 | generator.import = (_decl, _from) => { 93 | decl = _decl; 94 | from = _from; 95 | }; 96 | 97 | const source = ` 98 | export class Test {} 99 | `; 100 | generateFromSource(null, source, { generator }); 101 | 102 | t.is(decl, '* as React'); 103 | t.is(from, 'react'); 104 | }); 105 | -------------------------------------------------------------------------------- /tests/import-react-component.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | } 4 | -------------------------------------------------------------------------------- /tests/import-react-component.jsx: -------------------------------------------------------------------------------- 1 | import {Component} from 'react'; 2 | 3 | export class Test extends Component { 4 | } 5 | -------------------------------------------------------------------------------- /tests/instance-of-proptype-names.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | import Member from './member'; 4 | 5 | export interface TestProps { 6 | test?: Member; 7 | } 8 | 9 | export class Test extends React.Component { 10 | render(): JSX.Element; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/instance-of-proptype-names.jsx: -------------------------------------------------------------------------------- 1 | import React, {PropTypes as p} from 'react'; 2 | import Member from './member'; 3 | 4 | export class Test extends React.Component { 5 | static propTypes = { 6 | test: p.instanceOf(Member) 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /tests/multiple-components-dot-notation-default.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | export interface ComponentProps { 5 | optionalAny?: any; 6 | } 7 | 8 | const Component: React.FC & { 9 | OtherComponent: typeof Component2; 10 | AnotherComponent: typeof Component3; 11 | }; 12 | 13 | export default Component; 14 | 15 | export interface Component2Props { 16 | optionalString?: string; 17 | } 18 | 19 | const Component2: React.FC; 20 | 21 | const Component3: React.FC; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tests/multiple-components-dot-notation-default.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const Component = ({ optionalAny }) =>
; 4 | Component.propTypes = { 5 | optionalAny: React.PropTypes.any, 6 | }; 7 | 8 | const Component2 = () =>
; 9 | 10 | const Component3 = () =>
; 11 | 12 | Component2.propTypes = { 13 | optionalString: React.PropTypes.string, 14 | }; 15 | 16 | Component.OtherComponent = Component2 17 | Component.AnotherComponent = Component3 18 | 19 | export default Component; 20 | -------------------------------------------------------------------------------- /tests/multiple-components-dot-notation.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | export interface ComponentProps { 5 | optionalAny?: any; 6 | } 7 | 8 | const Component: React.FC & { 9 | OtherComponent: typeof ComponentX; 10 | }; 11 | 12 | export default Component; 13 | 14 | export interface ComponentXProps { 15 | optionalString?: string; 16 | } 17 | 18 | const ComponentX: React.FC; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /tests/multiple-components-dot-notation.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const Component = ({ optionalAny }) =>
; 4 | Component.propTypes = { 5 | optionalAny: React.PropTypes.any, 6 | }; 7 | 8 | const ComponentX = () =>
; 9 | 10 | ComponentX.propTypes = { 11 | optionalString: React.PropTypes.string, 12 | }; 13 | 14 | Component.OtherComponent = ComponentX 15 | 16 | export default Component; 17 | -------------------------------------------------------------------------------- /tests/multiple-components-object-default.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | export interface Component2Props { 5 | optionalString?: string; 6 | } 7 | 8 | export const Component2: React.FC; 9 | 10 | export interface ComponentProps { 11 | optionalAny?: any; 12 | } 13 | 14 | const Component: React.FC; 15 | 16 | const Composed: { 17 | Component: typeof Component; 18 | Asdf: typeof Component2; 19 | }; 20 | 21 | export default Composed; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tests/multiple-components-object-default.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const Component = ({ optionalAny }) =>
; 4 | Component.propTypes = { 5 | optionalAny: React.PropTypes.any, 6 | }; 7 | 8 | export const Component2 = () =>
; 9 | 10 | Component2.propTypes = { 11 | optionalString: React.PropTypes.string, 12 | }; 13 | 14 | const Composed = { 15 | Component, 16 | Asdf: Component2, 17 | }; 18 | 19 | export default Composed; 20 | -------------------------------------------------------------------------------- /tests/multiple-components-object-unnamed-default.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | export interface Component2Props { 5 | optionalString?: string; 6 | } 7 | 8 | export const Component2: React.FC; 9 | 10 | export interface ComponentProps { 11 | optionalAny?: any; 12 | } 13 | 14 | const Component: React.FC; 15 | 16 | const _default: { 17 | Component: typeof Component; 18 | Asdf: typeof Component2; 19 | }; 20 | 21 | export default _default; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tests/multiple-components-object-unnamed-default.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const Component = ({ optionalAny }) =>
; 4 | Component.propTypes = { 5 | optionalAny: React.PropTypes.any, 6 | }; 7 | 8 | export const Component2 = () =>
; 9 | 10 | Component2.propTypes = { 11 | optionalString: React.PropTypes.string, 12 | }; 13 | 14 | export default { 15 | Component, 16 | Asdf: Component2, 17 | }; 18 | -------------------------------------------------------------------------------- /tests/multiple-components-object.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | export interface ComponentProps { 5 | optionalAny?: any; 6 | } 7 | 8 | const Component: React.FC; 9 | 10 | export interface Component2Props { 11 | optionalString?: string; 12 | } 13 | 14 | const Component2: React.FC; 15 | 16 | export const Composed: { 17 | Component: typeof Component; 18 | Component2: typeof Component2; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /tests/multiple-components-object.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const Component = ({ optionalAny }) =>
; 4 | Component.propTypes = { 5 | optionalAny: React.PropTypes.any, 6 | }; 7 | 8 | const Component2 = () =>
; 9 | 10 | Component2.propTypes = { 11 | optionalString: React.PropTypes.string, 12 | }; 13 | 14 | export const Composed = { 15 | Component, 16 | Component2, 17 | }; 18 | -------------------------------------------------------------------------------- /tests/named-export-specifiers.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | export interface ComponentProps { 5 | optionalAny?: any; 6 | } 7 | 8 | export const Component: React.FC; 9 | 10 | export const Component2: React.FC; 11 | } 12 | -------------------------------------------------------------------------------- /tests/named-export-specifiers.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const Component = ({optionalAny}) =>
; 4 | Component.propTypes = { 5 | optionalAny: React.PropTypes.any, 6 | }; 7 | 8 | const Component2 = () =>
; 9 | 10 | export { Component, Component2 }; 11 | -------------------------------------------------------------------------------- /tests/parse-prop-types-test.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-implicit-dependencies 2 | import test from 'ava'; 3 | 4 | import { getTypeFromPropType } from '../src/analyzer'; 5 | import { IProp } from '../src/deprecated'; 6 | 7 | const instanceOfResolver = (): any => undefined; 8 | const reactPropTypesMemberExpression: any = { 9 | type: 'MemberExpression', 10 | object: { 11 | name: 'React', 12 | }, 13 | property: { 14 | name: 'PropTypes', 15 | }, 16 | }; 17 | 18 | test('The PropType parser should return any on unknown PropTypes', (t) => { 19 | const ast: any = { 20 | type: '', 21 | loc: {}, 22 | }; 23 | const expected = { 24 | type: 'any', 25 | optional: true, 26 | }; 27 | t.deepEqual(getTypeFromPropType(ast, instanceOfResolver), expected); 28 | }); 29 | test('The PropType parser should return any[] for generic array prop types', (t) => { 30 | const ast: any = { 31 | type: 'MemberExpression', 32 | loc: {}, 33 | object: reactPropTypesMemberExpression, 34 | property: { 35 | name: 'array', 36 | }, 37 | }; 38 | const expected = { 39 | type: 'any[]', 40 | optional: true, 41 | }; 42 | t.deepEqual(getTypeFromPropType(ast, instanceOfResolver), expected); 43 | }); 44 | test('The PropType parser should return boolean for bool prop types', (t) => { 45 | const ast: any = { 46 | type: 'MemberExpression', 47 | loc: {}, 48 | object: reactPropTypesMemberExpression, 49 | property: { 50 | name: 'bool', 51 | }, 52 | }; 53 | const expected = { 54 | type: 'boolean', 55 | optional: true, 56 | }; 57 | t.deepEqual(getTypeFromPropType(ast, instanceOfResolver), expected); 58 | }); 59 | test('The PropType parser should return a generic function for func prop types', (t) => { 60 | const ast: any = { 61 | type: 'MemberExpression', 62 | loc: {}, 63 | object: reactPropTypesMemberExpression, 64 | property: { 65 | name: 'func', 66 | }, 67 | }; 68 | const expected = { 69 | type: '(...args: any[]) => any', 70 | optional: true, 71 | }; 72 | t.deepEqual(getTypeFromPropType(ast, instanceOfResolver), expected); 73 | }); 74 | test('The PropType parser should return a generic required function for func.isRequired prop types', (t) => { 75 | const ast: any = { 76 | type: 'MemberExpression', 77 | loc: {}, 78 | object: { 79 | type: 'MemberExpression', 80 | object: reactPropTypesMemberExpression, 81 | property: { 82 | name: 'func', 83 | }, 84 | }, 85 | property: { 86 | name: 'isRequired', 87 | }, 88 | }; 89 | const result: IProp = getTypeFromPropType(ast, instanceOfResolver); 90 | t.is(result.type, '(...args: any[]) => any'); 91 | t.is(result.optional, false); 92 | }); 93 | test('The PropType parser should return number for number prop types', (t) => { 94 | const ast: any = { 95 | type: 'MemberExpression', 96 | loc: {}, 97 | object: reactPropTypesMemberExpression, 98 | property: { 99 | name: 'number', 100 | }, 101 | }; 102 | const expected = { 103 | type: 'number', 104 | optional: true, 105 | }; 106 | t.deepEqual(getTypeFromPropType(ast, instanceOfResolver), expected); 107 | }); 108 | test('The PropType parser should return Object for object prop types', (t) => { 109 | const ast: any = { 110 | type: 'MemberExpression', 111 | loc: {}, 112 | object: reactPropTypesMemberExpression, 113 | property: { 114 | name: 'object', 115 | }, 116 | }; 117 | const expected = { 118 | type: 'Object', 119 | optional: true, 120 | }; 121 | t.deepEqual(getTypeFromPropType(ast, instanceOfResolver), expected); 122 | }); 123 | test('The PropType parser should return string for string prop types', (t) => { 124 | const ast: any = { 125 | type: 'MemberExpression', 126 | loc: {}, 127 | object: reactPropTypesMemberExpression, 128 | property: { 129 | name: 'string', 130 | }, 131 | }; 132 | const expected = { 133 | type: 'string', 134 | optional: true, 135 | }; 136 | t.deepEqual(getTypeFromPropType(ast, instanceOfResolver), expected); 137 | }); 138 | test('The PropType parser should return React.ReactNode for node prop types', (t) => { 139 | const ast: any = { 140 | type: 'MemberExpression', 141 | loc: {}, 142 | object: reactPropTypesMemberExpression, 143 | property: { 144 | name: 'node', 145 | }, 146 | }; 147 | const expected = { 148 | type: 'React.ReactNode', 149 | optional: true, 150 | }; 151 | t.deepEqual(getTypeFromPropType(ast, instanceOfResolver), expected); 152 | }); 153 | test('The PropType parser should return React.ReactElement for element prop types', (t) => { 154 | const ast: any = { 155 | type: 'MemberExpression', 156 | loc: {}, 157 | object: reactPropTypesMemberExpression, 158 | property: { 159 | name: 'element', 160 | }, 161 | }; 162 | const expected = { 163 | type: 'React.ReactElement', 164 | optional: true, 165 | }; 166 | t.deepEqual(getTypeFromPropType(ast, instanceOfResolver), expected); 167 | }); 168 | test('The PropType parser should return number[] for arrayOf(React.PropTypes.number) prop types', (t) => { 169 | const ast: any = { 170 | type: 'CallExpression', 171 | loc: {}, 172 | callee: { 173 | type: 'MemberExpression', 174 | loc: {}, 175 | object: reactPropTypesMemberExpression, 176 | property: { 177 | name: 'arrayOf', 178 | }, 179 | }, 180 | arguments: [ 181 | { 182 | type: 'MemberExpression', 183 | loc: {}, 184 | object: reactPropTypesMemberExpression, 185 | property: { 186 | name: 'number', 187 | }, 188 | }, 189 | ], 190 | }; 191 | const result: IProp = getTypeFromPropType(ast, instanceOfResolver); 192 | t.is(result.type, 'number[]'); 193 | t.is(result.optional, true); 194 | }); 195 | test( 196 | 'The PropType parser should return number|string for' + 197 | 'oneOfType([React.PropTypes.number, React.PropTypes.string]) prop types', 198 | (t) => { 199 | const ast: any = { 200 | type: 'CallExpression', 201 | loc: {}, 202 | callee: { 203 | type: 'MemberExpression', 204 | loc: {}, 205 | object: reactPropTypesMemberExpression, 206 | property: { 207 | name: 'oneOfType', 208 | }, 209 | }, 210 | arguments: [ 211 | { 212 | type: 'ArrayExpression', 213 | loc: {}, 214 | elements: [ 215 | { 216 | type: 'MemberExpression', 217 | loc: {}, 218 | object: reactPropTypesMemberExpression, 219 | property: { 220 | name: 'number', 221 | }, 222 | }, 223 | { 224 | type: 'MemberExpression', 225 | loc: {}, 226 | object: reactPropTypesMemberExpression, 227 | property: { 228 | name: 'string', 229 | }, 230 | }, 231 | ], 232 | }, 233 | ], 234 | }; 235 | const result: IProp = getTypeFromPropType(ast, instanceOfResolver); 236 | t.is(result.type, 'number|string'); 237 | t.is(result.optional, true); 238 | } 239 | ); 240 | test('The PropType parser should return Message for instanceOf(Message) prop types', (t) => { 241 | const ast: any = { 242 | type: 'CallExpression', 243 | loc: {}, 244 | callee: { 245 | type: 'MemberExpression', 246 | loc: {}, 247 | object: reactPropTypesMemberExpression, 248 | property: { 249 | name: 'instanceOf', 250 | }, 251 | }, 252 | arguments: [ 253 | { 254 | type: 'Identifier', 255 | loc: {}, 256 | name: 'Message', 257 | }, 258 | ], 259 | }; 260 | const result: IProp = getTypeFromPropType(ast, (): string => './some/path'); 261 | t.is(result.type, 'Message'); 262 | t.is(result.optional, true); 263 | t.is(result.importPath, './some/path'); 264 | }); 265 | test('The PropType parser should return any for unresolved instanceOf(Message) prop types', (t) => { 266 | const ast: any = { 267 | type: 'CallExpression', 268 | loc: {}, 269 | callee: { 270 | type: 'MemberExpression', 271 | loc: {}, 272 | object: reactPropTypesMemberExpression, 273 | property: { 274 | name: 'instanceOf', 275 | }, 276 | }, 277 | arguments: [ 278 | { 279 | type: 'Identifier', 280 | loc: {}, 281 | name: 'Message', 282 | }, 283 | ], 284 | }; 285 | const result: IProp = getTypeFromPropType(ast); 286 | t.is(result.type, 'any'); 287 | t.is(result.optional, true); 288 | t.is(result.importPath, undefined); 289 | }); 290 | -------------------------------------------------------------------------------- /tests/parsing-test.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-implicit-dependencies 2 | import test, { ExecutionContext } from 'ava'; 3 | import chalk from 'chalk'; 4 | import * as diff from 'diff'; 5 | import * as fs from 'fs'; 6 | import * as path from 'path'; 7 | import * as react2dts from '../src/index'; 8 | 9 | let basedir = path.join(__dirname, '..', '..', 'tests'); 10 | if (process.env.WALLABY) { 11 | basedir = path.join(__dirname); 12 | } 13 | 14 | function normalize(input: string): string { 15 | return input.replace(/\s+/g, ' ').replace(/ => /g, '=>'); 16 | } 17 | 18 | function textDiff(t: ExecutionContext, actual: string, expected: string): void { 19 | if (diff.diffChars(normalize(expected), normalize(actual)).length > 1) { 20 | const differences = diff.diffChars(expected, actual); 21 | const result = differences 22 | .map((part) => { 23 | const value = part.value.trim() 24 | ? part.value 25 | : (part.added ? '+' : '-') + part.value; 26 | return part.added 27 | ? chalk.green(value) 28 | : part.removed 29 | ? chalk.red(value) 30 | : chalk.grey(value); 31 | }) 32 | .join(''); 33 | t.fail(`\n${result}`); 34 | } else { 35 | t.pass(); 36 | } 37 | } 38 | 39 | function compare( 40 | t: ExecutionContext, 41 | moduleName: string | null, 42 | file1: string, 43 | file2: string, 44 | opts: react2dts.IOptions = {}, 45 | reactImport = 'react' 46 | ): void { 47 | textDiff( 48 | t, 49 | react2dts.generateFromFile( 50 | moduleName, 51 | path.join(basedir, file1), 52 | opts, 53 | reactImport 54 | ), 55 | fs.readFileSync(path.join(basedir, file2)).toString() 56 | ); 57 | } 58 | 59 | test('Parsing should create definition from es6 class component', (t) => { 60 | const opts: react2dts.IOptions = { 61 | instanceOfResolver: (): string => './path/to/Message', 62 | }; 63 | compare(t, 'component', 'es6-class.jsx', 'es6-class.d.ts', opts); 64 | }); 65 | test('Parsing should create definition from es7 class component', (t) => { 66 | const opts: react2dts.IOptions = { 67 | instanceOfResolver: (): string => './path/to/Message', 68 | }; 69 | compare(t, 'component', 'es7-class.jsx', 'es7-class.d.ts', opts); 70 | }); 71 | test('Parsing should create top-level module definition from es7 class component', (t) => { 72 | const opts: react2dts.IOptions = { 73 | instanceOfResolver: (): string => './path/to/Message', 74 | }; 75 | compare(t, null, 'es7-class.jsx', 'es7-class-top-level-module.d.ts', opts); 76 | }); 77 | test('Parsing should create definition from babeled es7 class component', (t) => { 78 | const opts: react2dts.IOptions = { 79 | instanceOfResolver: (): string => './path/to/Message', 80 | }; 81 | compare(t, 'component', 'es7-class-babeled.js', 'es7-class.d.ts', opts); 82 | }); 83 | test('Parsing should create definition from es7 class component babeled to es6', (t) => { 84 | const opts: react2dts.IOptions = { 85 | instanceOfResolver: (): string => './path/to/Message', 86 | }; 87 | compare( 88 | t, 89 | 'component', 90 | 'es7-class-babeled-to-es6.js', 91 | 'es7-class-babeled-to-es6.d.ts', 92 | opts 93 | ); 94 | }); 95 | test('Parsing should create definition from es7 class component with separate default export', (t) => { 96 | compare( 97 | t, 98 | 'component', 99 | 'es7-class-separate-export.jsx', 100 | 'es7-class-separate-export.d.ts' 101 | ); 102 | }); 103 | test('Parsing should create definition from stateless function component', (t) => { 104 | compare(t, 'component', 'stateless.jsx', 'stateless.d.ts'); 105 | }); 106 | test('Parsing should create definition from class extending Component', (t) => { 107 | compare( 108 | t, 109 | 'component', 110 | 'import-react-component.jsx', 111 | 'import-react-component.d.ts' 112 | ); 113 | }); 114 | test('Parsing should create definition from component exported as an object of components', (t) => { 115 | compare( 116 | t, 117 | 'component', 118 | 'multiple-components-object.jsx', 119 | 'multiple-components-object.d.ts' 120 | ); 121 | }); 122 | test("Parsing should create definition from default export that's an object of components", (t) => { 123 | compare( 124 | t, 125 | 'component', 126 | 'multiple-components-object-default.jsx', 127 | 'multiple-components-object-default.d.ts' 128 | ); 129 | }); 130 | test("Parsing should create definition from unnamed default export that's an object of components", (t) => { 131 | compare( 132 | t, 133 | 'component', 134 | 'multiple-components-object-unnamed-default.jsx', 135 | 'multiple-components-object-unnamed-default.d.ts' 136 | ); 137 | }); 138 | test('Parsing should add dot notation members for component', (t) => { 139 | compare( 140 | t, 141 | 'component', 142 | 'multiple-components-dot-notation.jsx', 143 | 'multiple-components-dot-notation.d.ts' 144 | ); 145 | }); 146 | test('Parsing should add dot notation members for default export component', (t) => { 147 | compare( 148 | t, 149 | 'component', 150 | 'multiple-components-dot-notation-default.jsx', 151 | 'multiple-components-dot-notation-default.d.ts' 152 | ); 153 | }); 154 | test('Parsing should create definition from class import PropTypes and instanceOf dependency', (t) => { 155 | compare( 156 | t, 157 | 'component', 158 | 'instance-of-proptype-names.jsx', 159 | 'instance-of-proptype-names.d.ts' 160 | ); 161 | }); 162 | test('Parsing should create definition from file without propTypes', (t) => { 163 | compare( 164 | t, 165 | 'component', 166 | 'component-without-proptypes.jsx', 167 | 'component-without-proptypes.d.ts' 168 | ); 169 | }); 170 | test('Parsing should create definition from file with references in propTypes', (t) => { 171 | compare( 172 | t, 173 | 'component', 174 | 'references-in-proptypes.jsx', 175 | 'references-in-proptypes.d.ts' 176 | ); 177 | }); 178 | test('Parsing should create definition from file with reference as propTypes', (t) => { 179 | compare( 180 | t, 181 | 'component', 182 | 'reference-as-proptypes.jsx', 183 | 'reference-as-proptypes.d.ts' 184 | ); 185 | }); 186 | test('Parsing should create definition from file with unnamed default export', (t) => { 187 | compare( 188 | t, 189 | 'path', 190 | 'unnamed-default-export.jsx', 191 | 'unnamed-default-export.d.ts' 192 | ); 193 | }); 194 | test('Parsing should create definition from file with named export specifiers', (t) => { 195 | compare( 196 | t, 197 | 'component', 198 | 'named-export-specifiers.jsx', 199 | 'named-export-specifiers.d.ts' 200 | ); 201 | }); 202 | test('Parsing should create preact definition', (t) => { 203 | compare( 204 | t, 205 | 'path', 206 | 'preact-definition.jsx', 207 | 'preact-definition.d.ts', 208 | {}, 209 | 'preact' 210 | ); 211 | }); 212 | test('Parsing should suppport props-types repo', (t) => { 213 | compare(t, 'path', 'prop-types.jsx', 'prop-types.d.ts', {}); 214 | }); 215 | test('Parsing should suppport props-types repo (with a default import)', (t) => { 216 | compare(t, 'path', 'prop-types-default-import.jsx', 'prop-types.d.ts', {}); 217 | }); 218 | test('Parsing should support an SFC with default export', (t) => { 219 | compare( 220 | t, 221 | 'component', 222 | 'stateless-default-export.jsx', 223 | 'stateless-default-export.d.ts' 224 | ); 225 | }); 226 | test('Parsing should support an SFC with default export babeled to es6', (t) => { 227 | compare( 228 | t, 229 | 'component', 230 | 'stateless-export-as-default.js', 231 | 'stateless-export-as-default.d.ts' 232 | ); 233 | }); 234 | test('Parsing should support components that extend PureComponent', (t) => { 235 | compare(t, 'component', 'pure-component.jsx', 'pure-component.d.ts'); 236 | }); 237 | test('Parsing should support prop-types as reference to constant', (t) => { 238 | compare(t, 'component', 'const-as-proptypes.jsx', 'const-as-proptypes.d.ts'); 239 | }); 240 | test('Parsing should suppport custom eol style', (t) => { 241 | textDiff( 242 | t, 243 | react2dts.generateFromFile( 244 | 'component', 245 | path.join(basedir, 'pure-component.jsx'), 246 | { eol: '\n' }, 247 | 'react' 248 | ), 249 | fs 250 | .readFileSync(path.join(basedir, 'pure-component.d.ts')) 251 | .toString() 252 | .replace('\r\n', '\n') 253 | ); 254 | }); 255 | test('Parsing should suppport users to set additional babylon plugins', (t) => { 256 | compare(t, 'Component', 'babylon-plugin.jsx', 'babylon-plugin.d.ts', { 257 | babylonPlugins: ['dynamicImport'], 258 | }); 259 | }); 260 | -------------------------------------------------------------------------------- /tests/preact-definition.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'path' { 2 | import * as React from 'preact'; 3 | 4 | export interface SomeComponentProps { 5 | onClick?: (...args: any[]) => any; 6 | } 7 | 8 | export class SomeComponent extends React.Component { 9 | render(): JSX.Element; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/preact-definition.jsx: -------------------------------------------------------------------------------- 1 | import {Component, PropTypes} from 'react'; 2 | 3 | export class SomeComponent extends Component { 4 | 5 | static propTypes = { 6 | onClick: PropTypes.func 7 | }; 8 | 9 | render() { 10 | return ( 11 |
12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/prop-types-default-import.jsx: -------------------------------------------------------------------------------- 1 | import {Component} from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class extends Component { 5 | 6 | static propTypes = { 7 | optionalString: PropTypes.string 8 | }; 9 | 10 | render() { 11 | return ( 12 |
13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/prop-types.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'path' { 2 | import * as React from 'react'; 3 | 4 | export interface Props { 5 | optionalString?: string; 6 | } 7 | 8 | export default class extends React.Component { 9 | render(): JSX.Element; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/prop-types.jsx: -------------------------------------------------------------------------------- 1 | import {Component} from 'react'; 2 | import * as P from 'prop-types'; 3 | 4 | export default class extends Component { 5 | 6 | static propTypes = { 7 | optionalString: P.string 8 | }; 9 | 10 | render() { 11 | return ( 12 |
13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/pure-component.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | export interface Props { 5 | optionalString?: string; 6 | } 7 | 8 | export default class extends React.PureComponent { 9 | render(): JSX.Element; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/pure-component.jsx: -------------------------------------------------------------------------------- 1 | import {PureComponent} from 'react'; 2 | import * as PropTypes from 'prop-types'; 3 | 4 | export default class extends PureComponent { 5 | 6 | static propTypes = { 7 | optionalString: PropTypes.string 8 | }; 9 | 10 | render() { 11 | return ( 12 |
13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/reference-as-proptypes.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | export interface SomeComponentProps { 5 | someString?: string; 6 | } 7 | 8 | export default class SomeComponent extends React.Component { 9 | render(): JSX.Element; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/reference-as-proptypes.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class SomeInternalComponent extends React.Component { 4 | static propTypes = { 5 | someString: React.PropTypes.string 6 | }; 7 | } 8 | 9 | export default class SomeComponent extends React.Component { 10 | static propTypes = SomeInternalComponent.propTypes; 11 | } 12 | -------------------------------------------------------------------------------- /tests/references-in-proptypes.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | export type SomeComponentSomeOneOf = "foo" | "bar"; 5 | export type SomeComponentAnotherOneOf = "foo" | "bar"; 6 | 7 | export interface SomeComponentSomeShape { 8 | string?: string; 9 | } 10 | 11 | export interface SomeComponentProps { 12 | someOneOf?: SomeComponentSomeOneOf; 13 | anotherOneOf?: SomeComponentAnotherOneOf; 14 | someShape?: SomeComponentSomeShape; 15 | } 16 | 17 | export default class SomeComponent extends React.Component { 18 | render(): JSX.Element; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/references-in-proptypes.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const FOO = 'foo'; 4 | const BAR = 'bar'; 5 | const fooOrBar = ['foo', 'bar']; 6 | const fooOrBarWithConsts = [FOO, BAR]; 7 | const shape = { string: React.PropTypes.string }; 8 | 9 | export default class SomeComponent extends React.Component { 10 | static propTypes = { 11 | someOneOf: React.PropTypes.oneOf(fooOrBar), 12 | anotherOneOf: React.PropTypes.oneOf(fooOrBarWithConsts), 13 | someShape: React.PropTypes.shape(shape) 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /tests/stateless-default-export.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | export interface ComponentProps { 5 | optionalString?: string; 6 | } 7 | 8 | const Component: React.FC; 9 | export default Component; 10 | 11 | export const Component2: React.FC; 12 | } 13 | -------------------------------------------------------------------------------- /tests/stateless-default-export.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export default function Component({optionalString}) { 4 | return
; 5 | } 6 | 7 | Component.propTypes = { 8 | optionalString: React.PropTypes.string, 9 | }; 10 | 11 | export const Component2 = () =>
; 12 | -------------------------------------------------------------------------------- /tests/stateless-export-as-default.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | export interface ComponentProps { 5 | text: string; 6 | className?: string; 7 | } 8 | 9 | const Component: React.FC; 10 | export default Component; 11 | } 12 | -------------------------------------------------------------------------------- /tests/stateless-export-as-default.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Component = ({ className, text }) => React.createElement( 5 | 'div', 6 | { className: className }, 7 | text 8 | ); 9 | 10 | Component.displayName = 'Component'; 11 | 12 | Component.propTypes = { 13 | text: PropTypes.string.isRequired, 14 | className: PropTypes.string 15 | }; 16 | 17 | export { Component as default }; 18 | -------------------------------------------------------------------------------- /tests/stateless.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'component' { 2 | import * as React from 'react'; 3 | 4 | export interface ComponentProps { 5 | optionalAny?: any; 6 | } 7 | 8 | export const Component: React.FC; 9 | 10 | export const Component2: React.FC; 11 | } 12 | -------------------------------------------------------------------------------- /tests/stateless.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export const Component = ({optionalAny}) =>
; 4 | Component.propTypes = { 5 | optionalAny: React.PropTypes.any, 6 | }; 7 | 8 | export const Component2 = () =>
; 9 | -------------------------------------------------------------------------------- /tests/unnamed-default-export.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'path' { 2 | import * as React from 'react'; 3 | 4 | export interface Props { 5 | onClick?: (...args: any[]) => any; 6 | } 7 | 8 | export default class extends React.Component { 9 | render(): JSX.Element; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/unnamed-default-export.jsx: -------------------------------------------------------------------------------- 1 | import {Component, PropTypes} from 'react'; 2 | 3 | export default class extends Component { 4 | 5 | static propTypes = { 6 | onClick: PropTypes.func 7 | }; 8 | 9 | render() { 10 | return ( 11 |
12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "declaration": true, 5 | "lib": ["dom", "es5", "es2015.core", "es2015.iterable", "es2015.promise"], 6 | "listFiles": false, 7 | "module": "commonjs", 8 | "noFallthroughCasesInSwitch": true, 9 | "noImplicitAny": true, 10 | "noImplicitReturns": true, 11 | "noImplicitThis": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "outDir": "dist", 15 | "pretty": true, 16 | "strictNullChecks": true, 17 | "stripInternal": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "target": "es5", 20 | "esModuleInterop": true 21 | }, 22 | "exclude": ["index.d.ts", "dist", "node_modules", "tests/*.d.ts"] 23 | } 24 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@knisterpeter/standard-tslint", 3 | "rules": { 4 | "trailing-comma": false, 5 | "space-before-function-paren": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /typings/astq.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'astq' { 2 | namespace ASTQ {} 3 | class ASTQ { 4 | public adapter( 5 | adapter: 6 | | 'mozast' 7 | | 'graphql' 8 | | 'xmldom' 9 | | 'parse5' 10 | | 'json' 11 | | 'cheero' 12 | | 'unist' 13 | | 'asty', 14 | force: boolean 15 | ): void; 16 | public query( 17 | ast: any, 18 | query: string, 19 | options?: any, 20 | trace?: boolean 21 | ): any[]; 22 | } 23 | 24 | export = ASTQ; 25 | } 26 | -------------------------------------------------------------------------------- /typings/babylon.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'babylon' { 2 | export function parse(code: string, options: Object): any; 3 | } 4 | -------------------------------------------------------------------------------- /typings/get-stdin.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'get-stdin' { 2 | function getStdin(): PromiseLike; 3 | namespace getStdin {} 4 | export = getStdin; 5 | } 6 | -------------------------------------------------------------------------------- /wallaby.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(wallaby) { 2 | process.env.WALLABY = 'true'; 3 | return { 4 | files: [ 5 | 'src/**/*.ts', 6 | {pattern: 'tests/**/*.js*', instrument: false}, 7 | {pattern: 'tests/**/*.d.ts', instrument: false}, 8 | {pattern: 'node_modules/dts-dom/package.json', instrument: false}, 9 | {pattern: 'node_modules/dts-dom/bin/index.d.ts', instrument: false} 10 | ], 11 | tests: [ 12 | 'tests/**/*-test.ts', 13 | '!tests/cli-test.ts', 14 | ], 15 | env: { 16 | type: 'node' 17 | }, 18 | testFramework: 'ava', 19 | debug: false 20 | }; 21 | } 22 | --------------------------------------------------------------------------------