Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 119 additions & 100 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand All @@ -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<React.HTMLAttributes<HTMLDivElement>> = ()...
```
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<React.HTMLAttributes<HTMLDivElement>> = ()...
```

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.
Expand All @@ -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<IColumnProps, {}> {
render() {
return <div>Test</div>;
}
render() {
return <div>Test</div>;
}
}
```

Expand All @@ -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 <div>Grid</div>;
const smaller = () => {
return;
};
return <div>Grid</div>;
};
```

Expand All @@ -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!**

Expand Down
26 changes: 26 additions & 0 deletions src/__tests__/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
21 changes: 13 additions & 8 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export interface ParserOptions {
componentNameResolver?: ComponentNameResolver;
shouldExtractLiteralValuesFromEnum?: boolean;
shouldRemoveUndefinedFromOptional?: boolean;
shouldExtractValuesFromUnion?: boolean;
savePropValueAsString?: boolean;
}

Expand Down Expand Up @@ -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);
Expand All @@ -222,6 +225,7 @@ export class Parser {
this.shouldRemoveUndefinedFromOptional = Boolean(
shouldRemoveUndefinedFromOptional
);
this.shouldExtractValuesFromUnion = Boolean(shouldExtractValuesFromUnion);
this.savePropValueAsString = Boolean(savePropValueAsString);
}

Expand Down Expand Up @@ -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)
}))
};
}

Expand Down