codegraph_go/
parser_impl.rs

1//! Implementation of the CodeParser trait for Go
2
3use codegraph::CodeGraph;
4use codegraph_parser_api::{
5    CodeIR, CodeParser, FileInfo, ParserConfig, ParserError, ParserMetrics, ProjectInfo,
6};
7use std::fs;
8use std::path::{Path, PathBuf};
9use std::sync::Mutex;
10use std::time::{Duration, Instant};
11
12use crate::extractor;
13use crate::mapper;
14
15/// Go language parser implementing the CodeParser trait
16pub struct GoParser {
17    config: ParserConfig,
18    metrics: Mutex<ParserMetrics>,
19}
20
21impl GoParser {
22    pub fn new() -> Self {
23        Self {
24            config: ParserConfig::default(),
25            metrics: Mutex::new(ParserMetrics::default()),
26        }
27    }
28
29    pub fn with_config(config: ParserConfig) -> Self {
30        Self {
31            config,
32            metrics: Mutex::new(ParserMetrics::default()),
33        }
34    }
35
36    fn update_metrics(
37        &self,
38        success: bool,
39        duration: Duration,
40        entities: usize,
41        relationships: usize,
42    ) {
43        let mut metrics = self.metrics.lock().unwrap();
44        metrics.files_attempted += 1;
45        if success {
46            metrics.files_succeeded += 1;
47        } else {
48            metrics.files_failed += 1;
49        }
50        metrics.total_parse_time += duration;
51        metrics.total_entities += entities;
52        metrics.total_relationships += relationships;
53    }
54
55    fn ir_to_graph(
56        &self,
57        ir: &CodeIR,
58        graph: &mut CodeGraph,
59        file_path: &Path,
60    ) -> Result<FileInfo, ParserError> {
61        mapper::ir_to_graph(ir, graph, file_path)
62    }
63}
64
65impl Default for GoParser {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71impl CodeParser for GoParser {
72    fn language(&self) -> &str {
73        "go"
74    }
75
76    fn file_extensions(&self) -> &[&str] {
77        &[".go"]
78    }
79
80    fn parse_file(&self, path: &Path, graph: &mut CodeGraph) -> Result<FileInfo, ParserError> {
81        let start = Instant::now();
82        let metadata =
83            fs::metadata(path).map_err(|e| ParserError::IoError(path.to_path_buf(), e))?;
84
85        if metadata.len() as usize > self.config.max_file_size {
86            return Err(ParserError::FileTooLarge(
87                path.to_path_buf(),
88                metadata.len() as usize,
89            ));
90        }
91
92        let source =
93            fs::read_to_string(path).map_err(|e| ParserError::IoError(path.to_path_buf(), e))?;
94        let result = self.parse_source(&source, path, graph);
95
96        let duration = start.elapsed();
97        if let Ok(ref info) = result {
98            self.update_metrics(true, duration, info.entity_count(), 0);
99        } else {
100            self.update_metrics(false, duration, 0, 0);
101        }
102
103        result
104    }
105
106    fn parse_source(
107        &self,
108        source: &str,
109        file_path: &Path,
110        graph: &mut CodeGraph,
111    ) -> Result<FileInfo, ParserError> {
112        let start = Instant::now();
113        let ir = extractor::extract(source, file_path, &self.config)?;
114        let mut file_info = self.ir_to_graph(&ir, graph, file_path)?;
115
116        file_info.parse_time = start.elapsed();
117        file_info.line_count = source.lines().count();
118        file_info.byte_count = source.len();
119
120        Ok(file_info)
121    }
122
123    fn config(&self) -> &ParserConfig {
124        &self.config
125    }
126
127    fn metrics(&self) -> ParserMetrics {
128        self.metrics.lock().unwrap().clone()
129    }
130
131    fn reset_metrics(&mut self) {
132        *self.metrics.lock().unwrap() = ParserMetrics::default();
133    }
134
135    fn parse_files(
136        &self,
137        paths: &[PathBuf],
138        graph: &mut CodeGraph,
139    ) -> Result<ProjectInfo, ParserError> {
140        if self.config.parallel {
141            self.parse_files_parallel(paths, graph)
142        } else {
143            self.parse_files_sequential(paths, graph)
144        }
145    }
146}
147
148impl GoParser {
149    /// Parse files sequentially
150    fn parse_files_sequential(
151        &self,
152        paths: &[PathBuf],
153        graph: &mut CodeGraph,
154    ) -> Result<ProjectInfo, ParserError> {
155        let mut files = Vec::new();
156        let mut failed_files = Vec::new();
157        let mut total_functions = 0;
158        let mut total_classes = 0;
159        let mut total_parse_time = Duration::ZERO;
160
161        for path in paths {
162            match self.parse_file(path, graph) {
163                Ok(info) => {
164                    total_functions += info.functions.len();
165                    total_classes += info.classes.len();
166                    total_parse_time += info.parse_time;
167                    files.push(info);
168                }
169                Err(e) => {
170                    failed_files.push((path.clone(), e.to_string()));
171                }
172            }
173        }
174
175        Ok(ProjectInfo {
176            files,
177            failed_files,
178            total_functions,
179            total_classes,
180            total_parse_time,
181        })
182    }
183
184    /// Parse files in parallel using rayon
185    fn parse_files_parallel(
186        &self,
187        paths: &[PathBuf],
188        graph: &mut CodeGraph,
189    ) -> Result<ProjectInfo, ParserError> {
190        use rayon::prelude::*;
191
192        let graph_mutex = Mutex::new(graph);
193
194        // Configure thread pool if parallel_workers is specified
195        let pool = if let Some(num_threads) = self.config.parallel_workers {
196            rayon::ThreadPoolBuilder::new()
197                .num_threads(num_threads)
198                .build()
199                .map_err(|e| {
200                    ParserError::GraphError(format!("Failed to create thread pool: {e}"))
201                })?
202        } else {
203            rayon::ThreadPoolBuilder::new().build().map_err(|e| {
204                ParserError::GraphError(format!("Failed to create thread pool: {e}"))
205            })?
206        };
207
208        let results: Vec<_> = pool.install(|| {
209            paths
210                .par_iter()
211                .map(|path| {
212                    let mut graph = graph_mutex.lock().unwrap();
213                    match self.parse_file(path, &mut graph) {
214                        Ok(info) => Ok(info),
215                        Err(e) => Err((path.clone(), e.to_string())),
216                    }
217                })
218                .collect()
219        });
220
221        let mut files = Vec::new();
222        let mut failed_files = Vec::new();
223        let mut total_functions = 0;
224        let mut total_classes = 0;
225        let mut total_parse_time = Duration::ZERO;
226
227        for result in results {
228            match result {
229                Ok(info) => {
230                    total_functions += info.functions.len();
231                    total_classes += info.classes.len();
232                    total_parse_time += info.parse_time;
233                    files.push(info);
234                }
235                Err((path, error)) => {
236                    failed_files.push((path, error));
237                }
238            }
239        }
240
241        Ok(ProjectInfo {
242            files,
243            failed_files,
244            total_functions,
245            total_classes,
246            total_parse_time,
247        })
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254
255    #[test]
256    fn test_language() {
257        let parser = GoParser::new();
258        assert_eq!(parser.language(), "go");
259    }
260
261    #[test]
262    fn test_file_extensions() {
263        let parser = GoParser::new();
264        assert_eq!(parser.file_extensions(), &[".go"]);
265    }
266
267    #[test]
268    fn test_can_parse() {
269        let parser = GoParser::new();
270        assert!(parser.can_parse(Path::new("main.go")));
271        assert!(!parser.can_parse(Path::new("main.rs")));
272    }
273}