import * as taskTypes from "../../types/builtin-tasks"; import { ResolvedFile, Resolver } from "./resolver"; export class DependencyGraph implements taskTypes.DependencyGraph { public static async createFromResolvedFiles( resolver: Resolver, resolvedFiles: ResolvedFile[] ): Promise { const graph = new DependencyGraph(); for (const resolvedFile of resolvedFiles) { await graph._addDependenciesFrom(resolver, resolvedFile); } return graph; } private _resolvedFiles = new Map(); private _dependenciesPerFile = new Map>(); private readonly _visitedFiles = new Set(); private constructor() {} public getResolvedFiles(): ResolvedFile[] { return Array.from(this._resolvedFiles.values()); } public has(file: ResolvedFile): boolean { return this._resolvedFiles.has(file.sourceName); } public isEmpty(): boolean { return this._resolvedFiles.size === 0; } public entries(): Array<[ResolvedFile, Set]> { return Array.from(this._dependenciesPerFile.entries()).map( ([key, value]) => [this._resolvedFiles.get(key)!, value] ); } public getDependencies(file: ResolvedFile): ResolvedFile[] { const dependencies = this._dependenciesPerFile.get(file.sourceName) ?? new Set(); return [...dependencies]; } public getTransitiveDependencies( file: ResolvedFile ): taskTypes.TransitiveDependency[] { const visited = new Set(); const transitiveDependencies = this._getTransitiveDependencies( file, visited, [] ); return [...transitiveDependencies]; } public getConnectedComponents(): DependencyGraph[] { const undirectedGraph: Record> = {}; for (const [ sourceName, dependencies, ] of this._dependenciesPerFile.entries()) { undirectedGraph[sourceName] = undirectedGraph[sourceName] ?? new Set(); for (const dependency of dependencies) { undirectedGraph[dependency.sourceName] = undirectedGraph[dependency.sourceName] ?? new Set(); undirectedGraph[sourceName].add(dependency.sourceName); undirectedGraph[dependency.sourceName].add(sourceName); } } const components: Array> = []; const visited = new Set(); for (const node of Object.keys(undirectedGraph)) { if (visited.has(node)) { continue; } visited.add(node); const component = new Set([node]); const stack = [...undirectedGraph[node]]; while (stack.length > 0) { const newNode = stack.pop()!; if (visited.has(newNode)) { continue; } visited.add(newNode); component.add(newNode); [...undirectedGraph[newNode]].forEach((adjacent) => { if (!visited.has(adjacent)) { stack.push(adjacent); } }); } components.push(component); } const connectedComponents: DependencyGraph[] = []; for (const component of components) { const dependencyGraph = new DependencyGraph(); for (const sourceName of component) { const file = this._resolvedFiles.get(sourceName)!; const dependencies = this._dependenciesPerFile.get(sourceName)!; dependencyGraph._resolvedFiles.set(sourceName, file); dependencyGraph._dependenciesPerFile.set(sourceName, dependencies); } connectedComponents.push(dependencyGraph); } return connectedComponents; } private _getTransitiveDependencies( file: ResolvedFile, visited: Set, path: ResolvedFile[] ): Set { if (visited.has(file)) { return new Set(); } visited.add(file); const directDependencies: taskTypes.TransitiveDependency[] = this.getDependencies(file).map((dependency) => ({ dependency, path, })); const transitiveDependencies = new Set( directDependencies ); for (const { dependency } of transitiveDependencies) { this._getTransitiveDependencies( dependency, visited, path.concat(dependency) ).forEach((x) => transitiveDependencies.add(x)); } return transitiveDependencies; } private async _addDependenciesFrom( resolver: Resolver, file: ResolvedFile ): Promise { if (this._visitedFiles.has(file.absolutePath)) { return; } this._visitedFiles.add(file.absolutePath); const dependencies = new Set(); this._resolvedFiles.set(file.sourceName, file); this._dependenciesPerFile.set(file.sourceName, dependencies); for (const imp of file.content.imports) { const dependency = await resolver.resolveImport(file, imp); dependencies.add(dependency); await this._addDependenciesFrom(resolver, dependency); } } }