hardhat 2.17.4 → 2.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/builtin-tasks/help.js +36 -5
  3. package/builtin-tasks/help.js.map +1 -1
  4. package/internal/cli/ArgumentsParser.d.ts +7 -2
  5. package/internal/cli/ArgumentsParser.d.ts.map +1 -1
  6. package/internal/cli/ArgumentsParser.js +72 -11
  7. package/internal/cli/ArgumentsParser.js.map +1 -1
  8. package/internal/cli/HelpPrinter.d.ts +7 -3
  9. package/internal/cli/HelpPrinter.d.ts.map +1 -1
  10. package/internal/cli/HelpPrinter.js +53 -22
  11. package/internal/cli/HelpPrinter.js.map +1 -1
  12. package/internal/cli/cli.js +29 -15
  13. package/internal/cli/cli.js.map +1 -1
  14. package/internal/core/config/config-env.d.ts +2 -1
  15. package/internal/core/config/config-env.d.ts.map +1 -1
  16. package/internal/core/config/config-env.js +7 -1
  17. package/internal/core/config/config-env.js.map +1 -1
  18. package/internal/core/errors-list.d.ts +28 -0
  19. package/internal/core/errors-list.d.ts.map +1 -1
  20. package/internal/core/errors-list.js +36 -2
  21. package/internal/core/errors-list.js.map +1 -1
  22. package/internal/core/runtime-environment.d.ts +5 -3
  23. package/internal/core/runtime-environment.d.ts.map +1 -1
  24. package/internal/core/runtime-environment.js +30 -7
  25. package/internal/core/runtime-environment.js.map +1 -1
  26. package/internal/core/tasks/dsl.d.ts +10 -1
  27. package/internal/core/tasks/dsl.d.ts.map +1 -1
  28. package/internal/core/tasks/dsl.js +64 -4
  29. package/internal/core/tasks/dsl.js.map +1 -1
  30. package/internal/core/tasks/task-definitions.d.ts +31 -4
  31. package/internal/core/tasks/task-definitions.d.ts.map +1 -1
  32. package/internal/core/tasks/task-definitions.js +51 -5
  33. package/internal/core/tasks/task-definitions.js.map +1 -1
  34. package/internal/core/tasks/util.d.ts +6 -0
  35. package/internal/core/tasks/util.d.ts.map +1 -0
  36. package/internal/core/tasks/util.js +19 -0
  37. package/internal/core/tasks/util.js.map +1 -0
  38. package/internal/lib/hardhat-lib.d.ts.map +1 -1
  39. package/internal/lib/hardhat-lib.js +1 -1
  40. package/internal/lib/hardhat-lib.js.map +1 -1
  41. package/internal/sentry/anonymizer.d.ts.map +1 -1
  42. package/internal/sentry/anonymizer.js +3 -1
  43. package/internal/sentry/anonymizer.js.map +1 -1
  44. package/package.json +1 -1
  45. package/register.js +1 -1
  46. package/register.js.map +1 -1
  47. package/src/builtin-tasks/help.ts +47 -5
  48. package/src/internal/cli/ArgumentsParser.ts +79 -13
  49. package/src/internal/cli/HelpPrinter.ts +89 -24
  50. package/src/internal/cli/cli.ts +43 -25
  51. package/src/internal/core/config/config-env.ts +11 -0
  52. package/src/internal/core/errors-list.ts +38 -2
  53. package/src/internal/core/runtime-environment.ts +32 -7
  54. package/src/internal/core/tasks/dsl.ts +99 -4
  55. package/src/internal/core/tasks/task-definitions.ts +97 -3
  56. package/src/internal/core/tasks/util.ts +18 -0
  57. package/src/internal/lib/hardhat-lib.ts +1 -0
  58. package/src/internal/sentry/anonymizer.ts +3 -1
  59. package/src/register.ts +1 -0
  60. package/src/types/runtime.ts +35 -1
  61. package/types/runtime.d.ts +24 -1
  62. package/types/runtime.d.ts.map +1 -1
