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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@ propsParser: require('react-docgen-typescript').withCustomConfig('./tsconfig.jso

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.

**Styled components example:**

```typescript
componentNameResolver: (exp, source) => exp.getName() === 'StyledComponentClass' && getDefaultExportForFile(source);
```

> The parser exports `getDefaultExportForFile` helper through its public API.

## Example

In the example folder you can see React Styleguidist integration.
Expand Down
15 changes: 15 additions & 0 deletions src/__tests__/data/StatelessDisplayNameStyledComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as React from 'react';

export interface StyledComponentClassProps {
/** myProp description */
myProp: string;
}

/** Stateless description */
const StyledComponentClass: React.SFC<StyledComponentClassProps> = props => (
<div>My Property = {props.myProp}</div>
);

// If we had a styled component, it would emit StyledComponentClass
// by default.
export default StyledComponentClass;
26 changes: 26 additions & 0 deletions src/__tests__/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {
getDefaultExportForFile,
parse,
PropFilter,
withCustomConfig,
Expand Down Expand Up @@ -792,4 +793,29 @@ describe('parser', () => {
assert.isTrue(programProviderInvoked);
});
});

describe('componentNameResolver', () => {
it('should override default behavior', () => {
const [parsed] = parse(
fixturePath('StatelessDisplayNameStyledComponent'),
{
componentNameResolver: (exp, source) => (
exp.getName() === 'StyledComponentClass' &&
getDefaultExportForFile(source)
)
}
);
assert.equal(parsed.displayName, 'StatelessDisplayNameStyledComponent');
});

it('should fallback to default behavior without a match', () => {
const [parsed] = parse(
fixturePath('StatelessDisplayNameStyledComponent'),
{
componentNameResolver: () => false
}
);
assert.equal(parsed.displayName, 'StatelessDisplayNameStyledComponent');
});
});
});
23 changes: 16 additions & 7 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@ export interface ParentType {

export type PropFilter = (props: PropItem, component: Component) => boolean;

export type ComponentNameResolver = (exp: ts.Symbol, source: ts.SourceFile) => string | undefined | null | false;

export interface ParserOptions {
propFilter?: StaticPropFilter | PropFilter;
componentNameResolver?: ComponentNameResolver;
}

export interface StaticPropFilter {
Expand Down Expand Up @@ -166,7 +169,8 @@ class Parser {

public getComponentInfo(
exp: ts.Symbol,
source: ts.SourceFile
source: ts.SourceFile,
componentNameResolver: ComponentNameResolver = () => undefined
): ComponentDoc | null {
if (!!exp.declarations && exp.declarations.length === 0) {
return null;
Expand Down Expand Up @@ -204,7 +208,8 @@ class Parser {
this.extractPropsFromTypeIfStatefulComponent(type);

if (propsType) {
const componentName = computeComponentName(exp, source);
const resolvedComponentName = componentNameResolver(exp, source);
const componentName = resolvedComponentName || computeComponentName(exp, source);
const defaultProps = this.extractDefaultPropsFromComponent(exp, source);
const props = this.getPropsInfo(propsType, defaultProps);

Expand Down Expand Up @@ -667,15 +672,19 @@ function computeComponentName(exp: ts.Symbol, source: ts.SourceFile) {
exportName === '__function' ||
exportName === 'StatelessComponent'
) {
// Default export for a file: named after file
return getDefaultExportForFile(source);
} else {
return exportName;
}
}

// Default export for a file: named after file
export function getDefaultExportForFile(source: ts.SourceFile) {
const name = path.basename(source.fileName, path.extname(source.fileName));

return name === 'index'
? path.basename(path.dirname(source.fileName))
: name;
} else {
return exportName;
}
}

function getParentType(prop: ts.Symbol): ParentType | undefined {
Expand Down Expand Up @@ -745,7 +754,7 @@ function parseWithProgramProvider(
docs,
checker
.getExportsOfModule(moduleSymbol)
.map(exp => parser.getComponentInfo(exp, sourceFile))
.map(exp => parser.getComponentInfo(exp, sourceFile, parserOpts.componentNameResolver))
.filter((comp): comp is ComponentDoc => comp !== null)
.filter((comp, index, comps) =>
comps
Expand Down