testgap_core/
language_registry.rs1use crate::types::Language;
2use std::path::Path;
3
4pub fn get_language(lang: Language) -> tree_sitter::Language {
6 match lang {
7 Language::Rust => tree_sitter_rust::LANGUAGE.into(),
8 Language::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
9 Language::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
10 Language::Python => tree_sitter_python::LANGUAGE.into(),
11 Language::Go => tree_sitter_go::LANGUAGE.into(),
12 }
13}
14
15pub fn function_query(lang: Language) -> &'static str {
17 match lang {
18 Language::Rust => {
19 r#"
20 (function_item
21 name: (identifier) @name
22 ) @function
23 "#
24 }
25 Language::JavaScript => {
26 r#"
27 (function_declaration
28 name: (identifier) @name
29 ) @function
30
31 (export_statement
32 declaration: (function_declaration
33 name: (identifier) @name
34 ) @function
35 )
36
37 (lexical_declaration
38 (variable_declarator
39 name: (identifier) @name
40 value: (arrow_function) @function
41 )
42 )
43
44 (variable_declaration
45 (variable_declarator
46 name: (identifier) @name
47 value: (arrow_function) @function
48 )
49 )
50 "#
51 }
52 Language::TypeScript => {
53 r#"
55 (function_declaration
56 name: (identifier) @name
57 ) @function
58
59 (export_statement
60 declaration: (function_declaration
61 name: (identifier) @name
62 ) @function
63 )
64
65 (lexical_declaration
66 (variable_declarator
67 name: (identifier) @name
68 value: (arrow_function) @function
69 )
70 )
71 "#
72 }
73 Language::Python => {
74 r#"
75 (function_definition
76 name: (identifier) @name
77 ) @function
78 "#
79 }
80 Language::Go => {
81 r#"
82 (function_declaration
83 name: (identifier) @name
84 ) @function
85
86 (method_declaration
87 name: (field_identifier) @name
88 ) @function
89 "#
90 }
91 }
92}
93
94pub fn is_test_file(
96 path: &Path,
97 test_dirs: &[String],
98 suffixes: &[String],
99 prefixes: &[String],
100) -> bool {
101 let path_str = path.to_string_lossy();
102
103 for dir in test_dirs {
105 if path_str.contains(&format!("/{dir}/")) || path_str.contains(&format!("\\{dir}\\")) {
106 return true;
107 }
108 }
109
110 if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
112 let full_name = path.file_name().and_then(|s| s.to_str()).unwrap_or("");
114
115 for suffix in suffixes {
116 if stem.ends_with(suffix) || full_name.contains(&format!("{suffix}.")) {
117 return true;
118 }
119 }
120 for prefix in prefixes {
121 if stem.starts_with(prefix) {
122 return true;
123 }
124 }
125 }
126
127 false
129}
130
131pub fn detect_language(path: &Path) -> Option<Language> {
133 path.extension()
134 .and_then(|ext| ext.to_str())
135 .and_then(Language::from_extension)
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use std::path::PathBuf;
142
143 fn test_dirs() -> Vec<String> {
145 vec![
146 "tests".into(),
147 "test".into(),
148 "__tests__".into(),
149 "spec".into(),
150 ]
151 }
152
153 fn test_suffixes() -> Vec<String> {
154 vec![
155 "_test".into(),
156 ".test".into(),
157 ".spec".into(),
158 "_spec".into(),
159 ]
160 }
161
162 fn test_prefixes() -> Vec<String> {
163 vec!["test_".into()]
164 }
165
166 #[test]
169 fn detect_language_rust() {
170 assert_eq!(
171 detect_language(Path::new("src/lib.rs")),
172 Some(Language::Rust)
173 );
174 }
175
176 #[test]
177 fn detect_language_javascript() {
178 assert_eq!(
179 detect_language(Path::new("index.js")),
180 Some(Language::JavaScript)
181 );
182 }
183
184 #[test]
185 fn detect_language_jsx() {
186 assert_eq!(
187 detect_language(Path::new("App.jsx")),
188 Some(Language::JavaScript)
189 );
190 }
191
192 #[test]
193 fn detect_language_typescript() {
194 assert_eq!(
195 detect_language(Path::new("main.ts")),
196 Some(Language::TypeScript)
197 );
198 }
199
200 #[test]
201 fn detect_language_tsx() {
202 assert_eq!(
203 detect_language(Path::new("App.tsx")),
204 Some(Language::TypeScript)
205 );
206 }
207
208 #[test]
209 fn detect_language_python() {
210 assert_eq!(
211 detect_language(Path::new("main.py")),
212 Some(Language::Python)
213 );
214 }
215
216 #[test]
217 fn detect_language_go() {
218 assert_eq!(detect_language(Path::new("main.go")), Some(Language::Go));
219 }
220
221 #[test]
222 fn detect_language_unsupported_returns_none() {
223 assert_eq!(detect_language(Path::new("README.txt")), None);
224 assert_eq!(detect_language(Path::new("docs.md")), None);
225 }
226
227 #[test]
228 fn detect_language_no_extension_returns_none() {
229 assert_eq!(detect_language(Path::new("Makefile")), None);
230 }
231
232 #[test]
235 fn is_test_file_in_tests_dir() {
236 let path = PathBuf::from("project/tests/foo.rs");
237 assert!(is_test_file(
238 &path,
239 &test_dirs(),
240 &test_suffixes(),
241 &test_prefixes()
242 ));
243 }
244
245 #[test]
246 fn is_test_file_in_test_dir() {
247 let path = PathBuf::from("project/test/helper.js");
248 assert!(is_test_file(
249 &path,
250 &test_dirs(),
251 &test_suffixes(),
252 &test_prefixes()
253 ));
254 }
255
256 #[test]
257 fn is_test_file_in_dunder_tests_dir() {
258 let path = PathBuf::from("src/__tests__/App.test.tsx");
259 assert!(is_test_file(
260 &path,
261 &test_dirs(),
262 &test_suffixes(),
263 &test_prefixes()
264 ));
265 }
266
267 #[test]
268 fn is_test_file_in_spec_dir() {
269 let path = PathBuf::from("project/spec/helper_spec.rb");
270 assert!(is_test_file(
271 &path,
272 &test_dirs(),
273 &test_suffixes(),
274 &test_prefixes()
275 ));
276 }
277
278 #[test]
279 fn is_test_file_with_test_suffix() {
280 let path = PathBuf::from("src/foo_test.rs");
281 assert!(is_test_file(
282 &path,
283 &test_dirs(),
284 &test_suffixes(),
285 &test_prefixes()
286 ));
287 }
288
289 #[test]
290 fn is_test_file_with_dot_test_suffix() {
291 let path = PathBuf::from("src/foo.test.ts");
292 assert!(is_test_file(
293 &path,
294 &test_dirs(),
295 &test_suffixes(),
296 &test_prefixes()
297 ));
298 }
299
300 #[test]
301 fn is_test_file_with_spec_suffix() {
302 let path = PathBuf::from("src/foo.spec.js");
303 assert!(is_test_file(
304 &path,
305 &test_dirs(),
306 &test_suffixes(),
307 &test_prefixes()
308 ));
309 }
310
311 #[test]
312 fn is_test_file_with_test_prefix() {
313 let path = PathBuf::from("src/test_foo.py");
314 assert!(is_test_file(
315 &path,
316 &test_dirs(),
317 &test_suffixes(),
318 &test_prefixes()
319 ));
320 }
321
322 #[test]
325 fn is_test_file_regular_source_rs() {
326 let path = PathBuf::from("src/lib.rs");
327 assert!(!is_test_file(
328 &path,
329 &test_dirs(),
330 &test_suffixes(),
331 &test_prefixes()
332 ));
333 }
334
335 #[test]
336 fn is_test_file_regular_source_go() {
337 let path = PathBuf::from("cmd/main.go");
338 assert!(!is_test_file(
339 &path,
340 &test_dirs(),
341 &test_suffixes(),
342 &test_prefixes()
343 ));
344 }
345
346 #[test]
347 fn is_test_file_regular_source_py() {
348 let path = PathBuf::from("src/utils.py");
349 assert!(!is_test_file(
350 &path,
351 &test_dirs(),
352 &test_suffixes(),
353 &test_prefixes()
354 ));
355 }
356}