use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use petgraph::graph::NodeIndex;
use petgraph::Graph;
use thiserror::Error;
#[cfg(feature = "pipeline-records")]
use tracing::debug;
#[derive(Debug, Error)]
pub enum GraphError {
#[error("no source records provided")]
Empty,
}
#[derive(Debug, Clone)]
pub struct DependencyGraph {
pub(crate) graph: Graph<PathBuf, ()>,
pub(crate) path_to_node: BTreeMap<PathBuf, NodeIndex>,
}
impl DependencyGraph {
pub fn node_count(&self) -> usize {
self.graph.node_count()
}
pub fn edge_count(&self) -> usize {
self.graph.edge_count()
}
pub fn node_path(&self, idx: usize) -> Option<&Path> {
let ni = NodeIndex::new(idx);
self.graph.node_weight(ni).map(|p| p.as_path())
}
pub fn node_for_path(&self, path: &Path) -> Option<usize> {
self.path_to_node.get(path).map(|ni| ni.index())
}
pub fn edges_as_pairs(&self) -> Vec<(usize, usize)> {
self.graph
.raw_edges()
.iter()
.map(|e| (e.source().index(), e.target().index()))
.collect()
}
pub fn neighbors(&self, idx: usize) -> Vec<usize> {
let ni = NodeIndex::new(idx);
self.graph.neighbors(ni).map(|n| n.index()).collect()
}
}
pub fn build_dependency_graph_from_edges(
node_paths: &[String],
edges: &[(usize, usize)],
) -> DependencyGraph {
let mut graph: Graph<PathBuf, ()> = Graph::new();
let mut path_to_node: BTreeMap<PathBuf, NodeIndex> = BTreeMap::new();
for path_str in node_paths {
let path = PathBuf::from(path_str);
let ni = graph.add_node(path.clone());
path_to_node.insert(path, ni);
}
let node_count = node_paths.len();
for &(from, to) in edges {
if from >= node_count || to >= node_count || from == to {
continue;
}
let from_ni = NodeIndex::new(from);
let to_ni = NodeIndex::new(to);
if !graph.contains_edge(from_ni, to_ni) {
graph.add_edge(from_ni, to_ni, ());
}
}
DependencyGraph {
graph,
path_to_node,
}
}
#[cfg(feature = "pipeline-records")]
pub fn build_dependency_graph(
records: &[sdivi_parsing::feature_record::FeatureRecord],
) -> DependencyGraph {
build_dependency_graph_with_tsconfig(records, None, None)
}
#[cfg(feature = "pipeline-records")]
pub fn build_dependency_graph_with_go_module(
records: &[sdivi_parsing::feature_record::FeatureRecord],
go_module: Option<&str>,
) -> DependencyGraph {
build_dependency_graph_with_tsconfig(records, go_module, None)
}
#[cfg(feature = "pipeline-records")]
pub fn build_dependency_graph_with_tsconfig(
records: &[sdivi_parsing::feature_record::FeatureRecord],
go_module: Option<&str>,
tsconfig: Option<&crate::tsconfig::TsConfigPaths>,
) -> DependencyGraph {
use crate::resolve::{build_stem_map, compute_java_roots, resolve_imports};
let mut graph: Graph<PathBuf, ()> = Graph::new();
let mut path_to_node: BTreeMap<PathBuf, NodeIndex> = BTreeMap::new();
for record in records {
let ni = graph.add_node(record.path.clone());
path_to_node.insert(record.path.clone(), ni);
}
let stem_map = build_stem_map(&path_to_node);
let java_roots = compute_java_roots(&path_to_node);
for record in records {
let from_ni = path_to_node[&record.path];
for import in &record.imports {
let targets = resolve_imports(
import,
&record.path,
&record.language,
&stem_map,
&path_to_node,
go_module,
&java_roots,
tsconfig,
);
if targets.is_empty() {
debug!(%import, path = ?record.path, "unresolved import dropped");
}
for to_ni in targets {
if to_ni != from_ni && !graph.contains_edge(from_ni, to_ni) {
graph.add_edge(from_ni, to_ni, ());
}
}
}
}
DependencyGraph {
graph,
path_to_node,
}
}