Skip to content

Commit 86f1b63

Browse files
schaablefvictorio
authored andcommitted
PR fixes
1 parent 38c6eb8 commit 86f1b63

File tree

6 files changed

+107
-54
lines changed

6 files changed

+107
-54
lines changed

packages/hardhat-viem/src/internal/tasks.ts

+97-47
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Artifact } from "hardhat/types";
1+
import type { Artifact, Artifacts } from "hardhat/types";
22
import type { ArtifactsEmittedPerFile } from "hardhat/types/builtin-tasks";
33

44
import { join, dirname, relative } from "path";
@@ -15,34 +15,32 @@ import {
1515
parseFullyQualifiedName,
1616
} from "hardhat/utils/contract-names";
1717
import { getAllFilesMatching } from "hardhat/internal/util/fs-utils";
18+
import { replaceBackslashes } from "hardhat/utils/source-names";
1819

1920
interface EmittedArtifacts {
2021
artifactsEmittedPerFile: ArtifactsEmittedPerFile;
2122
}
2223

2324
/**
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`.
3029
*/
3130
subtask(TASK_COMPILE_SOLIDITY).setAction(
3231
async (_, { config, artifacts }, runSuper) => {
3332
const superRes = await runSuper();
3433

35-
const fqns = await artifacts.getAllFullyQualifiedNames();
36-
const contractNames = fqns.map(
37-
(fqn) => parseFullyQualifiedName(fqn).contractName
38-
);
34+
const duplicateContractNames = await findDuplicateContractNames(artifacts);
3935

40-
const artifactsDTs = generateArtifactsDefinition(contractNames);
36+
const duplicateArtifactsDTs = generateDuplicateArtifactsDefinition(
37+
duplicateContractNames
38+
);
4139

4240
try {
4341
await writeFile(
44-
join(config.paths.artifacts, "artifacts.d.ts"),
45-
artifactsDTs
42+
join(config.paths.artifacts, "duplicate-artifacts.d.ts"),
43+
duplicateArtifactsDTs
4644
);
4745
} catch (error) {
4846
console.error("Error writing artifacts definition:", error);
@@ -53,21 +51,15 @@ subtask(TASK_COMPILE_SOLIDITY).setAction(
5351
);
5452

5553
/**
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`.
5958
*/
6059
subtask(TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS).setAction(
6160
async (_, { artifacts, config }, runSuper): Promise<EmittedArtifacts> => {
6261
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);
7163

7264
await Promise.all(
7365
artifactsEmittedPerFile.map(async ({ file, artifactsEmitted }) => {
@@ -80,8 +72,11 @@ subtask(TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS).setAction(
8072
artifactsEmitted.map(async (contractName) => {
8173
const fqn = getFullyQualifiedName(file.sourceName, contractName);
8274
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+
);
8580

8681
const typeName = `${contractName}$Type`;
8782

@@ -94,7 +89,7 @@ subtask(TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS).setAction(
9489
fp.push(writeFile(join(srcDir, `${contractName}.d.ts`), declaration));
9590
}
9691

97-
const dTs = generateDTsFile(contractTypeData);
92+
const dTs = generateArtifactsDefinition(contractTypeData);
9893
fp.push(writeFile(join(srcDir, "artifacts.d.ts"), dTs));
9994

10095
try {
@@ -110,26 +105,31 @@ subtask(TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS).setAction(
110105
);
111106

112107
/**
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.
115111
*/
116112
subtask(TASK_COMPILE_REMOVE_OBSOLETE_ARTIFACTS).setAction(
117113
async (_, { config, artifacts }, runSuper) => {
118114
const superRes = await runSuper();
119115

120116
const fqns = await artifacts.getAllFullyQualifiedNames();
121-
const existingSourceFiles = new Set(
117+
const existingSourceNames = new Set(
122118
fqns.map((fqn) => parseFullyQualifiedName(fqn).sourceName)
123119
);
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")
126124
);
127125

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+
);
131131

132-
if (!existingSourceFiles.has(sourceName)) {
132+
if (!existingSourceNames.has(sourceName)) {
133133
await rm(dir, { force: true, recursive: true });
134134
}
135135
}
@@ -143,26 +143,35 @@ const AUTOGENERATED_FILE_PREFACE = `// This file was autogenerated by hardhat-vi
143143
// tslint:disable
144144
// eslint-disable`;
145145

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+
) {
147153
return `${AUTOGENERATED_FILE_PREFACE}
148154
149155
import "hardhat/types/artifacts";
150156
151157
declare module "hardhat/types/artifacts" {
152158
interface ArtifactsMap {
153-
${contractNames
154-
.filter((name, i) => contractNames.indexOf(name) !== i)
159+
${Array.from(duplicateContractNames)
155160
.map((name) => `${name}: never;`)
156161
.join("\n ")}
157162
}
158163
}
159164
`;
160165
}
161166

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) {
163172
const { contractName, sourceName } = artifact;
164173
const fqn = getFullyQualifiedName(sourceName, contractName);
165-
const validNames = isDup ? [fqn] : [contractName, fqn];
174+
const validNames = isDuplicate ? [fqn] : [contractName, fqn];
166175
const json = JSON.stringify(artifact, undefined, 2);
167176
const contractTypeName = `${contractName}$Type`;
168177

@@ -179,10 +188,7 @@ function generateContractDeclaration(artifact: Artifact, isDup: boolean) {
179188
const constructorArgs =
180189
inputs.length > 0
181190
? `constructorArgs: [${inputs
182-
.map(
183-
({ name, type }) =>
184-
`AbiParameterToPrimitiveType<${JSON.stringify({ name, type })}>`
185-
)
191+
.map(({ name, type }) => getArgType(name, type))
186192
.join(", ")}]`
187193
: `constructorArgs?: []`;
188194

@@ -222,7 +228,11 @@ declare module "@nomicfoundation/hardhat-viem/types" {
222228
`;
223229
}
224230

225-
function generateDTsFile(
231+
/**
232+
* Generates TypeScript code to extend the `ArtifactsMap` interface with
233+
* contract types.
234+
*/
235+
function generateArtifactsDefinition(
226236
contractTypeData: Array<{
227237
contractName: string;
228238
fqn: string;
@@ -250,3 +260,43 @@ declare module "hardhat/types/artifacts" {
250260
}
251261
`;
252262
}
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+
}

packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/A.sol/A.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ export interface A$Type {
5959
declare module "@nomicfoundation/hardhat-viem/types" {
6060
export function deployContract(
6161
contractName: "A",
62-
constructorArgs: [AbiParameterToPrimitiveType<{"name":"_owner","type":"address"}>],
62+
constructorArgs: [_owner: AbiParameterToPrimitiveType<{"name":"_owner","type":"address"}>],
6363
config?: DeployContractConfig
6464
): Promise<GetContractReturnType<A$Type["abi"]>>;
6565
export function deployContract(
6666
contractName: "contracts/A.sol:A",
67-
constructorArgs: [AbiParameterToPrimitiveType<{"name":"_owner","type":"address"}>],
67+
constructorArgs: [_owner: AbiParameterToPrimitiveType<{"name":"_owner","type":"address"}>],
6868
config?: DeployContractConfig
6969
): Promise<GetContractReturnType<A$Type["abi"]>>;
7070

packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/C.sol/B.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export interface B$Type {
5151
declare module "@nomicfoundation/hardhat-viem/types" {
5252
export function deployContract(
5353
contractName: "contracts/C.sol:B",
54-
constructorArgs: [AbiParameterToPrimitiveType<{"name":"_b","type":"uint256"}>, AbiParameterToPrimitiveType<{"name":"_s","type":"string"}>],
54+
constructorArgs: [_b: AbiParameterToPrimitiveType<{"name":"_b","type":"uint256"}>, _s: AbiParameterToPrimitiveType<{"name":"_s","type":"string"}>],
5555
config?: DeployContractConfig
5656
): Promise<GetContractReturnType<B$Type["abi"]>>;
5757

packages/hardhat-viem/test/integration.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,12 @@ describe("Integration tests", function () {
314314
await this.hre.run(TASK_CLEAN);
315315
});
316316

317-
it("should generate artifacts.d.ts", async function () {
318-
const snapshotPath = path.join("snapshots", "artifacts.d.ts");
319-
const generatedFilePath = path.join("artifacts", "artifacts.d.ts");
317+
it("should generate duplicate-artifacts.d.ts", async function () {
318+
const snapshotPath = path.join("snapshots", "duplicate-artifacts.d.ts");
319+
const generatedFilePath = path.join(
320+
"artifacts",
321+
"duplicate-artifacts.d.ts"
322+
);
320323

321324
await assertSnapshotMatch(snapshotPath, generatedFilePath);
322325
});

packages/hardhat-viem/test/update-snapshots.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { TASK_COMPILE, TASK_CLEAN } from "hardhat/builtin-tasks/task-names";
55
import { resetHardhatContext } from "hardhat/plugins-testing";
66

77
const snapshotPartialPaths = [
8-
"artifacts.d.ts",
8+
"duplicate-artifacts.d.ts",
99
path.join("contracts", "A.sol", "A.d.ts"),
1010
path.join("contracts", "A.sol", "B.d.ts"),
1111
path.join("contracts", "A.sol", "artifacts.d.ts"),

0 commit comments

Comments
 (0)