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::{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 ..Default::default()
219 },
220 }
221 }
222
223 fn collect_names(&mut self, node: TsNode<'_>) {
226 let mut cursor = node.walk();
227 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
228 for child in children {
229 match child.kind() {
230 "class_declaration"
231 | "interface_declaration"
232 | "enum_declaration"
233 | "annotation_type_declaration"
234 | "record_declaration" => {
235 if let Some(name_node) = child.child_by_field_name("name") {
236 let name = self.text(name_node).to_owned();
237 self.type_index.entry(name).or_default();
238 }
239 }
240 _ => {}
241 }
242 }
243 }
244
245 fn visit_program(&mut self, node: TsNode<'_>) {
248 let mut cursor = node.walk();
249 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
250 for child in children {
251 self.visit_top_level(child, &[]);
252 }
253 }
254
255 fn visit_top_level(&mut self, node: TsNode<'_>, scope: &[String]) {
256 match node.kind() {
257 "class_declaration" => self.visit_class(node, scope),
258 "interface_declaration" => self.visit_interface(node, scope),
259 "enum_declaration" => self.visit_enum(node, scope),
260 "record_declaration" => self.visit_record(node, scope),
261 _ => {}
262 }
263 }
264
265 fn visit_class(&mut self, node: TsNode<'_>, scope: &[String]) {
266 let Some(name_node) = node.child_by_field_name("name") else {
267 return;
268 };
269 let name = self.text(name_node).to_owned();
270 let id = self
271 .type_index
272 .get(&name)
273 .cloned()
274 .unwrap_or_else(NodeId::new);
275 let graph_node = self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
276 self.nodes.push(graph_node);
277
278 if let Some(superclass) = node.child_by_field_name("superclass") {
280 let type_name = self.extract_simple_type(superclass);
281 if let Some(t) = type_name {
282 self.deferred_inherits.push((id.clone(), t));
283 }
284 }
285
286 if let Some(interfaces) = node.child_by_field_name("interfaces") {
288 let mut c = interfaces.walk();
289 let iface_children: Vec<TsNode<'_>> = interfaces.named_children(&mut c).collect();
290 for iface in iface_children {
291 if let Some(t) = self.extract_simple_type(iface) {
292 self.deferred_implements.push((id.clone(), t));
293 }
294 }
295 }
296
297 self.extract_annotation_uses(node, &id);
299
300 let mut class_scope = scope.to_vec();
301 class_scope.push(name.clone());
302
303 if let Some(body) = node.child_by_field_name("body") {
305 let mut c = body.walk();
306 let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
307 for child in body_children {
308 match child.kind() {
309 "method_declaration" | "constructor_declaration" => {
310 self.visit_method(child, &class_scope, id.clone());
311 }
312 "class_declaration" => {
313 let nested_id = self.visit_class_nested(child, &class_scope);
314 if let Some(nid) = nested_id {
315 self.edges.push(Edge {
316 src: id.clone(),
317 dst: nid,
318 kind: EdgeKind::Contains,
319 });
320 }
321 }
322 "interface_declaration" => {
323 let nested_id = self.visit_interface_nested(child, &class_scope);
324 if let Some(nid) = nested_id {
325 self.edges.push(Edge {
326 src: id.clone(),
327 dst: nid,
328 kind: EdgeKind::Contains,
329 });
330 }
331 }
332 "field_declaration" => {
333 self.extract_field_uses(child, &id);
334 }
335 _ => {}
336 }
337 }
338 }
339 }
340
341 fn visit_class_nested(&mut self, node: TsNode<'_>, scope: &[String]) -> Option<NodeId> {
343 let name_node = node.child_by_field_name("name")?;
344 let name = self.text(name_node).to_owned();
345 let id = self
346 .type_index
347 .get(&name)
348 .cloned()
349 .unwrap_or_else(NodeId::new);
350 let mut graph_node =
351 self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
352 let mods = Self::modifiers_text(node, self.source);
354 graph_node.metadata.is_static = mods.contains("static");
355 self.nodes.push(graph_node);
356
357 if let Some(superclass) = node.child_by_field_name("superclass") {
358 if let Some(t) = self.extract_simple_type(superclass) {
359 self.deferred_inherits.push((id.clone(), t));
360 }
361 }
362 if let Some(interfaces) = node.child_by_field_name("interfaces") {
363 let mut c = interfaces.walk();
364 for iface in interfaces.named_children(&mut c).collect::<Vec<_>>() {
365 if let Some(t) = self.extract_simple_type(iface) {
366 self.deferred_implements.push((id.clone(), t));
367 }
368 }
369 }
370 self.extract_annotation_uses(node, &id);
371
372 let mut nested_scope = scope.to_vec();
373 nested_scope.push(name);
374 if let Some(body) = node.child_by_field_name("body") {
375 let mut c = body.walk();
376 for child in body.named_children(&mut c).collect::<Vec<_>>() {
377 if matches!(
378 child.kind(),
379 "method_declaration" | "constructor_declaration"
380 ) {
381 self.visit_method(child, &nested_scope, id.clone());
382 } else if child.kind() == "field_declaration" {
383 self.extract_field_uses(child, &id);
384 }
385 }
386 }
387 Some(id)
388 }
389
390 fn visit_interface_nested(&mut self, node: TsNode<'_>, scope: &[String]) -> Option<NodeId> {
392 let name_node = node.child_by_field_name("name")?;
393 let name = self.text(name_node).to_owned();
394 let id = self
395 .type_index
396 .get(&name)
397 .cloned()
398 .unwrap_or_else(NodeId::new);
399 let is_functional = self.has_functional_interface_annotation(node);
400 let mut graph_node =
401 self.make_node(id.clone(), NodeKind::Interface, name.clone(), scope, node);
402 if is_functional {
403 graph_node.metadata.is_abstract = true;
404 }
405 self.nodes.push(graph_node);
406
407 if let Some(extends) = node.child_by_field_name("extends") {
408 let mut c = extends.walk();
409 for ext in extends.named_children(&mut c).collect::<Vec<_>>() {
410 if let Some(t) = self.extract_simple_type(ext) {
411 self.deferred_implements.push((id.clone(), t));
412 }
413 }
414 }
415 self.extract_annotation_uses(node, &id);
416
417 let mut nested_scope = scope.to_vec();
418 nested_scope.push(name);
419 if let Some(body) = node.child_by_field_name("body") {
420 let mut c = body.walk();
421 for child in body.named_children(&mut c).collect::<Vec<_>>() {
422 if matches!(child.kind(), "method_declaration" | "constant_declaration") {
423 self.visit_method(child, &nested_scope, id.clone());
424 }
425 }
426 }
427 Some(id)
428 }
429
430 fn visit_interface(&mut self, node: TsNode<'_>, scope: &[String]) {
431 let Some(name_node) = node.child_by_field_name("name") else {
432 return;
433 };
434 let name = self.text(name_node).to_owned();
435 let id = self
436 .type_index
437 .get(&name)
438 .cloned()
439 .unwrap_or_else(NodeId::new);
440 let is_functional = self.has_functional_interface_annotation(node);
441 let mut graph_node =
442 self.make_node(id.clone(), NodeKind::Interface, name.clone(), scope, node);
443 if is_functional {
444 graph_node.metadata.is_abstract = true;
445 }
446 self.nodes.push(graph_node);
447
448 self.extract_annotation_uses(node, &id);
450
451 if let Some(extends) = node.child_by_field_name("extends") {
453 let mut c = extends.walk();
454 let ext_children: Vec<TsNode<'_>> = extends.named_children(&mut c).collect();
455 for ext in ext_children {
456 if let Some(t) = self.extract_simple_type(ext) {
457 self.deferred_implements.push((id.clone(), t));
458 }
459 }
460 }
461
462 let mut iface_scope = scope.to_vec();
463 iface_scope.push(name.clone());
464
465 if let Some(body) = node.child_by_field_name("body") {
467 let mut c = body.walk();
468 let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
469 for child in body_children {
470 if matches!(child.kind(), "method_declaration" | "constant_declaration") {
471 self.visit_method(child, &iface_scope, id.clone());
472 }
473 }
474 }
475 }
476
477 fn visit_enum(&mut self, node: TsNode<'_>, scope: &[String]) {
478 let Some(name_node) = node.child_by_field_name("name") else {
479 return;
480 };
481 let name = self.text(name_node).to_owned();
482 let id = self
483 .type_index
484 .get(&name)
485 .cloned()
486 .unwrap_or_else(NodeId::new);
487 let graph_node = self.make_node(id.clone(), NodeKind::Enum, name.clone(), scope, node);
488 self.nodes.push(graph_node);
489
490 if let Some(interfaces) = node.child_by_field_name("interfaces") {
492 let mut c = interfaces.walk();
493 let iface_children: Vec<TsNode<'_>> = interfaces.named_children(&mut c).collect();
494 for iface in iface_children {
495 if let Some(t) = self.extract_simple_type(iface) {
496 self.deferred_implements.push((id.clone(), t));
497 }
498 }
499 }
500
501 let mut enum_scope = scope.to_vec();
502 enum_scope.push(name.clone());
503
504 if let Some(body) = node.child_by_field_name("body") {
505 let mut c = body.walk();
506 let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
507 for child in body_children {
508 if child.kind() == "method_declaration" {
509 self.visit_method(child, &enum_scope, id.clone());
510 }
511 }
512 }
513 }
514
515 fn visit_record(&mut self, node: TsNode<'_>, scope: &[String]) {
516 let Some(name_node) = node.child_by_field_name("name") else {
517 return;
518 };
519 let name = self.text(name_node).to_owned();
520 let id = self
522 .type_index
523 .get(&name)
524 .cloned()
525 .unwrap_or_else(NodeId::new);
526 let graph_node = self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
527 self.nodes.push(graph_node);
528
529 let mut record_scope = scope.to_vec();
530 record_scope.push(name);
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, &record_scope, id.clone());
538 }
539 }
540 }
541 }
542
543 fn visit_method(&mut self, node: TsNode<'_>, scope: &[String], container_id: NodeId) {
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
549 .fn_index
550 .get(&name)
551 .cloned()
552 .unwrap_or_else(NodeId::new);
553 let kind = if node.kind() == "constructor_declaration" {
554 NodeKind::Function
555 } else {
556 NodeKind::Method
557 };
558 let graph_node = self.make_node(id.clone(), kind, name, scope, node);
559 self.edges.push(Edge {
560 src: container_id,
561 dst: id.clone(),
562 kind: EdgeKind::Contains,
563 });
564 self.nodes.push(graph_node);
565
566 if let Some(params) = node.child_by_field_name("parameters") {
568 let mut c = params.walk();
569 let param_list: Vec<TsNode<'_>> = params.named_children(&mut c).collect();
570 for param in param_list {
571 if param.kind() == "formal_parameter" || param.kind() == "spread_parameter" {
572 if let Some(type_node) = param.child_by_field_name("type") {
573 for tname in self.collect_type_names(type_node) {
574 self.deferred_uses.push((id.clone(), tname));
575 }
576 }
577 }
578 }
579 }
580
581 if let Some(ret) = node.child_by_field_name("type") {
583 for tname in self.collect_type_names(ret) {
584 self.deferred_uses.push((id.clone(), tname));
585 }
586 }
587
588 self.extract_annotation_uses(node, &id);
590
591 if let Some(throws) = node.child_by_field_name("throws") {
593 let mut c = throws.walk();
594 for exc in throws.named_children(&mut c).collect::<Vec<_>>() {
595 if let Some(t) = self.extract_simple_type(exc) {
596 self.deferred_throws.push((id.clone(), t));
597 }
598 }
599 }
600
601 if let Some(body) = node.child_by_field_name("body") {
603 self.collect_calls(body, &id);
604 }
605 }
606
607 fn collect_imports(&mut self, node: TsNode<'_>) {
610 let mut cursor = node.walk();
611 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
612 for child in children {
613 if child.kind() != "import_declaration" {
614 continue;
615 }
616 let raw = self.text(child);
619 let clean = raw
620 .trim_start_matches("import")
621 .trim_start_matches(" static")
622 .trim()
623 .trim_end_matches(';')
624 .trim();
625 let leaf = clean.split('.').next_back().unwrap_or(clean);
627 if leaf == "*" {
629 continue;
630 }
631 self.deferred_imports
632 .push((self.package_id.clone(), leaf.to_owned()));
633 }
634 }
635
636 fn extract_annotation_uses(&mut self, node: TsNode<'_>, node_id: &NodeId) {
640 let mut c = node.walk();
641 let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
642 for child in children {
643 if child.kind() == "modifiers" {
644 let mut cc = child.walk();
645 let mod_children: Vec<TsNode<'_>> = child.named_children(&mut cc).collect();
646 for mc in mod_children {
647 if mc.kind() == "annotation" || mc.kind() == "marker_annotation" {
648 let mut ccc = mc.walk();
650 let ann_children: Vec<TsNode<'_>> = mc.named_children(&mut ccc).collect();
651 if let Some(ann_name_node) = ann_children.first() {
652 let ann_name = self.text(*ann_name_node).to_owned();
653 if !ann_name.is_empty() {
654 self.deferred_annotated.push((node_id.clone(), ann_name));
655 }
656 }
657 }
658 }
659 }
660 }
661 }
662
663 fn has_functional_interface_annotation(&self, node: TsNode<'_>) -> bool {
665 let mut c = node.walk();
666 for child in node.named_children(&mut c).collect::<Vec<_>>() {
667 if child.kind() == "modifiers" {
668 let mut cc = child.walk();
669 for mc in child.named_children(&mut cc).collect::<Vec<_>>() {
670 if mc.kind() == "annotation" || mc.kind() == "marker_annotation" {
671 let mut ccc = mc.walk();
672 if let Some(ann) = mc.named_children(&mut ccc).collect::<Vec<_>>().first() {
673 if self.text(*ann) == "FunctionalInterface" {
674 return true;
675 }
676 }
677 }
678 }
679 }
680 }
681 false
682 }
683
684 fn extract_field_uses(&mut self, field_decl: TsNode<'_>, container_id: &NodeId) {
686 if let Some(type_node) = field_decl.child_by_field_name("type") {
687 for tname in self.collect_type_names(type_node) {
688 self.deferred_uses.push((container_id.clone(), tname));
689 }
690 }
691 }
692
693 fn extract_simple_type(&self, node: TsNode<'_>) -> Option<String> {
695 match node.kind() {
696 "type_identifier" | "identifier" => Some(self.text(node).to_owned()),
697 "generic_type" => node
698 .child_by_field_name("name")
699 .map(|n| self.text(n).to_owned()),
700 _ => {
701 let mut c = node.walk();
703 let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
704 children
705 .into_iter()
706 .find_map(|ch| self.extract_simple_type(ch))
707 }
708 }
709 }
710
711 fn collect_type_names(&self, node: TsNode<'_>) -> Vec<String> {
713 let mut names = Vec::new();
714 self.walk_type_names(node, &mut names);
715 names
716 }
717
718 fn walk_type_names(&self, node: TsNode<'_>, out: &mut Vec<String>) {
719 match node.kind() {
720 "type_identifier" => {
721 let name = self.text(node).to_owned();
722 if !is_builtin_java_type(&name) {
723 out.push(name);
724 }
725 }
726 "integral_type" | "floating_point_type" | "boolean_type" | "void_type" => {}
728 _ => {
729 let mut c = node.walk();
730 for child in node.named_children(&mut c) {
731 self.walk_type_names(child, out);
732 }
733 }
734 }
735 }
736
737 fn collect_calls(&mut self, node: TsNode<'_>, caller_id: &NodeId) {
740 let mut cursor = node.walk();
741 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
742 for child in children {
743 match child.kind() {
744 "method_invocation" | "object_creation_expression" => {
745 if let Some(callee) = self.callee_name(child) {
746 self.record_call(caller_id.clone(), callee);
747 }
748 if let Some(args) = child.child_by_field_name("arguments") {
750 self.collect_calls(args, caller_id);
751 }
752 }
753 _ => self.collect_calls(child, caller_id),
754 }
755 }
756 }
757
758 fn callee_name(&self, call_expr: TsNode<'_>) -> Option<String> {
759 match call_expr.kind() {
762 "method_invocation" => call_expr
763 .child_by_field_name("name")
764 .map(|n| self.text(n).to_owned()),
765 "object_creation_expression" => {
766 call_expr
767 .child_by_field_name("type")
768 .and_then(|t| match t.kind() {
769 "type_identifier" => Some(self.text(t).to_owned()),
770 "generic_type" => t
771 .child_by_field_name("name")
772 .map(|n| self.text(n).to_owned()),
773 _ => None,
774 })
775 }
776 _ => None,
777 }
778 }
779
780 fn record_call(&mut self, caller_id: NodeId, callee_name: String) {
781 if callee_name.is_empty() {
782 return;
783 }
784 if let Some(callee_id) = self.fn_index.get(&callee_name).cloned() {
785 let edge = Edge {
786 src: caller_id,
787 dst: callee_id,
788 kind: EdgeKind::Calls,
789 };
790 if !self.edges.contains(&edge) {
791 self.edges.push(edge);
792 }
793 } else if !self
794 .deferred_calls
795 .iter()
796 .any(|(c, n)| c == &caller_id && n == &callee_name)
797 {
798 self.deferred_calls.push((caller_id, callee_name));
799 }
800 }
801}
802
803fn is_builtin_java_type(name: &str) -> bool {
805 matches!(
806 name,
807 "String"
808 | "Object"
809 | "Integer"
810 | "Long"
811 | "Double"
812 | "Float"
813 | "Boolean"
814 | "Byte"
815 | "Short"
816 | "Character"
817 | "Number"
818 | "Math"
819 | "System"
820 | "StringBuilder"
821 | "StringBuffer"
822 | "Comparable"
823 | "Serializable"
824 | "Cloneable"
825 | "Iterable"
826 | "Iterator"
827 | "Collection"
828 | "List"
829 | "Set"
830 | "Map"
831 | "Queue"
832 | "Deque"
833 | "Stack"
834 | "ArrayList"
835 | "HashMap"
836 | "HashSet"
837 | "LinkedList"
838 | "Optional"
839 | "Stream"
840 | "Collectors"
841 | "Arrays"
842 | "Collections"
843 | "Enum"
844 | "Throwable"
845 | "Exception"
846 | "RuntimeException"
847 | "Error"
848 | "Override"
849 | "Deprecated"
850 | "SuppressWarnings"
851 | "FunctionalInterface"
852 | "void"
853 )
854}
855
856#[cfg(test)]
859mod tests {
860 use super::JavaParser;
861 use crate::parser::LanguageParser;
862 use gitcortex_core::schema::{EdgeKind, NodeKind};
863 use std::path::Path;
864
865 fn parse(
866 src: &str,
867 ) -> (
868 Vec<gitcortex_core::graph::Node>,
869 Vec<gitcortex_core::graph::Edge>,
870 ) {
871 let r = JavaParser::new()
872 .parse(Path::new("Test.java"), src)
873 .unwrap();
874 (r.nodes, r.edges)
875 }
876
877 #[allow(clippy::type_complexity)]
878 fn parse_full(
879 src: &str,
880 ) -> (
881 Vec<gitcortex_core::graph::Node>,
882 Vec<gitcortex_core::graph::Edge>,
883 Vec<(gitcortex_core::graph::NodeId, String)>,
884 Vec<(gitcortex_core::graph::NodeId, String)>,
885 Vec<(gitcortex_core::graph::NodeId, String)>,
886 Vec<(gitcortex_core::graph::NodeId, String)>,
887 Vec<(gitcortex_core::graph::NodeId, String)>,
888 Vec<(gitcortex_core::graph::NodeId, String)>,
889 Vec<(gitcortex_core::graph::NodeId, String)>,
890 ) {
891 let r = JavaParser::new()
892 .parse(Path::new("Test.java"), src)
893 .unwrap();
894 (
895 r.nodes,
896 r.edges,
897 r.deferred_calls,
898 r.deferred_uses,
899 r.deferred_implements,
900 r.deferred_imports,
901 r.deferred_inherits,
902 r.deferred_throws,
903 r.deferred_annotated,
904 )
905 }
906
907 #[test]
908 fn parses_class_and_method() {
909 let src = "public class Greeter { public String greet(String name) { return name; } }";
910 let (nodes, edges) = parse(src);
911 let classes: Vec<_> = nodes
912 .iter()
913 .filter(|n| n.kind == NodeKind::Struct)
914 .collect();
915 let methods: Vec<_> = nodes
916 .iter()
917 .filter(|n| n.kind == NodeKind::Method)
918 .collect();
919 assert_eq!(classes.len(), 1, "expected 1 class");
920 assert_eq!(classes[0].name, "Greeter");
921 assert_eq!(methods.len(), 1, "expected 1 method");
922 let contains: Vec<_> = edges
923 .iter()
924 .filter(|e| e.kind == EdgeKind::Contains)
925 .collect();
926 assert!(!contains.is_empty(), "expected Contains edge");
927 }
928
929 #[test]
930 fn parses_interface() {
931 let src = "public interface Greeter { String greet(String name); }";
932 let (nodes, _) = parse(src);
933 let interfaces: Vec<_> = nodes
934 .iter()
935 .filter(|n| n.kind == NodeKind::Interface || n.kind == NodeKind::Trait)
936 .collect();
937 assert_eq!(interfaces.len(), 1);
938 assert_eq!(interfaces[0].name, "Greeter");
939 }
940
941 #[test]
942 fn parses_enum() {
943 let src = "public enum Direction { NORTH, SOUTH, EAST, WEST }";
944 let (nodes, _) = parse(src);
945 let enums: Vec<_> = nodes.iter().filter(|n| n.kind == NodeKind::Enum).collect();
946 assert_eq!(enums.len(), 1);
947 assert_eq!(enums[0].name, "Direction");
948 }
949
950 #[test]
951 fn detects_extends_and_implements() {
952 let src = "interface Base {}\nclass Child extends Base implements Base {}";
953 let (_, _, _, _, implements, _, inherits, ..) = parse_full(src);
954 let impl_edges: Vec<_> = implements.iter().filter(|(_, n)| n == "Base").collect();
955 let inh_edges: Vec<_> = inherits.iter().filter(|(_, n)| n == "Base").collect();
956 assert!(
957 impl_edges.len() + inh_edges.len() >= 2,
958 "expected extends+implements edges to Base, implements={implements:?} inherits={inherits:?}"
959 );
960 }
961
962 #[test]
963 fn detects_type_annotation_uses() {
964 let src = "class Service {}\nclass Controller {\n public Service handle(Service svc) { return svc; }\n}";
965 let (_, _, _, uses, ..) = parse_full(src);
966 let svc_uses: Vec<_> = uses.iter().filter(|(_, n)| n == "Service").collect();
967 assert!(
968 svc_uses.len() >= 2,
969 "expected Uses edges to Service (param + return), got: {uses:?}"
970 );
971 }
972
973 #[test]
974 fn detects_import_declaration() {
975 let src = "import com.example.MyService;\nimport java.util.List;\npublic class App {}";
976 let (_, _, _, _, _, imports, ..) = parse_full(src);
977 assert!(
978 imports.iter().any(|(_, n)| n == "MyService"),
979 "expected import 'MyService', got: {imports:?}"
980 );
981 }
982
983 #[test]
984 fn module_node_is_emitted() {
985 let src = "public class App {}";
986 let (nodes, _) = parse(src);
987 let modules: Vec<_> = nodes
988 .iter()
989 .filter(|n| n.kind == NodeKind::Module)
990 .collect();
991 assert_eq!(modules.len(), 1);
992 assert_eq!(modules[0].name, "Test"); }
994}