1
- import type { Artifact } from "hardhat/types" ;
1
+ import type { Artifact , Artifacts } from "hardhat/types" ;
2
2
import type { ArtifactsEmittedPerFile } from "hardhat/types/builtin-tasks" ;
3
3
4
4
import { join , dirname , relative } from "path" ;
@@ -15,34 +15,32 @@ import {
15
15
parseFullyQualifiedName ,
16
16
} from "hardhat/utils/contract-names" ;
17
17
import { getAllFilesMatching } from "hardhat/internal/util/fs-utils" ;
18
+ import { replaceBackslashes } from "hardhat/utils/source-names" ;
18
19
19
20
interface EmittedArtifacts {
20
21
artifactsEmittedPerFile : ArtifactsEmittedPerFile ;
21
22
}
22
23
23
24
/**
24
- * This override generates an artifacts.d.ts file that's used
25
- * to type hre.artifacts.
26
- *
27
- * TODO: Can we avoid regenerating this every time? The reason we override
28
- * this task is that deleting a `.sol` file doesn't emit any artifact, yet
29
- * we may need to regenerate this file.
25
+ * Override task that generates an `duplicate-artifacts.d.ts` file with `never`
26
+ * types for duplicate contract names. This file is used in conjunction with
27
+ * the `artifacts.d.ts` file inside each contract directory to type
28
+ * `hre.artifacts`.
30
29
*/
31
30
subtask ( TASK_COMPILE_SOLIDITY ) . setAction (
32
31
async ( _ , { config, artifacts } , runSuper ) => {
33
32
const superRes = await runSuper ( ) ;
34
33
35
- const fqns = await artifacts . getAllFullyQualifiedNames ( ) ;
36
- const contractNames = fqns . map (
37
- ( fqn ) => parseFullyQualifiedName ( fqn ) . contractName
38
- ) ;
34
+ const duplicateContractNames = await findDuplicateContractNames ( artifacts ) ;
39
35
40
- const artifactsDTs = generateArtifactsDefinition ( contractNames ) ;
36
+ const duplicateArtifactsDTs = generateDuplicateArtifactsDefinition (
37
+ duplicateContractNames
38
+ ) ;
41
39
42
40
try {
43
41
await writeFile (
44
- join ( config . paths . artifacts , "artifacts.d.ts" ) ,
45
- artifactsDTs
42
+ join ( config . paths . artifacts , "duplicate- artifacts.d.ts" ) ,
43
+ duplicateArtifactsDTs
46
44
) ;
47
45
} catch ( error ) {
48
46
console . error ( "Error writing artifacts definition:" , error ) ;
@@ -53,21 +51,15 @@ subtask(TASK_COMPILE_SOLIDITY).setAction(
53
51
) ;
54
52
55
53
/**
56
- * This override generates a .ts file per contract, and a file.d.ts
57
- * per solidity file, which is used in conjunction to artifacts.d.ts
58
- * to type hre.artifacts.
54
+ * Override task to emit TypeScript and definition files for each contract.
55
+ * Generates a `.d.ts` file per contract, and a `artifacts.d.ts` per solidity
56
+ * file, which is used in conjunction to the root `duplicate-artifacts.d.ts`
57
+ * to type `hre.artifacts`.
59
58
*/
60
59
subtask ( TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS ) . setAction (
61
60
async ( _ , { artifacts, config } , runSuper ) : Promise < EmittedArtifacts > => {
62
61
const { artifactsEmittedPerFile } : EmittedArtifacts = await runSuper ( ) ;
63
-
64
- const fqns = await artifacts . getAllFullyQualifiedNames ( ) ;
65
- const contractNames = fqns . map (
66
- ( fqn ) => parseFullyQualifiedName ( fqn ) . contractName
67
- ) ;
68
- const dupContractNames = contractNames . filter (
69
- ( name , i ) => contractNames . indexOf ( name ) !== i
70
- ) ;
62
+ const duplicateContractNames = await findDuplicateContractNames ( artifacts ) ;
71
63
72
64
await Promise . all (
73
65
artifactsEmittedPerFile . map ( async ( { file, artifactsEmitted } ) => {
@@ -80,8 +72,11 @@ subtask(TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS).setAction(
80
72
artifactsEmitted . map ( async ( contractName ) => {
81
73
const fqn = getFullyQualifiedName ( file . sourceName , contractName ) ;
82
74
const artifact = await artifacts . readArtifact ( fqn ) ;
83
- const isDup = dupContractNames . includes ( contractName ) ;
84
- const declaration = generateContractDeclaration ( artifact , isDup ) ;
75
+ const isDuplicate = duplicateContractNames . has ( contractName ) ;
76
+ const declaration = generateContractDeclaration (
77
+ artifact ,
78
+ isDuplicate
79
+ ) ;
85
80
86
81
const typeName = `${ contractName } $Type` ;
87
82
@@ -94,7 +89,7 @@ subtask(TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS).setAction(
94
89
fp . push ( writeFile ( join ( srcDir , `${ contractName } .d.ts` ) , declaration ) ) ;
95
90
}
96
91
97
- const dTs = generateDTsFile ( contractTypeData ) ;
92
+ const dTs = generateArtifactsDefinition ( contractTypeData ) ;
98
93
fp . push ( writeFile ( join ( srcDir , "artifacts.d.ts" ) , dTs ) ) ;
99
94
100
95
try {
@@ -110,26 +105,31 @@ subtask(TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS).setAction(
110
105
) ;
111
106
112
107
/**
113
- * This override deletes the obsolete dir files that were kept just because
114
- * of the files that we generated.
108
+ * Override task for cleaning up outdated artifacts.
109
+ * Deletes directories with stale `artifacts.d.ts` files that no longer have
110
+ * a matching `.sol` file.
115
111
*/
116
112
subtask ( TASK_COMPILE_REMOVE_OBSOLETE_ARTIFACTS ) . setAction (
117
113
async ( _ , { config, artifacts } , runSuper ) => {
118
114
const superRes = await runSuper ( ) ;
119
115
120
116
const fqns = await artifacts . getAllFullyQualifiedNames ( ) ;
121
- const existingSourceFiles = new Set (
117
+ const existingSourceNames = new Set (
122
118
fqns . map ( ( fqn ) => parseFullyQualifiedName ( fqn ) . sourceName )
123
119
) ;
124
- const allFilesDTs = await getAllFilesMatching ( config . paths . artifacts , ( f ) =>
125
- f . endsWith ( "file.d.ts" )
120
+ const allArtifactsDTs = await getAllFilesMatching (
121
+ config . paths . artifacts ,
122
+ ( f ) =>
123
+ f . endsWith ( "artifacts.d.ts" ) && ! f . endsWith ( "duplicate-artifacts.d.ts" )
126
124
) ;
127
125
128
- for ( const fileDTs of allFilesDTs ) {
129
- const dir = dirname ( fileDTs ) ;
130
- const sourceName = relative ( config . paths . artifacts , dir ) ;
126
+ for ( const artifactDTs of allArtifactsDTs ) {
127
+ const dir = dirname ( artifactDTs ) ;
128
+ const sourceName = replaceBackslashes (
129
+ relative ( config . paths . artifacts , dir )
130
+ ) ;
131
131
132
- if ( ! existingSourceFiles . has ( sourceName ) ) {
132
+ if ( ! existingSourceNames . has ( sourceName ) ) {
133
133
await rm ( dir , { force : true , recursive : true } ) ;
134
134
}
135
135
}
@@ -143,26 +143,35 @@ const AUTOGENERATED_FILE_PREFACE = `// This file was autogenerated by hardhat-vi
143
143
// tslint:disable
144
144
// eslint-disable` ;
145
145
146
- function generateArtifactsDefinition ( contractNames : string [ ] ) {
146
+ /**
147
+ * Generates TypeScript code that extends the `ArtifactsMap` with `never` types
148
+ * for duplicate contract names.
149
+ */
150
+ function generateDuplicateArtifactsDefinition (
151
+ duplicateContractNames : Set < string >
152
+ ) {
147
153
return `${ AUTOGENERATED_FILE_PREFACE }
148
154
149
155
import "hardhat/types/artifacts";
150
156
151
157
declare module "hardhat/types/artifacts" {
152
158
interface ArtifactsMap {
153
- ${ contractNames
154
- . filter ( ( name , i ) => contractNames . indexOf ( name ) !== i )
159
+ ${ Array . from ( duplicateContractNames )
155
160
. map ( ( name ) => `${ name } : never;` )
156
161
. join ( "\n " ) }
157
162
}
158
163
}
159
164
` ;
160
165
}
161
166
162
- function generateContractDeclaration ( artifact : Artifact , isDup : boolean ) {
167
+ /**
168
+ * Generates TypeScript code to declare a contract and its associated
169
+ * TypeScript types.
170
+ */
171
+ function generateContractDeclaration ( artifact : Artifact , isDuplicate : boolean ) {
163
172
const { contractName, sourceName } = artifact ;
164
173
const fqn = getFullyQualifiedName ( sourceName , contractName ) ;
165
- const validNames = isDup ? [ fqn ] : [ contractName , fqn ] ;
174
+ const validNames = isDuplicate ? [ fqn ] : [ contractName , fqn ] ;
166
175
const json = JSON . stringify ( artifact , undefined , 2 ) ;
167
176
const contractTypeName = `${ contractName } $Type` ;
168
177
@@ -179,10 +188,7 @@ function generateContractDeclaration(artifact: Artifact, isDup: boolean) {
179
188
const constructorArgs =
180
189
inputs . length > 0
181
190
? `constructorArgs: [${ inputs
182
- . map (
183
- ( { name, type } ) =>
184
- `AbiParameterToPrimitiveType<${ JSON . stringify ( { name, type } ) } >`
185
- )
191
+ . map ( ( { name, type } ) => getArgType ( name , type ) )
186
192
. join ( ", " ) } ]`
187
193
: `constructorArgs?: []` ;
188
194
@@ -222,7 +228,11 @@ declare module "@nomicfoundation/hardhat-viem/types" {
222
228
` ;
223
229
}
224
230
225
- function generateDTsFile (
231
+ /**
232
+ * Generates TypeScript code to extend the `ArtifactsMap` interface with
233
+ * contract types.
234
+ */
235
+ function generateArtifactsDefinition (
226
236
contractTypeData : Array < {
227
237
contractName : string ;
228
238
fqn : string ;
@@ -250,3 +260,43 @@ declare module "hardhat/types/artifacts" {
250
260
}
251
261
` ;
252
262
}
263
+
264
+ /**
265
+ * Returns the type of a function argument in one of the following formats:
266
+ * - If the 'name' is provided:
267
+ * "name: AbiParameterToPrimitiveType<{ name: string; type: string; }>"
268
+ *
269
+ * - If the 'name' is empty:
270
+ * "AbiParameterToPrimitiveType<{ name: string; type: string; }>"
271
+ */
272
+ function getArgType ( name : string , type : string ) {
273
+ const argType = `AbiParameterToPrimitiveType<${ JSON . stringify ( {
274
+ name,
275
+ type,
276
+ } ) } >`;
277
+
278
+ return name !== "" ? `${ name } : ${ argType } ` : argType ;
279
+ }
280
+
281
+ /**
282
+ * Returns a set of duplicate contract names.
283
+ */
284
+ async function findDuplicateContractNames ( artifacts : Artifacts ) {
285
+ const fqns = await artifacts . getAllFullyQualifiedNames ( ) ;
286
+ const contractNames = fqns . map (
287
+ ( fqn ) => parseFullyQualifiedName ( fqn ) . contractName
288
+ ) ;
289
+
290
+ const duplicates = new Set < string > ( ) ;
291
+ const existing = new Set < string > ( ) ;
292
+
293
+ for ( const name of contractNames ) {
294
+ if ( existing . has ( name ) ) {
295
+ duplicates . add ( name ) ;
296
+ }
297
+
298
+ existing . add ( name ) ;
299
+ }
300
+
301
+ return duplicates ;
302
+ }
0 commit comments