UNPKG

hardhat

Version:

Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.

337 lines 16.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Resolver = exports.ResolvedFile = void 0; const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const resolve_1 = __importDefault(require("resolve")); const source_names_1 = require("../../utils/source-names"); const errors_1 = require("../core/errors"); const errors_list_1 = require("../core/errors-list"); const hash_1 = require("../util/hash"); const fs_utils_1 = require("../util/fs-utils"); const remappings_1 = require("../../utils/remappings"); const NODE_MODULES = "node_modules"; class ResolvedFile { constructor(sourceName, absolutePath, content, contentHash, lastModificationDate, libraryName, libraryVersion) { this.sourceName = sourceName; this.absolutePath = absolutePath; this.content = content; this.contentHash = contentHash; this.lastModificationDate = lastModificationDate; (0, errors_1.assertHardhatInvariant)((libraryName === undefined && libraryVersion === undefined) || (libraryName !== undefined && libraryVersion !== undefined), "Libraries should have both name and version, or neither one"); if (libraryName !== undefined && libraryVersion !== undefined) { this.library = { name: libraryName, version: libraryVersion, }; } } getVersionedName() { return (this.sourceName + (this.library !== undefined ? `@v${this.library.version}` : "")); } } exports.ResolvedFile = ResolvedFile; class Resolver { constructor(_projectRoot, _parser, _remappings, _readFile, _transformImportName) { this._projectRoot = _projectRoot; this._parser = _parser; this._remappings = _remappings; this._readFile = _readFile; this._transformImportName = _transformImportName; this._cache = new Map(); } /** * Resolves a source name into a ResolvedFile. * * @param sourceName The source name as it would be provided to solc. */ async resolveSourceName(sourceName) { const cached = this._cache.get(sourceName); if (cached !== undefined) { return cached; } const remappedSourceName = (0, remappings_1.applyRemappings)(this._remappings, sourceName); (0, source_names_1.validateSourceNameFormat)(remappedSourceName); let resolvedFile; if (await (0, source_names_1.isLocalSourceName)(this._projectRoot, remappedSourceName)) { resolvedFile = await this._resolveLocalSourceName(sourceName, remappedSourceName); } else { resolvedFile = await this._resolveLibrarySourceName(sourceName, remappedSourceName); } this._cache.set(sourceName, resolvedFile); return resolvedFile; } /** * Resolves an import from an already resolved file. * @param from The file were the import statement is present. * @param importName The path in the import statement. */ async resolveImport(from, importName) { // sanity check for deprecated task if (importName !== (await this._transformImportName(importName))) { throw new errors_1.HardhatError(errors_list_1.ERRORS.TASK_DEFINITIONS.DEPRECATED_TRANSFORM_IMPORT_TASK); } const imported = (0, remappings_1.applyRemappings)(this._remappings, importName); const scheme = this._getUriScheme(imported); if (scheme !== undefined) { throw new errors_1.HardhatError(errors_list_1.ERRORS.RESOLVER.INVALID_IMPORT_PROTOCOL, { from: from.sourceName, imported, protocol: scheme, }); } if ((0, source_names_1.replaceBackslashes)(imported) !== imported) { throw new errors_1.HardhatError(errors_list_1.ERRORS.RESOLVER.INVALID_IMPORT_BACKSLASH, { from: from.sourceName, imported, }); } if ((0, source_names_1.isAbsolutePathSourceName)(imported)) { throw new errors_1.HardhatError(errors_list_1.ERRORS.RESOLVER.INVALID_IMPORT_ABSOLUTE_PATH, { from: from.sourceName, imported, }); } // Edge-case where an import can contain the current package's name in monorepos. // The path can be resolved because there's a symlink in the node modules. if (await (0, source_names_1.includesOwnPackageName)(imported)) { throw new errors_1.HardhatError(errors_list_1.ERRORS.RESOLVER.INCLUDES_OWN_PACKAGE_NAME, { from: from.sourceName, imported, }); } try { let sourceName; const isRelativeImport = this._isRelativeImport(imported); if (isRelativeImport) { sourceName = await this._relativeImportToSourceName(from, imported); } else { sourceName = (0, source_names_1.normalizeSourceName)(importName); // The sourceName of the imported file is not transformed } const cached = this._cache.get(sourceName); if (cached !== undefined) { return cached; } let resolvedFile; // We have this special case here, because otherwise local relative // imports can be treated as library imports. For example if // `contracts/c.sol` imports `../non-existent/a.sol` if (from.library === undefined && isRelativeImport && !this._isRelativeImportToLibrary(from, imported)) { resolvedFile = await this._resolveLocalSourceName(sourceName, (0, remappings_1.applyRemappings)(this._remappings, sourceName)); } else { resolvedFile = await this.resolveSourceName(sourceName); } this._cache.set(sourceName, resolvedFile); return resolvedFile; } catch (error) { if (errors_1.HardhatError.isHardhatErrorType(error, errors_list_1.ERRORS.RESOLVER.FILE_NOT_FOUND) || errors_1.HardhatError.isHardhatErrorType(error, errors_list_1.ERRORS.RESOLVER.LIBRARY_FILE_NOT_FOUND)) { if (imported !== importName) { throw new errors_1.HardhatError(errors_list_1.ERRORS.RESOLVER.IMPORTED_MAPPED_FILE_NOT_FOUND, { imported, importName, from: from.sourceName, }, error); } else { throw new errors_1.HardhatError(errors_list_1.ERRORS.RESOLVER.IMPORTED_FILE_NOT_FOUND, { imported, from: from.sourceName, }, error); } } if (errors_1.HardhatError.isHardhatErrorType(error, errors_list_1.ERRORS.RESOLVER.WRONG_SOURCE_NAME_CASING)) { throw new errors_1.HardhatError(errors_list_1.ERRORS.RESOLVER.INVALID_IMPORT_WRONG_CASING, { imported, from: from.sourceName, }, error); } if (errors_1.HardhatError.isHardhatErrorType(error, errors_list_1.ERRORS.RESOLVER.LIBRARY_NOT_INSTALLED)) { throw new errors_1.HardhatError(errors_list_1.ERRORS.RESOLVER.IMPORTED_LIBRARY_NOT_INSTALLED, { library: error.messageArguments.library, from: from.sourceName, }, error); } if (errors_1.HardhatError.isHardhatErrorType(error, errors_list_1.ERRORS.GENERAL.INVALID_READ_OF_DIRECTORY)) { throw new errors_1.HardhatError(errors_list_1.ERRORS.RESOLVER.INVALID_IMPORT_OF_DIRECTORY, { imported, from: from.sourceName, }, error); } // eslint-disable-next-line @nomicfoundation/hardhat-internal-rules/only-hardhat-error throw error; } } async _resolveLocalSourceName(sourceName, remappedSourceName) { await this._validateSourceNameExistenceAndCasing(this._projectRoot, remappedSourceName, false); const absolutePath = path_1.default.join(this._projectRoot, remappedSourceName); return this._resolveFile(sourceName, absolutePath); } async _resolveLibrarySourceName(sourceName, remappedSourceName) { const normalizedSourceName = remappedSourceName.replace(/^node_modules\//, ""); const libraryName = this._getLibraryName(normalizedSourceName); let packageJsonPath; try { packageJsonPath = this._resolveNodeModulesFileFromProjectRoot(path_1.default.join(libraryName, "package.json")); } catch (error) { // if the project is using a dependency from hardhat itself but it can't // be found, this means that a global installation is being used, so we // resolve the dependency relative to this file if (libraryName === "hardhat") { const hardhatCoreDir = path_1.default.join(__dirname, "..", ".."); packageJsonPath = path_1.default.join(hardhatCoreDir, "package.json"); } else { throw new errors_1.HardhatError(errors_list_1.ERRORS.RESOLVER.LIBRARY_NOT_INSTALLED, { library: libraryName, }, error); } } let nodeModulesPath = path_1.default.dirname(path_1.default.dirname(packageJsonPath)); if (this._isScopedPackage(normalizedSourceName)) { nodeModulesPath = path_1.default.dirname(nodeModulesPath); } let absolutePath; if (path_1.default.basename(nodeModulesPath) !== NODE_MODULES) { // this can happen in monorepos that use PnP, in those // cases we handle resolution differently const packageRoot = path_1.default.dirname(packageJsonPath); const pattern = new RegExp(`^${libraryName}/?`); const fileName = normalizedSourceName.replace(pattern, ""); await this._validateSourceNameExistenceAndCasing(packageRoot, // TODO: this is _not_ a source name; we should handle this scenario in // a better way fileName, true); absolutePath = path_1.default.join(packageRoot, fileName); } else { await this._validateSourceNameExistenceAndCasing(nodeModulesPath, normalizedSourceName, true); absolutePath = path_1.default.join(nodeModulesPath, normalizedSourceName); } const packageInfo = await fs_extra_1.default.readJson(packageJsonPath); const libraryVersion = packageInfo.version; return this._resolveFile(sourceName, // We resolve to the real path here, as we may be resolving a linked library await (0, fs_utils_1.getRealPath)(absolutePath), libraryName, libraryVersion); } async _relativeImportToSourceName(from, imported) { // This is a special case, were we turn relative imports from local files // into library imports if necessary. The reason for this is that many // users just do `import "../node_modules/lib/a.sol";`. if (this._isRelativeImportToLibrary(from, imported)) { return this._relativeImportToLibraryToSourceName(from, imported); } const sourceName = (0, source_names_1.normalizeSourceName)(path_1.default.join(path_1.default.dirname(from.sourceName), imported)); // If the file with the import is local, and the normalized version // starts with ../ means that it's trying to get outside of the project. if (from.library === undefined && sourceName.startsWith("../")) { throw new errors_1.HardhatError(errors_list_1.ERRORS.RESOLVER.INVALID_IMPORT_OUTSIDE_OF_PROJECT, { from: from.sourceName, imported }); } if (from.library !== undefined && !this._isInsideSameDir(from.sourceName, sourceName)) { // If the file is being imported from a library, this means that it's // trying to reach another one. throw new errors_1.HardhatError(errors_list_1.ERRORS.RESOLVER.ILLEGAL_IMPORT, { from: from.sourceName, imported, }); } return sourceName; } async _resolveFile(sourceName, absolutePath, libraryName, libraryVersion) { const rawContent = await this._readFile(absolutePath); const stats = await fs_extra_1.default.stat(absolutePath); const lastModificationDate = new Date(stats.ctime); const contentHash = (0, hash_1.createNonCryptographicHashBasedIdentifier)(Buffer.from(rawContent)).toString("hex"); const parsedContent = this._parser.parse(rawContent, absolutePath, contentHash); const content = { rawContent, ...parsedContent, }; return new ResolvedFile(sourceName, absolutePath, content, contentHash, lastModificationDate, libraryName, libraryVersion); } _isRelativeImport(imported) { return imported.startsWith("./") || imported.startsWith("../"); } _resolveNodeModulesFileFromProjectRoot(fileName) { return resolve_1.default.sync(fileName, { basedir: this._projectRoot, preserveSymlinks: true, }); } _getLibraryName(sourceName) { let endIndex; if (this._isScopedPackage(sourceName)) { endIndex = sourceName.indexOf("/", sourceName.indexOf("/") + 1); } else if (sourceName.indexOf("/") === -1) { endIndex = sourceName.length; } else { endIndex = sourceName.indexOf("/"); } return sourceName.slice(0, endIndex); } _getUriScheme(s) { const re = /([a-zA-Z]+):\/\//; const match = re.exec(s); if (match === null) { return undefined; } return match[1]; } _isInsideSameDir(sourceNameInDir, sourceNameToTest) { const firstSlash = sourceNameInDir.indexOf("/"); const dir = firstSlash !== -1 ? sourceNameInDir.substring(0, firstSlash) : sourceNameInDir; return sourceNameToTest.startsWith(dir); } _isScopedPackage(packageOrPackageFile) { return packageOrPackageFile.startsWith("@"); } _isRelativeImportToLibrary(from, imported) { return (this._isRelativeImport(imported) && from.library === undefined && imported.includes(`${NODE_MODULES}/`)); } _relativeImportToLibraryToSourceName(from, imported) { const sourceName = (0, source_names_1.normalizeSourceName)(path_1.default.join(path_1.default.dirname(from.sourceName), imported)); const nmIndex = sourceName.indexOf(`${NODE_MODULES}/`); return sourceName.substr(nmIndex + NODE_MODULES.length + 1); } async _validateSourceNameExistenceAndCasing(fromDir, sourceName, isLibrary) { try { await (0, source_names_1.validateSourceNameExistenceAndCasing)(fromDir, sourceName); } catch (error) { if (errors_1.HardhatError.isHardhatErrorType(error, errors_list_1.ERRORS.SOURCE_NAMES.FILE_NOT_FOUND)) { throw new errors_1.HardhatError(isLibrary ? errors_list_1.ERRORS.RESOLVER.LIBRARY_FILE_NOT_FOUND : errors_list_1.ERRORS.RESOLVER.FILE_NOT_FOUND, { file: sourceName }, error); } if (errors_1.HardhatError.isHardhatErrorType(error, errors_list_1.ERRORS.SOURCE_NAMES.WRONG_CASING)) { throw new errors_1.HardhatError(errors_list_1.ERRORS.RESOLVER.WRONG_SOURCE_NAME_CASING, { incorrect: sourceName, correct: error.messageArguments.correct, }, error); } // eslint-disable-next-line @nomicfoundation/hardhat-internal-rules/only-hardhat-error throw error; } } } exports.Resolver = Resolver; //# sourceMappingURL=resolver.js.map