Skip to content

Commit 81f1a82

Browse files
committed
feat(parser): objects / shapes
1 parent cab1176 commit 81f1a82

File tree

1 file changed

+68
-18
lines changed

1 file changed

+68
-18
lines changed

Diff for: src/parser.ts

+68-18
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,23 @@ import * as t from './types';
66
*/
77
interface ParserOptions {
88
/**
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'
1213
*/
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;
1426
}
1527

1628
/**
@@ -32,7 +44,7 @@ export function createProgram(files: string[], options: ts.CompilerOptions) {
3244
export function parseFile(
3345
filePath: string,
3446
options: ts.CompilerOptions,
35-
parserOptions: ParserOptions = {},
47+
parserOptions: Partial<ParserOptions> = {},
3648
) {
3749
const program = ts.createProgram([filePath], options);
3850
return parseFromProgram(filePath, program, parserOptions);
@@ -47,9 +59,29 @@ export function parseFile(
4759
export function parseFromProgram(
4860
filePath: string,
4961
program: ts.Program,
50-
parserOptions: ParserOptions = {},
62+
parserOptions: Partial<ParserOptions> = {},
5163
) {
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+
};
5385

5486
const checker = program.getTypeChecker();
5587
const sourceFile = program.getSourceFile(filePath);
@@ -204,7 +236,7 @@ export function parseFromProgram(
204236
function parsePropsType(name: string, type: ts.Type) {
205237
const properties = type
206238
.getProperties()
207-
.filter(symbol => shouldInclude({ name: symbol.getName() }));
239+
.filter(symbol => shouldInclude({ name: symbol.getName(), depth: 1 }));
208240
if (properties.length === 0) {
209241
return;
210242
}
@@ -231,15 +263,17 @@ export function parseFromProgram(
231263
return t.propTypeNode(
232264
symbol.getName(),
233265
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()),
239267
);
240268
}
241269

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+
243277
{
244278
const typeNode = type as any;
245279

@@ -260,11 +294,11 @@ export function parseFromProgram(
260294
if (checker.isArrayType(type)) {
261295
// @ts-ignore - Private method
262296
const arrayType: ts.Type = checker.getElementTypeOfArrayType(type);
263-
return t.arrayNode(checkType(arrayType, typeStack));
297+
return t.arrayNode(checkType(arrayType, typeStack, name));
264298
}
265299

266300
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)));
268302
}
269303

270304
if (type.flags & ts.TypeFlags.String) {
@@ -302,8 +336,24 @@ export function parseFromProgram(
302336
}
303337

304338
// 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+
}
307357
}
308358

309359
// Object without properties or object keyword

0 commit comments

Comments
 (0)