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