ladybird/Tests/LibJS/Runtime/syntax/destructuring-assignment.js
Andreas Kling c23b944340 LibJS: Reject parenthesized nested assignment patterns
Reject parenthesized object and array literals when a destructuring
assignment element is refined into a nested assignment pattern. These
grouped forms do not cover AssignmentPattern, while parenthesized simple
assignment targets such as identifiers and member expressions remain
valid.

Add local syntax coverage for the invalid nested patterns and the valid
simple target cases.
2026-05-22 01:56:57 +02:00

352 lines
9.8 KiB
JavaScript

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);
});
});