Skip to content

Commit 816d358

Browse files
bebrawpvasek
authored andcommitted
chore: Refactor component name logic to a generic interface
1 parent 5371c6d commit 816d358

File tree

3 files changed

+58
-15
lines changed

3 files changed

+58
-15
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,22 @@ propsParser: require('react-docgen-typescript').withCustomConfig('./tsconfig.jso
4747

4848
Note: `children` without a doc comment will not be documented.
4949

50+
- componentNameResolver:
51+
52+
```typescript
53+
(exp: ts.Symbol, source: ts.SourceFile) => string | undefined | null | false
54+
```
55+
56+
If a string is returned, then the component will use that name. Else it will fallback to the default logic of parser.
57+
58+
**Styled components example:**
59+
60+
```typescript
61+
componentNameResolver: (exp, source) => exp.getName() === 'StyledComponentClass' && getDefaultExportForFile(source);
62+
```
63+
64+
> The parser exports `getDefaultExportForFile` helper through its public API.
65+
5066
## Example
5167

5268
In the example folder you can see React Styleguidist integration.

src/__tests__/parser.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as fs from 'fs';
33
import * as path from 'path';
44
import * as ts from 'typescript';
55
import {
6+
getDefaultExportForFile,
67
parse,
78
PropFilter,
89
withCustomConfig,
@@ -532,13 +533,6 @@ describe('parser', () => {
532533
assert.equal(parsed.displayName, 'StatelessDisplayNameDefaultExport');
533534
});
534535

535-
it('should be taken from filename for styled components', () => {
536-
const [parsed] = parse(
537-
fixturePath('StatelessDisplayNameStyledComponent')
538-
);
539-
assert.equal(parsed.displayName, 'StatelessDisplayNameStyledComponent');
540-
});
541-
542536
it('should be taken from stateful component `displayName` property (using default export)', () => {
543537
const [parsed] = parse(fixturePath('StatefulDisplayNameDefaultExport'));
544538
assert.equal(parsed.displayName, 'StatefulDisplayNameDefaultExport');
@@ -799,4 +793,29 @@ describe('parser', () => {
799793
assert.isTrue(programProviderInvoked);
800794
});
801795
});
796+
797+
describe('componentNameResolver', () => {
798+
it('should override default behavior', () => {
799+
const [parsed] = parse(
800+
fixturePath('StatelessDisplayNameStyledComponent'),
801+
{
802+
componentNameResolver: (exp, source) => (
803+
exp.getName() === 'StyledComponentClass' &&
804+
getDefaultExportForFile(source)
805+
)
806+
}
807+
);
808+
assert.equal(parsed.displayName, 'StatelessDisplayNameStyledComponent');
809+
});
810+
811+
it('should fallback to default behavior without a match', () => {
812+
const [parsed] = parse(
813+
fixturePath('StatelessDisplayNameStyledComponent'),
814+
{
815+
componentNameResolver: () => false
816+
}
817+
);
818+
assert.equal(parsed.displayName, 'StatelessDisplayNameStyledComponent');
819+
});
820+
});
802821
});

src/parser.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@ export interface ParentType {
4141

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

44+
export type ComponentNameResolver = (exp: ts.Symbol, source: ts.SourceFile) => string | undefined | null | false;
45+
4446
export interface ParserOptions {
4547
propFilter?: StaticPropFilter | PropFilter;
48+
componentNameResolver?: ComponentNameResolver;
4649
}
4750

4851
export interface StaticPropFilter {
@@ -166,7 +169,8 @@ class Parser {
166169

167170
public getComponentInfo(
168171
exp: ts.Symbol,
169-
source: ts.SourceFile
172+
source: ts.SourceFile,
173+
componentNameResolver: ComponentNameResolver = () => undefined
170174
): ComponentDoc | null {
171175
if (!!exp.declarations && exp.declarations.length === 0) {
172176
return null;
@@ -204,7 +208,8 @@ class Parser {
204208
this.extractPropsFromTypeIfStatefulComponent(type);
205209

206210
if (propsType) {
207-
const componentName = computeComponentName(exp, source);
211+
const resolvedComponentName = componentNameResolver(exp, source);
212+
const componentName = resolvedComponentName || computeComponentName(exp, source);
208213
const defaultProps = this.extractDefaultPropsFromComponent(exp, source);
209214
const props = this.getPropsInfo(propsType, defaultProps);
210215

@@ -665,18 +670,21 @@ function computeComponentName(exp: ts.Symbol, source: ts.SourceFile) {
665670
if (
666671
exportName === 'default' ||
667672
exportName === '__function' ||
668-
exportName === 'StyledComponentClass' ||
669673
exportName === 'StatelessComponent'
670674
) {
671-
// Default export for a file: named after file
675+
return getDefaultExportForFile(source);
676+
} else {
677+
return exportName;
678+
}
679+
}
680+
681+
// Default export for a file: named after file
682+
export function getDefaultExportForFile(source: ts.SourceFile) {
672683
const name = path.basename(source.fileName, path.extname(source.fileName));
673684

674685
return name === 'index'
675686
? path.basename(path.dirname(source.fileName))
676687
: name;
677-
} else {
678-
return exportName;
679-
}
680688
}
681689

682690
function getParentType(prop: ts.Symbol): ParentType | undefined {
@@ -746,7 +754,7 @@ function parseWithProgramProvider(
746754
docs,
747755
checker
748756
.getExportsOfModule(moduleSymbol)
749-
.map(exp => parser.getComponentInfo(exp, sourceFile))
757+
.map(exp => parser.getComponentInfo(exp, sourceFile, parserOpts.componentNameResolver))
750758
.filter((comp): comp is ComponentDoc => comp !== null)
751759
.filter((comp, index, comps) =>
752760
comps

0 commit comments

Comments
 (0)