// Copyright 2018 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.
//
// Flags: --harmony-class-fields

// Utility function for testing that the expected strings occur
// in the stack trace produced when running the given function.
function testTrace(name, fun, expected, unexpected) {
  var threw = false;
  try {
    fun();
  } catch (e) {
    for (var i = 0; i < expected.length; i++) {
      assertTrue(
        e.stack.indexOf(expected[i]) != -1,
        name + " doesn't contain expected[" + i + "] stack = " + e.stack
      );
    }
    if (unexpected) {
      for (var i = 0; i < unexpected.length; i++) {
        assertEquals(
          e.stack.indexOf(unexpected[i]),
          -1,
          name + " contains unexpected[" + i + "]"
        );
      }
    }
    threw = true;
  }
  assertTrue(threw, name + " didn't throw");
}

function thrower() {
  FAIL;
}

function testClassConstruction() {
  class X {
    static x = thrower();
  }
}

// ReferenceError: FAIL is not defined
//     at thrower
//     at <static_fields_initializer>
//     at testClassConstruction
//     at testTrace
testTrace(
  "during class construction",
  testClassConstruction,
  ["thrower", "<static_fields_initializer>"],
  ["anonymous"]
);

function testClassConstruction2() {
  class X {
    [thrower()];
  }
}

// ReferenceError: FAIL is not defined
//    at thrower
//    at testClassConstruction2
//    at testTrace
testTrace("during class construction2", testClassConstruction2, ["thrower"]);

function testClassInstantiation() {
  class X {
    x = thrower();
  }

  new X();
}

// ReferenceError: FAIL is not defined
//     at thrower
//     at X.<instance_members_initializer>
//     at new X
//     at testClassInstantiation
//     at testTrace
testTrace(
  "during class instantiation",
  testClassInstantiation,
  ["thrower", "X.<instance_members_initializer>", "new X"],
  ["anonymous"]
);

function testClassInstantiationWithSuper() {
  class Base {}

  class X extends Base {
    x = thrower();
  }

  new X();
}

// ReferenceError: FAIL is not defined
//     at thrower
//     at X.<instance_members_initializer>
//     at new X
//     at testClassInstantiation
//     at testTrace
testTrace(
  "during class instantiation with super",
  testClassInstantiationWithSuper,
  ["thrower", "X.<instance_members_initializer>", "new X"],
  ["Base", "anonymous"]
);

function testClassInstantiationWithSuper2() {
  class Base {}

  class X extends Base {
    constructor() {
      super();
    }
    x = thrower();
  }

  new X();
}

// ReferenceError: FAIL is not defined
//     at thrower
//     at X.<instance_members_initializer>
//     at new X
//     at testClassInstantiation
//     at testTrace
testTrace(
  "during class instantiation with super2",
  testClassInstantiationWithSuper2,
  ["thrower", "X.<instance_members_initializer>", "new X"],
  ["Base", "anonymous"]
);

function testClassInstantiationWithSuper3() {
  class Base {
    x = thrower();
  }

  class X extends Base {
    constructor() {
      super();
    }
  }

  new X();
}

// ReferenceError: FAIL is not defined
//     at thrower
//     at X.<instance_members_initializer>
//     at new Base
//     at new X
//     at testClassInstantiationWithSuper3
//     at testTrace
testTrace(
  "during class instantiation with super3",
  testClassInstantiationWithSuper3,
  ["thrower", "X.<instance_members_initializer>", "new Base", "new X"],
  ["anonymous"]
);

function testClassFieldCall() {
  class X {
    x = thrower;
  }

  let x = new X();
  x.x();
}

// ReferenceError: FAIL is not defined
//     at X.thrower [as x]
//     at testClassFieldCall
//     at testTrace
testTrace(
  "during class field call",
  testClassFieldCall,
  ["X.thrower"],
  ["anonymous"]
);

function testStaticClassFieldCall() {
  class X {
    static x = thrower;
  }

  X.x();
}

// ReferenceError: FAIL is not defined
//     at Function.thrower [as x]
//     at testStaticClassFieldCall
//     at testTrace
testTrace(
  "during static class field call",
  testStaticClassFieldCall,
  ["Function.thrower"],
  ["anonymous"]
);

function testClassFieldCallWithFNI() {
  class X {
    x = function() {
      FAIL;
    };
  }

  let x = new X();
  x.x();
}

// ReferenceError: FAIL is not defined
//     at X.x
//     at testClassFieldCallWithFNI
//     at testTrace
testTrace(
  "during class field call with FNI",
  testClassFieldCallWithFNI,
  ["X.x"],
  ["anonymous"]
);

function testStaticClassFieldCallWithFNI() {
  class X {
    static x = function() {
      FAIL;
    };
  }

  X.x();
}

// ReferenceError: FAIL is not defined
//     at Function.x
//     at testStaticClassFieldCallWithFNI
//     at testTrace
testTrace(
  "during static class field call with FNI",
  testStaticClassFieldCallWithFNI,
  ["Function.x"],
  ["anonymous"]
);