the_code_graph_parser/
registry.rs1use std::collections::HashMap;
2use std::path::Path;
3
4use domain::model::Language;
5
6use crate::{
7 GoParser, JavaScriptParser, LanguageParser, PythonParser, RustParser, TypeScriptParser,
8};
9
10pub struct ParserRegistry {
12 parsers: Vec<Box<dyn LanguageParser>>,
13 extension_map: HashMap<String, usize>,
14}
15
16impl ParserRegistry {
17 pub fn new() -> Self {
19 let mut registry = Self {
20 parsers: Vec::new(),
21 extension_map: HashMap::new(),
22 };
23 registry.register(Box::new(TypeScriptParser::new()));
24 registry.register(Box::new(JavaScriptParser::new()));
25 registry.register(Box::new(RustParser::new()));
26 registry.register(Box::new(PythonParser::new()));
27 registry.register(Box::new(GoParser::new()));
28 registry
29 }
30
31 fn register(&mut self, parser: Box<dyn LanguageParser>) {
32 let idx = self.parsers.len();
33 for ext in parser.file_extensions() {
34 self.extension_map.insert(ext.to_string(), idx);
35 }
36 self.parsers.push(parser);
37 }
38
39 pub fn parser_for_file(&self, path: &Path) -> Option<&dyn LanguageParser> {
41 let ext = path.extension()?.to_str()?;
42 let idx = self.extension_map.get(ext)?;
43 Some(self.parsers[*idx].as_ref())
44 }
45
46 pub fn parser_for_language(&self, lang: Language) -> Option<&dyn LanguageParser> {
48 self.parsers
49 .iter()
50 .find(|p| p.language() == lang)
51 .map(|p| p.as_ref())
52 }
53
54 pub fn supported_extensions(&self) -> Vec<&str> {
56 self.extension_map.keys().map(|s| s.as_str()).collect()
57 }
58}
59
60impl Default for ParserRegistry {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66const _: fn() = || {
68 fn assert_send_sync<T: Send + Sync>() {}
69 assert_send_sync::<ParserRegistry>();
70};
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use std::path::Path;
76
77 #[test]
78 fn parser_for_ts_file() {
79 let registry = ParserRegistry::new();
80 let parser = registry.parser_for_file(Path::new("foo.ts"));
81 assert!(parser.is_some());
82 assert_eq!(parser.unwrap().language(), Language::TypeScript);
83 }
84
85 #[test]
86 fn parser_for_tsx_file() {
87 let registry = ParserRegistry::new();
88 let parser = registry.parser_for_file(Path::new("foo.tsx"));
89 assert!(parser.is_some());
90 assert_eq!(parser.unwrap().language(), Language::TypeScript);
91 }
92
93 #[test]
94 fn parser_for_js_file() {
95 let registry = ParserRegistry::new();
96 let parser = registry.parser_for_file(Path::new("foo.js"));
97 assert!(parser.is_some());
98 assert_eq!(parser.unwrap().language(), Language::JavaScript);
99 }
100
101 #[test]
102 fn parser_for_jsx_file() {
103 let registry = ParserRegistry::new();
104 let parser = registry.parser_for_file(Path::new("foo.jsx"));
105 assert!(parser.is_some());
106 assert_eq!(parser.unwrap().language(), Language::JavaScript);
107 }
108
109 #[test]
110 fn parser_for_rs_file() {
111 let registry = ParserRegistry::new();
112 let parser = registry.parser_for_file(Path::new("foo.rs"));
113 assert!(parser.is_some());
114 assert_eq!(parser.unwrap().language(), Language::Rust);
115 }
116
117 #[test]
118 fn parser_for_py_file() {
119 let registry = ParserRegistry::new();
120 let parser = registry.parser_for_file(Path::new("foo.py"));
121 assert!(parser.is_some());
122 assert_eq!(parser.unwrap().language(), Language::Python);
123 }
124
125 #[test]
126 fn parser_for_go_file() {
127 let registry = ParserRegistry::new();
128 let parser = registry.parser_for_file(Path::new("foo.go"));
129 assert!(parser.is_some());
130 assert_eq!(parser.unwrap().language(), Language::Go);
131 }
132
133 #[test]
134 fn parser_for_txt_returns_none() {
135 let registry = ParserRegistry::new();
136 assert!(registry.parser_for_file(Path::new("foo.txt")).is_none());
137 }
138
139 #[test]
140 fn parser_for_language_typescript() {
141 let registry = ParserRegistry::new();
142 let parser = registry.parser_for_language(Language::TypeScript);
143 assert!(parser.is_some());
144 }
145
146 #[test]
147 fn parser_for_language_rust() {
148 let registry = ParserRegistry::new();
149 let parser = registry.parser_for_language(Language::Rust);
150 assert!(parser.is_some());
151 }
152
153 #[test]
154 fn supported_extensions_contains_all_five_languages() {
155 let registry = ParserRegistry::new();
156 let exts = registry.supported_extensions();
157 assert!(exts.contains(&"ts"));
159 assert!(exts.contains(&"tsx"));
160 assert!(exts.contains(&"js"));
161 assert!(exts.contains(&"jsx"));
162 assert!(exts.contains(&"rs"));
164 assert!(exts.contains(&"py"));
166 assert!(exts.contains(&"go"));
168 }
169
170 #[test]
171 fn registry_is_thread_safe() {
172 use std::sync::Arc;
173 use std::thread;
174
175 let registry = Arc::new(ParserRegistry::new());
176 let r1 = Arc::clone(®istry);
177 let r2 = Arc::clone(®istry);
178
179 let t1 = thread::spawn(move || {
180 let parser = r1.parser_for_file(Path::new("foo.rs"));
181 assert!(parser.is_some());
182 assert_eq!(parser.unwrap().language(), Language::Rust);
183 });
184
185 let t2 = thread::spawn(move || {
186 let parser = r2.parser_for_file(Path::new("foo.py"));
187 assert!(parser.is_some());
188 assert_eq!(parser.unwrap().language(), Language::Python);
189 });
190
191 t1.join().expect("thread 1 panicked");
192 t2.join().expect("thread 2 panicked");
193 }
194
195 #[test]
196 fn ac51_parse_from_multiple_threads_all_languages() {
197 use std::sync::Arc;
198 use std::thread;
199
200 let registry = Arc::new(ParserRegistry::new());
201
202 let sources: Vec<(&str, &str)> = vec![
203 ("test.rs", "fn hello() {} struct Foo {}"),
204 ("test.py", "def hello(): pass\nclass Foo: pass"),
205 ("test.go", "package main\nfunc Hello() {}"),
206 ];
207
208 let handles: Vec<_> = sources
209 .into_iter()
210 .map(|(filename, source)| {
211 let reg = Arc::clone(®istry);
212 let filename = filename.to_string();
213 let source = source.to_string();
214 thread::spawn(move || {
215 let parser = reg
216 .parser_for_file(Path::new(&filename))
217 .expect("parser should exist");
218 let result = parser.parse(source.as_bytes(), Path::new(&filename));
219 assert!(result.is_ok(), "parse failed for {filename}");
220 let pr = result.unwrap();
221 assert!(!pr.symbols.is_empty(), "expected symbols from {filename}");
222 })
223 })
224 .collect();
225
226 for h in handles {
227 h.join().expect("thread panicked");
228 }
229 }
230}