Skip to main content

gitcortex_indexer/parser/
java.rs

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
73// ── Internal visitor ──────────────────────────────────────────────────────────
74
75struct FileVisitor<'src> {
76    source: &'src [u8],
77    file: PathBuf,
78    /// NodeId of the file-level package node (anchor for Imports edges).
79    package_id: NodeId,
80    nodes: Vec<Node>,
81    edges: Vec<Edge>,
82    /// class/interface/enum name → NodeId
83    type_index: HashMap<String, NodeId>,
84    /// method name → NodeId
85    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        // Use the file stem as the compilation unit name.
99        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        // Package-private (no modifier) = effectively PubCrate within package
167        Visibility::PubCrate
168    }
169
170    fn is_async(_node: TsNode<'_>) -> bool {
171        // Java doesn't have async/await; treat `synchronized` as a proxy for async
172        false
173    }
174
175    /// Returns the text of the `modifiers` child node, or `""` if absent.
176    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    // ── Pass 1: pre-allocate NodeIds ──────────────────────────────────────────
225
226    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    // ── Pass 2: emit nodes + edges ────────────────────────────────────────────
247
248    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        // extends (super class) → Inherits edge
280        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        // implements (interfaces) → Implements edges
288        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        // Annotations on the class → Annotated edges
299        self.extract_annotation_uses(node, &id);
300
301        let mut class_scope = scope.to_vec();
302        class_scope.push(name.clone());
303
304        // Visit class body
305        if let Some(body) = node.child_by_field_name("body") {
306            let mut c = body.walk();
307            let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
308            for child in body_children {
309                match child.kind() {
310                    "method_declaration" | "constructor_declaration" => {
311                        self.visit_method(child, &class_scope, id.clone());
312                    }
313                    "class_declaration" => {
314                        let nested_id = self.visit_class_nested(child, &class_scope);
315                        if let Some(nid) = nested_id {
316                            self.edges.push(Edge {
317                                src: id.clone(),
318                                dst: nid,
319                                kind: EdgeKind::Contains,
320                            });
321                        }
322                    }
323                    "interface_declaration" => {
324                        let nested_id = self.visit_interface_nested(child, &class_scope);
325                        if let Some(nid) = nested_id {
326                            self.edges.push(Edge {
327                                src: id.clone(),
328                                dst: nid,
329                                kind: EdgeKind::Contains,
330                            });
331                        }
332                    }
333                    "field_declaration" => {
334                        self.extract_field_uses(child, &id);
335                    }
336                    _ => {}
337                }
338            }
339        }
340    }
341
342    /// Visit a nested class declaration, returning the new node's id.
343    fn visit_class_nested(&mut self, node: TsNode<'_>, scope: &[String]) -> Option<NodeId> {
344        let name_node = node.child_by_field_name("name")?;
345        let name = self.text(name_node).to_owned();
346        let id = self
347            .type_index
348            .get(&name)
349            .cloned()
350            .unwrap_or_else(NodeId::new);
351        let mut graph_node =
352            self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
353        // make_node reads `static` from modifiers — already handled there
354        let mods = Self::modifiers_text(node, self.source);
355        graph_node.metadata.is_static = mods.contains("static");
356        self.nodes.push(graph_node);
357
358        if let Some(superclass) = node.child_by_field_name("superclass") {
359            if let Some(t) = self.extract_simple_type(superclass) {
360                self.deferred_inherits.push((id.clone(), t));
361            }
362        }
363        if let Some(interfaces) = node.child_by_field_name("interfaces") {
364            let mut c = interfaces.walk();
365            for iface in interfaces.named_children(&mut c).collect::<Vec<_>>() {
366                if let Some(t) = self.extract_simple_type(iface) {
367                    self.deferred_implements.push((id.clone(), t));
368                }
369            }
370        }
371        self.extract_annotation_uses(node, &id);
372
373        let mut nested_scope = scope.to_vec();
374        nested_scope.push(name);
375        if let Some(body) = node.child_by_field_name("body") {
376            let mut c = body.walk();
377            for child in body.named_children(&mut c).collect::<Vec<_>>() {
378                if matches!(
379                    child.kind(),
380                    "method_declaration" | "constructor_declaration"
381                ) {
382                    self.visit_method(child, &nested_scope, id.clone());
383                } else if child.kind() == "field_declaration" {
384                    self.extract_field_uses(child, &id);
385                }
386            }
387        }
388        Some(id)
389    }
390
391    /// Visit a nested interface declaration, returning the new node's id.
392    fn visit_interface_nested(&mut self, node: TsNode<'_>, scope: &[String]) -> Option<NodeId> {
393        let name_node = node.child_by_field_name("name")?;
394        let name = self.text(name_node).to_owned();
395        let id = self
396            .type_index
397            .get(&name)
398            .cloned()
399            .unwrap_or_else(NodeId::new);
400        let is_functional = self.has_functional_interface_annotation(node);
401        let mut graph_node =
402            self.make_node(id.clone(), NodeKind::Interface, name.clone(), scope, node);
403        if is_functional {
404            graph_node.metadata.is_abstract = true;
405        }
406        self.nodes.push(graph_node);
407
408        if let Some(extends) = node.child_by_field_name("extends") {
409            let mut c = extends.walk();
410            for ext in extends.named_children(&mut c).collect::<Vec<_>>() {
411                if let Some(t) = self.extract_simple_type(ext) {
412                    self.deferred_implements.push((id.clone(), t));
413                }
414            }
415        }
416        self.extract_annotation_uses(node, &id);
417
418        let mut nested_scope = scope.to_vec();
419        nested_scope.push(name);
420        if let Some(body) = node.child_by_field_name("body") {
421            let mut c = body.walk();
422            for child in body.named_children(&mut c).collect::<Vec<_>>() {
423                if matches!(child.kind(), "method_declaration" | "constant_declaration") {
424                    self.visit_method(child, &nested_scope, id.clone());
425                }
426            }
427        }
428        Some(id)
429    }
430
431    fn visit_interface(&mut self, node: TsNode<'_>, scope: &[String]) {
432        let Some(name_node) = node.child_by_field_name("name") else {
433            return;
434        };
435        let name = self.text(name_node).to_owned();
436        let id = self
437            .type_index
438            .get(&name)
439            .cloned()
440            .unwrap_or_else(NodeId::new);
441        let is_functional = self.has_functional_interface_annotation(node);
442        let mut graph_node =
443            self.make_node(id.clone(), NodeKind::Interface, name.clone(), scope, node);
444        if is_functional {
445            graph_node.metadata.is_abstract = true;
446        }
447        self.nodes.push(graph_node);
448
449        // Annotations on the interface → Annotated edges
450        self.extract_annotation_uses(node, &id);
451
452        // extends (parent interfaces) → Implements edges
453        if let Some(extends) = node.child_by_field_name("extends") {
454            let mut c = extends.walk();
455            let ext_children: Vec<TsNode<'_>> = extends.named_children(&mut c).collect();
456            for ext in ext_children {
457                if let Some(t) = self.extract_simple_type(ext) {
458                    self.deferred_implements.push((id.clone(), t));
459                }
460            }
461        }
462
463        let mut iface_scope = scope.to_vec();
464        iface_scope.push(name.clone());
465
466        // Interface body methods
467        if let Some(body) = node.child_by_field_name("body") {
468            let mut c = body.walk();
469            let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
470            for child in body_children {
471                if matches!(child.kind(), "method_declaration" | "constant_declaration") {
472                    self.visit_method(child, &iface_scope, id.clone());
473                }
474            }
475        }
476    }
477
478    fn visit_enum(&mut self, node: TsNode<'_>, scope: &[String]) {
479        let Some(name_node) = node.child_by_field_name("name") else {
480            return;
481        };
482        let name = self.text(name_node).to_owned();
483        let id = self
484            .type_index
485            .get(&name)
486            .cloned()
487            .unwrap_or_else(NodeId::new);
488        let graph_node = self.make_node(id.clone(), NodeKind::Enum, name.clone(), scope, node);
489        self.nodes.push(graph_node);
490
491        // implements (interfaces) → Implements edges
492        if let Some(interfaces) = node.child_by_field_name("interfaces") {
493            let mut c = interfaces.walk();
494            let iface_children: Vec<TsNode<'_>> = interfaces.named_children(&mut c).collect();
495            for iface in iface_children {
496                if let Some(t) = self.extract_simple_type(iface) {
497                    self.deferred_implements.push((id.clone(), t));
498                }
499            }
500        }
501
502        let mut enum_scope = scope.to_vec();
503        enum_scope.push(name.clone());
504
505        if let Some(body) = node.child_by_field_name("body") {
506            let mut c = body.walk();
507            let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
508            for child in body_children {
509                if child.kind() == "method_declaration" {
510                    self.visit_method(child, &enum_scope, id.clone());
511                }
512            }
513        }
514    }
515
516    fn visit_record(&mut self, node: TsNode<'_>, scope: &[String]) {
517        let Some(name_node) = node.child_by_field_name("name") else {
518            return;
519        };
520        let name = self.text(name_node).to_owned();
521        // Records are treated as Struct (they are essentially final data classes)
522        let id = self
523            .type_index
524            .get(&name)
525            .cloned()
526            .unwrap_or_else(NodeId::new);
527        let graph_node = self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
528        self.nodes.push(graph_node);
529
530        let mut record_scope = scope.to_vec();
531        record_scope.push(name);
532
533        if let Some(body) = node.child_by_field_name("body") {
534            let mut c = body.walk();
535            let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
536            for child in body_children {
537                if child.kind() == "method_declaration" {
538                    self.visit_method(child, &record_scope, id.clone());
539                }
540            }
541        }
542    }
543
544    fn visit_method(&mut self, node: TsNode<'_>, scope: &[String], container_id: NodeId) {
545        let Some(name_node) = node.child_by_field_name("name") else {
546            return;
547        };
548        let name = self.text(name_node).to_owned();
549        let id = self
550            .fn_index
551            .get(&name)
552            .cloned()
553            .unwrap_or_else(NodeId::new);
554        // Register this method in fn_index so later method bodies in the same
555        // file resolve calls intra-file via `record_call`'s fn_index lookup.
556        // Without this, every intra-file Java call gets pushed onto
557        // `deferred_calls` and is later matched across all languages by
558        // name, producing spurious cross-language edges.
559        self.fn_index.insert(name.clone(), id.clone());
560        let kind = if node.kind() == "constructor_declaration" {
561            NodeKind::Function
562        } else {
563            NodeKind::Method
564        };
565        let graph_node = self.make_node(id.clone(), kind, name, scope, node);
566        self.edges.push(Edge {
567            src: container_id,
568            dst: id.clone(),
569            kind: EdgeKind::Contains,
570        });
571        self.nodes.push(graph_node);
572
573        // Parameter types → Uses edges
574        if let Some(params) = node.child_by_field_name("parameters") {
575            let mut c = params.walk();
576            let param_list: Vec<TsNode<'_>> = params.named_children(&mut c).collect();
577            for param in param_list {
578                if param.kind() == "formal_parameter" || param.kind() == "spread_parameter" {
579                    if let Some(type_node) = param.child_by_field_name("type") {
580                        for tname in self.collect_type_names(type_node) {
581                            self.deferred_uses.push((id.clone(), tname));
582                        }
583                    }
584                }
585            }
586        }
587
588        // Return type → Uses edges
589        if let Some(ret) = node.child_by_field_name("type") {
590            for tname in self.collect_type_names(ret) {
591                self.deferred_uses.push((id.clone(), tname));
592            }
593        }
594
595        // Annotations on the method → Annotated edges
596        self.extract_annotation_uses(node, &id);
597
598        // throws clause → Throws edges
599        if let Some(throws) = node.child_by_field_name("throws") {
600            let mut c = throws.walk();
601            for exc in throws.named_children(&mut c).collect::<Vec<_>>() {
602                if let Some(t) = self.extract_simple_type(exc) {
603                    self.deferred_throws.push((id.clone(), t));
604                }
605            }
606        }
607
608        // Calls in the method body
609        if let Some(body) = node.child_by_field_name("body") {
610            self.collect_calls(body, &id);
611        }
612    }
613
614    // ── Pass 3: collect import declarations ───────────────────────────────────
615
616    fn collect_imports(&mut self, node: TsNode<'_>) {
617        let mut cursor = node.walk();
618        let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
619        for child in children {
620            if child.kind() != "import_declaration" {
621                continue;
622            }
623            // import_declaration text: `import com.example.Foo;` or `import static ...`
624            // The last identifier in the import path is the leaf name.
625            let raw = self.text(child);
626            let clean = raw
627                .trim_start_matches("import")
628                .trim_start_matches(" static")
629                .trim()
630                .trim_end_matches(';')
631                .trim();
632            // Get the last segment after the last '.'
633            let leaf = clean.split('.').next_back().unwrap_or(clean);
634            // Skip wildcard imports (*)
635            if leaf == "*" {
636                continue;
637            }
638            self.deferred_imports
639                .push((self.package_id.clone(), leaf.to_owned()));
640        }
641    }
642
643    // ── Helpers ───────────────────────────────────────────────────────────────
644
645    /// Extract annotations on a node as Annotated edges.
646    fn extract_annotation_uses(&mut self, node: TsNode<'_>, node_id: &NodeId) {
647        let mut c = node.walk();
648        let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
649        for child in children {
650            if child.kind() == "modifiers" {
651                let mut cc = child.walk();
652                let mod_children: Vec<TsNode<'_>> = child.named_children(&mut cc).collect();
653                for mc in mod_children {
654                    if mc.kind() == "annotation" || mc.kind() == "marker_annotation" {
655                        // Annotation name is the first named child (type_identifier or identifier)
656                        let mut ccc = mc.walk();
657                        let ann_children: Vec<TsNode<'_>> = mc.named_children(&mut ccc).collect();
658                        if let Some(ann_name_node) = ann_children.first() {
659                            let ann_name = self.text(*ann_name_node).to_owned();
660                            if !ann_name.is_empty() {
661                                self.deferred_annotated.push((node_id.clone(), ann_name));
662                            }
663                        }
664                    }
665                }
666            }
667        }
668    }
669
670    /// Returns true when a node has a `@FunctionalInterface` annotation.
671    fn has_functional_interface_annotation(&self, node: TsNode<'_>) -> bool {
672        let mut c = node.walk();
673        for child in node.named_children(&mut c).collect::<Vec<_>>() {
674            if child.kind() == "modifiers" {
675                let mut cc = child.walk();
676                for mc in child.named_children(&mut cc).collect::<Vec<_>>() {
677                    if mc.kind() == "annotation" || mc.kind() == "marker_annotation" {
678                        let mut ccc = mc.walk();
679                        if let Some(ann) = mc.named_children(&mut ccc).collect::<Vec<_>>().first() {
680                            if self.text(*ann) == "FunctionalInterface" {
681                                return true;
682                            }
683                        }
684                    }
685                }
686            }
687        }
688        false
689    }
690
691    /// Extract Uses edges from a field declaration's type.
692    fn extract_field_uses(&mut self, field_decl: TsNode<'_>, container_id: &NodeId) {
693        if let Some(type_node) = field_decl.child_by_field_name("type") {
694            for tname in self.collect_type_names(type_node) {
695                self.deferred_uses.push((container_id.clone(), tname));
696            }
697        }
698    }
699
700    /// Extract the simple class/interface name from a type node.
701    fn extract_simple_type(&self, node: TsNode<'_>) -> Option<String> {
702        match node.kind() {
703            "type_identifier" | "identifier" => Some(self.text(node).to_owned()),
704            // `generic_type` in tree-sitter-java is `(type_identifier (type_arguments …))`
705            // with NO `name` field — strip the type arguments and keep the raw
706            // type name so `extends TypeAdapter<T>` resolves to `TypeAdapter`.
707            // (A `name`-field lookup here previously returned None, dropping the
708            // inherits edge for every generic superclass.)
709            "generic_type" => {
710                let mut c = node.walk();
711                let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
712                children.into_iter().find_map(|ch| match ch.kind() {
713                    "type_identifier" | "scoped_type_identifier" | "identifier" => {
714                        Some(self.text(ch).to_owned())
715                    }
716                    _ => None,
717                })
718            }
719            // `scoped_type_identifier` (`a.b.Foo`) — keep the last segment.
720            "scoped_type_identifier" => self.text(node).rsplit('.').next().map(|s| s.to_owned()),
721            _ => {
722                // Try first named child
723                let mut c = node.walk();
724                let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
725                children
726                    .into_iter()
727                    .find_map(|ch| self.extract_simple_type(ch))
728            }
729        }
730    }
731
732    /// Walk a type expression and collect non-builtin type names.
733    fn collect_type_names(&self, node: TsNode<'_>) -> Vec<String> {
734        let mut names = Vec::new();
735        self.walk_type_names(node, &mut names);
736        names
737    }
738
739    fn walk_type_names(&self, node: TsNode<'_>, out: &mut Vec<String>) {
740        match node.kind() {
741            "type_identifier" => {
742                let name = self.text(node).to_owned();
743                if !is_builtin_java_type(&name) {
744                    out.push(name);
745                }
746            }
747            // Skip integral_type (int, long, etc.) and floating_point_type
748            "integral_type" | "floating_point_type" | "boolean_type" | "void_type" => {}
749            _ => {
750                let mut c = node.walk();
751                for child in node.named_children(&mut c) {
752                    self.walk_type_names(child, out);
753                }
754            }
755        }
756    }
757
758    // ── Call collection ───────────────────────────────────────────────────────
759
760    fn collect_calls(&mut self, node: TsNode<'_>, caller_id: &NodeId) {
761        let mut cursor = node.walk();
762        let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
763        for child in children {
764            match child.kind() {
765                "method_invocation" | "object_creation_expression" => {
766                    if let Some(callee) = self.callee_name(child) {
767                        self.record_call(caller_id.clone(), callee);
768                    }
769                    // Recurse into arguments
770                    if let Some(args) = child.child_by_field_name("arguments") {
771                        self.collect_calls(args, caller_id);
772                    }
773                }
774                _ => self.collect_calls(child, caller_id),
775            }
776        }
777    }
778
779    fn callee_name(&self, call_expr: TsNode<'_>) -> Option<String> {
780        // method_invocation: object?.method(args) → name field is the method name
781        // object_creation_expression: new Type(args) → type field
782        match call_expr.kind() {
783            "method_invocation" => call_expr
784                .child_by_field_name("name")
785                .map(|n| self.text(n).to_owned()),
786            "object_creation_expression" => {
787                call_expr
788                    .child_by_field_name("type")
789                    .and_then(|t| match t.kind() {
790                        "type_identifier" => Some(self.text(t).to_owned()),
791                        "generic_type" => t
792                            .child_by_field_name("name")
793                            .map(|n| self.text(n).to_owned()),
794                        _ => None,
795                    })
796            }
797            _ => None,
798        }
799    }
800
801    fn record_call(&mut self, caller_id: NodeId, callee_name: String) {
802        if callee_name.is_empty() {
803            return;
804        }
805        if let Some(callee_id) = self.fn_index.get(&callee_name).cloned() {
806            let edge = Edge {
807                src: caller_id,
808                dst: callee_id,
809                kind: EdgeKind::Calls,
810            };
811            if !self.edges.contains(&edge) {
812                self.edges.push(edge);
813            }
814        } else if !self
815            .deferred_calls
816            .iter()
817            .any(|(c, n)| c == &caller_id && n == &callee_name)
818        {
819            self.deferred_calls.push((caller_id, callee_name));
820        }
821    }
822}
823
824/// Returns true for Java primitive types and common standard library types.
825fn is_builtin_java_type(name: &str) -> bool {
826    matches!(
827        name,
828        "String"
829            | "Object"
830            | "Integer"
831            | "Long"
832            | "Double"
833            | "Float"
834            | "Boolean"
835            | "Byte"
836            | "Short"
837            | "Character"
838            | "Number"
839            | "Math"
840            | "System"
841            | "StringBuilder"
842            | "StringBuffer"
843            | "Comparable"
844            | "Serializable"
845            | "Cloneable"
846            | "Iterable"
847            | "Iterator"
848            | "Collection"
849            | "List"
850            | "Set"
851            | "Map"
852            | "Queue"
853            | "Deque"
854            | "Stack"
855            | "ArrayList"
856            | "HashMap"
857            | "HashSet"
858            | "LinkedList"
859            | "Optional"
860            | "Stream"
861            | "Collectors"
862            | "Arrays"
863            | "Collections"
864            | "Enum"
865            | "Throwable"
866            | "Exception"
867            | "RuntimeException"
868            | "Error"
869            | "Override"
870            | "Deprecated"
871            | "SuppressWarnings"
872            | "FunctionalInterface"
873            | "void"
874    )
875}
876
877// ── Tests ─────────────────────────────────────────────────────────────────────
878
879#[cfg(test)]
880mod tests {
881    use super::JavaParser;
882    use crate::parser::LanguageParser;
883    use gitcortex_core::schema::{EdgeKind, NodeKind};
884    use std::path::Path;
885
886    fn parse(
887        src: &str,
888    ) -> (
889        Vec<gitcortex_core::graph::Node>,
890        Vec<gitcortex_core::graph::Edge>,
891    ) {
892        let r = JavaParser::new()
893            .parse(Path::new("Test.java"), src)
894            .unwrap();
895        (r.nodes, r.edges)
896    }
897
898    #[allow(clippy::type_complexity)]
899    fn parse_full(
900        src: &str,
901    ) -> (
902        Vec<gitcortex_core::graph::Node>,
903        Vec<gitcortex_core::graph::Edge>,
904        Vec<(gitcortex_core::graph::NodeId, String)>,
905        Vec<(gitcortex_core::graph::NodeId, String)>,
906        Vec<(gitcortex_core::graph::NodeId, String)>,
907        Vec<(gitcortex_core::graph::NodeId, String)>,
908        Vec<(gitcortex_core::graph::NodeId, String)>,
909        Vec<(gitcortex_core::graph::NodeId, String)>,
910        Vec<(gitcortex_core::graph::NodeId, String)>,
911    ) {
912        let r = JavaParser::new()
913            .parse(Path::new("Test.java"), src)
914            .unwrap();
915        (
916            r.nodes,
917            r.edges,
918            r.deferred_calls,
919            r.deferred_uses,
920            r.deferred_implements,
921            r.deferred_imports,
922            r.deferred_inherits,
923            r.deferred_throws,
924            r.deferred_annotated,
925        )
926    }
927
928    #[test]
929    fn parses_class_and_method() {
930        let src = "public class Greeter { public String greet(String name) { return name; } }";
931        let (nodes, edges) = parse(src);
932        let classes: Vec<_> = nodes
933            .iter()
934            .filter(|n| n.kind == NodeKind::Struct)
935            .collect();
936        let methods: Vec<_> = nodes
937            .iter()
938            .filter(|n| n.kind == NodeKind::Method)
939            .collect();
940        assert_eq!(classes.len(), 1, "expected 1 class");
941        assert_eq!(classes[0].name, "Greeter");
942        assert_eq!(methods.len(), 1, "expected 1 method");
943        let contains: Vec<_> = edges
944            .iter()
945            .filter(|e| e.kind == EdgeKind::Contains)
946            .collect();
947        assert!(!contains.is_empty(), "expected Contains edge");
948    }
949
950    #[test]
951    fn parses_interface() {
952        let src = "public interface Greeter { String greet(String name); }";
953        let (nodes, _) = parse(src);
954        let interfaces: Vec<_> = nodes
955            .iter()
956            .filter(|n| n.kind == NodeKind::Interface || n.kind == NodeKind::Trait)
957            .collect();
958        assert_eq!(interfaces.len(), 1);
959        assert_eq!(interfaces[0].name, "Greeter");
960    }
961
962    #[test]
963    fn parses_enum() {
964        let src = "public enum Direction { NORTH, SOUTH, EAST, WEST }";
965        let (nodes, _) = parse(src);
966        let enums: Vec<_> = nodes.iter().filter(|n| n.kind == NodeKind::Enum).collect();
967        assert_eq!(enums.len(), 1);
968        assert_eq!(enums[0].name, "Direction");
969    }
970
971    #[test]
972    fn detects_extends_and_implements() {
973        let src = "interface Base {}\nclass Child extends Base implements Base {}";
974        let (_, _, _, _, implements, _, inherits, ..) = parse_full(src);
975        let impl_edges: Vec<_> = implements.iter().filter(|(_, n)| n == "Base").collect();
976        let inh_edges: Vec<_> = inherits.iter().filter(|(_, n)| n == "Base").collect();
977        assert!(
978            impl_edges.len() + inh_edges.len() >= 2,
979            "expected extends+implements edges to Base, implements={implements:?} inherits={inherits:?}"
980        );
981    }
982
983    #[test]
984    fn detects_type_annotation_uses() {
985        let src = "class Service {}\nclass Controller {\n    public Service handle(Service svc) { return svc; }\n}";
986        let (_, _, _, uses, ..) = parse_full(src);
987        let svc_uses: Vec<_> = uses.iter().filter(|(_, n)| n == "Service").collect();
988        assert!(
989            svc_uses.len() >= 2,
990            "expected Uses edges to Service (param + return), got: {uses:?}"
991        );
992    }
993
994    #[test]
995    fn detects_import_declaration() {
996        let src = "import com.example.MyService;\nimport java.util.List;\npublic class App {}";
997        let (_, _, _, _, _, imports, ..) = parse_full(src);
998        assert!(
999            imports.iter().any(|(_, n)| n == "MyService"),
1000            "expected import 'MyService', got: {imports:?}"
1001        );
1002    }
1003
1004    #[test]
1005    fn module_node_is_emitted() {
1006        let src = "public class App {}";
1007        let (nodes, _) = parse(src);
1008        let modules: Vec<_> = nodes
1009            .iter()
1010            .filter(|n| n.kind == NodeKind::Module)
1011            .collect();
1012        assert_eq!(modules.len(), 1);
1013        assert_eq!(modules[0].name, "Test"); // from "Test.java"
1014    }
1015}