Skip to content

Commit 76b414e

Browse files
committed
Validate plain task object on the task manager
1 parent 051755d commit 76b414e

File tree

6 files changed

+848
-206
lines changed

6 files changed

+848
-206
lines changed

v-next/core/src/internal/tasks/builders.ts

+38-159
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ import { HardhatError } from "@ignored/hardhat-vnext-errors";
1818

1919
import { ArgumentType } from "../../types/arguments.js";
2020
import { TaskDefinitionType } from "../../types/tasks.js";
21-
import {
22-
RESERVED_ARGUMENT_NAMES,
23-
isArgumentValueValid,
24-
isArgumentNameValid,
25-
} from "../arguments.js";
2621

27-
import { formatTaskId, isValidActionUrl } from "./utils.js";
22+
import { formatTaskId } from "./utils.js";
23+
import {
24+
validateAction,
25+
validateId,
26+
validateOption,
27+
validatePositionalArgument,
28+
} from "./validations.js";
2829

2930
export class EmptyTaskDefinitionBuilderImplementation
3031
implements EmptyTaskDefinitionBuilder
@@ -34,11 +35,7 @@ export class EmptyTaskDefinitionBuilderImplementation
3435
#description: string;
3536

3637
constructor(id: string | string[], description: string = "") {
37-
if (id.length === 0) {
38-
throw new HardhatError(
39-
HardhatError.ERRORS.TASK_DEFINITIONS.EMPTY_TASK_ID,
40-
);
41-
}
38+
validateId(id);
4239

4340
this.#id = Array.isArray(id) ? id : [id];
4441
this.#description = description;
@@ -72,11 +69,7 @@ export class NewTaskDefinitionBuilderImplementation
7269
#action?: NewTaskActionFunction | string;
7370

7471
constructor(id: string | string[], description: string = "") {
75-
if (id.length === 0) {
76-
throw new HardhatError(
77-
HardhatError.ERRORS.TASK_DEFINITIONS.EMPTY_TASK_ID,
78-
);
79-
}
72+
validateId(id);
8073

8174
this.#id = Array.isArray(id) ? id : [id];
8275
this.#description = description;
@@ -88,14 +81,7 @@ export class NewTaskDefinitionBuilderImplementation
8881
}
8982

9083
public setAction(action: NewTaskActionFunction | string): this {
91-
if (typeof action === "string" && !isValidActionUrl(action)) {
92-
throw new HardhatError(
93-
HardhatError.ERRORS.TASK_DEFINITIONS.INVALID_FILE_ACTION,
94-
{
95-
action,
96-
},
97-
);
98-
}
84+
validateAction(action);
9985

10086
this.#action = action;
10187

@@ -115,45 +101,17 @@ export class NewTaskDefinitionBuilderImplementation
115101
}): this {
116102
const argumentType = type ?? ArgumentType.STRING;
117103

118-
if (!isArgumentNameValid(name)) {
119-
throw new HardhatError(HardhatError.ERRORS.ARGUMENTS.INVALID_NAME, {
120-
name,
121-
});
122-
}
123-
124-
if (this.#usedNames.has(name)) {
125-
throw new HardhatError(HardhatError.ERRORS.ARGUMENTS.DUPLICATED_NAME, {
126-
name,
127-
});
128-
}
129-
130-
if (RESERVED_ARGUMENT_NAMES.has(name)) {
131-
throw new HardhatError(HardhatError.ERRORS.ARGUMENTS.RESERVED_NAME, {
132-
name,
133-
});
134-
}
135-
136-
if (!isArgumentValueValid(argumentType, defaultValue)) {
137-
throw new HardhatError(
138-
HardhatError.ERRORS.TASK_DEFINITIONS.INVALID_VALUE_FOR_TYPE,
139-
{
140-
value: defaultValue,
141-
name: "defaultValue",
142-
type: argumentType,
143-
task: formatTaskId(this.#id),
144-
},
145-
);
146-
}
147-
148-
this.#usedNames.add(name);
149-
150-
this.#options[name] = {
104+
const optionDefinition = {
151105
name,
152106
description,
153107
type: argumentType,
154108
defaultValue,
155109
};
156110

111+
validateOption(optionDefinition, this.#usedNames, this.#id);
112+
113+
this.#options[name] = optionDefinition;
114+
157115
return this;
158116
}
159117

@@ -223,69 +181,23 @@ export class NewTaskDefinitionBuilderImplementation
223181
}): this {
224182
const argumentType = type ?? ArgumentType.STRING;
225183

226-
if (!isArgumentNameValid(name)) {
227-
throw new HardhatError(HardhatError.ERRORS.ARGUMENTS.INVALID_NAME, {
228-
name,
229-
});
230-
}
231-
232-
if (this.#usedNames.has(name)) {
233-
throw new HardhatError(HardhatError.ERRORS.ARGUMENTS.DUPLICATED_NAME, {
234-
name,
235-
});
236-
}
237-
238-
if (RESERVED_ARGUMENT_NAMES.has(name)) {
239-
throw new HardhatError(HardhatError.ERRORS.ARGUMENTS.RESERVED_NAME, {
240-
name,
241-
});
242-
}
243-
244-
if (defaultValue !== undefined) {
245-
if (!isArgumentValueValid(argumentType, defaultValue, isVariadic)) {
246-
throw new HardhatError(
247-
HardhatError.ERRORS.TASK_DEFINITIONS.INVALID_VALUE_FOR_TYPE,
248-
{
249-
value: defaultValue,
250-
name: "defaultValue",
251-
type: argumentType,
252-
task: formatTaskId(this.#id),
253-
},
254-
);
255-
}
256-
}
257-
258-
if (this.#positionalArgs.length > 0) {
259-
const lastArg = this.#positionalArgs[this.#positionalArgs.length - 1];
260-
261-
if (lastArg.isVariadic) {
262-
throw new HardhatError(
263-
HardhatError.ERRORS.TASK_DEFINITIONS.POSITIONAL_ARG_AFTER_VARIADIC,
264-
{
265-
name,
266-
},
267-
);
268-
}
269-
270-
if (lastArg.defaultValue !== undefined && defaultValue === undefined) {
271-
throw new HardhatError(
272-
HardhatError.ERRORS.TASK_DEFINITIONS.REQUIRED_ARG_AFTER_OPTIONAL,
273-
{
274-
name,
275-
},
276-
);
277-
}
278-
}
279-
280-
this.#usedNames.add(name);
281-
282-
this.#positionalArgs.push({
184+
const positionalArgDef = {
283185
name,
284186
description,
285187
type: argumentType,
286188
defaultValue,
287189
isVariadic,
288-
});
190+
};
191+
192+
const lastArg = this.#positionalArgs.at(-1);
193+
validatePositionalArgument(
194+
positionalArgDef,
195+
this.#usedNames,
196+
this.#id,
197+
lastArg,
198+
);
199+
200+
this.#positionalArgs.push(positionalArgDef);
289201

