hardhat
Version:
Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
170 lines • 6.75 kB
JavaScript
import { assertHardhatInvariant } from "@nomicfoundation/hardhat-errors";
export class DependencyGraphImplementation {
#fileByInputSourceName = new Map();
#rootByUserSourceName = new Map();
#dependenciesMap = new Map();
/**
* Adds a root file to the graph. All the roots of the dependency graph must
* be added before any dependencry.
*
* @param userSourceName The source name used to identify the file, as it
* would appear in the artifacts and used by the user. This is not always the
* same as the source name used by solc, as it differs when an npm file is
* acting as a root.
* @param root The root file.
*/
addRootFile(userSourceName, root) {
this.#addFile(root);
this.#rootByUserSourceName.set(userSourceName, root);
}
/**
* Adds a dependency from a file to another one.
*
* @param from The file that depends on another one, which must be already
* present in the graph.
* @param to The dependency, which will be added to the list of dependencies
* of the file, and added to the graph if needed.
* @param remapping The remapping that was used to resolve this dependency, if
* any.
*/
addDependency(from, to, remapping) {
const dependencies = this.#dependenciesMap.get(from);
assertHardhatInvariant(dependencies !== undefined, "File `from` from not present");
if (!this.hasFile(to)) {
this.#addFile(to);
}
let edgeRemappings = dependencies.get(to);
if (edgeRemappings === undefined) {
edgeRemappings = new Set();
dependencies.set(to, edgeRemappings);
}
if (remapping !== undefined) {
edgeRemappings.add(remapping);
}
}
/**
* Returns a map of user source names to root files.
*/
getRoots() {
return this.#rootByUserSourceName;
}
/**
* Returns a sorted map of userSourceName to inputSourceName for every
* root of the graph.
*/
getRootsUserSourceNameMap() {
return Object.fromEntries([...this.getRoots().entries()]
.map(([userSourceName, root]) => [userSourceName, root.inputSourceName])
.sort(([userSourceName1, _], [userSourceName2, __]) => userSourceName1.localeCompare(userSourceName2)));
}
/**
* Returns a set of all the files in the graph.
*/
getAllFiles() {
return this.#dependenciesMap.keys();
}
hasFile(file) {
return this.#dependenciesMap.has(file);
}
getDependencies(file) {
const dependencies = this.#dependenciesMap.get(file);
if (dependencies === undefined) {
return new Set();
}
return new Set(Array.from(dependencies.entries()).map(([to, remappings]) => ({
file: to,
remappings,
})));
}
getFileByInputSourceName(inputSourceName) {
return this.#fileByInputSourceName.get(inputSourceName);
}
getSubgraph(...rootUserSourceNames) {
const subgraph = new DependencyGraphImplementation();
const filesToTraverse = [];
for (const rootUserSourceName of rootUserSourceNames) {
const root = this.#rootByUserSourceName.get(rootUserSourceName);
assertHardhatInvariant(root !== undefined, "We should have a root for every root user source name");
subgraph.addRootFile(rootUserSourceName, root);
filesToTraverse.push(root);
}
let fileToAnalyze;
while ((fileToAnalyze = filesToTraverse.pop()) !== undefined) {
for (const dependency of this.getDependencies(fileToAnalyze)) {
if (!subgraph.hasFile(dependency.file)) {
filesToTraverse.push(dependency.file);
}
subgraph.addDependency(fileToAnalyze, dependency.file);
for (const remapping of dependency.remappings) {
subgraph.addDependency(fileToAnalyze, dependency.file, remapping);
}
}
}
return subgraph;
}
merge(other) {
const merged = new DependencyGraphImplementation();
for (const [userSourceName, root] of this.#rootByUserSourceName) {
merged.addRootFile(userSourceName, root);
}
for (const [userSourceName, root] of other.#rootByUserSourceName) {
if (merged.hasFile(root)) {
continue;
}
merged.addRootFile(userSourceName, root);
}
for (const [from, dependencies] of this.#dependenciesMap) {
for (const [to, remappings] of dependencies) {
merged.addDependency(from, to);
for (const remapping of remappings) {
merged.addDependency(from, to, remapping);
}
}
}
for (const [from, dependencies] of other.#dependenciesMap) {
for (const [to, remappings] of dependencies) {
merged.addDependency(from, to);
for (const remapping of remappings) {
merged.addDependency(from, to, remapping);
}
}
}
return merged;
}
getAllRemappings() {
return this.#dependenciesMap
.values()
.flatMap((dependencies) => dependencies.values().flatMap((remappings) => remappings.values()))
.toArray()
.sort();
}
toJSON() {
return {
fileByInputSourceName: Object.fromEntries(this.#fileByInputSourceName),
rootByUserSourceName: Object.fromEntries(this.#rootByUserSourceName
.entries()
.map(([userSourceName, file]) => [
userSourceName,
file.inputSourceName,
])),
dependencies: Object.fromEntries(this.#dependenciesMap
.entries()
.map(([from, dependencies]) => [
from.inputSourceName,
Object.fromEntries(dependencies
.entries()
.map(([to, remappings]) => [
to.inputSourceName,
[...remappings].sort(),
])),
])),
};
}
#addFile(file) {
assertHardhatInvariant(!this.hasFile(file), `File ${file.inputSourceName} already present`);
assertHardhatInvariant(this.#fileByInputSourceName.get(file.inputSourceName) === undefined, `File "${file.inputSourceName}" already present`);
this.#fileByInputSourceName.set(file.inputSourceName, file);
this.#dependenciesMap.set(file, new Map());
}
}
//# sourceMappingURL=dependency-graph.js.map