//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

// ES6 super chain tests

WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");

class SimpleParent {
    constructor() {
        this.foo = 'SimpleParent';
    }
}

let calls_to_ConstructorCountingParent = 0;
class ConstructorCountingParent {
    constructor() {
        calls_to_ConstructorCountingParent++;
    }
}

class UninitializedThisReturningArgumentConstructor extends SimpleParent {
    constructor(arg) {
        return arg;
    }
};

class InitializedThisReturningArgumentConstructor extends SimpleParent {
    constructor(arg) {
        super();
        return arg;
    }
};

var tests = [
    {
        name: "Simple derived class constructor using this",
        body: function () {
            class DerivedClassUsingThis extends SimpleParent {
                constructor() {
                    super();
                    this.bar = "DerivedClassUsingThis";
                }
            };

            let result = new DerivedClassUsingThis();

            assert.areEqual("DerivedClassUsingThis", result.bar, "This is initialized with the return value from super()");
            assert.areEqual("SimpleParent", result.foo, "Parent class returned the object from super to derived constructor");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof DerivedClassUsingThis, "Result object is instanceof derived class");
        }
    },
    {
        name: "Simple derived class constructor using this without calling super causing use before declaration error",
        body: function () {
            class DerivedClassUsingThisIllegally extends SimpleParent {
                constructor() {
                    this.bar = "DerivedClassUsingThisIllegally";
                }
            };

            assert.throws(function() { new DerivedClassUsingThisIllegally(); }, ReferenceError, "It's a ReferenceError to access 'this' without calling super", "Use before declaration");
        }
    },
    {
        name: "Simple derived class constructor using this before calling super causing use before declaration error",
        body: function () {
            class DerivedClassUsingThisIllegally extends SimpleParent {
                constructor() {
                    this.bar = "DerivedClassUsingThisIllegally";
                    super();
                }
            };

            assert.throws(function() { new DerivedClassUsingThisIllegally(); }, ReferenceError, "It's a ReferenceError to access 'this' without calling super", "Use before declaration");
        }
    },
    {
        name: "Simple derived class constructor using this via a lambda",
        body: function () {
            class DerivedClassUsingThisViaLambda extends SimpleParent {
                constructor() {
                    var arrow = () => { this.bar = 'DerivedClassUsingThisViaLambda'; };
                    super();
                    arrow();
                }
            };

            let result = new DerivedClassUsingThisViaLambda();

            assert.areEqual("DerivedClassUsingThisViaLambda", result.bar, "Arrow is defined using this (before super call) and called after super call in derived constructor");
            assert.areEqual("SimpleParent", result.foo, "Parent class returned the object from super to derived constructor");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof DerivedClassUsingThisViaLambda, "Result object is instanceof derived class");
        }
    },
    {
        name: "Simple derived class constructor using this without calling super via a lambda causing use before declaration error",
        body: function () {
            class DerivedClassUsingThisIllegallyViaLambda extends SimpleParent {
                constructor() {
                    var arrow = () => { this.bar = 'DerivedClassUsingThisIllegallyViaLambda'; };
                    arrow();
                }
            };

            assert.throws(function() { new DerivedClassUsingThisIllegallyViaLambda(); }, ReferenceError, "It's a ReferenceError to access 'this' without calling super", "Use before declaration");
        }
    },
    {
        name: "Simple derived class constructor using this before calling super via a lambda causing use before declaration error",
        body: function () {
            class DerivedClassUsingThisIllegallyViaLambda extends SimpleParent {
                constructor() {
                    var arrow = () => { this.bar = 'DerivedClassUsingThisIllegallyViaLambda'; };
                    arrow();
                    super();
                }
            };

            assert.throws(function() { new DerivedClassUsingThisIllegallyViaLambda(); }, ReferenceError, "It's a ReferenceError to access 'this' without calling super", "Use before declaration");
        }
    },
    {
        name: "Simple derived class constructor using this and super via a lambda",
        body: function () {
            class DerivedClassUsingThisAndSuperViaLambda extends SimpleParent {
                constructor() {
                    var this_arrow = () => { this.bar = 'DerivedClassUsingThisAndSuperViaLambda'; };
                    var super_arrow = () => { super(); }
                    super_arrow();
                    this_arrow();
                }
            };

            let result = new DerivedClassUsingThisAndSuperViaLambda();

            assert.areEqual("DerivedClassUsingThisAndSuperViaLambda", result.bar, "Arrow is defined using this (before super call) and called after super call in derived constructor");
            assert.areEqual("SimpleParent", result.foo, "Parent class returned the object from super to derived constructor");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof DerivedClassUsingThisAndSuperViaLambda, "Result object is instanceof derived class");
        }
    },
    {
        name: "Simple derived class constructor using this and super via a lambda (lambda with super call defined first)",
        body: function () {
            class DerivedClassUsingThisAndSuperViaLambda extends SimpleParent {
                constructor() {
                    var super_arrow = () => { super(); }
                    var this_arrow = () => { this.bar = 'DerivedClassUsingThisAndSuperViaLambda'; };
                    super_arrow();
                    this_arrow();
                }
            };

            let result = new DerivedClassUsingThisAndSuperViaLambda();

            assert.areEqual("DerivedClassUsingThisAndSuperViaLambda", result.bar, "Arrow is defined using this (before super call) and called after super call in derived constructor");
            assert.areEqual("SimpleParent", result.foo, "Parent class returned the object from super to derived constructor");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof DerivedClassUsingThisAndSuperViaLambda, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class constructor throws ReferenceError accessing this with no super call",
        body: function () {
            class IllegalThisAccessConstructor extends SimpleParent {
                constructor() {
                    this.bar = 'something';
                }
            };

            assert.throws(function() { new IllegalThisAccessConstructor(); }, ReferenceError, "It's a ReferenceError to access 'this' without calling super", "Use before declaration");
        }
    },
    {
        name: "Derived class constructor throws ReferenceError accessing this before super call",
        body: function () {
            class IllegalThisBeforeSuperConstructor extends SimpleParent {
                constructor() {
                    this.bar = 'something';
                    super();
                }
            };

            assert.throws(function() { new IllegalThisBeforeSuperConstructor(); }, ReferenceError, "It's a ReferenceError to access 'this' before calling super", "Use before declaration");
        }
    },
    {
        name: "Derived class throws ReferenceError with empty constructor (implicit access of this)",
        body: function () {
            class EmptyConstructor extends SimpleParent {
                constructor() {
                    // Implicitly "return this;"
                }
            };

            assert.throws(function() { new EmptyConstructor(); }, ReferenceError, "It's a ReferenceError to implicit return 'this' from class constructor without calling super", "Use before declaration");
        }
    },
    {
        name: "Derived class with default constructor returns initialized this argument",
        body: function () {
            class DefaultConstructor extends SimpleParent {
                // Implicitly
                // constructor(...args) { super(...args); }
            };

            let obj = new DefaultConstructor();

            assert.areEqual('SimpleParent', obj.foo, "Object from base constructor should have been returned.");
            assert.isTrue(obj instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(obj instanceof DefaultConstructor, "Result object is instanceof derived class");

            class DefaultConstructorReturningArgumentViaBaseClass extends UninitializedThisReturningArgumentConstructor {
                // Implicitly
                // constructor(...args) { super(...args); }
            };

            obj = new DefaultConstructorReturningArgumentViaBaseClass({ bar: 'DefaultConstructorReturningArgumentViaBaseClass' });

            assert.areEqual('DefaultConstructorReturningArgumentViaBaseClass', obj.bar, "Object from base constructor should have been returned.");
            assert.isFalse(obj instanceof UninitializedThisReturningArgumentConstructor, "Result object is not instanceof base class");
            assert.isFalse(obj instanceof DefaultConstructorReturningArgumentViaBaseClass, "Result object is not instanceof derived class");
        }
    },
    {
        name: "Derived class throws TypeError when returning non-object if this is initialized",
        body: function () {
            assert.throws(function() { new InitializedThisReturningArgumentConstructor(null); }, TypeError, "Returning null from derived constructor should throw TypeError", "Derived class constructor can return only object or undefined");
            assert.throws(function() { new InitializedThisReturningArgumentConstructor('string'); }, TypeError, "Returning 'string' from derived constructor should throw TypeError", "Derived class constructor can return only object or undefined");
            assert.throws(function() { new InitializedThisReturningArgumentConstructor(5); }, TypeError, "Returning 5 from derived constructor should throw TypeError", "Derived class constructor can return only object or undefined");
        }
    },
    {
        name: "Derived class throws TypeError when returning non-object if this is uninitialized",
        body: function () {
            assert.throws(function() { new UninitializedThisReturningArgumentConstructor(null); }, TypeError, "Returning null from derived constructor should throw TypeError", "Derived class constructor can return only object or undefined");
            assert.throws(function() { new UninitializedThisReturningArgumentConstructor('string'); }, TypeError, "Returning 'string' from derived constructor should throw TypeError", "Derived class constructor can return only object or undefined");
            assert.throws(function() { new UninitializedThisReturningArgumentConstructor(5); }, TypeError, "Returning 5 from derived constructor should throw TypeError", "Derived class constructor can return only object or undefined");
        }
    },
    {
        name: "TypeError is thrown when trying to derive from a function which is not a constructor",
        body: function () {
            assert.throws(function () { class A extends JSON { }; }, TypeError, "JSON object does not have an internal Construct slot", "Function is not a constructor");
            assert.throws(function () { class A extends Math { }; }, TypeError, "Math object does not have an internal Construct slot", "Function is not a constructor");

            function* gf(a) { yield 1 + a + this.a; }
            assert.throws(function () { class A extends gf { } }, TypeError, "Class deriving from a generator function throws TypeError", "Function is not a constructor");

            assert.throws(function () { class A extends (a) => a {} }, TypeError, "Class deriving from a lambda throws TypeError", "Function is not a constructor");
        }
    },
    {
        name: "Derived class returning undefined, returns 'this' instead",
        body: function () {
            class ImplicitReturnUndefinedNotInitializedThis extends SimpleParent {
                constructor() {
                    return;
                }
            }

            assert.throws(function() { new ImplicitReturnUndefinedNotInitializedThis(); }, ReferenceError, "Derived class constructor implicitly returning undefined with no super call throws ReferenceError", "Use before declaration");

            class ExplicitReturnUndefinedNotInitializedThis extends SimpleParent {
                constructor() {
                    return undefined;
                }
            }

            assert.throws(function() { new ExplicitReturnUndefinedNotInitializedThis(); }, ReferenceError, "Derived class constructor explicitly returning undefined with no super call throws ReferenceError", "Use before declaration");

            class ImplicitReturnUndefinedInitializedThis extends SimpleParent {
                constructor() {
                    super();
                    this.bar = 'ImplicitReturnUndefinedInitializedThis';
                    return;
                }
            }

            let result = new ImplicitReturnUndefinedInitializedThis();

            assert.areEqual('SimpleParent', result.foo, "Object from base constructor should have been returned.");
            assert.areEqual('ImplicitReturnUndefinedInitializedThis', result.bar, "'this' modified in derived constructor.");
            assert.isTrue(result instanceof ImplicitReturnUndefinedInitializedThis, "Result object is instanceof derived class");
            assert.isTrue(result instanceof SimpleParent, "Result object is not instanceof base class");

            class ExplicitReturnUndefinedInitializedThis extends SimpleParent {
                constructor() {
                    super();
                    this.bar = 'ExplicitReturnUndefinedInitializedThis';
                    return undefined;
                }
            }

            result = new ExplicitReturnUndefinedInitializedThis();

            assert.areEqual('SimpleParent', result.foo, "Object from base constructor should have been returned.");
            assert.areEqual('ExplicitReturnUndefinedInitializedThis', result.bar, "'this' modified in derived constructor.");
            assert.isTrue(result instanceof ExplicitReturnUndefinedInitializedThis, "Result object is instanceof derived class");
            assert.isTrue(result instanceof SimpleParent, "Result object is not instanceof base class");
        }
    },
    {
        name: "Derived class returns any object and avoids super call / this initialization",
        body: function () {
            let obj = new UninitializedThisReturningArgumentConstructor({ foo: 'value' });

            assert.areEqual('value', obj.foo, "We returned an object from the class constructor which didn't initialize 'this' so we should get that object back");
        }
    },
    {
        name: "Derived class throws ReferenceError with multiple calls to super ('this' is already initialized)",
        body: function () {
            // Reset call counter in parent
            calls_to_ConstructorCountingParent = 0;

            class IllegalSuperCallConstructor extends ConstructorCountingParent {
                constructor() {
                    super();
                    super();
                }
            };

            assert.throws(function() { new IllegalSuperCallConstructor(); }, ReferenceError, "It's a ReferenceError to call super multiple times in a derived class constructor", "Multiple calls to 'super' in a class constructor are not allowed");
            assert.areEqual(2, calls_to_ConstructorCountingParent, "We do call the super function body twice but we throw ReferenceError when trying to assign the 'this' value after the second super call");
        }
    },
    {
        name: "Derived class throws ReferenceError with multiple calls to super ('this' is already initialized) with one call in a lambda",
        body: function () {
            // Reset call counter in parent
            calls_to_ConstructorCountingParent = 0;

            class IllegalSuperCallConstructor extends ConstructorCountingParent {
                constructor() {
                    let arrow = () => { super(); };
                    super();
                    arrow();
                }
            };

            assert.throws(function() { new IllegalSuperCallConstructor(); }, ReferenceError, "It's a ReferenceError to call super multiple times in a derived class constructor", "Multiple calls to 'super' in a class constructor are not allowed");
            assert.areEqual(2, calls_to_ConstructorCountingParent, "We do call the super function body twice but we throw ReferenceError when trying to assign the 'this' value after the second super call");
        }
    },
    {
        name: "Derived class throws ReferenceError with multiple calls to super ('this' is already initialized) with multiple calls in lambdas",
        body: function () {
            // Reset call counter in parent
            calls_to_ConstructorCountingParent = 0;

            class IllegalSuperCallConstructor extends ConstructorCountingParent {
                constructor() {
                    let arrow = () => { super(); };
                    let arrow2 = () => { super(); };
                    arrow();
                    arrow2();
                }
            };

            assert.throws(function() { new IllegalSuperCallConstructor(); }, ReferenceError, "It's a ReferenceError to call super multiple times in a derived class constructor", "Multiple calls to 'super' in a class constructor are not allowed");
            assert.areEqual(2, calls_to_ConstructorCountingParent, "We do call the super function body twice but we throw ReferenceError when trying to assign the 'this' value after the second super call");
        }
    },
    {
        name: "Derived class throws ReferenceError with multiple calls to super ('this' is already initialized) with multiple calls in the same lambda",
        body: function () {
            // Reset call counter in parent
            calls_to_ConstructorCountingParent = 0;

            class IllegalSuperCallConstructor extends ConstructorCountingParent {
                constructor() {
                    let arrow = () => { super(); super(); };
                    arrow();
                }
            };

            assert.throws(function() { new IllegalSuperCallConstructor(); }, ReferenceError, "It's a ReferenceError to call super multiple times in a derived class constructor", "Multiple calls to 'super' in a class constructor are not allowed");
            assert.areEqual(2, calls_to_ConstructorCountingParent, "We do call the super function body twice but we throw ReferenceError when trying to assign the 'this' value after the second super call");
        }
    },
    {
        name: "Derived class constructor captures this and super in a lambda but doesn't call the lambda",
        body: function () {
            class DerivedClassCapturingThisAndSuper extends SimpleParent {
                constructor() {
                    let arrow = () => { this.bar = 'lambda'; super(); };
                    super();
                    this.bar = "DerivedClassCapturingThisAndSuper";
                }
            };

            let result = new DerivedClassCapturingThisAndSuper();

            assert.areEqual("DerivedClassCapturingThisAndSuper", result.bar, "This is initialized with the return value from super()");
            assert.areEqual("SimpleParent", result.foo, "Parent class returned the object from super to derived constructor");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof DerivedClassCapturingThisAndSuper, "Result object is instanceof derived class");
        }
    },
    {
        name: "Creating an instance of base class doesn't block new.target calculation for super chain",
        body: function () {
            let parent = new SimpleParent();
            class SimpleDerivedClass extends SimpleParent {
                constructor() {
                    super();
                    this.bar = "SimpleDerivedClass";
                }
            };

            let result = new SimpleDerivedClass();

            assert.isFalse(Object.hasOwnProperty(parent, 'bar'), "Parent object doesn't have derived class values");
            assert.areEqual("SimpleParent", parent.foo, "Parent class initialized the object");
            assert.isTrue(parent instanceof SimpleParent, "Parent object is instanceof base class");
            assert.isFalse(parent instanceof SimpleDerivedClass, "Parent object isn't instanceof derived class");

            assert.areEqual("SimpleDerivedClass", result.bar, "This is initialized with the return value from super()");
            assert.areEqual("SimpleParent", result.foo, "Parent class returned the object from super to derived constructor");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SimpleDerivedClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Chain of multiple derived classes",
        body: function () {
            class MiddleDerivedClass extends SimpleParent {
                constructor() {
                    super();
                    this.bar = "MiddleDerivedClass";
                }
            };

            class BottomDerivedClass extends MiddleDerivedClass {
                constructor() {
                    super();
                    this.baz = "BottomDerivedClass";
                }
            };

            let result = new BottomDerivedClass();

            assert.areEqual("BottomDerivedClass", result.baz, "This is initialized with the return value from super()");
            assert.areEqual("MiddleDerivedClass", result.bar, "This is initialized with the return value from super()");
            assert.areEqual("SimpleParent", result.foo, "Parent class returned the object from super to derived constructor");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof MiddleDerivedClass, "Result object is instanceof derived class");
            assert.isTrue(result instanceof BottomDerivedClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class constructor leaks lambda which performs super",
        body: function() {
            class A {
                constructor() {
                    this.a = 'A';
                }
            }

            class B extends A {
                constructor() {
                    super();
                    this.b = 'B';
                }
            }

            class C extends B {
                constructor() {
                    var s = () => { super(); this.c = 'C'; return this; };
                    return s;
                }
            }

            var maker = new C();
            var result = maker();

            assert.areEqual("C", result.c, "This is initialized with the return value from super()");
            assert.areEqual("B", result.b, "This is initialized with the return value from super()");
            assert.areEqual("A", result.a, "Parent class returned the object from super to derived constructor");
            assert.isTrue(result instanceof A, "Result object is instanceof base class");
            assert.isTrue(result instanceof B, "Result object is instanceof derived class");
            assert.isTrue(result instanceof C, "Result object is instanceof derived class");

            assert.throws(function() { var result2 = maker(); }, ReferenceError, "Calling the escaped lambda again will throw since 'this' of the parent is already initialized", "Multiple calls to 'super' in a class constructor are not allowed");

            // creating a new lambda should let us construct one more object
            maker = new C();
            result = maker();

            assert.areEqual("C", result.c, "This is initialized with the return value from super()");
            assert.areEqual("B", result.b, "This is initialized with the return value from super()");
            assert.areEqual("A", result.a, "Parent class returned the object from super to derived constructor");
            assert.isTrue(result instanceof A, "Result object is instanceof base class");
            assert.isTrue(result instanceof B, "Result object is instanceof derived class");
            assert.isTrue(result instanceof C, "Result object is instanceof derived class");

            assert.throws(function() { var result2 = maker(); }, ReferenceError, "Calling the escaped lambda again will throw since 'this' of the parent is already initialized", "Multiple calls to 'super' in a class constructor are not allowed");
        }
    },
    {
        name: "Derived class constructor leaks lambda which performs super (lambda comes from middle derived class)",
        body: function() {
            class A {
                constructor() {
                    this.a = 'A';
                }
            }

            class B extends A {
                constructor() {
                    var s = () => { super(); this.b = 'B'; return this; };
                    return s;
                }
            }

            class C extends B {
                constructor() {
                    super();
                }
            }

            var maker = new C();
            var result = maker();

            assert.areEqual("B", result.b, "This is initialized with the return value from super()");
            assert.areEqual("A", result.a, "Parent class returned the object from super to derived constructor");
            assert.isTrue(result instanceof A, "Result object is instanceof base class");
            assert.isTrue(result instanceof B, "Result object is instanceof derived class");
            assert.isTrue(result instanceof C, "Result object is instanceof derived class");

            assert.throws(function() { var result2 = maker(); }, ReferenceError, "Calling the escaped lambda again will throw since 'this' of the parent is already initialized", "Multiple calls to 'super' in a class constructor are not allowed");

            // creating a new lambda should let us construct one more object
            maker = new C();
            result = maker();

            assert.areEqual("B", result.b, "This is initialized with the return value from super()");
            assert.areEqual("A", result.a, "Parent class returned the object from super to derived constructor");
            assert.isTrue(result instanceof A, "Result object is instanceof base class");
            assert.isTrue(result instanceof B, "Result object is instanceof derived class");
            assert.isTrue(result instanceof C, "Result object is instanceof derived class");

            assert.throws(function() { var result2 = maker(); }, ReferenceError, "Calling the escaped lambda again will throw since 'this' of the parent is already initialized", "Multiple calls to 'super' in a class constructor are not allowed");
        }
    },
    {
        name: "Derived class constructor leaks lambda which references 'this' in TDZ",
        body: function() {
            class A {
                constructor() {
                    this.a = 'A';
                }
            }

            class B extends A {
                constructor() {
                    super();
                    this.b = 'B';
                }
            }

            class C extends B {
                constructor() {
                    var s = () => { this.c = 'C'; super(); return this; };
                    return s;
                }
            }

            var maker = new C();

            assert.throws(function() { var result = maker(); }, ReferenceError, "Calling the escaped lambda throws since 'this' is accessed before super call", "Use before declaration");
        }
    },
    {
        name: "Derived class constructor leaks lambda which references 'this' in TDZ (lambda comes from middle derived class)",
        body: function() {
            class A {
                constructor() {
                    this.a = 'A';
                }
            }

            class B extends A {
                constructor() {
                    var s = () => { this.b = 'B'; super(); return this; };
                    return s;
                }
            }

            class C extends B {
                constructor() {
                    super();
                }
            }

            var maker = new C();

            assert.throws(function() { var result = maker(); }, ReferenceError, "Calling the escaped lambda throws since 'this' is accessed before super call", "Use before declaration");
        }
    },
    {
        name: "Derived class with null extends expression cannot be new'd",
        body: function () {
            class NullExtendsExpression extends null {
            };

            assert.throws(function() { new NullExtendsExpression(); }, TypeError, "Class that extends null throws when we attempt to call super as [[construct]]");
        }
    },
    {
        name: "Derived class with null extends expression can be new'd if constructor returns object",
        body: function () {
            class NullExtendsExpressionWithConstructor extends null {
                constructor(arg) {
                    return arg;
                }
            };

            var result = new NullExtendsExpressionWithConstructor({foo:'value'});

            assert.areEqual('value', result.foo, "Class derived from null expression can return an object safely");
            assert.isFalse(result instanceof NullExtendsExpressionWithConstructor, "Result object is not instanceof class");
        }
    },
    {
        name: "Derived constructor with a super call that isn't executed",
        body: function () {
            let returnFalse = () => { return false; };
            class DerivedClassAccessThisImplicitReturn extends SimpleParent {
                constructor() {
                    if (returnFalse()) {
                        super();
                    }
                    this.bar = '';
                }
            };

            assert.throws(function() { new DerivedClassAccessThisImplicitReturn(); }, ReferenceError, "When super isn't called, this is undecl", "Use before declaration");

            class DerivedClassImplicitReturn extends SimpleParent {
                constructor() {
                    if (returnFalse()) {
                        super();
                    }
                }
            };

            assert.throws(function() { new DerivedClassImplicitReturn(); }, ReferenceError, "When super isn't called, this is undecl", "Use before declaration");

            class DerivedClassAccessThisExplicitReturn extends SimpleParent {
                constructor() {
                    if (returnFalse()) {
                        super();
                    }
                    this.bar = '';
                    return this;
                }
            };

            assert.throws(function() { new DerivedClassAccessThisExplicitReturn(); }, ReferenceError, "When super isn't called, this is undecl", "Use before declaration");

            class DerivedClassExplicitReturn extends SimpleParent {
                constructor() {
                    if (returnFalse()) {
                        super();
                    }
                    return this;
                }
            };

            assert.throws(function() { new DerivedClassExplicitReturn(); }, ReferenceError, "When super isn't called, this is undecl", "Use before declaration");

            class DerivedClassAccessThisViaLambdaImplicitReturn extends SimpleParent {
                constructor() {
                    let arrow = () => { this.foo = ''; }
                    if (returnFalse()) {
                        super();
                    }
                    arrow();
                }
            };

            assert.throws(function() { new DerivedClassAccessThisViaLambdaImplicitReturn(); }, ReferenceError, "When super isn't called, this is undecl", "Use before declaration");

            class DerivedClassAccessThisViaLambdaExplicitReturn extends SimpleParent {
                constructor() {
                    let arrow = () => { this.foo = ''; }
                    if (returnFalse()) {
                        super();
                    }
                    arrow();
                    return this;
                }
            };

            assert.throws(function() { new DerivedClassAccessThisViaLambdaExplicitReturn(); }, ReferenceError, "When super isn't called, this is undecl", "Use before declaration");

            class DerivedClassWithThisScopeCaptureNoAccessImplicitReturn extends SimpleParent {
                constructor() {
                    let arrow = () => { this.foo = ''; }
                    if (returnFalse()) {
                        super();
                    }
                }
            };

            assert.throws(function() { new DerivedClassWithThisScopeCaptureNoAccessImplicitReturn(); }, ReferenceError, "When super isn't called, this is undecl", "Use before declaration");

            class DerivedClassWithThisScopeCaptureNoAccessExplicitReturn extends SimpleParent {
                constructor() {
                    let arrow = () => { this.foo = ''; }
                    if (returnFalse()) {
                        super();
                    }
                    return this;
                }
            };

            assert.throws(function() { new DerivedClassWithThisScopeCaptureNoAccessExplicitReturn(); }, ReferenceError, "When super isn't called, this is undecl", "Use before declaration");
        }
    },
    {
        name: "Derived class with super call inside an eval",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    eval('super();');
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class with super call inside an eval which accesses this",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    eval('super(); this.bar = "SuperCallInEvalClass";');
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.areEqual('SuperCallInEvalClass', result.bar, "Class derived from SimpleParent can return an object safely");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class with super call inside an eval which accesses new.target",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    eval('assert.areEqual(new.target, SuperCallInEvalClass, "new.target === SuperCallInEvalClass"); super();');
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class with super call inside an eval which accesses new.target and this",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    eval('assert.areEqual(new.target, SuperCallInEvalClass); super(); this.bar = "SuperCallInEvalClass";');
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.areEqual('SuperCallInEvalClass', result.bar, "Result object has derived field bar set to 'SuperCallInEvalClass'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class with super call inside an eval access to this inside eval and constructor",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    eval('super(); this.bar = "SuperCallInEvalClass";');
                    this.baz = "SuperCallInEvalClass_ctor";
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.areEqual('SuperCallInEvalClass', result.bar, "Result object has derived field bar set to 'SuperCallInEvalClass'");
            assert.areEqual('SuperCallInEvalClass_ctor', result.baz, "Result object has derived field baz set to 'SuperCallInEvalClass_ctor'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class 'this' access inside an eval",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    super();
                    eval('this.bar = "SuperCallInEvalClass";');
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.areEqual('SuperCallInEvalClass', result.bar, "Result object has derived field bar set to 'SuperCallInEvalClass'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class 'this' access inside and outside an eval",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    super();
                    eval('this.bar = "SuperCallInEvalClass";');
                    this.baz = "SuperCallInEvalClass_ctor";
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.areEqual('SuperCallInEvalClass', result.bar, "Result object has derived field bar set to 'SuperCallInEvalClass'");
            assert.areEqual('SuperCallInEvalClass_ctor', result.baz, "Result object has derived field baz set to 'SuperCallInEvalClass_ctor'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class super call inside an eval with an unused arrow super call",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    let arrow = () => { super(); }
                    eval('super(); this.bar = "SuperCallInEvalClass";');
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.areEqual('SuperCallInEvalClass', result.bar, "Result object has derived field bar set to 'SuperCallInEvalClass'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class super call inside an arrow with this access in an eval",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    let arrow = () => { super(); }
                    arrow();
                    eval('this.bar = "SuperCallInEvalClass";');
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.areEqual('SuperCallInEvalClass', result.bar, "Result object has derived field bar set to 'SuperCallInEvalClass'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class super call inside an arrow accessing new.target with this access in an eval",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    let arrow = () => {
                        assert.areEqual(SuperCallInEvalClass, new.target, "SuperCallInEvalClass === new.target");
                        super();
                    }
                    arrow();
                    eval('this.bar = "SuperCallInEvalClass";');
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.areEqual('SuperCallInEvalClass', result.bar, "Result object has derived field bar set to 'SuperCallInEvalClass'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class with arrow and eval accessing new.target, this - super in the arrow",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    let arrow = () => {
                        assert.areEqual(SuperCallInEvalClass, new.target, "SuperCallInEvalClass === new.target");
                        super();
                        this.baz = "SuperCallInEvalClass_arrow";
                    }
                    arrow();
                    eval('assert.areEqual(SuperCallInEvalClass, new.target, "SuperCallInEvalClass === new.target"); this.bar = "SuperCallInEvalClass";');
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.areEqual('SuperCallInEvalClass', result.bar, "Result object has derived field bar set to 'SuperCallInEvalClass'");
            assert.areEqual('SuperCallInEvalClass_arrow', result.baz, "Result object has derived field baz set to 'SuperCallInEvalClass_arrow'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class with arrow and eval accessing new.target, this - super in the eval",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    let arrow = () => {
                        assert.areEqual(SuperCallInEvalClass, new.target, "SuperCallInEvalClass === new.target");
                        this.baz = "SuperCallInEvalClass_arrow";
                    }
                    eval('assert.areEqual(SuperCallInEvalClass, new.target, "SuperCallInEvalClass === new.target"); super(); this.bar = "SuperCallInEvalClass";');
                    arrow();
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.areEqual('SuperCallInEvalClass', result.bar, "Result object has derived field bar set to 'SuperCallInEvalClass'");
            assert.areEqual('SuperCallInEvalClass_arrow', result.baz, "Result object has derived field baz set to 'SuperCallInEvalClass_arrow'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class with arrow and multiple evals accessing new.target, this - super in the eval",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    eval('assert.areEqual(SuperCallInEvalClass, new.target, "SuperCallInEvalClass === new.target");');
                    let arrow = () => {
                        assert.areEqual(SuperCallInEvalClass, new.target, "SuperCallInEvalClass === new.target");
                        this.baz = "SuperCallInEvalClass_arrow";
                    }
                    eval('super();');
                    arrow();
                    eval('this.bar = "SuperCallInEvalClass";');
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.areEqual('SuperCallInEvalClass', result.bar, "Result object has derived field bar set to 'SuperCallInEvalClass'");
            assert.areEqual('SuperCallInEvalClass_arrow', result.baz, "Result object has derived field baz set to 'SuperCallInEvalClass_arrow'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class testing all arrow/eval scenarios together",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor(callSuperInCtor, callSuperInLambda, callSuperInEval) {
                    assert.areEqual(SuperCallInEvalClass, new.target, "SuperCallInEvalClass === new.target");
                    eval('assert.areEqual(SuperCallInEvalClass, new.target, "SuperCallInEvalClass === new.target");');

                    let arrow_pre = () => {
                        assert.areEqual(SuperCallInEvalClass, new.target, "SuperCallInEvalClass === new.target");
                    }
                    let arrow_post = () => {
                        this.baz = "SuperCallInEvalClass_arrow";
                    }
                    let arrow_super = () => {
                        super();
                    }

                    arrow_pre();
                    if (callSuperInCtor) {
                        super();
                    }
                    if (callSuperInLambda) {
                        arrow_super();
                    }
                    if (callSuperInEval) {
                        eval('super();');
                    }

                    arrow_post();
                    eval('this.bar = "SuperCallInEvalClass_eval";');
                    this.bot = 'SuperCallInEvalClass_ctor';
                }
            };

            function verifyObj(result) {
                assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
                assert.areEqual('SuperCallInEvalClass_eval', result.bar, "Result object has derived field bar set to 'SuperCallInEvalClass_eval'");
                assert.areEqual('SuperCallInEvalClass_arrow', result.baz, "Result object has derived field baz set to 'SuperCallInEvalClass_arrow'");
                assert.areEqual('SuperCallInEvalClass_ctor', result.bot, "Result object has derived field bot set to 'SuperCallInEvalClass_ctor'");
                assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
                assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
            }

            verifyObj(new SuperCallInEvalClass(true,false,false));
            verifyObj(new SuperCallInEvalClass(false,true,false));
            verifyObj(new SuperCallInEvalClass(false,false,true));

            assert.throws(function() { new SuperCallInEvalClass(false,false,false); }, ReferenceError, "Not calling super at all will cause a ReferenceError", "Use before declaration");
            assert.throws(function() { new SuperCallInEvalClass(true,true,false); }, ReferenceError, "Calling super in the ctor and a lambda throws a ReferenceError", "Multiple calls to 'super' in a class constructor are not allowed");
            assert.throws(function() { new SuperCallInEvalClass(true,false,true); }, ReferenceError, "Calling super in the ctor and eval throws a ReferenceError", "Multiple calls to 'super' in a class constructor are not allowed");
            assert.throws(function() { new SuperCallInEvalClass(false,true,true); }, ReferenceError, "Calling super in a lambda and eval throws a ReferenceError", "Multiple calls to 'super' in a class constructor are not allowed");
        }
    },
    {
        name: "Chained derived constructors performing super calls",
        body: function () {
            class DerivedClassUsingSuperInEval extends SimpleParent {
                constructor() {
                    eval('super();');
                }
            };
            class DerivedClassUsingSuperInArrow extends DerivedClassUsingSuperInEval {
                constructor() {
                    let arrow = () => { super(); }
                    arrow();
                }
            };
            class DerivedClassUsingDefaultConstructor extends DerivedClassUsingSuperInArrow {

            }
            class BottomLevelDerivedClass extends DerivedClassUsingDefaultConstructor {
                constructor() {
                    super();
                }
            };

            var result = new BottomLevelDerivedClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof DerivedClassUsingSuperInEval, "Result object is instanceof derived class");
            assert.isTrue(result instanceof DerivedClassUsingSuperInArrow, "Result object is instanceof derived class");
            assert.isTrue(result instanceof DerivedClassUsingDefaultConstructor, "Result object is instanceof derived class");
            assert.isTrue(result instanceof BottomLevelDerivedClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class with nested evals performing super",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    var inner = "SuperCallInEvalClass_inner_eval";
                    var outer = "SuperCallInEvalClass_outer_eval";
                    eval('eval("super(); this.bar = inner;"); this.baz = outer;');
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.areEqual('SuperCallInEvalClass_inner_eval', result.bar, "Result object has derived field bar set to 'SuperCallInEvalClass_inner_eval'");
            assert.areEqual('SuperCallInEvalClass_outer_eval', result.baz, "Result object has derived field baz set to 'SuperCallInEvalClass_outer_eval'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class with eval in arrow performing super",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    var inner = "SuperCallInEvalClass_inner_eval";
                    var outer = "SuperCallInEvalClass_outer_eval";
                    var arrow = () => {
                        eval("super(); this.bar = inner;");
                        this.baz = outer;
                    };
                    arrow();
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.areEqual('SuperCallInEvalClass_inner_eval', result.bar, "Result object has derived field bar set to 'SuperCallInEvalClass_inner_eval'");
            assert.areEqual('SuperCallInEvalClass_outer_eval', result.baz, "Result object has derived field baz set to 'SuperCallInEvalClass_outer_eval'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class with nested eval in arrow performing super",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    var inner = "SuperCallInEvalClass_inner_eval";
                    var outer = "SuperCallInEvalClass_outer_eval";
                    var arrow = () => {
                        eval(`eval("super(); this.bar = inner;");`);
                        this.baz = outer;
                    };
                    arrow();
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.areEqual('SuperCallInEvalClass_inner_eval', result.bar, "Result object has derived field bar set to 'SuperCallInEvalClass_inner_eval'");
            assert.areEqual('SuperCallInEvalClass_outer_eval', result.baz, "Result object has derived field baz set to 'SuperCallInEvalClass_outer_eval'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class with nested eval nested in arrow performing super",
        body: function () {
            class SuperCallInEvalClass extends SimpleParent {
                constructor() {
                    var inner = "SuperCallInEvalClass_inner_eval";
                    var outer = "SuperCallInEvalClass_outer_eval";
                    var arrow = () => {
                        eval(`eval("super(); this.bar = inner;");`);
                        this.baz = outer;
                    };
                    var exec_arrow = () => {
                        arrow();
                    };
                    exec_arrow();
                }
            };

            var result = new SuperCallInEvalClass();

            assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
            assert.areEqual('SuperCallInEvalClass_inner_eval', result.bar, "Result object has derived field bar set to 'SuperCallInEvalClass_inner_eval'");
            assert.areEqual('SuperCallInEvalClass_outer_eval', result.baz, "Result object has derived field baz set to 'SuperCallInEvalClass_outer_eval'");
            assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
            assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
        }
    },
    {
        name: "Derived class with nested constructor calls and arrow performing super inside eval",
        body: function () {
            class B { }
            class A extends B
            {
                constructor()
                {
                    eval("(()=>{ super(); this.value = 1; class X extends Object { constructor() { eval(\"super(); this.value = 2; \"); } } var x = new X(); this.x = x;})()");
                }
            }
            var a=new A();

            assert.isTrue(a instanceof A, "object a should be instanceof derived class");
            assert.isTrue(a instanceof B, "object should be instanceof base class");
            assert.areEqual(1, a.value, "assignment to 'this' inside constructor eval nested arrow function");
            assert.areEqual(2, a.x.value, "assignment to 'this' inside nested constructor and eval");
        }
    },
    {
         name: "Derived class with arrow in eval performing super",
         body: function () {
             class SuperCallInEvalClass extends SimpleParent {
                 constructor() {
                     var inner = "SuperCallInEvalClass_inner_eval";
                     var outer = "SuperCallInEvalClass_outer_eval";
                     eval('(()=>{super(); this.bar = inner;})(); this.baz = outer;');
                 }
             };

             var result = new SuperCallInEvalClass();

             assert.areEqual('SimpleParent', result.foo, "Class derived from SimpleParent has foo field set to 'SimpleParent'");
             assert.areEqual('SuperCallInEvalClass_inner_eval', result.bar, "Result object has derived field bar set to 'SuperCallInEvalClass_inner_eval'");
             assert.areEqual('SuperCallInEvalClass_outer_eval', result.baz, "Result object has derived field baz set to 'SuperCallInEvalClass_outer_eval'");
             assert.isTrue(result instanceof SimpleParent, "Result object is instanceof base class");
             assert.isTrue(result instanceof SuperCallInEvalClass, "Result object is instanceof derived class");
         }
    },
    {
        name: "Eval class hierarchy with super call",
        body: function () {
            let result = eval(`
                class EvalSimpleParent {
                    constructor() {
                        this.foo = "EvalSimpleParent";
                    }
                };
                class EvalDerivedClass extends EvalSimpleParent {
                    constructor() {
                        super();
                        this.bar = "EvalDerivedClass";
                    }
                };
                var o = new EvalDerivedClass();

                assert.isTrue(o instanceof EvalSimpleParent, "Result object is instanceof base class");
                assert.isTrue(o instanceof EvalDerivedClass, "Result object is instanceof derived class");

                o;
            `);

            assert.areEqual('EvalSimpleParent', result.foo, "Class derived from EvalSimpleParent has foo field set to 'EvalSimpleParent'");
            assert.areEqual('EvalDerivedClass', result.bar, "Result object has derived field bar set to 'EvalDerivedClass'");
        }
    },
    {
        name: "Eval class hierarchy with super call inside arrow function",
        body: function () {
            let result = eval(`
                class EvalSimpleParent {
                    constructor() {
                        this.foo = "EvalSimpleParent";
                    }
                };
                class EvalDerivedClass extends EvalSimpleParent {
                    constructor() {
                        var inner = "EvalDerivedClass_inner";
                        var outer = "EvalDerivedClass_outer";
                        var arrow = () => {
                            super();
                            this.baz = inner;
                        };
                        arrow();
                        this.bar = outer;
                    }
                };
                var o = new EvalDerivedClass();

                assert.isTrue(o instanceof EvalSimpleParent, "Result object is instanceof base class");
                assert.isTrue(o instanceof EvalDerivedClass, "Result object is instanceof derived class");

                o;
            `);

            assert.areEqual('EvalSimpleParent', result.foo, "Class derived from EvalSimpleParent has foo field set to 'EvalSimpleParent'");
            assert.areEqual('EvalDerivedClass_outer', result.bar, "Result object has derived field bar set to 'EvalDerivedClass_outer'");
            assert.areEqual('EvalDerivedClass_inner', result.baz, "Result object has derived field baz set to 'EvalDerivedClass_inner'");
        }
    },
    {
        name: "Eval class hierarchy with super call inside nested eval",
        body: function () {
            let result = eval(`
                class EvalSimpleParent {
                    constructor() {
                        this.foo = "EvalSimpleParent";
                    }
                };
                class EvalDerivedClass extends EvalSimpleParent {
                    constructor() {
                        var inner = "EvalDerivedClass_inner";
                        var outer = "EvalDerivedClass_outer";
                        eval('super(); this.bar = inner;');
                        this.baz = outer;
                    }
                };
                var o = new EvalDerivedClass();

                assert.isTrue(o instanceof EvalSimpleParent, "Result object is instanceof base class");
                assert.isTrue(o instanceof EvalDerivedClass, "Result object is instanceof derived class");

                o;
            `);

            assert.areEqual('EvalSimpleParent', result.foo, "Class derived from EvalSimpleParent has foo field set to 'EvalSimpleParent'");
            assert.areEqual('EvalDerivedClass_inner', result.bar, "Result object has derived field bar set to 'EvalDerivedClass_inner'");
            assert.areEqual('EvalDerivedClass_outer', result.baz, "Result object has derived field baz set to 'EvalDerivedClass_outer'");
        }
    },
    {
        name: "Eval class hierarchy with super call inside eval inside arrow function",
        body: function () {
            let result = eval(`
                class EvalSimpleParent {
                    constructor() {
                        this.foo = "EvalSimpleParent";
                    }
                };
                class EvalDerivedClass extends EvalSimpleParent {
                    constructor() {
                        var moreinner = "EvalDerivedClass_moreinner";
                        var inner = "EvalDerivedClass_inner";
                        var outer = "EvalDerivedClass_outer";
                        var arrow = () => {
                            eval('super(); this.bat = moreinner;');
                            this.baz = inner;
                        };
                        arrow();
                        this.bar = outer;
                    }
                };
                var o = new EvalDerivedClass();

                assert.isTrue(o instanceof EvalSimpleParent, "Result object is instanceof base class");
                assert.isTrue(o instanceof EvalDerivedClass, "Result object is instanceof derived class");

                o;
            `);

            assert.areEqual('EvalSimpleParent', result.foo, "Class derived from EvalSimpleParent has foo field set to 'EvalSimpleParent'");
            assert.areEqual('EvalDerivedClass_outer', result.bar, "Result object has derived field bar set to 'EvalDerivedClass_outer'");
            assert.areEqual('EvalDerivedClass_inner', result.baz, "Result object has derived field baz set to 'EvalDerivedClass_inner'");
            assert.areEqual('EvalDerivedClass_moreinner', result.bat, "Result object has derived field bat set to 'EvalDerivedClass_moreinner'");
        }
    },
    {
        name: "ES5-style class as a base class - explicit return of this from base",
        body: function () {
            function Base() {
                assert.areEqual(new.target, Derived, "new.target === Derived");
                this.foo = "Base";
                return this;
            }
            Base.prototype = {
                constructor: Base
            }
            class Derived extends Base {
                constructor() {
                    super();
                    this.bar = "Derived";
                }
            }
            let result = new Derived();

            assert.isTrue(result instanceof Base, "Result object is instanceof base class");
            assert.isTrue(result instanceof Derived, "Result object is instanceof derived class");
            assert.areEqual('Base', result.foo, "Class derived from Base has foo field set to 'Base'");
            assert.areEqual('Derived', result.bar, "Result object has derived field bar set to 'Derived'");
        }
    },
    {
        name: "ES5-style class as a base class with the super call inside an eval - explicit return of this from base",
        body: function () {
            function Base() {
                assert.areEqual(new.target, Derived, "new.target === Derived");
                this.foo = "Base";
                return this;
            }
            Base.prototype = {
                constructor: Base
            }
            class Derived extends Base {
                constructor() {
                    eval('super();');
                    this.bar = "Derived";
                }
            }
            let result = new Derived();

            assert.isTrue(result instanceof Base, "Result object is instanceof base class");
            assert.isTrue(result instanceof Derived, "Result object is instanceof derived class");
            assert.areEqual('Base', result.foo, "Class derived from Base has foo field set to 'Base'");
            assert.areEqual('Derived', result.bar, "Result object has derived field bar set to 'Derived'");
        }
    },
    {
        name: "ES5-style class as a base class with the super call inside an arrow - explicit return of this from base",
        body: function () {
            function Base() {
                assert.areEqual(new.target, Derived, "new.target === Derived");
                this.foo = "Base";
                return this;
            }
            Base.prototype = {
                constructor: Base
            }
            class Derived extends Base {
                constructor() {
                    let arrow = () => { super(); };
                    arrow();
                    this.bar = "Derived";
                }
            }
            let result = new Derived();

            assert.isTrue(result instanceof Base, "Result object is instanceof base class");
            assert.isTrue(result instanceof Derived, "Result object is instanceof derived class");
            assert.areEqual('Base', result.foo, "Class derived from Base has foo field set to 'Base'");
            assert.areEqual('Derived', result.bar, "Result object has derived field bar set to 'Derived'");
        }
    },
    {
        name: "ES5-style class as a base class - implicit return of this from base",
        body: function () {
            function Base() {
                assert.areEqual(new.target, Derived, "new.target === Derived");
                this.foo = "Base";
            }
            Base.prototype = {
                constructor: Base
            }
            class Derived extends Base {
                constructor() {
                    super();
                    this.bar = "Derived";
                }
            }
            let result = new Derived();

            assert.isTrue(result instanceof Base, "Result object is instanceof base class");
            assert.isTrue(result instanceof Derived, "Result object is instanceof derived class");
            assert.areEqual('Base', result.foo, "Class derived from Base has foo field set to 'Base'");
            assert.areEqual('Derived', result.bar, "Result object has derived field bar set to 'Derived'");
        }
    },
    {
        name: "ES5-style class as a base class with the super call inside an eval - implicit return of this from base",
        body: function () {
            function Base() {
                assert.areEqual(new.target, Derived, "new.target === Derived");
                this.foo = "Base";
            }
            Base.prototype = {
                constructor: Base
            }
            class Derived extends Base {
                constructor() {
                    eval('super();');
                    this.bar = "Derived";
                }
            }
            let result = new Derived();

            assert.isTrue(result instanceof Base, "Result object is instanceof base class");
            assert.isTrue(result instanceof Derived, "Result object is instanceof derived class");
            assert.areEqual('Base', result.foo, "Class derived from Base has foo field set to 'Base'");
            assert.areEqual('Derived', result.bar, "Result object has derived field bar set to 'Derived'");
        }
    },
    {
        name: "ES5-style class as a base class with the super call inside an arrow - implicit return of this from base",
        body: function () {
            function Base() {
                assert.areEqual(new.target, Derived, "new.target === Derived");
                this.foo = "Base";
            }
            Base.prototype = {
                constructor: Base
            }
            class Derived extends Base {
                constructor() {
                    let arrow = () => { super(); };
                    arrow();
                    this.bar = "Derived";
                }
            }
            let result = new Derived();

            assert.isTrue(result instanceof Base, "Result object is instanceof base class");
            assert.isTrue(result instanceof Derived, "Result object is instanceof derived class");
            assert.areEqual('Base', result.foo, "Class derived from Base has foo field set to 'Base'");
            assert.areEqual('Derived', result.bar, "Result object has derived field bar set to 'Derived'");
        }
    },
    {
        name: "Function as a base class can't assign this to non-object via super call",
        body: function () {
            function BaseReturnsArgument(arg) {
                assert.areEqual(new.target, DerivedFromBase, "new.target === DerivedFromBase");
                this.foo = 'BaseReturnsArgument';
                return arg;
            }
            BaseReturnsArgument.prototype = {
                constructor: BaseReturnsArgument
            }
            class DerivedFromBase extends BaseReturnsArgument {
                constructor(arg) {
                    super(arg);
                    assert.isFalse(this === arg, "If base function returns non-object, 'this' is returned instead");
                    this.bar = 'DerivedFromBase';
                }
            }

            function testDerivedClass(arg) {
                let result = new DerivedFromBase(arg);

                assert.isTrue(result instanceof BaseReturnsArgument, "Result object is instanceof base class");
                assert.isTrue(result instanceof DerivedFromBase, "Result object is instanceof derived class");
                assert.areEqual('BaseReturnsArgument', result.foo, "Class derived from Base has foo field set to 'Base'");
                assert.areEqual('DerivedFromBase', result.bar, "Result object has derived field bar set to 'Derived'");
            }

            testDerivedClass();
            testDerivedClass(undefined);
            testDerivedClass(null);
            testDerivedClass('string');
            testDerivedClass(5);
            testDerivedClass(Symbol());
        }
    },
    {
        name: "Function as a base class can return an object to override this",
        body: function () {
            function Base() {
                assert.areEqual(new.target, Derived, "new.target === Derived");
                this.foo = 'bad';
                return { foo: 'Base' };
            }
            Base.prototype = {
                constructor: Base
            }
            class Derived extends Base {
                constructor() {
                    super();
                    this.bar = 'Derived';
                }
            }

            let result = new Derived();

            assert.isFalse(result instanceof Base, "Result object is instanceof base class");
            assert.isFalse(result instanceof Derived, "Result object is instanceof derived class");
            assert.areEqual('Base', result.foo, "Class derived from Base has foo field set to 'Base'");
            assert.areEqual('Derived', result.bar, "Result object has derived field bar set to 'Derived'");
        }
    },
    {
        name: "Super call with expressions in arguments",
        body: function () {
            function Base() {
                assert.areEqual(new.target, Derived, "new.target === Derived");
                assert.areEqual(3, arguments[0], 'arguments[0] === 3')
                assert.areEqual('str', arguments[1], 'arguments[1] === "str"')
                assert.areEqual(1, arguments[2], 'arguments[2] === 1')
                assert.areEqual(2, arguments[3], 'arguments[3] === 2')
                assert.areEqual(3, arguments[4], 'arguments[4] === 3')
                assert.areEqual(5, arguments.length, 'arguments.length === 5');
                this.foo = 'Base';
            }
            Base.prototype = {
                constructor: Base
            }
            function foo() { return 'str'; }
            class Derived extends Base {
                constructor() {
                    super(1+2,foo(),...[1,2,3]);
                    this.bar = 'Derived';
                }
            }

            let result = new Derived();

            assert.isTrue(result instanceof Base, "Result object is instanceof base class");
            assert.isTrue(result instanceof Derived, "Result object is instanceof derived class");
            assert.areEqual('Base', result.foo, "Class derived from Base has foo field set to 'Base'");
            assert.areEqual('Derived', result.bar, "Result object has derived field bar set to 'Derived'");
        }
    },
];

testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });
