"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.findRelativeConfig = findRelativeConfig;
exports.findRootConfig = findRootConfig;
exports.loadConfig = loadConfig;

function _debug() {
  var data = _interopRequireDefault(require("debug"));

  _debug = function _debug() {
    return data;
  };

  return data;
}

function _path() {
  var data = _interopRequireDefault(require("path"));

  _path = function _path() {
    return data;
  };

  return data;
}

function _fs() {
  var data = _interopRequireDefault(require("fs"));

  _fs = function _fs() {
    return data;
  };

  return data;
}

function _json() {
  var data = _interopRequireDefault(require("json5"));

  _json = function _json() {
    return data;
  };

  return data;
}

function _resolve() {
  var data = _interopRequireDefault(require("resolve"));

  _resolve = function _resolve() {
    return data;
  };

  return data;
}

var _caching = require("../caching");

var _configApi = _interopRequireDefault(require("../helpers/config-api"));

var _utils = require("./utils");

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

var debug = (0, _debug().default)("babel:config:loading:files:configuration");
var BABEL_CONFIG_JS_FILENAME = "babel.config.js";
var BABELRC_FILENAME = ".babelrc";
var BABELRC_JS_FILENAME = ".babelrc.js";
var BABELIGNORE_FILENAME = ".babelignore";

function findRelativeConfig(packageData, envName) {
  var config = null;
  var ignore = null;

  var dirname = _path().default.dirname(packageData.filepath);

  var _loop = function _loop() {
    if (_isArray) {
      if (_i >= _iterator.length) return "break";
      _ref = _iterator[_i++];
    } else {
      _i = _iterator.next();
      if (_i.done) return "break";
      _ref = _i.value;
    }

    var loc = _ref;

    if (!config) {
      config = [BABELRC_FILENAME, BABELRC_JS_FILENAME].reduce(function (previousConfig, name) {
        var filepath = _path().default.join(loc, name);

        var config = readConfig(filepath, envName);

        if (config && previousConfig) {
          throw new Error("Multiple configuration files found. Please remove one:\n" + (" - " + _path().default.basename(previousConfig.filepath) + "\n") + (" - " + name + "\n") + ("from " + loc));
        }

        return config || previousConfig;
      }, null);
      var pkgConfig = packageData.pkg && packageData.pkg.dirname === loc ? packageToBabelConfig(packageData.pkg) : null;

      if (pkgConfig) {
        if (config) {
          throw new Error("Multiple configuration files found. Please remove one:\n" + (" - " + _path().default.basename(pkgConfig.filepath) + "#babel\n") + (" - " + _path().default.basename(config.filepath) + "\n") + ("from " + loc));
        }

        config = pkgConfig;
      }

      if (config) {
        debug("Found configuration %o from %o.", config.filepath, dirname);
      }
    }

    if (!ignore) {
      var ignoreLoc = _path().default.join(loc, BABELIGNORE_FILENAME);

      ignore = readIgnoreConfig(ignoreLoc);

      if (ignore) {
        debug("Found ignore %o from %o.", ignore.filepath, dirname);
      }
    }
  };

  for (var _iterator = packageData.directories, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
    var _ref;

    var _ret = _loop();

    if (_ret === "break") break;
  }

  return {
    config: config,
    ignore: ignore
  };
}

function findRootConfig(dirname, envName) {
  var filepath = _path().default.resolve(dirname, BABEL_CONFIG_JS_FILENAME);

  var conf = readConfig(filepath, envName);

  if (conf) {
    debug("Found root config %o in $o.", BABEL_CONFIG_JS_FILENAME, dirname);
  }

  return conf;
}

function loadConfig(name, dirname, envName) {
  var filepath = _resolve().default.sync(name, {
    basedir: dirname
  });

  var conf = readConfig(filepath, envName);

  if (!conf) {
    throw new Error("Config file " + filepath + " contains no configuration data");
  }

  debug("Loaded config %o from $o.", name, dirname);
  return conf;
}

function readConfig(filepath, envName) {
  return _path().default.extname(filepath) === ".js" ? readConfigJS(filepath, {
    envName: envName
  }) : readConfigJSON5(filepath);
}

