fob_graph/memory/
symbols.rs

1//! Symbol analysis methods for ModuleGraph.
2
3use rustc_hash::FxHashMap as HashMap;
4
5use super::super::ModuleId;
6use super::super::symbol::{
7    Symbol, SymbolMetadata, SymbolStatistics, UnreachableCode, UnusedSymbol,
8};
9use super::graph::ModuleGraph;
10use super::types::{ClassMemberInfo, EnumMemberInfo};
11use crate::Result;
12
13impl ModuleGraph {
14    /// Get all unused symbols across the entire graph.
15    ///
16    /// This queries the symbol table for each module and returns symbols
17    /// that are declared but never referenced.
18    pub fn unused_symbols(&self) -> Result<Vec<UnusedSymbol>> {
19        let inner = self.inner.read();
20        let mut unused = Vec::new();
21
22        for module in inner.modules.values() {
23            for symbol in module.symbol_table.unused_symbols() {
24                unused.push(UnusedSymbol {
25                    module_id: module.id.clone(),
26                    symbol: symbol.clone(),
27                });
28            }
29        }
30
31        Ok(unused)
32    }
33
34    /// Get unused symbols for a specific module.
35    ///
36    /// Returns an empty vector if the module doesn't exist.
37    pub fn unused_symbols_in_module(&self, id: &ModuleId) -> Result<Vec<Symbol>> {
38        let inner = self.inner.read();
39
40        if let Some(module) = inner.modules.get(id) {
41            Ok(module
42                .symbol_table
43                .unused_symbols()
44                .into_iter()
45                .cloned()
46                .collect())
47        } else {
48            Ok(Vec::new())
49        }
50    }
51
52    /// Get all symbols across the entire graph (not just unused ones).
53    ///
54    /// This is useful for code quality analysis that needs to check all symbols,
55    /// regardless of whether they're used or not.
56    pub fn all_symbols(&self) -> Result<Vec<UnusedSymbol>> {
57        let inner = self.inner.read();
58        let mut all = Vec::new();
59
60        for module in inner.modules.values() {
61            for symbol in &module.symbol_table.symbols {
62                all.push(UnusedSymbol {
63                    module_id: module.id.clone(),
64                    symbol: symbol.clone(),
65                });
66            }
67        }
68
69        Ok(all)
70    }
71
72    /// Get unreachable code detected across the graph.
73    ///
74    /// Note: Unreachable code detection must be performed during module analysis
75    /// (when source code is available) rather than from the graph.
76    /// Use `crate::graph::semantic::detect_unreachable_code()` during module building.
77    ///
78    /// This method currently returns an empty vector as a placeholder for graph-level
79    /// aggregation if unreachable code data is stored in modules in the future.
80    pub fn unreachable_code(&self) -> Result<Vec<UnreachableCode>> {
81        // Architectural limitation: Unreachable code aggregation is deferred because:
82        // 1. The Module struct doesn't store source text (by design for memory efficiency)
83        // 2. Unreachable code detection requires source text analysis
84        // 3. Storing unreachable code data in Module would increase memory usage
85        //
86        // Current approach: Use `crate::semantic::detect_unreachable_code()` during module
87        // building when source text is available, rather than aggregating from the graph.
88        //
89        // Future enhancement: If graph-level aggregation is needed, consider:
90        // - Adding optional unreachable_code field to Module (with source text caching)
91        // - Or maintaining a separate map of module_id -> unreachable_code
92        // - Or requiring callers to provide source text when querying
93        Ok(Vec::new())
94    }
95
96    /// Compute symbol statistics across the entire graph.
97    ///
98    /// Aggregates symbol information from all modules to provide
99    /// a high-level view of symbol usage patterns.
100    pub fn symbol_statistics(&self) -> Result<SymbolStatistics> {
101        let inner = self.inner.read();
102
103        let tables: Vec<_> = inner
104            .modules
105            .values()
106            .map(|m| m.symbol_table.as_ref())
107            .collect();
108
109        Ok(SymbolStatistics::from_tables(tables.into_iter()))
110    }
111
112    /// Get all unused private class members across the graph, grouped by class.
113    ///
114    /// Private class members are safe to remove when unused, as they cannot be accessed
115    /// from outside the class. This method groups results by class name for easier analysis.
116    pub fn unused_private_class_members(&self) -> Result<HashMap<String, Vec<UnusedSymbol>>> {
117        let inner = self.inner.read();
118        let mut by_class: HashMap<String, Vec<UnusedSymbol>> = HashMap::default();
119
120        for module in inner.modules.values() {
121            for symbol in &module.symbol_table.symbols {
122                if symbol.is_unused_private_member() {
123                    if let Some(class_name) = symbol.class_name() {
124                        by_class
125                            .entry(class_name.to_string())
126                            .or_default()
127                            .push(UnusedSymbol {
128                                module_id: module.id.clone(),
129                                symbol: symbol.clone(),
130                            });
131                    }
132                }
133            }
134        }
135
136        Ok(by_class)
137    }
138
139    /// Get all class members (public and private) for comprehensive analysis.
140    ///
141    /// This provides complete visibility into class structure, useful for refactoring
142    /// and understanding class complexity.
143    pub fn all_class_members(&self) -> Result<Vec<ClassMemberInfo>> {
144        let inner = self.inner.read();
145        let mut members = Vec::new();
146
147        for module in inner.modules.values() {
148            for symbol in &module.symbol_table.symbols {
149                if let SymbolMetadata::ClassMember(metadata) = &symbol.metadata {
150                    members.push(ClassMemberInfo {
151                        module_id: module.id.clone(),
152                        symbol: symbol.clone(),
153                        metadata: metadata.clone(),
154                    });
155                }
156            }
157        }
158
159        Ok(members)
160    }
161
162    /// Get unused public class members.
163    ///
164    /// Warning: Removing public members is potentially breaking for library code.
165    /// Use with caution and only for application code where you control all usage.
166    pub fn unused_public_class_members(&self) -> Result<Vec<UnusedSymbol>> {
167        let inner = self.inner.read();
168        let mut unused = Vec::new();
169
170        for module in inner.modules.values() {
171            for symbol in &module.symbol_table.symbols {
172                if symbol.is_unused() {
173                    if let SymbolMetadata::ClassMember(metadata) = &symbol.metadata {
174                        if !matches!(
175                            metadata.visibility,
176                            super::super::symbol::Visibility::Private
177                        ) {
178                            unused.push(UnusedSymbol {
179                                module_id: module.id.clone(),
180                                symbol: symbol.clone(),
181                            });
182                        }
183                    }
184                }
185            }
186        }
187
188        Ok(unused)
189    }
190
191    /// Get all unused enum members across the graph, grouped by enum.
192    ///
193    /// Enum members that are never referenced can often be safely removed,
194    /// especially in application code. This groups results by enum for easier analysis.
195    pub fn unused_enum_members(&self) -> Result<HashMap<String, Vec<UnusedSymbol>>> {
196        let inner = self.inner.read();
197        let mut by_enum: HashMap<String, Vec<UnusedSymbol>> = HashMap::default();
198
199        for module in inner.modules.values() {
200            for symbol in &module.symbol_table.symbols {
201                if symbol.is_unused_enum_member() {
202                    if let Some(enum_name) = symbol.enum_name() {
203                        by_enum
204                            .entry(enum_name.to_string())
205                            .or_default()
206                            .push(UnusedSymbol {
207                                module_id: module.id.clone(),
208                                symbol: symbol.clone(),
209                            });
210                    }
211                }
212            }
213        }
214
215        Ok(by_enum)
216    }
217
218    /// Get all enum members (used and unused) for comprehensive enum analysis.
219    ///
220    /// This provides complete visibility into enum structure, useful for
221    /// understanding enum coverage and usage patterns.
222    pub fn all_enum_members(&self) -> Result<HashMap<String, Vec<EnumMemberInfo>>> {
223        let inner = self.inner.read();
224        let mut by_enum: HashMap<String, Vec<EnumMemberInfo>> = HashMap::default();
225
226        for module in inner.modules.values() {
227            for symbol in &module.symbol_table.symbols {
228                if let SymbolMetadata::EnumMember(metadata) = &symbol.metadata {
229                    by_enum
230                        .entry(metadata.enum_name.clone())
231                        .or_default()
232                        .push(EnumMemberInfo {
233                            module_id: module.id.clone(),
234                            symbol: symbol.clone(),
235                            value: metadata.value.clone(),
236                        });
237                }
238            }
239        }
240
241        Ok(by_enum)
242    }
243}