Skip to main content

gitcortex_indexer/parser/
java.rs

1use std::{
2    collections::HashMap,
3    path::{Path, PathBuf},
4};
5
6use gitcortex_core::{
7    error::{GitCortexError, Result},
8    graph::{Edge, Node, NodeId, NodeMetadata, Span},
9    schema::{EdgeConfidence, EdgeKind, NodeKind, Visibility},
10};
11use tree_sitter::{Node as TsNode, Parser};
12
13use super::{capture_definition, LanguageParser, ParseResult};
14
15pub struct JavaParser {
16    language: tree_sitter::Language,
17}
18
19impl JavaParser {
20    pub fn new() -> Self {
21        Self {
22            language: tree_sitter_java::LANGUAGE.into(),
23        }
24    }
25}
26
27impl Default for JavaParser {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl LanguageParser for JavaParser {
34    fn extensions(&self) -> &[&str] {
35        &["java"]
36    }
37
38    fn parse(&self, path: &Path, source: &str) -> Result<ParseResult> {
39        let mut parser = Parser::new();
40        parser
41            .set_language(&self.language)
42            .map_err(|e| GitCortexError::Parse {
43                file: path.to_owned(),
44                message: e.to_string(),
45            })?;
46
47        let tree = parser
48            .parse(source, None)
49            .ok_or_else(|| GitCortexError::Parse {
50                file: path.to_owned(),
51                message: "tree-sitter returned no parse tree".into(),
52            })?;
53
54        let mut visitor = FileVisitor::new(path, source);
55        visitor.collect_names(tree.root_node());
56        visitor.visit_program(tree.root_node());
57        visitor.collect_imports(tree.root_node());
58
59        Ok(ParseResult {
60            nodes: visitor.nodes,
61            edges: visitor.edges,
62            deferred_calls: visitor.deferred_calls,
63            deferred_uses: visitor.deferred_uses,
64            deferred_implements: visitor.deferred_implements,
65            deferred_imports: visitor.deferred_imports,
66            deferred_inherits: visitor.deferred_inherits,
67            deferred_throws: visitor.deferred_throws,
68            deferred_annotated: visitor.deferred_annotated,
69            deferred_doc_refs: Vec::new(),
70        })
71    }
72}
73
74// ── Internal visitor ──────────────────────────────────────────────────────────
75
76struct FileVisitor<'src> {
77    source: &'src [u8],
78    file: PathBuf,
79    /// NodeId of the file-level package node (anchor for Imports edges).
80    package_id: NodeId,
81    nodes: Vec<Node>,
82    edges: Vec<Edge>,
83    /// class/interface/enum name → NodeId
84    type_index: HashMap<String, NodeId>,
85    /// method name → NodeId
86    fn_index: HashMap<String, NodeId>,
87    deferred_calls: Vec<(NodeId, String, u32)>,
88    deferred_uses: Vec<(NodeId, String)>,
89    deferred_implements: Vec<(NodeId, String)>,
90    deferred_imports: Vec<(NodeId, String)>,
91    deferred_inherits: Vec<(NodeId, String)>,
92    deferred_throws: Vec<(NodeId, String)>,
93    deferred_annotated: Vec<(NodeId, String)>,
94}
95
96impl<'src> FileVisitor<'src> {
97    fn new(file: &Path, source: &'src str) -> Self {
98        let package_id = NodeId::new();
99        // Use the file stem as the compilation unit name.
100        let unit_name = file
101            .file_stem()
102            .and_then(|s| s.to_str())
103            .unwrap_or("Unknown")
104            .to_owned();
105        let package_node = Node {
106            id: package_id.clone(),
107            qualified_name: unit_name.clone(),
108            kind: NodeKind::Module,
109            name: unit_name,
110            file: file.to_owned(),
111            span: Span {
112                start_line: 1,
113                end_line: 1,
114            },
115            metadata: NodeMetadata {
116                loc: source.lines().count() as u32,
117                visibility: Visibility::Pub,
118                is_async: false,
119                is_unsafe: false,
120                ..Default::default()
121            },
122        };
123        let nodes = vec![package_node];
124        Self {
125            source: source.as_bytes(),
126            file: file.to_owned(),
127            package_id,
128            nodes,
129            edges: Vec::new(),
130            type_index: HashMap::new(),
131            fn_index: HashMap::new(),
132            deferred_calls: Vec::new(),
133            deferred_uses: Vec::new(),
134            deferred_implements: Vec::new(),
135            deferred_imports: Vec::new(),
136            deferred_inherits: Vec::new(),
137            deferred_throws: Vec::new(),
138            deferred_annotated: Vec::new(),
139        }
140    }
141
142    fn text<'t>(&self, node: TsNode<'t>) -> &'src str {
143        node.utf8_text(self.source).unwrap_or("")
144    }
145
146    fn span(node: TsNode<'_>) -> Span {
147        Span {
148            start_line: node.start_position().row as u32 + 1,
149            end_line: node.end_position().row as u32 + 1,
150        }
151    }
152
153    fn visibility(node: TsNode<'_>, source: &[u8]) -> Visibility {
154        let mut cursor = node.walk();
155        for child in node.children(&mut cursor) {
156            if child.kind() == "modifiers" {
157                let text = child.utf8_text(source).unwrap_or("");
158                if text.contains("public") {
159                    return Visibility::Pub;
160                }
161                if text.contains("protected") {
162                    return Visibility::PubCrate;
163                }
164                return Visibility::Private;
165            }
166        }
167        // Package-private (no modifier) = effectively PubCrate within package
168        Visibility::PubCrate
169    }
170
171    fn is_async(_node: TsNode<'_>) -> bool {
172        // Java doesn't have async/await; treat `synchronized` as a proxy for async
173        false
174    }
175
176    /// Returns the text of the `modifiers` child node, or `""` if absent.
177    fn modifiers_text<'t>(node: TsNode<'t>, source: &'t [u8]) -> &'t str {
178        let mut cursor = node.walk();
179        for child in node.children(&mut cursor) {
180            if child.kind() == "modifiers" {
181                return child.utf8_text(source).unwrap_or("");
182            }
183        }
184        ""
185    }
186
187    fn qualified(scope: &[String], name: &str) -> String {
188        if scope.is_empty() {
189            name.to_owned()
190        } else {
191            format!("{}.{name}", scope.join("."))
192        }
193    }
194
195    fn make_node(
196        &self,
197        id: NodeId,
198        kind: NodeKind,
199        name: String,
200        scope: &[String],
201        ts_node: TsNode<'_>,
202    ) -> Node {
203        let mods = Self::modifiers_text(ts_node, self.source);
204        Node {
205            id,
206            qualified_name: Self::qualified(scope, &name),
207            kind,
208            name,
209            file: self.file.clone(),
210            span: Self::span(ts_node),
211            metadata: NodeMetadata {
212                loc: (ts_node.end_position().row - ts_node.start_position().row + 1) as u32,
213                visibility: Self::visibility(ts_node, self.source),
214                is_async: Self::is_async(ts_node),
215                is_unsafe: false,
216                is_abstract: mods.contains("abstract"),
217                is_final: mods.contains("final"),
218                is_static: mods.contains("static"),
219                definition: capture_definition(self.source, ts_node),
220                ..Default::default()
221            },
222        }
223    }
224
225    // ── Pass 1: pre-allocate NodeIds ──────────────────────────────────────────
226
227    fn collect_names(&mut self, node: TsNode<'_>) {
228        let mut cursor = node.walk();
229        let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
230        for child in children {
231            match child.kind() {
232                "class_declaration"
233                | "interface_declaration"
234                | "enum_declaration"
235                | "annotation_type_declaration"
236                | "record_declaration" => {
237                    if let Some(name_node) = child.child_by_field_name("name") {
238                        let name = self.text(name_node).to_owned();
239                        self.type_index.entry(name).or_default();
240                    }
241                }
242                _ => {}
243            }
244        }
245    }
246
247    // ── Pass 2: emit nodes + edges ────────────────────────────────────────────
248
249    fn visit_program(&mut self, node: TsNode<'_>) {
250        let mut cursor = node.walk();
251        let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
252        for child in children {
253            self.visit_top_level(child, &[]);
254        }
255    }
256
257    fn visit_top_level(&mut self, node: TsNode<'_>, scope: &[String]) {
258        match node.kind() {
259            "class_declaration" => self.visit_class(node, scope),
260            "interface_declaration" => self.visit_interface(node, scope),
261            "enum_declaration" => self.visit_enum(node, scope),
262            "record_declaration" => self.visit_record(node, scope),
263            _ => {}
264        }
265    }
266
267    fn visit_class(&mut self, node: TsNode<'_>, scope: &[String]) {
268        let Some(name_node) = node.child_by_field_name("name") else {
269            return;
270        };
271        let name = self.text(name_node).to_owned();
272        let id = self
273            .type_index
274            .get(&name)
275            .cloned()
276            .unwrap_or_else(NodeId::new);
277        let graph_node = self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
278        self.nodes.push(graph_node);
279
280        // extends (super class) → Inherits edge
281        if let Some(superclass) = node.child_by_field_name("superclass") {
282            let type_name = self.extract_simple_type(superclass);
283            if let Some(t) = type_name {
284                self.deferred_inherits.push((id.clone(), t));
285            }
286        }
287
288        // implements (interfaces) → Implements edges
289        if let Some(interfaces) = node.child_by_field_name("interfaces") {
290            let mut c = interfaces.walk();
291            let iface_children: Vec<TsNode<'_>> = interfaces.named_children(&mut c).collect();
292            for iface in iface_children {
293                if let Some(t) = self.extract_simple_type(iface) {
294                    self.deferred_implements.push((id.clone(), t));
295                }
296            }
297        }
298
299        // Annotations on the class → Annotated edges
300        self.extract_annotation_uses(node, &id);
301
302        let mut class_scope = scope.to_vec();
303        class_scope.push(name.clone());
304
305        // Visit class body
306        if let Some(body) = node.child_by_field_name("body") {
307            let mut c = body.walk();
308            let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
309            for child in body_children {
310                match child.kind() {
311                    "method_declaration" | "constructor_declaration" => {
312                        self.visit_method(child, &class_scope, id.clone());
313                    }
314                    "class_declaration" => {
315                        let nested_id = self.visit_class_nested(child, &class_scope);
316                        if let Some(nid) = nested_id {
317                            self.edges.push(Edge {
318                                src: id.clone(),
319                                dst: nid,
320                                kind: EdgeKind::Contains,
321                                line: None,
322                                confidence: EdgeConfidence::Extracted,
323                            });
324                        }
325                    }
326                    "interface_declaration" => {
327                        let nested_id = self.visit_interface_nested(child, &class_scope);
328                        if let Some(nid) = nested_id {
329                            self.edges.push(Edge {
330                                src: id.clone(),
331                                dst: nid,
332                                kind: EdgeKind::Contains,
333                                line: None,
334                                confidence: EdgeConfidence::Extracted,
335                            });
336                        }
337                    }
338                    "record_declaration" => {
339                        let nested_id = self.visit_record_nested(child, &class_scope);
340                        if let Some(nid) = nested_id {
341                            self.edges.push(Edge {
342                                src: id.clone(),
343                                dst: nid,
344                                kind: EdgeKind::Contains,
345                                line: None,
346                                confidence: EdgeConfidence::Extracted,
347                            });
348                        }
349                    }
350                    "field_declaration" => {
351                        self.visit_field_declaration(child, &id, &class_scope);
352                    }
353                    _ => {}
354                }
355            }
356        }
357    }
358
359    /// Visit a nested class declaration, returning the new node's id.
360    fn visit_class_nested(&mut self, node: TsNode<'_>, scope: &[String]) -> Option<NodeId> {
361        let name_node = node.child_by_field_name("name")?;
362        let name = self.text(name_node).to_owned();
363        let id = self
364            .type_index
365            .get(&name)
366            .cloned()
367            .unwrap_or_else(NodeId::new);
368        let mut graph_node =
369            self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
370        // make_node reads `static` from modifiers — already handled there
371        let mods = Self::modifiers_text(node, self.source);
372        graph_node.metadata.is_static = mods.contains("static");
373        self.nodes.push(graph_node);
374
375        if let Some(superclass) = node.child_by_field_name("superclass") {
376            if let Some(t) = self.extract_simple_type(superclass) {
377                self.deferred_inherits.push((id.clone(), t));
378            }
379        }
380        if let Some(interfaces) = node.child_by_field_name("interfaces") {
381            let mut c = interfaces.walk();
382            for iface in interfaces.named_children(&mut c).collect::<Vec<_>>() {
383                if let Some(t) = self.extract_simple_type(iface) {
384                    self.deferred_implements.push((id.clone(), t));
385                }
386            }
387        }
388        self.extract_annotation_uses(node, &id);
389
390        let mut nested_scope = scope.to_vec();
391        nested_scope.push(name);
392        if let Some(body) = node.child_by_field_name("body") {
393            let mut c = body.walk();
394            for child in body.named_children(&mut c).collect::<Vec<_>>() {
395                if matches!(
396                    child.kind(),
397                    "method_declaration" | "constructor_declaration"
398                ) {
399                    self.visit_method(child, &nested_scope, id.clone());
400                } else if child.kind() == "record_declaration" {
401                    if let Some(nid) = self.visit_record_nested(child, &nested_scope) {
402                        self.edges.push(Edge {
403                            src: id.clone(),
404                            dst: nid,
405                            kind: EdgeKind::Contains,
406                            line: None,
407                            confidence: EdgeConfidence::Extracted,
408                        });
409                    }
410                } else if child.kind() == "field_declaration" {
411                    self.visit_field_declaration(child, &id, &nested_scope);
412                }
413            }
414        }
415        Some(id)
416    }
417
418    /// Visit a nested interface declaration, returning the new node's id.
419    fn visit_interface_nested(&mut self, node: TsNode<'_>, scope: &[String]) -> Option<NodeId> {
420        let name_node = node.child_by_field_name("name")?;
421        let name = self.text(name_node).to_owned();
422        let id = self
423            .type_index
424            .get(&name)
425            .cloned()
426            .unwrap_or_else(NodeId::new);
427        let is_functional = self.has_functional_interface_annotation(node);
428        let mut graph_node =
429            self.make_node(id.clone(), NodeKind::Interface, name.clone(), scope, node);
430        if is_functional {
431            graph_node.metadata.is_abstract = true;
432        }
433        self.nodes.push(graph_node);
434
435        if let Some(extends) = node.child_by_field_name("extends") {
436            let mut c = extends.walk();
437            for ext in extends.named_children(&mut c).collect::<Vec<_>>() {
438                if let Some(t) = self.extract_simple_type(ext) {
439                    self.deferred_implements.push((id.clone(), t));
440                }
441            }
442        }
443        self.extract_annotation_uses(node, &id);
444
445        let mut nested_scope = scope.to_vec();
446        nested_scope.push(name);
447        if let Some(body) = node.child_by_field_name("body") {
448            let mut c = body.walk();
449            for child in body.named_children(&mut c).collect::<Vec<_>>() {
450                if matches!(child.kind(), "method_declaration" | "constant_declaration") {
451                    self.visit_method(child, &nested_scope, id.clone());
452                }
453            }
454        }
455        Some(id)
456    }
457
458    fn visit_interface(&mut self, node: TsNode<'_>, scope: &[String]) {
459        let Some(name_node) = node.child_by_field_name("name") else {
460            return;
461        };
462        let name = self.text(name_node).to_owned();
463        let id = self
464            .type_index
465            .get(&name)
466            .cloned()
467            .unwrap_or_else(NodeId::new);
468        let is_functional = self.has_functional_interface_annotation(node);
469        let mut graph_node =
470            self.make_node(id.clone(), NodeKind::Interface, name.clone(), scope, node);
471        if is_functional {
472            graph_node.metadata.is_abstract = true;
473        }
474        self.nodes.push(graph_node);
475
476        // Annotations on the interface → Annotated edges
477        self.extract_annotation_uses(node, &id);
478
479        // extends (parent interfaces) → Implements edges
480        if let Some(extends) = node.child_by_field_name("extends") {
481            let mut c = extends.walk();
482            let ext_children: Vec<TsNode<'_>> = extends.named_children(&mut c).collect();
483            for ext in ext_children {
484                if let Some(t) = self.extract_simple_type(ext) {
485                    self.deferred_implements.push((id.clone(), t));
486                }
487            }
488        }
489
490        let mut iface_scope = scope.to_vec();
491        iface_scope.push(name.clone());
492
493        // Interface body methods
494        if let Some(body) = node.child_by_field_name("body") {
495            let mut c = body.walk();
496            let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
497            for child in body_children {
498                if matches!(child.kind(), "method_declaration" | "constant_declaration") {
499                    self.visit_method(child, &iface_scope, id.clone());
500                }
501            }
502        }
503    }
504
505    fn visit_enum(&mut self, node: TsNode<'_>, scope: &[String]) {
506        let Some(name_node) = node.child_by_field_name("name") else {
507            return;
508        };
509        let name = self.text(name_node).to_owned();
510        let id = self
511            .type_index
512            .get(&name)
513            .cloned()
514            .unwrap_or_else(NodeId::new);
515        let graph_node = self.make_node(id.clone(), NodeKind::Enum, name.clone(), scope, node);
516        self.nodes.push(graph_node);
517
518        // implements (interfaces) → Implements edges
519        if let Some(interfaces) = node.child_by_field_name("interfaces") {
520            let mut c = interfaces.walk();
521            let iface_children: Vec<TsNode<'_>> = interfaces.named_children(&mut c).collect();
522            for iface in iface_children {
523                if let Some(t) = self.extract_simple_type(iface) {
524                    self.deferred_implements.push((id.clone(), t));
525                }
526            }
527        }
528
529        // Annotations on the enum itself → Annotated edges (mirrors the
530        // class/interface/method treatment; enums were previously skipped).
531        self.extract_annotation_uses(node, &id);
532
533        let mut enum_scope = scope.to_vec();
534        enum_scope.push(name.clone());
535
536        if let Some(body) = node.child_by_field_name("body") {
537            let mut c = body.walk();
538            let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
539            for child in body_children {
540                if child.kind() == "method_declaration" {
541                    self.visit_method(child, &enum_scope, id.clone());
542                }
543            }
544        }
545    }
546
547    fn visit_record(&mut self, node: TsNode<'_>, scope: &[String]) {
548        let Some(name_node) = node.child_by_field_name("name") else {
549            return;
550        };
551        let name = self.text(name_node).to_owned();
552        // Records are treated as Struct (they are essentially final data classes)
553        let id = self
554            .type_index
555            .get(&name)
556            .cloned()
557            .unwrap_or_else(NodeId::new);
558        let graph_node = self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
559        self.nodes.push(graph_node);
560
561        // Annotations on the record itself → Annotated edges.
562        self.extract_annotation_uses(node, &id);
563
564        let mut record_scope = scope.to_vec();
565        record_scope.push(name);
566
567        if let Some(body) = node.child_by_field_name("body") {
568            let mut c = body.walk();
569            let body_children: Vec<TsNode<'_>> = body.named_children(&mut c).collect();
570            for child in body_children {
571                if child.kind() == "method_declaration" {
572                    self.visit_method(child, &record_scope, id.clone());
573                }
574            }
575        }
576    }
577
578    /// Visit a nested record declaration (inside a class body), returning its NodeId.
579    fn visit_record_nested(&mut self, node: TsNode<'_>, scope: &[String]) -> Option<NodeId> {
580        let name_node = node.child_by_field_name("name")?;
581        let name = self.text(name_node).to_owned();
582        let id = self
583            .type_index
584            .get(&name)
585            .cloned()
586            .unwrap_or_else(NodeId::new);
587        let graph_node = self.make_node(id.clone(), NodeKind::Struct, name.clone(), scope, node);
588        self.nodes.push(graph_node);
589        self.extract_annotation_uses(node, &id);
590
591        let mut record_scope = scope.to_vec();
592        record_scope.push(name);
593
594        if let Some(body) = node.child_by_field_name("body") {
595            let mut c = body.walk();
596            for child in body.named_children(&mut c).collect::<Vec<_>>() {
597                if child.kind() == "method_declaration" {
598                    self.visit_method(child, &record_scope, id.clone());
599                }
600            }
601        }
602        Some(id)
603    }
604
605    fn visit_method(&mut self, node: TsNode<'_>, scope: &[String], container_id: NodeId) {
606        let Some(name_node) = node.child_by_field_name("name") else {
607            return;
608        };
609        let name = self.text(name_node).to_owned();
610        let id = self
611            .fn_index
612            .get(&name)
613            .cloned()
614            .unwrap_or_else(NodeId::new);
615        // Register this method in fn_index so later method bodies in the same
616        // file resolve calls intra-file via `record_call`'s fn_index lookup.
617        // Without this, every intra-file Java call gets pushed onto
618        // `deferred_calls` and is later matched across all languages by
619        // name, producing spurious cross-language edges.
620        self.fn_index.insert(name.clone(), id.clone());
621        let kind = if node.kind() == "constructor_declaration" {
622            NodeKind::Function
623        } else {
624            NodeKind::Method
625        };
626        let mut graph_node = self.make_node(id.clone(), kind, name, scope, node);
627        if let Some(body) = node.child_by_field_name("body") {
628            graph_node.metadata.lld.complexity = Some(super::cyclomatic_complexity(
629                body,
630                &super::complexity::java_decision,
631            ));
632        }
633        self.edges.push(Edge {
634            src: container_id,
635            dst: id.clone(),
636            kind: EdgeKind::Contains,
637            line: None,
638            confidence: EdgeConfidence::Extracted,
639        });
640        self.nodes.push(graph_node);
641
642        // Parameter types → Uses edges
643        if let Some(params) = node.child_by_field_name("parameters") {
644            let mut c = params.walk();
645            let param_list: Vec<TsNode<'_>> = params.named_children(&mut c).collect();
646            for param in param_list {
647                if param.kind() == "formal_parameter" || param.kind() == "spread_parameter" {
648                    if let Some(type_node) = param.child_by_field_name("type") {
649                        for tname in self.collect_type_names(type_node) {
650                            self.deferred_uses.push((id.clone(), tname));
651                        }
652                    }
653                }
654            }
655        }
656
657        // Return type → Uses edges
658        if let Some(ret) = node.child_by_field_name("type") {
659            for tname in self.collect_type_names(ret) {
660                self.deferred_uses.push((id.clone(), tname));
661            }
662        }
663
664        // Annotations on the method → Annotated edges
665        self.extract_annotation_uses(node, &id);
666
667        // throws clause → Throws edges
668        if let Some(throws) = node.child_by_field_name("throws") {
669            let mut c = throws.walk();
670            for exc in throws.named_children(&mut c).collect::<Vec<_>>() {
671                if let Some(t) = self.extract_simple_type(exc) {
672                    self.deferred_throws.push((id.clone(), t));
673                }
674            }
675        }
676
677        // Calls in the method body
678        if let Some(body) = node.child_by_field_name("body") {
679            self.collect_calls(body, &id);
680        }
681    }
682
683    // ── Pass 3: collect import declarations ───────────────────────────────────
684
685    fn collect_imports(&mut self, node: TsNode<'_>) {
686        let mut cursor = node.walk();
687        let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
688        for child in children {
689            if child.kind() != "import_declaration" {
690                continue;
691            }
692            // import_declaration text: `import com.example.Foo;` or `import static ...`
693            // The last identifier in the import path is the leaf name.
694            let raw = self.text(child);
695            let clean = raw
696                .trim_start_matches("import")
697                .trim_start_matches(" static")
698                .trim()
699                .trim_end_matches(';')
700                .trim();
701            // Get the last segment after the last '.'
702            let leaf = clean.split('.').next_back().unwrap_or(clean);
703            // Skip wildcard imports (*)
704            if leaf == "*" {
705                continue;
706            }
707            self.deferred_imports
708                .push((self.package_id.clone(), leaf.to_owned()));
709        }
710    }
711
712    // ── Helpers ───────────────────────────────────────────────────────────────
713
714    /// Extract annotations on a node as Annotated edges.
715    fn extract_annotation_uses(&mut self, node: TsNode<'_>, node_id: &NodeId) {
716        let mut c = node.walk();
717        let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
718        for child in children {
719            if child.kind() == "modifiers" {
720                let mut cc = child.walk();
721                let mod_children: Vec<TsNode<'_>> = child.named_children(&mut cc).collect();
722                for mc in mod_children {
723                    if mc.kind() == "annotation" || mc.kind() == "marker_annotation" {
724                        // Annotation name is the first named child (type_identifier or identifier)
725                        let mut ccc = mc.walk();
726                        let ann_children: Vec<TsNode<'_>> = mc.named_children(&mut ccc).collect();
727                        if let Some(ann_name_node) = ann_children.first() {
728                            let ann_name = self.text(*ann_name_node).to_owned();
729                            if !ann_name.is_empty() {
730                                self.deferred_annotated.push((node_id.clone(), ann_name));
731                            }
732                        }
733                    }
734                }
735            }
736        }
737    }
738
739    /// Returns true when a node has a `@FunctionalInterface` annotation.
740    fn has_functional_interface_annotation(&self, node: TsNode<'_>) -> bool {
741        let mut c = node.walk();
742        for child in node.named_children(&mut c).collect::<Vec<_>>() {
743            if child.kind() == "modifiers" {
744                let mut cc = child.walk();
745                for mc in child.named_children(&mut cc).collect::<Vec<_>>() {
746                    if mc.kind() == "annotation" || mc.kind() == "marker_annotation" {
747                        let mut ccc = mc.walk();
748                        if let Some(ann) = mc.named_children(&mut ccc).collect::<Vec<_>>().first() {
749                            if self.text(*ann) == "FunctionalInterface" {
750                                return true;
751                            }
752                        }
753                    }
754                }
755            }
756        }
757        false
758    }
759
760    /// Visit a field declaration: always emit Uses edges from the declared
761    /// type back to the container (pre-existing behavior), and additionally,
762    /// for `static final` fields, emit a `Constant` Def node per declarator
763    /// with a `Contains` edge from the container plus its own annotations —
764    /// closing the "static final fields not modeled" gap noted in the
765    /// coverage matrix. Non-static-final fields intentionally still get no
766    /// Def node — same scope the coverage matrix called out, not a new gap.
767    fn visit_field_declaration(
768        &mut self,
769        field_decl: TsNode<'_>,
770        container_id: &NodeId,
771        scope: &[String],
772    ) {
773        if let Some(type_node) = field_decl.child_by_field_name("type") {
774            for tname in self.collect_type_names(type_node) {
775                self.deferred_uses.push((container_id.clone(), tname));
776            }
777        }
778
779        let mods = Self::modifiers_text(field_decl, self.source);
780        if !(mods.contains("static") && mods.contains("final")) {
781            return;
782        }
783
784        let mut cursor = field_decl.walk();
785        let declarators: Vec<TsNode<'_>> = field_decl
786            .children_by_field_name("declarator", &mut cursor)
787            .collect();
788        for decl in declarators {
789            let Some(name_node) = decl.child_by_field_name("name") else {
790                continue;
791            };
792            let name = self.text(name_node).to_owned();
793            let id = NodeId::new();
794            let graph_node =
795                self.make_node(id.clone(), NodeKind::Constant, name, scope, field_decl);
796            self.nodes.push(graph_node);
797            self.edges.push(Edge {
798                src: container_id.clone(),
799                dst: id.clone(),
800                kind: EdgeKind::Contains,
801                line: None,
802                confidence: EdgeConfidence::Extracted,
803            });
804            // Annotations on the field (`@Deprecated`, `@SerializedName`, …)
805            // → Annotated edges + node.metadata.annotations (mirrored by the
806            // indexer regardless of edge resolution).
807            self.extract_annotation_uses(field_decl, &id);
808        }
809    }
810
811    /// Extract the simple class/interface name from a type node.
812    fn extract_simple_type(&self, node: TsNode<'_>) -> Option<String> {
813        match node.kind() {
814            "type_identifier" | "identifier" => Some(self.text(node).to_owned()),
815            // `generic_type` in tree-sitter-java is `(type_identifier (type_arguments …))`
816            // with NO `name` field — strip the type arguments and keep the raw
817            // type name so `extends TypeAdapter<T>` resolves to `TypeAdapter`.
818            // (A `name`-field lookup here previously returned None, dropping the
819            // inherits edge for every generic superclass.)
820            "generic_type" => {
821                let mut c = node.walk();
822                let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
823                children.into_iter().find_map(|ch| match ch.kind() {
824                    "type_identifier" | "scoped_type_identifier" | "identifier" => {
825                        Some(self.text(ch).to_owned())
826                    }
827                    _ => None,
828                })
829            }
830            // `scoped_type_identifier` (`a.b.Foo`) — keep the last segment.
831            "scoped_type_identifier" => self.text(node).rsplit('.').next().map(|s| s.to_owned()),
832            _ => {
833                // Try first named child
834                let mut c = node.walk();
835                let children: Vec<TsNode<'_>> = node.named_children(&mut c).collect();
836                children
837                    .into_iter()
838                    .find_map(|ch| self.extract_simple_type(ch))
839            }
840        }
841    }
842
843    /// Walk a type expression and collect non-builtin type names.
844    fn collect_type_names(&self, node: TsNode<'_>) -> Vec<String> {
845        let mut names = Vec::new();
846        self.walk_type_names(node, &mut names);
847        names
848    }
849
850    fn walk_type_names(&self, node: TsNode<'_>, out: &mut Vec<String>) {
851        match node.kind() {
852            "type_identifier" => {
853                let name = self.text(node).to_owned();
854                if !is_builtin_java_type(&name) {
855                    out.push(name);
856                }
857            }
858            // Skip integral_type (int, long, etc.) and floating_point_type
859            "integral_type" | "floating_point_type" | "boolean_type" | "void_type" => {}
860            _ => {
861                let mut c = node.walk();
862                for child in node.named_children(&mut c) {
863                    self.walk_type_names(child, out);
864                }
865            }
866        }
867    }
868
869    // ── Call collection ───────────────────────────────────────────────────────
870
871    fn collect_calls(&mut self, node: TsNode<'_>, caller_id: &NodeId) {
872        let mut cursor = node.walk();
873        let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
874        for child in children {
875            match child.kind() {
876                "method_invocation" | "object_creation_expression" => {
877                    if let Some(callee) = self.callee_name(child) {
878                        let line = child.start_position().row as u32 + 1;
879                        self.record_call(caller_id.clone(), callee, line);
880                    }
881                    // Recurse into arguments
882                    if let Some(args) = child.child_by_field_name("arguments") {
883                        self.collect_calls(args, caller_id);
884                    }
885                }
886                _ => self.collect_calls(child, caller_id),
887            }
888        }
889    }
890
891    fn callee_name(&self, call_expr: TsNode<'_>) -> Option<String> {
892        // method_invocation: object?.method(args) → name field is the method name
893        // object_creation_expression: new Type(args) → type field
894        match call_expr.kind() {
895            "method_invocation" => call_expr
896                .child_by_field_name("name")
897                .map(|n| self.text(n).to_owned()),
898            "object_creation_expression" => {
899                call_expr
900                    .child_by_field_name("type")
901                    .and_then(|t| match t.kind() {
902                        "type_identifier" => Some(self.text(t).to_owned()),
903                        "generic_type" => t
904                            .child_by_field_name("name")
905                            .map(|n| self.text(n).to_owned()),
906                        _ => None,
907                    })
908            }
909            _ => None,
910        }
911    }
912
913    fn record_call(&mut self, caller_id: NodeId, callee_name: String, line: u32) {
914        if callee_name.is_empty() {
915            return;
916        }
917        if let Some(callee_id) = self.fn_index.get(&callee_name).cloned() {
918            let edge = Edge::call(caller_id, callee_id, line);
919            if !self.edges.contains(&edge) {
920                self.edges.push(edge);
921            }
922        } else if !self
923            .deferred_calls
924            .iter()
925            .any(|(c, n, _)| c == &caller_id && n == &callee_name)
926        {
927            self.deferred_calls.push((caller_id, callee_name, line));
928        }
929    }
930}
931
932/// Returns true for Java primitive types and common standard library types.
933fn is_builtin_java_type(name: &str) -> bool {
934    matches!(
935        name,
936        "String"
937            | "Object"
938            | "Integer"
939            | "Long"
940            | "Double"
941            | "Float"
942            | "Boolean"
943            | "Byte"
944            | "Short"
945            | "Character"
946            | "Number"
947            | "Math"
948            | "System"
949            | "StringBuilder"
950            | "StringBuffer"
951            | "Comparable"
952            | "Serializable"
953            | "Cloneable"
954            | "Iterable"
955            | "Iterator"
956            | "Collection"
957            | "List"
958            | "Set"
959            | "Map"
960            | "Queue"
961            | "Deque"
962            | "Stack"
963            | "ArrayList"
964            | "HashMap"
965            | "HashSet"
966            | "LinkedList"
967            | "Optional"
968            | "Stream"
969            | "Collectors"
970            | "Arrays"
971            | "Collections"
972            | "Enum"
973            | "Throwable"
974            | "Exception"
975            | "RuntimeException"
976            | "Error"
977            | "Override"
978            | "Deprecated"
979            | "SuppressWarnings"
980            | "FunctionalInterface"
981            | "void"
982    )
983}
984
985// ── Tests ─────────────────────────────────────────────────────────────────────
986
987#[cfg(test)]
988mod tests {
989    use super::JavaParser;
990    use crate::parser::LanguageParser;
991    use gitcortex_core::schema::{EdgeKind, NodeKind};
992    use std::path::Path;
993
994    fn parse(
995        src: &str,
996    ) -> (
997        Vec<gitcortex_core::graph::Node>,
998        Vec<gitcortex_core::graph::Edge>,
999    ) {
1000        let r = JavaParser::new()
1001            .parse(Path::new("Test.java"), src)
1002            .unwrap();
1003        (r.nodes, r.edges)
1004    }
1005
1006    #[allow(clippy::type_complexity)]
1007    fn parse_full(
1008        src: &str,
1009    ) -> (
1010        Vec<gitcortex_core::graph::Node>,
1011        Vec<gitcortex_core::graph::Edge>,
1012        Vec<(gitcortex_core::graph::NodeId, String, u32)>,
1013        Vec<(gitcortex_core::graph::NodeId, String)>,
1014        Vec<(gitcortex_core::graph::NodeId, String)>,
1015        Vec<(gitcortex_core::graph::NodeId, String)>,
1016        Vec<(gitcortex_core::graph::NodeId, String)>,
1017        Vec<(gitcortex_core::graph::NodeId, String)>,
1018        Vec<(gitcortex_core::graph::NodeId, String)>,
1019    ) {
1020        let r = JavaParser::new()
1021            .parse(Path::new("Test.java"), src)
1022            .unwrap();
1023        (
1024            r.nodes,
1025            r.edges,
1026            r.deferred_calls,
1027            r.deferred_uses,
1028            r.deferred_implements,
1029            r.deferred_imports,
1030            r.deferred_inherits,
1031            r.deferred_throws,
1032            r.deferred_annotated,
1033        )
1034    }
1035
1036    #[test]
1037    fn parses_class_and_method() {
1038        let src = "public class Greeter { public String greet(String name) { return name; } }";
1039        let (nodes, edges) = parse(src);
1040        let classes: Vec<_> = nodes
1041            .iter()
1042            .filter(|n| n.kind == NodeKind::Struct)
1043            .collect();
1044        let methods: Vec<_> = nodes
1045            .iter()
1046            .filter(|n| n.kind == NodeKind::Method)
1047            .collect();
1048        assert_eq!(classes.len(), 1, "expected 1 class");
1049        assert_eq!(classes[0].name, "Greeter");
1050        assert_eq!(methods.len(), 1, "expected 1 method");
1051        let contains: Vec<_> = edges
1052            .iter()
1053            .filter(|e| e.kind == EdgeKind::Contains)
1054            .collect();
1055        assert!(!contains.is_empty(), "expected Contains edge");
1056    }
1057
1058    #[test]
1059    fn parses_interface() {
1060        let src = "public interface Greeter { String greet(String name); }";
1061        let (nodes, _) = parse(src);
1062        let interfaces: Vec<_> = nodes
1063            .iter()
1064            .filter(|n| n.kind == NodeKind::Interface || n.kind == NodeKind::Trait)
1065            .collect();
1066        assert_eq!(interfaces.len(), 1);
1067        assert_eq!(interfaces[0].name, "Greeter");
1068    }
1069
1070    #[test]
1071    fn parses_enum() {
1072        let src = "public enum Direction { NORTH, SOUTH, EAST, WEST }";
1073        let (nodes, _) = parse(src);
1074        let enums: Vec<_> = nodes.iter().filter(|n| n.kind == NodeKind::Enum).collect();
1075        assert_eq!(enums.len(), 1);
1076        assert_eq!(enums[0].name, "Direction");
1077    }
1078
1079    #[test]
1080    fn detects_extends_and_implements() {
1081        let src = "interface Base {}\nclass Child extends Base implements Base {}";
1082        let (_, _, _, _, implements, _, inherits, ..) = parse_full(src);
1083        let impl_edges: Vec<_> = implements.iter().filter(|(_, n)| n == "Base").collect();
1084        let inh_edges: Vec<_> = inherits.iter().filter(|(_, n)| n == "Base").collect();
1085        assert!(
1086            impl_edges.len() + inh_edges.len() >= 2,
1087            "expected extends+implements edges to Base, implements={implements:?} inherits={inherits:?}"
1088        );
1089    }
1090
1091    #[test]
1092    fn detects_type_annotation_uses() {
1093        let src = "class Service {}\nclass Controller {\n    public Service handle(Service svc) { return svc; }\n}";
1094        let (_, _, _, uses, ..) = parse_full(src);
1095        let svc_uses: Vec<_> = uses.iter().filter(|(_, n)| n == "Service").collect();
1096        assert!(
1097            svc_uses.len() >= 2,
1098            "expected Uses edges to Service (param + return), got: {uses:?}"
1099        );
1100    }
1101
1102    #[test]
1103    fn detects_import_declaration() {
1104        let src = "import com.example.MyService;\nimport java.util.List;\npublic class App {}";
1105        let (_, _, _, _, _, imports, ..) = parse_full(src);
1106        assert!(
1107            imports.iter().any(|(_, n)| n == "MyService"),
1108            "expected import 'MyService', got: {imports:?}"
1109        );
1110    }
1111
1112    #[test]
1113    fn module_node_is_emitted() {
1114        let src = "public class App {}";
1115        let (nodes, _) = parse(src);
1116        let modules: Vec<_> = nodes
1117            .iter()
1118            .filter(|n| n.kind == NodeKind::Module)
1119            .collect();
1120        assert_eq!(modules.len(), 1);
1121        assert_eq!(modules[0].name, "Test"); // from "Test.java"
1122    }
1123
1124    // ── Track C: annotations (Increment 1) ──────────────────────────────────
1125
1126    #[test]
1127    fn detects_method_annotation() {
1128        let src = "class Base { void greet() {} }\nclass Child extends Base {\n    @Override\n    void greet() {}\n}";
1129        let (_, _, _, _, _, _, _, _, annotated) = parse_full(src);
1130        assert!(
1131            annotated.iter().any(|(_, n)| n == "Override"),
1132            "expected @Override annotation captured on method, got: {annotated:?}"
1133        );
1134    }
1135
1136    #[test]
1137    fn detects_class_level_annotation() {
1138        let src = "@Deprecated\nclass Legacy {}";
1139        let (_, _, _, _, _, _, _, _, annotated) = parse_full(src);
1140        assert!(
1141            annotated.iter().any(|(_, n)| n == "Deprecated"),
1142            "expected @Deprecated annotation captured on class, got: {annotated:?}"
1143        );
1144    }
1145
1146    #[test]
1147    fn detects_enum_level_annotation() {
1148        let src = "@Deprecated\npublic enum Direction { NORTH, SOUTH }";
1149        let (_, _, _, _, _, _, _, _, annotated) = parse_full(src);
1150        assert!(
1151            annotated.iter().any(|(_, n)| n == "Deprecated"),
1152            "expected @Deprecated annotation captured on enum, got: {annotated:?}"
1153        );
1154    }
1155
1156    #[test]
1157    fn detects_record_level_annotation() {
1158        let src = "@Deprecated\npublic record Point(int x, int y) {}";
1159        let (_, _, _, _, _, _, _, _, annotated) = parse_full(src);
1160        assert!(
1161            annotated.iter().any(|(_, n)| n == "Deprecated"),
1162            "expected @Deprecated annotation captured on record, got: {annotated:?}"
1163        );
1164    }
1165
1166    // ── Track C: static final fields as Defs (Increment 2) ─────────────────
1167
1168    #[test]
1169    fn static_final_field_becomes_constant_node() {
1170        let src = "class Config {\n    public static final int MAX_RETRIES = 3;\n    private int instanceCounter;\n}";
1171        let (nodes, edges) = parse(src);
1172        let consts: Vec<_> = nodes
1173            .iter()
1174            .filter(|n| n.kind == NodeKind::Constant)
1175            .collect();
1176        assert_eq!(consts.len(), 1, "expected 1 constant node, got: {consts:?}");
1177        assert_eq!(consts[0].name, "MAX_RETRIES");
1178        assert!(
1179            consts[0].metadata.is_static && consts[0].metadata.is_final,
1180            "expected MAX_RETRIES to be marked static+final"
1181        );
1182
1183        let contains_to_const: Vec<_> = edges
1184            .iter()
1185            .filter(|e| e.kind == EdgeKind::Contains && e.dst == consts[0].id)
1186            .collect();
1187        assert_eq!(
1188            contains_to_const.len(),
1189            1,
1190            "expected a Contains edge from the class to the constant"
1191        );
1192
1193        // Plain instance fields still get no Def node — unchanged scope.
1194        let non_const_field_nodes = nodes.iter().filter(|n| n.name == "instanceCounter").count();
1195        assert_eq!(non_const_field_nodes, 0);
1196    }
1197
1198    #[test]
1199    fn static_final_field_multi_declarator() {
1200        let src = "class Config {\n    static final int A = 1, B = 2;\n}";
1201        let (nodes, _) = parse(src);
1202        let names: Vec<&str> = nodes
1203            .iter()
1204            .filter(|n| n.kind == NodeKind::Constant)
1205            .map(|n| n.name.as_str())
1206            .collect();
1207        assert_eq!(
1208            names,
1209            vec!["A", "B"],
1210            "expected both declarators as constants: {names:?}"
1211        );
1212    }
1213
1214    #[test]
1215    fn static_final_field_annotation_captured() {
1216        let src =
1217            "class Config {\n    @Deprecated\n    public static final int MAX_RETRIES = 3;\n}";
1218        let (_, _, _, _, _, _, _, _, annotated) = parse_full(src);
1219        assert!(
1220            annotated.iter().any(|(_, n)| n == "Deprecated"),
1221            "expected @Deprecated annotation captured on static final field, got: {annotated:?}"
1222        );
1223    }
1224}