Skip to content

Commit 2e858cb

Browse files
authored
Feature: Object pattern parameter parsing (#160)
* Feature: Object pattern parameter parsing * Fix import lint
1 parent 6121478 commit 2e858cb

File tree

3 files changed

+183
-17
lines changed

3 files changed

+183
-17
lines changed

packages/webdoc-parser/src/symbols-babel/extract-metadata.js

Lines changed: 123 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import {
66
type BabelNodeExpression,
77
type BabelNodeFlow,
88
type BabelNodeMemberExpression,
9+
type BabelNodeObjectExpression,
10+
type BabelNodeObjectTypeAnnotation,
911
type BabelNodeQualifiedName,
12+
type BabelNodeTSLiteralType,
1013
type BabelNodeTSTypeAnnotation,
1114
type BabelNodeTypeAnnotation,
1215
type BabelTSTypeAnnotation,
@@ -17,6 +20,7 @@ import {
1720
type FunctionExpression,
1821
type InterfaceDeclaration,
1922
type ObjectMethod,
23+
type ObjectPattern,
2024
type TSInterfaceDeclaration,
2125
type TSMethodSignature,
2226
type TSQualifiedName,
@@ -46,7 +50,8 @@ import {
4650
isNumberLiteralTypeAnnotation,
4751
isNumberTypeAnnotation,
4852
isNumericLiteral,
49-
isObjectExpression,
53+
isObjectPattern,
54+
isObjectProperty,
5055
isObjectTypeAnnotation,
5156
isObjectTypeIndexer,
5257
isObjectTypeProperty,
@@ -100,9 +105,7 @@ import {
100105
isTypeAnnotation,
101106
isTypeParameterInstantiation,
102107
isTypeofTypeAnnotation,
103-
isUnaryExpression,
104-
isUnionTypeAnnotation,
105-
isVoidTypeAnnotation,
108+
isUnaryExpression, isUnionTypeAnnotation, isVoidTypeAnnotation,
106109
} from "@babel/types";
107110

108111
import type {DataType, Param, Return} from "@webdoc/types";
@@ -228,23 +231,52 @@ export function extractParams(
228231
const extraRaw = paramNode.right.extra && paramNode.right.extra.raw;
229232
const [defaultValue, dataType] = extractAssignedValue(paramNode.right);
230233

231-
param = {
232-
identifier: paramNode.left.name,
233-
optional: paramNode.optional || false,
234-
default: paramNode.right.raw || extraRaw || defaultValue,
235-
dataType,
236-
};
237-
238-
// This will override the inferred data type.
239-
paramTypeAnnotation = paramNode.left.typeAnnotation;
240-
} else if (isObjectExpression(paramNode)) {
234+
if (isObjectPattern(paramNode.left)) {
235+
try {
236+
params.push(({
237+
identifier: `__arg${i}`,
238+
optional: false,
239+
...(paramNode.left.typeAnnotation && {
240+
dataType: extractType(paramNode.left.typeAnnotation),
241+
}),
242+
}: $Shape<Param>));
243+
params.push(...extractParamsFromObjectPatternRecursive(
244+
paramNode.left, paramNode.right,
245+
paramNode.left.typeAnnotation ? paramNode.left.typeAnnotation.typeAnnotation : null));
246+
} catch (e) {
247+
((params: any)).flawed = true;
248+
parserLogger.error(tag.Indexer, `Failed parse param: ${e}`);
249+
}
250+
} else {
251+
param = {
252+
identifier: paramNode.left.name,
253+
optional: paramNode.optional || false,
254+
default: paramNode.right.raw || extraRaw || defaultValue,
255+
dataType,
256+
};
257+
// This will override the inferred data type.
258+
paramTypeAnnotation = paramNode.left.typeAnnotation;
259+
}
260+
} else if (isObjectPattern(paramNode)) {
241261
// TODO: Find a way to document {x, y, z} parameters
242262
// e.g. function ({x, y, z}), you would need to give the object pattern an anonymous like
243263
// "", " ", " ", " " or using &zwnj; because it is truly invisible
244264

245-
((params: any)).flawed = true;
246-
parserLogger.error(tag.Indexer, "Object patterns as parameters can't be documented, at line");
247-
parserLogger.warn(tag.Indexer, JSON.stringify(paramNode, null, 2));
265+
try {
266+
params.push(({
267+
identifier: `__arg${i}`,
268+
optional: false,
269+
...(paramNode.typeAnnotation && {
270+
dataType: extractType(paramNode.typeAnnotation),
271+
}),
272+
}: $Shape<Param>));
273+
params.push(...extractParamsFromObjectPatternRecursive(
274+
paramNode, null,
275+
paramNode.typeAnnotation ? paramNode.typeAnnotation.typeAnnotation : null));
276+
} catch (e) {
277+
((params: any)).flawed = true;
278+
parserLogger.error(tag.Indexer, `Failed parse param: ${e}`);
279+
}
248280
} else {
249281
((params: any)).flawed = true;
250282
parserLogger.error(tag.Indexer, "Parameter node couldn't be parsed, " +
@@ -266,6 +298,80 @@ export function extractParams(
266298
return params;
267299
}
268300

301+
function extractParamsFromObjectPatternRecursive(
302+
left: ObjectPattern,
303+
right?: ?BabelNodeObjectExpression,
304+
typeAnnotation?: BabelNodeObjectTypeAnnotation | BabelNodeTSLiteralType,
305+
): Param[] {
306+
const params: Param[] = [];
307+
308+
for (const prop of left.properties) {
309+
if (isObjectProperty(prop)) {
310+
let defaultValue: any;
311+
312+
if (isAssignmentPattern(prop.value)) {
313+
defaultValue = prop.value.right;
314+
} else if (right) {
315+
for (const defaultValueProp of right.properties) {
316+
if (defaultValueProp.key && defaultValueProp.key.name === prop.key.name) {
317+
defaultValue = defaultValueProp.value;
318+
break;
319+
}
320+
}
321+
}
322+
323+
let valueTypeAnnotation: any;
324+
325+
if (typeAnnotation && isObjectTypeAnnotation(typeAnnotation)) {
326+
for (const typeProp of typeAnnotation.properties) {
327+
if (isIdentifier(typeProp.key) && typeProp.key.name === prop.key.name) {
328+
valueTypeAnnotation = {typeAnnotation: typeProp.value};
329+
break;
330+
}
331+
}
332+
} else if (typeAnnotation && isTSTypeLiteral(typeAnnotation)) {
333+
for (const typeProp of typeAnnotation.members) {
334+
if (isIdentifier(typeProp.key) && typeProp.key.name === prop.key.name) {
335+
valueTypeAnnotation = typeProp.typeAnnotation;
336+
break;
337+
}
338+
}
339+
}
340+
341+
if (isObjectPattern(prop.value)) {
342+
const embeddedParams = extractParamsFromObjectPatternRecursive(
343+
prop.value,
344+
defaultValue,
345+
valueTypeAnnotation ? valueTypeAnnotation.typeAnnotation : null,
346+
);
347+
const prefix = isIdentifier(prop.key) ? prop.key.name : "$error$";
348+
349+
for (const embeddedParam of embeddedParams) {
350+
embeddedParam.identifier = `.${prefix}${embeddedParam.identifier}`;
351+
}
352+
353+
params.push(...embeddedParams);
354+
} else if (isIdentifier(prop.key)) {
355+
const [defaultValueRaw, impliedDataType] = defaultValue ?
356+
extractAssignedValue(defaultValue) : [null, null];
357+
358+
// Prefix with dot so it's implied to be not top-level
359+
const param: $Shape<Param> = {
360+
identifier: "." + prop.key.name,
361+
optional: valueTypeAnnotation && valueTypeAnnotation.optional,
362+
variadic: false,
363+
...(defaultValueRaw && {default: defaultValueRaw}),
364+
dataType: valueTypeAnnotation ? extractType(valueTypeAnnotation) : impliedDataType,
365+
};
366+
367+
params.push(param);
368+
}
369+
}
370+
}
371+
372+
return params;
373+
}
374+
269375
// Extract the returns for the method/function
270376
export function extractReturns(
271377
node: ClassMethod | ObjectMethod | FunctionDeclaration | FunctionExpression,

packages/webdoc-parser/test/lang-flow.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,34 @@ describe("@webdoc/parser.LanguageIntegration{@lang flow}", function() {
3434

3535
expect(symbolTree.members[0].members[0].meta.dataType[0]).to.equal("Array<T>");
3636
});
37+
38+
it("should expand object pattern parameters", function() {
39+
const symbolTree = buildSymbolTree(`
40+
function length({ p0: { x: x0, y: y0 } }: { p0: { x: number, y: number} }) {}
41+
`);
42+
43+
const lengthFn = symbolTree.members[0];
44+
45+
expect(lengthFn.meta.params.length).to.equal(3);
46+
expect(lengthFn.meta.params[1].identifier).to.equal(".p0.x");
47+
expect(lengthFn.meta.params[1].dataType[0]).to.equal("number");
48+
expect(lengthFn.meta.params[2].identifier).to.equal(".p0.y");
49+
expect(lengthFn.meta.params[2].dataType[0]).to.equal("number");
50+
});
51+
52+
it("should expand object pattern parameters with default values", function() {
53+
const symbolTree = buildSymbolTree(`
54+
function length({ p0: { x: x0, y: y0 } }: { p0: { x: number, y: number} }
55+
= { p0: { x: 1, y: 2 } } ) {}
56+
`);
57+
58+
const lengthFn = symbolTree.members[0];
59+
60+
expect(lengthFn.meta.params.length).to.equal(3);
61+
expect(lengthFn.meta.params[0].dataType[0]).to.equal("{ p0: { x: number, y: number } }");
62+
expect(lengthFn.meta.params[1].default).to.equal("1");
63+
expect(lengthFn.meta.params[2].default).to.equal("2");
64+
expect(lengthFn.meta.params[1].dataType[0]).to.equal("number");
65+
expect(lengthFn.meta.params[2].dataType[0]).to.equal("number");
66+
});
3767
});

packages/webdoc-parser/test/lang-ts.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,4 +238,34 @@ describe("@webdoc/parser.LanguageIntegration{@lang ts}", function() {
238238

239239
expect(symbolTree.members[0].meta.params[0].dataType[0]).to.equal("string");
240240
});
241+
242+
it("should expand object pattern parameters", function() {
243+
const symbolTree = buildSymbolTree(`
244+
function length({ p0: { x: x0, y: y0 } }: { p0: { x: number, y: number} }) {}
245+
`, "*.ts");
246+
247+
const lengthFn = symbolTree.members[0];
248+
249+
expect(lengthFn.meta.params.length).to.equal(3);
250+
expect(lengthFn.meta.params[1].identifier).to.equal(".p0.x");
251+
expect(lengthFn.meta.params[1].dataType[0]).to.equal("number");
252+
expect(lengthFn.meta.params[2].identifier).to.equal(".p0.y");
253+
expect(lengthFn.meta.params[2].dataType[0]).to.equal("number");
254+
});
255+
256+
it("should expand object pattern parameters with default values", function() {
257+
const symbolTree = buildSymbolTree(`
258+
function length({ p0: { x: x0, y: y0 } }: { p0: { x: number, y: number} }
259+
= { p0: { x: 1, y: 2 } } ) {}
260+
`, "*.ts");
261+
262+
const lengthFn = symbolTree.members[0];
263+
264+
expect(lengthFn.meta.params.length).to.equal(3);
265+
expect(lengthFn.meta.params[0].dataType[0]).to.equal("{ p0 : { x : number, y : number } }");
266+
expect(lengthFn.meta.params[1].default).to.equal("1");
267+
expect(lengthFn.meta.params[2].default).to.equal("2");
268+
expect(lengthFn.meta.params[1].dataType[0]).to.equal("number");
269+
expect(lengthFn.meta.params[2].dataType[0]).to.equal("number");
270+
});
241271
});

0 commit comments

Comments
 (0)