Skip to main content

deagle_parse/
java_parser.rs

1//! Java language parser using tree-sitter-java.
2
3use deagle_core::{DeagleError, EdgeKind, Language, Node, NodeKind, Result};
4use std::path::Path;
5
6use crate::ParseResult;
7
8pub fn parse(path: &Path, content: &str) -> Result<Vec<Node>> {
9    parse_with_edges(path, content).map(|r| r.nodes)
10}
11
12pub fn parse_with_edges(path: &Path, content: &str) -> Result<ParseResult> {
13    let mut parser = tree_sitter::Parser::new();
14    let language = tree_sitter_java::LANGUAGE;
15    parser.set_language(&language.into()).map_err(|e| DeagleError::Parse {
16        file: path.display().to_string(),
17        message: format!("Failed to set language: {}", e),
18    })?;
19
20    let tree = parser.parse(content, None).ok_or_else(|| DeagleError::Parse {
21        file: path.display().to_string(),
22        message: "Failed to parse file".into(),
23    })?;
24
25    let mut nodes = Vec::new();
26    let file_path = path.to_string_lossy().to_string();
27
28    nodes.push(Node {
29        id: 0,
30        name: path.file_name().and_then(|n| n.to_str()).unwrap_or("unknown").to_string(),
31        kind: NodeKind::File,
32        language: Language::Java,
33        file_path: file_path.clone(),
34        line_start: 1,
35        line_end: content.lines().count() as u32,
36        content: None,
37    });
38
39    extract_definitions(tree.root_node(), content, &file_path, &mut nodes, false);
40
41    let mut edges = Vec::new();
42    for i in 1..nodes.len() {
43        edges.push((0, i, EdgeKind::Contains));
44    }
45
46    Ok(ParseResult { nodes, edges })
47}
48
49fn extract_definitions(
50    node: tree_sitter::Node,
51    source: &str,
52    file_path: &str,
53    results: &mut Vec<Node>,
54    inside_class: bool,
55) {
56    let kind = match node.kind() {
57        "method_declaration" => Some(if inside_class { NodeKind::Method } else { NodeKind::Function }),
58        "constructor_declaration" => Some(NodeKind::Method),
59        "class_declaration" => Some(NodeKind::Class),
60        "interface_declaration" => Some(NodeKind::Interface),
61        "enum_declaration" => Some(NodeKind::Enum),
62        "import_declaration" => Some(NodeKind::Import),
63        "constant_declaration" => Some(NodeKind::Constant),
64        "field_declaration" => {
65            // Check for static final (constants)
66            let text = node.utf8_text(source.as_bytes()).unwrap_or_default();
67            if text.contains("static") && text.contains("final") {
68                Some(NodeKind::Constant)
69            } else {
70                None
71            }
72        }
73        "package_declaration" => Some(NodeKind::Module),
74        _ => None,
75    };
76
77    if let Some(kind) = kind {
78        if let Some(name) = extract_name(node, source, kind) {
79            let start = node.start_position();
80            let end = node.end_position();
81            let content = node.utf8_text(source.as_bytes()).ok().map(|s| {
82                crate::truncate_content(s, 500)
83            });
84
85            results.push(Node {
86                id: 0,
87                name,
88                kind,
89                language: Language::Java,
90                file_path: file_path.to_string(),
91                line_start: (start.row + 1) as u32,
92                line_end: (end.row + 1) as u32,
93                content,
94            });
95
96            if kind == NodeKind::Class || kind == NodeKind::Interface || kind == NodeKind::Enum {
97                if let Some(body) = node.child_by_field_name("body") {
98                    let mut cursor = body.walk();
99                    for child in body.children(&mut cursor) {
100                        extract_definitions(child, source, file_path, results, true);
101                    }
102                }
103                return;
104            }
105        }
106    }
107
108    if !matches!(node.kind(), "class_declaration" | "interface_declaration" | "enum_declaration") {
109        let mut cursor = node.walk();
110        for child in node.children(&mut cursor) {
111            extract_definitions(child, source, file_path, results, inside_class);
112        }
113    }
114}
115
116fn extract_name(node: tree_sitter::Node, source: &str, kind: NodeKind) -> Option<String> {
117    match kind {
118        NodeKind::Import | NodeKind::Module => {
119            node.utf8_text(source.as_bytes()).ok().map(|s| s.trim().to_string())
120        }
121        NodeKind::Constant => {
122            // For field_declaration with static final, find the variable_declarator name
123            let mut cursor = node.walk();
124            for child in node.children(&mut cursor) {
125                if child.kind() == "variable_declarator" {
126                    if let Some(n) = child.child_by_field_name("name") {
127                        return n.utf8_text(source.as_bytes()).ok().map(|s| s.to_string());
128                    }
129                }
130            }
131            node.child_by_field_name("name")
132                .and_then(|n| n.utf8_text(source.as_bytes()).ok())
133                .map(|s| s.to_string())
134        }
135        _ => {
136            node.child_by_field_name("name")
137                .and_then(|n| n.utf8_text(source.as_bytes()).ok())
138                .map(|s| s.to_string())
139        }
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use std::path::PathBuf;
147
148    const SAMPLE_JAVA: &str = r#"
149package com.example.app;
150
151import java.util.List;
152import java.util.Map;
153
154public class Application {
155    public static final int MAX_SIZE = 1024;
156
157    private String name;
158
159    public Application(String name) {
160        this.name = name;
161    }
162
163    public String getName() {
164        return name;
165    }
166
167    public void process(List<String> items) {
168        for (String item : items) {
169            System.out.println(item);
170        }
171    }
172}
173
174interface Service {
175    void execute();
176    String status();
177}
178
179enum Priority {
180    LOW, MEDIUM, HIGH, CRITICAL
181}
182"#;
183
184    #[test]
185    fn test_parse_java_finds_all() {
186        let path = PathBuf::from("App.java");
187        let nodes = parse(&path, SAMPLE_JAVA).unwrap();
188        let kinds: Vec<_> = nodes.iter().map(|n| n.kind).collect();
189        assert!(kinds.contains(&NodeKind::Module), "package");
190        assert!(kinds.contains(&NodeKind::Import), "import");
191        assert!(kinds.contains(&NodeKind::Class), "class");
192        assert!(kinds.contains(&NodeKind::Interface), "interface");
193        assert!(kinds.contains(&NodeKind::Enum), "enum");
194        assert!(kinds.contains(&NodeKind::Method), "method");
195    }
196
197    #[test]
198    fn test_parse_java_class() {
199        let path = PathBuf::from("App.java");
200        let nodes = parse(&path, SAMPLE_JAVA).unwrap();
201        let classes: Vec<_> = nodes.iter().filter(|n| n.kind == NodeKind::Class).collect();
202        assert_eq!(classes.len(), 1);
203        assert_eq!(classes[0].name, "Application");
204    }
205
206    #[test]
207    fn test_parse_java_methods() {
208        let path = PathBuf::from("App.java");
209        let nodes = parse(&path, SAMPLE_JAVA).unwrap();
210        let methods: Vec<_> = nodes.iter().filter(|n| n.kind == NodeKind::Method).collect();
211        assert!(methods.iter().any(|m| m.name == "getName"));
212        assert!(methods.iter().any(|m| m.name == "process"));
213    }
214
215    #[test]
216    fn test_parse_java_interface() {
217        let path = PathBuf::from("App.java");
218        let nodes = parse(&path, SAMPLE_JAVA).unwrap();
219        let ifaces: Vec<_> = nodes.iter().filter(|n| n.kind == NodeKind::Interface).collect();
220        assert_eq!(ifaces.len(), 1);
221        assert_eq!(ifaces[0].name, "Service");
222    }
223
224    #[test]
225    fn test_parse_java_enum() {
226        let path = PathBuf::from("App.java");
227        let nodes = parse(&path, SAMPLE_JAVA).unwrap();
228        let enums: Vec<_> = nodes.iter().filter(|n| n.kind == NodeKind::Enum).collect();
229        assert_eq!(enums.len(), 1);
230        assert_eq!(enums[0].name, "Priority");
231    }
232
233    #[test]
234    fn test_parse_java_edges() {
235        let path = PathBuf::from("App.java");
236        let result = parse_with_edges(&path, SAMPLE_JAVA).unwrap();
237        assert!(!result.edges.is_empty());
238    }
239
240    #[test]
241    fn test_parse_empty_java() {
242        let path = PathBuf::from("Empty.java");
243        let nodes = parse(&path, "").unwrap();
244        assert!(nodes.len() <= 1);
245    }
246}