fob_graph/memory/
chains.rs

1//! Dependency chain analysis methods for ModuleGraph.
2
3use rustc_hash::FxHashMap as HashMap;
4
5use super::super::ModuleId;
6use super::super::dependency_chain::{ChainAnalysis, DependencyChain, find_chains};
7use super::graph::ModuleGraph;
8use crate::Result;
9
10impl ModuleGraph {
11    /// Find all dependency chains from entry points to a target module.
12    ///
13    /// This traces all possible paths through the import graph, useful for understanding
14    /// why a module is included in the bundle and what depends on it.
15    pub fn dependency_chains_to(&self, target: &ModuleId) -> Result<Vec<DependencyChain>> {
16        let entry_points = self.entry_points()?;
17        let inner = self.inner.read();
18
19        let get_deps = |module: &ModuleId| -> Vec<ModuleId> {
20            inner
21                .dependencies
22                .get(module)
23                .map(|set| set.iter().cloned().collect())
24                .unwrap_or_default()
25        };
26
27        Ok(find_chains(&entry_points, target, get_deps))
28    }
29
30    /// Analyze dependency chains to a module.
31    ///
32    /// Provides comprehensive statistics about all paths leading to a module.
33    pub fn analyze_dependency_chains(&self, target: &ModuleId) -> Result<ChainAnalysis> {
34        let chains = self.dependency_chains_to(target)?;
35        Ok(ChainAnalysis::from_chains(target.clone(), chains))
36    }
37
38    /// Get the import depth of a module from entry points.
39    ///
40    /// Returns the shortest distance from any entry point to this module,
41    /// or None if the module is unreachable.
42    pub fn import_depth(&self, module: &ModuleId) -> Result<Option<usize>> {
43        let analysis = self.analyze_dependency_chains(module)?;
44        Ok(analysis.min_depth)
45    }
46
47    /// Group modules by their import depth from entry points.
48    ///
49    /// This creates layers of the dependency graph, useful for visualizing
50    /// the structure and understanding module organization.
51    pub fn modules_by_depth(&self) -> Result<HashMap<usize, Vec<ModuleId>>> {
52        let all_modules = self.modules()?;
53        let mut by_depth: HashMap<usize, Vec<ModuleId>> = HashMap::default();
54
55        for module in all_modules {
56            if let Some(depth) = self.import_depth(&module.id)? {
57                by_depth.entry(depth).or_default().push(module.id.clone());
58            }
59        }
60
61        Ok(by_depth)
62    }
63
64    /// Check if a module is only reachable through dead code.
65    ///
66    /// A module is considered "reachable only through dead code" if:
67    /// - It has no direct path from any entry point, OR
68    /// - All paths to it go through unreachable modules
69    ///
70    /// This is a strong indicator that the module can be safely removed.
71    pub fn is_reachable_only_through_dead_code(&self, module: &ModuleId) -> Result<bool> {
72        let analysis = self.analyze_dependency_chains(module)?;
73
74        // If not reachable at all, it's definitely dead
75        if !analysis.is_reachable() {
76            return Ok(true);
77        }
78
79        // If we have any chain, the module is reachable from an entry point
80        // More sophisticated analysis would check if all chains go through
81        // modules that are themselves unreachable, but that requires
82        // recursive analysis which we'll skip for now.
83        Ok(false)
84    }
85}