interlockjs/interlock

View on GitHub
src/resolve.js

Summary

Maintainability
A
2 hrs
Test Coverage
import path from "path";

import { some } from "lodash";

import { isFile, isDir, getPossiblePaths } from "./util/file";


function select (items, fn) {
  for (const item of items) {
    const result = fn(item);
    if (result) { return result; }
  }
  return null;
}

function resolveFile (absPath, extensions) {
  let altPath;

  if (isFile(absPath)) { return absPath; }

  return some(extensions, function (ext) {
    altPath = absPath + ext;
    return isFile(altPath) && altPath;
  }) && altPath || null;
}

function resolveDir (absPath, extensions) {
  if (!isDir(absPath)) { return null; }

  const packageJsonPath = path.join(absPath, "package.json");
  if (isFile(packageJsonPath)) {
    const main = require(packageJsonPath).main; // eslint-disable-line global-require
    if (main) {
      const mainAbsPath = path.join(absPath, main);
      return resolveFile(mainAbsPath, extensions) || resolveDir(mainAbsPath, extensions);
    }
  }

  const fallback = path.join(absPath, "index.js");
  return isFile(fallback) && fallback || null;
}

function resolveSimple (requireStr, contextPath, nsRoot, extensions) {
  const absPath = path.resolve(contextPath, requireStr);

  const resolvedPath = resolveFile(absPath, extensions) || resolveDir(absPath, extensions);
  if (resolvedPath) {
    return {
      resolvedPath,
      nsPath: path.relative(nsRoot, resolvedPath)
    };
  }
  return null;
}

/**
 * Evaluates a require string to an absolute path, along with that file's
 * namespace meta-data.
 *
 * @param  {String} requireStr   The value passed to the `require()` statement.
 * @param  {String} contextPath  The directory from where relative requires are evaluated.
 * @param  {String} ns           The default/active namespace.
 * @param  {String} nsRoot       The root directory of the default/active namespace.
 * @param  {Array}  extensions   All possible file extensions to try.
 * @param  {Array}  searchPaths  Additional directories in which to attempt to locate
 *                               specified resource.
 *
 * @returns {Object}             Object with three properties:
 *                                 - path: the resolved absolute path of the resource;
 *                                 - ns: the namespace associated with that resource;
 *                                 - nsPath: the root path of the namespace.
 *
 * @returns {null}               Will return null if resolve fails.
 */
export default function resolve (requireStr, contextPath, ns, nsRoot, extensions, searchPaths = []) { // eslint-disable-line max-len,max-params
  const resolvedSimple = resolveSimple(requireStr, contextPath, nsRoot, extensions);

  if (resolvedSimple) {
    return {
      path: resolvedSimple.resolvedPath,
      ns,
      nsPath: resolvedSimple.nsPath,
      nsRoot,
      uri: `${ns}:${resolvedSimple.nsPath}`
    };
  }

  if (/^(\.\.?)?\//.test(requireStr)) { return null; }

  ns = requireStr.split("/")[0];
  const resolvedPath = select(searchPaths.concat(getPossiblePaths(contextPath, "node_modules")), searchPath => { // eslint-disable-line max-len
    const searchCandidate = path.join(searchPath, requireStr);
    nsRoot = path.join(searchPath, ns);
    return resolveFile(searchCandidate, extensions) || resolveDir(searchCandidate, extensions);
  });

  if (resolvedPath) {
    const nsPath = path.relative(nsRoot, resolvedPath);
    return {
      path: resolvedPath,
      ns,
      nsPath,
      nsRoot,
      uri: `${ns}:${nsPath}`
    };
  }

  return null;
}