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}