Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 89 additions & 5 deletions src/LuaTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -699,13 +699,19 @@ export class LuaTransformer {

let restParamName: tstl.Identifier;
let dotsLiteral: tstl.DotsLiteral;
let identifierIndex = 0;

// Only push parameter name to paramName array if it isn't a spread parameter
for (const param of parameters) {
if (ts.isIdentifier(param.name) && param.name.originalKeywordKind === ts.SyntaxKind.ThisKeyword) {
continue;
}
const paramName = this.transformIdentifier(param.name as ts.Identifier);

// Binding patterns become ____TS_bindingPattern0, ____TS_bindingPattern1, etc as function parameters
// See transformFunctionBody for how these values are destructured
const paramName = ts.isObjectBindingPattern(param.name) || ts.isArrayBindingPattern(param.name)
? tstl.createIdentifier(`____TS_bindingPattern${identifierIndex++}`)
: this.transformIdentifier(param.name as ts.Identifier);

// This parameter is a spread parameter (...param)
if (!param.dotDotDotToken) {
Expand Down Expand Up @@ -737,6 +743,18 @@ export class LuaTransformer {

headerStatements.push(...defaultValueDeclarations);

// Add object binding patterns
let identifierIndex = 0;
const bindingPatternDeclarations: tstl.Statement[] = [];
parameters.forEach(binding => {
if (ts.isObjectBindingPattern(binding.name) || ts.isArrayBindingPattern(binding.name)) {
const identifier = tstl.createIdentifier(`____TS_bindingPattern${identifierIndex++}`);
bindingPatternDeclarations.push(...this.transformBindingPattern(binding.name, identifier));
}
});

headerStatements.push(...bindingPatternDeclarations);

// Push spread operator here
if (spreadIdentifier) {
const spreadTable = this.wrapInTable(tstl.createDotsLiteral());
Expand Down Expand Up @@ -766,6 +784,57 @@ export class LuaTransformer {
return tstl.createIfStatement(nilCondition, ifBlock, undefined, declaration);
}

public * transformBindingPattern(
pattern: ts.BindingPattern,
table: tstl.Identifier,
propertyAccessStack: ts.PropertyName[] = []
): IterableIterator<tstl.Statement>
{
const isObjectBindingPattern = ts.isObjectBindingPattern(pattern);
for (let index = 0; index < pattern.elements.length; index++) {
const element = pattern.elements[index];
if (ts.isBindingElement(element)) {
if (ts.isArrayBindingPattern(element.name) || ts.isObjectBindingPattern(element.name)) {
// nested binding pattern
const propertyName = isObjectBindingPattern
? element.propertyName
: ts.createNumericLiteral(String(index + 1));
propertyAccessStack.push(propertyName);
yield* this.transformBindingPattern(element.name, table, propertyAccessStack);
} else {
// Disallow ellipsis destructure
if (element.dotDotDotToken) {
throw TSTLErrors.ForbiddenEllipsisDestruction(element);
}
// Build the path to the table
let tableExpression: tstl.Expression = table;
propertyAccessStack.forEach(property => {
const propertyName = ts.isPropertyName(property)
? this.transformPropertyName(property)
: this.transformNumericLiteral(property);
tableExpression = tstl.createTableIndexExpression(tableExpression, propertyName);
});
// The identifier of the new variable
const variableName = this.transformIdentifier(element.name as ts.Identifier);
// The field to extract
const propertyName = this.transformIdentifier(
(element.propertyName || element.name) as ts.Identifier);
const expression = isObjectBindingPattern
? tstl.createTableIndexExpression(tableExpression, tstl.createStringLiteral(propertyName.text))
: tstl.createTableIndexExpression(tableExpression, tstl.createNumericLiteral(index + 1));
if (element.initializer) {
const defaultExpression = tstl.createBinaryExpression(expression,
this.transformExpression(element.initializer), tstl.SyntaxKind.OrOperator);
yield* this.createLocalOrExportedOrGlobalDeclaration(variableName, defaultExpression);
} else {
yield* this.createLocalOrExportedOrGlobalDeclaration(variableName, expression);
}
}
}
}
propertyAccessStack.pop();
}

public transformModuleDeclaration(statement: ts.ModuleDeclaration): tstl.Statement[] {
const decorators = tsHelper.getCustomDecorators(this.checker.getTypeAtLocation(statement), this.checker);
// If phantom namespace elide the declaration and return the body
Expand Down Expand Up @@ -976,8 +1045,25 @@ export class LuaTransformer {
statement
);
}
} else if (ts.isArrayBindingPattern(statement.name)) {
// Destructuring type
} else if (ts.isArrayBindingPattern(statement.name) || ts.isObjectBindingPattern(statement.name)) {
// Destructuring types

// For nested bindings and object bindings, fall back to transformBindingPattern
if (ts.isObjectBindingPattern(statement.name)
|| statement.name.elements.some(elem => !ts.isBindingElement(elem) || !ts.isIdentifier(elem.name))) {
const statements = [];
let table: tstl.Identifier;
if (ts.isIdentifier(statement.initializer)) {
table = this.transformIdentifier(statement.initializer);
} else {
// Contain the expression in a temporary variable
table = tstl.createIdentifier("____");
statements.push(tstl.createVariableDeclarationStatement(
table, this.transformExpression(statement.initializer)));
}
statements.push(...this.transformBindingPattern(statement.name, table));
return statements;
}

// Disallow ellipsis destruction
if (statement.name.elements.some(elem => !ts.isBindingElement(elem) || elem.dotDotDotToken !== undefined)) {
Expand Down Expand Up @@ -1009,8 +1095,6 @@ export class LuaTransformer {
statement
);
}
} else {
throw TSTLErrors.UnsupportedKind("variable declaration", statement.name.kind, statement);
}
}

Expand Down
77 changes: 77 additions & 0 deletions test/unit/bindingpatterns.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Expect, Test, TestCase, FocusTest, TestCases } from "alsatian";

import * as util from "../src/util";

const testCases = [
["{x}", "{x: true}", "x"],
["[x, y]", "[false, true]", "y"],
["{x: [y, z]}", "{x: [false, true]}", "z"],
["{x: [, z]}", "{x: [false, true]}", "z"],
["{x: [{y}]}", "{x: [{y: true}]}", "y"],
["[[y, z]]", "[[false, true]]", "z"],
["{x, y}", "{x: false, y: true}", "y"],
["{x: foo, y}", "{x: true, y: false}", "foo"],
["{x: foo, y: bar}", "{x: false, y: true}", "bar"],
["{x: {x, y}, z}", "{x: {x: true, y: false}, z: false}", "x"],
["{x: {x, y}, z}", "{x: {x: false, y: true}, z: false}", "y"],
["{x: {x, y}, z}", "{x: {x: false, y: false}, z: true}", "z"],
];

const testCasesDefault = [
["{x = true}", "{}", "x"],
["{x, y = true}", "{x: false}", "y"],
];

export class BindingPatternTests {

@TestCase("{x, y}, z", "{x: false, y: false}, true", "z")
@TestCase("{x, y}, {z}", "{x: false, y: false}, {z: true}", "z")
@TestCases(testCases)
@TestCases(testCasesDefault)
@Test("Object bindings in functions")
public tesBindingPatternParameters(
bindingString: string,
objectString: string,
returnVariable: string
): void {
const result = util.transpileAndExecute(`
function test(${bindingString}) {
return ${returnVariable};
}
return test(${objectString});
`);
Expect(result).toBe(true);
}

@TestCases(testCases)
@TestCases(testCasesDefault)
public testBindingPatternDeclarations(
bindingString: string,
objectString: string,
returnVariable: string
): void {
const result = util.transpileAndExecute(`
let ${bindingString} = ${objectString};
return ${returnVariable};
`);
Expect(result).toBe(true);
}

@TestCases(testCases)
@Test("Object bindings with call expressions")
public testBindingPatternCallExpressions(
bindingString: string,
objectString: string,
returnVariable: string
): void {
const result = util.transpileAndExecute(`
function call() {
return ${objectString};
}
let ${bindingString} = call();
return ${returnVariable};
`);
Expect(result).toBe(true);
}

}
10 changes: 0 additions & 10 deletions test/unit/expressions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,16 +507,6 @@ export class ExpressionTests {
.toThrowError(TranspileError, "Unsupported property on math: unknownProperty");
}

@Test("Unsupported variable declaration type error")
public unsupportedVariableDeclarationType(): void {
const transformer = util.makeTestTransformer();

const mockNode: any = {name: ts.createLiteral(false)};

Expect(() => transformer.transformVariableDeclaration(mockNode as ts.VariableDeclaration))
.toThrowError(TranspileError, "Unsupported variable declaration kind: FalseKeyword");
}

@Test("Unsupported object literal element error")
public unsupportedObjectLiteralElementError(): void {
const transformer = util.makeTestTransformer();
Expand Down