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