Skip to content

Commit 5dfe5cc

Browse files
committed
Support rest pattern in destructuring
1 parent 55da476 commit 5dfe5cc

5 files changed

Lines changed: 135 additions & 66 deletions

File tree

src/LuaLib.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export enum LuaLibFeature {
3838
ObjectEntries = "ObjectEntries",
3939
ObjectFromEntries = "ObjectFromEntries",
4040
ObjectKeys = "ObjectKeys",
41+
ObjectRest = "ObjectRest",
4142
ObjectValues = "ObjectValues",
4243
Set = "Set",
4344
WeakMap = "WeakMap",

src/LuaTransformer.ts

Lines changed: 92 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,64 +1348,97 @@ export class LuaTransformer {
13481348
const isObjectBindingPattern = ts.isObjectBindingPattern(pattern);
13491349
for (let index = 0; index < pattern.elements.length; index++) {
13501350
const element = pattern.elements[index];
1351-
if (ts.isBindingElement(element)) {
1352-
if (ts.isArrayBindingPattern(element.name) || ts.isObjectBindingPattern(element.name)) {
1353-
// nested binding pattern
1354-
const propertyName = isObjectBindingPattern
1355-
? element.propertyName
1356-
: ts.createNumericLiteral(String(index + 1));
1357-
if (propertyName !== undefined) {
1358-
propertyAccessStack.push(propertyName);
1359-
}
1360-
result.push(...this.statementVisitResultToArray(
1361-
this.transformBindingPattern(element.name, table, propertyAccessStack)
1362-
));
1351+
if (ts.isOmittedExpression(element)) continue;
1352+
1353+
if (ts.isArrayBindingPattern(element.name) || ts.isObjectBindingPattern(element.name)) {
1354+
// nested binding pattern
1355+
const propertyName = isObjectBindingPattern
1356+
? element.propertyName
1357+
: ts.createNumericLiteral(String(index + 1));
1358+
if (propertyName !== undefined) {
1359+
propertyAccessStack.push(propertyName);
1360+
}
1361+
result.push(...this.statementVisitResultToArray(
1362+
this.transformBindingPattern(element.name, table, propertyAccessStack)
1363+
));
1364+
continue;
1365+
}
1366+
1367+
// Build the path to the table
1368+
let tableExpression: tstl.Expression = table;
1369+
propertyAccessStack.forEach(property => {
1370+
const propertyName = ts.isPropertyName(property)
1371+
? this.transformPropertyName(property)
1372+
: this.transformNumericLiteral(property);
1373+
tableExpression = tstl.createTableIndexExpression(
1374+
tableExpression,
1375+
this.expectExpression(propertyName)
1376+
);
1377+
});
1378+
1379+
// The identifier of the new variable
1380+
const variableName = this.transformIdentifier(element.name as ts.Identifier);
1381+
// The field to extract
1382+
const propertyName = this.transformIdentifier(
1383+
(element.propertyName || element.name) as ts.Identifier);
1384+
1385+
let expression: tstl.Expression;
1386+
if (element.dotDotDotToken) {
1387+
if (index !== pattern.elements.length - 1) continue;
1388+
1389+
if (isObjectBindingPattern) {
1390+
const elements = pattern.elements as ts.NodeArray<ts.BindingElement>;
1391+
const usedProperties = elements.map(e =>
1392+
tstl.createTableFieldExpression(
1393+
tstl.createStringLiteral(
1394+
this.getIdentifierText((e.propertyName || e.name) as ts.Identifier)
1395+
)
1396+
)
1397+
);
1398+
1399+
expression = this.transformLuaLibFunction(
1400+
LuaLibFeature.ObjectRest,
1401+
undefined,
1402+
tableExpression,
1403+
tstl.createTableExpression(usedProperties)
1404+
);
13631405
} else {
1364-
// Disallow ellipsis destructure
1365-
if (element.dotDotDotToken) {
1366-
throw TSTLErrors.ForbiddenEllipsisDestruction(element);
1367-
}
1368-
// Build the path to the table
1369-
let tableExpression: tstl.Expression = table;
1370-
propertyAccessStack.forEach(property => {
1371-
const propertyName = ts.isPropertyName(property)
1372-
? this.transformPropertyName(property)
1373-
: this.transformNumericLiteral(property);
1374-
tableExpression = tstl.createTableIndexExpression(
1375-
tableExpression,
1376-
this.expectExpression(propertyName)
1377-
);
1378-
});
1379-
// The identifier of the new variable
1380-
const variableName = this.transformIdentifier(element.name as ts.Identifier);
1381-
// The field to extract
1382-
const propertyName = this.transformIdentifier(
1383-
(element.propertyName || element.name) as ts.Identifier);
1384-
const expression = isObjectBindingPattern
1385-
? tstl.createTableIndexExpression(tableExpression, tstl.createStringLiteral(propertyName.text))
1386-
: tstl.createTableIndexExpression(tableExpression, tstl.createNumericLiteral(index + 1));
1387-
result.push(...this.createLocalOrExportedOrGlobalDeclaration(variableName, expression));
1388-
if (element.initializer) {
1389-
const identifier = this.shouldExportIdentifier(variableName)
1390-
? this.createExportedIdentifier(variableName)
1391-
: variableName;
1392-
result.push(tstl.createIfStatement(
1393-
tstl.createBinaryExpression(
1406+
expression = this.transformLuaLibFunction(
1407+
LuaLibFeature.ArraySlice,
1408+
undefined,
1409+
tableExpression,
1410+
tstl.createNumericLiteral(index)
1411+
);
1412+
}
1413+
} else {
1414+
expression = tstl.createTableIndexExpression(
1415+
tableExpression,
1416+
isObjectBindingPattern
1417+
? tstl.createStringLiteral(propertyName.text)
1418+
: tstl.createNumericLiteral(index + 1)
1419+
);
1420+
}
1421+
1422+
result.push(...this.createLocalOrExportedOrGlobalDeclaration(variableName, expression));
1423+
if (element.initializer) {
1424+
const identifier = this.shouldExportIdentifier(variableName)
1425+
? this.createExportedIdentifier(variableName)
1426+
: variableName;
1427+
result.push(tstl.createIfStatement(
1428+
tstl.createBinaryExpression(
1429+
identifier,
1430+
tstl.createNilLiteral(),
1431+
tstl.SyntaxKind.EqualityOperator
1432+
),
1433+
tstl.createBlock(
1434+
[
1435+
tstl.createAssignmentStatement(
13941436
identifier,
1395-
tstl.createNilLiteral(),
1396-
tstl.SyntaxKind.EqualityOperator
1437+
this.transformExpression(element.initializer)
13971438
),
1398-
tstl.createBlock(
1399-
[
1400-
tstl.createAssignmentStatement(
1401-
identifier,
1402-
this.transformExpression(element.initializer)
1403-
),
1404-
]
1405-
)
1406-
));
1407-
}
1408-
}
1439+
]
1440+
)
1441+
));
14091442
}
14101443
}
14111444
propertyAccessStack.pop();
@@ -1830,10 +1863,11 @@ export class LuaTransformer {
18301863
// Destructuring types
18311864

18321865
const statements: tstl.Statement[] = [];
1833-
1834-
// For nested bindings and object bindings, fall back to transformBindingPattern
1866+
// For object, nested, omitted or rest bindings fall back to transformBindingPattern
18351867
if (ts.isObjectBindingPattern(statement.name)
1836-
|| statement.name.elements.some(elem => !ts.isBindingElement(elem) || !ts.isIdentifier(elem.name))) {
1868+
|| statement.name.elements.some(elem =>
1869+
ts.isOmittedExpression(elem) || !ts.isIdentifier(elem.name) || elem.dotDotDotToken !== undefined
1870+
)) {
18371871
const statements = [];
18381872
let table: tstl.Identifier;
18391873
if (statement.initializer !== undefined && ts.isIdentifier(statement.initializer)) {
@@ -1852,11 +1886,6 @@ export class LuaTransformer {
18521886
return statements;
18531887
}
18541888

1855-
// Disallow ellipsis destruction
1856-
if (statement.name.elements.some(elem => !ts.isBindingElement(elem) || elem.dotDotDotToken !== undefined)) {
1857-
throw TSTLErrors.ForbiddenEllipsisDestruction(statement);
1858-
}
1859-
18601889
const vars = statement.name.elements.length > 0
18611890
? this.filterUndefinedAndCast(
18621891
statement.name.elements.map(e => this.transformArrayBindingElement(e)),

src/TSTLErrors.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ export class TSTLErrors {
1212
public static DefaultImportsNotSupported = (node: ts.Node) =>
1313
new TranspileError(`Default Imports are not supported, please use named imports instead!`, node);
1414

15-
public static ForbiddenEllipsisDestruction = (node: ts.Node) =>
16-
new TranspileError(`Ellipsis destruction is not allowed.`, node);
17-
1815
public static ForbiddenForIn = (node: ts.Node) =>
1916
new TranspileError(`Iterating over arrays with 'for ... in' is not allowed.`, node);
2017

src/lualib/ObjectRest.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
function __TS__ObjectRest<K extends keyof any, V>(
2+
this: void,
3+
target: Record<K, V>,
4+
usedProperties: K[]
5+
): Partial<Record<K, V>> {
6+
const result: Partial<Record<K, V>> = {};
7+
for (const property in target) {
8+
let isUsed = false;
9+
for (const usedProperty of usedProperties) {
10+
if (property === usedProperty) {
11+
isUsed = true;
12+
break;
13+
}
14+
}
15+
16+
if (!isUsed) {
17+
result[property] = target[property];
18+
}
19+
}
20+
21+
return result;
22+
}

test/unit/bindingpatterns.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,23 @@ test.each([
109109
expect(result).toBe(false);
110110
},
111111
);
112+
113+
describe("rest binding patterns", () => {
114+
test("should support object rest element", () => {
115+
const result = util.transpileAndExecute(`
116+
const { foo, ...rest } = { foo: 1, bar: 2 };
117+
return JSONStringify({ foo, rest })
118+
`);
119+
120+
expect(JSON.parse(result)).toEqual({ foo: 1, rest: { bar: 2 } });
121+
});
122+
123+
test("should support array rest element", () => {
124+
const result = util.transpileAndExecute(`
125+
const [foo, ...rest] = [1, 2, 3];
126+
return JSONStringify({ foo, rest });
127+
`);
128+
129+
expect(JSON.parse(result)).toEqual({ foo: 1, rest: [2, 3] });
130+
});
131+
});

0 commit comments

Comments
 (0)