Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: foundry root imports #308

Merged
merged 2 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 33 additions & 14 deletions server/src/frameworks/Foundry/FoundryProject.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import fs from "fs";
import { existsSync } from "fs";
import _ from "lodash";
import path from "path";
import {
Expand Down Expand Up @@ -98,24 +98,33 @@ export class FoundryProject extends Project {
}

public async resolveImportPath(file: string, importPath: string) {
try {
let transformedPath = importPath;
let transformedPath = importPath;

if (!importPath.startsWith(".")) {
for (const { from, to } of this.remappings) {
if (importPath.startsWith(from)) {
transformedPath = path.join(to, importPath.slice(from.length));
}
// Apply remappings to importPath if it's not a relative import
if (!importPath.startsWith(".")) {
for (const { from, to } of this.remappings) {
if (importPath.startsWith(from)) {
transformedPath = path.join(to, importPath.slice(from.length));
}
}
const resolvedPath = require.resolve(transformedPath, {
paths: [fs.realpathSync(path.dirname(file))],
});
}

// Try to resolve the import recursively, start from source directory up to project root
let testBaseDirectory = path.dirname(file);
let resolvedPath: string | undefined;

while (directoryContains(this.basePath, testBaseDirectory)) {
const testResolvedPath = path.resolve(testBaseDirectory, transformedPath);

if (existsSync(testResolvedPath)) {
resolvedPath = testResolvedPath;
break;
}

return toUnixStyle(fs.realpathSync(resolvedPath));
} catch (error) {
return undefined;
testBaseDirectory = path.dirname(testBaseDirectory);
}

return resolvedPath !== undefined ? toUnixStyle(resolvedPath) : undefined;
}

public async buildCompilation(
Expand All @@ -133,6 +142,16 @@ export class FoundryProject extends Project {
this.configSolcVersion
);

const sources = basicCompilation.input.sources;

// Modify source keys to be root-relative instead of absolute
// i,e, '/home/user/myProject/src/Contract.sol' => 'src/Contract.sol'
for (const [sourceKey, sourceValue] of Object.entries(sources)) {
const transformedSourceKey = path.relative(this.basePath, sourceKey);
sources[transformedSourceKey] = sourceValue;
delete sources[sourceKey];
}

const remappings = this.remappings.map(
(remapping) => `${remapping.from}=${remapping.to}`
);
Expand Down
14 changes: 14 additions & 0 deletions server/src/services/validation/CompilationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import { existsSync } from "fs";
import { HardhatRuntimeEnvironment } from "hardhat/types";
import path from "path";
import { CompilationDetails } from "../../frameworks/base/CompilationDetails";
import { Logger } from "../../utils/Logger";

export class CompilationService {
public static async compile(
{
cachedCompilerInfo,
logger,
}: {
cachedCompilerInfo: {
[solcVersion: string]: { isSolcJs: boolean; compilerPath: string };
};
logger: Logger;
},
compilationDetails: CompilationDetails
): Promise<any> {
Expand All @@ -22,6 +25,17 @@ export class CompilationService {
// Empty outputSelection for faster compilation
delete (input.settings as any).outputSelection;

logger.trace(
`Solc Input: ${JSON.stringify(
{
...compilationDetails.input,
sources: Object.keys(compilationDetails.input.sources),
},
null,
2
)}`
);

// Find or download solc compiler
let compilerPath: string;
let isSolcJs: boolean;
Expand Down
13 changes: 8 additions & 5 deletions server/src/utils/directoryContains.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import path from "path";

/**
* Returns true if testPath is equal to dirPath or a subdirectory of it
* @param dirPath
* @param testPath
* @returns
*/
export function directoryContains(dirPath: string, testPath: string): boolean {
const relative = path.relative(dirPath, testPath);
return (
!!relative &&
!relative.startsWith(`..${path.sep}`) &&
!path.isAbsolute(relative)
);

return !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
kanej marked this conversation as resolved.
Show resolved Hide resolved
}
90 changes: 90 additions & 0 deletions server/test/frameworks/foundry/FoundryProject.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { expect } from "chai";
import path from "path";
import { stub } from "sinon";
import * as basicCompilation from "../../../src/frameworks/shared/buildBasicCompilation";
import { FoundryProject } from "../../../src/frameworks/Foundry/FoundryProject";
import { ServerState } from "../../../src/types";
import { toUnixStyle } from "../../../src/utils";

describe("FoundryProject", function () {
let project: FoundryProject;
const serverStateMock = {
logger: {},
} as ServerState;

beforeEach(async () => {
project = new FoundryProject(
serverStateMock,
path.join(__dirname, "test_project"),
path.join(__dirname, "test_project", "foundry.toml")
);
});

describe("resolveImportPath", function () {
it("resolves relative imports", async () => {
const foundImport = await project.resolveImportPath(
path.join(project.basePath, "src", "A.sol"),
"./B.sol"
);
const notFoundImport = await project.resolveImportPath(
path.join(project.basePath, "src", "A.sol"),
"./C.sol"
);
expect(foundImport).to.eq(
toUnixStyle(path.join(project.basePath, "src", "B.sol"))
);
expect(notFoundImport).to.eq(undefined);
});

it("resolves root imports", async () => {
const importFromSameLevel = await project.resolveImportPath(
path.join(project.basePath, "src", "nested", "D.sol"),
"nested/E.sol"
);
const importFromParent = await project.resolveImportPath(
path.join(project.basePath, "src", "nested", "D.sol"),
"src/A.sol"
);
const importFromLib = await project.resolveImportPath(
path.join(project.basePath, "src", "nested", "D.sol"),
"lib/C.sol"
);
const illegalImport = await project.resolveImportPath(
path.join(project.basePath, "src", "A.sol"),
"foundry/Illegal.sol"
);

expect(importFromSameLevel).to.eq(
toUnixStyle(path.join(project.basePath, "src", "nested", "E.sol"))
);
expect(importFromParent).to.eq(
toUnixStyle(path.join(project.basePath, "src", "A.sol"))
);
expect(importFromLib).to.eq(
toUnixStyle(path.join(project.basePath, "lib", "C.sol"))
);
expect(illegalImport).to.eq(undefined);
});
});

describe("buildCompilation", function () {
it("replaces absolute paths provided by buildBasicCompilation with root-relative paths", async () => {
const sourceUri = path.join(project.basePath, "src", "A.sol");

stub(basicCompilation, "buildBasicCompilation").resolves({
input: {
sources: {
[sourceUri]: { content: "" },
},
settings: {},
},
} as any);

const compilation = await project.buildCompilation(sourceUri, []);
expect(compilation.input.sources).to.deep.eq({
[path.join("src", "A.sol")]: { content: "" },
});
});
});
});
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.