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