1use 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 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 if s.len() > 500 { format!("{}...", &s[..500]) } else { s.to_string() }
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 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}