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

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

var tests = [
  {
    name: "BLUE 540289: AV on deferred parse of first class method",
    body: function () {
      assert.throws(function() { eval("function f() { var o = { \"a\": class { \"b\""); }, SyntaxError);
    }
  },
  {
    name: "BLUE 558906: [ES6][Class] get and set should be valid method names",
    body: function () {
      class foo {
        set(key) { } // No error
        get() { }    // No error
      }
    }
  },
  {
    name: "BLUE 573391: Classes extending null with a non-default constructor crash",
    body: function () {
      class A { constructor() { } };
      var test1 = class { constructor(args) { } };
      var test2 = class extends null { constructor(args) { } };
      var test3 = class extends A { constructor(args) { } };
      var test4 = class extends A { constructor(args) { super(args) } };
    }
  },
  {
    name: "BLUE 603997: Method formals redeclaration error",
    body: function() {
      assert.throws(function() { eval("class { method(a) { var a; }; }"); },           SyntaxError, "Method formal parameters cannot be redeclared.");
      assert.throws(function() { eval("class { method(a) { let a; }; }"); },           SyntaxError, "Method formal parameters cannot be redeclared.");
      assert.throws(function() { eval("class { method(a) { const a; }; }"); },         SyntaxError, "Method formal parameters cannot be redeclared.");

      assert.throws(function() { eval("class { method(a,b,c) { var b; }; }"); },       SyntaxError, "Method formal parameters cannot be redeclared.");
      assert.throws(function() { eval("class { method(a,b,c) { let b; }; }"); },       SyntaxError, "Method formal parameters cannot be redeclared.");
      assert.throws(function() { eval("class { method(a,b,c) { const b; }; }"); },     SyntaxError, "Method formal parameters cannot be redeclared.");

      assert.throws(function() { eval("class { set method(a) { var a; }; }"); },       SyntaxError, "Method formal parameters cannot be redeclared.");
      assert.throws(function() { eval("class { set method(a) { let a; }; }"); },       SyntaxError, "Method formal parameters cannot be redeclared.");
      assert.throws(function() { eval("class { set method(a) { const a; }; }"); },     SyntaxError, "Method formal parameters cannot be redeclared.");

      assert.throws(function() { eval("class { set method(a,b,c) { var b; }; }"); },   SyntaxError, "Method formal parameters cannot be redeclared.");
      assert.throws(function() { eval("class { set method(a,b,c) { let b; }; }"); },   SyntaxError, "Method formal parameters cannot be redeclared.");
      assert.throws(function() { eval("class { set method(a,b,c) { const b; }; }"); }, SyntaxError, "Method formal parameters cannot be redeclared.");

      assert.throws(function() { eval("class { method(a,a,c) { }; }"); },              SyntaxError, "Method formal parameters cannot be redeclared.");
      assert.throws(function() { eval("class { set method(a,a,c) { }; }"); },          SyntaxError, "Method formal parameters cannot be redeclared.");
    }
  },
  {
    name: "BLUE 629214: Class methods with a prefix crash in deferred parse",
    body: function () {
      function test1() { class a { static "a"() { } } }
      function test2() { class a { static get "a"() { } } }
      function test3() { class a { static set "a"(x) { } } }
      function test4() { class a { get "a"() { } } }
      function test5() { class a { set "a"(x) { } } }
      function test6() { class a { *"a"(x) { } } }
      function test7() { class a { method() {} "a"() {} } }
      function test8() { class a { method() {} static "a"() { } } }
      function test9() { class a { method() {} static get "a"() { } } }
      function test10() { class a { method() {} static set "a"(x) { } } }
      function test11() { class a { method() {} get "a"() { } } }
      function test12() { class a { method() {} set "a"(x) { } } }
      function test13() { class a { method() {} *"a"(x) { } } }
    }
  },
  {
    name: "OS 102456: Assert when deleting a non-method property from a class",
    body: function () {
      u3056 = function() {};
      class c extends u3056 {};
      c.y = "str";
      delete c.x;
      delete c.y;
    }
  },
  {
    name: "OS 112921: Nested evals attempt to load super into a scope slot",
    body: function () {
      class z{window(){((eval("")))((this))}};
      eval("class z{window(){((eval(\"\")))((this))}};");
    }
  },
  {
    name: "OS 101184: Class methods without separators inside an array break deferred parsing heuristics",
    body: function () {
      Function("[class z{\u3056(){}functional(){}}]");
    }
  },
  {
    name: "OS 182090: Class method after a semicolon terminated method does not force PID",
    body: function () {
      z = (class {
              if (shouldBailout) { /*bLoop*/ };
              "" (x) {}
              })
    }
  },
  {
    name: "OS 257621: Class expressions should not have trailing call parens",
    body: function () {
      assert.throws(function () { eval('class{}();'); }, SyntaxError, "Class expressions cannot be called without parens", "Expected identifier");
      assert.doesNotThrow(function () { eval('new (class {})();'); }, "Parenthesized class expressions can be called");
    }
  },
  {
    name: "OS 1114090: Getters in superclass should use instance of subclass as actual 'this' object",
    body: function () {

        class Person {
            getFullName() {
                return this.firstName + " " + this.lastName;
            }
            get fullName() {
                return this.firstName + " " + this.lastName;
            }
        }

        class MedicalWorker extends Person { } // to show it works through inheritance chain

        class Doctor extends MedicalWorker {
            constructor(firstName,lastName) {
                super();
                this.firstName = firstName;
                this.lastName = lastName;
            }
            getFullNameExplicit() { return "Dr. " + super.getFullName(); }
            getFullNameProperty() { return "Dr. " + super.fullName; }
            getFullNameEvalCall() { return "Dr. " + eval('super.getFullName()'); }
            getFullNameEvalProperty() { return "Dr. " + eval('super.fullName'); }
            getFullNameLambdaCall() { return "Dr. " + (()=>super.getFullName()) (); }
            getFullNameLambdaProperty() { return "Dr. " + (()=>super.fullName) (); }
        }

        let x = new Doctor("John","Smith");
        assert.areEqual("Dr. John Smith", x.getFullNameExplicit(), "explicit super call should use instance of subclass as actual 'this' object");
        assert.areEqual("Dr. John Smith", x.getFullNameProperty(), "property accessor in superclass should use instance of subclass as actual 'this' object");
        assert.areEqual("Dr. John Smith", x.getFullNameEvalCall(), "super called from within eval should have same behavior as outside of eval");
        assert.areEqual("Dr. John Smith", x.getFullNameEvalProperty(), "super object property access from within eval should have same behavior as outside of eval");
        assert.areEqual("Dr. John Smith", x.getFullNameLambdaCall(), "super called from within lambda should have same behavior as outside of lambda");
        assert.areEqual("Dr. John Smith", x.getFullNameLambdaProperty(), "super object property access from within lambda should have same behavior as outside of lambda");
    }
  },
  {
    name: "OS 4586602: Setters in superclass should use instance of subclass as actual 'this' object",
    body: function () {

        // test case from OS4586602

        class Proto {
            set  x(v) {  return this._x = v;  }
        };

        class Obj extends Proto {
            set x(v) { super.x = v;  }
        };

        var object = new Obj();
        object.x=1;
        assert.areEqual(1, object._x, "setters in superclass should use instance of subclass as actual 'this' object");

        // behavior according to ECMA2015 when superclass accessors are not present

        class A { }

        class B extends A {
            getA() { return super.i; }
            setA(v) { super.i = v; }
        }

        let b = new B();
        b.setA(15);

        assert.areEqual(true, b.hasOwnProperty('i'), "Property 'i' should exist in actualThis object(b)");
        assert.areEqual(15, b.i, "Property 'i' should match assigned value in actualThis object(b)");
        assert.areEqual(false, A.prototype.hasOwnProperty('i'), "Property 'i' should not exist in base object(A.prototype)");
        assert.areEqual(undefined, b.getA(), "Property 'i' should be undefined in base object(A.prototype)");

        // other cases similar to getter tests above

        class Base {
            setName(v) { this._name = v; }
            set name(v) { this._name = v; }
        }

        class Middle extends Base { } // to show it works through inheritance chain

        class Subclass extends Middle {
            setNameExplicit(name) { super.setName(name); }
            setNameProperty(name) { super.name=name; }
            setNameEvalCall(name) { eval('super.setName(name)'); }
            setNameEvalProperty(name) { eval('super.name=name'); }
            setNameLambdaCall(name) { (()=>super.setName(name)) (); }
            setNameLambdaProperty(name) { (()=>super.name=name) (); }
        }

        let x = new Subclass();
        x.setNameExplicit("explicit");
        assert.areEqual("explicit", x._name, "explicit super call should use instance of subclass as actual 'this' object");
        x.setNameProperty("property");
        assert.areEqual("property", x._name, "property accessor in superclass should use instance of subclass as actual 'this' object");
        x.setNameEvalCall("evalCall");
        assert.areEqual("evalCall", x._name, "super called from within eval should have same behavior as outside of eval");
        x.setNameEvalProperty("evalProperty");
        assert.areEqual("evalProperty", x._name, "super object property access from within eval should have same behavior as outside of eval");
        x.setNameLambdaCall("lambdaCall");
        assert.areEqual("lambdaCall", x._name, "super called from within lambda should have same behavior as outside of lambda");
        x.setNameLambdaProperty("lambdaProperty");
        assert.areEqual("lambdaProperty", x._name, "super object property access from within lambda should have same behavior as outside of lambda");
    }
  },
  {
    name: "OS 1001915: Function built-in properties \'length\', \'caller\', \'arguments\' should not shadow class members",
    body: function () {
        class A {
            static length() { }
            static caller() { }
            static arguments() { }
        };
        assert.areEqual("length() { }", A.length.toString(), "Accessing static method \'length\'");
        assert.areEqual("caller() { }", A.caller.toString(), "Accessing static method \'caller\'");
        assert.areEqual("arguments() { }", A.arguments.toString(), "Accessing static method \'arguments\'");
        for (var p in A) {
            assert.areEqual(p+"() { }", A[p].toString(), "PropertyString for \'"+p+"\' should have a matching cached value");
        }

        class B {
            set length(a) { this._length=a; }
            get length() { return this._length; }
            set caller(a) { this._caller=a; }
            get caller() { return this._caller; }
            set arguments(a) { this._arguments=a; }
            get arguments() { return this._arguments; }
        };
        var b=new B();
        b.length=100;
        b.caller="Caller";
        b.arguments=function() { };
        assert.areEqual(100, b.length, "Get/set accessor \'length\'");
        assert.areEqual("Caller", b.caller, "Get/set accessor \'caller\'");
        assert.areEqual("function () { }", b.arguments.toString(), "Get/set accessor \'arguments\'");
    }
  },
  {
    name: "OS4497562,OS4497908: InitClassMember overriding existing accessor property",
    body: function () {
        class A {
            set z(v) {}
            z() {} // bug repro: ASSERT(jscript\core\lib\Runtime\Types\DictionaryTypeHandler.cpp, line 1737) Expect to only come down this path for ...
        };

        class B {
            static set z(v) {}
            static z() {} // bug repro: ASSERT(jscript\core\lib\Runtime\Types\DictionaryTypeHandler.cpp, line 1737) Expect to only come down this path for ...
        };

        class C {
            set z(v) {}
            *z() {} // bug repro: ASSERT(jscript\core\lib\Runtime\Types\DictionaryTypeHandler.cpp, line 1737) Expect to only come down this path for ...
        };

        class D {
            set z(v) {}
            ["z"]() {} // bug repro: ASSERT(jscript\core\lib\Runtime\Types\DictionaryTypeHandler.cpp, line 1737) Expect to only come down this path for ...
        };
    }
  },
  {
    name: "OS4352944: ES6 class super.<method> calls in an eval inside constructor",
    body: function () {
        var count = 0;
        class A {
            constructor() { count++; }
            increment()   { count++; }
            decrement()   { count--; }
            getCount()    { return count; }
        }

        class B extends A {
            constructor() {
                eval(" \
                    super(); \
                    assert.areEqual(1,super.getCount()); \
                    super.increment(); \
                    assert.areEqual(2, super.getCount()); \
                    super.decrement(); \
                    assert.areEqual(1, super.getCount()); \
                ");
            }
        }
        var bar = new B();
    }
  },
  {
    name: "OS6135311: Incorrect base register assignment in inlined LdNewTarget",
    body: function () {
        function func5 () {
            assert.areEqual(class0, new.target, "new.target should return subclass constructor for super constructor call");
            function v0() {};
        }
        class class0 extends func5 {  }

        new class0();
        new class0(-1);
        new class0([2,3], NaN);
        new class0("cat", 100, {});
    }
  },
];

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

// Bug 516429 at global scope
class a {};
a = null; // No error

// Bug 257621 at global scope
assert.doesNotThrow(function () { eval('new (class {})();'); }, "Parenthesized class expressions can be new'd");