@@ -2,6 +2,9 @@ import {
2
2
  HardhatParamDefinitions,
3
3
  ParamDefinition,
4
4
  ParamDefinitionsMap,
5
+ ScopeDefinition,
6
+ ScopesMap,
7
+ TaskDefinition,
5
8
  TasksMap,
6
9
  } from "../../types";
7
10
  import { HardhatError } from "../core/errors";
@@ -15,55 +18,69 @@ export class HelpPrinter {
15
18
  private readonly _executableName: string,
16
19
  private readonly _version: string,
17
20
  private readonly _hardhatParamDefinitions: HardhatParamDefinitions,
18
- private readonly _tasks: TasksMap
21
+ private readonly _tasks: TasksMap,
22
+ private readonly _scopes: ScopesMap
19
23
  ) {}
20
24
 
21
25
  public printGlobalHelp(includeSubtasks = false) {
22
26
  console.log(`${this._programName} version ${this._version}\n`);
23
27
 
24
28
  console.log(
25
- `Usage: ${this._executableName} [GLOBAL OPTIONS] <TASK> [TASK OPTIONS]\n`
29
+ `Usage: ${this._executableName} [GLOBAL OPTIONS] [SCOPE] <TASK> [TASK OPTIONS]\n`
26
30
  );
27
31
 
28
32
  console.log("GLOBAL OPTIONS:\n");
29
33
 
30
- this._printParamDetails(this._hardhatParamDefinitions);
34
+ let length = this._printParamDetails(this._hardhatParamDefinitions);
31
35
 
32
36
  console.log("\n\nAVAILABLE TASKS:\n");
33
37
 
34
- const tasksToShow: TasksMap = {};
35
- for (const [taskName, taskDefinition] of Object.entries(this._tasks)) {
36
- if (includeSubtasks || !taskDefinition.isSubtask) {
37
- tasksToShow[taskName] = taskDefinition;
38
- }
39
- }
40
-
41
- const nameLength = Object.keys(tasksToShow)
42
- .map((n) => n.length)
43
- .reduce((a, b) => Math.max(a, b), 0);
38
+ length = this._printTasks(this._tasks, includeSubtasks, length);
44
39
 
45
- for (const name of Object.keys(tasksToShow).sort()) {
46
- const { description = "" } = this._tasks[name];
40
+ if (Object.keys(this._scopes).length > 0) {
41
+ console.log("\n\nAVAILABLE TASK SCOPES:\n");
47
42
 
48
- console.log(` ${name.padEnd(nameLength)}\t${description}`);
43
+ this._printScopes(this._scopes, length);
49
44
  }
50
45
 
51
46
  console.log("");
52
47
 
53
48
  console.log(
54
- `To get help for a specific task run: npx ${this._executableName} help [task]\n`
49
+ `To get help for a specific task run: npx ${this._executableName} help [SCOPE] <TASK>\n`
55
50
  );
56
51
  }
57
52
 
58
- public printTaskHelp(taskName: string) {
59
- const taskDefinition = this._tasks[taskName];
53
+ public printScopeHelp(
54
+ scopeDefinition: ScopeDefinition,
55
+ includeSubtasks = false
56
+ ) {
57
+ const name = scopeDefinition.name;
58
+ const description = scopeDefinition.description ?? "";
59
+
60
+ console.log(`${this._programName} version ${this._version}`);
60
61
 
61
- if (taskDefinition === undefined) {
62
- throw new HardhatError(ERRORS.ARGUMENTS.UNRECOGNIZED_TASK, {
63
- task: taskName,
62
+ console.log(
63
+ `\nUsage: hardhat [GLOBAL OPTIONS] ${name} <TASK> [TASK OPTIONS]`
64
+ );
65
+
66
+ console.log(`\nAVAILABLE TASKS:\n`);
67
+
68
+ if (this._scopes[name] === undefined) {
69
+ throw new HardhatError(ERRORS.ARGUMENTS.UNRECOGNIZED_SCOPE, {
70
+ scope: name,
64
71
  });
65
72
  }
66
73
 
74
+ this._printTasks(this._scopes[name].tasks, includeSubtasks);
75
+
76
+ console.log(`\n${name}: ${description}`);
77
+
78
+ console.log(
79
+ `\nFor global options help run: ${this._executableName} help\n`
80
+ );
81
+ }
82
+
83
+ public printTaskHelp(taskDefinition: TaskDefinition) {
67
84
  const {
68
85
  description = "",
69
86
  name,
@@ -78,8 +95,11 @@ export class HelpPrinter {
78
95
  positionalParamDefinitions
79
96
  );
80
97
 
98
+ const scope =
99
+ taskDefinition.scope !== undefined ? `${taskDefinition.scope} ` : "";
100
+
81
101
  console.log(
82
- `Usage: ${this._executableName} [GLOBAL OPTIONS] ${name}${paramsList}${positionalParamsList}\n`
102
+ `Usage: ${this._executableName} [GLOBAL OPTIONS] ${scope}${name}${paramsList}${positionalParamsList}\n`
83
103
  );
84
104
 
85
105
  if (Object.keys(paramDefinitions).length > 0) {
@@ -103,6 +123,49 @@ export class HelpPrinter {
103
123
  console.log(`For global options help run: ${this._executableName} help\n`);
104
124
  }
105
125
 
126
+ private _printTasks(
127
+ tasksMap: TasksMap,
128
+ includeSubtasks: boolean,
129
+ length: number = 0
130
+ ) {
131
+ const taskNameList = Object.entries(tasksMap)
132
+ .filter(
133
+ ([, taskDefinition]) => includeSubtasks || !taskDefinition.isSubtask
134
+ )
135
+ .map(([taskName]) => taskName)
136
+ .sort();
137
+
138
+ const nameLength = taskNameList
139
+ .map((n) => n.length)
140
+ .reduce((a, b) => Math.max(a, b), length);
141
+
142
+ for (const name of taskNameList) {
143
+ const { description = "" } = tasksMap[name];
144
+
145
+ console.log(` ${name.padEnd(nameLength)}\t${description}`);
146
+ }
147
+
148
+ return nameLength;
149
+ }
150
+
151
+ private _printScopes(scopesMap: ScopesMap, length: number) {
152
+ const scopeNamesList = Object.entries(scopesMap)
153
+ .map(([scopeName]) => scopeName)
154
+ .sort();
155
+
156
+ const nameLength = scopeNamesList
157
+ .map((n) => n.length)
158
+ .reduce((a, b) => Math.max(a, b), length);
159
+
160
+ for (const name of scopeNamesList) {
161
+ const { description = "" } = scopesMap[name];
162
+
163
+ console.log(` ${name.padEnd(nameLength)}\t${description}`);
164
+ }
165
+
166
+ return nameLength;
167
+ }
168
+
106
169
  private _getParamValueDescription<T>(paramDefinition: ParamDefinition<T>) {
107
170
  return `<${paramDefinition.type.name.toUpperCase()}>`;
108
171
  }
@@ -162,7 +225,7 @@ export class HelpPrinter {
162
225
  return paramsList;
163
226
  }
164
227
 
165
- private _printParamDetails(paramDefinitions: ParamDefinitionsMap) {
228
+ private _printParamDetails(paramDefinitions: ParamDefinitionsMap): number {
166
229
  const paramsNameLength = Object.keys(paramDefinitions)
167
230
  .map((n) => ArgumentsParser.paramNameToCLA(n).length)
168
231
  .reduce((a, b) => Math.max(a, b), 0);
@@ -185,6 +248,8 @@ export class HelpPrinter {
185
248
 
186
249
  console.log(msg);
187
250
  }
251
+
252
+ return paramsNameLength;
188
253
  }
189
254
 
190
255
  private _printPositionalParamDetails(
@@ -127,15 +127,12 @@ async function main() {
127
127
 
128
128
  const argumentsParser = new ArgumentsParser();
129
129
 
130
- const {
131
- hardhatArguments,
132
- taskName: parsedTaskName,
133
- unparsedCLAs,
134
- } = argumentsParser.parseHardhatArguments(
135
- HARDHAT_PARAM_DEFINITIONS,
136
- envVariableArguments,
137
- process.argv.slice(2)
138
- );
130
+ const { hardhatArguments, scopeOrTaskName, allUnparsedCLAs } =
131
+ argumentsParser.parseHardhatArguments(
132
+ HARDHAT_PARAM_DEFINITIONS,
133
+ envVariableArguments,
134
+ process.argv.slice(2)
135
+ );
139
136
 
140
137
  if (hardhatArguments.verbose) {
141
138
  Reporter.setVerbose(true);
@@ -160,13 +157,13 @@ async function main() {
160
157
  // The code marked with the tag #INIT-DEP can be deleted after HarhatV3 is out.
161
158
 
162
159
  // Create a new Hardhat project
163
- if (parsedTaskName === "init") {
160
+ if (scopeOrTaskName === "init") {
164
161
  return await createNewProject();
165
162
  }
166
163
  // #INIT-DEP - START OF DEPRECATED CODE
167
164
  else {
168
165
  if (
169
- parsedTaskName === undefined &&
166
+ scopeOrTaskName === undefined &&
170
167
  hardhatArguments.config === undefined &&
171
168
  !isCwdInsideProject()
172
169
  ) {
@@ -213,21 +210,29 @@ async function main() {
213
210
  }
214
211
  }
215
212
 
216
- let taskName = parsedTaskName ?? TASK_HELP;
217
-
218
- const showEmptyConfigWarning = true;
219
- const showSolidityConfigWarnings = taskName === TASK_COMPILE;
220
-
221
213
  const ctx = HardhatContext.createHardhatContext();
222
214
 
223
215
  const { resolvedConfig, userConfig } = loadConfigAndTasks(
224
216
  hardhatArguments,
225
217
  {
226
- showEmptyConfigWarning,
227
- showSolidityConfigWarnings,
218
+ showEmptyConfigWarning: true,
219
+ showSolidityConfigWarnings: scopeOrTaskName === TASK_COMPILE,
228
220
  }
229
221
  );
230
222
 
223
+ const envExtenders = ctx.environmentExtenders;
224
+ const providerExtenders = ctx.providerExtenders;
225
+ const taskDefinitions = ctx.tasksDSL.getTaskDefinitions();
226
+ const scopesDefinitions = ctx.tasksDSL.getScopesDefinitions();
227
+
228
+ // eslint-disable-next-line prefer-const
229
+ let { scopeName, taskName, unparsedCLAs } =
230
+ argumentsParser.parseScopeAndTaskNames(
231
+ allUnparsedCLAs,
232
+ taskDefinitions,
233
+ scopesDefinitions
234
+ );
235
+
231
236
  let telemetryConsent: boolean | undefined = hasConsentedTelemetry();
232
237
 
233
238
  const isHelpCommand = hardhatArguments.help || taskName === TASK_HELP;
@@ -252,22 +257,34 @@ async function main() {
252
257
  Reporter.setEnabled(true);
253
258
  }
254
259
 
255
- const envExtenders = ctx.environmentExtenders;
256
- const providerExtenders = ctx.providerExtenders;
257
- const taskDefinitions = ctx.tasksDSL.getTaskDefinitions();
258
-
259
260
  const [abortAnalytics, hitPromise] = await analytics.sendTaskHit();
260
261
 
261
262
  let taskArguments: TaskArguments;
262
263
 
263
264
  // --help is a also special case
264
265
  if (hardhatArguments.help && taskName !== TASK_HELP) {
265
- taskArguments = { task: taskName };
266
+ // we "move" the task and scope names to the task arguments,
267
+ // and run the help task
268
+ if (scopeName !== undefined) {
269
+ taskArguments = { scopeOrTask: scopeName, task: taskName };
270
+ } else {
271
+ taskArguments = { scopeOrTask: taskName };
272
+ }
266
273
  taskName = TASK_HELP;
274
+ scopeName = undefined;
267
275
  } else {
268
- const taskDefinition = taskDefinitions[taskName];
276
+ const taskDefinition = ctx.tasksDSL.getTaskDefinition(
277
+ scopeName,
278
+ taskName
279
+ );
269
280
 
270
281
  if (taskDefinition === undefined) {
282
+ if (scopeName !== undefined) {
283
+ throw new HardhatError(ERRORS.ARGUMENTS.UNRECOGNIZED_SCOPED_TASK, {
284
+ scope: scopeName,
285
+ task: taskName,
286
+ });
287
+ }
271
288
  throw new HardhatError(ERRORS.ARGUMENTS.UNRECOGNIZED_TASK, {
272
289
  task: taskName,
273
290
  });
@@ -289,6 +306,7 @@ async function main() {
289
306
  resolvedConfig,
290
307
  hardhatArguments,
291
308
  taskDefinitions,
309
+ scopesDefinitions,
292
310
  envExtenders,
293
311
  ctx.experimentalHardhatNetworkMessageTraceHooks,
294
312
  userConfig,
@@ -300,7 +318,7 @@ async function main() {
300
318
  try {
301
319
  const timestampBeforeRun = new Date().getTime();
302
320
 
303
- await env.run(taskName, taskArguments);
321
+ await env.run({ scope: scopeName, task: taskName }, taskArguments);
304
322
 
305
323
  const timestampAfterRun = new Date().getTime();
306
324
 
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  ActionType,
3
3
  ConfigExtender,
4
+ ConfigurableScopeDefinition,
4
5
  ConfigurableTaskDefinition,
5
6
  EnvironmentExtender,
6
7
  ExperimentalHardhatNetworkMessageTraceHook,
@@ -116,6 +117,16 @@ export function subtask<TaskArgumentsT extends TaskArguments>(
116
117
  // Backwards compatibility alias
117
118
  export const internalTask = subtask;
118
119
 
120
+ export function scope(
121
+ name: string,
122
+ description?: string
123
+ ): ConfigurableScopeDefinition {
124
+ const ctx = HardhatContext.getHardhatContext();
125
+ const dsl = ctx.tasksDSL;
126
+
127
+ return dsl.scope(name, description);
128
+ }
129
+
119
130
  export const types = argumentTypes;
120
131
 
121
132
  /**
@@ -613,8 +613,26 @@ Please double check your task definitions.`,
613
613
  What makes these types special is that they can be represented as strings, so you can write them down in the terminal.`,
614
614
  shouldBeReported: false,
615
615
  },
616
- DEPRECATED_TRANSFORM_IMPORT_TASK: {
616
+ TASK_SCOPE_CLASH: {
617
617
  number: 213,
618
+ message:
619
+ "A clash was found while creating scope '%scopeName%', since a task with that name already exists.",
620
+ title: "Attempted to create a scope with a name already used by a task",
621
+ description: `You can't create a scope if a task with that name already exists.
622
+ Please double check your task definitions.`,
623
+ shouldBeReported: false,
624
+ },
625
+ SCOPE_TASK_CLASH: {
626
+ number: 214,
627
+ message:
628
+ "A clash was found while creating task '%taskName%', since a scope with that name already exists.",
629
+ title: "Attempted to create a task with a name already used by a scope",
630
+ description: `You can't create a task if a scope with that name already exists.
631
+ Please double check your task definitions.`,
632
+ shouldBeReported: false,
633
+ },
634
+ DEPRECATED_TRANSFORM_IMPORT_TASK: {
635
+ number: 215,
618
636
  title: "Use of deprecated remapping task",
619
637
  message:
620
638
  "Task TASK_COMPILE_TRANSFORM_IMPORT_NAME is deprecated. Please update your @nomicfoundation/hardhat-foundry plugin version.",
@@ -654,7 +672,7 @@ Please double check your arguments.`,
654
672
  },
655
673
  UNRECOGNIZED_TASK: {
656
674
  number: 303,
657
- message: "Unrecognized task %task%",
675
+ message: "Unrecognized task '%task%'",
658
676
  title: "Unrecognized task",
659
677
  description: `Tried to run a nonexistent task.
660
678
 
@@ -756,6 +774,24 @@ This is not supported. Please run the help task to see the available options.`,
756
774
  This flag can only be used in typescript projects.`,
757
775
  shouldBeReported: false,
758
776
  },
777
+ UNRECOGNIZED_SCOPE: {
778
+ number: 314,
779
+ message: "Unrecognized scope '%scope%'",
780
+ title: "Unrecognized scope",
781
+ description: `Tried to run a task from a nonexistent scope.
782
+
783
+ Please double check the scope of the task you are trying to run.`,
784
+ shouldBeReported: false,
785
+ },
786
+ UNRECOGNIZED_SCOPED_TASK: {
787
+ number: 315,
788
+ message: "Unrecognized task '%task%' under scope '%scope%'",
789
+ title: "Unrecognized scoped task",
790
+ description: `Tried to run a nonexistent scoped task.
791
+
792
+ Please double check the name of the task you are trying to run.`,
793
+ shouldBeReported: false,
794
+ },
759
795
  },
760
796
  RESOLVER: {
761
797
  FILE_NOT_FOUND: {
@@ -10,13 +10,14 @@ import {
10
10
  HardhatUserConfig,
11
11
  Network,
12
12
  ParamDefinition,
13
+ ProviderExtender,
13
14
  RunSuperFunction,
14
15
  RunTaskFunction,
15
16
  SubtaskArguments,
16
17
  TaskArguments,
17
18
  TaskDefinition,
18
19
  TasksMap,
19
- ProviderExtender,
20
+ ScopesMap,
20
21
  } from "../../types";
21
22
  import { Artifacts } from "../artifacts";
22
23
  import { MessageTrace } from "../hardhat-network/stack-traces/message-trace";
@@ -34,6 +35,7 @@ import {
34
35
  createTaskProfile,
35
36
  TaskProfile,
36
37
  } from "./task-profiling";
38
+ import { parseTaskIdentifier } from "./tasks/util";
37
39
 
38
40
  const log = debug("hardhat:core:hre");
39
41
 
@@ -65,6 +67,7 @@ export class Environment implements HardhatRuntimeEnvironment {
65
67
  * @param config The hardhat's config object.
66
68
  * @param hardhatArguments The parsed hardhat's arguments.
67
69
  * @param tasks A map of tasks.
70
+ * @param scopes A map of scopes.
68
71
  * @param environmentExtenders A list of environment extenders.
69
72
  * @param providerExtenders A list of provider extenders.
70
73
  */
@@ -72,6 +75,7 @@ export class Environment implements HardhatRuntimeEnvironment {
72
75
  public readonly config: HardhatConfig,
73
76
  public readonly hardhatArguments: HardhatArguments,
74
77
  public readonly tasks: TasksMap,
78
+ public readonly scopes: ScopesMap,
75
79
  environmentExtenders: EnvironmentExtender[] = [],
76
80
  experimentalHardhatNetworkMessageTraceHooks: ExperimentalHardhatNetworkMessageTraceHook[] = [],
77
81
  public readonly userConfig: HardhatUserConfig = {},
@@ -122,7 +126,7 @@ export class Environment implements HardhatRuntimeEnvironment {
122
126
  /**
123
127
  * Executes the task with the given name.
124
128
  *
125
- * @param name The task's name.
129
+ * @param taskIdentifier The task or scoped task to be executed.
126
130
  * @param taskArguments A map of task's arguments.
127
131
  * @param subtaskArguments A map of subtasks to their arguments.
128
132
  *
@@ -130,18 +134,39 @@ export class Environment implements HardhatRuntimeEnvironment {
130
134
  * @returns a promise with the task's execution result.
131
135
  */
132
136
  public readonly run: RunTaskFunction = async (
133
- name,
137
+ taskIdentifier,
134
138
  taskArguments = {},
135
139
  subtaskArguments = {},
136
140
  callerTaskProfile?: TaskProfile
137
141
  ) => {
138
- const taskDefinition = this.tasks[name];
142
+ const { scope, task } = parseTaskIdentifier(taskIdentifier);
143
+
144
+ let taskDefinition;
145
+ if (scope === undefined) {
146
+ taskDefinition = this.tasks[task];
147
+ log("Running task %s", task);
148
+ } else {
149
+ const scopeDefinition = this.scopes[scope];
150
+ if (scopeDefinition === undefined) {
151
+ throw new HardhatError(ERRORS.ARGUMENTS.UNRECOGNIZED_SCOPE, {
152
+ scope,
153
+ });
154
+ }
139
155
 
140
- log("Running task %s", name);
156
+ taskDefinition = scopeDefinition.tasks?.[task];
157
+ log("Running scoped task %s %s", scope, task);
158
+ }
141
159
 
142
160
  if (taskDefinition === undefined) {
161
+ if (scope !== undefined) {
162
+ throw new HardhatError(ERRORS.ARGUMENTS.UNRECOGNIZED_SCOPED_TASK, {
163
+ scope,
164
+ task,
165
+ });
166
+ }
167
+
143
168
  throw new HardhatError(ERRORS.ARGUMENTS.UNRECOGNIZED_TASK, {
144
- task: name,
169
+ task,
145
170
  });
146
171
  }
147
172
 
@@ -153,7 +178,7 @@ export class Environment implements HardhatRuntimeEnvironment {
153
178
 
154
179
  let taskProfile: TaskProfile | undefined;
155
180
  if (this.hardhatArguments.flamegraph === true) {
156
- taskProfile = createTaskProfile(name);
181
+ taskProfile = createTaskProfile(task);
157
182
 
158
183
  if (callerTaskProfile !== undefined) {
159
184
  callerTaskProfile.children.push(taskProfile);
@@ -1,14 +1,21 @@
1
1
  import {
2
2
  ActionType,
3
+ ScopeDefinition,
4
+ ScopesMap,
3
5
  TaskArguments,
4
6
  TaskDefinition,
7
+ TaskIdentifier,
5
8
  TasksMap,
6
9
  } from "../../../types";
10
+ import { HardhatError, assertHardhatInvariant } from "../errors";
11
+ import { ERRORS } from "../errors-list";
7
12
 
8
13
  import {
9
14
  OverriddenTaskDefinition,
15
+ SimpleScopeDefinition,
10
16
  SimpleTaskDefinition,
11
17
  } from "./task-definitions";
18
+ import { parseTaskIdentifier } from "./util";
12
19
 
13
20
  /**
14
21
  * This class defines the DSL used in Hardhat config files
@@ -18,6 +25,7 @@ export class TasksDSL {
18
25
  public readonly internalTask = this.subtask;
19
26
 
20
27
  private readonly _tasks: TasksMap = {};
28
+ private readonly _scopes: ScopesMap = {};
21
29
 
22
30
  /**
23
31
  * Creates a task, overriding any previous task with the same name.
@@ -56,6 +64,8 @@ export class TasksDSL {
56
64
  descriptionOrAction?: string | ActionType<TaskArgumentsT>,
57
65
  action?: ActionType<TaskArgumentsT>
58
66
  ): TaskDefinition {
67
+ // if this function is updated, update the corresponding callback
68
+ // passed to `new SimpleScopeDefinition`
59
69
  return this._addTask(name, descriptionOrAction, action, false);
60
70
  }
61
71
 
@@ -96,9 +106,56 @@ export class TasksDSL {
96
106
  descriptionOrAction?: string | ActionType<TaskArgumentsT>,
97
107
  action?: ActionType<TaskArgumentsT>
98
108
  ): TaskDefinition {
109
+ // if this function is updated, update the corresponding callback
110
+ // passed to `new SimpleScopeDefinition`
99
111
  return this._addTask(name, descriptionOrAction, action, true);
100
112
  }
101
113
 
114
+ public scope(name: string, description?: string): ScopeDefinition {
115
+ if (this._tasks[name] !== undefined) {
116
+ throw new HardhatError(ERRORS.TASK_DEFINITIONS.TASK_SCOPE_CLASH, {
117
+ scopeName: name,
118
+ });
119
+ }
120
+
121
+ const scopeDefinition = this._scopes[name];
122
+
123
+ if (scopeDefinition !== undefined) {
124
+ // if the scope already exists, the only thing we might
125
+ // do is to update its description
126
+ if (description !== undefined) {
127
+ scopeDefinition.setDescription(description);
128
+ }
129
+
130
+ return scopeDefinition;
131
+ }
132
+
133
+ const scope = new SimpleScopeDefinition(
134
+ name,
135
+ description,
136
+ (taskName, descriptionOrAction, action) =>
137
+ // if this function is updated, update the dsl.task function too
138
+ this._addTask(
139
+ { scope: name, task: taskName },
140
+ descriptionOrAction,
141
+ action,
142
+ false
143
+ ),
144
+ (subtaskName, descriptionOrAction, action) =>
145
+ // if this function is updated, update the dsl.subtask function too
146
+ this._addTask(
147
+ { scope: name, task: subtaskName },
148
+ descriptionOrAction,
149
+ action,
150
+ true
151
+ )
152
+ );
153
+
154
+ this._scopes[name] = scope;
155
+
156
+ return scope;
157
+ }
158
+
102
159
  /**
103
160
  * Retrieves the task definitions.
104
161
  *
@@ -108,13 +165,41 @@ export class TasksDSL {
108
165
  return this._tasks;
109
166
  }
110
167
 
168
+ /**
169
+ * Retrieves the scoped task definitions.
170
+ *
171
+ * @returns The scoped tasks container.
172
+ */
173
+ public getScopesDefinitions(): ScopesMap {
174
+ return this._scopes;
175
+ }
176
+
177
+ public getTaskDefinition(
178
+ scope: string | undefined,
179
+ name: string
180
+ ): TaskDefinition | undefined {
181
+ if (scope === undefined) {
182
+ return this._tasks[name];
183
+ } else {
184
+ return this._scopes[scope]?.tasks?.[name];
185
+ }
186
+ }
187
+
111
188
  private _addTask<TaskArgumentsT extends TaskArguments>(
112
- name: string,
189
+ taskIdentifier: TaskIdentifier,
113
190
  descriptionOrAction?: string | ActionType<TaskArgumentsT>,
114
191
  action?: ActionType<TaskArgumentsT>,
115
192
  isSubtask?: boolean
116
193
  ) {
117
- const parentTaskDefinition = this._tasks[name];
194
+ const { scope, task } = parseTaskIdentifier(taskIdentifier);
195
+
196
+ if (scope === undefined && this._scopes[task] !== undefined) {
197
+ throw new HardhatError(ERRORS.TASK_DEFINITIONS.SCOPE_TASK_CLASH, {
198
+ taskName: task,
199
+ });
200
+ }
201
+
202
+ const parentTaskDefinition = this.getTaskDefinition(scope, task);
118
203
 
119
204
  let taskDefinition: TaskDefinition;
120
205
 
@@ -124,7 +209,7 @@ export class TasksDSL {
124
209
  isSubtask
125
210
  );
126
211
  } else {
127
- taskDefinition = new SimpleTaskDefinition(name, isSubtask);
212
+ taskDefinition = new SimpleTaskDefinition(taskIdentifier, isSubtask);
128
213
  }
129
214
 
130
215
  if (descriptionOrAction instanceof Function) {
@@ -140,7 +225,17 @@ export class TasksDSL {
140
225
  taskDefinition.setAction(action);
141
226
  }
142
227
 
143
- this._tasks[name] = taskDefinition;
228
+ if (scope === undefined) {
229
+ this._tasks[task] = taskDefinition;
230
+ } else {
231
+ const scopeDefinition = this._scopes[scope];
232
+ assertHardhatInvariant(
233
+ scopeDefinition !== undefined,
234
+ "It shouldn't be possible to create a task in a scope that doesn't exist"
235
+ );
236
+ scopeDefinition.tasks[task] = taskDefinition;
237
+ }
238
+
144
239
  return taskDefinition;
145
240
  }
146
241
  }