// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.


(function TestClass() {
  'use strict';

  var calls = 0;
  class Base {
    constructor(_) {
      assertEquals(Base, new.target);
      calls++;
    }
  }
  assertInstanceof(new Base(1), Base);
  assertInstanceof(new Base(1, 2), Base);
  assertInstanceof(new Base(), Base);
  assertEquals(3, calls);
})();


(function TestDerivedClass() {
  'use strict';

  var calls = 0;
  class Base {
    constructor(expected) {
      assertEquals(expected, new.target);
    }
  }
  class Derived extends Base {
    constructor(expected) {
      super(expected);
      assertEquals(expected, new.target);
      calls++;
    }
  }
  new Derived(Derived, 'extra');
  new Derived(Derived);
  assertEquals(2, calls);

  class Derived2 extends Derived {}
  calls = 0;
  new Derived2(Derived2);
  new Derived2(Derived2, 'extra');
  assertEquals(2, calls);
})();


(function TestFunctionCall() {
  var calls;
  function f(expected) {
    calls++;
    assertEquals(expected, new.target);
  }

  calls = 0;
  f(undefined);
  f(undefined, 'extra');
  f();
  assertEquals(3, calls);

  calls = 0;
  f.call({}, undefined);
  f.call({}, undefined, 'extra');
  f.call({});
  assertEquals(3, calls);

  calls = 0;
  f.apply({}, [undefined]);
  f.apply({}, [undefined, 'extra']);
  f.apply({}, []);
  assertEquals(3, calls);
})();


(function TestFunctionConstruct() {
  var calls;
  function f(expected) {
    calls++;
    assertEquals(expected, new.target);
  }

  calls = 0;
  new f(f);
  new f(f, 'extra');
  assertEquals(2, calls);
})();


(function TestClassExtendsFunction() {
  'use strict';

  var calls = 0;
  function f(expected) {
    assertEquals(expected, new.target);
  }
  class Derived extends f {
    constructor(expected) {
      super(expected);
      assertEquals(expected, new.target);
      calls++;
    }
  }

  new Derived(Derived);
  new Derived(Derived, 'extra');
  assertEquals(2, calls);
})();


(function TestFunctionReturnObject() {
  function f(expected) {
    assertEquals(expected, new.target);
    return /abc/;
  }

  assertInstanceof(new f(f), RegExp);
  assertInstanceof(new f(f, 'extra'), RegExp);

  assertInstanceof(f(undefined), RegExp);
  assertInstanceof(f(), RegExp);
  assertInstanceof(f(undefined, 'extra'), RegExp);
})();


(function TestClassReturnObject() {
  'use strict';

  class Base {
    constructor(expected) {
      assertEquals(expected, new.target);
      return /abc/;
    }
  }

  assertInstanceof(new Base(Base), RegExp);
  assertInstanceof(new Base(Base, 'extra'), RegExp);

  class Derived extends Base {}
  assertInstanceof(new Derived(Derived), RegExp);
  assertInstanceof(new Derived(Derived, 'extra'), RegExp);

  class Derived2 extends Base {
    constructor(expected) {
      super(expected);
      assertInstanceof(this, RegExp);
    }
  }
  assertInstanceof(new Derived2(Derived2), RegExp);
  assertInstanceof(new Derived2(Derived2, 'extra'), RegExp);
})();


(function TestReflectConstruct() {
  var calls = 0;
  function f(expected) {
    calls++;
    assertEquals(expected, new.target);
  }

  var o = Reflect.construct(f, [f]);
  assertEquals(Object.getPrototypeOf(o), f.prototype);
  o = Reflect.construct(f, [f, 'extra']);
  assertEquals(Object.getPrototypeOf(o), f.prototype);
  assertEquals(2, calls);

  calls = 0;
  o = Reflect.construct(f, [f], f);
  assertEquals(Object.getPrototypeOf(o), f.prototype);
  o = Reflect.construct(f, [f, 'extra'], f);
  assertEquals(Object.getPrototypeOf(o), f.prototype);
  assertEquals(2, calls);


  function g() {}
  calls = 0;
  o = Reflect.construct(f, [g], g);
  assertEquals(Object.getPrototypeOf(o), g.prototype);
  o = Reflect.construct(f, [g, 'extra'], g);
  assertEquals(Object.getPrototypeOf(o), g.prototype);
  assertEquals(2, calls);
})();


