Skip to content

Commit

Permalink
Merge branch 'v-next' of github.com:NomicFoundation/hardhat into hard…
Browse files Browse the repository at this point in the history
…hat-chai-matchers
  • Loading branch information
ChristopherDedominici committed Dec 17, 2024
2 parents af84d41 + 2c3614d commit 39c01b8
Show file tree
Hide file tree
Showing 2 changed files with 212 additions and 1 deletion.
73 changes: 72 additions & 1 deletion v-next/hardhat/src/internal/core/config-validation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { HardhatUserConfig } from "../../types/config.js";
import type {
HardhatUserConfig,
ProjectPathsUserConfig,
} from "../../types/config.js";
import type {
HardhatUserConfigValidationError,
HookManager,
Expand Down Expand Up @@ -85,6 +88,17 @@ export function collectValidationErrorsForUserConfig(
): HardhatUserConfigValidationError[] {
const validationErrors: HardhatUserConfigValidationError[] = [];

if (config.paths !== undefined) {
if (isObject(config.paths)) {
validationErrors.push(...validatePaths(config.paths));
} else {
validationErrors.push({
path: ["paths"],
message: "paths must be an object",
});
}
}

if (config.tasks !== undefined) {
if (Array.isArray(config.tasks)) {
validationErrors.push(...validateTasksConfig(config.tasks));
Expand All @@ -110,6 +124,63 @@ export function collectValidationErrorsForUserConfig(
return validationErrors;
}

export function validatePaths(
paths: ProjectPathsUserConfig,
): HardhatUserConfigValidationError[] {
const validationErrors: HardhatUserConfigValidationError[] = [];

if (paths.cache !== undefined) {
validationErrors.push(...validatePath(paths.cache, "cache"));
}

if (paths.artifacts !== undefined) {
validationErrors.push(...validatePath(paths.artifacts, "artifacts"));
}

if (paths.tests !== undefined) {
// paths.tests of type TestPathsUserConfig is not validated because it is customizable by the user
if (!isObject(paths.tests)) {
validationErrors.push(...validatePath(paths.tests, "tests"));
}
}

if (paths.sources !== undefined) {
if (Array.isArray(paths.sources)) {
for (const [index, source] of paths.sources.entries()) {
validationErrors.push(...validatePath(source, "sources", index));
}
// paths.sources of type SourcePathsUserConfig is not validated because it is customizable by the user
} else if (!isObject(paths.sources)) {
validationErrors.push(...validatePath(paths.sources, "sources"));
}
}

return validationErrors;
}

function validatePath(
filePath: unknown,
pathName: "cache" | "artifacts" | "tests" | "sources",
index?: number,
): HardhatUserConfigValidationError[] {
const validationErrors: HardhatUserConfigValidationError[] = [];

if (typeof filePath !== "string") {
const messagePrefix =
index !== undefined
? `paths.${pathName} at index ${index}`
: `paths.${pathName}`;

validationErrors.push({
path:
index !== undefined ? ["paths", pathName, index] : ["paths", pathName],
message: `${messagePrefix} must be a string`,
});
}

return validationErrors;
}

export function validateTasksConfig(
tasks: TaskDefinition[],
path: Array<string | number> = [],
Expand Down
140 changes: 140 additions & 0 deletions v-next/hardhat/test/internal/core/config-validation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { HardhatUserConfig } from "../../../src/config.js";
import type { ProjectPathsUserConfig } from "../../../src/types/config.js";
import type { HardhatPlugin } from "../../../src/types/plugins.js";
import type {
EmptyTaskDefinition,
Expand All @@ -19,6 +20,7 @@ import {
validateTasksConfig,
validatePluginsConfig,
collectValidationErrorsForUserConfig,
validatePaths,
} from "../../../src/internal/core/config-validation.js";
import {
type PositionalArgumentDefinition,
Expand Down Expand Up @@ -1217,6 +1219,127 @@ describe("config validation", function () {
});
});

describe("validatePaths", function () {
describe("when the paths are valid", function () {
it("should work when all the paths are strings", async function () {
const paths: Required<ProjectPathsUserConfig> = {
cache: "./cache",
artifacts: "./artifacts",
tests: "./tests",
sources: "./sources",
};

const validationErrors = validatePaths(paths);

assert.equal(validationErrors.length, 0);
});

it("should work when the sources property is an array", async function () {
const paths: ProjectPathsUserConfig = {
sources: ["./sources", "./sources2"],
};

const validationErrors = validatePaths(paths);

assert.equal(validationErrors.length, 0);
});

it("should work when the tests and sources properties are both objects", async function () {
// Objects are not validated because they are customizable by the user
const paths: ProjectPathsUserConfig = {
tests: { randomProperty: "randomValue" },
sources: {},
};

const validationErrors = validatePaths(paths);

assert.equal(validationErrors.length, 0);
});
});

describe("when the paths are not valid", function () {
it("should return an error when the cache path is not a string", async function () {
const paths: ProjectPathsUserConfig = {
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing validations for js users who can bypass type checks */
cache: 123 as any,
};

const validationErrors = validatePaths(paths);

assert.equal(validationErrors.length, 1);
assert.deepEqual(validationErrors[0].path, ["paths", "cache"]);
assert.equal(
validationErrors[0].message,
"paths.cache must be a string",
);
});

it("should return an error when the artifacts path is not a string", async function () {
const paths: ProjectPathsUserConfig = {
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing validations for js users who can bypass type checks */
artifacts: 123 as any,
};

const validationErrors = validatePaths(paths);

assert.equal(validationErrors.length, 1);
assert.deepEqual(validationErrors[0].path, ["paths", "artifacts"]);
assert.equal(
validationErrors[0].message,
"paths.artifacts must be a string",
);
});

it("should return an error when the tests path is not a string", async function () {
const paths: ProjectPathsUserConfig = {
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing validations for js users who can bypass type checks */
tests: 123 as any,
};

const validationErrors = validatePaths(paths);

assert.equal(validationErrors.length, 1);
assert.deepEqual(validationErrors[0].path, ["paths", "tests"]);
assert.equal(
validationErrors[0].message,
"paths.tests must be a string",
);
});

it("should return an error when the sources path is not a string", async function () {
const paths: ProjectPathsUserConfig = {
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing validations for js users who can bypass type checks */
sources: 123 as any,
};

const validationErrors = validatePaths(paths);

assert.equal(validationErrors.length, 1);
assert.deepEqual(validationErrors[0].path, ["paths", "sources"]);
assert.equal(
validationErrors[0].message,
"paths.sources must be a string",
);
});

it("should return an error when one of the source paths in the array are not strings", async function () {
const paths: ProjectPathsUserConfig = {
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing validations for js users who can bypass type checks */
sources: ["./sources", 123 as any],
};

const validationErrors = validatePaths(paths);

assert.equal(validationErrors.length, 1);
assert.deepEqual(validationErrors[0].path, ["paths", "sources", 1]);
assert.equal(
validationErrors[0].message,
"paths.sources at index 1 must be a string",
);
});
});
});

describe("validateTasksConfig", function () {
it("should return an error if a task is not a valid task definition", function () {
const tasks: TaskDefinition[] = [
Expand Down Expand Up @@ -1393,13 +1516,30 @@ describe("config validation", function () {
describe("collectValidationErrorsForUserConfig", function () {
it("should return an empty array if the config is valid", function () {
const config = {
paths: {},
tasks: [],
plugins: [],
};

assert.deepEqual(collectValidationErrorsForUserConfig(config), []);
});

it("should return an error if the paths are not an object", function () {
const config: HardhatUserConfig = {
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing validations for js users who can bypass type checks */
paths: 1 as any,
tasks: [],
plugins: [],
};

assert.deepEqual(collectValidationErrorsForUserConfig(config), [
{
message: "paths must be an object",
path: ["paths"],
},
]);
});

it("should return an error if the tasks is not an array", function () {
const config: HardhatUserConfig = {
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- testing validations for js users who can bypass type checks */
Expand Down

0 comments on commit 39c01b8

Please sign in to comment.