// Copyright 2017 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.

"use strict";

{
  class C {
    static a;
  }

  assertEquals(undefined, C.a);
  let descriptor = Object.getOwnPropertyDescriptor(C, 'a');
  assertTrue(C.hasOwnProperty('a'));
  assertTrue(descriptor.writable);
  assertTrue(descriptor.enumerable);
  assertTrue(descriptor.configurable);

  let c = new C;
  assertEquals(undefined, c.a);
}

{
  let x = 'a';
  class C {
    static a;
    static hasOwnProperty = function() { return 1; }
    static b = x;
    static c = 1;
  }

  assertEquals(undefined, C.a);
  assertEquals('a', C.b);
  assertEquals(1, C.c);
  assertEquals(1, C.hasOwnProperty());

  let c = new C;
  assertEquals(undefined, c.a);
  assertEquals(undefined, c.b);
  assertEquals(undefined, c.c);
}

{
  assertThrows(() => {
    class C {
      static x = Object.freeze(this);
      static c = 42;
    }
  }, TypeError);
}

{
  class C {
    static c = this;
    static d = () => this;
  }

  assertEquals(C, C.c);
  assertEquals(C, C.d());

  let c = new C;
  assertEquals(undefined, c.c);
  assertEquals(undefined, c.d);
}

{
  class C {
    static c = 1;
    static d = this.c;
  }

  assertEquals(1, C.c);
  assertEquals(1, C.d);

  let c = new C;
  assertEquals(undefined, c.c);
  assertEquals(undefined, c.d);
}

{
  class C {
    static b = 1;
    static c = () => this.b;
  }

  assertEquals(1, C.b);
  assertEquals(1, C.c());

  let c = new C;
  assertEquals(undefined, c.c);
}

{
  let x = 'a';
  class C {
    static b = 1;
    static c = () => this.b;
    static e = () => x;
  }

  assertEquals(1, C.b);
  assertEquals('a', C.e());

  let a = {b : 2 };
  assertEquals(1, C.c.call(a));

  let c = new C;
  assertEquals(undefined, c.b);
  assertEquals(undefined, c.c);
}

{
  let x = 'a';
  class C {
    static c = 1;
    static d = function() { return this.c; };
    static e = function() { return x; };
  }

  assertEquals(1, C.c);
  assertEquals(1, C.d());
  assertEquals('a', C.e());

  C.c = 2;
  assertEquals(2, C.d());

  let a = {c : 3 };
  assertEquals(3, C.d.call(a));

  assertThrows(C.d.bind(undefined));

  let c = new C;
  assertEquals(undefined, c.c);
  assertEquals(undefined, c.d);
  assertEquals(undefined, c.e);
}

{
  class C {
    static c = function() { return 1 };
  }

  assertEquals('c', C.c.name);
}

{
  let d = function() { return new.target; }
  class C {
    static c = d;
  }

  assertEquals(undefined, C.c());
  assertEquals(new d, new C.c());
}

{
  class C {
    static c = () => new.target;
  }

  assertEquals(undefined, C.c());
}

{
   class C {
     static c = () => {
       let b;
       class A {
         constructor() {
           b = new.target;
         }
       };
       new A;
       assertEquals(A, b);
     }
  }

  C.c();
}

{
  class C {
    static c = new.target;
  }

  assertEquals(undefined, C.c);
}

{
  class B {
    static d = 1;
    static b = () => this.d;
  }

  class C extends B {
    static c = super.d;
    static d = () => super.d;
    static e = () => super.b();
  }

  assertEquals(1, C.c);
  assertEquals(1, C.d());
  assertEquals(1, C.e());
}

{
  let foo = undefined;
  class B {
    static set d(x) {
      foo = x;
    }
  }

  class C extends B {
    static d = 2;
  }

  assertEquals(undefined, foo);
  assertEquals(2, C.d);
}


{
  let C  = class {
    static c;
  };

  assertEquals("C", C.name);
}

{
  class C {
    static c = new C;
  }

  assertTrue(C.c instanceof C);
}