(function TestRestParametersFunction() {
  function f(...rest) {
    assertEquals(rest[0], new.target);
  }

  assertInstanceof(new f(f), f);
  assertInstanceof(new f(f, 'extra'), f);
})();


(function TestRestParametersClass() {
  'use strict';

  class Base {
    constructor(...rest) {
      assertEquals(rest[0], new.target);
    }
  }

  assertInstanceof(new Base(Base), Base);
  assertInstanceof(new Base(Base, 'extra'), Base);

  class Derived extends Base {}

  assertInstanceof(new Derived(Derived), Derived);
  assertInstanceof(new Derived(Derived, 'extra'), Derived);
})();


(function TestArrowFunction() {
  function f(expected) {
    (() => {
      assertEquals(expected, new.target);
    })();
  }

  assertInstanceof(new f(f), f);
  assertInstanceof(new f(f, 'extra'), f);
})();


(function TestRestParametersClass() {
  'use strict';

  class Base {
    constructor(expected) {
      (() => {
        assertEquals(expected, new.target);
      })();
    }
  }

  assertInstanceof(new Base(Base), Base);
  assertInstanceof(new Base(Base, 'extra'), Base);

  class Derived extends Base {}

  assertInstanceof(new Derived(Derived), Derived);
  assertInstanceof(new Derived(Derived, 'extra'), Derived);
})();


(function TestSloppyArguments() {
  var length, a0, a1, a2, nt;
  function f(x) {
    assertEquals(length, arguments.length);
    assertEquals(a0, arguments[0]);
    assertEquals(a1, arguments[1]);
    assertEquals(a2, arguments[2]);
    assertEquals(nt, new.target);

    if (length > 0) {
      x = 42;
      assertEquals(42, x);
      assertEquals(42, arguments[0]);

      arguments[0] = 33;
      assertEquals(33, x);
      assertEquals(33, arguments[0]);
    }
  }

  nt = f;
  length = 0;
  new f();

  length = 1;
  a0 = 1;
  new f(1);

  length = 2;
  a0 = 1;
  a1 = 2;
  new f(1, 2);

  length = 3;
  a0 = 1;
  a1 = 2;
  a2 = 3;
  new f(1, 2, 3);

  nt = undefined;
  a0 = a1 = a2 = undefined;
  length = 0;
  f();

  length = 1;
  a0 = 1;
  f(1);

  length = 2;
  a0 = 1;
  a1 = 2;
  f(1, 2);

  length = 3;
  a0 = 1;
  a1 = 2;
  a2 = 3;
  f(1, 2, 3);
})();


(function TestStrictArguments() {
  var length, a0, a1, a2, nt;
  function f(x) {
    'use strict';
    assertEquals(length, arguments.length);
    assertEquals(a0, arguments[0]);
    assertEquals(a1, arguments[1]);
    assertEquals(a2, arguments[2]);
    assertEquals(nt, new.target);

    if (length > 0) {
      x = 42;
      assertEquals(a0, arguments[0]);

      arguments[0] = 33;
      assertEquals(33, arguments[0]);
    }
  }

  nt = f;
  length = 0;
  new f();

  length = 1;
  a0 = 1;
  new f(1);

  length = 2;
  a0 = 1;
  a1 = 2;
  new f(1, 2);

  length = 3;
  a0 = 1;
  a1 = 2;
  a2 = 3;
  new f(1, 2, 3);

  nt = undefined;
  a0 = a1 = a2 = undefined;
  length = 0;
  f();

  length = 1;
  a0 = 1;
  f(1);

  length = 2;
  a0 = 1;
  a1 = 2;
  f(1, 2);

  length = 3;
  a0 = 1;
  a1 = 2;
  a2 = 3;
  f(1, 2, 3);
})();