290202
return this;
291203
}
@@ -303,11 +215,7 @@ export class TaskOverrideDefinitionBuilderImplementation
303215
#action?: TaskOverrideActionFunction | string;
304216

305217
constructor(id: string | string[]) {
306-
if (id.length === 0) {
307-
throw new HardhatError(
308-
HardhatError.ERRORS.TASK_DEFINITIONS.EMPTY_TASK_ID,
309-
);
310-
}
218+
validateId(id);
311219

312220
this.#id = Array.isArray(id) ? id : [id];
313221
}
@@ -318,14 +226,7 @@ export class TaskOverrideDefinitionBuilderImplementation
318226
}
319227

320228
public setAction(action: TaskOverrideActionFunction | string): this {
321-
if (typeof action === "string" && !isValidActionUrl(action)) {
322-
throw new HardhatError(
323-
HardhatError.ERRORS.TASK_DEFINITIONS.INVALID_FILE_ACTION,
324-
{
325-
action,
326-
},
327-
);
328-
}
229+
validateAction(action);
329230

330231
this.#action = action;
331232

@@ -345,43 +246,21 @@ export class TaskOverrideDefinitionBuilderImplementation
345246
}): this {
346247
const argumentType = type ?? ArgumentType.STRING;
347248

348-
if (!isArgumentNameValid(name)) {
349-
throw new HardhatError(HardhatError.ERRORS.ARGUMENTS.INVALID_NAME, {
350-
name,
351-
});
352-
}
353-
354-
if (name in this.#options) {
355-
throw new HardhatError(HardhatError.ERRORS.ARGUMENTS.DUPLICATED_NAME, {
356-
name,
357-
});
358-
}
359-
360-
if (RESERVED_ARGUMENT_NAMES.has(name)) {
361-
throw new HardhatError(HardhatError.ERRORS.ARGUMENTS.RESERVED_NAME, {
362-
name,
363-
});
364-
}
365-
366-
if (!isArgumentValueValid(argumentType, defaultValue)) {
367-
throw new HardhatError(
368-
HardhatError.ERRORS.TASK_DEFINITIONS.INVALID_VALUE_FOR_TYPE,
369-
{
370-
value: defaultValue,
371-
name: "defaultValue",
372-
type: argumentType,
373-
task: formatTaskId(this.#id),
374-
},
375-
);
376-
}
377-
378-
this.#options[name] = {
249+
const optionDefinition = {
379250
name,
380251
description,
381252
type: argumentType,
382253
defaultValue,
383254
};
384255

256+
validateOption(
257+
optionDefinition,
258+
new Set(Object.keys(this.#options)),
259+
this.#id,
260+
);
261+
262+
this.#options[name] = optionDefinition;
263+
385264
return this;
386265
}
387266

v-next/core/src/internal/tasks/task-manager.ts

+42
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { PositionalArgumentDefinition } from "../../types/arguments.js";
12
import type { GlobalOptionDefinitions } from "../../types/global-options.js";
23
import type { HardhatRuntimeEnvironment } from "../../types/hre.js";
34
import type {
@@ -17,6 +18,12 @@ import { TaskDefinitionType } from "../../types/tasks.js";
1718

1819
import { ResolvedTask } from "./resolved-task.js";
1920
import { formatTaskId, getActorFragment } from "./utils.js";
21+
import {
22+
validateAction,
23+
validateId,
24+
validateOption,
25+
validatePositionalArgument,
26+
} from "./validations.js";
2027

2128
export class TaskManagerImplementation implements TaskManager {
2229
readonly #hre: HardhatRuntimeEnvironment;
@@ -35,6 +42,7 @@ export class TaskManagerImplementation implements TaskManager {
3542
}
3643

3744
for (const taskDefinition of plugin.tasks) {
45+
this.#validateTaskDefinition(taskDefinition);
3846
this.#reduceTaskDefinition(
3947
globalOptionDefinitions,
4048
taskDefinition,
@@ -45,6 +53,7 @@ export class TaskManagerImplementation implements TaskManager {
4553

4654
// reduce global user defined tasks
4755
for (const taskDefinition of this.#hre.config.tasks) {
56+
this.#validateTaskDefinition(taskDefinition);
4857
this.#reduceTaskDefinition(globalOptionDefinitions, taskDefinition);
4958
}
5059
}
@@ -243,4 +252,37 @@ export class TaskManagerImplementation implements TaskManager {
243252

244253
task.actions.push({ pluginId, action: taskDefinition.action });
245254
}
255+
256+
#validateTaskDefinition(taskDefinition: TaskDefinition): void {
257+
validateId(taskDefinition.id);
258+
259+
// Empty tasks don't have actions, options, or positional arguments
260+
if (taskDefinition.type === TaskDefinitionType.EMPTY_TASK) {
261+
return;
262+
}
263+
264+
const usedNames = new Set<string>();
265+
266+
validateAction(taskDefinition.action);
267+
268+
Object.values(taskDefinition.options).forEach((optionDefinition) =>
269+
validateOption(optionDefinition, usedNames, taskDefinition.id),
270+
);
271+
272+
// Override tasks don't have positional arguments
273+
if (taskDefinition.type === TaskDefinitionType.TASK_OVERRIDE) {
274+
return;
275+
}
276+
277+
let lastArg: PositionalArgumentDefinition;
278+
taskDefinition.positionalArguments.forEach((posArgDefinition) => {
279+
validatePositionalArgument(
280+
posArgDefinition,
281+
usedNames,
282+
taskDefinition.id,
283+
lastArg,
284+
);
285+
lastArg = posArgDefinition;
286+
});
287+
}
246288
}

v-next/core/src/internal/tasks/utils.ts

-6
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,3 @@ export function formatTaskId(taskId: string | string[]): string {
99
export function getActorFragment(pluginId: string | undefined): string {
1010
return pluginId !== undefined ? `Plugin ${pluginId} is` : "You are";
1111
}
12-
13-
const FILE_PROTOCOL_PATTERN = /^file:\/\/.+/;
14-
15-
export function isValidActionUrl(action: string): boolean {
16-
return FILE_PROTOCOL_PATTERN.test(action);
17-
}

0 commit comments

Comments
 (0)