Skip to content

Commit 13b133b

Browse files
committed
add tests for config validation
1 parent 4c68ba3 commit 13b133b

File tree

4 files changed

+1545
-365
lines changed

4 files changed

+1545
-365
lines changed

v-next/core/src/internal/config-validation.ts

+112-112
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@ import type {
55
} from "../types/hooks.js";
66
import type { HardhatPlugin } from "../types/plugins.js";
77

8-
import {
9-
isOptionDefinition,
10-
isPositionalArgumentDefinition,
11-
isTaskDefinition,
12-
} from "../type-guards.js";
8+
import { isObject } from "@ignored/hardhat-vnext-utils/lang";
9+
1310
import {
1411
ArgumentType,
1512
type OptionDefinition,
@@ -23,6 +20,50 @@ import {
2320
type TaskOverrideDefinition,
2421
} from "../types/tasks.js";
2522

23+
function isValidEnumValue(
24+
theEnum: Record<string, string>,
25+
value: string,
26+
): boolean {
27+
// Enums are objects that have entries that map:
28+
// 1) keys to values
29+
// 2) values to keys
30+
const key = theEnum[value];
31+
if (key === undefined) {
32+
return false;
33+
}
34+
35+
return theEnum[key] === value;
36+
}
37+
38+
/**
39+
* Returns true if `potential` is a `TaskDefinition`.
40+
*/
41+
function isTaskDefinition(potential: unknown): potential is TaskDefinition {
42+
return (
43+
typeof potential === "object" &&
44+
potential !== null &&
45+
"type" in potential &&
46+
typeof potential.type === "string" &&
47+
isValidEnumValue(TaskDefinitionType, potential.type)
48+
);
49+
}
50+
51+
/**
52+
* Returns true if `potential` is a `PositionalArgumentDefinition`.
53+
*/
54+
function isPositionalArgumentDefinition(
55+
potential: unknown,
56+
): potential is PositionalArgumentDefinition {
57+
return (
58+
typeof potential === "object" &&
59+
potential !== null &&
60+
"type" in potential &&
61+
typeof potential.type === "string" &&
62+
isValidEnumValue(ArgumentType, potential.type) &&
63+
"isVariadic" in potential
64+
);
65+
}
66+
2667
export async function validateUserConfig(
2768
hooks: HookManager,
2869
config: HardhatUserConfig,
@@ -39,7 +80,7 @@ export async function validateUserConfig(
3980
return [...validationErrors, ...results.flat(1)];
4081
}
4182

42-
function collectValidationErrorsForUserConfig(
83+
export function collectValidationErrorsForUserConfig(
4384
config: HardhatUserConfig,
4485
): HardhatUserConfigValidationError[] {
4586
const validationErrors: HardhatUserConfigValidationError[] = [];
@@ -69,7 +110,7 @@ function collectValidationErrorsForUserConfig(
69110
return validationErrors;
70111
}
71112

72-
function validateTasksConfig(
113+
export function validateTasksConfig(
73114
tasks: TaskDefinition[],
74115
path: Array<string | number> = [],
75116
): HardhatUserConfigValidationError[] {
@@ -110,7 +151,7 @@ function validateTasksConfig(
110151
return validationErrors;
111152
}
112153

113-
function validateEmptyTask(
154+
export function validateEmptyTask(
114155
task: EmptyTaskDefinition,
115156
path: Array<string | number>,
116157
): HardhatUserConfigValidationError[] {
@@ -136,7 +177,7 @@ function validateEmptyTask(
136177
return validationErrors;
137178
}
138179

139-
function validateNewTask(
180+
export function validateNewTask(
140181
task: NewTaskDefinition,
141182
path: Array<string | number>,
142183
): HardhatUserConfigValidationError[] {
@@ -166,7 +207,7 @@ function validateNewTask(
166207
});
167208
}
168209

169-
if (typeof task.options === "object" && task.options !== null) {
210+
if (isObject(task.options)) {
170211
validationErrors.push(
171212
...validateOptions(task.options, [...path, "options"]),
172213
);
@@ -191,7 +232,7 @@ function validateNewTask(
191232
return validationErrors;
192233
}
193234

194-
function validateTaskOverride(
235+
export function validateTaskOverride(
195236
task: TaskOverrideDefinition,
196237
path: Array<string | number>,
197238
): HardhatUserConfigValidationError[] {
@@ -221,7 +262,7 @@ function validateTaskOverride(
221262
});
222263
}
223264

224-
if (typeof task.options === "object" && task.options !== null) {
265+
if (isObject(task.options)) {
225266
validationErrors.push(
226267
...validateOptions(task.options, [...path, "options"]),
227268
);
@@ -235,7 +276,7 @@ function validateTaskOverride(
235276
return validationErrors;
236277
}
237278

238-
function validateOptions(
279+
export function validateOptions(
239280
options: Record<string, OptionDefinition>,
240281
path: Array<string | number>,
241282
): HardhatUserConfigValidationError[] {
@@ -256,7 +297,7 @@ function validateOptions(
256297
});
257298
}
258299

259-
if (!isOptionDefinition(option)) {
300+
if (ArgumentType[option.type] === undefined) {
260301
validationErrors.push({
261302
path: [...path, name, "type"],
262303
message: "option type must be a valid ArgumentType",
@@ -270,7 +311,8 @@ function validateOptions(
270311
});
271312
} else {
272313
switch (option.type) {
273-
case ArgumentType.STRING: {
314+
case ArgumentType.STRING:
315+
case ArgumentType.FILE: {
274316
if (typeof option.defaultValue !== "string") {
275317
validationErrors.push({
276318
path: [...path, name, "defaultValue"],
@@ -288,7 +330,8 @@ function validateOptions(
288330
}
289331
break;
290332
}
291-
case ArgumentType.INT: {
333+
case ArgumentType.INT:
334+
case ArgumentType.FLOAT: {
292335
if (typeof option.defaultValue !== "number") {
293336
validationErrors.push({
294337
path: [...path, name, "defaultValue"],
@@ -306,32 +349,14 @@ function validateOptions(
306349
}
307350
break;
308351
}
309-
case ArgumentType.FLOAT: {
310-
if (typeof option.defaultValue !== "number") {
311-
validationErrors.push({
312-
path: [...path, name, "defaultValue"],
313-
message: "option defaultValue must be a number",
314-
});
315-
}
316-
break;
317-
}
318-
case ArgumentType.FILE: {
319-
if (typeof option.defaultValue !== "string") {
320-
validationErrors.push({
321-
path: [...path, name, "defaultValue"],
322-
message: "option defaultValue must be a string",
323-
});
324-
}
325-
break;
326-
}
327352
}
328353
}
329354
}
330355

331356
return validationErrors;
332357
}
333358

334-
function validatePositionalArguments(
359+
export function validatePositionalArguments(
335360
positionalArgs: PositionalArgumentDefinition[],
336361
path: Array<string | number>,
337362
): HardhatUserConfigValidationError[] {
@@ -361,95 +386,67 @@ function validatePositionalArguments(
361386

362387
if (arg.defaultValue !== undefined) {
363388
switch (arg.type) {
364-
case ArgumentType.STRING: {
389+
case ArgumentType.STRING:
390+
case ArgumentType.FILE: {
365391
if (
366-
typeof arg.defaultValue === "string" ||
367-
(Array.isArray(arg.defaultValue) &&
368-
arg.defaultValue.every((v) => typeof v === "string"))
392+
typeof arg.defaultValue !== "string" &&
393+
(!Array.isArray(arg.defaultValue) ||
394+
arg.defaultValue.some((v) => typeof v !== "string"))
369395
) {
370-
break;
396+
validationErrors.push({
397+
path: [...path, "positionalArguments", index, "defaultValue"],
398+
message:
399+
"positional argument defaultValue must be a string or an array of strings",
400+
});
371401
}
372402

373-
validationErrors.push({
374-
path: [...path, "positionalArguments", index, "defaultValue"],
375-
message:
376-
"positional argument defaultValue must be a string or an array of strings",
377-
});
403+
break;
378404
}
379405
case ArgumentType.BOOLEAN: {
380406
if (
381-
typeof arg.defaultValue === "boolean" ||
382-
(Array.isArray(arg.defaultValue) &&
383-
arg.defaultValue.every((v) => typeof v === "boolean"))
384-
) {
385-
break;
386-
}
387-
388-
validationErrors.push({
389-
path: [...path, "positionalArguments", index, "defaultValue"],
390-
message:
391-
"positional argument defaultValue must be a boolean or an array of booleans",
392-
});
393-
}
394-
case ArgumentType.INT: {
395-
if (
396-
typeof arg.defaultValue === "number" ||
397-
(Array.isArray(arg.defaultValue) &&
398-
arg.defaultValue.every((v) => typeof v === "number"))
399-
) {
400-
break;
401-
}
402-
403-
validationErrors.push({
404-
path: [...path, "positionalArguments", index, "defaultValue"],
405-
message:
406-
"positional argument defaultValue must be a number or an array of numbers",
407-
});
408-
}
409-
case ArgumentType.BIGINT: {
410-
if (
411-
typeof arg.defaultValue === "bigint" ||
412-
(Array.isArray(arg.defaultValue) &&
413-
arg.defaultValue.every((v) => typeof v === "bigint"))
407+
typeof arg.defaultValue !== "boolean" &&
408+
(!Array.isArray(arg.defaultValue) ||
409+
arg.defaultValue.some((v) => typeof v !== "boolean"))
414410
) {
415-
break;
411+
validationErrors.push({
412+
path: [...path, "positionalArguments", index, "defaultValue"],
413+
message:
414+
"positional argument defaultValue must be a boolean or an array of booleans",
415+
});
416416
}
417417

418-
validationErrors.push({
419-
path: [...path, "positionalArguments", index, "defaultValue"],
420-
message:
421-
"positional argument defaultValue must be a bigint or an array of bigints",
422-
});
418+
break;
423419
}
420+
case ArgumentType.INT:
424421
case ArgumentType.FLOAT: {
425422
if (
426-
typeof arg.defaultValue === "number" ||
427-
(Array.isArray(arg.defaultValue) &&
428-
arg.defaultValue.every((v) => typeof v === "number"))
423+
typeof arg.defaultValue !== "number" &&
424+
(!Array.isArray(arg.defaultValue) ||
425+
arg.defaultValue.some((v) => typeof v !== "number"))
429426
) {
430-
break;
427+
validationErrors.push({
428+
path: [...path, "positionalArguments", index, "defaultValue"],
429+
message:
430+
"positional argument defaultValue must be a number or an array of numbers",
431+
});
431432
}
432433

433-
validationErrors.push({
434-
path: [...path, "positionalArguments", index, "defaultValue"],
435-
message:
436-
"positional argument defaultValue must be a number or an array of numbers",
437-
});
434+
break;
438435
}
439-
case ArgumentType.FILE: {
436+
case ArgumentType.BIGINT: {
440437
if (
441-
typeof arg.defaultValue === "string" ||
442-
(Array.isArray(arg.defaultValue) &&
443-
arg.defaultValue.every((v) => typeof v === "string"))
438+
typeof arg.defaultValue !== "bigint" &&
439+
(!Array.isArray(arg.defaultValue) ||
440+
arg.defaultValue.some((v) => typeof v !== "bigint"))
444441
) {
445-
break;
442+
validationErrors.push({
443+
path: [...path, "positionalArguments", index, "defaultValue"],
444+
message:
445+
"positional argument defaultValue must be a bigint or an array of bigints",
446+
});
446447
}
447448

448-
validationErrors.push({
449-
path: [...path, "positionalArguments", index, "defaultValue"],
450-
message:
451-
"positional argument defaultValue must be a string or an array of strings",
452-
});
449+
break;
453450
}
454451
}
455452
}
@@ -470,7 +467,7 @@ function validatePositionalArguments(
470467
return validationErrors;
471468
}
472469

473-
function validatePluginsConfig(
470+
export function validatePluginsConfig(
474471
plugins: HardhatPlugin[],
475472
path: Array<string | number> = [],
476473
): HardhatUserConfigValidationError[] {
@@ -527,16 +524,19 @@ function validatePluginsConfig(
527524
plugin.hookHandlers !== null
528525
) {
529526
for (const [hookName, handler] of Object.entries(plugin.hookHandlers)) {
530-
if (typeof handler === "function" || typeof handler === "string") {
531-
continue;
527+
if (typeof handler !== "function" && typeof handler !== "string") {
528+
validationErrors.push({
529+
path: [...path, "plugins", index, "hookHandlers", hookName],
530+
message:
531+
"plugin hookHandlers must be an object of functions or strings",
532+
});
532533
}
533-
534-
validationErrors.push({
535-
path: [...path, "plugins", index, "hookHandlers", hookName],
536-
message:
537-
"plugin hookHandlers must be an object of functions or strings",
538-
});
539534
}
535+
} else {
536+
validationErrors.push({
537+
path: [...path, "plugins", index, "hookHandlers"],
538+
message: "plugin hookHandlers must be an object",
539+
});
540540
}
541541
}
542542

0 commit comments

Comments
 (0)