diff --git a/src/__tests__/buildFilter.ts b/src/__tests__/buildFilter.ts index c324b556..4c66a260 100644 --- a/src/__tests__/buildFilter.ts +++ b/src/__tests__/buildFilter.ts @@ -1,18 +1,20 @@ import { assert, expect } from 'chai'; import { buildFilter } from '../buildFilter'; -import { ParserOptions, PropItem, Props } from '../parser'; +import { ParentType, ParserOptions, PropItem } from '../parser'; function createProp( name: string, required: boolean = false, defaultValue?: any, description: string = '', - type = { name: 'string' } + type = { name: 'string' }, + parent?: ParentType ): PropItem { return { defaultValue, description, name, + parent, required, type }; @@ -165,5 +167,56 @@ describe('buildFilter', () => { ) ).to.eql([prop1]); }); + + it('should be possible to filter by interface in which prop was declared.', () => { + const stringType = { name: 'string' }; + const htmlAttributesInterface = { + fileName: 'node_modules/@types/react/index.d.ts', + name: 'HTMLAttributes' + }; + const excludedType = { name: 'ExcludedType', fileName: 'src/types.ts' }; + const prop1 = createProp( + 'foo', + false, + undefined, + 'foo description', + stringType + ); + const prop2 = createProp( + 'bar', + false, + undefined, + 'bar description', + stringType, + excludedType + ); + const prop3 = createProp( + 'onFocus', + false, + undefined, + 'onFocus description', + stringType, + htmlAttributesInterface + ); + const opts: ParserOptions = { + propFilter: (prop, component) => { + if (prop.parent == null) { + return true; + } + + if (prop.parent.fileName.indexOf('@types/react/index.d.ts') > -1) { + return false; + } + + return prop.parent.name !== 'ExcludedType'; + } + }; + const filterFn = buildFilter(opts); + expect( + [prop1, prop2, prop3].filter(prop => + filterFn(prop, { name: 'SomeComponent' }) + ) + ).to.eql([prop1]); + }); }); }); diff --git a/src/__tests__/parser.ts b/src/__tests__/parser.ts index 9a125fe1..bea0cfd6 100644 --- a/src/__tests__/parser.ts +++ b/src/__tests__/parser.ts @@ -630,6 +630,32 @@ describe('parser', () => { { propFilter } ); }); + + it('should allow filtering by parent interface', () => { + const propFilter: PropFilter = (prop, component) => { + if (prop.parent == null) { + return true; + } + + return ( + prop.parent.fileName.indexOf('@types/react') < 0 && + prop.parent.name !== 'HTMLAttributes' + ); + }; + + check( + 'ColumnWithHtmlAttributes', + { + Column: { + prop1: { type: 'string', required: false }, + prop2: { type: 'number' } + } + }, + true, + undefined, + { propFilter } + ); + }); }); describe('skipPropsWithName', () => { diff --git a/src/parser.ts b/src/parser.ts index 151a4aee..4859fdca 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -22,6 +22,7 @@ export interface PropItem { type: PropItemType; description: string; defaultValue: any; + parent?: ParentType; } export interface Component { @@ -33,6 +34,11 @@ export interface PropItemType { value?: any; } +export interface ParentType { + name: string; + fileName: string; +} + export type PropFilter = (props: PropItem, component: Component) => boolean; export interface ParserOptions { @@ -329,10 +335,13 @@ class Parser { defaultValue = { value: jsDocComment.tags.default }; } + const parent = getParentType(prop); + result[propName] = { defaultValue, description: jsDocComment.fullComment, name: propName, + parent, required: !isOptional, type: { name: propTypeString } }; @@ -689,3 +698,35 @@ function computeComponentName(exp: ts.Symbol, source: ts.SourceFile) { return exportName; } } + +function getParentType(prop: ts.Symbol): ParentType | undefined { + const decalarations = prop.getDeclarations(); + + if (decalarations == null || decalarations.length === 0) { + return undefined; + } + + // Props can be declared only in one place + const { parent } = decalarations[0]; + + if (!isInterfaceOrTypeAliasDeclaration(parent)) { + return undefined; + } + + const parentName = parent.name.text; + const { fileName } = parent.getSourceFile(); + + return { + fileName, + name: parentName + }; +} + +function isInterfaceOrTypeAliasDeclaration( + node: ts.Node +): node is ts.InterfaceDeclaration | ts.TypeAliasDeclaration { + return ( + node.kind === ts.SyntaxKind.InterfaceDeclaration || + node.kind === ts.SyntaxKind.TypeAliasDeclaration + ); +}