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 "record_declaration" => {
334 let nested_id = self.visit_record_nested(child, &class_scope);
335 if let Some(nid) = nested_id {
336 self.edges.push(Edge {
337 src: id.clone(),
338 dst: nid,
339 kind: EdgeKind::Contains,
340 });
341 }
342 }
343 "field_declaration" => {
344 self.extract_field_uses(child, &id);
345 }
346 _ => {}
347 }
348 }
349 }
350 }
351
352 fn visit_class_nested(&mut self, node: TsNode<'_>, scope: &[String]) -> Option<NodeId> {
354 let name_node = node.child_by_field_name("name")?;
355 let name = self.text(name_node).to_owned();
356 let id = self
357 .type_index
358 .get(&name)
359 .cloned()
360 .unwrap_or_else(NodeId::new);
361 let mut graph_node =
362 self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
363 let mods = Self::modifiers_text(node, self.source);
365 graph_node.metadata.is_static = mods.contains("static");
366 self.nodes.push(graph_node);
367
368 if let Some(superclass) = node.child_by_field_name("superclass") {
369 if let Some(t) = self.extract_simple_type(superclass) {
370 self.deferred_inherits.push((id.clone(), t));
371 }
372 }
373 if let Some(interfaces) = node.child_by_field_name("interfaces") {
374 let mut c = interfaces.walk();
375 for iface in interfaces.named_children(&mut c).collect::<Vec<_>>() {
376 if let Some(t) = self.extract_simple_type(iface) {
377 self.deferred_implements.push((id.clone(), t));
378 }
379 }
380 }
381 self.extract_annotation_uses(node, &id);
382
383 let mut nested_scope = scope.to_vec();
384 nested_scope.push(name);
385 if let Some(body) = node.child_by_field_name("body") {
386 let mut c = body.walk();
387 for child in body.named_children(&mut c).collect::<Vec<_>>() {
388 if matches!(
389 child.kind(),
390 "method_declaration" | "constructor_declaration"
391 ) {
392 self.visit_method(child, &nested_scope, id.clone());
393 } else if child.kind() == "record_declaration" {
394 if let Some(nid) = self.visit_record_nested(child, &nested_scope) {
395 self.edges.push(Edge {
396 src: id.clone(),
397 dst: nid,
398 kind: EdgeKind::Contains,
399 });
400 }
401 } else if child.kind() == "field_declaration" {
402 self.extract_field_uses(child, &id);
403 }
404 }
405 }
406 Some(id)
407 }
408
409 fn visit_interface_nested(&mut self, node: TsNode<'_>, scope: &[String]) -> Option<NodeId> {
411 let name_node = node.child_by_field_name("name")?;
412 let name = self.text(name_node).to_owned();
413 let id = self
414 .type_index
415 .get(&name)
416 .cloned()
417 .unwrap_or_else(NodeId::new);
418 let is_functional = self.has_functional_interface_annotation(node);
419 let mut graph_node =
420 self.make_node(id.clone(), NodeKind::Interface, name.clone(), scope, node);
421 if is_functional {
422 graph_node.metadata.is_abstract = true;
423 }
424 self.nodes.push(graph_node);
425
426 if let Some(extends) = node.child_by_field_name("extends") {
427 let mut c = extends.walk();
428 for ext in extends.named_children(&mut c).collect::<Vec<_>>() {
429 if let Some(t) = self.extract_simple_type(ext) {
430 self.deferred_implements.push((id.clone(), t));
431 }
432 }
433 }
434 self.extract_annotation_uses(node, &id);
435
436 let mut nested_scope = scope.to_vec();
437 nested_scope.push(name);
438 if let Some(body) = node.child_by_field_name("body") {
439 let mut c = body.walk();
440 for child in body.named_children(&mut c).collect::<Vec<_>>() {
441 if matches!(child.kind(), "method_declaration" | "constant_declaration") {
442 self.visit_method(child, &nested_scope, id.clone());
443 }
444 }
445 }
446 Some(id)
447 }
448
449 fn visit_interface(&mut self, node: TsNode<'_>, scope: &[String]) {
450 let Some(name_node) = node.child_by_field_name("name") else {
451 return;
452 };
453 let name = self.text(name_node).to_owned();
454 let id = self
455 .type_index
456 .get(&name)
457 .cloned()
458 .unwrap_or_else(NodeId::new);
459 let is_functional = self.has_functional_interface_annotation(node);
460 let mut graph_node =
461 self.make_node(id.clone(), NodeKind::Interface, name.clone(), scope, node);
462 if is_functional {
463 graph_node.metadata.is_abstract = true;
464 }
465 self.nodes.push(graph_node);
466
467 self.extract_annotation_uses(node, &id);
469
470 if let Some(extends) = node.child_by_field_name("extends") {
472 let mut c = extends.walk();
473 let ext_children: Vec<TsNode<'_>> = extends.named_children(&mut c).collect();
474 for ext in ext_children {
475 if let Some(t) = self.extract_simple_type(ext) {
476 self.deferred_implements.push((id.clone(), t));
477 }
478 }
479 }
480
481 let mut iface_scope = scope.to_vec();
482 iface_scope.push(name.clone());
483
484 if let Some(body) = node.child_by_field_name("body") {
486 let mut c = body.walk();
487 let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
488 for child in body_children {
489 if matches!(child.kind(), "method_declaration" | "constant_declaration") {
490 self.visit_method(child, &iface_scope, id.clone());
491 }
492 }
493 }
494 }
495
496 fn visit_enum(&mut self, node: TsNode<'_>, scope: &[String]) {
497 let Some(name_node) = node.child_by_field_name("name") else {
498 return;
499 };
500 let name = self.text(name_node).to_owned();
501 let id = self
502 .type_index
503 .get(&name)
504 .cloned()
505 .unwrap_or_else(NodeId::new);
506 let graph_node = self.make_node(id.clone(), NodeKind::Enum, name.clone(), scope, node);
507 self.nodes.push(graph_node);
508
509 if let Some(interfaces) = node.child_by_field_name("interfaces") {
511 let mut c = interfaces.walk();
512 let iface_children: Vec<TsNode<'_>> = interfaces.named_children(&mut c).collect();
513 for iface in iface_children {
514 if let Some(t) = self.extract_simple_type(iface) {
515 self.deferred_implements.push((id.clone(), t));
516 }
517 }
518 }
519
520 let mut enum_scope = scope.to_vec();
521 enum_scope.push(name.clone());
522
523 if let Some(body) = node.child_by_field_name("body") {
524 let mut c = body.walk();
525 let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
526 for child in body_children {
527 if child.kind() == "method_declaration" {
528 self.visit_method(child, &enum_scope, id.clone());
529 }
530 }
531 }
532 }
533
534 fn visit_record(&mut self, node: TsNode<'_>, scope: &[String]) {
535 let Some(name_node) = node.child_by_field_name("name") else {
536 return;
537 };
538 let name = self.text(name_node).to_owned();
539 let id = self
541 .type_index
542 .get(&name)
543 .cloned()
544 .unwrap_or_else(NodeId::new);
545 let graph_node = self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
546 self.nodes.push(graph_node);
547
548 let mut record_scope = scope.to_vec();
549 record_scope.push(name);
550
551 if let Some(body) = node.child_by_field_name("body") {
552 let mut c = body.walk();
553 let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
554 for child in body_children {
555 if child.kind() == "method_declaration" {
556 self.visit_method(child, &record_scope, id.clone());
557 }
558 }
559 }
560 }
561
562 fn visit_record_nested(&mut self, node: TsNode<'_>, scope: &[String]) -> Option<NodeId> {
564 let name_node = node.child_by_field_name("name")?;
565 let name = self.text(name_node).to_owned();
566 let id = self
567 .type_index
568 .get(&name)
569 .cloned()
570 .unwrap_or_else(NodeId::new);
571 let graph_node = self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
572 self.nodes.push(graph_node);
573
574 let mut record_scope = scope.to_vec();
575 record_scope.push(name);
576
577 if let Some(body) = node.child_by_field_name("body") {
578 let mut c = body.walk();
579 for child in body.named_children(&mut c).collect::<Vec<_>>() {
580 if child.kind() == "method_declaration" {
581 self.visit_method(child, &record_scope, id.clone());
582 }
583 }
584 }
585 Some(id)
586 }
587
588 fn visit_method(&mut self, node: TsNode<'_>, scope: &[String], container_id: NodeId) {
589 let Some(name_node) = node.child_by_field_name("name") else {
590 return;
591 };
592 let name = self.text(name_node).to_owned();
593 let id = self
594 .fn_index
595 .get(&name)
596 .cloned()
597 .unwrap_or_else(NodeId::new);
598 self.fn_index.insert(name.clone(), id.clone());
604 let kind = if node.kind() == "constructor_declaration" {
605 NodeKind::Function
606 } else {
607 NodeKind::Method
608 };
609 let graph_node = self.make_node(id.clone(), kind, name, scope, node);
610 self.edges.push(Edge {
611 src: container_id,
612 dst: id.clone(),
613 kind: EdgeKind::Contains,
614 });
615 self.nodes.push(graph_node);
616
617 if let Some(params) = node.child_by_field_name("parameters") {
619 let mut c = params.walk();
620 let param_list: Vec<TsNode<'_>> = params.named_children(&mut c).collect();
621 for param in param_list {
622 if param.kind() == "formal_parameter" || param.kind() == "spread_parameter" {
623 if let Some(type_node) = param.child_by_field_name("type") {
624 for tname in self.collect_type_names(type_node) {
625 self.deferred_uses.push((id.clone(), tname));
626 }
627 }
628 }
629 }
630 }
631
632 if let Some(ret) = node.child_by_field_name("type") {
634 for tname in self.collect_type_names(ret) {
635 self.deferred_uses.push((id.clone(), tname));
636 }
637 }
638
639 self.extract_annotation_uses(node, &id);
641
642 if let Some(throws) = node.child_by_field_name("throws") {
644 let mut c = throws.walk();
645 for exc in throws.named_children(&mut c).collect::<Vec<_>>() {
646 if let Some(t) = self.extract_simple_type(exc) {
647 self.deferred_throws.push((id.clone(), t));
648 }
649 }
650 }
651
652 if let Some(body) = node.child_by_field_name("body") {
654 self.collect_calls(body, &id);
655 }
656 }
657
658 fn collect_imports(&mut self, node: TsNode<'_>) {
661 let mut cursor = node.walk();
662 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
663 for child in children {
664 if child.kind() != "import_declaration" {
665 continue;
666 }
667 let raw = self.text(child);
670 let clean = raw
671 .trim_start_matches("import")
672 .trim_start_matches(" static")
673 .trim()
674 .trim_end_matches(';')
675 .trim();
676 let leaf = clean.split('.').next_back().unwrap_or(clean);
678 if leaf == "*" {
680 continue;
681 }
682 self.deferred_imports
683 .push((self.package_id.clone(), leaf.to_owned()));
684 }
685 }
686
687 fn extract_annotation_uses(&mut self, node: TsNode<'_>, node_id: &NodeId) {
691 let mut c = node.walk();
692 let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
693 for child in children {
694 if child.kind() == "modifiers" {
695 let mut cc = child.walk();
696 let mod_children: Vec<TsNode<'_>> = child.named_children(&mut cc).collect();
697 for mc in mod_children {
698 if mc.kind() == "annotation" || mc.kind() == "marker_annotation" {
699 let mut ccc = mc.walk();
701 let ann_children: Vec<TsNode<'_>> = mc.named_children(&mut ccc).collect();
702 if let Some(ann_name_node) = ann_children.first() {
703 let ann_name = self.text(*ann_name_node).to_owned();
704 if !ann_name.is_empty() {
705 self.deferred_annotated.push((node_id.clone(), ann_name));
706 }
707 }
708 }
709 }
710 }
711 }
712 }
713
714 fn has_functional_interface_annotation(&self, node: TsNode<'_>) -> bool {
716 let mut c = node.walk();
717 for child in node.named_children(&mut c).collect::<Vec<_>>() {
718 if child.kind() == "modifiers" {
719 let mut cc = child.walk();
720 for mc in child.named_children(&mut cc).collect::<Vec<_>>() {
721 if mc.kind() == "annotation" || mc.kind() == "marker_annotation" {
722 let mut ccc = mc.walk();
723 if let Some(ann) = mc.named_children(&mut ccc).collect::<Vec<_>>().first() {
724 if self.text(*ann) == "FunctionalInterface" {
725 return true;
726 }
727 }
728 }
729 }
730 }
731 }
732 false
733 }
734
735 fn extract_field_uses(&mut self, field_decl: TsNode<'_>, container_id: &NodeId) {
737 if let Some(type_node) = field_decl.child_by_field_name("type") {
738 for tname in self.collect_type_names(type_node) {
739 self.deferred_uses.push((container_id.clone(), tname));
740 }
741 }
742 }
743
744 fn extract_simple_type(&self, node: TsNode<'_>) -> Option<String> {
746 match node.kind() {
747 "type_identifier" | "identifier" => Some(self.text(node).to_owned()),
748 "generic_type" => {
754 let mut c = node.walk();
755 let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
756 children.into_iter().find_map(|ch| match ch.kind() {
757 "type_identifier" | "scoped_type_identifier" | "identifier" => {
758 Some(self.text(ch).to_owned())
759 }
760 _ => None,
761 })
762 }
763 "scoped_type_identifier" => self.text(node).rsplit('.').next().map(|s| s.to_owned()),
765 _ => {
766 let mut c = node.walk();
768 let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
769 children
770 .into_iter()
771 .find_map(|ch| self.extract_simple_type(ch))
772 }
773 }
774 }
775
776 fn collect_type_names(&self, node: TsNode<'_>) -> Vec<String> {
778 let mut names = Vec::new();
779 self.walk_type_names(node, &mut names);
780 names
781 }
782
783 fn walk_type_names(&self, node: TsNode<'_>, out: &mut Vec<String>) {
784 match node.kind() {
785 "type_identifier" => {
786 let name = self.text(node).to_owned();
787 if !is_builtin_java_type(&name) {
788 out.push(name);
789 }
790 }
791 "integral_type" | "floating_point_type" | "boolean_type" | "void_type" => {}
793 _ => {
794 let mut c = node.walk();
795 for child in node.named_children(&mut c) {
796 self.walk_type_names(child, out);
797 }
798 }
799 }
800 }
801
802 fn collect_calls(&mut self, node: TsNode<'_>, caller_id: &NodeId) {
805 let mut cursor = node.walk();
806 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
807 for child in children {
808 match child.kind() {
809 "method_invocation" | "object_creation_expression" => {
810 if let Some(callee) = self.callee_name(child) {
811 self.record_call(caller_id.clone(), callee);
812 }
813 if let Some(args) = child.child_by_field_name("arguments") {
815 self.collect_calls(args, caller_id);
816 }
817 }
818 _ => self.collect_calls(child, caller_id),
819 }
820 }
821 }
822
823 fn callee_name(&self, call_expr: TsNode<'_>) -> Option<String> {
824 match call_expr.kind() {
827 "method_invocation" => call_expr
828 .child_by_field_name("name")
829 .map(|n| self.text(n).to_owned()),
830 "object_creation_expression" => {
831 call_expr
832 .child_by_field_name("type")
833 .and_then(|t| match t.kind() {
834 "type_identifier" => Some(self.text(t).to_owned()),
835 "generic_type" => t
836 .child_by_field_name("name")
837 .map(|n| self.text(n).to_owned()),
838 _ => None,
839 })
840 }
841 _ => None,
842 }
843 }
844
845 fn record_call(&mut self, caller_id: NodeId, callee_name: String) {
846 if callee_name.is_empty() {
847 return;
848 }
849 if let Some(callee_id) = self.fn_index.get(&callee_name).cloned() {
850 let edge = Edge {
851 src: caller_id,
852 dst: callee_id,
853 kind: EdgeKind::Calls,
854 };
855 if !self.edges.contains(&edge) {
856 self.edges.push(edge);
857 }
858 } else if !self
859 .deferred_calls
860 .iter()
861 .any(|(c, n)| c == &caller_id && n == &callee_name)
862 {
863 self.deferred_calls.push((caller_id, callee_name));
864 }
865 }
866}
867
868fn is_builtin_java_type(name: &str) -> bool {
870 matches!(
871 name,
872 "String"
873 | "Object"
874 | "Integer"
875 | "Long"
876 | "Double"
877 | "Float"
878 | "Boolean"
879 | "Byte"
880 | "Short"
881 | "Character"
882 | "Number"
883 | "Math"
884 | "System"
885 | "StringBuilder"
886 | "StringBuffer"
887 | "Comparable"
888 | "Serializable"
889 | "Cloneable"
890 | "Iterable"
891 | "Iterator"
892 | "Collection"
893 | "List"
894 | "Set"
895 | "Map"
896 | "Queue"
897 | "Deque"
898 | "Stack"
899 | "ArrayList"
900 | "HashMap"
901 | "HashSet"
902 | "LinkedList"
903 | "Optional"
904 | "Stream"
905 | "Collectors"
906 | "Arrays"
907 | "Collections"
908 | "Enum"
909 | "Throwable"
910 | "Exception"
911 | "RuntimeException"
912 | "Error"
913 | "Override"
914 | "Deprecated"
915 | "SuppressWarnings"
916 | "FunctionalInterface"
917 | "void"
918 )
919}
920
921#[cfg(test)]
924mod tests {
925 use super::JavaParser;
926 use crate::parser::LanguageParser;
927 use gitcortex_core::schema::{EdgeKind, NodeKind};
928 use std::path::Path;
929
930 fn parse(
931 src: &str,
932 ) -> (
933 Vec<gitcortex_core::graph::Node>,
934 Vec<gitcortex_core::graph::Edge>,
935 ) {
936 let r = JavaParser::new()
937 .parse(Path::new("Test.java"), src)
938 .unwrap();
939 (r.nodes, r.edges)
940 }
941
942 #[allow(clippy::type_complexity)]
943 fn parse_full(
944 src: &str,
945 ) -> (
946 Vec<gitcortex_core::graph::Node>,
947 Vec<gitcortex_core::graph::Edge>,
948 Vec<(gitcortex_core::graph::NodeId, String)>,
949 Vec<(gitcortex_core::graph::NodeId, String)>,
950 Vec<(gitcortex_core::graph::NodeId, String)>,
951 Vec<(gitcortex_core::graph::NodeId, String)>,
952 Vec<(gitcortex_core::graph::NodeId, String)>,
953 Vec<(gitcortex_core::graph::NodeId, String)>,
954 Vec<(gitcortex_core::graph::NodeId, String)>,
955 ) {
956 let r = JavaParser::new()
957 .parse(Path::new("Test.java"), src)
958 .unwrap();
959 (
960 r.nodes,
961 r.edges,
962 r.deferred_calls,
963 r.deferred_uses,
964 r.deferred_implements,
965 r.deferred_imports,
966 r.deferred_inherits,
967 r.deferred_throws,
968 r.deferred_annotated,
969 )
970 }
971
972 #[test]
973 fn parses_class_and_method() {
974 let src = "public class Greeter { public String greet(String name) { return name; } }";
975 let (nodes, edges) = parse(src);
976 let classes: Vec<_> = nodes
977 .iter()
978 .filter(|n| n.kind == NodeKind::Struct)
979 .collect();
980 let methods: Vec<_> = nodes
981 .iter()
982 .filter(|n| n.kind == NodeKind::Method)
983 .collect();
984 assert_eq!(classes.len(), 1, "expected 1 class");
985 assert_eq!(classes[0].name, "Greeter");
986 assert_eq!(methods.len(), 1, "expected 1 method");
987 let contains: Vec<_> = edges
988 .iter()
989 .filter(|e| e.kind == EdgeKind::Contains)
990 .collect();
991 assert!(!contains.is_empty(), "expected Contains edge");
992 }
993
994 #[test]
995 fn parses_interface() {
996 let src = "public interface Greeter { String greet(String name); }";
997 let (nodes, _) = parse(src);
998 let interfaces: Vec<_> = nodes
999 .iter()
1000 .filter(|n| n.kind == NodeKind::Interface || n.kind == NodeKind::Trait)
1001 .collect();
1002 assert_eq!(interfaces.len(), 1);
1003 assert_eq!(interfaces[0].name, "Greeter");
1004 }
1005
1006 #[test]
1007 fn parses_enum() {
1008 let src = "public enum Direction { NORTH, SOUTH, EAST, WEST }";
1009 let (nodes, _) = parse(src);
1010 let enums: Vec<_> = nodes.iter().filter(|n| n.kind == NodeKind::Enum).collect();
1011 assert_eq!(enums.len(), 1);
1012 assert_eq!(enums[0].name, "Direction");
1013 }
1014
1015 #[test]
1016 fn detects_extends_and_implements() {
1017 let src = "interface Base {}\nclass Child extends Base implements Base {}";
1018 let (_, _, _, _, implements, _, inherits, ..) = parse_full(src);
1019 let impl_edges: Vec<_> = implements.iter().filter(|(_, n)| n == "Base").collect();
1020 let inh_edges: Vec<_> = inherits.iter().filter(|(_, n)| n == "Base").collect();
1021 assert!(
1022 impl_edges.len() + inh_edges.len() >= 2,
1023 "expected extends+implements edges to Base, implements={implements:?} inherits={inherits:?}"
1024 );
1025 }
1026
1027 #[test]
1028 fn detects_type_annotation_uses() {
1029 let src = "class Service {}\nclass Controller {\n public Service handle(Service svc) { return svc; }\n}";
1030 let (_, _, _, uses, ..) = parse_full(src);
1031 let svc_uses: Vec<_> = uses.iter().filter(|(_, n)| n == "Service").collect();
1032 assert!(
1033 svc_uses.len() >= 2,
1034 "expected Uses edges to Service (param + return), got: {uses:?}"
1035 );
1036 }
1037
1038 #[test]
1039 fn detects_import_declaration() {
1040 let src = "import com.example.MyService;\nimport java.util.List;\npublic class App {}";
1041 let (_, _, _, _, _, imports, ..) = parse_full(src);
1042 assert!(
1043 imports.iter().any(|(_, n)| n == "MyService"),
1044 "expected import 'MyService', got: {imports:?}"
1045 );
1046 }
1047
1048 #[test]
1049 fn module_node_is_emitted() {
1050 let src = "public class App {}";
1051 let (nodes, _) = parse(src);
1052 let modules: Vec<_> = nodes
1053 .iter()
1054 .filter(|n| n.kind == NodeKind::Module)
1055 .collect();
1056 assert_eq!(modules.len(), 1);
1057 assert_eq!(modules[0].name, "Test"); }
1059}