Skip to main content

tldr_core/ast/
extractor.rs

1//! Code structure extraction (spec Section 2.1.2)
2//!
3//! Extracts functions, classes, methods, and imports from source files.
4
5use std::collections::HashSet;
6use std::path::Path;
7
8use tree_sitter::{Node, Tree};
9
10use crate::fs::tree::{collect_files, get_file_tree};
11use crate::types::{CodeStructure, DefinitionInfo, FileStructure, IgnoreSpec, Language};
12use crate::TldrResult;
13
14use super::extract::is_upper_case_name;
15use super::imports::extract_imports_from_tree;
16use super::parser::parse_file;
17
18/// Extract code structure from all files in a directory.
19///
20/// # Arguments
21/// * `root` - Root directory to scan
22/// * `language` - Programming language to extract
23/// * `max_results` - Maximum number of files (0 = unlimited)
24/// * `ignore_spec` - Optional ignore patterns
25///
26/// # Edge Cases (per spec)
27/// - Syntax error in file: Skip file, continue with others
28/// - Binary file: Skip silently
29/// - Encoding error: UTF-8 lossy fallback
30/// - Empty file: Include with empty lists
31pub fn get_code_structure(
32    root: &Path,
33    language: Language,
34    max_results: usize,
35    ignore_spec: Option<&IgnoreSpec>,
36) -> TldrResult<CodeStructure> {
37    // Handle single file case: extract structure directly
38    if root.is_file() {
39        let parent = root.parent().unwrap_or(root);
40        match extract_file_structure(root, parent, language) {
41            Ok(structure) => {
42                return Ok(CodeStructure {
43                    root: root.to_path_buf(),
44                    language,
45                    files: vec![structure],
46                });
47            }
48            Err(e) => {
49                return Err(e);
50            }
51        }
52    }
53
54    // Get file tree filtered by language extensions
55    let extensions: HashSet<String> = language
56        .extensions()
57        .iter()
58        .map(|s| s.to_string())
59        .collect();
60
61    let tree = get_file_tree(root, Some(&extensions), true, ignore_spec)?;
62    let files = collect_files(&tree, root);
63
64    let mut file_structures = Vec::new();
65
66    for file_path in files {
67        // Apply max_results limit
68        if max_results > 0 && file_structures.len() >= max_results {
69            break;
70        }
71
72        // Try to extract structure, skip on error (per spec edge case handling)
73        match extract_file_structure(&file_path, root, language) {
74            Ok(structure) => file_structures.push(structure),
75            Err(e) => {
76                // Log error but continue - recoverable errors per spec
77                if e.is_recoverable() {
78                    eprintln!("Warning: Skipping {} - {}", file_path.display(), e);
79                } else {
80                    return Err(e);
81                }
82            }
83        }
84    }
85
86    Ok(CodeStructure {
87        root: root.to_path_buf(),
88        language,
89        files: file_structures,
90    })
91}
92
93/// Extract structure from a single file
94fn extract_file_structure(
95    path: &Path,
96    root: &Path,
97    language: Language,
98) -> TldrResult<FileStructure> {
99    let (tree, source, _) = parse_file(path)?;
100
101    let relative_path = path.strip_prefix(root).unwrap_or(path).to_path_buf();
102
103    let functions = extract_functions(&tree, &source, language);
104    let classes = extract_classes(&tree, &source, language);
105    let methods = extract_methods(&tree, &source, language);
106    let imports = extract_imports_from_tree(&tree, &source, language)?;
107    let definitions = extract_definitions(&tree, &source, language);
108
109    Ok(FileStructure {
110        path: relative_path,
111        functions,
112        classes,
113        methods,
114        imports,
115        definitions,
116    })
117}
118
119/// Extract function names from a syntax tree
120pub fn extract_functions(tree: &Tree, source: &str, language: Language) -> Vec<String> {
121    let mut functions = Vec::new();
122    let root = tree.root_node();
123
124    match language {
125        Language::Python => extract_python_functions(&root, source, &mut functions, false),
126        Language::TypeScript | Language::JavaScript => {
127            extract_ts_functions(&root, source, &mut functions, false)
128        }
129        Language::Go => extract_go_functions(&root, source, &mut functions),
130        Language::Rust => extract_rust_functions(&root, source, &mut functions),
131        Language::Java => extract_java_functions(&root, source, &mut functions, false),
132        Language::C => extract_c_functions(&root, source, &mut functions),
133        Language::Cpp => extract_cpp_functions(&root, source, &mut functions),
134        Language::Ruby => extract_ruby_functions(&root, source, &mut functions, false),
135        Language::Scala => extract_scala_functions(&root, source, &mut functions),
136        Language::Kotlin => extract_kotlin_functions(&root, source, &mut functions, false),
137        Language::Ocaml => extract_ocaml_functions(&root, source, &mut functions),
138        Language::Php => extract_php_functions(&root, source, &mut functions, false),
139        Language::Swift => extract_swift_functions(&root, source, &mut functions),
140        Language::CSharp => {} // C# has no free functions
141        Language::Elixir => extract_elixir_functions(&root, source, &mut functions),
142        Language::Lua => extract_lua_functions(&root, source, &mut functions),
143        Language::Luau => extract_luau_functions(&root, source, &mut functions),
144    }
145
146    functions
147}
148
149/// Extract class names from a syntax tree
150pub fn extract_classes(tree: &Tree, source: &str, language: Language) -> Vec<String> {
151    let mut classes = Vec::new();
152    let root = tree.root_node();
153
154    match language {
155        Language::Python => extract_python_classes(&root, source, &mut classes),
156        Language::TypeScript | Language::JavaScript => {
157            extract_ts_classes(&root, source, &mut classes)
158        }
159        Language::Go => extract_go_structs(&root, source, &mut classes),
160        Language::Rust => extract_rust_structs(&root, source, &mut classes),
161        Language::Java => extract_java_classes(&root, source, &mut classes),
162        Language::C => extract_c_structs(&root, source, &mut classes),
163        Language::Cpp => extract_cpp_classes(&root, source, &mut classes),
164        Language::Ruby => extract_ruby_classes(&root, source, &mut classes),
165        Language::Scala => extract_scala_classes(&root, source, &mut classes),
166        Language::Kotlin => extract_kotlin_classes(&root, source, &mut classes),
167        Language::Php => extract_php_classes(&root, source, &mut classes),
168        Language::Swift => extract_swift_classes(&root, source, &mut classes),
169        Language::CSharp => extract_csharp_classes(&root, source, &mut classes),
170        Language::Elixir => extract_elixir_classes(&root, source, &mut classes),
171        Language::Lua => {}  // Lua has no native classes
172        Language::Luau => {} // Luau has no native classes
173        _ => {}
174    }
175
176    classes
177}
178
179/// Extract method names (methods inside classes) from a syntax tree
180pub fn extract_methods(tree: &Tree, source: &str, language: Language) -> Vec<String> {
181    let mut methods = Vec::new();
182    let root = tree.root_node();
183
184    match language {
185        Language::Python => extract_python_functions(&root, source, &mut methods, true),
186        Language::TypeScript | Language::JavaScript => {
187            extract_ts_functions(&root, source, &mut methods, true)
188        }
189        Language::Go => {} // Go methods are extracted as functions with receivers
190        Language::Rust => extract_rust_impl_methods(&root, source, &mut methods),
191        Language::Java => extract_java_functions(&root, source, &mut methods, true),
192        Language::C => {} // C has no methods
193        Language::Cpp => extract_cpp_methods(&root, source, &mut methods),
194        Language::Ruby => extract_ruby_functions(&root, source, &mut methods, true),
195        Language::Scala => extract_scala_methods(&root, source, &mut methods),
196        Language::Kotlin => extract_kotlin_functions(&root, source, &mut methods, true),
197        Language::Php => extract_php_functions(&root, source, &mut methods, true),
198        Language::Swift => extract_swift_methods(&root, source, &mut methods),
199        Language::CSharp => extract_csharp_methods(&root, source, &mut methods),
200        Language::Elixir => {} // Elixir has no methods (modules are not OOP classes)
201        Language::Lua => {}    // Lua has no methods
202        Language::Luau => {}   // Luau has no methods
203        _ => {}
204    }
205
206    methods
207}
208
209// =============================================================================
210// Python extraction
211// =============================================================================
212
213fn extract_python_functions(
214    node: &Node,
215    source: &str,
216    functions: &mut Vec<String>,
217    methods_only: bool,
218) {
219    let mut cursor = node.walk();
220
221    for child in node.children(&mut cursor) {
222        match child.kind() {
223            "function_definition" => {
224                // Check if inside a class
225                let is_method = is_inside_class(&child);
226
227                if methods_only == is_method {
228                    if let Some(name_node) = child.child_by_field_name("name") {
229                        let name = get_node_text(&name_node, source);
230                        functions.push(name);
231                    }
232                }
233            }
234            "class_definition" => {
235                // Recurse into class body for methods
236                if let Some(body) = child.child_by_field_name("body") {
237                    extract_python_functions(&body, source, functions, methods_only);
238                }
239            }
240            _ => {
241                // Recurse into other nodes
242                extract_python_functions(&child, source, functions, methods_only);
243            }
244        }
245    }
246}
247
248fn extract_python_classes(node: &Node, source: &str, classes: &mut Vec<String>) {
249    let mut cursor = node.walk();
250
251    for child in node.children(&mut cursor) {
252        if child.kind() == "class_definition" {
253            if let Some(name_node) = child.child_by_field_name("name") {
254                let name = get_node_text(&name_node, source);
255                classes.push(name);
256            }
257        }
258        extract_python_classes(&child, source, classes);
259    }
260}
261
262// =============================================================================
263// TypeScript/JavaScript extraction
264// =============================================================================
265
266fn extract_ts_functions(
267    node: &Node,
268    source: &str,
269    functions: &mut Vec<String>,
270    methods_only: bool,
271) {
272    let mut cursor = node.walk();
273
274    for child in node.children(&mut cursor) {
275        match child.kind() {
276            "function_declaration"
277            | "function"
278            | "generator_function_declaration"
279            | "generator_function" => {
280                if !methods_only {
281                    if let Some(name_node) = child.child_by_field_name("name") {
282                        let name = get_node_text(&name_node, source);
283                        functions.push(name);
284                    }
285                }
286            }
287            "function_expression" => {
288                // Named function expression: const x = function name() {}
289                // Use variable name if inside a variable_declarator, else the function's own name
290                if !methods_only {
291                    let mut extracted = false;
292                    if let Some(parent) = child.parent() {
293                        if parent.kind() == "variable_declarator" {
294                            if let Some(name_node) = parent.child_by_field_name("name") {
295                                let name = get_node_text(&name_node, source);
296                                functions.push(name);
297                                extracted = true;
298                            }
299                        }
300                    }
301                    if !extracted {
302                        if let Some(name_node) = child.child_by_field_name("name") {
303                            let name = get_node_text(&name_node, source);
304                            functions.push(name);
305                        }
306                    }
307                }
308            }
309            "method_definition" | "method_signature" | "abstract_method_signature" => {
310                // VAL-001: `method_signature` covers interface methods and
311                // abstract class signatures in some grammar versions;
312                // `abstract_method_signature` covers `abstract foo(): void;`
313                // inside `abstract class` bodies in tree-sitter-typescript.
314                // Both expose the name via the "name" field (property_identifier).
315                if methods_only {
316                    if let Some(name_node) = child.child_by_field_name("name") {
317                        let name = get_node_text(&name_node, source);
318                        functions.push(name);
319                    }
320                }
321            }
322            "arrow_function" => {
323                // Arrow functions assigned to variables
324                if !methods_only {
325                    if let Some(parent) = child.parent() {
326                        if parent.kind() == "variable_declarator" {
327                            if let Some(name_node) = parent.child_by_field_name("name") {
328                                let name = get_node_text(&name_node, source);
329                                functions.push(name);
330                            }
331                        }
332                    }
333                }
334            }
335            "class_declaration" | "class" => {
336                // Recurse into class body for methods
337                if let Some(body) = child.child_by_field_name("body") {
338                    extract_ts_functions(&body, source, functions, methods_only);
339                }
340            }
341            _ => {
342                extract_ts_functions(&child, source, functions, methods_only);
343            }
344        }
345    }
346}
347
348fn extract_ts_classes(node: &Node, source: &str, classes: &mut Vec<String>) {
349    let mut cursor = node.walk();
350
351    for child in node.children(&mut cursor) {
352        if child.kind() == "class_declaration" || child.kind() == "class" {
353            if let Some(name_node) = child.child_by_field_name("name") {
354                let name = get_node_text(&name_node, source);
355                classes.push(name);
356            }
357        }
358        extract_ts_classes(&child, source, classes);
359    }
360}
361
362// =============================================================================
363// Go extraction
364// =============================================================================
365
366fn extract_go_functions(node: &Node, source: &str, functions: &mut Vec<String>) {
367    let mut cursor = node.walk();
368
369    for child in node.children(&mut cursor) {
370        if child.kind() == "function_declaration" || child.kind() == "method_declaration" {
371            if let Some(name_node) = child.child_by_field_name("name") {
372                let name = get_node_text(&name_node, source);
373                functions.push(name);
374            }
375        }
376        extract_go_functions(&child, source, functions);
377    }
378}
379
380/// Extract Go struct types as classes.
381/// Go uses `type_declaration` containing `type_spec` with a `struct_type` body.
382fn extract_go_structs(node: &Node, source: &str, structs: &mut Vec<String>) {
383    let mut cursor = node.walk();
384
385    for child in node.children(&mut cursor) {
386        if child.kind() == "type_declaration" {
387            // type_declaration contains one or more type_spec children
388            let mut inner_cursor = child.walk();
389            for inner in child.children(&mut inner_cursor) {
390                if inner.kind() == "type_spec" {
391                    // Check if it's a struct type (has struct_type child)
392                    let mut has_struct = false;
393                    let mut type_name = None;
394                    let mut spec_cursor = inner.walk();
395                    for spec_child in inner.children(&mut spec_cursor) {
396                        if spec_child.kind() == "type_identifier" {
397                            type_name = Some(get_node_text(&spec_child, source));
398                        }
399                        if spec_child.kind() == "struct_type"
400                            || spec_child.kind() == "interface_type"
401                        {
402                            has_struct = true;
403                        }
404                    }
405                    if has_struct {
406                        if let Some(name) = type_name {
407                            structs.push(name);
408                        }
409                    }
410                }
411            }
412        }
413        extract_go_structs(&child, source, structs);
414    }
415}
416
417// =============================================================================
418// Rust extraction
419// =============================================================================
420
421fn extract_rust_functions(node: &Node, source: &str, functions: &mut Vec<String>) {
422    let mut cursor = node.walk();
423
424    for child in node.children(&mut cursor) {
425        if child.kind() == "function_item" {
426            // Only top-level functions (not inside impl blocks)
427            if !is_inside_impl(&child) {
428                if let Some(name_node) = child.child_by_field_name("name") {
429                    let name = get_node_text(&name_node, source);
430                    functions.push(name);
431                }
432            }
433        }
434        extract_rust_functions(&child, source, functions);
435    }
436}
437
438fn extract_rust_structs(node: &Node, source: &str, structs: &mut Vec<String>) {
439    let mut cursor = node.walk();
440
441    for child in node.children(&mut cursor) {
442        if child.kind() == "struct_item"
443            || child.kind() == "enum_item"
444            || child.kind() == "trait_item"
445        {
446            if let Some(name_node) = child.child_by_field_name("name") {
447                let name = get_node_text(&name_node, source);
448                structs.push(name);
449            }
450        }
451        extract_rust_structs(&child, source, structs);
452    }
453}
454
455fn extract_rust_impl_methods(node: &Node, source: &str, methods: &mut Vec<String>) {
456    let mut cursor = node.walk();
457
458    for child in node.children(&mut cursor) {
459        if child.kind() == "impl_item" {
460            // Get methods from impl block
461            if let Some(body) = child.child_by_field_name("body") {
462                let mut body_cursor = body.walk();
463                for item in body.children(&mut body_cursor) {
464                    if item.kind() == "function_item" {
465                        if let Some(name_node) = item.child_by_field_name("name") {
466                            let name = get_node_text(&name_node, source);
467                            methods.push(name);
468                        }
469                    }
470                }
471            }
472        } else if child.kind() == "trait_item" {
473            // Get methods from trait definition (declaration_list body)
474            let mut trait_cursor = child.walk();
475            for trait_child in child.children(&mut trait_cursor) {
476                if trait_child.kind() == "declaration_list" {
477                    let mut body_cursor = trait_child.walk();
478                    for item in trait_child.children(&mut body_cursor) {
479                        if item.kind() == "function_item"
480                            || item.kind() == "function_signature_item"
481                        {
482                            if let Some(name_node) = item.child_by_field_name("name") {
483                                let name = get_node_text(&name_node, source);
484                                methods.push(name);
485                            }
486                        }
487                    }
488                }
489            }
490        }
491        extract_rust_impl_methods(&child, source, methods);
492    }
493}
494
495// =============================================================================
496// Java extraction
497// =============================================================================
498
499fn extract_java_functions(
500    node: &Node,
501    source: &str,
502    functions: &mut Vec<String>,
503    methods_only: bool,
504) {
505    let mut cursor = node.walk();
506
507    for child in node.children(&mut cursor) {
508        match child.kind() {
509            "method_declaration" => {
510                let is_in_class = is_inside_class(&child);
511                if methods_only == is_in_class {
512                    if let Some(name_node) = child.child_by_field_name("name") {
513                        let name = get_node_text(&name_node, source);
514                        functions.push(name);
515                    }
516                }
517            }
518            "constructor_declaration" => {
519                // Constructors are always inside a class, so they are methods
520                if methods_only {
521                    if let Some(name_node) = child.child_by_field_name("name") {
522                        let name = get_node_text(&name_node, source);
523                        functions.push(name);
524                    }
525                }
526            }
527            _ => {}
528        }
529        extract_java_functions(&child, source, functions, methods_only);
530    }
531}
532
533fn extract_java_classes(node: &Node, source: &str, classes: &mut Vec<String>) {
534    let mut cursor = node.walk();
535
536    for child in node.children(&mut cursor) {
537        if child.kind() == "class_declaration" || child.kind() == "interface_declaration" {
538            if let Some(name_node) = child.child_by_field_name("name") {
539                let name = get_node_text(&name_node, source);
540                classes.push(name);
541            }
542        }
543        extract_java_classes(&child, source, classes);
544    }
545}
546
547// =============================================================================
548// C extraction
549// =============================================================================
550
551fn extract_c_functions(node: &Node, source: &str, functions: &mut Vec<String>) {
552    let mut cursor = node.walk();
553
554    for child in node.children(&mut cursor) {
555        if child.kind() == "function_definition" {
556            // Get the declarator which contains the function name
557            if let Some(declarator) = child.child_by_field_name("declarator") {
558                if let Some(name) = extract_c_function_name(&declarator, source) {
559                    functions.push(name);
560                }
561            }
562        }
563        extract_c_functions(&child, source, functions);
564    }
565}
566
567/// Extract function name from a C declarator node
568/// Handles: function_declarator, pointer_declarator wrapping function_declarator
569fn extract_c_function_name(node: &Node, source: &str) -> Option<String> {
570    match node.kind() {
571        "function_declarator" => {
572            // Direct function declarator - get the declarator field which is the identifier
573            if let Some(declarator) = node.child_by_field_name("declarator") {
574                if declarator.kind() == "identifier" {
575                    return Some(get_node_text(&declarator, source));
576                } else if declarator.kind() == "parenthesized_declarator" {
577                    // Handle (*func_ptr)(args) style
578                    return extract_c_function_name(&declarator, source);
579                }
580            }
581        }
582        "pointer_declarator" => {
583            // int *foo() - recurse into the declarator
584            if let Some(declarator) = node.child_by_field_name("declarator") {
585                return extract_c_function_name(&declarator, source);
586            }
587        }
588        "identifier" => {
589            return Some(get_node_text(node, source));
590        }
591        _ => {
592            // Try children
593            let mut cursor = node.walk();
594            for child in node.children(&mut cursor) {
595                if let Some(name) = extract_c_function_name(&child, source) {
596                    return Some(name);
597                }
598            }
599        }
600    }
601    None
602}
603
604fn extract_c_structs(node: &Node, source: &str, structs: &mut Vec<String>) {
605    let mut cursor = node.walk();
606
607    for child in node.children(&mut cursor) {
608        match child.kind() {
609            "struct_specifier" | "enum_specifier" => {
610                // Named struct/enum: struct Foo { ... }
611                // Require a body field so we don't emit bare parameter type
612                // references (`void foo(struct Bar *b)`) or forward
613                // declarations (`struct Bar;`) as struct definitions.
614                if child.child_by_field_name("body").is_some() {
615                    if let Some(name_node) = child.child_by_field_name("name") {
616                        let name = get_node_text(&name_node, source);
617                        structs.push(name);
618                    }
619                }
620            }
621            "type_definition" => {
622                // typedef struct { ... } Name;
623                // The type_definition contains a struct_specifier (anonymous) and a type_identifier
624                let mut has_struct_or_enum = false;
625                let mut typedef_name = None;
626                let mut inner_cursor = child.walk();
627                for inner in child.children(&mut inner_cursor) {
628                    // Only count as a new struct/enum definition when the
629                    // inner specifier actually has a body. `typedef struct
630                    // Existing OtherName;` references an existing type and
631                    // must not register `OtherName` as a new struct.
632                    if (inner.kind() == "struct_specifier" || inner.kind() == "enum_specifier")
633                        && inner.child_by_field_name("body").is_some()
634                    {
635                        has_struct_or_enum = true;
636                    }
637                    if inner.kind() == "type_identifier" {
638                        typedef_name = Some(get_node_text(&inner, source));
639                    }
640                }
641                if has_struct_or_enum {
642                    if let Some(name) = typedef_name {
643                        structs.push(name);
644                    }
645                }
646            }
647            _ => {}
648        }
649        extract_c_structs(&child, source, structs);
650    }
651}
652
653// =============================================================================
654// C++ extraction
655// =============================================================================
656
657fn extract_cpp_functions(node: &Node, source: &str, functions: &mut Vec<String>) {
658    let mut cursor = node.walk();
659
660    for child in node.children(&mut cursor) {
661        if child.kind() == "function_definition" {
662            // Only count as free function if NOT inside a class/struct body
663            if !is_inside_cpp_class(&child) {
664                if let Some(declarator) = child.child_by_field_name("declarator") {
665                    if let Some(name) = extract_cpp_function_name(&declarator, source) {
666                        functions.push(name);
667                    }
668                }
669            }
670        }
671        extract_cpp_functions(&child, source, functions);
672    }
673}
674
675/// Extract function name from a C++ declarator node
676/// Handles: function_declarator, qualified_identifier, reference_declarator
677fn extract_cpp_function_name(node: &Node, source: &str) -> Option<String> {
678    match node.kind() {
679        "function_declarator" => {
680            if let Some(declarator) = node.child_by_field_name("declarator") {
681                return extract_cpp_function_name(&declarator, source);
682            }
683        }
684        "qualified_identifier" | "scoped_identifier" => {
685            // namespace::function - get the name part
686            if let Some(name) = node.child_by_field_name("name") {
687                return Some(get_node_text(&name, source));
688            }
689            // Fallback: get full qualified name
690            return Some(get_node_text(node, source));
691        }
692        "pointer_declarator" | "reference_declarator" => {
693            if let Some(declarator) = node.child_by_field_name("declarator") {
694                return extract_cpp_function_name(&declarator, source);
695            }
696        }
697        "identifier" | "field_identifier" | "destructor_name" => {
698            return Some(get_node_text(node, source));
699        }
700        "operator_name" => {
701            // operator+ etc.
702            return Some(get_node_text(node, source));
703        }
704        _ => {
705            // Try children
706            let mut cursor = node.walk();
707            for child in node.children(&mut cursor) {
708                if let Some(name) = extract_cpp_function_name(&child, source) {
709                    return Some(name);
710                }
711            }
712        }
713    }
714    None
715}
716
717fn extract_cpp_classes(node: &Node, source: &str, classes: &mut Vec<String>) {
718    let mut cursor = node.walk();
719
720    for child in node.children(&mut cursor) {
721        match child.kind() {
722            "class_specifier" | "struct_specifier" | "enum_specifier" => {
723                // Get the name field
724                if let Some(name_node) = child.child_by_field_name("name") {
725                    let name = get_node_text(&name_node, source);
726                    classes.push(name);
727                }
728            }
729            _ => {}
730        }
731        extract_cpp_classes(&child, source, classes);
732    }
733}
734
735/// Extract C++ methods: function_definition nodes inside class/struct bodies.
736/// Only counts methods with actual bodies (function_definition), not forward
737/// declarations or `= default`/`= delete` stubs.
738fn extract_cpp_methods(node: &Node, source: &str, methods: &mut Vec<String>) {
739    let mut cursor = node.walk();
740
741    for child in node.children(&mut cursor) {
742        match child.kind() {
743            "class_specifier" | "struct_specifier" => {
744                // Look inside the class/struct body (field_declaration_list)
745                if let Some(body) = child.child_by_field_name("body") {
746                    let mut body_cursor = body.walk();
747                    for body_child in body.children(&mut body_cursor) {
748                        if body_child.kind() == "function_definition" {
749                            // Skip `= default` and `= delete` methods (no real body)
750                            if cpp_has_default_or_delete(&body_child) {
751                                continue;
752                            }
753                            // Inline method definition with body: void greet() { ... }
754                            if let Some(declarator) = body_child.child_by_field_name("declarator") {
755                                if let Some(name) = extract_cpp_function_name(&declarator, source) {
756                                    methods.push(name);
757                                }
758                            }
759                        }
760                    }
761                }
762            }
763            _ => {}
764        }
765        extract_cpp_methods(&child, source, methods);
766    }
767}
768
769/// Check if a C++ function_definition has `= default` or `= delete` (no real body)
770fn cpp_has_default_or_delete(node: &Node) -> bool {
771    let mut cursor = node.walk();
772    for child in node.children(&mut cursor) {
773        if child.kind() == "default_method_clause" || child.kind() == "delete_method_clause" {
774            return true;
775        }
776    }
777    false
778}
779
780/// Check if a C++ node is inside a class or struct specifier body
781fn is_inside_cpp_class(node: &Node) -> bool {
782    let mut current = node.parent();
783    while let Some(parent) = current {
784        match parent.kind() {
785            "class_specifier" | "struct_specifier" => return true,
786            "field_declaration_list" => {
787                // field_declaration_list is the body of a class/struct
788                if let Some(grandparent) = parent.parent() {
789                    if matches!(grandparent.kind(), "class_specifier" | "struct_specifier") {
790                        return true;
791                    }
792                }
793            }
794            "translation_unit" => return false, // Top-level
795            _ => {}
796        }
797        current = parent.parent();
798    }
799    false
800}
801
802// =============================================================================
803// Ruby extraction
804// =============================================================================
805
806/// Extract Ruby functions/methods from AST
807/// Ruby uses `method` nodes for both top-level functions and class methods.
808/// `methods_only` = false: top-level methods only
809/// `methods_only` = true: methods inside classes/modules only
810fn extract_ruby_functions(
811    node: &Node,
812    source: &str,
813    functions: &mut Vec<String>,
814    methods_only: bool,
815) {
816    let mut cursor = node.walk();
817
818    for child in node.children(&mut cursor) {
819        match child.kind() {
820            "method" => {
821                // Check if this method is inside a class or module
822                let is_method = is_inside_ruby_class_or_module(&child);
823
824                if methods_only == is_method {
825                    // Get the method name from the identifier child
826                    let mut method_cursor = child.walk();
827                    for method_child in child.children(&mut method_cursor) {
828                        if method_child.kind() == "identifier" {
829                            let name = get_node_text(&method_child, source);
830                            functions.push(name);
831                            break;
832                        }
833                    }
834                }
835            }
836            "singleton_method" => {
837                // def self.method_name or def object.method_name
838                // These are class methods (module functions)
839                if methods_only {
840                    let mut method_cursor = child.walk();
841                    for method_child in child.children(&mut method_cursor) {
842                        // The name is the second identifier (after "self" or object)
843                        if method_child.kind() == "identifier" {
844                            let name = get_node_text(&method_child, source);
845                            // Skip "self" - we want the method name
846                            if name != "self" {
847                                functions.push(name);
848                                break;
849                            }
850                        }
851                    }
852                }
853            }
854            "class" | "module" => {
855                // Recurse into class/module body for methods.
856                // In Ruby's tree-sitter grammar, `child_by_field_name("body")`
857                // returns the `body_statement` node. We must use only ONE
858                // recursion path to avoid double-counting methods.
859                let body_found = child.child_by_field_name("body");
860                if let Some(body) = body_found {
861                    extract_ruby_functions(&body, source, functions, methods_only);
862                } else {
863                    // Fallback: iterate children for body_statement
864                    let mut class_cursor = child.walk();
865                    for class_child in child.children(&mut class_cursor) {
866                        if class_child.kind() == "body_statement" {
867                            extract_ruby_functions(&class_child, source, functions, methods_only);
868                        }
869                    }
870                }
871            }
872            _ => {
873                // Recurse into other nodes
874                extract_ruby_functions(&child, source, functions, methods_only);
875            }
876        }
877    }
878}
879
880/// Extract Ruby class and module names
881fn extract_ruby_classes(node: &Node, source: &str, classes: &mut Vec<String>) {
882    let mut cursor = node.walk();
883
884    for child in node.children(&mut cursor) {
885        match child.kind() {
886            "class" | "module" => {
887                // Get the class/module name from the constant child
888                let mut class_cursor = child.walk();
889                for class_child in child.children(&mut class_cursor) {
890                    if class_child.kind() == "constant" || class_child.kind() == "scope_resolution"
891                    {
892                        let name = get_node_text(&class_child, source);
893                        classes.push(name);
894                        break;
895                    }
896                }
897            }
898            _ => {}
899        }
900        // Recurse to find nested classes/modules
901        extract_ruby_classes(&child, source, classes);
902    }
903}
904
905/// Check if a Ruby node is inside a class or module definition
906fn is_inside_ruby_class_or_module(node: &Node) -> bool {
907    let mut current = node.parent();
908    while let Some(parent) = current {
909        match parent.kind() {
910            "class" | "module" | "body_statement" => {
911                // body_statement alone isn't enough - check if its parent is class/module
912                if parent.kind() == "body_statement" {
913                    if let Some(grandparent) = parent.parent() {
914                        if grandparent.kind() == "class" || grandparent.kind() == "module" {
915                            return true;
916                        }
917                    }
918                } else {
919                    return true;
920                }
921            }
922            "program" => return false, // Top-level
923            _ => {}
924        }
925        current = parent.parent();
926    }
927    false
928}
929
930// =============================================================================
931// Scala extraction
932// =============================================================================
933
934/// Extract Scala top-level functions (def inside object, or standalone)
935/// Scala uses: function_definition for def foo(...) = ...
936fn extract_scala_functions(node: &Node, source: &str, functions: &mut Vec<String>) {
937    let mut cursor = node.walk();
938
939    for child in node.children(&mut cursor) {
940        match child.kind() {
941            "function_definition" => {
942                // Only top-level functions (in objects or at package level)
943                // Skip methods inside class/trait definitions
944                if !is_inside_scala_class_or_trait(&child) {
945                    if let Some(name_node) = child.child_by_field_name("name") {
946                        let name = get_node_text(&name_node, source);
947                        functions.push(name);
948                    }
949                }
950            }
951            "object_definition" => {
952                // Recurse into object body for functions
953                if let Some(body) = child.child_by_field_name("body") {
954                    extract_scala_functions(&body, source, functions);
955                }
956            }
957            "template_body" => {
958                // Recurse into template body
959                extract_scala_functions(&child, source, functions);
960            }
961            _ => {
962                // Recurse into other nodes
963                extract_scala_functions(&child, source, functions);
964            }
965        }
966    }
967}
968
969/// Extract Scala class and trait names (not objects -- objects are singletons, not classes)
970fn extract_scala_classes(node: &Node, source: &str, classes: &mut Vec<String>) {
971    let mut cursor = node.walk();
972
973    for child in node.children(&mut cursor) {
974        match child.kind() {
975            "class_definition" | "trait_definition" => {
976                if let Some(name_node) = child.child_by_field_name("name") {
977                    let name = get_node_text(&name_node, source);
978                    classes.push(name);
979                }
980            }
981            _ => {}
982        }
983        // Recurse to find nested classes
984        extract_scala_classes(&child, source, classes);
985    }
986}
987
988/// Extract Scala methods (functions inside class/trait)
989fn extract_scala_methods(node: &Node, source: &str, methods: &mut Vec<String>) {
990    let mut cursor = node.walk();
991
992    for child in node.children(&mut cursor) {
993        match child.kind() {
994            "function_definition" => {
995                // Only methods inside class/trait definitions
996                if is_inside_scala_class_or_trait(&child) {
997                    if let Some(name_node) = child.child_by_field_name("name") {
998                        let name = get_node_text(&name_node, source);
999                        methods.push(name);
1000                    }
1001                }
1002            }
1003            "class_definition" | "trait_definition" => {
1004                // Recurse into class/trait body for methods
1005                if let Some(body) = child.child_by_field_name("body") {
1006                    extract_scala_methods(&body, source, methods);
1007                }
1008            }
1009            "template_body" => {
1010                // Recurse into template body
1011                extract_scala_methods(&child, source, methods);
1012            }
1013            _ => {
1014                // Recurse into other nodes
1015                extract_scala_methods(&child, source, methods);
1016            }
1017        }
1018    }
1019}
1020
1021/// Check if a Scala node is inside a class or trait definition (but not object)
1022fn is_inside_scala_class_or_trait(node: &Node) -> bool {
1023    let mut current = node.parent();
1024    while let Some(parent) = current {
1025        match parent.kind() {
1026            "class_definition" | "trait_definition" => return true,
1027            "object_definition" => return false, // Object methods are considered functions
1028            "compilation_unit" => return false,  // Top-level
1029            _ => {}
1030        }
1031        current = parent.parent();
1032    }
1033    false
1034}
1035
1036// =============================================================================
1037// Kotlin extraction
1038// =============================================================================
1039
1040/// Extract Kotlin functions/methods from AST
1041/// Kotlin uses `function_declaration` for both top-level functions and class methods.
1042/// `methods_only` = false: top-level functions only (not inside class/object/interface)
1043/// `methods_only` = true: methods inside class/object/interface only
1044fn extract_kotlin_functions(
1045    node: &Node,
1046    source: &str,
1047    functions: &mut Vec<String>,
1048    methods_only: bool,
1049) {
1050    let mut cursor = node.walk();
1051
1052    for child in node.children(&mut cursor) {
1053        match child.kind() {
1054            "function_declaration" => {
1055                // Check if inside a class, object, or interface
1056                let is_method = is_inside_kotlin_class_or_object(&child);
1057
1058                if methods_only == is_method {
1059                    if let Some(name_node) = child.child_by_field_name("name") {
1060                        let name = get_node_text(&name_node, source);
1061                        functions.push(name);
1062                    }
1063                }
1064            }
1065            "class_declaration" | "object_declaration" | "companion_object" => {
1066                // Recurse into class/object/companion body for methods
1067                let mut class_cursor = child.walk();
1068                for class_child in child.children(&mut class_cursor) {
1069                    if class_child.kind() == "class_body" {
1070                        extract_kotlin_functions(&class_child, source, functions, methods_only);
1071                    }
1072                }
1073            }
1074            _ => {
1075                extract_kotlin_functions(&child, source, functions, methods_only);
1076            }
1077        }
1078    }
1079}
1080
1081/// Extract Kotlin class, object, and interface names
1082fn extract_kotlin_classes(node: &Node, source: &str, classes: &mut Vec<String>) {
1083    let mut cursor = node.walk();
1084
1085    for child in node.children(&mut cursor) {
1086        match child.kind() {
1087            "class_declaration" | "object_declaration" => {
1088                if let Some(name_node) = child.child_by_field_name("name") {
1089                    let name = get_node_text(&name_node, source);
1090                    classes.push(name);
1091                }
1092            }
1093            _ => {}
1094        }
1095        extract_kotlin_classes(&child, source, classes);
1096    }
1097}
1098
1099/// Check if a Kotlin node is inside a class, object, or interface definition
1100fn is_inside_kotlin_class_or_object(node: &Node) -> bool {
1101    let mut current = node.parent();
1102    while let Some(parent) = current {
1103        match parent.kind() {
1104            "class_declaration" | "object_declaration" | "companion_object" => return true,
1105            "class_body" => {
1106                // class_body is a container -- check if its parent is a class-like node
1107                if let Some(grandparent) = parent.parent() {
1108                    if matches!(
1109                        grandparent.kind(),
1110                        "class_declaration" | "object_declaration" | "companion_object"
1111                    ) {
1112                        return true;
1113                    }
1114                }
1115            }
1116            "source_file" => return false, // Top-level
1117            _ => {}
1118        }
1119        current = parent.parent();
1120    }
1121    false
1122}
1123
1124// =============================================================================
1125// OCaml extraction
1126// =============================================================================
1127
1128/// Extract OCaml function names from AST.
1129/// OCaml functions are `value_definition` nodes containing `let_binding` children
1130/// that have `parameter` children (distinguishing functions from value bindings).
1131/// The function name is in the `pattern` field of the `let_binding`.
1132fn extract_ocaml_functions(node: &Node, source: &str, functions: &mut Vec<String>) {
1133    let mut cursor = node.walk();
1134
1135    for child in node.children(&mut cursor) {
1136        if child.kind() == "value_definition" {
1137            let mut inner_cursor = child.walk();
1138            for inner in child.children(&mut inner_cursor) {
1139                if inner.kind() == "let_binding" {
1140                    // Only extract if it has parameters (i.e., is a function, not a value binding)
1141                    if ocaml_binding_has_params_simple(&inner) {
1142                        if let Some(pattern_node) = inner.child_by_field_name("pattern") {
1143                            let name = get_node_text(&pattern_node, source);
1144                            // Skip anonymous bindings like `let () = ...`
1145                            if name != "()" && !name.is_empty() {
1146                                functions.push(name);
1147                            }
1148                        }
1149                    }
1150                }
1151            }
1152        }
1153        extract_ocaml_functions(&child, source, functions);
1154    }
1155}
1156
1157/// Check if an OCaml let_binding has parameter children (i.e., is a function definition).
1158fn ocaml_binding_has_params_simple(node: &Node) -> bool {
1159    let mut cursor = node.walk();
1160    for child in node.children(&mut cursor) {
1161        if child.kind() == "parameter" {
1162            return true;
1163        }
1164    }
1165    false
1166}
1167
1168// =============================================================================
1169// PHP extraction
1170// =============================================================================
1171
1172/// Extract PHP functions/methods from AST
1173/// PHP uses `function_definition` for standalone functions and `method_declaration` for class methods.
1174/// `methods_only` = false: top-level functions only
1175/// `methods_only` = true: methods inside classes only
1176fn extract_php_functions(
1177    node: &Node,
1178    source: &str,
1179    functions: &mut Vec<String>,
1180    methods_only: bool,
1181) {
1182    let mut cursor = node.walk();
1183
1184    for child in node.children(&mut cursor) {
1185        match child.kind() {
1186            "function_definition" => {
1187                // Standalone function: function hello() {}
1188                // Check if inside a class
1189                let is_method = is_inside_php_class(&child);
1190
1191                if methods_only == is_method {
1192                    // Get the function name
1193                    if let Some(name_node) = child.child_by_field_name("name") {
1194                        let name = get_node_text(&name_node, source);
1195                        functions.push(name);
1196                    }
1197                }
1198            }
1199            "method_declaration" => {
1200                // Class method: public function greet() {}
1201                if methods_only {
1202                    if let Some(name_node) = child.child_by_field_name("name") {
1203                        let name = get_node_text(&name_node, source);
1204                        functions.push(name);
1205                    }
1206                }
1207            }
1208            "class_declaration" | "interface_declaration" | "trait_declaration" => {
1209                // Recurse into class/interface/trait body for methods.
1210                // In PHP's tree-sitter grammar, the body field IS the declaration_list.
1211                // Only recurse via one path to avoid double-counting.
1212                if let Some(body) = child.child_by_field_name("body") {
1213                    extract_php_functions(&body, source, functions, methods_only);
1214                } else {
1215                    // Fallback: look for declaration_list directly
1216                    let mut class_cursor = child.walk();
1217                    for class_child in child.children(&mut class_cursor) {
1218                        if class_child.kind() == "declaration_list" {
1219                            extract_php_functions(&class_child, source, functions, methods_only);
1220                        }
1221                    }
1222                }
1223            }
1224            _ => {
1225                // Recurse into other nodes
1226                extract_php_functions(&child, source, functions, methods_only);
1227            }
1228        }
1229    }
1230}
1231
1232/// Extract PHP class, interface, and trait names
1233fn extract_php_classes(node: &Node, source: &str, classes: &mut Vec<String>) {
1234    let mut cursor = node.walk();
1235
1236    for child in node.children(&mut cursor) {
1237        match child.kind() {
1238            "class_declaration" | "interface_declaration" | "trait_declaration" => {
1239                // Get the class/interface/trait name
1240                if let Some(name_node) = child.child_by_field_name("name") {
1241                    let name = get_node_text(&name_node, source);
1242                    classes.push(name);
1243                }
1244            }
1245            _ => {}
1246        }
1247        // Recurse to find nested classes (though PHP doesn't support truly nested classes,
1248        // we still recurse for completeness)
1249        extract_php_classes(&child, source, classes);
1250    }
1251}
1252
1253/// Check if a PHP node is inside a class, interface, or trait definition
1254fn is_inside_php_class(node: &Node) -> bool {
1255    let mut current = node.parent();
1256    while let Some(parent) = current {
1257        match parent.kind() {
1258            "class_declaration"
1259            | "interface_declaration"
1260            | "trait_declaration"
1261            | "declaration_list" => {
1262                // declaration_list alone isn't enough - check if its parent is a class type
1263                if parent.kind() == "declaration_list" {
1264                    if let Some(grandparent) = parent.parent() {
1265                        if matches!(
1266                            grandparent.kind(),
1267                            "class_declaration" | "interface_declaration" | "trait_declaration"
1268                        ) {
1269                            return true;
1270                        }
1271                    }
1272                } else {
1273                    return true;
1274                }
1275            }
1276            "program" => return false, // Top-level
1277            _ => {}
1278        }
1279        current = parent.parent();
1280    }
1281    false
1282}
1283
1284// =============================================================================
1285// Swift extraction (stubs - pending full implementation)
1286// =============================================================================
1287
1288fn extract_swift_functions(node: &Node, source: &str, functions: &mut Vec<String>) {
1289    let mut cursor = node.walk();
1290    for child in node.children(&mut cursor) {
1291        if child.kind() == "function_declaration" && !is_inside_class(&child) {
1292            if let Some(name_node) = child.child_by_field_name("name") {
1293                let name = get_node_text(&name_node, source);
1294                functions.push(name);
1295            }
1296        }
1297        extract_swift_functions(&child, source, functions);
1298    }
1299}
1300
1301fn extract_swift_classes(node: &Node, source: &str, classes: &mut Vec<String>) {
1302    let mut cursor = node.walk();
1303    for child in node.children(&mut cursor) {
1304        if child.kind() == "class_declaration" || child.kind() == "protocol_declaration" {
1305            if let Some(name_node) = child.child_by_field_name("name") {
1306                let name = get_node_text(&name_node, source);
1307                classes.push(name);
1308            }
1309        }
1310        extract_swift_classes(&child, source, classes);
1311    }
1312}
1313
1314fn extract_swift_methods(node: &Node, source: &str, methods: &mut Vec<String>) {
1315    let mut cursor = node.walk();
1316    for child in node.children(&mut cursor) {
1317        match child.kind() {
1318            "function_declaration" => {
1319                if is_inside_class(&child) {
1320                    if let Some(name_node) = child.child_by_field_name("name") {
1321                        let name = get_node_text(&name_node, source);
1322                        methods.push(name);
1323                    }
1324                }
1325            }
1326            "init_declaration" => {
1327                // Swift init() constructors inside classes
1328                if is_inside_class(&child) {
1329                    methods.push("init".to_string());
1330                }
1331            }
1332            _ => {}
1333        }
1334        extract_swift_methods(&child, source, methods);
1335    }
1336}
1337
1338// =============================================================================
1339// C# extraction (stubs - pending full implementation)
1340// =============================================================================
1341
1342fn extract_csharp_classes(node: &Node, source: &str, classes: &mut Vec<String>) {
1343    let mut cursor = node.walk();
1344    for child in node.children(&mut cursor) {
1345        match child.kind() {
1346            "class_declaration" | "interface_declaration" | "struct_declaration" => {
1347                if let Some(name_node) = child.child_by_field_name("name") {
1348                    let name = get_node_text(&name_node, source);
1349                    classes.push(name);
1350                }
1351            }
1352            _ => {}
1353        }
1354        extract_csharp_classes(&child, source, classes);
1355    }
1356}
1357
1358fn extract_csharp_methods(node: &Node, source: &str, methods: &mut Vec<String>) {
1359    let mut cursor = node.walk();
1360    for child in node.children(&mut cursor) {
1361        match child.kind() {
1362            "method_declaration" => {
1363                if let Some(name_node) = child.child_by_field_name("name") {
1364                    let name = get_node_text(&name_node, source);
1365                    methods.push(name);
1366                }
1367            }
1368            "constructor_declaration" => {
1369                // C# constructors: public Animal(string name) { }
1370                // The constructor name is an identifier child (same as the class name)
1371                if let Some(name_node) = child.child_by_field_name("name") {
1372                    let name = get_node_text(&name_node, source);
1373                    methods.push(name);
1374                } else {
1375                    // Fallback: find the first identifier child
1376                    let mut inner_cursor = child.walk();
1377                    for inner in child.children(&mut inner_cursor) {
1378                        if inner.kind() == "identifier" {
1379                            let name = get_node_text(&inner, source);
1380                            methods.push(name);
1381                            break;
1382                        }
1383                    }
1384                }
1385            }
1386            _ => {}
1387        }
1388        extract_csharp_methods(&child, source, methods);
1389    }
1390}
1391
1392// =============================================================================
1393// Elixir extraction (stubs - pending full implementation)
1394// =============================================================================
1395
1396fn extract_elixir_functions(node: &Node, source: &str, functions: &mut Vec<String>) {
1397    let mut cursor = node.walk();
1398    for child in node.children(&mut cursor) {
1399        if child.kind() == "call" {
1400            // def/defp in Elixir are calls
1401            let mut inner_cursor = child.walk();
1402            for inner in child.children(&mut inner_cursor) {
1403                if inner.kind() == "identifier" {
1404                    let text = get_node_text(&inner, source);
1405                    if text == "def" || text == "defp" {
1406                        // Next sibling should be the function call with name
1407                        if let Some(args) = inner.next_sibling() {
1408                            if args.kind() == "arguments" || args.kind() == "call" {
1409                                if let Some(name_node) = args.child(0) {
1410                                    if name_node.kind() == "identifier"
1411                                        || name_node.kind() == "call"
1412                                    {
1413                                        let fname = if name_node.kind() == "call" {
1414                                            if let Some(n) = name_node.child(0) {
1415                                                get_node_text(&n, source)
1416                                            } else {
1417                                                get_node_text(&name_node, source)
1418                                            }
1419                                        } else {
1420                                            get_node_text(&name_node, source)
1421                                        };
1422                                        if !functions.contains(&fname) {
1423                                            functions.push(fname);
1424                                        }
1425                                    }
1426                                }
1427                            }
1428                        }
1429                    }
1430                }
1431            }
1432        }
1433        extract_elixir_functions(&child, source, functions);
1434    }
1435}
1436
1437fn extract_elixir_classes(node: &Node, source: &str, classes: &mut Vec<String>) {
1438    let mut cursor = node.walk();
1439    for child in node.children(&mut cursor) {
1440        if child.kind() == "call" {
1441            let mut inner_cursor = child.walk();
1442            for inner in child.children(&mut inner_cursor) {
1443                if inner.kind() == "identifier" {
1444                    let text = get_node_text(&inner, source);
1445                    if text == "defmodule" {
1446                        if let Some(args) = inner.next_sibling() {
1447                            if let Some(name_node) = args.child(0) {
1448                                let name = get_node_text(&name_node, source);
1449                                classes.push(name);
1450                            }
1451                        }
1452                    }
1453                }
1454            }
1455        }
1456        extract_elixir_classes(&child, source, classes);
1457    }
1458}
1459
1460// =============================================================================
1461// Lua extraction (stubs - pending full implementation)
1462// =============================================================================
1463
1464fn extract_lua_functions(node: &Node, source: &str, functions: &mut Vec<String>) {
1465    let mut cursor = node.walk();
1466    for child in node.children(&mut cursor) {
1467        match child.kind() {
1468            "function_declaration" => {
1469                // Handles: function foo(), local function foo(),
1470                //          function Table.method(), function Table:method()
1471                if let Some(name) = extract_lua_function_name(&child, source) {
1472                    functions.push(name);
1473                }
1474                // Don't recurse into function_declaration children (no nested functions to find)
1475                continue;
1476            }
1477            "variable_declaration" => {
1478                // Handles: local foo = function() end
1479                // Structure: variable_declaration > [local] assignment_statement >
1480                //   variable_list > identifier + expression_list > function_definition
1481                extract_lua_variable_function(&child, source, functions);
1482                // Don't recurse -- we already looked inside via extract_lua_variable_function
1483                continue;
1484            }
1485            "assignment_statement" => {
1486                // Handles: foo = function() end (without local, at top level)
1487                extract_lua_assignment_function(&child, source, functions);
1488                continue;
1489            }
1490            _ => {}
1491        }
1492        extract_lua_functions(&child, source, functions);
1493    }
1494}
1495
1496/// Extract function name from a Lua function_declaration node.
1497/// Handles: identifier (simple name), dot_index_expression (Table.method),
1498/// method_index_expression (Table:method)
1499fn extract_lua_function_name(node: &Node, source: &str) -> Option<String> {
1500    // Try field name first
1501    if let Some(name_node) = node.child_by_field_name("name") {
1502        let name = get_node_text(&name_node, source);
1503        return Some(name);
1504    }
1505    // Fallback: iterate children for identifier/dot_index_expression/method_index_expression
1506    let mut cursor = node.walk();
1507    for child in node.children(&mut cursor) {
1508        match child.kind() {
1509            "identifier" => {
1510                let name = get_node_text(&child, source);
1511                // Skip keywords
1512                if name != "function" && name != "local" && name != "end" {
1513                    return Some(name);
1514                }
1515            }
1516            "dot_index_expression" | "method_index_expression" => {
1517                // Table.method or Table:method -- extract the last identifier (method name)
1518                if let Some(field) = child.child_by_field_name("field") {
1519                    return Some(get_node_text(&field, source));
1520                }
1521                // Fallback: get the last identifier child
1522                let mut inner_cursor = child.walk();
1523                let mut last_ident = None;
1524                for inner in child.children(&mut inner_cursor) {
1525                    if inner.kind() == "identifier" {
1526                        last_ident = Some(get_node_text(&inner, source));
1527                    }
1528                }
1529                return last_ident;
1530            }
1531            _ => {}
1532        }
1533    }
1534    None
1535}
1536
1537/// Extract function from a variable_declaration like: local foo = function() end
1538fn extract_lua_variable_function(node: &Node, source: &str, functions: &mut Vec<String>) {
1539    let mut cursor = node.walk();
1540    for child in node.children(&mut cursor) {
1541        if child.kind() == "assignment_statement" {
1542            extract_lua_assignment_function(&child, source, functions);
1543        }
1544    }
1545}
1546
1547/// Extract function from an assignment_statement if RHS is function_definition
1548fn extract_lua_assignment_function(node: &Node, source: &str, functions: &mut Vec<String>) {
1549    // Check if the expression_list contains a function_definition
1550    let mut has_function_def = false;
1551    let mut inner_cursor = node.walk();
1552    for inner in node.children(&mut inner_cursor) {
1553        if inner.kind() == "expression_list" {
1554            let mut expr_cursor = inner.walk();
1555            for expr in inner.children(&mut expr_cursor) {
1556                if expr.kind() == "function_definition" {
1557                    has_function_def = true;
1558                    break;
1559                }
1560            }
1561        }
1562    }
1563    if !has_function_def {
1564        return;
1565    }
1566    // Get the variable name from variable_list
1567    let mut inner_cursor2 = node.walk();
1568    for inner in node.children(&mut inner_cursor2) {
1569        if inner.kind() == "variable_list" {
1570            if let Some(name_node) = inner.child(0) {
1571                if name_node.kind() == "identifier" {
1572                    functions.push(get_node_text(&name_node, source));
1573                    return;
1574                }
1575            }
1576        }
1577    }
1578}
1579
1580// =============================================================================
1581// Luau extraction (stubs - pending full implementation)
1582// =============================================================================
1583
1584fn extract_luau_functions(node: &Node, source: &str, functions: &mut Vec<String>) {
1585    let mut cursor = node.walk();
1586    for child in node.children(&mut cursor) {
1587        match child.kind() {
1588            "function_declaration" | "local_function" => {
1589                if let Some(name_node) = child.child_by_field_name("name") {
1590                    let name = get_node_text(&name_node, source);
1591                    functions.push(name);
1592                }
1593            }
1594            _ => {}
1595        }
1596        extract_luau_functions(&child, source, functions);
1597    }
1598}
1599
1600// =============================================================================
1601// Definition extraction (DefinitionInfo population)
1602// =============================================================================
1603
1604/// Extract all definitions (functions, classes, structs, methods) from a syntax tree
1605/// as `DefinitionInfo` entries with line ranges and signatures.
1606///
1607/// This walks the tree-sitter AST recursively and classifies nodes as function-like
1608/// or class-like, mirroring the logic in `search/enriched.rs::classify_node`.
1609fn extract_definitions(tree: &Tree, source: &str, language: Language) -> Vec<DefinitionInfo> {
1610    let mut definitions = Vec::new();
1611    let root = tree.root_node();
1612    collect_definitions(root, source, language, &mut definitions);
1613    definitions
1614}
1615
1616/// Recursively collect definition nodes from a tree-sitter AST.
1617fn collect_definitions(
1618    node: Node,
1619    source: &str,
1620    language: Language,
1621    definitions: &mut Vec<DefinitionInfo>,
1622) {
1623    let kind = node.kind();
1624
1625    // Elixir: def/defp/defmodule are macro calls parsed as "call" nodes.
1626    // Handle them specially before the generic path.
1627    if language == Language::Elixir && kind == "call" {
1628        if let Some(def_info) = try_elixir_call_definition(node, source) {
1629            definitions.push(def_info);
1630        }
1631    }
1632
1633    // Constants: detect const/static/UPPER_CASE assignments across languages.
1634    if let Some(const_def) = try_constant_definition(node, source, language) {
1635        definitions.push(const_def);
1636    }
1637
1638    let (is_func, is_class) = classify_definition_node(kind, language);
1639
1640    // VAL-001: In C, `struct_specifier` / `enum_specifier` without a `body`
1641    // field is a bare type reference (parameter type like `struct sockaddr *a`,
1642    // forward declaration, or sizeof expression) — NOT a definition. Guard
1643    // here rather than in classify_definition_node because classify takes a
1644    // `&str` kind without access to node fields.
1645    let is_bodyless_c_specifier = is_class
1646        && matches!(kind, "struct_specifier" | "enum_specifier")
1647        && node.child_by_field_name("body").is_none();
1648
1649    if (is_func || is_class) && !is_bodyless_c_specifier {
1650        if let Some(name) = get_definition_node_name(node, source) {
1651            let line_start = node.start_position().row as u32 + 1; // 1-indexed
1652            let line_end = node.end_position().row as u32 + 1;
1653
1654            // Extract signature: skip doc comments/attributes, use actual def line
1655            let signature = extract_def_signature(node, source);
1656
1657            let entry_kind = if is_class {
1658                match kind {
1659                    "struct_item" | "struct_definition" | "struct_specifier" => "struct",
1660                    "enum_item" => "enum",
1661                    "trait_item" => "trait",
1662                    "interface_declaration" => "interface",
1663                    "module" => "module",
1664                    _ => "class",
1665                }
1666            } else {
1667                // Check if inside a class/impl => method
1668                if is_inside_class_or_impl(&node, language) {
1669                    "method"
1670                } else {
1671                    "function"
1672                }
1673            };
1674
1675            definitions.push(DefinitionInfo {
1676                name,
1677                kind: entry_kind.to_string(),
1678                line_start,
1679                line_end,
1680                signature,
1681            });
1682        }
1683    }
1684
1685    // VAL-004: Class-scope field/property declarations emit with kind="field".
1686    if let Some(field_defs) = try_field_definition(node, source, language) {
1687        definitions.extend(field_defs);
1688    }
1689
1690    // Recurse into children
1691    let mut cursor = node.walk();
1692    for child in node.children(&mut cursor) {
1693        collect_definitions(child, source, language, definitions);
1694    }
1695}
1696
1697/// Try to classify a tree-sitter node as a constant definition.
1698///
1699/// Returns `Some(DefinitionInfo)` with `kind: "constant"` if the node represents a
1700/// module-level constant. Uses explicit `const`/`static`/`final` keywords for languages
1701/// that have them, and UPPER_CASE naming convention for Python/JS/TS/Ruby/C/C++.
1702fn try_constant_definition(
1703    node: Node,
1704    source: &str,
1705    language: Language,
1706) -> Option<DefinitionInfo> {
1707    let kind = node.kind();
1708
1709    match language {
1710        Language::Python => {
1711            // UPPER_CASE = value (at module level: expression_statement → assignment)
1712            if kind != "expression_statement" {
1713                return None;
1714            }
1715            let inner = node.child(0)?;
1716            if inner.kind() != "assignment" {
1717                return None;
1718            }
1719            let left = inner.child_by_field_name("left")?;
1720            if left.kind() != "identifier" {
1721                return None;
1722            }
1723            let name = get_node_text(&left, source);
1724            if !is_upper_case_name(&name) {
1725                return None;
1726            }
1727            Some(make_constant_def(node, name, source))
1728        }
1729
1730        Language::Rust => {
1731            // const_item: `const NAME: Type = value;`
1732            // static_item: `static NAME: Type = value;`
1733            if kind != "const_item" && kind != "static_item" {
1734                return None;
1735            }
1736            let name = node
1737                .child_by_field_name("name")
1738                .map(|n| get_node_text(&n, source))?;
1739            Some(make_constant_def(node, name, source))
1740        }
1741
1742        Language::Go => {
1743            // const_spec inside const_declaration (individual constant in a group)
1744            if kind != "const_spec" {
1745                return None;
1746            }
1747            let name = node
1748                .child_by_field_name("name")
1749                .map(|n| get_node_text(&n, source))?;
1750            Some(make_constant_def(node, name, source))
1751        }
1752
1753        Language::TypeScript | Language::JavaScript => {
1754            // `const UPPER_CASE = ...` (also found inside `export const ...` via recursion)
1755            if kind != "lexical_declaration" {
1756                return None;
1757            }
1758            let decl_text = get_node_text(&node, source);
1759            if !decl_text.starts_with("const ") {
1760                return None;
1761            }
1762            let mut cursor = node.walk();
1763            let declarator = node
1764                .children(&mut cursor)
1765                .find(|c| c.kind() == "variable_declarator")?;
1766            let name = declarator
1767                .child_by_field_name("name")
1768                .map(|n| get_node_text(&n, source))?;
1769            if !is_upper_case_name(&name) {
1770                return None;
1771            }
1772            Some(make_constant_def(node, name, source))
1773        }
1774
1775        Language::C | Language::Cpp => {
1776            match kind {
1777                "preproc_def" => {
1778                    // #define UPPER_CASE value
1779                    let mut cursor = node.walk();
1780                    let ident = node
1781                        .children(&mut cursor)
1782                        .find(|c| c.kind() == "identifier")?;
1783                    let name = get_node_text(&ident, source);
1784                    if !is_upper_case_name(&name) {
1785                        return None;
1786                    }
1787                    Some(make_constant_def(node, name, source))
1788                }
1789                "declaration" => {
1790                    // const/constexpr TYPE UPPER_CASE = value;
1791                    let mut cursor = node.walk();
1792                    let has_const = node.children(&mut cursor).any(|c| {
1793                        if c.kind() != "type_qualifier" {
1794                            return false;
1795                        }
1796                        let text = get_node_text(&c, source);
1797                        text == "const"
1798                            || (language == Language::Cpp && text == "constexpr")
1799                    });
1800                    if !has_const {
1801                        return None;
1802                    }
1803                    let mut cursor2 = node.walk();
1804                    let init_decl = node
1805                        .children(&mut cursor2)
1806                        .find(|c| c.kind() == "init_declarator")?;
1807                    let decl = init_decl.child_by_field_name("declarator")?;
1808                    let name = get_node_text(&decl, source);
1809                    if !is_upper_case_name(&name) {
1810                        return None;
1811                    }
1812                    Some(make_constant_def(node, name, source))
1813                }
1814                _ => None,
1815            }
1816        }
1817
1818        Language::Ruby => {
1819            // CONSTANT = value (LHS is a `constant` node in tree-sitter-ruby)
1820            if kind != "assignment" {
1821                return None;
1822            }
1823            let mut cursor = node.walk();
1824            let const_node = node
1825                .children(&mut cursor)
1826                .find(|c| c.kind() == "constant")?;
1827            let name = get_node_text(&const_node, source);
1828            Some(make_constant_def(node, name, source))
1829        }
1830
1831        Language::Java => {
1832            // static final fields: `public static final TYPE NAME = value;`
1833            if kind != "field_declaration" {
1834                return None;
1835            }
1836            let mut cursor = node.walk();
1837            let modifiers = node
1838                .children(&mut cursor)
1839                .find(|c| c.kind() == "modifiers")?;
1840            let mod_text = get_node_text(&modifiers, source);
1841            if !mod_text.contains("static") || !mod_text.contains("final") {
1842                return None;
1843            }
1844            let mut cursor2 = node.walk();
1845            let declarator = node
1846                .children(&mut cursor2)
1847                .find(|c| c.kind() == "variable_declarator")?;
1848            let name = declarator
1849                .child_by_field_name("name")
1850                .map(|n| get_node_text(&n, source))?;
1851            Some(make_constant_def(node, name, source))
1852        }
1853
1854        Language::Kotlin => {
1855            // top-level val/const val declarations
1856            // tree-sitter-kotlin-ng: property_declaration
1857            if kind != "property_declaration" {
1858                return None;
1859            }
1860            let text = get_node_text(&node, source);
1861            if !text.starts_with("val ") && !text.starts_with("const val ") {
1862                return None;
1863            }
1864            // Only UPPER_CASE or const val
1865            let mut cursor = node.walk();
1866            let var_decl = node
1867                .children(&mut cursor)
1868                .find(|c| c.kind() == "variable_declaration")?;
1869            let name_node = if let Some(n) = var_decl.child_by_field_name("name") {
1870                Some(n)
1871            } else {
1872                let mut vc = var_decl.walk();
1873                let found = var_decl
1874                    .children(&mut vc)
1875                    .find(|c| c.kind() == "simple_identifier" || c.kind() == "identifier");
1876                found
1877            };
1878            let name = name_node.map(|n| get_node_text(&n, source))?;
1879            if !text.starts_with("const val ") && !is_upper_case_name(&name) {
1880                return None;
1881            }
1882            Some(make_constant_def(node, name, source))
1883        }
1884
1885        Language::Swift => {
1886            // `let` declarations at module level
1887            if kind != "property_declaration" {
1888                return None;
1889            }
1890            // VAL-004: class-scope `let`/`var` are fields, not module
1891            // constants — skip to avoid emitting duplicate definitions.
1892            if let Some(parent) = node.parent() {
1893                if matches!(
1894                    parent.kind(),
1895                    "class_body" | "enum_body" | "protocol_body" | "struct_body"
1896                ) {
1897                    return None;
1898                }
1899            }
1900            let text = get_node_text(&node, source);
1901            if !text.starts_with("let ") {
1902                return None;
1903            }
1904            let name_node = if let Some(n) = node.child_by_field_name("name") {
1905                Some(n)
1906            } else {
1907                let mut cursor = node.walk();
1908                let found = node.children(&mut cursor).find(|c| {
1909                    c.kind() == "pattern"
1910                        || c.kind() == "simple_identifier"
1911                        || c.kind() == "identifier"
1912                });
1913                found
1914            };
1915            let name_node = name_node?;
1916            let name = get_node_text(&name_node, source);
1917            // Skip if name looks like a destructuring pattern
1918            if name.contains('(') || name.contains('{') {
1919                return None;
1920            }
1921            Some(make_constant_def(node, name, source))
1922        }
1923
1924        Language::CSharp => {
1925            // const fields: `public const TYPE NAME = value;`
1926            if kind != "field_declaration" {
1927                return None;
1928            }
1929            let mut cursor = node.walk();
1930            let has_const = node.children(&mut cursor).any(|c| {
1931                c.kind() == "modifier" && get_node_text(&c, source) == "const"
1932            });
1933            if !has_const {
1934                return None;
1935            }
1936            let mut cursor2 = node.walk();
1937            let var_decl = node
1938                .children(&mut cursor2)
1939                .find(|c| c.kind() == "variable_declaration")?;
1940            let mut cursor3 = var_decl.walk();
1941            let declarator = var_decl
1942                .children(&mut cursor3)
1943                .find(|c| c.kind() == "variable_declarator")?;
1944            let name = declarator
1945                .child_by_field_name("name")
1946                .or_else(|| declarator.child(0))
1947                .map(|n| get_node_text(&n, source))?;
1948            Some(make_constant_def(node, name, source))
1949        }
1950
1951        Language::Scala => {
1952            // val UPPER_CASE = value
1953            if kind != "val_definition" {
1954                return None;
1955            }
1956            let name_node = if let Some(n) = node.child_by_field_name("pattern") {
1957                Some(n)
1958            } else {
1959                let mut cursor = node.walk();
1960                let found = node
1961                    .children(&mut cursor)
1962                    .find(|c| c.kind() == "identifier");
1963                found
1964            };
1965            let name_node = name_node?;
1966            let name = get_node_text(&name_node, source);
1967            if !is_upper_case_name(&name) {
1968                return None;
1969            }
1970            Some(make_constant_def(node, name, source))
1971        }
1972
1973        Language::Php => {
1974            // const NAME = value; or define('NAME', value);
1975            if kind != "const_declaration" {
1976                return None;
1977            }
1978            let mut cursor = node.walk();
1979            let element = node
1980                .children(&mut cursor)
1981                .find(|c| c.kind() == "const_element")?;
1982            let name = element
1983                .child_by_field_name("name")
1984                .or_else(|| element.child(0))
1985                .map(|n| get_node_text(&n, source))?;
1986            Some(make_constant_def(node, name, source))
1987        }
1988
1989        Language::Elixir => {
1990            // @module_attribute: `@attr value`
1991            if kind != "unary_operator" {
1992                return None;
1993            }
1994            let text = get_node_text(&node, source);
1995            if !text.starts_with('@') {
1996                return None;
1997            }
1998            let mut cursor = node.walk();
1999            let ident = node
2000                .children(&mut cursor)
2001                .find(|c| c.kind() == "identifier")?;
2002            let name = format!("@{}", get_node_text(&ident, source));
2003            // Skip common non-constant attributes
2004            if name == "@doc" || name == "@moduledoc" || name == "@spec" || name == "@type" {
2005                return None;
2006            }
2007            Some(make_constant_def(node, name, source))
2008        }
2009
2010        Language::Lua | Language::Luau | Language::Ocaml => None,
2011    }
2012}
2013
2014/// Build a `DefinitionInfo` with `kind: "constant"` from a node and its name.
2015/// Uses the full node span for `line_start`/`line_end` and the first line for `signature`.
2016fn make_constant_def(node: Node, name: String, source: &str) -> DefinitionInfo {
2017    let line_start = node.start_position().row as u32 + 1;
2018    let line_end = node.end_position().row as u32 + 1;
2019    let sig_start = node.start_byte();
2020    let signature = source[sig_start..]
2021        .lines()
2022        .next()
2023        .unwrap_or("")
2024        .trim()
2025        .to_string();
2026    DefinitionInfo {
2027        name,
2028        kind: "constant".to_string(),
2029        line_start,
2030        line_end,
2031        signature,
2032    }
2033}
2034
2035/// VAL-004: Try to classify a tree-sitter node as a class-scope field /
2036/// property declaration. Returns one `DefinitionInfo` per declared name so
2037/// that a single Java statement like `int x, y;` emits two field entries.
2038///
2039/// Only emits when the node is a direct child of a class-like body (class,
2040/// interface, enum, struct, protocol, actor body depending on language).
2041/// Top-level Kotlin `val/var` parses as `property_declaration` too but must
2042/// NOT be emitted — the parent check prevents that.
2043fn try_field_definition(
2044    node: Node,
2045    source: &str,
2046    language: Language,
2047) -> Option<Vec<DefinitionInfo>> {
2048    let kind = node.kind();
2049
2050    // Per-language kind gating.
2051    let kind_matches = match language {
2052        Language::Java => matches!(kind, "field_declaration"),
2053        Language::Kotlin => matches!(kind, "property_declaration"),
2054        Language::Swift => matches!(kind, "property_declaration"),
2055        Language::TypeScript | Language::JavaScript => {
2056            matches!(kind, "public_field_definition" | "field_definition")
2057        }
2058        _ => false,
2059    };
2060    if !kind_matches {
2061        return None;
2062    }
2063
2064    // Must sit directly inside a class-like body. This excludes top-level
2065    // declarations (e.g. Kotlin `val topLevelX = 1` under `source_file`).
2066    let parent = node.parent()?;
2067    let parent_kind = parent.kind();
2068    let parent_is_class_body = matches!(
2069        parent_kind,
2070        "class_body"           // Java, Kotlin, TS, Swift
2071            | "interface_body" // Java
2072            | "enum_body"      // Java / Swift
2073            | "annotation_type_body" // Java
2074            | "protocol_body"  // Swift
2075            | "struct_body"    // (reserved)
2076    );
2077    if !parent_is_class_body {
2078        return None;
2079    }
2080
2081    // Extract names.
2082    let mut defs: Vec<DefinitionInfo> = Vec::new();
2083    let line_start = node.start_position().row as u32 + 1;
2084    let line_end = node.end_position().row as u32 + 1;
2085    let signature = extract_def_signature(node, source);
2086
2087    match language {
2088        Language::Java => {
2089            // field_declaration → variable_declarator+ → identifier ("name" field)
2090            let mut cursor = node.walk();
2091            for child in node.children(&mut cursor) {
2092                if child.kind() == "variable_declarator" {
2093                    let name_opt = child
2094                        .child_by_field_name("name")
2095                        .and_then(|n| n.utf8_text(source.as_bytes()).ok().map(|s| s.to_string()))
2096                        .or_else(|| {
2097                            let mut c2 = child.walk();
2098                            for inner in child.children(&mut c2) {
2099                                if inner.kind() == "identifier" {
2100                                    return inner
2101                                        .utf8_text(source.as_bytes())
2102                                        .ok()
2103                                        .map(|s| s.to_string());
2104                                }
2105                            }
2106                            None
2107                        });
2108                    if let Some(name) = name_opt {
2109                        defs.push(DefinitionInfo {
2110                            name,
2111                            kind: "field".to_string(),
2112                            line_start,
2113                            line_end,
2114                            signature: signature.clone(),
2115                        });
2116                    }
2117                }
2118            }
2119        }
2120        Language::Kotlin => {
2121            // property_declaration → variable_declaration → identifier
2122            let mut cursor = node.walk();
2123            for child in node.children(&mut cursor) {
2124                if child.kind() == "variable_declaration" {
2125                    let mut c2 = child.walk();
2126                    for inner in child.children(&mut c2) {
2127                        if inner.kind() == "identifier" {
2128                            if let Ok(name) = inner.utf8_text(source.as_bytes()) {
2129                                defs.push(DefinitionInfo {
2130                                    name: name.to_string(),
2131                                    kind: "field".to_string(),
2132                                    line_start,
2133                                    line_end,
2134                                    signature: signature.clone(),
2135                                });
2136                            }
2137                            break;
2138                        }
2139                    }
2140                }
2141            }
2142        }
2143        Language::Swift => {
2144            // property_declaration → pattern → simple_identifier
2145            let mut cursor = node.walk();
2146            for child in node.children(&mut cursor) {
2147                if child.kind() == "pattern" {
2148                    let mut c2 = child.walk();
2149                    for inner in child.children(&mut c2) {
2150                        if inner.kind() == "simple_identifier" {
2151                            if let Ok(name) = inner.utf8_text(source.as_bytes()) {
2152                                defs.push(DefinitionInfo {
2153                                    name: name.to_string(),
2154                                    kind: "field".to_string(),
2155                                    line_start,
2156                                    line_end,
2157                                    signature: signature.clone(),
2158                                });
2159                            }
2160                            break;
2161                        }
2162                    }
2163                }
2164            }
2165        }
2166        Language::TypeScript | Language::JavaScript => {
2167            // public_field_definition / field_definition: property_identifier
2168            // is either the "name" field or an inline child.
2169            let name_opt = node
2170                .child_by_field_name("name")
2171                .and_then(|n| n.utf8_text(source.as_bytes()).ok().map(|s| s.to_string()))
2172                .or_else(|| {
2173                    let mut cursor = node.walk();
2174                    for child in node.children(&mut cursor) {
2175                        if child.kind() == "property_identifier"
2176                            || child.kind() == "private_property_identifier"
2177                        {
2178                            return child
2179                                .utf8_text(source.as_bytes())
2180                                .ok()
2181                                .map(|s| s.to_string());
2182                        }
2183                    }
2184                    None
2185                });
2186            if let Some(name) = name_opt {
2187                defs.push(DefinitionInfo {
2188                    name,
2189                    kind: "field".to_string(),
2190                    line_start,
2191                    line_end,
2192                    signature,
2193                });
2194            }
2195        }
2196        _ => {}
2197    }
2198
2199    if defs.is_empty() {
2200        None
2201    } else {
2202        Some(defs)
2203    }
2204}
2205
2206/// Try to extract a definition from an Elixir `call` node.
2207/// In Elixir, `def`/`defp`/`defmodule` are macro calls that tree-sitter parses as `call` nodes.
2208fn try_elixir_call_definition(node: Node, source: &str) -> Option<DefinitionInfo> {
2209    let mut cursor = node.walk();
2210    for child in node.children(&mut cursor) {
2211        if child.kind() != "identifier" {
2212            continue;
2213        }
2214        let keyword = child.utf8_text(source.as_bytes()).ok()?;
2215        let args = child.next_sibling()?;
2216
2217        match keyword {
2218            "def" | "defp" => {
2219                let first_arg = args.child(0)?;
2220                let name = if first_arg.kind() == "call" {
2221                    // def process(data) → call node wrapping name + args
2222                    first_arg.child(0)?.utf8_text(source.as_bytes()).ok()?
2223                } else {
2224                    first_arg.utf8_text(source.as_bytes()).ok()?
2225                };
2226                let line_start = node.start_position().row as u32 + 1;
2227                let line_end = node.end_position().row as u32 + 1;
2228                let signature = extract_def_signature(node, source);
2229                return Some(DefinitionInfo {
2230                    name: name.to_string(),
2231                    kind: "function".to_string(),
2232                    line_start,
2233                    line_end,
2234                    signature,
2235                });
2236            }
2237            "defmodule" => {
2238                let first_arg = args.child(0)?;
2239                let name = first_arg.utf8_text(source.as_bytes()).ok()?;
2240                let line_start = node.start_position().row as u32 + 1;
2241                let line_end = node.end_position().row as u32 + 1;
2242                let signature = extract_def_signature(node, source);
2243                return Some(DefinitionInfo {
2244                    name: name.to_string(),
2245                    kind: "module".to_string(),
2246                    line_start,
2247                    line_end,
2248                    signature,
2249                });
2250            }
2251            _ => {}
2252        }
2253    }
2254    None
2255}
2256
2257/// Classify a tree-sitter node kind as function-like or class-like.
2258/// Mirrors `search/enriched.rs::classify_node`.
2259fn classify_definition_node(kind: &str, _language: Language) -> (bool, bool) {
2260    let is_func = matches!(
2261        kind,
2262        "function_definition"
2263            | "function_declaration"
2264            | "function_item"     // Rust
2265            | "method_definition"
2266            | "method_signature"           // TS: interface methods & abstract class signature (VAL-001)
2267            | "abstract_method_signature"  // TS: `abstract foo(): void;` inside abstract class (VAL-001)
2268            | "method_declaration"
2269            | "method"            // Ruby
2270            | "singleton_method"  // Ruby class methods
2271            | "arrow_function"
2272            | "function_expression"
2273            | "function"           // JS/TS
2274            | "func_literal"       // Go
2275            | "function_type"
2276            | "value_definition"   // OCaml top-level let binding (functions and values)
2277            | "init_declaration"   // Swift init constructor (VAL-002)
2278            | "constructor_declaration" // Java / C# constructor (VAL-003)
2279    );
2280
2281    let is_class = matches!(
2282        kind,
2283        "class_definition"
2284            | "class_declaration"
2285            | "abstract_class_declaration"  // TS: `abstract class Foo {}` (VAL-001)
2286            | "class_specifier"   // C++
2287            | "class"             // Ruby
2288            | "module"            // Ruby
2289            | "struct_item"        // Rust
2290            | "struct_definition"  // C/C++
2291            | "struct_specifier"   // C
2292            | "enum_item"          // Rust
2293            | "trait_item"         // Rust
2294            | "type_spec"          // Go struct
2295            | "interface_declaration"
2296            | "type_definition"    // OCaml type definition
2297            | "module_definition"  // OCaml module definition
2298            | "companion_object"   // Kotlin companion object (name: "Companion" by convention)
2299    );
2300
2301    (is_func, is_class)
2302}
2303
2304/// Extract the name from a function/class definition node.
2305/// Mirrors `search/enriched.rs::get_definition_name`.
2306fn get_definition_node_name(node: Node, source: &str) -> Option<String> {
2307    // Swift `init_declaration` has no `name` field — the node starts with the
2308    // literal token `init`. Mirror `extract_swift_methods` which also emits
2309    // the literal string "init".
2310    if node.kind() == "init_declaration" {
2311        return Some("init".to_string());
2312    }
2313
2314    // Most languages use a "name" field
2315    if let Some(name_node) = node.child_by_field_name("name") {
2316        let text = name_node.utf8_text(source.as_bytes()).ok()?;
2317        return Some(text.to_string());
2318    }
2319
2320    // Java `constructor_declaration` may not expose a `name` field in all
2321    // grammar versions — fall back to the first identifier child (same
2322    // fallback as `extract_csharp_methods`).
2323    if node.kind() == "constructor_declaration" {
2324        let mut cursor = node.walk();
2325        for child in node.children(&mut cursor) {
2326            if child.kind() == "identifier" {
2327                let text = child.utf8_text(source.as_bytes()).ok()?;
2328                return Some(text.to_string());
2329            }
2330        }
2331    }
2332
2333    // C/C++ function_definition uses "declarator" instead of "name"
2334    if node.kind() == "function_definition" {
2335        if let Some(declarator) = node.child_by_field_name("declarator") {
2336            return extract_name_from_declarator(declarator, source);
2337        }
2338    }
2339
2340    // For arrow functions assigned to variables, check parent
2341    if node.kind() == "arrow_function" || node.kind() == "function_expression" {
2342        if let Some(parent) = node.parent() {
2343            if parent.kind() == "variable_declarator" {
2344                if let Some(name_node) = parent.child_by_field_name("name") {
2345                    let text = name_node.utf8_text(source.as_bytes()).ok()?;
2346                    return Some(text.to_string());
2347                }
2348            }
2349        }
2350    }
2351
2352    // OCaml: value_definition contains a let_binding child with a "pattern" field.
2353    // The pattern field holds the function/value name (e.g. `let top_level x = ...`
2354    // has pattern="top_level"). Skip anonymous bindings like `let () = ...`.
2355    if node.kind() == "value_definition" {
2356        let mut cursor = node.walk();
2357        for child in node.children(&mut cursor) {
2358            if child.kind() == "let_binding" {
2359                if let Some(pattern_node) = child.child_by_field_name("pattern") {
2360                    let text = pattern_node.utf8_text(source.as_bytes()).ok()?;
2361                    if text != "()" && !text.is_empty() {
2362                        return Some(text.to_string());
2363                    }
2364                }
2365            }
2366        }
2367        return None;
2368    }
2369
2370    // Kotlin: companion_object has no identifier child; use "Companion" by convention.
2371    if node.kind() == "companion_object" {
2372        return Some("Companion".to_string());
2373    }
2374
2375    None
2376}
2377
2378/// Extract a function name from a C/C++ declarator chain.
2379/// Handles function_declarator, pointer_declarator, reference_declarator, etc.
2380fn extract_name_from_declarator(node: Node, source: &str) -> Option<String> {
2381    match node.kind() {
2382        "identifier" | "field_identifier" | "destructor_name" => {
2383            Some(get_node_text(&node, source))
2384        }
2385        "function_declarator" | "pointer_declarator" | "reference_declarator" => {
2386            let inner = node.child_by_field_name("declarator")?;
2387            extract_name_from_declarator(inner, source)
2388        }
2389        "qualified_identifier" | "scoped_identifier" => {
2390            if let Some(name) = node.child_by_field_name("name") {
2391                return Some(get_node_text(&name, source));
2392            }
2393            Some(get_node_text(&node, source))
2394        }
2395        _ => {
2396            // Fallback: try children
2397            let mut cursor = node.walk();
2398            for child in node.children(&mut cursor) {
2399                if let Some(name) = extract_name_from_declarator(child, source) {
2400                    return Some(name);
2401                }
2402            }
2403            None
2404        }
2405    }
2406}
2407
2408/// Check if a node is inside a class/struct body or impl block.
2409///
2410/// The `language` parameter is used to disambiguate node kinds that are shared
2411/// across tree-sitter grammars but have different semantics. For example,
2412/// `"module"` is the root node in tree-sitter-python (not a class scope) but
2413/// represents a Ruby module definition (a class scope) in tree-sitter-ruby.
2414fn is_inside_class_or_impl(node: &Node, language: Language) -> bool {
2415    let mut current = node.parent();
2416    while let Some(parent) = current {
2417        let kind = parent.kind();
2418        // "module" is a class-scope node in Ruby but the root node in Python.
2419        // Only treat it as a class scope when the language is Ruby.
2420        let module_is_class = !matches!(language, Language::Python);
2421        if matches!(
2422            kind,
2423            "class_definition"
2424                | "class_declaration"
2425                | "abstract_class_declaration"  // TS (VAL-001)
2426                | "class_specifier"   // C++
2427                | "class"             // Ruby
2428                | "class_body"
2429                | "impl_item"
2430                | "struct_item"
2431                | "trait_item"
2432                | "interface_declaration"  // TS/Java/C# (VAL-001)
2433                | "interface_body"         // TS body wrapper (VAL-001)
2434                | "companion_object"  // Kotlin
2435                | "object_declaration" // Kotlin
2436        ) || (kind == "module" && module_is_class) // Ruby module
2437        {
2438            return true;
2439        }
2440        current = parent.parent();
2441    }
2442    false
2443}
2444
2445/// Extract the actual definition signature from a tree-sitter node,
2446/// skipping doc comments, attributes, and decorators.
2447/// Mirrors `search/enriched.rs::extract_definition_signature`.
2448fn extract_def_signature(node: Node, source: &str) -> String {
2449    // Strategy: find the first child node that isn't a comment or attribute,
2450    // then use its start position as the beginning of the actual definition.
2451    let mut cursor = node.walk();
2452    for child in node.children(&mut cursor) {
2453        let ckind = child.kind();
2454        // Skip doc comments and attributes/decorators
2455        if ckind == "line_comment"
2456            || ckind == "block_comment"
2457            || ckind == "comment"
2458            || ckind == "attribute_item"    // Rust #[...]
2459            || ckind == "attribute"         // Rust #[...]
2460            || ckind == "decorator"         // Python @decorator
2461            || ckind == "decorator_list"
2462        // Python
2463        {
2464            continue;
2465        }
2466        // Found the first non-comment child -- extract its line as signature
2467        let start_byte = child.start_byte();
2468        let line_from_start = &source[start_byte..];
2469        let sig = line_from_start
2470            .lines()
2471            .next()
2472            .unwrap_or("")
2473            .trim()
2474            .to_string();
2475        if !sig.is_empty() {
2476            return sig;
2477        }
2478    }
2479
2480    // Fallback: find the first non-comment line in the node's text
2481    let node_text = &source[node.start_byte()..node.end_byte()];
2482    for line in node_text.lines() {
2483        let trimmed = line.trim();
2484        if !trimmed.is_empty()
2485            && !trimmed.starts_with("///")
2486            && !trimmed.starts_with("//!")
2487            && !trimmed.starts_with("//")
2488            && !trimmed.starts_with("/*")
2489            && !trimmed.starts_with("*")
2490            && !trimmed.starts_with("#[")
2491            && !trimmed.starts_with("@")
2492            && !trimmed.starts_with("#")
2493        {
2494            return trimmed.to_string();
2495        }
2496    }
2497
2498    // Last resort: use the first line
2499    source[node.start_byte()..]
2500        .lines()
2501        .next()
2502        .unwrap_or("")
2503        .trim()
2504        .to_string()
2505}
2506
2507// =============================================================================
2508// Helper functions
2509// =============================================================================
2510
2511/// Get text content of a node
2512fn get_node_text(node: &Node, source: &str) -> String {
2513    source[node.byte_range()].to_string()
2514}
2515
2516/// Check if a node is inside a class definition
2517fn is_inside_class(node: &Node) -> bool {
2518    let mut current = node.parent();
2519    while let Some(parent) = current {
2520        match parent.kind() {
2521            "class_definition" | "class_declaration" | "class" | "class_body" => return true,
2522            _ => current = parent.parent(),
2523        }
2524    }
2525    false
2526}
2527
2528/// Check if a node is inside an impl block or trait definition (Rust)
2529fn is_inside_impl(node: &Node) -> bool {
2530    let mut current = node.parent();
2531    while let Some(parent) = current {
2532        if parent.kind() == "impl_item" || parent.kind() == "trait_item" {
2533            return true;
2534        }
2535        current = parent.parent();
2536    }
2537    false
2538}
2539
2540#[cfg(test)]
2541mod tests {
2542    use super::*;
2543    use crate::ast::parser::parse;
2544
2545    #[test]
2546    fn test_extract_python_functions() {
2547        let source = r#"
2548def foo():
2549    pass
2550
2551def bar(x):
2552    return x
2553
2554class MyClass:
2555    def method(self):
2556        pass
2557"#;
2558        let tree = parse(source, Language::Python).unwrap();
2559        let functions = extract_functions(&tree, source, Language::Python);
2560
2561        assert!(functions.contains(&"foo".to_string()));
2562        assert!(functions.contains(&"bar".to_string()));
2563        // method should not be in functions (it's a method)
2564        assert!(!functions.contains(&"method".to_string()));
2565    }
2566
2567    #[test]
2568    fn test_extract_python_classes() {
2569        let source = r#"
2570class MyClass:
2571    pass
2572
2573class AnotherClass:
2574    def method(self):
2575        pass
2576"#;
2577        let tree = parse(source, Language::Python).unwrap();
2578        let classes = extract_classes(&tree, source, Language::Python);
2579
2580        assert!(classes.contains(&"MyClass".to_string()));
2581        assert!(classes.contains(&"AnotherClass".to_string()));
2582    }
2583
2584    #[test]
2585    fn test_extract_python_methods() {
2586        let source = r#"
2587class MyClass:
2588    def method1(self):
2589        pass
2590
2591    def method2(self, x):
2592        return x
2593"#;
2594        let tree = parse(source, Language::Python).unwrap();
2595        let methods = extract_methods(&tree, source, Language::Python);
2596
2597        assert!(methods.contains(&"method1".to_string()));
2598        assert!(methods.contains(&"method2".to_string()));
2599    }
2600
2601    #[test]
2602    fn test_extract_typescript_functions() {
2603        let source = r#"
2604function foo() {}
2605
2606const bar = () => {};
2607
2608class MyClass {
2609    method() {}
2610}
2611"#;
2612        let tree = parse(source, Language::TypeScript).unwrap();
2613        let functions = extract_functions(&tree, source, Language::TypeScript);
2614
2615        assert!(functions.contains(&"foo".to_string()));
2616        // Arrow function detection depends on variable declarator
2617    }
2618
2619    #[test]
2620    fn test_extract_go_functions() {
2621        let source = r#"
2622package main
2623
2624func foo() {}
2625
2626func (r *Receiver) bar() {}
2627"#;
2628        let tree = parse(source, Language::Go).unwrap();
2629        let functions = extract_functions(&tree, source, Language::Go);
2630
2631        assert!(functions.contains(&"foo".to_string()));
2632        assert!(functions.contains(&"bar".to_string()));
2633    }
2634
2635    #[test]
2636    fn test_extract_c_functions() {
2637        let source = r#"
2638void hello(void) {
2639}
2640
2641int main(int argc, char** argv) {
2642    return 0;
2643}
2644
2645static void helper(int x) {
2646}
2647"#;
2648        let tree = parse(source, Language::C).unwrap();
2649        let functions = extract_functions(&tree, source, Language::C);
2650
2651        assert!(
2652            functions.contains(&"hello".to_string()),
2653            "Should find hello function"
2654        );
2655        assert!(
2656            functions.contains(&"main".to_string()),
2657            "Should find main function"
2658        );
2659        assert!(
2660            functions.contains(&"helper".to_string()),
2661            "Should find helper function"
2662        );
2663    }
2664
2665    #[test]
2666    fn test_extract_c_structs() {
2667        let source = r#"
2668struct Point {
2669    int x;
2670    int y;
2671};
2672
2673enum Color {
2674    RED,
2675    GREEN,
2676    BLUE
2677};
2678"#;
2679        let tree = parse(source, Language::C).unwrap();
2680        let classes = extract_classes(&tree, source, Language::C);
2681
2682        assert!(
2683            classes.contains(&"Point".to_string()),
2684            "Should find Point struct"
2685        );
2686        assert!(
2687            classes.contains(&"Color".to_string()),
2688            "Should find Color enum"
2689        );
2690    }
2691
2692    #[test]
2693    fn test_extract_c_structs_requires_body_val_001() {
2694        // VAL-001: extract_c_structs must only emit struct/enum specifiers
2695        // that have a `body` field. Bare parameter type references
2696        // (`struct Bar *b`), forward declarations (`struct Bar;`), and
2697        // typedef aliases of existing structs must NOT be emitted.
2698        let source = r#"
2699struct Foo { int x; };
2700void use_bar(struct Bar *b);
2701struct Forward;
2702typedef struct { int y; } Anon;
2703typedef struct Existing OtherName;
2704enum E { A };
2705enum F;
2706void use_enum(enum G *e);
2707"#;
2708        let tree = parse(source, Language::C).unwrap();
2709        let classes = extract_classes(&tree, source, Language::C);
2710
2711        let expected: std::collections::HashSet<String> =
2712            ["Foo", "Anon", "E"].iter().map(|s| s.to_string()).collect();
2713        let got: std::collections::HashSet<String> = classes.iter().cloned().collect();
2714
2715        assert_eq!(
2716            got, expected,
2717            "VAL-001: extract_c_structs must only emit bodied struct/enum \
2718             definitions. Expected {:?}, got {:?}",
2719            expected, got
2720        );
2721
2722        // Explicit negative assertions for clarity.
2723        for forbidden in ["Bar", "Forward", "Existing", "OtherName", "F", "G"] {
2724            assert!(
2725                !classes.contains(&forbidden.to_string()),
2726                "VAL-001: must NOT emit `{}` (no body / alias / param type), \
2727                 got classes = {:?}",
2728                forbidden,
2729                classes
2730            );
2731        }
2732    }
2733
2734    #[test]
2735    fn test_extract_cpp_functions() {
2736        let source = r#"
2737void hello() {
2738}
2739
2740int main() {
2741    return 0;
2742}
2743
2744namespace greeting {
2745    void greet(const std::string& name) {
2746    }
2747}
2748"#;
2749        let tree = parse(source, Language::Cpp).unwrap();
2750        let functions = extract_functions(&tree, source, Language::Cpp);
2751
2752        assert!(
2753            functions.contains(&"hello".to_string()),
2754            "Should find hello function"
2755        );
2756        assert!(
2757            functions.contains(&"main".to_string()),
2758            "Should find main function"
2759        );
2760        // Namespace functions should also be found
2761        assert!(
2762            functions.contains(&"greet".to_string()),
2763            "Should find greet function"
2764        );
2765    }
2766
2767    #[test]
2768    fn test_extract_cpp_classes() {
2769        let source = r#"
2770class Greeter {
2771public:
2772    void greet();
2773};
2774
2775struct Point {
2776    int x, y;
2777};
2778"#;
2779        let tree = parse(source, Language::Cpp).unwrap();
2780        let classes = extract_classes(&tree, source, Language::Cpp);
2781
2782        assert!(
2783            classes.contains(&"Greeter".to_string()),
2784            "Should find Greeter class"
2785        );
2786        assert!(
2787            classes.contains(&"Point".to_string()),
2788            "Should find Point struct"
2789        );
2790    }
2791
2792    #[test]
2793    fn test_extract_ruby_functions() {
2794        let source = r##"
2795def top_level_function
2796  puts "I'm a function"
2797end
2798
2799def another_function(x)
2800  x * 2
2801end
2802
2803class MyClass
2804  def method_in_class
2805    puts "method"
2806  end
2807end
2808"##;
2809        let tree = parse(source, Language::Ruby).unwrap();
2810        let functions = extract_functions(&tree, source, Language::Ruby);
2811
2812        assert!(
2813            functions.contains(&"top_level_function".to_string()),
2814            "Should find top_level_function"
2815        );
2816        assert!(
2817            functions.contains(&"another_function".to_string()),
2818            "Should find another_function"
2819        );
2820        // Method inside class should NOT be in functions
2821        assert!(
2822            !functions.contains(&"method_in_class".to_string()),
2823            "Should not find method_in_class in functions"
2824        );
2825    }
2826
2827    #[test]
2828    fn test_extract_ruby_methods() {
2829        let source = r##"
2830def top_level_function
2831  puts "I'm a function"
2832end
2833
2834class MyClass
2835  def initialize(name)
2836    @name = name
2837  end
2838
2839  def greet
2840    puts "Hello"
2841  end
2842end
2843
2844module MyModule
2845  def module_method
2846    puts "module method"
2847  end
2848end
2849"##;
2850        let tree = parse(source, Language::Ruby).unwrap();
2851        let methods = extract_methods(&tree, source, Language::Ruby);
2852
2853        // Methods inside class should be found
2854        assert!(
2855            methods.contains(&"initialize".to_string()),
2856            "Should find initialize method"
2857        );
2858        assert!(
2859            methods.contains(&"greet".to_string()),
2860            "Should find greet method"
2861        );
2862        assert!(
2863            methods.contains(&"module_method".to_string()),
2864            "Should find module_method"
2865        );
2866        // Top-level function should NOT be in methods
2867        assert!(
2868            !methods.contains(&"top_level_function".to_string()),
2869            "Should not find top_level_function in methods"
2870        );
2871    }
2872
2873    #[test]
2874    fn test_extract_ruby_classes() {
2875        let source = r##"
2876class MyClass
2877  def initialize
2878  end
2879end
2880
2881class AnotherClass < BaseClass
2882  def method
2883  end
2884end
2885
2886module MyModule
2887  class NestedClass
2888  end
2889end
2890"##;
2891        let tree = parse(source, Language::Ruby).unwrap();
2892        let classes = extract_classes(&tree, source, Language::Ruby);
2893
2894        assert!(
2895            classes.contains(&"MyClass".to_string()),
2896            "Should find MyClass"
2897        );
2898        assert!(
2899            classes.contains(&"AnotherClass".to_string()),
2900            "Should find AnotherClass"
2901        );
2902        assert!(
2903            classes.contains(&"MyModule".to_string()),
2904            "Should find MyModule"
2905        );
2906        assert!(
2907            classes.contains(&"NestedClass".to_string()),
2908            "Should find NestedClass"
2909        );
2910    }
2911
2912    #[test]
2913    fn test_extract_kotlin_functions() {
2914        let source = r#"
2915fun topLevel() {
2916    println("hello")
2917}
2918
2919fun anotherTopLevel(x: Int): Int {
2920    return x * 2
2921}
2922
2923class MyClass {
2924    fun classMethod() {}
2925}
2926"#;
2927        let tree = parse(source, Language::Kotlin).unwrap();
2928        let functions = extract_functions(&tree, source, Language::Kotlin);
2929
2930        assert!(
2931            functions.contains(&"topLevel".to_string()),
2932            "Should find topLevel function"
2933        );
2934        assert!(
2935            functions.contains(&"anotherTopLevel".to_string()),
2936            "Should find anotherTopLevel function"
2937        );
2938        // Methods inside classes should NOT be in top-level functions
2939        assert!(
2940            !functions.contains(&"classMethod".to_string()),
2941            "Should not find classMethod in functions"
2942        );
2943    }
2944
2945    #[test]
2946    fn test_extract_kotlin_methods() {
2947        let source = r#"
2948fun topLevel() {}
2949
2950class MyClass {
2951    fun method1() {}
2952    fun method2(x: Int): String { return x.toString() }
2953}
2954
2955object Singleton {
2956    fun singletonMethod() {}
2957}
2958"#;
2959        let tree = parse(source, Language::Kotlin).unwrap();
2960        let methods = extract_methods(&tree, source, Language::Kotlin);
2961
2962        assert!(
2963            methods.contains(&"method1".to_string()),
2964            "Should find method1"
2965        );
2966        assert!(
2967            methods.contains(&"method2".to_string()),
2968            "Should find method2"
2969        );
2970        // Top-level functions should NOT be in methods
2971        assert!(
2972            !methods.contains(&"topLevel".to_string()),
2973            "Should not find topLevel in methods"
2974        );
2975    }
2976
2977    #[test]
2978    fn test_extract_kotlin_classes() {
2979        let source = r#"
2980class HttpClient(val engine: Engine) {
2981    fun config() {}
2982}
2983
2984object Singleton {
2985    fun method() {}
2986}
2987
2988interface MyInterface {
2989    fun abstractMethod()
2990}
2991"#;
2992        let tree = parse(source, Language::Kotlin).unwrap();
2993        let classes = extract_classes(&tree, source, Language::Kotlin);
2994
2995        assert!(
2996            classes.contains(&"HttpClient".to_string()),
2997            "Should find HttpClient class"
2998        );
2999        assert!(
3000            classes.contains(&"Singleton".to_string()),
3001            "Should find Singleton object"
3002        );
3003        assert!(
3004            classes.contains(&"MyInterface".to_string()),
3005            "Should find MyInterface interface"
3006        );
3007    }
3008
3009    #[test]
3010    fn test_extract_ocaml_functions() {
3011        let source = r#"
3012let greet name =
3013  Printf.printf "Hello, %s!\n" name
3014
3015let add x y = x + y
3016
3017let value = 42
3018
3019let rec factorial n =
3020  if n <= 1 then 1
3021  else n * factorial (n - 1)
3022
3023let () = greet "world"
3024"#;
3025        let tree = parse(source, Language::Ocaml).unwrap();
3026        let functions = extract_functions(&tree, source, Language::Ocaml);
3027
3028        assert!(
3029            functions.contains(&"greet".to_string()),
3030            "Should find greet function"
3031        );
3032        assert!(
3033            functions.contains(&"add".to_string()),
3034            "Should find add function"
3035        );
3036        assert!(
3037            functions.contains(&"factorial".to_string()),
3038            "Should find factorial function"
3039        );
3040        // 'value' has no parameters, it is a value binding not a function
3041        assert!(
3042            !functions.contains(&"value".to_string()),
3043            "Should not find value binding as function"
3044        );
3045        // let () = ... is not a named function
3046        assert!(
3047            !functions.contains(&"()".to_string()),
3048            "Should not find anonymous let () binding"
3049        );
3050    }
3051
3052    // =========================================================================
3053    // Rust extraction tests -- traits, impl blocks, and struct/enum
3054    // =========================================================================
3055
3056    #[test]
3057    fn test_extract_rust_classes_includes_traits() {
3058        let source = r#"
3059pub struct Config {
3060    pub name: String,
3061}
3062
3063pub enum Color {
3064    Red,
3065    Green,
3066    Blue,
3067}
3068
3069pub trait Serialize {
3070    fn serialize(&self) -> String;
3071}
3072
3073trait Deserialize {
3074    fn deserialize(input: &str) -> Self;
3075}
3076"#;
3077        let tree = parse(source, Language::Rust).unwrap();
3078        let classes = extract_classes(&tree, source, Language::Rust);
3079
3080        assert!(
3081            classes.contains(&"Config".to_string()),
3082            "Should find Config struct"
3083        );
3084        assert!(
3085            classes.contains(&"Color".to_string()),
3086            "Should find Color enum"
3087        );
3088        assert!(
3089            classes.contains(&"Serialize".to_string()),
3090            "Should find Serialize trait"
3091        );
3092        assert!(
3093            classes.contains(&"Deserialize".to_string()),
3094            "Should find Deserialize trait"
3095        );
3096    }
3097
3098    #[test]
3099    fn test_extract_rust_functions_excludes_trait_methods() {
3100        let source = r#"
3101pub fn top_level() -> bool {
3102    true
3103}
3104
3105pub trait Visitor {
3106    fn visit_bool(&self, v: bool) {}
3107    fn visit_i32(&self, v: i32) {}
3108}
3109
3110impl Config {
3111    pub fn new() -> Self {
3112        Config {}
3113    }
3114}
3115"#;
3116        let tree = parse(source, Language::Rust).unwrap();
3117        let functions = extract_functions(&tree, source, Language::Rust);
3118
3119        assert!(
3120            functions.contains(&"top_level".to_string()),
3121            "Should find top_level function"
3122        );
3123        // Trait methods should NOT appear as top-level functions
3124        assert!(
3125            !functions.contains(&"visit_bool".to_string()),
3126            "Trait method visit_bool should not be a top-level function"
3127        );
3128        assert!(
3129            !functions.contains(&"visit_i32".to_string()),
3130            "Trait method visit_i32 should not be a top-level function"
3131        );
3132        // impl methods should NOT appear as top-level functions
3133        assert!(
3134            !functions.contains(&"new".to_string()),
3135            "Impl method new should not be a top-level function"
3136        );
3137    }
3138
3139    #[test]
3140    fn test_extract_rust_methods_includes_trait_methods() {
3141        let source = r#"
3142pub trait Visitor {
3143    fn visit_bool(&self, v: bool) {}
3144    fn visit_i32(&self, v: i32) {}
3145}
3146
3147impl Config {
3148    pub fn new() -> Self {
3149        Config {}
3150    }
3151    pub fn name(&self) -> &str {
3152        &self.name
3153    }
3154}
3155
3156fn top_level() {}
3157"#;
3158        let tree = parse(source, Language::Rust).unwrap();
3159        let methods = extract_methods(&tree, source, Language::Rust);
3160
3161        // Trait default methods should be in methods
3162        assert!(
3163            methods.contains(&"visit_bool".to_string()),
3164            "Should find visit_bool trait method"
3165        );
3166        assert!(
3167            methods.contains(&"visit_i32".to_string()),
3168            "Should find visit_i32 trait method"
3169        );
3170        // Impl methods should be in methods
3171        assert!(
3172            methods.contains(&"new".to_string()),
3173            "Should find new impl method"
3174        );
3175        assert!(
3176            methods.contains(&"name".to_string()),
3177            "Should find name impl method"
3178        );
3179        // Top-level functions should NOT be in methods
3180        assert!(
3181            !methods.contains(&"top_level".to_string()),
3182            "top_level should not be in methods"
3183        );
3184    }
3185
3186    // =========================================================================
3187    // Fixture-based extraction accuracy tests (18 languages)
3188    // =========================================================================
3189    //
3190    // Each test reads a fixture file, parses it, and asserts the expected
3191    // counts for functions, classes, and methods. These document the current
3192    // extraction coverage and identify gaps for unimplemented languages.
3193
3194    /// Helper: run all three extractors on a source string for a given language
3195    /// and return (functions, classes, methods) counts.
3196    fn extract_counts(source: &str, lang: Language) -> (usize, usize, usize) {
3197        let tree = parse(source, lang).expect("parsing should succeed");
3198        let functions = extract_functions(&tree, source, lang);
3199        let classes = extract_classes(&tree, source, lang);
3200        let methods = extract_methods(&tree, source, lang);
3201        (functions.len(), classes.len(), methods.len())
3202    }
3203
3204    #[test]
3205    fn test_extractor_python() {
3206        let source = include_str!("../../tests/fixtures/extractor/test_python.py");
3207        let (f, c, m) = extract_counts(source, Language::Python);
3208        assert_eq!(f, 3, "Python: expected 3 functions, got {}", f);
3209        assert_eq!(c, 2, "Python: expected 2 classes, got {}", c);
3210        assert_eq!(m, 5, "Python: expected 5 methods, got {}", m);
3211    }
3212
3213    #[test]
3214    fn test_extractor_go() {
3215        let source = include_str!("../../tests/fixtures/extractor/test_go.go");
3216        let (f, c, m) = extract_counts(source, Language::Go);
3217        assert_eq!(f, 4, "Go: expected 4 functions, got {}", f);
3218        assert_eq!(c, 2, "Go: expected 2 structs, got {}", c);
3219        assert_eq!(m, 0, "Go: expected 0 methods, got {}", m);
3220    }
3221
3222    #[test]
3223    fn test_extractor_rust() {
3224        let source = include_str!("../../tests/fixtures/extractor/test_rust.rs");
3225        let (f, c, m) = extract_counts(source, Language::Rust);
3226        assert_eq!(f, 3, "Rust: expected 3 functions, got {}", f);
3227        assert_eq!(c, 2, "Rust: expected 2 structs, got {}", c);
3228        assert_eq!(m, 4, "Rust: expected 4 methods, got {}", m);
3229    }
3230
3231    #[test]
3232    fn test_extractor_java() {
3233        let source = include_str!("../../tests/fixtures/extractor/test_java.java");
3234        let (f, c, m) = extract_counts(source, Language::Java);
3235        assert_eq!(f, 0, "Java: expected 0 functions, got {}", f);
3236        assert_eq!(c, 3, "Java: expected 3 classes, got {}", c);
3237        assert_eq!(m, 6, "Java: expected 6 methods, got {}", m);
3238    }
3239
3240    #[test]
3241    fn test_extractor_c() {
3242        let source = include_str!("../../tests/fixtures/extractor/test_c.c");
3243        let (f, c, m) = extract_counts(source, Language::C);
3244        assert_eq!(f, 4, "C: expected 4 functions, got {}", f);
3245        assert_eq!(c, 2, "C: expected 2 structs, got {}", c);
3246        assert_eq!(m, 0, "C: expected 0 methods, got {}", m);
3247    }
3248
3249    #[test]
3250    fn test_extractor_cpp() {
3251        let source = include_str!("../../tests/fixtures/extractor/test_cpp.cpp");
3252        let (f, c, m) = extract_counts(source, Language::Cpp);
3253        assert_eq!(f, 2, "C++: expected 2 functions, got {}", f);
3254        assert_eq!(c, 2, "C++: expected 2 classes, got {}", c);
3255        assert_eq!(m, 5, "C++: expected 5 methods, got {}", m);
3256    }
3257
3258    #[test]
3259    fn test_extractor_typescript() {
3260        let source = include_str!("../../tests/fixtures/extractor/test_typescript.ts");
3261        let (f, c, m) = extract_counts(source, Language::TypeScript);
3262        assert_eq!(f, 3, "TypeScript: expected 3 functions, got {}", f);
3263        assert_eq!(c, 2, "TypeScript: expected 2 classes, got {}", c);
3264        assert_eq!(m, 4, "TypeScript: expected 4 methods, got {}", m);
3265    }
3266
3267    #[test]
3268    fn test_extractor_javascript() {
3269        let source = include_str!("../../tests/fixtures/extractor/test_javascript.js");
3270        // Note: JS uses the TypeScript parser in this crate
3271        let (f, c, m) = extract_counts(source, Language::JavaScript);
3272        assert_eq!(f, 5, "JavaScript: expected 5 functions, got {}", f);
3273        assert_eq!(c, 2, "JavaScript: expected 2 classes, got {}", c);
3274        assert_eq!(m, 4, "JavaScript: expected 4 methods, got {}", m);
3275    }
3276
3277    #[test]
3278    fn test_extractor_ruby() {
3279        let source = include_str!("../../tests/fixtures/extractor/test_ruby.rb");
3280        let (f, c, m) = extract_counts(source, Language::Ruby);
3281        assert_eq!(f, 2, "Ruby: expected 2 functions, got {}", f);
3282        assert_eq!(c, 2, "Ruby: expected 2 classes, got {}", c);
3283        assert_eq!(m, 5, "Ruby: expected 5 methods, got {}", m);
3284    }
3285
3286    #[test]
3287    fn test_extractor_php() {
3288        let source = include_str!("../../tests/fixtures/extractor/test_php.php");
3289        let (f, c, m) = extract_counts(source, Language::Php);
3290        assert_eq!(f, 2, "PHP: expected 2 functions, got {}", f);
3291        assert_eq!(c, 2, "PHP: expected 2 classes, got {}", c);
3292        assert_eq!(m, 5, "PHP: expected 5 methods, got {}", m);
3293    }
3294
3295    #[test]
3296    fn test_extractor_kotlin() {
3297        let source = include_str!("../../tests/fixtures/extractor/test_kotlin.kt");
3298        let (f, c, m) = extract_counts(source, Language::Kotlin);
3299        assert_eq!(f, 2, "Kotlin: expected 2 functions, got {}", f);
3300        assert_eq!(c, 2, "Kotlin: expected 2 classes, got {}", c);
3301        assert_eq!(m, 5, "Kotlin: expected 5 methods, got {}", m);
3302    }
3303
3304    #[test]
3305    fn test_extractor_swift() {
3306        let source = include_str!("../../tests/fixtures/extractor/test_swift.swift");
3307        let (f, c, m) = extract_counts(source, Language::Swift);
3308        assert_eq!(f, 3, "Swift: expected 3 functions, got {}", f);
3309        assert_eq!(c, 2, "Swift: expected 2 classes, got {}", c);
3310        assert_eq!(m, 5, "Swift: expected 5 methods, got {}", m);
3311    }
3312
3313    #[test]
3314    fn test_extractor_csharp() {
3315        let source = include_str!("../../tests/fixtures/extractor/test_csharp.cs");
3316        let (f, c, m) = extract_counts(source, Language::CSharp);
3317        assert_eq!(f, 0, "C#: expected 0 functions, got {}", f);
3318        assert_eq!(c, 3, "C#: expected 3 classes, got {}", c);
3319        assert_eq!(
3320            m, 7,
3321            "C#: expected 7 methods (including constructors), got {}",
3322            m
3323        );
3324    }
3325
3326    #[test]
3327    fn test_extractor_scala() {
3328        let source = include_str!("../../tests/fixtures/extractor/test_scala.scala");
3329        let (f, c, m) = extract_counts(source, Language::Scala);
3330        assert_eq!(f, 2, "Scala: expected 2 functions, got {}", f);
3331        assert_eq!(c, 2, "Scala: expected 2 classes, got {}", c);
3332        assert_eq!(m, 4, "Scala: expected 4 methods, got {}", m);
3333    }
3334
3335    #[test]
3336    fn test_extractor_ocaml() {
3337        let source = include_str!("../../tests/fixtures/extractor/test_ocaml.ml");
3338        let (f, c, m) = extract_counts(source, Language::Ocaml);
3339        assert_eq!(f, 3, "OCaml: expected 3 functions, got {}", f);
3340        assert_eq!(c, 0, "OCaml: expected 0 classes, got {}", c);
3341        assert_eq!(m, 0, "OCaml: expected 0 methods, got {}", m);
3342    }
3343
3344    #[test]
3345    fn test_extractor_elixir() {
3346        let source = include_str!("../../tests/fixtures/extractor/test_elixir.ex");
3347        let (f, c, m) = extract_counts(source, Language::Elixir);
3348        assert_eq!(f, 3, "Elixir: expected 3 functions, got {}", f);
3349        assert_eq!(c, 1, "Elixir: expected 1 class (module), got {}", c);
3350        assert_eq!(m, 0, "Elixir: expected 0 methods, got {}", m);
3351    }
3352
3353    #[test]
3354    fn test_extractor_lua() {
3355        let source = include_str!("../../tests/fixtures/extractor/test_lua.lua");
3356        let (f, c, m) = extract_counts(source, Language::Lua);
3357        assert_eq!(f, 5, "Lua: expected 5 functions, got {}", f);
3358        assert_eq!(c, 0, "Lua: expected 0 classes, got {}", c);
3359        assert_eq!(m, 0, "Lua: expected 0 methods, got {}", m);
3360    }
3361
3362    #[test]
3363    fn test_extractor_luau() {
3364        let source = include_str!("../../tests/fixtures/extractor/test_luau.luau");
3365        let (f, c, m) = extract_counts(source, Language::Luau);
3366        assert_eq!(f, 3, "Luau: expected 3 functions, got {}", f);
3367        assert_eq!(c, 0, "Luau: expected 0 classes, got {}", c);
3368        assert_eq!(m, 0, "Luau: expected 0 methods, got {}", m);
3369    }
3370
3371    // ── constant definitions ──────────────────────────────────────────────
3372
3373    fn get_constants(source: &str, language: Language) -> Vec<DefinitionInfo> {
3374        let tree = parse(source, language).unwrap();
3375        let defs = extract_definitions(&tree, source, language);
3376        defs.into_iter().filter(|d| d.kind == "constant").collect()
3377    }
3378
3379    #[test]
3380    fn test_python_constant_definitions() {
3381        let source = "MAX_RETRIES = 3\n\nEXTERNAL_FUNCTIONS = {\n    \"foo\": bar,\n    \"baz\": qux,\n}\n\nlower_case = 42\n";
3382        let consts = get_constants(source, Language::Python);
3383        assert_eq!(consts.len(), 2);
3384        assert_eq!(consts[0].name, "MAX_RETRIES");
3385        assert_eq!(consts[0].line_start, 1);
3386        assert_eq!(consts[0].line_end, 1);
3387        assert_eq!(consts[0].signature, "MAX_RETRIES = 3");
3388        assert_eq!(consts[1].name, "EXTERNAL_FUNCTIONS");
3389        assert_eq!(consts[1].line_start, 3);
3390        assert_eq!(consts[1].line_end, 6);
3391        assert_eq!(consts[1].signature, "EXTERNAL_FUNCTIONS = {");
3392    }
3393
3394    #[test]
3395    fn test_rust_constant_definitions() {
3396        let source = "const MAX_SIZE: usize = 100;\npub static GLOBAL: &str = \"hello\";\nlet x = 5;\n";
3397        let consts = get_constants(source, Language::Rust);
3398        assert_eq!(consts.len(), 2);
3399        assert_eq!(consts[0].name, "MAX_SIZE");
3400        assert_eq!(consts[0].kind, "constant");
3401        assert_eq!(consts[1].name, "GLOBAL");
3402    }
3403
3404    #[test]
3405    fn test_go_constant_definitions() {
3406        let source = "package main\n\nconst MaxRetries = 3\n\nconst (\n\tA = 1\n\tB = 2\n)\n";
3407        let consts = get_constants(source, Language::Go);
3408        assert_eq!(consts.len(), 3);
3409        let names: Vec<&str> = consts.iter().map(|c| c.name.as_str()).collect();
3410        assert!(names.contains(&"MaxRetries"));
3411        assert!(names.contains(&"A"));
3412        assert!(names.contains(&"B"));
3413    }
3414
3415    #[test]
3416    fn test_typescript_constant_definitions() {
3417        let source = "const MAX_RETRIES = 3;\nexport const API_URL = \"https://example.com\";\nconst lower = 42;\n";
3418        let consts = get_constants(source, Language::TypeScript);
3419        assert_eq!(consts.len(), 2);
3420        assert_eq!(consts[0].name, "MAX_RETRIES");
3421        assert_eq!(consts[1].name, "API_URL");
3422    }
3423
3424    #[test]
3425    fn test_javascript_multiline_constant() {
3426        let source = "const EXTERNAL_FUNCTIONS = {\n  foo: 1,\n  bar: 2,\n};\n";
3427        let consts = get_constants(source, Language::JavaScript);
3428        assert_eq!(consts.len(), 1);
3429        assert_eq!(consts[0].name, "EXTERNAL_FUNCTIONS");
3430        assert_eq!(consts[0].line_start, 1);
3431        assert_eq!(consts[0].line_end, 4);
3432        assert_eq!(consts[0].signature, "const EXTERNAL_FUNCTIONS = {");
3433    }
3434
3435    #[test]
3436    fn test_c_constant_definitions() {
3437        let source = "#define MAX_SIZE 100\nconst int BUFFER_LEN = 256;\nint x = 5;\n";
3438        let consts = get_constants(source, Language::C);
3439        assert_eq!(consts.len(), 2);
3440        assert_eq!(consts[0].name, "MAX_SIZE");
3441        assert_eq!(consts[1].name, "BUFFER_LEN");
3442    }
3443
3444    #[test]
3445    fn test_java_constant_definitions() {
3446        let source = "class Config {\n    public static final int MAX_RETRIES = 3;\n    public static final String API_URL = \"https://example.com\";\n    private int x = 5;\n}\n";
3447        let consts = get_constants(source, Language::Java);
3448        assert_eq!(consts.len(), 2);
3449        assert_eq!(consts[0].name, "MAX_RETRIES");
3450        assert_eq!(consts[1].name, "API_URL");
3451    }
3452
3453    #[test]
3454    fn test_ruby_constant_definitions() {
3455        let source = "MAX_RETRIES = 3\nAPI_URL = \"https://example.com\"\nlower = 42\n";
3456        let consts = get_constants(source, Language::Ruby);
3457        assert_eq!(consts.len(), 2);
3458        assert_eq!(consts[0].name, "MAX_RETRIES");
3459        assert_eq!(consts[1].name, "API_URL");
3460    }
3461
3462    // ── Bug fix: Python top-level def must be "function" not "method" ─────
3463
3464    #[test]
3465    fn test_python_toplevel_function_kind_is_function() {
3466        let source = r#"
3467def top_level():
3468    pass
3469
3470class MyClass:
3471    def method(self):
3472        pass
3473"#;
3474        let tree = parse(source, Language::Python).unwrap();
3475        let defs = extract_definitions(&tree, source, Language::Python);
3476        let top_level = defs.iter().find(|d| d.name == "top_level");
3477        assert!(top_level.is_some(), "top_level definition not found");
3478        assert_eq!(
3479            top_level.unwrap().kind,
3480            "function",
3481            "top-level Python def must have kind 'function', not 'method'"
3482        );
3483        let method = defs.iter().find(|d| d.name == "method");
3484        assert!(method.is_some(), "method definition not found");
3485        assert_eq!(
3486            method.unwrap().kind,
3487            "method",
3488            "Python def inside class must have kind 'method'"
3489        );
3490    }
3491
3492    // ── Bug fix: OCaml definitions array must be populated ────────────────
3493
3494    #[test]
3495    fn test_ocaml_definitions_non_empty() {
3496        let source = r#"
3497let top_level x = x * 2
3498
3499let another_func x y = x + y
3500
3501let rec factorial n =
3502  if n <= 1 then 1
3503  else n * factorial (n - 1)
3504
3505let () =
3506  let result = factorial 5 in
3507  Printf.printf "%d\n" result
3508"#;
3509        let tree = parse(source, Language::Ocaml).unwrap();
3510        let defs = extract_definitions(&tree, source, Language::Ocaml);
3511        assert!(
3512            !defs.is_empty(),
3513            "OCaml definitions array must be non-empty; got 0"
3514        );
3515        let names: Vec<&str> = defs.iter().map(|d| d.name.as_str()).collect();
3516        assert!(
3517            names.contains(&"top_level"),
3518            "OCaml: expected 'top_level' in definitions, got {:?}",
3519            names
3520        );
3521        assert!(
3522            names.contains(&"another_func"),
3523            "OCaml: expected 'another_func' in definitions, got {:?}",
3524            names
3525        );
3526        assert!(
3527            names.contains(&"factorial"),
3528            "OCaml: expected 'factorial' in definitions, got {:?}",
3529            names
3530        );
3531        // Anonymous entry-point let () = ... must not appear
3532        assert!(
3533            !names.contains(&"()"),
3534            "OCaml: '()' binding must not appear in definitions"
3535        );
3536    }
3537
3538    // ── Bug fix: Kotlin companion object must produce a named definition ──
3539
3540    #[test]
3541    fn test_kotlin_companion_object_definition() {
3542        let source = r#"
3543class Animal(val name: String) {
3544    fun speak(): String = "..."
3545
3546    companion object {
3547        fun create(name: String): Animal = Animal(name)
3548    }
3549}
3550"#;
3551        let tree = parse(source, Language::Kotlin).unwrap();
3552        let defs = extract_definitions(&tree, source, Language::Kotlin);
3553        let companion = defs.iter().find(|d| d.name == "Companion");
3554        assert!(
3555            companion.is_some(),
3556            "Kotlin: companion object must produce a 'Companion' definition; definitions: {:?}",
3557            defs.iter().map(|d| (&d.name, &d.kind)).collect::<Vec<_>>()
3558        );
3559        assert_eq!(
3560            companion.unwrap().kind,
3561            "class",
3562            "Kotlin companion object kind must be 'class'"
3563        );
3564    }
3565
3566    // ── Bug fix: C struct_specifier without body must not enter definitions ──
3567
3568    #[test]
3569    fn test_c_struct_ref_not_emitted_as_definition_val_001() {
3570        // VAL-001: In C, `struct sockaddr *addr` in a parameter list parses as a
3571        // `struct_specifier` with a `name` field but NO `body`. It is a type
3572        // reference, not a definition, and must not appear in definitions[].
3573        let source = r#"
3574int open_connection(struct sockaddr *addr, struct sockaddr_in *sin) {
3575    return 0;
3576}
3577"#;
3578        let tree = parse(source, Language::C).unwrap();
3579        let defs = extract_definitions(&tree, source, Language::C);
3580        let names: Vec<String> = defs.iter().map(|d| d.name.clone()).collect();
3581
3582        assert!(
3583            names.contains(&"open_connection".to_string()),
3584            "VAL-001: open_connection must be in definitions; got {:?}",
3585            names
3586        );
3587        assert!(
3588            !names.contains(&"sockaddr".to_string()),
3589            "VAL-001: bare struct_specifier `struct sockaddr` (no body) \
3590             must NOT appear in definitions; got {:?}",
3591            names
3592        );
3593        assert!(
3594            !names.contains(&"sockaddr_in".to_string()),
3595            "VAL-001: bare struct_specifier `struct sockaddr_in` (no body) \
3596             must NOT appear in definitions; got {:?}",
3597            names
3598        );
3599    }
3600
3601    // ── Bug fix: Swift init_declaration must appear in definitions as method ──
3602
3603    #[test]
3604    fn test_swift_init_emitted_as_method_definition_val_002() {
3605        // VAL-002: Swift `init` inside a class must appear in definitions[]
3606        // with kind="method". `extract_swift_methods` already handles this for
3607        // the methods[] array; definitions[] must be consistent.
3608        let source = r#"
3609class Foo {
3610    var x: Int = 0
3611    init(x: Int) { self.x = x }
3612    func bar() {}
3613}
3614"#;
3615        let tree = parse(source, Language::Swift).unwrap();
3616        let defs = extract_definitions(&tree, source, Language::Swift);
3617        let named: Vec<(String, String)> = defs
3618            .iter()
3619            .map(|d| (d.name.clone(), d.kind.clone()))
3620            .collect();
3621
3622        let init = defs.iter().find(|d| d.name == "init");
3623        assert!(
3624            init.is_some(),
3625            "VAL-002: Swift init must be in definitions; got {:?}",
3626            named
3627        );
3628        assert_eq!(
3629            init.unwrap().kind,
3630            "method",
3631            "VAL-002: Swift init inside class must have kind='method'; got {:?}",
3632            named
3633        );
3634    }
3635
3636    // ── Bug fix: Java constructor_declaration must appear as method ─────
3637
3638    #[test]
3639    fn test_java_constructor_emitted_as_method_definition_val_003() {
3640        // VAL-003: Java `public Store()` constructor must appear in
3641        // definitions[] with kind="method" (mirroring C# / existing methods).
3642        let source = r#"
3643public class Store {
3644    public Store() {}
3645    public void get() {}
3646}
3647"#;
3648        let tree = parse(source, Language::Java).unwrap();
3649        let defs = extract_definitions(&tree, source, Language::Java);
3650        let named: Vec<(String, String)> = defs
3651            .iter()
3652            .map(|d| (d.name.clone(), d.kind.clone()))
3653            .collect();
3654
3655        let ctor = defs.iter().find(|d| d.name == "Store" && d.kind == "method");
3656        assert!(
3657            ctor.is_some(),
3658            "VAL-003: Java constructor `Store` must be in definitions with \
3659             kind='method'; got {:?}",
3660            named
3661        );
3662        // Regular method still present
3663        assert!(
3664            defs.iter().any(|d| d.name == "get" && d.kind == "method"),
3665            "VAL-003: Java method `get` must remain in definitions; got {:?}",
3666            named
3667        );
3668    }
3669
3670    // ── Bug fix: Class-scope fields must appear in definitions as kind=field ──
3671
3672    #[test]
3673    fn test_class_fields_emitted_as_definitions_val_004() {
3674        // VAL-004: Class-scope field/property declarations must appear in
3675        // definitions[] with kind="field". Covers Java, Kotlin, Swift, TS.
3676        //
3677        // Guard: Kotlin top-level `val/var` parses as property_declaration
3678        // too, but must NOT be emitted as a field because it is not inside a
3679        // class_body.
3680
3681        // Java
3682        {
3683            let source = r#"
3684public class Store {
3685    private int count = 0;
3686    public String name;
3687    int x, y;
3688    public void get() {}
3689}
3690"#;
3691            let tree = parse(source, Language::Java).unwrap();
3692            let defs = extract_definitions(&tree, source, Language::Java);
3693            let fields: Vec<String> = defs
3694                .iter()
3695                .filter(|d| d.kind == "field")
3696                .map(|d| d.name.clone())
3697                .collect();
3698            for expected in ["count", "name", "x", "y"] {
3699                assert!(
3700                    fields.contains(&expected.to_string()),
3701                    "VAL-004 (Java): field `{}` must appear in definitions; \
3702                     got fields={:?}, all defs={:?}",
3703                    expected,
3704                    fields,
3705                    defs.iter()
3706                        .map(|d| (&d.name, &d.kind))
3707                        .collect::<Vec<_>>()
3708                );
3709            }
3710        }
3711
3712        // Kotlin: class-scope val/var must be fields; top-level must NOT.
3713        {
3714            let source = r#"
3715class Foo {
3716    val x: Int = 0
3717    var y: String = "hi"
3718    fun bar() {}
3719}
3720
3721val topLevelX = 1
3722"#;
3723            let tree = parse(source, Language::Kotlin).unwrap();
3724            let defs = extract_definitions(&tree, source, Language::Kotlin);
3725            let fields: Vec<String> = defs
3726                .iter()
3727                .filter(|d| d.kind == "field")
3728                .map(|d| d.name.clone())
3729                .collect();
3730            assert!(
3731                fields.contains(&"x".to_string()),
3732                "VAL-004 (Kotlin): class-scope `val x` must be a field; \
3733                 got fields={:?}",
3734                fields
3735            );
3736            assert!(
3737                fields.contains(&"y".to_string()),
3738                "VAL-004 (Kotlin): class-scope `var y` must be a field; \
3739                 got fields={:?}",
3740                fields
3741            );
3742            assert!(
3743                !fields.contains(&"topLevelX".to_string()),
3744                "VAL-004 (Kotlin): top-level `val topLevelX` must NOT be a \
3745                 field (only class-scope properties are fields); got \
3746                 fields={:?}",
3747                fields
3748            );
3749        }
3750
3751        // Swift
3752        {
3753            let source = r#"
3754class Foo {
3755    var x: Int = 0
3756    let y: String = "hi"
3757    func bar() {}
3758}
3759"#;
3760            let tree = parse(source, Language::Swift).unwrap();
3761            let defs = extract_definitions(&tree, source, Language::Swift);
3762            let fields: Vec<String> = defs
3763                .iter()
3764                .filter(|d| d.kind == "field")
3765                .map(|d| d.name.clone())
3766                .collect();
3767            for expected in ["x", "y"] {
3768                assert!(
3769                    fields.contains(&expected.to_string()),
3770                    "VAL-004 (Swift): class-scope property `{}` must be a \
3771                     field; got fields={:?}",
3772                    expected,
3773                    fields
3774                );
3775            }
3776        }
3777
3778        // TypeScript
3779        {
3780            let source = r#"
3781class Foo {
3782    public count: number = 0;
3783    name: string = "hi";
3784    bar() {}
3785}
3786"#;
3787            let tree = parse(source, Language::TypeScript).unwrap();
3788            let defs = extract_definitions(&tree, source, Language::TypeScript);
3789            let fields: Vec<String> = defs
3790                .iter()
3791                .filter(|d| d.kind == "field")
3792                .map(|d| d.name.clone())
3793                .collect();
3794            for expected in ["count", "name"] {
3795                assert!(
3796                    fields.contains(&expected.to_string()),
3797                    "VAL-004 (TypeScript): class field `{}` must be in \
3798                     definitions; got fields={:?}",
3799                    expected,
3800                    fields
3801                );
3802            }
3803        }
3804    }
3805
3806    /// VAL-001: TypeScript signature-only methods (abstract class methods
3807    /// and interface methods) must appear in both `definitions[]` (kind=method)
3808    /// and `methods[]`. Previously `classify_definition_node` only matched
3809    /// `method_definition`, so `method_signature` / `abstract_method_signature`
3810    /// nodes were silently dropped.
3811    #[test]
3812    fn test_typescript_abstract_and_interface_methods_emitted_val_001() {
3813        let source = r#"
3814export abstract class Repo<T> {
3815  abstract save(item: T): Promise<void>;
3816  find(id: string): T | null { return null; }
3817}
3818interface IFace {
3819  greet(name: string): void;
3820  defaulted(): number;
3821}
3822"#;
3823        let tree = parse(source, Language::TypeScript).unwrap();
3824
3825        // === definitions[] assertions ===
3826        let defs = extract_definitions(&tree, source, Language::TypeScript);
3827        let by_name: std::collections::BTreeMap<String, String> = defs
3828            .iter()
3829            .map(|d| (d.name.clone(), d.kind.clone()))
3830            .collect();
3831
3832        assert_eq!(
3833            by_name.get("Repo").map(String::as_str),
3834            Some("class"),
3835            "VAL-001: `Repo` must be kind=class; got defs={:?}",
3836            defs
3837        );
3838        assert_eq!(
3839            by_name.get("IFace").map(String::as_str),
3840            Some("interface"),
3841            "VAL-001: `IFace` must be kind=interface; got defs={:?}",
3842            defs
3843        );
3844        for expected in ["save", "find", "greet", "defaulted"] {
3845            assert_eq!(
3846                by_name.get(expected).map(String::as_str),
3847                Some("method"),
3848                "VAL-001: `{}` must appear in definitions with kind=method; got defs={:?}",
3849                expected,
3850                defs
3851            );
3852        }
3853
3854        // === methods[] assertions ===
3855        let methods = extract_methods(&tree, source, Language::TypeScript);
3856        for expected in ["save", "find", "greet", "defaulted"] {
3857            assert!(
3858                methods.contains(&expected.to_string()),
3859                "VAL-001: `{}` must appear in methods[]; got methods={:?}",
3860                expected,
3861                methods
3862            );
3863        }
3864    }
3865}