(function test() {
  function makeC() {
    var x = 1;

    return class {
      static a = () => () => x;
    }
  }

  let C = makeC();
  let f = C.a();
  assertEquals(1, f());
})()

{
  let c = "c";
  class C {
    static ["a"] = 1;
    static ["b"];
    static [c];
  }

  assertEquals(1, C.a);
  assertEquals(undefined, C.b);
  assertEquals(undefined, C[c]);
}

{
  let log = [];
  function run(i) {
    log.push(i);
    return i;
  }

  class C {
    static [run(1)] = run(6);
    static [run(2)] = run(7);
    [run(3)]() { run(9);}
    static [run(4)] = run(8);
    static [run(5)]() { throw new Error('should not execute');};
  }

  let c = new C;
  c[3]();
  assertEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], log);
}



function x() {

  // This tests lazy parsing.
  return function() {
    let log = [];
    function run(i) {
      log.push(i);
      return i;
    }

    class C {
      static [run(1)] = run(6);
      static [run(2)] = run(7);
      [run(3)]() { run(9);}
      static [run(4)] = run(8);
      static [run(5)]() { throw new Error('should not execute');};
    }

    let c = new C;
    c[3]();
    assertEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], log);
  }
}
x()();

{
  let log = [];
  function run(i) {
    log.push(i);
    return i;
  }

  class C {
    [run(1)] = run(7);
    [run(2)] = run(8);
    [run(3)]() { run(9);}
    static [run(4)] = run(6);
    [run(5)]() { throw new Error('should not execute');};
  }

  let c = new C;
  c[3]();
  assertEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], log);
}

function y() {
  // This tests lazy parsing.
  return function() {
    let log = [];
    function run(i) {
      log.push(i);
      return i;
    }

    class C {
      [run(1)] = run(7);
      [run(2)] = run(8);
      [run(3)]() { run(9);}
      static [run(4)] = run(6);
      [run(5)]() { throw new Error('should not execute');};
    }

    let c = new C;
    c[3]();
    assertEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], log);
  }
}
y()();

{
  class C {}
  class D {
    static [C];
  }

  assertThrows(() => { class X { static [X] } });
  assertEquals(undefined, D[C]);
}

{
  function t() {
    return class {
      static ['x'] = 2;
    }
  }

  let klass = t();
  let obj = new klass;
  assertEquals(2, klass.x);
}

{
  let x = 'a';
  class C {
    a;
    b = x;
    c = 1;
    hasOwnProperty() { return 1;}
    static [x] = 2;
    static b = 3;
    static d;
  }

  assertEquals(2, C.a);
  assertEquals(3, C.b);
  assertEquals(undefined, C.d);
  assertEquals(undefined, C.c);

  let c = new C;
  assertEquals(undefined, c.a);
  assertEquals('a', c.b);
  assertEquals(1, c.c);
  assertEquals(undefined, c.d);
  assertEquals(1, c.hasOwnProperty());
}

{
  function t() {
    return class {
      ['x'] = 1;
      static ['x'] = 2;
    }
  }

  let klass = t();
  let obj = new klass;
  assertEquals(1, obj.x);
  assertEquals(2, klass.x);
}


{
  class X {
    static p = function() { return arguments[0]; }
  }

  assertEquals(1, X.p(1));
}

{
  class X {
    static t = () => {
      function p() { return arguments[0]; };
      return p;
    }
  }

  let p = X.t();
  assertEquals(1, p(1));
}

{
  class X {
    static t = () => {
      function p() { return eval("arguments[0]"); };
      return p;
    }
  }

  let p = X.t();
  assertEquals(1, p(1));
}

{
  class X {
    static p = eval("(function() { return arguments[0]; })(1)");
  }

  assertEquals(1, X.p);
}

{
  let p = { z: class { static y = this.name } }
  assertEquals(p.z.y, 'z');

  let q = { ["z"]: class { static y = this.name } }
  assertEquals(q.z.y, 'z');

  const C = class {
    static x = this.name;
  }
  assertEquals(C.x, 'C');
}
