diff --git a/README.md b/README.md index 3d055407..c0a0fca3 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ It can be used with [React Styleguidist](https://github.com/styleguidist/react-s ## Installation -``` +```bash npm install --save-dev react-docgen-typescript ``` @@ -19,91 +19,107 @@ npm install --save-dev react-docgen-typescript Include following line in your `styleguide.config.js`: ```javascript -propsParser: require('react-docgen-typescript').withDefaultConfig([parserOptions]).parse +propsParser: require("react-docgen-typescript").withDefaultConfig([ + parserOptions, +]).parse; ``` or if you want to use custom tsconfig file ```javascript -propsParser: require('react-docgen-typescript').withCustomConfig('./tsconfig.json', [parserOptions]).parse +propsParser: require("react-docgen-typescript").withCustomConfig( + "./tsconfig.json", + [parserOptions] +).parse; ``` ### parserOptions -- propFilter: +#### `propFilter` - ```typescript - { - skipPropsWithName?: string[] | string; - skipPropsWithoutDoc?: boolean; - } - ``` - - or - - ```typescript - (prop: PropItem, component: Component) => boolean - ``` - - In case you do not want to print out all the HTML props, because your component is typed like this: - ```typescript - const MyComponent: React.FC> = ()... - ``` - you can use this workaround inside `propFilter`: - ```typescript - if (prop.parent) { - return !prop.parent.fileName.includes('node_modules') - } +```typescript +{ + skipPropsWithName?: string[] | string; + skipPropsWithoutDoc?: boolean; +} +``` + +or + +```typescript +(prop: PropItem, component: Component) => boolean; +``` + +In case you do not want to print out all the HTML props, because your component is typed like this: + +```typescript +const MyComponent: React.FC> = ()... +``` + +you can use this workaround inside `propFilter`: + +```typescript +if (prop.parent) { + return !prop.parent.fileName.includes("node_modules"); +} + +return true; +``` + +Note: `children` without a doc comment will not be documented. + +#### `componentNameResolver` - return true - ``` - - Note: `children` without a doc comment will not be documented. - -- componentNameResolver: - - ```typescript - (exp: ts.Symbol, source: ts.SourceFile) => string | undefined | null | false - ``` - - If a string is returned, then the component will use that name. Else it will fallback to the default logic of parser. - -- shouldExtractLiteralValuesFromEnum: boolean - - If set to true, string enums and unions will be converted to docgen enum format. Useful if you use Storybook and want to generate knobs automatically using [addon-smart-knobs](https://github.com/storybookjs/addon-smart-knobs). - -- shouldRemoveUndefinedFromOptional: boolean - - If set to true, types that are optional will not display " | undefined" in the type. - -- savePropValueAsString: boolean - - If set to true, defaultValue to props will be string. - Example: - ```javascript - Component.defaultProps = { - counter: 123, - disabled: false - } - ``` - Will return: - ```javascript - counter: { - defaultValue: '123', - required: true, - type: 'number' - }, - disabled: { - defaultValue: 'false', - required: true, - type: 'boolean' - } - ``` +```typescript +(exp: ts.Symbol, source: ts.SourceFile) => string | undefined | null | false; +``` + +If a string is returned, then the component will use that name. Else it will fallback to the default logic of parser. + +#### `shouldExtractLiteralValuesFromEnum`: boolean + +If set to true, string enums and unions will be converted to docgen enum format. Useful if you use Storybook and want to generate knobs automatically using [addon-smart-knobs](https://github.com/storybookjs/addon-smart-knobs). + +#### `shouldExtractValuesFromUnion`: boolean + +If set to true, every unions will be converted to docgen enum format. + +#### `shouldRemoveUndefinedFromOptional`: boolean + +If set to true, types that are optional will not display " | undefined" in the type. + +#### `savePropValueAsString`: boolean + +If set to true, defaultValue to props will be string. +Example: + +```javascript +Component.defaultProps = { + counter: 123, + disabled: false, +}; +``` + +Will return: + +```javascript + counter: { + defaultValue: '123', + required: true, + type: 'number' + }, + disabled: { + defaultValue: 'false', + required: true, + type: 'boolean' + } +``` **Styled components example:** ```typescript -componentNameResolver: (exp, source) => exp.getName() === 'StyledComponentClass' && getDefaultExportForFile(source); +componentNameResolver: (exp, source) => + exp.getName() === "StyledComponentClass" && getDefaultExportForFile(source); ``` > The parser exports `getDefaultExportForFile` helper through its public API. @@ -117,32 +133,32 @@ In the example folder you can see React Styleguidist integration. The component [`Column.tsx`](./examples/react-styleguidist-example/components/Column.tsx) ```javascript -import * as React from 'react'; -import { Component } from 'react'; +import * as React from "react"; +import { Component } from "react"; /** * Column properties. */ export interface IColumnProps { - /** prop1 description */ - prop1?: string; - /** prop2 description */ - prop2: number; - /** - * prop3 description - */ - prop3: () => void; - /** prop4 description */ - prop4: 'option1' | 'option2' | 'option3'; + /** prop1 description */ + prop1?: string; + /** prop2 description */ + prop2: number; + /** + * prop3 description + */ + prop3: () => void; + /** prop4 description */ + prop4: "option1" | "option2" | "option3"; } /** * Form column. */ export class Column extends Component { - render() { - return
Test
; - } + render() { + return
Test
; + } } ``` @@ -153,30 +169,32 @@ Will generate the following stylesheet: The functional component [`Grid.tsx`](./examples/react-styleguidist-example/components/Grid.tsx) ```javascript -import * as React from 'react'; +import * as React from "react"; /** * Grid properties. */ export interface IGridProps { - /** prop1 description */ - prop1?: string; - /** prop2 description */ - prop2: number; - /** - * prop3 description - */ - prop3: () => void; - /** Working grid description */ - prop4: 'option1' | 'option2' | 'option3'; + /** prop1 description */ + prop1?: string; + /** prop2 description */ + prop2: number; + /** + * prop3 description + */ + prop3: () => void; + /** Working grid description */ + prop4: "option1" | "option2" | "option3"; } /** * Form Grid. */ export const Grid = (props: IGridProps) => { - const smaller = () => {return;}; - return
Grid
; + const smaller = () => { + return; + }; + return
Grid
; }; ``` @@ -185,13 +203,14 @@ Will generate the following stylesheet: ![Stylesheet example](https://github.com/styleguidist/react-docgen-typescript/raw/master/stylesheet-example-grid.png "Stylesheet example") ## Contributions + The typescript is pretty complex and there are many different ways how to define components and their props so it's realy hard to support all these use cases. That means only one thing, contributions are highly welcome. Just keep in mind that each PR should also include tests for the part it's fixing. -Thanks to all contributors without their help there wouldn't be a single +Thanks to all contributors without their help there wouldn't be a single bug fixed or feature implemented. Check the [**contributors**](https://github.com/styleguidist/react-docgen-typescript/graphs/contributors) tab to find out more. All those people supported this project. **THANK YOU!** diff --git a/src/__tests__/parser.ts b/src/__tests__/parser.ts index 86d2f587..aa1f9985 100644 --- a/src/__tests__/parser.ts +++ b/src/__tests__/parser.ts @@ -921,6 +921,32 @@ describe('parser', () => { }); }); + describe('Extracting values from unions', () => { + it('extracts all values from union', () => { + check( + 'ExtractLiteralValuesFromEnum', + { + ExtractLiteralValuesFromEnum: { + sampleComplexUnion: { + raw: 'number | "string1" | "string2"', + type: 'enum', + value: [ + { value: 'number' }, + { value: '"string1"' }, + { value: '"string2"' } + ] + } + } + }, + false, + null, + { + shouldExtractValuesFromUnion: true + } + ); + }); + }); + describe('Returning not string default props ', () => { it('returns not string defaultProps', () => { check( diff --git a/src/parser.ts b/src/parser.ts index a51b6fe7..bdd43564 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -80,6 +80,7 @@ export interface ParserOptions { componentNameResolver?: ComponentNameResolver; shouldExtractLiteralValuesFromEnum?: boolean; shouldRemoveUndefinedFromOptional?: boolean; + shouldExtractValuesFromUnion?: boolean; savePropValueAsString?: boolean; } @@ -206,13 +207,15 @@ export class Parser { private propFilter: PropFilter; private shouldRemoveUndefinedFromOptional: boolean; private shouldExtractLiteralValuesFromEnum: boolean; + private shouldExtractValuesFromUnion: boolean; private savePropValueAsString: boolean; constructor(program: ts.Program, opts: ParserOptions) { const { savePropValueAsString, shouldExtractLiteralValuesFromEnum, - shouldRemoveUndefinedFromOptional + shouldRemoveUndefinedFromOptional, + shouldExtractValuesFromUnion } = opts; this.checker = program.getTypeChecker(); this.propFilter = buildFilter(opts); @@ -222,6 +225,7 @@ export class Parser { this.shouldRemoveUndefinedFromOptional = Boolean( shouldRemoveUndefinedFromOptional ); + this.shouldExtractValuesFromUnion = Boolean(shouldExtractValuesFromUnion); this.savePropValueAsString = Boolean(savePropValueAsString); } @@ -493,18 +497,19 @@ export class Parser { let propTypeString = this.checker.typeToString(propType); if ( - this.shouldExtractLiteralValuesFromEnum && propType.isUnion() && - propType.types.every(type => type.isStringLiteral()) + (this.shouldExtractValuesFromUnion || + (this.shouldExtractLiteralValuesFromEnum && + propType.types.every(type => type.isStringLiteral()))) ) { return { name: 'enum', raw: propTypeString, - value: propType.types - .map(type => ({ - value: type.isStringLiteral() ? `"${type.value}"` : undefined - })) - .filter(Boolean) + value: propType.types.map(type => ({ + value: type.isStringLiteral() + ? `"${type.value}"` + : this.checker.typeToString(type) + })) }; }