hardhat 3.1.3 → 3.1.5

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 (65) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/src/internal/builtin-plugins/coverage/coverage-manager.d.ts +14 -14
  3. package/dist/src/internal/builtin-plugins/coverage/coverage-manager.d.ts.map +1 -1
  4. package/dist/src/internal/builtin-plugins/coverage/coverage-manager.js +66 -110
  5. package/dist/src/internal/builtin-plugins/coverage/coverage-manager.js.map +1 -1
  6. package/dist/src/internal/builtin-plugins/coverage/hook-handlers/solidity.d.ts.map +1 -1
  7. package/dist/src/internal/builtin-plugins/coverage/hook-handlers/solidity.js +13 -6
  8. package/dist/src/internal/builtin-plugins/coverage/hook-handlers/solidity.js.map +1 -1
  9. package/dist/src/internal/builtin-plugins/coverage/process-coverage.d.ts +21 -0
  10. package/dist/src/internal/builtin-plugins/coverage/process-coverage.d.ts.map +1 -0
  11. package/dist/src/internal/builtin-plugins/coverage/process-coverage.js +291 -0
  12. package/dist/src/internal/builtin-plugins/coverage/process-coverage.js.map +1 -0
  13. package/dist/src/internal/builtin-plugins/coverage/reports/html.d.ts +3 -0
  14. package/dist/src/internal/builtin-plugins/coverage/reports/html.d.ts.map +1 -0
  15. package/dist/src/internal/builtin-plugins/coverage/reports/html.js +37 -0
  16. package/dist/src/internal/builtin-plugins/coverage/reports/html.js.map +1 -0
  17. package/dist/src/internal/builtin-plugins/coverage/types.d.ts +9 -3
  18. package/dist/src/internal/builtin-plugins/coverage/types.d.ts.map +1 -1
  19. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.d.ts +1 -3
  20. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.d.ts.map +1 -1
  21. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js +4 -1
  22. package/dist/src/internal/builtin-plugins/solidity/build-system/build-system.js.map +1 -1
  23. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/compiler.d.ts +1 -7
  24. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/compiler.d.ts.map +1 -1
  25. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/compiler.js.map +1 -1
  26. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.d.ts +1 -1
  27. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.d.ts.map +1 -1
  28. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/index.d.ts +1 -1
  29. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/index.d.ts.map +1 -1
  30. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/index.js.map +1 -1
  31. package/dist/src/internal/builtin-plugins/solidity/type-extensions.d.ts +23 -2
  32. package/dist/src/internal/builtin-plugins/solidity/type-extensions.d.ts.map +1 -1
  33. package/dist/src/internal/builtin-plugins/solidity/type-extensions.js.map +1 -1
  34. package/dist/src/internal/builtin-plugins/solidity-test/reporter.d.ts.map +1 -1
  35. package/dist/src/internal/builtin-plugins/solidity-test/reporter.js +66 -82
  36. package/dist/src/internal/builtin-plugins/solidity-test/reporter.js.map +1 -1
  37. package/dist/src/types/solidity/build-system.d.ts +1 -1
  38. package/dist/src/types/solidity/build-system.d.ts.map +1 -1
  39. package/dist/src/types/solidity/compiler.d.ts +9 -0
  40. package/dist/src/types/solidity/compiler.d.ts.map +1 -0
  41. package/dist/src/types/solidity/compiler.js +2 -0
  42. package/dist/src/types/solidity/compiler.js.map +1 -0
  43. package/dist/src/types/solidity.d.ts +1 -0
  44. package/dist/src/types/solidity.d.ts.map +1 -1
  45. package/dist/src/types/solidity.js +1 -0
  46. package/dist/src/types/solidity.js.map +1 -1
  47. package/package.json +3 -2
  48. package/src/internal/builtin-plugins/coverage/coverage-manager.ts +121 -176
  49. package/src/internal/builtin-plugins/coverage/hook-handlers/solidity.ts +19 -8
  50. package/src/internal/builtin-plugins/coverage/process-coverage.ts +442 -0
  51. package/src/internal/builtin-plugins/coverage/reports/html.ts +56 -0
  52. package/src/internal/builtin-plugins/coverage/types.ts +8 -3
  53. package/src/internal/builtin-plugins/solidity/build-system/build-system.ts +14 -6
  54. package/src/internal/builtin-plugins/solidity/build-system/compiler/compiler.ts +1 -9
  55. package/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.ts +1 -1
  56. package/src/internal/builtin-plugins/solidity/build-system/compiler/index.ts +3 -1
  57. package/src/internal/builtin-plugins/solidity/type-extensions.ts +39 -2
  58. package/src/internal/builtin-plugins/solidity-test/reporter.ts +85 -104
  59. package/src/types/solidity/build-system.ts +1 -1
  60. package/src/types/solidity/compiler.ts +10 -0
  61. package/src/types/solidity.ts +1 -0
  62. package/templates/hardhat-2/04-mocha-viem-ts/package.json +1 -1
  63. package/templates/hardhat-3/01-node-test-runner-viem/package.json +8 -8
  64. package/templates/hardhat-3/02-mocha-ethers/package.json +5 -5
  65. package/templates/hardhat-3/03-minimal/package.json +1 -1
