// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// tslint:disable

import * as assert from "assert";
import { EOL } from "os";
import { runtime } from "../../index";
import * as pulumi from "../../index";
import { output } from "../../resource";
import { assertAsyncThrows, asyncTest } from "../util";
import * as typescript from "typescript";

interface ClosureCase {
    pre?: () => void;               // an optional function to run before this case.
    title: string;                  // a title banner for the test case.
    func?: Function;                // the function whose body and closure to serialize.
    factoryFunc?: Function;         // the function whose body and closure to serialize (as a factory).
    expectText?: string;            // optionally also validate the serialization to JavaScript text.
    expectPackages?: Set<string>;   // optionally also validate the packages required by the JavaScript text.
    error?: string;                 // error message we expect to be thrown if we are unable to serialize closure.
    afters?: ClosureCase[];         // an optional list of test cases to run afterwards.
}

export const exportedValue = 42;

// This group of tests ensure that we serialize closures properly.
describe("closure", () => {
    const cases: ClosureCase[] = [];

    cases.push({
        title: "Empty function closure",
        func: function () { },
        expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({  }) {

return function () { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    cases.push({
        title: "Empty named function",
        func: function f() { },
        expectText: `exports.handler = __f;

function __f() {
  return (function() {
    with({ f: __f }) {

return function /*f*/() { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    cases.push({
        title: "Named function with self-reference",
        func: function f() { f(); },
        expectText: `exports.handler = __f;

function __f() {
  return (function() {
    with({ f: __f }) {

return function /*f*/() { f(); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    cases.push({
        title: "Function closure with this capture",
        func: function () { console.log(this); },
        expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({  }) {

return function () { console.log(this); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    cases.push({
        title: "Function closure with this and arguments capture",
        func: function () { console.log(this + arguments); },
        expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({  }) {

return function () { console.log(this + arguments); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    cases.push({
        title: "Empty arrow closure",
        func: () => { },
        expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({  }) {

return () => { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    cases.push({
        title: "Arrow closure with this capture",
        func: () => { console.log(this); },
        expectText: undefined,
        error:
`Error serializing function 'func': closure.spec.js(0,0)

function 'func': closure.spec.js(0,0): which could not be serialized because
  arrow function captured 'this'. Assign 'this' to another name outside function and capture that.

Function code:
  () => { console.log(this); }
`,
    });

    const awaiterCode =
`
function __f1() {
  return (function() {
    with({  }) {

return function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`;

    cases.push({
        title: "Async lambda that does not capture this",
        func: async () => { },
        expectText: `exports.handler = __f0;
${awaiterCode}
function __f0() {
  return (function() {
    with({ __awaiter: __f1 }) {

return () => __awaiter(this, void 0, void 0, function* () { });

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    cases.push({
        title: "Async lambda that does capture this",
        func: async () => { console.log(this); },
        expectText: undefined,
        error: `Error serializing function 'func': closure.spec.js(0,0)

function 'func': closure.spec.js(0,0): which could not be serialized because
  arrow function captured 'this'. Assign 'this' to another name outside function and capture that.

Function code:
  () => __awaiter(this, void 0, void 0, function* () { console.log(this); })
`,
    });

    cases.push({
        title: "Async function that does not capture this",
        func: async function() { },
        expectText: `exports.handler = __f0;
${awaiterCode}
function __f0() {
  return (function() {
    with({ __awaiter: __f1 }) {

return function () {
            return __awaiter(this, void 0, void 0, function* () { });
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    cases.push({
        title: "Async function that does capture this",
        func: async function () { console.log(this); },
        expectText: `exports.handler = __f0;
${awaiterCode}
function __f0() {
  return (function() {
    with({ __awaiter: __f1 }) {

return function () {
            return __awaiter(this, void 0, void 0, function* () { console.log(this); });
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    cases.push({
        title: "Arrow closure with this and arguments capture",
        func: (function() { return () => { console.log(this + arguments); } }).apply(this, [0, 1]),
        expectText: undefined,
        error: `Error serializing function '<anonymous>': closure.spec.js(0,0)

function '<anonymous>': closure.spec.js(0,0): which could not be serialized because
  arrow function captured 'this'. Assign 'this' to another name outside function and capture that.

Function code:
  () => { console.log(this + arguments); }
`,
    });

    cases.push({
        title: "Arrow closure with this capture inside function closure",
        func: function () { () => { console.log(this); } },
        expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({  }) {

return function () { () => { console.log(this); }; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    cases.push({
        title: "Arrow closure with this and arguments capture inside function closure",
        func: function () { () => { console.log(this + arguments); } },
        expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({  }) {

return function () { () => { console.log(this + arguments); }; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    {
        class Task {
            run: any;
            constructor() {
                this.run = async function() { };
            }
        }

        const task = new Task();

        cases.push({
            title: "Invocation of async function that does not capture this #1",
            func: async function() { await task.run(); },
            expectText: `exports.handler = __f0;

var __task = {run: __f2};

function __f1() {
  return (function() {
    with({  }) {

return function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({ __awaiter: __f1 }) {

return function () {
                    return __awaiter(this, void 0, void 0, function* () { });
                };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ __awaiter: __f1, task: __task }) {

return function () {
                return __awaiter(this, void 0, void 0, function* () { yield task.run(); });
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        class Task {
            run: any;
            constructor() {
                this.run = async function() { console.log(this); };
            }
        }

        const task = new Task();

        cases.push({
            title: "Invocation of async function that does capture this #1",
            func: async function() { await task.run(); },
            expectText: `exports.handler = __f0;

var __task_proto = {};
Object.defineProperty(__task_proto, "constructor", { configurable: true, writable: true, value: __f2 });
var __task = Object.create(__task_proto);
__task.run = __f3;

function __f1() {
  return (function() {
    with({  }) {

return function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({ __awaiter: __f1 }) {

return function /*constructor*/() {
                this.run = function () {
                    return __awaiter(this, void 0, void 0, function* () { console.log(this); });
                };
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f3() {
  return (function() {
    with({ __awaiter: __f1 }) {

return function () {
                    return __awaiter(this, void 0, void 0, function* () { console.log(this); });
                };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ __awaiter: __f1, task: __task }) {

return function () {
                return __awaiter(this, void 0, void 0, function* () { yield task.run(); });
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        class Task {
            run: any;
            constructor() {
                this.run = async () => { };
            }
        }

        const task = new Task();

        cases.push({
            title: "Invocation of async lambda that does not capture this #1",
            func: async function() { await task.run(); },
            expectText: `exports.handler = __f0;

var __task = {run: __f2};

function __f1() {
  return (function() {
    with({  }) {

return function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({ __awaiter: __f1 }) {

return () => __awaiter(this, void 0, void 0, function* () { });

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ __awaiter: __f1, task: __task }) {

return function () {
                return __awaiter(this, void 0, void 0, function* () { yield task.run(); });
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        class Task {
            run: any;
            constructor() {
                this.run = async () => { console.log(this); };
            }
        }

        const task = new Task();

        cases.push({
            title: "Invocation of async lambda that capture this #1",
            func: async function() { await task.run(); },
            expectText: undefined,
            error: `Error serializing function 'func': closure.spec.js(0,0)

function 'func': closure.spec.js(0,0): captured
  variable 'task' which indirectly referenced
    function '<anonymous>': closure.spec.js(0,0): which could not be serialized because
      arrow function captured 'this'. Assign 'this' to another name outside function and capture that.

Function code:
  () => __awaiter(this, void 0, void 0, function* () { console.log(this); })
`,
        });
    }

    cases.push({
        title: "Empty function closure w/ args",
        func: function (x: any, y: any, z: any) { },
        expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({  }) {

return function (x, y, z) { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    cases.push({
        title: "Empty arrow closure w/ args",
        func: (x: any, y: any, z: any) => { },
        expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({  }) {

return (x, y, z) => { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    // Serialize captures.
    cases.push({
        title: "Doesn't serialize global captures",
        func: () => { console.log("Just a global object reference"); },
        expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({  }) {

return () => { console.log("Just a global object reference"); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });
    {
        const wcap = "foo";
        const xcap = 97;
        const ycap = [ true, -1, "yup" ];
        const zcap = {
            a: "a",
            b: false,
            c: [ 0 ],
        };
        cases.push({
            title: "Serializes basic captures",
            func: () => { console.log(wcap + `${xcap}` + ycap.length + eval(zcap.a + zcap.b + zcap.c)); },
            expectText: `exports.handler = __f0;

var __ycap = [true, -1, "yup"];
var __zcap = {};
__zcap.a = "a";
__zcap.b = false;
var __zcap_c = [0];
__zcap.c = __zcap_c;

function __f0() {
  return (function() {
    with({ wcap: "foo", xcap: 97, ycap: __ycap, zcap: __zcap }) {

return () => { console.log(wcap + \`\${xcap}\` + ycap.length + eval(zcap.a + zcap.b + zcap.c)); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }
    {
        let nocap1 = 1, nocap2 = 2, nocap3 = 3, nocap4 = 4, nocap5 = 5, nocap6 = 6, nocap7 = 7;
        let nocap8 = 8, nocap9 = 9, nocap10 = 10;
        let cap1 = 100, cap2 = 200, cap3 = 300, cap4 = 400, cap5 = 500, cap6 = 600, cap7 = 700;
        let cap8 = 800;

        const functext = `(nocap1, nocap2) => {
    let zz = nocap1 + nocap2; // not a capture: args
    let yy = nocap3; // not a capture: var later on
    if (zz) {
        zz += cap1; // true capture
        let cap1 = 9; // because let is properly scoped
        zz += nocap4; // not a capture
        var nocap4 = 7; // because var is function scoped
        zz += cap2; // true capture
        const cap2 = 33;
        var nocap3 = 8; // block the above capture
    }
    let f1 = (nocap5) => {
        yy += nocap5; // not a capture: args
        cap3++; // capture
    };
    let f2 = (function (nocap6) {
        zz += nocap6; // not a capture: args
        if (cap4) { // capture
            yy = 0;
        }
    });
    let www = nocap7(); // not a capture; it is defined below
    if (true) {
        function nocap7() {
        }
    }
    let [{t: [nocap8]},,nocap9 = "hello",...nocap10] = [{t: [true]},null,undefined,1,2];
    let vvv = [nocap8, nocap9, nocap10]; // not a capture; declarations from destructuring
    let aaa = { // captures in property and method declarations
        [cap5]: cap6,
        [cap7]() {
            cap8
        }
    }
}`;
        cases.push({
            title: "Doesn't serialize non-free variables (but retains frees)",
            func: eval(functext),
            expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({ cap1: 100, cap2: 200, cap3: 300, cap4: 400, cap5: 500, cap6: 600, cap7: 700, cap8: 800 }) {

return (nocap1, nocap2) => {
    let zz = nocap1 + nocap2; // not a capture: args
    let yy = nocap3; // not a capture: var later on
    if (zz) {
        zz += cap1; // true capture
        let cap1 = 9; // because let is properly scoped
        zz += nocap4; // not a capture
        var nocap4 = 7; // because var is function scoped
        zz += cap2; // true capture
        const cap2 = 33;
        var nocap3 = 8; // block the above capture
    }
    let f1 = (nocap5) => {
        yy += nocap5; // not a capture: args
        cap3++; // capture
    };
    let f2 = (function (nocap6) {
        zz += nocap6; // not a capture: args
        if (cap4) { // capture
            yy = 0;
        }
    });
    let www = nocap7(); // not a capture; it is defined below
    if (true) {
        function nocap7() {
        }
    }
    let [{t: [nocap8]},,nocap9 = "hello",...nocap10] = [{t: [true]},null,undefined,1,2];
    let vvv = [nocap8, nocap9, nocap10]; // not a capture; declarations from destructuring
    let aaa = { // captures in property and method declarations
        [cap5]: cap6,
        [cap7]() {
            cap8
        }
    }
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }
    {
        let nocap1 = 1;
        let cap1 = 100;

        cases.push({
            title: "Complex capturing cases #1",
            func: () => {
                // cap1 is captured here.
                // nocap1 introduces a new variable that shadows the outer one.
                let [nocap1 = cap1] = [];
                console.log(nocap1);
            },
            expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({ cap1: 100 }) {

return () => {
                // cap1 is captured here.
                // nocap1 introduces a new variable that shadows the outer one.
                let [nocap1 = cap1] = [];
                console.log(nocap1);
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }
    {
        let nocap1 = 1;
        let cap1 = 100;

        cases.push({
            title: "Complex capturing cases #2",
            func: () => {
    // cap1 is captured here.
    // nocap1 introduces a new variable that shadows the outer one.
    let {nocap1 = cap1} = {};
    console.log(nocap1);
},
            expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({ cap1: 100 }) {

return () => {
                // cap1 is captured here.
                // nocap1 introduces a new variable that shadows the outer one.
                let { nocap1 = cap1 } = {};
                console.log(nocap1);
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }
    {
        let nocap1 = 1;
        let cap1 = 100;

        cases.push({
            title: "Complex capturing cases #3",
            func: () => {
    // cap1 is captured here.
    // nocap1 introduces a new variable that shadows the outer one.
    let {x: nocap1 = cap1} = {};
    console.log(nocap1);
},
            expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({ cap1: 100 }) {

return () => {
                // cap1 is captured here.
                // nocap1 introduces a new variable that shadows the outer one.
                let { x: nocap1 = cap1 } = {};
                console.log(nocap1);
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    cases.push({
        title: "Don't capture built-ins",
        func: () => { let x: any = eval("undefined + null + NaN + Infinity + __filename"); require("os"); },
        expectPackages: new Set(),
        expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({  }) {

return () => { let x = eval("undefined + null + NaN + Infinity + __filename"); require("os"); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    {
        const os = require("os");

        cases.push({
            title: "Capture built in module by ref",
            func: () => os,
            expectPackages: new Set(["os"]),
            expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({ os: require("os") }) {

return () => os;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const os = require("os");

        cases.push({
            title: "Wrapped lambda function",
            func: (a: any,
                   b: any,
                   c: any) => {
                       const v =
                           os;
                       return { v };
                   },
            expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({ os: require("os") }) {

return (a, b, c) => {
                const v = os;
                return { v };
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const os = require("os");
        function wrap(handler: Function) {
            return () => handler;
        }

        const func = wrap(() => os);

        cases.push({
            title: "Capture module through indirect function references",
            func: func,
            expectText: `exports.handler = __f0;

function __f1() {
  return (function() {
    with({ os: require("os") }) {

return () => os;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ handler: __f1 }) {

return () => handler;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const util = require("../util");
        cases.push({
            title: "Capture user-defined module by value",
            func: () => util,
            expectText: `exports.handler = __f0;

var __util = {};
Object.defineProperty(__util, "__esModule", { value: true });
__util.asyncTest = __asyncTest;
__util.assertAsyncThrows = __assertAsyncThrows;

function __f1() {
  return (function() {
    with({  }) {

return function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __asyncTest() {
  return (function() {
    with({ __awaiter: __f1, asyncTest: __asyncTest }) {

return function /*asyncTest*/(test) {
    return (done) => {
        const go = () => __awaiter(this, void 0, void 0, function* () {
            let caught;
            try {
                yield test();
            }
            catch (err) {
                caught = err;
            }
            finally {
                done(caught);
            }
        });
        go();
    };
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __assertAsyncThrows() {
  return (function() {
    with({ __awaiter: __f1, assert: require("assert"), assertAsyncThrows: __assertAsyncThrows }) {

return function /*assertAsyncThrows*/(test) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            yield test();
        }
        catch (err) {
            return err.message;
        }
        assert(false, "Function was expected to throw, but didn't");
        return "";
    });
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ util: __util }) {

return () => util;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    cases.push({
        title: "Don't capture catch variables",
        func: () => { try { } catch (err) { console.log(err); } },
        expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({  }) {

return () => { try { }
        catch (err) {
            console.log(err);
        } };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    // Recursive function serialization.
    {
        const fff = "fff!";
        const ggg = "ggg!";
        const xcap = {
            fff: function () { console.log(fff); },
            ggg: () => { console.log(ggg); },
            zzz: {
                a: [ (a1: any, a2: any) => { console.log(a1 + a2); } ],
            },
        };
        const func = () => {
    xcap.fff();
    xcap.ggg();
    xcap.zzz.a[0]("x", "y");
};

        cases.push({
            title: "Serializes recursive function captures",
            func: func,
            expectText: `exports.handler = __f0;

var __xcap = {};
__xcap.fff = __f1;
__xcap.ggg = __f2;
var __xcap_zzz = {};
var __xcap_zzz_a = [__f3];
__xcap_zzz.a = __xcap_zzz_a;
__xcap.zzz = __xcap_zzz;

function __f1() {
  return (function() {
    with({ fff: "fff!" }) {

return function () { console.log(fff); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({ ggg: "ggg!" }) {

return () => { console.log(ggg); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f3() {
  return (function() {
    with({  }) {

return (a1, a2) => { console.log(a1 + a2); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ xcap: __xcap }) {

return () => {
            xcap.fff();
            xcap.ggg();
            xcap.zzz.a[0]("x", "y");
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        class CapCap {
            constructor() {
                (<any>this).x = 42;
                (<any>this).f = () => { console.log((<any>this).x); };
            }
        }

        const cap: any = new CapCap();

        cases.push({
            title: "Serializes `this` capturing arrow functions",
            func: cap.f,
            expectText: undefined,
            error: `Error serializing function '<anonymous>': closure.spec.js(0,0)

function '<anonymous>': closure.spec.js(0,0): which could not be serialized because
  arrow function captured 'this'. Assign 'this' to another name outside function and capture that.

Function code:
  () => { console.log(this.x); }
`,
        });
    }

    cases.push({
        title: "Don't serialize `this` in function expressions",
        func: function() { return this; },
        expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({  }) {

return function () { return this; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    {
        const mutable: any = {};
        cases.push({
            title: "Serialize mutable objects by value at the time of capture (pre-mutation)",
            func: function() { return mutable; },
            expectText: `exports.handler = __f0;

var __mutable = {};

function __f0() {
  return (function() {
    with({ mutable: __mutable }) {

return function () { return mutable; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
            afters: [{
                pre: () => { mutable.timesTheyAreAChangin = true; },
                title: "Serialize mutable objects by value at the time of capture (post-mutation)",
                func: function() { return mutable; },
                expectText: `exports.handler = __f0;

var __mutable = {timesTheyAreAChangin: true};

function __f0() {
  return (function() {
    with({ mutable: __mutable }) {

return function () { return mutable; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
            }],
        });
    }

    {
        const v = { d: output(4) };
        cases.push({
            title: "Output capture",
            func: function () { console.log(v); },
            expectText: `exports.handler = __f0;

var __v = {};
var __v_d_proto = {};
__f1.prototype = __v_d_proto;
Object.defineProperty(__v_d_proto, "constructor", { configurable: true, writable: true, value: __f1 });
Object.defineProperty(__v_d_proto, "apply", { configurable: true, writable: true, value: __f2 });
Object.defineProperty(__v_d_proto, "get", { configurable: true, writable: true, value: __f3 });
var __v_d = Object.create(__v_d_proto);
__v_d.value = 4;
__v.d = __v_d;

function __f1() {
  return (function() {
    with({  }) {

return function /*constructor*/(value) {
        this.value = value;
    };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({  }) {

return function /*apply*/(func) {
        throw new Error("'apply' is not allowed from inside a cloud-callback. Use 'get' to retrieve the value of this Output directly.");
    };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f3() {
  return (function() {
    with({  }) {

return function /*get*/() {
        return this.value;
    };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ v: __v }) {

return function () { console.log(v); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const v = {
            d1: output(4),
            d2: output("str"),
            d3: output(undefined),
            d4: output({ a: 1, b: true }),
        };
        cases.push({
            title: "Multiple output capture",
            func: function () { console.log(v); },
            expectText: `exports.handler = __f0;

var __v = {};
var __v_d1_proto = {};
__f1.prototype = __v_d1_proto;
Object.defineProperty(__v_d1_proto, "constructor", { configurable: true, writable: true, value: __f1 });
Object.defineProperty(__v_d1_proto, "apply", { configurable: true, writable: true, value: __f2 });
Object.defineProperty(__v_d1_proto, "get", { configurable: true, writable: true, value: __f3 });
var __v_d1 = Object.create(__v_d1_proto);
__v_d1.value = 4;
__v.d1 = __v_d1;
var __v_d2 = Object.create(__v_d1_proto);
__v_d2.value = "str";
__v.d2 = __v_d2;
var __v_d3 = Object.create(__v_d1_proto);
__v_d3.value = undefined;
__v.d3 = __v_d3;
var __v_d4 = Object.create(__v_d1_proto);
var __v_d4_value = {a: 1, b: true};
__v_d4.value = __v_d4_value;
__v.d4 = __v_d4;

function __f1() {
  return (function() {
    with({  }) {

return function /*constructor*/(value) {
        this.value = value;
    };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({  }) {

return function /*apply*/(func) {
        throw new Error("'apply' is not allowed from inside a cloud-callback. Use 'get' to retrieve the value of this Output directly.");
    };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f3() {
  return (function() {
    with({  }) {

return function /*get*/() {
        return this.value;
    };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ v: __v }) {

return function () { console.log(v); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const v = {
            d1: output(4),
            d2: <any>undefined,
        };

        v.d2 = output({ a: 1, b: v });

        cases.push({
            title: "Recursive output capture",
            func: function () { console.log(v); },
            expectText: `exports.handler = __f0;

var __v = {};
var __v_d1_proto = {};
__f1.prototype = __v_d1_proto;
Object.defineProperty(__v_d1_proto, "constructor", { configurable: true, writable: true, value: __f1 });
Object.defineProperty(__v_d1_proto, "apply", { configurable: true, writable: true, value: __f2 });
Object.defineProperty(__v_d1_proto, "get", { configurable: true, writable: true, value: __f3 });
var __v_d1 = Object.create(__v_d1_proto);
__v_d1.value = 4;
__v.d1 = __v_d1;
var __v_d2 = Object.create(__v_d1_proto);
var __v_d2_value = {};
__v_d2_value.a = 1;
__v_d2_value.b = __v;
__v_d2.value = __v_d2_value;
__v.d2 = __v_d2;

function __f1() {
  return (function() {
    with({  }) {

return function /*constructor*/(value) {
        this.value = value;
    };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({  }) {

return function /*apply*/(func) {
        throw new Error("'apply' is not allowed from inside a cloud-callback. Use 'get' to retrieve the value of this Output directly.");
    };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f3() {
  return (function() {
    with({  }) {

return function /*get*/() {
        return this.value;
    };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ v: __v }) {

return function () { console.log(v); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const obj = { method1() { return this.method2(); }, method2: () => { return; } };

        cases.push({
            title: "Capture object with methods",
            func: function () { console.log(obj); },
            expectText: `exports.handler = __f0;

var __obj = {method1: __f1, method2: __f2};

function __f1() {
  return (function() {
    with({  }) {

return function /*method1*/() { return this.method2(); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({  }) {

return () => { return; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ obj: __obj }) {

return function () { console.log(obj); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        cases.push({
            title: "Undeclared variable in typeof",
            // @ts-ignore
            func: function () { const x = typeof a; },
            expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({ a: undefined }) {

return function () { const x = typeof a; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const a = 0;
        cases.push({
            title: "Declared variable in typeof",
            // @ts-ignore
            func: function () { const x = typeof a; },
            expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({ a: 0 }) {

return function () { const x = typeof a; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const array: any[] = [];
        const obj = { 80: "foo", arr: array };
        array.push(obj);

        cases.push({
            title: "Capture numeric property",
            func: function () { return array; },
            expectText: `exports.handler = __f0;

var __array = [];
var __array_0 = {};
__array_0["80"] = "foo";
__array_0.arr = __array;
__array[0] = __array_0;

function __f0() {
  return (function() {
    with({ array: __array }) {

return function () { return array; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const outer: any = { o: 1 };
        const array = [outer];
        outer.b = array;
        const C = (function () {
            function C() {}
            C.prototype.m = function () { return this.n() + outer; };
            C.prototype.n = function () { return array; };
            (<any>C).m = function () { return this.n(); };
            return C;
        }());

        cases.push({
            title: "Serialize es5-style class",
            func: () => C,
            expectText: `exports.handler = __f0;

var __C_prototype = {};
Object.defineProperty(__C_prototype, "constructor", { configurable: true, writable: true, value: __C });
var __outer = {};
__outer.o = 1;
var __outer_b = [];
__outer_b[0] = __outer;
__outer.b = __outer_b;
__C_prototype.m = __f1;
__C_prototype.n = __f2;
__C.prototype = __C_prototype;
__C.m = __f3;

function __C() {
  return (function() {
    with({ C: __C }) {

return function /*C*/() { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f1() {
  return (function() {
    with({ outer: __outer }) {

return function () { return this.n() + outer; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({ array: __outer_b }) {

return function () { return array; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f3() {
  return (function() {
    with({  }) {

return function () { return this.n(); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ C: __C }) {

return () => C;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const outer: any = { o: 1 };
        const array = [outer];
        outer.b = array;
        class C {
            public static s() { return array; }
            public m() { return this.n(); }
            public n() { return outer; }
        }
        cases.push({
            title: "Serialize class",
            func: () => C,
            expectText: `exports.handler = __f0;

var __f1_prototype = {};
Object.defineProperty(__f1_prototype, "constructor", { configurable: true, writable: true, value: __f1 });
Object.defineProperty(__f1_prototype, "m", { configurable: true, writable: true, value: __f2 });
var __outer = {};
__outer.o = 1;
var __outer_b = [];
__outer_b[0] = __outer;
__outer.b = __outer_b;
Object.defineProperty(__f1_prototype, "n", { configurable: true, writable: true, value: __f3 });
__f1.prototype = __f1_prototype;
__f1.s = __f4;

function __f1() {
  return (function() {
    with({  }) {

return function /*constructor*/() { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({  }) {

return function /*m*/() { return this.n(); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f3() {
  return (function() {
    with({ outer: __outer }) {

return function /*n*/() { return outer; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f4() {
  return (function() {
    with({ array: __outer_b }) {

return function /*s*/() { return array; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ C: __f1 }) {

return () => C;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        class C {
            private x: number;
            public static s() { return 0; }
            constructor() {
                this.x = 1;
            }
            public m() { return this.n(); }
            public n() { return 1; }
        }
        cases.push({
            title: "Serialize class with constructor and field",
            func: () => C,
            expectText: `exports.handler = __f0;

var __f1_prototype = {};
Object.defineProperty(__f1_prototype, "constructor", { configurable: true, writable: true, value: __f1 });
Object.defineProperty(__f1_prototype, "m", { configurable: true, writable: true, value: __f2 });
Object.defineProperty(__f1_prototype, "n", { configurable: true, writable: true, value: __f3 });
__f1.prototype = __f1_prototype;
__f1.s = __f4;

function __f1() {
  return (function() {
    with({  }) {

return function /*constructor*/() {
                this.x = 1;
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({  }) {

return function /*m*/() { return this.n(); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f3() {
  return (function() {
    with({  }) {

return function /*n*/() { return 1; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f4() {
  return (function() {
    with({  }) {

return function /*s*/() { return 0; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ C: __f1 }) {

return () => C;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        class C {
            private x: number;
            public static s() { return 0; }
            constructor() {
                this.x = 1;
            }
            public m() { return this.n(); }
            public n() { return 1; }
        }
        cases.push({
            title: "Serialize constructed class",
            func: () => new C(),
            expectText: `exports.handler = __f0;

var __f1_prototype = {};
Object.defineProperty(__f1_prototype, "constructor", { configurable: true, writable: true, value: __f1 });
Object.defineProperty(__f1_prototype, "m", { configurable: true, writable: true, value: __f2 });
Object.defineProperty(__f1_prototype, "n", { configurable: true, writable: true, value: __f3 });
__f1.prototype = __f1_prototype;
__f1.s = __f4;

function __f1() {
  return (function() {
    with({  }) {

return function /*constructor*/() {
                this.x = 1;
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({  }) {

return function /*m*/() { return this.n(); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f3() {
  return (function() {
    with({  }) {

return function /*n*/() { return 1; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f4() {
  return (function() {
    with({  }) {

return function /*s*/() { return 0; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ C: __f1 }) {

return () => new C();

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        class C {
            public m() { return this.n(); }
            public n() { return 0; }
        }
        cases.push({
            title: "Serialize instance class methods",
            func: new C().m,
            expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({  }) {

return function /*m*/() { return this.n(); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        class C {
            public delete() { return 0; }
        }
        cases.push({
            title: "Serialize method with reserved name",
            func: new C().delete,
            expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({  }) {

return function /*delete*/() { return 0; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }


    {
        class C {
            public static m() { return this.n(); }
            public static n() { return 0; }
        }
        cases.push({
            title: "Serialize static class methods",
            func: C.m,
            expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({  }) {

return function /*m*/() { return this.n(); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const D = (function () {
            function D() {
                ;
            }
            (<any>D).m = function () { return this.n(); };
            (<any>D).n = function () { return 0; };
            return D;
        }());
        cases.push({
            title: "Serialize static class methods (es5 class style)",
            func: (<any>D).m,
            expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({  }) {

return function () { return this.n(); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const array: any[] = [1];
        array.push(array);

        cases.push({
            title: "Cyclic object #1",
            func: () => array,
            expectText: `exports.handler = __f0;

var __array = [];
__array[0] = 1;
__array[1] = __array;

function __f0() {
  return (function() {
    with({ array: __array }) {

return () => array;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const obj: any = {a: 1};
        obj.b = obj;

        cases.push({
            title: "Cyclic object #2",
            func: () => obj,
            expectText: `exports.handler = __f0;

var __obj = {};
__obj.a = 1;
__obj.b = __obj;

function __f0() {
  return (function() {
    with({ obj: __obj }) {

return () => obj;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const obj: any = {a: []};
        obj.a.push(obj);
        obj.b = obj.a;

        cases.push({
            title: "Cyclic object #3",
            func: () => obj,
            expectText: `exports.handler = __f0;

var __obj = {};
var __obj_a = [];
__obj_a[0] = __obj;
__obj.a = __obj_a;
__obj.b = __obj_a;

function __f0() {
  return (function() {
    with({ obj: __obj }) {

return () => obj;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const obj: any = {a: []};
        obj.a.push(obj);
        obj.b = obj.a;
        const obj2 = [obj, obj];

        cases.push({
            title: "Cyclic object #4",
            func: () => obj2,
            expectText: `exports.handler = __f0;

var __obj2 = [];
var __obj2_0 = {};
var __obj2_0_a = [];
__obj2_0_a[0] = __obj2_0;
__obj2_0.a = __obj2_0_a;
__obj2_0.b = __obj2_0_a;
__obj2[0] = __obj2_0;
__obj2[1] = __obj2_0;

function __f0() {
  return (function() {
    with({ obj2: __obj2 }) {

return () => obj2;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const obj: any = {a: 1};

        function f1() { return obj; }
        function f2() { console.log(obj); }

        cases.push({
            title: "Object captured across multiple functions",
            func: () => { f1(); obj.a = 2; f2(); },
            expectText: `exports.handler = __f0;

var __obj = {a: 1};

function __f1() {
  return (function() {
    with({ obj: __obj, f1: __f1 }) {

return function /*f1*/() { return obj; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({ obj: __obj, f2: __f2 }) {

return function /*f2*/() { console.log(obj); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ f1: __f1, obj: __obj, f2: __f2 }) {

return () => { f1(); obj.a = 2; f2(); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const v = {};
        Object.defineProperty(v, "key", {
            configurable: true,
            value: 1,
        });
        cases.push({
            title: "Complex property descriptor #1",
            func: () => v,
            expectText: `exports.handler = __f0;

var __v = {};
Object.defineProperty(__v, "key", { configurable: true, value: 1 });

function __f0() {
  return (function() {
    with({ v: __v }) {

return () => v;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const v = {};
        Object.defineProperty(v, "key", {
            writable: true,
            enumerable: true,
            value: 1,
        });
        cases.push({
            title: "Complex property descriptor #2",
            func: () => v,
            expectText: `exports.handler = __f0;

var __v = {};
Object.defineProperty(__v, "key", { enumerable: true, writable: true, value: 1 });

function __f0() {
  return (function() {
    with({ v: __v }) {

return () => v;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const v = [1, 2, 3];
        delete v[1];

        cases.push({
            title: "Test array #1",
            func: () => v,
            expectText: `exports.handler = __f0;

var __v = [];
__v[0] = 1;
__v[2] = 3;

function __f0() {
  return (function() {
    with({ v: __v }) {

return () => v;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const v = [1, 2, 3];
        delete v[1];
        (<any>v).foo = "";

        cases.push({
            title: "Test array #2",
            func: () => v,
            expectText: `exports.handler = __f0;

var __v = [];
__v[0] = 1;
__v[2] = 3;
__v.foo = "";

function __f0() {
  return (function() {
    with({ v: __v }) {

return () => v;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const v = () => { return 1; };
        (<any>v).foo = "bar";

        cases.push({
            title: "Test function with property",
            func: v,
            expectText: `exports.handler = __f0;

__f0.foo = "bar";

function __f0() {
  return (function() {
    with({  }) {

return () => { return 1; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const x = Object.create(null);
        const v = () => { return x; };

        cases.push({
            title: "Test null prototype",
            func: v,
            expectText: `exports.handler = __f0;

var __x = Object.create(null);

function __f0() {
  return (function() {
    with({ x: __x }) {

return () => { return x; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const x = Object.create(Number.prototype);
        const v = () => { return x; };

        cases.push({
            title: "Test non-default object prototype",
            func: v,
            expectText: `exports.handler = __f0;

var __x = Object.create(global.Number.prototype);

function __f0() {
  return (function() {
    with({ x: __x }) {

return () => { return x; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const x = Object.create({ x() { return v; } });
        const v = () => { return x; };

        cases.push({
            title: "Test recursive prototype object prototype",
            func: v,
            expectText: `exports.handler = __f0;

var __x_proto = {x: __f1};
var __x = Object.create(__x_proto);

function __f1() {
  return (function() {
    with({ v: __f0 }) {

return function /*x*/() { return v; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ x: __x }) {

return () => { return x; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const v = () => { return 0; };
        Object.setPrototypeOf(v, () => { return 1; });

        cases.push({
            title: "Test non-default function prototype",
            func: v,
            expectText: `exports.handler = __f0;

Object.setPrototypeOf(__f0, __f1);

function __f0() {
  return (function() {
    with({  }) {

return () => { return 0; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f1() {
  return (function() {
    with({  }) {

return () => { return 1; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        function *f() { yield 1; }

        cases.push({
            title: "Test generator func",
            func: f,
            expectText: `exports.handler = __f;

var __f_prototype = Object.create(Object.getPrototypeOf((function*(){}).prototype));
__f.prototype = __f_prototype;
Object.setPrototypeOf(__f, Object.getPrototypeOf(function*(){}));

function __f() {
  return (function() {
    with({ f: __f }) {

return function* /*f*/() { yield 1; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const gf = (function *() { yield 1; });

        cases.push({
            title: "Test anonymous generator func",
            func: gf,
            expectText: `exports.handler = __f0;

var __f0_prototype = Object.create(Object.getPrototypeOf((function*(){}).prototype));
__f0.prototype = __f0_prototype;
Object.setPrototypeOf(__f0, Object.getPrototypeOf(function*(){}));

function __f0() {
  return (function() {
    with({  }) {

return function* () { yield 1; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        class C {
            private _x: number;

            constructor() {
                this._x = 0;
            }

            get foo() {
                return this._x;
            }

            set foo(v: number) {
                this._x = v;
            }
        }

        cases.push({
            title: "Test getter/setter #1",
            func: () => C,
            expectText: `exports.handler = __f0;

var __f1_prototype = {};
Object.defineProperty(__f1_prototype, "constructor", { configurable: true, writable: true, value: __f1 });
Object.defineProperty(__f1_prototype, "foo", { configurable: true, get: __f2, set: __f3 });
__f1.prototype = __f1_prototype;

function __f1() {
  return (function() {
    with({  }) {

return function /*constructor*/() {
                this._x = 0;
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({  }) {

return function /*foo*/() {
                return this._x;
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f3() {
  return (function() {
    with({  }) {

return function /*foo*/(v) {
                this._x = v;
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ C: __f1 }) {

return () => C;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const methodName = "method name";
        class C {
            [methodName](a: number) {
                return a;
            }
        }

        cases.push({
            title: "Test computed method name.",
            func: () => C,
            expectText: `exports.handler = __f0;

var __f1_prototype = {};
Object.defineProperty(__f1_prototype, "constructor", { configurable: true, writable: true, value: __f1 });
Object.defineProperty(__f1_prototype, "method name", { configurable: true, writable: true, value: __f2 });
__f1.prototype = __f1_prototype;

function __f1() {
  return (function() {
    with({  }) {

return function /*constructor*/() { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({  }) {

return function (a) {
                return a;
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ C: __f1 }) {

return () => C;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const sym = Symbol("test_symbol");
        class C {
            [sym](a: number) {
                return a;
            }

            getSym() { return sym; }
        }

        cases.push({
            title: "Test symbols #1",
            func: () => C,
            expectText: `exports.handler = __f0;

var __f1_prototype = {};
Object.defineProperty(__f1_prototype, "constructor", { configurable: true, writable: true, value: __f1 });
var __sym = Object.create(global.Symbol.prototype);
Object.defineProperty(__f1_prototype, "getSym", { configurable: true, writable: true, value: __f2 });
Object.defineProperty(__f1_prototype, __sym, { configurable: true, writable: true, value: __f3 });
__f1.prototype = __f1_prototype;

function __f1() {
  return (function() {
    with({  }) {

return function /*constructor*/() { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({ sym: __sym }) {

return function /*getSym*/() { return sym; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f3() {
  return (function() {
    with({  }) {

return function (a) {
                return a;
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ C: __f1 }) {

return () => C;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        class C {
            [Symbol.iterator](a: number) {
                return a;
            }
        }

        cases.push({
            title: "Test Symbol.iterator",
            func: () => C,
            expectText: `exports.handler = __f0;

var __f1_prototype = {};
Object.defineProperty(__f1_prototype, "constructor", { configurable: true, writable: true, value: __f1 });
Object.defineProperty(__f1_prototype, Symbol.iterator, { configurable: true, writable: true, value: __f2 });
__f1.prototype = __f1_prototype;

function __f1() {
  return (function() {
    with({  }) {

return function /*constructor*/() { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({  }) {

return function (a) {
                return a;
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ C: __f1 }) {

return () => C;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        class D {
            public n: number;
            constructor(n: number) {
                this.n = n;
                console.log("DConstructor");
            }
            dMethod(x: number) { return x; }
            dVirtual() { return 1; }
        }
        class C extends D {
            constructor(n: number) {
                super(n + 1);
                console.log("CConstructor");
            }
            cMethod() {
                return "" +
                    super.dMethod + super["dMethod"] +
                    super.dMethod(1) + super["dMethod"](2) +
                    super.dMethod(super.dMethod(3));
                }
            dVirtual() { return 3; }
        }

        cases.push({
            title: "Test class extension",
            func: () => C,
            expectText: `exports.handler = __f0;

var __f2_prototype = {};
Object.defineProperty(__f2_prototype, "constructor", { configurable: true, writable: true, value: __f2 });
Object.defineProperty(__f2_prototype, "dMethod", { configurable: true, writable: true, value: __f3 });
Object.defineProperty(__f2_prototype, "dVirtual", { configurable: true, writable: true, value: __f4 });
__f2.prototype = __f2_prototype;
var __f1_prototype = Object.create(__f2_prototype);
Object.defineProperty(__f1_prototype, "constructor", { configurable: true, writable: true, value: __f1 });
Object.defineProperty(__f1_prototype, "cMethod", { configurable: true, writable: true, value: __f5 });
Object.defineProperty(__f1_prototype, "dVirtual", { configurable: true, writable: true, value: __f6 });
__f1.prototype = __f1_prototype;
Object.setPrototypeOf(__f1, __f2);

function __f2() {
  return (function() {
    with({  }) {

return function /*constructor*/(n) {
                this.n = n;
                console.log("DConstructor");
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f3() {
  return (function() {
    with({  }) {

return function /*dMethod*/(x) { return x; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f4() {
  return (function() {
    with({  }) {

return function /*dVirtual*/() { return 1; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f1() {
  return (function() {
    with({ __super: __f2 }) {

return function /*constructor*/(n) {
    __super.call(this, n + 1);
    console.log("CConstructor");
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f5() {
  return (function() {
    with({ __super: __f2 }) {

return function /*cMethod*/() {
    return "" +
        __super.prototype.dMethod + __super.prototype["dMethod"] +
        __super.prototype.dMethod.call(this, 1) + __super.prototype["dMethod"].call(this, 2) +
        __super.prototype.dMethod.call(this, __super.prototype.dMethod.call(this, 3));
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f6() {
  return (function() {
    with({ __super: __f2 }) {

return function /*dVirtual*/() { return 3; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ C: __f1 }) {

return () => C;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        class A {
            public n: number;
            constructor(n: number) {
                this.n = n;
                console.log("AConstruction");
            }
            method(x: number) { return x; }
        }
        class B extends A {
            constructor(n: number) {
                super(n + 1);
                console.log("BConstructor");
            }
            method(n: number) { return 1 + super.method(n + 1); }
        }
        class C extends B {
            constructor(n: number) {
                super(n * 2);
                console.log("CConstructor");
            }
            method(n: number) { return 2 * super.method(n * 2); }
        }

        cases.push({
            title: "Three level inheritance",
            func: () => C,
            expectText: `exports.handler = __f0;

var __f3_prototype = {};
Object.defineProperty(__f3_prototype, "constructor", { configurable: true, writable: true, value: __f3 });
Object.defineProperty(__f3_prototype, "method", { configurable: true, writable: true, value: __f4 });
__f3.prototype = __f3_prototype;
var __f2_prototype = Object.create(__f3_prototype);
Object.defineProperty(__f2_prototype, "constructor", { configurable: true, writable: true, value: __f2 });
Object.defineProperty(__f2_prototype, "method", { configurable: true, writable: true, value: __f5 });
__f2.prototype = __f2_prototype;
Object.setPrototypeOf(__f2, __f3);
var __f1_prototype = Object.create(__f2_prototype);
Object.defineProperty(__f1_prototype, "constructor", { configurable: true, writable: true, value: __f1 });
Object.defineProperty(__f1_prototype, "method", { configurable: true, writable: true, value: __f6 });
__f1.prototype = __f1_prototype;
Object.setPrototypeOf(__f1, __f2);

function __f3() {
  return (function() {
    with({  }) {

return function /*constructor*/(n) {
                this.n = n;
                console.log("AConstruction");
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f4() {
  return (function() {
    with({  }) {

return function /*method*/(x) { return x; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({ __super: __f3 }) {

return function /*constructor*/(n) {
    __super.call(this, n + 1);
    console.log("BConstructor");
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f5() {
  return (function() {
    with({ __super: __f3 }) {

return function /*method*/(n) { return 1 + __super.prototype.method.call(this, n + 1); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f1() {
  return (function() {
    with({ __super: __f2 }) {

return function /*constructor*/(n) {
    __super.call(this, n * 2);
    console.log("CConstructor");
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f6() {
  return (function() {
    with({ __super: __f2 }) {

return function /*method*/(n) { return 2 * __super.prototype.method.call(this, n * 2); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ C: __f1 }) {

return () => C;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`});
    }

    {
        const sym = Symbol();

        class A {
            public n: number;
            constructor(n: number) {
                this.n = n;
                console.log("AConstruction");
            }
            public [sym](x: number) { return x; }
        }
        class B extends A {
            constructor(n: number) {
                super(n + 1);
                console.log("BConstructor");
            }
            // @ts-ignore
            public [sym](n: number) { return 1 + super[sym](n + 1); }
        }
        class C extends B {
            constructor(n: number) {
                super(n * 2);
                console.log("CConstructor");
            }
            // @ts-ignore
            public [sym](n: number) { return 2 * super[sym](n * 2); }
        }

        cases.push({
            title: "Three level inheritance with symbols",
            func: () => C,
            expectText: `exports.handler = __f0;

var __f3_prototype = {};
Object.defineProperty(__f3_prototype, "constructor", { configurable: true, writable: true, value: __f3 });
var __f3_prototype_sym = Object.create(global.Symbol.prototype);
Object.defineProperty(__f3_prototype, __f3_prototype_sym, { configurable: true, writable: true, value: __f4 });
__f3.prototype = __f3_prototype;
var __f2_prototype = Object.create(__f3_prototype);
Object.defineProperty(__f2_prototype, "constructor", { configurable: true, writable: true, value: __f2 });
Object.defineProperty(__f2_prototype, __f3_prototype_sym, { configurable: true, writable: true, value: __f5 });
__f2.prototype = __f2_prototype;
Object.setPrototypeOf(__f2, __f3);
var __f1_prototype = Object.create(__f2_prototype);
Object.defineProperty(__f1_prototype, "constructor", { configurable: true, writable: true, value: __f1 });
Object.defineProperty(__f1_prototype, __f3_prototype_sym, { configurable: true, writable: true, value: __f6 });
__f1.prototype = __f1_prototype;
Object.setPrototypeOf(__f1, __f2);

function __f3() {
  return (function() {
    with({  }) {

return function /*constructor*/(n) {
                this.n = n;
                console.log("AConstruction");
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f4() {
  return (function() {
    with({  }) {

return function (x) { return x; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({ __super: __f3 }) {

return function /*constructor*/(n) {
    __super.call(this, n + 1);
    console.log("BConstructor");
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f5() {
  return (function() {
    with({ sym: __f3_prototype_sym, __super: __f3 }) {

return function /*__computed*/(n) { return 1 + __super.prototype[sym].call(this, n + 1); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f1() {
  return (function() {
    with({ __super: __f2 }) {

return function /*constructor*/(n) {
    __super.call(this, n * 2);
    console.log("CConstructor");
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f6() {
  return (function() {
    with({ sym: __f3_prototype_sym, __super: __f2 }) {

return function /*__computed*/(n) { return 2 * __super.prototype[sym].call(this, n * 2); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ C: __f1 }) {

return () => C;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`});
    }

    {
        const sym = Symbol();

        class A {
            public n: number;
            static method(x: number) { return x; }
            static [sym](x: number) { return x * x; }
            constructor(n: number) {
                this.n = n;
                console.log("AConstruction");
            }
        }
        class B extends A {
            static method(n: number) { return 1 + super.method(n + 1); }
            // @ts-ignore
            static [sym](x: number) { return x * super[sym](x + 1); }
            constructor(n: number) {
                super(n + 1);
                console.log("BConstructor");
            }
        }

        cases.push({
            title: "Two level static inheritance",
            func: () => B,
            expectText: `exports.handler = __f0;

__f2.method = __f3;
var __f2_sym = Object.create(global.Symbol.prototype);
__f2[__f2_sym] = __f4;
__f1.method = __f5;
__f1[__f2_sym] = __f6;
Object.setPrototypeOf(__f1, __f2);

function __f2() {
  return (function() {
    with({  }) {

return function /*constructor*/(n) {
                this.n = n;
                console.log("AConstruction");
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f3() {
  return (function() {
    with({  }) {

return function /*method*/(x) { return x; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f4() {
  return (function() {
    with({  }) {

return function (x) { return x * x; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f1() {
  return (function() {
    with({ __super: __f2 }) {

return function /*constructor*/(n) {
    __super.call(this, n + 1);
    console.log("BConstructor");
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f5() {
  return (function() {
    with({ __super: __f2 }) {

return function /*method*/(n) { return 1 + __super.method.call(this, n + 1); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f6() {
  return (function() {
    with({ sym: __f2_sym, __super: __f2 }) {

return function /*__computed*/(x) { return x * __super[sym].call(this, x + 1); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ B: __f1 }) {

return () => B;

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`});
    }

    {
        const o = { a: 1, b: 2 };

        cases.push({
            title: "Capture subset of properties #1",
            func: function () { console.log(o.a); },
            expectText: `exports.handler = __f0;

var __o = {a: 1};

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.a); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: 2, c: 3 };

        cases.push({
            title: "Capture subset of properties #2",
            func: function () { console.log(o.b + o.c); },
            expectText: `exports.handler = __f0;

var __o = {b: 2, c: 3};

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.b + o.c); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: 2, c: 3 };

        cases.push({
            title: "Capture all if object is used as is.",
            func: function () { console.log(o); },
            expectText: `exports.handler = __f0;

var __o = {a: 1, b: 2, c: 3};

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: 2, c() { return this; } };

        cases.push({
            title: "Capture all if object property is invoked, and it uses this.",
            func: function () { console.log(o.c()); },
            expectText: `exports.handler = __f0;

var __o = {a: 1, b: 2, c: __f1};

function __f1() {
  return (function() {
    with({  }) {

return function /*c*/() { return this; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.c()); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: 2, c() { const v = () => this; } };

        cases.push({
            title: "Capture all if object property is invoked, and it uses this in nested arrow function.",
            func: function () { console.log(o.c()); },
            expectText: `exports.handler = __f0;

var __o = {a: 1, b: 2, c: __f1};

function __f1() {
  return (function() {
    with({  }) {

return function /*c*/() { const v = () => this; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.c()); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: 2, c() { const v = function () { return this; }; } };

        cases.push({
            title: "Capture one if object property is invoked, but it uses this in nested function.",
            func: function () { console.log(o.c()); },
            expectText: `exports.handler = __f0;

var __o = {c: __f1};

function __f1() {
  return (function() {
    with({  }) {

return function /*c*/() { const v = function () { return this; }; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.c()); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: 2, c() { return this; } };

        cases.push({
            title: "Capture one if object property is captured, uses this, but is not invoked",
            func: function () { console.log(o.c); },
            expectText: `exports.handler = __f0;

var __o = {c: __f1};

function __f1() {
  return (function() {
    with({  }) {

return function /*c*/() { return this; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.c); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: 2, c() { return 0; } };

        cases.push({
            title: "Capture one if object property is invoked, and it does not use this.",
            func: function () { console.log(o.c()); },
            expectText: `exports.handler = __f0;

var __o = {c: __f1};

function __f1() {
  return (function() {
    with({  }) {

return function /*c*/() { return 0; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.c()); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: { c() { return this; } } };

        cases.push({
            title: "Capture subset if sub object property is invoked.",
            func: function () { console.log(o.b.c()); },
            expectText: `exports.handler = __f0;

var __o = {};
var __o_b = {c: __f1};
__o.b = __o_b;

function __f1() {
  return (function() {
    with({  }) {

return function /*c*/() { return this; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.b.c()); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
});
    }

    {
        const o = { a: 1, get b() { return this; } };

        cases.push({
            title: "Capture all if getter and getter uses this.",
            func: function () { console.log(o.b); },
            expectText: `exports.handler = __f0;

var __o = {};
__o.a = 1;
Object.defineProperty(__o, "b", { configurable: true, enumerable: true, get: __f1 });

function __f1() {
  return (function() {
    with({  }) {

return function /*b*/() { return this; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.b); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
});
    }

    {
        const o = { a: 1, get b() { return 0; } };

        cases.push({
            title: "Capture one if getter and getter does not use this.",
            func: function () { console.log(o.b); },
            expectText: `exports.handler = __f0;

var __o = {};
Object.defineProperty(__o, "b", { configurable: true, enumerable: true, get: __f1 });

function __f1() {
  return (function() {
    with({  }) {

return function /*b*/() { return 0; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.b); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
});
    }

    {
        const o = { a: 1, b: 1, c: 2 };
        function f1() {
            console.log(o.a);
            f2();
        }

        function f2() {
            console.log(o.c);
        }

        cases.push({
            title: "Capture multi props from different contexts #1",
            func: f1,
            expectText: `exports.handler = __f1;

var __o = {a: 1, c: 2};

function __f2() {
  return (function() {
    with({ o: __o, f2: __f2 }) {

return function /*f2*/() {
            console.log(o.c);
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f1() {
  return (function() {
    with({ o: __o, f2: __f2, f1: __f1 }) {

return function /*f1*/() {
            console.log(o.a);
            f2();
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
});
    }

    {
        const o = { a: 1 };
        function f1() {
            // @ts-ignore
            console.log(o.c);
        }

        cases.push({
            title: "Do not capture non-existent prop",
            func: f1,
            expectText: `exports.handler = __f1;

var __o = {};

function __f1() {
  return (function() {
    with({ o: __o, f1: __f1 }) {

return function /*f1*/() {
            // @ts-ignore
            console.log(o.c);
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
});
    }

    {
        const o = { a: 1, b: 1, c: 2 };
        function f1() {
            console.log(o.a);
            f2();
        }

        function f2() {
            console.log(o);
        }

        cases.push({
            title: "Capture all props from different contexts #1",
            func: f1,
            expectText: `exports.handler = __f1;

var __o = {a: 1, b: 1, c: 2};

function __f2() {
  return (function() {
    with({ o: __o, f2: __f2 }) {

return function /*f2*/() {
            console.log(o);
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f1() {
  return (function() {
    with({ o: __o, f2: __f2, f1: __f1 }) {

return function /*f1*/() {
            console.log(o.a);
            f2();
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
});
    }

    {
        const o = { a: 1, b: 1, c: 2 };
        function f1() {
            console.log(o);
            f2();
        }

        function f2() {
            console.log(o.a);
        }

        cases.push({
            title: "Capture all props from different contexts #2",
            func: f1,
            expectText: `exports.handler = __f1;

var __o = {a: 1, b: 1, c: 2};

function __f2() {
  return (function() {
    with({ o: __o, f2: __f2 }) {

return function /*f2*/() {
            console.log(o.a);
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f1() {
  return (function() {
    with({ o: __o, f2: __f2, f1: __f1 }) {

return function /*f1*/() {
            console.log(o);
            f2();
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
});
    }

    {
        class C {
            a: number;
            b: number;

            constructor() {
                this.a = 1;
                this.b = 2;
            }

            m() { console.log(this); }
        }
        const o = new C();

        cases.push({
            title: "Capture all props if prototype is and uses this #1",
            func: function () { o.m(); },
            expectText: `exports.handler = __f0;

var __o_proto = {};
__f1.prototype = __o_proto;
Object.defineProperty(__o_proto, "constructor", { configurable: true, writable: true, value: __f1 });
Object.defineProperty(__o_proto, "m", { configurable: true, writable: true, value: __f2 });
var __o = Object.create(__o_proto);
__o.a = 1;
__o.b = 2;

function __f1() {
  return (function() {
    with({  }) {

return function /*constructor*/() {
                this.a = 1;
                this.b = 2;
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({  }) {

return function /*m*/() { console.log(this); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { o.m(); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        class C {
            a: number;
            b: number;

            constructor() {
                this.a = 1;
                this.b = 2;
            }

            m() { }
        }
        const o = new C();

        cases.push({
            title: "Capture no props if prototype is used but does not use this #1",
            func: function () { o.m(); },
            expectText: `exports.handler = __f0;

var __o = {};
Object.defineProperty(__o, "m", { configurable: true, writable: true, value: __f1 });

function __f1() {
  return (function() {
    with({  }) {

return function /*m*/() { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { o.m(); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        class C {
            a: number;

            constructor() {
                this.a = 1;
            }

            m() { (<any>this).n(); }
        }

        class D extends C {
            b: number;
            constructor() {
                super();
                this.b = 2;
            }
            n() {}
        }
        const o = new D();

        cases.push({
            title: "Capture all props if prototype is accessed #2",
            func: function () { o.m(); },
            expectText: `exports.handler = __f0;

var __o_proto_proto = {};
__f1.prototype = __o_proto_proto;
Object.defineProperty(__o_proto_proto, "constructor", { configurable: true, writable: true, value: __f1 });
Object.defineProperty(__o_proto_proto, "m", { configurable: true, writable: true, value: __f2 });
var __o_proto = Object.create(__o_proto_proto);
__f3.prototype = __o_proto;
Object.setPrototypeOf(__f3, __f1);
Object.defineProperty(__o_proto, "constructor", { configurable: true, writable: true, value: __f3 });
Object.defineProperty(__o_proto, "n", { configurable: true, writable: true, value: __f4 });
var __o = Object.create(__o_proto);
__o.a = 1;
__o.b = 2;

function __f1() {
  return (function() {
    with({  }) {

return function /*constructor*/() {
                this.a = 1;
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f2() {
  return (function() {
    with({  }) {

return function /*m*/() { this.n(); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f3() {
  return (function() {
    with({ __super: __f1 }) {

return function /*constructor*/() {
    __super.call(this);
    this.b = 2;
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f4() {
  return (function() {
    with({ __super: __f1 }) {

return function /*n*/() { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { o.m(); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const table1: any = { primaryKey: 1, insert: () => { }, scan: () => { } };

        async function testScanReturnsAllValues() {
            await table1.insert({[table1.primaryKey.get()]: "val1", value1: 1, value2: "1"});
            await table1.insert({[table1.primaryKey.get()]: "val2", value1: 2, value2: "2"});

            const values = null;
            // @ts-ignore
            const value1 = values.find(v => v[table1.primaryKey.get()] === "val1");
            // @ts-ignore
            const value2 = values.find(v => v[table1.primaryKey.get()] === "val2");
        }

        cases.push({
            title: "Cloud table function",
            func: testScanReturnsAllValues,
            expectText: `exports.handler = __testScanReturnsAllValues;

var __table1 = {insert: __f1, primaryKey: 1};

function __f0() {
  return (function() {
    with({  }) {

return function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f1() {
  return (function() {
    with({  }) {

return () => { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __testScanReturnsAllValues() {
  return (function() {
    with({ __awaiter: __f0, table1: __table1, testScanReturnsAllValues: __testScanReturnsAllValues }) {

return function /*testScanReturnsAllValues*/() {
            return __awaiter(this, void 0, void 0, function* () {
                yield table1.insert({ [table1.primaryKey.get()]: "val1", value1: 1, value2: "1" });
                yield table1.insert({ [table1.primaryKey.get()]: "val2", value1: 2, value2: "2" });
                const values = null;
                // @ts-ignore
                const value1 = values.find(v => v[table1.primaryKey.get()] === "val1");
                // @ts-ignore
                const value2 = values.find(v => v[table1.primaryKey.get()] === "val2");
            });
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
});
    }

    {
        const o = { a: 1, b: { x: 1, doNotCapture: true }, c: 2 };
        function f1() {
            console.log(o);
        }

        cases.push({
            title: "Do not capture #1",
            func: f1,
            expectText: `exports.handler = __f1;

var __o = {a: 1, b: undefined, c: 2};

function __f1() {
  return (function() {
    with({ o: __o, f1: __f1 }) {

return function /*f1*/() {
            console.log(o);
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
});
    }

    {
        const o = { a: 1, b: () => console.log("the actual function") };
        (<any>o.b).doNotCapture = true;

        function f1() {
            console.log(o);
        }

        cases.push({
            title: "Do not capture #2",
            func: f1,
            expectText: `exports.handler = __f1;

var __o = {a: 1, b: __f0};

function __f0() {
  return (function() {
    with({ message: "Function 'b' cannot be called at runtime. It can only be used at deployment time.\\n\\nFunction code:\\n  () => console.log(\\"the actual function\\")\\n" }) {

return () => { throw new Error(message); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f1() {
  return (function() {
    with({ o: __o, f1: __f1 }) {

return function /*f1*/() {
            console.log(o);
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
});
    }

    {
        const lambda1 = () => console.log(1);
        const lambda2 = () => console.log(1);

        function f3() {
            return (lambda1(), lambda2());
        }

        cases.push({
            title: "Merge simple functions",
            func: f3,
            expectText: `exports.handler = __f3;

function __f0() {
  return (function() {
    with({  }) {

return () => console.log(1);

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f3() {
  return (function() {
    with({ lambda1: __f0, lambda2: __f0, f3: __f3 }) {

return function /*f3*/() {
            return (lambda1(), lambda2());
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
});
    }

    {
        const awaiter1 = function (thisArg: any, _arguments: any, P: any, generator: any) {
            return new (P || (P = Promise))(function (resolve: any, reject: any) {
                function fulfilled(value: any) { try { step(generator.next(value)); } catch (e) { reject(e); } }
                function rejected(value: any) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
                function step(result: any) { result.done ? resolve(result.value) : new P(function (resolve1: any) { resolve1(result.value); }).then(fulfilled, rejected); }
                step((generator = generator.apply(thisArg, _arguments || [])).next());
            });
        };
        const awaiter2 = function (thisArg: any, _arguments: any, P: any, generator: any) {
            return new (P || (P = Promise))(function (resolve: any, reject: any) {
                function fulfilled(value: any) { try { step(generator.next(value)); } catch (e) { reject(e); } }
                function rejected(value: any) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
                function step(result: any) { result.done ? resolve(result.value) : new P(function (resolve1: any) { resolve1(result.value); }).then(fulfilled, rejected); }
                step((generator = generator.apply(thisArg, _arguments || [])).next());
            });
        };

        function f3() {
            const v1 = awaiter1, v2 = awaiter2;
        }

        cases.push({
            title: "Share __awaiter functions",
            func: f3,
            expectText: `exports.handler = __f3;

function __f0() {
  return (function() {
    with({  }) {

return function (thisArg, _arguments, P, generator) {
            return new (P || (P = Promise))(function (resolve, reject) {
                function fulfilled(value) { try {
                    step(generator.next(value));
                }
                catch (e) {
                    reject(e);
                } }
                function rejected(value) { try {
                    step(generator["throw"](value));
                }
                catch (e) {
                    reject(e);
                } }
                function step(result) { result.done ? resolve(result.value) : new P(function (resolve1) { resolve1(result.value); }).then(fulfilled, rejected); }
                step((generator = generator.apply(thisArg, _arguments || [])).next());
            });
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f3() {
  return (function() {
    with({ awaiter1: __f0, awaiter2: __f0, f3: __f3 }) {

return function /*f3*/() {
            const v1 = awaiter1, v2 = awaiter2;
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
});
    }

    {
        cases.push({
            title: "Capture of exported variable #1",
            func: function () { console.log(exportedValue); },
            expectText: `exports.handler = __f0;

var __exports = {exportedValue: 42};

function __f0() {
  return (function() {
    with({ exports: __exports }) {

return function () { console.log(exports.exportedValue); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        cases.push({
            title: "Capture of exported variable #2",
            func: function () { console.log(exports.exportedValue); },
            expectText: `exports.handler = __f0;

var __exports = {exportedValue: 42};

function __f0() {
  return (function() {
    with({ exports: __exports }) {

return function () { console.log(exports.exportedValue); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        cases.push({
            title: "Capture of exported variable #3",
            func: function () { console.log(module.exports.exportedValue); },
            expectText: `exports.handler = __f0;

var __module = {};
var __module_exports = {exportedValue: 42};
__module.exports = __module_exports;

function __f0() {
  return (function() {
    with({ module: __module }) {

return function () { console.log(module.exports.exportedValue); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {

        function foo() {
            require("./util");
        }

        cases.push({
            title: "Required packages #1",
            func: function () { require("typescript"); foo(); if (true) { require("os") } },
            expectPackages: new Set(),
            expectText: `exports.handler = __f0;

function __foo() {
  return (function() {
    with({ foo: __foo }) {

return function /*foo*/() {
            require("./util");
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ foo: __foo }) {

return function () { require("typescript"); foo(); if (true) {
                require("os");
            } };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: { c: 2, d: 3 } };

        cases.push({
            title: "Analyze property chain #1",
            func: function () { console.log(o.b.c); },
            expectText: `exports.handler = __f0;

var __o = {};
var __o_b = {c: 2};
__o.b = __o_b;

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.b.c); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: { c: 2, d: 3 } };

        cases.push({
            title: "Analyze property chain #2",
            func: function () { console.log(o.b); console.log(o.b.c); },
            expectText: `exports.handler = __f0;

var __o = {};
var __o_b = {c: 2, d: 3};
__o.b = __o_b;

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.b); console.log(o.b.c); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: { c: 2, d: 3 } };

        cases.push({
            title: "Analyze property chain #3",
            func: function () { console.log(o.b); },
            expectText: `exports.handler = __f0;

var __o = {};
var __o_b = {c: 2, d: 3};
__o.b = __o_b;

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.b); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: { c: 2, d: 3 } };

        cases.push({
            title: "Analyze property chain #4",
            func: function () { console.log(o.a); },
            expectText: `exports.handler = __f0;

var __o = {a: 1};

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.a); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: { c: { d: 1, e: 3 } } };

        cases.push({
            title: "Analyze property chain #5",
            func: function () { console.log(o.b.c.d); },
            expectText: `exports.handler = __f0;

var __o = {};
var __o_b = {};
var __o_b_c = {d: 1};
__o_b.c = __o_b_c;
__o.b = __o_b;

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.b.c.d); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: { c: { d: 1, e: 3 } } };

        cases.push({
            title: "Analyze property chain #6",
            func: function () { console.log(o.b.c.d); console.log(o.b); },
            expectText: `exports.handler = __f0;

var __o = {};
var __o_b = {};
var __o_b_c = {d: 1, e: 3};
__o_b.c = __o_b_c;
__o.b = __o_b;

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.b.c.d); console.log(o.b); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: { c: { d: 1, e: 3 } } };

        cases.push({
            title: "Analyze property chain #7",
            func: function () { console.log(o.b.c.d); console.log(o.b.c); },
            expectText: `exports.handler = __f0;

var __o = {};
var __o_b = {};
var __o_b_c = {d: 1, e: 3};
__o_b.c = __o_b_c;
__o.b = __o_b;

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.b.c.d); console.log(o.b.c); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: { c: { d: 1, e: 3 } } };

        cases.push({
            title: "Analyze property chain #8",
            func: function () { console.log(o.b.c.d); console.log(o.b.c); console.log(o.b); },
            expectText: `exports.handler = __f0;

var __o = {};
var __o_b = {};
var __o_b_c = {d: 1, e: 3};
__o_b.c = __o_b_c;
__o.b = __o_b;

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.b.c.d); console.log(o.b.c); console.log(o.b); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: function () { } };

        cases.push({
            title: "Analyze property chain #9",
            func: function () { console.log(o.b.name); },
            expectText: `exports.handler = __f0;

var __o = {b: __f1};

function __f1() {
  return (function() {
    with({  }) {

return function () { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.b.name); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: function () { } };

        cases.push({
            title: "Analyze property chain #10",
            func: function () { console.log(o.b.name); console.log(o.b()); },
            expectText: `exports.handler = __f0;

var __o = {b: __f1};

function __f1() {
  return (function() {
    with({  }) {

return function () { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.b.name); console.log(o.b()); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: function () { } };

        cases.push({
            title: "Analyze property chain #11",
            func: function () { console.log(o.b()); console.log(o.b.name); },
            expectText: `exports.handler = __f0;

var __o = {b: __f1};

function __f1() {
  return (function() {
    with({  }) {

return function () { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.b()); console.log(o.b.name); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: function () { return this; } };

        cases.push({
            title: "Analyze property chain #12",
            func: function () { console.log(o.b.name); console.log(o.b()); },
            expectText: `exports.handler = __f0;

var __o = {a: 1, b: __f1};

function __f1() {
  return (function() {
    with({  }) {

return function () { return this; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.b.name); console.log(o.b()); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o = { a: 1, b: function () { return this; } };

        cases.push({
            title: "Analyze property chain #13",
            func: function () { console.log(o.b()); console.log(o.b.name); },
            expectText: `exports.handler = __f0;

var __o = {a: 1, b: __f1};

function __f1() {
  return (function() {
    with({  }) {

return function () { return this; };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ o: __o }) {

return function () { console.log(o.b()); console.log(o.b.name); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o1 = { c: 2, d: 3 };
        const o2 = { a: 1, b: o1 };

        cases.push({
            title: "Analyze property chain #14",
            func: function () { console.log(o2.b.d); console.log(o1); },
            expectText: `exports.handler = __f0;

var __o2 = {};
var __o2_b = {d: 3, c: 2};
__o2.b = __o2_b;

function __f0() {
  return (function() {
    with({ o2: __o2, o1: __o2_b }) {

return function () { console.log(o2.b.d); console.log(o1); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o1 = { c: 2, d: 3 };
        const o2 = { a: 1, b: o1 };

        cases.push({
            title: "Analyze property chain #15",
            func: function () { console.log(o1); console.log(o2.b.d); },
            expectText: `exports.handler = __f0;

var __o1 = {c: 2, d: 3};
var __o2 = {};
__o2.b = __o1;

function __f0() {
  return (function() {
    with({ o1: __o1, o2: __o2 }) {

return function () { console.log(o1); console.log(o2.b.d); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o1 = { c: 2, d: 3 };
        const o2 = { a: 1, b: o1 };
        const o3 = { a: 1, b: o1 };

        cases.push({
            title: "Analyze property chain #16",
            func: function () { console.log(o2.b.c); console.log(o3.b.d); },
            expectText: `exports.handler = __f0;

var __o2 = {};
var __o2_b = {c: 2, d: 3};
__o2.b = __o2_b;
var __o3 = {};
__o3.b = __o2_b;

function __f0() {
  return (function() {
    with({ o2: __o2, o3: __o3 }) {

return function () { console.log(o2.b.c); console.log(o3.b.d); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o1 = { c: 2, d: 3 };
        const o2 = { a: 1, b: o1 };
        const o3 = { a: 1, b: o1 };

        cases.push({
            title: "Analyze property chain #17",
            func: function () { console.log(o2.b.d); console.log(o3.b.d); },
            expectText: `exports.handler = __f0;

var __o2 = {};
var __o2_b = {d: 3};
__o2.b = __o2_b;
var __o3 = {};
__o3.b = __o2_b;

function __f0() {
  return (function() {
    with({ o2: __o2, o3: __o3 }) {

return function () { console.log(o2.b.d); console.log(o3.b.d); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o1 = { c: 2, d: 3 };
        const o2 = { a: 1, b: o1 };
        const o3 = { a: 1, b: o1 };

        cases.push({
            title: "Analyze property chain #18",
            func: function () { console.log(o2.b); console.log(o2.b.d); console.log(o3.b.d); },
            expectText: `exports.handler = __f0;

var __o2 = {};
var __o2_b = {c: 2, d: 3};
__o2.b = __o2_b;
var __o3 = {};
__o3.b = __o2_b;

function __f0() {
  return (function() {
    with({ o2: __o2, o3: __o3 }) {

return function () { console.log(o2.b); console.log(o2.b.d); console.log(o3.b.d); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o1 = { c: 2, d: 3 };
        const o2 = { a: 1, b: o1 };
        const o3 = { a: 1, b: o1 };

        cases.push({
            title: "Analyze property chain #19",
            func: function () { console.log(o2.b.d); console.log(o3.b.d); console.log(o2.b); },
            expectText: `exports.handler = __f0;

var __o2 = {};
var __o2_b = {c: 2, d: 3};
__o2.b = __o2_b;
var __o3 = {};
__o3.b = __o2_b;

function __f0() {
  return (function() {
    with({ o2: __o2, o3: __o3 }) {

return function () { console.log(o2.b.d); console.log(o3.b.d); console.log(o2.b); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o1 = { c: 2, d: 3 };
        const o2 = { a: 1, b: o1 };
        const o3 = { a: 1, b: o1 };

        cases.push({
            title: "Analyze property chain #20",
            func: function () { console.log(o2.b.d); console.log(o3.b.d); console.log(o1); },
            expectText: `exports.handler = __f0;

var __o2 = {};
var __o2_b = {d: 3, c: 2};
__o2.b = __o2_b;
var __o3 = {};
__o3.b = __o2_b;

function __f0() {
  return (function() {
    with({ o2: __o2, o3: __o3, o1: __o2_b }) {

return function () { console.log(o2.b.d); console.log(o3.b.d); console.log(o1); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const o1 = { c: 2, d: 3 };
        const o2 = { a: 1, b: o1 };
        const o3 = { a: 1, b: o1 };

        cases.push({
            title: "Analyze property chain #21",
            func: function () { console.log(o1); console.log(o2.b.d); console.log(o3.b.d);  },
            expectText: `exports.handler = __f0;

var __o1 = {c: 2, d: 3};
var __o2 = {};
__o2.b = __o1;
var __o3 = {};
__o3.b = __o1;

function __f0() {
  return (function() {
    with({ o1: __o1, o2: __o2, o3: __o3 }) {

return function () { console.log(o1); console.log(o2.b.d); console.log(o3.b.d); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        cases.push({
            title: "Capture non-built-in module",
            func: function () { typescript.parseCommandLine([""]); },
            expectText: `exports.handler = __f0;

function __f0() {
  return (function() {
    with({ typescript: require("typescript/lib/typescript.js") }) {

return function () { typescript.parseCommandLine([""]); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        cases.push({
            title: "Fail to capture non-deployment module due to native code",
            func: function () { console.log(pulumi); },
            error: `Error serializing function 'func': closure.spec.js(0,0)

function 'func': closure.spec.js(0,0): captured
  module './bin/index.js' which indirectly referenced
    function 'debug': index.js(0,0): which captured
      module './bin/runtime/settings.js' which indirectly referenced
        function 'getEngine': settings.js(0,0): which captured
          module './bin/proto/engine_grpc_pb.js' which indirectly referenced
(...)
Function code:
(...)
Module './bin/index.js' is a 'deployment only' module. In general these cannot be captured inside a 'run time' function.`
        });
    }

    {
        // Used just to validate that if we capture a Config object we see these values serialized over.
        // Specifically, the module that Config uses needs to be captured by value and not be
        // 'require-reference'.
        runtime.setConfig("test:TestingKey1", "TestingValue1");
        const testConfig = new pulumi.Config("test");

        cases.push({
            title: "Capture config created on the outside",
            func: function () { const v = testConfig.get("TestingKey1"); console.log(v); },
            expectText: `exports.handler = __f0;

var __testConfig_proto = {};
var __config = {["test:TestingKey1"]: "TestingValue1", ["test:TestingKey2"]: "TestingValue2", ["pkg:a"]: "foo", ["pkg:bar"]: "b", ["pkg:baz"]: "baz", ["otherpkg:a"]: "babble", ["otherpkg:nothere"]: "bazzle", ["pkg:boolf"]: "false", ["pkg:boolt"]: "true", ["pkg:num"]: "42.333", ["pkg:array"]: "[ 0, false, 2, \\"foo\\" ]", ["pkg:struct"]: "{ \\"foo\\": \\"bar\\", \\"mim\\": [] }", ["pkg:color"]: "orange", ["pkg:strlen"]: "abcdefgh", ["pkg:pattern"]: "aBcDeFgH", ["pkg:quantity"]: "8"};
var __options = {project: undefined};
var __runtime = {getConfig: __getConfig, getProject: __0_getProject};
var __metadata_1 = {getProject: __getProject};
__f1.prototype = __testConfig_proto;
Object.defineProperty(__testConfig_proto, "constructor", { configurable: true, writable: true, value: __f1 });
(...)
Object.defineProperty(__testConfig_proto, "fullKey", { configurable: true, writable: true, value: __f17 });
var __testConfig = Object.create(__testConfig_proto);
__testConfig.name = "test";

function __getConfig() {
  return (function() {
    with({ config: __config, getConfig: __getConfig }) {

return function /*getConfig*/(k) {
    return config[k];
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __0_getProject() {
  return (function() {
    with({ options: __options, getProject: __0_getProject }) {

return function /*getProject*/() {
    return options.project;
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __getProject() {
  return (function() {
    with({ runtime: __runtime, getProject: __getProject }) {

return function /*getProject*/() {
    const project = runtime.getProject();
    if (project) {
        return project;
    }
    throw new Error("Project unknown; are you using the Pulumi CLI?");
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f1() {
  return (function() {
    with({ metadata_1: __metadata_1 }) {

return function /*constructor*/(name) {
        if (name === undefined) {
            name = metadata_1.getProject();
        }
        if (name.endsWith(":config")) {
            name = name.replace(/:config$/, "");
        }
        this.name = name;
    };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
(...)
function __f0() {
  return (function() {
    with({ testConfig: __testConfig }) {

return function () { const v = testConfig.get("TestingKey1"); console.log(v); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        runtime.setConfig("test:TestingKey2", "TestingValue2");

        cases.push({
            title: "Capture config created on the inside",
            func: function () { const v = new pulumi.Config("test").get("TestingKey2"); console.log(v); },
            expectText: `exports.handler = __f0;

var __options = {project: undefined};
var __config = {["test:TestingKey1"]: "TestingValue1", ["test:TestingKey2"]: "TestingValue2", ["pkg:a"]: "foo", ["pkg:bar"]: "b", ["pkg:baz"]: "baz", ["otherpkg:a"]: "babble", ["otherpkg:nothere"]: "bazzle", ["pkg:boolf"]: "false", ["pkg:boolt"]: "true", ["pkg:num"]: "42.333", ["pkg:array"]: "[ 0, false, 2, \\"foo\\" ]", ["pkg:struct"]: "{ \\"foo\\": \\"bar\\", \\"mim\\": [] }", ["pkg:color"]: "orange", ["pkg:strlen"]: "abcdefgh", ["pkg:pattern"]: "aBcDeFgH", ["pkg:quantity"]: "8"};
var __runtime = {getProject: __0_getProject, getConfig: __getConfig};
var __metadata_1 = {getProject: __getProject};
var __f1_prototype = {};
Object.defineProperty(__f1_prototype, "constructor", { configurable: true, writable: true, value: __f1 });
(...)
Object.defineProperty(__f1_prototype, "fullKey", { configurable: true, writable: true, value: __f17 });
__f1.prototype = __f1_prototype;
var __pulumi = {Config: __f1};

function __0_getProject() {
  return (function() {
    with({ options: __options, getProject: __0_getProject }) {

return function /*getProject*/() {
    return options.project;
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __getConfig() {
  return (function() {
    with({ config: __config, getConfig: __getConfig }) {

return function /*getConfig*/(k) {
    return config[k];
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __getProject() {
  return (function() {
    with({ runtime: __runtime, getProject: __getProject }) {

return function /*getProject*/() {
    const project = runtime.getProject();
    if (project) {
        return project;
    }
    throw new Error("Project unknown; are you using the Pulumi CLI?");
};

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f1() {
  return (function() {
    with({ metadata_1: __metadata_1 }) {

return function /*constructor*/(name) {
        if (name === undefined) {
            name = metadata_1.getProject();
        }
        if (name.endsWith(":config")) {
            name = name.replace(/:config$/, "");
        }
        this.name = name;
    };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
(...)
function __f0() {
  return (function() {
    with({ pulumi: __pulumi }) {

return function () { const v = new pulumi.Config("test").get("TestingKey2"); console.log(v); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        cases.push({
            title: "Capture factory func #1",
            factoryFunc: () => {
                const serverlessExpress = require("aws-serverless-express");
                const express = require("express");
                const app = express();
                app.get("/", (req: any, res: any) => {
                    res.json({ succeeded: true });
                });

                const server = serverlessExpress.createServer(app);

                return (event: any, context: any) => {
                    serverlessExpress.proxy(server, event, context);
                };
            },
            expectText: `
function __f0() {
  return (function() {
    with({  }) {

return () => {
                const serverlessExpress = require("aws-serverless-express");
                const express = require("express");
                const app = express();
                app.get("/", (req, res) => {
                    res.json({ succeeded: true });
                });
                const server = serverlessExpress.createServer(app);
                return (event, context) => {
                    serverlessExpress.proxy(server, event, context);
                };
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

exports.handler = __f0();`,
        });
    }

    {
        const outerVal = [{}];
        (<any>outerVal[0]).inner = outerVal;

        function foo() {
            outerVal.pop();
        }

        function bar() {
            outerVal.join();
        }

        cases.push({
            title: "Capture factory func #2",
            factoryFunc: () => {
                outerVal.push({});
                foo();

                return (event: any, context: any) => {
                    bar();
                };
            },
            expectText: `
var __outerVal = [];
var __outerVal_0 = {};
__outerVal_0.inner = __outerVal;
__outerVal[0] = __outerVal_0;

function __foo() {
  return (function() {
    with({ outerVal: __outerVal, foo: __foo }) {

return function /*foo*/() {
            outerVal.pop();
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __bar() {
  return (function() {
    with({ outerVal: __outerVal, bar: __bar }) {

return function /*bar*/() {
            outerVal.join();
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ outerVal: __outerVal, foo: __foo, bar: __bar }) {

return () => {
                outerVal.push({});
                foo();
                return (event, context) => {
                    bar();
                };
            };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

exports.handler = __f0();`,
        });
    }

    cases.push({
        title: "Deconstructing function",
        // @ts-ignore
        func: function f({ whatever }) { },
        expectText: `exports.handler = __f;

function __f() {
  return (function() {
    with({ f: __f }) {

return function /*f*/({ whatever }) { };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
    });

    {
        const regex = /(abc)[\(123-456]\\a\b\z/gi;

        cases.push({
            title: "Regex #1",
            // @ts-ignore
            func: function() { console.log(regex); },
            expectText: `exports.handler = __f0;

var __regex = new RegExp("(abc)[\\\\(123-456]\\\\\\\\a\\\\b\\\\z", "gi");

function __f0() {
  return (function() {
    with({ regex: __regex }) {

return function () { console.log(regex); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    {
        const regex = /(abc)/g;

        function foo() {
            console.log(regex);
        }

        cases.push({
            title: "Regex #2",
            // @ts-ignore
            func: function() { console.log(regex); foo(); },
            expectText: `exports.handler = __f0;

var __regex = new RegExp("(abc)", "g");

function __foo() {
  return (function() {
    with({ regex: __regex, foo: __foo }) {

return function /*foo*/() {
            console.log(regex);
        };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}

function __f0() {
  return (function() {
    with({ regex: __regex, foo: __foo }) {

return function () { console.log(regex); foo(); };

    }
  }).apply(undefined, undefined).apply(this, arguments);
}
`,
        });
    }

    // Run a bunch of direct checks on async js functions if we're in node 8 or above.
    // We can't do this inline as node6 doesn't understand 'async functions'.  And we
    // can't do this in TS as TS will convert the async-function to be a normal non-async
    // function.
    const version = Number(process.version.match(/^v(\d+)\.\d+/)![1]);
    if (version >= 8) {
        const jsCases = require("./jsClosureCases");
        cases.push(...jsCases.cases);
    }

    // Make a callback to keep running tests.
    let remaining = cases;
    while (true) {
        const test = remaining.shift();
        if (!test) {
            return;
        }

        // if (test.title !== "Two level static inheritance") {
        //     continue;
        // }

        it(test.title, asyncTest(async () => {
            // Run pre-actions.
            if (test.pre) {
                test.pre();
            }

            // Invoke the test case.
            if (test.expectText) {
                const sf = await serializeFunction(test);
                compareTextWithWildcards(test.expectText, sf.text);
                if (test.expectPackages) {
                    assert.equal(sf.requiredPackages.size, test.expectPackages.size)
                    for (const p of sf.requiredPackages) {
                        assert.equal(test.expectPackages.has(p), true);
                    }
                }
            }
            else {
                const message = await assertAsyncThrows(async () => {
                    await serializeFunction(test);
                });

                // replace real locations with (0,0) so that our test baselines do not need to
                // updated any time this file changes.
                const regex = /\([0-9]+,[0-9]+\)/g;
                const withoutLocations = message.replace(regex, "(0,0)");
                if (test.error) {
                    compareTextWithWildcards(test.error, withoutLocations);
                }
            }
        }));

        // Schedule any additional tests.
        if (test.afters) {
            remaining = test.afters.concat(remaining);
        }
    }

    async function serializeFunction(test: ClosureCase) {
        if (test.func) {
            return await runtime.serializeFunction(test.func);
        }
        else if (test.factoryFunc) {
            return await runtime.serializeFunction(test.factoryFunc!, { isFactoryFunction: true });
        }
        else {
            throw new Error("Have to supply [func] or [factoryFunc]!");
        }
    }
});

/**
 * compareErrorText compares an "expected" error string and an "actual" error string
 * and issues an error if they do not match.
 *
 * This function accepts two repetition operators to make writing tests easier against
 * error messages that are dependent on the environment:
 *
 *  * (...) alone on a single line causes the matcher to accept zero or more lines
 *    between the repetition and the next line.
 *  * (...) within in the context of a line causes the matcher to accept zero or more characters
 *    between the repetition and the next character.
 *
 * This is useful when testing error messages that you get when capturing bulit-in modules,
 * because the specific error message differs between Node versions.
 * @param expected The expected error message string, potentially containing repetitions
 * @param actual The actual error message string
 */
function compareTextWithWildcards(expected: string, actual: string) {
    const wildcard = "(...)";

    if (!expected.includes(wildcard)) {
        // We get a nice diff view if we diff the entire string, so do that
        // if we didn't get a wildcard.
        assert.equal(actual, expected);
        return;
    }

    const expectedLines = expected.split(EOL);
    const actualLines = actual.split(EOL);
    let actualIndex = 0;
    for (let expectedIndex = 0; expectedIndex < expectedLines.length; expectedIndex++) {
        const expectedLine = expectedLines[expectedIndex].trim();
        if (expectedLine === wildcard) {
            if (expectedIndex + 1 === expectedLines.length) {
                return;
            }

            const nextLine = expectedLines[++expectedIndex].trim();
            while (true) {
                const actualLine = actualLines[actualIndex++].trim();
                if (actualLine === nextLine) {
                    break;
                }

                if (actualIndex === actualLines.length) {
                    assert.fail(`repetition failed to find match: expected terminator ${nextLine}, received ${actual}`);
                }
            }
        } else if (expectedLine.includes(wildcard)) {
            const line = actualLines[actualIndex++].trim();
            const index = expectedLine.indexOf(wildcard);
            const indexAfter = index + wildcard.length;
            assert.equal(line.substring(0, index), expectedLine.substring(0, index));

            if (indexAfter === expectedLine.length) {
                continue;
            }
            let repetitionIndex = index;
            for (; repetitionIndex < line.length; repetitionIndex++) {
                if (line[repetitionIndex] === expectedLine[indexAfter]) {
                    break;
                }
            }

            assert.equal(line.substring(repetitionIndex), expectedLine.substring(indexAfter));
        } else {
            assert.equal(actualLines[actualIndex++].trim(), expectedLine);
        }
    }
}