Skip to main content

the_code_graph_domain/use_cases/
dead_code.rs

1use crate::analysis::dead_code::detect_dead_code;
2use crate::error::Result;
3use crate::model::*;
4use crate::ports::GraphStore;
5
6pub struct DeadCodeUseCase<S> {
7    store: S,
8}
9
10impl<S: GraphStore> DeadCodeUseCase<S> {
11    pub fn new(store: S) -> Self {
12        Self { store }
13    }
14
15    /// Run dead code analysis: load all symbols + edges, detect dead code.
16    pub fn analyze(&self, config: &DeadCodeConfig) -> Result<DeadCodeAnalysis> {
17        let symbols = self.store.all_symbols()?;
18        let edges = self.store.all_edges()?;
19        Ok(detect_dead_code(&symbols, &edges, config))
20    }
21}
22
23#[cfg(test)]
24mod tests {
25    use super::*;
26    use crate::model::{Edge, EdgeKind, Location, SymbolKind, SymbolNode, Visibility};
27    use crate::test_support::InMemoryGraphStore;
28
29    fn build_store() -> InMemoryGraphStore {
30        let mut store = InMemoryGraphStore::new();
31
32        // Unused function — should be dead
33        store.insert_symbol(SymbolNode {
34            name: "orphan_fn".into(),
35            qualified_name: "src/old.rs::orphan_fn".into(),
36            kind: SymbolKind::Function,
37            location: Location {
38                file: "src/old.rs".into(),
39                line_start: 10,
40                line_end: 20,
41                col_start: 0,
42                col_end: 0,
43            },
44            visibility: Visibility::Private,
45            is_exported: false,
46            is_async: false,
47            is_test: false,
48            decorators: vec![],
49            signature: None,
50        });
51
52        // Used function — should be alive
53        store.insert_symbol(SymbolNode {
54            name: "active_fn".into(),
55            qualified_name: "src/core.rs::active_fn".into(),
56            kind: SymbolKind::Function,
57            location: Location {
58                file: "src/core.rs".into(),
59                line_start: 1,
60                line_end: 10,
61                col_start: 0,
62                col_end: 0,
63            },
64            visibility: Visibility::Public,
65            is_exported: false,
66            is_async: false,
67            is_test: false,
68            decorators: vec![],
69            signature: None,
70        });
71
72        // Edge: something calls active_fn
73        store.insert_edge(Edge {
74            kind: EdgeKind::Calls,
75            source: "src/main.rs::main".into(),
76            target: "src/core.rs::active_fn".into(),
77            metadata: None,
78        });
79
80        store
81    }
82
83    #[test]
84    fn use_case_detects_dead_code() {
85        let store = build_store();
86        let uc = DeadCodeUseCase::new(store);
87        let result = uc.analyze(&DeadCodeConfig::default()).unwrap();
88
89        assert_eq!(result.summary.total_symbols, 2);
90        assert_eq!(result.dead_symbols.len(), 1);
91        assert_eq!(
92            result.dead_symbols[0].qualified_name,
93            "src/old.rs::orphan_fn"
94        );
95    }
96
97    #[test]
98    fn use_case_with_include_tests() {
99        let mut store = build_store();
100        store.insert_symbol(SymbolNode {
101            name: "old_test".into(),
102            qualified_name: "tests/old.rs::old_test".into(),
103            kind: SymbolKind::Test,
104            location: Location {
105                file: "tests/old.rs".into(),
106                line_start: 1,
107                line_end: 5,
108                col_start: 0,
109                col_end: 0,
110            },
111            visibility: Visibility::Private,
112            is_exported: false,
113            is_async: false,
114            is_test: true,
115            decorators: vec![],
116            signature: None,
117        });
118
119        // Default: test excluded
120        let uc = DeadCodeUseCase::new(store.clone());
121        let result = uc.analyze(&DeadCodeConfig::default()).unwrap();
122        assert_eq!(result.dead_symbols.len(), 1); // only orphan_fn
123
124        // With include_tests: test also flagged
125        let config = DeadCodeConfig {
126            include_tests: true,
127            ..DeadCodeConfig::default()
128        };
129        let result = uc.analyze(&config).unwrap();
130        assert_eq!(result.dead_symbols.len(), 2);
131    }
132}