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
JavaScript
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