Skip to main content

the_code_graph_eval/
adapters.rs

1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4use domain::error::{CodeGraphError, Result};
5use domain::model::{Edge, FileNode};
6use domain::ports::FileData;
7use parser::resolver::{ResolveContext, ResolverRegistry};
8use parser::{ParseResult, ParserRegistry};
9use rayon::prelude::*;
10use sha2::{Digest, Sha256};
11
12// ---------------------------------------------------------------------------
13// EvalFileSystem — mirrors cli RealFileSystem
14// ---------------------------------------------------------------------------
15
16pub struct EvalFileSystem;
17
18impl domain::ports::FileSystem for EvalFileSystem {
19    fn list_files(&self, root: &Path, extensions: &[&str]) -> Result<Vec<PathBuf>> {
20        let mut builder = ignore::WalkBuilder::new(root);
21        builder.add_custom_ignore_filename(".code-graphignore");
22
23        let files: Vec<PathBuf> = builder
24            .build()
25            .filter_map(|entry| entry.ok())
26            .filter(|entry| entry.file_type().is_some_and(|ft| ft.is_file()))
27            .filter(|entry| {
28                entry
29                    .path()
30                    .extension()
31                    .and_then(|ext| ext.to_str())
32                    .is_some_and(|ext| extensions.contains(&ext))
33            })
34            .map(|entry| {
35                entry
36                    .path()
37                    .strip_prefix(root)
38                    .unwrap_or(entry.path())
39                    .to_path_buf()
40            })
41            .collect();
42
43        Ok(files)
44    }
45
46    fn read_file(&self, path: &Path) -> Result<String> {
47        std::fs::read_to_string(path).map_err(|e| CodeGraphError::FileSystem {
48            path: path.into(),
49            source: e,
50        })
51    }
52
53    fn file_hash(&self, path: &Path) -> Result<String> {
54        let content = std::fs::read(path).map_err(|e| CodeGraphError::FileSystem {
55            path: path.into(),
56            source: e,
57        })?;
58        let mut hasher = Sha256::new();
59        hasher.update(&content);
60        Ok(format!("{:x}", hasher.finalize()))
61    }
62}
63
64// ---------------------------------------------------------------------------
65// EvalParseProvider — mirrors cli RayonParseProvider
66// ---------------------------------------------------------------------------
67
68pub struct EvalParseProvider {
69    registry: ParserRegistry,
70}
71
72impl Default for EvalParseProvider {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78impl EvalParseProvider {
79    pub fn new() -> Self {
80        Self {
81            registry: ParserRegistry::new(),
82        }
83    }
84
85    fn compute_hash(content: &[u8]) -> String {
86        let mut hasher = Sha256::new();
87        hasher.update(content);
88        format!("{:x}", hasher.finalize())
89    }
90}
91
92impl domain::ports::ParseProvider for EvalParseProvider {
93    fn parse_and_resolve(
94        &self,
95        files: &[(PathBuf, Vec<u8>)],
96        project_root: &Path,
97    ) -> Result<Vec<FileData>> {
98        if files.is_empty() {
99            return Ok(vec![]);
100        }
101
102        // Phase 1: parallel parse
103        let parse_results: Vec<(PathBuf, Vec<u8>, ParseResult, domain::model::Language)> = files
104            .par_iter()
105            .filter_map(|(path, source)| {
106                let parser = self.registry.parser_for_file(path)?;
107                match parser.parse(source, path) {
108                    Ok(result) => Some((path.clone(), source.clone(), result, parser.language())),
109                    Err(e) => {
110                        tracing::warn!("parse failed for {}: {e}", path.display());
111                        None
112                    }
113                }
114            })
115            .collect();
116
117        // Phase 2: build ResolveContext
118        let parsed_files: HashMap<PathBuf, ParseResult> = parse_results
119            .iter()
120            .map(|(path, _, result, _)| (path.clone(), result.clone()))
121            .collect();
122
123        let file_tree: Vec<PathBuf> = files.iter().map(|(p, _)| p.clone()).collect();
124
125        let context = ResolveContext {
126            project_root: project_root.to_path_buf(),
127            parsed_files,
128            file_tree,
129        };
130
131        // Phase 3: resolve imports (parallel)
132        let resolver_registry = ResolverRegistry::new(project_root);
133
134        let file_data: Vec<FileData> = parse_results
135            .par_iter()
136            .map(|(path, source, parse_result, lang)| {
137                let resolved_edges = resolver_registry
138                    .resolve_file(path, *lang, parse_result, &context)
139                    .unwrap_or_else(|e| {
140                        tracing::warn!("resolve failed for {}: {e}", path.display());
141                        vec![]
142                    });
143
144                let mut all_edges: Vec<Edge> = parse_result.edges.clone();
145                all_edges.extend(resolved_edges);
146
147                let file = FileNode {
148                    path: path.clone(),
149                    language: *lang,
150                    hash: Self::compute_hash(source),
151                };
152
153                FileData {
154                    file,
155                    symbols: parse_result.symbols.clone(),
156                    edges: all_edges,
157                }
158            })
159            .collect();
160
161        Ok(file_data)
162    }
163}
164
165// ---------------------------------------------------------------------------
166// NoOpGitProvider — eval doesn't need git operations
167// ---------------------------------------------------------------------------
168
169use domain::model::DiffHunk;
170use domain::ports::GitProvider;
171
172pub struct NoOpGitProvider;
173
174impl GitProvider for NoOpGitProvider {
175    fn current_head(&self) -> Result<String> {
176        Ok("eval".into())
177    }
178    fn changed_files(&self, _from: &str, _to: &str) -> Result<Vec<PathBuf>> {
179        Ok(vec![])
180    }
181    fn diff_hunks(&self, _from: &str, _to: Option<&str>) -> Result<Vec<DiffHunk>> {
182        Ok(vec![])
183    }
184    fn modified_files(&self) -> Result<Vec<PathBuf>> {
185        Ok(vec![])
186    }
187}