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