Skip to main content

aptu_coder_core/languages/
cpp.rs

1// SPDX-FileCopyrightText: 2026 aptu-coder contributors
2// SPDX-License-Identifier: Apache-2.0
3use tree_sitter::Node;
4
5/// Tree-sitter query for extracting C/C++ elements (functions, classes, and structures).
6pub const ELEMENT_QUERY: &str = r"
7(function_definition
8  declarator: (function_declarator
9    declarator: (identifier) @func_name)) @function
10(function_definition
11  declarator: (function_declarator
12    declarator: (qualified_identifier
13      name: (identifier) @method_name))) @function
14(class_specifier
15  name: (type_identifier) @class_name) @class
16(struct_specifier
17  name: (type_identifier) @class_name) @class
18(template_declaration
19  (function_definition
20    declarator: (function_declarator
21      declarator: (identifier) @func_name))) @function
22";
23
24/// Tree-sitter query for extracting function calls.
25pub const CALL_QUERY: &str = r"
26(call_expression
27  function: (identifier) @call)
28(call_expression
29  function: (field_expression field: (field_identifier) @call))
30";
31
32/// Tree-sitter query for extracting type references.
33pub const REFERENCE_QUERY: &str = r"
34(type_identifier) @type_ref
35";
36
37/// Tree-sitter query for extracting C/C++ preprocessor directives (#include).
38pub const IMPORT_QUERY: &str = r"
39(preproc_include
40  path: (string_literal) @import_path)
41(preproc_include
42  path: (system_lib_string) @import_path)
43";
44
45/// Tree-sitter query for extracting definition and use sites.
46pub const DEFUSE_QUERY: &str = r"
47(init_declarator declarator: (identifier) @write.decl)
48(assignment_expression left: (identifier) @write.assign)
49(update_expression argument: (identifier) @writeread.update)
50(identifier) @read.usage
51";
52
53/// Extract the function name from a C/C++ `function_definition` node by
54/// walking the declarator chain: declarator -> function_declarator -> declarator -> identifier.
55#[must_use]
56pub fn extract_function_name(node: &Node, source: &str, _lang: &str) -> Option<String> {
57    node.child_by_field_name("declarator")
58        .and_then(|decl| extract_declarator_name(decl, source))
59}
60
61/// Find method name for a receiver type (class/struct context).
62#[must_use]
63pub fn find_method_for_receiver(
64    node: &Node,
65    source: &str,
66    _depth: Option<usize>,
67) -> Option<String> {
68    if node.kind() != "function_definition" {
69        return None;
70    }
71
72    // Walk up to find if we're in a class_specifier or struct_specifier
73    let mut parent = node.parent();
74    let mut in_class = false;
75    while let Some(p) = parent {
76        if p.kind() == "class_specifier" || p.kind() == "struct_specifier" {
77            in_class = true;
78            break;
79        }
80        parent = p.parent();
81    }
82
83    if !in_class {
84        return None;
85    }
86
87    // Extract the method name from function_declarator
88    if let Some(decl) = node.child_by_field_name("declarator") {
89        extract_declarator_name(decl, source)
90    } else {
91        None
92    }
93}
94
95/// Extract inheritance information from a class_specifier or struct_specifier node.
96#[must_use]
97pub fn extract_inheritance(node: &Node, source: &str) -> Vec<String> {
98    let mut inherits = Vec::new();
99
100    if node.kind() != "class_specifier" && node.kind() != "struct_specifier" {
101        return inherits;
102    }
103
104    // Look for base_class_clause child
105    for i in 0..node.named_child_count() {
106        if let Some(child) = node.named_child(u32::try_from(i).unwrap_or(u32::MAX))
107            && child.kind() == "base_class_clause"
108        {
109            // Walk base_class_clause for type_identifier nodes
110            for j in 0..child.named_child_count() {
111                if let Some(base) = child.named_child(u32::try_from(j).unwrap_or(u32::MAX))
112                    && base.kind() == "type_identifier"
113                {
114                    let text = &source[base.start_byte()..base.end_byte()];
115                    inherits.push(text.to_string());
116                }
117            }
118        }
119    }
120
121    inherits
122}
123
124/// Helper: extract name from a declarator node (handles identifiers and qualified identifiers).
125fn extract_declarator_name(node: Node, source: &str) -> Option<String> {
126    match node.kind() {
127        "identifier" | "field_identifier" => {
128            let start = node.start_byte();
129            let end = node.end_byte();
130            if end <= source.len() {
131                Some(source[start..end].to_string())
132            } else {
133                None
134            }
135        }
136        "qualified_identifier" => node.child_by_field_name("name").and_then(|n| {
137            let start = n.start_byte();
138            let end = n.end_byte();
139            if end <= source.len() {
140                Some(source[start..end].to_string())
141            } else {
142                None
143            }
144        }),
145        "function_declarator" => node
146            .child_by_field_name("declarator")
147            .and_then(|n| extract_declarator_name(n, source)),
148        "pointer_declarator" => node
149            .child_by_field_name("declarator")
150            .and_then(|n| extract_declarator_name(n, source)),
151        "reference_declarator" => node
152            .child_by_field_name("declarator")
153            .and_then(|n| extract_declarator_name(n, source)),
154        _ => None,
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use crate::DefUseKind;
162    use crate::parser::SemanticExtractor;
163    use tree_sitter::Parser;
164
165    fn parse_cpp(source: &str) -> tree_sitter::Tree {
166        let mut parser = Parser::new();
167        parser
168            .set_language(&tree_sitter_cpp::LANGUAGE.into())
169            .expect("failed to set C++ language");
170        parser.parse(source, None).expect("failed to parse source")
171    }
172
173    #[test]
174    fn test_free_function() {
175        // Arrange: free function definition
176        let source = "int add(int a, int b) { return a + b; }";
177        let tree = parse_cpp(source);
178        let root = tree.root_node();
179        let func_node = root.named_child(0).expect("expected function_definition");
180        // Act
181        let result = find_method_for_receiver(&func_node, source, None);
182        // Assert: free function should not be a method
183        assert_eq!(result, None);
184    }
185
186    #[test]
187    fn test_class_with_method() {
188        // Arrange: class with method
189        let source = "class Foo { public: int getValue() { return 42; } };";
190        let tree = parse_cpp(source);
191        let root = tree.root_node();
192        // Find the function_definition inside the class
193        let func_node = find_node_by_kind(root, "function_definition").expect("expected function");
194        // Act
195        let result = find_method_for_receiver(&func_node, source, None);
196        // Assert: method inside class should be recognized
197        assert_eq!(result, Some("getValue".to_string()));
198    }
199
200    #[test]
201    fn test_struct() {
202        // Arrange: struct with no base class
203        let source = "struct Point { int x; int y; };";
204        let tree = parse_cpp(source);
205        let root = tree.root_node();
206        let struct_node =
207            find_node_by_kind(root, "struct_specifier").expect("expected struct_specifier");
208        // Assert: node kind is correct
209        assert_eq!(struct_node.kind(), "struct_specifier");
210        // Act + Assert: struct with no inheritance returns empty
211        let result = extract_inheritance(&struct_node, source);
212        assert!(
213            result.is_empty(),
214            "expected no inheritance, got: {result:?}"
215        );
216    }
217
218    #[test]
219    fn test_include_directive() {
220        use tree_sitter::StreamingIterator;
221        // Arrange
222        let source = "#include <stdio.h>\n#include \"myfile.h\"\n";
223        let tree = parse_cpp(source);
224        // Act: run IMPORT_QUERY
225        let lang: tree_sitter::Language = tree_sitter_cpp::LANGUAGE.into();
226        let query = tree_sitter::Query::new(&lang, super::IMPORT_QUERY)
227            .expect("IMPORT_QUERY must be valid");
228        let mut cursor = tree_sitter::QueryCursor::new();
229        let mut iter = cursor.captures(&query, tree.root_node(), source.as_bytes());
230        let mut captures: Vec<String> = Vec::new();
231        while let Some((m, _)) = iter.next() {
232            for c in m.captures {
233                let text = c
234                    .node
235                    .utf8_text(source.as_bytes())
236                    .unwrap_or("")
237                    .to_string();
238                captures.push(text);
239            }
240        }
241        // Assert: both includes captured
242        assert!(
243            captures.iter().any(|s| s.contains("stdio.h")),
244            "expected stdio.h in captures: {captures:?}"
245        );
246        assert!(
247            captures.iter().any(|s| s.contains("myfile.h")),
248            "expected myfile.h in captures: {captures:?}"
249        );
250    }
251
252    #[test]
253    fn test_template_function() {
254        use tree_sitter::StreamingIterator;
255        // Arrange: template function definition
256        let source = "template<typename T> T max(T a, T b) { return a > b ? a : b; }";
257        let tree = parse_cpp(source);
258        // Act: run ELEMENT_QUERY
259        let lang: tree_sitter::Language = tree_sitter_cpp::LANGUAGE.into();
260        let query = tree_sitter::Query::new(&lang, super::ELEMENT_QUERY)
261            .expect("ELEMENT_QUERY must be valid");
262        let mut cursor = tree_sitter::QueryCursor::new();
263        let mut iter = cursor.captures(&query, tree.root_node(), source.as_bytes());
264        let mut func_names: Vec<String> = Vec::new();
265        while let Some((m, _)) = iter.next() {
266            for c in m.captures {
267                let name = query.capture_names()[c.index as usize];
268                if name == "func_name" {
269                    if let Ok(text) = c.node.utf8_text(source.as_bytes()) {
270                        func_names.push(text.to_string());
271                    }
272                }
273            }
274        }
275        // Assert: "max" captured as func_name
276        assert!(
277            func_names.iter().any(|s| s == "max"),
278            "expected 'max' in func_names: {func_names:?}"
279        );
280    }
281
282    #[test]
283    fn test_class_with_inheritance() {
284        // Arrange: class with base class
285        let source = "class Derived : public Base { };";
286        let tree = parse_cpp(source);
287        let root = tree.root_node();
288        let class_node = find_node_by_kind(root, "class_specifier").expect("expected class");
289        // Act
290        let result = extract_inheritance(&class_node, source);
291        // Assert: should have "Base" as inheritance
292        assert!(!result.is_empty(), "expected inheritance information");
293        assert!(
294            result.iter().any(|s| s.contains("Base")),
295            "expected 'Base' in inheritance: {:?}",
296            result
297        );
298    }
299
300    /// Helper to find the first node of a given kind
301    fn find_node_by_kind<'a>(node: Node<'a>, kind: &str) -> Option<Node<'a>> {
302        if node.kind() == kind {
303            return Some(node);
304        }
305        for i in 0..node.child_count() {
306            if let Some(child) = node.child(u32::try_from(i).unwrap_or(u32::MAX))
307                && let Some(found) = find_node_by_kind(child, kind)
308            {
309                return Some(found);
310            }
311        }
312        None
313    }
314
315    #[test]
316    fn test_defuse_query_write_site() {
317        // Arrange
318        let src = "void f() { int a = 7; }\n";
319        let sites = SemanticExtractor::extract_def_use_for_file(src, "cpp", "a", "test.cpp", None);
320        assert!(!sites.is_empty(), "defuse sites should not be empty");
321        let has_write = sites.iter().any(|s| matches!(s.kind, DefUseKind::Write));
322        assert!(has_write, "should contain a Write DefUseSite");
323    }
324}