Skip to main content

sem_core/parser/
scope_resolve.rs

1//! Scope-aware reference resolver using tree-sitter ASTs.
2//!
3//! Instead of bag-of-words tokenization (current graph.rs Pass 2), this module
4//! walks the tree-sitter AST to find actual reference nodes (calls, attribute access)
5//! and resolves them using scope chains. This gives compiler-like accuracy for
6//! name resolution without needing a full language server.
7//!
8//! Key improvements over bag-of-words:
9//! - Distinguishes definitions from references in the AST
10//! - Resolves same-name entities via scope chains (no false collisions)
11//! - Tracks variable types through assignments (x = Foo() → x.method → Foo.method)
12//! - Uses AST structure, not string matching
13
14use std::collections::HashMap;
15use std::path::Path;
16
17use crate::model::entity::SemanticEntity;
18use crate::parser::graph::{EntityInfo, RefType};
19use crate::parser::plugins::code::languages::get_language_config;
20
21fn lang_from_ext(ext: &str) -> &'static str {
22    match ext {
23        ".py" | ".pyi" => "python",
24        ".ts" | ".tsx" | ".mts" | ".cts" => "typescript",
25        ".js" | ".jsx" | ".mjs" | ".cjs" => "typescript", // same AST structure
26        ".rs" => "rust",
27        ".go" => "go",
28        _ => "unknown",
29    }
30}
31
32/// A scope in the scope tree. Scopes are nested: module -> class -> function -> block.
33struct Scope {
34    parent: Option<usize>,
35    /// Definitions visible in this scope: name -> entity_id
36    defs: HashMap<String, String>,
37    /// Variable type bindings: var_name -> class_name (from `x = Foo()`)
38    types: HashMap<String, String>,
39    /// Unresolved call assignments: var_name -> function_name (from `x = func()`)
40    /// These get resolved after return type analysis.
41    pending_call_types: HashMap<String, String>,
42    /// Which entity owns this scope (if any)
43    owner_id: Option<String>,
44    /// What kind of scope: "module", "class", "function"
45    kind: &'static str,
46}
47
48/// Reference found in the AST
49struct AstRef {
50    /// The entity this reference is inside of
51    from_entity_id: String,
52    /// Kind of reference
53    kind: AstRefKind,
54}
55
56enum AstRefKind {
57    /// Bare name call: `foo()`
58    Call(String),
59    /// Attribute call: `x.method()`
60    MethodCall { receiver: String, method: String },
61    /// Bare name reference: `Foo` (type annotation, class instantiation)
62    Name(String),
63    /// Attribute access: `x.field`
64    Attribute { receiver: String, attr: String },
65}
66
67/// Result of scope-aware resolution
68pub struct ScopeResult {
69    pub edges: Vec<(String, String, RefType)>,
70    /// Debug info: which references were resolved and how
71    pub resolution_log: Vec<ResolutionEntry>,
72}
73
74#[derive(Clone)]
75pub struct ResolutionEntry {
76    pub from_entity: String,
77    pub reference: String,
78    pub resolved_to: Option<String>,
79    pub method: &'static str, // "scope_chain", "type_tracking", "import", "unresolved"
80}
81
82/// Resolve references using tree-sitter scope analysis.
83///
84/// For each file:
85/// 1. Parse with tree-sitter
86/// 2. Build a scope tree (module -> class -> function)
87/// 3. Walk entity AST subtrees to find reference nodes
88/// 4. Resolve each reference via scope chain + type tracking
89pub fn resolve_with_scopes(
90    root: &Path,
91    file_paths: &[String],
92    all_entities: &[SemanticEntity],
93    entity_map: &HashMap<String, EntityInfo>,
94) -> ScopeResult {
95    let mut all_edges: Vec<(String, String, RefType)> = Vec::new();
96    let mut log: Vec<ResolutionEntry> = Vec::new();
97
98    // Build global lookups
99    let mut symbol_table: HashMap<String, Vec<String>> = HashMap::new();
100    for entity in all_entities {
101        symbol_table
102            .entry(entity.name.clone())
103            .or_default()
104            .push(entity.id.clone());
105    }
106
107    // class_name -> [(member_name, member_entity_id)]
108    let mut class_members: HashMap<String, Vec<(String, String)>> = HashMap::new();
109    for entity in all_entities {
110        if let Some(ref pid) = entity.parent_id {
111            if let Some(parent) = entity_map.get(pid) {
112                if matches!(
113                    parent.entity_type.as_str(),
114                    "class" | "struct" | "interface" | "impl"
115                ) {
116                    class_members
117                        .entry(parent.name.clone())
118                        .or_default()
119                        .push((entity.name.clone(), entity.id.clone()));
120                }
121            }
122        }
123    }
124
125    // Go: methods are declared at file level with receiver syntax, not inside structs.
126    // Parse the receiver to populate class_members.
127    for entity in all_entities {
128        if entity.entity_type == "method" && entity.file_path.ends_with(".go") {
129            if let Some(struct_name) = extract_go_receiver_type(&entity.content) {
130                class_members
131                    .entry(struct_name)
132                    .or_default()
133                    .push((entity.name.clone(), entity.id.clone()));
134            }
135        }
136    }
137
138    // Entity line ranges for mapping AST nodes back to entities
139    let mut entity_ranges: HashMap<String, Vec<(usize, usize, String)>> = HashMap::new();
140    for entity in all_entities {
141        entity_ranges
142            .entry(entity.file_path.clone())
143            .or_default()
144            .push((entity.start_line, entity.end_line, entity.id.clone()));
145    }
146
147    // Build import table from AST (not regex)
148    let mut import_table: HashMap<(String, String), String> = HashMap::new();
149
150    // Return type map: function_entity_id -> class_name (if function returns ClassName())
151    let mut return_type_map: HashMap<String, String> = HashMap::new();
152
153    // Instance attribute types: (class_name, attr_name) -> class_name_of_attr
154    let mut instance_attr_types: HashMap<(String, String), String> = HashMap::new();
155
156    // __init__ param info: class_name -> (ordered_params, attr_to_param mapping)
157    // attr_to_param: attr_name -> param_name (for self.attr = param patterns)
158    let mut init_params: HashMap<String, Vec<String>> = HashMap::new();
159    let mut attr_to_param: HashMap<(String, String), String> = HashMap::new();
160
161    // Pre-parse all files and cache trees
162    let mut parsed_files: Vec<(String, String, tree_sitter::Tree)> = Vec::new();
163
164    for file_path in file_paths {
165        let full_path = root.join(file_path);
166        let content = match std::fs::read_to_string(&full_path) {
167            Ok(c) => c,
168            Err(_) => continue,
169        };
170
171        let ext = file_path
172            .rfind('.')
173            .map(|i| &file_path[i..])
174            .unwrap_or("");
175        let config = match get_language_config(ext) {
176            Some(c) => c,
177            None => continue,
178        };
179        let language = match (config.get_language)() {
180            Some(l) => l,
181            None => continue,
182        };
183
184        let mut parser = tree_sitter::Parser::new();
185        let _ = parser.set_language(&language);
186        let tree = match parser.parse(content.as_bytes(), None) {
187            Some(t) => t,
188            None => continue,
189        };
190
191        parsed_files.push((file_path.clone(), content, tree));
192    }
193
194    // Pass 1: Scan ALL files for return types and instance attr types first
195    // This ensures cross-file return type info is available during resolution
196    for (file_path, content, tree) in &parsed_files {
197        let source = content.as_bytes();
198        let ext = file_path.rfind('.').map(|i| &file_path[i..]).unwrap_or("");
199        let lang = lang_from_ext(ext);
200
201        scan_return_types(
202            tree.root_node(),
203            file_path,
204            all_entities,
205            source,
206            &mut return_type_map,
207            lang,
208        );
209
210        scan_init_self_attrs(
211            tree.root_node(),
212            file_path,
213            all_entities,
214            entity_map,
215            source,
216            &mut instance_attr_types,
217            &mut init_params,
218            &mut attr_to_param,
219            lang,
220        );
221    }
222
223    // Pass 1b: Infer constructor parameter types from call sites
224    // For `Transaction(get_connection())`, infer conn param has type Connection.
225    // Then resolve self.conn = conn -> (Transaction, conn) -> Connection
226    infer_constructor_param_types(
227        &parsed_files,
228        &return_type_map,
229        &init_params,
230        &attr_to_param,
231        &symbol_table,
232        entity_map,
233        &mut instance_attr_types,
234    );
235
236    // Pass 2: Build scopes, imports, and resolve references per file
237    for (file_path, content, tree) in &parsed_files {
238        let source = content.as_bytes();
239        let ext = file_path.rfind('.').map(|i| &file_path[i..]).unwrap_or("");
240        let lang = lang_from_ext(ext);
241
242        let mut scopes: Vec<Scope> = vec![Scope {
243            parent: None,
244            defs: HashMap::new(),
245            types: HashMap::new(),
246            pending_call_types: HashMap::new(),
247            owner_id: None,
248            kind: "module",
249        }];
250
251        let mut entity_scope_map: HashMap<String, usize> = HashMap::new();
252        let mut entity_inner_scope: HashMap<String, usize> = HashMap::new();
253
254        if let Some(ranges) = entity_ranges.get(file_path.as_str()) {
255            for (_start, _end, eid) in ranges {
256                if let Some(info) = entity_map.get(eid) {
257                    if info.parent_id.is_none() {
258                        scopes[0].defs.insert(info.name.clone(), eid.clone());
259                        entity_scope_map.insert(eid.clone(), 0);
260                    }
261                }
262            }
263        }
264
265        build_scopes_from_ast(
266            tree.root_node(),
267            0,
268            &mut scopes,
269            &mut entity_scope_map,
270            &mut entity_inner_scope,
271            all_entities,
272            entity_map,
273            file_path,
274            source,
275            lang,
276        );
277
278        extract_imports_from_ast(
279            tree.root_node(),
280            file_path,
281            source,
282            &symbol_table,
283            entity_map,
284            &mut import_table,
285            &mut scopes,
286            lang,
287        );
288
289        // Resolve pending call types using the complete return type map
290        inject_return_type_bindings(
291            &entity_inner_scope,
292            &mut scopes,
293            &return_type_map,
294            &import_table,
295            file_path,
296            entity_map,
297        );
298
299        let file_entities: Vec<&SemanticEntity> = all_entities
300            .iter()
301            .filter(|e| e.file_path == *file_path)
302            .collect();
303
304        for entity in &file_entities {
305            // Use the entity's inner scope (where local vars live), not the definition scope
306            let scope_idx = entity_inner_scope
307                .get(&entity.id)
308                .or_else(|| entity_scope_map.get(&entity.id))
309                .copied()
310                .unwrap_or(0);
311
312            let refs = extract_ast_refs(
313                tree.root_node(),
314                entity,
315                source,
316                lang,
317            );
318
319            for ast_ref in refs {
320                let resolution = resolve_ref(
321                    &ast_ref,
322                    scope_idx,
323                    &scopes,
324                    &symbol_table,
325                    &class_members,
326                    &import_table,
327                    &instance_attr_types,
328                    entity_map,
329                    file_path,
330                    &entity.id,
331                );
332
333                if let Some((target_id, ref_type, method)) = resolution {
334                    if target_id != entity.id {
335                        let is_parent_child = entity
336                            .parent_id
337                            .as_ref()
338                            .map_or(false, |pid| pid == &target_id || entity_map.get(&target_id).map_or(false, |t| t.parent_id.as_ref() == Some(&entity.id)));
339
340                        if !is_parent_child {
341                            all_edges.push((
342                                entity.id.clone(),
343                                target_id.clone(),
344                                ref_type,
345                            ));
346                            log.push(ResolutionEntry {
347                                from_entity: entity.id.clone(),
348                                reference: ref_description(&ast_ref),
349                                resolved_to: Some(target_id),
350                                method,
351                            });
352                        }
353                    }
354                } else {
355                    log.push(ResolutionEntry {
356                        from_entity: entity.id.clone(),
357                        reference: ref_description(&ast_ref),
358                        resolved_to: None,
359                        method: "unresolved",
360                    });
361                }
362            }
363        }
364    }
365
366    // Deduplicate edges
367    let mut seen = std::collections::HashSet::new();
368    all_edges.retain(|e| seen.insert((e.0.clone(), e.1.clone())));
369
370    ScopeResult {
371        edges: all_edges,
372        resolution_log: log,
373    }
374}
375
376fn ref_description(ast_ref: &AstRef) -> String {
377    match &ast_ref.kind {
378        AstRefKind::Call(name) => format!("{}()", name),
379        AstRefKind::MethodCall { receiver, method } => format!("{}.{}()", receiver, method),
380        AstRefKind::Name(name) => name.clone(),
381        AstRefKind::Attribute { receiver, attr } => format!("{}.{}", receiver, attr),
382    }
383}
384
385/// Build scope tree by walking the AST.
386/// Creates class scopes and maps methods to them.
387fn build_scopes_from_ast(
388    node: tree_sitter::Node,
389    current_scope: usize,
390    scopes: &mut Vec<Scope>,
391    entity_scope_map: &mut HashMap<String, usize>,
392    entity_inner_scope: &mut HashMap<String, usize>,
393    all_entities: &[SemanticEntity],
394    entity_map: &HashMap<String, EntityInfo>,
395    file_path: &str,
396    source: &[u8],
397    lang: &str,
398) {
399    let kind = node.kind();
400
401    // Class-like scope: Python class_definition, TS class_declaration,
402    // Rust struct_item/impl_item, Go type_declaration
403    let is_class_like = matches!(
404        kind,
405        "class_definition" | "class_declaration" | "struct_item" | "type_declaration"
406    );
407
408    // Rust impl_item: creates a class-like scope grouping methods under a struct
409    let is_impl = kind == "impl_item";
410
411    if is_class_like || is_impl {
412        let class_name = if is_impl {
413            // Rust: `impl TypeName { ... }` - extract from the `type` field
414            node.child_by_field_name("type")
415                .and_then(|n| n.utf8_text(source).ok())
416                .unwrap_or("")
417        } else if kind == "type_declaration" {
418            // Go: `type Foo struct { ... }` - name is inside type_spec child
419            let mut name = "";
420            let mut cursor = node.walk();
421            for child in node.named_children(&mut cursor) {
422                if child.kind() == "type_spec" {
423                    name = child
424                        .child_by_field_name("name")
425                        .and_then(|n| n.utf8_text(source).ok())
426                        .unwrap_or("");
427                    break;
428                }
429            }
430            name
431        } else {
432            node.child_by_field_name("name")
433                .and_then(|n| n.utf8_text(source).ok())
434                .unwrap_or("")
435        };
436
437        let class_entity = all_entities.iter().find(|e| {
438            e.file_path == file_path
439                && e.name == class_name
440                && matches!(e.entity_type.as_str(), "class" | "struct" | "interface")
441        });
442
443        if let Some(ce) = class_entity {
444            // Check if we already have a scope for this class (e.g. struct then impl)
445            let existing_scope = entity_inner_scope.get(&ce.id).copied();
446
447            let class_scope_idx = if let Some(idx) = existing_scope {
448                idx
449            } else {
450                let idx = scopes.len();
451                scopes.push(Scope {
452                    parent: Some(current_scope),
453                    defs: HashMap::new(),
454                    types: HashMap::new(),
455                    pending_call_types: HashMap::new(),
456                    owner_id: Some(ce.id.clone()),
457                    kind: "class",
458                });
459                entity_scope_map.insert(ce.id.clone(), current_scope);
460                entity_inner_scope.insert(ce.id.clone(), idx);
461                idx
462            };
463
464            // Register all child entities as defs in the class scope
465            for entity in all_entities {
466                if entity.parent_id.as_ref() == Some(&ce.id) {
467                    scopes[class_scope_idx]
468                        .defs
469                        .insert(entity.name.clone(), entity.id.clone());
470                    entity_scope_map.insert(entity.id.clone(), class_scope_idx);
471                }
472            }
473
474            let mut cursor = node.walk();
475            for child in node.named_children(&mut cursor) {
476                build_scopes_from_ast(
477                    child,
478                    class_scope_idx,
479                    scopes,
480                    entity_scope_map,
481                    entity_inner_scope,
482                    all_entities,
483                    entity_map,
484                    file_path,
485                    source,
486                    lang,
487                );
488            }
489            return;
490        } else if !is_impl {
491            // No matching entity, still recurse with a scope
492            let class_scope_idx = scopes.len();
493            scopes.push(Scope {
494                parent: Some(current_scope),
495                defs: HashMap::new(),
496                types: HashMap::new(),
497                pending_call_types: HashMap::new(),
498                owner_id: None,
499                kind: "class",
500            });
501            let mut cursor = node.walk();
502            for child in node.named_children(&mut cursor) {
503                build_scopes_from_ast(
504                    child,
505                    class_scope_idx,
506                    scopes,
507                    entity_scope_map,
508                    entity_inner_scope,
509                    all_entities,
510                    entity_map,
511                    file_path,
512                    source,
513                    lang,
514                );
515            }
516            return;
517        }
518    }
519
520    // Function-like scope: Python function_definition, Rust function_item,
521    // TS function_declaration/method_definition, Go function_declaration/method_declaration
522    let is_function_like = matches!(
523        kind,
524        "function_definition"
525            | "function_item"
526            | "function_declaration"
527            | "method_definition"
528            | "method_declaration"
529    );
530
531    if is_function_like {
532        let func_name = node.child_by_field_name("name")
533            .and_then(|n| n.utf8_text(source).ok())
534            .unwrap_or("");
535
536        // Go method_declaration: place inside the receiver's struct scope
537        let parent_scope = if kind == "method_declaration" && lang == "go" {
538            let receiver_type = node.utf8_text(source).ok()
539                .and_then(|t| extract_go_receiver_type(t));
540            if let Some(ref struct_name) = receiver_type {
541                // Find existing class scope for this struct
542                let found = scopes.iter().enumerate().find(|(_, s)| {
543                    s.kind == "class" && s.owner_id.as_ref().map_or(false, |oid| {
544                        entity_map.get(oid).map_or(false, |e| e.name == *struct_name)
545                    })
546                });
547                found.map(|(idx, _)| idx).unwrap_or(current_scope)
548            } else {
549                current_scope
550            }
551        } else {
552            current_scope
553        };
554
555        let func_scope_idx = scopes.len();
556        scopes.push(Scope {
557            parent: Some(parent_scope),
558            defs: HashMap::new(),
559            types: HashMap::new(),
560            pending_call_types: HashMap::new(),
561            owner_id: None,
562            kind: "function",
563        });
564
565        let func_entity = all_entities.iter().find(|e| {
566            e.file_path == file_path && e.name == func_name && {
567                let line = node.start_position().row + 1;
568                e.start_line <= line && line <= e.end_line
569            }
570        });
571
572        if let Some(fe) = func_entity {
573            scopes[func_scope_idx].owner_id = Some(fe.id.clone());
574            entity_scope_map.entry(fe.id.clone()).or_insert(parent_scope);
575            entity_inner_scope.insert(fe.id.clone(), func_scope_idx);
576            // For Go methods, also register in the struct's class scope defs
577            if kind == "method_declaration" && lang == "go" && parent_scope != current_scope {
578                scopes[parent_scope].defs.insert(fe.name.clone(), fe.id.clone());
579            }
580        }
581
582        scan_assignments(node, func_scope_idx, scopes, source, lang);
583        scan_function_params(node, func_scope_idx, scopes, source, lang);
584
585        // Go: add receiver parameter type binding
586        // func (t *Transaction) Execute(...) -> types["t"] = "Transaction"
587        if kind == "method_declaration" && lang == "go" {
588            if let Some(receiver) = node.child_by_field_name("receiver") {
589                // parameter_list -> parameter_declaration
590                let mut rcursor = receiver.walk();
591                for param in receiver.named_children(&mut rcursor) {
592                    if param.kind() == "parameter_declaration" {
593                        let param_name = param
594                            .child_by_field_name("name")
595                            .and_then(|n| n.utf8_text(source).ok())
596                            .unwrap_or("");
597                        let param_type = param
598                            .child_by_field_name("type")
599                            .map(|n| extract_base_type(n, source))
600                            .unwrap_or_default();
601                        if !param_name.is_empty() && !param_type.is_empty() {
602                            scopes[func_scope_idx]
603                                .types
604                                .insert(param_name.to_string(), param_type);
605                        }
606                    }
607                }
608            }
609        }
610
611        let mut cursor = node.walk();
612        for child in node.named_children(&mut cursor) {
613            build_scopes_from_ast(
614                child,
615                func_scope_idx,
616                scopes,
617                entity_scope_map,
618                entity_inner_scope,
619                all_entities,
620                entity_map,
621                file_path,
622                source,
623                lang,
624            );
625        }
626        return;
627    }
628
629    let mut cursor = node.walk();
630    for child in node.named_children(&mut cursor) {
631        build_scopes_from_ast(
632            child,
633            current_scope,
634            scopes,
635            entity_scope_map,
636            entity_inner_scope,
637            all_entities,
638            entity_map,
639            file_path,
640            source,
641            lang,
642        );
643    }
644}
645
646/// Scan for variable assignments and record type bindings.
647fn scan_assignments(
648    node: tree_sitter::Node,
649    scope_idx: usize,
650    scopes: &mut Vec<Scope>,
651    source: &[u8],
652    lang: &str,
653) {
654    let mut cursor = node.walk();
655    for child in node.named_children(&mut cursor) {
656        let ck = child.kind();
657        match lang {
658            "python" => {
659                if ck == "assignment" || ck == "expression_statement" {
660                    scan_single_assignment(child, scope_idx, scopes, source, lang);
661                }
662                if ck == "block" {
663                    scan_assignments(child, scope_idx, scopes, source, lang);
664                }
665            }
666            "typescript" => {
667                // TS: `const x = new Foo()` or `const x = func()`
668                if ck == "lexical_declaration" || ck == "variable_declaration" {
669                    scan_ts_var_declaration(child, scope_idx, scopes, source);
670                }
671                // Also: `x = Foo()` assignment_expression
672                if ck == "expression_statement" {
673                    scan_single_assignment(child, scope_idx, scopes, source, lang);
674                }
675                if ck == "statement_block" {
676                    scan_assignments(child, scope_idx, scopes, source, lang);
677                }
678            }
679            "rust" => {
680                if ck == "let_declaration" {
681                    scan_rust_let_declaration(child, scope_idx, scopes, source);
682                }
683                if ck == "block" || ck == "expression_statement" {
684                    scan_assignments(child, scope_idx, scopes, source, lang);
685                }
686            }
687            "go" => {
688                if ck == "short_var_declaration" {
689                    scan_go_short_var(child, scope_idx, scopes, source);
690                }
691                if ck == "var_declaration" {
692                    scan_go_var_declaration(child, scope_idx, scopes, source);
693                }
694                if ck == "block" {
695                    scan_assignments(child, scope_idx, scopes, source, lang);
696                }
697            }
698            _ => {}
699        }
700    }
701}
702
703/// Scan function parameter type annotations and add them as type bindings.
704/// e.g. `def foo(shelter: Shelter)` -> types["shelter"] = "Shelter"
705fn scan_function_params(
706    node: tree_sitter::Node,
707    scope_idx: usize,
708    scopes: &mut Vec<Scope>,
709    source: &[u8],
710    lang: &str,
711) {
712    let params_node = match node.child_by_field_name("parameters") {
713        Some(p) => p,
714        None => return,
715    };
716
717    let mut cursor = params_node.walk();
718    for child in params_node.named_children(&mut cursor) {
719        match lang {
720            "python" => {
721                // typed_parameter: name + type
722                if child.kind() == "typed_parameter" || child.kind() == "typed_default_parameter" {
723                    let param_name = child
724                        .child_by_field_name("name")
725                        .or_else(|| child.named_child(0).filter(|n| n.kind() == "identifier"))
726                        .and_then(|n| n.utf8_text(source).ok())
727                        .unwrap_or("");
728                    if param_name == "self" || param_name == "cls" || param_name.is_empty() {
729                        continue;
730                    }
731                    if let Some(type_node) = child.child_by_field_name("type") {
732                        let type_text = extract_base_type(type_node, source);
733                        if !type_text.is_empty()
734                            && type_text.chars().next().map_or(false, |c| c.is_uppercase())
735                        {
736                            scopes[scope_idx]
737                                .types
738                                .insert(param_name.to_string(), type_text);
739                        }
740                    }
741                }
742            }
743            "typescript" => {
744                // required_parameter / optional_parameter with type_annotation
745                if child.kind() == "required_parameter" || child.kind() == "optional_parameter" {
746                    let param_name = child
747                        .child_by_field_name("pattern")
748                        .or_else(|| child.named_child(0).filter(|n| n.kind() == "identifier"))
749                        .and_then(|n| n.utf8_text(source).ok())
750                        .unwrap_or("");
751                    if param_name == "this" || param_name.is_empty() {
752                        continue;
753                    }
754                    if let Some(type_node) = child.child_by_field_name("type") {
755                        let type_text = extract_base_type(type_node, source);
756                        if !type_text.is_empty()
757                            && type_text.chars().next().map_or(false, |c| c.is_uppercase())
758                        {
759                            scopes[scope_idx]
760                                .types
761                                .insert(param_name.to_string(), type_text);
762                        }
763                    }
764                }
765            }
766            "rust" => {
767                // parameter: pattern + type
768                if child.kind() == "parameter" {
769                    let param_name = child
770                        .child_by_field_name("pattern")
771                        .and_then(|n| {
772                            if n.kind() == "identifier" {
773                                n.utf8_text(source).ok()
774                            } else if n.kind() == "mut_pattern" {
775                                n.named_child(0).and_then(|c| c.utf8_text(source).ok())
776                            } else if n.kind() == "reference_pattern" {
777                                // &x or &mut x
778                                n.named_child(0).and_then(|c| {
779                                    if c.kind() == "identifier" {
780                                        c.utf8_text(source).ok()
781                                    } else if c.kind() == "mut_pattern" {
782                                        c.named_child(0).and_then(|cc| cc.utf8_text(source).ok())
783                                    } else {
784                                        None
785                                    }
786                                })
787                            } else {
788                                None
789                            }
790                        })
791                        .unwrap_or("");
792                    if param_name == "self" || param_name.is_empty() {
793                        continue;
794                    }
795                    if let Some(type_node) = child.child_by_field_name("type") {
796                        let type_text = extract_base_type(type_node, source);
797                        if !type_text.is_empty()
798                            && type_text.chars().next().map_or(false, |c| c.is_uppercase())
799                        {
800                            scopes[scope_idx]
801                                .types
802                                .insert(param_name.to_string(), type_text);
803                        }
804                    }
805                }
806            }
807            "go" => {
808                // parameter_declaration: name + type (handled separately for receivers above,
809                // but we also want regular function params)
810                if child.kind() == "parameter_declaration" {
811                    let param_name = child
812                        .child_by_field_name("name")
813                        .and_then(|n| n.utf8_text(source).ok())
814                        .unwrap_or("");
815                    if param_name.is_empty() {
816                        continue;
817                    }
818                    let param_type = child
819                        .child_by_field_name("type")
820                        .map(|n| extract_base_type(n, source))
821                        .unwrap_or_default();
822                    if !param_type.is_empty()
823                        && param_type.chars().next().map_or(false, |c| c.is_uppercase())
824                    {
825                        scopes[scope_idx]
826                            .types
827                            .insert(param_name.to_string(), param_type);
828                    }
829                }
830            }
831            _ => {}
832        }
833    }
834}
835
836/// Python: `x = Foo()` or `x = func()`
837fn scan_single_assignment(
838    node: tree_sitter::Node,
839    scope_idx: usize,
840    scopes: &mut Vec<Scope>,
841    source: &[u8],
842    _lang: &str,
843) {
844    let assign = if node.kind() == "assignment" {
845        node
846    } else {
847        let mut cursor = node.walk();
848        let children: Vec<_> = node.named_children(&mut cursor).collect();
849        match children.into_iter().find(|c| c.kind() == "assignment" || c.kind() == "assignment_expression") {
850            Some(a) => a,
851            None => return,
852        }
853    };
854
855    let left = match assign.child_by_field_name("left") {
856        Some(l) => l,
857        None => return,
858    };
859    let right = match assign.child_by_field_name("right") {
860        Some(r) => r,
861        None => return,
862    };
863
864    if left.kind() != "identifier" {
865        return;
866    }
867    let var_name = match left.utf8_text(source) {
868        Ok(n) => n.to_string(),
869        Err(_) => return,
870    };
871
872    record_type_from_rhs(right, &var_name, scope_idx, scopes, source);
873}
874
875/// TS: `const x = new Foo()` or `const x: Type = ...` or `const x = func()`
876fn scan_ts_var_declaration(
877    node: tree_sitter::Node,
878    scope_idx: usize,
879    scopes: &mut Vec<Scope>,
880    source: &[u8],
881) {
882    let mut cursor = node.walk();
883    for child in node.named_children(&mut cursor) {
884        if child.kind() == "variable_declarator" {
885            let var_name = child
886                .child_by_field_name("name")
887                .and_then(|n| n.utf8_text(source).ok())
888                .unwrap_or("")
889                .to_string();
890            if var_name.is_empty() {
891                continue;
892            }
893
894            // Check for explicit type annotation: `const x: Foo = ...`
895            if let Some(type_ann) = child.child_by_field_name("type") {
896                let type_text = extract_base_type(type_ann, source);
897                if !type_text.is_empty()
898                    && type_text.chars().next().map_or(false, |c| c.is_uppercase())
899                {
900                    scopes[scope_idx]
901                        .types
902                        .insert(var_name.clone(), type_text);
903                    continue;
904                }
905            }
906
907            // Check RHS value
908            if let Some(value) = child.child_by_field_name("value") {
909                record_type_from_rhs(value, &var_name, scope_idx, scopes, source);
910            }
911        }
912    }
913}
914
915/// Rust: `let x: Type = ...` or `let x = Foo::new()`
916fn scan_rust_let_declaration(
917    node: tree_sitter::Node,
918    scope_idx: usize,
919    scopes: &mut Vec<Scope>,
920    source: &[u8],
921) {
922    let var_name = node
923        .child_by_field_name("pattern")
924        .and_then(|n| {
925            // Pattern can be just an identifier or `mut x`
926            if n.kind() == "identifier" {
927                n.utf8_text(source).ok()
928            } else if n.kind() == "mut_pattern" {
929                n.named_child(0).and_then(|c| c.utf8_text(source).ok())
930            } else {
931                None
932            }
933        })
934        .unwrap_or("")
935        .to_string();
936
937    if var_name.is_empty() {
938        return;
939    }
940
941    // Check for explicit type annotation: `let x: Connection = ...`
942    if let Some(type_node) = node.child_by_field_name("type") {
943        let type_text = extract_base_type(type_node, source);
944        if !type_text.is_empty()
945            && type_text.chars().next().map_or(false, |c| c.is_uppercase())
946        {
947            scopes[scope_idx]
948                .types
949                .insert(var_name, type_text);
950            return;
951        }
952    }
953
954    // Check RHS value
955    if let Some(value) = node.child_by_field_name("value") {
956        record_type_from_rhs(value, &var_name, scope_idx, scopes, source);
957    }
958}
959
960/// Go: `x := Foo{}` or `x := NewFoo()`
961fn scan_go_short_var(
962    node: tree_sitter::Node,
963    scope_idx: usize,
964    scopes: &mut Vec<Scope>,
965    source: &[u8],
966) {
967    let left = match node.child_by_field_name("left") {
968        Some(l) => l,
969        None => return,
970    };
971    let right = match node.child_by_field_name("right") {
972        Some(r) => r,
973        None => return,
974    };
975
976    // left is expression_list, right is expression_list
977    let var_name = if left.kind() == "expression_list" {
978        left.named_child(0)
979            .and_then(|n| n.utf8_text(source).ok())
980            .unwrap_or("")
981            .to_string()
982    } else {
983        left.utf8_text(source).unwrap_or("").to_string()
984    };
985
986    if var_name.is_empty() {
987        return;
988    }
989
990    let rhs = if right.kind() == "expression_list" {
991        match right.named_child(0) {
992            Some(n) => n,
993            None => return,
994        }
995    } else {
996        right
997    };
998
999    record_type_from_rhs(rhs, &var_name, scope_idx, scopes, source);
1000}
1001
1002/// Go: `var x Type = ...` or `var x = Foo{}`
1003fn scan_go_var_declaration(
1004    node: tree_sitter::Node,
1005    scope_idx: usize,
1006    scopes: &mut Vec<Scope>,
1007    source: &[u8],
1008) {
1009    let mut cursor = node.walk();
1010    for child in node.named_children(&mut cursor) {
1011        if child.kind() == "var_spec" {
1012            let var_name = child
1013                .child_by_field_name("name")
1014                .and_then(|n| n.utf8_text(source).ok())
1015                .unwrap_or("")
1016                .to_string();
1017            if var_name.is_empty() {
1018                // Try first named child as name
1019                if let Some(first) = child.named_child(0) {
1020                    if first.kind() == "identifier" {
1021                        let name = first.utf8_text(source).unwrap_or("").to_string();
1022                        if !name.is_empty() {
1023                            // Check for type child
1024                            if let Some(type_node) = child.child_by_field_name("type") {
1025                                let type_text = extract_base_type(type_node, source);
1026                                if !type_text.is_empty()
1027                                    && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1028                                {
1029                                    scopes[scope_idx].types.insert(name, type_text);
1030                                }
1031                            }
1032                        }
1033                    }
1034                }
1035                continue;
1036            }
1037
1038            // Check for explicit type
1039            if let Some(type_node) = child.child_by_field_name("type") {
1040                let type_text = extract_base_type(type_node, source);
1041                if !type_text.is_empty()
1042                    && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1043                {
1044                    scopes[scope_idx]
1045                        .types
1046                        .insert(var_name, type_text);
1047                    continue;
1048                }
1049            }
1050
1051            // Check RHS value
1052            if let Some(value) = child.child_by_field_name("value") {
1053                let rhs = if value.kind() == "expression_list" {
1054                    value.named_child(0).unwrap_or(value)
1055                } else {
1056                    value
1057                };
1058                record_type_from_rhs(rhs, &var_name, scope_idx, scopes, source);
1059            }
1060        }
1061    }
1062}
1063
1064/// Record type binding from a RHS expression (works for all languages).
1065/// Handles: constructor calls, new expressions, struct literals, function calls.
1066fn record_type_from_rhs(
1067    rhs: tree_sitter::Node,
1068    var_name: &str,
1069    scope_idx: usize,
1070    scopes: &mut Vec<Scope>,
1071    source: &[u8],
1072) {
1073    match rhs.kind() {
1074        // Python/Go: Foo() or func()
1075        "call" | "call_expression" => {
1076            let func_node = rhs
1077                .child_by_field_name("function")
1078                .or_else(|| rhs.named_child(0));
1079            if let Some(func) = func_node {
1080                if func.kind() == "identifier" {
1081                    let name = func.utf8_text(source).unwrap_or("");
1082                    if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1083                        scopes[scope_idx]
1084                            .types
1085                            .insert(var_name.to_string(), name.to_string());
1086                    } else {
1087                        scopes[scope_idx]
1088                            .pending_call_types
1089                            .insert(var_name.to_string(), name.to_string());
1090                    }
1091                }
1092                // Rust: Type::new() / Type::from() etc.
1093                if func.kind() == "scoped_identifier" {
1094                    let text = func.utf8_text(source).unwrap_or("");
1095                    let parts: Vec<&str> = text.split("::").collect();
1096                    if parts.len() >= 2 {
1097                        let type_name = parts[0];
1098                        let method_name = parts[parts.len() - 1];
1099                        if type_name.chars().next().map_or(false, |c| c.is_uppercase()) {
1100                            scopes[scope_idx]
1101                                .types
1102                                .insert(var_name.to_string(), type_name.to_string());
1103                        } else {
1104                            scopes[scope_idx]
1105                                .pending_call_types
1106                                .insert(var_name.to_string(), method_name.to_string());
1107                        }
1108                    }
1109                }
1110                // Go: package.NewFoo() or package.GetFoo()
1111                if func.kind() == "selector_expression" {
1112                    let field = func
1113                        .child_by_field_name("field")
1114                        .and_then(|n| n.utf8_text(source).ok())
1115                        .unwrap_or("");
1116                    // Go convention: NewFoo() returns *Foo
1117                    if let Some(type_name) = field.strip_prefix("New") {
1118                        if !type_name.is_empty()
1119                            && type_name.chars().next().map_or(false, |c| c.is_uppercase())
1120                        {
1121                            scopes[scope_idx]
1122                                .types
1123                                .insert(var_name.to_string(), type_name.to_string());
1124                        }
1125                    } else if field.starts_with("Get") || field.chars().next().map_or(false, |c| c.is_uppercase()) {
1126                        // Other Go package functions: record for return type resolution
1127                        scopes[scope_idx]
1128                            .pending_call_types
1129                            .insert(var_name.to_string(), field.to_string());
1130                    }
1131                }
1132            }
1133        }
1134        // TS: new Foo()
1135        "new_expression" => {
1136            if let Some(constructor) = rhs.child_by_field_name("constructor") {
1137                let name = constructor.utf8_text(source).unwrap_or("");
1138                if !name.is_empty() {
1139                    scopes[scope_idx]
1140                        .types
1141                        .insert(var_name.to_string(), name.to_string());
1142                }
1143            }
1144        }
1145        // Go: Foo{} (composite_literal / struct literal)
1146        "composite_literal" => {
1147            if let Some(type_node) = rhs.child_by_field_name("type") {
1148                let name = type_node.utf8_text(source).unwrap_or("");
1149                if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1150                    scopes[scope_idx]
1151                        .types
1152                        .insert(var_name.to_string(), name.to_string());
1153                }
1154            }
1155        }
1156        _ => {}
1157    }
1158}
1159
1160/// Extract the base type name from a type annotation node.
1161/// Strips pointers, references, generics to get just the type name.
1162fn extract_base_type(type_node: tree_sitter::Node, source: &[u8]) -> String {
1163    let text = type_node.utf8_text(source).unwrap_or("").trim().to_string();
1164    // Strip reference/pointer prefixes and mut keyword
1165    let text = text.trim_start_matches('&').trim_start_matches('*');
1166    let text = text.strip_prefix("mut ").unwrap_or(text).trim_start();
1167    // Strip generic parameters (angle brackets and Python-style square brackets)
1168    let text = if let Some(i) = text.find('<') {
1169        &text[..i]
1170    } else if let Some(i) = text.find('[') {
1171        &text[..i]
1172    } else {
1173        text
1174    };
1175    // Strip lifetime annotations for Rust
1176    let text = text.trim();
1177    // For type_annotation nodes in TS, strip the leading `: `
1178    let text = text.trim_start_matches(':').trim();
1179    text.to_string()
1180}
1181
1182/// Parse Go receiver type from method content: `func (r *ReceiverType) Name(...)`
1183fn extract_go_receiver_type(content: &str) -> Option<String> {
1184    let after_func = content.strip_prefix("func")?.trim_start();
1185    let paren_start = after_func.find('(')?;
1186    let paren_end = after_func.find(')')?;
1187    let receiver_block = &after_func[paren_start + 1..paren_end];
1188    // Could be: "r ReceiverType", "r *ReceiverType", "*ReceiverType"
1189    let parts: Vec<&str> = receiver_block.split_whitespace().collect();
1190    let type_str = parts.last()?;
1191    let name = type_str.trim_start_matches('*');
1192    if name.is_empty() {
1193        None
1194    } else {
1195        Some(name.to_string())
1196    }
1197}
1198
1199/// Scan function bodies/signatures for return types to build a return type map.
1200fn scan_return_types(
1201    node: tree_sitter::Node,
1202    file_path: &str,
1203    all_entities: &[SemanticEntity],
1204    source: &[u8],
1205    return_type_map: &mut HashMap<String, String>,
1206    lang: &str,
1207) {
1208    let kind = node.kind();
1209
1210    let is_func = matches!(
1211        kind,
1212        "function_definition"
1213            | "function_item"
1214            | "function_declaration"
1215            | "method_definition"
1216            | "method_declaration"
1217    );
1218
1219    if is_func {
1220        let func_name = node
1221            .child_by_field_name("name")
1222            .and_then(|n| n.utf8_text(source).ok())
1223            .unwrap_or("");
1224
1225        let func_entity = all_entities.iter().find(|e| {
1226            e.file_path == file_path && e.name == func_name && {
1227                let line = node.start_position().row + 1;
1228                e.start_line <= line && line <= e.end_line
1229            }
1230        });
1231
1232        if let Some(fe) = func_entity {
1233            // Try explicit return type annotation first (TS/Rust/Go)
1234            let ret_type = match lang {
1235                "typescript" => {
1236                    // TS: function foo(): ReturnType { ... }
1237                    node.child_by_field_name("return_type")
1238                        .map(|n| extract_base_type(n, source))
1239                        .filter(|t| !t.is_empty() && t.chars().next().map_or(false, |c| c.is_uppercase()))
1240                }
1241                "rust" => {
1242                    // Rust: fn foo() -> ReturnType { ... }
1243                    node.child_by_field_name("return_type")
1244                        .map(|n| extract_base_type(n, source))
1245                        .filter(|t| !t.is_empty() && t.chars().next().map_or(false, |c| c.is_uppercase()))
1246                }
1247                "go" => {
1248                    // Go: func foo() ReturnType { ... }
1249                    node.child_by_field_name("result")
1250                        .map(|n| extract_base_type(n, source))
1251                        .filter(|t| !t.is_empty() && t.chars().next().map_or(false, |c| c.is_uppercase()))
1252                }
1253                _ => None,
1254            };
1255
1256            if let Some(rt) = ret_type {
1257                return_type_map.insert(fe.id.clone(), rt);
1258            } else {
1259                // Fall back to body heuristic: return ClassName()
1260                if let Some(ret_type) = find_return_constructor(node, source) {
1261                    return_type_map.insert(fe.id.clone(), ret_type);
1262                }
1263            }
1264        }
1265    }
1266
1267    let mut cursor = node.walk();
1268    for child in node.named_children(&mut cursor) {
1269        scan_return_types(child, file_path, all_entities, source, return_type_map, lang);
1270    }
1271}
1272
1273/// Find `return ClassName()` patterns in a function body (heuristic fallback).
1274fn find_return_constructor(node: tree_sitter::Node, source: &[u8]) -> Option<String> {
1275    let mut cursor = node.walk();
1276    for child in node.named_children(&mut cursor) {
1277        if child.kind() == "return_statement" {
1278            let mut inner_cursor = child.walk();
1279            for ret_child in child.named_children(&mut inner_cursor) {
1280                // Python: call, TS/Go: call_expression
1281                if ret_child.kind() == "call" || ret_child.kind() == "call_expression" {
1282                    if let Some(func) = ret_child.child_by_field_name("function") {
1283                        if func.kind() == "identifier" {
1284                            let name = func.utf8_text(source).unwrap_or("");
1285                            if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1286                                return Some(name.to_string());
1287                            }
1288                        }
1289                    }
1290                }
1291                // TS: new ClassName()
1292                if ret_child.kind() == "new_expression" {
1293                    if let Some(constructor) = ret_child.child_by_field_name("constructor") {
1294                        let name = constructor.utf8_text(source).unwrap_or("");
1295                        if !name.is_empty() {
1296                            return Some(name.to_string());
1297                        }
1298                    }
1299                }
1300                // Go: StructName{} (composite_literal)
1301                if ret_child.kind() == "composite_literal" {
1302                    if let Some(type_node) = ret_child.child_by_field_name("type") {
1303                        let name = type_node.utf8_text(source).unwrap_or("");
1304                        if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1305                            return Some(name.to_string());
1306                        }
1307                    }
1308                }
1309            }
1310        }
1311        // Recurse into blocks
1312        let ck = child.kind();
1313        if ck == "block" || ck == "statement_block" {
1314            if let Some(ret_type) = find_return_constructor(child, source) {
1315                return Some(ret_type);
1316            }
1317        }
1318    }
1319    None
1320}
1321
1322/// Scan for instance attribute types: __init__ self.attr patterns (Python/TS),
1323/// struct field declarations (Rust/Go).
1324fn scan_init_self_attrs(
1325    node: tree_sitter::Node,
1326    file_path: &str,
1327    all_entities: &[SemanticEntity],
1328    entity_map: &HashMap<String, EntityInfo>,
1329    source: &[u8],
1330    instance_attr_types: &mut HashMap<(String, String), String>,
1331    init_params_map: &mut HashMap<String, Vec<String>>,
1332    attr_to_param_map: &mut HashMap<(String, String), String>,
1333    lang: &str,
1334) {
1335    let kind = node.kind();
1336
1337    match lang {
1338        "python" | "typescript" => {
1339            if kind == "class_definition" || kind == "class_declaration" {
1340                let class_name = node
1341                    .child_by_field_name("name")
1342                    .and_then(|n| n.utf8_text(source).ok())
1343                    .unwrap_or("")
1344                    .to_string();
1345
1346                if !class_name.is_empty() {
1347                    scan_class_for_init(node, &class_name, source, instance_attr_types, init_params_map, attr_to_param_map, lang);
1348                }
1349            }
1350        }
1351        "rust" => {
1352            // Rust: extract field types directly from struct declarations
1353            if kind == "struct_item" {
1354                let struct_name = node
1355                    .child_by_field_name("name")
1356                    .and_then(|n| n.utf8_text(source).ok())
1357                    .unwrap_or("")
1358                    .to_string();
1359
1360                if !struct_name.is_empty() {
1361                    scan_rust_struct_fields(node, &struct_name, source, instance_attr_types);
1362                }
1363            }
1364        }
1365        "go" => {
1366            // Go: extract field types from type declarations containing struct_type
1367            if kind == "type_declaration" {
1368                scan_go_struct_fields(node, source, instance_attr_types);
1369            }
1370        }
1371        _ => {}
1372    }
1373
1374    let mut cursor = node.walk();
1375    for child in node.named_children(&mut cursor) {
1376        scan_init_self_attrs(child, file_path, all_entities, entity_map, source, instance_attr_types, init_params_map, attr_to_param_map, lang);
1377    }
1378}
1379
1380/// Rust: extract field types from `struct Foo { conn: Connection, ... }`
1381fn scan_rust_struct_fields(
1382    node: tree_sitter::Node,
1383    struct_name: &str,
1384    source: &[u8],
1385    instance_attr_types: &mut HashMap<(String, String), String>,
1386) {
1387    let mut cursor = node.walk();
1388    for child in node.named_children(&mut cursor) {
1389        if child.kind() == "field_declaration_list" {
1390            let mut inner_cursor = child.walk();
1391            for field in child.named_children(&mut inner_cursor) {
1392                if field.kind() == "field_declaration" {
1393                    let field_name = field
1394                        .child_by_field_name("name")
1395                        .and_then(|n| n.utf8_text(source).ok())
1396                        .unwrap_or("");
1397                    let field_type = field
1398                        .child_by_field_name("type")
1399                        .map(|n| extract_base_type(n, source))
1400                        .unwrap_or_default();
1401
1402                    if !field_name.is_empty()
1403                        && !field_type.is_empty()
1404                        && field_type.chars().next().map_or(false, |c| c.is_uppercase())
1405                    {
1406                        instance_attr_types.insert(
1407                            (struct_name.to_string(), field_name.to_string()),
1408                            field_type,
1409                        );
1410                    }
1411                }
1412            }
1413        }
1414    }
1415}
1416
1417/// Go: extract field types from `type Foo struct { conn Connection; ... }`
1418fn scan_go_struct_fields(
1419    node: tree_sitter::Node,
1420    source: &[u8],
1421    instance_attr_types: &mut HashMap<(String, String), String>,
1422) {
1423    let mut cursor = node.walk();
1424    for child in node.named_children(&mut cursor) {
1425        if child.kind() == "type_spec" {
1426            let struct_name = child
1427                .child_by_field_name("name")
1428                .and_then(|n| n.utf8_text(source).ok())
1429                .unwrap_or("")
1430                .to_string();
1431
1432            if struct_name.is_empty() {
1433                continue;
1434            }
1435
1436            // Look for struct_type child
1437            if let Some(type_node) = child.child_by_field_name("type") {
1438                if type_node.kind() == "struct_type" {
1439                    let mut fields_cursor = type_node.walk();
1440                    for field_list in type_node.named_children(&mut fields_cursor) {
1441                        if field_list.kind() == "field_declaration_list" {
1442                            let mut inner = field_list.walk();
1443                            for field in field_list.named_children(&mut inner) {
1444                                if field.kind() == "field_declaration" {
1445                                    // Go field: name type
1446                                    let field_name = field
1447                                        .child_by_field_name("name")
1448                                        .and_then(|n| n.utf8_text(source).ok())
1449                                        .unwrap_or("");
1450                                    let field_type = field
1451                                        .child_by_field_name("type")
1452                                        .map(|n| extract_base_type(n, source))
1453                                        .unwrap_or_default();
1454
1455                                    if !field_name.is_empty()
1456                                        && !field_type.is_empty()
1457                                        && field_type.chars().next().map_or(false, |c| c.is_uppercase())
1458                                    {
1459                                        instance_attr_types.insert(
1460                                            (struct_name.clone(), field_name.to_string()),
1461                                            field_type,
1462                                        );
1463                                    }
1464                                }
1465                            }
1466                        }
1467                    }
1468                }
1469            }
1470        }
1471    }
1472}
1473
1474fn scan_class_for_init(
1475    node: tree_sitter::Node,
1476    class_name: &str,
1477    source: &[u8],
1478    instance_attr_types: &mut HashMap<(String, String), String>,
1479    init_params_map: &mut HashMap<String, Vec<String>>,
1480    attr_to_param_map: &mut HashMap<(String, String), String>,
1481    lang: &str,
1482) {
1483    let mut cursor = node.walk();
1484    for child in node.named_children(&mut cursor) {
1485        let ck = child.kind();
1486
1487        // Python __init__
1488        if ck == "function_definition" {
1489            let name = child
1490                .child_by_field_name("name")
1491                .and_then(|n| n.utf8_text(source).ok())
1492                .unwrap_or("");
1493            if name == "__init__" {
1494                let params = extract_init_params(child, source);
1495                let ordered_params = extract_init_param_names_ordered(child, source);
1496                init_params_map.insert(class_name.to_string(), ordered_params);
1497                scan_init_body(child, class_name, &params, source, instance_attr_types, attr_to_param_map);
1498            }
1499        }
1500
1501        // TS constructor
1502        if ck == "method_definition" && lang == "typescript" {
1503            let name = child
1504                .child_by_field_name("name")
1505                .and_then(|n| n.utf8_text(source).ok())
1506                .unwrap_or("");
1507            if name == "constructor" {
1508                // Scan for this.attr = param patterns
1509                scan_ts_constructor_body(child, class_name, source, instance_attr_types, init_params_map, attr_to_param_map);
1510            }
1511        }
1512
1513        // TS: typed class field declarations `private conn: Connection`
1514        if (ck == "public_field_definition" || ck == "property_declaration" || ck == "field_definition") && lang == "typescript" {
1515            let field_name = child
1516                .child_by_field_name("name")
1517                .and_then(|n| n.utf8_text(source).ok())
1518                .unwrap_or("");
1519            if let Some(type_ann) = child.child_by_field_name("type") {
1520                let type_text = extract_base_type(type_ann, source);
1521                if !field_name.is_empty()
1522                    && !type_text.is_empty()
1523                    && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1524                {
1525                    instance_attr_types.insert(
1526                        (class_name.to_string(), field_name.to_string()),
1527                        type_text,
1528                    );
1529                }
1530            }
1531        }
1532
1533        if ck == "block" || ck == "class_body" || ck == "statement_block" {
1534            scan_class_for_init(child, class_name, source, instance_attr_types, init_params_map, attr_to_param_map, lang);
1535        }
1536    }
1537}
1538
1539/// TS: scan constructor body for `this.attr = param` patterns
1540fn scan_ts_constructor_body(
1541    node: tree_sitter::Node,
1542    class_name: &str,
1543    source: &[u8],
1544    instance_attr_types: &mut HashMap<(String, String), String>,
1545    init_params_map: &mut HashMap<String, Vec<String>>,
1546    attr_to_param_map: &mut HashMap<(String, String), String>,
1547) {
1548    // Extract constructor params
1549    let params = extract_init_params(node, source);
1550    let ordered_params = extract_init_param_names_ordered(node, source);
1551    init_params_map.insert(class_name.to_string(), ordered_params);
1552
1553    // Scan body for this.X = param
1554    scan_init_body_this(node, class_name, &params, source, instance_attr_types, attr_to_param_map);
1555}
1556
1557/// Scan constructor body for `this.attr = param` patterns (TS variant)
1558fn scan_init_body_this(
1559    node: tree_sitter::Node,
1560    class_name: &str,
1561    params: &HashMap<String, Option<String>>,
1562    source: &[u8],
1563    instance_attr_types: &mut HashMap<(String, String), String>,
1564    attr_to_param_map: &mut HashMap<(String, String), String>,
1565) {
1566    let mut cursor = node.walk();
1567    for child in node.named_children(&mut cursor) {
1568        let ck = child.kind();
1569        if ck == "expression_statement" {
1570            // Look for assignment: this.X = Y
1571            let mut inner_cursor = child.walk();
1572            for inner in child.named_children(&mut inner_cursor) {
1573                if inner.kind() == "assignment_expression" {
1574                    if let Some(left) = inner.child_by_field_name("left") {
1575                        if left.kind() == "member_expression" {
1576                            let obj = left.child_by_field_name("object")
1577                                .and_then(|n| n.utf8_text(source).ok())
1578                                .unwrap_or("");
1579                            let prop = left.child_by_field_name("property")
1580                                .and_then(|n| n.utf8_text(source).ok())
1581                                .unwrap_or("");
1582                            if obj == "this" && !prop.is_empty() {
1583                                if let Some(right) = inner.child_by_field_name("right") {
1584                                    if right.kind() == "identifier" {
1585                                        let rhs_name = right.utf8_text(source).unwrap_or("");
1586                                        if params.contains_key(rhs_name) {
1587                                            attr_to_param_map.insert(
1588                                                (class_name.to_string(), prop.to_string()),
1589                                                rhs_name.to_string(),
1590                                            );
1591                                            if let Some(Some(type_hint)) = params.get(rhs_name) {
1592                                                instance_attr_types.insert(
1593                                                    (class_name.to_string(), prop.to_string()),
1594                                                    type_hint.clone(),
1595                                                );
1596                                            }
1597                                        }
1598                                    }
1599                                    if right.kind() == "new_expression" {
1600                                        if let Some(ctor) = right.child_by_field_name("constructor") {
1601                                            let name = ctor.utf8_text(source).unwrap_or("");
1602                                            if !name.is_empty() {
1603                                                instance_attr_types.insert(
1604                                                    (class_name.to_string(), prop.to_string()),
1605                                                    name.to_string(),
1606                                                );
1607                                            }
1608                                        }
1609                                    }
1610                                }
1611                            }
1612                        }
1613                    }
1614                }
1615            }
1616        }
1617        if ck == "statement_block" || ck == "block" {
1618            scan_init_body_this(child, class_name, params, source, instance_attr_types, attr_to_param_map);
1619        }
1620    }
1621}
1622
1623/// Extract __init__ parameter names in order (excluding self).
1624fn extract_init_param_names_ordered(func_node: tree_sitter::Node, source: &[u8]) -> Vec<String> {
1625    let mut names = Vec::new();
1626    if let Some(params_node) = func_node.child_by_field_name("parameters") {
1627        let mut cursor = params_node.walk();
1628        for child in params_node.named_children(&mut cursor) {
1629            let param_name = if child.kind() == "identifier" {
1630                child.utf8_text(source).unwrap_or("").to_string()
1631            } else if child.kind() == "typed_parameter" || child.kind() == "typed_default_parameter" {
1632                child.child_by_field_name("name")
1633                    .or_else(|| child.named_child(0))
1634                    .and_then(|n| n.utf8_text(source).ok())
1635                    .unwrap_or("")
1636                    .to_string()
1637            } else {
1638                continue;
1639            };
1640            if param_name != "self" && param_name != "cls" && !param_name.is_empty() {
1641                names.push(param_name);
1642            }
1643        }
1644    }
1645    names
1646}
1647
1648fn extract_init_params(func_node: tree_sitter::Node, source: &[u8]) -> HashMap<String, Option<String>> {
1649    let mut params = HashMap::new();
1650    if let Some(params_node) = func_node.child_by_field_name("parameters") {
1651        let mut cursor = params_node.walk();
1652        for child in params_node.named_children(&mut cursor) {
1653            let param_name = if child.kind() == "identifier" {
1654                child.utf8_text(source).unwrap_or("").to_string()
1655            } else if child.kind() == "typed_parameter" || child.kind() == "typed_default_parameter" {
1656                child.child_by_field_name("name")
1657                    .or_else(|| child.named_child(0))
1658                    .and_then(|n| n.utf8_text(source).ok())
1659                    .unwrap_or("")
1660                    .to_string()
1661            } else {
1662                continue;
1663            };
1664            if param_name != "self" && param_name != "cls" {
1665                // Check for type annotation
1666                let type_hint = child.child_by_field_name("type")
1667                    .and_then(|n| n.utf8_text(source).ok())
1668                    .map(|s| s.to_string());
1669                params.insert(param_name, type_hint);
1670            }
1671        }
1672    }
1673    params
1674}
1675
1676fn scan_init_body(
1677    node: tree_sitter::Node,
1678    class_name: &str,
1679    params: &HashMap<String, Option<String>>,
1680    source: &[u8],
1681    instance_attr_types: &mut HashMap<(String, String), String>,
1682    attr_to_param_map: &mut HashMap<(String, String), String>,
1683) {
1684    let mut cursor = node.walk();
1685    for child in node.named_children(&mut cursor) {
1686        if child.kind() == "expression_statement" || child.kind() == "assignment" {
1687            let assign = if child.kind() == "assignment" {
1688                child
1689            } else {
1690                let mut inner_cursor = child.walk();
1691                let children: Vec<_> = child.named_children(&mut inner_cursor).collect();
1692                match children.into_iter().find(|c| c.kind() == "assignment") {
1693                    Some(a) => a,
1694                    None => continue,
1695                }
1696            };
1697
1698            if let Some(left) = assign.child_by_field_name("left") {
1699                if left.kind() == "attribute" {
1700                    let obj = left.child_by_field_name("object")
1701                        .and_then(|n| n.utf8_text(source).ok())
1702                        .unwrap_or("");
1703                    let attr = left.child_by_field_name("attribute")
1704                        .and_then(|n| n.utf8_text(source).ok())
1705                        .unwrap_or("");
1706
1707                    if obj == "self" && !attr.is_empty() {
1708                        if let Some(right) = assign.child_by_field_name("right") {
1709                            if right.kind() == "identifier" {
1710                                let rhs_name = right.utf8_text(source).unwrap_or("");
1711                                // Record attr -> param mapping for later inference
1712                                if params.contains_key(rhs_name) {
1713                                    attr_to_param_map.insert(
1714                                        (class_name.to_string(), attr.to_string()),
1715                                        rhs_name.to_string(),
1716                                    );
1717                                }
1718                                // If param has type hint, directly set the type
1719                                if let Some(Some(type_hint)) = params.get(rhs_name) {
1720                                    instance_attr_types.insert(
1721                                        (class_name.to_string(), attr.to_string()),
1722                                        type_hint.clone(),
1723                                    );
1724                                }
1725                            }
1726                            if right.kind() == "call" {
1727                                if let Some(func) = right.child_by_field_name("function") {
1728                                    if func.kind() == "identifier" {
1729                                        let fname = func.utf8_text(source).unwrap_or("");
1730                                        if fname.chars().next().map_or(false, |c| c.is_uppercase()) {
1731                                            instance_attr_types.insert(
1732                                                (class_name.to_string(), attr.to_string()),
1733                                                fname.to_string(),
1734                                            );
1735                                        }
1736                                    }
1737                                }
1738                            }
1739                        }
1740                    }
1741                }
1742            }
1743        }
1744        if child.kind() == "block" {
1745            scan_init_body(child, class_name, params, source, instance_attr_types, attr_to_param_map);
1746        }
1747    }
1748}
1749
1750/// Infer constructor parameter types by analyzing call sites across all files.
1751/// For `Transaction(get_connection())`, we know get_connection() returns Connection,
1752/// so Transaction.__init__'s conn param has type Connection,
1753/// and self.conn in Transaction has type Connection.
1754fn infer_constructor_param_types(
1755    parsed_files: &[(String, String, tree_sitter::Tree)],
1756    return_type_map: &HashMap<String, String>,
1757    init_params: &HashMap<String, Vec<String>>,
1758    attr_to_param: &HashMap<(String, String), String>,
1759    symbol_table: &HashMap<String, Vec<String>>,
1760    entity_map: &HashMap<String, EntityInfo>,
1761    instance_attr_types: &mut HashMap<(String, String), String>,
1762) {
1763    // Build func_name -> return_type lookup for quick access
1764    let mut func_name_returns: HashMap<String, String> = HashMap::new();
1765    for (eid, ret_type) in return_type_map {
1766        if let Some(info) = entity_map.get(eid) {
1767            func_name_returns.insert(info.name.clone(), ret_type.clone());
1768        }
1769    }
1770
1771    // Scan all files for constructor call sites: ClassName(arg1, arg2, ...)
1772    for (_file_path, content, tree) in parsed_files {
1773        let source = content.as_bytes();
1774        scan_constructor_calls(
1775            tree.root_node(),
1776            source,
1777            &func_name_returns,
1778            init_params,
1779            attr_to_param,
1780            instance_attr_types,
1781        );
1782    }
1783}
1784
1785fn scan_constructor_calls(
1786    node: tree_sitter::Node,
1787    source: &[u8],
1788    func_name_returns: &HashMap<String, String>,
1789    init_params: &HashMap<String, Vec<String>>,
1790    attr_to_param: &HashMap<(String, String), String>,
1791    instance_attr_types: &mut HashMap<(String, String), String>,
1792) {
1793    let kind = node.kind();
1794
1795    if kind == "call" {
1796        if let Some(func) = node.child_by_field_name("function") {
1797            if func.kind() == "identifier" {
1798                let class_name = func.utf8_text(source).unwrap_or("");
1799                // Only process uppercase names (constructor calls)
1800                if class_name.chars().next().map_or(false, |c| c.is_uppercase()) {
1801                    if let Some(param_names) = init_params.get(class_name) {
1802                        // Extract argument types
1803                        if let Some(args_node) = node.child_by_field_name("arguments") {
1804                            let mut arg_idx = 0;
1805                            let mut args_cursor = args_node.walk();
1806                            for arg in args_node.named_children(&mut args_cursor) {
1807                                if arg_idx >= param_names.len() {
1808                                    break;
1809                                }
1810                                let param_name = &param_names[arg_idx];
1811
1812                                // Try to infer the argument's type
1813                                let arg_type = infer_expr_type(arg, source, func_name_returns);
1814
1815                                if let Some(at) = arg_type {
1816                                    // Check if any self.attr maps to this param
1817                                    for ((cn, attr), pn) in attr_to_param.iter() {
1818                                        if cn == class_name && pn == param_name {
1819                                            instance_attr_types
1820                                                .entry((cn.clone(), attr.clone()))
1821                                                .or_insert(at.clone());
1822                                        }
1823                                    }
1824                                }
1825
1826                                arg_idx += 1;
1827                            }
1828                        }
1829                    }
1830                }
1831            }
1832        }
1833    }
1834
1835    let mut cursor = node.walk();
1836    for child in node.named_children(&mut cursor) {
1837        scan_constructor_calls(child, source, func_name_returns, init_params, attr_to_param, instance_attr_types);
1838    }
1839}
1840
1841/// Infer the type of an expression node.
1842fn infer_expr_type(
1843    node: tree_sitter::Node,
1844    source: &[u8],
1845    func_name_returns: &HashMap<String, String>,
1846) -> Option<String> {
1847    match node.kind() {
1848        "call" => {
1849            if let Some(func) = node.child_by_field_name("function") {
1850                if func.kind() == "identifier" {
1851                    let name = func.utf8_text(source).unwrap_or("");
1852                    // Constructor call: Foo() -> type is Foo
1853                    if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1854                        return Some(name.to_string());
1855                    }
1856                    // Function call with known return type
1857                    if let Some(ret) = func_name_returns.get(name) {
1858                        return Some(ret.clone());
1859                    }
1860                }
1861            }
1862            None
1863        }
1864        "identifier" => {
1865            // Could be a variable, but we don't have scope info here
1866            None
1867        }
1868        _ => None,
1869    }
1870}
1871
1872/// Resolve pending call types using the return type map.
1873/// For scopes with `x = func()` where func has a known return type, bind x to that type.
1874fn inject_return_type_bindings(
1875    _entity_inner_scope: &HashMap<String, usize>,
1876    scopes: &mut Vec<Scope>,
1877    return_type_map: &HashMap<String, String>,
1878    import_table: &HashMap<(String, String), String>,
1879    file_path: &str,
1880    entity_map: &HashMap<String, EntityInfo>,
1881) {
1882    // Build function name -> return type lookup
1883    let mut func_name_return_types: HashMap<String, String> = HashMap::new();
1884    for (eid, ret_type) in return_type_map {
1885        if let Some(info) = entity_map.get(eid) {
1886            func_name_return_types.insert(info.name.clone(), ret_type.clone());
1887        }
1888    }
1889
1890    // Also resolve through imports: if `get_connection` is imported and has a known return type
1891    for ((fp, local_name), target_id) in import_table {
1892        if fp == file_path {
1893            if let Some(ret_type) = return_type_map.get(target_id) {
1894                func_name_return_types.insert(local_name.clone(), ret_type.clone());
1895            }
1896        }
1897    }
1898
1899    // Resolve pending call types in all scopes
1900    for scope in scopes.iter_mut() {
1901        let resolved: Vec<(String, String)> = scope
1902            .pending_call_types
1903            .iter()
1904            .filter_map(|(var_name, func_name)| {
1905                func_name_return_types
1906                    .get(func_name)
1907                    .map(|ret_type| (var_name.clone(), ret_type.clone()))
1908            })
1909            .collect();
1910
1911        for (var_name, ret_type) in resolved {
1912            scope.types.insert(var_name, ret_type);
1913        }
1914    }
1915}
1916
1917/// Extract import statements from the AST.
1918fn extract_imports_from_ast(
1919    node: tree_sitter::Node,
1920    file_path: &str,
1921    source: &[u8],
1922    symbol_table: &HashMap<String, Vec<String>>,
1923    entity_map: &HashMap<String, EntityInfo>,
1924    import_table: &mut HashMap<(String, String), String>,
1925    scopes: &mut Vec<Scope>,
1926    lang: &str,
1927) {
1928    let mut cursor = node.walk();
1929    for child in node.named_children(&mut cursor) {
1930        let ck = child.kind();
1931        match ck {
1932            "import_from_statement" if lang == "python" => {
1933                extract_python_import(child, file_path, source, symbol_table, entity_map, import_table, scopes);
1934            }
1935            "import_statement" if lang == "typescript" => {
1936                extract_ts_import(child, file_path, source, symbol_table, entity_map, import_table, scopes);
1937            }
1938            "use_declaration" if lang == "rust" => {
1939                extract_rust_use(child, file_path, source, symbol_table, entity_map, import_table, scopes);
1940            }
1941            "import_declaration" if lang == "go" => {
1942                extract_go_import(child, file_path, source, symbol_table, entity_map, import_table, scopes);
1943            }
1944            _ => {
1945                extract_imports_from_ast(child, file_path, source, symbol_table, entity_map, import_table, scopes, lang);
1946            }
1947        }
1948    }
1949}
1950
1951/// TS: `import { Foo, Bar } from './module'` or `import Foo from './module'`
1952fn extract_ts_import(
1953    node: tree_sitter::Node,
1954    file_path: &str,
1955    source: &[u8],
1956    symbol_table: &HashMap<String, Vec<String>>,
1957    entity_map: &HashMap<String, EntityInfo>,
1958    import_table: &mut HashMap<(String, String), String>,
1959    scopes: &mut Vec<Scope>,
1960) {
1961    // Extract the source module from the `from '...'` clause
1962    let source_path = node
1963        .child_by_field_name("source")
1964        .and_then(|n| n.utf8_text(source).ok())
1965        .unwrap_or("")
1966        .trim_matches(|c: char| c == '\'' || c == '"');
1967
1968    let source_module = source_path
1969        .rsplit('/')
1970        .next()
1971        .unwrap_or(source_path);
1972    // Strip extensions
1973    let source_module = source_module
1974        .strip_suffix(".ts").or_else(|| source_module.strip_suffix(".js"))
1975        .or_else(|| source_module.strip_suffix(".tsx")).or_else(|| source_module.strip_suffix(".jsx"))
1976        .unwrap_or(source_module);
1977
1978    if source_module.is_empty() {
1979        return;
1980    }
1981
1982    // Walk children to find import clause
1983    let mut cursor = node.walk();
1984    for child in node.named_children(&mut cursor) {
1985        if child.kind() == "import_clause" {
1986            let mut clause_cursor = child.walk();
1987            for clause_child in child.named_children(&mut clause_cursor) {
1988                if clause_child.kind() == "named_imports" {
1989                    // { Foo, Bar as Baz }
1990                    let mut imports_cursor = clause_child.walk();
1991                    for spec in clause_child.named_children(&mut imports_cursor) {
1992                        if spec.kind() == "import_specifier" {
1993                            let original = spec
1994                                .child_by_field_name("name")
1995                                .and_then(|n| n.utf8_text(source).ok())
1996                                .unwrap_or("");
1997                            let local = spec
1998                                .child_by_field_name("alias")
1999                                .and_then(|n| n.utf8_text(source).ok())
2000                                .unwrap_or(original);
2001
2002                            if !original.is_empty() {
2003                                resolve_import_name(original, local, source_module, file_path, symbol_table, entity_map, import_table, scopes);
2004                            }
2005                        }
2006                    }
2007                } else if clause_child.kind() == "identifier" {
2008                    // Default import: import Foo from './module'
2009                    let name = clause_child.utf8_text(source).unwrap_or("");
2010                    if !name.is_empty() {
2011                        resolve_import_name(name, name, source_module, file_path, symbol_table, entity_map, import_table, scopes);
2012                    }
2013                }
2014            }
2015        }
2016    }
2017}
2018
2019/// Rust: `use crate::module::Name;` or `use crate::module::{A, B};`
2020/// Parse from the text of the use_declaration for reliability.
2021fn extract_rust_use(
2022    node: tree_sitter::Node,
2023    file_path: &str,
2024    source: &[u8],
2025    symbol_table: &HashMap<String, Vec<String>>,
2026    entity_map: &HashMap<String, EntityInfo>,
2027    import_table: &mut HashMap<(String, String), String>,
2028    scopes: &mut Vec<Scope>,
2029) {
2030    let text = node.utf8_text(source).unwrap_or("").trim().to_string();
2031    // Strip `use ` prefix and trailing `;`
2032    let text = text.strip_prefix("use ").unwrap_or(&text);
2033    let text = text.strip_prefix("pub use ").unwrap_or(text);
2034    let text = text.trim_end_matches(';').trim();
2035
2036    // Strip crate/super/self prefix
2037    let text = text
2038        .strip_prefix("crate::")
2039        .or_else(|| text.strip_prefix("super::"))
2040        .or_else(|| text.strip_prefix("self::"))
2041        .unwrap_or(text);
2042
2043    // Check for grouped import: module::{A, B, C}
2044    if let Some(brace_pos) = text.find("::{") {
2045        let module_path = &text[..brace_pos];
2046        let source_module = module_path.rsplit("::").next().unwrap_or(module_path);
2047
2048        let names_part = &text[brace_pos + 3..];
2049        let names_part = names_part.trim_end_matches('}');
2050
2051        for name_part in names_part.split(',') {
2052            let name_part = name_part.trim();
2053            if name_part.is_empty() {
2054                continue;
2055            }
2056            let (original, local) = if let Some(pos) = name_part.find(" as ") {
2057                (name_part[..pos].trim(), name_part[pos + 4..].trim())
2058            } else {
2059                (name_part, name_part)
2060            };
2061            if !original.is_empty() {
2062                resolve_import_name(original, local, source_module, file_path, symbol_table, entity_map, import_table, scopes);
2063            }
2064        }
2065    } else {
2066        // Simple import: module::Name
2067        let parts: Vec<&str> = text.split("::").collect();
2068        if parts.is_empty() {
2069            return;
2070        }
2071        let imported_name = parts.last().unwrap().trim();
2072        let (original, local) = if let Some(pos) = imported_name.find(" as ") {
2073            (&imported_name[..pos], imported_name[pos + 4..].trim())
2074        } else {
2075            (imported_name, imported_name)
2076        };
2077        let source_module = if parts.len() >= 2 {
2078            parts[parts.len() - 2]
2079        } else {
2080            parts[0]
2081        };
2082        if !original.is_empty() && !source_module.is_empty() {
2083            resolve_import_name(original, local, source_module, file_path, symbol_table, entity_map, import_table, scopes);
2084        }
2085    }
2086}
2087
2088/// Go: `import ("module/path")` - maps package names to entities
2089fn extract_go_import(
2090    node: tree_sitter::Node,
2091    file_path: &str,
2092    source: &[u8],
2093    symbol_table: &HashMap<String, Vec<String>>,
2094    entity_map: &HashMap<String, EntityInfo>,
2095    import_table: &mut HashMap<(String, String), String>,
2096    scopes: &mut Vec<Scope>,
2097) {
2098    let mut cursor = node.walk();
2099    for child in node.named_children(&mut cursor) {
2100        if child.kind() == "import_spec" || child.kind() == "import_spec_list" {
2101            extract_go_import_specs(child, file_path, source, symbol_table, entity_map, import_table, scopes);
2102        } else if child.kind() == "interpreted_string_literal" || child.kind() == "raw_string_literal" {
2103            let path = child.utf8_text(source).unwrap_or("")
2104                .trim_matches('"').trim_matches('`');
2105            let pkg_name = path.rsplit('/').next().unwrap_or(path);
2106            register_go_package_imports(pkg_name, file_path, symbol_table, entity_map, import_table, scopes);
2107        }
2108    }
2109}
2110
2111fn extract_go_import_specs(
2112    node: tree_sitter::Node,
2113    file_path: &str,
2114    source: &[u8],
2115    symbol_table: &HashMap<String, Vec<String>>,
2116    entity_map: &HashMap<String, EntityInfo>,
2117    import_table: &mut HashMap<(String, String), String>,
2118    scopes: &mut Vec<Scope>,
2119) {
2120    let mut cursor = node.walk();
2121    for child in node.named_children(&mut cursor) {
2122        if child.kind() == "import_spec" {
2123            let path_node = child.child_by_field_name("path")
2124                .or_else(|| child.named_child(0));
2125            if let Some(pn) = path_node {
2126                let path = pn.utf8_text(source).unwrap_or("")
2127                    .trim_matches('"').trim_matches('`');
2128                let pkg_name = path.rsplit('/').next().unwrap_or(path);
2129                register_go_package_imports(pkg_name, file_path, symbol_table, entity_map, import_table, scopes);
2130            }
2131        } else {
2132            extract_go_import_specs(child, file_path, source, symbol_table, entity_map, import_table, scopes);
2133        }
2134    }
2135}
2136
2137fn register_go_package_imports(
2138    pkg_name: &str,
2139    file_path: &str,
2140    symbol_table: &HashMap<String, Vec<String>>,
2141    entity_map: &HashMap<String, EntityInfo>,
2142    import_table: &mut HashMap<(String, String), String>,
2143    scopes: &mut Vec<Scope>,
2144) {
2145    for (name, target_ids) in symbol_table {
2146        for target_id in target_ids {
2147            if let Some(entity) = entity_map.get(target_id) {
2148                let stem = entity.file_path.rsplit('/').next().unwrap_or(&entity.file_path);
2149                let stem = stem.strip_suffix(".go").unwrap_or(stem);
2150                if stem == pkg_name || entity.file_path.contains(&format!("{}/", pkg_name)) {
2151                    import_table.insert(
2152                        (file_path.to_string(), name.clone()),
2153                        target_id.clone(),
2154                    );
2155                    if !scopes.is_empty() {
2156                        scopes[0].defs.insert(name.clone(), target_id.clone());
2157                    }
2158                }
2159            }
2160        }
2161    }
2162}
2163
2164/// Shared helper: resolve an imported name against the symbol table
2165fn resolve_import_name(
2166    original_name: &str,
2167    local_name: &str,
2168    source_module: &str,
2169    file_path: &str,
2170    symbol_table: &HashMap<String, Vec<String>>,
2171    entity_map: &HashMap<String, EntityInfo>,
2172    import_table: &mut HashMap<(String, String), String>,
2173    scopes: &mut Vec<Scope>,
2174) {
2175    if let Some(target_ids) = symbol_table.get(original_name) {
2176        let target = target_ids.iter().find(|id| {
2177            entity_map.get(*id).map_or(false, |e| {
2178                let stem = e.file_path.rsplit('/').next().unwrap_or(&e.file_path);
2179                let stem = stem
2180                    .strip_suffix(".py")
2181                    .or_else(|| stem.strip_suffix(".rs"))
2182                    .or_else(|| stem.strip_suffix(".ts"))
2183                    .or_else(|| stem.strip_suffix(".tsx"))
2184                    .or_else(|| stem.strip_suffix(".js"))
2185                    .or_else(|| stem.strip_suffix(".jsx"))
2186                    .or_else(|| stem.strip_suffix(".go"))
2187                    .unwrap_or(stem);
2188                stem == source_module
2189            })
2190        });
2191
2192        if let Some(target_id) = target {
2193            import_table.insert(
2194                (file_path.to_string(), local_name.to_string()),
2195                target_id.clone(),
2196            );
2197            if !scopes.is_empty() {
2198                scopes[0]
2199                    .defs
2200                    .insert(local_name.to_string(), target_id.clone());
2201            }
2202        }
2203    }
2204}
2205
2206fn extract_python_import(
2207    node: tree_sitter::Node,
2208    file_path: &str,
2209    source: &[u8],
2210    symbol_table: &HashMap<String, Vec<String>>,
2211    entity_map: &HashMap<String, EntityInfo>,
2212    import_table: &mut HashMap<(String, String), String>,
2213    scopes: &mut Vec<Scope>,
2214) {
2215    // import_from_statement has:
2216    //   module_name (dotted_name or relative_import)
2217    //   name fields (imported names)
2218    let module_node = node.child_by_field_name("module_name");
2219    let module_name = module_node
2220        .and_then(|n| n.utf8_text(source).ok())
2221        .unwrap_or("");
2222
2223    let source_module = module_name
2224        .trim_start_matches('.')
2225        .rsplit('.')
2226        .next()
2227        .unwrap_or(module_name.trim_start_matches('.'));
2228
2229    // Walk children to find imported names
2230    let mut cursor = node.walk();
2231    for child in node.named_children(&mut cursor) {
2232        if child.kind() == "dotted_name" || child.kind() == "aliased_import" {
2233            let (original, local) = if child.kind() == "aliased_import" {
2234                let orig = child
2235                    .child_by_field_name("name")
2236                    .and_then(|n| n.utf8_text(source).ok())
2237                    .unwrap_or("");
2238                let alias = child
2239                    .child_by_field_name("alias")
2240                    .and_then(|n| n.utf8_text(source).ok())
2241                    .unwrap_or(orig);
2242                (orig, alias)
2243            } else {
2244                let name = child.utf8_text(source).unwrap_or("");
2245                (name, name)
2246            };
2247
2248            if original.is_empty() {
2249                continue;
2250            }
2251
2252            // Resolve against symbol table, preferring entities from the source module
2253            if let Some(target_ids) = symbol_table.get(original) {
2254                let target = target_ids.iter().find(|id| {
2255                    entity_map.get(*id).map_or(false, |e| {
2256                        let stem = e.file_path.rsplit('/').next().unwrap_or(&e.file_path);
2257                        let stem = stem
2258                            .strip_suffix(".py")
2259                            .or_else(|| stem.strip_suffix(".rs"))
2260                            .or_else(|| stem.strip_suffix(".ts"))
2261                            .or_else(|| stem.strip_suffix(".js"))
2262                            .unwrap_or(stem);
2263                        stem == source_module
2264                    })
2265                });
2266
2267                if let Some(target_id) = target {
2268                    import_table.insert(
2269                        (file_path.to_string(), local.to_string()),
2270                        target_id.clone(),
2271                    );
2272                    // Also add to module scope definitions
2273                    if !scopes.is_empty() {
2274                        scopes[0]
2275                            .defs
2276                            .insert(local.to_string(), target_id.clone());
2277                    }
2278                }
2279            }
2280        }
2281    }
2282}
2283
2284/// Extract AST references from an entity's line range.
2285fn extract_ast_refs(
2286    root: tree_sitter::Node,
2287    entity: &SemanticEntity,
2288    source: &[u8],
2289    lang: &str,
2290) -> Vec<AstRef> {
2291    let mut refs = Vec::new();
2292    let start_row = entity.start_line.saturating_sub(1); // 1-indexed to 0-indexed
2293    let end_row = entity.end_line; // exclusive
2294
2295    collect_refs_in_range(root, start_row, end_row, &entity.id, &entity.name, source, &mut refs, lang);
2296    refs
2297}
2298
2299fn collect_refs_in_range(
2300    node: tree_sitter::Node,
2301    start_row: usize,
2302    end_row: usize,
2303    entity_id: &str,
2304    entity_name: &str,
2305    source: &[u8],
2306    refs: &mut Vec<AstRef>,
2307    lang: &str,
2308) {
2309    let node_start = node.start_position().row;
2310    let node_end = node.end_position().row;
2311
2312    if node_end < start_row || node_start >= end_row {
2313        return;
2314    }
2315
2316    let kind = node.kind();
2317
2318    // Python call: foo() or obj.method()
2319    if kind == "call" {
2320        if let Some(func) = node.child_by_field_name("function") {
2321            extract_call_ref(func, entity_id, entity_name, source, refs, lang);
2322        }
2323        if let Some(args) = node.child_by_field_name("arguments") {
2324            let mut cursor = args.walk();
2325            for child in args.named_children(&mut cursor) {
2326                collect_refs_in_range(child, start_row, end_row, entity_id, entity_name, source, refs, lang);
2327            }
2328        }
2329        return;
2330    }
2331
2332    // TS/Go: call_expression
2333    if kind == "call_expression" {
2334        if let Some(func) = node.child_by_field_name("function") {
2335            extract_call_ref(func, entity_id, entity_name, source, refs, lang);
2336        }
2337        if let Some(args) = node.child_by_field_name("arguments") {
2338            let mut cursor = args.walk();
2339            for child in args.named_children(&mut cursor) {
2340                collect_refs_in_range(child, start_row, end_row, entity_id, entity_name, source, refs, lang);
2341            }
2342        }
2343        return;
2344    }
2345
2346    // TS: new Foo()
2347    if kind == "new_expression" {
2348        if let Some(constructor) = node.child_by_field_name("constructor") {
2349            let name = constructor.utf8_text(source).unwrap_or("");
2350            if !name.is_empty() && name != entity_name && !is_builtin_for_lang(name, lang) {
2351                refs.push(AstRef {
2352                    from_entity_id: entity_id.to_string(),
2353                    kind: AstRefKind::Call(name.to_string()),
2354                });
2355            }
2356        }
2357        if let Some(args) = node.child_by_field_name("arguments") {
2358            let mut cursor = args.walk();
2359            for child in args.named_children(&mut cursor) {
2360                collect_refs_in_range(child, start_row, end_row, entity_id, entity_name, source, refs, lang);
2361            }
2362        }
2363        return;
2364    }
2365
2366    // Go: composite_literal (struct instantiation): Foo{...}
2367    if kind == "composite_literal" && lang == "go" {
2368        if let Some(type_node) = node.child_by_field_name("type") {
2369            let name = type_node.utf8_text(source).unwrap_or("");
2370            if name.chars().next().map_or(false, |c| c.is_uppercase())
2371                && name != entity_name
2372                && !is_builtin_for_lang(name, lang)
2373            {
2374                refs.push(AstRef {
2375                    from_entity_id: entity_id.to_string(),
2376                    kind: AstRefKind::Call(name.to_string()),
2377                });
2378            }
2379        }
2380    }
2381
2382    // Recurse into children
2383    let mut cursor = node.walk();
2384    for child in node.named_children(&mut cursor) {
2385        collect_refs_in_range(child, start_row, end_row, entity_id, entity_name, source, refs, lang);
2386    }
2387}
2388
2389/// Extract a call reference from a function/callee node (shared across languages)
2390fn extract_call_ref(
2391    func: tree_sitter::Node,
2392    entity_id: &str,
2393    entity_name: &str,
2394    source: &[u8],
2395    refs: &mut Vec<AstRef>,
2396    lang: &str,
2397) {
2398    match func.kind() {
2399        "identifier" => {
2400            let name = func.utf8_text(source).unwrap_or("");
2401            if !name.is_empty() && name != entity_name && !is_builtin_for_lang(name, lang) {
2402                refs.push(AstRef {
2403                    from_entity_id: entity_id.to_string(),
2404                    kind: AstRefKind::Call(name.to_string()),
2405                });
2406            }
2407        }
2408        // Python: obj.method()
2409        "attribute" => {
2410            extract_member_call_ref(func, "object", "attribute", entity_id, source, refs);
2411        }
2412        // TS: obj.method()
2413        "member_expression" => {
2414            extract_member_call_ref(func, "object", "property", entity_id, source, refs);
2415        }
2416        // Rust: obj.method() (field_expression)
2417        "field_expression" => {
2418            let obj = func
2419                .child_by_field_name("value")
2420                .and_then(|n| n.utf8_text(source).ok())
2421                .unwrap_or("");
2422            let field = func
2423                .child_by_field_name("field")
2424                .and_then(|n| n.utf8_text(source).ok())
2425                .unwrap_or("");
2426            if !obj.is_empty() && !field.is_empty() {
2427                push_method_call_ref(obj, field, entity_id, refs);
2428            }
2429        }
2430        // Go: obj.Method() (selector_expression)
2431        "selector_expression" => {
2432            let obj = func
2433                .child_by_field_name("operand")
2434                .and_then(|n| n.utf8_text(source).ok())
2435                .unwrap_or("");
2436            let field = func
2437                .child_by_field_name("field")
2438                .and_then(|n| n.utf8_text(source).ok())
2439                .unwrap_or("");
2440            if !obj.is_empty() && !field.is_empty() {
2441                push_method_call_ref(obj, field, entity_id, refs);
2442            }
2443        }
2444        // Rust: Type::method() (scoped_identifier)
2445        "scoped_identifier" => {
2446            let text = func.utf8_text(source).unwrap_or("");
2447            let parts: Vec<&str> = text.split("::").collect();
2448            if parts.len() >= 2 {
2449                let type_name = parts[parts.len() - 2];
2450                let method_name = parts[parts.len() - 1];
2451                if !type_name.is_empty() && !method_name.is_empty() {
2452                    // Treat as a call to the method/associated function
2453                    refs.push(AstRef {
2454                        from_entity_id: entity_id.to_string(),
2455                        kind: AstRefKind::Call(method_name.to_string()),
2456                    });
2457                    // Also reference the type itself
2458                    if type_name.chars().next().map_or(false, |c| c.is_uppercase())
2459                        && !is_builtin_for_lang(type_name, lang)
2460                    {
2461                        refs.push(AstRef {
2462                            from_entity_id: entity_id.to_string(),
2463                            kind: AstRefKind::Call(type_name.to_string()),
2464                        });
2465                    }
2466                }
2467            }
2468        }
2469        _ => {}
2470    }
2471}
2472
2473/// Extract a member/method call from a node with object+property fields
2474fn extract_member_call_ref(
2475    node: tree_sitter::Node,
2476    object_field: &str,
2477    attr_field: &str,
2478    entity_id: &str,
2479    source: &[u8],
2480    refs: &mut Vec<AstRef>,
2481) {
2482    let obj = node
2483        .child_by_field_name(object_field)
2484        .and_then(|n| n.utf8_text(source).ok())
2485        .unwrap_or("");
2486    let attr = node
2487        .child_by_field_name(attr_field)
2488        .and_then(|n| n.utf8_text(source).ok())
2489        .unwrap_or("");
2490    if !obj.is_empty() && !attr.is_empty() {
2491        push_method_call_ref(obj, attr, entity_id, refs);
2492    }
2493}
2494
2495fn push_method_call_ref(obj: &str, method: &str, entity_id: &str, refs: &mut Vec<AstRef>) {
2496    refs.push(AstRef {
2497        from_entity_id: entity_id.to_string(),
2498        kind: AstRefKind::MethodCall {
2499            receiver: obj.to_string(),
2500            method: method.to_string(),
2501        },
2502    });
2503}
2504
2505/// Resolve a single reference against scopes and symbol tables.
2506fn resolve_ref(
2507    ast_ref: &AstRef,
2508    scope_idx: usize,
2509    scopes: &[Scope],
2510    symbol_table: &HashMap<String, Vec<String>>,
2511    class_members: &HashMap<String, Vec<(String, String)>>,
2512    import_table: &HashMap<(String, String), String>,
2513    instance_attr_types: &HashMap<(String, String), String>,
2514    entity_map: &HashMap<String, EntityInfo>,
2515    file_path: &str,
2516    from_entity_id: &str,
2517) -> Option<(String, RefType, &'static str)> {
2518    match &ast_ref.kind {
2519        AstRefKind::Call(name) => {
2520            // 1. Walk scope chain for the name
2521            if let Some(eid) = lookup_scope_chain(scope_idx, scopes, name) {
2522                if eid != from_entity_id {
2523                    return Some((eid, RefType::Calls, "scope_chain"));
2524                }
2525            }
2526
2527            // 2. Check import table
2528            let key = (file_path.to_string(), name.clone());
2529            if let Some(target_id) = import_table.get(&key) {
2530                return Some((target_id.clone(), RefType::Calls, "import"));
2531            }
2532
2533            // 3. Check if it's a constructor call (capitalized name)
2534            if name.chars().next().map_or(false, |c| c.is_uppercase()) {
2535                if let Some(target_ids) = symbol_table.get(name.as_str()) {
2536                    let target = target_ids
2537                        .iter()
2538                        .find(|id| {
2539                            entity_map
2540                                .get(*id)
2541                                .map_or(false, |e| e.file_path == file_path)
2542                        })
2543                        .or_else(|| target_ids.first());
2544                    if let Some(tid) = target {
2545                        return Some((tid.clone(), RefType::TypeRef, "scope_chain"));
2546                    }
2547                }
2548            }
2549
2550            None
2551        }
2552
2553        AstRefKind::MethodCall { receiver, method } => {
2554            if receiver == "self" || receiver == "this" {
2555                // self.method() -> find in enclosing class
2556                let mut idx = scope_idx;
2557                loop {
2558                    if scopes[idx].kind == "class" {
2559                        if let Some(eid) = scopes[idx].defs.get(method.as_str()) {
2560                            return Some((eid.clone(), RefType::Calls, "scope_chain"));
2561                        }
2562                        break;
2563                    }
2564                    match scopes[idx].parent {
2565                        Some(p) => idx = p,
2566                        None => break,
2567                    }
2568                }
2569                return None;
2570            }
2571
2572            // Handle chained self.attr.method() pattern
2573            // receiver is "self.X" where X is an instance attribute
2574            if receiver.starts_with("self.") || receiver.starts_with("this.") {
2575                let attr_name = &receiver[5..]; // strip "self." or "this."
2576                // Find the enclosing class name
2577                let class_name = find_enclosing_class(scope_idx, scopes, entity_map);
2578                if let Some(cn) = class_name {
2579                    // Look up instance attribute type
2580                    if let Some(attr_type) = instance_attr_types.get(&(cn, attr_name.to_string())) {
2581                        if let Some(members) = class_members.get(attr_type.as_str()) {
2582                            if let Some((_, mid)) = members.iter().find(|(n, _)| n == method) {
2583                                return Some((mid.clone(), RefType::Calls, "type_tracking"));
2584                            }
2585                        }
2586                    }
2587                }
2588            }
2589
2590            // Handle chained var.field.method() pattern (e.g. Go receiver: t.Conn.Execute())
2591            if receiver.contains('.') && !receiver.starts_with("self.") && !receiver.starts_with("this.") {
2592                if let Some(dot_pos) = receiver.find('.') {
2593                    let var_part = &receiver[..dot_pos];
2594                    let field_part = &receiver[dot_pos + 1..];
2595                    if let Some(var_type) = lookup_type_in_scopes(scope_idx, scopes, var_part) {
2596                        if let Some(attr_type) = instance_attr_types.get(&(var_type, field_part.to_string())) {
2597                            if let Some(members) = class_members.get(attr_type.as_str()) {
2598                                if let Some((_, mid)) = members.iter().find(|(n, _)| n == method) {
2599                                    return Some((mid.clone(), RefType::Calls, "type_tracking"));
2600                                }
2601                            }
2602                        }
2603                    }
2604                }
2605            }
2606
2607            // receiver.method() -> look up receiver type, then resolve method
2608            let receiver_type = lookup_type_in_scopes(scope_idx, scopes, receiver);
2609
2610            if let Some(class_name) = receiver_type {
2611                if let Some(members) = class_members.get(class_name.as_str()) {
2612                    if let Some((_, mid)) = members.iter().find(|(n, _)| n == method) {
2613                        return Some((mid.clone(), RefType::Calls, "type_tracking"));
2614                    }
2615                }
2616            }
2617
2618            // Fallback: check import table for the receiver
2619            let key = (file_path.to_string(), receiver.clone());
2620            if let Some(target_id) = import_table.get(&key) {
2621                if let Some(info) = entity_map.get(target_id) {
2622                    if matches!(info.entity_type.as_str(), "class" | "struct") {
2623                        if let Some(members) = class_members.get(&info.name) {
2624                            if let Some((_, mid)) =
2625                                members.iter().find(|(n, _)| n == method)
2626                            {
2627                                return Some((
2628                                    mid.clone(),
2629                                    RefType::Calls,
2630                                    "type_tracking",
2631                                ));
2632                            }
2633                        }
2634                    }
2635                }
2636            }
2637
2638            // Go package-qualified call: package.Function()
2639            // Try the method name directly in the import table
2640            let key = (file_path.to_string(), method.clone());
2641            if let Some(target_id) = import_table.get(&key) {
2642                return Some((target_id.clone(), RefType::Calls, "import"));
2643            }
2644
2645            None
2646        }
2647
2648        AstRefKind::Name(name) => {
2649            if let Some(eid) = lookup_scope_chain(scope_idx, scopes, name) {
2650                return Some((eid, RefType::TypeRef, "scope_chain"));
2651            }
2652            let key = (file_path.to_string(), name.clone());
2653            if let Some(target_id) = import_table.get(&key) {
2654                return Some((target_id.clone(), RefType::Imports, "import"));
2655            }
2656            None
2657        }
2658
2659        AstRefKind::Attribute { receiver, attr } => {
2660            let receiver_type = lookup_type_in_scopes(scope_idx, scopes, receiver);
2661            if let Some(class_name) = receiver_type {
2662                if let Some(members) = class_members.get(class_name.as_str()) {
2663                    if let Some((_, mid)) = members.iter().find(|(n, _)| n == attr) {
2664                        return Some((mid.clone(), RefType::Calls, "type_tracking"));
2665                    }
2666                }
2667            }
2668            None
2669        }
2670    }
2671}
2672
2673/// Find the class name for the enclosing class scope.
2674fn find_enclosing_class(
2675    start_scope: usize,
2676    scopes: &[Scope],
2677    entity_map: &HashMap<String, EntityInfo>,
2678) -> Option<String> {
2679    let mut idx = start_scope;
2680    loop {
2681        if scopes[idx].kind == "class" {
2682            if let Some(ref oid) = scopes[idx].owner_id {
2683                return entity_map.get(oid).map(|e| e.name.clone());
2684            }
2685        }
2686        match scopes[idx].parent {
2687            Some(p) => idx = p,
2688            None => return None,
2689        }
2690    }
2691}
2692
2693/// Walk up the scope chain looking for a definition.
2694fn lookup_scope_chain(
2695    start_scope: usize,
2696    scopes: &[Scope],
2697    name: &str,
2698) -> Option<String> {
2699    let mut idx = start_scope;
2700    loop {
2701        if let Some(eid) = scopes[idx].defs.get(name) {
2702            return Some(eid.clone());
2703        }
2704        match scopes[idx].parent {
2705            Some(p) => idx = p,
2706            None => return None,
2707        }
2708    }
2709}
2710
2711/// Walk up the scope chain looking for a type binding.
2712fn lookup_type_in_scopes(
2713    start_scope: usize,
2714    scopes: &[Scope],
2715    var_name: &str,
2716) -> Option<String> {
2717    let mut idx = start_scope;
2718    loop {
2719        if let Some(type_name) = scopes[idx].types.get(var_name) {
2720            return Some(type_name.clone());
2721        }
2722        match scopes[idx].parent {
2723            Some(p) => idx = p,
2724            None => return None,
2725        }
2726    }
2727}
2728
2729fn is_builtin_for_lang(name: &str, lang: &str) -> bool {
2730    // Common builtins across languages
2731    if matches!(name, "None" | "True" | "False" | "null" | "undefined" | "nil") {
2732        return true;
2733    }
2734    match lang {
2735        "python" => matches!(
2736            name,
2737            "print" | "len" | "range" | "str" | "int" | "float" | "bool"
2738                | "list" | "dict" | "set" | "tuple" | "type" | "super"
2739                | "isinstance" | "issubclass" | "getattr" | "setattr"
2740                | "hasattr" | "delattr" | "open" | "input" | "map"
2741                | "filter" | "zip" | "enumerate" | "sorted" | "reversed"
2742                | "min" | "max" | "sum" | "any" | "all" | "abs"
2743                | "round" | "format" | "repr" | "id" | "hash"
2744                | "ValueError" | "TypeError" | "KeyError" | "RuntimeError"
2745                | "Exception" | "StopIteration"
2746        ),
2747        "typescript" => matches!(
2748            name,
2749            "console" | "parseInt" | "parseFloat" | "isNaN" | "isFinite"
2750                | "setTimeout" | "setInterval" | "clearTimeout" | "clearInterval"
2751                | "Promise" | "Array" | "Object" | "Map" | "Set" | "WeakMap" | "WeakSet"
2752                | "JSON" | "Math" | "Date" | "RegExp" | "Error" | "TypeError"
2753                | "RangeError" | "Symbol" | "Proxy" | "Reflect"
2754                | "String" | "Number" | "Boolean" | "BigInt"
2755                | "require" | "module" | "exports" | "process"
2756                | "Buffer" | "global" | "window" | "document"
2757                | "fetch" | "Response" | "Request" | "Headers" | "URL"
2758        ),
2759        "rust" => matches!(
2760            name,
2761            "println" | "eprintln" | "print" | "eprint" | "dbg"
2762                | "format" | "write" | "writeln"
2763                | "vec" | "panic" | "todo" | "unimplemented" | "unreachable"
2764                | "assert" | "assert_eq" | "assert_ne" | "debug_assert"
2765                | "Some" | "None" | "Ok" | "Err"
2766                | "Box" | "Vec" | "String" | "HashMap" | "HashSet"
2767                | "Arc" | "Rc" | "Mutex" | "RwLock" | "Cell" | "RefCell"
2768                | "Option" | "Result" | "Iterator" | "IntoIterator"
2769                | "Clone" | "Copy" | "Debug" | "Display" | "Default"
2770                | "From" | "Into" | "TryFrom" | "TryInto"
2771                | "Send" | "Sync" | "Sized" | "Unpin"
2772                | "cfg" | "derive" | "include" | "env"
2773        ),
2774        "go" => matches!(
2775            name,
2776            "fmt" | "log" | "os" | "io" | "strings" | "strconv" | "bytes"
2777                | "make" | "len" | "cap" | "append" | "copy" | "delete" | "close"
2778                | "panic" | "recover" | "new" | "print" | "println"
2779                | "error" | "string" | "int" | "int8" | "int16" | "int32" | "int64"
2780                | "uint" | "uint8" | "uint16" | "uint32" | "uint64"
2781                | "float32" | "float64" | "complex64" | "complex128"
2782                | "bool" | "byte" | "rune" | "uintptr"
2783                | "Println" | "Printf" | "Sprintf" | "Fprintf" | "Errorf"
2784        ),
2785        _ => false,
2786    }
2787}