hardhat 3.1.3 → 3.1.4
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.
- package/CHANGELOG.md +8 -0
- package/dist/src/internal/builtin-plugins/coverage/coverage-manager.d.ts +14 -14
- package/dist/src/internal/builtin-plugins/coverage/coverage-manager.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/coverage/coverage-manager.js +66 -110
- package/dist/src/internal/builtin-plugins/coverage/coverage-manager.js.map +1 -1
- package/dist/src/internal/builtin-plugins/coverage/hook-handlers/solidity.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/coverage/hook-handlers/solidity.js +13 -6
- package/dist/src/internal/builtin-plugins/coverage/hook-handlers/solidity.js.map +1 -1
- package/dist/src/internal/builtin-plugins/coverage/process-coverage.d.ts +21 -0
- package/dist/src/internal/builtin-plugins/coverage/process-coverage.d.ts.map +1 -0
- package/dist/src/internal/builtin-plugins/coverage/process-coverage.js +291 -0
- package/dist/src/internal/builtin-plugins/coverage/process-coverage.js.map +1 -0
- package/dist/src/internal/builtin-plugins/coverage/reports/html.d.ts +3 -0
- package/dist/src/internal/builtin-plugins/coverage/reports/html.d.ts.map +1 -0
- package/dist/src/internal/builtin-plugins/coverage/reports/html.js +37 -0
- package/dist/src/internal/builtin-plugins/coverage/reports/html.js.map +1 -0
- package/dist/src/internal/builtin-plugins/coverage/types.d.ts +9 -3
- package/dist/src/internal/builtin-plugins/coverage/types.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/internal/builtin-plugins/coverage/coverage-manager.ts +121 -176
- package/src/internal/builtin-plugins/coverage/hook-handlers/solidity.ts +19 -8
- package/src/internal/builtin-plugins/coverage/process-coverage.ts +442 -0
- package/src/internal/builtin-plugins/coverage/reports/html.ts +56 -0
- package/src/internal/builtin-plugins/coverage/types.ts +8 -3
- package/templates/hardhat-3/01-node-test-runner-viem/package.json +1 -1
- package/templates/hardhat-3/02-mocha-ethers/package.json +1 -1
- package/templates/hardhat-3/03-minimal/package.json +1 -1
|
@@ -3,18 +3,17 @@ import type {
|
|
|
3
3
|
CoverageManager,
|
|
4
4
|
CoverageMetadata,
|
|
5
5
|
Statement,
|
|
6
|
-
Tag,
|
|
7
6
|
} from "./types.js";
|
|
8
7
|
import type { TableItem } from "@nomicfoundation/hardhat-utils/format";
|
|
9
8
|
|
|
10
9
|
import path from "node:path";
|
|
11
10
|
|
|
12
|
-
import { assertHardhatInvariant } from "@nomicfoundation/hardhat-errors";
|
|
13
11
|
import { divider, formatTable } from "@nomicfoundation/hardhat-utils/format";
|
|
14
12
|
import {
|
|
15
13
|
ensureDir,
|
|
16
14
|
getAllFilesMatching,
|
|
17
15
|
readJsonFile,
|
|
16
|
+
readUtf8File,
|
|
18
17
|
remove,
|
|
19
18
|
writeJsonFile,
|
|
20
19
|
writeUtf8File,
|
|
@@ -22,36 +21,56 @@ import {
|
|
|
22
21
|
import chalk from "chalk";
|
|
23
22
|
import debug from "debug";
|
|
24
23
|
|
|
24
|
+
import { getProcessedCoverageInfo } from "./process-coverage.js";
|
|
25
|
+
import { generateHtmlReport } from "./reports/html.js";
|
|
26
|
+
|
|
25
27
|
const log = debug("hardhat:core:coverage:coverage-manager");
|
|
26
28
|
|
|
27
29
|
const MAX_COLUMN_WIDTH = 80;
|
|
28
30
|
|
|
29
31
|
type Line = number;
|
|
30
|
-
type Branch = [Line, Tag];
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* @private exposed for testing purposes only
|
|
34
35
|
*/
|
|
36
|
+
export interface FileReport {
|
|
37
|
+
// NOTE: currently, the counters for how many times a statement is executed are not implemented in EDR,
|
|
38
|
+
// so the only information available is whether a statement was executed, not how many times it was executed.
|
|
39
|
+
// Also, branch coverage is not available.
|
|
40
|
+
// In addition, partially executed lines (for example, ternary operators) cannot be determined, as this information is missing in EDR,
|
|
41
|
+
// since only whole statements can be registered as executed or not.
|
|
42
|
+
|
|
43
|
+
lineExecutionCounts: Map<Line, number>;
|
|
44
|
+
|
|
45
|
+
executedStatementsCount: number;
|
|
46
|
+
unexecutedStatementsCount: number;
|
|
47
|
+
|
|
48
|
+
executedLinesCount: number;
|
|
49
|
+
|
|
50
|
+
unexecutedLines: Set<Line>;
|
|
51
|
+
}
|
|
52
|
+
|
|
35
53
|
export interface Report {
|
|
36
|
-
[relativePath: string]:
|
|
37
|
-
tagExecutionCounts: Map<Tag, number>;
|
|
38
|
-
lineExecutionCounts: Map<Line, number>;
|
|
39
|
-
branchExecutionCounts: Map<Branch, number>;
|
|
40
|
-
|
|
41
|
-
executedTagsCount: number;
|
|
42
|
-
executedLinesCount: number;
|
|
43
|
-
executedBranchesCount: number;
|
|
44
|
-
|
|
45
|
-
partiallyExecutedLines: Set<Line>;
|
|
46
|
-
unexecutedLines: Set<Line>;
|
|
47
|
-
};
|
|
54
|
+
[relativePath: string]: FileReport;
|
|
48
55
|
}
|
|
49
56
|
|
|
57
|
+
type FilesMetadata = Map<
|
|
58
|
+
string, // relative path
|
|
59
|
+
Map<
|
|
60
|
+
string, // composite key
|
|
61
|
+
Statement
|
|
62
|
+
>
|
|
63
|
+
>;
|
|
64
|
+
|
|
50
65
|
export class CoverageManagerImplementation implements CoverageManager {
|
|
51
66
|
/**
|
|
52
67
|
* @private exposed for testing purposes only
|
|
53
68
|
*/
|
|
54
|
-
public
|
|
69
|
+
public filesMetadata: FilesMetadata = new Map<
|
|
70
|
+
string,
|
|
71
|
+
Map<string, Statement>
|
|
72
|
+
>();
|
|
73
|
+
|
|
55
74
|
/**
|
|
56
75
|
* @private exposed for testing purposes only
|
|
57
76
|
*/
|
|
@@ -80,13 +99,24 @@ export class CoverageManagerImplementation implements CoverageManager {
|
|
|
80
99
|
}
|
|
81
100
|
|
|
82
101
|
public async addMetadata(metadata: CoverageMetadata): Promise<void> {
|
|
83
|
-
// NOTE: The received metadata might contain duplicates. We deduplicate it
|
|
84
|
-
// when we generate the report.
|
|
85
102
|
for (const entry of metadata) {
|
|
86
|
-
|
|
87
|
-
|
|
103
|
+
log("Added metadata", JSON.stringify(metadata, null, 2));
|
|
104
|
+
|
|
105
|
+
let fileStatements = this.filesMetadata.get(entry.relativePath);
|
|
106
|
+
|
|
107
|
+
if (fileStatements === undefined) {
|
|
108
|
+
fileStatements = new Map();
|
|
109
|
+
this.filesMetadata.set(entry.relativePath, fileStatements);
|
|
110
|
+
}
|
|
88
111
|
|
|
89
|
-
|
|
112
|
+
const key = `${entry.relativePath}-${entry.tag}-${entry.startUtf16}-${entry.endUtf16}`;
|
|
113
|
+
|
|
114
|
+
const existingData = fileStatements.get(key);
|
|
115
|
+
|
|
116
|
+
if (existingData === undefined) {
|
|
117
|
+
fileStatements.set(key, entry);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
90
120
|
}
|
|
91
121
|
|
|
92
122
|
public async clearData(id: string): Promise<void> {
|
|
@@ -111,7 +141,7 @@ export class CoverageManagerImplementation implements CoverageManager {
|
|
|
111
141
|
|
|
112
142
|
await this.loadData(...ids);
|
|
113
143
|
|
|
114
|
-
const report = this.getReport();
|
|
144
|
+
const report = await this.getReport();
|
|
115
145
|
const lcovReport = this.formatLcovReport(report);
|
|
116
146
|
const markdownReport = this.formatMarkdownReport(report);
|
|
117
147
|
|
|
@@ -119,6 +149,10 @@ export class CoverageManagerImplementation implements CoverageManager {
|
|
|
119
149
|
await writeUtf8File(lcovReportPath, lcovReport);
|
|
120
150
|
log(`Saved lcov report to ${lcovReportPath}`);
|
|
121
151
|
|
|
152
|
+
const htmlReportPath = path.join(this.#coveragePath, "html");
|
|
153
|
+
await generateHtmlReport(report, htmlReportPath);
|
|
154
|
+
console.log(`Saved html report to ${htmlReportPath}`);
|
|
155
|
+
|
|
122
156
|
console.log(markdownReport);
|
|
123
157
|
console.log();
|
|
124
158
|
log("Printed markdown report");
|
|
@@ -153,143 +187,63 @@ export class CoverageManagerImplementation implements CoverageManager {
|
|
|
153
187
|
/**
|
|
154
188
|
* @private exposed for testing purposes only
|
|
155
189
|
*/
|
|
156
|
-
public getReport(): Report {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
const relativePaths = this.metadata.map(({ relativePath }) => relativePath);
|
|
160
|
-
|
|
161
|
-
const allStatements = this.metadata;
|
|
162
|
-
|
|
163
|
-
// NOTE: We preserve only the last statement per tag in the statementsByTag map.
|
|
164
|
-
const statementsByTag = new Map<string, Statement>();
|
|
165
|
-
for (const statement of allStatements) {
|
|
166
|
-
statementsByTag.set(statement.tag, statement);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const allExecutedTags = this.data;
|
|
170
|
-
|
|
171
|
-
const allExecutedStatementsByRelativePath = new Map<string, Statement[]>();
|
|
172
|
-
for (const tag of allExecutedTags) {
|
|
173
|
-
// NOTE: We should not encounter an executed tag we don't have metadata for.
|
|
174
|
-
const statement = statementsByTag.get(tag);
|
|
175
|
-
assertHardhatInvariant(statement !== undefined, "Expected a statement");
|
|
176
|
-
|
|
177
|
-
const relativePath = statement.relativePath;
|
|
178
|
-
const allExecutedStatements =
|
|
179
|
-
allExecutedStatementsByRelativePath.get(relativePath) ?? [];
|
|
180
|
-
allExecutedStatements.push(statement);
|
|
181
|
-
allExecutedStatementsByRelativePath.set(
|
|
182
|
-
relativePath,
|
|
183
|
-
allExecutedStatements,
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const uniqueExecutedTags = new Set(allExecutedTags);
|
|
188
|
-
const uniqueUnexecutedTags = Array.from(statementsByTag.keys()).filter(
|
|
189
|
-
(tag) => !uniqueExecutedTags.has(tag),
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
const uniqueUnexecutedStatementsByRelativePath = new Map<
|
|
193
|
-
string,
|
|
194
|
-
Statement[]
|
|
195
|
-
>();
|
|
196
|
-
for (const tag of uniqueUnexecutedTags) {
|
|
197
|
-
// NOTE: We cannot encounter an executed tag we don't have metadata for.
|
|
198
|
-
const statement = statementsByTag.get(tag);
|
|
199
|
-
assertHardhatInvariant(statement !== undefined, "Expected a statement");
|
|
200
|
-
|
|
201
|
-
const relativePath = statement.relativePath;
|
|
202
|
-
const unexecutedStatements =
|
|
203
|
-
uniqueUnexecutedStatementsByRelativePath.get(relativePath) ?? [];
|
|
204
|
-
unexecutedStatements.push(statement);
|
|
205
|
-
uniqueUnexecutedStatementsByRelativePath.set(
|
|
206
|
-
relativePath,
|
|
207
|
-
unexecutedStatements,
|
|
208
|
-
);
|
|
209
|
-
}
|
|
190
|
+
public async getReport(): Promise<Report> {
|
|
191
|
+
const allExecutedTags = new Set(this.data);
|
|
210
192
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const uniqueUnexecutedStatements =
|
|
215
|
-
uniqueUnexecutedStatementsByRelativePath.get(relativePath) ?? [];
|
|
216
|
-
|
|
217
|
-
const tagExecutionCounts = new Map<Tag, number>();
|
|
218
|
-
|
|
219
|
-
for (const statement of allExecutedStatements) {
|
|
220
|
-
const tagExecutionCount = tagExecutionCounts.get(statement.tag) ?? 0;
|
|
221
|
-
tagExecutionCounts.set(statement.tag, tagExecutionCount + 1);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const lineExecutionCounts = new Map<number, number>();
|
|
225
|
-
const branchExecutionCounts = new Map<Branch, number>();
|
|
226
|
-
|
|
227
|
-
for (const [tag, executionCount] of tagExecutionCounts) {
|
|
228
|
-
const statement = statementsByTag.get(tag);
|
|
229
|
-
assertHardhatInvariant(statement !== undefined, "Expected a statement");
|
|
230
|
-
|
|
231
|
-
for (
|
|
232
|
-
let line = statement.startLine;
|
|
233
|
-
line <= statement.endLine;
|
|
234
|
-
line++
|
|
235
|
-
) {
|
|
236
|
-
const lineExecutionCount = lineExecutionCounts.get(line) ?? 0;
|
|
237
|
-
lineExecutionCounts.set(line, lineExecutionCount + executionCount);
|
|
238
|
-
|
|
239
|
-
const branchExecutionCount =
|
|
240
|
-
branchExecutionCounts.get([line, tag]) ?? 0;
|
|
241
|
-
branchExecutionCounts.set(
|
|
242
|
-
[line, tag],
|
|
243
|
-
branchExecutionCount + executionCount,
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
193
|
+
const reportPromises = Array.from(this.filesMetadata.entries()).map(
|
|
194
|
+
async ([fileRelativePath, fileStatements]) => {
|
|
195
|
+
const statements = Array.from(fileStatements.values());
|
|
247
196
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
197
|
+
const fileContent = await readUtf8File(
|
|
198
|
+
path.join(process.cwd(), fileRelativePath),
|
|
199
|
+
);
|
|
251
200
|
|
|
252
|
-
|
|
253
|
-
|
|
201
|
+
const tags: Set<string> = new Set();
|
|
202
|
+
let executedStatementsCount = 0;
|
|
203
|
+
let unexecutedStatementsCount = 0;
|
|
254
204
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
for (
|
|
261
|
-
let line = statement.startLine;
|
|
262
|
-
line <= statement.endLine;
|
|
263
|
-
line++
|
|
264
|
-
) {
|
|
265
|
-
if (!lineExecutionCounts.has(line)) {
|
|
266
|
-
lineExecutionCounts.set(line, 0);
|
|
267
|
-
unexecutedLines.add(line);
|
|
205
|
+
for (const { tag } of statements) {
|
|
206
|
+
if (allExecutedTags.has(tag)) {
|
|
207
|
+
tags.add(tag);
|
|
208
|
+
executedStatementsCount++;
|
|
268
209
|
} else {
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (!branchExecutionCounts.has([line, statement.tag])) {
|
|
273
|
-
branchExecutionCounts.set([line, statement.tag], 0);
|
|
210
|
+
unexecutedStatementsCount++;
|
|
274
211
|
}
|
|
275
212
|
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
report[relativePath] = {
|
|
279
|
-
tagExecutionCounts,
|
|
280
|
-
lineExecutionCounts,
|
|
281
|
-
branchExecutionCounts,
|
|
282
213
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
214
|
+
const coverageInfo = getProcessedCoverageInfo(
|
|
215
|
+
fileContent,
|
|
216
|
+
statements,
|
|
217
|
+
tags,
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const lineExecutionCounts = new Map<number, number>();
|
|
221
|
+
coverageInfo.lines.executed.forEach((_, line) =>
|
|
222
|
+
lineExecutionCounts.set(line, 1),
|
|
223
|
+
);
|
|
224
|
+
coverageInfo.lines.unexecuted.forEach((_, line) =>
|
|
225
|
+
lineExecutionCounts.set(line, 0),
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const executedLinesCount = coverageInfo.lines.executed.size;
|
|
229
|
+
const unexecutedLines = new Set(coverageInfo.lines.unexecuted.keys());
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
path: fileRelativePath,
|
|
233
|
+
data: {
|
|
234
|
+
lineExecutionCounts,
|
|
235
|
+
executedStatementsCount,
|
|
236
|
+
unexecutedStatementsCount,
|
|
237
|
+
executedLinesCount,
|
|
238
|
+
unexecutedLines,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
},
|
|
242
|
+
);
|
|
286
243
|
|
|
287
|
-
|
|
288
|
-
unexecutedLines,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
244
|
+
const results = await Promise.all(reportPromises);
|
|
291
245
|
|
|
292
|
-
return
|
|
246
|
+
return Object.fromEntries(results.map((r) => [r.path, r.data]));
|
|
293
247
|
}
|
|
294
248
|
|
|
295
249
|
/**
|
|
@@ -315,12 +269,7 @@ export class CoverageManagerImplementation implements CoverageManager {
|
|
|
315
269
|
|
|
316
270
|
for (const [
|
|
317
271
|
relativePath,
|
|
318
|
-
{
|
|
319
|
-
branchExecutionCounts,
|
|
320
|
-
executedBranchesCount,
|
|
321
|
-
lineExecutionCounts,
|
|
322
|
-
executedLinesCount,
|
|
323
|
-
},
|
|
272
|
+
{ lineExecutionCounts, executedLinesCount },
|
|
324
273
|
] of Object.entries(report)) {
|
|
325
274
|
lcov += `SF:${relativePath}\n`;
|
|
326
275
|
|
|
@@ -336,11 +285,12 @@ export class CoverageManagerImplementation implements CoverageManager {
|
|
|
336
285
|
// BRF:<number of branches found>
|
|
337
286
|
// BRH:<number of branches hit>
|
|
338
287
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
lcov += `
|
|
288
|
+
// TODO: currently EDR does not provide branch coverage information.
|
|
289
|
+
// for (const [[line, tag], executionCount] of branchExecutionCounts) {
|
|
290
|
+
// lcov += `BRDA:${line},0,${tag},${executionCount === 0 ? "-" : executionCount}\n`;
|
|
291
|
+
// }
|
|
292
|
+
// lcov += `BRH:${executedBranchesCount}\n`;
|
|
293
|
+
// lcov += `BRF:${branchExecutionCounts.size}\n`;
|
|
344
294
|
|
|
345
295
|
// Then there is a list of execution counts for each instrumented line
|
|
346
296
|
// (i.e. a line which resulted in executable code):
|
|
@@ -487,25 +437,20 @@ export class CoverageManagerImplementation implements CoverageManager {
|
|
|
487
437
|
rows.push(divider);
|
|
488
438
|
|
|
489
439
|
rows.push(
|
|
490
|
-
[
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
"Statement %",
|
|
494
|
-
"Uncovered Lines",
|
|
495
|
-
"Partially Covered Lines",
|
|
496
|
-
].map((s) => chalk.yellow(s)),
|
|
440
|
+
["File Path", "Line %", "Statement %", "Uncovered Lines"].map((s) =>
|
|
441
|
+
chalk.yellow(s),
|
|
442
|
+
),
|
|
497
443
|
);
|
|
498
444
|
|
|
499
445
|
const bodyRows = Object.entries(report).map(
|
|
500
446
|
([
|
|
501
447
|
relativePath,
|
|
502
448
|
{
|
|
503
|
-
|
|
449
|
+
executedStatementsCount,
|
|
450
|
+
unexecutedStatementsCount,
|
|
504
451
|
lineExecutionCounts,
|
|
505
|
-
executedTagsCount,
|
|
506
452
|
executedLinesCount,
|
|
507
453
|
unexecutedLines,
|
|
508
|
-
partiallyExecutedLines,
|
|
509
454
|
},
|
|
510
455
|
]) => {
|
|
511
456
|
const lineCoverage =
|
|
@@ -513,22 +458,23 @@ export class CoverageManagerImplementation implements CoverageManager {
|
|
|
513
458
|
? 0
|
|
514
459
|
: (executedLinesCount * 100.0) / lineExecutionCounts.size;
|
|
515
460
|
const statementCoverage =
|
|
516
|
-
|
|
461
|
+
executedStatementsCount === 0
|
|
517
462
|
? 0
|
|
518
|
-
: (
|
|
463
|
+
: (executedStatementsCount * 100.0) /
|
|
464
|
+
(executedStatementsCount + unexecutedStatementsCount);
|
|
519
465
|
|
|
520
466
|
totalExecutedLines += executedLinesCount;
|
|
521
467
|
totalExecutableLines += lineExecutionCounts.size;
|
|
522
468
|
|
|
523
|
-
totalExecutedStatements +=
|
|
524
|
-
totalExecutableStatements +=
|
|
469
|
+
totalExecutedStatements += executedStatementsCount;
|
|
470
|
+
totalExecutableStatements +=
|
|
471
|
+
executedStatementsCount + unexecutedStatementsCount;
|
|
525
472
|
|
|
526
473
|
const row: string[] = [
|
|
527
474
|
this.formatRelativePath(relativePath),
|
|
528
475
|
this.formatCoverage(lineCoverage),
|
|
529
476
|
this.formatCoverage(statementCoverage),
|
|
530
477
|
this.formatLines(unexecutedLines),
|
|
531
|
-
this.formatLines(partiallyExecutedLines),
|
|
532
478
|
];
|
|
533
479
|
|
|
534
480
|
return row;
|
|
@@ -552,7 +498,6 @@ export class CoverageManagerImplementation implements CoverageManager {
|
|
|
552
498
|
this.formatCoverage(totalLineCoverage),
|
|
553
499
|
this.formatCoverage(totalStatementCoverage),
|
|
554
500
|
"",
|
|
555
|
-
"",
|
|
556
501
|
]);
|
|
557
502
|
|
|
558
503
|
return formatTable(rows);
|
|
@@ -3,14 +3,19 @@ import type { CoverageMetadata } from "../types.js";
|
|
|
3
3
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
addStatementCoverageInstrumentation,
|
|
8
|
+
latestSupportedSolidityVersion,
|
|
9
|
+
} from "@nomicfoundation/edr";
|
|
7
10
|
import {
|
|
8
11
|
assertHardhatInvariant,
|
|
9
12
|
HardhatError,
|
|
10
13
|
} from "@nomicfoundation/hardhat-errors";
|
|
11
14
|
import { ensureError } from "@nomicfoundation/hardhat-utils/error";
|
|
12
15
|
import { readUtf8File } from "@nomicfoundation/hardhat-utils/fs";
|
|
16
|
+
import { findClosestPackageRoot } from "@nomicfoundation/hardhat-utils/package";
|
|
13
17
|
import debug from "debug";
|
|
18
|
+
import { satisfies } from "semver";
|
|
14
19
|
|
|
15
20
|
import { CoverageManagerImplementation } from "../coverage-manager.js";
|
|
16
21
|
|
|
@@ -35,6 +40,13 @@ export default async (): Promise<Partial<SolidityHooks>> => ({
|
|
|
35
40
|
|
|
36
41
|
if (context.globalOptions.coverage && !isTestSource) {
|
|
37
42
|
try {
|
|
43
|
+
const latestSupportedVersion = latestSupportedSolidityVersion();
|
|
44
|
+
if (!satisfies(solcVersion, `<=${latestSupportedVersion}`)) {
|
|
45
|
+
console.log(
|
|
46
|
+
`Solidity version ${solcVersion} is not yet supported for coverage instrumentation. Hardhat will try the latest supported version ${latestSupportedVersion} instead.`,
|
|
47
|
+
);
|
|
48
|
+
solcVersion = latestSupportedVersion;
|
|
49
|
+
}
|
|
38
50
|
const { source, metadata } = addStatementCoverageInstrumentation(
|
|
39
51
|
fileContent,
|
|
40
52
|
sourceName,
|
|
@@ -63,13 +75,11 @@ export default async (): Promise<Partial<SolidityHooks>> => ({
|
|
|
63
75
|
fsPath,
|
|
64
76
|
);
|
|
65
77
|
const tag = Buffer.from(m.tag).toString("hex");
|
|
66
|
-
const startLine = lineNumbers[m.startUtf16];
|
|
67
|
-
const endLine = lineNumbers[m.endUtf16 - 1];
|
|
68
78
|
coverageMetadata.push({
|
|
69
79
|
relativePath,
|
|
70
80
|
tag,
|
|
71
|
-
|
|
72
|
-
|
|
81
|
+
startUtf16: m.startUtf16,
|
|
82
|
+
endUtf16: m.endUtf16,
|
|
73
83
|
});
|
|
74
84
|
break;
|
|
75
85
|
default:
|
|
@@ -124,9 +134,10 @@ export default async (): Promise<Partial<SolidityHooks>> => ({
|
|
|
124
134
|
// NOTE: We add the coverage.sol straight into sources here. The alternative
|
|
125
135
|
// would be to do it during the resolution phase. However, we decided this
|
|
126
136
|
// is a simpler solution, at least for now.
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
137
|
+
const packageRoot = await findClosestPackageRoot(import.meta.url);
|
|
138
|
+
const coverageSolPath = path.join(packageRoot, "coverage.sol");
|
|
139
|
+
|
|
140
|
+
const content = await readUtf8File(coverageSolPath);
|
|
130
141
|
solcInput.sources[COVERAGE_LIBRARY_PATH] = { content };
|
|
131
142
|
}
|
|
132
143
|
|