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}