(function TestOtherScopes() {
  function f1() { return eval("'use strict'; new.target") }
  assertSame(f1, new f1);
  function f2() { with ({}) return new.target }
  assertSame(f2, new f2);
  function f3({a}) { return new.target }
  assertSame(f3, new f3({}));
  function f4(...a) { return new.target }
  assertSame(f4, new f4);
  function f5() { 'use strict'; { let x; return new.target } }
  assertSame(f5, new f5);
  function f6() { with ({'new.target': 42}) return new.target }
  assertSame(f6, new f6);
})();


// Has to be top-level to be inlined.
function get_new_target() { return new.target; }
(function TestInlining() {
  "use strict";
  new function() { assertEquals(undefined, get_new_target()); }
  new function() { assertEquals(get_new_target, new get_new_target()); }

  class A extends get_new_target {
    constructor() {
      var new_target = super();
      this.new_target = new_target;
    }
  }
  assertEquals(A, new A().new_target);
})();


(function TestEarlyErrors() {
  assertThrows(function() { Function("new.target = 42"); }, ReferenceError);
  assertThrows(function() { Function("var foo = 1; new.target = foo = 42"); }, ReferenceError);
  assertThrows(function() { Function("var foo = 1; foo = new.target = 42"); }, ReferenceError);
  assertThrows(function() { Function("new.target--"); }, ReferenceError);
  assertThrows(function() { Function("--new.target"); }, ReferenceError);
  assertThrows(function() { Function("(new.target)++"); }, ReferenceError);
  assertThrows(function() { Function("++(new.target)"); }, ReferenceError);
  assertThrows(function() { Function("for (new.target of {});"); }, SyntaxError);
})();

(function TestOperatorPrecedence() {
  function A() {}
  function constructNewTargetDotProp() { return new new.target.Prop }
  constructNewTargetDotProp.Prop = A;
  assertInstanceof(new constructNewTargetDotProp, A);

  function constructNewTargetBracketProp() { return new new.target['Prop'] }
  constructNewTargetBracketProp.Prop = A;
  assertInstanceof(new constructNewTargetBracketProp, A);

  function refNewTargetDotProp() { return new.target.Prop; }
  function B() {}
  refNewTargetDotProp.Prop = B;
  assertEquals(new refNewTargetDotProp, B);

  function refNewTargetBracketProp() { return new.target['Prop']; }
  refNewTargetBracketProp.Prop = B;
  assertEquals(new refNewTargetBracketProp, B);

  var calls = 0;
  function constructNewTargetArgsDotProp(safe) {
    this.Prop = ++calls;
    return safe ? Object(new new.target().Prop) : this;
  }
  assertInstanceof(new constructNewTargetArgsDotProp,
                   constructNewTargetArgsDotProp);
  assertEquals(3, new constructNewTargetArgsDotProp(true) | 0);

  function constructNewTargetArgsBracketProp(safe) {
    this.Prop = ++calls;
    return safe ? Object(new new.target()['Prop']) : this;
  }
  assertInstanceof(new constructNewTargetArgsBracketProp,
                   constructNewTargetArgsBracketProp);
  assertEquals(6, new constructNewTargetArgsBracketProp(true) | 0);

  function callNewTargetArgsDotProp(safe) {
    this.Prop = ++calls;
    return safe ? Object(new.target().Prop) : this;
  }
  assertInstanceof(new callNewTargetArgsDotProp(), callNewTargetArgsDotProp);
  assertEquals(new callNewTargetArgsDotProp(true) | 0, 9);

  function callNewTargetArgsBracketProp(safe) {
    this.Prop = ++calls;
    return safe ? Object(new.target()['Prop']) : this;
  }
  assertInstanceof(new callNewTargetArgsBracketProp(),
                   callNewTargetArgsBracketProp);
  assertEquals(new callNewTargetArgsBracketProp(true) | 0, 12);

  function tagNewTarget(callSite, ...subs) {
    return callSite ? subs : new.target`${new.target.name}`;
  }
  assertEquals(new tagNewTarget, ["tagNewTarget"]);

  function C(callSite, ...subs) { return subs; }
  function tagNewTargetProp() { return new.target.Prop`${new.target.name}`; }
  tagNewTargetProp.Prop = C;
  assertEquals(new tagNewTargetProp, ["tagNewTargetProp"]);
})();
