trufflesuite/truffle

View on GitHub
packages/resolver/lib/sources/npm.ts

Summary

Maintainability
B
5 hrs
Test Coverage
import path from "path";
import fs from "fs";

import type { ResolverSource } from "../source";

export class NPM implements ResolverSource {
  workingDirectory: string;

  constructor(workingDirectory: string) {
    this.workingDirectory = workingDirectory;
  }

  require(importPath: string, searchPath: string) {
    if (importPath.indexOf(".") === 0 || importPath.indexOf("/") === 0) {
      return null;
    }
    const contractName = path.basename(importPath, ".sol");
    const regex = new RegExp(`(.*)/${contractName}`);
    let packageName = "";
    const matched = regex.exec(importPath);
    if (matched) {
      packageName = matched[1];
    }
    // during testing a temp dir is passed as search path - we need to check the
    // working dir in case a built contract was not copied over to it
    for (const basePath of [searchPath, this.workingDirectory]) {
      if (!basePath) {
        continue;
      }
      const result = this.resolveAndParse(basePath, packageName, contractName);
      // result is null if it fails to resolve
      if (result) {
        return result;
      }
      continue;
    }
    return null;
  }

  async resolve(import_path: string, _imported_from: string) {
    // If nothing's found, body returns `undefined`
    var body: string | undefined;
    var modulesDir = this.workingDirectory;

    while (true) {
      var expected_path = path.join(modulesDir, "node_modules", import_path);

      try {
        var body = fs.readFileSync(expected_path, { encoding: "utf8" });
        break;
      } catch (err) {}

      // Recurse outwards until impossible
      var oldModulesDir = modulesDir;
      modulesDir = path.join(modulesDir, "..");
      if (modulesDir === oldModulesDir) {
        break;
      }
    }
    return { body, filePath: import_path };
  }

  resolveAndParse(basePath: string, packageName: string, contractName: string) {
    const packagePath = path.join(basePath, "node_modules", packageName);
    const subDirs = [`build${path.sep}contracts`, "build"];
    for (const subDir of subDirs) {
      const possiblePath = path.join(
        packagePath,
        subDir,
        `${contractName}.json`
      );
      try {
        const result = fs.readFileSync(possiblePath, "utf8");
        return JSON.parse(result);
      } catch (e) {
        continue;
      }
    }
    return null;
  }

  // We're resolving package paths to other package paths, not absolute paths.
  // This will ensure the source fetcher conintues to use the correct sources for packages.
  // i.e., if some_module/contracts/MyContract.sol imported "./AnotherContract.sol",
  // we're going to resolve it to some_module/contracts/AnotherContract.sol, ensuring
  // that when this path is evaluated this source is used again.
  resolveDependencyPath(importPath: string, dependencyPath: string) {
    if (
      !(dependencyPath.startsWith("./") || dependencyPath.startsWith("../"))
    ) {
      //if it's *not* a relative path, return it unchanged
      return dependencyPath;
    }
    var dirname = path.dirname(importPath);
    return path.join(dirname, dependencyPath);
  }
}