Skip to main content

code_analyze_core/languages/
rust.rs

1// SPDX-FileCopyrightText: 2026 code-analyze-mcp contributors
2// SPDX-License-Identifier: Apache-2.0
3use tree_sitter::Node;
4
5/// Tree-sitter query for extracting Rust elements (functions and structs/enums/traits).
6pub const ELEMENT_QUERY: &str = r"
7(function_item
8  name: (identifier) @func_name
9  parameters: (parameters) @params) @function
10(struct_item) @class
11(enum_item) @class
12(trait_item) @class
13";
14
15/// Tree-sitter query for extracting function calls.
16pub const CALL_QUERY: &str = r"
17(call_expression function: (identifier) @call)
18(call_expression function: (field_expression field: (field_identifier) @call))
19(call_expression function: (scoped_identifier name: (identifier) @call))
20";
21
22/// Tree-sitter query for extracting type references.
23pub const REFERENCE_QUERY: &str = r"
24(type_identifier) @type_ref
25";
26
27/// Tree-sitter query for extracting imports.
28pub const IMPORT_QUERY: &str = r"
29(use_declaration argument: (_) @import_path) @import
30";
31
32/// Tree-sitter query for extracting `impl Trait for Type` blocks.
33/// Captures the trait name and the concrete implementor type.
34// Note: matches only simple trait names (type_identifier). Scoped traits
35// (e.g. `impl io::Sink for T`) are not matched; scoped coverage is out of scope for v1.
36pub const IMPL_TRAIT_QUERY: &str = r"
37(impl_item
38  trait: (type_identifier) @trait_name
39  type: (type_identifier) @impl_type)
40";
41
42/// Tree-sitter query for extracting impl blocks and methods.
43pub const IMPL_QUERY: &str = r"
44(impl_item
45  type: (type_identifier) @impl_type
46  body: (declaration_list
47    (function_item
48      name: (identifier) @method_name
49      parameters: (parameters) @method_params) @method))
50";
51
52/// Tree-sitter query for extracting definition and use sites.
53/// Captures write sites (let declarations, assignment LHS), read sites (identifiers in expression context),
54/// and write-read sites (compound assignments, +=, etc.).
55pub const DEFUSE_QUERY: &str = r"
56(let_declaration pattern: (identifier) @write.decl)
57(assignment_expression left: (identifier) @write.assign)
58(compound_assignment_expr left: (identifier) @writeread.compound)
59(identifier) @read.usage
60";
61
62/// Extract function name from a function node.
63#[must_use]
64pub fn extract_function_name(node: &Node, source: &str, _query_name: &str) -> Option<String> {
65    if node.kind() != "function_item" {
66        return None;
67    }
68    node.child_by_field_name("name").and_then(|n| {
69        let start = n.start_byte();
70        let end = n.end_byte();
71        if end <= source.len() {
72            Some(source[start..end].to_string())
73        } else {
74            None
75        }
76    })
77}
78
79/// Find method name for a receiver type.
80#[must_use]
81pub fn find_method_for_receiver(
82    node: &Node,
83    source: &str,
84    _depth: Option<usize>,
85) -> Option<String> {
86    if node.kind() != "method_item" && node.kind() != "function_item" {
87        return None;
88    }
89    node.child_by_field_name("name").and_then(|n| {
90        let start = n.start_byte();
91        let end = n.end_byte();
92        if end <= source.len() {
93            Some(source[start..end].to_string())
94        } else {
95            None
96        }
97    })
98}
99
100/// Find receiver type for a method.
101#[must_use]
102pub fn find_receiver_type(node: &Node, source: &str) -> Option<String> {
103    if node.kind() != "impl_item" {
104        return None;
105    }
106    node.child_by_field_name("type").and_then(|n| {
107        let start = n.start_byte();
108        let end = n.end_byte();
109        if end <= source.len() {
110            Some(source[start..end].to_string())
111        } else {
112            None
113        }
114    })
115}
116
117/// Extract inheritance information from a Rust class node.
118/// Rust class nodes (`struct_item`, `enum_item`, `trait_item`) have no syntactic inheritance.
119/// Inheritance is via `impl` blocks, not on the type declaration itself.
120#[must_use]
121pub fn extract_inheritance(_node: &Node, _source: &str) -> Vec<String> {
122    Vec::new()
123}
124
125#[cfg(all(test, feature = "lang-rust"))]
126mod tests {
127    use super::*;
128    use crate::parser::SemanticExtractor;
129    use crate::types::DefUseKind;
130    use tree_sitter::Parser;
131
132    fn parse_rust(source: &str) -> tree_sitter::Tree {
133        let mut parser = Parser::new();
134        parser
135            .set_language(&tree_sitter_rust::LANGUAGE.into())
136            .expect("failed to set Rust language");
137        parser.parse(source, None).expect("failed to parse source")
138    }
139
140    #[test]
141    fn test_extract_function_name_happy_path() {
142        // Arrange
143        let source = "fn foo() {}";
144        let tree = parse_rust(source);
145        let root = tree.root_node();
146        // find the function_item node
147        let func_node = root.named_child(0).expect("expected child");
148        assert_eq!(func_node.kind(), "function_item");
149        // Act
150        let result = extract_function_name(&func_node, source, "function");
151        // Assert
152        assert_eq!(result, Some("foo".to_string()));
153    }
154
155    #[test]
156    fn test_extract_function_name_wrong_kind() {
157        // Arrange: parse a struct_item; not a function_item
158        let source = "struct Bar {}";
159        let tree = parse_rust(source);
160        let root = tree.root_node();
161        let node = root.named_child(0).expect("expected child");
162        assert_eq!(node.kind(), "struct_item");
163        // Act
164        let result = extract_function_name(&node, source, "class");
165        // Assert
166        assert_eq!(result, None);
167    }
168
169    #[test]
170    fn test_find_method_for_receiver_happy_path() {
171        // Arrange: function_item works for find_method_for_receiver
172        let source = "fn bar() {}";
173        let tree = parse_rust(source);
174        let root = tree.root_node();
175        let node = root.named_child(0).expect("expected child");
176        // Act
177        let result = find_method_for_receiver(&node, source, None);
178        // Assert
179        assert_eq!(result, Some("bar".to_string()));
180    }
181
182    #[test]
183    fn test_find_method_for_receiver_wrong_kind() {
184        // Arrange: struct_item is not method_item or function_item
185        let source = "struct Baz {}";
186        let tree = parse_rust(source);
187        let root = tree.root_node();
188        let node = root.named_child(0).expect("expected child");
189        // Act
190        let result = find_method_for_receiver(&node, source, None);
191        // Assert
192        assert_eq!(result, None);
193    }
194
195    #[test]
196    fn test_find_receiver_type_happy_path() {
197        // Arrange: impl block is an impl_item
198        let source = "struct Foo; impl Foo { fn x() {} }";
199        let tree = parse_rust(source);
200        let root = tree.root_node();
201        // find impl_item node
202        let impl_node = (0..root.named_child_count())
203            .filter_map(|i| root.named_child(i as u32))
204            .find(|n| n.kind() == "impl_item")
205            .expect("expected impl_item node");
206        // Act
207        let result = find_receiver_type(&impl_node, source);
208        // Assert
209        assert_eq!(result, Some("Foo".to_string()));
210    }
211
212    #[test]
213    fn test_find_receiver_type_wrong_kind() {
214        // Arrange: function_item is not impl_item
215        let source = "fn qux() {}";
216        let tree = parse_rust(source);
217        let root = tree.root_node();
218        let node = root.named_child(0).expect("expected child");
219        // Act
220        let result = find_receiver_type(&node, source);
221        // Assert
222        assert_eq!(result, None);
223    }
224
225    #[test]
226    fn test_defuse_query_write_site() {
227        // Arrange
228        let source = "fn foo() { let x = 5; }";
229        let sites =
230            SemanticExtractor::extract_def_use_for_file(source, "rust", "x", "test.rs", None);
231        // Act & Assert
232        assert!(!sites.is_empty(), "defuse sites should not be empty");
233        let has_write = sites.iter().any(|s| matches!(s.kind, DefUseKind::Write));
234        assert!(has_write, "should contain a Write DefUseSite");
235    }
236}