diff --git a/src/core.test.ts b/src/core.test.ts index b9f8202..8599313 100644 --- a/src/core.test.ts +++ b/src/core.test.ts @@ -15,13 +15,14 @@ test('snapshot', async t => { const rootRelativeSourcesPath = path.relative(hre.config.paths.root, hre.config.paths.sources); const sourcesPathPrefix = path.normalize(rootRelativeSourcesPath + '/'); const include = (sourceName: string) => sourceName.startsWith(sourcesPathPrefix); + const exclude = (sourceName: string) => false; const [bip] = await hre.artifacts.getBuildInfoPaths(); const bi: BuildInfo = JSON.parse(await fs.readFile(bip!, 'utf8')); const config = { paths: hre.config.paths, exposed: { ...baseConfig, initializers: false } }; - const exposed = getExposed(bi.output, include, config); + const exposed = getExposed(bi.output, include, exclude, config); const exposedFiles = [...exposed.values()].sort((a, b) => a.absolutePath.localeCompare(b.absolutePath)) - for (const rf of exposedFiles) { + for (const rf of exposedFiles) { t.snapshot(rf.content.rawContent); } }); @@ -30,7 +31,7 @@ test('snapshot initializers', async t => { const [bip] = await hre.artifacts.getBuildInfoPaths(); const bi: BuildInfo = JSON.parse(await fs.readFile(bip!, 'utf8')); const config = { paths: hre.config.paths, exposed: { ...baseConfig, initializers: true } }; - const exposed = getExposed(bi.output, sourceName => sourceName === 'contracts/Initializers.sol', config); + const exposed = getExposed(bi.output, sourceName => sourceName === 'contracts/Initializers.sol', () => false, config); const exposedFiles = [...exposed.values()].sort((a, b) => a.absolutePath.localeCompare(b.absolutePath)) for (const rf of exposedFiles) { t.snapshot(rf.content.rawContent); @@ -41,9 +42,22 @@ test('snapshot imports', async t => { const [bip] = await hre.artifacts.getBuildInfoPaths(); const bi: BuildInfo = JSON.parse(await fs.readFile(bip!, 'utf8')); const config = { paths: hre.config.paths, exposed: { ...baseConfig, initializers: false, imports: true } }; - const exposed = getExposed(bi.output, sourceName => sourceName === 'contracts/Imported.sol', config); + const exposed = getExposed(bi.output, sourceName => sourceName === 'contracts/Imported.sol', () => false, config); const exposedFiles = [...exposed.values()].sort((a, b) => a.absolutePath.localeCompare(b.absolutePath)) - for (const rf of exposedFiles) { + for (const rf of exposedFiles) { + const absolutePath = path.relative(process.cwd(), rf.absolutePath); + const { rawContent } = rf.content; + t.snapshot({ absolutePath, rawContent }); + } +}); + +test('snapshot exluded imports', async t => { + const [bip] = await hre.artifacts.getBuildInfoPaths(); + const bi: BuildInfo = JSON.parse(await fs.readFile(bip!, 'utf8')); + const config = { paths: hre.config.paths, exposed: { ...baseConfig, initializers: false, imports: true } }; + const exposed = getExposed(bi.output, sourceName => sourceName === 'contracts/Imported.sol', sourceName => sourceName =='contracts/Imported2.sol', config); + const exposedFiles = [...exposed.values()].sort((a, b) => a.absolutePath.localeCompare(b.absolutePath)) + for (const rf of exposedFiles) { const absolutePath = path.relative(process.cwd(), rf.absolutePath); const { rawContent } = rf.content; t.snapshot({ absolutePath, rawContent }); diff --git a/src/core.test.ts.md b/src/core.test.ts.md index 744bb8f..2be2e74 100644 --- a/src/core.test.ts.md +++ b/src/core.test.ts.md @@ -1155,3 +1155,88 @@ Generated by [AVA](https://avajs.dev). }␊ `, } + +## snapshot exluded imports + +> Snapshot 1 + + { + absolutePath: 'contracts-exposed/$_/@openzeppelin/contracts/proxy/Clones.sol', + rawContent: `// SPDX-License-Identifier: UNLICENSED␊ + ␊ + pragma solidity >=0.6.0;␊ + ␊ + import "@openzeppelin/contracts/proxy/Clones.sol";␊ + ␊ + contract $Clones {␊ + bytes32 public constant __hh_exposed_bytecode_marker = "hardhat-exposed";␊ + ␊ + event return$clone(address instance);␊ + ␊ + event return$cloneDeterministic(address instance);␊ + ␊ + constructor() payable {␊ + }␊ + ␊ + function $clone(address implementation) external payable returns (address instance) {␊ + (instance) = Clones.clone(implementation);␊ + emit return$clone(instance);␊ + }␊ + ␊ + function $cloneDeterministic(address implementation,bytes32 salt) external payable returns (address instance) {␊ + (instance) = Clones.cloneDeterministic(implementation,salt);␊ + emit return$cloneDeterministic(instance);␊ + }␊ + ␊ + function $predictDeterministicAddress(address implementation,bytes32 salt,address deployer) external pure returns (address predicted) {␊ + (predicted) = Clones.predictDeterministicAddress(implementation,salt,deployer);␊ + }␊ + ␊ + function $predictDeterministicAddress(address implementation,bytes32 salt) external view returns (address predicted) {␊ + (predicted) = Clones.predictDeterministicAddress(implementation,salt);␊ + }␊ + ␊ + receive() external payable {}␊ + }␊ + `, + } + +> Snapshot 2 + + { + absolutePath: 'contracts-exposed/Imported.sol', + rawContent: `// SPDX-License-Identifier: UNLICENSED␊ + ␊ + pragma solidity >=0.6.0;␊ + ␊ + import "../contracts/Imported.sol";␊ + import "../contracts/Imported2.sol";␊ + import "@openzeppelin/contracts/proxy/Clones.sol";␊ + ␊ + contract $NotImported is NotImported {␊ + bytes32 public constant __hh_exposed_bytecode_marker = "hardhat-exposed";␊ + ␊ + constructor() payable {␊ + }␊ + ␊ + receive() external payable {}␊ + }␊ + ␊ + contract $Imported is Imported {␊ + bytes32 public constant __hh_exposed_bytecode_marker = "hardhat-exposed";␊ + ␊ + constructor() payable {␊ + }␊ + ␊ + function $_testNotImported(NotImported ni) external {␊ + super._testNotImported(ni);␊ + }␊ + ␊ + function $_testNotImported2(NotImported2 ni) external {␊ + super._testNotImported2(ni);␊ + }␊ + ␊ + receive() external payable {}␊ + }␊ + `, + } diff --git a/src/core.test.ts.snap b/src/core.test.ts.snap index 642ca28..93393e1 100644 Binary files a/src/core.test.ts.snap and b/src/core.test.ts.snap differ diff --git a/src/core.ts b/src/core.ts index 2534717..006369c 100644 --- a/src/core.ts +++ b/src/core.ts @@ -31,6 +31,7 @@ export const getExposedPath = (config: Config) => path.join(config.paths.root, c export function getExposed( solcOutput: SolcOutput, include: (sourceName: string) => boolean, + exclude: (sourceName: string) => boolean, config: Config, ): Map { const rootRelativeSourcesPath = path.relative(config.paths.root, config.paths.sources); @@ -41,7 +42,7 @@ export function getExposed( const imports: Record> = {}; for (const { ast } of Object.values(solcOutput.sources)) { - if (!include(ast.absolutePath)) { + if (!include(ast.absolutePath) || exclude(ast.absolutePath)) { continue; } @@ -74,6 +75,9 @@ export function getExposed( } for (const [absoluteImportedPath, contracts] of Object.entries(imports)) { + if (exclude(absoluteImportedPath)) { + continue; + } const filter: ContractFilter = node => contracts.has(node); const ast = solcOutput.sources[absoluteImportedPath]?.ast; assert(ast !== undefined); diff --git a/src/plugin.ts b/src/plugin.ts index 18468e1..840cf07 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -86,10 +86,15 @@ task(TASK_COMPILE_SOLIDITY_COMPILE_JOB, async (args, hre, superC }); async function getExposedJob(hre: HardhatRuntimeEnvironment, compilationJob: CompilationJob, output: CompilerOutput): Promise { + const path = await import('path'); + const { isMatch } = await import('micromatch'); const { getExposed } = await import('./core'); - const include = await getMatcher(hre.config); - const exposed = getExposed(output, include, hre.config); + const sourcesDir = path.relative(hre.config.paths.root, hre.config.paths.sources); + + const include = (sourceName: string) => sourceName.startsWith(sourcesDir) && hre.config.exposed.include.some(p => isMatch(path.relative(sourcesDir, sourceName), p)); + const exclude = (sourceName: string) => hre.config.exposed.exclude.some(p => isMatch(path.relative(sourcesDir, sourceName), p)); + const exposed = getExposed(output, include, exclude, hre.config); const cj: CompilationJob = { getResolvedFiles: () => [...exposed.values()], @@ -119,23 +124,3 @@ async function cleanExposed(hre: HardhatRuntimeEnvironment) { const exposedPath = getExposedPath(hre.config); await fs.rm(exposedPath, { recursive: true, force: true }); } - -async function getMatcher(config: HardhatConfig) { - const { isMatch } = await import('micromatch'); - const path = await import('path'); - - const sourcesDir = path.relative(config.paths.root, config.paths.sources); - const includePatterns = config.exposed.include; - const excludePatterns = config.exposed.exclude; - - return function (sourceName: string) { - if (!sourceName.startsWith(sourcesDir)) { - return false; - } - sourceName = path.relative(sourcesDir, sourceName); - return ( - includePatterns.some(p => isMatch(sourceName, p)) && - !excludePatterns.some(p => isMatch(sourceName, p)) - ); - }; -}