Skip to main content

infiniloom_engine/parser/
extraction.rs

1//! Symbol extraction utilities for parsing
2//!
3//! This module contains standalone functions for extracting metadata from AST nodes:
4//! - Signatures
5//! - Docstrings
6//! - Visibility modifiers
7//! - Function calls
8//! - Inheritance relationships
9
10use super::language::Language;
11use crate::types::{SymbolKind, Visibility};
12use std::collections::HashSet;
13use tree_sitter::Node;
14
15/// Find a safe character boundary at or before the given byte index.
16/// This prevents panics when slicing strings with multi-byte UTF-8 characters.
17fn safe_char_boundary(s: &str, mut index: usize) -> usize {
18    if index >= s.len() {
19        return s.len();
20    }
21    // Walk backwards to find a valid char boundary
22    while index > 0 && !s.is_char_boundary(index) {
23        index -= 1;
24    }
25    index
26}
27
28/// Extract function/method signature
29pub fn extract_signature(node: Node<'_>, source_code: &str, language: Language) -> Option<String> {
30    let sig_node = match language {
31        Language::Python => {
32            if node.kind() == "function_definition" {
33                let start = node.start_byte();
34                let mut end = start;
35                for byte in &source_code.as_bytes()[start..] {
36                    end += 1;
37                    if *byte == b':' || *byte == b'\n' {
38                        break;
39                    }
40                }
41                // SAFETY: Ensure we slice at valid UTF-8 char boundaries
42                let safe_start = safe_char_boundary(source_code, start);
43                let safe_end = safe_char_boundary(source_code, end);
44                return Some(
45                    source_code[safe_start..safe_end]
46                        .trim()
47                        .to_owned()
48                        .replace('\n', " "),
49                );
50            }
51            None
52        },
53        Language::JavaScript | Language::TypeScript => {
54            if node.kind().contains("function") || node.kind().contains("method") {
55                let start = node.start_byte();
56                let mut end = start;
57                let mut brace_count = 0;
58                for byte in &source_code.as_bytes()[start..] {
59                    if *byte == b'{' {
60                        brace_count += 1;
61                        if brace_count == 1 {
62                            break;
63                        }
64                    }
65                    end += 1;
66                }
67                // SAFETY: Ensure we slice at valid UTF-8 char boundaries
68                let safe_start = safe_char_boundary(source_code, start);
69                let safe_end = safe_char_boundary(source_code, end);
70                return Some(
71                    source_code[safe_start..safe_end]
72                        .trim()
73                        .to_owned()
74                        .replace('\n', " "),
75                );
76            }
77            None
78        },
79        Language::Rust => {
80            if node.kind() == "function_item" {
81                for child in node.children(&mut node.walk()) {
82                    if child.kind() == "block" {
83                        let start = node.start_byte();
84                        let end = child.start_byte();
85                        return Some(source_code[start..end].trim().to_owned().replace('\n', " "));
86                    }
87                }
88            }
89            None
90        },
91        Language::Go => {
92            if node.kind() == "function_declaration" || node.kind() == "method_declaration" {
93                for child in node.children(&mut node.walk()) {
94                    if child.kind() == "block" {
95                        let start = node.start_byte();
96                        let end = child.start_byte();
97                        return Some(source_code[start..end].trim().to_owned().replace('\n', " "));
98                    }
99                }
100            }
101            None
102        },
103        Language::Java => {
104            if node.kind() == "method_declaration" {
105                for child in node.children(&mut node.walk()) {
106                    if child.kind() == "block" {
107                        let start = node.start_byte();
108                        let end = child.start_byte();
109                        return Some(source_code[start..end].trim().to_owned().replace('\n', " "));
110                    }
111                }
112            }
113            None
114        },
115        Language::C
116        | Language::Cpp
117        | Language::CSharp
118        | Language::Php
119        | Language::Kotlin
120        | Language::Swift
121        | Language::Scala
122        | Language::Dart => {
123            for child in node.children(&mut node.walk()) {
124                if child.kind() == "block"
125                    || child.kind() == "compound_statement"
126                    || child.kind() == "function_body"
127                {
128                    let start = node.start_byte();
129                    let end = child.start_byte();
130                    return Some(source_code[start..end].trim().to_owned().replace('\n', " "));
131                }
132            }
133            None
134        },
135        Language::Ruby | Language::Lua => {
136            let start = node.start_byte();
137            let mut end = start;
138            for byte in &source_code.as_bytes()[start..] {
139                end += 1;
140                if *byte == b'\n' {
141                    break;
142                }
143            }
144            Some(source_code[start..end].trim().to_owned())
145        },
146        Language::Bash => {
147            let start = node.start_byte();
148            let mut end = start;
149            for byte in &source_code.as_bytes()[start..] {
150                if *byte == b'{' {
151                    break;
152                }
153                end += 1;
154            }
155            Some(source_code[start..end].trim().to_owned())
156        },
157        Language::Haskell
158        | Language::OCaml
159        | Language::FSharp
160        | Language::Elixir
161        | Language::Clojure
162        | Language::R
163        | Language::Hcl
164        | Language::Zig => {
165            let start = node.start_byte();
166            let mut end = start;
167            for byte in &source_code.as_bytes()[start..] {
168                end += 1;
169                if *byte == b'\n' || *byte == b'=' {
170                    break;
171                }
172            }
173            Some(source_code[start..end].trim().to_owned())
174        },
175    };
176
177    sig_node.or_else(|| {
178        let start = node.start_byte();
179        let end = std::cmp::min(start + 200, source_code.len());
180        // Ensure we slice at valid UTF-8 character boundaries
181        let safe_start = safe_char_boundary(source_code, start);
182        let safe_end = safe_char_boundary(source_code, end);
183        if safe_start >= safe_end {
184            return None;
185        }
186        let text = &source_code[safe_start..safe_end];
187        text.lines().next().map(|s| s.trim().to_owned())
188    })
189}
190
191/// Extract docstring/documentation comment
192pub fn extract_docstring(node: Node<'_>, source_code: &str, language: Language) -> Option<String> {
193    match language {
194        Language::Python => {
195            let mut cursor = node.walk();
196            for child in node.children(&mut cursor) {
197                if child.kind() == "block" {
198                    for stmt in child.children(&mut child.walk()) {
199                        if stmt.kind() == "expression_statement" {
200                            for expr in stmt.children(&mut stmt.walk()) {
201                                if expr.kind() == "string" {
202                                    if let Ok(text) = expr.utf8_text(source_code.as_bytes()) {
203                                        return Some(
204                                            text.trim_matches(|c| c == '"' || c == '\'')
205                                                .trim()
206                                                .to_owned(),
207                                        );
208                                    }
209                                }
210                            }
211                        }
212                    }
213                }
214            }
215            None
216        },
217        Language::JavaScript | Language::TypeScript => {
218            if let Some(prev_sibling) = node.prev_sibling() {
219                if prev_sibling.kind() == "comment" {
220                    if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
221                        if text.starts_with("/**") {
222                            return Some(clean_jsdoc(text));
223                        }
224                    }
225                }
226            }
227            None
228        },
229        Language::Rust => {
230            let start_byte = node.start_byte();
231            // SAFETY: Use floor_char_boundary to avoid panics on multi-byte UTF-8 characters
232            let safe_boundary = source_code.floor_char_boundary(start_byte);
233            let lines_before: Vec<_> = source_code[..safe_boundary]
234                .lines()
235                .rev()
236                .take_while(|line| line.trim().starts_with("///") || line.trim().is_empty())
237                .collect();
238
239            if !lines_before.is_empty() {
240                let doc: Vec<String> = lines_before
241                    .into_iter()
242                    .rev()
243                    .filter_map(|line| {
244                        let trimmed = line.trim();
245                        trimmed.strip_prefix("///").map(|s| s.trim().to_owned())
246                    })
247                    .collect();
248
249                if !doc.is_empty() {
250                    return Some(doc.join(" "));
251                }
252            }
253            None
254        },
255        Language::Go => {
256            if let Some(prev_sibling) = node.prev_sibling() {
257                if prev_sibling.kind() == "comment" {
258                    if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
259                        return Some(text.trim_start_matches("//").trim().to_owned());
260                    }
261                }
262            }
263            None
264        },
265        Language::Java => {
266            if let Some(prev_sibling) = node.prev_sibling() {
267                if prev_sibling.kind() == "block_comment" {
268                    if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
269                        if text.starts_with("/**") {
270                            return Some(clean_javadoc(text));
271                        }
272                    }
273                }
274            }
275            None
276        },
277        Language::C | Language::Cpp => {
278            if let Some(prev_sibling) = node.prev_sibling() {
279                if prev_sibling.kind() == "comment" {
280                    if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
281                        if text.starts_with("/**") || text.starts_with("/*") {
282                            return Some(clean_jsdoc(text));
283                        }
284                        return Some(text.trim_start_matches("//").trim().to_owned());
285                    }
286                }
287            }
288            None
289        },
290        Language::CSharp => {
291            let start_byte = node.start_byte();
292            // SAFETY: Use floor_char_boundary to avoid panics on multi-byte UTF-8 characters
293            let safe_boundary = source_code.floor_char_boundary(start_byte);
294            let lines_before: Vec<_> = source_code[..safe_boundary]
295                .lines()
296                .rev()
297                .take_while(|line| line.trim().starts_with("///") || line.trim().is_empty())
298                .collect();
299
300            if !lines_before.is_empty() {
301                let doc: Vec<String> = lines_before
302                    .into_iter()
303                    .rev()
304                    .filter_map(|line| {
305                        let trimmed = line.trim();
306                        trimmed.strip_prefix("///").map(|s| s.trim().to_owned())
307                    })
308                    .collect();
309
310                if !doc.is_empty() {
311                    return Some(doc.join(" "));
312                }
313            }
314            None
315        },
316        Language::Ruby => {
317            if let Some(prev_sibling) = node.prev_sibling() {
318                if prev_sibling.kind() == "comment" {
319                    if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
320                        return Some(text.trim_start_matches('#').trim().to_owned());
321                    }
322                }
323            }
324            None
325        },
326        Language::Php | Language::Kotlin | Language::Swift | Language::Scala => {
327            if let Some(prev_sibling) = node.prev_sibling() {
328                let kind = prev_sibling.kind();
329                if kind == "comment" || kind == "multiline_comment" || kind == "block_comment" {
330                    if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
331                        if text.starts_with("/**") {
332                            return Some(clean_jsdoc(text));
333                        }
334                    }
335                }
336            }
337            None
338        },
339        Language::Bash => {
340            if let Some(prev_sibling) = node.prev_sibling() {
341                if prev_sibling.kind() == "comment" {
342                    if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
343                        return Some(text.trim_start_matches('#').trim().to_owned());
344                    }
345                }
346            }
347            None
348        },
349        Language::Haskell => {
350            if let Some(prev_sibling) = node.prev_sibling() {
351                if prev_sibling.kind() == "comment" {
352                    if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
353                        let cleaned = text
354                            .trim_start_matches("{-")
355                            .trim_end_matches("-}")
356                            .trim_start_matches("--")
357                            .trim();
358                        return Some(cleaned.to_owned());
359                    }
360                }
361            }
362            None
363        },
364        Language::Elixir => {
365            if let Some(prev_sibling) = node.prev_sibling() {
366                if prev_sibling.kind() == "comment" {
367                    if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
368                        return Some(text.trim_start_matches('#').trim().to_owned());
369                    }
370                }
371            }
372            None
373        },
374        Language::Clojure => None,
375        Language::OCaml | Language::FSharp => {
376            if let Some(prev_sibling) = node.prev_sibling() {
377                if prev_sibling.kind() == "comment" {
378                    if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
379                        let cleaned = text
380                            .trim_start_matches("(**")
381                            .trim_start_matches("(*")
382                            .trim_end_matches("*)")
383                            .trim();
384                        return Some(cleaned.to_owned());
385                    }
386                }
387            }
388            None
389        },
390        Language::Lua => {
391            if let Some(prev_sibling) = node.prev_sibling() {
392                if prev_sibling.kind() == "comment" {
393                    if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
394                        let cleaned = text
395                            .trim_start_matches("--[[")
396                            .trim_end_matches("]]")
397                            .trim_start_matches("--")
398                            .trim();
399                        return Some(cleaned.to_owned());
400                    }
401                }
402            }
403            None
404        },
405        Language::R => {
406            if let Some(prev_sibling) = node.prev_sibling() {
407                if prev_sibling.kind() == "comment" {
408                    if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
409                        return Some(text.trim_start_matches('#').trim().to_owned());
410                    }
411                }
412            }
413            None
414        },
415        Language::Hcl => None,
416        // Zig and Dart both use /// doc comments (same style as Rust)
417        Language::Zig | Language::Dart => {
418            let start_byte = node.start_byte();
419            let safe_boundary = source_code.floor_char_boundary(start_byte);
420            let lines_before: Vec<_> = source_code[..safe_boundary]
421                .lines()
422                .rev()
423                .take_while(|line| line.trim().starts_with("///") || line.trim().is_empty())
424                .collect();
425
426            if !lines_before.is_empty() {
427                let doc: Vec<String> = lines_before
428                    .into_iter()
429                    .rev()
430                    .filter_map(|line| {
431                        let trimmed = line.trim();
432                        trimmed.strip_prefix("///").map(|s| s.trim().to_owned())
433                    })
434                    .collect();
435
436                if !doc.is_empty() {
437                    return Some(doc.join(" "));
438                }
439            }
440            None
441        },
442    }
443}
444
445/// Extract parent class/struct name for methods
446pub fn extract_parent(node: Node<'_>, source_code: &str) -> Option<String> {
447    let mut current = node.parent()?;
448
449    while let Some(parent) = current.parent() {
450        if ["class_definition", "class_declaration", "struct_item", "impl_item"]
451            .contains(&parent.kind())
452        {
453            for child in parent.children(&mut parent.walk()) {
454                if child.kind() == "identifier" || child.kind() == "type_identifier" {
455                    if let Ok(name) = child.utf8_text(source_code.as_bytes()) {
456                        return Some(name.to_owned());
457                    }
458                }
459            }
460        }
461        current = parent;
462    }
463
464    None
465}
466
467/// Extract visibility modifier from a node
468pub fn extract_visibility(node: Node<'_>, source_code: &str, language: Language) -> Visibility {
469    match language {
470        Language::Python => {
471            if let Some(name_node) = node.child_by_field_name("name") {
472                if let Ok(name) = name_node.utf8_text(source_code.as_bytes()) {
473                    if name.starts_with("__") && !name.ends_with("__") {
474                        return Visibility::Private;
475                    } else if name.starts_with('_') {
476                        return Visibility::Protected;
477                    }
478                }
479            }
480            Visibility::Public
481        },
482        Language::Rust => {
483            for child in node.children(&mut node.walk()) {
484                if child.kind() == "visibility_modifier" {
485                    if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
486                        if text.contains("pub(crate)") || text.contains("pub(super)") {
487                            return Visibility::Internal;
488                        } else if text.starts_with("pub") {
489                            return Visibility::Public;
490                        }
491                    }
492                }
493            }
494            Visibility::Private
495        },
496        Language::JavaScript | Language::TypeScript => {
497            for child in node.children(&mut node.walk()) {
498                let kind = child.kind();
499                if kind == "private" || kind == "accessibility_modifier" {
500                    if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
501                        return match text {
502                            "private" => Visibility::Private,
503                            "protected" => Visibility::Protected,
504                            _ => Visibility::Public,
505                        };
506                    }
507                }
508            }
509            if let Some(name_node) = node.child_by_field_name("name") {
510                if let Ok(name) = name_node.utf8_text(source_code.as_bytes()) {
511                    if name.starts_with('#') {
512                        return Visibility::Private;
513                    }
514                }
515            }
516            Visibility::Public
517        },
518        Language::Go => {
519            if let Some(name_node) = node.child_by_field_name("name") {
520                if let Ok(name) = name_node.utf8_text(source_code.as_bytes()) {
521                    if let Some(first_char) = name.chars().next() {
522                        if first_char.is_lowercase() {
523                            return Visibility::Private;
524                        }
525                    }
526                }
527            }
528            Visibility::Public
529        },
530        Language::Java => {
531            for child in node.children(&mut node.walk()) {
532                if child.kind() == "modifiers" {
533                    if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
534                        if text.contains("private") {
535                            return Visibility::Private;
536                        } else if text.contains("protected") {
537                            return Visibility::Protected;
538                        } else if text.contains("public") {
539                            return Visibility::Public;
540                        }
541                    }
542                }
543            }
544            Visibility::Internal
545        },
546        Language::C | Language::Cpp => {
547            for child in node.children(&mut node.walk()) {
548                if child.kind() == "storage_class_specifier" {
549                    if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
550                        if text == "static" {
551                            return Visibility::Private;
552                        }
553                    }
554                }
555            }
556            Visibility::Public
557        },
558        Language::CSharp | Language::Kotlin | Language::Swift | Language::Scala => {
559            for child in node.children(&mut node.walk()) {
560                let kind = child.kind();
561                if kind == "modifier" || kind == "modifiers" || kind == "visibility_modifier" {
562                    if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
563                        if text.contains("private") {
564                            return Visibility::Private;
565                        } else if text.contains("protected") {
566                            return Visibility::Protected;
567                        } else if text.contains("internal") {
568                            return Visibility::Internal;
569                        } else if text.contains("public") {
570                            return Visibility::Public;
571                        }
572                    }
573                }
574            }
575            Visibility::Internal
576        },
577        Language::Ruby => {
578            if let Some(name_node) = node.child_by_field_name("name") {
579                if let Ok(name) = name_node.utf8_text(source_code.as_bytes()) {
580                    if name.starts_with('_') {
581                        return Visibility::Private;
582                    }
583                }
584            }
585            Visibility::Public
586        },
587        Language::Php => {
588            for child in node.children(&mut node.walk()) {
589                if child.kind() == "visibility_modifier" {
590                    if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
591                        return match text {
592                            "private" => Visibility::Private,
593                            "protected" => Visibility::Protected,
594                            "public" => Visibility::Public,
595                            _ => Visibility::Public,
596                        };
597                    }
598                }
599            }
600            Visibility::Public
601        },
602        Language::Bash => Visibility::Public,
603        Language::Haskell
604        | Language::Elixir
605        | Language::Clojure
606        | Language::OCaml
607        | Language::FSharp
608        | Language::Lua
609        | Language::R
610        | Language::Hcl
611        | Language::Zig => Visibility::Public,
612        Language::Dart => {
613            // Dart uses _ prefix for private members
614            if let Some(name_node) = node.child_by_field_name("name") {
615                if let Ok(name) = name_node.utf8_text(source_code.as_bytes()) {
616                    if name.starts_with('_') {
617                        return Visibility::Private;
618                    }
619                }
620            }
621            Visibility::Public
622        },
623    }
624}
625
626/// Extract function calls from a function/method body
627pub fn extract_calls(node: Node<'_>, source_code: &str, language: Language) -> Vec<String> {
628    let mut calls = HashSet::new();
629
630    let body_node = find_body_node(node, language);
631    if let Some(body) = body_node {
632        collect_calls_recursive(body, source_code, language, &mut calls);
633    }
634
635    if calls.is_empty() {
636        collect_calls_recursive(node, source_code, language, &mut calls);
637    }
638
639    calls.into_iter().collect()
640}
641
642/// Find the body node of a function/method
643pub fn find_body_node(node: Node<'_>, language: Language) -> Option<Node<'_>> {
644    match language {
645        Language::Python => {
646            for child in node.children(&mut node.walk()) {
647                if child.kind() == "block" {
648                    return Some(child);
649                }
650            }
651        },
652        Language::Rust => {
653            for child in node.children(&mut node.walk()) {
654                if child.kind() == "block" {
655                    return Some(child);
656                }
657            }
658        },
659        Language::JavaScript | Language::TypeScript => {
660            for child in node.children(&mut node.walk()) {
661                let kind = child.kind();
662                if kind == "statement_block" {
663                    return Some(child);
664                }
665                if kind == "arrow_function" {
666                    if let Some(body) = find_body_node(child, language) {
667                        return Some(body);
668                    }
669                    return Some(child);
670                }
671            }
672            if node.kind() == "arrow_function" {
673                for child in node.children(&mut node.walk()) {
674                    let kind = child.kind();
675                    if kind != "formal_parameters"
676                        && kind != "identifier"
677                        && kind != "=>"
678                        && kind != "("
679                        && kind != ")"
680                        && kind != ","
681                    {
682                        return Some(child);
683                    }
684                }
685                return Some(node);
686            }
687        },
688        Language::Go => {
689            for child in node.children(&mut node.walk()) {
690                if child.kind() == "block" {
691                    return Some(child);
692                }
693            }
694        },
695        Language::Java => {
696            for child in node.children(&mut node.walk()) {
697                if child.kind() == "block" {
698                    return Some(child);
699                }
700            }
701        },
702        Language::C | Language::Cpp => {
703            for child in node.children(&mut node.walk()) {
704                if child.kind() == "compound_statement" {
705                    return Some(child);
706                }
707            }
708        },
709        Language::CSharp | Language::Php | Language::Kotlin | Language::Swift | Language::Scala => {
710            for child in node.children(&mut node.walk()) {
711                let kind = child.kind();
712                if kind == "block" || kind == "compound_statement" || kind == "function_body" {
713                    return Some(child);
714                }
715            }
716        },
717        Language::Ruby => {
718            for child in node.children(&mut node.walk()) {
719                if child.kind() == "body_statement" || child.kind() == "do_block" {
720                    return Some(child);
721                }
722            }
723        },
724        Language::Bash => {
725            for child in node.children(&mut node.walk()) {
726                if child.kind() == "compound_statement" {
727                    return Some(child);
728                }
729            }
730        },
731        Language::Haskell
732        | Language::Elixir
733        | Language::Clojure
734        | Language::OCaml
735        | Language::FSharp
736        | Language::R
737        | Language::Hcl
738        | Language::Zig
739        | Language::Dart => {
740            return Some(node);
741        },
742        Language::Lua => {
743            for child in node.children(&mut node.walk()) {
744                if child.kind() == "block" {
745                    return Some(child);
746                }
747            }
748        },
749    }
750    None
751}
752
753/// Maximum recursion depth for AST traversal to prevent stack overflow
754/// on deeply nested or malformed code (e.g., 75K+ nodes).
755const MAX_RECURSION_DEPTH: usize = 1000;
756
757/// Recursively collect function calls from a node
758///
759/// Uses a depth limit to prevent stack overflow on deeply nested code.
760pub fn collect_calls_recursive(
761    node: Node<'_>,
762    source_code: &str,
763    language: Language,
764    calls: &mut HashSet<String>,
765) {
766    collect_calls_recursive_with_depth(node, source_code, language, calls, 0);
767}
768
769/// Internal recursive function with depth tracking
770fn collect_calls_recursive_with_depth(
771    node: Node<'_>,
772    source_code: &str,
773    language: Language,
774    calls: &mut HashSet<String>,
775    depth: usize,
776) {
777    // Prevent stack overflow on deeply nested code
778    if depth >= MAX_RECURSION_DEPTH {
779        return;
780    }
781
782    let kind = node.kind();
783
784    let call_name = match language {
785        Language::Python => {
786            if kind == "call" {
787                node.child_by_field_name("function").and_then(|f| {
788                    if f.kind() == "identifier" {
789                        f.utf8_text(source_code.as_bytes()).ok().map(String::from)
790                    } else if f.kind() == "attribute" {
791                        f.child_by_field_name("attribute")
792                            .and_then(|a| a.utf8_text(source_code.as_bytes()).ok())
793                            .map(String::from)
794                    } else {
795                        None
796                    }
797                })
798            } else {
799                None
800            }
801        },
802        Language::Rust => {
803            if kind == "call_expression" {
804                node.child_by_field_name("function").and_then(|f| {
805                    if f.kind() == "identifier" {
806                        f.utf8_text(source_code.as_bytes()).ok().map(String::from)
807                    } else if f.kind() == "field_expression" {
808                        f.child_by_field_name("field")
809                            .and_then(|a| a.utf8_text(source_code.as_bytes()).ok())
810                            .map(String::from)
811                    } else if f.kind() == "scoped_identifier" {
812                        f.utf8_text(source_code.as_bytes()).ok().map(String::from)
813                    } else {
814                        None
815                    }
816                })
817            } else if kind == "macro_invocation" {
818                node.child_by_field_name("macro")
819                    .and_then(|m| m.utf8_text(source_code.as_bytes()).ok())
820                    .map(|s| format!("{}!", s))
821            } else {
822                None
823            }
824        },
825        Language::JavaScript | Language::TypeScript => {
826            if kind == "call_expression" {
827                node.child_by_field_name("function").and_then(|f| {
828                    if f.kind() == "identifier" {
829                        f.utf8_text(source_code.as_bytes()).ok().map(String::from)
830                    } else if f.kind() == "member_expression" {
831                        f.child_by_field_name("property")
832                            .and_then(|p| p.utf8_text(source_code.as_bytes()).ok())
833                            .map(String::from)
834                    } else {
835                        None
836                    }
837                })
838            } else {
839                None
840            }
841        },
842        Language::Go => {
843            if kind == "call_expression" {
844                node.child_by_field_name("function").and_then(|f| {
845                    if f.kind() == "identifier" {
846                        f.utf8_text(source_code.as_bytes()).ok().map(String::from)
847                    } else if f.kind() == "selector_expression" {
848                        f.child_by_field_name("field")
849                            .and_then(|a| a.utf8_text(source_code.as_bytes()).ok())
850                            .map(String::from)
851                    } else {
852                        None
853                    }
854                })
855            } else {
856                None
857            }
858        },
859        Language::Java => {
860            if kind == "method_invocation" {
861                node.child_by_field_name("name")
862                    .and_then(|n| n.utf8_text(source_code.as_bytes()).ok())
863                    .map(String::from)
864            } else {
865                None
866            }
867        },
868        Language::C | Language::Cpp => {
869            if kind == "call_expression" {
870                node.child_by_field_name("function").and_then(|f| {
871                    if f.kind() == "identifier" {
872                        f.utf8_text(source_code.as_bytes()).ok().map(String::from)
873                    } else if f.kind() == "field_expression" {
874                        f.child_by_field_name("field")
875                            .and_then(|a| a.utf8_text(source_code.as_bytes()).ok())
876                            .map(String::from)
877                    } else {
878                        None
879                    }
880                })
881            } else {
882                None
883            }
884        },
885        Language::CSharp | Language::Php | Language::Kotlin | Language::Swift | Language::Scala => {
886            if kind == "invocation_expression" || kind == "call_expression" {
887                node.children(&mut node.walk())
888                    .find(|child| child.kind() == "identifier" || child.kind() == "simple_name")
889                    .and_then(|child| child.utf8_text(source_code.as_bytes()).ok())
890                    .map(|s| s.to_owned())
891            } else {
892                None
893            }
894        },
895        Language::Ruby => {
896            if kind == "call" || kind == "method_call" {
897                node.child_by_field_name("method")
898                    .and_then(|m| m.utf8_text(source_code.as_bytes()).ok())
899                    .map(String::from)
900            } else {
901                None
902            }
903        },
904        Language::Bash => {
905            if kind == "command" {
906                node.child_by_field_name("name")
907                    .and_then(|n| n.utf8_text(source_code.as_bytes()).ok())
908                    .map(String::from)
909            } else {
910                None
911            }
912        },
913        Language::Zig => {
914            if kind == "call_expression" {
915                node.child_by_field_name("function").and_then(|f| {
916                    if f.kind() == "identifier" {
917                        f.utf8_text(source_code.as_bytes()).ok().map(String::from)
918                    } else if f.kind() == "field_expression" {
919                        // e.g., std.debug.print → extract "print"
920                        f.child_by_field_name("member")
921                            .and_then(|m| m.utf8_text(source_code.as_bytes()).ok())
922                            .map(String::from)
923                    } else {
924                        None
925                    }
926                })
927            } else {
928                None
929            }
930        },
931        Language::Dart => {
932            // Dart AST: calls are identifier + selector(argument_part) at expression_statement level.
933            // e.g., print('hello') → expression_statement(identifier, selector(argument_part))
934            // e.g., obj.method() → expression_statement(identifier, selector(unconditional_assignable_selector), selector(argument_part))
935            if kind == "expression_statement" {
936                let children: Vec<_> = node.children(&mut node.walk()).collect();
937                if children.len() >= 2 && children[0].kind() == "identifier" {
938                    let has_call = children.iter().any(|c| {
939                        c.kind() == "selector"
940                            && c.child(0).map_or(false, |gc| gc.kind() == "argument_part")
941                    });
942                    if has_call {
943                        // Find the last identifier before the argument_part selector
944                        // For obj.method() → "doSomething"; for print() → "print"
945                        let mut name = children[0]
946                            .utf8_text(source_code.as_bytes())
947                            .ok()
948                            .map(String::from);
949                        for child in &children[1..] {
950                            if child.kind() == "selector" {
951                                if let Some(gc) = child.child(0) {
952                                    if gc.kind() == "unconditional_assignable_selector" {
953                                        // Member access: extract the identifier
954                                        name = gc
955                                            .children(&mut gc.walk())
956                                            .find(|c| c.kind() == "identifier")
957                                            .and_then(|id| {
958                                                id.utf8_text(source_code.as_bytes()).ok()
959                                            })
960                                            .map(String::from);
961                                    }
962                                }
963                            }
964                        }
965                        name
966                    } else {
967                        None
968                    }
969                } else {
970                    None
971                }
972            } else {
973                None
974            }
975        },
976        Language::Haskell
977        | Language::Elixir
978        | Language::Clojure
979        | Language::OCaml
980        | Language::FSharp
981        | Language::Lua
982        | Language::R
983        | Language::Hcl => {
984            if kind == "function_call" || kind == "call" || kind == "application" {
985                node.children(&mut node.walk())
986                    .find(|child| child.kind() == "identifier" || child.kind() == "variable")
987                    .and_then(|child| child.utf8_text(source_code.as_bytes()).ok())
988                    .map(|s| s.to_owned())
989            } else {
990                None
991            }
992        },
993    };
994
995    if let Some(name) = call_name {
996        if !is_builtin(&name, language) {
997            calls.insert(name);
998        }
999    }
1000
1001    for child in node.children(&mut node.walk()) {
1002        collect_calls_recursive_with_depth(child, source_code, language, calls, depth + 1);
1003    }
1004}
1005
1006/// Check if a function name is a common built-in
1007pub fn is_builtin(name: &str, language: Language) -> bool {
1008    match language {
1009        Language::Python => {
1010            matches!(
1011                name,
1012                "print"
1013                    | "len"
1014                    | "range"
1015                    | "str"
1016                    | "int"
1017                    | "float"
1018                    | "list"
1019                    | "dict"
1020                    | "set"
1021                    | "tuple"
1022                    | "bool"
1023                    | "type"
1024                    | "isinstance"
1025                    | "hasattr"
1026                    | "getattr"
1027                    | "setattr"
1028                    | "super"
1029                    | "iter"
1030                    | "next"
1031                    | "open"
1032                    | "input"
1033                    | "format"
1034                    | "enumerate"
1035                    | "zip"
1036                    | "map"
1037                    | "filter"
1038                    | "sorted"
1039                    | "reversed"
1040                    | "sum"
1041                    | "min"
1042                    | "max"
1043                    | "abs"
1044                    | "round"
1045                    | "ord"
1046                    | "chr"
1047                    | "hex"
1048                    | "bin"
1049                    | "oct"
1050            )
1051        },
1052        Language::JavaScript | Language::TypeScript => {
1053            matches!(
1054                name,
1055                "console"
1056                    | "log"
1057                    | "error"
1058                    | "warn"
1059                    | "parseInt"
1060                    | "parseFloat"
1061                    | "setTimeout"
1062                    | "setInterval"
1063                    | "clearTimeout"
1064                    | "clearInterval"
1065                    | "JSON"
1066                    | "stringify"
1067                    | "parse"
1068                    | "toString"
1069                    | "valueOf"
1070                    | "push"
1071                    | "pop"
1072                    | "shift"
1073                    | "unshift"
1074                    | "slice"
1075                    | "splice"
1076                    | "map"
1077                    | "filter"
1078                    | "reduce"
1079                    | "forEach"
1080                    | "find"
1081                    | "findIndex"
1082                    | "includes"
1083                    | "indexOf"
1084                    | "join"
1085                    | "split"
1086                    | "replace"
1087            )
1088        },
1089        Language::Rust => {
1090            matches!(
1091                name,
1092                "println!"
1093                    | "print!"
1094                    | "eprintln!"
1095                    | "eprint!"
1096                    | "format!"
1097                    | "vec!"
1098                    | "panic!"
1099                    | "assert!"
1100                    | "assert_eq!"
1101                    | "assert_ne!"
1102                    | "debug!"
1103                    | "info!"
1104                    | "warn!"
1105                    | "error!"
1106                    | "trace!"
1107                    | "unwrap"
1108                    | "expect"
1109                    | "ok"
1110                    | "err"
1111                    | "some"
1112                    | "none"
1113                    | "clone"
1114                    | "to_string"
1115                    | "into"
1116                    | "from"
1117                    | "default"
1118                    | "iter"
1119                    | "into_iter"
1120                    | "collect"
1121                    | "map"
1122                    | "filter"
1123            )
1124        },
1125        Language::Go => {
1126            matches!(
1127                name,
1128                "fmt"
1129                    | "Println"
1130                    | "Printf"
1131                    | "Sprintf"
1132                    | "Errorf"
1133                    | "make"
1134                    | "new"
1135                    | "len"
1136                    | "cap"
1137                    | "append"
1138                    | "copy"
1139                    | "delete"
1140                    | "close"
1141                    | "panic"
1142                    | "recover"
1143                    | "print"
1144            )
1145        },
1146        Language::Java => {
1147            matches!(
1148                name,
1149                "println"
1150                    | "print"
1151                    | "printf"
1152                    | "toString"
1153                    | "equals"
1154                    | "hashCode"
1155                    | "getClass"
1156                    | "clone"
1157                    | "notify"
1158                    | "wait"
1159                    | "get"
1160                    | "set"
1161                    | "add"
1162                    | "remove"
1163                    | "size"
1164                    | "isEmpty"
1165                    | "contains"
1166                    | "iterator"
1167                    | "valueOf"
1168                    | "parseInt"
1169            )
1170        },
1171        Language::C | Language::Cpp => {
1172            matches!(
1173                name,
1174                "printf"
1175                    | "scanf"
1176                    | "malloc"
1177                    | "free"
1178                    | "memcpy"
1179                    | "memset"
1180                    | "strlen"
1181                    | "strcpy"
1182                    | "strcmp"
1183                    | "strcat"
1184                    | "sizeof"
1185                    | "cout"
1186                    | "cin"
1187                    | "endl"
1188                    | "cerr"
1189                    | "clog"
1190            )
1191        },
1192        Language::CSharp => {
1193            matches!(
1194                name,
1195                "WriteLine"
1196                    | "Write"
1197                    | "ReadLine"
1198                    | "ToString"
1199                    | "Equals"
1200                    | "GetHashCode"
1201                    | "GetType"
1202                    | "Add"
1203                    | "Remove"
1204                    | "Contains"
1205                    | "Count"
1206                    | "Clear"
1207                    | "ToList"
1208                    | "ToArray"
1209            )
1210        },
1211        Language::Ruby => {
1212            matches!(
1213                name,
1214                "puts"
1215                    | "print"
1216                    | "p"
1217                    | "gets"
1218                    | "each"
1219                    | "map"
1220                    | "select"
1221                    | "reject"
1222                    | "reduce"
1223                    | "inject"
1224                    | "find"
1225                    | "any?"
1226                    | "all?"
1227                    | "include?"
1228                    | "empty?"
1229                    | "nil?"
1230                    | "length"
1231                    | "size"
1232            )
1233        },
1234        Language::Php => {
1235            matches!(
1236                name,
1237                "echo"
1238                    | "print"
1239                    | "var_dump"
1240                    | "print_r"
1241                    | "isset"
1242                    | "empty"
1243                    | "array"
1244                    | "count"
1245                    | "strlen"
1246                    | "strpos"
1247                    | "substr"
1248                    | "explode"
1249                    | "implode"
1250                    | "json_encode"
1251                    | "json_decode"
1252            )
1253        },
1254        Language::Kotlin => {
1255            matches!(
1256                name,
1257                "println"
1258                    | "print"
1259                    | "readLine"
1260                    | "toString"
1261                    | "equals"
1262                    | "hashCode"
1263                    | "map"
1264                    | "filter"
1265                    | "forEach"
1266                    | "let"
1267                    | "also"
1268                    | "apply"
1269                    | "run"
1270                    | "with"
1271                    | "listOf"
1272                    | "mapOf"
1273                    | "setOf"
1274            )
1275        },
1276        Language::Swift => {
1277            matches!(
1278                name,
1279                "print"
1280                    | "debugPrint"
1281                    | "dump"
1282                    | "map"
1283                    | "filter"
1284                    | "reduce"
1285                    | "forEach"
1286                    | "contains"
1287                    | "count"
1288                    | "isEmpty"
1289                    | "append"
1290            )
1291        },
1292        Language::Scala => {
1293            matches!(
1294                name,
1295                "println"
1296                    | "print"
1297                    | "map"
1298                    | "filter"
1299                    | "flatMap"
1300                    | "foreach"
1301                    | "reduce"
1302                    | "fold"
1303                    | "foldLeft"
1304                    | "foldRight"
1305                    | "collect"
1306            )
1307        },
1308        Language::Bash
1309        | Language::Haskell
1310        | Language::Elixir
1311        | Language::Clojure
1312        | Language::OCaml
1313        | Language::FSharp
1314        | Language::Lua
1315        | Language::R
1316        | Language::Hcl
1317        | Language::Zig
1318        | Language::Dart => false,
1319    }
1320}
1321
1322/// Clean JSDoc comment
1323pub fn clean_jsdoc(text: &str) -> String {
1324    text.lines()
1325        .map(|line| {
1326            line.trim()
1327                .trim_start_matches("/**")
1328                .trim_start_matches("/*")
1329                .trim_start_matches('*')
1330                .trim_end_matches("*/")
1331                .trim()
1332        })
1333        .filter(|line| !line.is_empty())
1334        .collect::<Vec<_>>()
1335        .join(" ")
1336}
1337
1338/// Clean JavaDoc comment
1339pub fn clean_javadoc(text: &str) -> String {
1340    clean_jsdoc(text)
1341}
1342
1343/// Extract class inheritance (extends) and interface implementations (implements)
1344pub fn extract_inheritance(
1345    node: Node<'_>,
1346    source_code: &str,
1347    language: Language,
1348) -> (Option<String>, Vec<String>) {
1349    let mut extends = None;
1350    let mut implements = Vec::new();
1351
1352    match language {
1353        Language::Python => {
1354            // Python: class Foo(Bar, Baz): - all are considered base classes
1355            if node.kind() == "class_definition" {
1356                if let Some(args) = node.child_by_field_name("superclasses") {
1357                    for child in args.children(&mut args.walk()) {
1358                        if child.kind() == "identifier" || child.kind() == "attribute" {
1359                            if let Ok(name) = child.utf8_text(source_code.as_bytes()) {
1360                                if extends.is_none() {
1361                                    extends = Some(name.to_owned());
1362                                } else {
1363                                    implements.push(name.to_owned());
1364                                }
1365                            }
1366                        }
1367                    }
1368                }
1369            }
1370        },
1371        Language::JavaScript | Language::TypeScript => {
1372            // JS/TS: class Foo extends Bar implements Baz
1373            if node.kind() == "class_declaration" || node.kind() == "class" {
1374                for child in node.children(&mut node.walk()) {
1375                    if child.kind() == "class_heritage" {
1376                        for heritage in child.children(&mut child.walk()) {
1377                            if heritage.kind() == "extends_clause" {
1378                                for type_node in heritage.children(&mut heritage.walk()) {
1379                                    if type_node.kind() == "identifier"
1380                                        || type_node.kind() == "type_identifier"
1381                                    {
1382                                        if let Ok(name) =
1383                                            type_node.utf8_text(source_code.as_bytes())
1384                                        {
1385                                            extends = Some(name.to_owned());
1386                                        }
1387                                    }
1388                                }
1389                            } else if heritage.kind() == "implements_clause" {
1390                                for type_node in heritage.children(&mut heritage.walk()) {
1391                                    if type_node.kind() == "identifier"
1392                                        || type_node.kind() == "type_identifier"
1393                                    {
1394                                        if let Ok(name) =
1395                                            type_node.utf8_text(source_code.as_bytes())
1396                                        {
1397                                            implements.push(name.to_owned());
1398                                        }
1399                                    }
1400                                }
1401                            }
1402                        }
1403                    }
1404                }
1405            }
1406        },
1407        Language::Rust => {
1408            // Rust doesn't have class inheritance, but has trait implementations
1409            // impl Trait for Struct
1410            if node.kind() == "impl_item" {
1411                let mut has_for = false;
1412                for child in node.children(&mut node.walk()) {
1413                    if child.kind() == "for" {
1414                        has_for = true;
1415                    }
1416                    if child.kind() == "type_identifier" || child.kind() == "generic_type" {
1417                        if let Ok(name) = child.utf8_text(source_code.as_bytes()) {
1418                            if has_for {
1419                                // This is the struct being implemented
1420                            } else {
1421                                // This is the trait being implemented
1422                                implements.push(name.to_owned());
1423                            }
1424                        }
1425                    }
1426                }
1427            }
1428        },
1429        Language::Go => {
1430            // Go uses embedding for "inheritance"
1431            if node.kind() == "type_declaration" {
1432                for child in node.children(&mut node.walk()) {
1433                    if child.kind() == "type_spec" {
1434                        for spec_child in child.children(&mut child.walk()) {
1435                            if spec_child.kind() == "struct_type" {
1436                                for field in spec_child.children(&mut spec_child.walk()) {
1437                                    if field.kind() == "field_declaration" {
1438                                        // Embedded field (no name, just type)
1439                                        let has_name = field.child_by_field_name("name").is_some();
1440                                        if !has_name {
1441                                            if let Some(type_node) =
1442                                                field.child_by_field_name("type")
1443                                            {
1444                                                if let Ok(name) =
1445                                                    type_node.utf8_text(source_code.as_bytes())
1446                                                {
1447                                                    implements.push(name.to_owned());
1448                                                }
1449                                            }
1450                                        }
1451                                    }
1452                                }
1453                            }
1454                        }
1455                    }
1456                }
1457            }
1458        },
1459        Language::Java => {
1460            // Java: class Foo extends Bar implements Baz, Qux
1461            if node.kind() == "class_declaration" {
1462                for child in node.children(&mut node.walk()) {
1463                    if child.kind() == "superclass" {
1464                        for type_node in child.children(&mut child.walk()) {
1465                            if type_node.kind() == "type_identifier" {
1466                                if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1467                                    extends = Some(name.to_owned());
1468                                }
1469                            }
1470                        }
1471                    } else if child.kind() == "super_interfaces" {
1472                        for type_list in child.children(&mut child.walk()) {
1473                            if type_list.kind() == "type_list" {
1474                                for type_node in type_list.children(&mut type_list.walk()) {
1475                                    if type_node.kind() == "type_identifier" {
1476                                        if let Ok(name) =
1477                                            type_node.utf8_text(source_code.as_bytes())
1478                                        {
1479                                            implements.push(name.to_owned());
1480                                        }
1481                                    }
1482                                }
1483                            }
1484                        }
1485                    }
1486                }
1487            }
1488        },
1489        Language::C | Language::Cpp => {
1490            // C++: class Foo : public Bar, public Baz
1491            if node.kind() == "class_specifier" || node.kind() == "struct_specifier" {
1492                for child in node.children(&mut node.walk()) {
1493                    if child.kind() == "base_class_clause" {
1494                        for base in child.children(&mut child.walk()) {
1495                            if base.kind() == "type_identifier" {
1496                                if let Ok(name) = base.utf8_text(source_code.as_bytes()) {
1497                                    if extends.is_none() {
1498                                        extends = Some(name.to_owned());
1499                                    } else {
1500                                        implements.push(name.to_owned());
1501                                    }
1502                                }
1503                            }
1504                        }
1505                    }
1506                }
1507            }
1508        },
1509        Language::CSharp => {
1510            // C#: class Foo : Bar, IBaz
1511            if node.kind() == "class_declaration" {
1512                for child in node.children(&mut node.walk()) {
1513                    if child.kind() == "base_list" {
1514                        for base in child.children(&mut child.walk()) {
1515                            if base.kind() == "identifier" || base.kind() == "generic_name" {
1516                                if let Ok(name) = base.utf8_text(source_code.as_bytes()) {
1517                                    if name.starts_with('I') && name.len() > 1 {
1518                                        // Convention: interfaces start with I
1519                                        implements.push(name.to_owned());
1520                                    } else if extends.is_none() {
1521                                        extends = Some(name.to_owned());
1522                                    } else {
1523                                        implements.push(name.to_owned());
1524                                    }
1525                                }
1526                            }
1527                        }
1528                    }
1529                }
1530            }
1531        },
1532        Language::Ruby => {
1533            // Ruby: class Foo < Bar; include Baz
1534            if node.kind() == "class" {
1535                for child in node.children(&mut node.walk()) {
1536                    if child.kind() == "superclass" {
1537                        for type_node in child.children(&mut child.walk()) {
1538                            if type_node.kind() == "constant" {
1539                                if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1540                                    extends = Some(name.to_owned());
1541                                }
1542                            }
1543                        }
1544                    }
1545                }
1546            }
1547        },
1548        Language::Php => {
1549            // PHP: class Foo extends Bar implements Baz
1550            if node.kind() == "class_declaration" {
1551                for child in node.children(&mut node.walk()) {
1552                    if child.kind() == "base_clause" {
1553                        for type_node in child.children(&mut child.walk()) {
1554                            if type_node.kind() == "name" {
1555                                if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1556                                    extends = Some(name.to_owned());
1557                                }
1558                            }
1559                        }
1560                    } else if child.kind() == "class_interface_clause" {
1561                        for type_node in child.children(&mut child.walk()) {
1562                            if type_node.kind() == "name" {
1563                                if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1564                                    implements.push(name.to_owned());
1565                                }
1566                            }
1567                        }
1568                    }
1569                }
1570            }
1571        },
1572        Language::Kotlin => {
1573            // Kotlin: class Foo : Bar(), Baz
1574            if node.kind() == "class_declaration" {
1575                for child in node.children(&mut node.walk()) {
1576                    if child.kind() == "delegation_specifiers" {
1577                        for spec in child.children(&mut child.walk()) {
1578                            if spec.kind() == "delegation_specifier" {
1579                                for type_node in spec.children(&mut spec.walk()) {
1580                                    if type_node.kind() == "user_type" {
1581                                        if let Ok(name) =
1582                                            type_node.utf8_text(source_code.as_bytes())
1583                                        {
1584                                            if extends.is_none() {
1585                                                extends = Some(name.to_owned());
1586                                            } else {
1587                                                implements.push(name.to_owned());
1588                                            }
1589                                        }
1590                                    }
1591                                }
1592                            }
1593                        }
1594                    }
1595                }
1596            }
1597        },
1598        Language::Swift => {
1599            // Swift: class Foo: Bar, Protocol
1600            if node.kind() == "class_declaration" {
1601                for child in node.children(&mut node.walk()) {
1602                    if child.kind() == "type_inheritance_clause" {
1603                        for type_node in child.children(&mut child.walk()) {
1604                            if type_node.kind() == "type_identifier" {
1605                                if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1606                                    if extends.is_none() {
1607                                        extends = Some(name.to_owned());
1608                                    } else {
1609                                        implements.push(name.to_owned());
1610                                    }
1611                                }
1612                            }
1613                        }
1614                    }
1615                }
1616            }
1617        },
1618        Language::Scala => {
1619            // Scala: class Foo extends Bar with Baz
1620            if node.kind() == "class_definition" {
1621                for child in node.children(&mut node.walk()) {
1622                    if child.kind() == "extends_clause" {
1623                        for type_node in child.children(&mut child.walk()) {
1624                            if type_node.kind() == "type_identifier" {
1625                                if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1626                                    if extends.is_none() {
1627                                        extends = Some(name.to_owned());
1628                                    } else {
1629                                        implements.push(name.to_owned());
1630                                    }
1631                                }
1632                            }
1633                        }
1634                    }
1635                }
1636            }
1637        },
1638        Language::Dart => {
1639            // Dart: class Foo extends Bar with Mixin1, Mixin2 implements Baz, Qux
1640            // AST structure:
1641            //   class_definition
1642            //     superclass              (contains extends type + optional nested mixins)
1643            //       type_identifier       (the extended class)
1644            //       mixins                (nested inside superclass)
1645            //         type_identifier*    (mixin types)
1646            //     interfaces              (direct child of class_definition)
1647            //       type_identifier*      (implemented types)
1648            if node.kind() == "class_definition" {
1649                for child in node.children(&mut node.walk()) {
1650                    if child.kind() == "superclass" {
1651                        for sc_child in child.children(&mut child.walk()) {
1652                            if sc_child.kind() == "type_identifier" {
1653                                // Direct type_identifier in superclass = extends
1654                                if let Ok(name) = sc_child.utf8_text(source_code.as_bytes()) {
1655                                    extends = Some(name.to_owned());
1656                                }
1657                            } else if sc_child.kind() == "mixins" {
1658                                // mixins node is nested inside superclass
1659                                for mixin_type in sc_child.children(&mut sc_child.walk()) {
1660                                    if mixin_type.kind() == "type_identifier" {
1661                                        if let Ok(name) =
1662                                            mixin_type.utf8_text(source_code.as_bytes())
1663                                        {
1664                                            implements.push(name.to_owned());
1665                                        }
1666                                    }
1667                                }
1668                            }
1669                        }
1670                    } else if child.kind() == "interfaces" {
1671                        for type_node in child.children(&mut child.walk()) {
1672                            if type_node.kind() == "type_identifier" {
1673                                if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1674                                    implements.push(name.to_owned());
1675                                }
1676                            }
1677                        }
1678                    }
1679                }
1680            }
1681        },
1682        Language::Bash
1683        | Language::Haskell
1684        | Language::Elixir
1685        | Language::Clojure
1686        | Language::OCaml
1687        | Language::FSharp
1688        | Language::Lua
1689        | Language::R
1690        | Language::Hcl
1691        | Language::Zig => {},
1692    }
1693
1694    (extends, implements)
1695}
1696
1697/// Map capture name to SymbolKind
1698pub fn map_symbol_kind(capture_name: &str) -> SymbolKind {
1699    match capture_name {
1700        "function" => SymbolKind::Function,
1701        "class" => SymbolKind::Class,
1702        "method" => SymbolKind::Method,
1703        "struct" => SymbolKind::Struct,
1704        "enum" => SymbolKind::Enum,
1705        "interface" => SymbolKind::Interface,
1706        "trait" => SymbolKind::Trait,
1707        "constant" => SymbolKind::Constant,
1708        "module" => SymbolKind::Module,
1709        _ => SymbolKind::Function,
1710    }
1711}
1712
1713#[cfg(test)]
1714mod tests {
1715    use super::*;
1716
1717    // ==========================================================================
1718    // safe_char_boundary tests
1719    // ==========================================================================
1720
1721    #[test]
1722    fn test_safe_char_boundary_ascii() {
1723        let s = "hello world";
1724        assert_eq!(safe_char_boundary(s, 0), 0);
1725        assert_eq!(safe_char_boundary(s, 5), 5);
1726        assert_eq!(safe_char_boundary(s, 11), 11);
1727    }
1728
1729    #[test]
1730    fn test_safe_char_boundary_beyond_length() {
1731        let s = "hello";
1732        assert_eq!(safe_char_boundary(s, 100), 5);
1733        assert_eq!(safe_char_boundary(s, 5), 5);
1734    }
1735
1736    #[test]
1737    fn test_safe_char_boundary_empty_string() {
1738        let s = "";
1739        assert_eq!(safe_char_boundary(s, 0), 0);
1740        assert_eq!(safe_char_boundary(s, 10), 0);
1741    }
1742
1743    #[test]
1744    fn test_safe_char_boundary_multibyte_utf8() {
1745        // Chinese character "中" is 3 bytes: E4 B8 AD
1746        let s = "中文";
1747        // Index 0 is valid (start of first char)
1748        assert_eq!(safe_char_boundary(s, 0), 0);
1749        // Index 1 is in the middle of "中", should back up to 0
1750        assert_eq!(safe_char_boundary(s, 1), 0);
1751        // Index 2 is also in the middle
1752        assert_eq!(safe_char_boundary(s, 2), 0);
1753        // Index 3 is the start of "æ–‡"
1754        assert_eq!(safe_char_boundary(s, 3), 3);
1755        // Index 4 is in the middle of "æ–‡"
1756        assert_eq!(safe_char_boundary(s, 4), 3);
1757    }
1758
1759    #[test]
1760    fn test_safe_char_boundary_emoji() {
1761        // "👋" emoji is 4 bytes
1762        let s = "Hello 👋 World";
1763        // The emoji starts at byte 6
1764        assert_eq!(safe_char_boundary(s, 6), 6);
1765        // Middle of emoji should back up
1766        assert_eq!(safe_char_boundary(s, 7), 6);
1767        assert_eq!(safe_char_boundary(s, 8), 6);
1768        assert_eq!(safe_char_boundary(s, 9), 6);
1769        // After emoji (byte 10)
1770        assert_eq!(safe_char_boundary(s, 10), 10);
1771    }
1772
1773    #[test]
1774    fn test_safe_char_boundary_mixed_content() {
1775        // Mix of ASCII and multi-byte
1776        let s = "aбв"; // 'a' is 1 byte, 'б' and 'в' are 2 bytes each
1777        assert_eq!(safe_char_boundary(s, 0), 0);
1778        assert_eq!(safe_char_boundary(s, 1), 1); // Start of 'б'
1779        assert_eq!(safe_char_boundary(s, 2), 1); // Middle of 'б', back to 1
1780        assert_eq!(safe_char_boundary(s, 3), 3); // Start of 'в'
1781        assert_eq!(safe_char_boundary(s, 4), 3); // Middle of 'в'
1782        assert_eq!(safe_char_boundary(s, 5), 5); // End
1783    }
1784
1785    // ==========================================================================
1786    // clean_jsdoc tests
1787    // ==========================================================================
1788
1789    #[test]
1790    fn test_clean_jsdoc_simple() {
1791        let input = "/** This is a simple doc */";
1792        assert_eq!(clean_jsdoc(input), "This is a simple doc");
1793    }
1794
1795    #[test]
1796    fn test_clean_jsdoc_multiline() {
1797        let input = "/**\n * Line 1\n * Line 2\n */";
1798        let result = clean_jsdoc(input);
1799        // Trailing slash is kept when on its own line
1800        assert!(result.contains("Line 1"));
1801        assert!(result.contains("Line 2"));
1802    }
1803
1804    #[test]
1805    fn test_clean_jsdoc_with_asterisks() {
1806        let input = "/**\n * First line\n * Second line\n * Third line\n */";
1807        let result = clean_jsdoc(input);
1808        assert!(result.contains("First line"));
1809        assert!(result.contains("Second line"));
1810        assert!(result.contains("Third line"));
1811    }
1812
1813    #[test]
1814    fn test_clean_jsdoc_empty() {
1815        let input = "/** */";
1816        assert_eq!(clean_jsdoc(input), "");
1817    }
1818
1819    #[test]
1820    fn test_clean_jsdoc_c_style_comment() {
1821        let input = "/* Regular C comment */";
1822        assert_eq!(clean_jsdoc(input), "Regular C comment");
1823    }
1824
1825    #[test]
1826    fn test_clean_jsdoc_with_tags() {
1827        let input = "/**\n * Description\n * @param x The x value\n * @returns Result\n */";
1828        let result = clean_jsdoc(input);
1829        assert!(result.contains("Description"));
1830        assert!(result.contains("@param x"));
1831        assert!(result.contains("@returns"));
1832    }
1833
1834    #[test]
1835    fn test_clean_jsdoc_whitespace_handling() {
1836        let input = "/**   \n   *    Lots of spaces    \n   */";
1837        assert!(clean_jsdoc(input).contains("Lots of spaces"));
1838    }
1839
1840    // ==========================================================================
1841    // clean_javadoc tests
1842    // ==========================================================================
1843
1844    #[test]
1845    fn test_clean_javadoc_simple() {
1846        let input = "/** JavaDoc comment */";
1847        assert_eq!(clean_javadoc(input), "JavaDoc comment");
1848    }
1849
1850    #[test]
1851    fn test_clean_javadoc_multiline() {
1852        let input = "/**\n * Method description.\n * @param name The name\n */";
1853        let result = clean_javadoc(input);
1854        assert!(result.contains("Method description"));
1855        assert!(result.contains("@param name"));
1856    }
1857
1858    // ==========================================================================
1859    // map_symbol_kind tests
1860    // ==========================================================================
1861
1862    #[test]
1863    fn test_map_symbol_kind_function() {
1864        assert_eq!(map_symbol_kind("function"), SymbolKind::Function);
1865    }
1866
1867    #[test]
1868    fn test_map_symbol_kind_class() {
1869        assert_eq!(map_symbol_kind("class"), SymbolKind::Class);
1870    }
1871
1872    #[test]
1873    fn test_map_symbol_kind_method() {
1874        assert_eq!(map_symbol_kind("method"), SymbolKind::Method);
1875    }
1876
1877    #[test]
1878    fn test_map_symbol_kind_struct() {
1879        assert_eq!(map_symbol_kind("struct"), SymbolKind::Struct);
1880    }
1881
1882    #[test]
1883    fn test_map_symbol_kind_enum() {
1884        assert_eq!(map_symbol_kind("enum"), SymbolKind::Enum);
1885    }
1886
1887    #[test]
1888    fn test_map_symbol_kind_interface() {
1889        assert_eq!(map_symbol_kind("interface"), SymbolKind::Interface);
1890    }
1891
1892    #[test]
1893    fn test_map_symbol_kind_trait() {
1894        assert_eq!(map_symbol_kind("trait"), SymbolKind::Trait);
1895    }
1896
1897    #[test]
1898    fn test_map_symbol_kind_unknown() {
1899        // Unknown capture names default to Function
1900        assert_eq!(map_symbol_kind("unknown"), SymbolKind::Function);
1901        assert_eq!(map_symbol_kind(""), SymbolKind::Function);
1902        assert_eq!(map_symbol_kind("random"), SymbolKind::Function);
1903    }
1904
1905    // ==========================================================================
1906    // is_builtin tests - Python
1907    // ==========================================================================
1908
1909    #[test]
1910    fn test_is_builtin_python_print() {
1911        assert!(is_builtin("print", Language::Python));
1912        assert!(is_builtin("len", Language::Python));
1913        assert!(is_builtin("range", Language::Python));
1914        assert!(is_builtin("str", Language::Python));
1915        assert!(is_builtin("int", Language::Python));
1916        assert!(is_builtin("float", Language::Python));
1917        assert!(is_builtin("list", Language::Python));
1918        assert!(is_builtin("dict", Language::Python));
1919        assert!(is_builtin("set", Language::Python));
1920        assert!(is_builtin("tuple", Language::Python));
1921    }
1922
1923    #[test]
1924    fn test_is_builtin_python_type_funcs() {
1925        assert!(is_builtin("bool", Language::Python));
1926        assert!(is_builtin("type", Language::Python));
1927        assert!(is_builtin("isinstance", Language::Python));
1928        assert!(is_builtin("hasattr", Language::Python));
1929        assert!(is_builtin("getattr", Language::Python));
1930        assert!(is_builtin("setattr", Language::Python));
1931        assert!(is_builtin("super", Language::Python));
1932    }
1933
1934    #[test]
1935    fn test_is_builtin_python_itertools() {
1936        assert!(is_builtin("iter", Language::Python));
1937        assert!(is_builtin("next", Language::Python));
1938        assert!(is_builtin("enumerate", Language::Python));
1939        assert!(is_builtin("zip", Language::Python));
1940        assert!(is_builtin("map", Language::Python));
1941        assert!(is_builtin("filter", Language::Python));
1942        assert!(is_builtin("sorted", Language::Python));
1943        assert!(is_builtin("reversed", Language::Python));
1944    }
1945
1946    #[test]
1947    fn test_is_builtin_python_math() {
1948        assert!(is_builtin("sum", Language::Python));
1949        assert!(is_builtin("min", Language::Python));
1950        assert!(is_builtin("max", Language::Python));
1951        assert!(is_builtin("abs", Language::Python));
1952        assert!(is_builtin("round", Language::Python));
1953    }
1954
1955    #[test]
1956    fn test_is_builtin_python_not_builtin() {
1957        assert!(!is_builtin("my_function", Language::Python));
1958        assert!(!is_builtin("custom_print", Language::Python));
1959        assert!(!is_builtin("calculate", Language::Python));
1960    }
1961
1962    // ==========================================================================
1963    // is_builtin tests - JavaScript/TypeScript
1964    // ==========================================================================
1965
1966    #[test]
1967    fn test_is_builtin_js_console() {
1968        assert!(is_builtin("console", Language::JavaScript));
1969        assert!(is_builtin("log", Language::JavaScript));
1970        assert!(is_builtin("error", Language::JavaScript));
1971        assert!(is_builtin("warn", Language::JavaScript));
1972    }
1973
1974    #[test]
1975    fn test_is_builtin_js_parsing() {
1976        assert!(is_builtin("parseInt", Language::JavaScript));
1977        assert!(is_builtin("parseFloat", Language::JavaScript));
1978        assert!(is_builtin("JSON", Language::JavaScript));
1979        assert!(is_builtin("stringify", Language::JavaScript));
1980        assert!(is_builtin("parse", Language::JavaScript));
1981    }
1982
1983    #[test]
1984    fn test_is_builtin_js_timers() {
1985        assert!(is_builtin("setTimeout", Language::JavaScript));
1986        assert!(is_builtin("setInterval", Language::JavaScript));
1987        assert!(is_builtin("clearTimeout", Language::JavaScript));
1988        assert!(is_builtin("clearInterval", Language::JavaScript));
1989    }
1990
1991    #[test]
1992    fn test_is_builtin_js_array_methods() {
1993        assert!(is_builtin("push", Language::JavaScript));
1994        assert!(is_builtin("pop", Language::JavaScript));
1995        assert!(is_builtin("shift", Language::JavaScript));
1996        assert!(is_builtin("unshift", Language::JavaScript));
1997        assert!(is_builtin("slice", Language::JavaScript));
1998        assert!(is_builtin("splice", Language::JavaScript));
1999        assert!(is_builtin("map", Language::JavaScript));
2000        assert!(is_builtin("filter", Language::JavaScript));
2001        assert!(is_builtin("reduce", Language::JavaScript));
2002        assert!(is_builtin("forEach", Language::JavaScript));
2003    }
2004
2005    #[test]
2006    fn test_is_builtin_ts_same_as_js() {
2007        assert!(is_builtin("console", Language::TypeScript));
2008        assert!(is_builtin("map", Language::TypeScript));
2009        assert!(is_builtin("filter", Language::TypeScript));
2010    }
2011
2012    #[test]
2013    fn test_is_builtin_js_not_builtin() {
2014        assert!(!is_builtin("myFunction", Language::JavaScript));
2015        assert!(!is_builtin("customLog", Language::JavaScript));
2016    }
2017
2018    // ==========================================================================
2019    // is_builtin tests - Rust
2020    // ==========================================================================
2021
2022    #[test]
2023    fn test_is_builtin_rust_macros() {
2024        assert!(is_builtin("println!", Language::Rust));
2025        assert!(is_builtin("print!", Language::Rust));
2026        assert!(is_builtin("eprintln!", Language::Rust));
2027        assert!(is_builtin("eprint!", Language::Rust));
2028        assert!(is_builtin("format!", Language::Rust));
2029        assert!(is_builtin("vec!", Language::Rust));
2030        assert!(is_builtin("panic!", Language::Rust));
2031        assert!(is_builtin("assert!", Language::Rust));
2032        assert!(is_builtin("assert_eq!", Language::Rust));
2033        assert!(is_builtin("assert_ne!", Language::Rust));
2034    }
2035
2036    #[test]
2037    fn test_is_builtin_rust_logging() {
2038        assert!(is_builtin("debug!", Language::Rust));
2039        assert!(is_builtin("info!", Language::Rust));
2040        assert!(is_builtin("warn!", Language::Rust));
2041        assert!(is_builtin("error!", Language::Rust));
2042        assert!(is_builtin("trace!", Language::Rust));
2043    }
2044
2045    #[test]
2046    fn test_is_builtin_rust_common_methods() {
2047        assert!(is_builtin("unwrap", Language::Rust));
2048        assert!(is_builtin("expect", Language::Rust));
2049        assert!(is_builtin("ok", Language::Rust));
2050        assert!(is_builtin("err", Language::Rust));
2051        assert!(is_builtin("some", Language::Rust));
2052        assert!(is_builtin("none", Language::Rust));
2053        assert!(is_builtin("clone", Language::Rust));
2054        assert!(is_builtin("to_string", Language::Rust));
2055        assert!(is_builtin("into", Language::Rust));
2056        assert!(is_builtin("from", Language::Rust));
2057        assert!(is_builtin("default", Language::Rust));
2058    }
2059
2060    #[test]
2061    fn test_is_builtin_rust_iterators() {
2062        assert!(is_builtin("iter", Language::Rust));
2063        assert!(is_builtin("into_iter", Language::Rust));
2064        assert!(is_builtin("collect", Language::Rust));
2065        assert!(is_builtin("map", Language::Rust));
2066        assert!(is_builtin("filter", Language::Rust));
2067    }
2068
2069    #[test]
2070    fn test_is_builtin_rust_not_builtin() {
2071        assert!(!is_builtin("my_function", Language::Rust));
2072        assert!(!is_builtin("process_data", Language::Rust));
2073    }
2074
2075    // ==========================================================================
2076    // is_builtin tests - Go
2077    // ==========================================================================
2078
2079    #[test]
2080    fn test_is_builtin_go_fmt() {
2081        assert!(is_builtin("fmt", Language::Go));
2082        assert!(is_builtin("Println", Language::Go));
2083        assert!(is_builtin("Printf", Language::Go));
2084        assert!(is_builtin("Sprintf", Language::Go));
2085        assert!(is_builtin("Errorf", Language::Go));
2086    }
2087
2088    #[test]
2089    fn test_is_builtin_go_memory() {
2090        assert!(is_builtin("make", Language::Go));
2091        assert!(is_builtin("new", Language::Go));
2092        assert!(is_builtin("len", Language::Go));
2093        assert!(is_builtin("cap", Language::Go));
2094        assert!(is_builtin("append", Language::Go));
2095        assert!(is_builtin("copy", Language::Go));
2096        assert!(is_builtin("delete", Language::Go));
2097    }
2098
2099    #[test]
2100    fn test_is_builtin_go_control() {
2101        assert!(is_builtin("close", Language::Go));
2102        assert!(is_builtin("panic", Language::Go));
2103        assert!(is_builtin("recover", Language::Go));
2104        assert!(is_builtin("print", Language::Go));
2105    }
2106
2107    #[test]
2108    fn test_is_builtin_go_not_builtin() {
2109        assert!(!is_builtin("ProcessData", Language::Go));
2110        assert!(!is_builtin("handleRequest", Language::Go));
2111    }
2112
2113    // ==========================================================================
2114    // is_builtin tests - Java
2115    // ==========================================================================
2116
2117    #[test]
2118    fn test_is_builtin_java_io() {
2119        assert!(is_builtin("println", Language::Java));
2120        assert!(is_builtin("print", Language::Java));
2121        assert!(is_builtin("printf", Language::Java));
2122    }
2123
2124    #[test]
2125    fn test_is_builtin_java_object() {
2126        assert!(is_builtin("toString", Language::Java));
2127        assert!(is_builtin("equals", Language::Java));
2128        assert!(is_builtin("hashCode", Language::Java));
2129        assert!(is_builtin("getClass", Language::Java));
2130        assert!(is_builtin("clone", Language::Java));
2131        assert!(is_builtin("notify", Language::Java));
2132        assert!(is_builtin("wait", Language::Java));
2133    }
2134
2135    #[test]
2136    fn test_is_builtin_java_collections() {
2137        assert!(is_builtin("get", Language::Java));
2138        assert!(is_builtin("set", Language::Java));
2139        assert!(is_builtin("add", Language::Java));
2140        assert!(is_builtin("remove", Language::Java));
2141        assert!(is_builtin("size", Language::Java));
2142        assert!(is_builtin("isEmpty", Language::Java));
2143        assert!(is_builtin("contains", Language::Java));
2144        assert!(is_builtin("iterator", Language::Java));
2145    }
2146
2147    #[test]
2148    fn test_is_builtin_java_not_builtin() {
2149        assert!(!is_builtin("processData", Language::Java));
2150        assert!(!is_builtin("calculateTotal", Language::Java));
2151    }
2152
2153    // ==========================================================================
2154    // is_builtin tests - C/C++
2155    // ==========================================================================
2156
2157    #[test]
2158    fn test_is_builtin_c_io() {
2159        assert!(is_builtin("printf", Language::C));
2160        assert!(is_builtin("scanf", Language::C));
2161    }
2162
2163    #[test]
2164    fn test_is_builtin_c_memory() {
2165        assert!(is_builtin("malloc", Language::C));
2166        assert!(is_builtin("free", Language::C));
2167        assert!(is_builtin("memcpy", Language::C));
2168        assert!(is_builtin("memset", Language::C));
2169    }
2170
2171    #[test]
2172    fn test_is_builtin_c_string() {
2173        assert!(is_builtin("strlen", Language::C));
2174        assert!(is_builtin("strcpy", Language::C));
2175        assert!(is_builtin("strcmp", Language::C));
2176        assert!(is_builtin("strcat", Language::C));
2177    }
2178
2179    #[test]
2180    fn test_is_builtin_cpp_streams() {
2181        assert!(is_builtin("cout", Language::Cpp));
2182        assert!(is_builtin("cin", Language::Cpp));
2183        assert!(is_builtin("endl", Language::Cpp));
2184        assert!(is_builtin("cerr", Language::Cpp));
2185        assert!(is_builtin("clog", Language::Cpp));
2186    }
2187
2188    #[test]
2189    fn test_is_builtin_c_not_builtin() {
2190        assert!(!is_builtin("process_data", Language::C));
2191        assert!(!is_builtin("custom_malloc", Language::C));
2192    }
2193
2194    // ==========================================================================
2195    // is_builtin tests - C#
2196    // ==========================================================================
2197
2198    #[test]
2199    fn test_is_builtin_csharp_console() {
2200        assert!(is_builtin("WriteLine", Language::CSharp));
2201        assert!(is_builtin("Write", Language::CSharp));
2202        assert!(is_builtin("ReadLine", Language::CSharp));
2203    }
2204
2205    #[test]
2206    fn test_is_builtin_csharp_object() {
2207        assert!(is_builtin("ToString", Language::CSharp));
2208        assert!(is_builtin("Equals", Language::CSharp));
2209        assert!(is_builtin("GetHashCode", Language::CSharp));
2210        assert!(is_builtin("GetType", Language::CSharp));
2211    }
2212
2213    #[test]
2214    fn test_is_builtin_csharp_collections() {
2215        assert!(is_builtin("Add", Language::CSharp));
2216        assert!(is_builtin("Remove", Language::CSharp));
2217        assert!(is_builtin("Contains", Language::CSharp));
2218        assert!(is_builtin("Count", Language::CSharp));
2219        assert!(is_builtin("Clear", Language::CSharp));
2220        assert!(is_builtin("ToList", Language::CSharp));
2221        assert!(is_builtin("ToArray", Language::CSharp));
2222    }
2223
2224    // ==========================================================================
2225    // is_builtin tests - Ruby
2226    // ==========================================================================
2227
2228    #[test]
2229    fn test_is_builtin_ruby_io() {
2230        assert!(is_builtin("puts", Language::Ruby));
2231        assert!(is_builtin("print", Language::Ruby));
2232        assert!(is_builtin("p", Language::Ruby));
2233        assert!(is_builtin("gets", Language::Ruby));
2234    }
2235
2236    #[test]
2237    fn test_is_builtin_ruby_enumerable() {
2238        assert!(is_builtin("each", Language::Ruby));
2239        assert!(is_builtin("map", Language::Ruby));
2240        assert!(is_builtin("select", Language::Ruby));
2241        assert!(is_builtin("reject", Language::Ruby));
2242        assert!(is_builtin("reduce", Language::Ruby));
2243        assert!(is_builtin("inject", Language::Ruby));
2244        assert!(is_builtin("find", Language::Ruby));
2245    }
2246
2247    #[test]
2248    fn test_is_builtin_ruby_predicates() {
2249        assert!(is_builtin("any?", Language::Ruby));
2250        assert!(is_builtin("all?", Language::Ruby));
2251        assert!(is_builtin("include?", Language::Ruby));
2252        assert!(is_builtin("empty?", Language::Ruby));
2253        assert!(is_builtin("nil?", Language::Ruby));
2254    }
2255
2256    // ==========================================================================
2257    // is_builtin tests - PHP
2258    // ==========================================================================
2259
2260    #[test]
2261    fn test_is_builtin_php_io() {
2262        assert!(is_builtin("echo", Language::Php));
2263        assert!(is_builtin("print", Language::Php));
2264        assert!(is_builtin("var_dump", Language::Php));
2265        assert!(is_builtin("print_r", Language::Php));
2266    }
2267
2268    #[test]
2269    fn test_is_builtin_php_checks() {
2270        assert!(is_builtin("isset", Language::Php));
2271        assert!(is_builtin("empty", Language::Php));
2272    }
2273
2274    #[test]
2275    fn test_is_builtin_php_array_string() {
2276        assert!(is_builtin("array", Language::Php));
2277        assert!(is_builtin("count", Language::Php));
2278        assert!(is_builtin("strlen", Language::Php));
2279        assert!(is_builtin("strpos", Language::Php));
2280        assert!(is_builtin("substr", Language::Php));
2281        assert!(is_builtin("explode", Language::Php));
2282        assert!(is_builtin("implode", Language::Php));
2283        assert!(is_builtin("json_encode", Language::Php));
2284        assert!(is_builtin("json_decode", Language::Php));
2285    }
2286
2287    // ==========================================================================
2288    // is_builtin tests - Kotlin
2289    // ==========================================================================
2290
2291    #[test]
2292    fn test_is_builtin_kotlin_io() {
2293        assert!(is_builtin("println", Language::Kotlin));
2294        assert!(is_builtin("print", Language::Kotlin));
2295        assert!(is_builtin("readLine", Language::Kotlin));
2296    }
2297
2298    #[test]
2299    fn test_is_builtin_kotlin_scope() {
2300        assert!(is_builtin("let", Language::Kotlin));
2301        assert!(is_builtin("also", Language::Kotlin));
2302        assert!(is_builtin("apply", Language::Kotlin));
2303        assert!(is_builtin("run", Language::Kotlin));
2304        assert!(is_builtin("with", Language::Kotlin));
2305    }
2306
2307    #[test]
2308    fn test_is_builtin_kotlin_collections() {
2309        assert!(is_builtin("listOf", Language::Kotlin));
2310        assert!(is_builtin("mapOf", Language::Kotlin));
2311        assert!(is_builtin("setOf", Language::Kotlin));
2312        assert!(is_builtin("map", Language::Kotlin));
2313        assert!(is_builtin("filter", Language::Kotlin));
2314        assert!(is_builtin("forEach", Language::Kotlin));
2315    }
2316
2317    // ==========================================================================
2318    // is_builtin tests - Swift
2319    // ==========================================================================
2320
2321    #[test]
2322    fn test_is_builtin_swift_io() {
2323        assert!(is_builtin("print", Language::Swift));
2324        assert!(is_builtin("debugPrint", Language::Swift));
2325        assert!(is_builtin("dump", Language::Swift));
2326    }
2327
2328    #[test]
2329    fn test_is_builtin_swift_functional() {
2330        assert!(is_builtin("map", Language::Swift));
2331        assert!(is_builtin("filter", Language::Swift));
2332        assert!(is_builtin("reduce", Language::Swift));
2333        assert!(is_builtin("forEach", Language::Swift));
2334    }
2335
2336    #[test]
2337    fn test_is_builtin_swift_collection() {
2338        assert!(is_builtin("contains", Language::Swift));
2339        assert!(is_builtin("count", Language::Swift));
2340        assert!(is_builtin("isEmpty", Language::Swift));
2341        assert!(is_builtin("append", Language::Swift));
2342    }
2343
2344    // ==========================================================================
2345    // is_builtin tests - Scala
2346    // ==========================================================================
2347
2348    #[test]
2349    fn test_is_builtin_scala_io() {
2350        assert!(is_builtin("println", Language::Scala));
2351        assert!(is_builtin("print", Language::Scala));
2352    }
2353
2354    #[test]
2355    fn test_is_builtin_scala_functional() {
2356        assert!(is_builtin("map", Language::Scala));
2357        assert!(is_builtin("filter", Language::Scala));
2358        assert!(is_builtin("flatMap", Language::Scala));
2359        assert!(is_builtin("foreach", Language::Scala));
2360        assert!(is_builtin("reduce", Language::Scala));
2361        assert!(is_builtin("fold", Language::Scala));
2362        assert!(is_builtin("foldLeft", Language::Scala));
2363        assert!(is_builtin("foldRight", Language::Scala));
2364        assert!(is_builtin("collect", Language::Scala));
2365    }
2366
2367    // ==========================================================================
2368    // is_builtin tests - Languages with no builtins
2369    // ==========================================================================
2370
2371    #[test]
2372    fn test_is_builtin_bash_always_false() {
2373        assert!(!is_builtin("ls", Language::Bash));
2374        assert!(!is_builtin("echo", Language::Bash));
2375        assert!(!is_builtin("grep", Language::Bash));
2376    }
2377
2378    #[test]
2379    fn test_is_builtin_haskell_always_false() {
2380        assert!(!is_builtin("putStrLn", Language::Haskell));
2381        assert!(!is_builtin("map", Language::Haskell));
2382    }
2383
2384    #[test]
2385    fn test_is_builtin_elixir_always_false() {
2386        assert!(!is_builtin("IO.puts", Language::Elixir));
2387        assert!(!is_builtin("Enum.map", Language::Elixir));
2388    }
2389
2390    #[test]
2391    fn test_is_builtin_clojure_always_false() {
2392        assert!(!is_builtin("println", Language::Clojure));
2393        assert!(!is_builtin("map", Language::Clojure));
2394    }
2395
2396    #[test]
2397    fn test_is_builtin_ocaml_always_false() {
2398        assert!(!is_builtin("print_endline", Language::OCaml));
2399        assert!(!is_builtin("List.map", Language::OCaml));
2400    }
2401
2402    #[test]
2403    fn test_is_builtin_fsharp_always_false() {
2404        assert!(!is_builtin("printfn", Language::FSharp));
2405        assert!(!is_builtin("List.map", Language::FSharp));
2406    }
2407
2408    #[test]
2409    fn test_is_builtin_lua_always_false() {
2410        assert!(!is_builtin("print", Language::Lua));
2411        assert!(!is_builtin("pairs", Language::Lua));
2412    }
2413
2414    #[test]
2415    fn test_is_builtin_r_always_false() {
2416        assert!(!is_builtin("print", Language::R));
2417        assert!(!is_builtin("cat", Language::R));
2418    }
2419
2420    // ==========================================================================
2421    // Integration tests using tree-sitter parsing
2422    // ==========================================================================
2423
2424    // Helper to parse code and get the first node of a specific kind
2425    fn parse_and_find_node(
2426        code: &str,
2427        language: Language,
2428        node_kind: &str,
2429    ) -> Option<(tree_sitter::Tree, usize)> {
2430        let mut parser = tree_sitter::Parser::new();
2431
2432        let ts_language = match language {
2433            Language::Python => tree_sitter_python::LANGUAGE,
2434            Language::Rust => tree_sitter_rust::LANGUAGE,
2435            Language::JavaScript => tree_sitter_javascript::LANGUAGE,
2436            Language::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT,
2437            Language::Go => tree_sitter_go::LANGUAGE,
2438            Language::Java => tree_sitter_java::LANGUAGE,
2439            _ => return None,
2440        };
2441
2442        parser
2443            .set_language(&ts_language.into())
2444            .expect("Error loading grammar");
2445
2446        let tree = parser.parse(code, None)?;
2447        let root = tree.root_node();
2448
2449        fn find_node_recursive(node: Node<'_>, kind: &str) -> Option<usize> {
2450            if node.kind() == kind {
2451                return Some(node.id());
2452            }
2453            for child in node.children(&mut node.walk()) {
2454                if let Some(id) = find_node_recursive(child, kind) {
2455                    return Some(id);
2456                }
2457            }
2458            None
2459        }
2460
2461        find_node_recursive(root, node_kind).map(|_| (tree, 0))
2462    }
2463
2464    // Helper to find node by kind in tree
2465    fn find_node_in_tree<'a>(node: Node<'a>, kind: &str) -> Option<Node<'a>> {
2466        if node.kind() == kind {
2467            return Some(node);
2468        }
2469        for child in node.children(&mut node.walk()) {
2470            if let Some(found) = find_node_in_tree(child, kind) {
2471                return Some(found);
2472            }
2473        }
2474        None
2475    }
2476
2477    #[test]
2478    fn test_extract_signature_python() {
2479        // Note: Python signature extraction stops at first ':' or '\n'
2480        // So type annotations in parameters are cut off at the first ':'
2481        let code = "def hello(name):\n    return f'Hello {name}'";
2482        let mut parser = tree_sitter::Parser::new();
2483        parser
2484            .set_language(&tree_sitter_python::LANGUAGE.into())
2485            .unwrap();
2486        let tree = parser.parse(code, None).unwrap();
2487        let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2488
2489        let sig = extract_signature(func_node, code, Language::Python);
2490        assert!(sig.is_some());
2491        let sig = sig.unwrap();
2492        assert!(sig.contains("def hello"));
2493        assert!(sig.contains("name"));
2494    }
2495
2496    #[test]
2497    fn test_extract_signature_rust() {
2498        let code = "fn add(a: i32, b: i32) -> i32 { a + b }";
2499        let mut parser = tree_sitter::Parser::new();
2500        parser
2501            .set_language(&tree_sitter_rust::LANGUAGE.into())
2502            .unwrap();
2503        let tree = parser.parse(code, None).unwrap();
2504        let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2505
2506        let sig = extract_signature(func_node, code, Language::Rust);
2507        assert!(sig.is_some());
2508        let sig = sig.unwrap();
2509        assert!(sig.contains("fn add"));
2510        assert!(sig.contains("i32"));
2511    }
2512
2513    #[test]
2514    fn test_extract_signature_javascript() {
2515        let code = "function greet(name) { return 'Hello ' + name; }";
2516        let mut parser = tree_sitter::Parser::new();
2517        parser
2518            .set_language(&tree_sitter_javascript::LANGUAGE.into())
2519            .unwrap();
2520        let tree = parser.parse(code, None).unwrap();
2521        let func_node = find_node_in_tree(tree.root_node(), "function_declaration").unwrap();
2522
2523        let sig = extract_signature(func_node, code, Language::JavaScript);
2524        assert!(sig.is_some());
2525        let sig = sig.unwrap();
2526        assert!(sig.contains("function greet"));
2527        assert!(sig.contains("name"));
2528    }
2529
2530    #[test]
2531    fn test_extract_visibility_python_public() {
2532        let code = "def public_func():\n    pass";
2533        let mut parser = tree_sitter::Parser::new();
2534        parser
2535            .set_language(&tree_sitter_python::LANGUAGE.into())
2536            .unwrap();
2537        let tree = parser.parse(code, None).unwrap();
2538        let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2539
2540        let vis = extract_visibility(func_node, code, Language::Python);
2541        assert_eq!(vis, Visibility::Public);
2542    }
2543
2544    #[test]
2545    fn test_extract_visibility_python_private() {
2546        let code = "def __private_func():\n    pass";
2547        let mut parser = tree_sitter::Parser::new();
2548        parser
2549            .set_language(&tree_sitter_python::LANGUAGE.into())
2550            .unwrap();
2551        let tree = parser.parse(code, None).unwrap();
2552        let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2553
2554        let vis = extract_visibility(func_node, code, Language::Python);
2555        assert_eq!(vis, Visibility::Private);
2556    }
2557
2558    #[test]
2559    fn test_extract_visibility_python_protected() {
2560        let code = "def _protected_func():\n    pass";
2561        let mut parser = tree_sitter::Parser::new();
2562        parser
2563            .set_language(&tree_sitter_python::LANGUAGE.into())
2564            .unwrap();
2565        let tree = parser.parse(code, None).unwrap();
2566        let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2567
2568        let vis = extract_visibility(func_node, code, Language::Python);
2569        assert_eq!(vis, Visibility::Protected);
2570    }
2571
2572    #[test]
2573    fn test_extract_visibility_python_dunder() {
2574        // Note: Current implementation treats dunder methods as public because
2575        // the check for `starts_with("__") && !ends_with("__")` excludes them from Private,
2576        // and `starts_with('_')` is checked in an else-if, not reached for true dunders
2577        let code = "def __init__(self):\n    pass";
2578        let mut parser = tree_sitter::Parser::new();
2579        parser
2580            .set_language(&tree_sitter_python::LANGUAGE.into())
2581            .unwrap();
2582        let tree = parser.parse(code, None).unwrap();
2583        let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2584
2585        let vis = extract_visibility(func_node, code, Language::Python);
2586        // __init__ starts with _ so hits the else-if branch, returning Protected
2587        // This is the actual behavior - dunder methods are treated as Protected
2588        assert_eq!(vis, Visibility::Protected);
2589    }
2590
2591    #[test]
2592    fn test_extract_visibility_rust_pub() {
2593        let code = "pub fn public_func() {}";
2594        let mut parser = tree_sitter::Parser::new();
2595        parser
2596            .set_language(&tree_sitter_rust::LANGUAGE.into())
2597            .unwrap();
2598        let tree = parser.parse(code, None).unwrap();
2599        let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2600
2601        let vis = extract_visibility(func_node, code, Language::Rust);
2602        assert_eq!(vis, Visibility::Public);
2603    }
2604
2605    #[test]
2606    fn test_extract_visibility_rust_private() {
2607        let code = "fn private_func() {}";
2608        let mut parser = tree_sitter::Parser::new();
2609        parser
2610            .set_language(&tree_sitter_rust::LANGUAGE.into())
2611            .unwrap();
2612        let tree = parser.parse(code, None).unwrap();
2613        let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2614
2615        let vis = extract_visibility(func_node, code, Language::Rust);
2616        assert_eq!(vis, Visibility::Private);
2617    }
2618
2619    #[test]
2620    fn test_extract_visibility_rust_pub_crate() {
2621        let code = "pub(crate) fn crate_func() {}";
2622        let mut parser = tree_sitter::Parser::new();
2623        parser
2624            .set_language(&tree_sitter_rust::LANGUAGE.into())
2625            .unwrap();
2626        let tree = parser.parse(code, None).unwrap();
2627        let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2628
2629        let vis = extract_visibility(func_node, code, Language::Rust);
2630        assert_eq!(vis, Visibility::Internal);
2631    }
2632
2633    #[test]
2634    fn test_extract_visibility_go_exported() {
2635        let code = "func Exported() {}";
2636        let mut parser = tree_sitter::Parser::new();
2637        parser
2638            .set_language(&tree_sitter_go::LANGUAGE.into())
2639            .unwrap();
2640        let tree = parser.parse(code, None).unwrap();
2641        let func_node = find_node_in_tree(tree.root_node(), "function_declaration").unwrap();
2642
2643        let vis = extract_visibility(func_node, code, Language::Go);
2644        assert_eq!(vis, Visibility::Public);
2645    }
2646
2647    #[test]
2648    fn test_extract_visibility_go_unexported() {
2649        let code = "func unexported() {}";
2650        let mut parser = tree_sitter::Parser::new();
2651        parser
2652            .set_language(&tree_sitter_go::LANGUAGE.into())
2653            .unwrap();
2654        let tree = parser.parse(code, None).unwrap();
2655        let func_node = find_node_in_tree(tree.root_node(), "function_declaration").unwrap();
2656
2657        let vis = extract_visibility(func_node, code, Language::Go);
2658        assert_eq!(vis, Visibility::Private);
2659    }
2660
2661    #[test]
2662    fn test_extract_visibility_bash_always_public() {
2663        let code = "my_func() { echo hello; }";
2664        let mut parser = tree_sitter::Parser::new();
2665        parser
2666            .set_language(&tree_sitter_bash::LANGUAGE.into())
2667            .unwrap();
2668        let tree = parser.parse(code, None).unwrap();
2669        let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2670
2671        let vis = extract_visibility(func_node, code, Language::Bash);
2672        assert_eq!(vis, Visibility::Public);
2673    }
2674
2675    #[test]
2676    fn test_find_body_node_python() {
2677        let code = "def foo():\n    x = 1\n    return x";
2678        let mut parser = tree_sitter::Parser::new();
2679        parser
2680            .set_language(&tree_sitter_python::LANGUAGE.into())
2681            .unwrap();
2682        let tree = parser.parse(code, None).unwrap();
2683        let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2684
2685        let body = find_body_node(func_node, Language::Python);
2686        assert!(body.is_some());
2687        assert_eq!(body.unwrap().kind(), "block");
2688    }
2689
2690    #[test]
2691    fn test_find_body_node_rust() {
2692        let code = "fn foo() { let x = 1; x }";
2693        let mut parser = tree_sitter::Parser::new();
2694        parser
2695            .set_language(&tree_sitter_rust::LANGUAGE.into())
2696            .unwrap();
2697        let tree = parser.parse(code, None).unwrap();
2698        let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2699
2700        let body = find_body_node(func_node, Language::Rust);
2701        assert!(body.is_some());
2702        assert_eq!(body.unwrap().kind(), "block");
2703    }
2704
2705    #[test]
2706    fn test_find_body_node_javascript() {
2707        let code = "function foo() { return 1; }";
2708        let mut parser = tree_sitter::Parser::new();
2709        parser
2710            .set_language(&tree_sitter_javascript::LANGUAGE.into())
2711            .unwrap();
2712        let tree = parser.parse(code, None).unwrap();
2713        let func_node = find_node_in_tree(tree.root_node(), "function_declaration").unwrap();
2714
2715        let body = find_body_node(func_node, Language::JavaScript);
2716        assert!(body.is_some());
2717        assert_eq!(body.unwrap().kind(), "statement_block");
2718    }
2719
2720    #[test]
2721    fn test_extract_calls_python() {
2722        let code = "def foo():\n    bar()\n    custom_func(1, 2)";
2723        let mut parser = tree_sitter::Parser::new();
2724        parser
2725            .set_language(&tree_sitter_python::LANGUAGE.into())
2726            .unwrap();
2727        let tree = parser.parse(code, None).unwrap();
2728        let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2729
2730        let calls = extract_calls(func_node, code, Language::Python);
2731        assert!(calls.contains(&"bar".to_owned()));
2732        assert!(calls.contains(&"custom_func".to_owned()));
2733    }
2734
2735    #[test]
2736    fn test_extract_calls_python_filters_builtins() {
2737        let code = "def foo():\n    print('hello')\n    len([1,2,3])";
2738        let mut parser = tree_sitter::Parser::new();
2739        parser
2740            .set_language(&tree_sitter_python::LANGUAGE.into())
2741            .unwrap();
2742        let tree = parser.parse(code, None).unwrap();
2743        let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2744
2745        let calls = extract_calls(func_node, code, Language::Python);
2746        // Built-ins should be filtered out
2747        assert!(!calls.contains(&"print".to_owned()));
2748        assert!(!calls.contains(&"len".to_owned()));
2749    }
2750
2751    #[test]
2752    fn test_extract_calls_rust() {
2753        let code = "fn foo() { bar(); baz(1); }";
2754        let mut parser = tree_sitter::Parser::new();
2755        parser
2756            .set_language(&tree_sitter_rust::LANGUAGE.into())
2757            .unwrap();
2758        let tree = parser.parse(code, None).unwrap();
2759        let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2760
2761        let calls = extract_calls(func_node, code, Language::Rust);
2762        assert!(calls.contains(&"bar".to_owned()));
2763        assert!(calls.contains(&"baz".to_owned()));
2764    }
2765
2766    #[test]
2767    fn test_extract_docstring_rust() {
2768        let code = "/// This is a doc comment\nfn foo() {}";
2769        let mut parser = tree_sitter::Parser::new();
2770        parser
2771            .set_language(&tree_sitter_rust::LANGUAGE.into())
2772            .unwrap();
2773        let tree = parser.parse(code, None).unwrap();
2774        let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2775
2776        let docstring = extract_docstring(func_node, code, Language::Rust);
2777        assert!(docstring.is_some());
2778        assert!(docstring.unwrap().contains("This is a doc comment"));
2779    }
2780
2781    #[test]
2782    fn test_extract_docstring_rust_multiline() {
2783        let code = "/// Line 1\n/// Line 2\nfn foo() {}";
2784        let mut parser = tree_sitter::Parser::new();
2785        parser
2786            .set_language(&tree_sitter_rust::LANGUAGE.into())
2787            .unwrap();
2788        let tree = parser.parse(code, None).unwrap();
2789        let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2790
2791        let docstring = extract_docstring(func_node, code, Language::Rust);
2792        assert!(docstring.is_some());
2793        let doc = docstring.unwrap();
2794        assert!(doc.contains("Line 1"));
2795        assert!(doc.contains("Line 2"));
2796    }
2797
2798    // ==========================================================================
2799    // Zig call extraction tests
2800    // ==========================================================================
2801
2802    /// Helper: parse Zig code and collect all calls from the root node.
2803    fn collect_zig_calls(code: &str) -> HashSet<String> {
2804        let mut parser = tree_sitter::Parser::new();
2805        parser
2806            .set_language(&tree_sitter_zig::LANGUAGE.into())
2807            .expect("Error loading Zig grammar");
2808        let tree = parser.parse(code, None).expect("Failed to parse Zig code");
2809        let mut calls = HashSet::new();
2810        collect_calls_recursive(tree.root_node(), code, Language::Zig, &mut calls);
2811        calls
2812    }
2813
2814    #[test]
2815    fn test_zig_simple_call_extracted() {
2816        let code = r#"
2817fn main() void {
2818    const r = add(1, 2);
2819}
2820"#;
2821        let calls = collect_zig_calls(code);
2822        assert!(calls.contains("add"), "Expected 'add' in calls, got: {:?}", calls);
2823    }
2824
2825    #[test]
2826    fn test_zig_member_call_extracted() {
2827        let code = r#"
2828const std = @import("std");
2829fn main() void {
2830    std.debug.print("hi", .{});
2831}
2832"#;
2833        let calls = collect_zig_calls(code);
2834        assert!(calls.contains("print"), "Expected 'print' in calls, got: {:?}", calls);
2835    }
2836
2837    #[test]
2838    fn test_zig_no_false_positives_on_variable_declaration() {
2839        let code = r#"
2840fn main() void {
2841    const x = 42;
2842}
2843"#;
2844        let calls = collect_zig_calls(code);
2845        assert!(
2846            calls.is_empty(),
2847            "Expected no calls from a simple variable declaration, got: {:?}",
2848            calls
2849        );
2850    }
2851
2852    // ==========================================================================
2853    // Dart call extraction tests
2854    // ==========================================================================
2855
2856    /// Helper: parse Dart code and collect all calls from the root node.
2857    fn collect_dart_calls(code: &str) -> HashSet<String> {
2858        let mut parser = tree_sitter::Parser::new();
2859        parser
2860            .set_language(&tree_sitter_dart_orchard::LANGUAGE.into())
2861            .expect("Error loading Dart grammar");
2862        let tree = parser.parse(code, None).expect("Failed to parse Dart code");
2863        let mut calls = HashSet::new();
2864        collect_calls_recursive(tree.root_node(), code, Language::Dart, &mut calls);
2865        calls
2866    }
2867
2868    #[test]
2869    fn test_dart_simple_call_extracted() {
2870        let code = r#"
2871void main() {
2872  print('hello');
2873}
2874"#;
2875        let calls = collect_dart_calls(code);
2876        assert!(calls.contains("print"), "Expected 'print' in calls, got: {:?}", calls);
2877    }
2878
2879    #[test]
2880    fn test_dart_method_call_extracted() {
2881        let code = r#"
2882void main() {
2883  myObj.doSomething();
2884}
2885"#;
2886        let calls = collect_dart_calls(code);
2887        assert!(calls.contains("doSomething"), "Expected 'doSomething' in calls, got: {:?}", calls);
2888    }
2889
2890    #[test]
2891    fn test_dart_no_false_positives_on_variable_declaration() {
2892        let code = r#"
2893void main() {
2894  var x = 42;
2895}
2896"#;
2897        let calls = collect_dart_calls(code);
2898        assert!(
2899            calls.is_empty(),
2900            "Expected no calls from a simple variable declaration, got: {:?}",
2901            calls
2902        );
2903    }
2904
2905    // ==========================================================================
2906    // Dart inheritance extraction tests
2907    // ==========================================================================
2908
2909    /// Helper: parse Dart code and return symbols
2910    fn parse_dart_symbols(code: &str) -> Vec<crate::types::Symbol> {
2911        let mut parser = super::super::core::Parser::new();
2912        parser.parse(code, Language::Dart).unwrap_or_default()
2913    }
2914
2915    #[test]
2916    fn test_dart_inheritance_extends() {
2917        let code = r#"
2918class Animal {
2919  void speak() {}
2920}
2921
2922class Dog extends Animal {
2923  void speak() {}
2924}
2925"#;
2926        let symbols = parse_dart_symbols(code);
2927        let dog = symbols
2928            .iter()
2929            .find(|s| s.name == "Dog")
2930            .expect("Dog class not found");
2931        assert_eq!(dog.extends.as_deref(), Some("Animal"));
2932        assert!(dog.implements.is_empty());
2933    }
2934
2935    #[test]
2936    fn test_dart_inheritance_implements() {
2937        let code = r#"
2938class Serializable {
2939  void serialize() {}
2940}
2941
2942class Printable {
2943  void print() {}
2944}
2945
2946class Document implements Serializable, Printable {
2947  void serialize() {}
2948  void print() {}
2949}
2950"#;
2951        let symbols = parse_dart_symbols(code);
2952        let doc = symbols
2953            .iter()
2954            .find(|s| s.name == "Document")
2955            .expect("Document class not found");
2956        assert!(doc.extends.is_none());
2957        assert!(doc.implements.contains(&"Serializable".to_owned()));
2958        assert!(doc.implements.contains(&"Printable".to_owned()));
2959        assert_eq!(doc.implements.len(), 2);
2960    }
2961
2962    #[test]
2963    fn test_dart_inheritance_with_mixins() {
2964        let code = r#"
2965mixin Swimming {
2966  void swim() {}
2967}
2968
2969mixin Flying {
2970  void fly() {}
2971}
2972
2973class Duck with Swimming, Flying {
2974  void quack() {}
2975}
2976"#;
2977        let symbols = parse_dart_symbols(code);
2978        let duck = symbols
2979            .iter()
2980            .find(|s| s.name == "Duck")
2981            .expect("Duck class not found");
2982        assert!(duck.extends.is_none());
2983        // Mixins are treated as implements
2984        assert!(duck.implements.contains(&"Swimming".to_owned()));
2985        assert!(duck.implements.contains(&"Flying".to_owned()));
2986        assert_eq!(duck.implements.len(), 2);
2987    }
2988
2989    #[test]
2990    fn test_dart_inheritance_combined() {
2991        let code = r#"
2992class Animal {
2993  void speak() {}
2994}
2995
2996mixin Swimming {
2997  void swim() {}
2998}
2999
3000class Walkable {
3001  void walk() {}
3002}
3003
3004class Duck extends Animal with Swimming implements Walkable {
3005  void quack() {}
3006}
3007"#;
3008        let symbols = parse_dart_symbols(code);
3009        let duck = symbols
3010            .iter()
3011            .find(|s| s.name == "Duck")
3012            .expect("Duck class not found");
3013        assert_eq!(duck.extends.as_deref(), Some("Animal"));
3014        // Both mixins and implements are in the implements list
3015        assert!(duck.implements.contains(&"Swimming".to_owned()));
3016        assert!(duck.implements.contains(&"Walkable".to_owned()));
3017        assert_eq!(duck.implements.len(), 2);
3018    }
3019
3020    #[test]
3021    fn test_dart_inheritance_no_inheritance() {
3022        let code = r#"
3023class SimpleClass {
3024  void doSomething() {}
3025}
3026"#;
3027        let symbols = parse_dart_symbols(code);
3028        let cls = symbols
3029            .iter()
3030            .find(|s| s.name == "SimpleClass")
3031            .expect("SimpleClass not found");
3032        assert!(cls.extends.is_none());
3033        assert!(cls.implements.is_empty());
3034    }
3035}