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.visit_field_declaration(child, &id, &class_scope);
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.visit_field_declaration(child, &id, &nested_scope);
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 self.extract_annotation_uses(node, &id);
532
533 let mut enum_scope = scope.to_vec();
534 enum_scope.push(name.clone());
535
536 if let Some(body) = node.child_by_field_name("body") {
537 let mut c = body.walk();
538 let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
539 for child in body_children {
540 if child.kind() == "method_declaration" {
541 self.visit_method(child, &enum_scope, id.clone());
542 }
543 }
544 }
545 }
546
547 fn visit_record(&mut self, node: TsNode<'_>, scope: &[String]) {
548 let Some(name_node) = node.child_by_field_name("name") else {
549 return;
550 };
551 let name = self.text(name_node).to_owned();
552 let id = self
554 .type_index
555 .get(&name)
556 .cloned()
557 .unwrap_or_else(NodeId::new);
558 let graph_node = self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
559 self.nodes.push(graph_node);
560
561 self.extract_annotation_uses(node, &id);
563
564 let mut record_scope = scope.to_vec();
565 record_scope.push(name);
566
567 if let Some(body) = node.child_by_field_name("body") {
568 let mut c = body.walk();
569 let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
570 for child in body_children {
571 if child.kind() == "method_declaration" {
572 self.visit_method(child, &record_scope, id.clone());
573 }
574 }
575 }
576 }
577
578 fn visit_record_nested(&mut self, node: TsNode<'_>, scope: &[String]) -> Option<NodeId> {
580 let name_node = node.child_by_field_name("name")?;
581 let name = self.text(name_node).to_owned();
582 let id = self
583 .type_index
584 .get(&name)
585 .cloned()
586 .unwrap_or_else(NodeId::new);
587 let graph_node = self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
588 self.nodes.push(graph_node);
589 self.extract_annotation_uses(node, &id);
590
591 let mut record_scope = scope.to_vec();
592 record_scope.push(name);
593
594 if let Some(body) = node.child_by_field_name("body") {
595 let mut c = body.walk();
596 for child in body.named_children(&mut c).collect::<Vec<_>>() {
597 if child.kind() == "method_declaration" {
598 self.visit_method(child, &record_scope, id.clone());
599 }
600 }
601 }
602 Some(id)
603 }
604
605 fn visit_method(&mut self, node: TsNode<'_>, scope: &[String], container_id: NodeId) {
606 let Some(name_node) = node.child_by_field_name("name") else {
607 return;
608 };
609 let name = self.text(name_node).to_owned();
610 let id = self
611 .fn_index
612 .get(&name)
613 .cloned()
614 .unwrap_or_else(NodeId::new);
615 self.fn_index.insert(name.clone(), id.clone());
621 let kind = if node.kind() == "constructor_declaration" {
622 NodeKind::Function
623 } else {
624 NodeKind::Method
625 };
626 let mut graph_node = self.make_node(id.clone(), kind, name, scope, node);
627 if let Some(body) = node.child_by_field_name("body") {
628 graph_node.metadata.lld.complexity = Some(super::cyclomatic_complexity(
629 body,
630 &super::complexity::java_decision,
631 ));
632 }
633 self.edges.push(Edge {
634 src: container_id,
635 dst: id.clone(),
636 kind: EdgeKind::Contains,
637 line: None,
638 confidence: EdgeConfidence::Extracted,
639 });
640 self.nodes.push(graph_node);
641
642 if let Some(params) = node.child_by_field_name("parameters") {
644 let mut c = params.walk();
645 let param_list: Vec<TsNode<'_>> = params.named_children(&mut c).collect();
646 for param in param_list {
647 if param.kind() == "formal_parameter" || param.kind() == "spread_parameter" {
648 if let Some(type_node) = param.child_by_field_name("type") {
649 for tname in self.collect_type_names(type_node) {
650 self.deferred_uses.push((id.clone(), tname));
651 }
652 }
653 }
654 }
655 }
656
657 if let Some(ret) = node.child_by_field_name("type") {
659 for tname in self.collect_type_names(ret) {
660 self.deferred_uses.push((id.clone(), tname));
661 }
662 }
663
664 self.extract_annotation_uses(node, &id);
666
667 if let Some(throws) = node.child_by_field_name("throws") {
669 let mut c = throws.walk();
670 for exc in throws.named_children(&mut c).collect::<Vec<_>>() {
671 if let Some(t) = self.extract_simple_type(exc) {
672 self.deferred_throws.push((id.clone(), t));
673 }
674 }
675 }
676
677 if let Some(body) = node.child_by_field_name("body") {
679 self.collect_calls(body, &id);
680 }
681 }
682
683 fn collect_imports(&mut self, node: TsNode<'_>) {
686 let mut cursor = node.walk();
687 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
688 for child in children {
689 if child.kind() != "import_declaration" {
690 continue;
691 }
692 let raw = self.text(child);
695 let clean = raw
696 .trim_start_matches("import")
697 .trim_start_matches(" static")
698 .trim()
699 .trim_end_matches(';')
700 .trim();
701 let leaf = clean.split('.').next_back().unwrap_or(clean);
703 if leaf == "*" {
705 continue;
706 }
707 self.deferred_imports
708 .push((self.package_id.clone(), leaf.to_owned()));
709 }
710 }
711
712 fn extract_annotation_uses(&mut self, node: TsNode<'_>, node_id: &NodeId) {
716 let mut c = node.walk();
717 let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
718 for child in children {
719 if child.kind() == "modifiers" {
720 let mut cc = child.walk();
721 let mod_children: Vec<TsNode<'_>> = child.named_children(&mut cc).collect();
722 for mc in mod_children {
723 if mc.kind() == "annotation" || mc.kind() == "marker_annotation" {
724 let mut ccc = mc.walk();
726 let ann_children: Vec<TsNode<'_>> = mc.named_children(&mut ccc).collect();
727 if let Some(ann_name_node) = ann_children.first() {
728 let ann_name = self.text(*ann_name_node).to_owned();
729 if !ann_name.is_empty() {
730 self.deferred_annotated.push((node_id.clone(), ann_name));
731 }
732 }
733 }
734 }
735 }
736 }
737 }
738
739 fn has_functional_interface_annotation(&self, node: TsNode<'_>) -> bool {
741 let mut c = node.walk();
742 for child in node.named_children(&mut c).collect::<Vec<_>>() {
743 if child.kind() == "modifiers" {
744 let mut cc = child.walk();
745 for mc in child.named_children(&mut cc).collect::<Vec<_>>() {
746 if mc.kind() == "annotation" || mc.kind() == "marker_annotation" {
747 let mut ccc = mc.walk();
748 if let Some(ann) = mc.named_children(&mut ccc).collect::<Vec<_>>().first() {
749 if self.text(*ann) == "FunctionalInterface" {
750 return true;
751 }
752 }
753 }
754 }
755 }
756 }
757 false
758 }
759
760 fn visit_field_declaration(
768 &mut self,
769 field_decl: TsNode<'_>,
770 container_id: &NodeId,
771 scope: &[String],
772 ) {
773 if let Some(type_node) = field_decl.child_by_field_name("type") {
774 for tname in self.collect_type_names(type_node) {
775 self.deferred_uses.push((container_id.clone(), tname));
776 }
777 }
778
779 let mods = Self::modifiers_text(field_decl, self.source);
780 if !(mods.contains("static") && mods.contains("final")) {
781 return;
782 }
783
784 let mut cursor = field_decl.walk();
785 let declarators: Vec<TsNode<'_>> = field_decl
786 .children_by_field_name("declarator", &mut cursor)
787 .collect();
788 for decl in declarators {
789 let Some(name_node) = decl.child_by_field_name("name") else {
790 continue;
791 };
792 let name = self.text(name_node).to_owned();
793 let id = NodeId::new();
794 let graph_node =
795 self.make_node(id.clone(), NodeKind::Constant, name, scope, field_decl);
796 self.nodes.push(graph_node);
797 self.edges.push(Edge {
798 src: container_id.clone(),
799 dst: id.clone(),
800 kind: EdgeKind::Contains,
801 line: None,
802 confidence: EdgeConfidence::Extracted,
803 });
804 self.extract_annotation_uses(field_decl, &id);
808 }
809 }
810
811 fn extract_simple_type(&self, node: TsNode<'_>) -> Option<String> {
813 match node.kind() {
814 "type_identifier" | "identifier" => Some(self.text(node).to_owned()),
815 "generic_type" => {
821 let mut c = node.walk();
822 let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
823 children.into_iter().find_map(|ch| match ch.kind() {
824 "type_identifier" | "scoped_type_identifier" | "identifier" => {
825 Some(self.text(ch).to_owned())
826 }
827 _ => None,
828 })
829 }
830 "scoped_type_identifier" => self.text(node).rsplit('.').next().map(|s| s.to_owned()),
832 _ => {
833 let mut c = node.walk();
835 let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
836 children
837 .into_iter()
838 .find_map(|ch| self.extract_simple_type(ch))
839 }
840 }
841 }
842
843 fn collect_type_names(&self, node: TsNode<'_>) -> Vec<String> {
845 let mut names = Vec::new();
846 self.walk_type_names(node, &mut names);
847 names
848 }
849
850 fn walk_type_names(&self, node: TsNode<'_>, out: &mut Vec<String>) {
851 match node.kind() {
852 "type_identifier" => {
853 let name = self.text(node).to_owned();
854 if !is_builtin_java_type(&name) {
855 out.push(name);
856 }
857 }
858 "integral_type" | "floating_point_type" | "boolean_type" | "void_type" => {}
860 _ => {
861 let mut c = node.walk();
862 for child in node.named_children(&mut c) {
863 self.walk_type_names(child, out);
864 }
865 }
866 }
867 }
868
869 fn collect_calls(&mut self, node: TsNode<'_>, caller_id: &NodeId) {
872 let mut cursor = node.walk();
873 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
874 for child in children {
875 match child.kind() {
876 "method_invocation" | "object_creation_expression" => {
877 if let Some(callee) = self.callee_name(child) {
878 let line = child.start_position().row as u32 + 1;
879 self.record_call(caller_id.clone(), callee, line);
880 }
881 if let Some(args) = child.child_by_field_name("arguments") {
883 self.collect_calls(args, caller_id);
884 }
885 }
886 _ => self.collect_calls(child, caller_id),
887 }
888 }
889 }
890
891 fn callee_name(&self, call_expr: TsNode<'_>) -> Option<String> {
892 match call_expr.kind() {
895 "method_invocation" => call_expr
896 .child_by_field_name("name")
897 .map(|n| self.text(n).to_owned()),
898 "object_creation_expression" => {
899 call_expr
900 .child_by_field_name("type")
901 .and_then(|t| match t.kind() {
902 "type_identifier" => Some(self.text(t).to_owned()),
903 "generic_type" => t
904 .child_by_field_name("name")
905 .map(|n| self.text(n).to_owned()),
906 _ => None,
907 })
908 }
909 _ => None,
910 }
911 }
912
913 fn record_call(&mut self, caller_id: NodeId, callee_name: String, line: u32) {
914 if callee_name.is_empty() {
915 return;
916 }
917 if let Some(callee_id) = self.fn_index.get(&callee_name).cloned() {
918 let edge = Edge::call(caller_id, callee_id, line);
919 if !self.edges.contains(&edge) {
920 self.edges.push(edge);
921 }
922 } else if !self
923 .deferred_calls
924 .iter()
925 .any(|(c, n, _)| c == &caller_id && n == &callee_name)
926 {
927 self.deferred_calls.push((caller_id, callee_name, line));
928 }
929 }
930}
931
932fn is_builtin_java_type(name: &str) -> bool {
934 matches!(
935 name,
936 "String"
937 | "Object"
938 | "Integer"
939 | "Long"
940 | "Double"
941 | "Float"
942 | "Boolean"
943 | "Byte"
944 | "Short"
945 | "Character"
946 | "Number"
947 | "Math"
948 | "System"
949 | "StringBuilder"
950 | "StringBuffer"
951 | "Comparable"
952 | "Serializable"
953 | "Cloneable"
954 | "Iterable"
955 | "Iterator"
956 | "Collection"
957 | "List"
958 | "Set"
959 | "Map"
960 | "Queue"
961 | "Deque"
962 | "Stack"
963 | "ArrayList"
964 | "HashMap"
965 | "HashSet"
966 | "LinkedList"
967 | "Optional"
968 | "Stream"
969 | "Collectors"
970 | "Arrays"
971 | "Collections"
972 | "Enum"
973 | "Throwable"
974 | "Exception"
975 | "RuntimeException"
976 | "Error"
977 | "Override"
978 | "Deprecated"
979 | "SuppressWarnings"
980 | "FunctionalInterface"
981 | "void"
982 )
983}
984
985#[cfg(test)]
988mod tests {
989 use super::JavaParser;
990 use crate::parser::LanguageParser;
991 use gitcortex_core::schema::{EdgeKind, NodeKind};
992 use std::path::Path;
993
994 fn parse(
995 src: &str,
996 ) -> (
997 Vec<gitcortex_core::graph::Node>,
998 Vec<gitcortex_core::graph::Edge>,
999 ) {
1000 let r = JavaParser::new()
1001 .parse(Path::new("Test.java"), src)
1002 .unwrap();
1003 (r.nodes, r.edges)
1004 }
1005
1006 #[allow(clippy::type_complexity)]
1007 fn parse_full(
1008 src: &str,
1009 ) -> (
1010 Vec<gitcortex_core::graph::Node>,
1011 Vec<gitcortex_core::graph::Edge>,
1012 Vec<(gitcortex_core::graph::NodeId, String, u32)>,
1013 Vec<(gitcortex_core::graph::NodeId, String)>,
1014 Vec<(gitcortex_core::graph::NodeId, String)>,
1015 Vec<(gitcortex_core::graph::NodeId, String)>,
1016 Vec<(gitcortex_core::graph::NodeId, String)>,
1017 Vec<(gitcortex_core::graph::NodeId, String)>,
1018 Vec<(gitcortex_core::graph::NodeId, String)>,
1019 ) {
1020 let r = JavaParser::new()
1021 .parse(Path::new("Test.java"), src)
1022 .unwrap();
1023 (
1024 r.nodes,
1025 r.edges,
1026 r.deferred_calls,
1027 r.deferred_uses,
1028 r.deferred_implements,
1029 r.deferred_imports,
1030 r.deferred_inherits,
1031 r.deferred_throws,
1032 r.deferred_annotated,
1033 )
1034 }
1035
1036 #[test]
1037 fn parses_class_and_method() {
1038 let src = "public class Greeter { public String greet(String name) { return name; } }";
1039 let (nodes, edges) = parse(src);
1040 let classes: Vec<_> = nodes
1041 .iter()
1042 .filter(|n| n.kind == NodeKind::Struct)
1043 .collect();
1044 let methods: Vec<_> = nodes
1045 .iter()
1046 .filter(|n| n.kind == NodeKind::Method)
1047 .collect();
1048 assert_eq!(classes.len(), 1, "expected 1 class");
1049 assert_eq!(classes[0].name, "Greeter");
1050 assert_eq!(methods.len(), 1, "expected 1 method");
1051 let contains: Vec<_> = edges
1052 .iter()
1053 .filter(|e| e.kind == EdgeKind::Contains)
1054 .collect();
1055 assert!(!contains.is_empty(), "expected Contains edge");
1056 }
1057
1058 #[test]
1059 fn parses_interface() {
1060 let src = "public interface Greeter { String greet(String name); }";
1061 let (nodes, _) = parse(src);
1062 let interfaces: Vec<_> = nodes
1063 .iter()
1064 .filter(|n| n.kind == NodeKind::Interface || n.kind == NodeKind::Trait)
1065 .collect();
1066 assert_eq!(interfaces.len(), 1);
1067 assert_eq!(interfaces[0].name, "Greeter");
1068 }
1069
1070 #[test]
1071 fn parses_enum() {
1072 let src = "public enum Direction { NORTH, SOUTH, EAST, WEST }";
1073 let (nodes, _) = parse(src);
1074 let enums: Vec<_> = nodes.iter().filter(|n| n.kind == NodeKind::Enum).collect();
1075 assert_eq!(enums.len(), 1);
1076 assert_eq!(enums[0].name, "Direction");
1077 }
1078
1079 #[test]
1080 fn detects_extends_and_implements() {
1081 let src = "interface Base {}\nclass Child extends Base implements Base {}";
1082 let (_, _, _, _, implements, _, inherits, ..) = parse_full(src);
1083 let impl_edges: Vec<_> = implements.iter().filter(|(_, n)| n == "Base").collect();
1084 let inh_edges: Vec<_> = inherits.iter().filter(|(_, n)| n == "Base").collect();
1085 assert!(
1086 impl_edges.len() + inh_edges.len() >= 2,
1087 "expected extends+implements edges to Base, implements={implements:?} inherits={inherits:?}"
1088 );
1089 }
1090
1091 #[test]
1092 fn detects_type_annotation_uses() {
1093 let src = "class Service {}\nclass Controller {\n public Service handle(Service svc) { return svc; }\n}";
1094 let (_, _, _, uses, ..) = parse_full(src);
1095 let svc_uses: Vec<_> = uses.iter().filter(|(_, n)| n == "Service").collect();
1096 assert!(
1097 svc_uses.len() >= 2,
1098 "expected Uses edges to Service (param + return), got: {uses:?}"
1099 );
1100 }
1101
1102 #[test]
1103 fn detects_import_declaration() {
1104 let src = "import com.example.MyService;\nimport java.util.List;\npublic class App {}";
1105 let (_, _, _, _, _, imports, ..) = parse_full(src);
1106 assert!(
1107 imports.iter().any(|(_, n)| n == "MyService"),
1108 "expected import 'MyService', got: {imports:?}"
1109 );
1110 }
1111
1112 #[test]
1113 fn module_node_is_emitted() {
1114 let src = "public class App {}";
1115 let (nodes, _) = parse(src);
1116 let modules: Vec<_> = nodes
1117 .iter()
1118 .filter(|n| n.kind == NodeKind::Module)
1119 .collect();
1120 assert_eq!(modules.len(), 1);
1121 assert_eq!(modules[0].name, "Test"); }
1123
1124 #[test]
1127 fn detects_method_annotation() {
1128 let src = "class Base { void greet() {} }\nclass Child extends Base {\n @Override\n void greet() {}\n}";
1129 let (_, _, _, _, _, _, _, _, annotated) = parse_full(src);
1130 assert!(
1131 annotated.iter().any(|(_, n)| n == "Override"),
1132 "expected @Override annotation captured on method, got: {annotated:?}"
1133 );
1134 }
1135
1136 #[test]
1137 fn detects_class_level_annotation() {
1138 let src = "@Deprecated\nclass Legacy {}";
1139 let (_, _, _, _, _, _, _, _, annotated) = parse_full(src);
1140 assert!(
1141 annotated.iter().any(|(_, n)| n == "Deprecated"),
1142 "expected @Deprecated annotation captured on class, got: {annotated:?}"
1143 );
1144 }
1145
1146 #[test]
1147 fn detects_enum_level_annotation() {
1148 let src = "@Deprecated\npublic enum Direction { NORTH, SOUTH }";
1149 let (_, _, _, _, _, _, _, _, annotated) = parse_full(src);
1150 assert!(
1151 annotated.iter().any(|(_, n)| n == "Deprecated"),
1152 "expected @Deprecated annotation captured on enum, got: {annotated:?}"
1153 );
1154 }
1155
1156 #[test]
1157 fn detects_record_level_annotation() {
1158 let src = "@Deprecated\npublic record Point(int x, int y) {}";
1159 let (_, _, _, _, _, _, _, _, annotated) = parse_full(src);
1160 assert!(
1161 annotated.iter().any(|(_, n)| n == "Deprecated"),
1162 "expected @Deprecated annotation captured on record, got: {annotated:?}"
1163 );
1164 }
1165
1166 #[test]
1169 fn static_final_field_becomes_constant_node() {
1170 let src = "class Config {\n public static final int MAX_RETRIES = 3;\n private int instanceCounter;\n}";
1171 let (nodes, edges) = parse(src);
1172 let consts: Vec<_> = nodes
1173 .iter()
1174 .filter(|n| n.kind == NodeKind::Constant)
1175 .collect();
1176 assert_eq!(consts.len(), 1, "expected 1 constant node, got: {consts:?}");
1177 assert_eq!(consts[0].name, "MAX_RETRIES");
1178 assert!(
1179 consts[0].metadata.is_static && consts[0].metadata.is_final,
1180 "expected MAX_RETRIES to be marked static+final"
1181 );
1182
1183 let contains_to_const: Vec<_> = edges
1184 .iter()
1185 .filter(|e| e.kind == EdgeKind::Contains && e.dst == consts[0].id)
1186 .collect();
1187 assert_eq!(
1188 contains_to_const.len(),
1189 1,
1190 "expected a Contains edge from the class to the constant"
1191 );
1192
1193 let non_const_field_nodes = nodes.iter().filter(|n| n.name == "instanceCounter").count();
1195 assert_eq!(non_const_field_nodes, 0);
1196 }
1197
1198 #[test]
1199 fn static_final_field_multi_declarator() {
1200 let src = "class Config {\n static final int A = 1, B = 2;\n}";
1201 let (nodes, _) = parse(src);
1202 let names: Vec<&str> = nodes
1203 .iter()
1204 .filter(|n| n.kind == NodeKind::Constant)
1205 .map(|n| n.name.as_str())
1206 .collect();
1207 assert_eq!(
1208 names,
1209 vec!["A", "B"],
1210 "expected both declarators as constants: {names:?}"
1211 );
1212 }
1213
1214 #[test]
1215 fn static_final_field_annotation_captured() {
1216 let src =
1217 "class Config {\n @Deprecated\n public static final int MAX_RETRIES = 3;\n}";
1218 let (_, _, _, _, _, _, _, _, annotated) = parse_full(src);
1219 assert!(
1220 annotated.iter().any(|(_, n)| n == "Deprecated"),
1221 "expected @Deprecated annotation captured on static final field, got: {annotated:?}"
1222 );
1223 }
1224}