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