UNPKG

hardhat

Version:

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

402 lines 17.7 kB
import { HardhatError, assertHardhatInvariant, } from "@nomicfoundation/hardhat-errors"; import { DirectoryNotEmptyError, FileAlreadyExistsError, FileNotFoundError, FileSystemAccessError, InvalidFileFormatError, IsDirectoryError, NotADirectoryError, } from "@nomicfoundation/hardhat-utils/fs"; import { PackageJsonNotFoundError, PackageJsonReadError, } from "@nomicfoundation/hardhat-utils/package"; import { DispatcherError, RequestError, ResponseStatusCodeError, } from "@nomicfoundation/hardhat-utils/request"; import { SubprocessFileNotFoundError, SubprocessPathIsDirectoryError, } from "@nomicfoundation/hardhat-utils/subprocess"; import { EdrProviderStackTraceGenerationError, SolidityTestStackTraceGenerationError, } from "../../../builtin-plugins/network-manager/edr/stack-traces/stack-trace-generation-errors.js"; import { ProviderError, UnknownError, } from "../../../builtin-plugins/network-manager/provider-errors.js"; import { UsingHardhat2PluginError } from "../../../using-hardhat2-plugin-errors.js"; import { getHookExecutionFrame, getTaskExecutionFrame, isConfigLoadingBoundaryFrame, isConsoleEvaluationBoundaryFrame, isEdrFrame, isFirstPartyPluginFrame, isHookHandlerBoundaryFrame, isMochaTestExecutionBoundaryFrame, isNodeTestExecutionBoundaryFrame, isRunningInsideHardhatMonorepo, isScriptExecutionBoundaryFrame, isTaskActionBoundaryFrame, isThirdPartyFrame, } from "./codebase-dependent-helpers.js"; import { FrameOrigin, createErrorContext, getNodeErrorCode, hasErrorClassName, includesAny, } from "./helpers.js"; /** * Classifies the error based on a set of heuristics. * * This classification is later used to select different criteria to decide if * the error should be reported or not, and in some cases, how to display it in * the CLI. * * @param error The error to classify. * @param ignoreDevelopmentTimeFilter If true, the classifier will ignore the * development-time filter, which is used to exclude errors that happen during * development of Hardhat itself. This is only meant to be used for testing. * @returns The error category. */ export function classifyError(error, ignoreDevelopmentTimeFilter = false) { const context = createErrorContext(error); for (const matcher of ERROR_CATEGORY_MATCHERS) { if (ignoreDevelopmentTimeFilter && matcher === isDevelopmentTimeError) { continue; } const category = matcher(context); if (category !== undefined) { return category; } } return ErrorCategory.UNEXPECTED_ERROR; } export var ErrorCategory; (function (ErrorCategory) { ErrorCategory["CJS_TO_ESM_MIGRATION_ERROR"] = "CJS_TO_ESM_MIGRATION_ERROR"; ErrorCategory["HH2_TO_HH3_MIGRATION_ERROR"] = "HH2_TO_HH3_MIGRATION_ERROR"; ErrorCategory["TYPESCRIPT_SUPPORT_ERROR"] = "TYPESCRIPT_SUPPORT_ERROR"; ErrorCategory["DEVELOPMENT_TIME_ERROR"] = "DEVELOPMENT_TIME_ERROR"; ErrorCategory["HARDHAT_ERROR"] = "HARDHAT_ERROR"; ErrorCategory["CONFIG_LOADING_ERROR"] = "CONFIG_LOADING_ERROR"; ErrorCategory["CONSOLE_EVALUATION_ERROR"] = "CONSOLE_EVALUATION_ERROR"; ErrorCategory["SCRIPT_EXECUTION_ERROR"] = "SCRIPT_EXECUTION_ERROR"; ErrorCategory["NODE_TEST_EXECUTION_ERROR"] = "NODE_TEST_EXECUTION_ERROR"; ErrorCategory["MOCHA_TEST_EXECUTION_ERROR"] = "MOCHA_TEST_EXECUTION_ERROR"; ErrorCategory["TASK_ACTION_ERROR"] = "TASK_ACTION_ERROR"; ErrorCategory["PLUGIN_TASK_ACTION_ERROR"] = "PLUGIN_TASK_ACTION_ERROR"; ErrorCategory["USER_TASK_ACTION_ERROR"] = "USER_TASK_ACTION_ERROR"; ErrorCategory["PLUGIN_HOOK_HANDLER_ERROR"] = "PLUGIN_HOOK_HANDLER_ERROR"; ErrorCategory["PROVIDER_INTERACTION_ERROR"] = "PROVIDER_INTERACTION_ERROR"; ErrorCategory["EDR_ERROR"] = "EDR_ERROR"; ErrorCategory["NETWORK_INTERACTION_ERROR"] = "NETWORK_INTERACTION_ERROR"; ErrorCategory["RUNTIME_ENVIRONMENT_ERROR"] = "RUNTIME_ENVIRONMENT_ERROR"; ErrorCategory["FILESYSTEM_INTERACTION_ERROR"] = "FILESYSTEM_INTERACTION_ERROR"; ErrorCategory["UNEXPECTED_ERROR"] = "UNEXPECTED_ERROR"; })(ErrorCategory || (ErrorCategory = {})); export const USER_CODE_BOUNDARY_FRAME_MATCHERS = { [ErrorCategory.CONFIG_LOADING_ERROR]: isConfigLoadingBoundaryFrame, [ErrorCategory.CONSOLE_EVALUATION_ERROR]: isConsoleEvaluationBoundaryFrame, [ErrorCategory.SCRIPT_EXECUTION_ERROR]: isScriptExecutionBoundaryFrame, [ErrorCategory.NODE_TEST_EXECUTION_ERROR]: isNodeTestExecutionBoundaryFrame, [ErrorCategory.MOCHA_TEST_EXECUTION_ERROR]: isMochaTestExecutionBoundaryFrame, [ErrorCategory.PLUGIN_TASK_ACTION_ERROR]: isTaskActionBoundaryFrame, [ErrorCategory.USER_TASK_ACTION_ERROR]: isTaskActionBoundaryFrame, [ErrorCategory.PLUGIN_HOOK_HANDLER_ERROR]: isHookHandlerBoundaryFrame, }; // These are categories that only need the boundary check for classification const BOUNDARY_ONLY_ERROR_CATEGORIES = [ ErrorCategory.CONFIG_LOADING_ERROR, ErrorCategory.SCRIPT_EXECUTION_ERROR, ErrorCategory.NODE_TEST_EXECUTION_ERROR, ErrorCategory.MOCHA_TEST_EXECUTION_ERROR, ErrorCategory.CONSOLE_EVALUATION_ERROR, ]; // IMPORTANT: The order here matters, as the first matcher that returns a // category wins const ERROR_CATEGORY_MATCHERS = [ isDevelopmentTimeError, isESMMigrationError, isHH3MigrationError, isTypescriptSupportError, isHardhatError, isProviderInteractionError, isEdrError, isNetworkInteractionError, isRuntimeEnvironmentError, isFilesystemInteractionError, isTaskActionError, isBoundaryOnlyError, isPluginTaskActionError, isUserTaskActionError, isPluginHookHandlerError, ]; const ESM_MIGRATION_MARKERS = [ "is not defined in es module scope", "cannot use import statement outside a module", ]; /** * Classifies common CommonJS/ESM migration failures by matching standard Node * runtime markers. */ function isESMMigrationError(context) { for (const lowercaseMessage of context.lowercaseMessageByError.values()) { if (includesAny(lowercaseMessage, ...ESM_MIGRATION_MARKERS) || /require\(\) of es module/.test(lowercaseMessage)) { return ErrorCategory.CJS_TO_ESM_MIGRATION_ERROR; } } } const HH3_MIGRATION_MARKERS = [ "class extends value undefined is not a constructor or null", "the requested module 'hardhat' does not provide an export named", 'the requested module "hardhat" does not provide an export named', "the requested module 'hardhat/config' does not provide an export named", 'the requested module "hardhat/config" does not provide an export named', "the requested module 'hardhat/plugins' does not provide an export named", 'the requested module "hardhat/plugins" does not provide an export named', "the requested module 'hardhat/builtin-tasks/task-names' does not provide an export named", 'the requested module "hardhat/builtin-tasks/task-names" does not provide an export named', "the requested module 'hardhat/types/runtime' does not provide an export named", 'the requested module "hardhat/types/runtime" does not provide an export named', ]; const HH2_PLUGIN_ERROR_MARKER = "you are trying to use a hardhat 2 plugin in a hardhat 3 project"; /** * Classifies Hardhat 2 to Hardhat 3 migration failures by checking for known * migration error types and message patterns anywhere in the cause chain. * * This is temporary, and will be removed once the HH2 to HH3 migration by the * community is in a solid state. The matcher is broad, but right now mostly * correct. */ function isHH3MigrationError(context) { if (context.errorChain.some((candidate) => hasErrorClassName(candidate, UsingHardhat2PluginError) || (context.lowercaseMessageByError.get(candidate) ?? "").includes(HH2_PLUGIN_ERROR_MARKER))) { return ErrorCategory.HH2_TO_HH3_MIGRATION_ERROR; } for (const lowercaseMessage of context.lowercaseMessageByError.values()) { if (includesAny(lowercaseMessage, ...HH3_MIGRATION_MARKERS)) { return ErrorCategory.HH2_TO_HH3_MIGRATION_ERROR; } } if (context.errorChain.some((candidate) => candidate.stack?.includes("@nomiclabs") === true)) { return ErrorCategory.HH2_TO_HH3_MIGRATION_ERROR; } } /** * If Hardhat is being run from the monorepo, we don't report the error. */ function isDevelopmentTimeError(_context) { if (isRunningInsideHardhatMonorepo()) { return ErrorCategory.DEVELOPMENT_TIME_ERROR; } return undefined; } const TYPESCRIPT_SUPPORT_ERROR_CODES = new Set([ "ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX", "ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING", "ERR_NO_TYPESCRIPT", "ERR_UNKNOWN_FILE_EXTENSION", ]); /** * Classifies Node.js TypeScript support failures by matching Node's stable * error codes. ERR_UNKNOWN_FILE_EXTENSION also happens for non-TypeScript * extensions, so require a TypeScript extension in that error's message. */ function isTypescriptSupportError(context) { if (context.errorChain.some((candidate) => { const code = getTypescriptSupportErrorCode(candidate); if (code === undefined) { return false; } return (code !== "ERR_UNKNOWN_FILE_EXTENSION" || includesAny(context.lowercaseMessageByError.get(candidate), ".ts", ".mts", ".cts")); })) { return ErrorCategory.TYPESCRIPT_SUPPORT_ERROR; } } /** * Classifies top-level HardhatError instances that were not captured by a more * specific matcher earlier in the chain. */ function isHardhatError(context) { if (HardhatError.isHardhatError(context.error)) { return ErrorCategory.HARDHAT_ERROR; } } function isBoundaryOnlyError(context) { for (const category of BOUNDARY_ONLY_ERROR_CATEGORIES) { const boundaryMatcher = USER_CODE_BOUNDARY_FRAME_MATCHERS[category]; if (context.allStackFrames.some(boundaryMatcher)) { return category; } } } /** * Classifies task-action failures routed through ResolvedTask when the nearest * task-action frame belongs to first-party code. */ function isTaskActionError(context) { const taskActionFrame = getTaskExecutionFrame(context.allStackFrames); if (taskActionFrame !== undefined && isFirstPartyPluginFrame(taskActionFrame.location)) { return ErrorCategory.TASK_ACTION_ERROR; } } /** * Classifies task-action failures routed through ResolvedTask when the nearest * task-action frame belongs to a third-party plugin. */ function isPluginTaskActionError(context) { const taskActionFrame = getTaskExecutionFrame(context.allStackFrames); if (taskActionFrame !== undefined && isThirdPartyFrame(taskActionFrame.location)) { return ErrorCategory.PLUGIN_TASK_ACTION_ERROR; } } /** * Classifies task-action failures routed through ResolvedTask when the nearest * task-action frame belongs to user project code. */ function isUserTaskActionError(context) { const taskActionFrame = getTaskExecutionFrame(context.allStackFrames); if (taskActionFrame !== undefined && taskActionFrame.origin === FrameOrigin.USER_PROJECT) { return ErrorCategory.USER_TASK_ACTION_ERROR; } } /** * Classifies hook execution failures routed through HookManager when the * nearest hook-handler frame belongs to a third-party plugin. */ function isPluginHookHandlerError(context) { const hookExecutionFrame = getHookExecutionFrame(context.allStackFrames); if (hookExecutionFrame !== undefined && isThirdPartyFrame(hookExecutionFrame.location)) { return ErrorCategory.PLUGIN_HOOK_HANDLER_ERROR; } } const PROVIDER_INTERACTION_ERROR_WRAPPED_IN_UNKNOWN_EDR_ERROR_MARKERS = [ "unauthorized", "rate limit", "too many requests", "historical state unavailable", ]; /** * Classifies provider-facing failures, including Solidity errors, expected * provider errors, and selected UnknownError cases with provider-like causes. */ function isProviderInteractionError(context) { if (context.errorChain.some((candidate) => candidate.name === "SolidityError" || (ProviderError.isProviderError(candidate) && isUnknownEdrError(candidate, context) === false))) { return ErrorCategory.PROVIDER_INTERACTION_ERROR; } if (isUnknownEdrError(context.errorChain[0], context) && context.errorChain[1] !== undefined && includesAny(context.lowercaseMessageByError.get(context.errorChain[1]), ...PROVIDER_INTERACTION_ERROR_WRAPPED_IN_UNKNOWN_EDR_ERROR_MARKERS)) { return ErrorCategory.PROVIDER_INTERACTION_ERROR; } } /** * Classifies EDR-specific failures, including stack-trace generation errors * and remaining UnknownError cases from the provider layer. */ function isEdrError(context) { if (context.errorChain.some((candidate) => hasErrorClassName(candidate, EdrProviderStackTraceGenerationError) || hasErrorClassName(candidate, SolidityTestStackTraceGenerationError) || isUnknownEdrError(candidate, context))) { return ErrorCategory.EDR_ERROR; } } /** * Classifies network-related failures by collapsing request setup, request * transport, response status, and telemetry transport errors into one bucket. */ function isNetworkInteractionError(context) { if (context.errorChain.some((candidate) => hasErrorClassName(candidate, ResponseStatusCodeError)) || context.errorChain.some((candidate) => hasErrorClassName(candidate, DispatcherError)) || context.errorChain.some((candidate) => hasErrorClassName(candidate, RequestError)) || (context.lowercaseMessageByError.get(context.error) ?? "").includes("fetch failed")) { return ErrorCategory.NETWORK_INTERACTION_ERROR; } } /** * Classifies runtime-environment incompatibilities by matching a small set of * capability-related error messages. */ function isRuntimeEnvironmentError(context) { if (Array.from(context.lowercaseMessageByError.values()).some((message) => includesAny(message, "toreversed is not a function", "flatmap is not a function", "crypto is not defined"))) { return ErrorCategory.RUNTIME_ENVIRONMENT_ERROR; } } /** * Classifies project-data and filesystem-related failures by matching a known * filesystem/project-data error type or a raw Node.js filesystem error code * anywhere in the cause chain. */ function isFilesystemInteractionError(context) { if (context.errorChain.some((candidate) => isKnownFilesystemOrProjectDataError(candidate) || isNodeFilesystemError(candidate))) { return ErrorCategory.FILESYSTEM_INTERACTION_ERROR; } } function isUnknownEdrError(error, context) { const errorIndex = context.errorChain.indexOf(error); assertHardhatInvariant(errorIndex !== -1, "isUnknownEdrError must be called with an error from the error chain"); return (ProviderError.isProviderError(error) && (error.code === UnknownError.CODE || error.name === "UnknownError") && context.errorChain .slice(errorIndex) .some((candidate) => (context.stackFramesByError.get(candidate) ?? []).some(isEdrFrame))); } // This list should be kept up to date with hardhat-utils/fs errors const HARDHAT_UTILS_FILESYSTEM_ERROR_CLASSES = [ PackageJsonReadError, PackageJsonNotFoundError, InvalidFileFormatError, FileNotFoundError, IsDirectoryError, NotADirectoryError, FileAlreadyExistsError, DirectoryNotEmptyError, ]; // This list should be kept up to date with hardhat-utils/subprocess errors const HARDHAT_UTILS_SUBPROCESS_ERROR_CLASSES = [ SubprocessFileNotFoundError, SubprocessPathIsDirectoryError, ]; const NODE_FILESYSTEM_ERROR_CODES = new Set([ "EACCES", "EAGAIN", "EBADF", "EBUSY", "EEXIST", "EFBIG", "EINTR", "EINVAL", "EIO", "EISDIR", "ELOOP", "EMFILE", "ENAMETOOLONG", "ENFILE", "ENODEV", "ENOENT", "ENOSPC", "ENOTDIR", "ENOTEMPTY", "ENOTSUP", "ENXIO", "EOPNOTSUPP", "EOVERFLOW", "EPERM", "EROFS", "ESPIPE", "ETXTBSY", "EXDEV", ]); /** * Returns `true` for any of the filesystem/project-data error classes the * classifier knows about. This is the gate for the FILESYSTEM_INTERACTION_ERROR * category. */ export function isKnownFilesystemOrProjectDataError(error) { return (isHardhatUtilsFilesystemError(error) || isSubprocessFilesystemError(error) || hasErrorClassName(error, FileSystemAccessError)); } /** * Returns `true` for filesystem errors that callers commonly expect to surface * during normal operation (e.g. missing files, format errors). Used by both * the classifier (as part of the filesystem-interaction gate) and the filter * (to drop these errors from reporting). */ export function isHardhatUtilsFilesystemError(error) { return HARDHAT_UTILS_FILESYSTEM_ERROR_CLASSES.some((cls) => hasErrorClassName(error, cls)); } /** * Returns `true` for filesystem errors raised by the subprocess-spawning * helpers. These are unexpected enough to be worth reporting on their own. */ export function isSubprocessFilesystemError(error) { return HARDHAT_UTILS_SUBPROCESS_ERROR_CLASSES.some((cls) => hasErrorClassName(error, cls)); } /** * Returns `true` for the node errors with fs related codes. */ function isNodeFilesystemError(error) { const code = getNodeErrorCode(error); return code !== undefined && NODE_FILESYSTEM_ERROR_CODES.has(code); } function getTypescriptSupportErrorCode(error) { if ("code" in error && typeof error.code === "string" && TYPESCRIPT_SUPPORT_ERROR_CODES.has(error.code)) { return error.code; } } //# sourceMappingURL=classifier.js.map