@@ -2,7 +2,7 @@ import findup from "find-up";
2
2
import * as fs from "fs-extra" ;
3
3
import * as path from "path" ;
4
4
5
- import { HardhatRuntimeEnvironment } from "../../types" ;
5
+ import { HardhatRuntimeEnvironment , TaskDefinition } from "../../types" ;
6
6
import { HARDHAT_PARAM_DEFINITIONS } from "../core/params/hardhat-params" ;
7
7
import { getCacheDir } from "../util/global-dir" ;
8
8
import { createNonCryptographicHashBasedIdentifier } from "../util/hash" ;
@@ -22,19 +22,30 @@ interface CompletionEnv {
22
22
point : number ;
23
23
}
24
24
25
+ interface Task {
26
+ name : string ;
27
+ description : string ;
28
+ isSubtask : boolean ;
29
+ paramDefinitions : {
30
+ [ paramName : string ] : {
31
+ name : string ;
32
+ description : string ;
33
+ isFlag : boolean ;
34
+ } ;
35
+ } ;
36
+ }
37
+
25
38
interface CompletionData {
26
39
networks : string [ ] ;
27
40
tasks : {
28
- [ taskName : string ] : {
41
+ [ taskName : string ] : Task ;
42
+ } ;
43
+ scopes : {
44
+ [ scopeName : string ] : {
29
45
name : string ;
30
46
description : string ;
31
- isSubtask : boolean ;
32
- paramDefinitions : {
33
- [ paramName : string ] : {
34
- name : string ;
35
- description : string ;
36
- isFlag : boolean ;
37
- } ;
47
+ tasks : {
48
+ [ taskName : string ] : Task ;
38
49
} ;
39
50
} ;
40
51
} ;
@@ -63,12 +74,12 @@ export async function complete({
63
74
return [ ] ;
64
75
}
65
76
66
- const { networks, tasks } = completionData ;
77
+ const { networks, tasks, scopes } = completionData ;
67
78
68
79
const words = line . split ( / \s + / ) . filter ( ( x ) => x . length > 0 ) ;
69
80
70
81
const wordsBeforeCursor = line . slice ( 0 , point ) . split ( / \s + / ) ;
71
- // examples:
82
+ // 'prev' and 'last' variables examples:
72
83
// `hh compile --network|` => prev: "compile" last: "--network"
73
84
// `hh compile --network |` => prev: "--network" last: ""
74
85
// `hh compile --network ha|` => prev: "--network" last: "ha"
@@ -83,28 +94,58 @@ export async function complete({
83
94
} ) )
84
95
. filter ( ( x ) => ! words . includes ( x . name ) ) ;
85
96
86
- // check if the user entered a task
87
- let task : string | undefined ;
97
+ // Get the task or scope if the user has entered one
98
+ let taskName : string | undefined ;
99
+ let scopeName : string | undefined ;
100
+
88
101
let index = 1 ;
89
102
while ( index < words . length ) {
90
- if ( isGlobalFlag ( words [ index ] ) ) {
103
+ const word = words [ index ] ;
104
+
105
+ if ( isGlobalFlag ( word ) ) {
91
106
index += 1 ;
92
- } else if ( isGlobalParam ( words [ index ] ) ) {
107
+ } else if ( isGlobalParam ( word ) ) {
93
108
index += 2 ;
94
- } else if ( words [ index ] . startsWith ( "--" ) ) {
109
+ } else if ( word . startsWith ( "--" ) ) {
95
110
index += 1 ;
96
111
} else {
97
- task = words [ index ] ;
98
- break ;
112
+ // Possible scenarios:
113
+ // - no task or scope: `hh `
114
+ // - only a task: `hh task `
115
+ // - only a scope: `hh scope `
116
+ // - both a scope and a task (the task always follow the scope): `hh scope task `
117
+ // Between a scope and a task there could be other words, e.g.: `hh scope --flag task `
118
+ if ( scopeName === undefined ) {
119
+ if ( tasks [ word ] !== undefined ) {
120
+ taskName = word ;
121
+ break ;
122
+ } else if ( scopes [ word ] !== undefined ) {
123
+ scopeName = word ;
124
+ }
125
+ } else {
126
+ taskName = word ;
127
+ break ;
128
+ }
129
+
130
+ index += 1 ;
99
131
}
100
132
}
101
133
102
- // if a task was found but it's equal to the last word, it means
103
- // that the cursor is after the task, we ignore the task in this
104
- // case because if you have a task `foo` and `foobar` and the
105
- // line is: `hh foo|`, we want tasks to be suggested
106
- if ( task === last ) {
107
- task = undefined ;
134
+ // If a task or a scope is found and it is equal to the last word,
135
+ // this indicates that the cursor is positioned after the task or scope.
136
+ // In this case, we ignore the task or scope. For instance, if you have a task or a scope named 'foo' and 'foobar',
137
+ // and the line is 'hh foo|', we want to suggest the value for 'foo' and 'foobar'.
138
+ // Possible scenarios:
139
+ // - no task or scope: `hh ` -> task and scope already undefined
140
+ // - only a task: `hh task ` -> task set to undefined, scope already undefined
141
+ // - only a scope: `hh scope ` -> scope set to undefined, task already undefined
142
+ // - both a scope and a task (the task always follow the scope): `hh scope task ` -> task set to undefined, scope stays defined
143
+ if ( taskName === last || scopeName === last ) {
144
+ if ( taskName !== undefined && scopeName !== undefined ) {
145
+ [ taskName , scopeName ] = [ undefined , scopeName ] ;
146
+ } else {
147
+ [ taskName , scopeName ] = [ undefined , undefined ] ;
148
+ }
108
149
}
109
150
110
151
if ( prev === "--network" ) {
@@ -114,6 +155,16 @@ export async function complete({
114
155
} ) ) ;
115
156
}
116
157
158
+ const scopeDefinition =
159
+ scopeName === undefined ? undefined : scopes [ scopeName ] ;
160
+
161
+ const taskDefinition =
162
+ taskName === undefined
163
+ ? undefined
164
+ : scopeDefinition === undefined
165
+ ? tasks [ taskName ]
166
+ : scopeDefinition . tasks [ taskName ] ;
167
+
117
168
// if the previous word is a param, then a value is expected
118
169
// we don't complete anything here
119
170
if ( prev . startsWith ( "-" ) ) {
@@ -125,39 +176,60 @@ export async function complete({
125
176
}
126
177
127
178
const isTaskParam =
128
- task !== undefined &&
129
- tasks [ task ] ?. paramDefinitions [ paramName ] ?. isFlag === false ;
179
+ taskDefinition ?. paramDefinitions [ paramName ] ?. isFlag === false ;
130
180
131
181
if ( isTaskParam ) {
132
182
return HARDHAT_COMPLETE_FILES ;
133
183
}
134
184
}
135
185
136
- // if there's no task, we complete either tasks or params
137
- if ( task === undefined || tasks [ task ] === undefined ) {
186
+ // If there's no task or scope, we complete either tasks and scopes or params
187
+ if ( taskDefinition === undefined && scopeDefinition === undefined ) {
188
+ if ( last . startsWith ( "-" ) ) {
189
+ return coreParams . filter ( ( param ) => startsWithLast ( param . name ) ) ;
190
+ }
191
+
138
192
const taskSuggestions = Object . values ( tasks )
139
193
. filter ( ( x ) => ! x . isSubtask )
140
194
. map ( ( x ) => ( {
141
195
name : x . name ,
142
196
description : x . description ,
143
197
} ) ) ;
144
- if ( last . startsWith ( "-" ) ) {
145
- return coreParams . filter ( ( param ) => startsWithLast ( param . name ) ) ;
146
- }
147
- return taskSuggestions . filter ( ( x ) => startsWithLast ( x . name ) ) ;
198
+
199
+ const scopeSuggestions = Object . values ( scopes ) . map ( ( x ) => ( {
200
+ name : x . name ,
201
+ description : x . description ,
202
+ } ) ) ;
203
+
204
+ return taskSuggestions
205
+ . concat ( scopeSuggestions )
206
+ . filter ( ( x ) => startsWithLast ( x . name ) ) ;
207
+ }
208
+
209
+ // If there's a scope but not a task, we complete with the scopes'tasks
210
+ if ( taskDefinition === undefined && scopeDefinition !== undefined ) {
211
+ return Object . values ( scopes [ scopeName ! ] . tasks )
212
+ . filter ( ( x ) => ! x . isSubtask )
213
+ . map ( ( x ) => ( {
214
+ name : x . name ,
215
+ description : x . description ,
216
+ } ) )
217
+ . filter ( ( x ) => startsWithLast ( x . name ) ) ;
148
218
}
149
219
150
220
if ( ! last . startsWith ( "-" ) ) {
151
221
return HARDHAT_COMPLETE_FILES ;
152
222
}
153
223
154
- // if there's a task and the last word starts with -, we complete its params and the global params
155
- const taskParams = Object . values ( tasks [ task ] . paramDefinitions )
156
- . map ( ( param ) => ( {
157
- name : ArgumentsParser . paramNameToCLA ( param . name ) ,
158
- description : param . description ,
159
- } ) )
160
- . filter ( ( x ) => ! words . includes ( x . name ) ) ;
224
+ const taskParams =
225
+ taskDefinition === undefined
226
+ ? [ ]
227
+ : Object . values ( taskDefinition . paramDefinitions )
228
+ . map ( ( param ) => ( {
229
+ name : ArgumentsParser . paramNameToCLA ( param . name ) ,
230
+ description : param . description ,
231
+ } ) )
232
+ . filter ( ( x ) => ! words . includes ( x . name ) ) ;
161
233
162
234
return [ ...taskParams , ...coreParams ] . filter ( ( suggestion ) =>
163
235
startsWithLast ( suggestion . name )
@@ -195,27 +267,43 @@ async function getCompletionData(): Promise<CompletionData | undefined> {
195
267
196
268
// we extract the tasks data explicitly to make sure everything
197
269
// is serializable and to avoid saving unnecessary things from the HRE
198
- const tasks : CompletionData [ "tasks" ] = mapValues ( hre . tasks , ( task ) => ( {
199
- name : task . name ,
200
- description : task . description ?? "" ,
201
- isSubtask : task . isSubtask ,
202
- paramDefinitions : mapValues ( task . paramDefinitions , ( paramDefinition ) => ( {
203
- name : paramDefinition . name ,
204
- description : paramDefinition . description ?? "" ,
205
- isFlag : paramDefinition . isFlag ,
206
- } ) ) ,
270
+ const tasks : CompletionData [ "tasks" ] = mapValues ( hre . tasks , ( task ) =>
271
+ getTaskFromTaskDefinition ( task )
272
+ ) ;
273
+
274
+ const scopes : CompletionData [ "scopes" ] = mapValues ( hre . scopes , ( scope ) => ( {
275
+ name : scope . name ,
276
+ description : scope . description ?? "" ,
277
+ tasks : mapValues ( scope . tasks , ( task ) => getTaskFromTaskDefinition ( task ) ) ,
207
278
} ) ) ;
208
279
209
280
const completionData : CompletionData = {
210
281
networks,
211
282
tasks,
283
+ scopes,
212
284
} ;
213
285
214
286
await saveCachedCompletionData ( projectId , completionData , mtimes ) ;
215
287
216
288
return completionData ;
217
289
}
218
290
291
+ function getTaskFromTaskDefinition ( taskDef : TaskDefinition ) : Task {
292
+ return {
293
+ name : taskDef . name ,
294
+ description : taskDef . description ?? "" ,
295
+ isSubtask : taskDef . isSubtask ,
296
+ paramDefinitions : mapValues (
297
+ taskDef . paramDefinitions ,
298
+ ( paramDefinition ) => ( {
299
+ name : paramDefinition . name ,
300
+ description : paramDefinition . description ?? "" ,
301
+ isFlag : paramDefinition . isFlag ,
302
+ } )
303
+ ) ,
304
+ } ;
305
+ }
306
+
219
307
function getProjectId ( ) : string | undefined {
220
308
const packageJsonPath = findup . sync ( "package.json" ) ;
221
309
0 commit comments