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