“use strict”;

Object.defineProperty(exports, “__esModule”, {

value: true

}); exports.explode = explode; exports.verify = verify; exports.merge = merge;

var virtualTypes = _interopRequireWildcard(require(“./path/lib/virtual-types”));

var t = _interopRequireWildcard(require(“@babel/types”));

var _clone = _interopRequireDefault(require(“lodash/clone”));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _getRequireWildcardCache() { if (typeof WeakMap !== “function”) return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; if (obj != null) { var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj = obj; } } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

function explode(visitor) {

if (visitor._exploded) return visitor;
visitor._exploded = true;

for (const nodeType of Object.keys(visitor)) {
  if (shouldIgnoreKey(nodeType)) continue;
  const parts = nodeType.split("|");
  if (parts.length === 1) continue;
  const fns = visitor[nodeType];
  delete visitor[nodeType];

  for (const part of parts) {
    visitor[part] = fns;
  }
}

verify(visitor);
delete visitor.__esModule;
ensureEntranceObjects(visitor);
ensureCallbackArrays(visitor);

for (const nodeType of Object.keys(visitor)) {
  if (shouldIgnoreKey(nodeType)) continue;
  const wrapper = virtualTypes[nodeType];
  if (!wrapper) continue;
  const fns = visitor[nodeType];

  for (const type of Object.keys(fns)) {
    fns[type] = wrapCheck(wrapper, fns[type]);
  }

  delete visitor[nodeType];

  if (wrapper.types) {
    for (const type of wrapper.types) {
      if (visitor[type]) {
        mergePair(visitor[type], fns);
      } else {
        visitor[type] = fns;
      }
    }
  } else {
    mergePair(visitor, fns);
  }
}

for (const nodeType of Object.keys(visitor)) {
  if (shouldIgnoreKey(nodeType)) continue;
  const fns = visitor[nodeType];
  let aliases = t.FLIPPED_ALIAS_KEYS[nodeType];
  const deprecratedKey = t.DEPRECATED_KEYS[nodeType];

  if (deprecratedKey) {
    console.trace(`Visitor defined for ${nodeType} but it has been renamed to ${deprecratedKey}`);
    aliases = [deprecratedKey];
  }

  if (!aliases) continue;
  delete visitor[nodeType];

  for (const alias of aliases) {
    const existing = visitor[alias];

    if (existing) {
      mergePair(existing, fns);
    } else {
      visitor[alias] = (0, _clone.default)(fns);
    }
  }
}

for (const nodeType of Object.keys(visitor)) {
  if (shouldIgnoreKey(nodeType)) continue;
  ensureCallbackArrays(visitor[nodeType]);
}

return visitor;

}

function verify(visitor) {

if (visitor._verified) return;

if (typeof visitor === "function") {
  throw new Error("You passed `traverse()` a function when it expected a visitor object, " + "are you sure you didn't mean `{ enter: Function }`?");
}

for (const nodeType of Object.keys(visitor)) {
  if (nodeType === "enter" || nodeType === "exit") {
    validateVisitorMethods(nodeType, visitor[nodeType]);
  }

  if (shouldIgnoreKey(nodeType)) continue;

  if (t.TYPES.indexOf(nodeType) < 0) {
    throw new Error(`You gave us a visitor for the node type ${nodeType} but it's not a valid type`);
  }

  const visitors = visitor[nodeType];

  if (typeof visitors === "object") {
    for (const visitorKey of Object.keys(visitors)) {
      if (visitorKey === "enter" || visitorKey === "exit") {
        validateVisitorMethods(`${nodeType}.${visitorKey}`, visitors[visitorKey]);
      } else {
        throw new Error("You passed `traverse()` a visitor object with the property " + `${nodeType} that has the invalid property ${visitorKey}`);
      }
    }
  }
}

visitor._verified = true;

}

function validateVisitorMethods(path, val) {

const fns = [].concat(val);

for (const fn of fns) {
  if (typeof fn !== "function") {
    throw new TypeError(`Non-function found defined in ${path} with type ${typeof fn}`);
  }
}

}

function merge(visitors, states = [], wrapper) {

const rootVisitor = {};

for (let i = 0; i < visitors.length; i++) {
  const visitor = visitors[i];
  const state = states[i];
  explode(visitor);

  for (const type of Object.keys(visitor)) {
    let visitorType = visitor[type];

    if (state || wrapper) {
      visitorType = wrapWithStateOrWrapper(visitorType, state, wrapper);
    }

    const nodeVisitor = rootVisitor[type] = rootVisitor[type] || {};
    mergePair(nodeVisitor, visitorType);
  }
}

return rootVisitor;

}

function wrapWithStateOrWrapper(oldVisitor, state, wrapper) {

const newVisitor = {};

for (const key of Object.keys(oldVisitor)) {
  let fns = oldVisitor[key];
  if (!Array.isArray(fns)) continue;
  fns = fns.map(function (fn) {
    let newFn = fn;

    if (state) {
      newFn = function (path) {
        return fn.call(state, path, state);
      };
    }

    if (wrapper) {
      newFn = wrapper(state.key, key, newFn);
    }

    return newFn;
  });
  newVisitor[key] = fns;
}

return newVisitor;

}

function ensureEntranceObjects(obj) {

for (const key of Object.keys(obj)) {
  if (shouldIgnoreKey(key)) continue;
  const fns = obj[key];

  if (typeof fns === "function") {
    obj[key] = {
      enter: fns
    };
  }
}

}

function ensureCallbackArrays(obj) {

if (obj.enter && !Array.isArray(obj.enter)) obj.enter = [obj.enter];
if (obj.exit && !Array.isArray(obj.exit)) obj.exit = [obj.exit];

}

function wrapCheck(wrapper, fn) {

const newFn = function (path) {
  if (wrapper.checkPath(path)) {
    return fn.apply(this, arguments);
  }
};

newFn.toString = () => fn.toString();

return newFn;

}

function shouldIgnoreKey(key) {

if (key[0] === "_") return true;
if (key === "enter" || key === "exit" || key === "shouldSkip") return true;

if (key === "blacklist" || key === "noScope" || key === "skipKeys") {
  return true;
}

return false;

}

function mergePair(dest, src) {

for (const key of Object.keys(src)) {
  dest[key] = [].concat(dest[key] || [], src[key]);
}

}