Skip to content

Commit da0056a

Browse files
hazzard993tomblind
authored andcommitted
ObjectBindingPatterns Implementation (#376)
* First of Object Binding Pattern * Implemented transformObjectBindingPattern * Object destructure initializer support, lint fixes * Removed test that TypeScript will never allow to happen * ObjectBindings can use export * Added bindingpattern tests * Removed unused property * Adding back whitespace * Linting * Changed temp variable name and fixed call expressions * Added tests for call expressions * Array and Object mixing support * Refactoring, changes to variable declarations * Disallowing ellipsis destructuring * Added array/object mixed destructure tests * Naming and code style changes
1 parent 535f93f commit da0056a

File tree

3 files changed

+166
-15
lines changed

3 files changed

+166
-15
lines changed

src/LuaTransformer.ts

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -699,13 +699,19 @@ export class LuaTransformer {
699699

700700
let restParamName: tstl.Identifier;
701701
let dotsLiteral: tstl.DotsLiteral;
702+
let identifierIndex = 0;
702703

703704
// Only push parameter name to paramName array if it isn't a spread parameter
704705
for (const param of parameters) {
705706
if (ts.isIdentifier(param.name) && param.name.originalKeywordKind === ts.SyntaxKind.ThisKeyword) {
706707
continue;
707708
}
708-
const paramName = this.transformIdentifier(param.name as ts.Identifier);
709+
710+
// Binding patterns become ____TS_bindingPattern0, ____TS_bindingPattern1, etc as function parameters
711+
// See transformFunctionBody for how these values are destructured
712+
const paramName = ts.isObjectBindingPattern(param.name) || ts.isArrayBindingPattern(param.name)
713+
? tstl.createIdentifier(`____TS_bindingPattern${identifierIndex++}`)
714+
: this.transformIdentifier(param.name as ts.Identifier);
709715

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

738744
headerStatements.push(...defaultValueDeclarations);
739745

746+
// Add object binding patterns
747+
let identifierIndex = 0;
748+
const bindingPatternDeclarations: tstl.Statement[] = [];
749+
parameters.forEach(binding => {
750+
if (ts.isObjectBindingPattern(binding.name) || ts.isArrayBindingPattern(binding.name)) {
751+
const identifier = tstl.createIdentifier(`____TS_bindingPattern${identifierIndex++}`);
752+
bindingPatternDeclarations.push(...this.transformBindingPattern(binding.name, identifier));
753+
}
754+
});
755+
756+
headerStatements.push(...bindingPatternDeclarations);
757+
740758
// Push spread operator here
741759
if (spreadIdentifier) {
742760
const spreadTable = this.wrapInTable(tstl.createDotsLiteral());
@@ -766,6 +784,57 @@ export class LuaTransformer {
766784
return tstl.createIfStatement(nilCondition, ifBlock, undefined, declaration);
767785
}
768786

787+
public * transformBindingPattern(
788+
pattern: ts.BindingPattern,
789+
table: tstl.Identifier,
790+
propertyAccessStack: ts.PropertyName[] = []
791+
): IterableIterator<tstl.Statement>
792+
{
793+
const isObjectBindingPattern = ts.isObjectBindingPattern(pattern);
794+
for (let index = 0; index < pattern.elements.length; index++) {
795+
const element = pattern.elements[index];
796+
if (ts.isBindingElement(element)) {
797+
if (ts.isArrayBindingPattern(element.name) || ts.isObjectBindingPattern(element.name)) {
798+
// nested binding pattern
799+
const propertyName = isObjectBindingPattern
800+
? element.propertyName
801+
: ts.createNumericLiteral(String(index + 1));
802+
propertyAccessStack.push(propertyName);
803+
yield* this.transformBindingPattern(element.name, table, propertyAccessStack);
804+
} else {
805+
// Disallow ellipsis destructure
806+
if (element.dotDotDotToken) {
807+
throw TSTLErrors.ForbiddenEllipsisDestruction(element);
808+
}
809+
// Build the path to the table
810+
let tableExpression: tstl.Expression = table;
811+
propertyAccessStack.forEach(property => {
812+
const propertyName = ts.isPropertyName(property)
813+
? this.transformPropertyName(property)
814+
: this.transformNumericLiteral(property);
815+
tableExpression = tstl.createTableIndexExpression(tableExpression, propertyName);
816+
});
817+
// The identifier of the new variable
818+
const variableName = this.transformIdentifier(element.name as ts.Identifier);
819+
// The field to extract
820+
const propertyName = this.transformIdentifier(
821+
(element.propertyName || element.name) as ts.Identifier);
822+
const expression = isObjectBindingPattern
823+
? tstl.createTableIndexExpression(tableExpression, tstl.createStringLiteral(propertyName.text))
824+
: tstl.createTableIndexExpression(tableExpression, tstl.createNumericLiteral(index + 1));
825+
if (element.initializer) {
826+
const defaultExpression = tstl.createBinaryExpression(expression,
827+
this.transformExpression(element.initializer), tstl.SyntaxKind.OrOperator);
828+
yield* this.createLocalOrExportedOrGlobalDeclaration(variableName, defaultExpression);
829+
} else {
830+
yield* this.createLocalOrExportedOrGlobalDeclaration(variableName, expression);
831+
}
832+
}
833+
}
834+
}
835+
propertyAccessStack.pop();
836+
}
837+
769838
public transformModuleDeclaration(statement: ts.ModuleDeclaration): tstl.Statement[] {
770839
const decorators = tsHelper.getCustomDecorators(this.checker.getTypeAtLocation(statement), this.checker);
771840
// If phantom namespace elide the declaration and return the body
@@ -976,8 +1045,25 @@ export class LuaTransformer {
9761045
statement
9771046
);
9781047
}
979-
} else if (ts.isArrayBindingPattern(statement.name)) {
980-
// Destructuring type
1048+
} else if (ts.isArrayBindingPattern(statement.name) || ts.isObjectBindingPattern(statement.name)) {
1049+
// Destructuring types
1050+
1051+
// For nested bindings and object bindings, fall back to transformBindingPattern
1052+
if (ts.isObjectBindingPattern(statement.name)
1053+
|| statement.name.elements.some(elem => !ts.isBindingElement(elem) || !ts.isIdentifier(elem.name))) {
1054+
const statements = [];
1055+
let table: tstl.Identifier;
1056+
if (ts.isIdentifier(statement.initializer)) {
1057+
table = this.transformIdentifier(statement.initializer);
1058+
} else {
1059+
// Contain the expression in a temporary variable
1060+
table = tstl.createIdentifier("____");
1061+
statements.push(tstl.createVariableDeclarationStatement(
1062+
table, this.transformExpression(statement.initializer)));
1063+
}
1064+
statements.push(...this.transformBindingPattern(statement.name, table));
1065+
return statements;
1066+
}
9811067

9821068
// Disallow ellipsis destruction
9831069
if (statement.name.elements.some(elem => !ts.isBindingElement(elem) || elem.dotDotDotToken !== undefined)) {
@@ -1009,8 +1095,6 @@ export class LuaTransformer {
10091095
statement
10101096
);
10111097
}
1012-
} else {
1013-
throw TSTLErrors.UnsupportedKind("variable declaration", statement.name.kind, statement);
10141098
}
10151099
}
10161100

test/unit/bindingpatterns.spec.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { Expect, Test, TestCase, FocusTest, TestCases } from "alsatian";
2+
3+
import * as util from "../src/util";
4+
5+
const testCases = [
6+
["{x}", "{x: true}", "x"],
7+
["[x, y]", "[false, true]", "y"],
8+
["{x: [y, z]}", "{x: [false, true]}", "z"],
9+
["{x: [, z]}", "{x: [false, true]}", "z"],
10+
["{x: [{y}]}", "{x: [{y: true}]}", "y"],
11+
["[[y, z]]", "[[false, true]]", "z"],
12+
["{x, y}", "{x: false, y: true}", "y"],
13+
["{x: foo, y}", "{x: true, y: false}", "foo"],
14+
["{x: foo, y: bar}", "{x: false, y: true}", "bar"],
15+
["{x: {x, y}, z}", "{x: {x: true, y: false}, z: false}", "x"],
16+
["{x: {x, y}, z}", "{x: {x: false, y: true}, z: false}", "y"],
17+
["{x: {x, y}, z}", "{x: {x: false, y: false}, z: true}", "z"],
18+
];
19+
20+
const testCasesDefault = [
21+
["{x = true}", "{}", "x"],
22+
["{x, y = true}", "{x: false}", "y"],
23+
];
24+
25+
export class BindingPatternTests {
26+
27+
@TestCase("{x, y}, z", "{x: false, y: false}, true", "z")
28+
@TestCase("{x, y}, {z}", "{x: false, y: false}, {z: true}", "z")
29+
@TestCases(testCases)
30+
@TestCases(testCasesDefault)
31+
@Test("Object bindings in functions")
32+
public tesBindingPatternParameters(
33+
bindingString: string,
34+
objectString: string,
35+
returnVariable: string
36+
): void {
37+
const result = util.transpileAndExecute(`
38+
function test(${bindingString}) {
39+
return ${returnVariable};
40+
}
41+
return test(${objectString});
42+
`);
43+
Expect(result).toBe(true);
44+
}
45+
46+
@TestCases(testCases)
47+
@TestCases(testCasesDefault)
48+
public testBindingPatternDeclarations(
49+
bindingString: string,
50+
objectString: string,
51+
returnVariable: string
52+
): void {
53+
const result = util.transpileAndExecute(`
54+
let ${bindingString} = ${objectString};
55+
return ${returnVariable};
56+
`);
57+
Expect(result).toBe(true);
58+
}
59+
60+
@TestCases(testCases)
61+
@Test("Object bindings with call expressions")
62+
public testBindingPatternCallExpressions(
63+
bindingString: string,
64+
objectString: string,
65+
returnVariable: string
66+
): void {
67+
const result = util.transpileAndExecute(`
68+
function call() {
69+
return ${objectString};
70+
}
71+
let ${bindingString} = call();
72+
return ${returnVariable};
73+
`);
74+
Expect(result).toBe(true);
75+
}
76+
77+
}

test/unit/expressions.spec.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -529,16 +529,6 @@ export class ExpressionTests {
529529
.toThrowError(TranspileError, "Unsupported property on math: unknownProperty");
530530
}
531531

532-
@Test("Unsupported variable declaration type error")
533-
public unsupportedVariableDeclarationType(): void {
534-
const transformer = util.makeTestTransformer();
535-
536-
const mockNode: any = {name: ts.createLiteral(false)};
537-
538-
Expect(() => transformer.transformVariableDeclaration(mockNode as ts.VariableDeclaration))
539-
.toThrowError(TranspileError, "Unsupported variable declaration kind: FalseKeyword");
540-
}
541-
542532
@Test("Unsupported object literal element error")
543533
public unsupportedObjectLiteralElementError(): void {
544534
const transformer = util.makeTestTransformer();

0 commit comments

Comments
 (0)