describe("parsing", () => { test("single name", () => { expect(`var { a } = {};`).toEval(); expect(`const { a } = {};`).toEval(); expect(`let { a } = {};`).toEval(); }); test("single name with rest values", () => { expect(`var { a, ...rest } = {};`).toEval(); expect(`const { a, ...rest } = {};`).toEval(); expect(`let { a, ...rest } = {};`).toEval(); }); test("single aliased name", () => { expect(`var { a: b } = {};`).toEval(); expect(`const { a: b } = {};`).toEval(); expect(`let { a: b } = {};`).toEval(); }); test("single aliased name with rest values", () => { expect(`var { a: b, ...rest } = {};`).toEval(); expect(`const { a: b, ...rest } = {};`).toEval(); expect(`let { a: b, ...rest } = {};`).toEval(); }); test("array destructuring patterns", () => { expect(`var [ a ] = [];`).toEval(); expect(`const [ a ] = [];`).toEval(); expect(`let [ a ] = [];`).toEval(); }); test("destructuring assignment with rest assignments, array patterns", () => { expect(`var [ a, ...rest ] = [];`).toEval(); expect(`const [ a, ...rest ] = [];`).toEval(); expect(`let [ a, ...rest ] = [];`).toEval(); }); test("destructuring assignment with rest names, array patterns with recursive patterns", () => { expect(`var [ a, [ ...rest ] ] = [];`).toEval(); expect(`const [ a, [ ...rest ] ] = [];`).toEval(); expect(`let [ a, [ ...rest ] ] = [];`).toEval(); }); test("destructuring assignment with rest names, array patterns with recursive patterns 2", () => { expect(`var [ a, [ ...{length} ] ] = [];`).toEval(); expect(`const [ a, [ ...{length} ] ] = [];`).toEval(); expect(`let [ a, [ ...{length} ] ] = [];`).toEval(); }); test("function parameters cannot use member expressions", () => { expect("function f([a.b]) {}").not.toEval(); expect("function f([b[0]]) {}").not.toEval(); expect("function f({c:a.b}) {}").not.toEval(); expect("function f({a:b[0]}) {}").not.toEval(); expect("([a.b]) => 1").not.toEval(); expect("([b[0]]) => 2").not.toEval(); expect("({c:a.b}) => 3").not.toEval(); expect("({a:b[0]}) => 4").not.toEval(); }); test("assignment patterns cannot use parenthesized nested object or array patterns", () => { expect("var a, b; ({ a: ({ b: b }) } = { a: { b: 42 } });").not.toEval(); expect("var a, b; ({ a: ([b]) } = { a: [42] });").not.toEval(); expect("var a, b; ({ a: (a), b } = { a: 1, b: 2 });").toEval(); expect("var a, b; [(a), b] = [1, 2];").toEval(); }); }); describe("evaluating", () => { test("single name", () => { let o = { a: 1 }; { var { a } = o; expect(a).toBe(o.a); } { const { a } = o; expect(a).toBe(o.a); } { let { a } = o; expect(a).toBe(o.a); } }); test("single name with rest values", () => { let o = { a: 1, b: 2 }; { var { a, ...b } = o; expect(a).toBe(o.a); expect(b).toEqual({ b: 2 }); } { const { a, ...b } = o; expect(a).toBe(o.a); expect(b).toEqual({ b: 2 }); } { let { a, ...b } = o; expect(a).toBe(o.a); expect(b).toEqual({ b: 2 }); } }); test("single aliased name", () => { let o = { a: 1 }; { var { a: x } = o; expect(x).toBe(o.a); } { const { a: x } = o; expect(x).toBe(o.a); } { let { a: x } = o; expect(x).toBe(o.a); } }); test("single aliased name with rest values", () => { let o = { a: 1, b: 2 }; { var { a: x, ...b } = o; expect(x).toBe(o.a); expect(b).toEqual({ b: 2 }); } { const { a: x, ...b } = o; expect(x).toBe(o.a); expect(b).toEqual({ b: 2 }); } { let { a: x, ...b } = o; expect(x).toBe(o.a); expect(b).toEqual({ b: 2 }); } }); test("array patterns", () => { let o = [1, 2, 3, 4]; { var [a, b] = o; expect(a).toBe(o[0]); expect(b).toBe(o[1]); } { const [a, b] = o; expect(a).toBe(o[0]); expect(b).toBe(o[1]); } { let [a, b] = o; expect(a).toBe(o[0]); expect(b).toBe(o[1]); } }); test("destructuring assignment with rest names, array patterns", () => { let o = [1, 2, 3, 4]; { var [a, ...b] = o; expect(a).toBe(o[0]); expect(b).toEqual(o.slice(1)); } { const [a, ...b] = o; expect(a).toBe(o[0]); expect(b).toEqual(o.slice(1)); } { let [a, ...b] = o; expect(a).toBe(o[0]); expect(b).toEqual(o.slice(1)); } }); test("destructuring assignment with rest names, array patterns with recursive patterns", () => { let o = [1, [2, 3, 4]]; { var [a, [b, ...c]] = o; expect(a).toBe(o[0]); expect(b).toBe(o[1][0]); expect(c).toEqual(o[1].slice(1)); } { const [a, [b, ...c]] = o; expect(a).toBe(o[0]); expect(b).toBe(o[1][0]); expect(c).toEqual(o[1].slice(1)); } { let [a, [b, ...c]] = o; expect(a).toBe(o[0]); expect(b).toBe(o[1][0]); expect(c).toEqual(o[1].slice(1)); } }); test("destructuring assignment with rest names, array patterns with recursive patterns 2", () => { let o = [1, [2, 3, 4]]; { var [a, [...{ length }]] = o; expect(a).toBe(o[0]); expect(length).toBe(o[1].length); } { const [a, [...{ length }]] = o; expect(a).toBe(o[0]); expect(length).toBe(o[1].length); } { let [a, [...{ length }]] = o; expect(a).toBe(o[0]); expect(length).toBe(o[1].length); } { expect(() => { let [a, b, [...{ length }]] = o; }).toThrowWithMessage(TypeError, "ToObject on null or undefined"); } }); test("patterns with default", () => { let o = { a: 1 }; let { x = "foo", a = "bar" } = o; expect(x).toBe("foo"); expect(a).toBe(o.a); }); test("can use big int values as number-like properties", () => { let o = { "99999999999999999": 1 }; let { 123n: a = "foo", 99999999999999999n: b = "bar" } = o; expect(a).toBe("foo"); expect(b).toBe(1); }); }); describe("array iterator closing", () => { test("target evaluation throwing closes without stepping", () => { let nextCount = 0; let returnCount = 0; let thrown = new Error("target"); let iterator = { next() { ++nextCount; return { done: true }; }, return() { ++returnCount; return {}; }, }; let iterable = { [Symbol.iterator]() { return iterator; }, }; let thrower = () => { throw thrown; }; expect(() => { (0, ([{}[thrower()]] = iterable)); }).toThrow(thrown); expect(nextCount).toBe(0); expect(returnCount).toBe(1); }); test("iterator next throwing is not closed", () => { let returnCount = 0; let thrown = new Error("next"); let iterator = { next() { throw thrown; }, return() { ++returnCount; return {}; }, }; let iterable = { [Symbol.iterator]() { return iterator; }, }; expect(() => { let [value] = iterable; }).toThrow(thrown); expect(returnCount).toBe(0); }); test("default initializer throwing closes and preserves the original throw completion", () => { let returnCount = 0; let thrown = new Error("default"); let iterator = { next() { return { done: false, value: undefined }; }, return() { ++returnCount; return null; }, }; let iterable = { [Symbol.iterator]() { return iterator; }, }; let thrower = () => { throw thrown; }; expect(() => { [{} = thrower()] = iterable; }).toThrow(thrown); expect(returnCount).toBe(1); }); test("generator return through rest target closes the iterator", () => { let returnCount = 0; let iterator = { next() { throw new Error("unreachable"); }, return() { ++returnCount; return {}; }, }; let iterable = { [Symbol.iterator]() { return iterator; }, }; function* generator() { [...{}[yield]] = iterable; } let it = generator(); it.next(); let result = it.return(444); expect(returnCount).toBe(1); expect(result.value).toBe(444); expect(result.done).toBe(true); }); });