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 scan_function_params(node, func_scope_idx, scopes, source, lang);
584
585 if kind == "method_declaration" && lang == "go" {
588 if let Some(receiver) = node.child_by_field_name("receiver") {
589 let mut rcursor = receiver.walk();
591 for param in receiver.named_children(&mut rcursor) {
592 if param.kind() == "parameter_declaration" {
593 let param_name = param
594 .child_by_field_name("name")
595 .and_then(|n| n.utf8_text(source).ok())
596 .unwrap_or("");
597 let param_type = param
598 .child_by_field_name("type")
599 .map(|n| extract_base_type(n, source))
600 .unwrap_or_default();
601 if !param_name.is_empty() && !param_type.is_empty() {
602 scopes[func_scope_idx]
603 .types
604 .insert(param_name.to_string(), param_type);
605 }
606 }
607 }
608 }
609 }
610
611 let mut cursor = node.walk();
612 for child in node.named_children(&mut cursor) {
613 build_scopes_from_ast(
614 child,
615 func_scope_idx,
616 scopes,
617 entity_scope_map,
618 entity_inner_scope,
619 all_entities,
620 entity_map,
621 file_path,
622 source,
623 lang,
624 );
625 }
626 return;
627 }
628
629 let mut cursor = node.walk();
630 for child in node.named_children(&mut cursor) {
631 build_scopes_from_ast(
632 child,
633 current_scope,
634 scopes,
635 entity_scope_map,
636 entity_inner_scope,
637 all_entities,
638 entity_map,
639 file_path,
640 source,
641 lang,
642 );
643 }
644}
645
646fn scan_assignments(
648 node: tree_sitter::Node,
649 scope_idx: usize,
650 scopes: &mut Vec<Scope>,
651 source: &[u8],
652 lang: &str,
653) {
654 let mut cursor = node.walk();
655 for child in node.named_children(&mut cursor) {
656 let ck = child.kind();
657 match lang {
658 "python" => {
659 if ck == "assignment" || ck == "expression_statement" {
660 scan_single_assignment(child, scope_idx, scopes, source, lang);
661 }
662 if ck == "block" {
663 scan_assignments(child, scope_idx, scopes, source, lang);
664 }
665 }
666 "typescript" => {
667 if ck == "lexical_declaration" || ck == "variable_declaration" {
669 scan_ts_var_declaration(child, scope_idx, scopes, source);
670 }
671 if ck == "expression_statement" {
673 scan_single_assignment(child, scope_idx, scopes, source, lang);
674 }
675 if ck == "statement_block" {
676 scan_assignments(child, scope_idx, scopes, source, lang);
677 }
678 }
679 "rust" => {
680 if ck == "let_declaration" {
681 scan_rust_let_declaration(child, scope_idx, scopes, source);
682 }
683 if ck == "block" || ck == "expression_statement" {
684 scan_assignments(child, scope_idx, scopes, source, lang);
685 }
686 }
687 "go" => {
688 if ck == "short_var_declaration" {
689 scan_go_short_var(child, scope_idx, scopes, source);
690 }
691 if ck == "var_declaration" {
692 scan_go_var_declaration(child, scope_idx, scopes, source);
693 }
694 if ck == "block" {
695 scan_assignments(child, scope_idx, scopes, source, lang);
696 }
697 }
698 _ => {}
699 }
700 }
701}
702
703fn scan_function_params(
706 node: tree_sitter::Node,
707 scope_idx: usize,
708 scopes: &mut Vec<Scope>,
709 source: &[u8],
710 lang: &str,
711) {
712 let params_node = match node.child_by_field_name("parameters") {
713 Some(p) => p,
714 None => return,
715 };
716
717 let mut cursor = params_node.walk();
718 for child in params_node.named_children(&mut cursor) {
719 match lang {
720 "python" => {
721 if child.kind() == "typed_parameter" || child.kind() == "typed_default_parameter" {
723 let param_name = child
724 .child_by_field_name("name")
725 .or_else(|| child.named_child(0).filter(|n| n.kind() == "identifier"))
726 .and_then(|n| n.utf8_text(source).ok())
727 .unwrap_or("");
728 if param_name == "self" || param_name == "cls" || param_name.is_empty() {
729 continue;
730 }
731 if let Some(type_node) = child.child_by_field_name("type") {
732 let type_text = extract_base_type(type_node, source);
733 if !type_text.is_empty()
734 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
735 {
736 scopes[scope_idx]
737 .types
738 .insert(param_name.to_string(), type_text);
739 }
740 }
741 }
742 }
743 "typescript" => {
744 if child.kind() == "required_parameter" || child.kind() == "optional_parameter" {
746 let param_name = child
747 .child_by_field_name("pattern")
748 .or_else(|| child.named_child(0).filter(|n| n.kind() == "identifier"))
749 .and_then(|n| n.utf8_text(source).ok())
750 .unwrap_or("");
751 if param_name == "this" || param_name.is_empty() {
752 continue;
753 }
754 if let Some(type_node) = child.child_by_field_name("type") {
755 let type_text = extract_base_type(type_node, source);
756 if !type_text.is_empty()
757 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
758 {
759 scopes[scope_idx]
760 .types
761 .insert(param_name.to_string(), type_text);
762 }
763 }
764 }
765 }
766 "rust" => {
767 if child.kind() == "parameter" {
769 let param_name = child
770 .child_by_field_name("pattern")
771 .and_then(|n| {
772 if n.kind() == "identifier" {
773 n.utf8_text(source).ok()
774 } else if n.kind() == "mut_pattern" {
775 n.named_child(0).and_then(|c| c.utf8_text(source).ok())
776 } else if n.kind() == "reference_pattern" {
777 n.named_child(0).and_then(|c| {
779 if c.kind() == "identifier" {
780 c.utf8_text(source).ok()
781 } else if c.kind() == "mut_pattern" {
782 c.named_child(0).and_then(|cc| cc.utf8_text(source).ok())
783 } else {
784 None
785 }
786 })
787 } else {
788 None
789 }
790 })
791 .unwrap_or("");
792 if param_name == "self" || param_name.is_empty() {
793 continue;
794 }
795 if let Some(type_node) = child.child_by_field_name("type") {
796 let type_text = extract_base_type(type_node, source);
797 if !type_text.is_empty()
798 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
799 {
800 scopes[scope_idx]
801 .types
802 .insert(param_name.to_string(), type_text);
803 }
804 }
805 }
806 }
807 "go" => {
808 if child.kind() == "parameter_declaration" {
811 let param_name = child
812 .child_by_field_name("name")
813 .and_then(|n| n.utf8_text(source).ok())
814 .unwrap_or("");
815 if param_name.is_empty() {
816 continue;
817 }
818 let param_type = child
819 .child_by_field_name("type")
820 .map(|n| extract_base_type(n, source))
821 .unwrap_or_default();
822 if !param_type.is_empty()
823 && param_type.chars().next().map_or(false, |c| c.is_uppercase())
824 {
825 scopes[scope_idx]
826 .types
827 .insert(param_name.to_string(), param_type);
828 }
829 }
830 }
831 _ => {}
832 }
833 }
834}
835
836fn scan_single_assignment(
838 node: tree_sitter::Node,
839 scope_idx: usize,
840 scopes: &mut Vec<Scope>,
841 source: &[u8],
842 _lang: &str,
843) {
844 let assign = if node.kind() == "assignment" {
845 node
846 } else {
847 let mut cursor = node.walk();
848 let children: Vec<_> = node.named_children(&mut cursor).collect();
849 match children.into_iter().find(|c| c.kind() == "assignment" || c.kind() == "assignment_expression") {
850 Some(a) => a,
851 None => return,
852 }
853 };
854
855 let left = match assign.child_by_field_name("left") {
856 Some(l) => l,
857 None => return,
858 };
859 let right = match assign.child_by_field_name("right") {
860 Some(r) => r,
861 None => return,
862 };
863
864 if left.kind() != "identifier" {
865 return;
866 }
867 let var_name = match left.utf8_text(source) {
868 Ok(n) => n.to_string(),
869 Err(_) => return,
870 };
871
872 record_type_from_rhs(right, &var_name, scope_idx, scopes, source);
873}
874
875fn scan_ts_var_declaration(
877 node: tree_sitter::Node,
878 scope_idx: usize,
879 scopes: &mut Vec<Scope>,
880 source: &[u8],
881) {
882 let mut cursor = node.walk();
883 for child in node.named_children(&mut cursor) {
884 if child.kind() == "variable_declarator" {
885 let var_name = child
886 .child_by_field_name("name")
887 .and_then(|n| n.utf8_text(source).ok())
888 .unwrap_or("")
889 .to_string();
890 if var_name.is_empty() {
891 continue;
892 }
893
894 if let Some(type_ann) = child.child_by_field_name("type") {
896 let type_text = extract_base_type(type_ann, source);
897 if !type_text.is_empty()
898 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
899 {
900 scopes[scope_idx]
901 .types
902 .insert(var_name.clone(), type_text);
903 continue;
904 }
905 }
906
907 if let Some(value) = child.child_by_field_name("value") {
909 record_type_from_rhs(value, &var_name, scope_idx, scopes, source);
910 }
911 }
912 }
913}
914
915fn scan_rust_let_declaration(
917 node: tree_sitter::Node,
918 scope_idx: usize,
919 scopes: &mut Vec<Scope>,
920 source: &[u8],
921) {
922 let var_name = node
923 .child_by_field_name("pattern")
924 .and_then(|n| {
925 if n.kind() == "identifier" {
927 n.utf8_text(source).ok()
928 } else if n.kind() == "mut_pattern" {
929 n.named_child(0).and_then(|c| c.utf8_text(source).ok())
930 } else {
931 None
932 }
933 })
934 .unwrap_or("")
935 .to_string();
936
937 if var_name.is_empty() {
938 return;
939 }
940
941 if let Some(type_node) = node.child_by_field_name("type") {
943 let type_text = extract_base_type(type_node, source);
944 if !type_text.is_empty()
945 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
946 {
947 scopes[scope_idx]
948 .types
949 .insert(var_name, type_text);
950 return;
951 }
952 }
953
954 if let Some(value) = node.child_by_field_name("value") {
956 record_type_from_rhs(value, &var_name, scope_idx, scopes, source);
957 }
958}
959
960fn scan_go_short_var(
962 node: tree_sitter::Node,
963 scope_idx: usize,
964 scopes: &mut Vec<Scope>,
965 source: &[u8],
966) {
967 let left = match node.child_by_field_name("left") {
968 Some(l) => l,
969 None => return,
970 };
971 let right = match node.child_by_field_name("right") {
972 Some(r) => r,
973 None => return,
974 };
975
976 let var_name = if left.kind() == "expression_list" {
978 left.named_child(0)
979 .and_then(|n| n.utf8_text(source).ok())
980 .unwrap_or("")
981 .to_string()
982 } else {
983 left.utf8_text(source).unwrap_or("").to_string()
984 };
985
986 if var_name.is_empty() {
987 return;
988 }
989
990 let rhs = if right.kind() == "expression_list" {
991 match right.named_child(0) {
992 Some(n) => n,
993 None => return,
994 }
995 } else {
996 right
997 };
998
999 record_type_from_rhs(rhs, &var_name, scope_idx, scopes, source);
1000}
1001
1002fn scan_go_var_declaration(
1004 node: tree_sitter::Node,
1005 scope_idx: usize,
1006 scopes: &mut Vec<Scope>,
1007 source: &[u8],
1008) {
1009 let mut cursor = node.walk();
1010 for child in node.named_children(&mut cursor) {
1011 if child.kind() == "var_spec" {
1012 let var_name = child
1013 .child_by_field_name("name")
1014 .and_then(|n| n.utf8_text(source).ok())
1015 .unwrap_or("")
1016 .to_string();
1017 if var_name.is_empty() {
1018 if let Some(first) = child.named_child(0) {
1020 if first.kind() == "identifier" {
1021 let name = first.utf8_text(source).unwrap_or("").to_string();
1022 if !name.is_empty() {
1023 if let Some(type_node) = child.child_by_field_name("type") {
1025 let type_text = extract_base_type(type_node, source);
1026 if !type_text.is_empty()
1027 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1028 {
1029 scopes[scope_idx].types.insert(name, type_text);
1030 }
1031 }
1032 }
1033 }
1034 }
1035 continue;
1036 }
1037
1038 if let Some(type_node) = child.child_by_field_name("type") {
1040 let type_text = extract_base_type(type_node, source);
1041 if !type_text.is_empty()
1042 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1043 {
1044 scopes[scope_idx]
1045 .types
1046 .insert(var_name, type_text);
1047 continue;
1048 }
1049 }
1050
1051 if let Some(value) = child.child_by_field_name("value") {
1053 let rhs = if value.kind() == "expression_list" {
1054 value.named_child(0).unwrap_or(value)
1055 } else {
1056 value
1057 };
1058 record_type_from_rhs(rhs, &var_name, scope_idx, scopes, source);
1059 }
1060 }
1061 }
1062}
1063
1064fn record_type_from_rhs(
1067 rhs: tree_sitter::Node,
1068 var_name: &str,
1069 scope_idx: usize,
1070 scopes: &mut Vec<Scope>,
1071 source: &[u8],
1072) {
1073 match rhs.kind() {
1074 "call" | "call_expression" => {
1076 let func_node = rhs
1077 .child_by_field_name("function")
1078 .or_else(|| rhs.named_child(0));
1079 if let Some(func) = func_node {
1080 if func.kind() == "identifier" {
1081 let name = func.utf8_text(source).unwrap_or("");
1082 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1083 scopes[scope_idx]
1084 .types
1085 .insert(var_name.to_string(), name.to_string());
1086 } else {
1087 scopes[scope_idx]
1088 .pending_call_types
1089 .insert(var_name.to_string(), name.to_string());
1090 }
1091 }
1092 if func.kind() == "scoped_identifier" {
1094 let text = func.utf8_text(source).unwrap_or("");
1095 let parts: Vec<&str> = text.split("::").collect();
1096 if parts.len() >= 2 {
1097 let type_name = parts[0];
1098 let method_name = parts[parts.len() - 1];
1099 if type_name.chars().next().map_or(false, |c| c.is_uppercase()) {
1100 scopes[scope_idx]
1101 .types
1102 .insert(var_name.to_string(), type_name.to_string());
1103 } else {
1104 scopes[scope_idx]
1105 .pending_call_types
1106 .insert(var_name.to_string(), method_name.to_string());
1107 }
1108 }
1109 }
1110 if func.kind() == "selector_expression" {
1112 let field = func
1113 .child_by_field_name("field")
1114 .and_then(|n| n.utf8_text(source).ok())
1115 .unwrap_or("");
1116 if let Some(type_name) = field.strip_prefix("New") {
1118 if !type_name.is_empty()
1119 && type_name.chars().next().map_or(false, |c| c.is_uppercase())
1120 {
1121 scopes[scope_idx]
1122 .types
1123 .insert(var_name.to_string(), type_name.to_string());
1124 }
1125 } else if field.starts_with("Get") || field.chars().next().map_or(false, |c| c.is_uppercase()) {
1126 scopes[scope_idx]
1128 .pending_call_types
1129 .insert(var_name.to_string(), field.to_string());
1130 }
1131 }
1132 }
1133 }
1134 "new_expression" => {
1136 if let Some(constructor) = rhs.child_by_field_name("constructor") {
1137 let name = constructor.utf8_text(source).unwrap_or("");
1138 if !name.is_empty() {
1139 scopes[scope_idx]
1140 .types
1141 .insert(var_name.to_string(), name.to_string());
1142 }
1143 }
1144 }
1145 "composite_literal" => {
1147 if let Some(type_node) = rhs.child_by_field_name("type") {
1148 let name = type_node.utf8_text(source).unwrap_or("");
1149 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1150 scopes[scope_idx]
1151 .types
1152 .insert(var_name.to_string(), name.to_string());
1153 }
1154 }
1155 }
1156 _ => {}
1157 }
1158}
1159
1160fn extract_base_type(type_node: tree_sitter::Node, source: &[u8]) -> String {
1163 let text = type_node.utf8_text(source).unwrap_or("").trim().to_string();
1164 let text = text.trim_start_matches('&').trim_start_matches('*');
1166 let text = text.strip_prefix("mut ").unwrap_or(text).trim_start();
1167 let text = if let Some(i) = text.find('<') {
1169 &text[..i]
1170 } else if let Some(i) = text.find('[') {
1171 &text[..i]
1172 } else {
1173 text
1174 };
1175 let text = text.trim();
1177 let text = text.trim_start_matches(':').trim();
1179 text.to_string()
1180}
1181
1182fn extract_go_receiver_type(content: &str) -> Option<String> {
1184 let after_func = content.strip_prefix("func")?.trim_start();
1185 let paren_start = after_func.find('(')?;
1186 let paren_end = after_func.find(')')?;
1187 let receiver_block = &after_func[paren_start + 1..paren_end];
1188 let parts: Vec<&str> = receiver_block.split_whitespace().collect();
1190 let type_str = parts.last()?;
1191 let name = type_str.trim_start_matches('*');
1192 if name.is_empty() {
1193 None
1194 } else {
1195 Some(name.to_string())
1196 }
1197}
1198
1199fn scan_return_types(
1201 node: tree_sitter::Node,
1202 file_path: &str,
1203 all_entities: &[SemanticEntity],
1204 source: &[u8],
1205 return_type_map: &mut HashMap<String, String>,
1206 lang: &str,
1207) {
1208 let kind = node.kind();
1209
1210 let is_func = matches!(
1211 kind,
1212 "function_definition"
1213 | "function_item"
1214 | "function_declaration"
1215 | "method_definition"
1216 | "method_declaration"
1217 );
1218
1219 if is_func {
1220 let func_name = node
1221 .child_by_field_name("name")
1222 .and_then(|n| n.utf8_text(source).ok())
1223 .unwrap_or("");
1224
1225 let func_entity = all_entities.iter().find(|e| {
1226 e.file_path == file_path && e.name == func_name && {
1227 let line = node.start_position().row + 1;
1228 e.start_line <= line && line <= e.end_line
1229 }
1230 });
1231
1232 if let Some(fe) = func_entity {
1233 let ret_type = match lang {
1235 "typescript" => {
1236 node.child_by_field_name("return_type")
1238 .map(|n| extract_base_type(n, source))
1239 .filter(|t| !t.is_empty() && t.chars().next().map_or(false, |c| c.is_uppercase()))
1240 }
1241 "rust" => {
1242 node.child_by_field_name("return_type")
1244 .map(|n| extract_base_type(n, source))
1245 .filter(|t| !t.is_empty() && t.chars().next().map_or(false, |c| c.is_uppercase()))
1246 }
1247 "go" => {
1248 node.child_by_field_name("result")
1250 .map(|n| extract_base_type(n, source))
1251 .filter(|t| !t.is_empty() && t.chars().next().map_or(false, |c| c.is_uppercase()))
1252 }
1253 _ => None,
1254 };
1255
1256 if let Some(rt) = ret_type {
1257 return_type_map.insert(fe.id.clone(), rt);
1258 } else {
1259 if let Some(ret_type) = find_return_constructor(node, source) {
1261 return_type_map.insert(fe.id.clone(), ret_type);
1262 }
1263 }
1264 }
1265 }
1266
1267 let mut cursor = node.walk();
1268 for child in node.named_children(&mut cursor) {
1269 scan_return_types(child, file_path, all_entities, source, return_type_map, lang);
1270 }
1271}
1272
1273fn find_return_constructor(node: tree_sitter::Node, source: &[u8]) -> Option<String> {
1275 let mut cursor = node.walk();
1276 for child in node.named_children(&mut cursor) {
1277 if child.kind() == "return_statement" {
1278 let mut inner_cursor = child.walk();
1279 for ret_child in child.named_children(&mut inner_cursor) {
1280 if ret_child.kind() == "call" || ret_child.kind() == "call_expression" {
1282 if let Some(func) = ret_child.child_by_field_name("function") {
1283 if func.kind() == "identifier" {
1284 let name = func.utf8_text(source).unwrap_or("");
1285 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1286 return Some(name.to_string());
1287 }
1288 }
1289 }
1290 }
1291 if ret_child.kind() == "new_expression" {
1293 if let Some(constructor) = ret_child.child_by_field_name("constructor") {
1294 let name = constructor.utf8_text(source).unwrap_or("");
1295 if !name.is_empty() {
1296 return Some(name.to_string());
1297 }
1298 }
1299 }
1300 if ret_child.kind() == "composite_literal" {
1302 if let Some(type_node) = ret_child.child_by_field_name("type") {
1303 let name = type_node.utf8_text(source).unwrap_or("");
1304 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1305 return Some(name.to_string());
1306 }
1307 }
1308 }
1309 }
1310 }
1311 let ck = child.kind();
1313 if ck == "block" || ck == "statement_block" {
1314 if let Some(ret_type) = find_return_constructor(child, source) {
1315 return Some(ret_type);
1316 }
1317 }
1318 }
1319 None
1320}
1321
1322fn scan_init_self_attrs(
1325 node: tree_sitter::Node,
1326 file_path: &str,
1327 all_entities: &[SemanticEntity],
1328 entity_map: &HashMap<String, EntityInfo>,
1329 source: &[u8],
1330 instance_attr_types: &mut HashMap<(String, String), String>,
1331 init_params_map: &mut HashMap<String, Vec<String>>,
1332 attr_to_param_map: &mut HashMap<(String, String), String>,
1333 lang: &str,
1334) {
1335 let kind = node.kind();
1336
1337 match lang {
1338 "python" | "typescript" => {
1339 if kind == "class_definition" || kind == "class_declaration" {
1340 let class_name = node
1341 .child_by_field_name("name")
1342 .and_then(|n| n.utf8_text(source).ok())
1343 .unwrap_or("")
1344 .to_string();
1345
1346 if !class_name.is_empty() {
1347 scan_class_for_init(node, &class_name, source, instance_attr_types, init_params_map, attr_to_param_map, lang);
1348 }
1349 }
1350 }
1351 "rust" => {
1352 if kind == "struct_item" {
1354 let struct_name = node
1355 .child_by_field_name("name")
1356 .and_then(|n| n.utf8_text(source).ok())
1357 .unwrap_or("")
1358 .to_string();
1359
1360 if !struct_name.is_empty() {
1361 scan_rust_struct_fields(node, &struct_name, source, instance_attr_types);
1362 }
1363 }
1364 }
1365 "go" => {
1366 if kind == "type_declaration" {
1368 scan_go_struct_fields(node, source, instance_attr_types);
1369 }
1370 }
1371 _ => {}
1372 }
1373
1374 let mut cursor = node.walk();
1375 for child in node.named_children(&mut cursor) {
1376 scan_init_self_attrs(child, file_path, all_entities, entity_map, source, instance_attr_types, init_params_map, attr_to_param_map, lang);
1377 }
1378}
1379
1380fn scan_rust_struct_fields(
1382 node: tree_sitter::Node,
1383 struct_name: &str,
1384 source: &[u8],
1385 instance_attr_types: &mut HashMap<(String, String), String>,
1386) {
1387 let mut cursor = node.walk();
1388 for child in node.named_children(&mut cursor) {
1389 if child.kind() == "field_declaration_list" {
1390 let mut inner_cursor = child.walk();
1391 for field in child.named_children(&mut inner_cursor) {
1392 if field.kind() == "field_declaration" {
1393 let field_name = field
1394 .child_by_field_name("name")
1395 .and_then(|n| n.utf8_text(source).ok())
1396 .unwrap_or("");
1397 let field_type = field
1398 .child_by_field_name("type")
1399 .map(|n| extract_base_type(n, source))
1400 .unwrap_or_default();
1401
1402 if !field_name.is_empty()
1403 && !field_type.is_empty()
1404 && field_type.chars().next().map_or(false, |c| c.is_uppercase())
1405 {
1406 instance_attr_types.insert(
1407 (struct_name.to_string(), field_name.to_string()),
1408 field_type,
1409 );
1410 }
1411 }
1412 }
1413 }
1414 }
1415}
1416
1417fn scan_go_struct_fields(
1419 node: tree_sitter::Node,
1420 source: &[u8],
1421 instance_attr_types: &mut HashMap<(String, String), String>,
1422) {
1423 let mut cursor = node.walk();
1424 for child in node.named_children(&mut cursor) {
1425 if child.kind() == "type_spec" {
1426 let struct_name = child
1427 .child_by_field_name("name")
1428 .and_then(|n| n.utf8_text(source).ok())
1429 .unwrap_or("")
1430 .to_string();
1431
1432 if struct_name.is_empty() {
1433 continue;
1434 }
1435
1436 if let Some(type_node) = child.child_by_field_name("type") {
1438 if type_node.kind() == "struct_type" {
1439 let mut fields_cursor = type_node.walk();
1440 for field_list in type_node.named_children(&mut fields_cursor) {
1441 if field_list.kind() == "field_declaration_list" {
1442 let mut inner = field_list.walk();
1443 for field in field_list.named_children(&mut inner) {
1444 if field.kind() == "field_declaration" {
1445 let field_name = field
1447 .child_by_field_name("name")
1448 .and_then(|n| n.utf8_text(source).ok())
1449 .unwrap_or("");
1450 let field_type = field
1451 .child_by_field_name("type")
1452 .map(|n| extract_base_type(n, source))
1453 .unwrap_or_default();
1454
1455 if !field_name.is_empty()
1456 && !field_type.is_empty()
1457 && field_type.chars().next().map_or(false, |c| c.is_uppercase())
1458 {
1459 instance_attr_types.insert(
1460 (struct_name.clone(), field_name.to_string()),
1461 field_type,
1462 );
1463 }
1464 }
1465 }
1466 }
1467 }
1468 }
1469 }
1470 }
1471 }
1472}
1473
1474fn scan_class_for_init(
1475 node: tree_sitter::Node,
1476 class_name: &str,
1477 source: &[u8],
1478 instance_attr_types: &mut HashMap<(String, String), String>,
1479 init_params_map: &mut HashMap<String, Vec<String>>,
1480 attr_to_param_map: &mut HashMap<(String, String), String>,
1481 lang: &str,
1482) {
1483 let mut cursor = node.walk();
1484 for child in node.named_children(&mut cursor) {
1485 let ck = child.kind();
1486
1487 if ck == "function_definition" {
1489 let name = child
1490 .child_by_field_name("name")
1491 .and_then(|n| n.utf8_text(source).ok())
1492 .unwrap_or("");
1493 if name == "__init__" {
1494 let params = extract_init_params(child, source);
1495 let ordered_params = extract_init_param_names_ordered(child, source);
1496 init_params_map.insert(class_name.to_string(), ordered_params);
1497 scan_init_body(child, class_name, ¶ms, source, instance_attr_types, attr_to_param_map);
1498 }
1499 }
1500
1501 if ck == "method_definition" && lang == "typescript" {
1503 let name = child
1504 .child_by_field_name("name")
1505 .and_then(|n| n.utf8_text(source).ok())
1506 .unwrap_or("");
1507 if name == "constructor" {
1508 scan_ts_constructor_body(child, class_name, source, instance_attr_types, init_params_map, attr_to_param_map);
1510 }
1511 }
1512
1513 if (ck == "public_field_definition" || ck == "property_declaration" || ck == "field_definition") && lang == "typescript" {
1515 let field_name = child
1516 .child_by_field_name("name")
1517 .and_then(|n| n.utf8_text(source).ok())
1518 .unwrap_or("");
1519 if let Some(type_ann) = child.child_by_field_name("type") {
1520 let type_text = extract_base_type(type_ann, source);
1521 if !field_name.is_empty()
1522 && !type_text.is_empty()
1523 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1524 {
1525 instance_attr_types.insert(
1526 (class_name.to_string(), field_name.to_string()),
1527 type_text,
1528 );
1529 }
1530 }
1531 }
1532
1533 if ck == "block" || ck == "class_body" || ck == "statement_block" {
1534 scan_class_for_init(child, class_name, source, instance_attr_types, init_params_map, attr_to_param_map, lang);
1535 }
1536 }
1537}
1538
1539fn scan_ts_constructor_body(
1541 node: tree_sitter::Node,
1542 class_name: &str,
1543 source: &[u8],
1544 instance_attr_types: &mut HashMap<(String, String), String>,
1545 init_params_map: &mut HashMap<String, Vec<String>>,
1546 attr_to_param_map: &mut HashMap<(String, String), String>,
1547) {
1548 let params = extract_init_params(node, source);
1550 let ordered_params = extract_init_param_names_ordered(node, source);
1551 init_params_map.insert(class_name.to_string(), ordered_params);
1552
1553 scan_init_body_this(node, class_name, ¶ms, source, instance_attr_types, attr_to_param_map);
1555}
1556
1557fn scan_init_body_this(
1559 node: tree_sitter::Node,
1560 class_name: &str,
1561 params: &HashMap<String, Option<String>>,
1562 source: &[u8],
1563 instance_attr_types: &mut HashMap<(String, String), String>,
1564 attr_to_param_map: &mut HashMap<(String, String), String>,
1565) {
1566 let mut cursor = node.walk();
1567 for child in node.named_children(&mut cursor) {
1568 let ck = child.kind();
1569 if ck == "expression_statement" {
1570 let mut inner_cursor = child.walk();
1572 for inner in child.named_children(&mut inner_cursor) {
1573 if inner.kind() == "assignment_expression" {
1574 if let Some(left) = inner.child_by_field_name("left") {
1575 if left.kind() == "member_expression" {
1576 let obj = left.child_by_field_name("object")
1577 .and_then(|n| n.utf8_text(source).ok())
1578 .unwrap_or("");
1579 let prop = left.child_by_field_name("property")
1580 .and_then(|n| n.utf8_text(source).ok())
1581 .unwrap_or("");
1582 if obj == "this" && !prop.is_empty() {
1583 if let Some(right) = inner.child_by_field_name("right") {
1584 if right.kind() == "identifier" {
1585 let rhs_name = right.utf8_text(source).unwrap_or("");
1586 if params.contains_key(rhs_name) {
1587 attr_to_param_map.insert(
1588 (class_name.to_string(), prop.to_string()),
1589 rhs_name.to_string(),
1590 );
1591 if let Some(Some(type_hint)) = params.get(rhs_name) {
1592 instance_attr_types.insert(
1593 (class_name.to_string(), prop.to_string()),
1594 type_hint.clone(),
1595 );
1596 }
1597 }
1598 }
1599 if right.kind() == "new_expression" {
1600 if let Some(ctor) = right.child_by_field_name("constructor") {
1601 let name = ctor.utf8_text(source).unwrap_or("");
1602 if !name.is_empty() {
1603 instance_attr_types.insert(
1604 (class_name.to_string(), prop.to_string()),
1605 name.to_string(),
1606 );
1607 }
1608 }
1609 }
1610 }
1611 }
1612 }
1613 }
1614 }
1615 }
1616 }
1617 if ck == "statement_block" || ck == "block" {
1618 scan_init_body_this(child, class_name, params, source, instance_attr_types, attr_to_param_map);
1619 }
1620 }
1621}
1622
1623fn extract_init_param_names_ordered(func_node: tree_sitter::Node, source: &[u8]) -> Vec<String> {
1625 let mut names = Vec::new();
1626 if let Some(params_node) = func_node.child_by_field_name("parameters") {
1627 let mut cursor = params_node.walk();
1628 for child in params_node.named_children(&mut cursor) {
1629 let param_name = if child.kind() == "identifier" {
1630 child.utf8_text(source).unwrap_or("").to_string()
1631 } else if child.kind() == "typed_parameter" || child.kind() == "typed_default_parameter" {
1632 child.child_by_field_name("name")
1633 .or_else(|| child.named_child(0))
1634 .and_then(|n| n.utf8_text(source).ok())
1635 .unwrap_or("")
1636 .to_string()
1637 } else {
1638 continue;
1639 };
1640 if param_name != "self" && param_name != "cls" && !param_name.is_empty() {
1641 names.push(param_name);
1642 }
1643 }
1644 }
1645 names
1646}
1647
1648fn extract_init_params(func_node: tree_sitter::Node, source: &[u8]) -> HashMap<String, Option<String>> {
1649 let mut params = HashMap::new();
1650 if let Some(params_node) = func_node.child_by_field_name("parameters") {
1651 let mut cursor = params_node.walk();
1652 for child in params_node.named_children(&mut cursor) {
1653 let param_name = if child.kind() == "identifier" {
1654 child.utf8_text(source).unwrap_or("").to_string()
1655 } else if child.kind() == "typed_parameter" || child.kind() == "typed_default_parameter" {
1656 child.child_by_field_name("name")
1657 .or_else(|| child.named_child(0))
1658 .and_then(|n| n.utf8_text(source).ok())
1659 .unwrap_or("")
1660 .to_string()
1661 } else {
1662 continue;
1663 };
1664 if param_name != "self" && param_name != "cls" {
1665 let type_hint = child.child_by_field_name("type")
1667 .and_then(|n| n.utf8_text(source).ok())
1668 .map(|s| s.to_string());
1669 params.insert(param_name, type_hint);
1670 }
1671 }
1672 }
1673 params
1674}
1675
1676fn scan_init_body(
1677 node: tree_sitter::Node,
1678 class_name: &str,
1679 params: &HashMap<String, Option<String>>,
1680 source: &[u8],
1681 instance_attr_types: &mut HashMap<(String, String), String>,
1682 attr_to_param_map: &mut HashMap<(String, String), String>,
1683) {
1684 let mut cursor = node.walk();
1685 for child in node.named_children(&mut cursor) {
1686 if child.kind() == "expression_statement" || child.kind() == "assignment" {
1687 let assign = if child.kind() == "assignment" {
1688 child
1689 } else {
1690 let mut inner_cursor = child.walk();
1691 let children: Vec<_> = child.named_children(&mut inner_cursor).collect();
1692 match children.into_iter().find(|c| c.kind() == "assignment") {
1693 Some(a) => a,
1694 None => continue,
1695 }
1696 };
1697
1698 if let Some(left) = assign.child_by_field_name("left") {
1699 if left.kind() == "attribute" {
1700 let obj = left.child_by_field_name("object")
1701 .and_then(|n| n.utf8_text(source).ok())
1702 .unwrap_or("");
1703 let attr = left.child_by_field_name("attribute")
1704 .and_then(|n| n.utf8_text(source).ok())
1705 .unwrap_or("");
1706
1707 if obj == "self" && !attr.is_empty() {
1708 if let Some(right) = assign.child_by_field_name("right") {
1709 if right.kind() == "identifier" {
1710 let rhs_name = right.utf8_text(source).unwrap_or("");
1711 if params.contains_key(rhs_name) {
1713 attr_to_param_map.insert(
1714 (class_name.to_string(), attr.to_string()),
1715 rhs_name.to_string(),
1716 );
1717 }
1718 if let Some(Some(type_hint)) = params.get(rhs_name) {
1720 instance_attr_types.insert(
1721 (class_name.to_string(), attr.to_string()),
1722 type_hint.clone(),
1723 );
1724 }
1725 }
1726 if right.kind() == "call" {
1727 if let Some(func) = right.child_by_field_name("function") {
1728 if func.kind() == "identifier" {
1729 let fname = func.utf8_text(source).unwrap_or("");
1730 if fname.chars().next().map_or(false, |c| c.is_uppercase()) {
1731 instance_attr_types.insert(
1732 (class_name.to_string(), attr.to_string()),
1733 fname.to_string(),
1734 );
1735 }
1736 }
1737 }
1738 }
1739 }
1740 }
1741 }
1742 }
1743 }
1744 if child.kind() == "block" {
1745 scan_init_body(child, class_name, params, source, instance_attr_types, attr_to_param_map);
1746 }
1747 }
1748}
1749
1750fn infer_constructor_param_types(
1755 parsed_files: &[(String, String, tree_sitter::Tree)],
1756 return_type_map: &HashMap<String, String>,
1757 init_params: &HashMap<String, Vec<String>>,
1758 attr_to_param: &HashMap<(String, String), String>,
1759 symbol_table: &HashMap<String, Vec<String>>,
1760 entity_map: &HashMap<String, EntityInfo>,
1761 instance_attr_types: &mut HashMap<(String, String), String>,
1762) {
1763 let mut func_name_returns: HashMap<String, String> = HashMap::new();
1765 for (eid, ret_type) in return_type_map {
1766 if let Some(info) = entity_map.get(eid) {
1767 func_name_returns.insert(info.name.clone(), ret_type.clone());
1768 }
1769 }
1770
1771 for (_file_path, content, tree) in parsed_files {
1773 let source = content.as_bytes();
1774 scan_constructor_calls(
1775 tree.root_node(),
1776 source,
1777 &func_name_returns,
1778 init_params,
1779 attr_to_param,
1780 instance_attr_types,
1781 );
1782 }
1783}
1784
1785fn scan_constructor_calls(
1786 node: tree_sitter::Node,
1787 source: &[u8],
1788 func_name_returns: &HashMap<String, String>,
1789 init_params: &HashMap<String, Vec<String>>,
1790 attr_to_param: &HashMap<(String, String), String>,
1791 instance_attr_types: &mut HashMap<(String, String), String>,
1792) {
1793 let kind = node.kind();
1794
1795 if kind == "call" {
1796 if let Some(func) = node.child_by_field_name("function") {
1797 if func.kind() == "identifier" {
1798 let class_name = func.utf8_text(source).unwrap_or("");
1799 if class_name.chars().next().map_or(false, |c| c.is_uppercase()) {
1801 if let Some(param_names) = init_params.get(class_name) {
1802 if let Some(args_node) = node.child_by_field_name("arguments") {
1804 let mut arg_idx = 0;
1805 let mut args_cursor = args_node.walk();
1806 for arg in args_node.named_children(&mut args_cursor) {
1807 if arg_idx >= param_names.len() {
1808 break;
1809 }
1810 let param_name = ¶m_names[arg_idx];
1811
1812 let arg_type = infer_expr_type(arg, source, func_name_returns);
1814
1815 if let Some(at) = arg_type {
1816 for ((cn, attr), pn) in attr_to_param.iter() {
1818 if cn == class_name && pn == param_name {
1819 instance_attr_types
1820 .entry((cn.clone(), attr.clone()))
1821 .or_insert(at.clone());
1822 }
1823 }
1824 }
1825
1826 arg_idx += 1;
1827 }
1828 }
1829 }
1830 }
1831 }
1832 }
1833 }
1834
1835 let mut cursor = node.walk();
1836 for child in node.named_children(&mut cursor) {
1837 scan_constructor_calls(child, source, func_name_returns, init_params, attr_to_param, instance_attr_types);
1838 }
1839}
1840
1841fn infer_expr_type(
1843 node: tree_sitter::Node,
1844 source: &[u8],
1845 func_name_returns: &HashMap<String, String>,
1846) -> Option<String> {
1847 match node.kind() {
1848 "call" => {
1849 if let Some(func) = node.child_by_field_name("function") {
1850 if func.kind() == "identifier" {
1851 let name = func.utf8_text(source).unwrap_or("");
1852 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1854 return Some(name.to_string());
1855 }
1856 if let Some(ret) = func_name_returns.get(name) {
1858 return Some(ret.clone());
1859 }
1860 }
1861 }
1862 None
1863 }
1864 "identifier" => {
1865 None
1867 }
1868 _ => None,
1869 }
1870}
1871
1872fn inject_return_type_bindings(
1875 _entity_inner_scope: &HashMap<String, usize>,
1876 scopes: &mut Vec<Scope>,
1877 return_type_map: &HashMap<String, String>,
1878 import_table: &HashMap<(String, String), String>,
1879 file_path: &str,
1880 entity_map: &HashMap<String, EntityInfo>,
1881) {
1882 let mut func_name_return_types: HashMap<String, String> = HashMap::new();
1884 for (eid, ret_type) in return_type_map {
1885 if let Some(info) = entity_map.get(eid) {
1886 func_name_return_types.insert(info.name.clone(), ret_type.clone());
1887 }
1888 }
1889
1890 for ((fp, local_name), target_id) in import_table {
1892 if fp == file_path {
1893 if let Some(ret_type) = return_type_map.get(target_id) {
1894 func_name_return_types.insert(local_name.clone(), ret_type.clone());
1895 }
1896 }
1897 }
1898
1899 for scope in scopes.iter_mut() {
1901 let resolved: Vec<(String, String)> = scope
1902 .pending_call_types
1903 .iter()
1904 .filter_map(|(var_name, func_name)| {
1905 func_name_return_types
1906 .get(func_name)
1907 .map(|ret_type| (var_name.clone(), ret_type.clone()))
1908 })
1909 .collect();
1910
1911 for (var_name, ret_type) in resolved {
1912 scope.types.insert(var_name, ret_type);
1913 }
1914 }
1915}
1916
1917fn extract_imports_from_ast(
1919 node: tree_sitter::Node,
1920 file_path: &str,
1921 source: &[u8],
1922 symbol_table: &HashMap<String, Vec<String>>,
1923 entity_map: &HashMap<String, EntityInfo>,
1924 import_table: &mut HashMap<(String, String), String>,
1925 scopes: &mut Vec<Scope>,
1926 lang: &str,
1927) {
1928 let mut cursor = node.walk();
1929 for child in node.named_children(&mut cursor) {
1930 let ck = child.kind();
1931 match ck {
1932 "import_from_statement" if lang == "python" => {
1933 extract_python_import(child, file_path, source, symbol_table, entity_map, import_table, scopes);
1934 }
1935 "import_statement" if lang == "typescript" => {
1936 extract_ts_import(child, file_path, source, symbol_table, entity_map, import_table, scopes);
1937 }
1938 "use_declaration" if lang == "rust" => {
1939 extract_rust_use(child, file_path, source, symbol_table, entity_map, import_table, scopes);
1940 }
1941 "import_declaration" if lang == "go" => {
1942 extract_go_import(child, file_path, source, symbol_table, entity_map, import_table, scopes);
1943 }
1944 _ => {
1945 extract_imports_from_ast(child, file_path, source, symbol_table, entity_map, import_table, scopes, lang);
1946 }
1947 }
1948 }
1949}
1950
1951fn extract_ts_import(
1953 node: tree_sitter::Node,
1954 file_path: &str,
1955 source: &[u8],
1956 symbol_table: &HashMap<String, Vec<String>>,
1957 entity_map: &HashMap<String, EntityInfo>,
1958 import_table: &mut HashMap<(String, String), String>,
1959 scopes: &mut Vec<Scope>,
1960) {
1961 let source_path = node
1963 .child_by_field_name("source")
1964 .and_then(|n| n.utf8_text(source).ok())
1965 .unwrap_or("")
1966 .trim_matches(|c: char| c == '\'' || c == '"');
1967
1968 let source_module = source_path
1969 .rsplit('/')
1970 .next()
1971 .unwrap_or(source_path);
1972 let source_module = source_module
1974 .strip_suffix(".ts").or_else(|| source_module.strip_suffix(".js"))
1975 .or_else(|| source_module.strip_suffix(".tsx")).or_else(|| source_module.strip_suffix(".jsx"))
1976 .unwrap_or(source_module);
1977
1978 if source_module.is_empty() {
1979 return;
1980 }
1981
1982 let mut cursor = node.walk();
1984 for child in node.named_children(&mut cursor) {
1985 if child.kind() == "import_clause" {
1986 let mut clause_cursor = child.walk();
1987 for clause_child in child.named_children(&mut clause_cursor) {
1988 if clause_child.kind() == "named_imports" {
1989 let mut imports_cursor = clause_child.walk();
1991 for spec in clause_child.named_children(&mut imports_cursor) {
1992 if spec.kind() == "import_specifier" {
1993 let original = spec
1994 .child_by_field_name("name")
1995 .and_then(|n| n.utf8_text(source).ok())
1996 .unwrap_or("");
1997 let local = spec
1998 .child_by_field_name("alias")
1999 .and_then(|n| n.utf8_text(source).ok())
2000 .unwrap_or(original);
2001
2002 if !original.is_empty() {
2003 resolve_import_name(original, local, source_module, file_path, symbol_table, entity_map, import_table, scopes);
2004 }
2005 }
2006 }
2007 } else if clause_child.kind() == "identifier" {
2008 let name = clause_child.utf8_text(source).unwrap_or("");
2010 if !name.is_empty() {
2011 resolve_import_name(name, name, source_module, file_path, symbol_table, entity_map, import_table, scopes);
2012 }
2013 }
2014 }
2015 }
2016 }
2017}
2018
2019fn extract_rust_use(
2022 node: tree_sitter::Node,
2023 file_path: &str,
2024 source: &[u8],
2025 symbol_table: &HashMap<String, Vec<String>>,
2026 entity_map: &HashMap<String, EntityInfo>,
2027 import_table: &mut HashMap<(String, String), String>,
2028 scopes: &mut Vec<Scope>,
2029) {
2030 let text = node.utf8_text(source).unwrap_or("").trim().to_string();
2031 let text = text.strip_prefix("use ").unwrap_or(&text);
2033 let text = text.strip_prefix("pub use ").unwrap_or(text);
2034 let text = text.trim_end_matches(';').trim();
2035
2036 let text = text
2038 .strip_prefix("crate::")
2039 .or_else(|| text.strip_prefix("super::"))
2040 .or_else(|| text.strip_prefix("self::"))
2041 .unwrap_or(text);
2042
2043 if let Some(brace_pos) = text.find("::{") {
2045 let module_path = &text[..brace_pos];
2046 let source_module = module_path.rsplit("::").next().unwrap_or(module_path);
2047
2048 let names_part = &text[brace_pos + 3..];
2049 let names_part = names_part.trim_end_matches('}');
2050
2051 for name_part in names_part.split(',') {
2052 let name_part = name_part.trim();
2053 if name_part.is_empty() {
2054 continue;
2055 }
2056 let (original, local) = if let Some(pos) = name_part.find(" as ") {
2057 (name_part[..pos].trim(), name_part[pos + 4..].trim())
2058 } else {
2059 (name_part, name_part)
2060 };
2061 if !original.is_empty() {
2062 resolve_import_name(original, local, source_module, file_path, symbol_table, entity_map, import_table, scopes);
2063 }
2064 }
2065 } else {
2066 let parts: Vec<&str> = text.split("::").collect();
2068 if parts.is_empty() {
2069 return;
2070 }
2071 let imported_name = parts.last().unwrap().trim();
2072 let (original, local) = if let Some(pos) = imported_name.find(" as ") {
2073 (&imported_name[..pos], imported_name[pos + 4..].trim())
2074 } else {
2075 (imported_name, imported_name)
2076 };
2077 let source_module = if parts.len() >= 2 {
2078 parts[parts.len() - 2]
2079 } else {
2080 parts[0]
2081 };
2082 if !original.is_empty() && !source_module.is_empty() {
2083 resolve_import_name(original, local, source_module, file_path, symbol_table, entity_map, import_table, scopes);
2084 }
2085 }
2086}
2087
2088fn extract_go_import(
2090 node: tree_sitter::Node,
2091 file_path: &str,
2092 source: &[u8],
2093 symbol_table: &HashMap<String, Vec<String>>,
2094 entity_map: &HashMap<String, EntityInfo>,
2095 import_table: &mut HashMap<(String, String), String>,
2096 scopes: &mut Vec<Scope>,
2097) {
2098 let mut cursor = node.walk();
2099 for child in node.named_children(&mut cursor) {
2100 if child.kind() == "import_spec" || child.kind() == "import_spec_list" {
2101 extract_go_import_specs(child, file_path, source, symbol_table, entity_map, import_table, scopes);
2102 } else if child.kind() == "interpreted_string_literal" || child.kind() == "raw_string_literal" {
2103 let path = child.utf8_text(source).unwrap_or("")
2104 .trim_matches('"').trim_matches('`');
2105 let pkg_name = path.rsplit('/').next().unwrap_or(path);
2106 register_go_package_imports(pkg_name, file_path, symbol_table, entity_map, import_table, scopes);
2107 }
2108 }
2109}
2110
2111fn extract_go_import_specs(
2112 node: tree_sitter::Node,
2113 file_path: &str,
2114 source: &[u8],
2115 symbol_table: &HashMap<String, Vec<String>>,
2116 entity_map: &HashMap<String, EntityInfo>,
2117 import_table: &mut HashMap<(String, String), String>,
2118 scopes: &mut Vec<Scope>,
2119) {
2120 let mut cursor = node.walk();
2121 for child in node.named_children(&mut cursor) {
2122 if child.kind() == "import_spec" {
2123 let path_node = child.child_by_field_name("path")
2124 .or_else(|| child.named_child(0));
2125 if let Some(pn) = path_node {
2126 let path = pn.utf8_text(source).unwrap_or("")
2127 .trim_matches('"').trim_matches('`');
2128 let pkg_name = path.rsplit('/').next().unwrap_or(path);
2129 register_go_package_imports(pkg_name, file_path, symbol_table, entity_map, import_table, scopes);
2130 }
2131 } else {
2132 extract_go_import_specs(child, file_path, source, symbol_table, entity_map, import_table, scopes);
2133 }
2134 }
2135}
2136
2137fn register_go_package_imports(
2138 pkg_name: &str,
2139 file_path: &str,
2140 symbol_table: &HashMap<String, Vec<String>>,
2141 entity_map: &HashMap<String, EntityInfo>,
2142 import_table: &mut HashMap<(String, String), String>,
2143 scopes: &mut Vec<Scope>,
2144) {
2145 for (name, target_ids) in symbol_table {
2146 for target_id in target_ids {
2147 if let Some(entity) = entity_map.get(target_id) {
2148 let stem = entity.file_path.rsplit('/').next().unwrap_or(&entity.file_path);
2149 let stem = stem.strip_suffix(".go").unwrap_or(stem);
2150 if stem == pkg_name || entity.file_path.contains(&format!("{}/", pkg_name)) {
2151 import_table.insert(
2152 (file_path.to_string(), name.clone()),
2153 target_id.clone(),
2154 );
2155 if !scopes.is_empty() {
2156 scopes[0].defs.insert(name.clone(), target_id.clone());
2157 }
2158 }
2159 }
2160 }
2161 }
2162}
2163
2164fn resolve_import_name(
2166 original_name: &str,
2167 local_name: &str,
2168 source_module: &str,
2169 file_path: &str,
2170 symbol_table: &HashMap<String, Vec<String>>,
2171 entity_map: &HashMap<String, EntityInfo>,
2172 import_table: &mut HashMap<(String, String), String>,
2173 scopes: &mut Vec<Scope>,
2174) {
2175 if let Some(target_ids) = symbol_table.get(original_name) {
2176 let target = target_ids.iter().find(|id| {
2177 entity_map.get(*id).map_or(false, |e| {
2178 let stem = e.file_path.rsplit('/').next().unwrap_or(&e.file_path);
2179 let stem = stem
2180 .strip_suffix(".py")
2181 .or_else(|| stem.strip_suffix(".rs"))
2182 .or_else(|| stem.strip_suffix(".ts"))
2183 .or_else(|| stem.strip_suffix(".tsx"))
2184 .or_else(|| stem.strip_suffix(".js"))
2185 .or_else(|| stem.strip_suffix(".jsx"))
2186 .or_else(|| stem.strip_suffix(".go"))
2187 .unwrap_or(stem);
2188 stem == source_module
2189 })
2190 });
2191
2192 if let Some(target_id) = target {
2193 import_table.insert(
2194 (file_path.to_string(), local_name.to_string()),
2195 target_id.clone(),
2196 );
2197 if !scopes.is_empty() {
2198 scopes[0]
2199 .defs
2200 .insert(local_name.to_string(), target_id.clone());
2201 }
2202 }
2203 }
2204}
2205
2206fn extract_python_import(
2207 node: tree_sitter::Node,
2208 file_path: &str,
2209 source: &[u8],
2210 symbol_table: &HashMap<String, Vec<String>>,
2211 entity_map: &HashMap<String, EntityInfo>,
2212 import_table: &mut HashMap<(String, String), String>,
2213 scopes: &mut Vec<Scope>,
2214) {
2215 let module_node = node.child_by_field_name("module_name");
2219 let module_name = module_node
2220 .and_then(|n| n.utf8_text(source).ok())
2221 .unwrap_or("");
2222
2223 let source_module = module_name
2224 .trim_start_matches('.')
2225 .rsplit('.')
2226 .next()
2227 .unwrap_or(module_name.trim_start_matches('.'));
2228
2229 let mut cursor = node.walk();
2231 for child in node.named_children(&mut cursor) {
2232 if child.kind() == "dotted_name" || child.kind() == "aliased_import" {
2233 let (original, local) = if child.kind() == "aliased_import" {
2234 let orig = child
2235 .child_by_field_name("name")
2236 .and_then(|n| n.utf8_text(source).ok())
2237 .unwrap_or("");
2238 let alias = child
2239 .child_by_field_name("alias")
2240 .and_then(|n| n.utf8_text(source).ok())
2241 .unwrap_or(orig);
2242 (orig, alias)
2243 } else {
2244 let name = child.utf8_text(source).unwrap_or("");
2245 (name, name)
2246 };
2247
2248 if original.is_empty() {
2249 continue;
2250 }
2251
2252 if let Some(target_ids) = symbol_table.get(original) {
2254 let target = target_ids.iter().find(|id| {
2255 entity_map.get(*id).map_or(false, |e| {
2256 let stem = e.file_path.rsplit('/').next().unwrap_or(&e.file_path);
2257 let stem = stem
2258 .strip_suffix(".py")
2259 .or_else(|| stem.strip_suffix(".rs"))
2260 .or_else(|| stem.strip_suffix(".ts"))
2261 .or_else(|| stem.strip_suffix(".js"))
2262 .unwrap_or(stem);
2263 stem == source_module
2264 })
2265 });
2266
2267 if let Some(target_id) = target {
2268 import_table.insert(
2269 (file_path.to_string(), local.to_string()),
2270 target_id.clone(),
2271 );
2272 if !scopes.is_empty() {
2274 scopes[0]
2275 .defs
2276 .insert(local.to_string(), target_id.clone());
2277 }
2278 }
2279 }
2280 }
2281 }
2282}
2283
2284fn extract_ast_refs(
2286 root: tree_sitter::Node,
2287 entity: &SemanticEntity,
2288 source: &[u8],
2289 lang: &str,
2290) -> Vec<AstRef> {
2291 let mut refs = Vec::new();
2292 let start_row = entity.start_line.saturating_sub(1); let end_row = entity.end_line; collect_refs_in_range(root, start_row, end_row, &entity.id, &entity.name, source, &mut refs, lang);
2296 refs
2297}
2298
2299fn collect_refs_in_range(
2300 node: tree_sitter::Node,
2301 start_row: usize,
2302 end_row: usize,
2303 entity_id: &str,
2304 entity_name: &str,
2305 source: &[u8],
2306 refs: &mut Vec<AstRef>,
2307 lang: &str,
2308) {
2309 let node_start = node.start_position().row;
2310 let node_end = node.end_position().row;
2311
2312 if node_end < start_row || node_start >= end_row {
2313 return;
2314 }
2315
2316 let kind = node.kind();
2317
2318 if kind == "call" {
2320 if let Some(func) = node.child_by_field_name("function") {
2321 extract_call_ref(func, entity_id, entity_name, source, refs, lang);
2322 }
2323 if let Some(args) = node.child_by_field_name("arguments") {
2324 let mut cursor = args.walk();
2325 for child in args.named_children(&mut cursor) {
2326 collect_refs_in_range(child, start_row, end_row, entity_id, entity_name, source, refs, lang);
2327 }
2328 }
2329 return;
2330 }
2331
2332 if kind == "call_expression" {
2334 if let Some(func) = node.child_by_field_name("function") {
2335 extract_call_ref(func, entity_id, entity_name, source, refs, lang);
2336 }
2337 if let Some(args) = node.child_by_field_name("arguments") {
2338 let mut cursor = args.walk();
2339 for child in args.named_children(&mut cursor) {
2340 collect_refs_in_range(child, start_row, end_row, entity_id, entity_name, source, refs, lang);
2341 }
2342 }
2343 return;
2344 }
2345
2346 if kind == "new_expression" {
2348 if let Some(constructor) = node.child_by_field_name("constructor") {
2349 let name = constructor.utf8_text(source).unwrap_or("");
2350 if !name.is_empty() && name != entity_name && !is_builtin_for_lang(name, lang) {
2351 refs.push(AstRef {
2352 from_entity_id: entity_id.to_string(),
2353 kind: AstRefKind::Call(name.to_string()),
2354 });
2355 }
2356 }
2357 if let Some(args) = node.child_by_field_name("arguments") {
2358 let mut cursor = args.walk();
2359 for child in args.named_children(&mut cursor) {
2360 collect_refs_in_range(child, start_row, end_row, entity_id, entity_name, source, refs, lang);
2361 }
2362 }
2363 return;
2364 }
2365
2366 if kind == "composite_literal" && lang == "go" {
2368 if let Some(type_node) = node.child_by_field_name("type") {
2369 let name = type_node.utf8_text(source).unwrap_or("");
2370 if name.chars().next().map_or(false, |c| c.is_uppercase())
2371 && name != entity_name
2372 && !is_builtin_for_lang(name, lang)
2373 {
2374 refs.push(AstRef {
2375 from_entity_id: entity_id.to_string(),
2376 kind: AstRefKind::Call(name.to_string()),
2377 });
2378 }
2379 }
2380 }
2381
2382 let mut cursor = node.walk();
2384 for child in node.named_children(&mut cursor) {
2385 collect_refs_in_range(child, start_row, end_row, entity_id, entity_name, source, refs, lang);
2386 }
2387}
2388
2389fn extract_call_ref(
2391 func: tree_sitter::Node,
2392 entity_id: &str,
2393 entity_name: &str,
2394 source: &[u8],
2395 refs: &mut Vec<AstRef>,
2396 lang: &str,
2397) {
2398 match func.kind() {
2399 "identifier" => {
2400 let name = func.utf8_text(source).unwrap_or("");
2401 if !name.is_empty() && name != entity_name && !is_builtin_for_lang(name, lang) {
2402 refs.push(AstRef {
2403 from_entity_id: entity_id.to_string(),
2404 kind: AstRefKind::Call(name.to_string()),
2405 });
2406 }
2407 }
2408 "attribute" => {
2410 extract_member_call_ref(func, "object", "attribute", entity_id, source, refs);
2411 }
2412 "member_expression" => {
2414 extract_member_call_ref(func, "object", "property", entity_id, source, refs);
2415 }
2416 "field_expression" => {
2418 let obj = func
2419 .child_by_field_name("value")
2420 .and_then(|n| n.utf8_text(source).ok())
2421 .unwrap_or("");
2422 let field = func
2423 .child_by_field_name("field")
2424 .and_then(|n| n.utf8_text(source).ok())
2425 .unwrap_or("");
2426 if !obj.is_empty() && !field.is_empty() {
2427 push_method_call_ref(obj, field, entity_id, refs);
2428 }
2429 }
2430 "selector_expression" => {
2432 let obj = func
2433 .child_by_field_name("operand")
2434 .and_then(|n| n.utf8_text(source).ok())
2435 .unwrap_or("");
2436 let field = func
2437 .child_by_field_name("field")
2438 .and_then(|n| n.utf8_text(source).ok())
2439 .unwrap_or("");
2440 if !obj.is_empty() && !field.is_empty() {
2441 push_method_call_ref(obj, field, entity_id, refs);
2442 }
2443 }
2444 "scoped_identifier" => {
2446 let text = func.utf8_text(source).unwrap_or("");
2447 let parts: Vec<&str> = text.split("::").collect();
2448 if parts.len() >= 2 {
2449 let type_name = parts[parts.len() - 2];
2450 let method_name = parts[parts.len() - 1];
2451 if !type_name.is_empty() && !method_name.is_empty() {
2452 refs.push(AstRef {
2454 from_entity_id: entity_id.to_string(),
2455 kind: AstRefKind::Call(method_name.to_string()),
2456 });
2457 if type_name.chars().next().map_or(false, |c| c.is_uppercase())
2459 && !is_builtin_for_lang(type_name, lang)
2460 {
2461 refs.push(AstRef {
2462 from_entity_id: entity_id.to_string(),
2463 kind: AstRefKind::Call(type_name.to_string()),
2464 });
2465 }
2466 }
2467 }
2468 }
2469 _ => {}
2470 }
2471}
2472
2473fn extract_member_call_ref(
2475 node: tree_sitter::Node,
2476 object_field: &str,
2477 attr_field: &str,
2478 entity_id: &str,
2479 source: &[u8],
2480 refs: &mut Vec<AstRef>,
2481) {
2482 let obj = node
2483 .child_by_field_name(object_field)
2484 .and_then(|n| n.utf8_text(source).ok())
2485 .unwrap_or("");
2486 let attr = node
2487 .child_by_field_name(attr_field)
2488 .and_then(|n| n.utf8_text(source).ok())
2489 .unwrap_or("");
2490 if !obj.is_empty() && !attr.is_empty() {
2491 push_method_call_ref(obj, attr, entity_id, refs);
2492 }
2493}
2494
2495fn push_method_call_ref(obj: &str, method: &str, entity_id: &str, refs: &mut Vec<AstRef>) {
2496 refs.push(AstRef {
2497 from_entity_id: entity_id.to_string(),
2498 kind: AstRefKind::MethodCall {
2499 receiver: obj.to_string(),
2500 method: method.to_string(),
2501 },
2502 });
2503}
2504
2505fn resolve_ref(
2507 ast_ref: &AstRef,
2508 scope_idx: usize,
2509 scopes: &[Scope],
2510 symbol_table: &HashMap<String, Vec<String>>,
2511 class_members: &HashMap<String, Vec<(String, String)>>,
2512 import_table: &HashMap<(String, String), String>,
2513 instance_attr_types: &HashMap<(String, String), String>,
2514 entity_map: &HashMap<String, EntityInfo>,
2515 file_path: &str,
2516 from_entity_id: &str,
2517) -> Option<(String, RefType, &'static str)> {
2518 match &ast_ref.kind {
2519 AstRefKind::Call(name) => {
2520 if let Some(eid) = lookup_scope_chain(scope_idx, scopes, name) {
2522 if eid != from_entity_id {
2523 return Some((eid, RefType::Calls, "scope_chain"));
2524 }
2525 }
2526
2527 let key = (file_path.to_string(), name.clone());
2529 if let Some(target_id) = import_table.get(&key) {
2530 return Some((target_id.clone(), RefType::Calls, "import"));
2531 }
2532
2533 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
2535 if let Some(target_ids) = symbol_table.get(name.as_str()) {
2536 let target = target_ids
2537 .iter()
2538 .find(|id| {
2539 entity_map
2540 .get(*id)
2541 .map_or(false, |e| e.file_path == file_path)
2542 })
2543 .or_else(|| target_ids.first());
2544 if let Some(tid) = target {
2545 return Some((tid.clone(), RefType::TypeRef, "scope_chain"));
2546 }
2547 }
2548 }
2549
2550 None
2551 }
2552
2553 AstRefKind::MethodCall { receiver, method } => {
2554 if receiver == "self" || receiver == "this" {
2555 let mut idx = scope_idx;
2557 loop {
2558 if scopes[idx].kind == "class" {
2559 if let Some(eid) = scopes[idx].defs.get(method.as_str()) {
2560 return Some((eid.clone(), RefType::Calls, "scope_chain"));
2561 }
2562 break;
2563 }
2564 match scopes[idx].parent {
2565 Some(p) => idx = p,
2566 None => break,
2567 }
2568 }
2569 return None;
2570 }
2571
2572 if receiver.starts_with("self.") || receiver.starts_with("this.") {
2575 let attr_name = &receiver[5..]; let class_name = find_enclosing_class(scope_idx, scopes, entity_map);
2578 if let Some(cn) = class_name {
2579 if let Some(attr_type) = instance_attr_types.get(&(cn, attr_name.to_string())) {
2581 if let Some(members) = class_members.get(attr_type.as_str()) {
2582 if let Some((_, mid)) = members.iter().find(|(n, _)| n == method) {
2583 return Some((mid.clone(), RefType::Calls, "type_tracking"));
2584 }
2585 }
2586 }
2587 }
2588 }
2589
2590 if receiver.contains('.') && !receiver.starts_with("self.") && !receiver.starts_with("this.") {
2592 if let Some(dot_pos) = receiver.find('.') {
2593 let var_part = &receiver[..dot_pos];
2594 let field_part = &receiver[dot_pos + 1..];
2595 if let Some(var_type) = lookup_type_in_scopes(scope_idx, scopes, var_part) {
2596 if let Some(attr_type) = instance_attr_types.get(&(var_type, field_part.to_string())) {
2597 if let Some(members) = class_members.get(attr_type.as_str()) {
2598 if let Some((_, mid)) = members.iter().find(|(n, _)| n == method) {
2599 return Some((mid.clone(), RefType::Calls, "type_tracking"));
2600 }
2601 }
2602 }
2603 }
2604 }
2605 }
2606
2607 let receiver_type = lookup_type_in_scopes(scope_idx, scopes, receiver);
2609
2610 if let Some(class_name) = receiver_type {
2611 if let Some(members) = class_members.get(class_name.as_str()) {
2612 if let Some((_, mid)) = members.iter().find(|(n, _)| n == method) {
2613 return Some((mid.clone(), RefType::Calls, "type_tracking"));
2614 }
2615 }
2616 }
2617
2618 let key = (file_path.to_string(), receiver.clone());
2620 if let Some(target_id) = import_table.get(&key) {
2621 if let Some(info) = entity_map.get(target_id) {
2622 if matches!(info.entity_type.as_str(), "class" | "struct") {
2623 if let Some(members) = class_members.get(&info.name) {
2624 if let Some((_, mid)) =
2625 members.iter().find(|(n, _)| n == method)
2626 {
2627 return Some((
2628 mid.clone(),
2629 RefType::Calls,
2630 "type_tracking",
2631 ));
2632 }
2633 }
2634 }
2635 }
2636 }
2637
2638 let key = (file_path.to_string(), method.clone());
2641 if let Some(target_id) = import_table.get(&key) {
2642 return Some((target_id.clone(), RefType::Calls, "import"));
2643 }
2644
2645 None
2646 }
2647
2648 AstRefKind::Name(name) => {
2649 if let Some(eid) = lookup_scope_chain(scope_idx, scopes, name) {
2650 return Some((eid, RefType::TypeRef, "scope_chain"));
2651 }
2652 let key = (file_path.to_string(), name.clone());
2653 if let Some(target_id) = import_table.get(&key) {
2654 return Some((target_id.clone(), RefType::Imports, "import"));
2655 }
2656 None
2657 }
2658
2659 AstRefKind::Attribute { receiver, attr } => {
2660 let receiver_type = lookup_type_in_scopes(scope_idx, scopes, receiver);
2661 if let Some(class_name) = receiver_type {
2662 if let Some(members) = class_members.get(class_name.as_str()) {
2663 if let Some((_, mid)) = members.iter().find(|(n, _)| n == attr) {
2664 return Some((mid.clone(), RefType::Calls, "type_tracking"));
2665 }
2666 }
2667 }
2668 None
2669 }
2670 }
2671}
2672
2673fn find_enclosing_class(
2675 start_scope: usize,
2676 scopes: &[Scope],
2677 entity_map: &HashMap<String, EntityInfo>,
2678) -> Option<String> {
2679 let mut idx = start_scope;
2680 loop {
2681 if scopes[idx].kind == "class" {
2682 if let Some(ref oid) = scopes[idx].owner_id {
2683 return entity_map.get(oid).map(|e| e.name.clone());
2684 }
2685 }
2686 match scopes[idx].parent {
2687 Some(p) => idx = p,
2688 None => return None,
2689 }
2690 }
2691}
2692
2693fn lookup_scope_chain(
2695 start_scope: usize,
2696 scopes: &[Scope],
2697 name: &str,
2698) -> Option<String> {
2699 let mut idx = start_scope;
2700 loop {
2701 if let Some(eid) = scopes[idx].defs.get(name) {
2702 return Some(eid.clone());
2703 }
2704 match scopes[idx].parent {
2705 Some(p) => idx = p,
2706 None => return None,
2707 }
2708 }
2709}
2710
2711fn lookup_type_in_scopes(
2713 start_scope: usize,
2714 scopes: &[Scope],
2715 var_name: &str,
2716) -> Option<String> {
2717 let mut idx = start_scope;
2718 loop {
2719 if let Some(type_name) = scopes[idx].types.get(var_name) {
2720 return Some(type_name.clone());
2721 }
2722 match scopes[idx].parent {
2723 Some(p) => idx = p,
2724 None => return None,
2725 }
2726 }
2727}
2728
2729fn is_builtin_for_lang(name: &str, lang: &str) -> bool {
2730 if matches!(name, "None" | "True" | "False" | "null" | "undefined" | "nil") {
2732 return true;
2733 }
2734 match lang {
2735 "python" => matches!(
2736 name,
2737 "print" | "len" | "range" | "str" | "int" | "float" | "bool"
2738 | "list" | "dict" | "set" | "tuple" | "type" | "super"
2739 | "isinstance" | "issubclass" | "getattr" | "setattr"
2740 | "hasattr" | "delattr" | "open" | "input" | "map"
2741 | "filter" | "zip" | "enumerate" | "sorted" | "reversed"
2742 | "min" | "max" | "sum" | "any" | "all" | "abs"
2743 | "round" | "format" | "repr" | "id" | "hash"
2744 | "ValueError" | "TypeError" | "KeyError" | "RuntimeError"
2745 | "Exception" | "StopIteration"
2746 ),
2747 "typescript" => matches!(
2748 name,
2749 "console" | "parseInt" | "parseFloat" | "isNaN" | "isFinite"
2750 | "setTimeout" | "setInterval" | "clearTimeout" | "clearInterval"
2751 | "Promise" | "Array" | "Object" | "Map" | "Set" | "WeakMap" | "WeakSet"
2752 | "JSON" | "Math" | "Date" | "RegExp" | "Error" | "TypeError"
2753 | "RangeError" | "Symbol" | "Proxy" | "Reflect"
2754 | "String" | "Number" | "Boolean" | "BigInt"
2755 | "require" | "module" | "exports" | "process"
2756 | "Buffer" | "global" | "window" | "document"
2757 | "fetch" | "Response" | "Request" | "Headers" | "URL"
2758 ),
2759 "rust" => matches!(
2760 name,
2761 "println" | "eprintln" | "print" | "eprint" | "dbg"
2762 | "format" | "write" | "writeln"
2763 | "vec" | "panic" | "todo" | "unimplemented" | "unreachable"
2764 | "assert" | "assert_eq" | "assert_ne" | "debug_assert"
2765 | "Some" | "None" | "Ok" | "Err"
2766 | "Box" | "Vec" | "String" | "HashMap" | "HashSet"
2767 | "Arc" | "Rc" | "Mutex" | "RwLock" | "Cell" | "RefCell"
2768 | "Option" | "Result" | "Iterator" | "IntoIterator"
2769 | "Clone" | "Copy" | "Debug" | "Display" | "Default"
2770 | "From" | "Into" | "TryFrom" | "TryInto"
2771 | "Send" | "Sync" | "Sized" | "Unpin"
2772 | "cfg" | "derive" | "include" | "env"
2773 ),
2774 "go" => matches!(
2775 name,
2776 "fmt" | "log" | "os" | "io" | "strings" | "strconv" | "bytes"
2777 | "make" | "len" | "cap" | "append" | "copy" | "delete" | "close"
2778 | "panic" | "recover" | "new" | "print" | "println"
2779 | "error" | "string" | "int" | "int8" | "int16" | "int32" | "int64"
2780 | "uint" | "uint8" | "uint16" | "uint32" | "uint64"
2781 | "float32" | "float64" | "complex64" | "complex128"
2782 | "bool" | "byte" | "rune" | "uintptr"
2783 | "Println" | "Printf" | "Sprintf" | "Fprintf" | "Errorf"
2784 ),
2785 _ => false,
2786 }
2787}