@@ -0,0 +1,442 @@
1
+ import type {
2
+ CoverageMetadata,
3
+ ReportCoverageStatement,
4
+ Statement,
5
+ } from "./types.js";
6
+
7
+ import chalk from "chalk";
8
+
9
+ // Use constants for the Uint8Array to improve memory usage (1 byte vs 8 bytes per item)
10
+ const STATUS_NOT_EXECUTED = 0; // equivalent to false
11
+ const STATUS_EXECUTED = 1; // equivalent to true
12
+ const STATUS_IGNORED = 2; // equivalent to null
13
+
14
+ /**
15
+ * Processes the raw EDR coverage information for a file and returns the executed and
16
+ * non-executed statements and lines.
17
+ *
18
+ * @param fileContent The original file content being analyzed
19
+ * @param metadata Coverage metadata received from EDR for this file
20
+ * @param hitTags The coverage tags recorded as executed during the test run
21
+ * for this specific file.
22
+ *
23
+ * @returns An object containing:
24
+ * - statements: the executed and not-executed statements
25
+ * - lines: the executed and not-executed line numbers
26
+ */
27
+ export function getProcessedCoverageInfo(
28
+ fileContent: string,
29
+ metadata: CoverageMetadata,
30
+ hitTags: Set<string>,
31
+ ): {
32
+ lines: {
33
+ executed: Map<number, string>;
34
+ unexecuted: Map<number, string>;
35
+ };
36
+ } {
37
+ const statementsByExecution = partitionStatementsByExecution(
38
+ metadata,
39
+ hitTags,
40
+ );
41
+
42
+ const { start, end } = getCoverageBounds(
43
+ statementsByExecution,
44
+ fileContent.length,
45
+ );
46
+
47
+ const characterCoverage = buildCharacterCoverage(
48
+ fileContent,
49
+ start,
50
+ end,
51
+ statementsByExecution.unexecuted,
52
+ );
53
+
54
+ // printStatementsForDebugging(fileContent, statementsByExecution);
55
+ // printCharacterCoverageForDebugging(fileContent, characterCoverage);
56
+
57
+ return {
58
+ lines: partitionLinesByExecution(fileContent, characterCoverage),
59
+ };
60
+ }
61
+
62
+ function partitionStatementsByExecution(
63
+ metadata: CoverageMetadata,
64
+ hitTags: Set<string>,
65
+ ): {
66
+ executed: Statement[];
67
+ unexecuted: Statement[];
68
+ } {
69
+ const executed: Statement[] = [];
70
+ const unexecuted: Statement[] = [];
71
+
72
+ for (const node of metadata) {
73
+ if (hitTags.has(node.tag)) {
74
+ executed.push(node);
75
+ } else {
76
+ unexecuted.push(node);
77
+ }
78
+ }
79
+
80
+ return {
81
+ executed,
82
+ unexecuted,
83
+ };
84
+ }
85
+
86
+ // Determine the minimum and maximum character indexes that define the range
87
+ // where coverage should be calculated, excluding parts that the tests never
88
+ // reach. This removes irrelevant sections from coverage analysis, such as the
89
+ // beginning or end of each Solidity file.
90
+ // Example:
91
+ // // SPDX-License-Identifier: MIT
92
+ // pragma solidity ^0.8.0;
93
+ function getCoverageBounds(
94
+ statementsByExecution: {
95
+ executed: Statement[];
96
+ unexecuted: Statement[];
97
+ },
98
+ fileLength: number,
99
+ ): { start: number; end: number } {
100
+ let start = fileLength;
101
+ let end = 0;
102
+
103
+ const { executed, unexecuted } = statementsByExecution;
104
+
105
+ for (const s of [...executed, ...unexecuted]) {
106
+ if (s.startUtf16 < start) {
107
+ start = s.startUtf16;
108
+ }
109
+
110
+ if (s.endUtf16 > end) {
111
+ end = s.endUtf16;
112
+ }
113
+ }
114
+
115
+ return { start, end };
116
+ }
117
+
118
+ // Return an array with the same length as the file content. Each position in
119
+ // the array corresponds to a character in the file. The value at each position
120
+ // indicates whether that character was executed during tests (STATUS_EXECUTED),
121
+ // not executed (STATUS_NOT_EXECUTED), or not relevant for coverage
122
+ // (STATUS_IGNORED). STATUS_IGNORED is used to indicate characters that are not
123
+ // executed during test, such as those found at the start or end of every Solidity file.
124
+ // Example:
125
+ // // SPDX-License-Identifier: MIT
126
+ // pragma solidity ^0.8.0;
127
+ function buildCharacterCoverage(
128
+ fileContent: string,
129
+ start: number,
130
+ end: number,
131
+ unexecutedStatements: Statement[],
132
+ ): Uint8Array {
133
+ // Use Uint8Array instead of Array<null | boolean> for memory efficiency.
134
+ // We initialize with STATUS_IGNORED (equivalent to filling with null)
135
+ const characterCoverage = new Uint8Array(fileContent.length).fill(
136
+ STATUS_IGNORED,
137
+ );
138
+
139
+ // Initially mark all characters that may be executed during the tests as
140
+ // STATUS_EXECUTED. They will be set to false later if they are not executed.
141
+ // The coverage statement received from EDR will provide this information.
142
+ // Setting everything to true first simplifies the logic for extracting
143
+ // coverage data. Starting with false and toggling only covered characters
144
+ // would make statement processing more complex.
145
+ for (let i = start; i < end; i++) {
146
+ if (charMustBeIgnored(fileContent[i])) {
147
+ continue;
148
+ }
149
+
150
+ characterCoverage[i] = STATUS_EXECUTED;
151
+ }
152
+
153
+ for (const statement of unexecutedStatements) {
154
+ for (let i = statement.startUtf16; i < statement.endUtf16; i++) {
155
+ if (characterCoverage[i] === STATUS_IGNORED) {
156
+ continue;
157
+ }
158
+
159
+ characterCoverage[i] = STATUS_NOT_EXECUTED;
160
+ }
161
+ }
162
+
163
+ markNonExecutablePatterns(fileContent, characterCoverage);
164
+
165
+ return characterCoverage;
166
+ }
167
+
168
+ // The following characters are not relevant for the code coverage, so they
169
+ // must be ignored when marking characters as executed (true) or not executed (false)
170
+ function charMustBeIgnored(char: string): boolean {
171
+ const code = char.charCodeAt(0);
172
+
173
+ return (
174
+ code === 32 || // Space
175
+ (code >= 9 && code <= 13) || // \t (9), \n (10), \v (11), \f (12), \r (13)
176
+ code === 123 || // {
177
+ code === 125 // }
178
+ );
179
+ }
180
+
181
+ // Mark different types of substrings as not relevant for code coverage (set them to STATUS_IGNORED).
182
+ // For example, all "else" substrings or comments are not relevant for code coverage.
183
+ function markNonExecutablePatterns(
184
+ fileContent: string,
185
+ characterCoverage: Uint8Array,
186
+ ) {
187
+ // Comments that start with //
188
+ markMatchingCharsWithIgnored(
189
+ fileContent,
190
+ characterCoverage,
191
+ /\/\/.*?(?=\n|$)/g,
192
+ );
193
+
194
+ // Comments wrapped between /* and */
195
+ markMatchingCharsWithIgnored(
196
+ fileContent,
197
+ characterCoverage,
198
+ /\/\*[\s\S]*?\*\//g,
199
+ );
200
+
201
+ // Lines containing `else`, since they do not represent executable code by themselves.
202
+ // Keep in mind that `else` is preceded by a closing brace and followed by an opening brace.
203
+ // This can span multiple lines.
204
+ markMatchingCharsWithIgnored(
205
+ fileContent,
206
+ characterCoverage,
207
+ /\}\s*\belse\b\s*\{/g,
208
+ );
209
+
210
+ // Lines containing the function signature. This can span multiple lines
211
+ markMatchingCharsWithIgnored(
212
+ fileContent,
213
+ characterCoverage,
214
+ /^\s*(function\s+[A-Za-z_$][A-Za-z0-9_$]*\s*\([\s\S]*?\)[^{]*?)(?=\s*\{)/gm,
215
+ );
216
+
217
+ // Lines containing the catch signature. This can span multiple lines.
218
+ markMatchingCharsWithIgnored(
219
+ fileContent,
220
+ characterCoverage,
221
+ /\bcatch\b(?:\s+[A-Za-z_][A-Za-z0-9_]*)?\s*(?:\([\s\S]*?\))?(?=\s*\{)/g,
222
+ );
223
+ }
224
+
225
+ function markMatchingCharsWithIgnored(
226
+ fileContent: string,
227
+ characterCoverage: Uint8Array,
228
+ regex: RegExp,
229
+ ) {
230
+ for (const match of fileContent.matchAll(regex)) {
231
+ for (let i = match.index; i < match.index + match[0].length; i++) {
232
+ characterCoverage[i] = STATUS_IGNORED;
233
+ }
234
+ }
235
+ }
236
+
237
+ // Generate non overlapping coverage statements. Every character between the start
238
+ // and end indexes is guaranteed to be either executed or not executed.
239
+ // Unlike the statements received from EDR, which may include nested sub
240
+ // statements, these are processed and have no sub statements or overlapping statements.
241
+ // Example:
242
+ // [
243
+ // { start: 12, end: 20, executed: true },
244
+ // { start: 15, end: 17, executed: true }
245
+ // ]
246
+ // ->
247
+ // [
248
+ // { start: 12, end: 20, executed: true }
249
+ // ]
250
+ /* eslint-disable-next-line @typescript-eslint/no-unused-vars
251
+ -- currently not used, but it will be used to create more detailed.
252
+ It will be used to return statements from the function `getProcessedCoverageInfo` */
253
+ function getProcessedExecutedStatements(characterCoverage: Uint8Array): {
254
+ executed: ReportCoverageStatement[];
255
+ unexecuted: ReportCoverageStatement[];
256
+ } {
257
+ const executed = generateProcessedStatements(
258
+ characterCoverage,
259
+ STATUS_EXECUTED,
260
+ );
261
+ const unexecuted = generateProcessedStatements(
262
+ characterCoverage,
263
+ STATUS_NOT_EXECUTED,
264
+ );
265
+
266
+ return {
267
+ executed,
268
+ unexecuted,
269
+ };
270
+ }
271
+
272
+ // Based on the marked file, where each character is marked as either
273
+ // executed (STATUS_EXECUTED) or not executed (STATUS_NOT_EXECUTED),
274
+ // generate non-overlapping statements that indicate the start and
275
+ // end indices, along with whether they were executed or not.
276
+ function generateProcessedStatements(
277
+ characterCoverage: Uint8Array,
278
+ targetStatus: number,
279
+ ): ReportCoverageStatement[] {
280
+ const ranges: ReportCoverageStatement[] = [];
281
+ const fileLength = characterCoverage.length;
282
+
283
+ let start = -1;
284
+
285
+ for (let i = 0; i < fileLength; i++) {
286
+ if (characterCoverage[i] === targetStatus) {
287
+ if (start === -1) {
288
+ start = i; // begin new range
289
+ }
290
+ } else {
291
+ if (start !== -1) {
292
+ // Map back to boolean for the output object
293
+ ranges.push({
294
+ startUtf16: start,
295
+ endUtf16: i - 1,
296
+ executed: targetStatus === STATUS_EXECUTED,
297
+ });
298
+ start = -1;
299
+ }
300
+ }
301
+ }
302
+
303
+ // close last range if file ends inside a run
304
+ if (start !== -1) {
305
+ ranges.push({
306
+ startUtf16: start,
307
+ endUtf16: fileLength - 1,
308
+ executed: targetStatus === STATUS_EXECUTED,
309
+ });
310
+ }
311
+
312
+ return ranges;
313
+ }
314
+
315
+ // Return the executed and non executed lines based on the marked file.
316
+ // Some lines are excluded from the execution count, for example, comments.
317
+ function partitionLinesByExecution(
318
+ fileContent: string,
319
+ characterCoverage: Uint8Array,
320
+ ): {
321
+ executed: Map<number, string>;
322
+ unexecuted: Map<number, string>;
323
+ } {
324
+ const executed: Map<number, string> = new Map();
325
+ const unexecuted: Map<number, string> = new Map();
326
+
327
+ let lineStart = 0;
328
+ let isLineIgnored = true;
329
+ let isLineExecutedOrIgnored = true;
330
+ let lineNumber = 1; // File lines start at 1
331
+
332
+ // Helper to process a line and push to correct map
333
+ const processLine = (endIndex: number) => {
334
+ // Only process if the line isn't entirely ignored
335
+ if (!isLineIgnored) {
336
+ const lineText = fileContent.slice(lineStart, endIndex);
337
+
338
+ if (isLineExecutedOrIgnored) {
339
+ executed.set(lineNumber, lineText);
340
+ } else {
341
+ unexecuted.set(lineNumber, lineText);
342
+ }
343
+ }
344
+
345
+ // Reset state for the next line
346
+ lineStart = endIndex + 1;
347
+ isLineIgnored = true;
348
+ isLineExecutedOrIgnored = true;
349
+ lineNumber++;
350
+ };
351
+
352
+ for (let i = 0; i < fileContent.length; i++) {
353
+ const char = fileContent[i];
354
+ const status = characterCoverage[i];
355
+
356
+ if (status !== STATUS_IGNORED) {
357
+ isLineIgnored = false;
358
+ }
359
+ if (status === STATUS_NOT_EXECUTED) {
360
+ isLineExecutedOrIgnored = false;
361
+ }
362
+
363
+ if (char === "\n") {
364
+ processLine(i);
365
+ }
366
+ }
367
+
368
+ // Handle the final line if the file doesn't end in a newline
369
+ if (lineStart < fileContent.length) {
370
+ processLine(fileContent.length);
371
+ }
372
+
373
+ return { executed, unexecuted };
374
+ }
375
+
376
+ // Enable this function while debugging to display the coverage for a file.
377
+ // The file will be printed with green characters when they are executed,
378
+ // red characters when they are not executed,
379
+ // and gray characters when they are irrelevant for code coverage.
380
+ /* eslint-disable-next-line @typescript-eslint/no-unused-vars
381
+ -- this function can be enabled for debugging purposes */
382
+ function printStatementsForDebugging(
383
+ fileContent: string,
384
+ statementsByExecution: {
385
+ executed: Statement[];
386
+ unexecuted: Statement[];
387
+ },
388
+ ): void {
389
+ const relativePath =
390
+ statementsByExecution.executed.length > 0
391
+ ? statementsByExecution.executed[0].relativePath
392
+ : statementsByExecution.unexecuted[0].relativePath;
393
+
394
+ console.debug("Statements for file: " + relativePath);
395
+
396
+ console.debug("Executed statements:");
397
+ let counter = 0;
398
+ for (const statement of statementsByExecution.executed) {
399
+ console.debug(counter++ + " ---");
400
+
401
+ for (let i = statement.startUtf16; i < statement.endUtf16; i++) {
402
+ process.stdout.write(chalk.gray(fileContent[i]));
403
+ }
404
+
405
+ console.debug();
406
+ }
407
+
408
+ console.debug();
409
+
410
+ console.debug("Unexecuted statements:");
411
+ counter = 0;
412
+ for (const statement of statementsByExecution.unexecuted) {
413
+ console.debug(counter++ + " ---");
414
+
415
+ for (let i = statement.startUtf16; i < statement.endUtf16; i++) {
416
+ process.stdout.write(chalk.gray(fileContent[i]));
417
+ }
418
+
419
+ console.debug();
420
+ }
421
+ }
422
+
423
+ // Enable this function while debugging to display the coverage for a file.
424
+ // The file will be printed with green characters when they are executed,
425
+ // red characters when they are not executed,
426
+ // and gray characters when they are irrelevant for code coverage.
427
+ /* eslint-disable-next-line @typescript-eslint/no-unused-vars
428
+ -- this function can be enabled for debugging purposes */
429
+ function printCharacterCoverageForDebugging(
430
+ fileContent: string,
431
+ characterCoverage: Uint8Array,
432
+ ): void {
433
+ for (let i = 0; i < characterCoverage.length; i++) {
434
+ if (characterCoverage[i] === STATUS_IGNORED) {
435
+ process.stdout.write(chalk.gray(fileContent[i]));
436
+ } else if (characterCoverage[i] === STATUS_EXECUTED) {
437
+ process.stdout.write(chalk.green(fileContent[i]));
438
+ } else {
439
+ process.stdout.write(chalk.red(fileContent[i]));
440
+ }
441
+ }
442
+ }
@@ -0,0 +1,56 @@
1
+ import type { Report } from "../coverage-manager.js";
2
+ import type { FileCoverageData } from "@nomicfoundation/hardhat-vendored/coverage/types";
3
+
4
+ import path from "node:path";
5
+
6
+ import { mkdir } from "@nomicfoundation/hardhat-utils/fs";
7
+ import {
8
+ istanbulLibCoverage,
9
+ istanbulLibReport,
10
+ istanbulReports,
11
+ } from "@nomicfoundation/hardhat-vendored/coverage";
12
+
13
+ export async function generateHtmlReport(
14
+ report: Report,
15
+ htmlReportPath: string,
16
+ ): Promise<void> {
17
+ const baseDir = process.cwd();
18
+ const coverageMap = istanbulLibCoverage.createCoverageMap({});
19
+
20
+ await mkdir(htmlReportPath);
21
+
22
+ // Construct coverage data for each tested file,
23
+ // detailing whether each line was executed or not.
24
+ for (const [p, coverageInfo] of Object.entries(report)) {
25
+ const testedFilePath = path.join(baseDir, p);
26
+
27
+ const fc: FileCoverageData = {
28
+ path: testedFilePath,
29
+ statementMap: {},
30
+ fnMap: {}, // Cannot be derived from current report data
31
+ branchMap: {}, // Cannot be derived from current report data
32
+ s: {},
33
+ f: {}, // Cannot be derived from current report data
34
+ b: {},
35
+ };
36
+
37
+ for (const [line, count] of coverageInfo.lineExecutionCounts) {
38
+ fc.statementMap[line] = {
39
+ start: { line, column: 0 },
40
+ end: { line, column: 0 },
41
+ };
42
+
43
+ // TODO: currently EDR does not provide per-statement coverage counts
44
+ fc.s[line] = count > 0 ? 1 : 0; // mark as covered if hit at least once
45
+ }
46
+
47
+ coverageMap.addFileCoverage(fc);
48
+ }
49
+
50
+ const context = istanbulLibReport.createContext({
51
+ dir: htmlReportPath,
52
+ coverageMap,
53
+ });
54
+
55
+ istanbulReports.create("html", undefined).execute(context);
56
+ }
@@ -1,9 +1,14 @@
1
- export type Tag = string;
1
+ type Tag = string;
2
2
  export interface Statement {
3
3
  relativePath: string;
4
4
  tag: Tag;
5
- startLine: number;
6
- endLine: number;
5
+ startUtf16: number;
6
+ endUtf16: number;
7
+ }
8
+ export interface ReportCoverageStatement {
9
+ startUtf16: number;
10
+ endUtf16: number;
11
+ executed: boolean;
7
12
  }
8
13
  export type CoverageMetadata = Statement[];
9
14
  export type CoverageData = Tag[];
@@ -1,5 +1,4 @@
1
1
  import type { CompileCache } from "./cache.js";
2
- import type { Compiler } from "./compiler/compiler.js";
3
2
  import type { DependencyGraphImplementation } from "./dependency-graph.js";
4
3
  import type { Artifact } from "../../../../types/artifacts.js";
5
4
  import type { SolcConfig, SolidityConfig } from "../../../../types/config.js";
@@ -17,12 +16,13 @@ import type {
17
16
  RunCompilationJobResult,
18
17
  BuildScope,
19
18
  } from "../../../../types/solidity/build-system.js";
20
- import type { CompilationJob } from "../../../../types/solidity/compilation-job.js";
21
19
  import type {
20
+ CompilationJob,
21
+ Compiler,
22
22
  CompilerOutput,
23
23
  CompilerOutputError,
24
- } from "../../../../types/solidity/compiler-io.js";
25
- import type { SolidityBuildInfo } from "../../../../types/solidity.js";
24
+ SolidityBuildInfo,
25
+ } from "../../../../types/solidity.js";
26
26
 
27
27
  import os from "node:os";
28
28
  import path from "node:path";
@@ -633,9 +633,17 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
633
633
  "The long version of the compiler should match the long version of the compilation job",
634
634
  );
635
635
 
636
- const output = await compiler.compile(
637
- await runnableCompilationJob.getSolcInput(),
636
+ const input = await runnableCompilationJob.getSolcInput();
637
+
638
+ const output = await this.#hooks.runHandlerChain(
639
+ "solidity",
640
+ "invokeSolc",
641
+ [compiler, input, runnableCompilationJob.solcConfig],
642
+ async (_context, nextCompiler, nextSolcInput) => {
643
+ return nextCompiler.compile(nextSolcInput);
644
+ },
638
645
  );
646
+
639
647
  return { output, compiler };
640
648
  }
