Skip to content

Commit 95ecb1d

Browse files
Merge pull request #7904 from NomicFoundation/suppress-pragma-license-warnings
Suppress pragma+license warning in Solidity test files
2 parents a175880 + 71d8aed commit 95ecb1d

File tree

5 files changed

+373
-62
lines changed

5 files changed

+373
-62
lines changed

.changeset/many-deers-shout.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"hardhat": patch
3+
---
4+
5+
Suppressed pragma and license warnings in Solidity test files ([7894](https://github.com/NomicFoundation/hardhat/issues/7894)).

v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/build-system.ts

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -74,24 +74,10 @@ import {
7474
parseRootPath,
7575
} from "./root-paths-utils.js";
7676
import { SolcConfigSelector } from "./solc-config-selection.js";
77+
import { shouldSuppressWarning } from "./warning-suppression.js";
7778

7879
const log = debug("hardhat:core:solidity:build-system");
7980

80-
// Compiler warnings to suppress from build output.
81-
// Each rule specifies a warning message and the source file it applies to.
82-
// This allows suppressing known warnings from internal files (e.g., console.sol)
83-
// while still showing the same warning type from user code.
84-
export const SUPPRESSED_WARNINGS: Array<{
85-
message: string;
86-
sourceFile: string;
87-
}> = [
88-
{
89-
message:
90-
"Natspec memory-safe-assembly special comment for inline assembly is deprecated and scheduled for removal. Use the memory-safe block annotation instead.",
91-
sourceFile: path.normalize("hardhat/console.sol"),
92-
},
93-
];
94-
9581
interface CompilationResult {
9682
compilationJob: CompilationJob;
9783
compilerOutput: CompilerOutput;
@@ -1155,9 +1141,10 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
11551141

11561142
#shouldSuppressWarning(error: CompilerOutputError): boolean {
11571143
const msg = error.formattedMessage ?? error.message;
1158-
1159-
return SUPPRESSED_WARNINGS.some(
1160-
(rule) => msg.includes(rule.message) && msg.includes(rule.sourceFile),
1144+
return shouldSuppressWarning(
1145+
msg,
1146+
this.#options.solidityTestsPath,
1147+
this.#options.projectRoot,
11611148
);
11621149
}
11631150

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import path from "node:path";
2+
3+
// Compiler warnings to suppress from build output.
4+
// Supports two types of suppression rules:
5+
//
6+
// 1. scope: 'specific-file' - Suppress warnings from specific file paths
7+
// - Use this to suppress known warnings from internal/library files (e.g., console.sol)
8+
// - The same warning type will still be shown for user code
9+
//
10+
// 2. scope: 'test-files' - Suppress warnings from all test files
11+
// - Test files are identified as:
12+
// * Files ending in .t.sol (e.g., Counter.t.sol)
13+
// * Files inside test/contracts/ directory
14+
// (e.g., test/contracts/Example.sol)
15+
// - Use this for warnings that are acceptable in test code but not in production code
16+
// (e.g., missing SPDX license identifiers or pragma statements)
17+
export const SUPPRESSED_WARNINGS: Array<
18+
| {
19+
message: string;
20+
scope: "specific-file";
21+
filePath: string;
22+
}
23+
| {
24+
message: string;
25+
scope: "test-files";
26+
}
27+
> = [
28+
{
29+
message:
30+
"Natspec memory-safe-assembly special comment for inline assembly is deprecated and scheduled for removal. Use the memory-safe block annotation instead.",
31+
scope: "specific-file",
32+
// Normalize to handle different OS path separators
33+
filePath: path.normalize("hardhat/console.sol"),
34+
},
35+
{
36+
message: "SPDX license identifier not provided",
37+
scope: "test-files",
38+
},
39+
{
40+
message: "Source file does not specify required compiler version",
41+
scope: "test-files",
42+
},
43+
];
44+
45+
/**
46+
* Determines if a compiler warning should be suppressed based on the suppression rules.
47+
*
48+
* @param errorMessage - The formatted error message from the compiler
49+
* @param absoluteSolidityTestsPath - Absolute path to the Solidity test directory
50+
* @param absoluteProjectRoot - Absolute path to the project root
51+
* @returns true if the warning should be suppressed, false otherwise
52+
*/
53+
export function shouldSuppressWarning(
54+
errorMessage: string,
55+
absoluteSolidityTestsPath: string,
56+
absoluteProjectRoot: string,
57+
): boolean {
58+
// Compute relative path from project root to test directory.
59+
// Example:
60+
// absoluteSolidityTestsPath: /workspaces/hardhat-4/v-next/example-project/test/contracts
61+
// absoluteProjectRoot: /workspaces/hardhat-4/v-next/example-project
62+
// relativeTestPath: test/contracts/ - note the addition of the `/`
63+
// to avoid partial matches, e.g.: test/contractsUtils/
64+
const relativeTestPath = path.join(
65+
path.relative(absoluteProjectRoot, absoluteSolidityTestsPath),
66+
"/",
67+
);
68+
69+
return SUPPRESSED_WARNINGS.some((rule) => {
70+
if (!errorMessage.includes(rule.message)) {
71+
return false;
72+
}
73+
74+
if (rule.scope === "specific-file") {
75+
return errorMessage.includes(rule.filePath);
76+
}
77+
78+
// Check if the message contains a path to a test file
79+
// Test files are identified by:
80+
// - Ending in .t.sol (e.g., Counter.t.sol)
81+
// - Being inside the configured Solidity test directory
82+
83+
if (/\.t\.sol(:|$|\s)/.test(errorMessage)) {
84+
return true;
85+
}
86+
87+
// Extract file path from error message
88+
// Format: "Warning: message\n --> path/to/file.sol:line:column:"
89+
const pathMatches = errorMessage.match(/-->\s+([^\s:]+\.sol)/);
90+
91+
if (pathMatches !== null) {
92+
return pathMatches[1].includes(relativeTestPath);
93+
}
94+
95+
return false;
96+
});
97+
}

v-next/hardhat/test/internal/builtin-plugins/solidity/build-system/integration/warning-filtering.ts

Lines changed: 94 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@ import type { HardhatUserConfig } from "../../../../../../src/config.js";
33
import assert from "node:assert/strict";
44
import { describe, it } from "node:test";
55

6-
import { SUPPRESSED_WARNINGS } from "../../../../../../src/internal/builtin-plugins/solidity/build-system/build-system.js";
6+
import { SUPPRESSED_WARNINGS } from "../../../../../../src/internal/builtin-plugins/solidity/build-system/warning-suppression.js";
77
import { useTestProjectTemplate } from "../resolver/helpers.js";
88

9+
//
10+
// Integration smoke tests - full coverage is provided by
11+
// unit tests in warning-suppression.ts
12+
//
13+
914
const NATSPEC_MEMORY_SAFE_WARNING = SUPPRESSED_WARNINGS[0].message;
15+
const SPDX_WARNING = SUPPRESSED_WARNINGS[1].message;
16+
const PRAGMA_WARNING = SUPPRESSED_WARNINGS[2].message;
1017

1118
// Project with the warning in a regular contract file (not console.sol)
1219
const projectWithNatspecWarningInRegularFile = {
@@ -44,6 +51,36 @@ library console {
4451
},
4552
};
4653

54+
// Project with missing SPDX/pragma in a .t.sol test file (should suppress warnings)
55+
const projectWithMissingSpdxPragmaInTestFile = {
56+
name: "missing-spdx-pragma-test-file",
57+
version: "1.0.0",
58+
files: {
59+
"contracts/Counter.t.sol": `contract CounterTest {
60+
function testIncrement() public {
61+
// test code
62+
}
63+
}
64+
`,
65+
},
66+
};
67+
68+
// Project with missing SPDX/pragma in a regular contract (should show warnings)
69+
const projectWithMissingSpdxPragmaInRegularFile = {
70+
name: "missing-spdx-pragma-test",
71+
version: "1.0.0",
72+
files: {
73+
"contracts/Counter.sol": `contract Counter {
74+
uint256 public count;
75+
76+
function increment() public {
77+
count += 1;
78+
}
79+
}
80+
`,
81+
},
82+
};
83+
4784
const solidity0833Config: HardhatUserConfig = {
4885
solidity: {
4986
profiles: {
@@ -56,49 +93,62 @@ const solidity0833Config: HardhatUserConfig = {
5693

5794
const noop = () => {};
5895

59-
describe("build system - warning filtering", function () {
60-
it("should NOT filter out the deprecated natspec warning from regular files", async (t) => {
61-
const consoleWarnMock = t.mock.method(console, "warn", noop);
62-
63-
await using project = await useTestProjectTemplate(
64-
projectWithNatspecWarningInRegularFile,
65-
);
66-
67-
const hre = await project.getHRE(solidity0833Config);
68-
69-
await hre.tasks.getTask("build").run({ force: true });
96+
const testScenarios = [
97+
{
98+
name: "should NOT filter out the deprecated natspec warning from regular files",
99+
project: projectWithNatspecWarningInRegularFile,
100+
warnings: [NATSPEC_MEMORY_SAFE_WARNING],
101+
shouldSuppress: false,
102+
},
103+
{
104+
name: "should filter out the deprecated natspec warning from console.sol",
105+
project: projectWithNatspecWarningInConsole,
106+
warnings: [NATSPEC_MEMORY_SAFE_WARNING],
107+
shouldSuppress: true,
108+
},
109+
{
110+
name: "should filter out SPDX/pragma warnings from .t.sol test files (test-files scope)",
111+
project: projectWithMissingSpdxPragmaInTestFile,
112+
warnings: [SPDX_WARNING, PRAGMA_WARNING],
113+
shouldSuppress: true,
114+
},
115+
{
116+
name: "should NOT filter out SPDX/pragma warnings from regular contracts",
117+
project: projectWithMissingSpdxPragmaInRegularFile,
118+
warnings: [SPDX_WARNING, PRAGMA_WARNING],
119+
shouldSuppress: false,
120+
},
121+
];
70122

71-
// Check that the natspec warning WAS printed to console.warn
72-
const warningWasShown = consoleWarnMock.mock.calls.some((call) => {
73-
const message = String(call.arguments[0] ?? "");
74-
return message.includes(NATSPEC_MEMORY_SAFE_WARNING);
123+
describe("build system - warning filtering", function () {
124+
for (const scenario of testScenarios) {
125+
it(scenario.name, async (t) => {
126+
const consoleWarnMock = t.mock.method(console, "warn", noop);
127+
128+
await using project = await useTestProjectTemplate(scenario.project);
129+
130+
const hre = await project.getHRE(solidity0833Config);
131+
132+
await hre.tasks.getTask("build").run({ force: true });
133+
134+
for (const warning of scenario.warnings) {
135+
const warningWasShown = consoleWarnMock.mock.calls.some((call) => {
136+
const message = String(call.arguments[0] ?? "");
137+
return message.includes(warning);
138+
});
139+
140+
if (scenario.shouldSuppress) {
141+
assert.ok(
142+
!warningWasShown,
143+
`Expected warning "${warning}" to be suppressed, but it was shown`,
144+
);
145+
} else {
146+
assert.ok(
147+
warningWasShown,
148+
`Expected warning "${warning}" to be shown, but it was suppressed`,
149+
);
150+
}
151+
}
75152
});
76-
77-
assert.ok(
78-
warningWasShown,
79-
"Expected natspec memory-safe-assembly warning to be shown for regular files",
80-
);
81-
});
82-
83-
it("should filter out the deprecated natspec warning from console.sol", async (t) => {
84-
const consoleWarnMock = t.mock.method(console, "warn", noop);
85-
86-
await using project = await useTestProjectTemplate(
87-
projectWithNatspecWarningInConsole,
88-
);
89-
90-
const hre = await project.getHRE(solidity0833Config);
91-
92-
await hre.tasks.getTask("build").run({ force: true });
93-
94-
// Check that the natspec warning was NOT printed to console.warn
95-
for (const call of consoleWarnMock.mock.calls) {
96-
const message = String(call.arguments[0] ?? "");
97-
98-
assert.ok(
99-
!message.includes(NATSPEC_MEMORY_SAFE_WARNING),
100-
`Expected natspec memory-safe-assembly warning to be filtered out for console.sol, but found: ${message}`,
101-
);
102-
}
103-
});
153+
}
104154
});

0 commit comments

Comments
 (0)