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