Skip to main content

the_code_graph_parser/resolver/
mod.rs

1pub mod go;
2pub mod python;
3pub mod rust_lang;
4pub mod typescript;
5
6use std::collections::HashMap;
7use std::path::{Path, PathBuf};
8
9use domain::error::CodeGraphError;
10use domain::model::{Edge, Language};
11
12use crate::ParseResult;
13
14/// Trait for language-specific import resolvers.
15/// Called after all files are parsed (Phase 2 of parse-then-resolve).
16pub trait ImportResolver: Send + Sync {
17    /// Which languages this resolver handles.
18    fn languages(&self) -> &[Language];
19
20    /// Resolve raw imports from a single file into graph edges.
21    fn resolve(
22        &self,
23        file_path: &Path,
24        parse_result: &ParseResult,
25        context: &ResolveContext,
26    ) -> domain::error::Result<Vec<Edge>>;
27}
28
29/// Shared context for resolution — all parsed files and their results.
30pub struct ResolveContext {
31    pub project_root: PathBuf,
32    pub parsed_files: HashMap<PathBuf, ParseResult>,
33    pub file_tree: Vec<PathBuf>,
34}
35
36/// Registry of import resolvers with language-based dispatch.
37pub struct ResolverRegistry {
38    resolvers: Vec<Box<dyn ImportResolver>>,
39}
40
41impl ResolverRegistry {
42    /// Create registry with all supported import resolvers.
43    pub fn new(project_root: &Path) -> Self {
44        let mut registry = Self {
45            resolvers: Vec::new(),
46        };
47        registry.register(Box::new(typescript::TypeScriptResolver::new(project_root)));
48        let rust_config = rust_lang::RustConfig::load(project_root);
49        registry.register(Box::new(rust_lang::RustResolver::new(rust_config)));
50        let python_config = python::PythonConfig::load(project_root);
51        registry.register(Box::new(python::PythonResolver::new(python_config)));
52        let go_config = go::GoConfig::load(project_root);
53        registry.register(Box::new(go::GoResolver::new(go_config)));
54        registry
55    }
56
57    fn register(&mut self, resolver: Box<dyn ImportResolver>) {
58        self.resolvers.push(resolver);
59    }
60
61    /// Get the resolver for a specific language.
62    pub fn resolver_for_language(&self, lang: Language) -> Option<&dyn ImportResolver> {
63        self.resolvers
64            .iter()
65            .find(|r| r.languages().contains(&lang))
66            .map(|r| r.as_ref())
67    }
68
69    /// Resolve imports for a single file.
70    pub fn resolve_file(
71        &self,
72        file_path: &Path,
73        lang: Language,
74        parse_result: &ParseResult,
75        context: &ResolveContext,
76    ) -> domain::error::Result<Vec<Edge>> {
77        self.resolver_for_language(lang)
78            .ok_or_else(|| {
79                CodeGraphError::Resolution(format!("no resolver for language {lang:?}"))
80            })?
81            .resolve(file_path, parse_result, context)
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use std::path::Path;
89
90    #[test]
91    fn registry_has_all_four_resolvers() {
92        let registry = ResolverRegistry::new(Path::new("/tmp"));
93        assert!(registry
94            .resolver_for_language(Language::TypeScript)
95            .is_some());
96        assert!(registry
97            .resolver_for_language(Language::JavaScript)
98            .is_some());
99        assert!(registry.resolver_for_language(Language::Rust).is_some());
100        assert!(registry.resolver_for_language(Language::Python).is_some());
101        assert!(registry.resolver_for_language(Language::Go).is_some());
102    }
103
104    #[test]
105    fn resolve_file_returns_error_for_unknown_language_none() {
106        // All 5 languages are covered, so this test just verifies dispatch works
107        let registry = ResolverRegistry::new(Path::new("/tmp"));
108        let context = ResolveContext {
109            project_root: PathBuf::from("/tmp"),
110            parsed_files: HashMap::new(),
111            file_tree: Vec::new(),
112        };
113        let result = ParseResult::default();
114        // TypeScript should resolve without error (empty result)
115        let edges = registry.resolve_file(
116            Path::new("test.ts"),
117            Language::TypeScript,
118            &result,
119            &context,
120        );
121        assert!(edges.is_ok());
122    }
123}