Skip to main content

aptu_coder_core/languages/
mod.rs

1// SPDX-FileCopyrightText: 2026 aptu-coder contributors
2// SPDX-License-Identifier: Apache-2.0
3//! Language-specific handlers and query definitions for tree-sitter parsing.
4//!
5//! Provides query strings and extraction handlers for supported languages.
6//! Language support is controlled by Cargo `lang-*` features (by default all
7//! available language handlers are enabled): Rust, Go, Java, JavaScript, Python,
8//! TypeScript, TSX, Fortran, C/C++, and C#.
9
10#[cfg(feature = "lang-cpp")]
11pub mod cpp;
12#[cfg(feature = "lang-csharp")]
13pub mod csharp;
14#[cfg(feature = "lang-fortran")]
15pub mod fortran;
16#[cfg(feature = "lang-go")]
17pub mod go;
18#[cfg(feature = "lang-java")]
19pub mod java;
20#[cfg(feature = "lang-javascript")]
21pub mod javascript;
22#[cfg(feature = "lang-kotlin")]
23pub mod kotlin;
24#[cfg(feature = "lang-python")]
25pub mod python;
26#[cfg(feature = "lang-rust")]
27pub mod rust;
28#[cfg(any(feature = "lang-typescript", feature = "lang-tsx"))]
29pub mod typescript;
30
31use tree_sitter::{Language, Node};
32
33/// Extract the source text for a node with a bounds check.
34///
35/// Returns `None` if the node's byte range falls outside `source`.
36#[must_use]
37pub fn get_node_text(node: &Node, source: &str) -> Option<String> {
38    let end = node.end_byte();
39    if end <= source.len() {
40        Some(source[node.start_byte()..end].to_string())
41    } else {
42        None
43    }
44}
45
46/// Handler to extract function name from a node.
47pub type ExtractFunctionNameHandler = fn(&Node, &str, &str) -> Option<String>;
48
49/// Handler to find method name for a receiver type.
50pub type FindMethodForReceiverHandler = fn(&Node, &str, Option<usize>) -> Option<String>;
51
52/// Handler to find receiver type for a method.
53pub type FindReceiverTypeHandler = fn(&Node, &str) -> Option<String>;
54
55/// Handler to extract inheritance information from a class node.
56pub type ExtractInheritanceHandler = fn(&Node, &str) -> Vec<String>;
57
58/// Information about a supported language for code analysis.
59pub struct LanguageInfo {
60    pub name: &'static str,
61    pub language: Language,
62    pub element_query: &'static str,
63    pub call_query: &'static str,
64    pub reference_query: Option<&'static str>,
65    pub import_query: Option<&'static str>,
66    pub impl_query: Option<&'static str>,
67    pub impl_trait_query: Option<&'static str>,
68    pub defuse_query: Option<&'static str>,
69    pub extract_function_name: Option<ExtractFunctionNameHandler>,
70    pub find_method_for_receiver: Option<FindMethodForReceiverHandler>,
71    pub find_receiver_type: Option<FindReceiverTypeHandler>,
72    pub extract_inheritance: Option<ExtractInheritanceHandler>,
73}
74
75/// Get language information by language name.
76#[allow(clippy::too_many_lines)] // exhaustive match over all supported languages; splitting harms readability
77pub fn get_language_info(lang_name: &str) -> Option<LanguageInfo> {
78    match lang_name {
79        #[cfg(feature = "lang-rust")]
80        "rust" => Some(LanguageInfo {
81            name: "rust",
82            language: tree_sitter_rust::LANGUAGE.into(),
83            element_query: rust::ELEMENT_QUERY,
84            call_query: rust::CALL_QUERY,
85            reference_query: Some(rust::REFERENCE_QUERY),
86            import_query: Some(rust::IMPORT_QUERY),
87            impl_query: Some(rust::IMPL_QUERY),
88            impl_trait_query: Some(rust::IMPL_TRAIT_QUERY),
89            defuse_query: Some(rust::DEFUSE_QUERY),
90            extract_function_name: Some(rust::extract_function_name),
91            find_method_for_receiver: Some(rust::find_method_for_receiver),
92            find_receiver_type: Some(rust::find_receiver_type),
93            extract_inheritance: Some(rust::extract_inheritance),
94        }),
95        #[cfg(feature = "lang-python")]
96        "python" => Some(LanguageInfo {
97            name: "python",
98            language: tree_sitter_python::LANGUAGE.into(),
99            element_query: python::ELEMENT_QUERY,
100            call_query: python::CALL_QUERY,
101            reference_query: Some(python::REFERENCE_QUERY),
102            import_query: Some(python::IMPORT_QUERY),
103            impl_query: None,
104            impl_trait_query: None,
105            defuse_query: Some(python::DEFUSE_QUERY),
106            extract_function_name: None,
107            find_method_for_receiver: None,
108            find_receiver_type: None,
109            extract_inheritance: Some(python::extract_inheritance),
110        }),
111        #[cfg(feature = "lang-typescript")]
112        "typescript" => Some(LanguageInfo {
113            name: "typescript",
114            language: tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
115            element_query: typescript::ELEMENT_QUERY,
116            call_query: typescript::CALL_QUERY,
117            reference_query: Some(typescript::REFERENCE_QUERY),
118            import_query: Some(typescript::IMPORT_QUERY),
119            impl_query: None,
120            impl_trait_query: None,
121            defuse_query: Some(typescript::DEFUSE_QUERY),
122            extract_function_name: None,
123            find_method_for_receiver: None,
124            find_receiver_type: None,
125            extract_inheritance: Some(typescript::extract_inheritance),
126        }),
127        #[cfg(feature = "lang-tsx")]
128        "tsx" => Some(LanguageInfo {
129            name: "tsx",
130            language: tree_sitter_typescript::LANGUAGE_TSX.into(),
131            element_query: typescript::ELEMENT_QUERY,
132            call_query: typescript::CALL_QUERY,
133            reference_query: Some(typescript::REFERENCE_QUERY),
134            import_query: Some(typescript::IMPORT_QUERY),
135            impl_query: None,
136            impl_trait_query: None,
137            defuse_query: Some(typescript::DEFUSE_QUERY),
138            extract_function_name: None,
139            find_method_for_receiver: None,
140            find_receiver_type: None,
141            extract_inheritance: Some(typescript::extract_inheritance),
142        }),
143        #[cfg(feature = "lang-go")]
144        "go" => Some(LanguageInfo {
145            name: "go",
146            language: tree_sitter_go::LANGUAGE.into(),
147            element_query: go::ELEMENT_QUERY,
148            call_query: go::CALL_QUERY,
149            reference_query: Some(go::REFERENCE_QUERY),
150            import_query: Some(go::IMPORT_QUERY),
151            impl_query: None,
152            impl_trait_query: None,
153            defuse_query: Some(go::DEFUSE_QUERY),
154            extract_function_name: Some(go::extract_function_name),
155            find_method_for_receiver: Some(go::find_method_for_receiver),
156            find_receiver_type: Some(go::find_receiver_type),
157            extract_inheritance: Some(go::extract_inheritance),
158        }),
159        #[cfg(feature = "lang-cpp")]
160        "c" | "cpp" => Some(LanguageInfo {
161            name: if lang_name == "c" { "c" } else { "cpp" },
162            language: tree_sitter_cpp::LANGUAGE.into(),
163            element_query: cpp::ELEMENT_QUERY,
164            call_query: cpp::CALL_QUERY,
165            reference_query: Some(cpp::REFERENCE_QUERY),
166            import_query: Some(cpp::IMPORT_QUERY),
167            impl_query: None,
168            impl_trait_query: None,
169            defuse_query: Some(cpp::DEFUSE_QUERY),
170            extract_function_name: Some(cpp::extract_function_name),
171            find_method_for_receiver: Some(cpp::find_method_for_receiver),
172            find_receiver_type: None,
173            extract_inheritance: Some(cpp::extract_inheritance),
174        }),
175        #[cfg(feature = "lang-java")]
176        "java" => Some(LanguageInfo {
177            name: "java",
178            language: tree_sitter_java::LANGUAGE.into(),
179            element_query: java::ELEMENT_QUERY,
180            call_query: java::CALL_QUERY,
181            reference_query: Some(java::REFERENCE_QUERY),
182            import_query: Some(java::IMPORT_QUERY),
183            impl_query: None,
184            impl_trait_query: None,
185            defuse_query: Some(java::DEFUSE_QUERY),
186            extract_function_name: Some(java::extract_function_name),
187            find_method_for_receiver: Some(java::find_method_for_receiver),
188            find_receiver_type: Some(java::find_receiver_type),
189            extract_inheritance: Some(java::extract_inheritance),
190        }),
191        #[cfg(feature = "lang-kotlin")]
192        "kotlin" => Some(LanguageInfo {
193            name: "kotlin",
194            language: tree_sitter_kotlin_ng::LANGUAGE.into(),
195            element_query: kotlin::ELEMENT_QUERY,
196            call_query: kotlin::CALL_QUERY,
197            reference_query: Some(kotlin::REFERENCE_QUERY),
198            import_query: Some(kotlin::IMPORT_QUERY),
199            impl_query: None,
200            impl_trait_query: None,
201            defuse_query: Some(kotlin::DEFUSE_QUERY),
202            extract_function_name: Some(kotlin::extract_function_name),
203            find_method_for_receiver: Some(kotlin::find_method_for_receiver),
204            find_receiver_type: Some(kotlin::find_receiver_type),
205            extract_inheritance: Some(kotlin::extract_inheritance),
206        }),
207        #[cfg(feature = "lang-fortran")]
208        "fortran" => Some(LanguageInfo {
209            name: "fortran",
210            language: tree_sitter_fortran::LANGUAGE.into(),
211            element_query: fortran::ELEMENT_QUERY,
212            call_query: fortran::CALL_QUERY,
213            reference_query: Some(fortran::REFERENCE_QUERY),
214            import_query: Some(fortran::IMPORT_QUERY),
215            impl_query: None,
216            impl_trait_query: None,
217            defuse_query: None,
218            extract_function_name: None,
219            find_method_for_receiver: None,
220            find_receiver_type: None,
221            extract_inheritance: Some(fortran::extract_inheritance),
222        }),
223        #[cfg(feature = "lang-csharp")]
224        "csharp" => Some(LanguageInfo {
225            name: "csharp",
226            language: tree_sitter_c_sharp::LANGUAGE.into(),
227            element_query: csharp::ELEMENT_QUERY,
228            call_query: csharp::CALL_QUERY,
229            reference_query: Some(csharp::REFERENCE_QUERY),
230            import_query: Some(csharp::IMPORT_QUERY),
231            impl_query: None,
232            impl_trait_query: None,
233            defuse_query: Some(csharp::DEFUSE_QUERY),
234            extract_function_name: Some(csharp::extract_function_name),
235            find_method_for_receiver: Some(csharp::find_method_for_receiver),
236            find_receiver_type: Some(csharp::find_receiver_type),
237            extract_inheritance: Some(csharp::extract_inheritance),
238        }),
239        #[cfg(feature = "lang-javascript")]
240        "javascript" => Some(LanguageInfo {
241            name: "javascript",
242            language: tree_sitter_javascript::LANGUAGE.into(),
243            element_query: javascript::ELEMENT_QUERY,
244            call_query: javascript::CALL_QUERY,
245            reference_query: None,
246            import_query: Some(javascript::IMPORT_QUERY),
247            impl_query: None,
248            impl_trait_query: None,
249            defuse_query: Some(javascript::DEFUSE_QUERY),
250            extract_function_name: Some(javascript::extract_function_name),
251            find_method_for_receiver: Some(javascript::find_method_for_receiver),
252            find_receiver_type: Some(javascript::find_receiver_type),
253            extract_inheritance: Some(javascript::extract_inheritance),
254        }),
255        _ => None,
256    }
257}
258
259/// Get the tree-sitter Language object for a given language name.
260///
261/// Returns `None` if the language is not supported or not compiled in.
262#[must_use]
263pub fn get_ts_language(lang_name: &str) -> Option<Language> {
264    match lang_name {
265        #[cfg(feature = "lang-rust")]
266        "rust" => Some(tree_sitter_rust::LANGUAGE.into()),
267        #[cfg(feature = "lang-python")]
268        "python" => Some(tree_sitter_python::LANGUAGE.into()),
269        #[cfg(feature = "lang-typescript")]
270        "typescript" => Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
271        #[cfg(feature = "lang-tsx")]
272        "tsx" => Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
273        #[cfg(feature = "lang-go")]
274        "go" => Some(tree_sitter_go::LANGUAGE.into()),
275        #[cfg(feature = "lang-cpp")]
276        "c" | "cpp" => Some(tree_sitter_cpp::LANGUAGE.into()),
277        #[cfg(feature = "lang-java")]
278        "java" => Some(tree_sitter_java::LANGUAGE.into()),
279        #[cfg(feature = "lang-kotlin")]
280        "kotlin" => Some(tree_sitter_kotlin_ng::LANGUAGE.into()),
281        #[cfg(feature = "lang-fortran")]
282        "fortran" => Some(tree_sitter_fortran::LANGUAGE.into()),
283        #[cfg(feature = "lang-csharp")]
284        "csharp" => Some(tree_sitter_c_sharp::LANGUAGE.into()),
285        #[cfg(feature = "lang-javascript")]
286        "javascript" => Some(tree_sitter_javascript::LANGUAGE.into()),
287        _ => None,
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294
295    #[test]
296    fn test_get_language_info_known() {
297        // Happy path: known languages return Some
298        assert!(
299            get_language_info("rust").is_some(),
300            "expected Some for 'rust'"
301        );
302        assert!(get_language_info("go").is_some(), "expected Some for 'go'");
303        assert!(
304            get_language_info("python").is_some(),
305            "expected Some for 'python'"
306        );
307    }
308
309    #[test]
310    fn test_get_language_info_unknown() {
311        // Edge case: unknown language returns None
312        assert!(
313            get_language_info("cobol").is_none(),
314            "expected None for 'cobol'"
315        );
316    }
317
318    #[test]
319    fn test_get_ts_language_known() {
320        // Happy path: known language returns Some
321        assert!(
322            get_ts_language("rust").is_some(),
323            "expected Some for 'rust'"
324        );
325    }
326
327    #[test]
328    fn test_get_ts_language_unknown() {
329        // Edge case: unknown language returns None
330        assert!(
331            get_ts_language("cobol").is_none(),
332            "expected None for 'cobol'"
333        );
334    }
335}