Skip to main content

code_analyze_core/languages/
java.rs

1// SPDX-FileCopyrightText: 2026 code-analyze-mcp contributors
2// SPDX-License-Identifier: Apache-2.0
3/// Tree-sitter query for extracting Java elements (methods and classes).
4pub const ELEMENT_QUERY: &str = r"
5(method_declaration
6  name: (identifier) @method_name) @function
7(class_declaration
8  name: (identifier) @class_name) @class
9(interface_declaration
10  name: (identifier) @interface_name) @class
11(enum_declaration
12  name: (identifier) @enum_name) @class
13";
14
15/// Tree-sitter query for extracting function calls.
16pub const CALL_QUERY: &str = r"
17(method_invocation
18  name: (identifier) @call)
19";
20
21/// Tree-sitter query for extracting type references.
22pub const REFERENCE_QUERY: &str = r"
23(type_identifier) @type_ref
24";
25
26/// Tree-sitter query for extracting Java imports.
27pub const IMPORT_QUERY: &str = r"
28(import_declaration) @import_path
29";
30
31/// Tree-sitter query for extracting definition and use sites.
32pub const DEFUSE_QUERY: &str = r"
33(local_variable_declaration declarator: (variable_declarator name: (identifier) @write.local))
34(assignment_expression left: (identifier) @write.assign)
35(update_expression (identifier) @writeread.update)
36(identifier) @read.usage
37";
38
39use tree_sitter::Node;
40
41/// Extract inheritance information from a Java class node.
42#[must_use]
43pub fn extract_inheritance(node: &Node, source: &str) -> Vec<String> {
44    let mut inherits = Vec::new();
45
46    // Extract superclass (extends)
47    if let Some(superclass) = node.child_by_field_name("superclass") {
48        for i in 0..superclass.named_child_count() {
49            if let Some(child) = superclass.named_child(u32::try_from(i).unwrap_or(u32::MAX))
50                && child.kind() == "type_identifier"
51            {
52                let text = &source[child.start_byte()..child.end_byte()];
53                inherits.push(format!("extends {text}"));
54            }
55        }
56    }
57
58    // Extract interfaces (implements)
59    if let Some(interfaces) = node.child_by_field_name("interfaces") {
60        for i in 0..interfaces.named_child_count() {
61            if let Some(type_list) = interfaces.named_child(u32::try_from(i).unwrap_or(u32::MAX)) {
62                for j in 0..type_list.named_child_count() {
63                    if let Some(type_node) =
64                        type_list.named_child(u32::try_from(j).unwrap_or(u32::MAX))
65                        && type_node.kind() == "type_identifier"
66                    {
67                        let text = &source[type_node.start_byte()..type_node.end_byte()];
68                        inherits.push(format!("implements {text}"));
69                    }
70                }
71            }
72        }
73    }
74
75    inherits
76}
77
78#[cfg(all(test, feature = "lang-java"))]
79mod tests {
80    use super::*;
81    use crate::DefUseKind;
82    use crate::parser::SemanticExtractor;
83    use tree_sitter::{Parser, StreamingIterator};
84
85    fn parse_java(src: &str) -> tree_sitter::Tree {
86        let mut parser = Parser::new();
87        parser
88            .set_language(&tree_sitter_java::LANGUAGE.into())
89            .expect("Error loading Java language");
90        parser.parse(src, None).expect("Failed to parse Java")
91    }
92
93    #[test]
94    fn test_java_element_query_happy_path() {
95        // Arrange
96        let src = "class Animal { void eat() {} }";
97        let tree = parse_java(src);
98        let root = tree.root_node();
99
100        // Act -- verify ELEMENT_QUERY compiles and matches class + method
101        let query = tree_sitter::Query::new(&tree_sitter_java::LANGUAGE.into(), ELEMENT_QUERY)
102            .expect("ELEMENT_QUERY must be valid");
103        let mut cursor = tree_sitter::QueryCursor::new();
104        let mut matches = cursor.matches(&query, root, src.as_bytes());
105
106        let mut captured_classes: Vec<String> = Vec::new();
107        let mut captured_functions: Vec<String> = Vec::new();
108        while let Some(mat) = matches.next() {
109            for capture in mat.captures {
110                let name = query.capture_names()[capture.index as usize];
111                let node = capture.node;
112                match name {
113                    "class" => {
114                        if let Some(n) = node.child_by_field_name("name") {
115                            captured_classes.push(src[n.start_byte()..n.end_byte()].to_string());
116                        }
117                    }
118                    "function" => {
119                        if let Some(n) = node.child_by_field_name("name") {
120                            captured_functions.push(src[n.start_byte()..n.end_byte()].to_string());
121                        }
122                    }
123                    _ => {}
124                }
125            }
126        }
127
128        // Assert
129        assert!(
130            captured_classes.contains(&"Animal".to_string()),
131            "expected Animal class, got {:?}",
132            captured_classes
133        );
134        assert!(
135            captured_functions.contains(&"eat".to_string()),
136            "expected eat function, got {:?}",
137            captured_functions
138        );
139    }
140
141    #[test]
142    fn test_java_extract_inheritance() {
143        // Arrange
144        let src = "class Dog extends Animal implements ICanRun, ICanSwim {}";
145        let tree = parse_java(src);
146        let root = tree.root_node();
147
148        // Act -- find the class_declaration node and call extract_inheritance
149        let mut class_node: Option<tree_sitter::Node> = None;
150        let mut stack = vec![root];
151        while let Some(node) = stack.pop() {
152            if node.kind() == "class_declaration" {
153                class_node = Some(node);
154                break;
155            }
156            for i in 0..node.child_count() {
157                if let Some(child) = node.child(u32::try_from(i).unwrap_or(u32::MAX)) {
158                    stack.push(child);
159                }
160            }
161        }
162        let class = class_node.expect("class_declaration not found");
163        let bases = extract_inheritance(&class, src);
164
165        // Assert
166        assert!(
167            bases.iter().any(|b| b.contains("Animal")),
168            "expected extends Animal, got {:?}",
169            bases
170        );
171        assert!(
172            bases.iter().any(|b| b.contains("ICanRun")),
173            "expected implements ICanRun, got {:?}",
174            bases
175        );
176        assert!(
177            bases.iter().any(|b| b.contains("ICanSwim")),
178            "expected implements ICanSwim, got {:?}",
179            bases
180        );
181    }
182
183    #[test]
184    fn test_defuse_query_write_site() {
185        // Arrange
186        let src = "class C { void m() { int z = 5; } }\n";
187        let sites =
188            SemanticExtractor::extract_def_use_for_file(src, "java", "z", "test.java", None);
189        assert!(!sites.is_empty(), "defuse sites should not be empty");
190        let has_write = sites.iter().any(|s| matches!(s.kind, DefUseKind::Write));
191        assert!(has_write, "should contain a Write DefUseSite");
192    }
193}