1use std::collections::HashMap;
15use std::path::Path;
16
17use rayon::prelude::*;
18
19use crate::model::entity::SemanticEntity;
20use crate::parser::graph::{EntityInfo, RefType};
21use crate::parser::plugins::code::languages::{
22 get_language_config, AssignmentStrategy, CallNodeStyle, ClassNameField, InitStrategy,
23 ParamNameField, ScopeResolveConfig,
24};
25
26pub struct Scope {
28 parent: Option<usize>,
29 defs: HashMap<String, String>,
31 types: HashMap<String, String>,
33 pending_call_types: HashMap<String, String>,
36 owner_id: Option<String>,
38 kind: &'static str,
40}
41
42struct AstRef {
44 kind: AstRefKind,
46}
47
48enum AstRefKind {
49 Call(String),
51 MethodCall { receiver: String, method: String },
53}
54
55pub struct ScopeResult {
57 pub edges: Vec<(String, String, RefType)>,
58 pub resolution_log: Vec<ResolutionEntry>,
60}
61
62#[derive(Clone)]
63pub struct ResolutionEntry {
64 pub from_entity: String,
65 pub reference: String,
66 pub resolved_to: Option<String>,
67 pub method: &'static str, }
69
70pub fn resolve_with_scopes(
78 root: &Path,
79 file_paths: &[String],
80 all_entities: &[SemanticEntity],
81 entity_map: &HashMap<String, EntityInfo>,
82 pre_parsed: Option<Vec<(String, String, tree_sitter::Tree)>>,
83) -> ScopeResult {
84 let mut all_edges: Vec<(String, String, RefType)> = Vec::new();
85 let mut log: Vec<ResolutionEntry> = Vec::new();
86
87 let mut symbol_table: HashMap<String, Vec<String>> = HashMap::new();
89 for entity in all_entities {
90 symbol_table
91 .entry(entity.name.clone())
92 .or_default()
93 .push(entity.id.clone());
94 }
95
96 let mut class_members: HashMap<String, Vec<(String, String)>> = HashMap::new();
98 for entity in all_entities {
99 if let Some(ref pid) = entity.parent_id {
100 if let Some(parent) = entity_map.get(pid) {
101 if matches!(
102 parent.entity_type.as_str(),
103 "class" | "struct" | "interface" | "impl"
104 ) {
105 class_members
106 .entry(parent.name.clone())
107 .or_default()
108 .push((entity.name.clone(), entity.id.clone()));
109 }
110 }
111 }
112 }
113
114 for entity in all_entities {
117 if entity.entity_type == "method" && entity.file_path.ends_with(".go") {
118 if let Some(struct_name) = extract_go_receiver_type(&entity.content) {
119 class_members
120 .entry(struct_name)
121 .or_default()
122 .push((entity.name.clone(), entity.id.clone()));
123 }
124 }
125 }
126
127 let mut entity_ranges: HashMap<String, Vec<(usize, usize, String)>> = HashMap::new();
129 for entity in all_entities {
130 entity_ranges
131 .entry(entity.file_path.clone())
132 .or_default()
133 .push((entity.start_line, entity.end_line, entity.id.clone()));
134 }
135
136 let mut return_type_map: HashMap<String, String> = HashMap::new();
138
139 let mut instance_attr_types: HashMap<(String, String), String> = HashMap::new();
141
142 let mut init_params: HashMap<String, Vec<String>> = HashMap::new();
145 let mut attr_to_param: HashMap<(String, String), String> = HashMap::new();
146
147 let mut owned_parsed_files: Vec<(String, String, tree_sitter::Tree)> = Vec::new();
149 let pre_set: std::collections::HashSet<String> = if let Some(pp) = pre_parsed {
150 let set = pp.iter().map(|(fp, _, _)| fp.clone()).collect();
151 owned_parsed_files = pp;
152 set
153 } else {
154 std::collections::HashSet::new()
155 };
156 for file_path in file_paths {
158 if pre_set.contains(file_path) {
159 continue;
160 }
161 let full_path = root.join(file_path);
162 let content = match std::fs::read_to_string(&full_path) {
163 Ok(c) => c,
164 Err(_) => continue,
165 };
166 let ext = file_path.rfind('.').map(|i| &file_path[i..]).unwrap_or("");
167 let config = match get_language_config(ext) {
168 Some(c) => c,
169 None => continue,
170 };
171 let language = match (config.get_language)() {
172 Some(l) => l,
173 None => continue,
174 };
175 let mut parser = tree_sitter::Parser::new();
176 let _ = parser.set_language(&language);
177 if let Some(tree) = parser.parse(content.as_bytes(), None) {
178 owned_parsed_files.push((file_path.clone(), content, tree));
179 }
180 }
181 let parsed_files: &[(String, String, tree_sitter::Tree)] = &owned_parsed_files;
182
183 for (file_path, content, tree) in parsed_files {
186 let source = content.as_bytes();
187 let ext = file_path.rfind('.').map(|i| &file_path[i..]).unwrap_or("");
188 let config = match get_language_config(ext).and_then(|c| c.scope_resolve) {
189 Some(c) => c,
190 None => continue,
191 };
192
193 scan_return_types(
194 tree.root_node(),
195 file_path,
196 all_entities,
197 source,
198 &mut return_type_map,
199 config,
200 );
201
202 scan_init_self_attrs(
203 tree.root_node(),
204 file_path,
205 all_entities,
206 entity_map,
207 source,
208 &mut instance_attr_types,
209 &mut init_params,
210 &mut attr_to_param,
211 config,
212 );
213 }
214
215 infer_constructor_param_types(
219 parsed_files,
220 &return_type_map,
221 &init_params,
222 &attr_to_param,
223 &symbol_table,
224 entity_map,
225 &mut instance_attr_types,
226 );
227
228 let per_file_results: Vec<(Vec<(String, String, RefType)>, Vec<ResolutionEntry>)> = parsed_files
230 .par_iter()
231 .filter_map(|(file_path, content, tree)| {
232 let source = content.as_bytes();
233 let ext = file_path.rfind('.').map(|i| &file_path[i..]).unwrap_or("");
234 let config = get_language_config(ext).and_then(|c| c.scope_resolve)?;
235
236 let mut scopes: Vec<Scope> = vec![Scope {
237 parent: None,
238 defs: HashMap::new(),
239 types: HashMap::new(),
240 pending_call_types: HashMap::new(),
241 owner_id: None,
242 kind: "module",
243 }];
244
245 let mut entity_scope_map: HashMap<String, usize> = HashMap::new();
246 let mut entity_inner_scope: HashMap<String, usize> = HashMap::new();
247
248 if let Some(ranges) = entity_ranges.get(file_path.as_str()) {
249 for (_start, _end, eid) in ranges {
250 if let Some(info) = entity_map.get(eid) {
251 if info.parent_id.is_none() {
252 scopes[0].defs.insert(info.name.clone(), eid.clone());
253 entity_scope_map.insert(eid.clone(), 0);
254 }
255 }
256 }
257 }
258
259 build_scopes_from_ast(
260 tree.root_node(),
261 0,
262 &mut scopes,
263 &mut entity_scope_map,
264 &mut entity_inner_scope,
265 all_entities,
266 entity_map,
267 file_path,
268 source,
269 config,
270 );
271
272 let mut local_import_table: HashMap<(String, String), String> = HashMap::new();
273 extract_imports_from_ast(
274 tree.root_node(),
275 file_path,
276 source,
277 &symbol_table,
278 entity_map,
279 &mut local_import_table,
280 &mut scopes,
281 config,
282 );
283
284 inject_return_type_bindings(
286 &entity_inner_scope,
287 &mut scopes,
288 &return_type_map,
289 &local_import_table,
290 file_path,
291 entity_map,
292 );
293
294 let file_entities: Vec<&SemanticEntity> = all_entities
295 .iter()
296 .filter(|e| e.file_path == *file_path)
297 .collect();
298
299 let mut file_edges: Vec<(String, String, RefType)> = Vec::new();
300 let mut file_log: Vec<ResolutionEntry> = Vec::new();
301
302 for entity in &file_entities {
303 let scope_idx = entity_inner_scope
304 .get(&entity.id)
305 .or_else(|| entity_scope_map.get(&entity.id))
306 .copied()
307 .unwrap_or(0);
308
309 let refs = extract_ast_refs(
310 tree.root_node(),
311 entity,
312 source,
313 config,
314 );
315
316 for ast_ref in refs {
317 let resolution = resolve_ref(
318 &ast_ref,
319 scope_idx,
320 &scopes,
321 &symbol_table,
322 &class_members,
323 &local_import_table,
324 &instance_attr_types,
325 entity_map,
326 file_path,
327 &entity.id,
328 );
329
330 if let Some((target_id, ref_type, method)) = resolution {
331 if target_id != entity.id {
332 let is_parent_child = entity
333 .parent_id
334 .as_ref()
335 .map_or(false, |pid| pid == &target_id || entity_map.get(&target_id).map_or(false, |t| t.parent_id.as_ref() == Some(&entity.id)));
336
337 if !is_parent_child {
338 file_edges.push((
339 entity.id.clone(),
340 target_id.clone(),
341 ref_type,
342 ));
343 file_log.push(ResolutionEntry {
344 from_entity: entity.id.clone(),
345 reference: ref_description(&ast_ref),
346 resolved_to: Some(target_id),
347 method,
348 });
349 }
350 }
351 } else {
352 file_log.push(ResolutionEntry {
353 from_entity: entity.id.clone(),
354 reference: ref_description(&ast_ref),
355 resolved_to: None,
356 method: "unresolved",
357 });
358 }
359 }
360 }
361
362 Some((file_edges, file_log))
363 })
364 .collect();
365
366 for (file_edges, file_log) in per_file_results {
367 all_edges.extend(file_edges);
368 log.extend(file_log);
369 }
370
371 let mut seen = std::collections::HashSet::new();
373 all_edges.retain(|e| seen.insert((e.0.clone(), e.1.clone())));
374
375 ScopeResult {
376 edges: all_edges,
377 resolution_log: log,
378 }
379}
380
381fn ref_description(ast_ref: &AstRef) -> String {
382 match &ast_ref.kind {
383 AstRefKind::Call(name) => format!("{}()", name),
384 AstRefKind::MethodCall { receiver, method } => format!("{}.{}()", receiver, method),
385 }
386}
387
388fn build_scopes_from_ast(
393 root: tree_sitter::Node,
394 root_scope: usize,
395 scopes: &mut Vec<Scope>,
396 entity_scope_map: &mut HashMap<String, usize>,
397 entity_inner_scope: &mut HashMap<String, usize>,
398 all_entities: &[SemanticEntity],
399 entity_map: &HashMap<String, EntityInfo>,
400 file_path: &str,
401 source: &[u8],
402 config: &ScopeResolveConfig,
403) {
404 let mut worklist: Vec<(tree_sitter::Node, usize)> = vec![(root, root_scope)];
406
407 while let Some((node, current_scope)) = worklist.pop() {
408 let kind = node.kind();
409
410 let is_class_like = config.class_scope_nodes.contains(&kind);
412
413 let is_impl = config.impl_scope_nodes.contains(&kind);
415
416 if is_class_like || is_impl {
417 let class_name = if is_impl {
418 node.child_by_field_name("type")
419 .and_then(|n| n.utf8_text(source).ok())
420 .unwrap_or("")
421 } else {
422 match &config.class_name_field {
423 ClassNameField::Simple(field) => {
424 node.child_by_field_name(field)
425 .and_then(|n| n.utf8_text(source).ok())
426 .unwrap_or("")
427 }
428 ClassNameField::TypeSpec { spec_kind, field } => {
429 let mut name = "";
430 let mut cursor = node.walk();
431 for child in node.named_children(&mut cursor) {
432 if child.kind() == *spec_kind {
433 name = child
434 .child_by_field_name(field)
435 .and_then(|n| n.utf8_text(source).ok())
436 .unwrap_or("");
437 break;
438 }
439 }
440 name
441 }
442 ClassNameField::ImplType(field) => {
443 node.child_by_field_name(field)
444 .and_then(|n| n.utf8_text(source).ok())
445 .unwrap_or("")
446 }
447 }
448 };
449
450 let class_entity = all_entities.iter().find(|e| {
451 e.file_path == file_path
452 && e.name == class_name
453 && matches!(e.entity_type.as_str(), "class" | "struct" | "interface")
454 });
455
456 if let Some(ce) = class_entity {
457 let existing_scope = entity_inner_scope.get(&ce.id).copied();
458
459 let class_scope_idx = if let Some(idx) = existing_scope {
460 idx
461 } else {
462 let idx = scopes.len();
463 scopes.push(Scope {
464 parent: Some(current_scope),
465 defs: HashMap::new(),
466 types: HashMap::new(),
467 pending_call_types: HashMap::new(),
468 owner_id: Some(ce.id.clone()),
469 kind: "class",
470 });
471 entity_scope_map.insert(ce.id.clone(), current_scope);
472 entity_inner_scope.insert(ce.id.clone(), idx);
473 idx
474 };
475
476 for entity in all_entities {
477 if entity.parent_id.as_ref() == Some(&ce.id) {
478 scopes[class_scope_idx]
479 .defs
480 .insert(entity.name.clone(), entity.id.clone());
481 entity_scope_map.insert(entity.id.clone(), class_scope_idx);
482 }
483 }
484
485 let mut cursor = node.walk();
486 let children: Vec<_> = node.named_children(&mut cursor).collect();
487 for child in children.into_iter().rev() {
488 worklist.push((child, class_scope_idx));
489 }
490 continue;
491 } else if !is_impl {
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 let children: Vec<_> = node.named_children(&mut cursor).collect();
503 for child in children.into_iter().rev() {
504 worklist.push((child, class_scope_idx));
505 }
506 continue;
507 }
508 }
509
510 let is_function_like = config.function_scope_nodes.contains(&kind);
512
513 if is_function_like {
514 let func_name = node.child_by_field_name("name")
515 .and_then(|n| n.utf8_text(source).ok())
516 .unwrap_or("");
517
518 let parent_scope = if config.external_method && kind == "method_declaration" {
519 let receiver_type = node.utf8_text(source).ok()
520 .and_then(|t| extract_go_receiver_type(t));
521 if let Some(ref struct_name) = receiver_type {
522 let found = scopes.iter().enumerate().find(|(_, s)| {
523 s.kind == "class" && s.owner_id.as_ref().map_or(false, |oid| {
524 entity_map.get(oid).map_or(false, |e| e.name == *struct_name)
525 })
526 });
527 found.map(|(idx, _)| idx).unwrap_or(current_scope)
528 } else {
529 current_scope
530 }
531 } else {
532 current_scope
533 };
534
535 let func_scope_idx = scopes.len();
536 scopes.push(Scope {
537 parent: Some(parent_scope),
538 defs: HashMap::new(),
539 types: HashMap::new(),
540 pending_call_types: HashMap::new(),
541 owner_id: None,
542 kind: "function",
543 });
544
545 let func_entity = all_entities.iter().find(|e| {
546 e.file_path == file_path && e.name == func_name && {
547 let line = node.start_position().row + 1;
548 e.start_line <= line && line <= e.end_line
549 }
550 });
551
552 if let Some(fe) = func_entity {
553 scopes[func_scope_idx].owner_id = Some(fe.id.clone());
554 entity_scope_map.entry(fe.id.clone()).or_insert(parent_scope);
555 entity_inner_scope.insert(fe.id.clone(), func_scope_idx);
556 if config.external_method && kind == "method_declaration" && parent_scope != current_scope {
557 scopes[parent_scope].defs.insert(fe.name.clone(), fe.id.clone());
558 }
559 }
560
561 scan_assignments(node, func_scope_idx, scopes, source, config);
562 scan_function_params(node, func_scope_idx, scopes, source, config);
563
564 if config.external_method && kind == "method_declaration" {
565 if let Some(receiver) = node.child_by_field_name("receiver") {
566 let mut rcursor = receiver.walk();
567 for param in receiver.named_children(&mut rcursor) {
568 if param.kind() == "parameter_declaration" {
569 let param_name = param
570 .child_by_field_name("name")
571 .and_then(|n| n.utf8_text(source).ok())
572 .unwrap_or("");
573 let param_type = param
574 .child_by_field_name("type")
575 .map(|n| extract_base_type(n, source))
576 .unwrap_or_default();
577 if !param_name.is_empty() && !param_type.is_empty() {
578 scopes[func_scope_idx]
579 .types
580 .insert(param_name.to_string(), param_type);
581 }
582 }
583 }
584 }
585 }
586
587 let mut cursor = node.walk();
588 let children: Vec<_> = node.named_children(&mut cursor).collect();
589 for child in children.into_iter().rev() {
590 worklist.push((child, func_scope_idx));
591 }
592 continue;
593 }
594
595 let mut cursor = node.walk();
596 let children: Vec<_> = node.named_children(&mut cursor).collect();
597 for child in children.into_iter().rev() {
598 worklist.push((child, current_scope));
599 }
600 }
601}
602
603fn scan_assignments(
605 root: tree_sitter::Node,
606 scope_idx: usize,
607 scopes: &mut Vec<Scope>,
608 source: &[u8],
609 config: &ScopeResolveConfig,
610) {
611 let mut worklist = vec![root];
612 while let Some(node) = worklist.pop() {
613 let mut cursor = node.walk();
614 for child in node.named_children(&mut cursor) {
615 let ck = child.kind();
616
617 for rule in config.assignment_rules {
619 if ck == rule.node_kind {
620 match rule.strategy {
621 AssignmentStrategy::LeftRight => {
622 scan_single_assignment(child, scope_idx, scopes, source);
623 }
624 AssignmentStrategy::Declarators => {
625 scan_ts_var_declaration(child, scope_idx, scopes, source);
626 }
627 AssignmentStrategy::PatternBased => {
628 scan_rust_let_declaration(child, scope_idx, scopes, source);
629 }
630 AssignmentStrategy::ShortVar => {
631 scan_go_short_var(child, scope_idx, scopes, source);
632 }
633 AssignmentStrategy::VarSpec => {
634 scan_go_var_declaration(child, scope_idx, scopes, source);
635 }
636 }
637 }
638 }
639
640 if config.assignment_recurse_into.contains(&ck) {
642 worklist.push(child);
643 }
644 }
645 }
646}
647
648fn scan_function_params(
651 node: tree_sitter::Node,
652 scope_idx: usize,
653 scopes: &mut Vec<Scope>,
654 source: &[u8],
655 config: &ScopeResolveConfig,
656) {
657 let params_node = match node.child_by_field_name("parameters") {
658 Some(p) => p,
659 None => return,
660 };
661
662 let mut cursor = params_node.walk();
663 for child in params_node.named_children(&mut cursor) {
664 for rule in config.param_rules {
665 if child.kind() != rule.node_kind {
666 continue;
667 }
668
669 let param_name = match &rule.name_field {
670 ParamNameField::Simple(field) => {
671 child.child_by_field_name(field)
672 .and_then(|n| n.utf8_text(source).ok())
673 .unwrap_or("")
674 }
675 ParamNameField::WithFallback(field) => {
676 child.child_by_field_name(field)
677 .or_else(|| child.named_child(0).filter(|n| n.kind() == "identifier"))
678 .and_then(|n| n.utf8_text(source).ok())
679 .unwrap_or("")
680 }
681 ParamNameField::RustPattern => {
682 child.child_by_field_name("pattern")
683 .and_then(|n| {
684 if n.kind() == "identifier" {
685 n.utf8_text(source).ok()
686 } else if n.kind() == "mut_pattern" {
687 n.named_child(0).and_then(|c| c.utf8_text(source).ok())
688 } else if n.kind() == "reference_pattern" {
689 n.named_child(0).and_then(|c| {
690 if c.kind() == "identifier" {
691 c.utf8_text(source).ok()
692 } else if c.kind() == "mut_pattern" {
693 c.named_child(0).and_then(|cc| cc.utf8_text(source).ok())
694 } else {
695 None
696 }
697 })
698 } else {
699 None
700 }
701 })
702 .unwrap_or("")
703 }
704 };
705
706 if param_name.is_empty() || rule.skip_names.contains(¶m_name) {
707 continue;
708 }
709
710 if let Some(type_node) = child.child_by_field_name(rule.type_field) {
711 let type_text = extract_base_type(type_node, source);
712 if !type_text.is_empty()
713 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
714 {
715 scopes[scope_idx]
716 .types
717 .insert(param_name.to_string(), type_text);
718 }
719 }
720 }
721 }
722}
723
724fn scan_single_assignment(
726 node: tree_sitter::Node,
727 scope_idx: usize,
728 scopes: &mut Vec<Scope>,
729 source: &[u8],
730) {
731 let assign = if node.kind() == "assignment" {
732 node
733 } else {
734 let mut cursor = node.walk();
735 let children: Vec<_> = node.named_children(&mut cursor).collect();
736 match children.into_iter().find(|c| c.kind() == "assignment" || c.kind() == "assignment_expression") {
737 Some(a) => a,
738 None => return,
739 }
740 };
741
742 let left = match assign.child_by_field_name("left") {
743 Some(l) => l,
744 None => return,
745 };
746 let right = match assign.child_by_field_name("right") {
747 Some(r) => r,
748 None => return,
749 };
750
751 if left.kind() != "identifier" {
752 return;
753 }
754 let var_name = match left.utf8_text(source) {
755 Ok(n) => n.to_string(),
756 Err(_) => return,
757 };
758
759 record_type_from_rhs(right, &var_name, scope_idx, scopes, source);
760}
761
762fn scan_ts_var_declaration(
764 node: tree_sitter::Node,
765 scope_idx: usize,
766 scopes: &mut Vec<Scope>,
767 source: &[u8],
768) {
769 let mut cursor = node.walk();
770 for child in node.named_children(&mut cursor) {
771 if child.kind() == "variable_declarator" {
772 let var_name = child
773 .child_by_field_name("name")
774 .and_then(|n| n.utf8_text(source).ok())
775 .unwrap_or("")
776 .to_string();
777 if var_name.is_empty() {
778 continue;
779 }
780
781 if let Some(type_ann) = child.child_by_field_name("type") {
783 let type_text = extract_base_type(type_ann, source);
784 if !type_text.is_empty()
785 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
786 {
787 scopes[scope_idx]
788 .types
789 .insert(var_name.clone(), type_text);
790 continue;
791 }
792 }
793
794 if let Some(value) = child.child_by_field_name("value") {
796 record_type_from_rhs(value, &var_name, scope_idx, scopes, source);
797 }
798 }
799 }
800}
801
802fn scan_rust_let_declaration(
804 node: tree_sitter::Node,
805 scope_idx: usize,
806 scopes: &mut Vec<Scope>,
807 source: &[u8],
808) {
809 let var_name = node
810 .child_by_field_name("pattern")
811 .and_then(|n| {
812 if n.kind() == "identifier" {
814 n.utf8_text(source).ok()
815 } else if n.kind() == "mut_pattern" {
816 n.named_child(0).and_then(|c| c.utf8_text(source).ok())
817 } else {
818 None
819 }
820 })
821 .unwrap_or("")
822 .to_string();
823
824 if var_name.is_empty() {
825 return;
826 }
827
828 if let Some(type_node) = node.child_by_field_name("type") {
830 let type_text = extract_base_type(type_node, source);
831 if !type_text.is_empty()
832 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
833 {
834 scopes[scope_idx]
835 .types
836 .insert(var_name, type_text);
837 return;
838 }
839 }
840
841 if let Some(value) = node.child_by_field_name("value") {
843 record_type_from_rhs(value, &var_name, scope_idx, scopes, source);
844 }
845}
846
847fn scan_go_short_var(
849 node: tree_sitter::Node,
850 scope_idx: usize,
851 scopes: &mut Vec<Scope>,
852 source: &[u8],
853) {
854 let left = match node.child_by_field_name("left") {
855 Some(l) => l,
856 None => return,
857 };
858 let right = match node.child_by_field_name("right") {
859 Some(r) => r,
860 None => return,
861 };
862
863 let var_name = if left.kind() == "expression_list" {
865 left.named_child(0)
866 .and_then(|n| n.utf8_text(source).ok())
867 .unwrap_or("")
868 .to_string()
869 } else {
870 left.utf8_text(source).unwrap_or("").to_string()
871 };
872
873 if var_name.is_empty() {
874 return;
875 }
876
877 let rhs = if right.kind() == "expression_list" {
878 match right.named_child(0) {
879 Some(n) => n,
880 None => return,
881 }
882 } else {
883 right
884 };
885
886 record_type_from_rhs(rhs, &var_name, scope_idx, scopes, source);
887}
888
889fn scan_go_var_declaration(
891 node: tree_sitter::Node,
892 scope_idx: usize,
893 scopes: &mut Vec<Scope>,
894 source: &[u8],
895) {
896 let mut cursor = node.walk();
897 for child in node.named_children(&mut cursor) {
898 if child.kind() == "var_spec" {
899 let var_name = child
900 .child_by_field_name("name")
901 .and_then(|n| n.utf8_text(source).ok())
902 .unwrap_or("")
903 .to_string();
904 if var_name.is_empty() {
905 if let Some(first) = child.named_child(0) {
907 if first.kind() == "identifier" {
908 let name = first.utf8_text(source).unwrap_or("").to_string();
909 if !name.is_empty() {
910 if let Some(type_node) = child.child_by_field_name("type") {
912 let type_text = extract_base_type(type_node, source);
913 if !type_text.is_empty()
914 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
915 {
916 scopes[scope_idx].types.insert(name, type_text);
917 }
918 }
919 }
920 }
921 }
922 continue;
923 }
924
925 if let Some(type_node) = child.child_by_field_name("type") {
927 let type_text = extract_base_type(type_node, source);
928 if !type_text.is_empty()
929 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
930 {
931 scopes[scope_idx]
932 .types
933 .insert(var_name, type_text);
934 continue;
935 }
936 }
937
938 if let Some(value) = child.child_by_field_name("value") {
940 let rhs = if value.kind() == "expression_list" {
941 value.named_child(0).unwrap_or(value)
942 } else {
943 value
944 };
945 record_type_from_rhs(rhs, &var_name, scope_idx, scopes, source);
946 }
947 }
948 }
949}
950
951fn record_type_from_rhs(
954 rhs: tree_sitter::Node,
955 var_name: &str,
956 scope_idx: usize,
957 scopes: &mut Vec<Scope>,
958 source: &[u8],
959) {
960 match rhs.kind() {
961 "call" | "call_expression" => {
963 let func_node = rhs
964 .child_by_field_name("function")
965 .or_else(|| rhs.named_child(0));
966 if let Some(func) = func_node {
967 if func.kind() == "identifier" {
968 let name = func.utf8_text(source).unwrap_or("");
969 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
970 scopes[scope_idx]
971 .types
972 .insert(var_name.to_string(), name.to_string());
973 } else {
974 scopes[scope_idx]
975 .pending_call_types
976 .insert(var_name.to_string(), name.to_string());
977 }
978 }
979 if func.kind() == "scoped_identifier" {
981 let text = func.utf8_text(source).unwrap_or("");
982 let parts: Vec<&str> = text.split("::").collect();
983 if parts.len() >= 2 {
984 let type_name = parts[0];
985 let method_name = parts[parts.len() - 1];
986 if type_name.chars().next().map_or(false, |c| c.is_uppercase()) {
987 scopes[scope_idx]
988 .types
989 .insert(var_name.to_string(), type_name.to_string());
990 } else {
991 scopes[scope_idx]
992 .pending_call_types
993 .insert(var_name.to_string(), method_name.to_string());
994 }
995 }
996 }
997 if func.kind() == "selector_expression" {
999 let field = func
1000 .child_by_field_name("field")
1001 .and_then(|n| n.utf8_text(source).ok())
1002 .unwrap_or("");
1003 if let Some(type_name) = field.strip_prefix("New") {
1005 if !type_name.is_empty()
1006 && type_name.chars().next().map_or(false, |c| c.is_uppercase())
1007 {
1008 scopes[scope_idx]
1009 .types
1010 .insert(var_name.to_string(), type_name.to_string());
1011 }
1012 } else if field.starts_with("Get") || field.chars().next().map_or(false, |c| c.is_uppercase()) {
1013 scopes[scope_idx]
1015 .pending_call_types
1016 .insert(var_name.to_string(), field.to_string());
1017 }
1018 }
1019 }
1020 }
1021 "new_expression" => {
1023 if let Some(constructor) = rhs.child_by_field_name("constructor") {
1024 let name = constructor.utf8_text(source).unwrap_or("");
1025 if !name.is_empty() {
1026 scopes[scope_idx]
1027 .types
1028 .insert(var_name.to_string(), name.to_string());
1029 }
1030 }
1031 }
1032 "composite_literal" => {
1034 if let Some(type_node) = rhs.child_by_field_name("type") {
1035 let name = type_node.utf8_text(source).unwrap_or("");
1036 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1037 scopes[scope_idx]
1038 .types
1039 .insert(var_name.to_string(), name.to_string());
1040 }
1041 }
1042 }
1043 _ => {}
1044 }
1045}
1046
1047fn extract_base_type(type_node: tree_sitter::Node, source: &[u8]) -> String {
1050 let text = type_node.utf8_text(source).unwrap_or("").trim().to_string();
1051 let text = text.trim_start_matches('&').trim_start_matches('*');
1053 let text = text.strip_prefix("mut ").unwrap_or(text).trim_start();
1054 let text = if let Some(i) = text.find('<') {
1056 &text[..i]
1057 } else if let Some(i) = text.find('[') {
1058 &text[..i]
1059 } else {
1060 text
1061 };
1062 let text = text.trim();
1064 let text = text.trim_start_matches(':').trim();
1066 text.to_string()
1067}
1068
1069fn extract_go_receiver_type(content: &str) -> Option<String> {
1071 let after_func = content.strip_prefix("func")?.trim_start();
1072 let paren_start = after_func.find('(')?;
1073 let paren_end = after_func.find(')')?;
1074 let receiver_block = &after_func[paren_start + 1..paren_end];
1075 let parts: Vec<&str> = receiver_block.split_whitespace().collect();
1077 let type_str = parts.last()?;
1078 let name = type_str.trim_start_matches('*');
1079 if name.is_empty() {
1080 None
1081 } else {
1082 Some(name.to_string())
1083 }
1084}
1085
1086fn scan_return_types(
1088 root: tree_sitter::Node,
1089 file_path: &str,
1090 all_entities: &[SemanticEntity],
1091 source: &[u8],
1092 return_type_map: &mut HashMap<String, String>,
1093 config: &ScopeResolveConfig,
1094) {
1095 let mut worklist = vec![root];
1096 while let Some(node) = worklist.pop() {
1097 let kind = node.kind();
1098
1099 let is_func = config.function_scope_nodes.contains(&kind);
1100
1101 if is_func {
1102 let func_name = node
1103 .child_by_field_name("name")
1104 .and_then(|n| n.utf8_text(source).ok())
1105 .unwrap_or("");
1106
1107 let func_entity = all_entities.iter().find(|e| {
1108 e.file_path == file_path && e.name == func_name && {
1109 let line = node.start_position().row + 1;
1110 e.start_line <= line && line <= e.end_line
1111 }
1112 });
1113
1114 if let Some(fe) = func_entity {
1115 let ret_type = config.return_type_field.and_then(|field| {
1117 node.child_by_field_name(field)
1118 .map(|n| extract_base_type(n, source))
1119 .filter(|t| !t.is_empty() && t.chars().next().map_or(false, |c| c.is_uppercase()))
1120 });
1121
1122 if let Some(rt) = ret_type {
1123 return_type_map.insert(fe.id.clone(), rt);
1124 } else {
1125 if let Some(ret_type) = find_return_constructor(node, source) {
1127 return_type_map.insert(fe.id.clone(), ret_type);
1128 }
1129 }
1130 }
1131 }
1132
1133 let mut cursor = node.walk();
1134 let children: Vec<_> = node.named_children(&mut cursor).collect();
1135 for child in children.into_iter().rev() {
1136 worklist.push(child);
1137 }
1138 }
1139}
1140
1141fn find_return_constructor(root: tree_sitter::Node, source: &[u8]) -> Option<String> {
1143 let mut worklist = vec![root];
1144 while let Some(node) = worklist.pop() {
1145 let mut cursor = node.walk();
1146 for child in node.named_children(&mut cursor) {
1147 if child.kind() == "return_statement" {
1148 let mut inner_cursor = child.walk();
1149 for ret_child in child.named_children(&mut inner_cursor) {
1150 if ret_child.kind() == "call" || ret_child.kind() == "call_expression" {
1152 if let Some(func) = ret_child.child_by_field_name("function") {
1153 if func.kind() == "identifier" {
1154 let name = func.utf8_text(source).unwrap_or("");
1155 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1156 return Some(name.to_string());
1157 }
1158 }
1159 }
1160 }
1161 if ret_child.kind() == "new_expression" {
1163 if let Some(constructor) = ret_child.child_by_field_name("constructor") {
1164 let name = constructor.utf8_text(source).unwrap_or("");
1165 if !name.is_empty() {
1166 return Some(name.to_string());
1167 }
1168 }
1169 }
1170 if ret_child.kind() == "composite_literal" {
1172 if let Some(type_node) = ret_child.child_by_field_name("type") {
1173 let name = type_node.utf8_text(source).unwrap_or("");
1174 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1175 return Some(name.to_string());
1176 }
1177 }
1178 }
1179 }
1180 }
1181 let ck = child.kind();
1183 if ck == "block" || ck == "statement_block" {
1184 worklist.push(child);
1185 }
1186 }
1187 }
1188 None
1189}
1190
1191fn scan_init_self_attrs(
1194 root: tree_sitter::Node,
1195 _file_path: &str,
1196 _all_entities: &[SemanticEntity],
1197 _entity_map: &HashMap<String, EntityInfo>,
1198 source: &[u8],
1199 instance_attr_types: &mut HashMap<(String, String), String>,
1200 init_params_map: &mut HashMap<String, Vec<String>>,
1201 attr_to_param_map: &mut HashMap<(String, String), String>,
1202 config: &ScopeResolveConfig,
1203) {
1204 let mut worklist = vec![root];
1205 while let Some(node) = worklist.pop() {
1206 let kind = node.kind();
1207
1208 match &config.init_strategy {
1209 InitStrategy::ConstructorBody { class_nodes, self_keyword, .. } => {
1210 if class_nodes.contains(&kind) {
1211 let class_name = node
1212 .child_by_field_name("name")
1213 .and_then(|n| n.utf8_text(source).ok())
1214 .unwrap_or("")
1215 .to_string();
1216
1217 if !class_name.is_empty() {
1218 let lang = if *self_keyword == "this" { "typescript" } else { "python" };
1220 scan_class_for_init(node, &class_name, source, instance_attr_types, init_params_map, attr_to_param_map, lang);
1221 }
1222 }
1223 }
1224 InitStrategy::StructFields { struct_nodes } => {
1225 if struct_nodes.contains(&kind) {
1226 if kind == "struct_item" {
1228 let struct_name = node
1229 .child_by_field_name("name")
1230 .and_then(|n| n.utf8_text(source).ok())
1231 .unwrap_or("")
1232 .to_string();
1233
1234 if !struct_name.is_empty() {
1235 scan_rust_struct_fields(node, &struct_name, source, instance_attr_types);
1236 }
1237 }
1238 if kind == "type_declaration" {
1240 scan_go_struct_fields(node, source, instance_attr_types);
1241 }
1242 }
1243 }
1244 InitStrategy::None => {}
1245 }
1246
1247 let mut cursor = node.walk();
1248 let children: Vec<_> = node.named_children(&mut cursor).collect();
1249 for child in children.into_iter().rev() {
1250 worklist.push(child);
1251 }
1252 }
1253}
1254
1255fn scan_rust_struct_fields(
1257 node: tree_sitter::Node,
1258 struct_name: &str,
1259 source: &[u8],
1260 instance_attr_types: &mut HashMap<(String, String), String>,
1261) {
1262 let mut cursor = node.walk();
1263 for child in node.named_children(&mut cursor) {
1264 if child.kind() == "field_declaration_list" {
1265 let mut inner_cursor = child.walk();
1266 for field in child.named_children(&mut inner_cursor) {
1267 if field.kind() == "field_declaration" {
1268 let field_name = field
1269 .child_by_field_name("name")
1270 .and_then(|n| n.utf8_text(source).ok())
1271 .unwrap_or("");
1272 let field_type = field
1273 .child_by_field_name("type")
1274 .map(|n| extract_base_type(n, source))
1275 .unwrap_or_default();
1276
1277 if !field_name.is_empty()
1278 && !field_type.is_empty()
1279 && field_type.chars().next().map_or(false, |c| c.is_uppercase())
1280 {
1281 instance_attr_types.insert(
1282 (struct_name.to_string(), field_name.to_string()),
1283 field_type,
1284 );
1285 }
1286 }
1287 }
1288 }
1289 }
1290}
1291
1292fn scan_go_struct_fields(
1294 node: tree_sitter::Node,
1295 source: &[u8],
1296 instance_attr_types: &mut HashMap<(String, String), String>,
1297) {
1298 let mut cursor = node.walk();
1299 for child in node.named_children(&mut cursor) {
1300 if child.kind() == "type_spec" {
1301 let struct_name = child
1302 .child_by_field_name("name")
1303 .and_then(|n| n.utf8_text(source).ok())
1304 .unwrap_or("")
1305 .to_string();
1306
1307 if struct_name.is_empty() {
1308 continue;
1309 }
1310
1311 if let Some(type_node) = child.child_by_field_name("type") {
1313 if type_node.kind() == "struct_type" {
1314 let mut fields_cursor = type_node.walk();
1315 for field_list in type_node.named_children(&mut fields_cursor) {
1316 if field_list.kind() == "field_declaration_list" {
1317 let mut inner = field_list.walk();
1318 for field in field_list.named_children(&mut inner) {
1319 if field.kind() == "field_declaration" {
1320 let field_name = field
1322 .child_by_field_name("name")
1323 .and_then(|n| n.utf8_text(source).ok())
1324 .unwrap_or("");
1325 let field_type = field
1326 .child_by_field_name("type")
1327 .map(|n| extract_base_type(n, source))
1328 .unwrap_or_default();
1329
1330 if !field_name.is_empty()
1331 && !field_type.is_empty()
1332 && field_type.chars().next().map_or(false, |c| c.is_uppercase())
1333 {
1334 instance_attr_types.insert(
1335 (struct_name.clone(), field_name.to_string()),
1336 field_type,
1337 );
1338 }
1339 }
1340 }
1341 }
1342 }
1343 }
1344 }
1345 }
1346 }
1347}
1348
1349fn scan_class_for_init(
1350 root: tree_sitter::Node,
1351 class_name: &str,
1352 source: &[u8],
1353 instance_attr_types: &mut HashMap<(String, String), String>,
1354 init_params_map: &mut HashMap<String, Vec<String>>,
1355 attr_to_param_map: &mut HashMap<(String, String), String>,
1356 lang: &str,
1357) {
1358 let mut worklist = vec![root];
1359 while let Some(node) = worklist.pop() {
1360 let mut cursor = node.walk();
1361 for child in node.named_children(&mut cursor) {
1362 let ck = child.kind();
1363
1364 if ck == "function_definition" {
1366 let name = child
1367 .child_by_field_name("name")
1368 .and_then(|n| n.utf8_text(source).ok())
1369 .unwrap_or("");
1370 if name == "__init__" {
1371 let params = extract_init_params(child, source);
1372 let ordered_params = extract_init_param_names_ordered(child, source);
1373 init_params_map.insert(class_name.to_string(), ordered_params);
1374 scan_init_body(child, class_name, ¶ms, source, instance_attr_types, attr_to_param_map);
1375 }
1376 }
1377
1378 if ck == "method_definition" && lang == "typescript" {
1380 let name = child
1381 .child_by_field_name("name")
1382 .and_then(|n| n.utf8_text(source).ok())
1383 .unwrap_or("");
1384 if name == "constructor" {
1385 scan_ts_constructor_body(child, class_name, source, instance_attr_types, init_params_map, attr_to_param_map);
1387 }
1388 }
1389
1390 if (ck == "public_field_definition" || ck == "property_declaration" || ck == "field_definition") && lang == "typescript" {
1392 let field_name = child
1393 .child_by_field_name("name")
1394 .and_then(|n| n.utf8_text(source).ok())
1395 .unwrap_or("");
1396 if let Some(type_ann) = child.child_by_field_name("type") {
1397 let type_text = extract_base_type(type_ann, source);
1398 if !field_name.is_empty()
1399 && !type_text.is_empty()
1400 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1401 {
1402 instance_attr_types.insert(
1403 (class_name.to_string(), field_name.to_string()),
1404 type_text,
1405 );
1406 }
1407 }
1408 }
1409
1410 if ck == "block" || ck == "class_body" || ck == "statement_block" {
1411 worklist.push(child);
1412 }
1413 }
1414 }
1415}
1416
1417fn scan_ts_constructor_body(
1419 node: tree_sitter::Node,
1420 class_name: &str,
1421 source: &[u8],
1422 instance_attr_types: &mut HashMap<(String, String), String>,
1423 init_params_map: &mut HashMap<String, Vec<String>>,
1424 attr_to_param_map: &mut HashMap<(String, String), String>,
1425) {
1426 let params = extract_init_params(node, source);
1428 let ordered_params = extract_init_param_names_ordered(node, source);
1429 init_params_map.insert(class_name.to_string(), ordered_params);
1430
1431 scan_init_body_this(node, class_name, ¶ms, source, instance_attr_types, attr_to_param_map);
1433}
1434
1435fn scan_init_body_this(
1437 root: tree_sitter::Node,
1438 class_name: &str,
1439 params: &HashMap<String, Option<String>>,
1440 source: &[u8],
1441 instance_attr_types: &mut HashMap<(String, String), String>,
1442 attr_to_param_map: &mut HashMap<(String, String), String>,
1443) {
1444 let mut worklist = vec![root];
1445 while let Some(node) = worklist.pop() {
1446 let mut cursor = node.walk();
1447 for child in node.named_children(&mut cursor) {
1448 let ck = child.kind();
1449 if ck == "expression_statement" {
1450 let mut inner_cursor = child.walk();
1452 for inner in child.named_children(&mut inner_cursor) {
1453 if inner.kind() == "assignment_expression" {
1454 if let Some(left) = inner.child_by_field_name("left") {
1455 if left.kind() == "member_expression" {
1456 let obj = left.child_by_field_name("object")
1457 .and_then(|n| n.utf8_text(source).ok())
1458 .unwrap_or("");
1459 let prop = left.child_by_field_name("property")
1460 .and_then(|n| n.utf8_text(source).ok())
1461 .unwrap_or("");
1462 if obj == "this" && !prop.is_empty() {
1463 if let Some(right) = inner.child_by_field_name("right") {
1464 if right.kind() == "identifier" {
1465 let rhs_name = right.utf8_text(source).unwrap_or("");
1466 if params.contains_key(rhs_name) {
1467 attr_to_param_map.insert(
1468 (class_name.to_string(), prop.to_string()),
1469 rhs_name.to_string(),
1470 );
1471 if let Some(Some(type_hint)) = params.get(rhs_name) {
1472 instance_attr_types.insert(
1473 (class_name.to_string(), prop.to_string()),
1474 type_hint.clone(),
1475 );
1476 }
1477 }
1478 }
1479 if right.kind() == "new_expression" {
1480 if let Some(ctor) = right.child_by_field_name("constructor") {
1481 let name = ctor.utf8_text(source).unwrap_or("");
1482 if !name.is_empty() {
1483 instance_attr_types.insert(
1484 (class_name.to_string(), prop.to_string()),
1485 name.to_string(),
1486 );
1487 }
1488 }
1489 }
1490 }
1491 }
1492 }
1493 }
1494 }
1495 }
1496 }
1497 if ck == "statement_block" || ck == "block" {
1498 worklist.push(child);
1499 }
1500 }
1501 }
1502}
1503
1504fn extract_init_param_names_ordered(func_node: tree_sitter::Node, source: &[u8]) -> Vec<String> {
1506 let mut names = Vec::new();
1507 if let Some(params_node) = func_node.child_by_field_name("parameters") {
1508 let mut cursor = params_node.walk();
1509 for child in params_node.named_children(&mut cursor) {
1510 let param_name = if child.kind() == "identifier" {
1511 child.utf8_text(source).unwrap_or("").to_string()
1512 } else if child.kind() == "typed_parameter" || child.kind() == "typed_default_parameter" {
1513 child.child_by_field_name("name")
1514 .or_else(|| child.named_child(0))
1515 .and_then(|n| n.utf8_text(source).ok())
1516 .unwrap_or("")
1517 .to_string()
1518 } else {
1519 continue;
1520 };
1521 if param_name != "self" && param_name != "cls" && !param_name.is_empty() {
1522 names.push(param_name);
1523 }
1524 }
1525 }
1526 names
1527}
1528
1529fn extract_init_params(func_node: tree_sitter::Node, source: &[u8]) -> HashMap<String, Option<String>> {
1530 let mut params = HashMap::new();
1531 if let Some(params_node) = func_node.child_by_field_name("parameters") {
1532 let mut cursor = params_node.walk();
1533 for child in params_node.named_children(&mut cursor) {
1534 let param_name = if child.kind() == "identifier" {
1535 child.utf8_text(source).unwrap_or("").to_string()
1536 } else if child.kind() == "typed_parameter" || child.kind() == "typed_default_parameter" {
1537 child.child_by_field_name("name")
1538 .or_else(|| child.named_child(0))
1539 .and_then(|n| n.utf8_text(source).ok())
1540 .unwrap_or("")
1541 .to_string()
1542 } else {
1543 continue;
1544 };
1545 if param_name != "self" && param_name != "cls" {
1546 let type_hint = child.child_by_field_name("type")
1548 .and_then(|n| n.utf8_text(source).ok())
1549 .map(|s| s.to_string());
1550 params.insert(param_name, type_hint);
1551 }
1552 }
1553 }
1554 params
1555}
1556
1557fn scan_init_body(
1558 root: tree_sitter::Node,
1559 class_name: &str,
1560 params: &HashMap<String, Option<String>>,
1561 source: &[u8],
1562 instance_attr_types: &mut HashMap<(String, String), String>,
1563 attr_to_param_map: &mut HashMap<(String, String), String>,
1564) {
1565 let mut worklist = vec![root];
1566 while let Some(node) = worklist.pop() {
1567 let mut cursor = node.walk();
1568 for child in node.named_children(&mut cursor) {
1569 if child.kind() == "expression_statement" || child.kind() == "assignment" {
1570 let assign = if child.kind() == "assignment" {
1571 child
1572 } else {
1573 let mut inner_cursor = child.walk();
1574 let children: Vec<_> = child.named_children(&mut inner_cursor).collect();
1575 match children.into_iter().find(|c| c.kind() == "assignment") {
1576 Some(a) => a,
1577 None => continue,
1578 }
1579 };
1580
1581 if let Some(left) = assign.child_by_field_name("left") {
1582 if left.kind() == "attribute" {
1583 let obj = left.child_by_field_name("object")
1584 .and_then(|n| n.utf8_text(source).ok())
1585 .unwrap_or("");
1586 let attr = left.child_by_field_name("attribute")
1587 .and_then(|n| n.utf8_text(source).ok())
1588 .unwrap_or("");
1589
1590 if obj == "self" && !attr.is_empty() {
1591 if let Some(right) = assign.child_by_field_name("right") {
1592 if right.kind() == "identifier" {
1593 let rhs_name = right.utf8_text(source).unwrap_or("");
1594 if params.contains_key(rhs_name) {
1596 attr_to_param_map.insert(
1597 (class_name.to_string(), attr.to_string()),
1598 rhs_name.to_string(),
1599 );
1600 }
1601 if let Some(Some(type_hint)) = params.get(rhs_name) {
1603 instance_attr_types.insert(
1604 (class_name.to_string(), attr.to_string()),
1605 type_hint.clone(),
1606 );
1607 }
1608 }
1609 if right.kind() == "call" {
1610 if let Some(func) = right.child_by_field_name("function") {
1611 if func.kind() == "identifier" {
1612 let fname = func.utf8_text(source).unwrap_or("");
1613 if fname.chars().next().map_or(false, |c| c.is_uppercase()) {
1614 instance_attr_types.insert(
1615 (class_name.to_string(), attr.to_string()),
1616 fname.to_string(),
1617 );
1618 }
1619 }
1620 }
1621 }
1622 }
1623 }
1624 }
1625 }
1626 }
1627 if child.kind() == "block" {
1628 worklist.push(child);
1629 }
1630 }
1631 }
1632}
1633
1634fn infer_constructor_param_types(
1639 parsed_files: &[(String, String, tree_sitter::Tree)],
1640 return_type_map: &HashMap<String, String>,
1641 init_params: &HashMap<String, Vec<String>>,
1642 attr_to_param: &HashMap<(String, String), String>,
1643 _symbol_table: &HashMap<String, Vec<String>>,
1644 entity_map: &HashMap<String, EntityInfo>,
1645 instance_attr_types: &mut HashMap<(String, String), String>,
1646) {
1647 let mut func_name_returns: HashMap<String, String> = HashMap::new();
1649 for (eid, ret_type) in return_type_map {
1650 if let Some(info) = entity_map.get(eid) {
1651 func_name_returns.insert(info.name.clone(), ret_type.clone());
1652 }
1653 }
1654
1655 for (_file_path, content, tree) in parsed_files {
1657 let source = content.as_bytes();
1658 scan_constructor_calls(
1659 tree.root_node(),
1660 source,
1661 &func_name_returns,
1662 init_params,
1663 attr_to_param,
1664 instance_attr_types,
1665 );
1666 }
1667}
1668
1669fn scan_constructor_calls(
1670 root: tree_sitter::Node,
1671 source: &[u8],
1672 func_name_returns: &HashMap<String, String>,
1673 init_params: &HashMap<String, Vec<String>>,
1674 attr_to_param: &HashMap<(String, String), String>,
1675 instance_attr_types: &mut HashMap<(String, String), String>,
1676) {
1677 let mut worklist = vec![root];
1678 while let Some(node) = worklist.pop() {
1679 let kind = node.kind();
1680
1681 if kind == "call" {
1682 if let Some(func) = node.child_by_field_name("function") {
1683 if func.kind() == "identifier" {
1684 let class_name = func.utf8_text(source).unwrap_or("");
1685 if class_name.chars().next().map_or(false, |c| c.is_uppercase()) {
1687 if let Some(param_names) = init_params.get(class_name) {
1688 if let Some(args_node) = node.child_by_field_name("arguments") {
1690 let mut arg_idx = 0;
1691 let mut args_cursor = args_node.walk();
1692 for arg in args_node.named_children(&mut args_cursor) {
1693 if arg_idx >= param_names.len() {
1694 break;
1695 }
1696 let param_name = ¶m_names[arg_idx];
1697
1698 let arg_type = infer_expr_type(arg, source, func_name_returns);
1700
1701 if let Some(at) = arg_type {
1702 for ((cn, attr), pn) in attr_to_param.iter() {
1704 if cn == class_name && pn == param_name {
1705 instance_attr_types
1706 .entry((cn.clone(), attr.clone()))
1707 .or_insert(at.clone());
1708 }
1709 }
1710 }
1711
1712 arg_idx += 1;
1713 }
1714 }
1715 }
1716 }
1717 }
1718 }
1719 }
1720
1721 let mut cursor = node.walk();
1722 let children: Vec<_> = node.named_children(&mut cursor).collect();
1723 for child in children.into_iter().rev() {
1724 worklist.push(child);
1725 }
1726 }
1727}
1728
1729fn infer_expr_type(
1731 node: tree_sitter::Node,
1732 source: &[u8],
1733 func_name_returns: &HashMap<String, String>,
1734) -> Option<String> {
1735 match node.kind() {
1736 "call" => {
1737 if let Some(func) = node.child_by_field_name("function") {
1738 if func.kind() == "identifier" {
1739 let name = func.utf8_text(source).unwrap_or("");
1740 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1742 return Some(name.to_string());
1743 }
1744 if let Some(ret) = func_name_returns.get(name) {
1746 return Some(ret.clone());
1747 }
1748 }
1749 }
1750 None
1751 }
1752 "identifier" => {
1753 None
1755 }
1756 _ => None,
1757 }
1758}
1759
1760fn inject_return_type_bindings(
1763 _entity_inner_scope: &HashMap<String, usize>,
1764 scopes: &mut Vec<Scope>,
1765 return_type_map: &HashMap<String, String>,
1766 import_table: &HashMap<(String, String), String>,
1767 file_path: &str,
1768 entity_map: &HashMap<String, EntityInfo>,
1769) {
1770 let mut func_name_return_types: HashMap<String, String> = HashMap::new();
1772 for (eid, ret_type) in return_type_map {
1773 if let Some(info) = entity_map.get(eid) {
1774 func_name_return_types.insert(info.name.clone(), ret_type.clone());
1775 }
1776 }
1777
1778 for ((fp, local_name), target_id) in import_table {
1780 if fp == file_path {
1781 if let Some(ret_type) = return_type_map.get(target_id) {
1782 func_name_return_types.insert(local_name.clone(), ret_type.clone());
1783 }
1784 }
1785 }
1786
1787 for scope in scopes.iter_mut() {
1789 let resolved: Vec<(String, String)> = scope
1790 .pending_call_types
1791 .iter()
1792 .filter_map(|(var_name, func_name)| {
1793 func_name_return_types
1794 .get(func_name)
1795 .map(|ret_type| (var_name.clone(), ret_type.clone()))
1796 })
1797 .collect();
1798
1799 for (var_name, ret_type) in resolved {
1800 scope.types.insert(var_name, ret_type);
1801 }
1802 }
1803}
1804
1805fn extract_imports_from_ast(
1807 root: tree_sitter::Node,
1808 file_path: &str,
1809 source: &[u8],
1810 symbol_table: &HashMap<String, Vec<String>>,
1811 entity_map: &HashMap<String, EntityInfo>,
1812 import_table: &mut HashMap<(String, String), String>,
1813 scopes: &mut Vec<Scope>,
1814 config: &ScopeResolveConfig,
1815) {
1816 let mut worklist = vec![root];
1817 while let Some(node) = worklist.pop() {
1818 let mut cursor = node.walk();
1819 for child in node.named_children(&mut cursor) {
1820 let ck = child.kind();
1821 let handled = match ck {
1822 "import_from_statement" => {
1823 extract_python_import(child, file_path, source, symbol_table, entity_map, import_table, scopes);
1824 true
1825 }
1826 "import_statement" if !config.self_keywords.contains(&"cls") => {
1827 extract_ts_import(child, file_path, source, symbol_table, entity_map, import_table, scopes);
1829 true
1830 }
1831 "use_declaration" => {
1832 extract_rust_use(child, file_path, source, symbol_table, entity_map, import_table, scopes);
1833 true
1834 }
1835 "import_declaration" => {
1836 extract_go_import(child, file_path, source, symbol_table, entity_map, import_table, scopes);
1837 true
1838 }
1839 _ => false,
1840 };
1841 if !handled {
1842 worklist.push(child);
1843 }
1844 }
1845 }
1846}
1847
1848fn extract_ts_import(
1850 node: tree_sitter::Node,
1851 file_path: &str,
1852 source: &[u8],
1853 symbol_table: &HashMap<String, Vec<String>>,
1854 entity_map: &HashMap<String, EntityInfo>,
1855 import_table: &mut HashMap<(String, String), String>,
1856 scopes: &mut Vec<Scope>,
1857) {
1858 let source_path = node
1860 .child_by_field_name("source")
1861 .and_then(|n| n.utf8_text(source).ok())
1862 .unwrap_or("")
1863 .trim_matches(|c: char| c == '\'' || c == '"');
1864
1865 let source_module = source_path
1866 .rsplit('/')
1867 .next()
1868 .unwrap_or(source_path);
1869 let source_module = source_module
1871 .strip_suffix(".ts").or_else(|| source_module.strip_suffix(".js"))
1872 .or_else(|| source_module.strip_suffix(".tsx")).or_else(|| source_module.strip_suffix(".jsx"))
1873 .unwrap_or(source_module);
1874
1875 if source_module.is_empty() {
1876 return;
1877 }
1878
1879 let mut cursor = node.walk();
1881 for child in node.named_children(&mut cursor) {
1882 if child.kind() == "import_clause" {
1883 let mut clause_cursor = child.walk();
1884 for clause_child in child.named_children(&mut clause_cursor) {
1885 if clause_child.kind() == "named_imports" {
1886 let mut imports_cursor = clause_child.walk();
1888 for spec in clause_child.named_children(&mut imports_cursor) {
1889 if spec.kind() == "import_specifier" {
1890 let original = spec
1891 .child_by_field_name("name")
1892 .and_then(|n| n.utf8_text(source).ok())
1893 .unwrap_or("");
1894 let local = spec
1895 .child_by_field_name("alias")
1896 .and_then(|n| n.utf8_text(source).ok())
1897 .unwrap_or(original);
1898
1899 if !original.is_empty() {
1900 resolve_import_name(original, local, source_module, file_path, symbol_table, entity_map, import_table, scopes);
1901 }
1902 }
1903 }
1904 } else if clause_child.kind() == "identifier" {
1905 let name = clause_child.utf8_text(source).unwrap_or("");
1907 if !name.is_empty() {
1908 resolve_import_name(name, name, source_module, file_path, symbol_table, entity_map, import_table, scopes);
1909 }
1910 }
1911 }
1912 }
1913 }
1914}
1915
1916fn extract_rust_use(
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) {
1927 let text = node.utf8_text(source).unwrap_or("").trim().to_string();
1928 let text = text.strip_prefix("use ").unwrap_or(&text);
1930 let text = text.strip_prefix("pub use ").unwrap_or(text);
1931 let text = text.trim_end_matches(';').trim();
1932
1933 let text = text
1935 .strip_prefix("crate::")
1936 .or_else(|| text.strip_prefix("super::"))
1937 .or_else(|| text.strip_prefix("self::"))
1938 .unwrap_or(text);
1939
1940 if let Some(brace_pos) = text.find("::{") {
1942 let module_path = &text[..brace_pos];
1943 let source_module = module_path.rsplit("::").next().unwrap_or(module_path);
1944
1945 let names_part = &text[brace_pos + 3..];
1946 let names_part = names_part.trim_end_matches('}');
1947
1948 for name_part in names_part.split(',') {
1949 let name_part = name_part.trim();
1950 if name_part.is_empty() {
1951 continue;
1952 }
1953 let (original, local) = if let Some(pos) = name_part.find(" as ") {
1954 (name_part[..pos].trim(), name_part[pos + 4..].trim())
1955 } else {
1956 (name_part, name_part)
1957 };
1958 if !original.is_empty() {
1959 resolve_import_name(original, local, source_module, file_path, symbol_table, entity_map, import_table, scopes);
1960 }
1961 }
1962 } else {
1963 let parts: Vec<&str> = text.split("::").collect();
1965 if parts.is_empty() {
1966 return;
1967 }
1968 let imported_name = parts.last().unwrap().trim();
1969 let (original, local) = if let Some(pos) = imported_name.find(" as ") {
1970 (&imported_name[..pos], imported_name[pos + 4..].trim())
1971 } else {
1972 (imported_name, imported_name)
1973 };
1974 let source_module = if parts.len() >= 2 {
1975 parts[parts.len() - 2]
1976 } else {
1977 parts[0]
1978 };
1979 if !original.is_empty() && !source_module.is_empty() {
1980 resolve_import_name(original, local, source_module, file_path, symbol_table, entity_map, import_table, scopes);
1981 }
1982 }
1983}
1984
1985fn extract_go_import(
1987 node: tree_sitter::Node,
1988 file_path: &str,
1989 source: &[u8],
1990 symbol_table: &HashMap<String, Vec<String>>,
1991 entity_map: &HashMap<String, EntityInfo>,
1992 import_table: &mut HashMap<(String, String), String>,
1993 scopes: &mut Vec<Scope>,
1994) {
1995 let mut cursor = node.walk();
1996 for child in node.named_children(&mut cursor) {
1997 if child.kind() == "import_spec" || child.kind() == "import_spec_list" {
1998 extract_go_import_specs(child, file_path, source, symbol_table, entity_map, import_table, scopes);
1999 } else if child.kind() == "interpreted_string_literal" || child.kind() == "raw_string_literal" {
2000 let path = child.utf8_text(source).unwrap_or("")
2001 .trim_matches('"').trim_matches('`');
2002 let pkg_name = path.rsplit('/').next().unwrap_or(path);
2003 register_go_package_imports(pkg_name, file_path, symbol_table, entity_map, import_table, scopes);
2004 }
2005 }
2006}
2007
2008fn extract_go_import_specs(
2009 root: tree_sitter::Node,
2010 file_path: &str,
2011 source: &[u8],
2012 symbol_table: &HashMap<String, Vec<String>>,
2013 entity_map: &HashMap<String, EntityInfo>,
2014 import_table: &mut HashMap<(String, String), String>,
2015 scopes: &mut Vec<Scope>,
2016) {
2017 let mut worklist = vec![root];
2018 while let Some(node) = worklist.pop() {
2019 let mut cursor = node.walk();
2020 for child in node.named_children(&mut cursor) {
2021 if child.kind() == "import_spec" {
2022 let path_node = child.child_by_field_name("path")
2023 .or_else(|| child.named_child(0));
2024 if let Some(pn) = path_node {
2025 let path = pn.utf8_text(source).unwrap_or("")
2026 .trim_matches('"').trim_matches('`');
2027 let pkg_name = path.rsplit('/').next().unwrap_or(path);
2028 register_go_package_imports(pkg_name, file_path, symbol_table, entity_map, import_table, scopes);
2029 }
2030 } else {
2031 worklist.push(child);
2032 }
2033 }
2034 }
2035}
2036
2037fn register_go_package_imports(
2038 pkg_name: &str,
2039 file_path: &str,
2040 symbol_table: &HashMap<String, Vec<String>>,
2041 entity_map: &HashMap<String, EntityInfo>,
2042 import_table: &mut HashMap<(String, String), String>,
2043 scopes: &mut Vec<Scope>,
2044) {
2045 for (name, target_ids) in symbol_table {
2046 for target_id in target_ids {
2047 if let Some(entity) = entity_map.get(target_id) {
2048 let stem = entity.file_path.rsplit('/').next().unwrap_or(&entity.file_path);
2049 let stem = stem.strip_suffix(".go").unwrap_or(stem);
2050 if stem == pkg_name || entity.file_path.contains(&format!("{}/", pkg_name)) {
2051 import_table.insert(
2052 (file_path.to_string(), name.clone()),
2053 target_id.clone(),
2054 );
2055 if !scopes.is_empty() {
2056 scopes[0].defs.insert(name.clone(), target_id.clone());
2057 }
2058 }
2059 }
2060 }
2061 }
2062}
2063
2064fn resolve_import_name(
2066 original_name: &str,
2067 local_name: &str,
2068 source_module: &str,
2069 file_path: &str,
2070 symbol_table: &HashMap<String, Vec<String>>,
2071 entity_map: &HashMap<String, EntityInfo>,
2072 import_table: &mut HashMap<(String, String), String>,
2073 scopes: &mut Vec<Scope>,
2074) {
2075 if let Some(target_ids) = symbol_table.get(original_name) {
2076 let target = target_ids.iter().find(|id| {
2077 entity_map.get(*id).map_or(false, |e| {
2078 let stem = e.file_path.rsplit('/').next().unwrap_or(&e.file_path);
2079 let stem = stem
2080 .strip_suffix(".py")
2081 .or_else(|| stem.strip_suffix(".rs"))
2082 .or_else(|| stem.strip_suffix(".ts"))
2083 .or_else(|| stem.strip_suffix(".tsx"))
2084 .or_else(|| stem.strip_suffix(".js"))
2085 .or_else(|| stem.strip_suffix(".jsx"))
2086 .or_else(|| stem.strip_suffix(".go"))
2087 .unwrap_or(stem);
2088 stem == source_module
2089 })
2090 });
2091
2092 if let Some(target_id) = target {
2093 import_table.insert(
2094 (file_path.to_string(), local_name.to_string()),
2095 target_id.clone(),
2096 );
2097 if !scopes.is_empty() {
2098 scopes[0]
2099 .defs
2100 .insert(local_name.to_string(), target_id.clone());
2101 }
2102 }
2103 }
2104}
2105
2106fn extract_python_import(
2107 node: tree_sitter::Node,
2108 file_path: &str,
2109 source: &[u8],
2110 symbol_table: &HashMap<String, Vec<String>>,
2111 entity_map: &HashMap<String, EntityInfo>,
2112 import_table: &mut HashMap<(String, String), String>,
2113 scopes: &mut Vec<Scope>,
2114) {
2115 let module_node = node.child_by_field_name("module_name");
2119 let module_name = module_node
2120 .and_then(|n| n.utf8_text(source).ok())
2121 .unwrap_or("");
2122
2123 let source_module = module_name
2124 .trim_start_matches('.')
2125 .rsplit('.')
2126 .next()
2127 .unwrap_or(module_name.trim_start_matches('.'));
2128
2129 let mut cursor = node.walk();
2131 for child in node.named_children(&mut cursor) {
2132 if child.kind() == "dotted_name" || child.kind() == "aliased_import" {
2133 let (original, local) = if child.kind() == "aliased_import" {
2134 let orig = child
2135 .child_by_field_name("name")
2136 .and_then(|n| n.utf8_text(source).ok())
2137 .unwrap_or("");
2138 let alias = child
2139 .child_by_field_name("alias")
2140 .and_then(|n| n.utf8_text(source).ok())
2141 .unwrap_or(orig);
2142 (orig, alias)
2143 } else {
2144 let name = child.utf8_text(source).unwrap_or("");
2145 (name, name)
2146 };
2147
2148 if original.is_empty() {
2149 continue;
2150 }
2151
2152 if let Some(target_ids) = symbol_table.get(original) {
2154 let target = target_ids.iter().find(|id| {
2155 entity_map.get(*id).map_or(false, |e| {
2156 let stem = e.file_path.rsplit('/').next().unwrap_or(&e.file_path);
2157 let stem = stem
2158 .strip_suffix(".py")
2159 .or_else(|| stem.strip_suffix(".rs"))
2160 .or_else(|| stem.strip_suffix(".ts"))
2161 .or_else(|| stem.strip_suffix(".js"))
2162 .unwrap_or(stem);
2163 stem == source_module
2164 })
2165 });
2166
2167 if let Some(target_id) = target {
2168 import_table.insert(
2169 (file_path.to_string(), local.to_string()),
2170 target_id.clone(),
2171 );
2172 if !scopes.is_empty() {
2174 scopes[0]
2175 .defs
2176 .insert(local.to_string(), target_id.clone());
2177 }
2178 }
2179 }
2180 }
2181 }
2182}
2183
2184fn extract_ast_refs(
2186 root: tree_sitter::Node,
2187 entity: &SemanticEntity,
2188 source: &[u8],
2189 config: &ScopeResolveConfig,
2190) -> Vec<AstRef> {
2191 let mut refs = Vec::new();
2192 let start_row = entity.start_line.saturating_sub(1); let end_row = entity.end_line; collect_refs_in_range(root, start_row, end_row, &entity.id, &entity.name, source, &mut refs, config);
2196 refs
2197}
2198
2199fn collect_refs_in_range(
2200 root: tree_sitter::Node,
2201 start_row: usize,
2202 end_row: usize,
2203 entity_id: &str,
2204 entity_name: &str,
2205 source: &[u8],
2206 refs: &mut Vec<AstRef>,
2207 config: &ScopeResolveConfig,
2208) {
2209 let mut worklist = vec![root];
2210 while let Some(node) = worklist.pop() {
2211 let node_start = node.start_position().row;
2212 let node_end = node.end_position().row;
2213
2214 if node_end < start_row || node_start >= end_row {
2215 continue;
2216 }
2217
2218 let kind = node.kind();
2219
2220 if config.call_nodes.contains(&kind) {
2222 match &config.call_style {
2223 CallNodeStyle::FunctionField(field) => {
2224 if let Some(func) = node.child_by_field_name(field) {
2225 extract_call_ref(func, entity_id, entity_name, source, refs, config);
2226 }
2227 }
2228 CallNodeStyle::DirectMethod { object_field, method_field } => {
2229 let method_name = node.child_by_field_name(method_field)
2230 .and_then(|n| n.utf8_text(source).ok())
2231 .unwrap_or("");
2232 if !method_name.is_empty() && method_name != entity_name && !is_builtin(method_name, config) {
2233 if let Some(obj_node) = node.child_by_field_name(object_field) {
2234 let receiver = obj_node.utf8_text(source).unwrap_or("").to_string();
2235 let receiver = receiver.trim_end_matches('.').to_string();
2237 refs.push(AstRef {
2238 kind: AstRefKind::MethodCall { receiver, method: method_name.to_string() },
2239 });
2240 } else {
2241 refs.push(AstRef {
2243 kind: AstRefKind::Call(method_name.to_string()),
2244 });
2245 }
2246 }
2247 }
2248 }
2249 let mut cursor = node.walk();
2252 let children: Vec<_> = node.named_children(&mut cursor).collect();
2253 for child in children.into_iter().rev() {
2254 worklist.push(child);
2255 }
2256 continue;
2257 }
2258
2259 if config.new_expr_nodes.contains(&kind) {
2261 if let Some(type_node) = node.child_by_field_name(config.new_expr_type_field) {
2262 let name = type_node.utf8_text(source).unwrap_or("");
2263 let name = name.rsplit('.').next().unwrap_or(name);
2265 if !name.is_empty() && name != entity_name && !is_builtin(name, config) {
2266 refs.push(AstRef {
2267 kind: AstRefKind::Call(name.to_string()),
2268 });
2269 }
2270 }
2271 let mut cursor = node.walk();
2272 let children: Vec<_> = node.named_children(&mut cursor).collect();
2273 for child in children.into_iter().rev() {
2274 worklist.push(child);
2275 }
2276 continue;
2277 }
2278
2279 if config.composite_literal_nodes.contains(&kind) {
2281 if let Some(type_node) = node.child_by_field_name("type") {
2282 let name = type_node.utf8_text(source).unwrap_or("");
2283 if name.chars().next().map_or(false, |c| c.is_uppercase())
2284 && name != entity_name
2285 && !is_builtin(name, config)
2286 {
2287 refs.push(AstRef {
2288 kind: AstRefKind::Call(name.to_string()),
2289 });
2290 }
2291 }
2292 }
2293
2294 let mut cursor = node.walk();
2296 let children: Vec<_> = node.named_children(&mut cursor).collect();
2297 for child in children.into_iter().rev() {
2298 worklist.push(child);
2299 }
2300 }
2301}
2302
2303fn extract_call_ref(
2305 func: tree_sitter::Node,
2306 _entity_id: &str,
2307 entity_name: &str,
2308 source: &[u8],
2309 refs: &mut Vec<AstRef>,
2310 config: &ScopeResolveConfig,
2311) {
2312 let func_kind = func.kind();
2313
2314 if func_kind == "identifier" {
2315 let name = func.utf8_text(source).unwrap_or("");
2316 if !name.is_empty() && name != entity_name && !is_builtin(name, config) {
2317 refs.push(AstRef {
2318 kind: AstRefKind::Call(name.to_string()),
2319 });
2320 }
2321 return;
2322 }
2323
2324 for ma in config.member_access {
2326 if func_kind == ma.node_kind {
2327 extract_member_call_ref(func, ma.object_field, ma.property_field, source, refs);
2328 return;
2329 }
2330 }
2331
2332 if config.scoped_call_nodes.contains(&func_kind) {
2334 let text = func.utf8_text(source).unwrap_or("");
2335 let parts: Vec<&str> = text.split("::").collect();
2336 if parts.len() >= 2 {
2337 let type_name = parts[parts.len() - 2];
2338 let method_name = parts[parts.len() - 1];
2339 if !type_name.is_empty() && !method_name.is_empty() {
2340 refs.push(AstRef {
2341 kind: AstRefKind::Call(method_name.to_string()),
2342 });
2343 if type_name.chars().next().map_or(false, |c| c.is_uppercase())
2344 && !is_builtin(type_name, config)
2345 {
2346 refs.push(AstRef {
2347 kind: AstRefKind::Call(type_name.to_string()),
2348 });
2349 }
2350 }
2351 }
2352 }
2353}
2354
2355fn extract_member_call_ref(
2357 node: tree_sitter::Node,
2358 object_field: &str,
2359 attr_field: &str,
2360 source: &[u8],
2361 refs: &mut Vec<AstRef>,
2362) {
2363 let obj = node
2364 .child_by_field_name(object_field)
2365 .and_then(|n| n.utf8_text(source).ok())
2366 .unwrap_or("");
2367 let attr = node
2368 .child_by_field_name(attr_field)
2369 .and_then(|n| n.utf8_text(source).ok())
2370 .unwrap_or("");
2371 if !obj.is_empty() && !attr.is_empty() {
2372 push_method_call_ref(obj, attr, refs);
2373 }
2374}
2375
2376fn push_method_call_ref(obj: &str, method: &str, refs: &mut Vec<AstRef>) {
2377 refs.push(AstRef {
2378 kind: AstRefKind::MethodCall {
2379 receiver: obj.to_string(),
2380 method: method.to_string(),
2381 },
2382 });
2383}
2384
2385fn resolve_ref(
2387 ast_ref: &AstRef,
2388 scope_idx: usize,
2389 scopes: &[Scope],
2390 symbol_table: &HashMap<String, Vec<String>>,
2391 class_members: &HashMap<String, Vec<(String, String)>>,
2392 import_table: &HashMap<(String, String), String>,
2393 instance_attr_types: &HashMap<(String, String), String>,
2394 entity_map: &HashMap<String, EntityInfo>,
2395 file_path: &str,
2396 from_entity_id: &str,
2397) -> Option<(String, RefType, &'static str)> {
2398 match &ast_ref.kind {
2399 AstRefKind::Call(name) => {
2400 if let Some(eid) = lookup_scope_chain(scope_idx, scopes, name) {
2402 if eid != from_entity_id {
2403 return Some((eid, RefType::Calls, "scope_chain"));
2404 }
2405 }
2406
2407 let key = (file_path.to_string(), name.clone());
2409 if let Some(target_id) = import_table.get(&key) {
2410 return Some((target_id.clone(), RefType::Calls, "import"));
2411 }
2412
2413 if let Some(target_ids) = symbol_table.get(name.as_str()) {
2415 let is_constructor = name.chars().next().map_or(false, |c| c.is_uppercase());
2416 let ref_type = if is_constructor { RefType::TypeRef } else { RefType::Calls };
2417 let target = target_ids
2419 .iter()
2420 .find(|id| {
2421 entity_map
2422 .get(*id)
2423 .map_or(false, |e| e.file_path == file_path)
2424 })
2425 .or_else(|| {
2426 if is_constructor || target_ids.len() == 1 {
2429 target_ids.first()
2430 } else {
2431 None
2432 }
2433 });
2434 if let Some(tid) = target {
2435 return Some((tid.clone(), ref_type, "scope_chain"));
2436 }
2437 }
2438
2439 None
2440 }
2441
2442 AstRefKind::MethodCall { receiver, method } => {
2443 if receiver == "self" || receiver == "this" {
2444 let mut idx = scope_idx;
2446 loop {
2447 if scopes[idx].kind == "class" {
2448 if let Some(eid) = scopes[idx].defs.get(method.as_str()) {
2449 return Some((eid.clone(), RefType::Calls, "scope_chain"));
2450 }
2451 break;
2452 }
2453 match scopes[idx].parent {
2454 Some(p) => idx = p,
2455 None => break,
2456 }
2457 }
2458 return None;
2459 }
2460
2461 if receiver.starts_with("self.") || receiver.starts_with("this.") {
2464 let attr_name = &receiver[5..]; let class_name = find_enclosing_class(scope_idx, scopes, entity_map);
2467 if let Some(cn) = class_name {
2468 if let Some(attr_type) = instance_attr_types.get(&(cn, attr_name.to_string())) {
2470 if let Some(members) = class_members.get(attr_type.as_str()) {
2471 if let Some((_, mid)) = members.iter().find(|(n, _)| n == method) {
2472 return Some((mid.clone(), RefType::Calls, "type_tracking"));
2473 }
2474 }
2475 }
2476 }
2477 }
2478
2479 if receiver.contains('.') && !receiver.starts_with("self.") && !receiver.starts_with("this.") {
2481 if let Some(dot_pos) = receiver.find('.') {
2482 let var_part = &receiver[..dot_pos];
2483 let field_part = &receiver[dot_pos + 1..];
2484 if let Some(var_type) = lookup_type_in_scopes(scope_idx, scopes, var_part) {
2485 if let Some(attr_type) = instance_attr_types.get(&(var_type, field_part.to_string())) {
2486 if let Some(members) = class_members.get(attr_type.as_str()) {
2487 if let Some((_, mid)) = members.iter().find(|(n, _)| n == method) {
2488 return Some((mid.clone(), RefType::Calls, "type_tracking"));
2489 }
2490 }
2491 }
2492 }
2493 }
2494 }
2495
2496 let receiver_type = lookup_type_in_scopes(scope_idx, scopes, receiver);
2498
2499 if let Some(class_name) = receiver_type {
2500 if let Some(members) = class_members.get(class_name.as_str()) {
2501 if let Some((_, mid)) = members.iter().find(|(n, _)| n == method) {
2502 return Some((mid.clone(), RefType::Calls, "type_tracking"));
2503 }
2504 }
2505 }
2506
2507 let key = (file_path.to_string(), receiver.clone());
2509 if let Some(target_id) = import_table.get(&key) {
2510 if let Some(info) = entity_map.get(target_id) {
2511 if matches!(info.entity_type.as_str(), "class" | "struct") {
2512 if let Some(members) = class_members.get(&info.name) {
2513 if let Some((_, mid)) =
2514 members.iter().find(|(n, _)| n == method)
2515 {
2516 return Some((
2517 mid.clone(),
2518 RefType::Calls,
2519 "type_tracking",
2520 ));
2521 }
2522 }
2523 }
2524 }
2525 }
2526
2527 let key = (file_path.to_string(), method.clone());
2530 if let Some(target_id) = import_table.get(&key) {
2531 return Some((target_id.clone(), RefType::Calls, "import"));
2532 }
2533
2534 None
2535 }
2536 }
2537}
2538
2539fn find_enclosing_class(
2541 start_scope: usize,
2542 scopes: &[Scope],
2543 entity_map: &HashMap<String, EntityInfo>,
2544) -> Option<String> {
2545 let mut idx = start_scope;
2546 loop {
2547 if scopes[idx].kind == "class" {
2548 if let Some(ref oid) = scopes[idx].owner_id {
2549 return entity_map.get(oid).map(|e| e.name.clone());
2550 }
2551 }
2552 match scopes[idx].parent {
2553 Some(p) => idx = p,
2554 None => return None,
2555 }
2556 }
2557}
2558
2559fn lookup_scope_chain(
2561 start_scope: usize,
2562 scopes: &[Scope],
2563 name: &str,
2564) -> Option<String> {
2565 let mut idx = start_scope;
2566 loop {
2567 if let Some(eid) = scopes[idx].defs.get(name) {
2568 return Some(eid.clone());
2569 }
2570 match scopes[idx].parent {
2571 Some(p) => idx = p,
2572 None => return None,
2573 }
2574 }
2575}
2576
2577fn lookup_type_in_scopes(
2579 start_scope: usize,
2580 scopes: &[Scope],
2581 var_name: &str,
2582) -> Option<String> {
2583 let mut idx = start_scope;
2584 loop {
2585 if let Some(type_name) = scopes[idx].types.get(var_name) {
2586 return Some(type_name.clone());
2587 }
2588 match scopes[idx].parent {
2589 Some(p) => idx = p,
2590 None => return None,
2591 }
2592 }
2593}
2594
2595fn is_builtin(name: &str, config: &ScopeResolveConfig) -> bool {
2596 if matches!(name, "None" | "True" | "False" | "null" | "undefined" | "nil") {
2598 return true;
2599 }
2600 config.builtins.contains(&name)
2601}