1use std::{
2 collections::HashMap,
3 path::{Path, PathBuf},
4};
5
6use gitcortex_core::{
7 error::{GitCortexError, Result},
8 graph::{Edge, Node, NodeId, NodeMetadata, Span},
9 schema::{EdgeKind, NodeKind, Visibility},
10};
11use tree_sitter::{Node as TsNode, Parser};
12
13use super::{capture_definition, LanguageParser, ParseResult};
14
15pub struct JavaParser {
16 language: tree_sitter::Language,
17}
18
19impl JavaParser {
20 pub fn new() -> Self {
21 Self {
22 language: tree_sitter_java::LANGUAGE.into(),
23 }
24 }
25}
26
27impl Default for JavaParser {
28 fn default() -> Self {
29 Self::new()
30 }
31}
32
33impl LanguageParser for JavaParser {
34 fn extensions(&self) -> &[&str] {
35 &["java"]
36 }
37
38 fn parse(&self, path: &Path, source: &str) -> Result<ParseResult> {
39 let mut parser = Parser::new();
40 parser
41 .set_language(&self.language)
42 .map_err(|e| GitCortexError::Parse {
43 file: path.to_owned(),
44 message: e.to_string(),
45 })?;
46
47 let tree = parser
48 .parse(source, None)
49 .ok_or_else(|| GitCortexError::Parse {
50 file: path.to_owned(),
51 message: "tree-sitter returned no parse tree".into(),
52 })?;
53
54 let mut visitor = FileVisitor::new(path, source);
55 visitor.collect_names(tree.root_node());
56 visitor.visit_program(tree.root_node());
57 visitor.collect_imports(tree.root_node());
58
59 Ok(ParseResult {
60 nodes: visitor.nodes,
61 edges: visitor.edges,
62 deferred_calls: visitor.deferred_calls,
63 deferred_uses: visitor.deferred_uses,
64 deferred_implements: visitor.deferred_implements,
65 deferred_imports: visitor.deferred_imports,
66 deferred_inherits: visitor.deferred_inherits,
67 deferred_throws: visitor.deferred_throws,
68 deferred_annotated: visitor.deferred_annotated,
69 })
70 }
71}
72
73struct FileVisitor<'src> {
76 source: &'src [u8],
77 file: PathBuf,
78 package_id: NodeId,
80 nodes: Vec<Node>,
81 edges: Vec<Edge>,
82 type_index: HashMap<String, NodeId>,
84 fn_index: HashMap<String, NodeId>,
86 deferred_calls: Vec<(NodeId, String)>,
87 deferred_uses: Vec<(NodeId, String)>,
88 deferred_implements: Vec<(NodeId, String)>,
89 deferred_imports: Vec<(NodeId, String)>,
90 deferred_inherits: Vec<(NodeId, String)>,
91 deferred_throws: Vec<(NodeId, String)>,
92 deferred_annotated: Vec<(NodeId, String)>,
93}
94
95impl<'src> FileVisitor<'src> {
96 fn new(file: &Path, source: &'src str) -> Self {
97 let package_id = NodeId::new();
98 let unit_name = file
100 .file_stem()
101 .and_then(|s| s.to_str())
102 .unwrap_or("Unknown")
103 .to_owned();
104 let package_node = Node {
105 id: package_id.clone(),
106 qualified_name: unit_name.clone(),
107 kind: NodeKind::Module,
108 name: unit_name,
109 file: file.to_owned(),
110 span: Span {
111 start_line: 1,
112 end_line: 1,
113 },
114 metadata: NodeMetadata {
115 loc: source.lines().count() as u32,
116 visibility: Visibility::Pub,
117 is_async: false,
118 is_unsafe: false,
119 ..Default::default()
120 },
121 };
122 let nodes = vec![package_node];
123 Self {
124 source: source.as_bytes(),
125 file: file.to_owned(),
126 package_id,
127 nodes,
128 edges: Vec::new(),
129 type_index: HashMap::new(),
130 fn_index: HashMap::new(),
131 deferred_calls: Vec::new(),
132 deferred_uses: Vec::new(),
133 deferred_implements: Vec::new(),
134 deferred_imports: Vec::new(),
135 deferred_inherits: Vec::new(),
136 deferred_throws: Vec::new(),
137 deferred_annotated: Vec::new(),
138 }
139 }
140
141 fn text<'t>(&self, node: TsNode<'t>) -> &'src str {
142 node.utf8_text(self.source).unwrap_or("")
143 }
144
145 fn span(node: TsNode<'_>) -> Span {
146 Span {
147 start_line: node.start_position().row as u32 + 1,
148 end_line: node.end_position().row as u32 + 1,
149 }
150 }
151
152 fn visibility(node: TsNode<'_>, source: &[u8]) -> Visibility {
153 let mut cursor = node.walk();
154 for child in node.children(&mut cursor) {
155 if child.kind() == "modifiers" {
156 let text = child.utf8_text(source).unwrap_or("");
157 if text.contains("public") {
158 return Visibility::Pub;
159 }
160 if text.contains("protected") {
161 return Visibility::PubCrate;
162 }
163 return Visibility::Private;
164 }
165 }
166 Visibility::PubCrate
168 }
169
170 fn is_async(_node: TsNode<'_>) -> bool {
171 false
173 }
174
175 fn modifiers_text<'t>(node: TsNode<'t>, source: &'t [u8]) -> &'t str {
177 let mut cursor = node.walk();
178 for child in node.children(&mut cursor) {
179 if child.kind() == "modifiers" {
180 return child.utf8_text(source).unwrap_or("");
181 }
182 }
183 ""
184 }
185
186 fn qualified(scope: &[String], name: &str) -> String {
187 if scope.is_empty() {
188 name.to_owned()
189 } else {
190 format!("{}.{name}", scope.join("."))
191 }
192 }
193
194 fn make_node(
195 &self,
196 id: NodeId,
197 kind: NodeKind,
198 name: String,
199 scope: &[String],
200 ts_node: TsNode<'_>,
201 ) -> Node {
202 let mods = Self::modifiers_text(ts_node, self.source);
203 Node {
204 id,
205 qualified_name: Self::qualified(scope, &name),
206 kind,
207 name,
208 file: self.file.clone(),
209 span: Self::span(ts_node),
210 metadata: NodeMetadata {
211 loc: (ts_node.end_position().row - ts_node.start_position().row + 1) as u32,
212 visibility: Self::visibility(ts_node, self.source),
213 is_async: Self::is_async(ts_node),
214 is_unsafe: false,
215 is_abstract: mods.contains("abstract"),
216 is_final: mods.contains("final"),
217 is_static: mods.contains("static"),
218 definition: capture_definition(self.source, ts_node),
219 ..Default::default()
220 },
221 }
222 }
223
224 fn collect_names(&mut self, node: TsNode<'_>) {
227 let mut cursor = node.walk();
228 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
229 for child in children {
230 match child.kind() {
231 "class_declaration"
232 | "interface_declaration"
233 | "enum_declaration"
234 | "annotation_type_declaration"
235 | "record_declaration" => {
236 if let Some(name_node) = child.child_by_field_name("name") {
237 let name = self.text(name_node).to_owned();
238 self.type_index.entry(name).or_default();
239 }
240 }
241 _ => {}
242 }
243 }
244 }
245
246 fn visit_program(&mut self, node: TsNode<'_>) {
249 let mut cursor = node.walk();
250 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
251 for child in children {
252 self.visit_top_level(child, &[]);
253 }
254 }
255
256 fn visit_top_level(&mut self, node: TsNode<'_>, scope: &[String]) {
257 match node.kind() {
258 "class_declaration" => self.visit_class(node, scope),
259 "interface_declaration" => self.visit_interface(node, scope),
260 "enum_declaration" => self.visit_enum(node, scope),
261 "record_declaration" => self.visit_record(node, scope),
262 _ => {}
263 }
264 }
265
266 fn visit_class(&mut self, node: TsNode<'_>, scope: &[String]) {
267 let Some(name_node) = node.child_by_field_name("name") else {
268 return;
269 };
270 let name = self.text(name_node).to_owned();
271 let id = self
272 .type_index
273 .get(&name)
274 .cloned()
275 .unwrap_or_else(NodeId::new);
276 let graph_node = self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
277 self.nodes.push(graph_node);
278
279 if let Some(superclass) = node.child_by_field_name("superclass") {
281 let type_name = self.extract_simple_type(superclass);
282 if let Some(t) = type_name {
283 self.deferred_inherits.push((id.clone(), t));
284 }
285 }
286
287 if let Some(interfaces) = node.child_by_field_name("interfaces") {
289 let mut c = interfaces.walk();
290 let iface_children: Vec<TsNode<'_>> = interfaces.named_children(&mut c).collect();
291 for iface in iface_children {
292 if let Some(t) = self.extract_simple_type(iface) {
293 self.deferred_implements.push((id.clone(), t));
294 }
295 }
296 }
297
298 self.extract_annotation_uses(node, &id);
300
301 let mut class_scope = scope.to_vec();
302 class_scope.push(name.clone());
303
304 if let Some(body) = node.child_by_field_name("body") {
306 let mut c = body.walk();
307 let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
308 for child in body_children {
309 match child.kind() {
310 "method_declaration" | "constructor_declaration" => {
311 self.visit_method(child, &class_scope, id.clone());
312 }
313 "class_declaration" => {
314 let nested_id = self.visit_class_nested(child, &class_scope);
315 if let Some(nid) = nested_id {
316 self.edges.push(Edge {
317 src: id.clone(),
318 dst: nid,
319 kind: EdgeKind::Contains,
320 });
321 }
322 }
323 "interface_declaration" => {
324 let nested_id = self.visit_interface_nested(child, &class_scope);
325 if let Some(nid) = nested_id {
326 self.edges.push(Edge {
327 src: id.clone(),
328 dst: nid,
329 kind: EdgeKind::Contains,
330 });
331 }
332 }
333 "field_declaration" => {
334 self.extract_field_uses(child, &id);
335 }
336 _ => {}
337 }
338 }
339 }
340 }
341
342 fn visit_class_nested(&mut self, node: TsNode<'_>, scope: &[String]) -> Option<NodeId> {
344 let name_node = node.child_by_field_name("name")?;
345 let name = self.text(name_node).to_owned();
346 let id = self
347 .type_index
348 .get(&name)
349 .cloned()
350 .unwrap_or_else(NodeId::new);
351 let mut graph_node =
352 self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
353 let mods = Self::modifiers_text(node, self.source);
355 graph_node.metadata.is_static = mods.contains("static");
356 self.nodes.push(graph_node);
357
358 if let Some(superclass) = node.child_by_field_name("superclass") {
359 if let Some(t) = self.extract_simple_type(superclass) {
360 self.deferred_inherits.push((id.clone(), t));
361 }
362 }
363 if let Some(interfaces) = node.child_by_field_name("interfaces") {
364 let mut c = interfaces.walk();
365 for iface in interfaces.named_children(&mut c).collect::<Vec<_>>() {
366 if let Some(t) = self.extract_simple_type(iface) {
367 self.deferred_implements.push((id.clone(), t));
368 }
369 }
370 }
371 self.extract_annotation_uses(node, &id);
372
373 let mut nested_scope = scope.to_vec();
374 nested_scope.push(name);
375 if let Some(body) = node.child_by_field_name("body") {
376 let mut c = body.walk();
377 for child in body.named_children(&mut c).collect::<Vec<_>>() {
378 if matches!(
379 child.kind(),
380 "method_declaration" | "constructor_declaration"
381 ) {
382 self.visit_method(child, &nested_scope, id.clone());
383 } else if child.kind() == "field_declaration" {
384 self.extract_field_uses(child, &id);
385 }
386 }
387 }
388 Some(id)
389 }
390
391 fn visit_interface_nested(&mut self, node: TsNode<'_>, scope: &[String]) -> Option<NodeId> {
393 let name_node = node.child_by_field_name("name")?;
394 let name = self.text(name_node).to_owned();
395 let id = self
396 .type_index
397 .get(&name)
398 .cloned()
399 .unwrap_or_else(NodeId::new);
400 let is_functional = self.has_functional_interface_annotation(node);
401 let mut graph_node =
402 self.make_node(id.clone(), NodeKind::Interface, name.clone(), scope, node);
403 if is_functional {
404 graph_node.metadata.is_abstract = true;
405 }
406 self.nodes.push(graph_node);
407
408 if let Some(extends) = node.child_by_field_name("extends") {
409 let mut c = extends.walk();
410 for ext in extends.named_children(&mut c).collect::<Vec<_>>() {
411 if let Some(t) = self.extract_simple_type(ext) {
412 self.deferred_implements.push((id.clone(), t));
413 }
414 }
415 }
416 self.extract_annotation_uses(node, &id);
417
418 let mut nested_scope = scope.to_vec();
419 nested_scope.push(name);
420 if let Some(body) = node.child_by_field_name("body") {
421 let mut c = body.walk();
422 for child in body.named_children(&mut c).collect::<Vec<_>>() {
423 if matches!(child.kind(), "method_declaration" | "constant_declaration") {
424 self.visit_method(child, &nested_scope, id.clone());
425 }
426 }
427 }
428 Some(id)
429 }
430
431 fn visit_interface(&mut self, node: TsNode<'_>, scope: &[String]) {
432 let Some(name_node) = node.child_by_field_name("name") else {
433 return;
434 };
435 let name = self.text(name_node).to_owned();
436 let id = self
437 .type_index
438 .get(&name)
439 .cloned()
440 .unwrap_or_else(NodeId::new);
441 let is_functional = self.has_functional_interface_annotation(node);
442 let mut graph_node =
443 self.make_node(id.clone(), NodeKind::Interface, name.clone(), scope, node);
444 if is_functional {
445 graph_node.metadata.is_abstract = true;
446 }
447 self.nodes.push(graph_node);
448
449 self.extract_annotation_uses(node, &id);
451
452 if let Some(extends) = node.child_by_field_name("extends") {
454 let mut c = extends.walk();
455 let ext_children: Vec<TsNode<'_>> = extends.named_children(&mut c).collect();
456 for ext in ext_children {
457 if let Some(t) = self.extract_simple_type(ext) {
458 self.deferred_implements.push((id.clone(), t));
459 }
460 }
461 }
462
463 let mut iface_scope = scope.to_vec();
464 iface_scope.push(name.clone());
465
466 if let Some(body) = node.child_by_field_name("body") {
468 let mut c = body.walk();
469 let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
470 for child in body_children {
471 if matches!(child.kind(), "method_declaration" | "constant_declaration") {
472 self.visit_method(child, &iface_scope, id.clone());
473 }
474 }
475 }
476 }
477
478 fn visit_enum(&mut self, node: TsNode<'_>, scope: &[String]) {
479 let Some(name_node) = node.child_by_field_name("name") else {
480 return;
481 };
482 let name = self.text(name_node).to_owned();
483 let id = self
484 .type_index
485 .get(&name)
486 .cloned()
487 .unwrap_or_else(NodeId::new);
488 let graph_node = self.make_node(id.clone(), NodeKind::Enum, name.clone(), scope, node);
489 self.nodes.push(graph_node);
490
491 if let Some(interfaces) = node.child_by_field_name("interfaces") {
493 let mut c = interfaces.walk();
494 let iface_children: Vec<TsNode<'_>> = interfaces.named_children(&mut c).collect();
495 for iface in iface_children {
496 if let Some(t) = self.extract_simple_type(iface) {
497 self.deferred_implements.push((id.clone(), t));
498 }
499 }
500 }
501
502 let mut enum_scope = scope.to_vec();
503 enum_scope.push(name.clone());
504
505 if let Some(body) = node.child_by_field_name("body") {
506 let mut c = body.walk();
507 let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
508 for child in body_children {
509 if child.kind() == "method_declaration" {
510 self.visit_method(child, &enum_scope, id.clone());
511 }
512 }
513 }
514 }
515
516 fn visit_record(&mut self, node: TsNode<'_>, scope: &[String]) {
517 let Some(name_node) = node.child_by_field_name("name") else {
518 return;
519 };
520 let name = self.text(name_node).to_owned();
521 let id = self
523 .type_index
524 .get(&name)
525 .cloned()
526 .unwrap_or_else(NodeId::new);
527 let graph_node = self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
528 self.nodes.push(graph_node);
529
530 let mut record_scope = scope.to_vec();
531 record_scope.push(name);
532
533 if let Some(body) = node.child_by_field_name("body") {
534 let mut c = body.walk();
535 let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
536 for child in body_children {
537 if child.kind() == "method_declaration" {
538 self.visit_method(child, &record_scope, id.clone());
539 }
540 }
541 }
542 }
543
544 fn visit_method(&mut self, node: TsNode<'_>, scope: &[String], container_id: NodeId) {
545 let Some(name_node) = node.child_by_field_name("name") else {
546 return;
547 };
548 let name = self.text(name_node).to_owned();
549 let id = self
550 .fn_index
551 .get(&name)
552 .cloned()
553 .unwrap_or_else(NodeId::new);
554 self.fn_index.insert(name.clone(), id.clone());
560 let kind = if node.kind() == "constructor_declaration" {
561 NodeKind::Function
562 } else {
563 NodeKind::Method
564 };
565 let graph_node = self.make_node(id.clone(), kind, name, scope, node);
566 self.edges.push(Edge {
567 src: container_id,
568 dst: id.clone(),
569 kind: EdgeKind::Contains,
570 });
571 self.nodes.push(graph_node);
572
573 if let Some(params) = node.child_by_field_name("parameters") {
575 let mut c = params.walk();
576 let param_list: Vec<TsNode<'_>> = params.named_children(&mut c).collect();
577 for param in param_list {
578 if param.kind() == "formal_parameter" || param.kind() == "spread_parameter" {
579 if let Some(type_node) = param.child_by_field_name("type") {
580 for tname in self.collect_type_names(type_node) {
581 self.deferred_uses.push((id.clone(), tname));
582 }
583 }
584 }
585 }
586 }
587
588 if let Some(ret) = node.child_by_field_name("type") {
590 for tname in self.collect_type_names(ret) {
591 self.deferred_uses.push((id.clone(), tname));
592 }
593 }
594
595 self.extract_annotation_uses(node, &id);
597
598 if let Some(throws) = node.child_by_field_name("throws") {
600 let mut c = throws.walk();
601 for exc in throws.named_children(&mut c).collect::<Vec<_>>() {
602 if let Some(t) = self.extract_simple_type(exc) {
603 self.deferred_throws.push((id.clone(), t));
604 }
605 }
606 }
607
608 if let Some(body) = node.child_by_field_name("body") {
610 self.collect_calls(body, &id);
611 }
612 }
613
614 fn collect_imports(&mut self, node: TsNode<'_>) {
617 let mut cursor = node.walk();
618 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
619 for child in children {
620 if child.kind() != "import_declaration" {
621 continue;
622 }
623 let raw = self.text(child);
626 let clean = raw
627 .trim_start_matches("import")
628 .trim_start_matches(" static")
629 .trim()
630 .trim_end_matches(';')
631 .trim();
632 let leaf = clean.split('.').next_back().unwrap_or(clean);
634 if leaf == "*" {
636 continue;
637 }
638 self.deferred_imports
639 .push((self.package_id.clone(), leaf.to_owned()));
640 }
641 }
642
643 fn extract_annotation_uses(&mut self, node: TsNode<'_>, node_id: &NodeId) {
647 let mut c = node.walk();
648 let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
649 for child in children {
650 if child.kind() == "modifiers" {
651 let mut cc = child.walk();
652 let mod_children: Vec<TsNode<'_>> = child.named_children(&mut cc).collect();
653 for mc in mod_children {
654 if mc.kind() == "annotation" || mc.kind() == "marker_annotation" {
655 let mut ccc = mc.walk();
657 let ann_children: Vec<TsNode<'_>> = mc.named_children(&mut ccc).collect();
658 if let Some(ann_name_node) = ann_children.first() {
659 let ann_name = self.text(*ann_name_node).to_owned();
660 if !ann_name.is_empty() {
661 self.deferred_annotated.push((node_id.clone(), ann_name));
662 }
663 }
664 }
665 }
666 }
667 }
668 }
669
670 fn has_functional_interface_annotation(&self, node: TsNode<'_>) -> bool {
672 let mut c = node.walk();
673 for child in node.named_children(&mut c).collect::<Vec<_>>() {
674 if child.kind() == "modifiers" {
675 let mut cc = child.walk();
676 for mc in child.named_children(&mut cc).collect::<Vec<_>>() {
677 if mc.kind() == "annotation" || mc.kind() == "marker_annotation" {
678 let mut ccc = mc.walk();
679 if let Some(ann) = mc.named_children(&mut ccc).collect::<Vec<_>>().first() {
680 if self.text(*ann) == "FunctionalInterface" {
681 return true;
682 }
683 }
684 }
685 }
686 }
687 }
688 false
689 }
690
691 fn extract_field_uses(&mut self, field_decl: TsNode<'_>, container_id: &NodeId) {
693 if let Some(type_node) = field_decl.child_by_field_name("type") {
694 for tname in self.collect_type_names(type_node) {
695 self.deferred_uses.push((container_id.clone(), tname));
696 }
697 }
698 }
699
700 fn extract_simple_type(&self, node: TsNode<'_>) -> Option<String> {
702 match node.kind() {
703 "type_identifier" | "identifier" => Some(self.text(node).to_owned()),
704 "generic_type" => {
710 let mut c = node.walk();
711 let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
712 children.into_iter().find_map(|ch| match ch.kind() {
713 "type_identifier" | "scoped_type_identifier" | "identifier" => {
714 Some(self.text(ch).to_owned())
715 }
716 _ => None,
717 })
718 }
719 "scoped_type_identifier" => self.text(node).rsplit('.').next().map(|s| s.to_owned()),
721 _ => {
722 let mut c = node.walk();
724 let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
725 children
726 .into_iter()
727 .find_map(|ch| self.extract_simple_type(ch))
728 }
729 }
730 }
731
732 fn collect_type_names(&self, node: TsNode<'_>) -> Vec<String> {
734 let mut names = Vec::new();
735 self.walk_type_names(node, &mut names);
736 names
737 }
738
739 fn walk_type_names(&self, node: TsNode<'_>, out: &mut Vec<String>) {
740 match node.kind() {
741 "type_identifier" => {
742 let name = self.text(node).to_owned();
743 if !is_builtin_java_type(&name) {
744 out.push(name);
745 }
746 }
747 "integral_type" | "floating_point_type" | "boolean_type" | "void_type" => {}
749 _ => {
750 let mut c = node.walk();
751 for child in node.named_children(&mut c) {
752 self.walk_type_names(child, out);
753 }
754 }
755 }
756 }
757
758 fn collect_calls(&mut self, node: TsNode<'_>, caller_id: &NodeId) {
761 let mut cursor = node.walk();
762 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
763 for child in children {
764 match child.kind() {
765 "method_invocation" | "object_creation_expression" => {
766 if let Some(callee) = self.callee_name(child) {
767 self.record_call(caller_id.clone(), callee);
768 }
769 if let Some(args) = child.child_by_field_name("arguments") {
771 self.collect_calls(args, caller_id);
772 }
773 }
774 _ => self.collect_calls(child, caller_id),
775 }
776 }
777 }
778
779 fn callee_name(&self, call_expr: TsNode<'_>) -> Option<String> {
780 match call_expr.kind() {
783 "method_invocation" => call_expr
784 .child_by_field_name("name")
785 .map(|n| self.text(n).to_owned()),
786 "object_creation_expression" => {
787 call_expr
788 .child_by_field_name("type")
789 .and_then(|t| match t.kind() {
790 "type_identifier" => Some(self.text(t).to_owned()),
791 "generic_type" => t
792 .child_by_field_name("name")
793 .map(|n| self.text(n).to_owned()),
794 _ => None,
795 })
796 }
797 _ => None,
798 }
799 }
800
801 fn record_call(&mut self, caller_id: NodeId, callee_name: String) {
802 if callee_name.is_empty() {
803 return;
804 }
805 if let Some(callee_id) = self.fn_index.get(&callee_name).cloned() {
806 let edge = Edge {
807 src: caller_id,
808 dst: callee_id,
809 kind: EdgeKind::Calls,
810 };
811 if !self.edges.contains(&edge) {
812 self.edges.push(edge);
813 }
814 } else if !self
815 .deferred_calls
816 .iter()
817 .any(|(c, n)| c == &caller_id && n == &callee_name)
818 {
819 self.deferred_calls.push((caller_id, callee_name));
820 }
821 }
822}
823
824fn is_builtin_java_type(name: &str) -> bool {
826 matches!(
827 name,
828 "String"
829 | "Object"
830 | "Integer"
831 | "Long"
832 | "Double"
833 | "Float"
834 | "Boolean"
835 | "Byte"
836 | "Short"
837 | "Character"
838 | "Number"
839 | "Math"
840 | "System"
841 | "StringBuilder"
842 | "StringBuffer"
843 | "Comparable"
844 | "Serializable"
845 | "Cloneable"
846 | "Iterable"
847 | "Iterator"
848 | "Collection"
849 | "List"
850 | "Set"
851 | "Map"
852 | "Queue"
853 | "Deque"
854 | "Stack"
855 | "ArrayList"
856 | "HashMap"
857 | "HashSet"
858 | "LinkedList"
859 | "Optional"
860 | "Stream"
861 | "Collectors"
862 | "Arrays"
863 | "Collections"
864 | "Enum"
865 | "Throwable"
866 | "Exception"
867 | "RuntimeException"
868 | "Error"
869 | "Override"
870 | "Deprecated"
871 | "SuppressWarnings"
872 | "FunctionalInterface"
873 | "void"
874 )
875}
876
877#[cfg(test)]
880mod tests {
881 use super::JavaParser;
882 use crate::parser::LanguageParser;
883 use gitcortex_core::schema::{EdgeKind, NodeKind};
884 use std::path::Path;
885
886 fn parse(
887 src: &str,
888 ) -> (
889 Vec<gitcortex_core::graph::Node>,
890 Vec<gitcortex_core::graph::Edge>,
891 ) {
892 let r = JavaParser::new()
893 .parse(Path::new("Test.java"), src)
894 .unwrap();
895 (r.nodes, r.edges)
896 }
897
898 #[allow(clippy::type_complexity)]
899 fn parse_full(
900 src: &str,
901 ) -> (
902 Vec<gitcortex_core::graph::Node>,
903 Vec<gitcortex_core::graph::Edge>,
904 Vec<(gitcortex_core::graph::NodeId, String)>,
905 Vec<(gitcortex_core::graph::NodeId, String)>,
906 Vec<(gitcortex_core::graph::NodeId, String)>,
907 Vec<(gitcortex_core::graph::NodeId, String)>,
908 Vec<(gitcortex_core::graph::NodeId, String)>,
909 Vec<(gitcortex_core::graph::NodeId, String)>,
910 Vec<(gitcortex_core::graph::NodeId, String)>,
911 ) {
912 let r = JavaParser::new()
913 .parse(Path::new("Test.java"), src)
914 .unwrap();
915 (
916 r.nodes,
917 r.edges,
918 r.deferred_calls,
919 r.deferred_uses,
920 r.deferred_implements,
921 r.deferred_imports,
922 r.deferred_inherits,
923 r.deferred_throws,
924 r.deferred_annotated,
925 )
926 }
927
928 #[test]
929 fn parses_class_and_method() {
930 let src = "public class Greeter { public String greet(String name) { return name; } }";
931 let (nodes, edges) = parse(src);
932 let classes: Vec<_> = nodes
933 .iter()
934 .filter(|n| n.kind == NodeKind::Struct)
935 .collect();
936 let methods: Vec<_> = nodes
937 .iter()
938 .filter(|n| n.kind == NodeKind::Method)
939 .collect();
940 assert_eq!(classes.len(), 1, "expected 1 class");
941 assert_eq!(classes[0].name, "Greeter");
942 assert_eq!(methods.len(), 1, "expected 1 method");
943 let contains: Vec<_> = edges
944 .iter()
945 .filter(|e| e.kind == EdgeKind::Contains)
946 .collect();
947 assert!(!contains.is_empty(), "expected Contains edge");
948 }
949
950 #[test]
951 fn parses_interface() {
952 let src = "public interface Greeter { String greet(String name); }";
953 let (nodes, _) = parse(src);
954 let interfaces: Vec<_> = nodes
955 .iter()
956 .filter(|n| n.kind == NodeKind::Interface || n.kind == NodeKind::Trait)
957 .collect();
958 assert_eq!(interfaces.len(), 1);
959 assert_eq!(interfaces[0].name, "Greeter");
960 }
961
962 #[test]
963 fn parses_enum() {
964 let src = "public enum Direction { NORTH, SOUTH, EAST, WEST }";
965 let (nodes, _) = parse(src);
966 let enums: Vec<_> = nodes.iter().filter(|n| n.kind == NodeKind::Enum).collect();
967 assert_eq!(enums.len(), 1);
968 assert_eq!(enums[0].name, "Direction");
969 }
970
971 #[test]
972 fn detects_extends_and_implements() {
973 let src = "interface Base {}\nclass Child extends Base implements Base {}";
974 let (_, _, _, _, implements, _, inherits, ..) = parse_full(src);
975 let impl_edges: Vec<_> = implements.iter().filter(|(_, n)| n == "Base").collect();
976 let inh_edges: Vec<_> = inherits.iter().filter(|(_, n)| n == "Base").collect();
977 assert!(
978 impl_edges.len() + inh_edges.len() >= 2,
979 "expected extends+implements edges to Base, implements={implements:?} inherits={inherits:?}"
980 );
981 }
982
983 #[test]
984 fn detects_type_annotation_uses() {
985 let src = "class Service {}\nclass Controller {\n public Service handle(Service svc) { return svc; }\n}";
986 let (_, _, _, uses, ..) = parse_full(src);
987 let svc_uses: Vec<_> = uses.iter().filter(|(_, n)| n == "Service").collect();
988 assert!(
989 svc_uses.len() >= 2,
990 "expected Uses edges to Service (param + return), got: {uses:?}"
991 );
992 }
993
994 #[test]
995 fn detects_import_declaration() {
996 let src = "import com.example.MyService;\nimport java.util.List;\npublic class App {}";
997 let (_, _, _, _, _, imports, ..) = parse_full(src);
998 assert!(
999 imports.iter().any(|(_, n)| n == "MyService"),
1000 "expected import 'MyService', got: {imports:?}"
1001 );
1002 }
1003
1004 #[test]
1005 fn module_node_is_emitted() {
1006 let src = "public class App {}";
1007 let (nodes, _) = parse(src);
1008 let modules: Vec<_> = nodes
1009 .iter()
1010 .filter(|n| n.kind == NodeKind::Module)
1011 .collect();
1012 assert_eq!(modules.len(), 1);
1013 assert_eq!(modules[0].name, "Test"); }
1015}