Skip to main content

agentic_codebase/index/
path_index.rs

1//! Index by file path.
2//!
3//! Maps file paths to the code unit IDs defined within each file.
4//! Building the index is O(n); lookup by path is O(1).
5
6use std::collections::HashMap;
7use std::path::{Path, PathBuf};
8
9use crate::graph::CodeGraph;
10
11/// Index that maps file paths to code unit IDs.
12#[derive(Debug, Clone, Default)]
13pub struct PathIndex {
14    by_path: HashMap<PathBuf, Vec<u64>>,
15    paths: Vec<PathBuf>,
16}
17
18impl PathIndex {
19    /// Build a `PathIndex` from all code units in the given graph.
20    pub fn build(graph: &CodeGraph) -> Self {
21        let mut by_path: HashMap<PathBuf, Vec<u64>> = HashMap::new();
22
23        for unit in graph.units() {
24            by_path
25                .entry(unit.file_path.clone())
26                .or_default()
27                .push(unit.id);
28        }
29
30        let mut paths: Vec<PathBuf> = by_path.keys().cloned().collect();
31        paths.sort();
32
33        Self { by_path, paths }
34    }
35
36    /// Look up all unit IDs in the given file path.
37    ///
38    /// Returns an empty slice if no units match.
39    pub fn lookup(&self, path: &Path) -> &[u64] {
40        self.by_path.get(path).map(|v| v.as_slice()).unwrap_or(&[])
41    }
42
43    /// Returns all distinct file paths present in the index, sorted.
44    pub fn paths(&self) -> &[PathBuf] {
45        &self.paths
46    }
47
48    /// Returns the number of distinct files in the index.
49    pub fn file_count(&self) -> usize {
50        self.paths.len()
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use crate::graph::CodeGraph;
58    use crate::types::{CodeUnit, CodeUnitType, Language, Span};
59
60    fn make_unit(file_path: &str) -> CodeUnit {
61        CodeUnit::new(
62            CodeUnitType::Function,
63            Language::Rust,
64            "test_fn".to_string(),
65            "mod::test_fn".to_string(),
66            PathBuf::from(file_path),
67            Span::new(1, 0, 10, 0),
68        )
69    }
70
71    #[test]
72    fn test_empty_index() {
73        let graph = CodeGraph::default();
74        let index = PathIndex::build(&graph);
75        assert_eq!(index.file_count(), 0);
76        assert!(index.paths().is_empty());
77        assert_eq!(index.lookup(Path::new("src/lib.rs")), &[] as &[u64]);
78    }
79
80    #[test]
81    fn test_path_lookup() {
82        let mut graph = CodeGraph::default();
83        graph.add_unit(make_unit("src/lib.rs"));
84        graph.add_unit(make_unit("src/lib.rs"));
85        graph.add_unit(make_unit("src/main.rs"));
86
87        let index = PathIndex::build(&graph);
88        assert_eq!(index.file_count(), 2);
89        assert_eq!(index.lookup(Path::new("src/lib.rs")), &[0, 1]);
90        assert_eq!(index.lookup(Path::new("src/main.rs")), &[2]);
91        assert_eq!(index.lookup(Path::new("src/other.rs")), &[] as &[u64]);
92    }
93
94    #[test]
95    fn test_paths_sorted() {
96        let mut graph = CodeGraph::default();
97        graph.add_unit(make_unit("src/z_file.rs"));
98        graph.add_unit(make_unit("src/a_file.rs"));
99        graph.add_unit(make_unit("src/m_file.rs"));
100
101        let index = PathIndex::build(&graph);
102        let paths = index.paths();
103        assert_eq!(paths.len(), 3);
104        assert_eq!(paths[0], PathBuf::from("src/a_file.rs"));
105        assert_eq!(paths[1], PathBuf::from("src/m_file.rs"));
106        assert_eq!(paths[2], PathBuf::from("src/z_file.rs"));
107    }
108}