Skip to content

Commit

Permalink
feat(parsing): parse object and array destructurings correctly (#76)
Browse files Browse the repository at this point in the history
  • Loading branch information
buehler committed Jul 5, 2018
1 parent e6e74dd commit 97cb4ce
Show file tree
Hide file tree
Showing 6 changed files with 693 additions and 146 deletions.
3 changes: 1 addition & 2 deletions .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ matrix:
environment:
matrix:
- nodejs_version: "10"
- nodejs_version: "9"
- nodejs_version: "8"

install:
- ps: Install-Product node $env:nodejs_version
Expand All @@ -19,5 +17,6 @@ test_script:
- npm test
- npm install -g codecov
- codecov
- npm run build

build: off
38 changes: 38 additions & 0 deletions src/declarations/ParameterDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,41 @@ import { TypedDeclaration } from './Declaration';
export class ParameterDeclaration implements TypedDeclaration {
constructor(public name: string, public type: string | undefined, public start?: number, public end?: number) { }
}

export class BoundParameterDeclaration extends ParameterDeclaration {
public parameters: ParameterDeclaration[] = [];
public typeReference: string | undefined;

public get name(): string {
return this.parameters.length ?
`${this.startCharacter} ${this.parameters.map(p => p.name).join(', ')} ${this.endCharacter}` :
this.startCharacter + this.endCharacter;
}

public set name(_: string) { }

public get type(): string {
return this.typeReference ||
this.parameters.length ?
`{ ${this.parameters.map(p => p.type).join(', ')} }` :
this.startCharacter + this.endCharacter;
}

public set type(_: string) { }

constructor(private startCharacter: string, private endCharacter: string, start?: number, end?: number) {
super('', '', start, end);
}
}

export class ObjectBoundParameterDeclaration extends BoundParameterDeclaration {
constructor(start?: number, end?: number) {
super('{', '}', start, end);
}
}

export class ArrayBoundParameterDeclaration extends BoundParameterDeclaration {
constructor(start?: number, end?: number) {
super('[', ']', start, end);
}
}
72 changes: 56 additions & 16 deletions src/node-parser/function-parser.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import {
ArrayBindingPattern,
FunctionDeclaration,
Identifier,
isTupleTypeNode,
isTypeLiteralNode,
isTypeReferenceNode,
MethodDeclaration,
MethodSignature,
Node,
ObjectBindingPattern,
ParameterDeclaration,
PropertySignature,
SyntaxKind,
VariableStatement,
} from 'typescript';
Expand All @@ -15,9 +17,18 @@ import { ConstructorDeclaration as TshConstructor } from '../declarations/Constr
import { DefaultDeclaration as TshDefault } from '../declarations/DefaultDeclaration';
import { FunctionDeclaration as TshFunction } from '../declarations/FunctionDeclaration';
import { MethodDeclaration as TshMethod } from '../declarations/MethodDeclaration';
import { ParameterDeclaration as TshParameter } from '../declarations/ParameterDeclaration';
import {
ArrayBoundParameterDeclaration,
ObjectBoundParameterDeclaration,
ParameterDeclaration as TshParameter,
} from '../declarations/ParameterDeclaration';
import { Resource } from '../resources/Resource';
import { isArrayBindingPattern, isIdentifier, isObjectBindingPattern } from '../type-guards/TypescriptGuards';
import {
isArrayBindingPattern,
isIdentifier,
isObjectBindingPattern,
isPropertySignature,
} from '../type-guards/TypescriptGuards';
import { parseIdentifier } from './identifier-parser';
import { getDefaultResourceIdentifier, getNodeType, isNodeDefaultExported, isNodeExported } from './parse-utilities';
import { parseVariable } from './variable-parser';
Expand Down Expand Up @@ -63,22 +74,51 @@ export function parseMethodParams(
): TshParameter[] {
return node.parameters.reduce(
(all: TshParameter[], cur: ParameterDeclaration) => {
let params = all;
const params = all;
if (isIdentifier(cur.name)) {
params.push(new TshParameter(
(cur.name as Identifier).text, getNodeType(cur.type), cur.getStart(), cur.getEnd(),
));
} else if (isObjectBindingPattern(cur.name) || isArrayBindingPattern(cur.name)) {
const identifiers = cur.name as ObjectBindingPattern | ArrayBindingPattern;
const elements = [...identifiers.elements];
// TODO: BindingElement
params = params.concat(<TshParameter[]>elements.map((o: any) => {
if (isIdentifier(o.name)) {
return new TshParameter(
(o.name as Identifier).text, undefined, o.getStart(), o.getEnd(),
);
}
}).filter(Boolean));
} else if (isObjectBindingPattern(cur.name)) {
const elements = cur.name.elements;
let types: (string | undefined)[] = [];
const boundParam = new ObjectBoundParameterDeclaration(cur.getStart(), cur.getEnd());

if (cur.type && isTypeReferenceNode(cur.type)) {
boundParam.typeReference = getNodeType(cur.type);
} else if (cur.type && isTypeLiteralNode(cur.type)) {
types = cur.type.members
.filter(member => isPropertySignature(member))
.map((signature: any) => getNodeType((signature as PropertySignature).type));
}

boundParam.parameters = elements.map((bindingElement, index) => new TshParameter(
bindingElement.name.getText(),
types[index],
bindingElement.getStart(),
bindingElement.getEnd(),
));

params.push(boundParam);
} else if (isArrayBindingPattern(cur.name)) {
const elements = cur.name.elements;
let types: (string | undefined)[] = [];
const boundParam = new ArrayBoundParameterDeclaration(cur.getStart(), cur.getEnd());

if (cur.type && isTypeReferenceNode(cur.type)) {
boundParam.typeReference = getNodeType(cur.type);
} else if (cur.type && isTupleTypeNode(cur.type)) {
types = cur.type.elementTypes.map(type => getNodeType(type));
}

boundParam.parameters = elements.map((bindingElement, index) => new TshParameter(
bindingElement.getText(),
types[index],
bindingElement.getStart(),
bindingElement.getEnd(),
));

params.push(boundParam);
}
return params;
},
Expand Down
85 changes: 85 additions & 0 deletions test/TypescriptParser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,91 @@ describe('TypescriptParser', () => {

});

describe('Parameters', () => {

const file = getWorkspaceFile('typescript-parser/parameters.ts');
let parsed: Resource;

beforeEach(async () => {
parsed = await parser.parseFile(file, rootPath);
});

it('should parse a normal parameter', () => {
const func = parsed.declarations[0] as FunctionDeclaration;
expect(func.parameters[0]).toMatchSnapshot();
});

it('should parse a simple array binding pattern', () => {
const func = parsed.declarations[1] as FunctionDeclaration;
expect(func.parameters[0]).toMatchSnapshot();
});

it('should parse an array with tuple type', () => {
const func = parsed.declarations[2] as FunctionDeclaration;
expect(func.parameters[0]).toMatchSnapshot();
});

it('should parse an array with undertyped tuple type', () => {
const func = parsed.declarations[3] as FunctionDeclaration;
expect(func.parameters[0]).toMatchSnapshot();
});

it('should parse an array with overtyped tuple type', () => {
const func = parsed.declarations[4] as FunctionDeclaration;
expect(func.parameters[0]).toMatchSnapshot();
});

it('should parse a simple object binding pattern ', () => {
const func = parsed.declarations[5] as FunctionDeclaration;
expect(func.parameters[0]).toMatchSnapshot();
});

it('should parse an object with type reference', () => {
const func = parsed.declarations[6] as FunctionDeclaration;
expect(func.parameters[0]).toMatchSnapshot();
});

it('should parse an object with type literal', () => {
const func = parsed.declarations[7] as FunctionDeclaration;
expect(func.parameters[0]).toMatchSnapshot();
});

it('should parse an object with undertyped type literal', () => {
const func = parsed.declarations[8] as FunctionDeclaration;
expect(func.parameters[0]).toMatchSnapshot();
});

it('should parse an object with overtyped type literal', () => {
const func = parsed.declarations[9] as FunctionDeclaration;
expect(func.parameters[0]).toMatchSnapshot();
});

it('should parse some mixed parameters (all above)', () => {
expect(parsed.declarations[10]).toMatchSnapshot();
});

it('should generate the correct name for an object', () => {
const func = parsed.declarations[9] as FunctionDeclaration;
expect(func.parameters[0].name).toMatchSnapshot();
});

it('should generate the correct name for an array', () => {
const func = parsed.declarations[2] as FunctionDeclaration;
expect(func.parameters[0].name).toMatchSnapshot();
});

it('should generate the correct type for an object', () => {
const func = parsed.declarations[9] as FunctionDeclaration;
expect(func.parameters[0].type).toMatchSnapshot();
});

it('should generate the correct type for an array', () => {
const func = parsed.declarations[2] as FunctionDeclaration;
expect(func.parameters[0].type).toMatchSnapshot();
});

});

describe('Variables', () => {

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

0 comments on commit 97cb4ce

Please sign in to comment.