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