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