├── .changeset ├── README.md └── config.json ├── .flowconfig ├── .github └── workflows │ ├── codeql.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .npmignore ├── .nvmrc ├── .prettierrc ├── LICENSE ├── assets └── example.png ├── babel.config.js ├── code-of-conduct.md ├── contributing.md ├── eslint.config.cjs ├── flow-typed ├── babel-errors.js ├── kind2string.js └── npm │ └── jest_v20.x.x.js ├── jest.config.js ├── package.json ├── packages ├── babel-plugin-extract-react-types │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── __snapshots__ │ │ └── index.test.js.snap │ ├── index.js │ ├── index.test.js │ └── package.json ├── extract-react-types-loader │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── index.js │ └── package.json ├── extract-react-types │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── __fixtures__ │ │ ├── component.tsx │ │ ├── componentB.tsx │ │ ├── nested-exports │ │ │ ├── index.ts │ │ │ └── nested-export.ts │ │ ├── props.ts │ │ ├── test.json │ │ └── types.ts │ ├── __snapshots__ │ │ ├── converters-flow.test.js.snap │ │ └── converters-typescript.test.js.snap │ ├── converters-flow.test.js │ ├── converters-typescript.test.js │ ├── package.json │ └── src │ │ ├── converter.js │ │ ├── export-manager.js │ │ ├── file-loader.js │ │ ├── index.js │ │ ├── kinds.js │ │ └── utils.js ├── kind2string │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.js │ │ └── utils.js │ └── test.js └── pretty-proptypes │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ └── src │ ├── HybridLayout │ └── index.js │ ├── LayoutRenderer │ ├── index.js │ └── index.test.js │ ├── PrettyConvert │ ├── AddBrackets.js │ ├── Toggle.js │ ├── converters.js │ ├── converters.test.js │ └── index.js │ ├── Prop │ ├── Heading.js │ └── index.js │ ├── Props │ ├── Props.test.js │ ├── Wrapper.js │ └── index.js │ ├── PropsTable │ ├── PropRow.js │ └── index.js │ ├── components │ ├── Button.js │ ├── Description.js │ ├── Expander.js │ ├── Indent.js │ ├── Outline.js │ ├── Required.js │ ├── Type.js │ ├── constants.js │ └── index.js │ ├── getPropTypes.js │ ├── index.js │ ├── renderPropType.js │ ├── types.js │ └── utils.js ├── readme.md └── yarn.lock /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | If you want to know about changesets, check out [its repo for its documentation](https://github.com/changesets/changesets) 4 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.0.1/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": true, 5 | "access": "public", 6 | "baseBranch": "master" 7 | } 8 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/packages/kind2string/.* 3 | .*/node_modules/pretty-format2/.* 4 | .*/node_modules/ast-pretty-print/.* 5 | .*/node_modules/pretty-format-ast/.* 6 | 7 | 8 | [include] 9 | 10 | [libs] 11 | 12 | [lints] 13 | 14 | [options] 15 | emoji=true 16 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL Advanced" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | branches: [ "master" ] 19 | schedule: 20 | - cron: '25 4 * * 4' 21 | 22 | jobs: 23 | analyze: 24 | name: Analyze (${{ matrix.language }}) 25 | # Runner size impacts CodeQL analysis time. To learn more, please see: 26 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 27 | # - https://gh.io/supported-runners-and-hardware-resources 28 | # - https://gh.io/using-larger-runners (GitHub.com only) 29 | # Consider using larger runners or machines with greater resources for possible analysis time improvements. 30 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} 31 | permissions: 32 | # required for all workflows 33 | security-events: write 34 | 35 | # required to fetch internal or private CodeQL packs 36 | packages: read 37 | 38 | # only required for workflows in private repositories 39 | actions: read 40 | contents: read 41 | 42 | strategy: 43 | fail-fast: false 44 | matrix: 45 | include: 46 | - language: actions 47 | build-mode: none 48 | - language: javascript-typescript 49 | build-mode: none 50 | # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' 51 | # Use `c-cpp` to analyze code written in C, C++ or both 52 | # Use 'java-kotlin' to analyze code written in Java, Kotlin or both 53 | # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 54 | # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, 55 | # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. 56 | # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how 57 | # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages 58 | steps: 59 | - name: Checkout repository 60 | uses: actions/checkout@v4 61 | 62 | # Add any setup steps before running the `github/codeql-action/init` action. 63 | # This includes steps like installing compilers or runtimes (`actions/setup-node` 64 | # or others). This is typically only required for manual builds. 65 | # - name: Setup runtime (example) 66 | # uses: actions/setup-example@v1 67 | 68 | # Initializes the CodeQL tools for scanning. 69 | - name: Initialize CodeQL 70 | uses: github/codeql-action/init@v3 71 | with: 72 | languages: ${{ matrix.language }} 73 | build-mode: ${{ matrix.build-mode }} 74 | # If you wish to specify custom queries, you can do so here or in a config file. 75 | # By default, queries listed here will override any specified in a config file. 76 | # Prefix the list here with "+" to use these queries and those in the config file. 77 | 78 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 79 | # queries: security-extended,security-and-quality 80 | 81 | # If the analyze step fails for one of the languages you are analyzing with 82 | # "We were unable to automatically build your code", modify the matrix above 83 | # to set the build mode to "manual" for that language. Then modify this step 84 | # to build your code. 85 | # ℹ️ Command-line programs to run using the OS shell. 86 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 87 | - if: matrix.build-mode == 'manual' 88 | shell: bash 89 | run: | 90 | echo 'If you are using a "manual" build mode for one or more of the' \ 91 | 'languages you are analyzing, replace this with the commands to build' \ 92 | 'your code, for example:' 93 | echo ' make bootstrap' 94 | echo ' make release' 95 | exit 1 96 | 97 | - name: Perform CodeQL Analysis 98 | uses: github/codeql-action/analyze@v3 99 | with: 100 | category: "/language:${{matrix.language}}" 101 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Repo 16 | uses: actions/checkout@v2 17 | 18 | - name: Setup Node.js 21.1.0 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: 21.1.0 22 | 23 | - name: Install Dependencies 24 | run: yarn 25 | 26 | - name: Create Release Pull Request or Publish to npm 27 | id: changesets 28 | uses: changesets/action@v1 29 | with: 30 | # this expects you to have a script called release which does a build for your packages and calls changeset publish 31 | publish: yarn release 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 35 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # Run linting and tests when pushing to master or targeting master in pull requests 2 | 3 | name: Test 4 | 5 | on: 6 | push: 7 | branches: [master] 8 | pull_request: 9 | branches: [master] 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [21.1.0] 18 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v2 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: yarn --frozen-lockfile 27 | - run: yarn lint 28 | - run: yarn test 29 | env: 30 | CI: true 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | **/dist 4 | 5 | # ignore all logs 6 | *.log 7 | 8 | # ignore editor generated files 9 | .vscode 10 | .idea 11 | .iml 12 | 13 | # ignore env file used for GH info fetching in version 14 | .env 15 | 16 | # ignore yarn.locks generated in packages 17 | packages/**/**/yarn.lock 18 | 19 | # ignore storybook static 20 | storybook-static 21 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | __fixtures__/** 2 | __/snapshots__/** 3 | flow-typed/** 4 | .flowconfig 5 | .prettierrc 6 | .travis.yml 7 | test.js 8 | yarn.lock -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v21.1.0 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 100 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ben Conolly 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. -------------------------------------------------------------------------------- /assets/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlassian/extract-react-types/46c2a6400bf83689ac28b8804ac30b94421345b8/assets/example.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = api => { 2 | // Cache the returned value forever and don't call this function again. 3 | api.cache(true); 4 | 5 | return { 6 | presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-flow'], 7 | plugins: [ 8 | 'emotion', 9 | '@babel/plugin-proposal-class-properties', 10 | '@babel/plugin-transform-runtime' 11 | ] 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at `bconolly@atlassian.com`. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | Contributions to Extract React Types in the form of issues and PRs are welcomed. 2 | 3 | ## Code of Conduct 4 | 5 | Extract React Types adheres to the [Contributor Covenant Code of Conduct](code-of-conduct.md). 6 | 7 | ## Version management 8 | 9 | Keystone uses @noviny's [@changesets/cli](https://github.com/atlassian/changesets) in combination with `bolt` to track package versions and publish packages. 10 | This tool allows each PR to indicate which packages need a version bump along with a changelog snippet. 11 | This information is then collated when performing a release to update package versions and `CHANGELOG.md` files. 12 | 13 | ### What all contributors need to do 14 | 15 | - Make your changes (as per usual) 16 | - Before you make a Pull Request, run the `bolt changeset` command and answer the questions that are asked. It will want to know: 17 | - which packages you want to publish 18 | - what version you are releasing them at 19 | - a message to summarise the changes (this message will be written to the changelog of bumped packages) 20 | - Before you accept the changeset, it will inform you of any other dependent packages within the repo that will also be bumped by this changeset. If this looks fine, agree, and a changeset will be generated in the `.changeset` directory. 21 | 22 | Each changeset contains two files; `changes.json`, which contains structured data which indicates the packages which need to be updated, and `changes.md`, which contains a markdown snippet which will be included in the `CHANGELOG.md` files for the updated packages. 23 | 24 | Here is what a `changeset.json` looks like: 25 | 26 | ``` 27 | { 28 | "releases": [ 29 | { "name": "@keystone-alpha/adapter-mongoose", "type": "patch" }, 30 | { "name": "@keystone-alpha/keystone", "type": "minor" } 31 | ], 32 | "dependents": [] 33 | } 34 | ``` 35 | 36 | You can have multiple changesets in a single PR. This will give you more granular changelogs, and is encouraged. 37 | 38 | ## Release Guidelines 39 | 40 | ## Publishing 41 | 42 | ### How to do a release 43 | 44 | > This should only ever be done by a very short list of core contributors 45 | 46 | Releasing is a two-step process. The first step updates the packages, and the second step publishes updated packages to npm. 47 | 48 | #### Steps to version packages 49 | 50 | The first step is `bolt apply-changesets`. This will find all changesets that have been created since the last release, and update the version in package.json as specified in those changesets, flattening out multiple bumps to a single package into a single version update. 51 | 52 | The `bolt release` command will release new versions of packages to npm. 53 | 54 | The commands to run are: 55 | 56 | ```sh 57 | git checkout master 58 | git pull 59 | git branch -D temp-release-branch 60 | git checkout -b temp-release-branch 61 | bolt apply-changesets 62 | git add . 63 | git commit -m "Run version-packages" 64 | git push --set-upstream origin temp-release-branch 65 | ``` 66 | 67 | Once you have run this you will need to make a pull request to merge this back into master. 68 | 69 | #### Release Process 70 | 71 | Once the version changes are merged back in to master, to do a manual release: 72 | 73 | ```sh 74 | git checkout master 75 | git pull 76 | bolt 77 | bolt release 78 | git push --tags 79 | bolt 80 | ``` 81 | 82 | The `bolt publish-changed` command finds packages where the version listed in the `package.json` is ahead of the version published on npm, and attempts to publish just those packages. 83 | 84 | Because of this, we should keep the following in mind: 85 | 86 | - Once the `apply-changesets` command has been run, the PR from the `temp-release-branch` should be merged before any other PRs are merged into master, to ensure that no changesets are skipped from being included in a release. 87 | - There is no reason you should ever manually edit the version in the `package.json` 88 | 89 | ### A quick note on changelogs 90 | 91 | The release process will automatically generate and update a `CHANGELOG.md` file, however this does not need to be the only way this file is modified. The changelogs are deliberately static assets so past changelogs can be updated or expanded upon. 92 | 93 | In addition, content added above the last released version will automatically be appended to the next release. If your changes do not fit comfortably within the summary of a changelog, we encourage you to add a more detailed account directly into the `CHANGELOG.md`. 94 | -------------------------------------------------------------------------------- /eslint.config.cjs: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('eslint/config'); 2 | const babelParser = require('babel-eslint'); 3 | const prettier = require('eslint-plugin-prettier'); 4 | const react = require('eslint-plugin-react'); 5 | const importPlugin = require('eslint-plugin-import'); 6 | const jestPlugin = require('eslint-plugin-jest'); 7 | 8 | module.exports = defineConfig([ 9 | { 10 | ...jestPlugin.configs['flat/recommended'], 11 | }, 12 | { 13 | ignores: [ 14 | '**/.*', 15 | '**/dist/*', 16 | '**/dist/**', 17 | '**/dist/**/*', 18 | 'dist/*', 19 | '**/node_modules/', 20 | 'flow-typed/*', 21 | './flow-typed/**/*' 22 | ], 23 | }, 24 | { 25 | languageOptions: { 26 | parser: babelParser, 27 | globals: { 28 | // flow globals 29 | TimeoutID: true, 30 | IntervalID: true, 31 | AnimationFrameID: true, 32 | es6: true, 33 | browser: true, 34 | node: true, 35 | 'jest/globals': true 36 | } 37 | }, 38 | plugins: { 39 | prettier, 40 | react, 41 | importPlugin, 42 | jestPlugin 43 | }, 44 | rules: { 45 | // Error on prettier violations 46 | 'prettier/prettier': 'error', 47 | 'import/no-unresolved': 'off', 48 | 'prefer-const': 'off', 49 | // New eslint style rules that is not disabled by prettier: 50 | 'lines-between-class-members': 'off', 51 | // Allowing warning and error console logging 52 | // use `invariant` and `warning` 53 | 'no-console': ['error'], 54 | // Opting out of prefer destructuring (nicer with flow in lots of cases) 55 | 'prefer-destructuring': 'off', 56 | // Disallowing the use of variables starting with `_` unless it called on `this`. 57 | // Allowed: `this._secret = Symbol()` 58 | // Not allowed: `const _secret = Symbol()` 59 | 'no-underscore-dangle': ['error', { allowAfterThis: true }], 60 | // Cannot reassign function parameters but allowing modification 61 | 'no-param-reassign': ['error', { props: false }], 62 | // Allowing ++ on numbers 63 | 'no-plusplus': 'off', 64 | // Allowing Math.pow rather than forcing `**` 65 | 'no-restricted-properties': [ 66 | 'off', 67 | { 68 | object: 'Math', 69 | property: 'pow' 70 | } 71 | ], 72 | // Allowing jsx in files with any file extension (old components have jsx but not the extension) 73 | 'react/jsx-filename-extension': 'off', 74 | // Not requiring default prop declarations all the time 75 | 'react/require-default-props': 'off', 76 | // Opt out of preferring stateless functions 77 | 'react/prefer-stateless-function': 'off', 78 | // Allowing files to have multiple components in it 79 | 'react/no-multi-comp': 'off', 80 | // Sometimes we use the PropTypes.object PropType for simplicity 81 | 'react/forbid-prop-types': 'off', 82 | // Allowing the non function setState approach 83 | 'react/no-access-state-in-setstate': 'off', 84 | // Opting out of this 85 | 'react/destructuring-assignment': 'off', 86 | // Adding 'skipShapeProps' as the rule has issues with correctly handling PropTypes.shape 87 | 'react/no-unused-prop-types': ['error', { skipShapeProps: true }], 88 | // Having issues with this rule not working correctly 89 | 'react/default-props-match-prop-types': 'off', 90 | // Allowing importing from dev deps (for stories and tests) 91 | 'import/no-extraneous-dependencies': 'off', 92 | 'spaced-comment': 'off', 93 | 'consistent-return': 'off', 94 | 'no-else-return': 'off', 95 | 'react/jsx-curly-brace-presence': 'off', 96 | 'import/no-useless-path-segments': 'off', 97 | 'react/sort-comp': 'off', 98 | 'no-use-before-define': 'off', 99 | 'no-restricted-syntax': 'off', 100 | 'jest/no-standalone-expect': 'off' 101 | } 102 | } 103 | ]); 104 | -------------------------------------------------------------------------------- /flow-typed/babel-errors.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare module 'babel-errors' { 3 | declare module.exports: { 4 | wrapErrorWithCodeFrame: (any, any) => any, 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /flow-typed/kind2string.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare module 'kind2string' { 3 | declare module.exports: any; 4 | } 5 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'jsdom', 3 | watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'] 4 | }; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "run-p start:*", 8 | "start:watch": "preconstruct watch", 9 | "test": "jest", 10 | "test:watch": "jest --watch", 11 | "changeset": "changeset", 12 | "apply-changesets": "changeset version", 13 | "release": "yarn build && changeset publish", 14 | "postinstall": "preconstruct dev", 15 | "lint": "yarn eslint \"./**/*.js\"", 16 | "lint:fix": "prettier --write packages/**/*.js", 17 | "validate": "yarn lint && yarn test && yarn flow", 18 | "build": "preconstruct build", 19 | "clean": "yarn delete:modules && yarn delete:dist", 20 | "delete:dist": "yarn ws exec --parallel -- rm -rf dist", 21 | "delete:modules": "yarn ws exec --parallel -- rm -rf node_modules && rm -rf node_modules" 22 | }, 23 | "workspaces": [ 24 | "packages/*" 25 | ], 26 | "preconstruct": { 27 | "packages": [ 28 | "packages/extract-react-types", 29 | "packages/kind2string", 30 | "packages/pretty-proptypes" 31 | ] 32 | }, 33 | "transform": { 34 | "^.+\\.jsx?$": "babel-jest" 35 | }, 36 | "dependencies": { 37 | "@aparna036/babel-explode-module": "^2.0.1", 38 | "@babel/core": "^7.4.4", 39 | "@babel/plugin-syntax-flow": "^7.2.0", 40 | "@babel/plugin-syntax-jsx": "^7.2.0", 41 | "@babel/plugin-syntax-typescript": "^7.2.0", 42 | "@babel/runtime": "^7.4.4", 43 | "@babel/types": "^7.0.0-beta.56", 44 | "@changesets/cli": "^2.22.0", 45 | "@emotion/core": "^10.0.14", 46 | "@preconstruct/cli": "^2.8.12", 47 | "ast-pretty-print": "^2.0.1", 48 | "babel-errors": "^1.1.1", 49 | "babel-eslint": "^10.0.1", 50 | "babel-file": "^3.0.0", 51 | "babel-flow-identifiers": "^1.1.3", 52 | "babel-helper-simplify-module": "^2.2.1", 53 | "babel-identifiers": "^1.1.2", 54 | "babel-normalize-comments": "^1.0.1", 55 | "babel-react-components": "^1.1.0", 56 | "babel-type-scopes": "^1.1.0", 57 | "babylon": "^7.0.0-beta.22", 58 | "babylon-options": "^2.0.1", 59 | "eslint": "^9.28.0", 60 | "eslint-config-prettier": "^4.0.0", 61 | "eslint-plugin-import": "^2.31.0", 62 | "eslint-plugin-jest": "^28.12.0", 63 | "eslint-plugin-jsx-a11y": "^6.2.1", 64 | "eslint-plugin-prettier": "^5.4.1", 65 | "eslint-plugin-react": "^7.37.5", 66 | "jest-in-case": "^1.0.2", 67 | "jest-watch-typeahead": "^1.0.0", 68 | "react-markings": "^1.2.0", 69 | "read-file-async": "^1.0.0", 70 | "resolve": "^1.10.1", 71 | "resolve-async": "^1.0.1", 72 | "strip-indent": "^2.0.0" 73 | }, 74 | "devDependencies": { 75 | "@babel/plugin-proposal-class-properties": "^7.4.4", 76 | "@babel/plugin-transform-runtime": "^7.4.4", 77 | "@babel/preset-env": "^7.4.4", 78 | "@babel/preset-flow": "^7.0.0", 79 | "@babel/preset-react": "^7.0.0", 80 | "@babel/preset-typescript": "^7.0.0", 81 | "@testing-library/react": "^12.1.4", 82 | "babel-jest": "^27.0.0", 83 | "babel-loader": "^8.2.2", 84 | "babel-plugin-emotion": "^10.0.14", 85 | "flow-bin": "^0.98.0", 86 | "jest": "^27.0.0", 87 | "jest-in-case": "^1.0.2", 88 | "jsdom": "^11.7.0", 89 | "npm-run-all": "^4.1.5", 90 | "prettier": "^1.13.7", 91 | "react": "^16.3.1", 92 | "react-addons-test-utils": "^15.6.2", 93 | "react-dom": "^16.3.1" 94 | }, 95 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 96 | } 97 | -------------------------------------------------------------------------------- /packages/babel-plugin-extract-react-types/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-extract-react-types 2 | 3 | ## 0.2.0 4 | 5 | ### Minor Changes 6 | 7 | - 33d0126: Introduces new functionality enabling permalinks to each section of a props list. It's done in a backwards-compatible manner requiring no code changes for a dependency bump, but you might want to double check styles are suitable to the surroundings of where the props are displayed. 8 | 9 | ## 0.1.14 10 | 11 | ### Patch Changes 12 | 13 | - Updated dependencies [[`c1aa88d`](https://github.com/atlassian/extract-react-types/commit/c1aa88d6b5913b933711c3cc0139de63b2633678)]: 14 | - extract-react-types@0.30.0 15 | 16 | ## 0.1.13 17 | 18 | ### Patch Changes 19 | 20 | - Updated dependencies [[`683eac7`](https://github.com/atlassian/extract-react-types/commit/683eac7d701293b1ff6a6fc345e9b1b59d0b02e9)]: 21 | - extract-react-types@0.29.0 22 | 23 | ## 0.1.12 24 | 25 | ### Patch Changes 26 | 27 | - [`68bcec6`](https://github.com/atlassian/extract-react-types/commit/68bcec67728218b861fedb99c735a5ddc062ee53) [#152](https://github.com/atlassian/extract-react-types/pull/152) Thanks [@danieldelcore](https://github.com/danieldelcore)! - Added a safe bail-out for when extract-react-types encounters an unsupported keyword or syntax. 28 | In that case, the type will be output as a raw string and summary type will be `raw`. 29 | - Updated dependencies [[`68bcec6`](https://github.com/atlassian/extract-react-types/commit/68bcec67728218b861fedb99c735a5ddc062ee53)]: 30 | - extract-react-types@0.28.0 31 | 32 | ## 0.1.11 33 | 34 | ### Patch Changes 35 | 36 | - Updated dependencies [[`cf31e5e`](https://github.com/atlassian/extract-react-types/commit/cf31e5e4e99648994ceb6bb1719e20226f816532)]: 37 | - extract-react-types@0.27.0 38 | 39 | ## 0.1.10 40 | 41 | ### Patch Changes 42 | 43 | - Updated dependencies [[`19b9bc8`](https://github.com/atlassian/extract-react-types/commit/19b9bc8164216ae3ed40d6abfc93920016ba63e2)]: 44 | - extract-react-types@0.26.0 45 | 46 | ## 0.1.9 47 | 48 | ### Patch Changes 49 | 50 | - Updated dependencies [[`d1115ee`](https://github.com/atlassian/extract-react-types/commit/d1115eecdeedda23caa558f253ee4f769e3f0606)]: 51 | - extract-react-types@0.25.0 52 | 53 | ## 0.1.8 54 | 55 | ### Patch Changes 56 | 57 | - Updated dependencies [[`99f6c8a`](https://github.com/atlassian/extract-react-types/commit/99f6c8a1cd0c41091caa870d233b34c0500b0565), [`849c979`](https://github.com/atlassian/extract-react-types/commit/849c979faf91b6b1f24a85ce267698639e4caeb8)]: 58 | - extract-react-types@0.24.0 59 | 60 | ## 0.1.7 61 | 62 | - Updated dependencies [acb8499](https://github.com/atlassian/extract-react-types/commit/acb8499): 63 | - extract-react-types@0.23.0 64 | 65 | ## 0.1.6 66 | 67 | - Updated dependencies [dc4b719](https://github.com/atlassian/extract-react-types/commit/dc4b719): 68 | - extract-react-types@0.22.0 69 | 70 | ## 0.1.5 71 | 72 | - Updated dependencies [dc4b719](https://github.com/atlassian/extract-react-types/commit/dc4b719): 73 | - extract-react-types@0.21.0 74 | 75 | ## 0.1.4 76 | 77 | - Updated dependencies [533d172](https://github.com/atlassian/extract-react-types/commit/533d172): 78 | - extract-react-types@0.20.0 79 | 80 | ## 0.1.3 81 | 82 | - Updated dependencies [d232e30](https://github.com/atlassian/extract-react-types/commit/d232e30): 83 | - extract-react-types@0.19.0 84 | 85 | ## 0.1.2 86 | 87 | - Updated dependencies [907688c](https://github.com/atlassian/extract-react-types/commit/907688c): 88 | - extract-react-types@0.18.0 89 | 90 | ## 0.1.1 91 | 92 | - Updated dependencies [e682bbb](https://github.com/atlassian/extract-react-types/commit/e682bbb): 93 | - extract-react-types@0.17.0 94 | 95 | ## 0.1.0 96 | 97 | - [minor][277b0be](https://github.com/atlassian/extract-react-types/commit/277b0be): 98 | 99 | - Add babel-plugin-extract-react-types 100 | 101 | - Updated dependencies [277b0be](https://github.com/atlassian/extract-react-types/commit/277b0be): 102 | - extract-react-types@0.16.0 103 | -------------------------------------------------------------------------------- /packages/babel-plugin-extract-react-types/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mitchell Hamilton 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. -------------------------------------------------------------------------------- /packages/babel-plugin-extract-react-types/README.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-extract-react-types 2 | 3 | > A Babel plugin to store the types of React components as a property on the component for documentation 4 | 5 | ## Install 6 | 7 | ```bash 8 | yarn add babel-plugin-extract-react-types pretty-proptypes 9 | ``` 10 | 11 | ## Usage 12 | 13 | `.babelrc` 14 | 15 | ```json 16 | { 17 | "plugins": ["babel-plugin-extract-react-types"] 18 | } 19 | ``` 20 | 21 | ```jsx 22 | import SomeComponent from 'somewhere'; 23 | import Props from 'pretty-proptypes'; 24 | 25 | ; 26 | ``` 27 | 28 | ## Inspiration 29 | 30 | - [babel-plugin-react-docgen](https://github.com/storybooks/babel-plugin-react-docgen) 31 | -------------------------------------------------------------------------------- /packages/babel-plugin-extract-react-types/index.js: -------------------------------------------------------------------------------- 1 | const { findExportedComponents } = require('extract-react-types'); 2 | 3 | module.exports = babel => { 4 | let t = babel.types; 5 | return { 6 | visitor: { 7 | Program(programPath, state) { 8 | let typeSystem = state.file.opts.parserOpts.plugins 9 | .map(plugin => (Array.isArray(plugin) ? plugin[0] : plugin)) 10 | .find(plugin => plugin === 'flow' || plugin === 'typescript'); 11 | 12 | if (!typeSystem) return; 13 | 14 | try { 15 | findExportedComponents(programPath, typeSystem, state.file.filename).forEach( 16 | ({ name, component }) => { 17 | // TODO: handle when name is null 18 | // it will only happen when it's a default and anonymous export 19 | // generate something like this 20 | // export default (var someName = function() {}, someName.___types = theTypes, someName) 21 | if (name !== null) { 22 | programPath.node.body.push( 23 | t.expressionStatement( 24 | t.assignmentExpression( 25 | '=', 26 | t.memberExpression(t.identifier(name), t.identifier('___types')), 27 | babel.parse(`(${JSON.stringify(component)})`).program.body[0].expression 28 | ) 29 | ) 30 | ); 31 | programPath.node.body.push( 32 | t.expressionStatement( 33 | t.assignmentExpression( 34 | '=', 35 | t.memberExpression(t.identifier(name), t.identifier('___displayName')), 36 | t.stringLiteral(name) 37 | ) 38 | ) 39 | ); 40 | } 41 | } 42 | ); 43 | } catch (e) {} 44 | } 45 | } 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /packages/babel-plugin-extract-react-types/index.test.js: -------------------------------------------------------------------------------- 1 | const { transformSync } = require('@babel/core'); 2 | const jestInCase = require('jest-in-case'); 3 | 4 | let flowCases = [ 5 | { 6 | name: 'class', 7 | code: ` 8 | // @flow 9 | 10 | type Props = { 11 | /** this does something */ 12 | wow: boolean 13 | }; 14 | 15 | export class SomeComponent extends React.Component { 16 | render() { 17 | return null; 18 | } 19 | } 20 | ` 21 | }, 22 | { 23 | name: 'arrow function', 24 | code: ` 25 | // @flow 26 | 27 | type Props = { 28 | /** this does something */ 29 | wow: boolean 30 | }; 31 | 32 | export const SomeComponent = (props: Props) => { 33 | return null; 34 | }; 35 | ` 36 | }, 37 | { 38 | name: 'function declaration', 39 | code: ` 40 | // @flow 41 | 42 | type Props = { 43 | /** this does something */ 44 | wow: boolean 45 | }; 46 | 47 | export function SomeComponent(props: Props) { 48 | return null; 49 | } 50 | ` 51 | }, 52 | { 53 | name: 'default class', 54 | code: ` 55 | // @flow 56 | 57 | type Props = { 58 | /** this does something */ 59 | wow: boolean 60 | }; 61 | 62 | export default class SomeComponent extends React.Component { 63 | render() { 64 | return null; 65 | } 66 | } 67 | ` 68 | }, 69 | { 70 | name: 'default function declaration', 71 | code: ` 72 | // @flow 73 | 74 | type Props = { 75 | /** this does something */ 76 | wow: boolean 77 | }; 78 | 79 | export default class SomeComponent extends React.Component { 80 | render() { 81 | return null; 82 | } 83 | } 84 | ` 85 | }, 86 | { 87 | name: 'class declaration export default identifier', 88 | code: ` 89 | // @flow 90 | 91 | type Props = { 92 | /** this does something */ 93 | wow: boolean 94 | }; 95 | 96 | class SomeComponent extends React.Component { 97 | render() { 98 | return null; 99 | } 100 | } 101 | 102 | export default SomeComponent 103 | ` 104 | }, 105 | { 106 | name: 'arrow function export default identifier', 107 | code: ` 108 | // @flow 109 | 110 | type Props = { 111 | /** this does something */ 112 | wow: boolean 113 | }; 114 | 115 | const SomeComponent = (props: Props) => { 116 | return null; 117 | }; 118 | 119 | export default SomeComponent 120 | ` 121 | }, 122 | { 123 | name: 'export default function declaration', 124 | code: ` 125 | // @flow 126 | 127 | type Props = { 128 | /** this does something */ 129 | wow: boolean 130 | }; 131 | 132 | export default function SomeComponent (props: Props) { 133 | return null; 134 | }; 135 | ` 136 | }, 137 | { 138 | name: 'React.memo', 139 | code: ` 140 | // @flow 141 | 142 | type Props = { 143 | /** this does something */ 144 | wow: boolean 145 | }; 146 | 147 | export const SomeComponent = React.memo((props: Props) => { 148 | return null 149 | }) 150 | ` 151 | }, 152 | { 153 | name: 'memo', 154 | code: ` 155 | // @flow 156 | 157 | type Props = { 158 | /** this does something */ 159 | wow: boolean 160 | }; 161 | 162 | export const SomeComponent = memo((props: Props) => { 163 | return null 164 | }) 165 | ` 166 | }, 167 | { 168 | name: 'forwardRef', 169 | code: ` 170 | // @flow 171 | 172 | type Props = { 173 | /** this does something */ 174 | wow: boolean 175 | }; 176 | 177 | export const SomeComponent = forwardRef((props: Props) => { 178 | return null 179 | }) 180 | ` 181 | }, 182 | { 183 | name: 'forwardRef function expression', 184 | code: ` 185 | // @flow 186 | 187 | type Props = { 188 | /** this does something */ 189 | wow: boolean 190 | }; 191 | 192 | export const SomeComponent = forwardRef(function (props: Props) { 193 | return null 194 | }) 195 | ` 196 | }, 197 | { 198 | name: 'forwardRef memo function expression', 199 | code: ` 200 | // @flow 201 | 202 | type Props = { 203 | /** this does something */ 204 | wow: boolean 205 | }; 206 | 207 | export const SomeComponent = memo(forwardRef(function (props: Props) { 208 | return null 209 | })) 210 | ` 211 | }, 212 | { 213 | name: 'forwardRef memo arrow function', 214 | code: ` 215 | // @flow 216 | 217 | type Props = { 218 | /** this does something */ 219 | wow: boolean 220 | }; 221 | 222 | export const SomeComponent = memo(forwardRef((props: Props) => { 223 | return null 224 | })) 225 | ` 226 | }, 227 | { 228 | name: 'arrow function then export', 229 | code: ` 230 | // @flow 231 | 232 | type Props = { 233 | /** this does something */ 234 | wow: boolean 235 | }; 236 | 237 | const SomeComponent = (props: Props) => { 238 | return null 239 | } 240 | 241 | export { SomeComponent } 242 | ` 243 | }, 244 | { 245 | name: 'class declaration then export', 246 | code: ` 247 | // @flow 248 | 249 | type Props = { 250 | /** this does something */ 251 | wow: boolean 252 | }; 253 | 254 | class SomeComponent extends React.Component { 255 | render() { 256 | return null; 257 | } 258 | } 259 | 260 | export { SomeComponent } 261 | ` 262 | }, 263 | { 264 | name: 'default export with named identifier', 265 | code: ` 266 | // @flow 267 | 268 | type Props = { 269 | /** this does something */ 270 | wow: boolean 271 | }; 272 | 273 | const CustomComponent = (props: Props) => { 274 | return null; 275 | }; 276 | 277 | export default CustomComponent 278 | ` 279 | } 280 | ]; 281 | 282 | describe('File type', () => { 283 | jestInCase( 284 | 'flow', 285 | ({ code }) => { 286 | let transformedCode = transformSync(code, { 287 | plugins: [ 288 | require.resolve('./index'), 289 | require.resolve('@babel/plugin-syntax-jsx'), 290 | require.resolve('@babel/plugin-syntax-flow') 291 | ], 292 | babelrc: false, 293 | configFile: false 294 | }).code; 295 | expect(transformedCode).toMatchSnapshot(); 296 | }, 297 | flowCases 298 | ); 299 | 300 | let tsCases = [ 301 | { 302 | name: 'named', 303 | code: `export class SomeComponent extends React.Component<{ foo: boolean }> { 304 | 305 | }` 306 | }, 307 | { 308 | name: 'default', 309 | code: `export default class SomeComponent extends React.Component<{ foo: boolean }> { 310 | 311 | }` 312 | } 313 | ]; 314 | 315 | jestInCase( 316 | 'typescript', 317 | ({ code }) => { 318 | let transformedCode = transformSync(code, { 319 | plugins: [ 320 | require.resolve('./index'), 321 | require.resolve('@babel/plugin-syntax-jsx'), 322 | require.resolve('@babel/plugin-syntax-typescript') 323 | ], 324 | babelrc: false, 325 | configFile: false 326 | }).code; 327 | expect(transformedCode).toMatchSnapshot(); 328 | }, 329 | tsCases 330 | ); 331 | }); 332 | -------------------------------------------------------------------------------- /packages/babel-plugin-extract-react-types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-extract-react-types", 3 | "version": "0.2.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "repository": "atlassian/extract-react-types", 7 | "description": "Plugin to allow pretty-proptypes to more easily pull in component data", 8 | "files": [ 9 | "index.js" 10 | ], 11 | "keywords": [ 12 | "react", 13 | "flow", 14 | "typescript", 15 | "prop-types", 16 | "documentation" 17 | ], 18 | "author": "Mitchell Hamilton", 19 | "dependencies": { 20 | "extract-react-types": "^0.30.0" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7.4.4", 24 | "@babel/plugin-syntax-flow": "^7.2.0", 25 | "@babel/plugin-syntax-jsx": "^7.2.0", 26 | "@babel/plugin-syntax-typescript": "^7.2.0", 27 | "jest-in-case": "^1.0.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/extract-react-types-loader/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # extract-react-types-loader 2 | 3 | ## 1.0.0 4 | 5 | ### Major Changes 6 | 7 | - [`1ec5f76`](https://github.com/atlassian/extract-react-types/commit/1ec5f76c5e99ce78e952a83eac178fcc22e5f557) [#196](https://github.com/atlassian/extract-react-types/pull/196) Thanks [@marionebl](https://github.com/marionebl)! - Remove Atlaskit specific process.env switches (#195) 8 | 9 | BREAKING CHANGE 10 | This removes the previously available process.env switches to conditionally disable the loader. 11 | To restore the previous behaviour you'll have to use the `resolveLoader` webpack config, e.g. 12 | 13 | ```js 14 | // webpack.config.js 15 | const enabled = 16 | ['production', 'staging'].includes(process.env.WEBSITE_ENV) || 17 | process.env.FORCE_EXTRACT_REACT_TYPES; 18 | 19 | module.exports = { 20 | /* ... */ 21 | resolveLoader: { 22 | alias: { 23 | 'extract-react-types-loader': enabled 24 | ? undefined 25 | : require.resolve('./noop-extract-react-types-loader') 26 | } 27 | } 28 | }; 29 | ``` 30 | 31 | ```js 32 | // noop-extract-react-types-loader.js 33 | module.exports = function noopExtractReactPropTypesLoader() { 34 | return `module.exports = { 35 | component: { 36 | kind: 'object', 37 | members: [ 38 | { 39 | kind: 'property', 40 | key: { kind: 'id', name: 'Warning' }, 41 | value: { kind: 'any' }, 42 | optional: false, 43 | leadingComments: [ 44 | { 45 | type: 'commentBlock', 46 | value: `extract-react-types is not being run in dev mode for speed reasons. If you need to 47 | see prop types add the environment variable \`FORCE_EXTRACT_REACT_TYPES\` 48 | raw: '**' 49 | } 50 | ], 51 | default: { 52 | kind: 'string', 53 | value: 'Prop types are not shown in dev mode' 54 | } 55 | } 56 | ], 57 | referenceIdName: 'NoopPropTpes' 58 | } 59 | };` 60 | } 61 | ``` 62 | 63 | ## 0.3.17 64 | 65 | ### Patch Changes 66 | 67 | - [`c1aa88d`](https://github.com/atlassian/extract-react-types/commit/c1aa88d6b5913b933711c3cc0139de63b2633678) [#172](https://github.com/atlassian/extract-react-types/pull/172) Thanks [@danieldelcore](https://github.com/danieldelcore)! - Forced minor (no changes) 68 | 69 | - Updated dependencies [[`c1aa88d`](https://github.com/atlassian/extract-react-types/commit/c1aa88d6b5913b933711c3cc0139de63b2633678)]: 70 | - extract-react-types@0.30.0 71 | 72 | ## 0.3.16 73 | 74 | ### Patch Changes 75 | 76 | - Updated dependencies [[`683eac7`](https://github.com/atlassian/extract-react-types/commit/683eac7d701293b1ff6a6fc345e9b1b59d0b02e9)]: 77 | - extract-react-types@0.29.0 78 | 79 | ## 0.3.15 80 | 81 | ### Patch Changes 82 | 83 | - Updated dependencies [[`68bcec6`](https://github.com/atlassian/extract-react-types/commit/68bcec67728218b861fedb99c735a5ddc062ee53)]: 84 | - extract-react-types@0.28.0 85 | 86 | ## 0.3.14 87 | 88 | ### Patch Changes 89 | 90 | - Updated dependencies [[`cf31e5e`](https://github.com/atlassian/extract-react-types/commit/cf31e5e4e99648994ceb6bb1719e20226f816532)]: 91 | - extract-react-types@0.27.0 92 | 93 | ## 0.3.13 94 | 95 | ### Patch Changes 96 | 97 | - Updated dependencies [[`19b9bc8`](https://github.com/atlassian/extract-react-types/commit/19b9bc8164216ae3ed40d6abfc93920016ba63e2)]: 98 | - extract-react-types@0.26.0 99 | 100 | ## 0.3.12 101 | 102 | ### Patch Changes 103 | 104 | - Updated dependencies [[`d1115ee`](https://github.com/atlassian/extract-react-types/commit/d1115eecdeedda23caa558f253ee4f769e3f0606)]: 105 | - extract-react-types@0.25.0 106 | 107 | ## 0.3.11 108 | 109 | ### Patch Changes 110 | 111 | - Updated dependencies [[`99f6c8a`](https://github.com/atlassian/extract-react-types/commit/99f6c8a1cd0c41091caa870d233b34c0500b0565), [`849c979`](https://github.com/atlassian/extract-react-types/commit/849c979faf91b6b1f24a85ce267698639e4caeb8)]: 112 | - extract-react-types@0.24.0 113 | 114 | ## 0.3.10 115 | 116 | - Updated dependencies [acb8499](https://github.com/atlassian/extract-react-types/commit/acb8499): 117 | - extract-react-types@0.23.0 118 | 119 | ## 0.3.9 120 | 121 | ### Patch Changes 122 | 123 | - [patch][361a24b](https://github.com/atlassian/extract-react-types/commit/361a24b): 124 | Add readme 125 | 126 | - Updated dependencies [dc4b719](https://github.com/atlassian/extract-react-types/commit/dc4b719): 127 | - extract-react-types@0.22.0 128 | 129 | ## 0.3.8 130 | 131 | - Updated dependencies [dc4b719](https://github.com/atlassian/extract-react-types/commit/dc4b719): 132 | - extract-react-types@0.21.0 133 | 134 | ## 0.3.7 135 | 136 | ### Patch Changes 137 | 138 | - [patch][e6cc1f5](https://github.com/atlassian/extract-react-types/commit/e6cc1f5): 139 | Remove dangerous debug code that broke everything 140 | 141 | ## 0.3.5 142 | 143 | ### Patch Changes 144 | 145 | - [patch][47a2b1d](https://github.com/atlassian/extract-react-types/commit/47a2b1d): 146 | Don't pull types from src if types file exits 147 | 148 | - Updated dependencies [533d172](https://github.com/atlassian/extract-react-types/commit/533d172): 149 | - extract-react-types@0.20.0 150 | 151 | ## 0.3.4 152 | 153 | - Updated dependencies [d232e30](https://github.com/atlassian/extract-react-types/commit/d232e30): 154 | - extract-react-types@0.19.0 155 | 156 | ## 0.3.3 157 | 158 | ### Patch Changes 159 | 160 | - [patch][e4c1b4b](https://github.com/atlassian/extract-react-types/commit/e4c1b4b): 161 | - Added support for loading modules from src as well as node_modules 162 | 163 | * Updated dependencies [907688c](https://github.com/atlassian/extract-react-types/commit/907688c): 164 | - extract-react-types@0.18.0 165 | 166 | ## 0.3.2 167 | 168 | - Updated dependencies [e682bbb](https://github.com/atlassian/extract-react-types/commit/e682bbb): 169 | - extract-react-types@0.17.0 170 | 171 | ## 0.3.1 172 | 173 | - [patch][3299e3a](https://github.com/atlassian/extract-react-types/commit/3299e3a): 174 | 175 | - Fix the dev mode data to align with new structure for ERT 0.15, as previously no render was occurring 176 | 177 | - Updated dependencies [277b0be](https://github.com/atlassian/extract-react-types/commit/277b0be): 178 | - Updated dependencies [8f04dad](https://github.com/atlassian/extract-react-types/commit/8f04dad): 179 | - Updated dependencies [6bc521c](https://github.com/atlassian/extract-react-types/commit/6bc521c): 180 | - extract-react-types@0.16.0 181 | 182 | ## 0.3.0 183 | 184 | - [minor][882a85c](https://bitbucket.org/atlassian/atlaskit-mk-2/commits/882a85c): 185 | 186 | - Use version 0.15.0 of extract-react-types - the breaking change cannot be absorbed by changes in this package. 187 | 188 | ## 0.2.2 189 | 190 | - [patch] Upgrade extract-react-types to add TypeScript support. [c742e5a](https://bitbucket.org/atlassian/atlaskit-mk-2/commits/c742e5a) 191 | 192 | ## 0.2.1 193 | 194 | - [patch] Remove console log [e16d2b6](https://bitbucket.org/atlassian/atlaskit-mk-2/commits/e16d2b6) 195 | 196 | ## 0.2.0 197 | 198 | - [minor] Add pathFilter function to resolve atlaskit:src paths within atlaskit [c5214a3](https://bitbucket.org/atlassian/atlaskit-mk-2/commits/c5214a3) 199 | 200 | ## 0.1.3 201 | 202 | - [patch] Sanity test release, no actual change [481c086](https://bitbucket.org/atlassian/atlaskit-mk-2/commits/481c086) 203 | 204 | ## 0.1.2 205 | 206 | - [patch] Upgrade extract-react-types version [f78d035](https://bitbucket.org/atlassian/atlaskit-mk-2/commits/f78d035) 207 | 208 | ## 0.1.1 209 | 210 | - [patch] Makes packages Flow types compatible with version 0.67 [25daac0](https://bitbucket.org/atlassian/atlaskit-mk-2/commits/25daac0) 211 | 212 | ## 0.1.0 213 | 214 | - [minor] Npm fell behind code [9684be0](https://bitbucket.org/atlassian/atlaskit-mk-2/commits/9684be0) 215 | -------------------------------------------------------------------------------- /packages/extract-react-types-loader/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ben Conolly 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. -------------------------------------------------------------------------------- /packages/extract-react-types-loader/README.md: -------------------------------------------------------------------------------- 1 | # Extract React Types Loader 2 | 3 | Please see [the documentation for pretty-proptypes](https://github.com/atlassian/extract-react-types/tree/master/packages/pretty-proptypes) for information on how to use this package. 4 | -------------------------------------------------------------------------------- /packages/extract-react-types-loader/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | const path = require('path'); 3 | const { extractReactTypes } = require('extract-react-types'); 4 | 5 | module.exports = function extractReactTypesLoader(content /* : string */) { 6 | const filename = this.resource; 7 | const ext = path.extname(filename); 8 | const typeSystem = ext === '.ts' || ext === '.tsx' ? 'typescript' : 'flow'; 9 | 10 | const resolveOpts = { 11 | pathFilter: (pkg, location, dist) => { 12 | if ( 13 | !pkg.types && 14 | pkg['atlaskit:src'] && 15 | location.includes('node_modules') && 16 | location.includes(pkg.main) 17 | ) { 18 | return location.replace(dist, pkg['atlaskit:src']); 19 | } 20 | return null; 21 | }, 22 | /* This is here for instances where there are paths which are not packages */ 23 | moduleDirectory: ['node_modules', 'src'] 24 | }; 25 | 26 | const types = extractReactTypes(content, typeSystem, filename, resolveOpts); 27 | return `module.exports = ${JSON.stringify(types)}`; 28 | }; 29 | -------------------------------------------------------------------------------- /packages/extract-react-types-loader/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "extract-react-types-loader", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "atlassian/extract-react-types", 6 | "description": "Load extract-react-types data given a fiile path, using webpack", 7 | "license": "MIT", 8 | "author": "Ben Conolly", 9 | "keywords": [ 10 | "react", 11 | "flow", 12 | "typescript", 13 | "prop-types", 14 | "documentation" 15 | ], 16 | "dependencies": { 17 | "extract-react-types": "^0.30.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/extract-react-types/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # extract-react-types 2 | 3 | ## 0.30.3 4 | 5 | ### Patch Changes 6 | 7 | - 882277e: Bumps dev deps to resolve vulnerability with ansi-regex 8 | 9 | ## 0.30.2 10 | 11 | ### Patch Changes 12 | 13 | - [`f5866b0`](https://github.com/atlassian/extract-react-types/commit/f5866b0d5a93663d9f9f4f827a9b0512539ede08) [#188](https://github.com/atlassian/extract-react-types/pull/188) Thanks [@declan-warn](https://github.com/declan-warn)! - Adds support for string type keys in TypeScript. 14 | 15 | ## 0.30.1 16 | 17 | ### Patch Changes 18 | 19 | - [`7a46827`](https://github.com/atlassian/extract-react-types/commit/7a468274d4ad3f6db8c4ad42613cecb122ed002e) [#183](https://github.com/atlassian/extract-react-types/pull/183) Thanks [@danieldelcore](https://github.com/danieldelcore)! - Adds support for React.FC and FC 20 | 21 | ## 0.30.0 22 | 23 | ### Minor Changes 24 | 25 | - [`c1aa88d`](https://github.com/atlassian/extract-react-types/commit/c1aa88d6b5913b933711c3cc0139de63b2633678) [#172](https://github.com/atlassian/extract-react-types/pull/172) Thanks [@danieldelcore](https://github.com/danieldelcore)! - Forced minor (no changes) 26 | 27 | ## 0.29.3 28 | 29 | ### Patch Changes 30 | 31 | - [`4589e9f`](https://github.com/atlassian/extract-react-types/commit/4589e9f272adb4c7c4135c6e8165bf40346cacf4) [#169](https://github.com/atlassian/extract-react-types/pull/169) Thanks [@danieldelcore](https://github.com/danieldelcore)! - Fixes a bug where variable names clash with type properties of the same name, causing a "Missing converter for: [path]" error 32 | 33 | * [`ff72fd8`](https://github.com/atlassian/extract-react-types/commit/ff72fd809d0cab377283f5cf8fb48954e917d583) [#171](https://github.com/atlassian/extract-react-types/pull/171) Thanks [@danieldelcore](https://github.com/danieldelcore)! - Internal refactor. Changes internal logic and file structure only. 34 | 35 | ## 0.29.2 36 | 37 | ### Patch Changes 38 | 39 | - [`8688631`](https://github.com/atlassian/extract-react-types/commit/8688631d360bc7d4f42f166d62ac14ae07750f5e) [#167](https://github.com/atlassian/extract-react-types/pull/167) Thanks [@danieldelcore](https://github.com/danieldelcore)! - Minor variable rename and code formatting 40 | 41 | ## 0.29.1 42 | 43 | ### Patch Changes 44 | 45 | - [`945eb91`](https://github.com/atlassian/extract-react-types/commit/945eb91b43a0ab0b0baa354515007c8b0aad79f2) [#164](https://github.com/atlassian/extract-react-types/pull/164) Thanks [@danieldelcore](https://github.com/danieldelcore)! - forwardRef wrapped in a memo now works with type inference 46 | 47 | ## 0.29.0 48 | 49 | ### Minor Changes 50 | 51 | - [`683eac7`](https://github.com/atlassian/extract-react-types/commit/683eac7d701293b1ff6a6fc345e9b1b59d0b02e9) [#159](https://github.com/atlassian/extract-react-types/pull/159) Thanks [@danieldelcore](https://github.com/danieldelcore)! - Add support for memos typed with generics 52 | 53 | ## 0.28.0 54 | 55 | ### Minor Changes 56 | 57 | - [`68bcec6`](https://github.com/atlassian/extract-react-types/commit/68bcec67728218b861fedb99c735a5ddc062ee53) [#152](https://github.com/atlassian/extract-react-types/pull/152) Thanks [@danieldelcore](https://github.com/danieldelcore)! - Added a safe bail-out for when extract-react-types encounters an unsupported keyword or syntax. 58 | In that case, the type will be output as a raw string and summary type will be `raw`. 59 | 60 | ## 0.27.1 61 | 62 | ### Patch Changes 63 | 64 | - [`1eea112`](https://github.com/atlassian/extract-react-types/commit/1eea112cfc308f07219ee5e20f4813f25ab25fda) [#151](https://github.com/atlassian/extract-react-types/pull/151) Thanks [@danieldelcore](https://github.com/danieldelcore)! - Minor clean-up and formatting 65 | 66 | ## 0.27.0 67 | 68 | ### Minor Changes 69 | 70 | - [`cf31e5e`](https://github.com/atlassian/extract-react-types/commit/cf31e5e4e99648994ceb6bb1719e20226f816532) [#143](https://github.com/atlassian/extract-react-types/pull/143) Thanks [@danieldelcore](https://github.com/danieldelcore)! - Added support for React.FC type annotations 71 | 72 | ## 0.26.0 73 | 74 | ### Minor Changes 75 | 76 | - [`19b9bc8`](https://github.com/atlassian/extract-react-types/commit/19b9bc8164216ae3ed40d6abfc93920016ba63e2) [#134](https://github.com/atlassian/extract-react-types/pull/134) Thanks [@danieldelcore](https://github.com/danieldelcore)! - Add support for forwardRefs typed via TS type arguments (aka generics) 77 | 78 | ## 0.25.0 79 | 80 | ### Minor Changes 81 | 82 | - [`d1115ee`](https://github.com/atlassian/extract-react-types/commit/d1115eecdeedda23caa558f253ee4f769e3f0606) [#126](https://github.com/atlassian/extract-react-types/pull/126) Thanks [@pgmanutd](https://github.com/pgmanutd)! - add support for TSTypeQuery 83 | 84 | ## 0.24.0 85 | 86 | ### Minor Changes 87 | 88 | - [`99f6c8a`](https://github.com/atlassian/extract-react-types/commit/99f6c8a1cd0c41091caa870d233b34c0500b0565) [#118](https://github.com/atlassian/extract-react-types/pull/118) Thanks [@gwyneplaine](https://github.com/gwyneplaine)! - Update ert to resolve export { default } to a relevant react component 89 | 90 | ### Patch Changes 91 | 92 | - [`63c7367`](https://github.com/atlassian/extract-react-types/commit/63c7367b5aa449ef8fffbf1d9daf0da71c5dabff) Thanks [@Noviny](https://github.com/Noviny)! - Interal refactor, no logic changes 93 | 94 | ## 0.23.0 95 | 96 | ### Minor Changes 97 | 98 | - [minor][acb8499](https://github.com/atlassian/extract-react-types/commit/acb8499): 99 | Adds support for the TSUnknownKeyword 100 | 101 | ## 0.22.1 102 | 103 | ### Patch Changes 104 | 105 | - [patch][3385ac3](https://github.com/atlassian/extract-react-types/commit/3385ac3): 106 | Enusre 'value' exists before attempting to filter its members 107 | 108 | ## 0.22.0 109 | 110 | ### Minor Changes 111 | 112 | - [minor][dc4b719](https://github.com/atlassian/extract-react-types/commit/dc4b719): 113 | Adds support for TypeScript's as expression. 114 | 115 | ### Patch Changes 116 | 117 | - [patch][2e473dd](https://github.com/atlassian/extract-react-types/commit/2e473dd): 118 | Fix to ensure members exist before attempting to filter them 119 | - [patch][6eea533](https://github.com/atlassian/extract-react-types/commit/6eea533): 120 | Introduces a workaround for TypeScript types that are unable to be resolved and return null 121 | 122 | ## 0.21.0 123 | 124 | ### Minor Changes 125 | 126 | - [minor][dc4b719](https://github.com/atlassian/extract-react-types/commit/dc4b719): 127 | Adds support for TypeScript's as expression. 128 | 129 | ## 0.20.1 130 | 131 | ### Patch Changes 132 | 133 | - [patch][e6cc1f5](https://github.com/atlassian/extract-react-types/commit/e6cc1f5): 134 | Remove dangerous debug code that broke everything 135 | 136 | ## 0.20.0 137 | 138 | ### Minor Changes 139 | 140 | - [minor][533d172](https://github.com/atlassian/extract-react-types/commit/533d172): 141 | Add support for opaque types 142 | 143 | ## 0.19.0 144 | 145 | ### Minor Changes 146 | 147 | - [minor][d232e30](https://github.com/atlassian/extract-react-types/commit/d232e30): 148 | Implement stub for TSConditionalType 149 | 150 | ## 0.18.0 151 | 152 | ### Minor Changes 153 | 154 | - [minor][907688c](https://github.com/atlassian/extract-react-types/commit/907688c): 155 | sets 'all' option in flow parser to true 156 | 157 | ### Patch Changes 158 | 159 | - [patch][e4c1b4b](https://github.com/atlassian/extract-react-types/commit/e4c1b4b): 160 | - Changed the babel-explode-module to be referenced from @aparna036/babel-explode-module which has the opaque type 161 | variable declaration support. 162 | 163 | ## 0.17.0 164 | 165 | ### Minor Changes 166 | 167 | - [minor][e682bbb](https://github.com/atlassian/extract-react-types/commit/e682bbb): 168 | Changes default export of `extract-react-types` to a named export. See below for changes. 169 | 170 | ```diff 171 | -import extractReactTypes from 'extract-react-types'; 172 | +import { extractReactTypes } from 'extract-react-types'; 173 | // or in cjs 174 | -const extractReactTypes = require('extract-react-types'); 175 | +const { extractReactTypes } = require('extract-react-types'); 176 | ``` 177 | 178 | ### Patch Changes 179 | 180 | - [patch][4b3b4a4](https://github.com/atlassian/extract-react-types/commit/4b3b4a4): 181 | - Add logicalExpression converter 182 | 183 | ## 0.16.1 184 | 185 | - [patch][e401ba8](https://github.com/atlassian/extract-react-types/commit/e401ba8): 186 | 187 | - Add converter for typeCastExpression 188 | 189 | - [patch][6769531](https://github.com/atlassian/extract-react-types/commit/6769531): 190 | - Decorators should work again 191 | - Allow default props to be missing 192 | - Return correct names for nested properties in an object 193 | - Add `key` field to arrays (missing previously) 194 | 195 | ## 0.16.0 196 | 197 | - [minor][277b0be](https://github.com/atlassian/extract-react-types/commit/277b0be): 198 | 199 | - Add findExportedComponents function 200 | 201 | - [minor][8f04dad](https://github.com/atlassian/extract-react-types/commit/8f04dad): 202 | 203 | - Add name to function components like class components 204 | 205 | - [minor][6bc521c](https://github.com/atlassian/extract-react-types/commit/6bc521c): 206 | - Support memo, forwardRef and function expressions 207 | 208 | ## 0.15.1 209 | 210 | - **bug fix** We were calling `convert` in our initial function setup. We have switched to using nodes instead so we do not run convert on all assignment expressions. 211 | 212 | ## 0.15.0 213 | 214 | - **breaking:** We have changed how we approach types in a file. We try and resolve default exports, rather than resolving all react class componets in the file. This causes two breaking changes: 215 | - instead of an array of classes, `Program` now has a single `component` property. 216 | - Because of this, anything that relied on the `classes` attribute is invalid. 217 | - **breaking** We now will attempt to resolve the default export before falling back its previous method of analyzing props (using the first component class in a file). 218 | - 🎉 FEATURE 🎉: `extract-react-types` now supports functional components as default exports. We will resolve both the props and the default props as a best effort. Huge thanks to [Peter Gleeson](https://github.com/petegleeson) for working on this. 219 | 220 | ## 0.14.7 221 | 222 | - add support for non-imported TS index access 223 | 224 | ## 0.14.6 225 | 226 | - add support for TSIndexedAccessType and fix TSQualifiedName for imported types 227 | 228 | ## 0.14.5 229 | 230 | - Do not throw an error when prop types contain a generic spread 231 | - Update generic converter to convert utility types - at this time only $Exact, with the intent to add more. 232 | $Exact is now converted to T directly to make object spreading 233 | easier to work with as we do not care about exactness when spreading 234 | from a prop documentation perspective. 235 | - Gitignore vscode metadata 236 | - Add referenceIdName to identifiers converted in type mode 237 | This provides a name that can be used when wanting to print the name of 238 | a generic or similar, which is what we're doing with the typeof node. 239 | - Update typeof to use referenceIdName if name does not exist 240 | 241 | ## 0.14.4 242 | 243 | - `getProp` recursive function now relies on `resolveFromGeneric` to escape from intersections, allowing for nested interrsections to find props. 244 | 245 | ## 0.14.3 246 | 247 | - call to `loadFileSync` in `ExportNamedDeclaration` was not being passed in the loaderOpts, causing an error in the parsing. Options are now passed through correctly. 248 | - Fix Id to have additional optional property. 249 | 250 | ## 0.14.2 251 | 252 | - fix decorator plugin implementation 253 | 254 | ## v0.14.1 255 | 256 | - add decorators plugin to the babel process. 257 | 258 | ## v0.14.0 259 | 260 | - Add Proper Typescript support 261 | 262 | Most typescript types should now have converters. Using `extract-react-types` with typescript is no longer likely to be disappointing and upsetting. 263 | 264 | If you find any converters that were not added, please contact @noviny, or submit a pull request. <3 265 | 266 | ## v0.13.1 267 | 268 | - Fix incorrect typing 269 | - Add Changelog 270 | -------------------------------------------------------------------------------- /packages/extract-react-types/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 - present Atlassian Pty Ltd 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /packages/extract-react-types/README.md: -------------------------------------------------------------------------------- 1 | # extract-react-types 2 | 3 | > Extract Flow & TypeScript types from React Components 4 | 5 | ## Features 6 | 7 | - Supports flow and typescript 8 | - Extracts the description of the props too ( Great for documentation ) 9 | 10 | ## Usage 11 | 12 | ```sh 13 | $ yarn add extract-react-types 14 | ``` 15 | 16 | ```js 17 | // Component.js 18 | 19 | class Component extends React.Component<{ foo: boolean }> {} 20 | ``` 21 | 22 | Output: 23 | 24 | ```js 25 | { 26 | "kind": "program", 27 | "classes": [ 28 | { 29 | "kind": "object", 30 | "members": [ 31 | { 32 | "kind": "property", 33 | "key": { 34 | "kind": "id", 35 | "name": "foo" 36 | }, 37 | "value": { 38 | "kind": "boolean" 39 | }, 40 | "optional": false 41 | } 42 | ], 43 | "name": { 44 | "kind": "id", 45 | "name": "Component", 46 | "type": null 47 | } 48 | } 49 | ] 50 | } 51 | ``` 52 | 53 | ## Related projects: 54 | 55 | - [pretty-proptypes](https://github.com/atlassian/extract-react-types/tree/master/packages/pretty-proptypes) 56 | - [babel-plugin-extract-react-types](https://github.com/atlassian/extract-react-types/tree/master/packages/babel-plugin-extract-react-types) 57 | - [extract-react-types-loader](https://github.com/atlassian/extract-react-types/tree/master/packages/extract-react-types-loader) 58 | -------------------------------------------------------------------------------- /packages/extract-react-types/__fixtures__/component.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | type Props = { 3 | className?: string 4 | } 5 | export default ({ className }: Props) =>
-------------------------------------------------------------------------------- /packages/extract-react-types/__fixtures__/componentB.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | type Props = { 3 | className?: string 4 | } 5 | export const x = ({ className }: Props) =>
-------------------------------------------------------------------------------- /packages/extract-react-types/__fixtures__/nested-exports/index.ts: -------------------------------------------------------------------------------- 1 | export * from './nested-export'; -------------------------------------------------------------------------------- /packages/extract-react-types/__fixtures__/nested-exports/nested-export.ts: -------------------------------------------------------------------------------- 1 | export interface NestedInterface1 { 2 | nestedProperty1: boolean; 3 | nestedProperty2: string; 4 | } -------------------------------------------------------------------------------- /packages/extract-react-types/__fixtures__/props.ts: -------------------------------------------------------------------------------- 1 | export interface Props { 2 | id: number; 3 | name: string; 4 | } 5 | 6 | export * from './types'; -------------------------------------------------------------------------------- /packages/extract-react-types/__fixtures__/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "some cool package", 3 | "version": "0.0.1" 4 | } -------------------------------------------------------------------------------- /packages/extract-react-types/__fixtures__/types.ts: -------------------------------------------------------------------------------- 1 | export interface Type { 2 | c: boolean 3 | } 4 | 5 | export * from './nested-exports'; -------------------------------------------------------------------------------- /packages/extract-react-types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "extract-react-types", 3 | "version": "0.30.3", 4 | "main": "dist/extract-react-types.cjs.js", 5 | "repository": "atlassian/extract-react-types", 6 | "description": "Parse prop-types from react components using typescript or flow", 7 | "author": "James Kyle ", 8 | "license": "Apache-2.0", 9 | "files": [ 10 | "dist", 11 | "src" 12 | ], 13 | "keywords": [ 14 | "react", 15 | "flow", 16 | "typescript", 17 | "prop-types", 18 | "documentation" 19 | ], 20 | "dependencies": { 21 | "@aparna036/babel-explode-module": "^2.0.1", 22 | "@babel/core": "^7.4.4", 23 | "@babel/runtime": "^7.4.4", 24 | "@babel/types": "^7.0.0-beta.56", 25 | "ast-pretty-print": "^2.0.1", 26 | "babel-errors": "^1.1.1", 27 | "babel-file": "^3.0.0", 28 | "babel-flow-identifiers": "^1.1.3", 29 | "babel-helper-simplify-module": "^2.2.1", 30 | "babel-identifiers": "^1.1.2", 31 | "babel-normalize-comments": "^1.0.1", 32 | "babel-react-components": "^1.1.0", 33 | "babel-type-scopes": "^1.1.0", 34 | "babylon-options": "^2.0.1", 35 | "babylon": "^7.0.0-beta.22", 36 | "read-file-async": "^1.0.0", 37 | "resolve-async": "^1.0.1", 38 | "resolve": "^1.10.1", 39 | "strip-indent": "^2.0.0" 40 | }, 41 | "devDependencies": { 42 | "babel-jest": "^27.0.0", 43 | "flow-bin": "^0.98.0", 44 | "jest": "^27.0.0", 45 | "prettier": "^1.13.7" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/extract-react-types/src/export-manager.js: -------------------------------------------------------------------------------- 1 | import { explodeModule } from '@aparna036/babel-explode-module'; 2 | import { explodedToStatements } from 'babel-helper-simplify-module'; 3 | import printAST from 'ast-pretty-print'; 4 | import { loadFileSync, resolveImportFilePathSync } from './file-loader'; 5 | 6 | export function hasDestructuredDefaultExport(path) { 7 | const exportPath = path 8 | .get('body') 9 | .find( 10 | bodyPath => 11 | bodyPath.isExportNamedDeclaration() && 12 | bodyPath.get('specifiers').filter(n => n.node.exported.name === 'default').length 13 | ); 14 | 15 | return Boolean(exportPath); 16 | } 17 | 18 | export function followExports(path, context, convert) { 19 | const exportPath = path 20 | .get('body') 21 | .find( 22 | bodyPath => 23 | bodyPath.isExportNamedDeclaration() && 24 | bodyPath.get('specifiers').filter(n => n.node.exported.name === 'default') 25 | ); 26 | 27 | if (!exportPath) { 28 | throw new Error({ 29 | message: 'No export path found' 30 | }); 31 | } 32 | 33 | try { 34 | const filePath = resolveImportFilePathSync(exportPath, context.resolveOptions); 35 | const file = loadFileSync(filePath, context.parserOpts); 36 | const converted = convert(file.path, context); 37 | return converted; 38 | } catch (e) { 39 | throw new Error(e); 40 | } 41 | } 42 | 43 | export function findExports( 44 | path, 45 | exportsToFind: 'all' | 'default' 46 | ): Array<{ name: string | null, path: any }> { 47 | let formattedExports = []; 48 | 49 | path 50 | .get('body') 51 | .filter(bodyPath => 52 | // we only check for named and default exports here, we don't want export all 53 | exportsToFind === 'default' 54 | ? bodyPath.isExportDefaultDeclaration() 55 | : (bodyPath.isExportNamedDeclaration() && 56 | bodyPath.node.source === null && 57 | // exportKind is 'value' or 'type' in flow 58 | (bodyPath.node.exportKind === 'value' || 59 | // exportKind is undefined in typescript 60 | bodyPath.node.exportKind === undefined)) || 61 | bodyPath.isExportDefaultDeclaration() 62 | ) 63 | .forEach(exportPath => { 64 | const declaration = exportPath.get('declaration'); 65 | 66 | if (exportPath.isExportDefaultDeclaration()) { 67 | if (declaration.isIdentifier()) { 68 | let binding = path.scope.bindings[declaration.node.name].path; 69 | 70 | if (binding.isVariableDeclarator()) { 71 | binding = binding.get('init'); 72 | } 73 | 74 | formattedExports.push({ 75 | name: declaration.node.name, 76 | path: binding 77 | }); 78 | } else { 79 | let name = null; 80 | 81 | if ( 82 | (declaration.isClassDeclaration() || declaration.isFunctionDeclaration()) && 83 | declaration.node.id !== null 84 | ) { 85 | name = declaration.node.id.name; 86 | } 87 | 88 | formattedExports.push({ name, path: declaration }); 89 | } 90 | } else { 91 | const specifiers = exportPath.get('specifiers'); 92 | 93 | if (specifiers.length === 0) { 94 | if (declaration.isFunctionDeclaration() || declaration.isClassDeclaration()) { 95 | let identifier = declaration.node.id; 96 | formattedExports.push({ 97 | name: identifier === null ? null : identifier.name, 98 | path: declaration 99 | }); 100 | } 101 | 102 | if (declaration.isVariableDeclaration()) { 103 | declaration.get('declarations').forEach(declarator => { 104 | formattedExports.push({ 105 | name: declarator.node.id.name, 106 | path: declarator.get('init') 107 | }); 108 | }); 109 | } 110 | } else { 111 | specifiers.forEach(specifier => { 112 | let name = specifier.node.local.name; 113 | let binding = path.scope.bindings[name].path; 114 | if (binding.isVariableDeclarator()) { 115 | binding = binding.get('init'); 116 | } 117 | formattedExports.push({ 118 | name, 119 | path: binding 120 | }); 121 | }); 122 | } 123 | } 124 | }); 125 | 126 | return formattedExports; 127 | } 128 | 129 | export function matchExported(file: Object, exportName: string) { 130 | const exploded = explodeModule(file.path.node); 131 | const statements = explodedToStatements(exploded); 132 | const program = Object.assign({}, file.path.node, { 133 | body: statements 134 | }); 135 | 136 | file.path.replaceWith(program); 137 | 138 | const match = exploded.exports.find(item => item.external === exportName); 139 | 140 | if (!match) { 141 | return null; 142 | } 143 | 144 | let local = match.local; 145 | 146 | if (!local) { 147 | return null; 148 | } 149 | 150 | if (local === 'default' && match.source) { 151 | local = exportName; 152 | } 153 | 154 | let statement = file.path.get('body').find(item => { 155 | // Ignore export all & default declarations, since they do not have specifiers/ids. 156 | if (!item.isDeclaration() || item.isExportAllDeclaration()) return false; 157 | 158 | let id = null; 159 | 160 | if (item.isVariableDeclaration()) { 161 | id = item.node.declarations[0].id; 162 | } else if (item.isImportDeclaration()) { 163 | id = item.node.specifiers[0].local; 164 | } else if (item.isExportNamedDeclaration()) { 165 | id = item.node.specifiers[0].exported; 166 | } else if (item.node.id) { 167 | id = item.node.id; 168 | } else { 169 | throw new Error(`Unexpected node:\n\n${printAST(item)}`); 170 | } 171 | 172 | if (!id) { 173 | throw new Error(`Couldn't find id on node:\n\n${printAST(item)}`); 174 | } 175 | 176 | return id.name === local; 177 | }); 178 | 179 | return statement || null; 180 | } 181 | -------------------------------------------------------------------------------- /packages/extract-react-types/src/file-loader.js: -------------------------------------------------------------------------------- 1 | const { readFileSync } = require('fs'); 2 | const { dirname } = require('path'); 3 | const { sync: resolveSync } = require('resolve'); 4 | const readFileAsync = require('read-file-async'); 5 | const resolveAsync = require('resolve-async'); 6 | const createFile = require('babel-file'); 7 | 8 | function getPathFileName(path) { 9 | return path.hub.file.opts.filename; 10 | } 11 | 12 | function getImportSource(path) { 13 | return path.node.source.value; 14 | } 15 | 16 | function toResolveOptions(fromPath, resolveOpts) { 17 | return Object.assign({}, resolveOpts, { basedir: dirname(fromPath) }); 18 | } 19 | 20 | export function resolveFilePathAsync(path, filePath, resolveOpts) { 21 | let fromPath = getPathFileName(path); 22 | let opts = toResolveOptions(fromPath, resolveOpts); 23 | return resolveAsync(filePath, opts); 24 | } 25 | 26 | export function resolveFilePathSync(path, filePath, resolveOpts) { 27 | let fromPath = getPathFileName(path); 28 | let opts = toResolveOptions(fromPath, resolveOpts); 29 | return resolveSync(filePath, opts); 30 | } 31 | 32 | export function resolveImportFilePathAsync(importDeclaration, resolveOpts) { 33 | let fromPath = getPathFileName(importDeclaration); 34 | let toPath = getImportSource(importDeclaration); 35 | let opts = toResolveOptions(fromPath, resolveOpts); 36 | return resolveAsync(toPath, opts); 37 | } 38 | 39 | export function resolveImportFilePathSync(importDeclaration, resolveOpts) { 40 | let fromPath = getPathFileName(importDeclaration); 41 | let toPath = getImportSource(importDeclaration); 42 | let opts = toResolveOptions(fromPath, resolveOpts); 43 | return resolveSync(toPath, opts); 44 | } 45 | 46 | export function loadFileAsync(filePath, parserOpts) { 47 | return readFileAsync(filePath).then(buffer => 48 | createFile(buffer.toString(), { filename: filePath, parserOpts }) 49 | ); 50 | } 51 | 52 | export function loadFileSync(filePath, parserOpts) { 53 | let buffer = readFileSync(filePath); 54 | return createFile(buffer.toString(), { filename: filePath, parserOpts }); 55 | } 56 | 57 | export function loadImportAsync(importDeclaration, resolveOpts, parserOpts) { 58 | return resolveImportFilePathAsync(importDeclaration, resolveOpts).then(resolved => 59 | loadFileAsync(resolved, parserOpts) 60 | ); 61 | } 62 | 63 | export function loadImportSync(importDeclaration, resolveOpts, parserOpts) { 64 | const resolved = resolveImportFilePathSync(importDeclaration, resolveOpts); 65 | return loadFileSync(resolved, parserOpts); 66 | } 67 | -------------------------------------------------------------------------------- /packages/extract-react-types/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import createBabelFile from 'babel-file'; 3 | import createBabylonOptions from 'babylon-options'; 4 | 5 | import convert, { convertComponentExports } from './converter'; 6 | import { findExports } from './export-manager'; 7 | 8 | export type * from './kinds'; 9 | 10 | function getContext( 11 | typeSystem: 'flow' | 'typescript', 12 | filename?: string, 13 | resolveOptions?: Object = {} 14 | ) { 15 | const plugins = ['jsx', ['decorators', { decoratorsBeforeExport: true }]]; 16 | if (!resolveOptions.extensions) { 17 | // The resolve package that babel-file-loader uses only resolves .js files by default instead of the 18 | // default extension list of node (.js, .json and .node) so add .json back here. 19 | resolveOptions.extensions = ['.js', '.json']; 20 | } 21 | 22 | if (typeSystem !== 'flow' && typeSystem !== 'typescript') { 23 | throw new Error('typeSystem must be either "flow" or "typescript"'); 24 | } 25 | 26 | if (typeSystem === 'flow') { 27 | plugins.push(['flow', { all: true }]); 28 | } 29 | 30 | if (typeSystem === 'typescript') { 31 | plugins.push('typescript'); 32 | 33 | resolveOptions.extensions.push('.tsx'); 34 | resolveOptions.extensions.push('.ts'); 35 | } 36 | 37 | /* $FlowFixMe - need to update types in babylon-options */ 38 | const parserOpts = createBabylonOptions({ stage: 2, plugins }); 39 | 40 | return { resolveOptions, parserOpts }; 41 | } 42 | 43 | export function extractReactTypes( 44 | code: string, 45 | typeSystem: 'flow' | 'typescript', 46 | filename?: string, 47 | inputResolveOptions?: Object 48 | ) { 49 | const { resolveOptions, parserOpts } = getContext(typeSystem, filename, inputResolveOptions); 50 | const file = createBabelFile(code, { parserOpts, filename }); 51 | return convert(file.path, { resolveOptions, parserOpts }); 52 | } 53 | 54 | export function findExportedComponents( 55 | programPath: any, 56 | typeSystem: 'flow' | 'typescript', 57 | filename?: string, 58 | resolveOptions?: Object 59 | ) { 60 | return convertComponentExports( 61 | findExports(programPath, 'all'), 62 | getContext(typeSystem, filename, resolveOptions) 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /packages/extract-react-types/src/kinds.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export type String = { kind: 'string', value?: string }; 3 | export type Param = { 4 | kind: 'param', 5 | value: AnyKind, 6 | type: AnyKind | null 7 | }; 8 | export type TypeParams = { kind: 'typeParams', params: Array }; 9 | export type TypeParam = { kind: 'typeParam', name: string }; 10 | export type TypeParamsDeclaration = { 11 | kind: 'typeParamsDeclaration', 12 | params: Array 13 | }; 14 | 15 | export type Id = { kind: 'id', name: string, type?: ?$Diff, referenceIdName?: string }; 16 | export type TemplateLiteral = { 17 | kind: 'templateLiteral', 18 | expressions: Array, 19 | quasis: Array 20 | }; 21 | export type Rest = { 22 | kind: 'rest', 23 | argument: Id 24 | }; 25 | export type TypeCastExpression = { 26 | kind: 'typeCastExpression', 27 | expression: Id 28 | }; 29 | export type TemplateExpression = { kind: 'templateExpression', tag: Id }; 30 | export type AssignmentPattern = { 31 | kind: 'assignmentPattern', 32 | left: AnyKind, 33 | right: AnyKind 34 | }; 35 | export type ObjectPattern = { 36 | kind: 'objectPattern', 37 | members: Array 38 | }; 39 | 40 | export type ArrayType = { 41 | kind: 'arrayType', 42 | type: AnyTypeKind 43 | }; 44 | 45 | export type LogicalExpression = { 46 | kind: 'logicalExpression', 47 | operator: string, 48 | left: AnyValueKind, 49 | right: AnyValueKind 50 | }; 51 | 52 | export type Obj = { kind: 'object', members: Array }; 53 | export type Property = { 54 | kind: 'property', 55 | key: Id | String, 56 | value: AnyKind, 57 | optional?: boolean 58 | }; 59 | export type ClassKind = { kind: 'class', name: Id }; 60 | export type Spread = { 61 | kind: 'spread', 62 | value: Property | Generic 63 | }; 64 | export type Unary = { 65 | kind: 'unary', 66 | operator: string, 67 | argument: AnyKind 68 | }; 69 | export type JSXAttribute = { 70 | kind: 'JSXAttribute', 71 | name: JSXIdentifier, 72 | value: any 73 | }; 74 | export type JSXExpressionContainer = { 75 | kind: 'JSXExpressionContainer', 76 | expression: any 77 | }; 78 | export type JSXElement = { kind: 'JSXElement', value: any }; 79 | export type JSXIdentifier = { kind: 'JSXIdentifier', value: any }; 80 | export type JSXOpeningElement = { 81 | kind: 'JSXOpeningElement', 82 | name: any, 83 | attributes: any 84 | }; 85 | export type JSXMemberExpression = { 86 | kind: 'JSXMemberExpression', 87 | object: any, 88 | property: any 89 | }; 90 | export type Call = { 91 | kind: 'call', 92 | callee: Id | Func, 93 | args: Array 94 | }; 95 | export type New = { 96 | kind: 'new', 97 | callee: Id, 98 | args: Array 99 | }; 100 | export type Typeof = { kind: 'typeof', name: string, type: AnyKind }; 101 | export type Exists = { kind: 'exists' }; 102 | export type Number = { 103 | kind: 'number', 104 | value?: number 105 | }; 106 | export type Null = { kind: 'null' }; 107 | export type Unknown = { kind: 'unknown' }; 108 | export type TemplateElement = { 109 | kind: 'templateElement', 110 | value: { raw: string, cooked: string } 111 | }; 112 | export type Boolean = { kind: 'boolean', value?: boolean }; 113 | export type ArrayExpression = { kind: 'array', elements: any }; 114 | export type BinaryExpression = { 115 | kind: 'binary', 116 | operator: string, 117 | left: AnyValueKind, 118 | right: AnyValueKind 119 | }; 120 | export type MemberExpression = { 121 | kind: 'memberExpression', 122 | object: Id | MemberExpression | Obj, 123 | property: Id 124 | }; 125 | export type Func = { 126 | kind: 'function', 127 | id?: Id | null, 128 | async?: boolean, 129 | generator?: boolean, 130 | parameters: Array, 131 | returnType: AnyTypeKind | null 132 | }; 133 | export type Union = { kind: 'union', types: Array }; 134 | export type Generic = { 135 | kind: 'generic', 136 | value: AnyTypeKind, 137 | typeParams?: TypeParams 138 | }; 139 | export type Initial = { kind: 'initial', id: Id, value: AnyValueKind }; 140 | export type Variable = { kind: 'variable', declarations: Array }; 141 | export type Intersection = { 142 | kind: 'intersection', 143 | types: Array 144 | }; 145 | export type Void = { kind: 'void' }; 146 | export type Mixed = { kind: 'mixed' }; 147 | export type Any = { kind: 'any' }; 148 | export type Nullable = { kind: 'nullable', arguments: AnyTypeKind }; 149 | export type Literal = { kind: 'literal' }; 150 | export type Tuple = { kind: 'tuple', types: AnyTypeKind }; 151 | export type Import = { 152 | kind: 'import', 153 | importKind: 'value' | 'type', 154 | name: string, 155 | moduleSpecifier: string, 156 | external?: boolean 157 | }; 158 | 159 | export type OpaqueType = { 160 | kind: 'opaqueType', 161 | id: AnyTypeKind, 162 | supertype?: AnyTypeKind, 163 | impltype?: AnyTypeKind, 164 | typeParameters?: AnyTypeKind 165 | }; 166 | 167 | export type InterfaceDeclaration = { kind: 'interfaceDeclaration', id: AnyTypeKind }; 168 | 169 | export type Program = { kind: 'program', component?: Property }; 170 | 171 | export type ExportSpecifier = { kind: 'exportSpecifier', local: Id, exported: Id }; 172 | export type Export = { kind: 'export', exports: Array, source?: String }; 173 | 174 | export type This = { 175 | kind: 'custom', 176 | value: 'this' 177 | }; 178 | 179 | export type TypeQuery = { kind: 'typeQuery', exprName: AnyValueKind }; 180 | 181 | export type AnyTypeKind = 182 | | Any 183 | | AssignmentPattern 184 | | Boolean 185 | | ClassKind 186 | | Exists 187 | | Func 188 | | Generic 189 | | Id 190 | | Intersection 191 | | Literal 192 | | Mixed 193 | | Null 194 | | Nullable 195 | | Number 196 | | Obj 197 | | ObjectPattern 198 | | Property 199 | | Spread 200 | | String 201 | | Tuple 202 | | Typeof 203 | | TypeParams 204 | | Unary 205 | | Union 206 | | Void; 207 | export type AnyValueKind = 208 | | TemplateElement 209 | | ArrayExpression 210 | | AssignmentPattern 211 | | BinaryExpression 212 | | Boolean 213 | | Call 214 | | Func 215 | | Id 216 | | Import 217 | | JSXAttribute 218 | | JSXElement 219 | | JSXExpressionContainer 220 | | JSXIdentifier 221 | | JSXMemberExpression 222 | | JSXOpeningElement 223 | | MemberExpression 224 | | New 225 | | Null 226 | | Number 227 | | ObjectPattern 228 | | Param 229 | | Property 230 | | Spread 231 | | String 232 | | TemplateExpression 233 | | TemplateLiteral 234 | | Variable 235 | | LogicalExpression; 236 | 237 | export type AnyKind = AnyTypeKind | AnyValueKind | Program; 238 | -------------------------------------------------------------------------------- /packages/extract-react-types/src/utils.js: -------------------------------------------------------------------------------- 1 | export function hasTypeAnnotation(path, left, right) { 2 | if (!path.isVariableDeclarator() || !path.node.id.typeAnnotation) { 3 | return false; 4 | } 5 | 6 | const { typeName, typeParameters } = path.node.id.typeAnnotation.typeAnnotation; 7 | 8 | if (!typeParameters) return false; 9 | 10 | if ( 11 | (typeName.left && typeName.left.name === left && typeName.right.name === right) || 12 | ((right && typeName.name === right) || typeName.name === left) 13 | ) { 14 | return true; 15 | } 16 | 17 | return false; 18 | } 19 | -------------------------------------------------------------------------------- /packages/kind2string/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # pretty-proptypes 2 | 3 | ## 0.8.1 4 | 5 | ### Patch Changes 6 | 7 | - [`68bcec6`](https://github.com/atlassian/extract-react-types/commit/68bcec67728218b861fedb99c735a5ddc062ee53) [#152](https://github.com/atlassian/extract-react-types/pull/152) Thanks [@danieldelcore](https://github.com/danieldelcore)! - Added a safe bail-out for when extract-react-types encounters an unsupported keyword or syntax. 8 | In that case, the type will be output as a raw string and summary type will be `raw`. 9 | 10 | ## 0.8.0 11 | 12 | ### Minor Changes 13 | 14 | - [`d1115ee`](https://github.com/atlassian/extract-react-types/commit/d1115eecdeedda23caa558f253ee4f769e3f0606) [#126](https://github.com/atlassian/extract-react-types/pull/126) Thanks [@pgmanutd](https://github.com/pgmanutd)! - add typeQuery converter 15 | 16 | ## 0.7.3 17 | 18 | ### Patch Changes 19 | 20 | - Updated dependencies [[`99f6c8a`](https://github.com/atlassian/extract-react-types/commit/99f6c8a1cd0c41091caa870d233b34c0500b0565), [`849c979`](https://github.com/atlassian/extract-react-types/commit/849c979faf91b6b1f24a85ce267698639e4caeb8)]: 21 | - extract-react-types@0.24.0 22 | 23 | ## 0.7.2 24 | 25 | - Updated dependencies [acb8499](https://github.com/atlassian/extract-react-types/commit/acb8499): 26 | - extract-react-types@0.23.0 27 | 28 | ## 0.7.1 29 | 30 | ### Patch Changes 31 | 32 | - [`6827845`](https://github.com/atlassian/extract-react-types/commit/68278457981fc557dc470f79ca56b686814c3e21) Thanks [@Noviny](https://github.com/Noviny)! - Fix build from previous release 33 | 34 | ## 0.7.0 35 | 36 | ### Minor Changes 37 | 38 | - [`dc667b4`](https://github.com/atlassian/extract-react-types/commit/dc667b45277ca0440f67f24051e1d0ada07f5e4d) [#101](https://github.com/atlassian/extract-react-types/pull/101) Thanks [@chasestarr](https://github.com/chasestarr)! - Export the `converters` object as a named export. 39 | 40 | ## 0.6.3 41 | 42 | ### Patch Changes 43 | 44 | - [patch][52e37e6](https://github.com/atlassian/extract-react-types/commit/52e37e6): 45 | Introduces flag "EXTRACT_REACT_TYPES_HIDE_ERRORS" to suppress errors 46 | 47 | - Updated dependencies [dc4b719](https://github.com/atlassian/extract-react-types/commit/dc4b719): 48 | - extract-react-types@0.22.0 49 | 50 | ## 0.6.2 51 | 52 | - Updated dependencies [dc4b719](https://github.com/atlassian/extract-react-types/commit/dc4b719): 53 | - extract-react-types@0.21.0 54 | 55 | ## 0.6.1 56 | 57 | ### Patch Changes 58 | 59 | - [patch][e6cc1f5](https://github.com/atlassian/extract-react-types/commit/e6cc1f5): 60 | Remove dangerous debug code that broke everything 61 | 62 | ## 0.6.0 63 | 64 | ### Minor Changes 65 | 66 | - [minor][533d172](https://github.com/atlassian/extract-react-types/commit/533d172): 67 | Add support for opaque types 68 | 69 | ## 0.5.6 70 | 71 | - Updated dependencies [d232e30](https://github.com/atlassian/extract-react-types/commit/d232e30): 72 | - extract-react-types@0.19.0 73 | 74 | ## 0.5.5 75 | 76 | - Updated dependencies [907688c](https://github.com/atlassian/extract-react-types/commit/907688c): 77 | - extract-react-types@0.18.0 78 | 79 | ## 0.5.4 80 | 81 | ### Patch Changes 82 | 83 | - [patch][287e4b4](https://github.com/atlassian/extract-react-types/commit/287e4b4): 84 | - Updated build to use preconstruct (this shouldn't affect usage, but calling this out just in case) 85 | - [patch][4b3b4a4](https://github.com/atlassian/extract-react-types/commit/4b3b4a4): 86 | 87 | - Add logicalExpression converter 88 | 89 | - Updated dependencies [e682bbb](https://github.com/atlassian/extract-react-types/commit/e682bbb): 90 | - extract-react-types@0.17.0 91 | 92 | ## 0.5.3 93 | 94 | - [patch][e401ba8](https://github.com/atlassian/extract-react-types/commit/e401ba8): 95 | - Add converter for typeCastExpression 96 | 97 | ## 0.5.2 98 | 99 | - Updated dependencies [277b0be](https://github.com/atlassian/extract-react-types/commit/277b0be): 100 | - Updated dependencies [8f04dad](https://github.com/atlassian/extract-react-types/commit/8f04dad): 101 | - Updated dependencies [6bc521c](https://github.com/atlassian/extract-react-types/commit/6bc521c): 102 | - extract-react-types@0.16.0 103 | 104 | ## 0.5.1 105 | 106 | Thanks [Michael Blaszczyk](https://github.com/Blasz) for these contributions! 107 | 108 | - Add converters for typeParam and typeParamsDeclaration 109 | - Add reduceToObj function - previously this lived in pretty-proptypes 110 | 111 | ## 0.5.0 112 | 113 | - Add new converters for export and exportSpecifier types 114 | - Modernize dependent version of extract-react-types 115 | -------------------------------------------------------------------------------- /packages/kind2string/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Atlassian 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. -------------------------------------------------------------------------------- /packages/kind2string/README.md: -------------------------------------------------------------------------------- 1 | # Kind 2 String - a parser for extract-react-types 2 | 3 | Kind 2 String is designed to take the data structures output by [extract-react-types](https://www.npmjs.com/package/extract-react-types) and convert them to a (useful) string, as well as performing safe traversal through the output of `extract-react-types` so that trying to display information in your docs does not throw errors. 4 | 5 | It exposes a `convert` method which allows you to ensure the data structure resolves to a string. It also exposes its `converter` object, allowing you to overwrite converters of your choice, if you wish to perform some other action other than the default string converter. 6 | 7 | Default use-case: 8 | 9 | ```js 10 | import generatedData from './extract-react-type-write-location'; 11 | import convert from 'kind2string'; 12 | 13 | export default () =>
convert(generatedData)
; 14 | ``` 15 | 16 | Also, if you are handling the kinds in a custom way, it is good to pass the final kind to `kind2string`, to ensure that you always pass a string to your react components. 17 | 18 | For examples of how to use this, the [@atlaskit/docs](https://www.npmjs.com/package/@atlaskit/docs) uses this package. A good pattern on how to implement `kind2string` can be found in the [prettyproptypes](https://github.com/atlassian/extract-react-types/tree/master/packages/pretty-proptypes) file. 19 | -------------------------------------------------------------------------------- /packages/kind2string/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kind2string", 3 | "version": "0.8.1", 4 | "main": "dist/kind2string.cjs.js", 5 | "module": "dist/kind2string.esm.js", 6 | "license": "MIT", 7 | "author": "Atlassian", 8 | "repository": "atlassian/extract-react-types", 9 | "description": "Utility to ensure extract-react-types output can be rendered without errors", 10 | "files": [ 11 | "dist" 12 | ], 13 | "devDependencies": { 14 | "babel-jest": "^27.0.0", 15 | "extract-react-types": "^0.30.0" 16 | }, 17 | "dependencies": { 18 | "@babel/runtime": "^7.4.4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/kind2string/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | // We explicitly want to be able to have mode in these functions, 4 | // even in instances where we don't use it. 5 | 6 | /* eslint-disable no-console */ 7 | 8 | /*:: 9 | import * as K from 'extract-react-types' 10 | */ 11 | 12 | import { resolveToLast, resolveFromGeneric, reduceToObj } from './utils'; 13 | 14 | const unaryWhiteList = ['-', '+', '!']; 15 | const hideErrors = !!process.env.EXTRACT_REACT_TYPES_HIDE_ERRORS; 16 | 17 | function mapConvertAndJoin(array, joiner = ', ') { 18 | if (!Array.isArray(array)) return ''; 19 | return array.map(a => convert(a)).join(joiner); 20 | } 21 | 22 | function getKind(type: K.AnyKind) { 23 | switch (type.kind) { 24 | case 'nullable': 25 | return `nullable ${getKind(type.arguments)}`; 26 | case 'id': 27 | return convert(type); 28 | case 'exists': 29 | case 'typeof': 30 | return convert(type); 31 | case 'generic': { 32 | if (type.typeParams) { 33 | let typeParams = type.typeParams.params.map(getKind).join(', '); 34 | return `${convert(type.value)}<${typeParams}>`; 35 | } 36 | return getKind(resolveFromGeneric(type)); 37 | } 38 | default: 39 | return type.kind; 40 | } 41 | } 42 | 43 | const converters = { 44 | /* 45 | If the value here is undefined, we can safely assume that we're dealing with 46 | a BooleanTypeAnnotation and not a BooleanLiteralTypeAnnotation. 47 | */ 48 | boolean: (type: K.Boolean, mode: string): string => 49 | type.value != null ? type.value.toString() : type.kind, 50 | exists: (type: K.Exists, mode: string): '*' => `*`, 51 | /* 52 | If the value here is undefined, we can safely assume that we're dealing with 53 | a NumberTypeAnnotation and not a NumberLiteralTypeAnnotation. 54 | */ 55 | number: (type: K.Number, mode: string): string => 56 | type.value != null ? type.value.toString() : type.kind, 57 | /* 58 | If the value here is undefined, we can safely assume that we're dealing with 59 | a StringTypeAnnotation and not a StringLiteralTypeAnnotation. 60 | */ 61 | string: (type: K.String, mode: string): string => 62 | type.value != null ? `"${type.value.toString()}"` : type.kind, 63 | custom: (type: any, mode: string): string => type.value.toString(), 64 | any: (type: K.Any, mode: string): string => type.kind, 65 | void: (type: K.Void, mode: string): 'undefined' => 'undefined', 66 | literal: (type: any, mode: string): string => type.kind, 67 | mixed: (type: K.Mixed, mode: string): string => type.kind, 68 | null: (type: K.Null, mode: string): 'null' => 'null', 69 | unknown: (type: K.Unknown): string => type.kind, 70 | logicalExpression: (type, mode: string): string => { 71 | return `${convert(type.left)} ${type.operator} ${convert(type.right)}`; 72 | }, 73 | unary: (type: K.Unary, mode: string): string => { 74 | let space = unaryWhiteList.includes(type.operator) ? '' : ' '; 75 | return `${type.operator}${space}${convert(type.argument)}`; 76 | }, 77 | 78 | id: (type: K.Id, mode: string): string => type.name, 79 | // TODO - this is not right and needs to be improved 80 | opaqueType: (type: K.OpaqueType, mode: string): string => { 81 | return convert(type.id); 82 | }, 83 | interfaceDeclaration: (type: K.InterfaceDeclaration) => { 84 | return convert(type.id); 85 | }, 86 | typeCastExpression: (type: K.TypeCastExpression, mode: string): string => { 87 | return convert(type.expression); 88 | }, 89 | JSXMemberExpression: (type: any, mode: string): string => { 90 | return `${convert(type.object)}.${convert(type.property)}`; 91 | }, 92 | JSXExpressionContainer: (type: any, mode: string): string => { 93 | return `{${convert(type.expression)}}`; 94 | }, 95 | JSXOpeningElement: (type /*JSXOpeningElement*/, mode: string): string => { 96 | return `${convert(type.name)} ${mapConvertAndJoin(type.attributes, ' ')}`; 97 | }, 98 | JSXElement: (type: K.JSXElement, mode: string): string => { 99 | return `<${convert(type.value)} />`; 100 | }, 101 | 102 | JSXIdentifier: (type: K.JSXIdentifier, mode: string): string => { 103 | return `${type.value}`; 104 | }, 105 | 106 | JSXAttribute: (type: K.JSXAttribute, mode: string): string => { 107 | return `${convert(type.name)}=${convert(type.value)}`; 108 | }, 109 | 110 | binary: (type: K.BinaryExpression, mode: string): string => { 111 | const left = convert(type.left); 112 | const right = convert(type.right); 113 | return `${left} ${type.operator} ${right}`; 114 | }, 115 | 116 | function: (type: K.Func, mode: string): string => { 117 | return `(${mapConvertAndJoin(type.parameters)}) => ${ 118 | type.returnType === null ? 'undefined' : convert(type.returnType) 119 | }`; 120 | }, 121 | /* 122 | TODO: Make this resolve members in a unique way that will allow us to 123 | handle property keys with no assigned value 124 | */ 125 | objectPattern: (type: K.ObjectPattern, mode: string): string => { 126 | // ({ a, b }) => undefined ({a: a, b: b}) => undefined 127 | // ({ a = 2, b }) => undefined ({a: a = 2, b: b })=> undefined 128 | return `{ ${mapConvertAndJoin(type.members)} }`; 129 | }, 130 | 131 | rest: (type: K.Rest, mode: string): string => { 132 | return `...${convert(type.argument)}`; 133 | }, 134 | 135 | assignmentPattern: (type: K.AssignmentPattern, mode: string): string => { 136 | return `${convert(type.left)} = ${convert(type.right)}`; 137 | }, 138 | 139 | param: (type: K.Param, mode: string): string => { 140 | // this will not hold once we have types 141 | return convert(type.value); 142 | }, 143 | 144 | array: (type: K.ArrayExpression, mode: string): string => { 145 | return `[${mapConvertAndJoin(type.elements)}]`; 146 | }, 147 | 148 | arrayType: (type: K.ArrayType, mode: string): string => { 149 | return `Array of ${convert(type.type)}`; 150 | }, 151 | 152 | spread: (type: K.Spread, mode: string): string => { 153 | return `...${convert(type.value)}`; 154 | }, 155 | 156 | property: (type: K.Property, mode: string): string => { 157 | const sameId = 158 | type.key.kind === 'id' && type.value.kind === 'id' && type.key.name === type.value.name; 159 | 160 | const assignmentSameId = 161 | type.value.kind === 'assignmentPattern' && 162 | type.key.kind === 'id' && 163 | type.value.left.kind === 'id' && 164 | type.key.name === type.value.left.name; 165 | 166 | if (sameId) { 167 | // If both keys are IDs we're applying syntactic sugar 168 | return `${convert(type.key)}`; 169 | } else if (assignmentSameId) { 170 | // If the value is an assignment pattern with a left hand ID that is the same as our type.key, just return the resolved value. 171 | return `${convert(type.value)}`; 172 | } else { 173 | return `${convert(type.key)}: ${convert(type.value)}`; 174 | } 175 | }, 176 | 177 | object: (type: K.Obj, mode: string): string => { 178 | if (type.members.length === 0) return `{}`; 179 | return `{ ${mapConvertAndJoin(type.members)} }`; 180 | }, 181 | 182 | memberExpression: (type: K.MemberExpression, mode: string): string => { 183 | const object = resolveToLast(type.object); 184 | const property = convert(type.property); 185 | 186 | if (!object && !hideErrors) { 187 | console.error('Object property does not exist on this member expression'); 188 | return ''; 189 | } 190 | 191 | switch (object.kind) { 192 | case 'id': 193 | return `${convert(type.object)}.${property}`; 194 | case 'object': { 195 | const mem = object.members.find(m => { 196 | if (typeof m.key !== 'string') { 197 | // Issue here is that convert(key) can result in either a String type or an Id type, 198 | // one returns the value wrapped in quotations, the other does not. 199 | // We're stripping the quotations so we can do an accurate match against the property which is always an Id 200 | return convert(m.key).replace(/"/g, '') === property; 201 | } 202 | return m.key === property; 203 | }); 204 | if (mem && mem.value) { 205 | return convert(mem.value); 206 | } else { 207 | console.error(`${property} not found in ${convert(object)}`); 208 | return 'undefined'; 209 | } 210 | } 211 | case 'import': 212 | return `${convert(type.object)}.${property}`; 213 | default: 214 | console.error('failed to resolve member expression'); 215 | return ''; 216 | } 217 | }, 218 | 219 | call: (type: K.Call, mode: string): string => { 220 | let callSignature = ''; 221 | if (type.callee.referenceIdName) { 222 | callSignature = type.callee.referenceIdName; 223 | } else if (type.callee.id) { 224 | callSignature = convert(type.callee.id); 225 | } else { 226 | callSignature = convert(type.callee); 227 | } 228 | // $FlowFixMe - this is incorrectly reading type.callee.referenceIdName as possibly not a string. 229 | return `${callSignature}(${mapConvertAndJoin(type.args)})`; 230 | }, 231 | 232 | new: (type: K.New, mode: string): string => { 233 | const callee = convert(type.callee); 234 | const args = mapConvertAndJoin(type.args); 235 | return `new ${callee}(${args})`; 236 | }, 237 | 238 | variable: (type: K.Variable, mode: string): string => { 239 | const val = type.declarations[type.declarations.length - 1]; 240 | if (val.value) { 241 | return convert(val.value); 242 | } 243 | return convert(val.id); 244 | }, 245 | 246 | templateExpression: (type: K.TemplateExpression, mode: string): string => { 247 | return `${convert(type.tag)}`; 248 | }, 249 | 250 | templateLiteral: (type: K.TemplateLiteral, mode: string): string => { 251 | let str = type.quasis.reduce((newStr, v, i) => { 252 | let quasi = convert(v); 253 | let newStrClone = newStr; 254 | newStrClone = `${newStrClone}${quasi}`; 255 | if (type.expressions[i]) { 256 | let exp = convert(type.expressions[i]); 257 | newStrClone = `${newStrClone}\${${exp}}`; 258 | } 259 | return newStrClone; 260 | }, ''); 261 | return `\`${str}\``; 262 | }, 263 | 264 | templateElement: (type: K.TemplateElement, mode: string): string => { 265 | return type.value.cooked.toString(); 266 | }, 267 | class: (type: K.ClassKind, mode: string): string => { 268 | return convert(type.name); 269 | }, 270 | // We should write these 271 | generic: (type: K.Generic, mode: string): string => { 272 | const typeParams = type.typeParams ? convert(type.typeParams) : ''; 273 | const value = convert(type.value); 274 | return `${value}${typeParams}`; 275 | }, 276 | intersection: (type: K.Intersection, mode: string): string => 277 | `${mapConvertAndJoin(type.types, ' & ')}`, 278 | 279 | nullable: (type: K.Nullable, mode: string): string => `?${convert(type.arguments)}`, 280 | typeParam: (type: K.TypeParam, mode: string): string => `${type.name}`, 281 | typeParams: (type: K.TypeParams, mode: string): string => 282 | `<${mapConvertAndJoin(type.params, ', ')}>`, 283 | typeParamsDeclaration: (type: K.TypeParamsDeclaration, mode: string): string => 284 | `<${mapConvertAndJoin(type.params, ', ')}>`, 285 | typeof: (type: K.Typeof, mode: string): string => { 286 | return type.name ? `typeof ${type.name}` : `${type.type.kind}`; 287 | }, 288 | union: (type: K.Union, mode: string): string => `${mapConvertAndJoin(type.types, ' | ')}`, 289 | import: (type: K.Import, mode: string): string => { 290 | if (type.name === 'default') { 291 | return `${type.moduleSpecifier}`; 292 | } else { 293 | return `${type.moduleSpecifier}.${type.name}`; 294 | } 295 | }, 296 | export: (type: K.Export, mode: string): string => { 297 | if (type.exports.length === 1) { 298 | return convert(type.exports[0]); 299 | } else { 300 | console.warn( 301 | `kind2string has received an export type with multiple exports, and have no way of printing this. 302 | The exports we found were: ${type.exports.map(xport => convert(xport, mode)).join(', ')} 303 | from file: ${convert(type.source, mode)}` 304 | ); 305 | return ''; 306 | } 307 | }, 308 | exportSpecifier: (type, mode) => convert(type.exported), 309 | 310 | // TS 311 | tuple: (type: K.Tuple, mode: string): string => `[${mapConvertAndJoin(type.types)}]`, 312 | typeQuery: (type: K.TypeQuery): string => { 313 | return type.exprName ? `typeof ${type.exprName.name}` : `${type.kind}`; 314 | }, 315 | 316 | // ERT - Misc 317 | raw: (type: any): string => type.name 318 | }; 319 | 320 | function convert(type: any, mode: string = 'value') { 321 | if (!type) { 322 | console.error('No type argument has been passed in'); 323 | return ''; 324 | } 325 | 326 | const converter = converters[type.kind]; 327 | if (!converter) { 328 | if (!type.kind) { 329 | console.error('convert was passed an object without a kind', type); 330 | } else { 331 | console.error('could not find converter for', type.kind); 332 | } 333 | } else { 334 | return converter(type); 335 | } 336 | return ''; 337 | } 338 | 339 | export default convert; 340 | export { converters, getKind, resolveFromGeneric, reduceToObj }; 341 | -------------------------------------------------------------------------------- /packages/kind2string/src/utils.js: -------------------------------------------------------------------------------- 1 | const hideErrors = !!process.env.EXTRACT_REACT_TYPES_HIDE_ERRORS; 2 | 3 | export function resolveToLast(type /*: MemberExpression | Obj | Id*/) { 4 | switch (type.kind) { 5 | case 'id': 6 | case 'object': 7 | case 'import': 8 | return type; 9 | case 'memberExpression': 10 | return resolveToLast(type.object); 11 | default: 12 | if (hideErrors) { 13 | /* eslint-disable-next-line no-console */ 14 | console.error(`Unexpected initial type of member expression`, JSON.stringify(type)); 15 | } 16 | break; 17 | } 18 | } 19 | 20 | export function resolveFromGeneric(type) { 21 | if (type.kind !== 'generic') return type; 22 | if (type.typeParams) { 23 | // If a generic type is an Array, we don't want to just return the value, 24 | // But also the entire type object, so we can parse the typeParams later on. 25 | return type; 26 | } 27 | if (type.value.kind === 'generic') { 28 | return resolveFromGeneric(type.value); 29 | } 30 | return type.value; 31 | } 32 | 33 | export function reduceToObj(type) { 34 | const reducableKinds = ['generic', 'object', 'intersection']; 35 | if (type.kind === 'generic') { 36 | // Only attempt to reduce generic if it has a reducable value 37 | // Unreducable generics that have an identifier value, e.g. ElementConfig, are still valid 38 | // so we return early to avoid the console warn below 39 | return reducableKinds.includes(type.value.kind) ? reduceToObj(type.value) : []; 40 | } else if (type.kind === 'object') { 41 | return type.members; 42 | } else if (type.kind === 'intersection') { 43 | return type.types.reduce((acc, i) => [...acc, ...reduceToObj(i)], []); 44 | } else if (type.kind === 'typeQuery') { 45 | // typeQuery is not reducable so we return early to avoid the console warn below 46 | return []; 47 | } 48 | /* eslint-disable-next-line no-console */ 49 | console.warn('was expecting to reduce to an object and could not', type); 50 | return []; 51 | } 52 | -------------------------------------------------------------------------------- /packages/kind2string/test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { extractReactTypes } from 'extract-react-types'; 3 | import convert, { converters } from './src'; 4 | 5 | const assembleERTAST = (propTypes, defaultProps, type = 'flow') => { 6 | let file = ` 7 | class Component extends React.Component<${propTypes}> { 8 | defaultProps = ${defaultProps} 9 | }`; 10 | let res = extractReactTypes(file, type); 11 | return res.component.members; 12 | }; 13 | 14 | const getSingleDefault = defaultPropVal => { 15 | return assembleERTAST(`{ a: any }`, `{ a: ${defaultPropVal} }`)[0].default; 16 | }; 17 | const getSingleProp = defaultPropType => { 18 | const propTypes = assembleERTAST(`{ a: ${defaultPropType} }`, `{}`)[0]; 19 | return convert(propTypes.value); 20 | }; 21 | const getSingleTSPropTypes = defaultPropType => { 22 | const propTypes = assembleERTAST(`{ a: ${defaultPropType} }`, `{}`, 'typescript')[0]; 23 | 24 | return convert(propTypes); 25 | }; 26 | 27 | const base = { 28 | kind: 'memberExpression', 29 | property: { 30 | kind: 'id', 31 | name: 'a' 32 | } 33 | }; 34 | const flatMemberExpressionId = { 35 | ...base, 36 | object: { 37 | kind: 'id', 38 | name: 'testObject' 39 | } 40 | }; 41 | const flatMemberExpressionObject = { 42 | ...base, 43 | object: { 44 | kind: 'object', 45 | members: [ 46 | { 47 | kind: 'property', 48 | key: { 49 | kind: 'id', 50 | name: 'redHerring' 51 | }, 52 | value: { 53 | kind: 'number', 54 | value: NaN 55 | } 56 | }, 57 | { 58 | kind: 'property', 59 | key: { 60 | kind: 'string', 61 | value: 'a' 62 | }, 63 | value: { 64 | kind: 'number', 65 | value: 34 66 | } 67 | } 68 | ] 69 | } 70 | }; 71 | const ErroneousMemberExpression = { 72 | ...base, 73 | object: flatMemberExpressionObject, 74 | property: { 75 | kind: 'id', 76 | name: 'badprop' 77 | } 78 | }; 79 | 80 | const nestedMemberExpressionId = { 81 | ...base, 82 | object: flatMemberExpressionId 83 | }; 84 | 85 | const nestedMemberExpressionObject = { 86 | ...base, 87 | object: flatMemberExpressionObject 88 | }; 89 | 90 | describe('kind 2 string tests', () => { 91 | describe('converters', () => { 92 | describe('memberExpression', () => { 93 | describe('If the object property is of the type Obj', () => { 94 | it('and the property does not exist, we should log an error and return an empty string', () => { 95 | expect(convert(ErroneousMemberExpression)).toBe('undefined'); 96 | }); 97 | it('and the property does exist, we should return the value', () => { 98 | expect(convert(flatMemberExpressionObject)).toBe('34'); 99 | }); 100 | }); 101 | describe('If the object property is of the type Id', () => { 102 | it('should return the ObjectId and Property name as a string representation', () => { 103 | expect(convert(flatMemberExpressionId)).toBe('testObject.a'); 104 | }); 105 | }); 106 | describe('If the object property is of the type MemberExpression', () => { 107 | it('and the final type is of type object', () => { 108 | expect(convert(nestedMemberExpressionObject)).toBe('34'); 109 | }); 110 | it('and the final type is of type id', () => { 111 | expect(convert(nestedMemberExpressionId)).toBe('testObject.a.a'); 112 | }); 113 | }); 114 | }); 115 | describe('exists', () => { 116 | it('return a string representation of the exist kind', () => { 117 | const str = `*`; 118 | const result = '*'; 119 | const final = getSingleProp(str); 120 | expect(final).toBe(result); 121 | }); 122 | }); 123 | describe('boolean', () => { 124 | describe('If no value exists', () => { 125 | it('return the string representation of the kind', () => { 126 | const str = `{ c: boolean }`; 127 | const result = `{ c: boolean }`; 128 | const final = getSingleProp(str); 129 | expect(final).toBe(result); 130 | }); 131 | }); 132 | describe('If a value exists', () => { 133 | it('return the string representatio of the value', () => { 134 | const str = `{ c: true }`; 135 | const result = `{ c: true }`; 136 | const final = getSingleProp(str); 137 | expect(final).toBe(result); 138 | }); 139 | }); 140 | }); 141 | describe('number', () => { 142 | describe('If no value exists', () => { 143 | it('return the string representation of the kind', () => { 144 | const str = `{ c: number }`; 145 | const result = `{ c: number }`; 146 | const final = getSingleProp(str); 147 | expect(final).toBe(result); 148 | }); 149 | it('return the string representatio of the value', () => { 150 | const str = `{ c: 3 }`; 151 | const result = `{ c: 3 }`; 152 | const final = getSingleProp(str); 153 | expect(final).toBe(result); 154 | }); 155 | }); 156 | }); 157 | describe('string', () => { 158 | describe('If no value exists', () => { 159 | it('return the string representation of the kind', () => { 160 | const str = `{ c: string }`; 161 | const result = `{ c: string }`; 162 | const final = getSingleProp(str); 163 | expect(final).toBe(result); 164 | }); 165 | it('return the string representatio of the value', () => { 166 | const str = `{ c: "hello" }`; 167 | const result = `{ c: "hello" }`; 168 | const final = getSingleProp(str); 169 | expect(final).toBe(result); 170 | }); 171 | }); 172 | }); 173 | describe('templateLiteral', () => { 174 | it('should resolve to same string', () => { 175 | let str = '`abc${a}de`'; 176 | let reId = getSingleDefault(str); 177 | let final = convert(reId); 178 | expect(final).toBe(str); 179 | }); 180 | it('should resolve excaped characters', () => { 181 | let str = '`abc${a}de\n`'; 182 | let reId = getSingleDefault(str); 183 | let final = convert(reId); 184 | expect(final).toBe(str); 185 | }); 186 | }); 187 | describe('any', () => { 188 | it('should resolve to whatever type kind is passed in', () => { 189 | let str = `any`; 190 | let final = getSingleProp(str); 191 | expect(final).toBe(str); 192 | }); 193 | }); 194 | describe('object', () => { 195 | it('should test a spread object', () => { 196 | let defaults = `{ a: { ...something, b: "val" } }`; 197 | let reId = getSingleDefault(defaults); 198 | let final = convert(reId); 199 | expect(final).toBe(defaults); 200 | }); 201 | }); 202 | describe('function', () => { 203 | it('should test a spread object', () => { 204 | let defaults = `() => {}`; 205 | let returnVal = `() => undefined`; 206 | let reId = getSingleDefault(defaults); 207 | let final = convert(reId); 208 | expect(final).toBe(returnVal); 209 | }); 210 | it('should test a default of a function type', () => { 211 | let defaults = `(a = () => {}) => {}`; 212 | let returnVal = `(a = () => undefined) => undefined`; 213 | let reId = getSingleDefault(defaults); 214 | let final = convert(reId); 215 | expect(final).toBe(returnVal); 216 | }); 217 | it('should handle a spread', () => { 218 | let defaults = `({ ...res }) => {}`; 219 | let returnVal = `({ ...res }) => undefined`; 220 | let reId = getSingleDefault(defaults); 221 | let final = convert(reId); 222 | expect(final).toBe(returnVal); 223 | }); 224 | }); 225 | describe('ObjectPattern', () => { 226 | it('should', () => { 227 | let defaults = `({ a, b }) => {}`; 228 | let returnVal = `({ a, b }) => undefined`; 229 | let reId = getSingleDefault(defaults); 230 | let final = convert(reId); 231 | expect(final).toBe(returnVal); 232 | }); 233 | 234 | it('should handle assignment', () => { 235 | let defaults = `({ a = 24, b = 3 }) => {}`; 236 | let returnVal = `({ a = 24, b = 3 }) => undefined`; 237 | let reId = getSingleDefault(defaults); 238 | let final = convert(reId); 239 | expect(final).toBe(returnVal); 240 | }); 241 | }); 242 | describe('JSXElement', () => { 243 | it('resolve to a self-closing JSXElement with attributes', () => { 244 | let defaults = `
`; 245 | let reId = getSingleDefault(defaults); 246 | let final = convert(reId); 247 | expect(final).toBe(defaults); 248 | }); 249 | }); 250 | describe('intersection', () => { 251 | it('should return a string representation of an intersection type', () => { 252 | let prop = `true & false`; 253 | let final = getSingleProp(prop); 254 | expect(final).toBe(prop); 255 | }); 256 | }); 257 | describe('nullable', () => { 258 | it('should return a string representation of a nullable type value', () => { 259 | let prop = `{ b: ?string }`; 260 | let final = getSingleProp(prop); 261 | expect(final).toBe(prop); 262 | }); 263 | }); 264 | describe('typeof', () => { 265 | it('if no name property is present it should return a string representation of a typeof invocation', () => { 266 | let prop = `typeof 34`; 267 | let result = 'number'; 268 | let final = getSingleProp(prop); 269 | expect(final).toBe(result); 270 | }); 271 | it('if a name property is present, it should return a string representation of the id of the type', () => { 272 | let prop = 'typeof Foo'; 273 | let final = getSingleProp(prop); 274 | expect(final).toBe(prop); 275 | }); 276 | }); 277 | describe('generic', () => { 278 | it('If type params exist it should return a string representation of a generic type with params', () => { 279 | let prop = `Array`; 280 | let final = getSingleProp(prop); 281 | expect(final).toBe(prop); 282 | }); 283 | it('If type params do not exist, it should return the name of the type as a string', () => { 284 | let prop = `Foo`; 285 | let final = getSingleProp(prop); 286 | expect(final).toBe(prop); 287 | }); 288 | }); 289 | describe('tuples', () => { 290 | it('Resolves down to a string representation of a tuple', () => { 291 | let prop = `[string, number]`; 292 | let final = getSingleTSPropTypes(prop); 293 | expect(final).toBe(`a: [string, number]`); 294 | }); 295 | }); 296 | describe('typeQuery', () => { 297 | it('Resolves down to a string representation of a typeof invocation', () => { 298 | const prop = `typeof Foo`; 299 | const final = getSingleTSPropTypes(prop); 300 | expect(final).toBe(`a: ${prop}`); 301 | }); 302 | }); 303 | describe('type params', () => { 304 | it('should return a string representation of a function type with type params', () => { 305 | let prop = '() => '; 306 | let file = ` 307 | type Foo = (bar: T) => T; 308 | class Component extends React.Component<{ a: Foo }> {} 309 | `; 310 | let res = extractReactTypes(file, 'flow'); 311 | let final = convert(res.component.members[0].value); 312 | expect(final).toBe(prop); 313 | }); 314 | }); 315 | it('exposes converters object', () => { 316 | expect(converters).not.toBeUndefined(); 317 | }); 318 | }); 319 | describe('arrayType', () => { 320 | it('should convert the array syntax to a string', () => { 321 | let prop = `number[]`; 322 | let final = getSingleProp(prop); 323 | expect(final).toBe('Array of number'); 324 | }); 325 | }); 326 | describe('import', () => { 327 | it('Resolves down to a string representation of an external import', () => { 328 | let file = ` 329 | import { Component } from 'react'; 330 | class Something extends React.Component<{ a: Component }> {}`; 331 | let res = extractReactTypes(file, 'flow').component.members[0].value; 332 | let converted = convert(res); 333 | expect(converted).toBe('react.Component'); 334 | }); 335 | 336 | it.skip('Resolves down to a string representation for namespaced external imports', () => { 337 | let file = ` 338 | import { Component } from 'react'; 339 | import * as foo from 'bar'; 340 | class Something extends Component<{ a: foo }> {} 341 | `; 342 | let res = extractReactTypes(file, 'flow').classes[0].members[0].value; 343 | let converted = convert(res); 344 | expect(converted).toBe('foo'); 345 | }); 346 | }); 347 | describe('typecastExpression', () => { 348 | it('should convert a typecast expression to a string', () => { 349 | let file = ` 350 | type Props = { bar: string } 351 | 352 | class Component extends React.Component { 353 | static defaultProps = { 354 | bar: (ascii: string), 355 | } 356 | } 357 | `; 358 | let res = extractReactTypes(file, 'flow').component.value.members[0].default; 359 | let converted = convert(res); 360 | expect(converted).toBe('ascii'); 361 | }); 362 | }); 363 | describe('logicalExpression', () => { 364 | it('should work', () => { 365 | let file = `class Button extends React.Component<{ or: string }> { 366 | static defaultProps = { 367 | or: 'me' || 'you' || 'someone else' && 'impossible state', 368 | } 369 | }`; 370 | 371 | let res = extractReactTypes(file, 'flow').component.members[0].default; 372 | let converted = convert(res); 373 | expect(converted).toBe('"me" || "you" || "someone else" && "impossible state"'); 374 | }); 375 | }); 376 | describe('utilities', () => { 377 | describe('resolveLast', () => {}); 378 | }); 379 | }); 380 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # pretty-proptypes 2 | 3 | ## 1.7.0 4 | 5 | ### Minor Changes 6 | 7 | - 67a16ca: Adds "sortProps" and "requiredPropsFirst" props to LayoutRenderer and HybridLayout 8 | 9 | ### Patch Changes 10 | 11 | - 67a16ca: Permalink generation should also work now when `extract-react-types` is used. 12 | 13 | ## 1.6.1 14 | 15 | ### Patch Changes 16 | 17 | - e0686d4: Changed the heading styles for deprecated props and removed "@deprecated" from the prop description. 18 | 19 | ## 1.6.0 20 | 21 | ### Minor Changes 22 | 23 | - 33d0126: Introduces new functionality enabling permalinks to each section of a props list. It's done in a backwards-compatible manner requiring no code changes for a dependency bump, but you might want to double check styles are suitable to the surroundings of where the props are displayed. 24 | 25 | ## 1.5.2 26 | 27 | ### Patch Changes 28 | 29 | - 2bef5ff: Reintroduces null check in the case where props can't be parsed and return empty 30 | 31 | ## 1.5.0 32 | 33 | ### Minor Changes 34 | 35 | - f34959f: Adds the ability to override the Prop Expander + Fixes a few styling issues 36 | 37 | ## 1.4.0 38 | 39 | ### Minor Changes 40 | 41 | - 093e1af: Adds a generic layout renderer which can be used to create bespoke prop tables. Also refactors the internals of the hybrid layout to utilise it 42 | 43 | ## 1.3.0 44 | 45 | ### Minor Changes 46 | 47 | - [`a763063`](https://github.com/atlassian/extract-react-types/commit/a76306349bbccc1aa53a101ce7c444120c2a0ba4) [#199](https://github.com/atlassian/extract-react-types/pull/199) Thanks [@madou](https://github.com/madou)! - Props that have comments which start with `eslint-ignore` or `@ts-` are no longer rendered, 48 | other surrounding comments are still rendered for the prop however. 49 | 50 | * [`a763063`](https://github.com/atlassian/extract-react-types/commit/a76306349bbccc1aa53a101ce7c444120c2a0ba4) [#199](https://github.com/atlassian/extract-react-types/pull/199) Thanks [@madou](https://github.com/madou)! - Props that have comments which contain `@internal` or `@access private` are no longer rendered to the props table, 51 | essentially having the prop and all of its comments hidden. 52 | 53 | ## 1.2.0 54 | 55 | ### Minor Changes 56 | 57 | - [`84e9241`](https://github.com/atlassian/extract-react-types/commit/84e9241d0bfe9827f5331d96ae9f80cdf3e10894) [#174](https://github.com/atlassian/extract-react-types/pull/174) Thanks [@declan-warn](https://github.com/declan-warn)! - Added a new hybrid layout for displaying props. 58 | 59 | ## 1.1.4 60 | 61 | ### Patch Changes 62 | 63 | - [`a879c29`](https://github.com/atlassian/extract-react-types/commit/a879c295a2b3131d00087d606a5d85cac60924ec) [#146](https://github.com/atlassian/extract-react-types/pull/146) Thanks [@declan-warn](https://github.com/declan-warn)! - Add missing jsx pragma in `packages/pretty-proptypes/src/PropsTable/index.js` which fixes an erroneous `css` attribute being rendered in prop table `tr`'s. 64 | 65 | ## 1.1.3 66 | 67 | ### Patch Changes 68 | 69 | - [`0262fb3`](https://github.com/atlassian/extract-react-types/commit/0262fb3fb04147e5a7d2e2e1e5c20b7415ee13df) [#130](https://github.com/atlassian/extract-react-types/pull/130) Thanks [@pgmanutd](https://github.com/pgmanutd)! - add generic prop interface handling in pretty-proptypes 70 | 71 | ## 1.1.2 72 | 73 | ### Patch Changes 74 | 75 | - Updated dependencies [[`d1115ee`](https://github.com/atlassian/extract-react-types/commit/d1115eecdeedda23caa558f253ee4f769e3f0606)]: 76 | - kind2string@0.8.0 77 | 78 | ## 1.1.1 79 | 80 | ### Patch Changes 81 | 82 | - [`c7c50fa`](https://github.com/atlassian/extract-react-types/commit/c7c50fa09a571a77a5f1376a97b1872a8e85af2e) [#125](https://github.com/atlassian/extract-react-types/pull/125) Thanks [@gwyneplaine](https://github.com/gwyneplaine)! - Fix rendering bug in Props component 83 | 84 | ## 1.1.0 85 | 86 | ### Minor Changes 87 | 88 | - [`e2519d0`](https://github.com/atlassian/extract-react-types/commit/e2519d040a16f55ff501da8716371b1331a379e5) [#122](https://github.com/atlassian/extract-react-types/pull/122) Thanks [@gwyneplaine](https://github.com/gwyneplaine)! - Table view added to pretty-proptypes in the form of PropTable component, exported from src 89 | 90 | ## 1.0.6 91 | 92 | ### Patch Changes 93 | 94 | - Updated dependencies [[`99f6c8a`](https://github.com/atlassian/extract-react-types/commit/99f6c8a1cd0c41091caa870d233b34c0500b0565), [`849c979`](https://github.com/atlassian/extract-react-types/commit/849c979faf91b6b1f24a85ce267698639e4caeb8)]: 95 | - extract-react-types@0.24.0 96 | - kind2string@0.7.3 97 | 98 | ## 1.0.5 99 | 100 | - Updated dependencies [acb8499](https://github.com/atlassian/extract-react-types/commit/acb8499): 101 | - kind2string@0.7.2 102 | - extract-react-types@0.23.0 103 | 104 | ## 1.0.4 105 | 106 | ### Patch Changes 107 | 108 | - [`6827845`](https://github.com/atlassian/extract-react-types/commit/68278457981fc557dc470f79ca56b686814c3e21) Thanks [@Noviny](https://github.com/Noviny)! - Fix build from previous release 109 | 110 | - Updated dependencies [[`6827845`](https://github.com/atlassian/extract-react-types/commit/68278457981fc557dc470f79ca56b686814c3e21)]: 111 | - kind2string@0.7.1 112 | 113 | ## 1.0.3 114 | 115 | ### Patch Changes 116 | 117 | - Updated dependencies [[`dc667b4`](https://github.com/atlassian/extract-react-types/commit/dc667b45277ca0440f67f24051e1d0ada07f5e4d)]: 118 | - kind2string@0.7.0 119 | 120 | ## 1.0.2 121 | 122 | ### Patch Changes 123 | 124 | - [patch][089780f](https://github.com/atlassian/extract-react-types/commit/089780f): 125 | Change console.error for null members in pretty-proptypes to console.warn 126 | 127 | ## 1.0.1 128 | 129 | ### Patch Changes 130 | 131 | - [patch][ac12401](https://github.com/atlassian/extract-react-types/commit/ac12401): 132 | Work around added in object converter to not throw on null value in members list 133 | 134 | ## 1.0.0 135 | 136 | ### Major Changes 137 | 138 | - [major][13719db](https://github.com/atlassian/extract-react-types/commit/13719db): 139 | Upgrade to emotion 10. If you've been using emotion-server to server render this package, you can remove it now because server rendering will work without it. 140 | 141 | ### Patch Changes 142 | 143 | - [patch][58d12d8](https://github.com/atlassian/extract-react-types/commit/58d12d8): 144 | Fix a usage of the css prop that wasn't updated to emotion 10's syntax 145 | 146 | - Updated dependencies [dc4b719](https://github.com/atlassian/extract-react-types/commit/dc4b719): 147 | - kind2string@0.6.3 148 | - extract-react-types@0.22.0 149 | 150 | ## 0.6.6 151 | 152 | - Updated dependencies [dc4b719](https://github.com/atlassian/extract-react-types/commit/dc4b719): 153 | - kind2string@0.6.2 154 | - extract-react-types@0.21.0 155 | 156 | ## 0.6.5 157 | 158 | ### Patch Changes 159 | 160 | - [patch][e6cc1f5](https://github.com/atlassian/extract-react-types/commit/e6cc1f5): 161 | Remove dangerous debug code that broke everything 162 | 163 | ## 0.6.4 164 | 165 | - Updated dependencies [533d172](https://github.com/atlassian/extract-react-types/commit/533d172): 166 | - extract-react-types@0.20.0 167 | - kind2string@0.6.0 168 | 169 | ## 0.6.3 170 | 171 | - Updated dependencies [d232e30](https://github.com/atlassian/extract-react-types/commit/d232e30): 172 | - kind2string@0.5.6 173 | - extract-react-types@0.19.0 174 | 175 | ## 0.6.2 176 | 177 | - Updated dependencies [907688c](https://github.com/atlassian/extract-react-types/commit/907688c): 178 | - kind2string@0.5.5 179 | - extract-react-types@0.18.0 180 | 181 | ## 0.6.1 182 | 183 | ### Patch Changes 184 | 185 | - [patch][287e4b4](https://github.com/atlassian/extract-react-types/commit/287e4b4): 186 | 187 | - Updated build to use preconstruct (this shouldn't affect usage, but calling this out just in case) 188 | 189 | - Updated dependencies [e682bbb](https://github.com/atlassian/extract-react-types/commit/e682bbb): 190 | - kind2string@0.5.4 191 | - extract-react-types@0.17.0 192 | 193 | ## 0.6.0 194 | 195 | - [minor][277b0be](https://github.com/atlassian/extract-react-types/commit/277b0be): 196 | 197 | - Add component prop to Props component 198 | 199 | - Updated dependencies [277b0be](https://github.com/atlassian/extract-react-types/commit/277b0be): 200 | - Updated dependencies [8f04dad](https://github.com/atlassian/extract-react-types/commit/8f04dad): 201 | - Updated dependencies [6bc521c](https://github.com/atlassian/extract-react-types/commit/6bc521c): 202 | - kind2string@0.5.2 203 | - extract-react-types@0.16.0 204 | 205 | ## 0.5.0 206 | 207 | - Update to support 0.15.0 of `extract-react-types` 208 | - **breaking** The structure of the `Program` kind from `extract-react-types` has changed. `0.5.0` consumes and responds to that change, and will only work with later versions of `extract-react-types`. 209 | 210 | ## v0.4.2 211 | 212 | - Use to mean the prop type can be selected separately to the prop name 213 | 214 | Thanks [Maciej Adamczak](https://github.com/macku) for 215 | 216 | ## v0.4.1 217 | 218 | Thanks [Michael Blaszczyk](https://github.com/Blasz) for these contributions! 219 | 220 | - Support spreading generic types that cannot be reduced down to objects 221 | - Move reduceToObj to kind2string and then update kind2string dependency 222 | 223 | ## v0.4.0 224 | 225 | - pull in newer version of kind2string, adding support for 'export' and 'exportSpecifier' kinds. 226 | 227 | ## v0.3.0 228 | 229 | - Add arrayType converter 230 | - Remove whitespace created by converting a type that wasn't there 231 | 232 | ## v0.2.3 233 | 234 | - fix bug where object converter would assume all spreads had members when they were resolved from generic. Some spreads will resolve to an import. Used simple solution of allowing spreads that did not resolve to have members gets caught with a second call to prettyConvert. 235 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/README.md: -------------------------------------------------------------------------------- 1 | # PrettyPropTypes 2 | 3 | PrettyPropTypes is designed to display the output of 4 | [extract-react-types](https://www.npmjs.com/package/extract-react-types). It is 5 | designed to read the output from `extract-react-types`, and display rich prop 6 | information for consumers. 7 | 8 | ## Core usage pattern 9 | 10 | pretty-proptypes can display props from two sources. 11 | 12 | - Using `babel-plugin-extract-react-types` and passing the component to `Props` 13 | 14 | `.babelrc` 15 | 16 | ```json 17 | { 18 | "plugins": ["babel-plugin-extract-react-types"] 19 | } 20 | ``` 21 | 22 | ```js 23 | import MyCoolComponent from '../MyCoolComponent'; 24 | 25 | ; 26 | ``` 27 | 28 | - Directly passing a component's props to `Props` with `extract-react-types-loader` or getting types from `extract-react-types` and writing it to a file 29 | 30 | ```js 31 | 35 | ``` 36 | 37 | This analyses prop type definitions, and default props. It creates descriptions 38 | from comments before the type definitions, and will render markdown syntax using [react-markings](https://www.npmjs.com/package/react-markings). 39 | 40 | ## Quick Tips 41 | 42 | - Using [babel-plugin-extract-react-types](https://www.npmjs.com/package/babel-plugin-extract-react-types) 43 | is definitely the easiest way to get this information from your components, however 44 | you can use `extract-react-types-loader` or prebuild this data with `extract-react-types` and read it from a file if 45 | you prefer. 46 | - When using `extract-react-types` directly or `extract-react-types-loader`, they will currently only look at the default export 47 | of a file. `babel-plugin-extract-types` will look at the default export as well as named exports. 48 | 49 | ## Customisation Props 50 | 51 | ### Heading 52 | 53 | Display a heading for the collection of props. Pass in an empty string if you want 54 | no heading, otherwise it defaults to "Props". 55 | 56 | ### shouldCollapseProps 57 | 58 | Set whether the prop shapes should be shown by default, or whether they should 59 | be hidden, and require being expanded. 60 | 61 | ### Components 62 | 63 | Accepts an object that allows you to override particular style components within 64 | our prop definition. The currently modifiable components are: 65 | 66 | - Indent 67 | - Outline 68 | - Required 69 | - Type 70 | - StringType 71 | - TypeMeta 72 | 73 | Any that are not passed in will use the default component. 74 | 75 | ## Overrides 76 | 77 | The `override` prop allows you to override a specific prop's definition. If you 78 | want to keep the appearance aligned, we recommend using the `Prop` export from 79 | PrettyPropType. 80 | 81 | An override is invoked with all the props passed to the Prop component internally, 82 | and renders the result. In the example below, we are changing the `type` field, 83 | and stopping the shape component from appearing, while leaving other parts of the 84 | component the same. 85 | 86 | ```js 87 | import Props, { Prop } from 'pretty-proptypes' 88 | 89 | ${ null} type="All Components Object" /> }} 94 | />} 95 | ``` 96 | 97 | While you can pass style `components` directly to `Prop`, we recommend passing 98 | style components in the top level Props, and letting them flow down. 99 | 100 | ## Custom layouts 101 | 102 | In cases where a completely bespoke layout is required, use the `LayoutRenderer`. This component allows you to define a completely custom layout and substitute in your own UI. 103 | 104 | The `renderTypes` prop is called for every prop found on a given component and allows you to specify how that type should be rendered. 105 | 106 | If you don't want to override the default components, you can use the `components` property. Or import them directly from `pretty-proptypes`. 107 | 108 | ```js 109 | import { LayoutRenderer } from 'pretty-proptypes'; 110 | 111 | { 114 |
115 |

{name}

116 | {description} 117 | {required && Required} 118 | {type} 119 | 120 |
; 121 | }} 122 | />; 123 | ``` 124 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pretty-proptypes", 3 | "version": "1.7.0", 4 | "description": "prettily render prop types from react components", 5 | "repository": "atlassian/extract-react-types", 6 | "main": "dist/pretty-proptypes.cjs.js", 7 | "module": "dist/pretty-proptypes.esm.js", 8 | "keywords": [ 9 | "extract-react-types", 10 | "react", 11 | "props", 12 | "documentation" 13 | ], 14 | "author": "Ben Conolly", 15 | "license": "MIT", 16 | "dependencies": { 17 | "@babel/runtime": "^7.4.4", 18 | "@emotion/core": "^10.0.14", 19 | "kind2string": "^0.8.1", 20 | "react-markings": "^1.2.0" 21 | }, 22 | "files": [ 23 | "dist" 24 | ], 25 | "devDependencies": { 26 | "extract-react-types": "^0.30.0", 27 | "jsdom": "^11.7.0", 28 | "react": "^16.3.1", 29 | "react-addons-test-utils": "^15.6.2", 30 | "react-dom": "^16.3.1" 31 | }, 32 | "peerDependencies": { 33 | "react": ">=16" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/HybridLayout/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /** @jsx jsx */ 4 | import { jsx, css } from '@emotion/core'; 5 | import { Component, type Node } from 'react'; 6 | 7 | import md from 'react-markings'; 8 | import PropsWrapper from '../Props/Wrapper'; 9 | import LayoutRenderer, { type LayoutRendererProps } from '../LayoutRenderer'; 10 | import { 11 | HeadingType, 12 | HeadingDefault, 13 | Heading, 14 | HeadingRequired, 15 | HeadingDeprecated 16 | } from '../Prop/Heading'; 17 | import { colors } from '../components/constants'; 18 | 19 | type DynamicPropsProps = LayoutRendererProps & { 20 | heading?: string, 21 | shouldCollapseProps?: boolean 22 | }; 23 | 24 | const Description = ({ children }: { children: Node }) => ( 25 |
35 | {children} 36 |
37 | ); 38 | 39 | export default class HybridLayout extends Component { 40 | render() { 41 | const { 42 | props, 43 | heading, 44 | component, 45 | shouldCollapseProps, 46 | requiredPropsFirst, 47 | sortProps 48 | } = this.props; 49 | 50 | return ( 51 | 52 | ( 68 | caption { 93 | background: #e9f2ff; 94 | } 95 | `} 96 | > 97 | 147 | 148 | 149 | 150 | 157 | 158 | {defaultValue !== undefined && ( 159 | 160 | 161 | 164 | 165 | )} 166 | 167 | 168 | 185 | 186 | 187 |
104 | 112 | 123 | {name} 124 | 125 | {required && defaultValue === undefined && ( 126 | 132 | required 133 | 134 | )} 135 | {deprecated && ( 136 | 142 | deprecated 143 | 144 | )} 145 | 146 |
Description 151 | {description && ( 152 | 153 | {md([description && description.replace('@deprecated', '')])} 154 | 155 | )} 156 |
Default 162 | {defaultValue} 163 |
Type 174 | 175 | {type} 176 | 177 | 178 | 183 | 184 |
188 | )} 189 | /> 190 |
191 | ); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/LayoutRenderer/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /* eslint-disable no-underscore-dangle */ 4 | 5 | import { FC, type ComponentType } from 'react'; 6 | 7 | import type { CommonProps } from '../types'; 8 | import type { Components } from '../components'; 9 | 10 | import getPropTypes from '../getPropTypes'; 11 | import renderPropType from '../renderPropType'; 12 | import PrettyPropType from '../PrettyConvert'; 13 | import { getComponentDisplayName } from '../utils'; 14 | 15 | type Obj = { 16 | kind: 'object', 17 | members: Array 18 | }; 19 | 20 | type Gen = { 21 | kind: 'generic', 22 | value: any 23 | }; 24 | 25 | type Inter = { 26 | kind: 'intersection', 27 | types: Array 28 | }; 29 | 30 | type Prop = { 31 | kind: string, 32 | optional: boolean, 33 | key: { kind: string, name: string }, 34 | value: { kind: string } 35 | }; 36 | 37 | export type LayoutRendererProps = { 38 | /** 39 | * Types to render, passed directly from the output of extract-react-types-loader 40 | */ 41 | props?: { 42 | component?: Obj | Inter 43 | }, 44 | /** React Component to render props for. */ 45 | component?: ComponentType, 46 | /** Custom components to render to end-users */ 47 | components?: Components, 48 | /** 49 | * Function to render each prop in the props list. 50 | * Render props contain the prop's information, including name, description etc. 51 | */ 52 | renderType: CommonProps => ComponentType, 53 | /** 54 | * If true, required props will be placed first in the props array 55 | */ 56 | requiredPropsFirst?: boolean, 57 | /** 58 | * Sorting function applied to props list. 59 | * Given two prop objects, returns: 60 | * - 0 to maintain order 61 | * - < 0 if propA should render before propB 62 | * - > 0 if propB should render before propA 63 | */ 64 | sortProps?: (propA: Prop, propB: Prop) => number 65 | }; 66 | 67 | const getProps = props => (props && props.component ? getPropTypes(props.component) : []); 68 | 69 | const LayoutRenderer: FC = ({ 70 | props, 71 | component, 72 | components, 73 | requiredPropsFirst, 74 | sortProps, 75 | ...rest 76 | }) => { 77 | let resolvedProps = props; 78 | if (component) { 79 | /* $FlowFixMe the component prop is typed as a component because 80 | that's what people pass to Props and the ___types property shouldn't 81 | exist in the components types so we're just going to ignore this error */ 82 | if (component.___types) { 83 | resolvedProps = { type: 'program', component: component.___types }; 84 | } else { 85 | /* eslint-disable-next-line no-console */ 86 | console.error( 87 | 'A component was passed to but it does not have types attached.\n' + 88 | 'babel-plugin-extract-react-types may not be correctly installed.\n' + 89 | ' will fallback to the props prop to display types.' 90 | ); 91 | } 92 | } 93 | 94 | let renderProps = rest; 95 | const componentDisplayName = getComponentDisplayName(component || (props || {}).component); 96 | if (typeof componentDisplayName === 'string') { 97 | renderProps = { 98 | ...rest, 99 | componentDisplayName 100 | }; 101 | } 102 | 103 | // Sort prop list 104 | let finalProps = getProps(resolvedProps); 105 | if (sortProps) finalProps.sort(sortProps); 106 | if (requiredPropsFirst) { 107 | finalProps.sort((propA, propB) => { 108 | const propARequired = !(propA.optional || propA.default); 109 | const propBRequired = !(propB.optional || propB.default); 110 | if (propARequired === propBRequired) return 0; 111 | return propARequired ? -1 : 1; 112 | }); 113 | } 114 | 115 | return finalProps.map(propType => 116 | renderPropType( 117 | propType, 118 | { ...renderProps, components: { ...components, PropType: PrettyPropType } }, 119 | rest.renderType 120 | ) 121 | ); 122 | }; 123 | 124 | export default LayoutRenderer; 125 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/LayoutRenderer/index.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { render, screen } from '@testing-library/react'; 4 | import React from 'react'; 5 | import { extractReactTypes } from 'extract-react-types'; 6 | import LayoutRenderer from './index'; 7 | 8 | const assembleComponent = (propTypes, defaultProps, type = 'typescript') => { 9 | let file = ` 10 | class Component extends React.Component<${propTypes}> { 11 | defaultProps = ${defaultProps} 12 | }`; 13 | return extractReactTypes(file, type); 14 | }; 15 | 16 | test('requiredPropsFirst should place required props in front of other props', () => { 17 | const propTypes = assembleComponent( 18 | `{ 19 | /** 20 | * Required, but with a default value. Should be rendered second 21 | */ 22 | a: number, 23 | /** 24 | * Optional, should be rendered third 25 | */ 26 | b?: string, 27 | /** 28 | * Required, no default. Should be rendered first 29 | */ 30 | c: boolean, 31 | }`, 32 | `{a: 1}` 33 | ); 34 | 35 | const order = []; 36 | 37 | render( 38 | { 42 | order.push(props.name); 43 | return
; 44 | }} 45 | /> 46 | ); 47 | 48 | expect(order[0]).toEqual('c'); 49 | }); 50 | 51 | test('sortProps should run sort function before applying requiredPropsFirst', () => { 52 | const propTypes = assembleComponent( 53 | `{ 54 | c?: number, 55 | b?: string, 56 | a?: boolean, 57 | e: string, 58 | d: string, 59 | }`, 60 | `{a: 1}` 61 | ); 62 | 63 | const order = []; 64 | 65 | render( 66 | propA.key.name.localeCompare(propB.key.name)} 69 | requiredPropsFirst 70 | renderType={props => { 71 | order.push(props.name); 72 | return
; 73 | }} 74 | /> 75 | ); 76 | 77 | expect(order[0]).toEqual('d'); 78 | }); 79 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/PrettyConvert/AddBrackets.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx jsx */ 3 | import { jsx } from '@emotion/core'; 4 | import { Component, ComponentType, Fragment, type Node } from 'react'; 5 | import ExpanderDefault from '../components/Expander'; 6 | 7 | type Props = { 8 | openBracket: string, 9 | closeBracket: string, 10 | children: Node | void, 11 | Expander: ComponentType 12 | }; 13 | 14 | type State = { 15 | isHovered: boolean, 16 | isShown: boolean 17 | }; 18 | 19 | export default class AddBrackets extends Component { 20 | static defaultProps = { 21 | openBracket: '(', 22 | closeBracket: ')', 23 | Expander: ExpanderDefault 24 | }; 25 | 26 | state = { isHovered: false, isShown: true }; 27 | 28 | isHovered = () => this.setState({ isHovered: true }); 29 | isNotHovered = () => this.setState({ isHovered: false }); 30 | 31 | render() { 32 | const { openBracket, closeBracket, children, Expander } = this.props; 33 | const { isHovered, isShown } = this.state; 34 | 35 | return ( 36 | 37 | this.setState({ isShown: !isShown })} 40 | onMouseEnter={this.isHovered} 41 | onMouseLeave={this.isNotHovered} 42 | > 43 | {openBracket} 44 | 45 | {isShown ? ( 46 | children 47 | ) : ( 48 | this.setState({ isShown: true, isHovered: false })} 51 | onMouseEnter={this.isHovered} 52 | onMouseLeave={this.isNotHovered} 53 | > 54 | ... 55 | 56 | )} 57 | this.setState({ isShown: !isShown })} 60 | onMouseEnter={this.isHovered} 61 | onMouseLeave={this.isNotHovered} 62 | > 63 | {closeBracket} 64 | 65 | 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/PrettyConvert/Toggle.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx jsx */ 3 | import { jsx, css } from '@emotion/core'; 4 | import { Component, type Node, type ElementRef } from 'react'; 5 | 6 | const Collapse = ({ 7 | height, 8 | isCollapsed, 9 | innerRef, 10 | ...props 11 | }: { 12 | height: number, 13 | isCollapsed: boolean, 14 | innerRef: ElementRef<*> 15 | }) => ( 16 |
25 | ); 26 | 27 | type toggleProps = { 28 | beforeCollapse: (boolean, () => mixed) => Node, 29 | afterCollapse: (boolean, () => mixed) => Node, 30 | beginClosed: boolean, 31 | children: Node 32 | }; 33 | 34 | type toggleState = { 35 | isCollapsed: boolean, 36 | contentHeight: number 37 | }; 38 | 39 | export default class Toggle extends Component { 40 | static defaultProps = { 41 | beforeCollapse: () => null, 42 | afterCollapse: () => null, 43 | beginClosed: false, 44 | children: null 45 | }; 46 | 47 | content: ?HTMLElement; 48 | 49 | constructor(props: toggleProps) { 50 | super(props); 51 | 52 | this.state = { 53 | isCollapsed: this.props.beginClosed, 54 | contentHeight: 0 55 | }; 56 | } 57 | 58 | componentDidMount() { 59 | const contentHeight = this.content ? this.content.scrollHeight : 0; 60 | this.setState({ contentHeight }); 61 | } 62 | 63 | componentWillReceiveProps() { 64 | const contentHeight = this.content ? this.content.scrollHeight : 0; 65 | if (contentHeight !== this.state.contentHeight) { 66 | this.setState({ contentHeight }); 67 | } 68 | } 69 | 70 | getContent = (ref: ElementRef<*>) => { 71 | if (!ref) return; 72 | this.content = ref; 73 | }; 74 | 75 | toggleCollapse = () => { 76 | const contentHeight = this.content ? this.content.scrollHeight : 0; 77 | this.setState({ contentHeight, isCollapsed: !this.state.isCollapsed }); 78 | }; 79 | 80 | render() { 81 | let { beforeCollapse, children, afterCollapse } = this.props; 82 | let { isCollapsed, contentHeight } = this.state; 83 | 84 | return ( 85 |
86 | {beforeCollapse(isCollapsed, this.toggleCollapse)} 87 | 93 | {children} 94 | 95 | {afterCollapse(isCollapsed, this.toggleCollapse)} 96 |
97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/PrettyConvert/converters.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | // TODO: Remove this eslint-disable 4 | /* eslint-disable */ 5 | /** @jsx jsx */ 6 | import { jsx, css } from '@emotion/core'; 7 | import { type Node } from 'react'; 8 | import convert, { resolveFromGeneric } from 'kind2string'; 9 | import type { Components } from '../components'; 10 | import AddBrackets from './AddBrackets'; 11 | import { colors } from '../components/constants'; 12 | /*:: 13 | import * as K from 'extract-react-types' 14 | */ 15 | 16 | export const SIMPLE_TYPES = [ 17 | 'array', 18 | 'boolean', 19 | 'number', 20 | 'string', 21 | 'symbol', 22 | 'node', 23 | 'element', 24 | 'custom', 25 | 'any', 26 | 'void', 27 | 'mixed' 28 | ]; 29 | 30 | function printComplexType(type, components, depth) { 31 | if (typeof type === 'object' && !SIMPLE_TYPES.includes(type.kind)) { 32 | return prettyConvert(type, components, depth); 33 | } 34 | return null; 35 | } 36 | 37 | export const TypeMinWidth = (props: { children: Node }) => ( 38 | 45 | ); 46 | 47 | const Arrow = () => ( 48 | 53 | {' => '} 54 | 55 | ); 56 | 57 | export const converters: { [string]: ?Function } = { 58 | intersection: (type: K.Intersection, components: Components) => 59 | type.types.reduce( 60 | (acc, intersectionType, index) => 61 | index < type.types.length - 1 62 | ? [ 63 | ...acc, 64 | {prettyConvert(intersectionType, components)}, 65 |
&
66 | ] 67 | : [...acc, {prettyConvert(intersectionType, components)}], 68 | [] 69 | ), 70 | string: (type: K.String, components: Components) => { 71 | if (type.value != null) { 72 | return {convert(type)}; 73 | } 74 | return {convert(type)}; 75 | }, 76 | // nullable types are currently stripping infromation, as we show 'required' 77 | // when it is required. This may be incorrect, and should be reconsidered. 78 | nullable: (type: K.Nullable, components: Components, depth: number) => { 79 | return prettyConvert(type.arguments, components, depth); 80 | }, 81 | generic: (type: K.Generic, components: Components, depth: number) => { 82 | if (type.value && type.typeParams) { 83 | // As Flow does not know what the keyword Array means, we're doing a 84 | // check here for generic types with a nominal value of 'Array' 85 | // If a type meets this criteria, we print out its contents as per below. 86 | return ( 87 | 88 | {convert(type.value)} 89 | 90 | {type.typeParams && 91 | type.typeParams.params.map((param, index, array) => ( 92 | 93 | {prettyConvert(param, components, depth)} 94 | {type.typeParams && index === array.length - 1 ? '' : ', '} 95 | 96 | ))} 97 | 98 | 99 | ); 100 | } 101 | return prettyConvert(resolveFromGeneric(type), components); 102 | }, 103 | object: (type: K.Obj, components: Components, depth: number) => { 104 | if (type.members.length === 0) { 105 | return Object; 106 | } 107 | let simpleObj = 108 | type.members.filter(mem => { 109 | if (mem === null) { 110 | /** if the member is null, error out */ 111 | console.warn(`null property in members of ${type.referenceIdName} of kind ${type.kind} `); 112 | return false; 113 | } 114 | return !SIMPLE_TYPES.includes(mem.kind); 115 | }).length === 0; 116 | 117 | if (simpleObj) { 118 | return {convert(type)}; 119 | } 120 | 121 | return ( 122 | 123 | 124 | 125 | {type.members 126 | .filter(p => p) 127 | .map(prop => { 128 | if (prop.kind === 'spread') { 129 | const nestedObj = resolveFromGeneric(prop.value); 130 | // Spreads almost always resolve to an object, but they can 131 | // also resolve to an import. We just allow it to fall through 132 | // to prettyConvert if there are no members 133 | if (nestedObj.members) { 134 | return nestedObj.members.map(newProp => 135 | prettyConvert(newProp, components, depth) 136 | ); 137 | } 138 | } 139 | return prettyConvert(prop, components, depth); 140 | })} 141 | 142 | 143 | 144 | ); 145 | }, 146 | arrayType: (type: K.ArrayType, components: Components, depth: number) => { 147 | return ( 148 | 149 | Array 150 | 151 | {prettyConvert(type.type, components, depth)} 152 | 153 | 154 | ); 155 | }, 156 | property: (type: K.Property, components: Components, depth: number) => ( 157 |
158 | {type.key && ( 159 | 160 | {convert(type.key)} 161 | 162 | )} 163 | {type.value.kind !== 'generic' ? ` ${type.value.kind}` : ' '} 164 | {type.optional ? null : required}{' '} 165 | {printComplexType(type.value, components, depth)} 166 |
167 | ), 168 | union: (type: K.Union, components: Components, depth: number) => ( 169 | 170 | One of 171 | 172 | 173 | {type.types.map((t, index, array) => ( 174 |
175 | {prettyConvert(t, components, depth + 1)} 176 | {array.length - 1 === index ? '' : ', '} 177 |
178 | ))} 179 |
180 |
181 |
182 | ), 183 | function: (type: K.Func, components: Components, depth: number) => { 184 | let simpleReturn = type.returnType && SIMPLE_TYPES.includes(type.returnType.kind); 185 | 186 | let simpleParameters = 187 | type.parameters.filter(param => SIMPLE_TYPES.includes(param.value.kind)).length === 188 | type.parameters.length; 189 | 190 | if (simpleParameters && simpleReturn) { 191 | return ( 192 | 193 | {`(${type.parameters.map(convert).join(', ')})`} 194 | 195 | {`${convert(type.returnType)}`} 196 | 197 | ); 198 | } else if (simpleParameters || type.parameters.length < 2) { 199 | return ( 200 | 201 | 202 | {type.parameters.map((param, index, array) => [ 203 | prettyConvert(param, components, depth), 204 | array.length - 1 === index ? '' : ', ' 205 | ])} 206 | 207 | 208 | {type.returnType ? prettyConvert(type.returnType, components, depth) : 'undefined'} 209 | 210 | ); 211 | } else { 212 | return ( 213 | 214 | function 215 | 216 | 217 | {type.parameters.map((param, index, array) => ( 218 |
219 | {prettyConvert(param, components, depth + 1)} 220 | {array.length - 1 === index ? '' : ', '} 221 |
222 | ))} 223 |
224 |
225 | 226 | {type.returnType ? prettyConvert(type.returnType, components, depth) : 'undefined'} 227 |
228 | ); 229 | } 230 | }, 231 | param: (type: K.Param, components, depth) => ( 232 | {prettyConvert(type.value, components, depth)} 233 | ), 234 | typeof: (type: K.Typeof, components, depth) => prettyConvert(type.type, components, depth) 235 | }; 236 | 237 | const prettyConvert = (type: K.AnyKind, components: Components, depth: number = 1) => { 238 | if (!type) { 239 | return ''; 240 | } 241 | 242 | const converter = converters[type.kind]; 243 | if (!converter) { 244 | const stringType = convert(type); 245 | return {stringType}; 246 | } 247 | return converter(type, components, depth); 248 | }; 249 | 250 | export default prettyConvert; 251 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/PrettyConvert/converters.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { render } from '@testing-library/react'; 5 | import { extractReactTypes } from 'extract-react-types'; 6 | import components from '../components'; 7 | import prettyConvert from './converters'; 8 | 9 | const simpleStringKind = { kind: 'string', value: 'here it is' }; 10 | let getSimplePropKind = (obj = {}) => ({ 11 | kind: 'property', 12 | key: { kind: 'id', name: 'prop1' }, 13 | value: simpleStringKind, 14 | ...obj 15 | }); 16 | 17 | const assembleERTAST = (propTypes, defaultProps, type = 'flow') => { 18 | let file = ` 19 | class Component extends React.Component<${propTypes}> { 20 | defaultProps = ${defaultProps} 21 | }`; 22 | let res = extractReactTypes(file, type); 23 | return res.component.members; 24 | }; 25 | 26 | const getSingleDefault = defaultPropVal => { 27 | return assembleERTAST(`{ a: any }`, `{ a: ${defaultPropVal} }`)[0].default; 28 | }; 29 | 30 | const getSingleProp = defaultPropType => { 31 | const propTypes = assembleERTAST(`{ a: ${defaultPropType} }`, `{}`)[0]; 32 | return propTypes.value; 33 | }; 34 | 35 | test('return an empty string when no type is given', () => { 36 | // $FlowFixMe - deliberately passing in undefined here 37 | let res = prettyConvert(undefined, components); 38 | expect(res).toBe(''); 39 | }); 40 | 41 | test('fallback to kind2string type when no converter is found', () => { 42 | // $FlowFixMe - we are deliberately testing a null case here 43 | const { container: wrapper } = render(prettyConvert({ kind: 'sunshine' }, components)); 44 | const { container: other } = render(); 45 | 46 | expect(wrapper.innerHTML).toBe(other.innerHTML); 47 | }); 48 | 49 | test('prettyConvert string value type', () => { 50 | let kind = simpleStringKind; 51 | const { container: wrapper } = render(prettyConvert(kind, components)); 52 | const { container: other } = render( 53 | "{kind.value}" 54 | ); 55 | 56 | expect(wrapper.innerHTML).toBe(other.innerHTML); 57 | }); 58 | 59 | test('prettyConvert string type type', () => { 60 | const { container: wrapper } = render(prettyConvert({ kind: 'string' }, components)); 61 | const { container: other } = render(string); 62 | 63 | expect(wrapper.innerHTML).toBe(other.innerHTML); 64 | }); 65 | 66 | test('prettyConvert nullable value', () => { 67 | let kind = { 68 | kind: 'nullable', 69 | arguments: { kind: 'string' } 70 | }; 71 | const { container: wrapper } = render(prettyConvert(kind, components)); 72 | const { container: other } = render(string); 73 | expect(wrapper.innerHTML).toBe(other.innerHTML); 74 | }); 75 | 76 | test('prettyConvert simple property', () => { 77 | let simplePropKind = getSimplePropKind(); 78 | const { container: wrapper } = render(prettyConvert(simplePropKind, components)); 79 | 80 | expect(wrapper.textContent).toContain(simplePropKind.key.name); 81 | expect(wrapper.textContent).toContain(simplePropKind.value.kind); 82 | }); 83 | 84 | test('optional property', () => { 85 | const { container: wrapper } = render( 86 | prettyConvert(getSimplePropKind({ optional: true }), components) 87 | ); 88 | expect(wrapper.querySelector('[data-testid="required"]')).toBeFalsy(); 89 | }); 90 | 91 | test('simple object', () => { 92 | let values = getSingleDefault(`{ a: 'something', b: 'elsewhere' }`); 93 | const { container: wrapper } = render(prettyConvert(values, components)); 94 | 95 | expect(wrapper.textContent).toContain('a'); 96 | expect(wrapper.textContent).toContain('b'); 97 | }); 98 | 99 | test('object with nested object', () => { 100 | let values = getSingleDefault(`{ a: 'something', b: { c: 'elsewhere' }}`); 101 | const { container: wrapper } = render(prettyConvert(values, components)); 102 | expect(wrapper.textContent).toContain('c'); 103 | }); 104 | 105 | test('resolve generic of array', () => { 106 | let values = getSingleProp(`Array`); 107 | const { container } = render(prettyConvert(values, components)); 108 | expect(container.textContent).toContain('Array'); 109 | expect(container.textContent).toContain('string'); 110 | }); 111 | 112 | test('objects with null members do not throw', () => { 113 | const type = { 114 | kind: 'object', 115 | members: [ 116 | { 117 | kind: 'property', 118 | optional: false, 119 | key: { 120 | kind: 'id', 121 | name: 'children' 122 | }, 123 | value: { 124 | kind: 'generic', 125 | value: { 126 | kind: 'id', 127 | name: 'React.ReactNode' 128 | } 129 | } 130 | }, 131 | null 132 | ], 133 | referenceIdName: 'LabelTextProps' 134 | }; 135 | expect(() => prettyConvert(type, components)).not.toThrow(); 136 | }); 137 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/PrettyConvert/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx jsx */ 3 | import { jsx, css } from '@emotion/core'; 4 | import { Component, type Node } from 'react'; 5 | import { resolveFromGeneric } from 'kind2string'; 6 | import { gridSize } from '../components/constants'; 7 | import allComponents, { type Components } from '../components'; 8 | import Toggle from './Toggle'; 9 | import prettyConvert, { SIMPLE_TYPES } from './converters'; 10 | 11 | const Wrapper = (props: { children: Node }) => ( 12 | 22 | ); 23 | 24 | type PrettyPropTypeProps = { 25 | typeValue: Object, 26 | shouldCollapse?: boolean, 27 | components: Components 28 | }; 29 | 30 | export default class PrettyPropType extends Component { 31 | static defaultProps = { 32 | components: allComponents 33 | }; 34 | 35 | render() { 36 | let { shouldCollapse, typeValue: type, components } = this.props; 37 | // any instance of returning null means we are confident the information will 38 | // be displayed elsewhere so we do not need to also include it here. 39 | if (type.kind === 'generic') { 40 | type = resolveFromGeneric(type); 41 | } 42 | if (SIMPLE_TYPES.includes(type.kind)) return null; 43 | if (type.kind === 'nullable' && SIMPLE_TYPES.includes(type.arguments.kind)) { 44 | return null; 45 | } 46 | 47 | return shouldCollapse ? ( 48 | ( 51 |
52 | 53 | {isCollapsed ? 'Expand Prop Shape' : 'Hide Prop Shape'} 54 | 55 |
56 | )} 57 | > 58 | {prettyConvert(type, components)} 59 |
60 | ) : ( 61 | {prettyConvert(type, components)} 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/Prop/Heading.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx jsx */ 3 | import { jsx, css } from '@emotion/core'; 4 | import { type Node } from 'react'; 5 | import { colors, gridSize, borderRadius } from '../components/constants'; 6 | 7 | export const Heading = ({ children, ...rest }: { children: Node }) => ( 8 |

19 | {children} 20 |

21 | ); 22 | 23 | export const HeadingDefault = (props: { children: Node }) => ( 24 | 30 | ); 31 | 32 | export const HeadingRequired = (props: { children: Node }) => ( 33 | 39 | ); 40 | 41 | export const HeadingDeprecated = (props: { children: Node }) => ( 42 | 48 | ); 49 | 50 | export const HeadingType = (props: { children: Node }) => ( 51 | 61 | ); 62 | 63 | export const HeadingName = (props: { children: Node }) => ( 64 | 75 | ); 76 | 77 | const Whitespace = () => ' '; 78 | 79 | type PropTypeHeadingProps = { 80 | name: any, 81 | required: boolean, 82 | deprecated?: boolean, 83 | type: any, 84 | // This is probably giving up 85 | defaultValue?: any 86 | }; 87 | 88 | const PropTypeHeading = (props: PropTypeHeadingProps) => ( 89 | 90 | 91 | {props.name} 92 | 93 | 94 | {props.type} 95 | {props.defaultValue !== undefined && = {props.defaultValue}} 96 | {props.required && props.defaultValue === undefined ? ( 97 | required 98 | ) : null} 99 | {props.deprecated && deprecated} 100 | 101 | ); 102 | 103 | export default PropTypeHeading; 104 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/Prop/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx jsx */ 3 | import { jsx, css } from '@emotion/core'; 4 | import { Component, type ComponentType, type Node } from 'react'; 5 | import md from 'react-markings'; 6 | import { gridSize } from '../components/constants'; 7 | import PrettyPropType from '../PrettyConvert'; 8 | import PropTypeHeading from './Heading'; 9 | import type { CommonProps } from '../types'; 10 | 11 | const PropTypeWrapper = (props: { children: Node }) => ( 12 |
22 | ); 23 | 24 | type PropProps = CommonProps & { 25 | shapeComponent: ComponentType 26 | }; 27 | 28 | export default class Prop extends Component { 29 | static defaultProps = { 30 | shapeComponent: (props: CommonProps) => 31 | }; 32 | 33 | render() { 34 | let { shapeComponent: ShapeComponent, ...commonProps } = this.props; 35 | 36 | let { 37 | defaultValue, 38 | description, 39 | name, 40 | required, 41 | deprecated, 42 | type, 43 | components, 44 | componentDisplayName 45 | } = commonProps; 46 | 47 | return ( 48 | 51 | 58 | {description && ( 59 | 60 | {' '} 61 | {md([deprecated ? description.replace('@deprecated', '') : description])} 62 | 63 | )} 64 | 65 | 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/Props/Props.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import { render, screen } from '@testing-library/react'; 4 | import { extractReactTypes } from 'extract-react-types'; 5 | 6 | import Props from './'; 7 | 8 | const file = ` 9 | const MyComponent = (props: { simpleProp: string }) => null; 10 | 11 | export default MyComponent; 12 | `; 13 | 14 | test('should visualise props from extract-types-loader', () => { 15 | const { container, getByText } = render( 16 | 17 | ); 18 | 19 | const prop = getByText('simpleProp'); 20 | expect(prop).toBeTruthy(); 21 | expect(getByText('required')).toBeTruthy(); 22 | expect(getByText('string')).toBeTruthy(); 23 | }); 24 | 25 | test('simple facts about two props', () => { 26 | const file2 = ` 27 | const MyComponent = (props: { simpleProp: string, secondProp?: number }) => null; 28 | 29 | export default MyComponent; 30 | `; 31 | 32 | const { container, getByText } = render( 33 | 34 | ); 35 | 36 | expect(getByText('simpleProp')).toBeTruthy(); 37 | expect(getByText('secondProp')).toBeTruthy(); 38 | }); 39 | 40 | test('renders no children when passed on props', () => { 41 | const { container } = render(); 42 | expect(container.children).toHaveLength(0); 43 | }); 44 | 45 | describe('#typescript typeSystem', () => { 46 | test('should visualize generic props from extract-types-loader', () => { 47 | const code = ` 48 | import React from 'react'; 49 | 50 | export type OnSubmitHandler = ( 51 | values: FormData, 52 | callback?: (errors?: Record) => void, 53 | ) => void | Object | Promise; 54 | 55 | interface FormChildrenProps { 56 | ref: React.RefObject; 57 | onSubmit: ( 58 | event?: 59 | | React.FormEvent 60 | | React.SyntheticEvent, 61 | ) => void; 62 | onKeyDown: (event: React.KeyboardEvent) => void; 63 | } 64 | 65 | interface FormProps { 66 | children: (args: { 67 | formProps: FormChildrenProps; 68 | disabled: boolean; 69 | dirty: boolean; 70 | submitting: boolean; 71 | getValues: () => FormData; 72 | setFieldValue: (name: string, value: any) => void; 73 | reset: (initialValues?: FormData) => void; 74 | }) => ReactNode; 75 | onSubmit: OnSubmitHandler; 76 | /* When set the form and all fields will be disabled */ 77 | isDisabled?: boolean; 78 | } 79 | 80 | function Form = {}>( 81 | props: FormProps, 82 | ) {} 83 | 84 | Form.defaultProps = { 85 | isDisabled: true 86 | }; 87 | 88 | export default Form; 89 | `; 90 | 91 | const { container, getByText } = render( 92 | 93 | ); 94 | 95 | const childrenProp = document.querySelector('#Form-children'); 96 | expect(childrenProp).toBeTruthy(); 97 | expect(childrenProp.textContent).toContain('required'); 98 | expect(childrenProp.textContent).toContain('function'); 99 | 100 | const onSubmitProp = document.querySelector('#Form-onSubmit'); 101 | expect(onSubmitProp).toBeTruthy(); 102 | expect(onSubmitProp.textContent).toContain('required'); 103 | expect(onSubmitProp.textContent).toContain( 104 | '(values, callback) => undefined | Object | Promise' 105 | ); 106 | 107 | const isDisabledProp = document.querySelector('#Form-isDisabled'); 108 | expect(isDisabledProp).toBeTruthy(); 109 | expect(isDisabledProp.textContent).not.toContain('required'); 110 | expect(isDisabledProp.textContent).toContain('boolean'); 111 | expect(isDisabledProp.textContent).toContain('true'); 112 | expect(isDisabledProp.textContent).toContain( 113 | 'When set the form and all fields will be disabled' 114 | ); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/Props/Wrapper.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx jsx */ 3 | import { jsx, css } from '@emotion/core'; 4 | import { type Node } from 'react'; 5 | import { gridSize } from '../components/constants'; 6 | 7 | export const Wrapper = (props: { children: Node }) => ( 8 |
19 | ); 20 | 21 | export const H2 = ({ children, ...rest }: { children: Node }) => ( 22 |

28 | {children} 29 |

30 | ); 31 | 32 | const PropsWrapper = ({ children, heading }: { children: Node, heading?: string }) => ( 33 | 34 | {typeof heading === 'string' && heading.length === 0 ? null :

{heading || 'Props'}

} 35 | {children} 36 |
37 | ); 38 | 39 | export default PropsWrapper; 40 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/Props/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /* eslint-disable no-underscore-dangle */ 4 | 5 | import React, { Component, type ComponentType } from 'react'; 6 | 7 | import type { Components } from '../components'; 8 | import type { CommonProps } from '../types'; 9 | import PropsWrapper from './Wrapper'; 10 | import getPropTypes from '../getPropTypes'; 11 | import renderPropType from '../renderPropType'; 12 | import { getComponentDisplayName } from '../utils'; 13 | 14 | import Prop from '../Prop'; 15 | 16 | type Obj = { 17 | kind: 'object', 18 | members: Array 19 | }; 20 | 21 | type Gen = { 22 | kind: 'generic', 23 | value: any 24 | }; 25 | 26 | type Inter = { 27 | kind: 'intersection', 28 | types: Array 29 | }; 30 | 31 | type DynamicPropsProps = { 32 | components?: Components, 33 | heading?: string, 34 | shouldCollapseProps?: boolean, 35 | overrides?: { 36 | [string]: ComponentType 37 | }, 38 | props?: { 39 | component?: Obj | Inter 40 | }, 41 | component?: ComponentType 42 | }; 43 | 44 | const getProps = props => { 45 | if (props && props.component) { 46 | return getPropTypes(props.component); 47 | } 48 | return null; 49 | }; 50 | 51 | export default class Props extends Component { 52 | render() { 53 | let { props, heading, component, ...rest } = this.props; 54 | if (component) { 55 | /* $FlowFixMe the component prop is typed as a component because 56 | that's what people pass to Props and the ___types property shouldn't 57 | exist in the components types so we're just going to ignore this error */ 58 | if (component.___types) { 59 | props = { type: 'program', component: component.___types }; 60 | } else { 61 | /* eslint-disable-next-line no-console */ 62 | console.error( 63 | 'A component was passed to but it does not have types attached.\n' + 64 | 'babel-plugin-extract-react-types may not be correctly installed.\n' + 65 | ' will fallback to the props prop to display types.' 66 | ); 67 | } 68 | } 69 | let propTypes = getProps(props); 70 | if (!propTypes) return null; 71 | 72 | let renderProps = rest; 73 | // $FlowFixMe types are not exactly correct here ... sadly :/ 74 | const componentDisplayName = getComponentDisplayName(component || (props || {}).component); 75 | if (typeof componentDisplayName === 'string') { 76 | renderProps = { 77 | ...rest, 78 | componentDisplayName 79 | }; 80 | } 81 | 82 | return ( 83 | 84 | {propTypes.map(propType => renderPropType(propType, renderProps, Prop))} 85 | 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/PropsTable/PropRow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx jsx */ 3 | import { jsx } from '@emotion/core'; 4 | import { Fragment, Component, type ComponentType } from 'react'; 5 | import md from 'react-markings'; 6 | import PrettyPropType from '../PrettyConvert'; 7 | import { HeadingName, HeadingType, HeadingDefault } from '../Prop/Heading'; 8 | import type { CommonProps } from '../types'; 9 | 10 | type PropProps = CommonProps & { 11 | shapeComponent: ComponentType 12 | }; 13 | 14 | export default class Prop extends Component { 15 | static defaultProps = { 16 | shapeComponent: (props: CommonProps) => 17 | }; 18 | 19 | render() { 20 | let { shapeComponent: ShapeComponent, ...commonProps } = this.props; 21 | 22 | /** 23 | * TODO: extract + use `required` to display whether the prop is required. 24 | * Other layouts already do. 25 | * https://github.com/atlassian/extract-react-types/issues/192 26 | */ 27 | let { defaultValue, description, name, type, components, componentDisplayName } = commonProps; 28 | 29 | return ( 30 | 31 | 32 | td': { 37 | padding: '14px 8px' 38 | }, 39 | '&:target': { 40 | background: '#e9f2ff' 41 | } 42 | }} 43 | > 44 | 45 | {name} 46 | 47 | 53 | 54 | {type} 55 | 56 | 57 | 58 | 59 | 60 | {defaultValue !== undefined && {defaultValue}} 61 | 62 | {description && {md([description])}} 63 | 64 | 65 | 66 | 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/PropsTable/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /* eslint-disable no-underscore-dangle */ 4 | 5 | /** @jsx jsx */ 6 | import { jsx } from '@emotion/core'; 7 | import { Component, type ComponentType } from 'react'; 8 | 9 | import type { Components } from '../components'; 10 | import type { CommonProps } from '../types'; 11 | import PropsWrapper from '../Props/Wrapper'; 12 | import getPropTypes from '../getPropTypes'; 13 | import renderPropType from '../renderPropType'; 14 | import { getComponentDisplayName } from '../utils'; 15 | import PropRow from './PropRow'; 16 | 17 | type Obj = { 18 | kind: 'object', 19 | members: Array 20 | }; 21 | 22 | type Gen = { 23 | kind: 'generic', 24 | value: any 25 | }; 26 | 27 | type Inter = { 28 | kind: 'intersection', 29 | types: Array 30 | }; 31 | 32 | type DynamicPropsProps = { 33 | components?: Components, 34 | heading?: string, 35 | shouldCollapseProps?: boolean, 36 | overrides?: { 37 | [string]: ComponentType 38 | }, 39 | props?: { 40 | component?: Obj | Inter 41 | }, 42 | component?: ComponentType 43 | }; 44 | 45 | const getProps = props => { 46 | if (props && props.component) { 47 | return getPropTypes(props.component); 48 | } 49 | return null; 50 | }; 51 | 52 | export default class PropsTable extends Component { 53 | render() { 54 | let { props, heading, component, ...rest } = this.props; 55 | if (component) { 56 | /* $FlowFixMe the component prop is typed as a component because 57 | that's what people pass to Props and the ___types property shouldn't 58 | exist in the components types so we're just going to ignore this error */ 59 | if (component.___types) { 60 | props = { type: 'program', component: component.___types }; 61 | } else { 62 | /* eslint-disable-next-line no-console */ 63 | console.error( 64 | 'A component was passed to but it does not have types attached.\n' + 65 | 'babel-plugin-extract-react-types may not be correctly installed.\n' + 66 | ' will fallback to the props prop to display types.' 67 | ); 68 | } 69 | } 70 | let propTypes = getProps(props); 71 | if (!propTypes) return null; 72 | 73 | let renderProps = rest; 74 | // $FlowFixMe types are not exactly correct here ... sadly :/ 75 | const componentDisplayName = getComponentDisplayName(component || (props || {}).component); 76 | if (typeof componentDisplayName === 'string') { 77 | renderProps = { 78 | ...rest, 79 | componentDisplayName 80 | }; 81 | } 82 | 83 | return ( 84 | 85 | 86 | 87 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | {propTypes.map(propType => renderPropType(propType, renderProps, PropRow))} 99 |
NameTypeDefaultsDescription
100 |
101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/components/Button.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx jsx */ 3 | import { jsx, css } from '@emotion/core'; 4 | 5 | const Button = (props: { isCollapsed: boolean }) => ( 6 | 43 | ); 44 | 45 | export default Expander; 46 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/components/Indent.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx jsx */ 3 | import { jsx, css } from '@emotion/core'; 4 | import { type Node } from 'react'; 5 | 6 | export default function Indent(props: { children: Node }) { 7 | return ( 8 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/components/Outline.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx jsx */ 3 | import { jsx, css } from '@emotion/core'; 4 | import { type Node } from 'react'; 5 | import { colors } from './constants'; 6 | 7 | const Outline = (props: { children: Node }) => ( 8 | 15 | ); 16 | 17 | export default Outline; 18 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/components/Required.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx jsx */ 3 | import { jsx, css } from '@emotion/core'; 4 | import { type Node } from 'react'; 5 | import { colors } from './constants'; 6 | 7 | const Required = (props: { children: Node }) => ( 8 | 14 | ); 15 | 16 | export default Required; 17 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/components/Type.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx jsx */ 3 | import { jsx, css } from '@emotion/core'; 4 | import { type Node } from 'react'; 5 | import { borderRadius, colors } from './constants'; 6 | 7 | const baseType = css` 8 | background-color: ${colors.P50}; 9 | border-radius: ${borderRadius}px; 10 | color: ${colors.P500}; 11 | display: inline-block; 12 | margin: 2px 0; 13 | padding: 0 0.2em; 14 | `; 15 | 16 | const typeMeta = css` 17 | ${baseType} 18 | background-color: ${colors.N20}; 19 | color: ${colors.subtleText}; 20 | `; 21 | 22 | const stringType = css` 23 | ${baseType} 24 | background-color: ${colors.G50}; 25 | color: ${colors.G500}; 26 | `; 27 | 28 | const functionType = css``; 29 | 30 | const Type = (props: { children: Node }) => ; 31 | 32 | const TypeMeta = (props: { children: Node }) => ; 33 | 34 | const StringType = (props: { children: Node }) => ; 35 | 36 | const FunctionType = (props: { children: Node }) => ; 37 | 38 | export { TypeMeta, StringType, FunctionType }; 39 | export default Type; 40 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/components/constants.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export const gridSize: number = 8; 3 | export const borderRadius: number = 3; 4 | export const colors = { 5 | N20: '#F4F5F7', 6 | subtleText: '#5E6C84', 7 | R500: '#BF2600', 8 | N300: '#5E6C84', 9 | B50: '#DEEBFF', 10 | B500: '#0747A6', 11 | P50: '#EAE6FF', 12 | P500: '#403294', 13 | G50: '#E3FCEF', 14 | G500: '#006644', 15 | R75: '#FFBDAD', 16 | R50: '#FFEBE6', 17 | P300: '#6554C0', 18 | T300: '#00B8D9', 19 | R400: '#DE350B', 20 | N30: '#EBECF0', 21 | N800: '#172B4D' 22 | }; 23 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/components/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { ComponentType } from 'react'; 3 | import Indent from './Indent'; 4 | import Outline from './Outline'; 5 | import Required from './Required'; 6 | import Description from './Description'; 7 | import Button from './Button'; 8 | import Expander from './Expander'; 9 | import Type, { StringType, TypeMeta, FunctionType } from './Type'; 10 | 11 | const components = { 12 | Indent, 13 | Outline, 14 | Required, 15 | Type, 16 | StringType, 17 | TypeMeta, 18 | Description, 19 | Button, 20 | FunctionType, 21 | Expander 22 | }; 23 | 24 | export default components; 25 | 26 | export type Components = { 27 | Indent: ComponentType, 28 | Outline: ComponentType, 29 | Required: ComponentType, 30 | Type: ComponentType, 31 | StringType: ComponentType, 32 | TypeMeta: ComponentType, 33 | Description: ComponentType, 34 | Button: ComponentType, 35 | FunctionType: ComponentType, 36 | Expander: ComponentType 37 | }; 38 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/getPropTypes.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { reduceToObj, resolveFromGeneric } from 'kind2string'; 3 | import type { Kind } from './types'; 4 | 5 | const getPropTypes = (propTypesObj: Kind) => { 6 | let resolvedTypes = resolveFromGeneric(propTypesObj); 7 | let propTypes; 8 | if (resolvedTypes.kind === 'object') { 9 | propTypes = resolvedTypes.members; 10 | } else if (resolvedTypes.kind === 'intersection') { 11 | propTypes = resolvedTypes.types.reduce((acc, type) => [...acc, ...reduceToObj(type)], []); 12 | } else if (resolvedTypes.kind === 'generic') { 13 | const { value } = resolvedTypes; 14 | 15 | propTypes = value && value.members; 16 | } 17 | 18 | return propTypes; 19 | }; 20 | 21 | export default getPropTypes; 22 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/index.js: -------------------------------------------------------------------------------- 1 | export { default as Prop } from './Prop'; 2 | export { default } from './Props'; 3 | export { default as PropsTable } from './PropsTable'; 4 | export { default as components } from './components'; 5 | export { default as HybridLayout } from './HybridLayout'; 6 | export { default as LayoutRenderer } from './LayoutRenderer'; 7 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/renderPropType.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* eslint-disable no-param-reassign */ 3 | import React, { type ComponentType } from 'react'; 4 | import convert, { getKind, reduceToObj } from 'kind2string'; 5 | import allComponents from './components'; 6 | 7 | const IGNORE_COMMENTS_STARTING_WITH = ['eslint-disable', '@ts-']; 8 | const DEPRECATE_PROPS_THAT_CONTAIN = '@deprecated'; 9 | const HIDE_PROPS_THAT_CONTAIN = ['@internal', '@access private']; 10 | 11 | const shouldIgnoreComment = comment => { 12 | if (!comment) { 13 | return false; 14 | } 15 | 16 | for (let i = 0; i < IGNORE_COMMENTS_STARTING_WITH.length; i++) { 17 | const value = IGNORE_COMMENTS_STARTING_WITH[i]; 18 | if (comment.startsWith(value)) { 19 | return true; 20 | } 21 | } 22 | 23 | return false; 24 | }; 25 | 26 | const shouldHideProp = comment => { 27 | if (!comment) { 28 | return false; 29 | } 30 | 31 | for (let i = 0; i < HIDE_PROPS_THAT_CONTAIN.length; i++) { 32 | const value = HIDE_PROPS_THAT_CONTAIN[i]; 33 | if (comment.includes(value)) { 34 | return true; 35 | } 36 | } 37 | 38 | return false; 39 | }; 40 | 41 | const shouldDeprecateProp = comment => { 42 | if (!comment) { 43 | return false; 44 | } 45 | 46 | return comment.includes(DEPRECATE_PROPS_THAT_CONTAIN); 47 | }; 48 | 49 | const renderPropType = ( 50 | propType: any, 51 | { overrides = {}, shouldCollapseProps, components, componentDisplayName = {} }: any, 52 | PropComponent?: ComponentType 53 | ) => { 54 | components = { ...allComponents, ...components }; 55 | 56 | if (propType.kind === 'spread') { 57 | const furtherProps = reduceToObj(propType.value); 58 | if (Array.isArray(furtherProps) && furtherProps.length > 0) { 59 | /* Only render the spread contents if they are a non-empty value, otherwise render the 60 | * spread itself so we can see the spread of generics and other types that have not been 61 | * converted into an object */ 62 | return furtherProps.map(p => 63 | renderPropType(p, { overrides, shouldCollapseProps, components }) 64 | ); 65 | } 66 | } 67 | 68 | let description; 69 | if (propType.leadingComments) { 70 | description = propType.leadingComments 71 | .filter(({ value }) => !shouldIgnoreComment(value)) 72 | .reduce((acc, { value }) => acc.concat(`\n${value}`), ''); 73 | } 74 | 75 | if (!propType.value) { 76 | // eslint-disable-next-line no-console 77 | console.error( 78 | `Prop ${ 79 | propType.key 80 | } has no type; this usually indicates invalid propType or defaultProps config` 81 | ); 82 | 83 | return null; 84 | } 85 | 86 | if (shouldHideProp(description)) { 87 | return null; 88 | } 89 | 90 | const name = propType.kind === 'spread' ? '...' : convert(propType.key); 91 | const Component = overrides[name] || PropComponent; 92 | 93 | return ( 94 | 107 | ); 108 | }; 109 | 110 | export default renderPropType; 111 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Components } from './components'; 3 | 4 | export type Kind = any; 5 | 6 | export type CommonProps = { 7 | defaultValue?: string, 8 | description?: string, 9 | required: boolean, 10 | deprecated?: boolean, 11 | name: string, 12 | typeValue: Kind, 13 | type: string, 14 | shouldCollapse?: boolean, 15 | components: Components, 16 | // name of the component being rendered 17 | componentDisplayName: string 18 | }; 19 | -------------------------------------------------------------------------------- /packages/pretty-proptypes/src/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | // @flow strict-local 3 | 4 | export const getComponentDisplayName = ( 5 | data: 6 | | { 7 | ___displayName?: string 8 | } 9 | | { 10 | name?: { 11 | name?: string 12 | } 13 | } 14 | | void 15 | ): string | void => { 16 | // ensure displayName passes through, assuming it has been captured by Babel 17 | if (data && data.___displayName) { 18 | return String(data.___displayName); 19 | // or it might be obtainable from the converter logic in `packages/extract-react-types/src/converter` 20 | } else if (data && data.name && data.name.name) { 21 | return String(data.name.name); 22 | } 23 | 24 | return undefined; 25 | }; 26 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Extract React Types 2 | 3 | > One stop shop to document your react components. 4 | 5 |

6 | 7 |

8 | 9 | ## Getting started 🏁 10 | 11 | ### Step 1: Install 12 | 13 | `npm install --save-dev babel-plugin-extract-react-types pretty-proptypes` 14 | 15 | ### Step 2: Annotate your prop types 16 | 17 | ```typescript 18 | export interface AvatarPropTypes { 19 | /** Provides a url for avatars being used as a link. */ 20 | href?: string; 21 | /** Defines the size of the avatar */ 22 | size?: 'small' | 'medium' | 'large'; 23 | /** Name will be displayed in a tooltip */ 24 | name?: string; 25 | /** A url to load an image from (this can also be a base64 encoded image). */ 26 | src?: string; 27 | /** A `testId` prop is provided for specified elements, which is a unique string that appears as a data attribute `data-testid` in the rendered code, serving as a hook for automated tests */ 28 | testId?: string; 29 | } 30 | ``` 31 | 32 | ### Step 3: Output prop types 33 | 34 | pretty-proptypes can display props from two sources. 35 | 36 | **Option 1.** Using [babel-plugin-extract-react-types](./packages/babel-plugin-extract-react-types) and passing the component to Props 37 | 38 | `.babelrc` 39 | 40 | ```json 41 | { 42 | "plugins": ["babel-plugin-extract-react-types"] 43 | } 44 | ``` 45 | 46 | ```jsx 47 | import Props from 'pretty-proptypes'; 48 | import MyCoolComponent from '../MyCoolComponent'; 49 | 50 | ; 51 | ``` 52 | 53 | **Option 2.** Directly passing a component's props to Props with [extract-react-types-loader](./packages/extract-react-types-loader) or getting types from [extract-react-types](./packages/extract-react-types) and writing it to a file 54 | 55 | ```jsx 56 | import Props from 'pretty-proptypes'; 57 | 58 | ; 62 | ``` 63 | 64 | This analyses prop type definitions, and default props. It creates descriptions from comments before the type definitions, and will render markdown syntax using [react-markings](https://www.npmjs.com/package/react-markings). 65 | 66 | ## Packages 67 | 68 | 1. [extract-react-types](./packages/extract-react-types) _Extract Flow & TypeScript types from React Components_ 69 | 2. [extract-react-types-loader](./packages/extract-react-types-loader) _Webpack loader for extract-react-types_ 70 | 3. [babel-plugin-extract-react-types](./packages/babel-plugin-extract-react-types) _A Babel plugin to store the types of React components as a property on the component for documentation_ 71 | 4. [kind2string](./packages/kind2string) _kind2string is designed to take the data structures output by extract-react-types and convert it down to a (useful) string._ 72 | 5. [pretty-proptypes](./packages/pretty-proptypes) _PrettyPropTypes is designed to display the output of extract-react-types and display rich prop information for consumers._ 73 | 74 | ## Contribute 75 | 76 | Pull requests, issues and comments welcome - please read our [contributing guidelines](./contributing.md) and our [code of conduct](./code-of-conduct.md). 77 | 78 | [![Atlassian](https://raw.githubusercontent.com/atlassian-internal/oss-assets/master/banner-cheers-light.png)](https://atlassian.com) 79 | --------------------------------------------------------------------------------