@@ -6,11 +6,23 @@ import * as t from './types';
6
6
*/
7
7
interface ParserOptions {
8
8
/**
9
- * Called before a PropType is added to a component
10
- * @return true to include the PropType, false to skip it
11
- * @default () => true
9
+ * Called before a PropType is added to a component/object
10
+ * @return true to include the PropType, false to skip it, or undefined to
11
+ * use the default behaviour
12
+ * @default name !== 'ref'
12
13
*/
13
- shouldInclude ?: ( data : { name : string } ) => boolean ;
14
+ shouldInclude : ( data : { name : string ; depth : number } ) => boolean | undefined ;
15
+ /**
16
+ * Called before the shape of an object is resolved
17
+ * @return true to resolve the shape of the object, false to just use a object, or undefined to
18
+ * use the default behaviour
19
+ * @default propertyCount <= 50 && depth <= 3
20
+ */
21
+ shouldResolveObject : ( data : {
22
+ name : string ;
23
+ propertyCount : number ;
24
+ depth : number ;
25
+ } ) => boolean | undefined ;
14
26
}
15
27
16
28
/**
@@ -32,7 +44,7 @@ export function createProgram(files: string[], options: ts.CompilerOptions) {
32
44
export function parseFile (
33
45
filePath : string ,
34
46
options : ts . CompilerOptions ,
35
- parserOptions : ParserOptions = { } ,
47
+ parserOptions : Partial < ParserOptions > = { } ,
36
48
) {
37
49
const program = ts . createProgram ( [ filePath ] , options ) ;
38
50
return parseFromProgram ( filePath , program , parserOptions ) ;
@@ -47,9 +59,29 @@ export function parseFile(
47
59
export function parseFromProgram (
48
60
filePath : string ,
49
61
program : ts . Program ,
50
- parserOptions : ParserOptions = { } ,
62
+ parserOptions : Partial < ParserOptions > = { } ,
51
63
) {
52
- const { shouldInclude = ( ) => true } = parserOptions ;
64
+ const shouldInclude : ParserOptions [ 'shouldInclude' ] = data => {
65
+ if ( parserOptions . shouldInclude ) {
66
+ const result = parserOptions . shouldInclude ( data ) ;
67
+ if ( result !== undefined ) {
68
+ return result ;
69
+ }
70
+ }
71
+
72
+ return data . name !== 'ref' ;
73
+ } ;
74
+
75
+ const shouldResolveObject : ParserOptions [ 'shouldResolveObject' ] = data => {
76
+ if ( parserOptions . shouldResolveObject ) {
77
+ const result = parserOptions . shouldResolveObject ( data ) ;
78
+ if ( result !== undefined ) {
79
+ return result ;
80
+ }
81
+ }
82
+
83
+ return data . propertyCount <= 50 && data . depth <= 3 ;
84
+ } ;
53
85
54
86
const checker = program . getTypeChecker ( ) ;
55
87
const sourceFile = program . getSourceFile ( filePath ) ;
@@ -204,7 +236,7 @@ export function parseFromProgram(
204
236
function parsePropsType ( name : string , type : ts . Type ) {
205
237
const properties = type
206
238
. getProperties ( )
207
- . filter ( symbol => shouldInclude ( { name : symbol . getName ( ) } ) ) ;
239
+ . filter ( symbol => shouldInclude ( { name : symbol . getName ( ) , depth : 1 } ) ) ;
208
240
if ( properties . length === 0 ) {
209
241
return ;
210
242
}
@@ -231,15 +263,17 @@ export function parseFromProgram(
231
263
return t . propTypeNode (
232
264
symbol . getName ( ) ,
233
265
getDocumentation ( symbol ) ,
234
- // If the typeStack contains type.id we're dealing with a type that references itself.
235
- // To prevent getting stuck in an infinite loop we just set it to an objectNode
236
- typeStack . includes ( ( type as any ) . id )
237
- ? t . objectNode ( )
238
- : checkType ( type , [ ...typeStack , ( type as any ) . id ] ) ,
266
+ checkType ( type , typeStack , symbol . getName ( ) ) ,
239
267
) ;
240
268
}
241
269
242
- function checkType ( type : ts . Type , typeStack : number [ ] ) : t . Node {
270
+ function checkType ( type : ts . Type , typeStack : number [ ] , name : string ) : t . Node {
271
+ // If the typeStack contains type.id we're dealing with an object that references itself.
272
+ // To prevent getting stuck in an infinite loop we just set it to an objectNode
273
+ if ( typeStack . includes ( ( type as any ) . id ) ) {
274
+ return t . objectNode ( ) ;
275
+ }
276
+
243
277
{
244
278
const typeNode = type as any ;
245
279
@@ -260,11 +294,11 @@ export function parseFromProgram(
260
294
if ( checker . isArrayType ( type ) ) {
261
295
// @ts -ignore - Private method
262
296
const arrayType : ts . Type = checker . getElementTypeOfArrayType ( type ) ;
263
- return t . arrayNode ( checkType ( arrayType , typeStack ) ) ;
297
+ return t . arrayNode ( checkType ( arrayType , typeStack , name ) ) ;
264
298
}
265
299
266
300
if ( type . isUnion ( ) ) {
267
- return t . unionNode ( type . types . map ( x => checkType ( x , typeStack ) ) ) ;
301
+ return t . unionNode ( type . types . map ( x => checkType ( x , typeStack , name ) ) ) ;
268
302
}
269
303
270
304
if ( type . flags & ts . TypeFlags . String ) {
@@ -302,8 +336,24 @@ export function parseFromProgram(
302
336
}
303
337
304
338
// Object-like type
305
- if ( type . getProperties ( ) . length ) {
306
- return t . objectNode ( ) ;
339
+ {
340
+ const properties = type . getProperties ( ) ;
341
+ if ( properties . length ) {
342
+ if (
343
+ shouldResolveObject ( { name, propertyCount : properties . length , depth : typeStack . length } )
344
+ ) {
345
+ const filtered = properties . filter ( symbol =>
346
+ shouldInclude ( { name : symbol . getName ( ) , depth : typeStack . length + 1 } ) ,
347
+ ) ;
348
+ if ( filtered . length > 0 ) {
349
+ return t . interfaceNode (
350
+ filtered . map ( x => checkSymbol ( x , [ ...typeStack , ( type as any ) . id ] ) ) ,
351
+ ) ;
352
+ }
353
+ }
354
+
355
+ return t . objectNode ( ) ;
356
+ }
307
357
}
308
358
309
359
// Object without properties or object keyword
0 commit comments