/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ import mapExpression from "../mapExpression"; import { format } from "prettier"; import cases from "jest-in-case"; function test({ expression, newExpression, bindings, mappings, shouldMapBindings, expectedMapped, parseExpression = true, }) { const res = mapExpression(expression, mappings, bindings, shouldMapBindings); if (parseExpression) { expect( format(res.expression, { parser: "babel", }) ).toEqual(format(newExpression, { parser: "babel" })); } else { expect(res.expression).toEqual(newExpression); } expect(res.mapped).toEqual(expectedMapped); } function formatAwait(body) { return `(async () => { ${body} })();`; } describe("mapExpression", () => { cases("mapExpressions", test, [ { name: "await", expression: "await a()", newExpression: formatAwait("return a()"), bindings: [], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (multiple statements)", expression: "const x = await a(); x + x", newExpression: formatAwait("self.x = await a(); return x + x;"), bindings: [], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: true, originalExpression: false, }, }, { name: "await (inner)", expression: "async () => await a();", newExpression: "async () => await a();", bindings: [], mappings: {}, shouldMapBindings: true, expectedMapped: { await: false, bindings: false, originalExpression: false, }, }, { name: "await (multiple awaits)", expression: "const x = await a(); await b(x)", newExpression: formatAwait("self.x = await a(); return b(x);"), bindings: [], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: true, originalExpression: false, }, }, { name: "await (assignment)", expression: "let x = await sleep(100, 2)", newExpression: formatAwait("return (self.x = await sleep(100, 2))"), bindings: [], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: true, originalExpression: false, }, }, { name: "await (destructuring)", expression: "const { a, c: y } = await b()", newExpression: formatAwait( "return ({ a: self.a, c: self.y } = await b())" ), bindings: [], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: true, originalExpression: false, }, }, { name: "await (array destructuring)", expression: "const [a, y] = await b();", newExpression: formatAwait("return ([self.a, self.y] = await b())"), bindings: [], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: true, originalExpression: false, }, }, { name: "await (mixed destructuring)", expression: "const [{ a }] = await b();", newExpression: formatAwait("return ([{ a: self.a }] = await b())"), bindings: [], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: true, originalExpression: false, }, }, { name: "await (destructuring, multiple statements)", expression: "const { a, c: y } = await b(), { x } = await y()", newExpression: formatAwait(` ({ a: self.a, c: self.y } = await b()) return ({ x: self.x } = await y()); `), bindings: [], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: true, originalExpression: false, }, }, { name: "await (destructuring, bindings)", expression: "const { a, c: y } = await b();", newExpression: formatAwait("return ({ a, c: y } = await b())"), bindings: ["a", "y"], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: true, originalExpression: false, }, }, { name: "await (array destructuring, bindings)", expression: "const [a, y] = await b();", newExpression: formatAwait("return ([a, y] = await b())"), bindings: ["a", "y"], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: true, originalExpression: false, }, }, { name: "await (mixed destructuring, bindings)", expression: "const [{ a }] = await b();", newExpression: formatAwait("return ([{ a }] = await b())"), bindings: ["a"], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: true, originalExpression: false, }, }, { name: "await (destructuring with defaults, bindings)", expression: "const { c, a = 5 } = await b();", newExpression: formatAwait("return ({ c: self.c, a = 5 } = await b())"), bindings: ["a", "y"], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: true, originalExpression: false, }, }, { name: "await (array destructuring with defaults, bindings)", expression: "const [a, y = 10] = await b();", newExpression: formatAwait("return ([a, y = 10] = await b())"), bindings: ["a", "y"], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: true, originalExpression: false, }, }, { name: "await (mixed destructuring with defaults, bindings)", expression: "const [{ c = 5 }, a = 5] = await b();", newExpression: formatAwait( "return ([ { c: self.c = 5 }, a = 5] = await b())" ), bindings: ["a"], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: true, originalExpression: false, }, }, { name: "await (nested destructuring, bindings)", expression: "const { a, c: { y } } = await b();", newExpression: formatAwait(` return ({ a, c: { y } } = await b()); `), bindings: ["a", "y"], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: true, originalExpression: false, }, }, { name: "await (nested destructuring with defaults)", expression: "const { a, c: { y = 5 } = {} } = await b();", newExpression: formatAwait(`return ({ a: self.a, c: { y: self.y = 5 } = {}, } = await b()); `), bindings: [], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: true, originalExpression: false, }, }, { name: "await (very nested destructuring with defaults)", expression: "const { a, c: { y: { z = 10, b } = { b: 5 } } } = await b();", newExpression: formatAwait(` return ({ a: self.a, c: { y: { z: self.z = 10, b: self.b } = { b: 5 } } } = await b()); `), bindings: [], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: true, originalExpression: false, }, }, { name: "await (with SyntaxError)", expression: "await new Promise())", newExpression: formatAwait("await new Promise())"), parseExpression: false, bindings: [], mappings: {}, shouldMapBindings: true, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (no bindings, let assignment)", expression: "let a = await 123;", newExpression: `let a; (async () => { return a = await 123; })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (no bindings, var assignment)", expression: "var a = await 123;", newExpression: `var a; (async () => { return a = await 123; })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (no bindings, const assignment)", expression: "const a = await 123;", newExpression: `let a; (async () => { return a = await 123; })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (no bindings, multiple assignments)", expression: "let a = 1, b, c = 3; b = await 123; a + b + c", newExpression: `let a, b, c; (async () => { a = 1; c = 3; b = await 123; return a + b + c; })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (no bindings, object destructuring)", expression: "let {a, b, c} = await x;", newExpression: `let a, b, c; (async () => { return ({a, b, c} = await x); })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (no bindings, object destructuring with rest)", expression: "let {a, ...rest} = await x;", newExpression: `let a, rest; (async () => { return ({a, ...rest} = await x); })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (no bindings, object destructuring with renaming and default)", expression: "let {a: hello, b, c: world, d: $ = 4} = await x;", newExpression: `let hello, b, world, $; (async () => { return ({a: hello, b, c: world, d: $ = 4} = await x); })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (no bindings, nested object destructuring + renaming + default)", expression: `let { a: hello, c: { y: { z = 10, b: bill, d: [e, f = 20] }} } = await x; z;`, newExpression: `let hello, z, bill, e, f; (async () => { ({ a: hello, c: { y: { z = 10, b: bill, d: [e, f = 20] }}} = await x); return z; })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (no bindings, array destructuring)", expression: "let [a, b, c] = await x; c;", newExpression: `let a, b, c; (async () => { [a, b, c] = await x; return c; })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (no bindings, array destructuring with default)", expression: "let [a, b = 1, c = 2] = await x; c;", newExpression: `let a, b, c; (async () => { [a, b = 1, c = 2] = await x; return c; })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (no bindings, array destructuring with default and rest)", expression: "let [a, b = 1, c = 2, ...rest] = await x; rest;", newExpression: `let a, b, c, rest; (async () => { [a, b = 1, c = 2, ...rest] = await x; return rest; })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (no bindings, nested array destructuring with default)", expression: "let [a, b = 1, [c = 2, [d = 3, e = 4]]] = await x; c;", newExpression: `let a, b, c, d, e; (async () => { [a, b = 1, [c = 2, [d = 3, e = 4]]] = await x; return c; })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (no bindings, dynamic import)", expression: ` var {rainbowLog} = await import("./cool-module.js"); rainbowLog("dynamic");`, newExpression: `var rainbowLog; (async () => { ({rainbowLog} = await import("./cool-module.js")); return rainbowLog("dynamic"); })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (nullish coalesce operator)", expression: "await x; true ?? false", newExpression: `(async () => { await x; return true ?? false; })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (optional chaining operator)", expression: "await x; x?.y?.z", newExpression: `(async () => { await x; return x?.y?.z; })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (async function declaration with nullish coalesce operator)", expression: "async function coalesce(x) { await x; return x ?? false; }", newExpression: "async function coalesce(x) { await x; return x ?? false; }", shouldMapBindings: false, expectedMapped: { await: false, bindings: false, originalExpression: false, }, }, { name: "await (async function declaration with optional chaining operator)", expression: "async function chain(x) { await x; return x?.y?.z; }", newExpression: "async function chain(x) { await x; return x?.y?.z; }", shouldMapBindings: false, expectedMapped: { await: false, bindings: false, originalExpression: false, }, }, { // check that variable declaration in for loop is not put outside of the async iife name: "await (for loop)", expression: "for (let i=0;i<2;i++) {}; var b = await 1;", newExpression: `var b; (async () => { for (let i=0;i<2;i++) {} return (b = await 1); })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { // check that variable declaration in for-in loop is not put outside of the async iife name: "await (for..in loop)", expression: "for (let i in {}) {}; var b = await 1;", newExpression: `var b; (async () => { for (let i in {}) {} return (b = await 1); })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { // check that variable declaration in for-of loop is not put outside of the async iife name: "await (for..of loop)", expression: "for (let i of []) {}; var b = await 1;", newExpression: `var b; (async () => { for (let i of []) {} return (b = await 1); })()`, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (if condition)", expression: "if (await true) console.log(1);", newExpression: formatAwait("if (await true) console.log(1);"), shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (non-expression final statement: bug 1851759)", expression: `j = { "foo": 1, "bar": 2 }; await 42; for (var k in j) { console.log(k); }`, newExpression: formatAwait(` j = { foo: 1, bar: 2, }; await 42; for (var k in j) { console.log(k); }`), shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "simple", expression: "a", newExpression: "a", bindings: [], mappings: {}, shouldMapBindings: true, expectedMapped: { await: false, bindings: false, originalExpression: false, }, }, { name: "mappings", expression: "a", newExpression: "_a", bindings: [], mappings: { a: "_a", }, shouldMapBindings: true, expectedMapped: { await: false, bindings: false, originalExpression: true, }, }, { name: "declaration", expression: "var a = 3;", newExpression: "self.a = 3", bindings: [], mappings: {}, shouldMapBindings: true, expectedMapped: { await: false, bindings: true, originalExpression: false, }, }, { name: "declaration + destructuring", expression: "var { a } = { a: 3 };", newExpression: "({ a: self.a } = {\n a: 3 \n})", bindings: [], mappings: {}, shouldMapBindings: true, expectedMapped: { await: false, bindings: true, originalExpression: false, }, }, { name: "bindings", expression: "var a = 3;", newExpression: "a = 3", bindings: ["a"], mappings: {}, shouldMapBindings: true, expectedMapped: { await: false, bindings: true, originalExpression: false, }, }, { name: "bindings + destructuring", expression: "var { a } = { a: 3 };", newExpression: "({ a } = { \n a: 3 \n })", bindings: ["a"], mappings: {}, shouldMapBindings: true, expectedMapped: { await: false, bindings: true, originalExpression: false, }, }, { name: "bindings + destructuring + rest", expression: "var { a, ...foo } = {}", newExpression: "({ a, ...self.foo } = {})", bindings: ["a"], mappings: {}, shouldMapBindings: true, expectedMapped: { await: false, bindings: true, originalExpression: false, }, }, { name: "bindings + array destructuring + rest", expression: "var [a, ...foo] = []", newExpression: "([a, ...self.foo] = [])", bindings: ["a"], mappings: {}, shouldMapBindings: true, expectedMapped: { await: false, bindings: true, originalExpression: false, }, }, { name: "bindings + mappings", expression: "a = 3;", newExpression: "self.a = 3", bindings: ["_a"], mappings: { a: "_a" }, shouldMapBindings: true, expectedMapped: { await: false, bindings: true, originalExpression: false, }, }, { name: "bindings + mappings + destructuring", expression: "var { a } = { a: 4 }", newExpression: "({ a: self.a } = {\n a: 4 \n})", bindings: ["_a"], mappings: { a: "_a" }, shouldMapBindings: true, expectedMapped: { await: false, bindings: true, originalExpression: false, }, }, { name: "bindings without mappings", expression: "a = 3;", newExpression: "a = 3", bindings: [], mappings: { a: "_a" }, shouldMapBindings: false, expectedMapped: { await: false, bindings: false, originalExpression: false, }, }, { name: "object destructuring + bindings without mappings", expression: "({ a } = {});", newExpression: "({ a: _a } = {})", bindings: [], mappings: { a: "_a" }, shouldMapBindings: false, expectedMapped: { await: false, bindings: false, originalExpression: true, }, }, { name: "await (inside top-level block)", expression: "{ await 1 }", newExpression: formatAwait("{ await 1 }"), bindings: [], mappings: {}, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (inside top-level try block)", expression: "try { await 1 } catch (e) {}", newExpression: formatAwait("try { await 1; } catch (e) {}"), bindings: [], mappings: {}, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (inside top-level catch block)", expression: "try { throw 'Error' } catch (e) { await 2; }", newExpression: formatAwait( "try { throw 'Error' } catch (e) { await 2; }" ), bindings: [], mappings: {}, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (inside top-level if-else block)", expression: "if (false) { await 1; } else { await 2; }", newExpression: formatAwait(`if (false) { await 1; } else { await 2; }`), bindings: [], mappings: {}, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (in method name of a class)", expression: "class A { [await 0]() {} }", newExpression: formatAwait(`class A { [await 0]() {} }`), bindings: [], mappings: {}, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, { name: "await (for await)", expression: "for await (const num of [1,2,3]);", newExpression: formatAwait(`for await (const num of [1,2,3]);`), bindings: [], mappings: {}, shouldMapBindings: false, expectedMapped: { await: true, bindings: false, originalExpression: false, }, }, ]); });