Skip to content

Commit 97cb4ce

Browse files
authored
feat(parsing): parse object and array destructurings correctly (#76)
1 parent e6e74dd commit 97cb4ce

File tree

6 files changed

+693
-146
lines changed

6 files changed

+693
-146
lines changed

.appveyor.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ matrix:
88
environment:
99
matrix:
1010
- nodejs_version: "10"
11-
- nodejs_version: "9"
12-
- nodejs_version: "8"
1311

1412
install:
1513
- ps: Install-Product node $env:nodejs_version
@@ -19,5 +17,6 @@ test_script:
1917
- npm test
2018
- npm install -g codecov
2119
- codecov
20+
- npm run build
2221

2322
build: off

src/declarations/ParameterDeclaration.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,41 @@ import { TypedDeclaration } from './Declaration';
1111
export class ParameterDeclaration implements TypedDeclaration {
1212
constructor(public name: string, public type: string | undefined, public start?: number, public end?: number) { }
1313
}
14+
15+
export class BoundParameterDeclaration extends ParameterDeclaration {
16+
public parameters: ParameterDeclaration[] = [];
17+
public typeReference: string | undefined;
18+
19+
public get name(): string {
20+
return this.parameters.length ?
21+
`${this.startCharacter} ${this.parameters.map(p => p.name).join(', ')} ${this.endCharacter}` :
22+
this.startCharacter + this.endCharacter;
23+
}
24+
25+
public set name(_: string) { }
26+
27+
public get type(): string {
28+
return this.typeReference ||
29+
this.parameters.length ?
30+
`{ ${this.parameters.map(p => p.type).join(', ')} }` :
31+
this.startCharacter + this.endCharacter;
32+
}
33+
34+
public set type(_: string) { }
35+
36+
constructor(private startCharacter: string, private endCharacter: string, start?: number, end?: number) {
37+
super('', '', start, end);
38+
}
39+
}
40+
41+
export class ObjectBoundParameterDeclaration extends BoundParameterDeclaration {
42+
constructor(start?: number, end?: number) {
43+
super('{', '}', start, end);
44+
}
45+
}
46+
47+
export class ArrayBoundParameterDeclaration extends BoundParameterDeclaration {
48+
constructor(start?: number, end?: number) {
49+
super('[', ']', start, end);
50+
}
51+
}

src/node-parser/function-parser.ts

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import {
2-
ArrayBindingPattern,
32
FunctionDeclaration,
43
Identifier,
4+
isTupleTypeNode,
5+
isTypeLiteralNode,
6+
isTypeReferenceNode,
57
MethodDeclaration,
68
MethodSignature,
79
Node,
8-
ObjectBindingPattern,
910
ParameterDeclaration,
11+
PropertySignature,
1012
SyntaxKind,
1113
VariableStatement,
1214
} from 'typescript';
@@ -15,9 +17,18 @@ import { ConstructorDeclaration as TshConstructor } from '../declarations/Constr
1517
import { DefaultDeclaration as TshDefault } from '../declarations/DefaultDeclaration';
1618
import { FunctionDeclaration as TshFunction } from '../declarations/FunctionDeclaration';
1719
import { MethodDeclaration as TshMethod } from '../declarations/MethodDeclaration';
18-
import { ParameterDeclaration as TshParameter } from '../declarations/ParameterDeclaration';
20+
import {
21+
ArrayBoundParameterDeclaration,
22+
ObjectBoundParameterDeclaration,
23+
ParameterDeclaration as TshParameter,
24+
} from '../declarations/ParameterDeclaration';
1925
import { Resource } from '../resources/Resource';
20-
import { isArrayBindingPattern, isIdentifier, isObjectBindingPattern } from '../type-guards/TypescriptGuards';
26+
import {
27+
isArrayBindingPattern,
28+
isIdentifier,
29+
isObjectBindingPattern,
30+
isPropertySignature,
31+
} from '../type-guards/TypescriptGuards';
2132
import { parseIdentifier } from './identifier-parser';
2233
import { getDefaultResourceIdentifier, getNodeType, isNodeDefaultExported, isNodeExported } from './parse-utilities';
2334
import { parseVariable } from './variable-parser';
@@ -63,22 +74,51 @@ export function parseMethodParams(
6374
): TshParameter[] {
6475
return node.parameters.reduce(
6576
(all: TshParameter[], cur: ParameterDeclaration) => {
66-
let params = all;
77+
const params = all;
6778
if (isIdentifier(cur.name)) {
6879
params.push(new TshParameter(
6980
(cur.name as Identifier).text, getNodeType(cur.type), cur.getStart(), cur.getEnd(),
7081
));
71-
} else if (isObjectBindingPattern(cur.name) || isArrayBindingPattern(cur.name)) {
72-
const identifiers = cur.name as ObjectBindingPattern | ArrayBindingPattern;
73-
const elements = [...identifiers.elements];
74-
// TODO: BindingElement
75-
params = params.concat(<TshParameter[]>elements.map((o: any) => {
76-
if (isIdentifier(o.name)) {
77-
return new TshParameter(
78-
(o.name as Identifier).text, undefined, o.getStart(), o.getEnd(),
79-
);
80-
}
81-
}).filter(Boolean));
82+
} else if (isObjectBindingPattern(cur.name)) {
83+
const elements = cur.name.elements;
84+
let types: (string | undefined)[] = [];
85+
const boundParam = new ObjectBoundParameterDeclaration(cur.getStart(), cur.getEnd());
86+
87+
if (cur.type && isTypeReferenceNode(cur.type)) {
88+
boundParam.typeReference = getNodeType(cur.type);
89+
} else if (cur.type && isTypeLiteralNode(cur.type)) {
90+
types = cur.type.members
91+
.filter(member => isPropertySignature(member))
92+
.map((signature: any) => getNodeType((signature as PropertySignature).type));
93+
}
94+
95+
boundParam.parameters = elements.map((bindingElement, index) => new TshParameter(
96+
bindingElement.name.getText(),
97+
types[index],
98+
bindingElement.getStart(),
99+
bindingElement.getEnd(),
100+
));
101+
102+
params.push(boundParam);
103+
} else if (isArrayBindingPattern(cur.name)) {
104+
const elements = cur.name.elements;
105+
let types: (string | undefined)[] = [];
106+
const boundParam = new ArrayBoundParameterDeclaration(cur.getStart(), cur.getEnd());
107+
108+
if (cur.type && isTypeReferenceNode(cur.type)) {
109+
boundParam.typeReference = getNodeType(cur.type);
110+
} else if (cur.type && isTupleTypeNode(cur.type)) {
111+
types = cur.type.elementTypes.map(type => getNodeType(type));
112+
}
113+
114+
boundParam.parameters = elements.map((bindingElement, index) => new TshParameter(
115+
bindingElement.getText(),
116+
types[index],
117+
bindingElement.getStart(),
118+
bindingElement.getEnd(),
119+
));
120+
121+
params.push(boundParam);
82122
}
83123
return params;
84124
},

test/TypescriptParser.spec.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,91 @@ describe('TypescriptParser', () => {
253253

254254
});
255255

256+
describe('Parameters', () => {
257+
258+
const file = getWorkspaceFile('typescript-parser/parameters.ts');
259+
let parsed: Resource;
260+
261+
beforeEach(async () => {
262+
parsed = await parser.parseFile(file, rootPath);
263+
});
264+
265+
it('should parse a normal parameter', () => {
266+
const func = parsed.declarations[0] as FunctionDeclaration;
267+
expect(func.parameters[0]).toMatchSnapshot();
268+
});
269+
270+
it('should parse a simple array binding pattern', () => {
271+
const func = parsed.declarations[1] as FunctionDeclaration;
272+
expect(func.parameters[0]).toMatchSnapshot();
273+
});
274+
275+
it('should parse an array with tuple type', () => {
276+
const func = parsed.declarations[2] as FunctionDeclaration;
277+
expect(func.parameters[0]).toMatchSnapshot();
278+
});
279+
280+
it('should parse an array with undertyped tuple type', () => {
281+
const func = parsed.declarations[3] as FunctionDeclaration;
282+
expect(func.parameters[0]).toMatchSnapshot();
283+
});
284+
285+
it('should parse an array with overtyped tuple type', () => {
286+
const func = parsed.declarations[4] as FunctionDeclaration;
287+
expect(func.parameters[0]).toMatchSnapshot();
288+
});
289+
290+
it('should parse a simple object binding pattern ', () => {
291+
const func = parsed.declarations[5] as FunctionDeclaration;
292+
expect(func.parameters[0]).toMatchSnapshot();
293+
});
294+
295+
it('should parse an object with type reference', () => {
296+
const func = parsed.declarations[6] as FunctionDeclaration;
297+
expect(func.parameters[0]).toMatchSnapshot();
298+
});
299+
300+
it('should parse an object with type literal', () => {
301+
const func = parsed.declarations[7] as FunctionDeclaration;
302+
expect(func.parameters[0]).toMatchSnapshot();
303+
});
304+
305+
it('should parse an object with undertyped type literal', () => {
306+
const func = parsed.declarations[8] as FunctionDeclaration;
307+
expect(func.parameters[0]).toMatchSnapshot();
308+
});
309+
310+
it('should parse an object with overtyped type literal', () => {
311+
const func = parsed.declarations[9] as FunctionDeclaration;
312+
expect(func.parameters[0]).toMatchSnapshot();
313+
});
314+
315+
it('should parse some mixed parameters (all above)', () => {
316+
expect(parsed.declarations[10]).toMatchSnapshot();
317+
});
318+
319+
it('should generate the correct name for an object', () => {
320+
const func = parsed.declarations[9] as FunctionDeclaration;
321+
expect(func.parameters[0].name).toMatchSnapshot();
322+
});
323+
324+
it('should generate the correct name for an array', () => {
325+
const func = parsed.declarations[2] as FunctionDeclaration;
326+
expect(func.parameters[0].name).toMatchSnapshot();
327+
});
328+
329+
it('should generate the correct type for an object', () => {
330+
const func = parsed.declarations[9] as FunctionDeclaration;
331+
expect(func.parameters[0].type).toMatchSnapshot();
332+
});
333+
334+
it('should generate the correct type for an array', () => {
335+
const func = parsed.declarations[2] as FunctionDeclaration;
336+
expect(func.parameters[0].type).toMatchSnapshot();
337+
});
338+
339+
});
340+
256341
describe('Variables', () => {
257342

258343
const file = getWorkspaceFile('typescript-parser/variable.ts');

0 commit comments

Comments
 (0)