diff --git a/src/solc.ts b/src/solc.ts index 77f7be7a..afa8d47e 100644 --- a/src/solc.ts +++ b/src/solc.ts @@ -44,6 +44,26 @@ function tidy(_line: string) { return line; } +type PathModule = Pick; + +/** + * Build the source-map key for an imported Solidity file. + * + * solc's `findImports` callback always receives '/'-separated import paths, so + * the keys we register in the sources map must use '/' as well. `path.relative` + * returns OS-native separators — '/' on POSIX but '\' on Windows — so we + * normalise here. On POSIX this is a no-op; on Windows it converts the '\' + * separators that would otherwise cause "couldn't find the import" failures. + * + * `p` is injectable purely so this can be unit-tested deterministically for both + * `path.posix` and `path.win32` regardless of the host OS. + */ +export const toImportKey = ( + topDir: string, + importPath: string, + p: PathModule = path, +): string => p.relative(topDir, importPath).split(p.sep).join('/'); + const buildSources = (file: any, options: any) => { const sources = {}; const contractsFiles = []; @@ -60,7 +80,8 @@ const buildSources = (file: any, options: any) => { relPath, ); - relPath = path.relative(options.topDir, importPath); + // Normalise to '/' so this key matches what solc passes to findImports. + relPath = toImportKey(options.topDir, importPath); contractsFiles.push([importPath, relPath]); } diff --git a/test/solc-import-path.test.js b/test/solc-import-path.test.js new file mode 100644 index 00000000..5a0f6fdb --- /dev/null +++ b/test/solc-import-path.test.js @@ -0,0 +1,34 @@ +import assert from 'assert'; +import path from 'path'; +import { toImportKey } from '../built/solc.js'; + +/** + * Regression tests for import-path resolution in src/solc.ts (buildSources). + * + * solc's `findImports` callback always receives '/'-separated paths, so the keys + * we register in the sources map must use '/' too. `path.relative` returns + * OS-native separators, so `toImportKey` normalises them. The Windows case is + * exercised with `path.win32` explicitly, so it runs identically on a Linux CI + * and FAILS without the `.split(p.sep).join('/')` normalisation. + */ +describe('solc import-path key normalisation', function () { + const importPathPosix = '/home/dev/contracts/Escrow-imports/IERC20.sol'; + const topDirPosix = '/home/dev/contracts'; + + const importPathWin = 'C:\\Users\\dev\\contracts\\Escrow-imports\\IERC20.sol'; + const topDirWin = 'C:\\Users\\dev\\contracts'; + + const expected = 'Escrow-imports/IERC20.sol'; + + it('Linux/POSIX: yields a forward-slash import key', function () { + const key = toImportKey(topDirPosix, importPathPosix, path.posix); + assert.strictEqual(key, expected); + }); + + it('Windows: yields a forward-slash import key (fails without the fix)', function () { + // Without normalisation this would be 'Escrow-imports\\IERC20.sol', which + // never matches solc's '/'-separated key -> "couldn't find the import". + const key = toImportKey(topDirWin, importPathWin, path.win32); + assert.strictEqual(key, expected); + }); +});