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 if kind == "mod_item" {
610 let mod_name = node
611 .child_by_field_name("name")
612 .and_then(|n| n.utf8_text(source).ok())
613 .unwrap_or("");
614 let mod_scope_idx = scopes.len();
615 scopes.push(Scope {
616 parent: Some(current_scope),
617 defs: HashMap::new(),
618 types: HashMap::new(),
619 pending_call_types: HashMap::new(),
620 owner_id: None,
621 kind: "module",
622 });
623
624 let mod_entity = file_entities.iter().find(|e| {
626 e.name == mod_name
627 && e.entity_type == "module"
628 && {
629 let line = node.start_position().row + 1;
630 e.start_line <= line && line <= e.end_line
631 }
632 }).copied();
633
634 if let Some(me) = mod_entity {
635 scopes[mod_scope_idx].owner_id = Some(me.id.clone());
636 entity_scope_map.entry(me.id.clone()).or_insert(current_scope);
637 entity_inner_scope.insert(me.id.clone(), mod_scope_idx);
638
639 if let Some(children) = children_by_parent.get(me.id.as_str()) {
641 for child_entity in children {
642 scopes[mod_scope_idx]
643 .defs
644 .insert(child_entity.name.clone(), child_entity.id.clone());
645 entity_scope_map.insert(child_entity.id.clone(), mod_scope_idx);
646 }
647 }
648 }
649
650 let mut cursor = node.walk();
651 let children: Vec<_> = node.named_children(&mut cursor).collect();
652 for child in children.into_iter().rev() {
653 worklist.push((child, mod_scope_idx));
654 }
655 continue;
656 }
657
658 let is_function_like = config.function_scope_nodes.contains(&kind);
660
661 if is_function_like {
662 let func_name = node.child_by_field_name("name")
663 .and_then(|n| n.utf8_text(source).ok())
664 .unwrap_or("");
665
666 let parent_scope = if config.external_method && kind == "method_declaration" {
667 let receiver_type = node.utf8_text(source).ok()
668 .and_then(|t| extract_go_receiver_type(t));
669 if let Some(ref struct_name) = receiver_type {
670 let found = scopes.iter().enumerate().find(|(_, s)| {
671 s.kind == "class" && s.owner_id.as_ref().map_or(false, |oid| {
672 entity_map.get(oid).map_or(false, |e| e.name == *struct_name)
673 })
674 });
675 found.map(|(idx, _)| idx).unwrap_or(current_scope)
676 } else {
677 current_scope
678 }
679 } else {
680 current_scope
681 };
682
683 let func_scope_idx = scopes.len();
684 scopes.push(Scope {
685 parent: Some(parent_scope),
686 defs: HashMap::new(),
687 types: HashMap::new(),
688 pending_call_types: HashMap::new(),
689 owner_id: None,
690 kind: "function",
691 });
692
693 let func_entity = file_entities.iter().find(|e| {
694 e.name == func_name && {
695 let line = node.start_position().row + 1;
696 e.start_line <= line && line <= e.end_line
697 }
698 }).copied();
699
700 if let Some(fe) = func_entity {
701 scopes[func_scope_idx].owner_id = Some(fe.id.clone());
702 entity_scope_map.entry(fe.id.clone()).or_insert(parent_scope);
703 entity_inner_scope.insert(fe.id.clone(), func_scope_idx);
704 if config.external_method && kind == "method_declaration" && parent_scope != current_scope {
705 scopes[parent_scope].defs.insert(fe.name.clone(), fe.id.clone());
706 }
707 }
708
709 scan_assignments(node, func_scope_idx, scopes, source, config);
710 scan_function_params(node, func_scope_idx, scopes, source, config);
711
712 if config.external_method && kind == "method_declaration" {
713 if let Some(receiver) = node.child_by_field_name("receiver") {
714 let mut rcursor = receiver.walk();
715 for param in receiver.named_children(&mut rcursor) {
716 if param.kind() == "parameter_declaration" {
717 let param_name = param
718 .child_by_field_name("name")
719 .and_then(|n| n.utf8_text(source).ok())
720 .unwrap_or("");
721 let param_type = param
722 .child_by_field_name("type")
723 .map(|n| extract_base_type(n, source))
724 .unwrap_or_default();
725 if !param_name.is_empty() && !param_type.is_empty() {
726 scopes[func_scope_idx]
727 .types
728 .insert(param_name.to_string(), param_type);
729 }
730 }
731 }
732 }
733 }
734
735 let mut cursor = node.walk();
736 let children: Vec<_> = node.named_children(&mut cursor).collect();
737 for child in children.into_iter().rev() {
738 worklist.push((child, func_scope_idx));
739 }
740 continue;
741 }
742
743 let mut cursor = node.walk();
744 let children: Vec<_> = node.named_children(&mut cursor).collect();
745 for child in children.into_iter().rev() {
746 worklist.push((child, current_scope));
747 }
748 }
749}
750
751fn scan_assignments(
753 root: tree_sitter::Node,
754 scope_idx: usize,
755 scopes: &mut Vec<Scope>,
756 source: &[u8],
757 config: &ScopeResolveConfig,
758) {
759 let mut worklist = vec![root];
760 while let Some(node) = worklist.pop() {
761 let mut cursor = node.walk();
762 for child in node.named_children(&mut cursor) {
763 let ck = child.kind();
764
765 for rule in config.assignment_rules {
767 if ck == rule.node_kind {
768 match rule.strategy {
769 AssignmentStrategy::LeftRight => {
770 scan_single_assignment(child, scope_idx, scopes, source);
771 }
772 AssignmentStrategy::Declarators => {
773 scan_ts_var_declaration(child, scope_idx, scopes, source);
774 }
775 AssignmentStrategy::PatternBased => {
776 scan_rust_let_declaration(child, scope_idx, scopes, source);
777 }
778 AssignmentStrategy::ShortVar => {
779 scan_go_short_var(child, scope_idx, scopes, source);
780 }
781 AssignmentStrategy::VarSpec => {
782 scan_go_var_declaration(child, scope_idx, scopes, source);
783 }
784 }
785 }
786 }
787
788 if config.assignment_recurse_into.contains(&ck) {
790 worklist.push(child);
791 }
792 }
793 }
794}
795
796fn scan_function_params(
799 node: tree_sitter::Node,
800 scope_idx: usize,
801 scopes: &mut Vec<Scope>,
802 source: &[u8],
803 config: &ScopeResolveConfig,
804) {
805 let params_node = match node.child_by_field_name("parameters") {
806 Some(p) => p,
807 None => return,
808 };
809
810 let mut cursor = params_node.walk();
811 for child in params_node.named_children(&mut cursor) {
812 for rule in config.param_rules {
813 if child.kind() != rule.node_kind {
814 continue;
815 }
816
817 let param_name = match &rule.name_field {
818 ParamNameField::Simple(field) => {
819 child.child_by_field_name(field)
820 .and_then(|n| n.utf8_text(source).ok())
821 .unwrap_or("")
822 }
823 ParamNameField::WithFallback(field) => {
824 child.child_by_field_name(field)
825 .or_else(|| child.named_child(0).filter(|n| n.kind() == "identifier"))
826 .and_then(|n| n.utf8_text(source).ok())
827 .unwrap_or("")
828 }
829 ParamNameField::RustPattern => {
830 child.child_by_field_name("pattern")
831 .and_then(|n| {
832 if n.kind() == "identifier" {
833 n.utf8_text(source).ok()
834 } else if n.kind() == "mut_pattern" {
835 n.named_child(0).and_then(|c| c.utf8_text(source).ok())
836 } else if n.kind() == "reference_pattern" {
837 n.named_child(0).and_then(|c| {
838 if c.kind() == "identifier" {
839 c.utf8_text(source).ok()
840 } else if c.kind() == "mut_pattern" {
841 c.named_child(0).and_then(|cc| cc.utf8_text(source).ok())
842 } else {
843 None
844 }
845 })
846 } else {
847 None
848 }
849 })
850 .unwrap_or("")
851 }
852 };
853
854 if param_name.is_empty() || rule.skip_names.contains(¶m_name) {
855 continue;
856 }
857
858 if let Some(type_node) = child.child_by_field_name(rule.type_field) {
859 let type_text = extract_base_type(type_node, source);
860 if !type_text.is_empty()
861 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
862 {
863 scopes[scope_idx]
864 .types
865 .insert(param_name.to_string(), type_text);
866 }
867 }
868 }
869 }
870}
871
872fn scan_single_assignment(
874 node: tree_sitter::Node,
875 scope_idx: usize,
876 scopes: &mut Vec<Scope>,
877 source: &[u8],
878) {
879 let assign = if node.kind() == "assignment" {
880 node
881 } else {
882 let mut cursor = node.walk();
883 let children: Vec<_> = node.named_children(&mut cursor).collect();
884 match children.into_iter().find(|c| c.kind() == "assignment" || c.kind() == "assignment_expression") {
885 Some(a) => a,
886 None => return,
887 }
888 };
889
890 let left = match assign.child_by_field_name("left") {
891 Some(l) => l,
892 None => return,
893 };
894 let right = match assign.child_by_field_name("right") {
895 Some(r) => r,
896 None => return,
897 };
898
899 if left.kind() != "identifier" {
900 return;
901 }
902 let var_name = match left.utf8_text(source) {
903 Ok(n) => n.to_string(),
904 Err(_) => return,
905 };
906
907 record_type_from_rhs(right, &var_name, scope_idx, scopes, source);
908}
909
910fn scan_ts_var_declaration(
912 node: tree_sitter::Node,
913 scope_idx: usize,
914 scopes: &mut Vec<Scope>,
915 source: &[u8],
916) {
917 let mut cursor = node.walk();
918 for child in node.named_children(&mut cursor) {
919 if child.kind() == "variable_declarator" {
920 let var_name = child
921 .child_by_field_name("name")
922 .and_then(|n| n.utf8_text(source).ok())
923 .unwrap_or("")
924 .to_string();
925 if var_name.is_empty() {
926 continue;
927 }
928
929 if let Some(type_ann) = child.child_by_field_name("type") {
931 let type_text = extract_base_type(type_ann, source);
932 if !type_text.is_empty()
933 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
934 {
935 scopes[scope_idx]
936 .types
937 .insert(var_name.clone(), type_text);
938 continue;
939 }
940 }
941
942 if let Some(value) = child.child_by_field_name("value") {
944 record_type_from_rhs(value, &var_name, scope_idx, scopes, source);
945 }
946 }
947 }
948}
949
950fn scan_rust_let_declaration(
952 node: tree_sitter::Node,
953 scope_idx: usize,
954 scopes: &mut Vec<Scope>,
955 source: &[u8],
956) {
957 let var_name = node
958 .child_by_field_name("pattern")
959 .and_then(|n| {
960 if n.kind() == "identifier" {
962 n.utf8_text(source).ok()
963 } else if n.kind() == "mut_pattern" {
964 n.named_child(0).and_then(|c| c.utf8_text(source).ok())
965 } else {
966 None
967 }
968 })
969 .unwrap_or("")
970 .to_string();
971
972 if var_name.is_empty() {
973 return;
974 }
975
976 if let Some(type_node) = node.child_by_field_name("type") {
978 let type_text = extract_base_type(type_node, source);
979 if !type_text.is_empty()
980 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
981 {
982 scopes[scope_idx]
983 .types
984 .insert(var_name, type_text);
985 return;
986 }
987 }
988
989 if let Some(value) = node.child_by_field_name("value") {
991 record_type_from_rhs(value, &var_name, scope_idx, scopes, source);
992 }
993}
994
995fn scan_go_short_var(
997 node: tree_sitter::Node,
998 scope_idx: usize,
999 scopes: &mut Vec<Scope>,
1000 source: &[u8],
1001) {
1002 let left = match node.child_by_field_name("left") {
1003 Some(l) => l,
1004 None => return,
1005 };
1006 let right = match node.child_by_field_name("right") {
1007 Some(r) => r,
1008 None => return,
1009 };
1010
1011 let var_name = if left.kind() == "expression_list" {
1013 left.named_child(0)
1014 .and_then(|n| n.utf8_text(source).ok())
1015 .unwrap_or("")
1016 .to_string()
1017 } else {
1018 left.utf8_text(source).unwrap_or("").to_string()
1019 };
1020
1021 if var_name.is_empty() {
1022 return;
1023 }
1024
1025 let rhs = if right.kind() == "expression_list" {
1026 match right.named_child(0) {
1027 Some(n) => n,
1028 None => return,
1029 }
1030 } else {
1031 right
1032 };
1033
1034 record_type_from_rhs(rhs, &var_name, scope_idx, scopes, source);
1035}
1036
1037fn scan_go_var_declaration(
1039 node: tree_sitter::Node,
1040 scope_idx: usize,
1041 scopes: &mut Vec<Scope>,
1042 source: &[u8],
1043) {
1044 let mut cursor = node.walk();
1045 for child in node.named_children(&mut cursor) {
1046 if child.kind() == "var_spec" {
1047 let var_name = child
1048 .child_by_field_name("name")
1049 .and_then(|n| n.utf8_text(source).ok())
1050 .unwrap_or("")
1051 .to_string();
1052 if var_name.is_empty() {
1053 if let Some(first) = child.named_child(0) {
1055 if first.kind() == "identifier" {
1056 let name = first.utf8_text(source).unwrap_or("").to_string();
1057 if !name.is_empty() {
1058 if let Some(type_node) = child.child_by_field_name("type") {
1060 let type_text = extract_base_type(type_node, source);
1061 if !type_text.is_empty()
1062 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1063 {
1064 scopes[scope_idx].types.insert(name, type_text);
1065 }
1066 }
1067 }
1068 }
1069 }
1070 continue;
1071 }
1072
1073 if let Some(type_node) = child.child_by_field_name("type") {
1075 let type_text = extract_base_type(type_node, source);
1076 if !type_text.is_empty()
1077 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1078 {
1079 scopes[scope_idx]
1080 .types
1081 .insert(var_name, type_text);
1082 continue;
1083 }
1084 }
1085
1086 if let Some(value) = child.child_by_field_name("value") {
1088 let rhs = if value.kind() == "expression_list" {
1089 value.named_child(0).unwrap_or(value)
1090 } else {
1091 value
1092 };
1093 record_type_from_rhs(rhs, &var_name, scope_idx, scopes, source);
1094 }
1095 }
1096 }
1097}
1098
1099fn record_type_from_rhs(
1102 rhs: tree_sitter::Node,
1103 var_name: &str,
1104 scope_idx: usize,
1105 scopes: &mut Vec<Scope>,
1106 source: &[u8],
1107) {
1108 match rhs.kind() {
1109 "call" | "call_expression" => {
1111 let func_node = rhs
1112 .child_by_field_name("function")
1113 .or_else(|| rhs.named_child(0));
1114 if let Some(func) = func_node {
1115 if func.kind() == "identifier" {
1116 let name = func.utf8_text(source).unwrap_or("");
1117 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1118 scopes[scope_idx]
1119 .types
1120 .insert(var_name.to_string(), name.to_string());
1121 } else {
1122 scopes[scope_idx]
1123 .pending_call_types
1124 .insert(var_name.to_string(), name.to_string());
1125 }
1126 }
1127 if func.kind() == "scoped_identifier" {
1129 let text = func.utf8_text(source).unwrap_or("");
1130 let parts: Vec<&str> = text.split("::").collect();
1131 if parts.len() >= 2 {
1132 let type_name = parts[0];
1133 let method_name = parts[parts.len() - 1];
1134 if type_name.chars().next().map_or(false, |c| c.is_uppercase()) {
1135 scopes[scope_idx]
1136 .types
1137 .insert(var_name.to_string(), type_name.to_string());
1138 } else {
1139 scopes[scope_idx]
1140 .pending_call_types
1141 .insert(var_name.to_string(), method_name.to_string());
1142 }
1143 }
1144 }
1145 if func.kind() == "selector_expression" {
1147 let field = func
1148 .child_by_field_name("field")
1149 .and_then(|n| n.utf8_text(source).ok())
1150 .unwrap_or("");
1151 if let Some(type_name) = field.strip_prefix("New") {
1153 if !type_name.is_empty()
1154 && type_name.chars().next().map_or(false, |c| c.is_uppercase())
1155 {
1156 scopes[scope_idx]
1157 .types
1158 .insert(var_name.to_string(), type_name.to_string());
1159 }
1160 } else if field.starts_with("Get") || field.chars().next().map_or(false, |c| c.is_uppercase()) {
1161 scopes[scope_idx]
1163 .pending_call_types
1164 .insert(var_name.to_string(), field.to_string());
1165 }
1166 }
1167 }
1168 }
1169 "new_expression" => {
1171 if let Some(constructor) = rhs.child_by_field_name("constructor") {
1172 let name = constructor.utf8_text(source).unwrap_or("");
1173 if !name.is_empty() {
1174 scopes[scope_idx]
1175 .types
1176 .insert(var_name.to_string(), name.to_string());
1177 }
1178 }
1179 }
1180 "composite_literal" => {
1182 if let Some(type_node) = rhs.child_by_field_name("type") {
1183 let name = type_node.utf8_text(source).unwrap_or("");
1184 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1185 scopes[scope_idx]
1186 .types
1187 .insert(var_name.to_string(), name.to_string());
1188 }
1189 }
1190 }
1191 _ => {}
1192 }
1193}
1194
1195fn extract_base_type(type_node: tree_sitter::Node, source: &[u8]) -> String {
1198 let text = type_node.utf8_text(source).unwrap_or("").trim().to_string();
1199 let text = text.trim_start_matches('&').trim_start_matches('*');
1201 let text = text.strip_prefix("mut ").unwrap_or(text).trim_start();
1202 let text = if let Some(i) = text.find('<') {
1204 &text[..i]
1205 } else if let Some(i) = text.find('[') {
1206 &text[..i]
1207 } else {
1208 text
1209 };
1210 let text = text.trim();
1212 let text = text.trim_start_matches(':').trim();
1214 text.to_string()
1215}
1216
1217pub fn extract_go_receiver_type(content: &str) -> Option<String> {
1219 let after_func = content.strip_prefix("func")?.trim_start();
1220 let paren_start = after_func.find('(')?;
1221 let paren_end = after_func.find(')')?;
1222 let receiver_block = &after_func[paren_start + 1..paren_end];
1223 let parts: Vec<&str> = receiver_block.split_whitespace().collect();
1225 let type_str = parts.last()?;
1226 let name = type_str.trim_start_matches('*');
1227 if name.is_empty() {
1228 None
1229 } else {
1230 Some(name.to_string())
1231 }
1232}
1233
1234fn build_go_pkg_index(
1237 symbol_table: &HashMap<String, Vec<String>>,
1238 entity_map: &HashMap<String, EntityInfo>,
1239) -> HashMap<String, Vec<(String, String)>> {
1240 let mut idx: HashMap<String, Vec<(String, String)>> = HashMap::new();
1241 for (name, target_ids) in symbol_table.iter() {
1242 for target_id in target_ids {
1243 if let Some(entity) = entity_map.get(target_id) {
1244 if !entity.file_path.ends_with(".go") {
1245 continue;
1246 }
1247 let file_stem = entity.file_path.rsplit('/').next().unwrap_or(&entity.file_path);
1248 let file_stem = file_stem.strip_suffix(".go").unwrap_or(file_stem);
1249 idx.entry(file_stem.to_string())
1250 .or_default()
1251 .push((name.clone(), target_id.clone()));
1252 if let Some(parent_start) = entity.file_path.rfind('/') {
1253 let parent_path = &entity.file_path[..parent_start];
1254 if let Some(dir_name_start) = parent_path.rfind('/') {
1255 let dir_name = &parent_path[dir_name_start + 1..];
1256 if dir_name != file_stem {
1257 idx.entry(dir_name.to_string())
1258 .or_default()
1259 .push((name.clone(), target_id.clone()));
1260 }
1261 } else if !parent_path.is_empty() && parent_path != file_stem {
1262 idx.entry(parent_path.to_string())
1263 .or_default()
1264 .push((name.clone(), target_id.clone()));
1265 }
1266 }
1267 }
1268 }
1269 }
1270 idx
1271}
1272
1273fn scan_return_types(
1275 root: tree_sitter::Node,
1276 _file_path: &str,
1277 file_entities: &[&SemanticEntity],
1278 source: &[u8],
1279 return_type_map: &mut HashMap<String, String>,
1280 config: &ScopeResolveConfig,
1281) {
1282 let mut worklist = vec![root];
1283 while let Some(node) = worklist.pop() {
1284 let kind = node.kind();
1285
1286 let is_func = config.function_scope_nodes.contains(&kind);
1287
1288 if is_func {
1289 let func_name = node
1290 .child_by_field_name("name")
1291 .and_then(|n| n.utf8_text(source).ok())
1292 .unwrap_or("");
1293
1294 let func_entity = file_entities.iter().find(|e| {
1295 e.name == func_name && {
1296 let line = node.start_position().row + 1;
1297 e.start_line <= line && line <= e.end_line
1298 }
1299 }).copied();
1300
1301 if let Some(fe) = func_entity {
1302 let ret_type = config.return_type_field.and_then(|field| {
1304 node.child_by_field_name(field)
1305 .map(|n| extract_base_type(n, source))
1306 .filter(|t| !t.is_empty() && t.chars().next().map_or(false, |c| c.is_uppercase()))
1307 });
1308
1309 if let Some(rt) = ret_type {
1310 return_type_map.insert(fe.id.clone(), rt);
1311 } else {
1312 if let Some(ret_type) = find_return_constructor(node, source) {
1314 return_type_map.insert(fe.id.clone(), ret_type);
1315 }
1316 }
1317 }
1318 }
1319
1320 let mut cursor = node.walk();
1321 let children: Vec<_> = node.named_children(&mut cursor).collect();
1322 for child in children.into_iter().rev() {
1323 worklist.push(child);
1324 }
1325 }
1326}
1327
1328fn find_return_constructor(root: tree_sitter::Node, source: &[u8]) -> Option<String> {
1330 let mut worklist = vec![root];
1331 while let Some(node) = worklist.pop() {
1332 let mut cursor = node.walk();
1333 for child in node.named_children(&mut cursor) {
1334 if child.kind() == "return_statement" {
1335 let mut inner_cursor = child.walk();
1336 for ret_child in child.named_children(&mut inner_cursor) {
1337 if ret_child.kind() == "call" || ret_child.kind() == "call_expression" {
1339 if let Some(func) = ret_child.child_by_field_name("function") {
1340 if func.kind() == "identifier" {
1341 let name = func.utf8_text(source).unwrap_or("");
1342 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1343 return Some(name.to_string());
1344 }
1345 }
1346 }
1347 }
1348 if ret_child.kind() == "new_expression" {
1350 if let Some(constructor) = ret_child.child_by_field_name("constructor") {
1351 let name = constructor.utf8_text(source).unwrap_or("");
1352 if !name.is_empty() {
1353 return Some(name.to_string());
1354 }
1355 }
1356 }
1357 if ret_child.kind() == "composite_literal" {
1359 if let Some(type_node) = ret_child.child_by_field_name("type") {
1360 let name = type_node.utf8_text(source).unwrap_or("");
1361 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1362 return Some(name.to_string());
1363 }
1364 }
1365 }
1366 }
1367 }
1368 let ck = child.kind();
1370 if ck == "block" || ck == "statement_block" {
1371 worklist.push(child);
1372 }
1373 }
1374 }
1375 None
1376}
1377
1378fn scan_init_self_attrs(
1381 root: tree_sitter::Node,
1382 _file_path: &str,
1383 _file_entities: &[&SemanticEntity],
1384 _entity_map: &HashMap<String, EntityInfo>,
1385 source: &[u8],
1386 instance_attr_types: &mut HashMap<(String, String), String>,
1387 init_params_map: &mut HashMap<String, Vec<String>>,
1388 attr_to_param_map: &mut HashMap<(String, String), String>,
1389 config: &ScopeResolveConfig,
1390) {
1391 let mut worklist = vec![root];
1392 while let Some(node) = worklist.pop() {
1393 let kind = node.kind();
1394
1395 match &config.init_strategy {
1396 InitStrategy::ConstructorBody { class_nodes, self_keyword, .. } => {
1397 if class_nodes.contains(&kind) {
1398 let class_name = node
1399 .child_by_field_name("name")
1400 .and_then(|n| n.utf8_text(source).ok())
1401 .unwrap_or("")
1402 .to_string();
1403
1404 if !class_name.is_empty() {
1405 let lang = if *self_keyword == "this" { "typescript" } else { "python" };
1407 scan_class_for_init(node, &class_name, source, instance_attr_types, init_params_map, attr_to_param_map, lang);
1408 }
1409 }
1410 }
1411 InitStrategy::StructFields { struct_nodes } => {
1412 if struct_nodes.contains(&kind) {
1413 if kind == "struct_item" {
1415 let struct_name = node
1416 .child_by_field_name("name")
1417 .and_then(|n| n.utf8_text(source).ok())
1418 .unwrap_or("")
1419 .to_string();
1420
1421 if !struct_name.is_empty() {
1422 scan_rust_struct_fields(node, &struct_name, source, instance_attr_types);
1423 }
1424 }
1425 if kind == "type_declaration" {
1427 scan_go_struct_fields(node, source, instance_attr_types);
1428 }
1429 }
1430 }
1431 InitStrategy::None => {}
1432 }
1433
1434 let mut cursor = node.walk();
1435 let children: Vec<_> = node.named_children(&mut cursor).collect();
1436 for child in children.into_iter().rev() {
1437 worklist.push(child);
1438 }
1439 }
1440}
1441
1442fn scan_rust_struct_fields(
1444 node: tree_sitter::Node,
1445 struct_name: &str,
1446 source: &[u8],
1447 instance_attr_types: &mut HashMap<(String, String), String>,
1448) {
1449 let mut cursor = node.walk();
1450 for child in node.named_children(&mut cursor) {
1451 if child.kind() == "field_declaration_list" {
1452 let mut inner_cursor = child.walk();
1453 for field in child.named_children(&mut inner_cursor) {
1454 if field.kind() == "field_declaration" {
1455 let field_name = field
1456 .child_by_field_name("name")
1457 .and_then(|n| n.utf8_text(source).ok())
1458 .unwrap_or("");
1459 let field_type = field
1460 .child_by_field_name("type")
1461 .map(|n| extract_base_type(n, source))
1462 .unwrap_or_default();
1463
1464 if !field_name.is_empty()
1465 && !field_type.is_empty()
1466 && field_type.chars().next().map_or(false, |c| c.is_uppercase())
1467 {
1468 instance_attr_types.insert(
1469 (struct_name.to_string(), field_name.to_string()),
1470 field_type,
1471 );
1472 }
1473 }
1474 }
1475 }
1476 }
1477}
1478
1479fn scan_go_struct_fields(
1481 node: tree_sitter::Node,
1482 source: &[u8],
1483 instance_attr_types: &mut HashMap<(String, String), String>,
1484) {
1485 let mut cursor = node.walk();
1486 for child in node.named_children(&mut cursor) {
1487 if child.kind() == "type_spec" {
1488 let struct_name = child
1489 .child_by_field_name("name")
1490 .and_then(|n| n.utf8_text(source).ok())
1491 .unwrap_or("")
1492 .to_string();
1493
1494 if struct_name.is_empty() {
1495 continue;
1496 }
1497
1498 if let Some(type_node) = child.child_by_field_name("type") {
1500 if type_node.kind() == "struct_type" {
1501 let mut fields_cursor = type_node.walk();
1502 for field_list in type_node.named_children(&mut fields_cursor) {
1503 if field_list.kind() == "field_declaration_list" {
1504 let mut inner = field_list.walk();
1505 for field in field_list.named_children(&mut inner) {
1506 if field.kind() == "field_declaration" {
1507 let field_name = field
1509 .child_by_field_name("name")
1510 .and_then(|n| n.utf8_text(source).ok())
1511 .unwrap_or("");
1512 let field_type = field
1513 .child_by_field_name("type")
1514 .map(|n| extract_base_type(n, source))
1515 .unwrap_or_default();
1516
1517 if !field_name.is_empty()
1518 && !field_type.is_empty()
1519 && field_type.chars().next().map_or(false, |c| c.is_uppercase())
1520 {
1521 instance_attr_types.insert(
1522 (struct_name.clone(), field_name.to_string()),
1523 field_type,
1524 );
1525 }
1526 }
1527 }
1528 }
1529 }
1530 }
1531 }
1532 }
1533 }
1534}
1535
1536fn scan_class_for_init(
1537 root: tree_sitter::Node,
1538 class_name: &str,
1539 source: &[u8],
1540 instance_attr_types: &mut HashMap<(String, String), String>,
1541 init_params_map: &mut HashMap<String, Vec<String>>,
1542 attr_to_param_map: &mut HashMap<(String, String), String>,
1543 lang: &str,
1544) {
1545 let mut worklist = vec![root];
1546 while let Some(node) = worklist.pop() {
1547 let mut cursor = node.walk();
1548 for child in node.named_children(&mut cursor) {
1549 let ck = child.kind();
1550
1551 if ck == "function_definition" {
1553 let name = child
1554 .child_by_field_name("name")
1555 .and_then(|n| n.utf8_text(source).ok())
1556 .unwrap_or("");
1557 if name == "__init__" {
1558 let params = extract_init_params(child, source);
1559 let ordered_params = extract_init_param_names_ordered(child, source);
1560 init_params_map.insert(class_name.to_string(), ordered_params);
1561 scan_init_body(child, class_name, ¶ms, source, instance_attr_types, attr_to_param_map);
1562 }
1563 }
1564
1565 if ck == "method_definition" && lang == "typescript" {
1567 let name = child
1568 .child_by_field_name("name")
1569 .and_then(|n| n.utf8_text(source).ok())
1570 .unwrap_or("");
1571 if name == "constructor" {
1572 scan_ts_constructor_body(child, class_name, source, instance_attr_types, init_params_map, attr_to_param_map);
1574 }
1575 }
1576
1577 if (ck == "public_field_definition" || ck == "property_declaration" || ck == "field_definition") && lang == "typescript" {
1579 let field_name = child
1580 .child_by_field_name("name")
1581 .and_then(|n| n.utf8_text(source).ok())
1582 .unwrap_or("");
1583 if let Some(type_ann) = child.child_by_field_name("type") {
1584 let type_text = extract_base_type(type_ann, source);
1585 if !field_name.is_empty()
1586 && !type_text.is_empty()
1587 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1588 {
1589 instance_attr_types.insert(
1590 (class_name.to_string(), field_name.to_string()),
1591 type_text,
1592 );
1593 }
1594 }
1595 }
1596
1597 if ck == "block" || ck == "class_body" || ck == "statement_block" {
1598 worklist.push(child);
1599 }
1600 }
1601 }
1602}
1603
1604fn scan_ts_constructor_body(
1606 node: tree_sitter::Node,
1607 class_name: &str,
1608 source: &[u8],
1609 instance_attr_types: &mut HashMap<(String, String), String>,
1610 init_params_map: &mut HashMap<String, Vec<String>>,
1611 attr_to_param_map: &mut HashMap<(String, String), String>,
1612) {
1613 let params = extract_init_params(node, source);
1615 let ordered_params = extract_init_param_names_ordered(node, source);
1616 init_params_map.insert(class_name.to_string(), ordered_params);
1617
1618 scan_init_body_this(node, class_name, ¶ms, source, instance_attr_types, attr_to_param_map);
1620}
1621
1622fn scan_init_body_this(
1624 root: tree_sitter::Node,
1625 class_name: &str,
1626 params: &HashMap<String, Option<String>>,
1627 source: &[u8],
1628 instance_attr_types: &mut HashMap<(String, String), String>,
1629 attr_to_param_map: &mut HashMap<(String, String), String>,
1630) {
1631 let mut worklist = vec![root];
1632 while let Some(node) = worklist.pop() {
1633 let mut cursor = node.walk();
1634 for child in node.named_children(&mut cursor) {
1635 let ck = child.kind();
1636 if ck == "expression_statement" {
1637 let mut inner_cursor = child.walk();
1639 for inner in child.named_children(&mut inner_cursor) {
1640 if inner.kind() == "assignment_expression" {
1641 if let Some(left) = inner.child_by_field_name("left") {
1642 if left.kind() == "member_expression" {
1643 let obj = left.child_by_field_name("object")
1644 .and_then(|n| n.utf8_text(source).ok())
1645 .unwrap_or("");
1646 let prop = left.child_by_field_name("property")
1647 .and_then(|n| n.utf8_text(source).ok())
1648 .unwrap_or("");
1649 if obj == "this" && !prop.is_empty() {
1650 if let Some(right) = inner.child_by_field_name("right") {
1651 if right.kind() == "identifier" {
1652 let rhs_name = right.utf8_text(source).unwrap_or("");
1653 if params.contains_key(rhs_name) {
1654 attr_to_param_map.insert(
1655 (class_name.to_string(), prop.to_string()),
1656 rhs_name.to_string(),
1657 );
1658 if let Some(Some(type_hint)) = params.get(rhs_name) {
1659 instance_attr_types.insert(
1660 (class_name.to_string(), prop.to_string()),
1661 type_hint.clone(),
1662 );
1663 }
1664 }
1665 }
1666 if right.kind() == "new_expression" {
1667 if let Some(ctor) = right.child_by_field_name("constructor") {
1668 let name = ctor.utf8_text(source).unwrap_or("");
1669 if !name.is_empty() {
1670 instance_attr_types.insert(
1671 (class_name.to_string(), prop.to_string()),
1672 name.to_string(),
1673 );
1674 }
1675 }
1676 }
1677 }
1678 }
1679 }
1680 }
1681 }
1682 }
1683 }
1684 if ck == "statement_block" || ck == "block" {
1685 worklist.push(child);
1686 }
1687 }
1688 }
1689}
1690
1691fn extract_init_param_names_ordered(func_node: tree_sitter::Node, source: &[u8]) -> Vec<String> {
1693 let mut names = Vec::new();
1694 if let Some(params_node) = func_node.child_by_field_name("parameters") {
1695 let mut cursor = params_node.walk();
1696 for child in params_node.named_children(&mut cursor) {
1697 let param_name = if child.kind() == "identifier" {
1698 child.utf8_text(source).unwrap_or("").to_string()
1699 } else if child.kind() == "typed_parameter" || child.kind() == "typed_default_parameter" {
1700 child.child_by_field_name("name")
1701 .or_else(|| child.named_child(0))
1702 .and_then(|n| n.utf8_text(source).ok())
1703 .unwrap_or("")
1704 .to_string()
1705 } else {
1706 continue;
1707 };
1708 if param_name != "self" && param_name != "cls" && !param_name.is_empty() {
1709 names.push(param_name);
1710 }
1711 }
1712 }
1713 names
1714}
1715
1716fn extract_init_params(func_node: tree_sitter::Node, source: &[u8]) -> HashMap<String, Option<String>> {
1717 let mut params = HashMap::new();
1718 if let Some(params_node) = func_node.child_by_field_name("parameters") {
1719 let mut cursor = params_node.walk();
1720 for child in params_node.named_children(&mut cursor) {
1721 let param_name = if child.kind() == "identifier" {
1722 child.utf8_text(source).unwrap_or("").to_string()
1723 } else if child.kind() == "typed_parameter" || child.kind() == "typed_default_parameter" {
1724 child.child_by_field_name("name")
1725 .or_else(|| child.named_child(0))
1726 .and_then(|n| n.utf8_text(source).ok())
1727 .unwrap_or("")
1728 .to_string()
1729 } else {
1730 continue;
1731 };
1732 if param_name != "self" && param_name != "cls" {
1733 let type_hint = child.child_by_field_name("type")
1735 .and_then(|n| n.utf8_text(source).ok())
1736 .map(|s| s.to_string());
1737 params.insert(param_name, type_hint);
1738 }
1739 }
1740 }
1741 params
1742}
1743
1744fn scan_init_body(
1745 root: tree_sitter::Node,
1746 class_name: &str,
1747 params: &HashMap<String, Option<String>>,
1748 source: &[u8],
1749 instance_attr_types: &mut HashMap<(String, String), String>,
1750 attr_to_param_map: &mut HashMap<(String, String), String>,
1751) {
1752 let mut worklist = vec![root];
1753 while let Some(node) = worklist.pop() {
1754 let mut cursor = node.walk();
1755 for child in node.named_children(&mut cursor) {
1756 if child.kind() == "expression_statement" || child.kind() == "assignment" {
1757 let assign = if child.kind() == "assignment" {
1758 child
1759 } else {
1760 let mut inner_cursor = child.walk();
1761 let children: Vec<_> = child.named_children(&mut inner_cursor).collect();
1762 match children.into_iter().find(|c| c.kind() == "assignment") {
1763 Some(a) => a,
1764 None => continue,
1765 }
1766 };
1767
1768 if let Some(left) = assign.child_by_field_name("left") {
1769 if left.kind() == "attribute" {
1770 let obj = left.child_by_field_name("object")
1771 .and_then(|n| n.utf8_text(source).ok())
1772 .unwrap_or("");
1773 let attr = left.child_by_field_name("attribute")
1774 .and_then(|n| n.utf8_text(source).ok())
1775 .unwrap_or("");
1776
1777 if obj == "self" && !attr.is_empty() {
1778 if let Some(right) = assign.child_by_field_name("right") {
1779 if right.kind() == "identifier" {
1780 let rhs_name = right.utf8_text(source).unwrap_or("");
1781 if params.contains_key(rhs_name) {
1783 attr_to_param_map.insert(
1784 (class_name.to_string(), attr.to_string()),
1785 rhs_name.to_string(),
1786 );
1787 }
1788 if let Some(Some(type_hint)) = params.get(rhs_name) {
1790 instance_attr_types.insert(
1791 (class_name.to_string(), attr.to_string()),
1792 type_hint.clone(),
1793 );
1794 }
1795 }
1796 if right.kind() == "call" {
1797 if let Some(func) = right.child_by_field_name("function") {
1798 if func.kind() == "identifier" {
1799 let fname = func.utf8_text(source).unwrap_or("");
1800 if fname.chars().next().map_or(false, |c| c.is_uppercase()) {
1801 instance_attr_types.insert(
1802 (class_name.to_string(), attr.to_string()),
1803 fname.to_string(),
1804 );
1805 }
1806 }
1807 }
1808 }
1809 }
1810 }
1811 }
1812 }
1813 }
1814 if child.kind() == "block" {
1815 worklist.push(child);
1816 }
1817 }
1818 }
1819}
1820
1821fn infer_constructor_param_types(
1826 parsed_files: &[(String, String, tree_sitter::Tree)],
1827 return_type_map: &HashMap<String, String>,
1828 init_params: &HashMap<String, Vec<String>>,
1829 attr_to_param: &HashMap<(String, String), String>,
1830 _symbol_table: &HashMap<String, Vec<String>>,
1831 entity_map: &HashMap<String, EntityInfo>,
1832 instance_attr_types: &mut HashMap<(String, String), String>,
1833) {
1834 let mut func_name_returns: HashMap<String, String> = HashMap::new();
1836 for (eid, ret_type) in return_type_map {
1837 if let Some(info) = entity_map.get(eid) {
1838 func_name_returns.insert(info.name.clone(), ret_type.clone());
1839 }
1840 }
1841
1842 let local_results: Vec<HashMap<(String, String), String>> = maybe_par_iter!(parsed_files)
1845 .map(|(_file_path, content, tree)| {
1846 let source = content.as_bytes();
1847 let mut local_attr_types: HashMap<(String, String), String> = HashMap::new();
1848 scan_constructor_calls(
1849 tree.root_node(),
1850 source,
1851 &func_name_returns,
1852 init_params,
1853 attr_to_param,
1854 &mut local_attr_types,
1855 );
1856 local_attr_types
1857 })
1858 .collect();
1859
1860 for local in local_results {
1861 for (key, val) in local {
1862 instance_attr_types.entry(key).or_insert(val);
1863 }
1864 }
1865}
1866
1867fn scan_constructor_calls(
1868 root: tree_sitter::Node,
1869 source: &[u8],
1870 func_name_returns: &HashMap<String, String>,
1871 init_params: &HashMap<String, Vec<String>>,
1872 attr_to_param: &HashMap<(String, String), String>,
1873 instance_attr_types: &mut HashMap<(String, String), String>,
1874) {
1875 let mut worklist = vec![root];
1876 while let Some(node) = worklist.pop() {
1877 let kind = node.kind();
1878
1879 if kind == "call" {
1880 if let Some(func) = node.child_by_field_name("function") {
1881 if func.kind() == "identifier" {
1882 let class_name = func.utf8_text(source).unwrap_or("");
1883 if class_name.chars().next().map_or(false, |c| c.is_uppercase()) {
1885 if let Some(param_names) = init_params.get(class_name) {
1886 if let Some(args_node) = node.child_by_field_name("arguments") {
1888 let mut arg_idx = 0;
1889 let mut args_cursor = args_node.walk();
1890 for arg in args_node.named_children(&mut args_cursor) {
1891 if arg_idx >= param_names.len() {
1892 break;
1893 }
1894 let param_name = ¶m_names[arg_idx];
1895
1896 let arg_type = infer_expr_type(arg, source, func_name_returns);
1898
1899 if let Some(at) = arg_type {
1900 for ((cn, attr), pn) in attr_to_param.iter() {
1902 if cn == class_name && pn == param_name {
1903 instance_attr_types
1904 .entry((cn.clone(), attr.clone()))
1905 .or_insert(at.clone());
1906 }
1907 }
1908 }
1909
1910 arg_idx += 1;
1911 }
1912 }
1913 }
1914 }
1915 }
1916 }
1917 }
1918
1919 let mut cursor = node.walk();
1920 let children: Vec<_> = node.named_children(&mut cursor).collect();
1921 for child in children.into_iter().rev() {
1922 worklist.push(child);
1923 }
1924 }
1925}
1926
1927fn infer_expr_type(
1929 node: tree_sitter::Node,
1930 source: &[u8],
1931 func_name_returns: &HashMap<String, String>,
1932) -> Option<String> {
1933 match node.kind() {
1934 "call" => {
1935 if let Some(func) = node.child_by_field_name("function") {
1936 if func.kind() == "identifier" {
1937 let name = func.utf8_text(source).unwrap_or("");
1938 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1940 return Some(name.to_string());
1941 }
1942 if let Some(ret) = func_name_returns.get(name) {
1944 return Some(ret.clone());
1945 }
1946 }
1947 }
1948 None
1949 }
1950 "identifier" => {
1951 None
1953 }
1954 _ => None,
1955 }
1956}
1957
1958fn inject_return_type_bindings(
1961 _entity_inner_scope: &HashMap<String, usize>,
1962 scopes: &mut Vec<Scope>,
1963 return_type_map: &HashMap<String, String>,
1964 import_table: &HashMap<(String, String), String>,
1965 file_path: &str,
1966 entity_map: &HashMap<String, EntityInfo>,
1967) {
1968 let mut func_name_return_types: HashMap<String, String> = HashMap::new();
1970 for (eid, ret_type) in return_type_map {
1971 if let Some(info) = entity_map.get(eid) {
1972 func_name_return_types.insert(info.name.clone(), ret_type.clone());
1973 }
1974 }
1975
1976 for ((fp, local_name), target_id) in import_table {
1978 if fp == file_path {
1979 if let Some(ret_type) = return_type_map.get(target_id) {
1980 func_name_return_types.insert(local_name.clone(), ret_type.clone());
1981 }
1982 }
1983 }
1984
1985 for scope in scopes.iter_mut() {
1987 let resolved: Vec<(String, String)> = scope
1988 .pending_call_types
1989 .iter()
1990 .filter_map(|(var_name, func_name)| {
1991 func_name_return_types
1992 .get(func_name)
1993 .map(|ret_type| (var_name.clone(), ret_type.clone()))
1994 })
1995 .collect();
1996
1997 for (var_name, ret_type) in resolved {
1998 scope.types.insert(var_name, ret_type);
1999 }
2000 }
2001}
2002
2003fn extract_imports_from_ast(
2005 root: tree_sitter::Node,
2006 file_path: &str,
2007 source: &[u8],
2008 symbol_table: &HashMap<String, Vec<String>>,
2009 entity_map: &HashMap<String, EntityInfo>,
2010 import_table: &mut HashMap<(String, String), String>,
2011 scopes: &mut Vec<Scope>,
2012 config: &ScopeResolveConfig,
2013 go_pkg_index: &HashMap<String, Vec<(String, String)>>,
2014) {
2015 let mut worklist = vec![root];
2016 while let Some(node) = worklist.pop() {
2017 let mut cursor = node.walk();
2018 for child in node.named_children(&mut cursor) {
2019 let ck = child.kind();
2020 let handled = match ck {
2021 "import_from_statement" => {
2022 extract_python_import(child, file_path, source, symbol_table, entity_map, import_table, scopes);
2023 true
2024 }
2025 "import_statement" if config.self_keywords.contains(&"self") && config.self_keywords.contains(&"cls") => {
2026 extract_python_module_import(child, file_path, source, symbol_table, entity_map, import_table, scopes);
2028 true
2029 }
2030 "import_statement" if !config.self_keywords.contains(&"cls") => {
2031 extract_ts_import(child, file_path, source, symbol_table, entity_map, import_table, scopes);
2033 true
2034 }
2035 "use_declaration" => {
2036 extract_rust_use(child, file_path, source, symbol_table, entity_map, import_table, scopes);
2037 true
2038 }
2039 "import_declaration" => {
2040 extract_go_import(child, file_path, source, symbol_table, entity_map, import_table, scopes, go_pkg_index);
2041 true
2042 }
2043 _ => false,
2044 };
2045 if !handled {
2046 worklist.push(child);
2047 }
2048 }
2049 }
2050}
2051
2052fn extract_ts_import(
2054 node: tree_sitter::Node,
2055 file_path: &str,
2056 source: &[u8],
2057 symbol_table: &HashMap<String, Vec<String>>,
2058 entity_map: &HashMap<String, EntityInfo>,
2059 import_table: &mut HashMap<(String, String), String>,
2060 scopes: &mut Vec<Scope>,
2061) {
2062 let source_path = node
2064 .child_by_field_name("source")
2065 .and_then(|n| n.utf8_text(source).ok())
2066 .unwrap_or("")
2067 .trim_matches(|c: char| c == '\'' || c == '"');
2068
2069 let source_module = source_path
2070 .rsplit('/')
2071 .next()
2072 .unwrap_or(source_path);
2073 let source_module = source_module
2075 .strip_suffix(".ts").or_else(|| source_module.strip_suffix(".js"))
2076 .or_else(|| source_module.strip_suffix(".tsx")).or_else(|| source_module.strip_suffix(".jsx"))
2077 .unwrap_or(source_module);
2078
2079 if source_module.is_empty() {
2080 return;
2081 }
2082
2083 let mut cursor = node.walk();
2085 for child in node.named_children(&mut cursor) {
2086 if child.kind() == "import_clause" {
2087 let mut clause_cursor = child.walk();
2088 for clause_child in child.named_children(&mut clause_cursor) {
2089 if clause_child.kind() == "named_imports" {
2090 let mut imports_cursor = clause_child.walk();
2092 for spec in clause_child.named_children(&mut imports_cursor) {
2093 if spec.kind() == "import_specifier" {
2094 let original = spec
2095 .child_by_field_name("name")
2096 .and_then(|n| n.utf8_text(source).ok())
2097 .unwrap_or("");
2098 let local = spec
2099 .child_by_field_name("alias")
2100 .and_then(|n| n.utf8_text(source).ok())
2101 .unwrap_or(original);
2102
2103 if !original.is_empty() {
2104 resolve_import_name(original, local, source_module, file_path, symbol_table, entity_map, import_table, scopes);
2105 }
2106 }
2107 }
2108 } else if clause_child.kind() == "namespace_import" {
2109 let mut ns_cursor = clause_child.walk();
2112 let alias = clause_child
2113 .child_by_field_name("alias")
2114 .or_else(|| {
2115 clause_child.named_children(&mut ns_cursor)
2116 .find(|c| c.kind() == "identifier")
2117 })
2118 .and_then(|n| n.utf8_text(source).ok())
2119 .unwrap_or("");
2120 if !alias.is_empty() {
2121 register_namespace_import(alias, source_module, file_path, symbol_table, entity_map, import_table, scopes);
2122 }
2123 } else if clause_child.kind() == "identifier" {
2124 let name = clause_child.utf8_text(source).unwrap_or("");
2126 if !name.is_empty() {
2127 resolve_import_name(name, name, source_module, file_path, symbol_table, entity_map, import_table, scopes);
2128 }
2129 }
2130 }
2131 }
2132 }
2133}
2134
2135fn extract_rust_use(
2138 node: tree_sitter::Node,
2139 file_path: &str,
2140 source: &[u8],
2141 symbol_table: &HashMap<String, Vec<String>>,
2142 entity_map: &HashMap<String, EntityInfo>,
2143 import_table: &mut HashMap<(String, String), String>,
2144 scopes: &mut Vec<Scope>,
2145) {
2146 let text = node.utf8_text(source).unwrap_or("").trim().to_string();
2147 let text = text.strip_prefix("use ").unwrap_or(&text);
2149 let text = text.strip_prefix("pub use ").unwrap_or(text);
2150 let text = text.trim_end_matches(';').trim();
2151
2152 let text = text
2154 .strip_prefix("crate::")
2155 .or_else(|| text.strip_prefix("super::"))
2156 .or_else(|| text.strip_prefix("self::"))
2157 .unwrap_or(text);
2158
2159 if let Some(brace_pos) = text.find("::{") {
2161 let module_path = &text[..brace_pos];
2162 let source_module = module_path.rsplit("::").next().unwrap_or(module_path);
2163
2164 let names_part = &text[brace_pos + 3..];
2165 let names_part = names_part.trim_end_matches('}');
2166
2167 for name_part in names_part.split(',') {
2168 let name_part = name_part.trim();
2169 if name_part.is_empty() {
2170 continue;
2171 }
2172 let (original, local) = if let Some(pos) = name_part.find(" as ") {
2173 (name_part[..pos].trim(), name_part[pos + 4..].trim())
2174 } else {
2175 (name_part, name_part)
2176 };
2177 if !original.is_empty() {
2178 resolve_import_name(original, local, source_module, file_path, symbol_table, entity_map, import_table, scopes);
2179 }
2180 }
2181 } else {
2182 let parts: Vec<&str> = text.split("::").collect();
2184 if parts.is_empty() {
2185 return;
2186 }
2187 let imported_name = parts.last().unwrap().trim();
2188 let (original, local) = if let Some(pos) = imported_name.find(" as ") {
2189 (&imported_name[..pos], imported_name[pos + 4..].trim())
2190 } else {
2191 (imported_name, imported_name)
2192 };
2193 let source_module = if parts.len() >= 2 {
2194 parts[parts.len() - 2]
2195 } else {
2196 parts[0]
2197 };
2198 if !original.is_empty() && !source_module.is_empty() {
2199 resolve_import_name(original, local, source_module, file_path, symbol_table, entity_map, import_table, scopes);
2200 }
2201 }
2202}
2203
2204fn extract_go_import(
2206 node: tree_sitter::Node,
2207 file_path: &str,
2208 source: &[u8],
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 go_pkg_index: &HashMap<String, Vec<(String, String)>>,
2214) {
2215 let mut cursor = node.walk();
2216 for child in node.named_children(&mut cursor) {
2217 if child.kind() == "import_spec" || child.kind() == "import_spec_list" {
2218 extract_go_import_specs(child, file_path, source, symbol_table, entity_map, import_table, scopes, go_pkg_index);
2219 } else if child.kind() == "interpreted_string_literal" || child.kind() == "raw_string_literal" {
2220 let path = child.utf8_text(source).unwrap_or("")
2221 .trim_matches('"').trim_matches('`');
2222 let pkg_name = path.rsplit('/').next().unwrap_or(path);
2223 register_go_package_imports(pkg_name, file_path, symbol_table, entity_map, import_table, scopes, go_pkg_index);
2224 }
2225 }
2226}
2227
2228fn extract_go_import_specs(
2229 root: tree_sitter::Node,
2230 file_path: &str,
2231 source: &[u8],
2232 symbol_table: &HashMap<String, Vec<String>>,
2233 entity_map: &HashMap<String, EntityInfo>,
2234 import_table: &mut HashMap<(String, String), String>,
2235 scopes: &mut Vec<Scope>,
2236 go_pkg_index: &HashMap<String, Vec<(String, String)>>,
2237) {
2238 let mut worklist = vec![root];
2239 while let Some(node) = worklist.pop() {
2240 let mut cursor = node.walk();
2241 for child in node.named_children(&mut cursor) {
2242 if child.kind() == "import_spec" {
2243 let path_node = child.child_by_field_name("path")
2244 .or_else(|| child.named_child(0));
2245 if let Some(pn) = path_node {
2246 let path = pn.utf8_text(source).unwrap_or("")
2247 .trim_matches('"').trim_matches('`');
2248 let pkg_name = path.rsplit('/').next().unwrap_or(path);
2249 register_go_package_imports(pkg_name, file_path, symbol_table, entity_map, import_table, scopes, go_pkg_index);
2250 }
2251 } else {
2252 worklist.push(child);
2253 }
2254 }
2255 }
2256}
2257
2258fn register_go_package_imports(
2259 pkg_name: &str,
2260 file_path: &str,
2261 _symbol_table: &HashMap<String, Vec<String>>,
2262 _entity_map: &HashMap<String, EntityInfo>,
2263 import_table: &mut HashMap<(String, String), String>,
2264 scopes: &mut Vec<Scope>,
2265 go_pkg_index: &HashMap<String, Vec<(String, String)>>,
2266) {
2267 if let Some(entries) = go_pkg_index.get(pkg_name) {
2269 for (name, target_id) in entries {
2270 import_table.insert(
2271 (file_path.to_string(), name.clone()),
2272 target_id.clone(),
2273 );
2274 if !scopes.is_empty() {
2275 scopes[0].defs.insert(name.clone(), target_id.clone());
2276 }
2277 }
2278 }
2279}
2280
2281fn resolve_import_name(
2283 original_name: &str,
2284 local_name: &str,
2285 source_module: &str,
2286 file_path: &str,
2287 symbol_table: &HashMap<String, Vec<String>>,
2288 entity_map: &HashMap<String, EntityInfo>,
2289 import_table: &mut HashMap<(String, String), String>,
2290 scopes: &mut Vec<Scope>,
2291) {
2292 if let Some(target_ids) = symbol_table.get(original_name) {
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(".tsx"))
2301 .or_else(|| stem.strip_suffix(".js"))
2302 .or_else(|| stem.strip_suffix(".jsx"))
2303 .or_else(|| stem.strip_suffix(".go"))
2304 .unwrap_or(stem);
2305 stem == source_module
2306 })
2307 });
2308
2309 if let Some(target_id) = target {
2310 import_table.insert(
2311 (file_path.to_string(), local_name.to_string()),
2312 target_id.clone(),
2313 );
2314 if !scopes.is_empty() {
2315 scopes[0]
2316 .defs
2317 .insert(local_name.to_string(), target_id.clone());
2318 }
2319 }
2320 }
2321}
2322
2323fn register_namespace_import(
2327 alias: &str,
2328 source_module: &str,
2329 file_path: &str,
2330 symbol_table: &HashMap<String, Vec<String>>,
2331 entity_map: &HashMap<String, EntityInfo>,
2332 import_table: &mut HashMap<(String, String), String>,
2333 scopes: &mut Vec<Scope>,
2334) {
2335 for (name, target_ids) in symbol_table {
2337 for target_id in target_ids {
2338 if let Some(info) = entity_map.get(target_id) {
2339 let stem = info.file_path.rsplit('/').next().unwrap_or(&info.file_path);
2340 let stem = stem
2341 .strip_suffix(".ts")
2342 .or_else(|| stem.strip_suffix(".js"))
2343 .or_else(|| stem.strip_suffix(".tsx"))
2344 .or_else(|| stem.strip_suffix(".jsx"))
2345 .or_else(|| stem.strip_suffix(".py"))
2346 .unwrap_or(stem);
2347 if stem == source_module && info.parent_id.is_none() {
2348 import_table.insert(
2350 (file_path.to_string(), name.clone()),
2351 target_id.clone(),
2352 );
2353 if !scopes.is_empty() {
2354 scopes[0].defs.insert(name.clone(), target_id.clone());
2355 }
2356 }
2357 }
2358 }
2359 }
2360 let _ = (alias, file_path); }
2366
2367fn extract_python_import(
2368 node: tree_sitter::Node,
2369 file_path: &str,
2370 source: &[u8],
2371 symbol_table: &HashMap<String, Vec<String>>,
2372 entity_map: &HashMap<String, EntityInfo>,
2373 import_table: &mut HashMap<(String, String), String>,
2374 scopes: &mut Vec<Scope>,
2375) {
2376 let module_node = node.child_by_field_name("module_name");
2380 let module_name = module_node
2381 .and_then(|n| n.utf8_text(source).ok())
2382 .unwrap_or("");
2383
2384 let source_module = module_name
2385 .trim_start_matches('.')
2386 .rsplit('.')
2387 .next()
2388 .unwrap_or(module_name.trim_start_matches('.'));
2389
2390 let mut cursor = node.walk();
2392 for child in node.named_children(&mut cursor) {
2393 if child.kind() == "dotted_name" || child.kind() == "aliased_import" {
2394 let (original, local) = if child.kind() == "aliased_import" {
2395 let orig = child
2396 .child_by_field_name("name")
2397 .and_then(|n| n.utf8_text(source).ok())
2398 .unwrap_or("");
2399 let alias = child
2400 .child_by_field_name("alias")
2401 .and_then(|n| n.utf8_text(source).ok())
2402 .unwrap_or(orig);
2403 (orig, alias)
2404 } else {
2405 let name = child.utf8_text(source).unwrap_or("");
2406 (name, name)
2407 };
2408
2409 if original.is_empty() {
2410 continue;
2411 }
2412
2413 if let Some(target_ids) = symbol_table.get(original) {
2415 let target = target_ids.iter().find(|id| {
2416 entity_map.get(*id).map_or(false, |e| {
2417 let stem = e.file_path.rsplit('/').next().unwrap_or(&e.file_path);
2418 let stem = stem
2419 .strip_suffix(".py")
2420 .or_else(|| stem.strip_suffix(".rs"))
2421 .or_else(|| stem.strip_suffix(".ts"))
2422 .or_else(|| stem.strip_suffix(".js"))
2423 .unwrap_or(stem);
2424 stem == source_module
2425 })
2426 });
2427
2428 if let Some(target_id) = target {
2429 import_table.insert(
2430 (file_path.to_string(), local.to_string()),
2431 target_id.clone(),
2432 );
2433 if !scopes.is_empty() {
2435 scopes[0]
2436 .defs
2437 .insert(local.to_string(), target_id.clone());
2438 }
2439 }
2440 }
2441 }
2442 }
2443}
2444
2445fn extract_python_module_import(
2448 node: tree_sitter::Node,
2449 file_path: &str,
2450 source: &[u8],
2451 symbol_table: &HashMap<String, Vec<String>>,
2452 entity_map: &HashMap<String, EntityInfo>,
2453 import_table: &mut HashMap<(String, String), String>,
2454 scopes: &mut Vec<Scope>,
2455) {
2456 let mut cursor = node.walk();
2457 for child in node.named_children(&mut cursor) {
2458 let (module_name, _alias) = match child.kind() {
2459 "dotted_name" => {
2460 let name = child.utf8_text(source).unwrap_or("");
2461 (name, name)
2462 }
2463 "aliased_import" => {
2464 let orig = child
2465 .child_by_field_name("name")
2466 .and_then(|n| n.utf8_text(source).ok())
2467 .unwrap_or("");
2468 let alias = child
2469 .child_by_field_name("alias")
2470 .and_then(|n| n.utf8_text(source).ok())
2471 .unwrap_or(orig);
2472 (orig, alias)
2473 }
2474 _ => continue,
2475 };
2476
2477 if module_name.is_empty() {
2478 continue;
2479 }
2480
2481 let source_module = module_name.rsplit('.').next().unwrap_or(module_name);
2483
2484 register_namespace_import(
2486 _alias,
2487 source_module,
2488 file_path,
2489 symbol_table,
2490 entity_map,
2491 import_table,
2492 scopes,
2493 );
2494 }
2495}
2496
2497fn collect_all_file_refs(
2500 root: tree_sitter::Node,
2501 source: &[u8],
2502 config: &ScopeResolveConfig,
2503) -> Vec<AstRef> {
2504 let mut refs = Vec::new();
2505 let mut worklist = vec![root];
2506 while let Some(node) = worklist.pop() {
2507 let node_row = node.start_position().row;
2508 let kind = node.kind();
2509
2510 if config.call_nodes.contains(&kind) {
2512 match &config.call_style {
2513 CallNodeStyle::FunctionField(field) => {
2514 if let Some(func) = node.child_by_field_name(field) {
2515 extract_call_ref(func, "", "", source, &mut refs, config, node_row);
2517 }
2518 }
2519 CallNodeStyle::DirectMethod { object_field, method_field } => {
2520 let method_name = node.child_by_field_name(method_field)
2521 .and_then(|n| n.utf8_text(source).ok())
2522 .unwrap_or("");
2523 if !method_name.is_empty() && !is_builtin(method_name, config) {
2524 if let Some(obj_node) = node.child_by_field_name(object_field) {
2525 let receiver = obj_node.utf8_text(source).unwrap_or("").to_string();
2526 let receiver = receiver.trim_end_matches('.').to_string();
2527 refs.push(AstRef {
2528 kind: AstRefKind::MethodCall { receiver, method: method_name.to_string() },
2529 row: node_row,
2530 });
2531 } else {
2532 refs.push(AstRef {
2533 kind: AstRefKind::Call(method_name.to_string()),
2534 row: node_row,
2535 });
2536 }
2537 }
2538 }
2539 }
2540 let mut cursor = node.walk();
2541 let children: Vec<_> = node.named_children(&mut cursor).collect();
2542 for child in children.into_iter().rev() {
2543 worklist.push(child);
2544 }
2545 continue;
2546 }
2547
2548 if kind == "macro_invocation" {
2550 if let Some(macro_node) = node.child_by_field_name("macro") {
2551 let macro_name = macro_node.utf8_text(source).unwrap_or("");
2552 if !macro_name.is_empty() && !is_builtin(macro_name, config) {
2553 refs.push(AstRef {
2554 kind: AstRefKind::Call(macro_name.to_string()),
2555 row: node_row,
2556 });
2557 }
2558 }
2559 let mut cursor = node.walk();
2560 let children: Vec<_> = node.named_children(&mut cursor).collect();
2561 for child in children.into_iter().rev() {
2562 worklist.push(child);
2563 }
2564 continue;
2565 }
2566
2567 if config.new_expr_nodes.contains(&kind) {
2569 if let Some(type_node) = node.child_by_field_name(config.new_expr_type_field) {
2570 let name = type_node.utf8_text(source).unwrap_or("");
2571 let name = name.rsplit('.').next().unwrap_or(name);
2572 if !name.is_empty() && !is_builtin(name, config) {
2573 refs.push(AstRef {
2574 kind: AstRefKind::Call(name.to_string()),
2575 row: node_row,
2576 });
2577 }
2578 }
2579 let mut cursor = node.walk();
2580 let children: Vec<_> = node.named_children(&mut cursor).collect();
2581 for child in children.into_iter().rev() {
2582 worklist.push(child);
2583 }
2584 continue;
2585 }
2586
2587 if config.composite_literal_nodes.contains(&kind) {
2589 if let Some(type_node) = node.child_by_field_name("type") {
2590 let name = type_node.utf8_text(source).unwrap_or("");
2591 if name.chars().next().map_or(false, |c| c.is_uppercase())
2592 && !is_builtin(name, config)
2593 {
2594 refs.push(AstRef {
2595 kind: AstRefKind::Call(name.to_string()),
2596 row: node_row,
2597 });
2598 }
2599 }
2600 }
2601
2602 let mut cursor = node.walk();
2604 let children: Vec<_> = node.named_children(&mut cursor).collect();
2605 for child in children.into_iter().rev() {
2606 worklist.push(child);
2607 }
2608 }
2609 refs
2610}
2611
2612fn extract_call_ref(
2614 func: tree_sitter::Node,
2615 _entity_id: &str,
2616 entity_name: &str,
2617 source: &[u8],
2618 refs: &mut Vec<AstRef>,
2619 config: &ScopeResolveConfig,
2620 row: usize,
2621) {
2622 let func_kind = func.kind();
2623
2624 if func_kind == "identifier" {
2625 let name = func.utf8_text(source).unwrap_or("");
2626 if !name.is_empty() && name != entity_name && !is_builtin(name, config) {
2627 refs.push(AstRef {
2628 kind: AstRefKind::Call(name.to_string()),
2629 row,
2630 });
2631 }
2632 return;
2633 }
2634
2635 for ma in config.member_access {
2637 if func_kind == ma.node_kind {
2638 extract_member_call_ref(func, ma.object_field, ma.property_field, source, refs, row);
2639 return;
2640 }
2641 }
2642
2643 if config.scoped_call_nodes.contains(&func_kind) {
2645 let text = func.utf8_text(source).unwrap_or("");
2646 let parts: Vec<&str> = text.split("::").collect();
2647 if parts.len() >= 2 {
2648 let is_path_prefix =
2651 parts[0] == "super" || parts[0] == "self" || parts[0] == "crate";
2652 let method_name = parts[parts.len() - 1];
2653 if is_path_prefix {
2654 if !method_name.is_empty() && !is_builtin(method_name, config) {
2655 refs.push(AstRef {
2656 kind: AstRefKind::Call(method_name.to_string()),
2657 row,
2658 });
2659 }
2660 } else {
2661 let type_name = parts[parts.len() - 2];
2662 if !type_name.is_empty() && !method_name.is_empty() {
2663 refs.push(AstRef {
2664 kind: AstRefKind::Call(method_name.to_string()),
2665 row,
2666 });
2667 if type_name.chars().next().map_or(false, |c| c.is_uppercase())
2668 && !is_builtin(type_name, config)
2669 {
2670 refs.push(AstRef {
2671 kind: AstRefKind::Call(type_name.to_string()),
2672 row,
2673 });
2674 }
2675 }
2676 }
2677 }
2678 }
2679}
2680
2681fn extract_member_call_ref(
2683 node: tree_sitter::Node,
2684 object_field: &str,
2685 attr_field: &str,
2686 source: &[u8],
2687 refs: &mut Vec<AstRef>,
2688 row: usize,
2689) {
2690 let obj = node
2691 .child_by_field_name(object_field)
2692 .and_then(|n| n.utf8_text(source).ok())
2693 .unwrap_or("");
2694 let attr = node
2695 .child_by_field_name(attr_field)
2696 .and_then(|n| n.utf8_text(source).ok())
2697 .unwrap_or("");
2698 if !obj.is_empty() && !attr.is_empty() {
2699 push_method_call_ref(obj, attr, refs, row);
2700 }
2701}
2702
2703fn push_method_call_ref(obj: &str, method: &str, refs: &mut Vec<AstRef>, row: usize) {
2704 refs.push(AstRef {
2705 kind: AstRefKind::MethodCall {
2706 receiver: obj.to_string(),
2707 method: method.to_string(),
2708 },
2709 row,
2710 });
2711}
2712
2713fn resolve_ref(
2715 ast_ref: &AstRef,
2716 scope_idx: usize,
2717 scopes: &[Scope],
2718 symbol_table: &HashMap<String, Vec<String>>,
2719 class_members: &HashMap<String, Vec<(String, String)>>,
2720 import_table: &HashMap<(String, String), String>,
2721 instance_attr_types: &HashMap<(String, String), String>,
2722 entity_map: &HashMap<String, EntityInfo>,
2723 file_path: &str,
2724 from_entity_id: &str,
2725) -> Option<(String, RefType, &'static str)> {
2726 match &ast_ref.kind {
2727 AstRefKind::Call(name) => {
2728 if let Some(eid) = lookup_scope_chain(scope_idx, scopes, name) {
2730 if eid != from_entity_id {
2731 return Some((eid, RefType::Calls, "scope_chain"));
2732 }
2733 }
2734
2735 let key = (file_path.to_string(), name.clone());
2737 if let Some(target_id) = import_table.get(&key) {
2738 return Some((target_id.clone(), RefType::Calls, "import"));
2739 }
2740
2741 if let Some(target_ids) = symbol_table.get(name.as_str()) {
2743 let is_constructor = name.chars().next().map_or(false, |c| c.is_uppercase());
2744 let ref_type = if is_constructor { RefType::TypeRef } else { RefType::Calls };
2745 let target = target_ids
2747 .iter()
2748 .find(|id| {
2749 entity_map
2750 .get(*id)
2751 .map_or(false, |e| e.file_path == file_path)
2752 })
2753 .or_else(|| {
2754 if is_constructor {
2757 target_ids.first()
2758 } else {
2759 None
2760 }
2761 });
2762 if let Some(tid) = target {
2763 return Some((tid.clone(), ref_type, "scope_chain"));
2764 }
2765 }
2766
2767 None
2768 }
2769
2770 AstRefKind::MethodCall { receiver, method } => {
2771 if receiver == "self" || receiver == "this" {
2772 let mut idx = scope_idx;
2774 loop {
2775 if scopes[idx].kind == "class" {
2776 if let Some(eid) = scopes[idx].defs.get(method.as_str()) {
2777 return Some((eid.clone(), RefType::Calls, "scope_chain"));
2778 }
2779 break;
2780 }
2781 match scopes[idx].parent {
2782 Some(p) => idx = p,
2783 None => break,
2784 }
2785 }
2786 return None;
2787 }
2788
2789 if receiver.starts_with("self.") || receiver.starts_with("this.") {
2792 let attr_name = &receiver[5..]; let class_name = find_enclosing_class(scope_idx, scopes, entity_map);
2795 if let Some(cn) = class_name {
2796 if let Some(attr_type) = instance_attr_types.get(&(cn, attr_name.to_string())) {
2798 if let Some(members) = class_members.get(attr_type.as_str()) {
2799 if let Some((_, mid)) = members.iter().find(|(n, _)| n == method) {
2800 return Some((mid.clone(), RefType::Calls, "type_tracking"));
2801 }
2802 }
2803 }
2804 }
2805 }
2806
2807 if receiver.contains('.') && !receiver.starts_with("self.") && !receiver.starts_with("this.") {
2809 if let Some(dot_pos) = receiver.find('.') {
2810 let var_part = &receiver[..dot_pos];
2811 let field_part = &receiver[dot_pos + 1..];
2812 if let Some(var_type) = lookup_type_in_scopes(scope_idx, scopes, var_part) {
2813 if let Some(attr_type) = instance_attr_types.get(&(var_type, field_part.to_string())) {
2814 if let Some(members) = class_members.get(attr_type.as_str()) {
2815 if let Some((_, mid)) = members.iter().find(|(n, _)| n == method) {
2816 return Some((mid.clone(), RefType::Calls, "type_tracking"));
2817 }
2818 }
2819 }
2820 }
2821 }
2822 }
2823
2824 let receiver_type = lookup_type_in_scopes(scope_idx, scopes, receiver);
2826
2827 if let Some(class_name) = receiver_type {
2828 if let Some(members) = class_members.get(class_name.as_str()) {
2829 if let Some((_, mid)) = members.iter().find(|(n, _)| n == method) {
2830 return Some((mid.clone(), RefType::Calls, "type_tracking"));
2831 }
2832 }
2833 }
2834
2835 let key = (file_path.to_string(), receiver.clone());
2837 if let Some(target_id) = import_table.get(&key) {
2838 if let Some(info) = entity_map.get(target_id) {
2839 if matches!(info.entity_type.as_str(), "class" | "struct") {
2840 if let Some(members) = class_members.get(&info.name) {
2841 if let Some((_, mid)) =
2842 members.iter().find(|(n, _)| n == method)
2843 {
2844 return Some((
2845 mid.clone(),
2846 RefType::Calls,
2847 "type_tracking",
2848 ));
2849 }
2850 }
2851 }
2852 }
2853 }
2854
2855 let key = (file_path.to_string(), method.clone());
2858 if let Some(target_id) = import_table.get(&key) {
2859 return Some((target_id.clone(), RefType::Calls, "import"));
2860 }
2861
2862 None
2863 }
2864 }
2865}
2866
2867fn find_enclosing_class(
2869 start_scope: usize,
2870 scopes: &[Scope],
2871 entity_map: &HashMap<String, EntityInfo>,
2872) -> Option<String> {
2873 let mut idx = start_scope;
2874 loop {
2875 if scopes[idx].kind == "class" {
2876 if let Some(ref oid) = scopes[idx].owner_id {
2877 return entity_map.get(oid).map(|e| e.name.clone());
2878 }
2879 }
2880 match scopes[idx].parent {
2881 Some(p) => idx = p,
2882 None => return None,
2883 }
2884 }
2885}
2886
2887fn lookup_scope_chain(
2889 start_scope: usize,
2890 scopes: &[Scope],
2891 name: &str,
2892) -> Option<String> {
2893 let mut idx = start_scope;
2894 loop {
2895 if let Some(eid) = scopes[idx].defs.get(name) {
2896 return Some(eid.clone());
2897 }
2898 match scopes[idx].parent {
2899 Some(p) => idx = p,
2900 None => return None,
2901 }
2902 }
2903}
2904
2905fn lookup_type_in_scopes(
2907 start_scope: usize,
2908 scopes: &[Scope],
2909 var_name: &str,
2910) -> Option<String> {
2911 let mut idx = start_scope;
2912 loop {
2913 if let Some(type_name) = scopes[idx].types.get(var_name) {
2914 return Some(type_name.clone());
2915 }
2916 match scopes[idx].parent {
2917 Some(p) => idx = p,
2918 None => return None,
2919 }
2920 }
2921}
2922
2923fn is_builtin(name: &str, config: &ScopeResolveConfig) -> bool {
2924 if matches!(name, "None" | "True" | "False" | "null" | "undefined" | "nil") {
2926 return true;
2927 }
2928 config.builtins.contains(&name)
2929}