Skip to content

Commit c11ba34

Browse files
hazzard993Perryvw
authored andcommitted
SpreadAssignment support (#693)
* Support spreading strings * Support SpreadAssignment * Fix SpreadAssignment * Use for loop for spreading strings * Use Object.assign not MergeObjects * Remove string declaration * Avoid adding an empty table in SpreadElement table lists * Avoid mutating the first SpreadAssignment value * Remove JSON.parse in spreadElement test. Not needed * Add spreadElement destructure and multiple properties * Allow arrays to be spread * Add a comment to SpreadAssignment algorithm * Use if else in SpreadAssignment element * Remove unnecessary tests
1 parent 2c47c3b commit c11ba34

File tree

7 files changed

+113
-6
lines changed

7 files changed

+113
-6
lines changed

src/LuaLib.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export enum LuaLibFeature {
1818
ArraySlice = "ArraySlice",
1919
ArraySome = "ArraySome",
2020
ArraySplice = "ArraySplice",
21+
ArrayToObject = "ArrayToObject",
2122
ArrayFlat = "ArrayFlat",
2223
ArrayFlatMap = "ArrayFlatMap",
2324
ArraySetLength = "ArraySetLength",

src/LuaTransformer.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3930,7 +3930,8 @@ export class LuaTransformer {
39303930
}
39313931

39323932
public transformObjectLiteral(expression: ts.ObjectLiteralExpression): ExpressionVisitResult {
3933-
const properties: tstl.TableFieldExpression[] = [];
3933+
let properties: tstl.TableFieldExpression[] = [];
3934+
const tableExpressions: tstl.Expression[] = [];
39343935
// Add all property assignments
39353936
expression.properties.forEach(element => {
39363937
const name = element.name ? this.transformPropertyName(element.name) : undefined;
@@ -3947,12 +3948,46 @@ export class LuaTransformer {
39473948
} else if (ts.isMethodDeclaration(element)) {
39483949
const expression = this.transformFunctionExpression(element);
39493950
properties.push(tstl.createTableFieldExpression(expression, name, element));
3951+
} else if (ts.isSpreadAssignment(element)) {
3952+
// Create a table for preceding properties to preserve property order
3953+
// { x: 0, ...{ y: 2 }, y: 1, z: 2 } --> __TS__ObjectAssign({x = 0}, {y = 2}, {y = 1, z = 2})
3954+
if (properties.length > 0) {
3955+
const tableExpression = tstl.createTableExpression(properties, expression);
3956+
tableExpressions.push(tableExpression);
3957+
}
3958+
properties = [];
3959+
3960+
const type = this.checker.getTypeAtLocation(element.expression);
3961+
let tableExpression: tstl.Expression;
3962+
if (type && tsHelper.isArrayType(type, this.checker, this.program)) {
3963+
tableExpression = this.transformLuaLibFunction(
3964+
LuaLibFeature.ArrayToObject,
3965+
element.expression,
3966+
this.transformExpression(element.expression)
3967+
);
3968+
} else {
3969+
tableExpression = this.transformExpression(element.expression);
3970+
}
3971+
tableExpressions.push(tableExpression);
39503972
} else {
39513973
throw TSTLErrors.UnsupportedKind("object literal element", element.kind, expression);
39523974
}
39533975
});
39543976

3955-
return tstl.createTableExpression(properties, expression);
3977+
if (tableExpressions.length === 0) {
3978+
return tstl.createTableExpression(properties, expression);
3979+
} else {
3980+
if (properties.length > 0) {
3981+
const tableExpression = tstl.createTableExpression(properties, expression);
3982+
tableExpressions.push(tableExpression);
3983+
}
3984+
3985+
if (tableExpressions[0].kind !== tstl.SyntaxKind.TableExpression) {
3986+
tableExpressions.unshift(tstl.createTableExpression(undefined, expression));
3987+
}
3988+
3989+
return this.transformLuaLibFunction(LuaLibFeature.ObjectAssign, expression, ...tableExpressions);
3990+
}
39563991
}
39573992

39583993
public transformOmittedExpression(node: ts.OmittedExpression): ExpressionVisitResult {

src/lualib/ArrayToObject.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
function __TS__ArrayToObject(this: void, array: any[]): object {
2+
const object: Record<number, any> = {};
3+
for (let i = 0; i < array.length; i += 1) {
4+
object[i] = array[i];
5+
}
6+
return object;
7+
}

src/lualib/Spread.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
function __TS__Spread<T>(this: void, iterable: Iterable<T>): T[] {
2-
const arr: T[] = [];
3-
for (const item of iterable) {
4-
arr[arr.length] = item;
1+
function __TS__Spread<T>(this: void, iterable: string | Iterable<T>): T[] {
2+
const arr = [];
3+
if (typeof iterable === "string") {
4+
for (let i = 0; i < iterable.length; i += 1) {
5+
arr[arr.length] = iterable[i];
6+
}
7+
} else {
8+
for (const item of iterable) {
9+
arr[arr.length] = item;
10+
}
511
}
612
return (table.unpack || unpack)(arr);
713
}

test/translation/__snapshots__/transformation.spec.ts.snap

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,13 @@ exports[`Transformation (shorthandPropertyAssignment) 1`] = `
567567
f = function(____, x) return ({x = x}) end"
568568
`;
569569

570+
exports[`Transformation (spreadAssignment) 1`] = `
571+
"require(\\"lualib_bundle\\");
572+
local xy = __TS__ObjectAssign({x = 0, y = 1})
573+
local xyz = __TS__ObjectAssign({x = 0, y = 1}, {z = 2})
574+
local xyz2 = __TS__ObjectAssign({z = 2}, {x = 0, y = 1})"
575+
`;
576+
570577
exports[`Transformation (tryCatch) 1`] = `
571578
"do
572579
local ____try, er = pcall(
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const xy = { ...{ x: 0, y: 1 } };
2+
const xyz = { ...{ x: 0, y: 1 }, z: 2 };
3+
const xyz2 = { z: 2, ...{ x: 0, y: 1 } };

test/unit/spreadElement.spec.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,51 @@ test("Spread Element Iterable", () => {
6464
return JSONStringify(arr)`;
6565
expect(JSON.parse(util.transpileAndExecute(code))).toEqual([1, 2, 4, 8, 16, 32, 64, 128, 256]);
6666
});
67+
68+
test.each(["", "string", "string with spaces", "string 1 2 3"])('Spread Element String "%s"', str => {
69+
const code = `
70+
const arr = [..."${str}"];
71+
return JSONStringify(arr)`;
72+
expect(JSON.parse(util.transpileAndExecute(code))).toEqual([...str]);
73+
});
74+
75+
test.each([
76+
"{ value: false, ...{ value: true } }",
77+
"{ ...{ value: false }, value: true }",
78+
"{ ...{ value: false }, value: false, ...{ value: true } }",
79+
"{ ...{ x: true, y: true } }",
80+
"{ x: true, ...{ y: true, z: true } }",
81+
"{ ...{ x: true }, ...{ y: true, z: true } }",
82+
])('SpreadAssignment "%s"', expression => {
83+
const code = `return JSONStringify(${expression});`;
84+
expect(JSON.parse(util.transpileAndExecute(code))).toEqual(eval(`(${expression})`));
85+
});
86+
87+
test("SpreadAssignment Destructure", () => {
88+
const code = `let obj = { x: 0, y: 1, z: 2 };`;
89+
const luaCode = `
90+
${code}
91+
return JSONStringify({ a: 0, ...obj, b: 1, c: 2 });`;
92+
const jsCode = `
93+
${code}
94+
({ a: 0, ...obj, b: 1, c: 2 })`;
95+
expect(JSON.parse(util.transpileAndExecute(luaCode))).toStrictEqual(eval(jsCode));
96+
});
97+
98+
test("SpreadAssignment No Mutation", () => {
99+
const code = `
100+
const obj: { x: number, y: number, z?: number } = { x: 0, y: 1 };
101+
const merge = { ...obj, z: 2 };
102+
return obj.z;`;
103+
expect(util.transpileAndExecute(code)).toBe(undefined);
104+
});
105+
106+
test.each([
107+
"function spread() { return [0, 1, 2] } const object = { ...spread() };",
108+
"const object = { ...[0, 1, 2] };",
109+
])('SpreadAssignment Array "%s"', expressionToCreateObject => {
110+
const code = `
111+
${expressionToCreateObject}
112+
return JSONStringify([object[0], object[1], object[2]]);`;
113+
expect(JSON.parse(util.transpileAndExecute(code))).toEqual([0, 1, 2]);
114+
});

0 commit comments

Comments
 (0)