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