var LOADING_CONFIGS = new Set();
var readConfigJS = (0, _caching.makeStrongCache)(function (filepath, cache) {
  if (!_fs().default.existsSync(filepath)) {
    cache.forever();
    return null;
  }

  if (LOADING_CONFIGS.has(filepath)) {
    cache.never();
    debug("Auto-ignoring usage of config %o.", filepath);
    return {
      filepath: filepath,
      dirname: _path().default.dirname(filepath),
      options: {}
    };
  }

  var options;

  try {
    LOADING_CONFIGS.add(filepath);

    var configModule = require(filepath);

    options = configModule && configModule.__esModule ? configModule.default || undefined : configModule;
  } catch (err) {
    err.message = filepath + ": Error while loading config - " + err.message;
    throw err;
  } finally {
    LOADING_CONFIGS.delete(filepath);
  }

  if (typeof options === "function") {
    options = options((0, _configApi.default)(cache));
    if (!cache.configured()) throwConfigError();
  }

  if (!options || typeof options !== "object" || Array.isArray(options)) {
    throw new Error(filepath + ": Configuration should be an exported JavaScript object.");
  }

  if (typeof options.then === "function") {
    throw new Error("You appear to be using an async configuration, " + "which your current version of Babel does not support. " + "We may add support for this in the future, " + "but if you're on the most recent version of @babel/core and still " + "seeing this error, then you'll need to synchronously return your config.");
  }

  return {
    filepath: filepath,
    dirname: _path().default.dirname(filepath),
    options: options
  };
});
var packageToBabelConfig = (0, _caching.makeWeakCache)(function (file) {
  if (typeof file.options.babel === "undefined") return null;
  var babel = file.options.babel;

  if (typeof babel !== "object" || Array.isArray(babel) || babel === null) {
    throw new Error(file.filepath + ": .babel property must be an object");
  }

  return {
    filepath: file.filepath,
    dirname: file.dirname,
    options: babel
  };
});
var readConfigJSON5 = (0, _utils.makeStaticFileCache)(function (filepath, content) {
  var options;

  try {
    options = _json().default.parse(content);
  } catch (err) {
    err.message = filepath + ": Error while parsing config - " + err.message;
    throw err;
  }

  if (!options) throw new Error(filepath + ": No config detected");

  if (typeof options !== "object") {
    throw new Error(filepath + ": Config returned typeof " + typeof options);
  }

  if (Array.isArray(options)) {
    throw new Error(filepath + ": Expected config object but found array");
  }

  return {
    filepath: filepath,
    dirname: _path().default.dirname(filepath),
    options: options
  };
});
var readIgnoreConfig = (0, _utils.makeStaticFileCache)(function (filepath, content) {
  var ignore = content.split("\n").map(function (line) {
    return line.replace(/#(.*?)$/, "").trim();
  }).filter(function (line) {
    return !!line;
  });
  return {
    filepath: filepath,
    dirname: _path().default.dirname(filepath),
    ignore: ignore
  };
});

function throwConfigError() {
  throw new Error("Caching was left unconfigured. Babel's plugins, presets, and .babelrc.js files can be configured\nfor various types of caching, using the first param of their handler functions:\n\nmodule.exports = function(api) {\n  // The API exposes the following:\n\n  // Cache the returned value forever and don't call this function again.\n  api.cache(true);\n\n  // Don't cache at all. Not recommended because it will be very slow.\n  api.cache(false);\n\n  // Cached based on the value of some function. If this function returns a value different from\n  // a previously-encountered value, the plugins will re-evaluate.\n  var env = api.cache(() => process.env.NODE_ENV);\n\n  // If testing for a specific env, we recommend specifics to avoid instantiating a plugin for\n  // any possible NODE_ENV value that might come up during plugin execution.\n  var isProd = api.cache(() => process.env.NODE_ENV === \"production\");\n\n  // .cache(fn) will perform a linear search though instances to find the matching plugin based\n  // based on previous instantiated plugins. If you want to recreate the plugin and discard the\n  // previous instance whenever something changes, you may use:\n  var isProd = api.cache.invalidate(() => process.env.NODE_ENV === \"production\");\n\n  // Note, we also expose the following more-verbose versions of the above examples:\n  api.cache.forever(); // api.cache(true)\n  api.cache.never();   // api.cache(false)\n  api.cache.using(fn); // api.cache(fn)\n\n  // Return the value that will be cached.\n  return { };\n};");
}