import traverse from "../lib";
import assert from "assert";
import { parse } from "babylon";

function getPath(code) {
  const ast = parse(code);
  let path;
  traverse(ast, {
    Program: function(_path) {
      path = _path;
      _path.stop();
    },
  });
  return path;
}

function getIdentifierPath(code) {
  const ast = parse(code);
  let nodePath;
  traverse(ast, {
    Identifier: function(path) {
      nodePath = path;
      path.stop();
    },
  });

  return nodePath;
}

describe("scope", function() {
  describe("binding paths", function() {
    it("function declaration id", function() {
      assert.ok(
        getPath("function foo() {}").scope.getBinding("foo").path.type ===
          "FunctionDeclaration",
      );
    });

    it("function expression id", function() {
      assert.ok(
        getPath("(function foo() {})")
          .get("body")[0]
          .get("expression")
          .scope.getBinding("foo").path.type === "FunctionExpression",
      );
    });

    it("function param", function() {
      assert.ok(
        getPath("(function (foo) {})")
          .get("body")[0]
          .get("expression")
          .scope.getBinding("foo").path.type === "Identifier",
      );
    });

    it("variable declaration", function() {
      assert.ok(
        getPath("var foo = null;").scope.getBinding("foo").path.type ===
          "VariableDeclarator",
      );
      assert.ok(
        getPath("var { foo } = null;").scope.getBinding("foo").path.type ===
          "VariableDeclarator",
      );
      assert.ok(
        getPath("var [ foo ] = null;").scope.getBinding("foo").path.type ===
          "VariableDeclarator",
      );
      assert.ok(
        getPath("var { bar: [ foo ] } = null;").scope.getBinding("foo").path
          .type === "VariableDeclarator",
      );
    });

    it("purity", function() {
      assert.ok(
        getPath("({ x: 1 })").get("body")[0].get("expression").isPure(),
      );
      assert.ok(!getPath("`${a}`").get("body")[0].get("expression").isPure());
      assert.ok(
        getPath("let a = 1; `${a}`").get("body")[1].get("expression").isPure(),
      );
      assert.ok(
        !getPath("let a = 1; `${a++}`")
          .get("body")[1]
          .get("expression")
          .isPure(),
      );
      assert.ok(
        !getPath("tagged`foo`").get("body")[0].get("expression").isPure(),
      );
      assert.ok(
        getPath("String.raw`foo`").get("body")[0].get("expression").isPure(),
      );
    });

    test("label", function() {
      assert.strictEqual(
        getPath("foo: { }").scope.getBinding("foo"),
        undefined,
      );
      assert.strictEqual(
        getPath("foo: { }").scope.getLabel("foo").type,
        "LabeledStatement",
      );
      assert.strictEqual(
        getPath("foo: { }").scope.getLabel("toString"),
        undefined,
      );

      assert.strictEqual(
        getPath(
          `
        foo: { }
      `,
        ).scope.generateUid("foo"),
        "_foo",
      );
    });

    test("generateUid collision check with labels", function() {
      assert.strictEqual(
        getPath(
          `
        _foo: { }
      `,
        ).scope.generateUid("foo"),
        "_foo2",
      );

      assert.strictEqual(
        getPath(
          `
        _foo: { }
        _foo1: { }
        _foo2: { }
      `,
        ).scope.generateUid("foo"),
        "_foo3",
      );
    });

    it("reference paths", function() {
      const path = getIdentifierPath("function square(n) { return n * n}");
      const referencePaths = path.context.scope.bindings.n.referencePaths;
      assert.equal(referencePaths.length, 2);
      assert.deepEqual(referencePaths[0].node.loc.start, {
        line: 1,
        column: 28,
      });
      assert.deepEqual(referencePaths[1].node.loc.start, {
        line: 1,
        column: 32,
      });
    });
  });
});
