Skip to content

Commit 4c11d36

Browse files
authored
Merge pull request #5496 from NomicFoundation/use-error-assertions
Use `hardhat-test-utils`' functions
2 parents 3ca6732 + 250cd29 commit 4c11d36

File tree

17 files changed

+599
-555
lines changed

17 files changed

+599
-555
lines changed

config-v-next/eslint.cjs

+32-2
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ const path = require("path");
1010
* The only packages that should not use this config are our own eslint plugins/rules.
1111
*
1212
* @param {string} configFilePath The path to the config file that is using this function.
13-
* @param {{ onlyHardhatError?: boolean }} options An object with options to enable/disable certain rules.
13+
* @param {{ onlyHardhatError?: boolean, enforceHardhatTestUtils?: boolean }} options An object with options to enable/disable certain rules.
1414
* @returns {import("eslint").Linter.Config}
1515
*/
16-
function createConfig(configFilePath, options = { onlyHardhatError: true }) {
16+
function createConfig(
17+
configFilePath,
18+
options = { onlyHardhatError: true, enforceHardhatTestUtils: true }
19+
) {
1720
/**
1821
* @type {import("eslint").Linter.Config}
1922
*/
@@ -242,6 +245,18 @@ function createConfig(configFilePath, options = { onlyHardhatError: true }) {
242245
message:
243246
"Top-level await is only allowed in a few cases. Please discuss this change with the team.",
244247
},
248+
{
249+
selector:
250+
"CallExpression[callee.object.name='assert'][callee.property.name=doesNotThrow]",
251+
message:
252+
"Don't use assert.doesNotThrow. Just call the function directly, letting the error bubble up if thrown",
253+
},
254+
{
255+
selector:
256+
"CallExpression[callee.object.name='assert'][callee.property.name=doesNotReject]",
257+
message:
258+
"Don't use assert.doesNotReject. Just await the async-function-call/promise directly, letting the error bubble up if rejected",
259+
},
245260
],
246261
"@typescript-eslint/restrict-plus-operands": "error",
247262
"@typescript-eslint/restrict-template-expressions": [
@@ -413,6 +428,21 @@ function createConfig(configFilePath, options = { onlyHardhatError: true }) {
413428
});
414429
}
415430

431+
if (options.enforceHardhatTestUtils) {
432+
config.rules["no-restricted-syntax"].push(
433+
{
434+
selector:
435+
"CallExpression[callee.object.name='assert'][callee.property.name=throws]",
436+
message: "Don't use assert.throws. Use our test helpers instead.",
437+
},
438+
{
439+
selector:
440+
"CallExpression[callee.object.name='assert'][callee.property.name=rejects]",
441+
message: "Don't use assert.rejects. Use our test helpers instead.",
442+
}
443+
);
444+
}
445+
416446
return config;
417447
}
418448