641
649
 
@@ -2,6 +2,7 @@ import type {
2
2
  CompilerInput,
3
3
  CompilerOutput,
4
4
  } from "../../../../../types/solidity/compiler-io.js";
5
+ import type { Compiler } from "../../../../../types/solidity/compiler.js";
5
6
 
6
7
  import { spawn } from "node:child_process";
7
8
  import fsPromises from "node:fs/promises";
@@ -23,15 +24,6 @@ import {
23
24
  import { createNonClosingWriter } from "@nomicfoundation/hardhat-utils/stream";
24
25
  import * as semver from "semver";
25
26
 
26
- export interface Compiler {
27
- readonly version: string;
28
- readonly longVersion: string;
29
- readonly compilerPath: string;
30
- readonly isSolcJs: boolean;
31
-
32
- compile(input: CompilerInput): Promise<CompilerOutput>;
33
- }
34
-
35
27
  /**
36
28
  * Spawns a compilation process and returns its output.
37
29
  *
@@ -1,4 +1,4 @@
1
- import type { Compiler } from "./compiler.js";
1
+ import type { Compiler } from "../../../../../../src/types/solidity.js";
2
2
 
3
3
  import { execFile } from "node:child_process";
4
4
  import os from "node:os";
@@ -1,3 +1,5 @@
1
+ import type { Compiler } from "../../../../../types/solidity.js";
2
+
1
3
  import { execFile } from "node:child_process";
2
4
  import path from "node:path";
3
5
  import { pathToFileURL } from "node:url";
@@ -11,7 +13,7 @@ import { exists, isBinaryFile } from "@nomicfoundation/hardhat-utils/fs";
11
13
  import { getCacheDir } from "@nomicfoundation/hardhat-utils/global-dir";
12
14
  import debug from "debug";
13
15
 
14
- import { NativeCompiler, SolcJsCompiler, type Compiler } from "./compiler.js";
16
+ import { NativeCompiler, SolcJsCompiler } from "./compiler.js";
15
17
  import {
16
18
  CompilerDownloaderImplementation,
17
19
  CompilerPlatform,
@@ -1,5 +1,10 @@
1
+ import type { SolcConfig } from "../../../types/config.js";
1
2
  import type { SolidityBuildSystem } from "../../../types/solidity/build-system.js";
2
- import type { CompilerInput } from "../../../types/solidity.js";
3
+ import type {
4
+ Compiler,
5
+ CompilerInput,
6
+ CompilerOutput,
7
+ } from "../../../types/solidity.js";
3
8
 
4
9
  import "../../../types/config.js";
5
10
  declare module "../../../types/config.js" {
@@ -123,7 +128,10 @@ declare module "../../../types/hooks.js" {
123
128
  ) => Promise<void>;
124
129
 
125
130
  /**
126
- * Hook triggered within the compilation job when its' solc input is first constructed.
131
+ * Hook triggered to preprocess a Solidity file and manipulate its contents
132
+ * before it is passed along for compilation. Only files directly under
133
+ * the Hardhat project are preprocessed, Solidity files from npm
134
+ * dependencies are not included.
127
135
  *
128
136
  * @param context The hook context.
129
137
  * @param inputSourceName The input source name of the project file.
@@ -177,5 +185,34 @@ declare module "../../../types/hooks.js" {
177
185
  nextAbsolutePath: string,
178
186
  ) => Promise<string>,
179
187
  ) => Promise<string>;
188
+
189
+ /**
190
+ * Hook triggered to invoke a passed in Solc compiler on the
191
+ * Solc input generated for a given compilation job.
192
+ * This hook allows for manipulating the Solc input passed into the Solc
193
+ * compiler Hardhat has selected for the compilation job, and similarly to
194
+ * manipulate the Solc output.
195
+ *
196
+ * @param context The hook context.
197
+ * @param compile The Solc compiler selected by Hardhat for this compilation
198
+ * job.
199
+ * @param solcInput The solc input json constructed from the compilation
200
+ * job.
201
+ * @param solcConfig The configuration used to setup solc e.g. version.
202
+ * @param next A function to call the next handler for this hook, or the
203
+ * default implementation if no more handlers exist.
204
+ */
205
+ invokeSolc(
206
+ context: HookContext,
207
+ compiler: Compiler,
208
+ solcInput: CompilerInput,
209
+ solcConfig: SolcConfig,
210
+ next: (
211
+ nextContext: HookContext,
212
+ nextCompiler: Compiler,
213
+ nextSolcInput: CompilerInput,
214
+ nextSolcConfig: SolcConfig,
215
+ ) => Promise<CompilerOutput>,
216
+ ): Promise<CompilerOutput>;
180
217
  }
181
218
  }