pnpm-lock.yaml

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v-next/core/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"devDependencies": {
5656
"@eslint-community/eslint-plugin-eslint-comments": "^4.3.0",
5757
"@ignored/hardhat-vnext-node-test-reporter": "workspace:^3.0.0-next.2",
58+
"@nomicfoundation/hardhat-test-utils": "workspace:^",
5859
"@microsoft/api-extractor": "^7.43.4",
5960
"@types/node": "^20.14.9",
6061
"@types/semver": "^7.5.8",

v-next/core/test/internal/configuration-variables/configuration-variables.ts

+12-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
22
import { before, describe, it } from "node:test";
33

44
import { HardhatError } from "@ignored/hardhat-vnext-errors";
5+
import { assertRejectsWithHardhatError } from "@nomicfoundation/hardhat-test-utils";
56

67
import { ResolvedConfigurationVariableImplementation } from "../../../src/internal/configuration-variables.js";
78
import { HookManagerImplementation } from "../../../src/internal/hook-manager.js";
@@ -55,9 +56,10 @@ describe("ResolvedConfigurationVariable", () => {
5556
{ name: "foo", _type: "ConfigurationVariable" },
5657
);
5758

58-
await assert.rejects(
59+
await assertRejectsWithHardhatError(
5960
variable.get(),
60-
new HardhatError(HardhatError.ERRORS.GENERAL.ENV_VAR_NOT_FOUND),
61+
HardhatError.ERRORS.GENERAL.ENV_VAR_NOT_FOUND,
62+
{},
6163
);
6264
});
6365

@@ -99,11 +101,12 @@ describe("ResolvedConfigurationVariable", () => {
99101

100102
process.env.foo = "not a url";
101103

102-
await assert.rejects(
104+
await assertRejectsWithHardhatError(
103105
variable.getUrl(),
104-
new HardhatError(HardhatError.ERRORS.GENERAL.INVALID_URL, {
106+
HardhatError.ERRORS.GENERAL.INVALID_URL,
107+
{
105108
url: "not a url",
106-
}),
109+
},
107110
);
108111

109112
delete process.env.foo;
@@ -130,11 +133,12 @@ describe("ResolvedConfigurationVariable", () => {
130133

131134
process.env.foo = "not a bigint";
132135

133-
await assert.rejects(
136+
await assertRejectsWithHardhatError(
134137
variable.getBigInt(),
135-
new HardhatError(HardhatError.ERRORS.GENERAL.INVALID_BIGINT, {
138+
HardhatError.ERRORS.GENERAL.INVALID_BIGINT,
139+
{
136140
value: "not a bigint",
137-
}),
141+
},
138142
);
139143

140144
delete process.env.foo;

v-next/core/test/internal/global-options.ts

+37-30
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
22
import { after, before, describe, it } from "node:test";
33

44
import { HardhatError } from "@ignored/hardhat-vnext-errors";
5+
import { assertThrowsHardhatError } from "@nomicfoundation/hardhat-test-utils";
56

67
import { globalOption, ArgumentType } from "../../src/config.js";
78
import { RESERVED_ARGUMENT_NAMES } from "../../src/internal/arguments.js";
@@ -86,7 +87,7 @@ describe("Global Options", () => {
8687
defaultValue: false,
8788
});
8889

89-
assert.throws(
90+
assertThrowsHardhatError(
9091
() =>
9192
buildGlobalOptionDefinitions([
9293
{
@@ -98,19 +99,18 @@ describe("Global Options", () => {
9899
globalOptions: [globalOptionDefinition2],
99100
},
100101
]),
101-
new HardhatError(
102-
HardhatError.ERRORS.GENERAL.GLOBAL_OPTION_ALREADY_DEFINED,
103-
{
104-
plugin: "plugin2",
105-
globalOption: "globalOption1",
106-
definedByPlugin: "plugin1",
107-
},
108-
),
102+
103+
HardhatError.ERRORS.GENERAL.GLOBAL_OPTION_ALREADY_DEFINED,
104+
{
105+
plugin: "plugin2",
106+
globalOption: "globalOption1",
107+
definedByPlugin: "plugin1",
108+
},
109109
);
110110
});
111111

112112
it("should throw if an option name is not valid", () => {
113-
assert.throws(
113+
assertThrowsHardhatError(
114114
() =>
115115
buildGlobalOptionDefinitions([
116116
{
@@ -125,15 +125,16 @@ describe("Global Options", () => {
125125
],
126126
},
127127
]),
128-
new HardhatError(HardhatError.ERRORS.ARGUMENTS.INVALID_NAME, {
128+
HardhatError.ERRORS.ARGUMENTS.INVALID_NAME,
129+
{
129130
name: "foo bar",
130-
}),
131+
},
131132
);
132133
});
133134

134135
it("should throw if an option name is reserved", () => {
135136
RESERVED_ARGUMENT_NAMES.forEach((name) => {
136-
assert.throws(
137+
assertThrowsHardhatError(
137138
() =>
138139
buildGlobalOptionDefinitions([
139140
{
@@ -148,15 +149,16 @@ describe("Global Options", () => {
148149
],
149150
},
150151
]),
151-
new HardhatError(HardhatError.ERRORS.ARGUMENTS.RESERVED_NAME, {
152+
HardhatError.ERRORS.ARGUMENTS.RESERVED_NAME,
153+
{
152154
name,
153-
}),
155+
},
154156
);
155157
});
156158
});
157159

158160
it("should throw if an options default value does not match the type", () => {
159-
assert.throws(
161+
assertThrowsHardhatError(
160162
() =>
161163
buildGlobalOptionDefinitions([
162164
{
@@ -173,11 +175,12 @@ describe("Global Options", () => {
173175
],
174176
},
175177
]),
176-
new HardhatError(HardhatError.ERRORS.ARGUMENTS.INVALID_VALUE_FOR_TYPE, {
178+
HardhatError.ERRORS.ARGUMENTS.INVALID_VALUE_FOR_TYPE,
179+
{
177180
value: "bar",
178181
name: "defaultValue",
179182
type: ArgumentType.BOOLEAN,
180-
}),
183+
},
181184
);
182185
});
183186
});
@@ -210,37 +213,39 @@ describe("Global Options", () => {
210213
});
211214

212215
it("should throw if the option name is not valid", () => {
213-
assert.throws(
216+
assertThrowsHardhatError(
214217
() =>
215218
buildGlobalOptionDefinition({
216219
name: "foo bar",
217220
description: "Foo description",
218221
defaultValue: "bar",
219222
}),
220-
new HardhatError(HardhatError.ERRORS.ARGUMENTS.INVALID_NAME, {
223+
HardhatError.ERRORS.ARGUMENTS.INVALID_NAME,
224+
{
221225
name: "foo bar",
222-
}),
226+
},
223227
);
224228
});
225229

226230
it("should throw if the option name is reserved", () => {
227231
RESERVED_ARGUMENT_NAMES.forEach((name) => {
228-
assert.throws(
232+
assertThrowsHardhatError(
229233
() =>
230234
buildGlobalOptionDefinition({
231235
name,
232236
description: "Foo description",
233237
defaultValue: "bar",
234238
}),
235-
new HardhatError(HardhatError.ERRORS.ARGUMENTS.RESERVED_NAME, {
239+
HardhatError.ERRORS.ARGUMENTS.RESERVED_NAME,
240+
{
236241
name,
237-
}),
242+
},
238243
);
239244
});
240245
});
241246

242247
it("should throw if the default value does not match the type", () => {
243-
assert.throws(
248+
assertThrowsHardhatError(
244249
() =>
245250
buildGlobalOptionDefinition({
246251
name: "foo",
@@ -250,11 +255,12 @@ describe("Global Options", () => {
250255
Intentionally testing an invalid type */
251256
defaultValue: "bar" as any,
252257
}),
253-
new HardhatError(HardhatError.ERRORS.ARGUMENTS.INVALID_VALUE_FOR_TYPE, {
258+
HardhatError.ERRORS.ARGUMENTS.INVALID_VALUE_FOR_TYPE,
259+
{
254260
value: "bar",
255261
name: "defaultValue",
256262
type: ArgumentType.BOOLEAN,
257-
}),
263+
},
258264
);
259265
});
260266
});
@@ -416,13 +422,14 @@ describe("Global Options", () => {
416422

417423
setEnvVar("HARDHAT_GLOBAL_OPTION1", "not a boolean");
418424

419-
assert.throws(
425+
assertThrowsHardhatError(
420426
() => resolveGlobalOptions({}, globalOptionDefinitions),
421-
new HardhatError(HardhatError.ERRORS.ARGUMENTS.INVALID_VALUE_FOR_TYPE, {
427+
HardhatError.ERRORS.ARGUMENTS.INVALID_VALUE_FOR_TYPE,
428+
{
422429
value: "not a boolean",
423430
name: "globalOption1",
424431
type: ArgumentType.BOOLEAN,
425-
}),
432+
},
426433
);
427434
});
428435
});

v-next/core/test/internal/hook-manager.ts

+27-26
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import assert from "node:assert/strict";
2222
import { describe, it, beforeEach } from "node:test";
2323

2424
import { HardhatError } from "@ignored/hardhat-vnext-errors";
25+
import { ensureError } from "@ignored/hardhat-vnext-utils/error";
26+
import { assertRejectsWithHardhatError } from "@nomicfoundation/hardhat-test-utils";
2527

2628
import { HookManagerImplementation } from "../../src/internal/hook-manager.js";
2729
import { UserInterruptionManagerImplementation } from "../../src/internal/user-interruptions.js";
@@ -235,20 +237,23 @@ describe("HookManager", () => {
235237

236238
const manager = new HookManagerImplementation([examplePlugin]);
237239

238-
await assert.rejects(
239-
async () =>
240-
manager.runHandlerChain(
241-
"config",
242-
"extendUserConfig",
243-
[{}],
244-
async () => {
245-
return {};
246-
},
247-
),
248-
{
249-
code: "ERR_MODULE_NOT_FOUND",
250-
},
251-
);
240+
try {
241+
await manager.runHandlerChain(
242+
"config",
243+
"extendUserConfig",
244+
[{}],
245+
async () => {
246+
return {};
247+
},
248+
);
249+
} catch (error) {
250+
ensureError(error);
251+
assert.ok("code" in error, "Error has no code property");
252+
assert.equal(error.code, "ERR_MODULE_NOT_FOUND");
253+
return;
254+
}
255+
256+
assert.fail("Expected an error, but none was thrown");
252257
});
253258

254259
it("should throw if a non-filepath is given to the plugin being loaded", async () => {
@@ -261,7 +266,7 @@ describe("HookManager", () => {
261266

262267
const manager = new HookManagerImplementation([examplePlugin]);
263268

264-
await assert.rejects(
269+
await assertRejectsWithHardhatError(
265270
async () =>
266271
manager.runHandlerChain(
267272
"config",
@@ -271,14 +276,12 @@ describe("HookManager", () => {
271276
return {};
272277
},
273278
),
274-
new HardhatError(
275-
HardhatError.ERRORS.HOOKS.INVALID_HOOK_FACTORY_PATH,
276-
{
277-
hookCategoryName: "config",
278-
pluginId: "example",
279-
path: "./fixture-plugins/config-plugin.js",
280-
},
281-
),
279+
HardhatError.ERRORS.HOOKS.INVALID_HOOK_FACTORY_PATH,
280+
{
281+
hookCategoryName: "config",
282+
pluginId: "example",
283+
path: "./fixture-plugins/config-plugin.js",
284+
},
282285
);
283286
});
284287
});
@@ -903,9 +906,7 @@ describe("HookManager", () => {
903906
},
904907
};
905908

906-
assert.doesNotThrow(() =>
907-
hookManager.unregisterHandlers("config", exampleHook),
908-
);
909+
hookManager.unregisterHandlers("config", exampleHook);
909910
});
910911
});
911912
});

0 commit comments

Comments
 (0)