1use std::collections::{HashMap, HashSet};
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::import_resolution::{find_import_target, import_source_matches_file};
33use crate::parser::plugins::code::languages::{
34 get_language_config, AssignmentStrategy, CallNodeStyle, ClassNameField, InitStrategy,
35 ParamNameField, ScopeResolveConfig,
36};
37
38pub struct Scope {
40 parent: Option<usize>,
41 defs: HashMap<String, String>,
43 bindings: HashSet<String>,
45 types: HashMap<String, String>,
47 pending_call_types: HashMap<String, String>,
50 owner_id: Option<String>,
52 kind: &'static str,
54}
55
56struct AstRef {
58 kind: AstRefKind,
60 row: usize,
62}
63
64enum AstRefKind {
65 Call(String),
67 MethodCall { receiver: String, method: String },
69}
70
71pub struct ScopeResult {
73 pub edges: Vec<(String, String, RefType)>,
74 pub resolution_log: Vec<ResolutionEntry>,
76}
77
78#[derive(Clone)]
79pub struct ResolutionEntry {
80 pub from_entity: String,
81 pub reference: String,
82 pub resolved_to: Option<String>,
83 pub method: &'static str, }
85
86pub(crate) struct PreBuiltLookups {
96 pub(crate) symbol_table: Arc<HashMap<String, Vec<String>>>,
97 pub(crate) class_members: HashMap<String, Vec<(String, String)>>,
98 pub(crate) entity_ranges: HashMap<String, Vec<(usize, usize, String)>>,
99 pub(crate) go_pkg_index: HashMap<String, Vec<(String, String)>>,
102}
103
104pub fn resolve_with_scopes(
106 root: &Path,
107 file_paths: &[String],
108 all_entities: &[SemanticEntity],
109 entity_map: &HashMap<String, EntityInfo>,
110 pre_parsed: Option<Vec<(String, String, tree_sitter::Tree)>>,
111) -> ScopeResult {
112 resolve_with_scopes_full(root, file_paths, all_entities, entity_map, pre_parsed, None)
113}
114
115pub(crate) fn resolve_with_scopes_full(
117 root: &Path,
118 file_paths: &[String],
119 all_entities: &[SemanticEntity],
120 entity_map: &HashMap<String, EntityInfo>,
121 pre_parsed: Option<Vec<(String, String, tree_sitter::Tree)>>,
122 pre_built: Option<PreBuiltLookups>,
123) -> ScopeResult {
124 let mut all_edges: Vec<(String, String, RefType)> = Vec::new();
125 let mut log: Vec<ResolutionEntry> = Vec::new();
126
127 let (symbol_table, class_members, entity_ranges, go_pkg_index) = if let Some(pb) = pre_built {
129 (pb.symbol_table, pb.class_members, pb.entity_ranges, pb.go_pkg_index)
130 } else {
131 let mut symbol_table: HashMap<String, Vec<String>> = HashMap::new();
132 let mut class_members: HashMap<String, Vec<(String, String)>> = HashMap::new();
133 let mut entity_ranges: HashMap<String, Vec<(usize, usize, String)>> = HashMap::new();
134
135 for entity in all_entities {
136 symbol_table
137 .entry(entity.name.clone())
138 .or_default()
139 .push(entity.id.clone());
140
141 if let Some(ref pid) = entity.parent_id {
142 if let Some(parent) = entity_map.get(pid) {
143 if matches!(
144 parent.entity_type.as_str(),
145 "class" | "struct" | "interface" | "impl"
146 | "enum" | "protocol_declaration"
147 | "object_declaration" | "companion_object"
148 ) {
149 class_members
150 .entry(parent.name.clone())
151 .or_default()
152 .push((entity.name.clone(), entity.id.clone()));
153 }
154 }
155 }
156
157 if entity.entity_type == "method" && entity.file_path.ends_with(".go") {
158 if let Some(struct_name) = extract_go_receiver_type(&entity.content) {
159 class_members
160 .entry(struct_name)
161 .or_default()
162 .push((entity.name.clone(), entity.id.clone()));
163 }
164 }
165
166 entity_ranges
167 .entry(entity.file_path.clone())
168 .or_default()
169 .push((entity.start_line, entity.end_line, entity.id.clone()));
170 }
171
172 let go_pkg_index = build_go_pkg_index(&symbol_table, entity_map);
174
175 (Arc::new(symbol_table), class_members, entity_ranges, go_pkg_index)
176 };
177
178 let mut entities_by_file: HashMap<&str, Vec<&SemanticEntity>> = HashMap::new();
180 for entity in all_entities {
181 entities_by_file
182 .entry(entity.file_path.as_str())
183 .or_default()
184 .push(entity);
185 }
186
187 let mut children_by_parent: HashMap<&str, Vec<&SemanticEntity>> = HashMap::new();
189 for entity in all_entities {
190 if let Some(ref pid) = entity.parent_id {
191 children_by_parent
192 .entry(pid.as_str())
193 .or_default()
194 .push(entity);
195 }
196 }
197
198 let mut return_type_map: HashMap<String, String> = HashMap::new();
200
201 let mut instance_attr_types: HashMap<(String, String), String> = HashMap::new();
203
204 let mut init_params: HashMap<String, Vec<String>> = HashMap::new();
207 let mut attr_to_param: HashMap<(String, String), String> = HashMap::new();
208
209 let mut owned_parsed_files: Vec<(String, String, tree_sitter::Tree)> = Vec::new();
211 let pre_set: std::collections::HashSet<String> = if let Some(pp) = pre_parsed {
212 let set = pp.iter().map(|(fp, _, _)| fp.clone()).collect();
213 owned_parsed_files = pp;
214 set
215 } else {
216 std::collections::HashSet::new()
217 };
218 for file_path in file_paths {
220 if pre_set.contains(file_path) {
221 continue;
222 }
223 let full_path = root.join(file_path);
224 let content = match std::fs::read_to_string(&full_path) {
225 Ok(c) => c,
226 Err(_) => continue,
227 };
228 let ext = file_path.rfind('.').map(|i| &file_path[i..]).unwrap_or("");
229 let config = match get_language_config(ext) {
230 Some(c) => c,
231 None => continue,
232 };
233 let language = match (config.get_language)() {
234 Some(l) => l,
235 None => continue,
236 };
237 let mut parser = tree_sitter::Parser::new();
238 let _ = parser.set_language(&language);
239 if let Some(tree) = parser.parse(content.as_bytes(), None) {
240 owned_parsed_files.push((file_path.clone(), content, tree));
241 }
242 }
243 let parsed_files: &[(String, String, tree_sitter::Tree)] = &owned_parsed_files;
244
245 let pass1_results: Vec<(
249 HashMap<String, String>,
250 HashMap<(String, String), String>,
251 HashMap<String, Vec<String>>,
252 HashMap<(String, String), String>,
253 )> = maybe_par_iter!(parsed_files)
254 .filter_map(|(file_path, content, tree)| {
255 let source = content.as_bytes();
256 let ext = file_path.rfind('.').map(|i| &file_path[i..]).unwrap_or("");
257 let config = get_language_config(ext).and_then(|c| c.scope_resolve)?;
258
259 let file_entities = entities_by_file.get(file_path.as_str()).map(|v| v.as_slice()).unwrap_or(&[]);
260
261 let mut local_return_type_map: HashMap<String, String> = HashMap::new();
262 scan_return_types(
263 tree.root_node(),
264 file_path,
265 file_entities,
266 source,
267 &mut local_return_type_map,
268 config,
269 );
270
271 let mut local_instance_attr_types: HashMap<(String, String), String> = HashMap::new();
272 let mut local_init_params: HashMap<String, Vec<String>> = HashMap::new();
273 let mut local_attr_to_param: HashMap<(String, String), String> = HashMap::new();
274 scan_init_self_attrs(
275 tree.root_node(),
276 file_path,
277 file_entities,
278 entity_map,
279 source,
280 &mut local_instance_attr_types,
281 &mut local_init_params,
282 &mut local_attr_to_param,
283 config,
284 );
285
286 Some((local_return_type_map, local_instance_attr_types, local_init_params, local_attr_to_param))
287 })
288 .collect();
289
290 for (local_rtm, local_iat, local_ip, local_atp) in pass1_results {
291 return_type_map.extend(local_rtm);
292 instance_attr_types.extend(local_iat);
293 init_params.extend(local_ip);
294 attr_to_param.extend(local_atp);
295 }
296
297 infer_constructor_param_types(
301 parsed_files,
302 &return_type_map,
303 &init_params,
304 &attr_to_param,
305 &symbol_table,
306 entity_map,
307 &mut instance_attr_types,
308 );
309
310 let per_file_results: Vec<(Vec<(String, String, RefType)>, Vec<ResolutionEntry>)> = maybe_par_iter!(parsed_files)
312 .filter_map(|(file_path, content, tree)| {
313 let source = content.as_bytes();
314 let ext = file_path.rfind('.').map(|i| &file_path[i..]).unwrap_or("");
315 let config = get_language_config(ext).and_then(|c| c.scope_resolve)?;
316
317 let mut scopes: Vec<Scope> = vec![Scope {
318 parent: None,
319 defs: HashMap::new(),
320 bindings: HashSet::new(),
321 types: HashMap::new(),
322 pending_call_types: HashMap::new(),
323 owner_id: None,
324 kind: "module",
325 }];
326
327 let mut entity_scope_map: HashMap<String, usize> = HashMap::new();
328 let mut entity_inner_scope: HashMap<String, usize> = HashMap::new();
329
330 if let Some(ranges) = entity_ranges.get(file_path.as_str()) {
331 for (_start, _end, eid) in ranges {
332 if let Some(info) = entity_map.get(eid) {
333 if info.parent_id.is_none() {
334 scopes[0].defs.insert(info.name.clone(), eid.clone());
335 entity_scope_map.insert(eid.clone(), 0);
336 }
337 }
338 }
339 }
340
341 let file_entities: Vec<&SemanticEntity> = entities_by_file
342 .get(file_path.as_str())
343 .map(|v| v.as_slice())
344 .unwrap_or(&[])
345 .to_vec();
346
347 build_scopes_from_ast(
348 tree.root_node(),
349 0,
350 &mut scopes,
351 &mut entity_scope_map,
352 &mut entity_inner_scope,
353 &file_entities,
354 &children_by_parent,
355 entity_map,
356 file_path,
357 source,
358 config,
359 );
360
361 let mut local_import_table: HashMap<(String, String), String> = HashMap::new();
362 extract_imports_from_ast(
363 tree.root_node(),
364 file_path,
365 source,
366 &symbol_table,
367 entity_map,
368 &mut local_import_table,
369 &mut scopes,
370 config,
371 &go_pkg_index,
372 );
373
374 inject_return_type_bindings(
376 &entity_inner_scope,
377 &mut scopes,
378 &return_type_map,
379 &local_import_table,
380 file_path,
381 entity_map,
382 );
383
384 let mut file_edges: Vec<(String, String, RefType)> = Vec::new();
385 let mut file_log: Vec<ResolutionEntry> = Vec::new();
386
387 let all_file_refs = collect_all_file_refs(tree.root_node(), source, config);
389
390 for entity in &file_entities {
391 let scope_idx = entity_inner_scope
392 .get(&entity.id)
393 .or_else(|| entity_scope_map.get(&entity.id))
394 .copied()
395 .unwrap_or(0);
396
397 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) {
402 let is_self_ref = match &ast_ref.kind {
404 AstRefKind::Call(name) => name == &entity.name,
405 AstRefKind::MethodCall { .. } => false,
406 };
407 if is_self_ref {
408 continue;
409 }
410
411 let allow_cross_file = config.import_extractor.is_none();
414 let resolution = resolve_ref(
415 ast_ref,
416 scope_idx,
417 &scopes,
418 &symbol_table,
419 &class_members,
420 &local_import_table,
421 &instance_attr_types,
422 entity_map,
423 file_path,
424 &entity.id,
425 allow_cross_file,
426 );
427
428 if let Some((target_id, ref_type, method)) = resolution {
429 if target_id != entity.id {
430 let is_parent_child = entity
431 .parent_id
432 .as_ref()
433 .map_or(false, |pid| pid == &target_id || entity_map.get(&target_id).map_or(false, |t| t.parent_id.as_ref() == Some(&entity.id)));
434
435 if !is_parent_child {
436 file_edges.push((
437 entity.id.clone(),
438 target_id.clone(),
439 ref_type,
440 ));
441 file_log.push(ResolutionEntry {
442 from_entity: entity.id.clone(),
443 reference: ref_description(ast_ref),
444 resolved_to: Some(target_id),
445 method,
446 });
447 }
448 }
449 } else {
450 file_log.push(ResolutionEntry {
451 from_entity: entity.id.clone(),
452 reference: ref_description(ast_ref),
453 resolved_to: None,
454 method: "unresolved",
455 });
456 }
457 }
458 }
459
460 Some((file_edges, file_log))
461 })
462 .collect();
463
464 for (file_edges, file_log) in per_file_results {
465 all_edges.extend(file_edges);
466 log.extend(file_log);
467 }
468
469 let mut seen: std::collections::HashSet<(String, String)> = std::collections::HashSet::with_capacity(all_edges.len());
471 let deduped_edges: Vec<(String, String, RefType)> = {
472 let mut result = Vec::with_capacity(all_edges.len());
473 for edge in all_edges {
474 if seen.insert((edge.0.clone(), edge.1.clone())) {
475 result.push(edge);
476 }
477 }
478 result
479 };
480 let all_edges = deduped_edges;
481
482 ScopeResult {
483 edges: all_edges,
484 resolution_log: log,
485 }
486}
487
488fn ref_description(ast_ref: &AstRef) -> String {
489 match &ast_ref.kind {
490 AstRefKind::Call(name) => format!("{}()", name),
491 AstRefKind::MethodCall { receiver, method } => format!("{}.{}()", receiver, method),
492 }
493}
494
495fn build_scopes_from_ast(
500 root: tree_sitter::Node,
501 root_scope: usize,
502 scopes: &mut Vec<Scope>,
503 entity_scope_map: &mut HashMap<String, usize>,
504 entity_inner_scope: &mut HashMap<String, usize>,
505 file_entities: &[&SemanticEntity],
506 children_by_parent: &HashMap<&str, Vec<&SemanticEntity>>,
507 entity_map: &HashMap<String, EntityInfo>,
508 _file_path: &str,
509 source: &[u8],
510 config: &ScopeResolveConfig,
511) {
512 let mut worklist: Vec<(tree_sitter::Node, usize)> = vec![(root, root_scope)];
514
515 while let Some((node, current_scope)) = worklist.pop() {
516 let kind = node.kind();
517
518 let is_class_like = config.class_scope_nodes.contains(&kind);
520
521 let is_impl = config.impl_scope_nodes.contains(&kind);
523
524 if is_class_like || is_impl {
525 let class_name = if is_impl {
526 node.child_by_field_name("type")
527 .and_then(|n| n.utf8_text(source).ok())
528 .unwrap_or("")
529 } else {
530 match &config.class_name_field {
531 ClassNameField::Simple(field) => {
532 node.child_by_field_name(field)
533 .and_then(|n| n.utf8_text(source).ok())
534 .unwrap_or("")
535 }
536 ClassNameField::TypeSpec { spec_kind, field } => {
537 let mut name = "";
538 let mut cursor = node.walk();
539 for child in node.named_children(&mut cursor) {
540 if child.kind() == *spec_kind {
541 name = child
542 .child_by_field_name(field)
543 .and_then(|n| n.utf8_text(source).ok())
544 .unwrap_or("");
545 break;
546 }
547 }
548 name
549 }
550 ClassNameField::ImplType(field) => {
551 node.child_by_field_name(field)
552 .and_then(|n| n.utf8_text(source).ok())
553 .unwrap_or("")
554 }
555 }
556 };
557
558 let class_entity = file_entities.iter().find(|e| {
559 e.name == class_name
560 && matches!(
561 e.entity_type.as_str(),
562 "class" | "struct" | "interface"
563 | "enum" | "protocol_declaration"
564 | "object_declaration" | "companion_object"
565 )
566 }).copied();
567
568 if let Some(ce) = class_entity {
569 let existing_scope = entity_inner_scope.get(&ce.id).copied();
570
571 let class_scope_idx = if let Some(idx) = existing_scope {
572 idx
573 } else {
574 let idx = scopes.len();
575 scopes.push(Scope {
576 parent: Some(current_scope),
577 defs: HashMap::new(),
578 bindings: HashSet::new(),
579 types: HashMap::new(),
580 pending_call_types: HashMap::new(),
581 owner_id: Some(ce.id.clone()),
582 kind: "class",
583 });
584 entity_scope_map.insert(ce.id.clone(), current_scope);
585 entity_inner_scope.insert(ce.id.clone(), idx);
586 idx
587 };
588
589 if let Some(children) = children_by_parent.get(ce.id.as_str()) {
590 for entity in children {
591 scopes[class_scope_idx]
592 .defs
593 .insert(entity.name.clone(), entity.id.clone());
594 entity_scope_map.insert(entity.id.clone(), class_scope_idx);
595 }
596 }
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 } else if !is_impl {
605 let class_scope_idx = scopes.len();
606 scopes.push(Scope {
607 parent: Some(current_scope),
608 defs: HashMap::new(),
609 bindings: HashSet::new(),
610 types: HashMap::new(),
611 pending_call_types: HashMap::new(),
612 owner_id: None,
613 kind: "class",
614 });
615 let mut cursor = node.walk();
616 let children: Vec<_> = node.named_children(&mut cursor).collect();
617 for child in children.into_iter().rev() {
618 worklist.push((child, class_scope_idx));
619 }
620 continue;
621 }
622 }
623
624 if kind == "mod_item" {
627 let mod_name = node
628 .child_by_field_name("name")
629 .and_then(|n| n.utf8_text(source).ok())
630 .unwrap_or("");
631 let mod_scope_idx = scopes.len();
632 scopes.push(Scope {
633 parent: Some(current_scope),
634 defs: HashMap::new(),
635 bindings: HashSet::new(),
636 types: HashMap::new(),
637 pending_call_types: HashMap::new(),
638 owner_id: None,
639 kind: "module",
640 });
641
642 let mod_entity = file_entities.iter().find(|e| {
644 e.name == mod_name
645 && e.entity_type == "module"
646 && {
647 let line = node.start_position().row + 1;
648 e.start_line <= line && line <= e.end_line
649 }
650 }).copied();
651
652 if let Some(me) = mod_entity {
653 scopes[mod_scope_idx].owner_id = Some(me.id.clone());
654 entity_scope_map.entry(me.id.clone()).or_insert(current_scope);
655 entity_inner_scope.insert(me.id.clone(), mod_scope_idx);
656
657 if let Some(children) = children_by_parent.get(me.id.as_str()) {
659 for child_entity in children {
660 scopes[mod_scope_idx]
661 .defs
662 .insert(child_entity.name.clone(), child_entity.id.clone());
663 entity_scope_map.insert(child_entity.id.clone(), mod_scope_idx);
664 }
665 }
666 }
667
668 let mut cursor = node.walk();
669 let children: Vec<_> = node.named_children(&mut cursor).collect();
670 for child in children.into_iter().rev() {
671 worklist.push((child, mod_scope_idx));
672 }
673 continue;
674 }
675
676 let is_function_like = config.function_scope_nodes.contains(&kind);
678
679 if is_function_like {
680 let func_name = node.child_by_field_name("name")
681 .and_then(|n| n.utf8_text(source).ok())
682 .unwrap_or("");
683
684 let parent_scope = if config.external_method && kind == "method_declaration" {
685 let receiver_type = node.utf8_text(source).ok()
686 .and_then(|t| extract_go_receiver_type(t));
687 if let Some(ref struct_name) = receiver_type {
688 let found = scopes.iter().enumerate().find(|(_, s)| {
689 s.kind == "class" && s.owner_id.as_ref().map_or(false, |oid| {
690 entity_map.get(oid).map_or(false, |e| e.name == *struct_name)
691 })
692 });
693 found.map(|(idx, _)| idx).unwrap_or(current_scope)
694 } else {
695 current_scope
696 }
697 } else {
698 current_scope
699 };
700
701 let func_scope_idx = scopes.len();
702 scopes.push(Scope {
703 parent: Some(parent_scope),
704 defs: HashMap::new(),
705 bindings: HashSet::new(),
706 types: HashMap::new(),
707 pending_call_types: HashMap::new(),
708 owner_id: None,
709 kind: "function",
710 });
711
712 let func_entity = file_entities.iter().find(|e| {
713 e.name == func_name && {
714 let line = node.start_position().row + 1;
715 e.start_line <= line && line <= e.end_line
716 }
717 }).copied();
718
719 if let Some(fe) = func_entity {
720 scopes[func_scope_idx].owner_id = Some(fe.id.clone());
721 entity_scope_map.entry(fe.id.clone()).or_insert(parent_scope);
722 entity_inner_scope.insert(fe.id.clone(), func_scope_idx);
723 if config.external_method && kind == "method_declaration" && parent_scope != current_scope {
724 scopes[parent_scope].defs.insert(fe.name.clone(), fe.id.clone());
725 }
726 }
727
728 scan_assignments(node, func_scope_idx, scopes, source, config);
729 scan_function_params(node, func_scope_idx, scopes, source, config);
730
731 if config.external_method && kind == "method_declaration" {
732 if let Some(receiver) = node.child_by_field_name("receiver") {
733 let mut rcursor = receiver.walk();
734 for param in receiver.named_children(&mut rcursor) {
735 if param.kind() == "parameter_declaration" {
736 let param_name = param
737 .child_by_field_name("name")
738 .and_then(|n| n.utf8_text(source).ok())
739 .unwrap_or("");
740 let param_type = param
741 .child_by_field_name("type")
742 .map(|n| extract_base_type(n, source))
743 .unwrap_or_default();
744 if !param_name.is_empty() && !param_type.is_empty() {
745 scopes[func_scope_idx]
746 .types
747 .insert(param_name.to_string(), param_type);
748 }
749 }
750 }
751 }
752 }
753
754 let mut cursor = node.walk();
755 let children: Vec<_> = node.named_children(&mut cursor).collect();
756 for child in children.into_iter().rev() {
757 worklist.push((child, func_scope_idx));
758 }
759 continue;
760 }
761
762 let mut cursor = node.walk();
763 let children: Vec<_> = node.named_children(&mut cursor).collect();
764 for child in children.into_iter().rev() {
765 worklist.push((child, current_scope));
766 }
767 }
768}
769
770fn scan_assignments(
772 root: tree_sitter::Node,
773 scope_idx: usize,
774 scopes: &mut Vec<Scope>,
775 source: &[u8],
776 config: &ScopeResolveConfig,
777) {
778 let mut worklist = vec![root];
779 while let Some(node) = worklist.pop() {
780 let mut cursor = node.walk();
781 for child in node.named_children(&mut cursor) {
782 let ck = child.kind();
783
784 for rule in config.assignment_rules {
786 if ck == rule.node_kind {
787 match rule.strategy {
788 AssignmentStrategy::LeftRight => {
789 scan_single_assignment(child, scope_idx, scopes, source);
790 }
791 AssignmentStrategy::Declarators => {
792 scan_ts_var_declaration(child, scope_idx, scopes, source);
793 }
794 AssignmentStrategy::PatternBased => {
795 scan_rust_let_declaration(child, scope_idx, scopes, source);
796 }
797 AssignmentStrategy::ShortVar => {
798 scan_go_short_var(child, scope_idx, scopes, source);
799 }
800 AssignmentStrategy::VarSpec => {
801 scan_go_var_declaration(child, scope_idx, scopes, source);
802 }
803 }
804 }
805 }
806
807 if config.assignment_recurse_into.contains(&ck) {
809 worklist.push(child);
810 }
811 }
812 }
813}
814
815fn scan_function_params(
818 node: tree_sitter::Node,
819 scope_idx: usize,
820 scopes: &mut Vec<Scope>,
821 source: &[u8],
822 config: &ScopeResolveConfig,
823) {
824 let mut params_node = node.child_by_field_name("parameters");
828 if params_node.is_none() {
829 let mut c = node.walk();
831 for ch in node.named_children(&mut c) {
832 if ch.kind() == "function_value_parameters" {
833 params_node = Some(ch);
834 break;
835 }
836 }
837 }
838
839 let (iter_node, use_direct) = match params_node {
842 Some(p) => (p, false),
843 None => (node, true),
844 };
845
846 let mut cursor = iter_node.walk();
847 for child in iter_node.named_children(&mut cursor) {
848 if use_direct {
850 let is_param = config.param_rules.iter().any(|r| child.kind() == r.node_kind);
851 if !is_param {
852 continue;
853 }
854 }
855 for rule in config.param_rules {
856 if child.kind() != rule.node_kind {
857 continue;
858 }
859
860 let param_name = match &rule.name_field {
861 ParamNameField::Simple(field) => {
862 child.child_by_field_name(field)
863 .and_then(|n| n.utf8_text(source).ok())
864 .unwrap_or("")
865 }
866 ParamNameField::WithFallback(field) => {
867 child.child_by_field_name(field)
868 .or_else(|| child.named_child(0).filter(|n| n.kind() == "identifier"))
869 .and_then(|n| n.utf8_text(source).ok())
870 .unwrap_or("")
871 }
872 ParamNameField::RustPattern => {
873 child.child_by_field_name("pattern")
874 .and_then(|n| {
875 if n.kind() == "identifier" {
876 n.utf8_text(source).ok()
877 } else if n.kind() == "mut_pattern" {
878 n.named_child(0).and_then(|c| c.utf8_text(source).ok())
879 } else if n.kind() == "reference_pattern" {
880 n.named_child(0).and_then(|c| {
881 if c.kind() == "identifier" {
882 c.utf8_text(source).ok()
883 } else if c.kind() == "mut_pattern" {
884 c.named_child(0).and_then(|cc| cc.utf8_text(source).ok())
885 } else {
886 None
887 }
888 })
889 } else {
890 None
891 }
892 })
893 .unwrap_or("")
894 }
895 };
896
897 if param_name.is_empty() || rule.skip_names.contains(¶m_name) {
898 continue;
899 }
900 scopes[scope_idx].bindings.insert(param_name.to_string());
901
902 let mut type_node = child.child_by_field_name(rule.type_field);
905 if type_node.is_none() {
906 let mut tc = child.walk();
907 for ch in child.named_children(&mut tc) {
908 if matches!(ch.kind(), "user_type" | "type_annotation" | "type_identifier") {
909 type_node = Some(ch);
910 break;
911 }
912 }
913 }
914 if let Some(tn) = type_node {
915 let type_text = extract_base_type(tn, source);
916 if !type_text.is_empty()
917 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
918 {
919 scopes[scope_idx]
920 .types
921 .insert(param_name.to_string(), type_text);
922 }
923 }
924 }
925 }
926}
927
928fn scan_single_assignment(
930 node: tree_sitter::Node,
931 scope_idx: usize,
932 scopes: &mut Vec<Scope>,
933 source: &[u8],
934) {
935 let assign = if node.kind() == "assignment" {
936 node
937 } else {
938 let mut cursor = node.walk();
939 let children: Vec<_> = node.named_children(&mut cursor).collect();
940 match children.into_iter().find(|c| c.kind() == "assignment" || c.kind() == "assignment_expression") {
941 Some(a) => a,
942 None => return,
943 }
944 };
945
946 let left = match assign.child_by_field_name("left") {
947 Some(l) => l,
948 None => return,
949 };
950 let right = match assign.child_by_field_name("right") {
951 Some(r) => r,
952 None => return,
953 };
954
955 if left.kind() != "identifier" {
956 return;
957 }
958 let var_name = match left.utf8_text(source) {
959 Ok(n) => n.to_string(),
960 Err(_) => return,
961 };
962 scopes[scope_idx].bindings.insert(var_name.clone());
963
964 record_type_from_rhs(right, &var_name, scope_idx, scopes, source);
965}
966
967fn scan_ts_var_declaration(
970 node: tree_sitter::Node,
971 scope_idx: usize,
972 scopes: &mut Vec<Scope>,
973 source: &[u8],
974) {
975 let mut cursor = node.walk();
976 for child in node.named_children(&mut cursor) {
977 if child.kind() == "variable_declarator" {
978 let var_name = child
979 .child_by_field_name("name")
980 .and_then(|n| n.utf8_text(source).ok())
981 .unwrap_or("")
982 .to_string();
983 if var_name.is_empty() {
984 continue;
985 }
986 scopes[scope_idx].bindings.insert(var_name.clone());
987
988 if let Some(type_ann) = child.child_by_field_name("type") {
990 let type_text = extract_base_type(type_ann, source);
991 if !type_text.is_empty()
992 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
993 {
994 scopes[scope_idx]
995 .types
996 .insert(var_name.clone(), type_text);
997 continue;
998 }
999 }
1000
1001 if let Some(value) = child.child_by_field_name("value") {
1003 record_type_from_rhs(value, &var_name, scope_idx, scopes, source);
1004 }
1005 }
1006 }
1007
1008 if node.kind() == "property_declaration" {
1012 let var_name_opt = node
1014 .child_by_field_name("name")
1015 .and_then(|n| n.utf8_text(source).ok());
1016 let var_name = if let Some(name) = var_name_opt {
1017 name.to_string()
1018 } else {
1019 let mut c = node.walk();
1021 let mut found = String::new();
1022 for ch in node.named_children(&mut c) {
1023 if ch.kind() == "pattern" {
1024 if let Some(id) = ch.named_child(0) {
1025 if id.kind() == "simple_identifier" || id.kind() == "identifier" {
1026 if let Ok(name) = id.utf8_text(source) {
1027 found = name.to_string();
1028 }
1029 }
1030 }
1031 break;
1032 }
1033 }
1034 found
1035 };
1036
1037 if !var_name.is_empty() {
1038 if let Some(type_ann) = node.child_by_field_name("type") {
1040 let type_text = extract_base_type(type_ann, source);
1041 if !type_text.is_empty()
1042 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1043 {
1044 scopes[scope_idx].types.insert(var_name.clone(), type_text);
1045 return;
1046 }
1047 }
1048 if let Some(value) = node.child_by_field_name("value") {
1050 record_type_from_rhs(value, &var_name, scope_idx, scopes, source);
1051 } else {
1052 let mut c = node.walk();
1054 for ch in node.named_children(&mut c) {
1055 if ch.kind() == "call_expression" || ch.kind() == "new_expression" {
1056 record_type_from_rhs(ch, &var_name, scope_idx, scopes, source);
1057 break;
1058 }
1059 }
1060 }
1061 return;
1062 }
1063
1064 let mut c = node.walk();
1066 for child in node.named_children(&mut c) {
1067 if child.kind() == "variable_declaration" {
1068 let var_name_kt = child
1069 .child_by_field_name("name")
1070 .or_else(|| child.named_child(0).filter(|n| n.kind() == "identifier"))
1071 .and_then(|n| n.utf8_text(source).ok())
1072 .unwrap_or("")
1073 .to_string();
1074
1075 if !var_name_kt.is_empty() {
1076 if let Some(type_ann) = node.child_by_field_name("type") {
1078 let type_text = extract_base_type(type_ann, source);
1079 if !type_text.is_empty()
1080 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1081 {
1082 scopes[scope_idx].types.insert(var_name_kt.clone(), type_text);
1083 return;
1084 }
1085 }
1086 let mut c2 = node.walk();
1088 for sibling in node.named_children(&mut c2) {
1089 if sibling.kind() == "call_expression" || sibling.kind() == "new_expression" {
1090 record_type_from_rhs(sibling, &var_name_kt, scope_idx, scopes, source);
1091 break;
1092 }
1093 }
1094 }
1095 break;
1096 }
1097 }
1098 }
1099}
1100
1101fn scan_rust_let_declaration(
1103 node: tree_sitter::Node,
1104 scope_idx: usize,
1105 scopes: &mut Vec<Scope>,
1106 source: &[u8],
1107) {
1108 let var_name = node
1109 .child_by_field_name("pattern")
1110 .and_then(|n| {
1111 if n.kind() == "identifier" {
1113 n.utf8_text(source).ok()
1114 } else if n.kind() == "mut_pattern" {
1115 n.named_child(0).and_then(|c| c.utf8_text(source).ok())
1116 } else {
1117 None
1118 }
1119 })
1120 .unwrap_or("")
1121 .to_string();
1122
1123 if var_name.is_empty() {
1124 return;
1125 }
1126 scopes[scope_idx].bindings.insert(var_name.clone());
1127
1128 if let Some(type_node) = node.child_by_field_name("type") {
1130 let type_text = extract_base_type(type_node, source);
1131 if !type_text.is_empty()
1132 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1133 {
1134 scopes[scope_idx]
1135 .types
1136 .insert(var_name, type_text);
1137 return;
1138 }
1139 }
1140
1141 if let Some(value) = node.child_by_field_name("value") {
1143 record_type_from_rhs(value, &var_name, scope_idx, scopes, source);
1144 }
1145}
1146
1147fn scan_go_short_var(
1149 node: tree_sitter::Node,
1150 scope_idx: usize,
1151 scopes: &mut Vec<Scope>,
1152 source: &[u8],
1153) {
1154 let left = match node.child_by_field_name("left") {
1155 Some(l) => l,
1156 None => return,
1157 };
1158 let right = match node.child_by_field_name("right") {
1159 Some(r) => r,
1160 None => return,
1161 };
1162
1163 let var_name = if left.kind() == "expression_list" {
1165 left.named_child(0)
1166 .and_then(|n| n.utf8_text(source).ok())
1167 .unwrap_or("")
1168 .to_string()
1169 } else {
1170 left.utf8_text(source).unwrap_or("").to_string()
1171 };
1172
1173 if var_name.is_empty() {
1174 return;
1175 }
1176 scopes[scope_idx].bindings.insert(var_name.clone());
1177
1178 let rhs = if right.kind() == "expression_list" {
1179 match right.named_child(0) {
1180 Some(n) => n,
1181 None => return,
1182 }
1183 } else {
1184 right
1185 };
1186
1187 record_type_from_rhs(rhs, &var_name, scope_idx, scopes, source);
1188}
1189
1190fn scan_go_var_declaration(
1192 node: tree_sitter::Node,
1193 scope_idx: usize,
1194 scopes: &mut Vec<Scope>,
1195 source: &[u8],
1196) {
1197 let mut cursor = node.walk();
1198 for child in node.named_children(&mut cursor) {
1199 if child.kind() == "var_spec" {
1200 let var_name = child
1201 .child_by_field_name("name")
1202 .and_then(|n| n.utf8_text(source).ok())
1203 .unwrap_or("")
1204 .to_string();
1205 if var_name.is_empty() {
1206 if let Some(first) = child.named_child(0) {
1208 if first.kind() == "identifier" {
1209 let name = first.utf8_text(source).unwrap_or("").to_string();
1210 if !name.is_empty() {
1211 scopes[scope_idx].bindings.insert(name.clone());
1212 if let Some(type_node) = child.child_by_field_name("type") {
1214 let type_text = extract_base_type(type_node, source);
1215 if !type_text.is_empty()
1216 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1217 {
1218 scopes[scope_idx].types.insert(name, type_text);
1219 }
1220 }
1221 }
1222 }
1223 }
1224 continue;
1225 }
1226 scopes[scope_idx].bindings.insert(var_name.clone());
1227
1228 if let Some(type_node) = child.child_by_field_name("type") {
1230 let type_text = extract_base_type(type_node, source);
1231 if !type_text.is_empty()
1232 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1233 {
1234 scopes[scope_idx]
1235 .types
1236 .insert(var_name, type_text);
1237 continue;
1238 }
1239 }
1240
1241 if let Some(value) = child.child_by_field_name("value") {
1243 let rhs = if value.kind() == "expression_list" {
1244 value.named_child(0).unwrap_or(value)
1245 } else {
1246 value
1247 };
1248 record_type_from_rhs(rhs, &var_name, scope_idx, scopes, source);
1249 }
1250 }
1251 }
1252}
1253
1254fn record_type_from_rhs(
1257 rhs: tree_sitter::Node,
1258 var_name: &str,
1259 scope_idx: usize,
1260 scopes: &mut Vec<Scope>,
1261 source: &[u8],
1262) {
1263 match rhs.kind() {
1264 "call" | "call_expression" => {
1266 let func_node = rhs
1267 .child_by_field_name("function")
1268 .or_else(|| rhs.named_child(0));
1269 if let Some(func) = func_node {
1270 if func.kind() == "identifier" || func.kind() == "simple_identifier" || func.kind() == "type_identifier" {
1271 let name = func.utf8_text(source).unwrap_or("");
1272 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1273 scopes[scope_idx]
1274 .types
1275 .insert(var_name.to_string(), name.to_string());
1276 } else {
1277 scopes[scope_idx]
1278 .pending_call_types
1279 .insert(var_name.to_string(), name.to_string());
1280 }
1281 }
1282 if func.kind() == "scoped_identifier" {
1284 let text = func.utf8_text(source).unwrap_or("");
1285 let parts: Vec<&str> = text.split("::").collect();
1286 if parts.len() >= 2 {
1287 let type_name = parts[0];
1288 let method_name = parts[parts.len() - 1];
1289 if type_name.chars().next().map_or(false, |c| c.is_uppercase()) {
1290 scopes[scope_idx]
1291 .types
1292 .insert(var_name.to_string(), type_name.to_string());
1293 } else {
1294 scopes[scope_idx]
1295 .pending_call_types
1296 .insert(var_name.to_string(), method_name.to_string());
1297 }
1298 }
1299 }
1300 if func.kind() == "selector_expression" {
1302 let field = func
1303 .child_by_field_name("field")
1304 .and_then(|n| n.utf8_text(source).ok())
1305 .unwrap_or("");
1306 if let Some(type_name) = field.strip_prefix("New") {
1308 if !type_name.is_empty()
1309 && type_name.chars().next().map_or(false, |c| c.is_uppercase())
1310 {
1311 scopes[scope_idx]
1312 .types
1313 .insert(var_name.to_string(), type_name.to_string());
1314 }
1315 } else if field.starts_with("Get") || field.chars().next().map_or(false, |c| c.is_uppercase()) {
1316 scopes[scope_idx]
1318 .pending_call_types
1319 .insert(var_name.to_string(), field.to_string());
1320 }
1321 }
1322 }
1323 }
1324 "new_expression" => {
1326 if let Some(constructor) = rhs.child_by_field_name("constructor") {
1327 let name = constructor.utf8_text(source).unwrap_or("");
1328 if !name.is_empty() {
1329 scopes[scope_idx]
1330 .types
1331 .insert(var_name.to_string(), name.to_string());
1332 }
1333 }
1334 }
1335 "composite_literal" => {
1337 if let Some(type_node) = rhs.child_by_field_name("type") {
1338 let name = type_node.utf8_text(source).unwrap_or("");
1339 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1340 scopes[scope_idx]
1341 .types
1342 .insert(var_name.to_string(), name.to_string());
1343 }
1344 }
1345 }
1346 _ => {}
1347 }
1348}
1349
1350fn extract_base_type(type_node: tree_sitter::Node, source: &[u8]) -> String {
1353 let text = type_node.utf8_text(source).unwrap_or("").trim().to_string();
1354 let text = text.trim_start_matches('&').trim_start_matches('*');
1356 let text = text.strip_prefix("mut ").unwrap_or(text).trim_start();
1357 let text = if let Some(i) = text.find('<') {
1359 &text[..i]
1360 } else if let Some(i) = text.find('[') {
1361 &text[..i]
1362 } else {
1363 text
1364 };
1365 let text = text.trim();
1367 let text = text.trim_start_matches(':').trim();
1369 text.to_string()
1370}
1371
1372pub fn extract_go_receiver_type(content: &str) -> Option<String> {
1374 let after_func = content.strip_prefix("func")?.trim_start();
1375 let paren_start = after_func.find('(')?;
1376 let paren_end = after_func.find(')')?;
1377 let receiver_block = &after_func[paren_start + 1..paren_end];
1378 let parts: Vec<&str> = receiver_block.split_whitespace().collect();
1380 let type_str = parts.last()?;
1381 let name = type_str.trim_start_matches('*');
1382 if name.is_empty() {
1383 None
1384 } else {
1385 Some(name.to_string())
1386 }
1387}
1388
1389fn build_go_pkg_index(
1392 symbol_table: &HashMap<String, Vec<String>>,
1393 entity_map: &HashMap<String, EntityInfo>,
1394) -> HashMap<String, Vec<(String, String)>> {
1395 let mut idx: HashMap<String, Vec<(String, String)>> = HashMap::new();
1396 for (name, target_ids) in symbol_table.iter() {
1397 for target_id in target_ids {
1398 if let Some(entity) = entity_map.get(target_id) {
1399 if !entity.file_path.ends_with(".go") {
1400 continue;
1401 }
1402 let file_stem = entity.file_path.rsplit('/').next().unwrap_or(&entity.file_path);
1403 let file_stem = file_stem.strip_suffix(".go").unwrap_or(file_stem);
1404 idx.entry(file_stem.to_string())
1405 .or_default()
1406 .push((name.clone(), target_id.clone()));
1407 if let Some(parent_start) = entity.file_path.rfind('/') {
1408 let parent_path = &entity.file_path[..parent_start];
1409 if let Some(dir_name_start) = parent_path.rfind('/') {
1410 let dir_name = &parent_path[dir_name_start + 1..];
1411 if dir_name != file_stem {
1412 idx.entry(dir_name.to_string())
1413 .or_default()
1414 .push((name.clone(), target_id.clone()));
1415 }
1416 } else if !parent_path.is_empty() && parent_path != file_stem {
1417 idx.entry(parent_path.to_string())
1418 .or_default()
1419 .push((name.clone(), target_id.clone()));
1420 }
1421 }
1422 }
1423 }
1424 }
1425 idx
1426}
1427
1428fn scan_return_types(
1430 root: tree_sitter::Node,
1431 _file_path: &str,
1432 file_entities: &[&SemanticEntity],
1433 source: &[u8],
1434 return_type_map: &mut HashMap<String, String>,
1435 config: &ScopeResolveConfig,
1436) {
1437 let mut worklist = vec![root];
1438 while let Some(node) = worklist.pop() {
1439 let kind = node.kind();
1440
1441 let is_func = config.function_scope_nodes.contains(&kind);
1442
1443 if is_func {
1444 let func_name = node
1445 .child_by_field_name("name")
1446 .and_then(|n| n.utf8_text(source).ok())
1447 .unwrap_or("");
1448
1449 let func_entity = file_entities.iter().find(|e| {
1450 e.name == func_name && {
1451 let line = node.start_position().row + 1;
1452 e.start_line <= line && line <= e.end_line
1453 }
1454 }).copied();
1455
1456 if let Some(fe) = func_entity {
1457 let ret_type = config.return_type_field.and_then(|field| {
1459 node.child_by_field_name(field)
1460 .map(|n| extract_base_type(n, source))
1461 .filter(|t| !t.is_empty() && t.chars().next().map_or(false, |c| c.is_uppercase()))
1462 });
1463
1464 if let Some(rt) = ret_type {
1465 return_type_map.insert(fe.id.clone(), rt);
1466 } else {
1467 if let Some(ret_type) = find_return_constructor(node, source) {
1469 return_type_map.insert(fe.id.clone(), ret_type);
1470 }
1471 }
1472 }
1473 }
1474
1475 let mut cursor = node.walk();
1476 let children: Vec<_> = node.named_children(&mut cursor).collect();
1477 for child in children.into_iter().rev() {
1478 worklist.push(child);
1479 }
1480 }
1481}
1482
1483fn find_return_constructor(root: tree_sitter::Node, source: &[u8]) -> Option<String> {
1485 let mut worklist = vec![root];
1486 while let Some(node) = worklist.pop() {
1487 let mut cursor = node.walk();
1488 for child in node.named_children(&mut cursor) {
1489 if child.kind() == "return_statement" {
1490 let mut inner_cursor = child.walk();
1491 for ret_child in child.named_children(&mut inner_cursor) {
1492 if ret_child.kind() == "call" || ret_child.kind() == "call_expression" {
1494 if let Some(func) = ret_child.child_by_field_name("function") {
1495 if func.kind() == "identifier" {
1496 let name = func.utf8_text(source).unwrap_or("");
1497 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1498 return Some(name.to_string());
1499 }
1500 }
1501 }
1502 }
1503 if ret_child.kind() == "new_expression" {
1505 if let Some(constructor) = ret_child.child_by_field_name("constructor") {
1506 let name = constructor.utf8_text(source).unwrap_or("");
1507 if !name.is_empty() {
1508 return Some(name.to_string());
1509 }
1510 }
1511 }
1512 if ret_child.kind() == "composite_literal" {
1514 if let Some(type_node) = ret_child.child_by_field_name("type") {
1515 let name = type_node.utf8_text(source).unwrap_or("");
1516 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
1517 return Some(name.to_string());
1518 }
1519 }
1520 }
1521 }
1522 }
1523 let ck = child.kind();
1525 if ck == "block" || ck == "statement_block" {
1526 worklist.push(child);
1527 }
1528 }
1529 }
1530 None
1531}
1532
1533fn scan_init_self_attrs(
1536 root: tree_sitter::Node,
1537 _file_path: &str,
1538 _file_entities: &[&SemanticEntity],
1539 _entity_map: &HashMap<String, EntityInfo>,
1540 source: &[u8],
1541 instance_attr_types: &mut HashMap<(String, String), String>,
1542 init_params_map: &mut HashMap<String, Vec<String>>,
1543 attr_to_param_map: &mut HashMap<(String, String), String>,
1544 config: &ScopeResolveConfig,
1545) {
1546 let mut worklist = vec![root];
1547 while let Some(node) = worklist.pop() {
1548 let kind = node.kind();
1549
1550 match &config.init_strategy {
1551 InitStrategy::ConstructorBody { class_nodes, init_node_kind, self_keyword: _, .. } => {
1552 if class_nodes.contains(&kind) {
1553 let class_name = node
1554 .child_by_field_name("name")
1555 .and_then(|n| n.utf8_text(source).ok())
1556 .unwrap_or("")
1557 .to_string();
1558
1559 if !class_name.is_empty() {
1560 let lang = match *init_node_kind {
1562 "function_definition" => "python",
1563 "method_definition" => "typescript",
1564 "init_declaration" => "swift",
1565 "anonymous_initializer" => "kotlin",
1566 _ => "typescript",
1567 };
1568 scan_class_for_init(node, &class_name, source, instance_attr_types, init_params_map, attr_to_param_map, lang);
1569 }
1570 }
1571 }
1572 InitStrategy::StructFields { struct_nodes } => {
1573 if struct_nodes.contains(&kind) {
1574 if kind == "struct_item" {
1576 let struct_name = node
1577 .child_by_field_name("name")
1578 .and_then(|n| n.utf8_text(source).ok())
1579 .unwrap_or("")
1580 .to_string();
1581
1582 if !struct_name.is_empty() {
1583 scan_rust_struct_fields(node, &struct_name, source, instance_attr_types);
1584 }
1585 }
1586 if kind == "type_declaration" {
1588 scan_go_struct_fields(node, source, instance_attr_types);
1589 }
1590 }
1591 }
1592 InitStrategy::None => {}
1593 }
1594
1595 let mut cursor = node.walk();
1596 let children: Vec<_> = node.named_children(&mut cursor).collect();
1597 for child in children.into_iter().rev() {
1598 worklist.push(child);
1599 }
1600 }
1601}
1602
1603fn scan_rust_struct_fields(
1605 node: tree_sitter::Node,
1606 struct_name: &str,
1607 source: &[u8],
1608 instance_attr_types: &mut HashMap<(String, String), String>,
1609) {
1610 let mut cursor = node.walk();
1611 for child in node.named_children(&mut cursor) {
1612 if child.kind() == "field_declaration_list" {
1613 let mut inner_cursor = child.walk();
1614 for field in child.named_children(&mut inner_cursor) {
1615 if field.kind() == "field_declaration" {
1616 let field_name = field
1617 .child_by_field_name("name")
1618 .and_then(|n| n.utf8_text(source).ok())
1619 .unwrap_or("");
1620 let field_type = field
1621 .child_by_field_name("type")
1622 .map(|n| extract_base_type(n, source))
1623 .unwrap_or_default();
1624
1625 if !field_name.is_empty()
1626 && !field_type.is_empty()
1627 && field_type.chars().next().map_or(false, |c| c.is_uppercase())
1628 {
1629 instance_attr_types.insert(
1630 (struct_name.to_string(), field_name.to_string()),
1631 field_type,
1632 );
1633 }
1634 }
1635 }
1636 }
1637 }
1638}
1639
1640fn scan_go_struct_fields(
1642 node: tree_sitter::Node,
1643 source: &[u8],
1644 instance_attr_types: &mut HashMap<(String, String), String>,
1645) {
1646 let mut cursor = node.walk();
1647 for child in node.named_children(&mut cursor) {
1648 if child.kind() == "type_spec" {
1649 let struct_name = child
1650 .child_by_field_name("name")
1651 .and_then(|n| n.utf8_text(source).ok())
1652 .unwrap_or("")
1653 .to_string();
1654
1655 if struct_name.is_empty() {
1656 continue;
1657 }
1658
1659 if let Some(type_node) = child.child_by_field_name("type") {
1661 if type_node.kind() == "struct_type" {
1662 let mut fields_cursor = type_node.walk();
1663 for field_list in type_node.named_children(&mut fields_cursor) {
1664 if field_list.kind() == "field_declaration_list" {
1665 let mut inner = field_list.walk();
1666 for field in field_list.named_children(&mut inner) {
1667 if field.kind() == "field_declaration" {
1668 let field_name = field
1670 .child_by_field_name("name")
1671 .and_then(|n| n.utf8_text(source).ok())
1672 .unwrap_or("");
1673 let field_type = field
1674 .child_by_field_name("type")
1675 .map(|n| extract_base_type(n, source))
1676 .unwrap_or_default();
1677
1678 if !field_name.is_empty()
1679 && !field_type.is_empty()
1680 && field_type.chars().next().map_or(false, |c| c.is_uppercase())
1681 {
1682 instance_attr_types.insert(
1683 (struct_name.clone(), field_name.to_string()),
1684 field_type,
1685 );
1686 }
1687 }
1688 }
1689 }
1690 }
1691 }
1692 }
1693 }
1694 }
1695}
1696
1697fn scan_class_for_init(
1698 root: tree_sitter::Node,
1699 class_name: &str,
1700 source: &[u8],
1701 instance_attr_types: &mut HashMap<(String, String), String>,
1702 init_params_map: &mut HashMap<String, Vec<String>>,
1703 attr_to_param_map: &mut HashMap<(String, String), String>,
1704 lang: &str,
1705) {
1706 if lang == "kotlin" {
1708 scan_kotlin_primary_constructor(root, class_name, source, instance_attr_types);
1709 }
1710
1711 let mut worklist = vec![root];
1712 while let Some(node) = worklist.pop() {
1713 let mut cursor = node.walk();
1714 for child in node.named_children(&mut cursor) {
1715 let ck = child.kind();
1716
1717 if ck == "function_definition" && lang == "python" {
1719 let name = child
1720 .child_by_field_name("name")
1721 .and_then(|n| n.utf8_text(source).ok())
1722 .unwrap_or("");
1723 if name == "__init__" {
1724 let params = extract_init_params(child, source);
1725 let ordered_params = extract_init_param_names_ordered(child, source);
1726 init_params_map.insert(class_name.to_string(), ordered_params);
1727 scan_init_body(child, class_name, ¶ms, source, instance_attr_types, attr_to_param_map);
1728 }
1729 }
1730
1731 if ck == "method_definition" && lang == "typescript" {
1733 let name = child
1734 .child_by_field_name("name")
1735 .and_then(|n| n.utf8_text(source).ok())
1736 .unwrap_or("");
1737 if name == "constructor" {
1738 scan_ts_constructor_body(child, class_name, source, instance_attr_types, init_params_map, attr_to_param_map);
1740 }
1741 }
1742
1743 if ck == "init_declaration" && lang == "swift" {
1745 scan_swift_init_body(child, class_name, source, instance_attr_types, init_params_map, attr_to_param_map);
1746 }
1747
1748 if ck == "anonymous_initializer" && lang == "kotlin" {
1750 scan_kotlin_init_body(child, class_name, source, instance_attr_types, attr_to_param_map);
1751 }
1752
1753 if (ck == "public_field_definition" || ck == "property_declaration" || ck == "field_definition") && lang == "typescript" {
1755 let field_name = child
1756 .child_by_field_name("name")
1757 .and_then(|n| n.utf8_text(source).ok())
1758 .unwrap_or("");
1759 if let Some(type_ann) = child.child_by_field_name("type") {
1760 let type_text = extract_base_type(type_ann, source);
1761 if !field_name.is_empty()
1762 && !type_text.is_empty()
1763 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1764 {
1765 instance_attr_types.insert(
1766 (class_name.to_string(), field_name.to_string()),
1767 type_text,
1768 );
1769 }
1770 }
1771 }
1772
1773 if ck == "property_declaration" && lang == "swift" {
1775 scan_swift_property_declaration(child, class_name, source, instance_attr_types);
1776 }
1777
1778 if ck == "property_declaration" && lang == "kotlin" {
1780 scan_kotlin_property_declaration(child, class_name, source, instance_attr_types);
1781 }
1782
1783 if ck == "block" || ck == "class_body" || ck == "statement_block"
1784 || ck == "struct_body" || ck == "function_body" || ck == "code_block"
1785 || ck == "statements" || ck == "enum_class_body"
1786 {
1787 worklist.push(child);
1788 }
1789 }
1790 }
1791}
1792
1793fn scan_swift_init_body(
1795 node: tree_sitter::Node,
1796 class_name: &str,
1797 source: &[u8],
1798 instance_attr_types: &mut HashMap<(String, String), String>,
1799 init_params_map: &mut HashMap<String, Vec<String>>,
1800 attr_to_param_map: &mut HashMap<(String, String), String>,
1801) {
1802 let params = extract_init_params(node, source);
1803 let ordered_params = extract_init_param_names_ordered(node, source);
1804 init_params_map.insert(class_name.to_string(), ordered_params);
1805
1806 let mut worklist = vec![node];
1808 while let Some(wnode) = worklist.pop() {
1809 let mut cursor = wnode.walk();
1810 for child in wnode.named_children(&mut cursor) {
1811 let ck = child.kind();
1812 if ck == "directly_assigned_expression" || ck == "assignment" {
1814 if let Some(left) = child.child_by_field_name("left").or_else(|| child.named_child(0)) {
1815 if left.kind() == "navigation_expression" {
1816 let obj = left.child_by_field_name("target")
1817 .and_then(|n| n.utf8_text(source).ok())
1818 .unwrap_or("");
1819 let prop = left.child_by_field_name("suffix")
1820 .and_then(|n| n.utf8_text(source).ok())
1821 .unwrap_or("");
1822 if obj == "self" && !prop.is_empty() {
1823 if let Some(right) = child.child_by_field_name("right").or_else(|| child.named_child(1)) {
1824 if right.kind() == "simple_identifier" || right.kind() == "identifier" {
1825 let rhs_name = right.utf8_text(source).unwrap_or("");
1826 if params.contains_key(rhs_name) {
1827 attr_to_param_map.insert(
1828 (class_name.to_string(), prop.to_string()),
1829 rhs_name.to_string(),
1830 );
1831 if let Some(Some(type_hint)) = params.get(rhs_name) {
1832 instance_attr_types.insert(
1833 (class_name.to_string(), prop.to_string()),
1834 type_hint.clone(),
1835 );
1836 }
1837 }
1838 }
1839 }
1840 }
1841 }
1842 }
1843 }
1844 if ck == "function_body" || ck == "code_block" || ck == "statements"
1845 || ck == "expression_statement" || ck == "block"
1846 {
1847 worklist.push(child);
1848 }
1849 }
1850 }
1851}
1852
1853fn scan_swift_property_declaration(
1855 node: tree_sitter::Node,
1856 class_name: &str,
1857 source: &[u8],
1858 instance_attr_types: &mut HashMap<(String, String), String>,
1859) {
1860 let mut cursor = node.walk();
1862 for child in node.named_children(&mut cursor) {
1863 if child.kind() == "pattern" || child.kind() == "simple_identifier" || child.kind() == "identifier" {
1864 let field_name = child.utf8_text(source).unwrap_or("");
1865 if let Some(type_ann) = node.child_by_field_name("type") {
1866 let type_text = extract_base_type(type_ann, source);
1867 if !field_name.is_empty()
1868 && !type_text.is_empty()
1869 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1870 {
1871 instance_attr_types.insert(
1872 (class_name.to_string(), field_name.to_string()),
1873 type_text,
1874 );
1875 }
1876 }
1877 }
1878 }
1879}
1880
1881fn scan_kotlin_property_declaration(
1883 node: tree_sitter::Node,
1884 class_name: &str,
1885 source: &[u8],
1886 instance_attr_types: &mut HashMap<(String, String), String>,
1887) {
1888 let field_name = node
1889 .child_by_field_name("name")
1890 .and_then(|n| n.utf8_text(source).ok())
1891 .unwrap_or("");
1892 let field_type = node
1893 .child_by_field_name("type")
1894 .map(|n| extract_base_type(n, source))
1895 .unwrap_or_default();
1896
1897 if !field_name.is_empty()
1898 && !field_type.is_empty()
1899 && field_type.chars().next().map_or(false, |c| c.is_uppercase())
1900 {
1901 instance_attr_types.insert(
1902 (class_name.to_string(), field_name.to_string()),
1903 field_type,
1904 );
1905 }
1906}
1907
1908fn scan_kotlin_primary_constructor(
1910 class_node: tree_sitter::Node,
1911 class_name: &str,
1912 source: &[u8],
1913 instance_attr_types: &mut HashMap<(String, String), String>,
1914) {
1915 let mut cursor = class_node.walk();
1917 for child in class_node.named_children(&mut cursor) {
1918 if child.kind() == "primary_constructor" {
1919 let mut pc_cursor = child.walk();
1920 for param in child.named_children(&mut pc_cursor) {
1921 if param.kind() == "class_parameter" {
1922 let text = param.utf8_text(source).unwrap_or("");
1924 let has_val_var = text.starts_with("val ") || text.starts_with("var ")
1925 || text.contains("val ") || text.contains("var ");
1926 if has_val_var {
1927 let param_name = param
1928 .child_by_field_name("name")
1929 .and_then(|n| n.utf8_text(source).ok())
1930 .unwrap_or("");
1931 let param_type = param
1932 .child_by_field_name("type")
1933 .map(|n| extract_base_type(n, source))
1934 .unwrap_or_default();
1935 if !param_name.is_empty()
1936 && !param_type.is_empty()
1937 && param_type.chars().next().map_or(false, |c| c.is_uppercase())
1938 {
1939 instance_attr_types.insert(
1940 (class_name.to_string(), param_name.to_string()),
1941 param_type,
1942 );
1943 }
1944 }
1945 }
1946 }
1947 }
1948 }
1949}
1950
1951fn scan_kotlin_init_body(
1953 node: tree_sitter::Node,
1954 class_name: &str,
1955 source: &[u8],
1956 instance_attr_types: &mut HashMap<(String, String), String>,
1957 attr_to_param_map: &mut HashMap<(String, String), String>,
1958) {
1959 let mut worklist = vec![node];
1960 while let Some(wnode) = worklist.pop() {
1961 let mut cursor = wnode.walk();
1962 for child in wnode.named_children(&mut cursor) {
1963 let ck = child.kind();
1964 if ck == "assignment" || ck == "directly_assigned_expression" {
1965 if let Some(left) = child.child_by_field_name("left").or_else(|| child.named_child(0)) {
1966 if left.kind() == "navigation_expression" {
1967 let obj = left.child_by_field_name("expression")
1968 .and_then(|n| n.utf8_text(source).ok())
1969 .unwrap_or("");
1970 let prop = left.child_by_field_name("navigation_suffix")
1971 .and_then(|n| n.utf8_text(source).ok())
1972 .unwrap_or("");
1973 if obj == "this" && !prop.is_empty() {
1974 if let Some(right) = child.child_by_field_name("right").or_else(|| child.named_child(1)) {
1975 if right.kind() == "simple_identifier" || right.kind() == "identifier" {
1976 let rhs_name = right.utf8_text(source).unwrap_or("");
1977 attr_to_param_map.insert(
1978 (class_name.to_string(), prop.to_string()),
1979 rhs_name.to_string(),
1980 );
1981 }
1982 if right.kind() == "call_expression" {
1984 let callee = right.child_by_field_name("function")
1985 .and_then(|n| n.utf8_text(source).ok())
1986 .unwrap_or("");
1987 if !callee.is_empty()
1988 && callee.chars().next().map_or(false, |c| c.is_uppercase())
1989 {
1990 instance_attr_types.insert(
1991 (class_name.to_string(), prop.to_string()),
1992 callee.to_string(),
1993 );
1994 }
1995 }
1996 }
1997 }
1998 }
1999 }
2000 }
2001 if ck == "statements" || ck == "block" || ck == "expression_statement" {
2002 worklist.push(child);
2003 }
2004 }
2005 }
2006}
2007
2008fn scan_ts_constructor_body(
2010 node: tree_sitter::Node,
2011 class_name: &str,
2012 source: &[u8],
2013 instance_attr_types: &mut HashMap<(String, String), String>,
2014 init_params_map: &mut HashMap<String, Vec<String>>,
2015 attr_to_param_map: &mut HashMap<(String, String), String>,
2016) {
2017 let params = extract_init_params(node, source);
2019 let ordered_params = extract_init_param_names_ordered(node, source);
2020 init_params_map.insert(class_name.to_string(), ordered_params);
2021
2022 scan_init_body_this(node, class_name, ¶ms, source, instance_attr_types, attr_to_param_map);
2024}
2025
2026fn scan_init_body_this(
2028 root: tree_sitter::Node,
2029 class_name: &str,
2030 params: &HashMap<String, Option<String>>,
2031 source: &[u8],
2032 instance_attr_types: &mut HashMap<(String, String), String>,
2033 attr_to_param_map: &mut HashMap<(String, String), String>,
2034) {
2035 let mut worklist = vec![root];
2036 while let Some(node) = worklist.pop() {
2037 let mut cursor = node.walk();
2038 for child in node.named_children(&mut cursor) {
2039 let ck = child.kind();
2040 if ck == "expression_statement" {
2041 let mut inner_cursor = child.walk();
2043 for inner in child.named_children(&mut inner_cursor) {
2044 if inner.kind() == "assignment_expression" {
2045 if let Some(left) = inner.child_by_field_name("left") {
2046 if left.kind() == "member_expression" {
2047 let obj = left.child_by_field_name("object")
2048 .and_then(|n| n.utf8_text(source).ok())
2049 .unwrap_or("");
2050 let prop = left.child_by_field_name("property")
2051 .and_then(|n| n.utf8_text(source).ok())
2052 .unwrap_or("");
2053 if obj == "this" && !prop.is_empty() {
2054 if let Some(right) = inner.child_by_field_name("right") {
2055 if right.kind() == "identifier" {
2056 let rhs_name = right.utf8_text(source).unwrap_or("");
2057 if params.contains_key(rhs_name) {
2058 attr_to_param_map.insert(
2059 (class_name.to_string(), prop.to_string()),
2060 rhs_name.to_string(),
2061 );
2062 if let Some(Some(type_hint)) = params.get(rhs_name) {
2063 instance_attr_types.insert(
2064 (class_name.to_string(), prop.to_string()),
2065 type_hint.clone(),
2066 );
2067 }
2068 }
2069 }
2070 if right.kind() == "new_expression" {
2071 if let Some(ctor) = right.child_by_field_name("constructor") {
2072 let name = ctor.utf8_text(source).unwrap_or("");
2073 if !name.is_empty() {
2074 instance_attr_types.insert(
2075 (class_name.to_string(), prop.to_string()),
2076 name.to_string(),
2077 );
2078 }
2079 }
2080 }
2081 }
2082 }
2083 }
2084 }
2085 }
2086 }
2087 }
2088 if ck == "statement_block" || ck == "block" {
2089 worklist.push(child);
2090 }
2091 }
2092 }
2093}
2094
2095fn extract_init_param_names_ordered(func_node: tree_sitter::Node, source: &[u8]) -> Vec<String> {
2097 let mut names = Vec::new();
2098 if let Some(params_node) = func_node.child_by_field_name("parameters") {
2099 let mut cursor = params_node.walk();
2100 for child in params_node.named_children(&mut cursor) {
2101 let param_name = if child.kind() == "identifier" {
2102 child.utf8_text(source).unwrap_or("").to_string()
2103 } else if child.kind() == "typed_parameter" || child.kind() == "typed_default_parameter" {
2104 child.child_by_field_name("name")
2105 .or_else(|| child.named_child(0))
2106 .and_then(|n| n.utf8_text(source).ok())
2107 .unwrap_or("")
2108 .to_string()
2109 } else {
2110 continue;
2111 };
2112 if param_name != "self" && param_name != "cls" && !param_name.is_empty() {
2113 names.push(param_name);
2114 }
2115 }
2116 }
2117 names
2118}
2119
2120fn extract_init_params(func_node: tree_sitter::Node, source: &[u8]) -> HashMap<String, Option<String>> {
2121 let mut params = HashMap::new();
2122 if let Some(params_node) = func_node.child_by_field_name("parameters") {
2123 let mut cursor = params_node.walk();
2124 for child in params_node.named_children(&mut cursor) {
2125 let param_name = if child.kind() == "identifier" {
2126 child.utf8_text(source).unwrap_or("").to_string()
2127 } else if child.kind() == "typed_parameter" || child.kind() == "typed_default_parameter" {
2128 child.child_by_field_name("name")
2129 .or_else(|| child.named_child(0))
2130 .and_then(|n| n.utf8_text(source).ok())
2131 .unwrap_or("")
2132 .to_string()
2133 } else {
2134 continue;
2135 };
2136 if param_name != "self" && param_name != "cls" {
2137 let type_hint = child.child_by_field_name("type")
2139 .and_then(|n| n.utf8_text(source).ok())
2140 .map(|s| s.to_string());
2141 params.insert(param_name, type_hint);
2142 }
2143 }
2144 }
2145 params
2146}
2147
2148fn scan_init_body(
2149 root: tree_sitter::Node,
2150 class_name: &str,
2151 params: &HashMap<String, Option<String>>,
2152 source: &[u8],
2153 instance_attr_types: &mut HashMap<(String, String), String>,
2154 attr_to_param_map: &mut HashMap<(String, String), String>,
2155) {
2156 let mut worklist = vec![root];
2157 while let Some(node) = worklist.pop() {
2158 let mut cursor = node.walk();
2159 for child in node.named_children(&mut cursor) {
2160 if child.kind() == "expression_statement" || child.kind() == "assignment" {
2161 let assign = if child.kind() == "assignment" {
2162 child
2163 } else {
2164 let mut inner_cursor = child.walk();
2165 let children: Vec<_> = child.named_children(&mut inner_cursor).collect();
2166 match children.into_iter().find(|c| c.kind() == "assignment") {
2167 Some(a) => a,
2168 None => continue,
2169 }
2170 };
2171
2172 if let Some(left) = assign.child_by_field_name("left") {
2173 if left.kind() == "attribute" {
2174 let obj = left.child_by_field_name("object")
2175 .and_then(|n| n.utf8_text(source).ok())
2176 .unwrap_or("");
2177 let attr = left.child_by_field_name("attribute")
2178 .and_then(|n| n.utf8_text(source).ok())
2179 .unwrap_or("");
2180
2181 if obj == "self" && !attr.is_empty() {
2182 if let Some(right) = assign.child_by_field_name("right") {
2183 if right.kind() == "identifier" {
2184 let rhs_name = right.utf8_text(source).unwrap_or("");
2185 if params.contains_key(rhs_name) {
2187 attr_to_param_map.insert(
2188 (class_name.to_string(), attr.to_string()),
2189 rhs_name.to_string(),
2190 );
2191 }
2192 if let Some(Some(type_hint)) = params.get(rhs_name) {
2194 instance_attr_types.insert(
2195 (class_name.to_string(), attr.to_string()),
2196 type_hint.clone(),
2197 );
2198 }
2199 }
2200 if right.kind() == "call" {
2201 if let Some(func) = right.child_by_field_name("function") {
2202 if func.kind() == "identifier" {
2203 let fname = func.utf8_text(source).unwrap_or("");
2204 if fname.chars().next().map_or(false, |c| c.is_uppercase()) {
2205 instance_attr_types.insert(
2206 (class_name.to_string(), attr.to_string()),
2207 fname.to_string(),
2208 );
2209 }
2210 }
2211 }
2212 }
2213 }
2214 }
2215 }
2216 }
2217 }
2218 if child.kind() == "block" {
2219 worklist.push(child);
2220 }
2221 }
2222 }
2223}
2224
2225fn infer_constructor_param_types(
2230 parsed_files: &[(String, String, tree_sitter::Tree)],
2231 return_type_map: &HashMap<String, String>,
2232 init_params: &HashMap<String, Vec<String>>,
2233 attr_to_param: &HashMap<(String, String), String>,
2234 _symbol_table: &HashMap<String, Vec<String>>,
2235 entity_map: &HashMap<String, EntityInfo>,
2236 instance_attr_types: &mut HashMap<(String, String), String>,
2237) {
2238 let mut func_name_returns: HashMap<String, String> = HashMap::new();
2240 for (eid, ret_type) in return_type_map {
2241 if let Some(info) = entity_map.get(eid) {
2242 func_name_returns.insert(info.name.clone(), ret_type.clone());
2243 }
2244 }
2245
2246 let local_results: Vec<HashMap<(String, String), String>> = maybe_par_iter!(parsed_files)
2249 .map(|(_file_path, content, tree)| {
2250 let source = content.as_bytes();
2251 let mut local_attr_types: HashMap<(String, String), String> = HashMap::new();
2252 scan_constructor_calls(
2253 tree.root_node(),
2254 source,
2255 &func_name_returns,
2256 init_params,
2257 attr_to_param,
2258 &mut local_attr_types,
2259 );
2260 local_attr_types
2261 })
2262 .collect();
2263
2264 for local in local_results {
2265 for (key, val) in local {
2266 instance_attr_types.entry(key).or_insert(val);
2267 }
2268 }
2269}
2270
2271fn scan_constructor_calls(
2272 root: tree_sitter::Node,
2273 source: &[u8],
2274 func_name_returns: &HashMap<String, String>,
2275 init_params: &HashMap<String, Vec<String>>,
2276 attr_to_param: &HashMap<(String, String), String>,
2277 instance_attr_types: &mut HashMap<(String, String), String>,
2278) {
2279 let mut worklist = vec![root];
2280 while let Some(node) = worklist.pop() {
2281 let kind = node.kind();
2282
2283 if kind == "call" {
2284 if let Some(func) = node.child_by_field_name("function") {
2285 if func.kind() == "identifier" {
2286 let class_name = func.utf8_text(source).unwrap_or("");
2287 if class_name.chars().next().map_or(false, |c| c.is_uppercase()) {
2289 if let Some(param_names) = init_params.get(class_name) {
2290 if let Some(args_node) = node.child_by_field_name("arguments") {
2292 let mut arg_idx = 0;
2293 let mut args_cursor = args_node.walk();
2294 for arg in args_node.named_children(&mut args_cursor) {
2295 if arg_idx >= param_names.len() {
2296 break;
2297 }
2298 let param_name = ¶m_names[arg_idx];
2299
2300 let arg_type = infer_expr_type(arg, source, func_name_returns);
2302
2303 if let Some(at) = arg_type {
2304 for ((cn, attr), pn) in attr_to_param.iter() {
2306 if cn == class_name && pn == param_name {
2307 instance_attr_types
2308 .entry((cn.clone(), attr.clone()))
2309 .or_insert(at.clone());
2310 }
2311 }
2312 }
2313
2314 arg_idx += 1;
2315 }
2316 }
2317 }
2318 }
2319 }
2320 }
2321 }
2322
2323 let mut cursor = node.walk();
2324 let children: Vec<_> = node.named_children(&mut cursor).collect();
2325 for child in children.into_iter().rev() {
2326 worklist.push(child);
2327 }
2328 }
2329}
2330
2331fn infer_expr_type(
2333 node: tree_sitter::Node,
2334 source: &[u8],
2335 func_name_returns: &HashMap<String, String>,
2336) -> Option<String> {
2337 match node.kind() {
2338 "call" => {
2339 if let Some(func) = node.child_by_field_name("function") {
2340 if func.kind() == "identifier" {
2341 let name = func.utf8_text(source).unwrap_or("");
2342 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
2344 return Some(name.to_string());
2345 }
2346 if let Some(ret) = func_name_returns.get(name) {
2348 return Some(ret.clone());
2349 }
2350 }
2351 }
2352 None
2353 }
2354 "identifier" => {
2355 None
2357 }
2358 _ => None,
2359 }
2360}
2361
2362fn inject_return_type_bindings(
2365 _entity_inner_scope: &HashMap<String, usize>,
2366 scopes: &mut Vec<Scope>,
2367 return_type_map: &HashMap<String, String>,
2368 import_table: &HashMap<(String, String), String>,
2369 file_path: &str,
2370 entity_map: &HashMap<String, EntityInfo>,
2371) {
2372 let mut func_name_return_types: HashMap<String, String> = HashMap::new();
2374 for (eid, ret_type) in return_type_map {
2375 if let Some(info) = entity_map.get(eid) {
2376 func_name_return_types.insert(info.name.clone(), ret_type.clone());
2377 }
2378 }
2379
2380 for ((fp, local_name), target_id) in import_table {
2382 if fp == file_path {
2383 if let Some(ret_type) = return_type_map.get(target_id) {
2384 func_name_return_types.insert(local_name.clone(), ret_type.clone());
2385 }
2386 }
2387 }
2388
2389 for scope in scopes.iter_mut() {
2391 let resolved: Vec<(String, String)> = scope
2392 .pending_call_types
2393 .iter()
2394 .filter_map(|(var_name, func_name)| {
2395 func_name_return_types
2396 .get(func_name)
2397 .map(|ret_type| (var_name.clone(), ret_type.clone()))
2398 })
2399 .collect();
2400
2401 for (var_name, ret_type) in resolved {
2402 scope.types.insert(var_name, ret_type);
2403 }
2404 }
2405}
2406
2407fn extract_imports_from_ast(
2409 root: tree_sitter::Node,
2410 file_path: &str,
2411 source: &[u8],
2412 symbol_table: &HashMap<String, Vec<String>>,
2413 entity_map: &HashMap<String, EntityInfo>,
2414 import_table: &mut HashMap<(String, String), String>,
2415 scopes: &mut Vec<Scope>,
2416 config: &ScopeResolveConfig,
2417 go_pkg_index: &HashMap<String, Vec<(String, String)>>,
2418) {
2419 let mut worklist = vec![root];
2420 while let Some(node) = worklist.pop() {
2421 let mut cursor = node.walk();
2422 for child in node.named_children(&mut cursor) {
2423 let ck = child.kind();
2424 let handled = match ck {
2425 "import_from_statement" => {
2426 extract_python_import(child, file_path, source, symbol_table, entity_map, import_table, scopes);
2427 true
2428 }
2429 "import_statement" if config.self_keywords.contains(&"self") && config.self_keywords.contains(&"cls") => {
2430 extract_python_module_import(child, file_path, source, symbol_table, entity_map, import_table, scopes);
2432 true
2433 }
2434 "import_statement" if !config.self_keywords.contains(&"cls") => {
2435 extract_ts_import(child, file_path, source, symbol_table, entity_map, import_table, scopes);
2437 true
2438 }
2439 "use_declaration" => {
2440 extract_rust_use(child, file_path, source, symbol_table, entity_map, import_table, scopes);
2441 true
2442 }
2443 "import_declaration" => {
2444 extract_go_import(child, file_path, source, symbol_table, entity_map, import_table, scopes, go_pkg_index);
2445 true
2446 }
2447 _ => false,
2448 };
2449 if !handled {
2450 worklist.push(child);
2451 }
2452 }
2453 }
2454}
2455
2456fn extract_ts_import(
2458 node: tree_sitter::Node,
2459 file_path: &str,
2460 source: &[u8],
2461 symbol_table: &HashMap<String, Vec<String>>,
2462 entity_map: &HashMap<String, EntityInfo>,
2463 import_table: &mut HashMap<(String, String), String>,
2464 scopes: &mut Vec<Scope>,
2465) {
2466 let source_path = node
2468 .child_by_field_name("source")
2469 .and_then(|n| n.utf8_text(source).ok())
2470 .unwrap_or("")
2471 .trim_matches(|c: char| c == '\'' || c == '"');
2472
2473 if source_path.is_empty() {
2474 return;
2475 }
2476
2477 let mut cursor = node.walk();
2479 for child in node.named_children(&mut cursor) {
2480 if child.kind() == "import_clause" {
2481 let mut clause_cursor = child.walk();
2482 for clause_child in child.named_children(&mut clause_cursor) {
2483 if clause_child.kind() == "named_imports" {
2484 let mut imports_cursor = clause_child.walk();
2486 for spec in clause_child.named_children(&mut imports_cursor) {
2487 if spec.kind() == "import_specifier" {
2488 let original = spec
2489 .child_by_field_name("name")
2490 .and_then(|n| n.utf8_text(source).ok())
2491 .unwrap_or("");
2492 let local = spec
2493 .child_by_field_name("alias")
2494 .and_then(|n| n.utf8_text(source).ok())
2495 .unwrap_or(original);
2496
2497 if !original.is_empty() {
2498 resolve_import_name(original, local, source_path, file_path, &[".ts", ".tsx", ".js", ".jsx"], symbol_table, entity_map, import_table, scopes);
2499 }
2500 }
2501 }
2502 } else if clause_child.kind() == "namespace_import" {
2503 let mut ns_cursor = clause_child.walk();
2506 let alias = clause_child
2507 .child_by_field_name("alias")
2508 .or_else(|| {
2509 clause_child.named_children(&mut ns_cursor)
2510 .find(|c| c.kind() == "identifier")
2511 })
2512 .and_then(|n| n.utf8_text(source).ok())
2513 .unwrap_or("");
2514 if !alias.is_empty() {
2515 register_namespace_import(alias, source_path, file_path, &[".ts", ".tsx", ".js", ".jsx"], symbol_table, entity_map, import_table, scopes);
2516 }
2517 } else if clause_child.kind() == "identifier" {
2518 let name = clause_child.utf8_text(source).unwrap_or("");
2520 if !name.is_empty() {
2521 resolve_import_name(name, name, source_path, file_path, &[".ts", ".tsx", ".js", ".jsx"], symbol_table, entity_map, import_table, scopes);
2522 }
2523 }
2524 }
2525 }
2526 }
2527}
2528
2529fn extract_rust_use(
2532 node: tree_sitter::Node,
2533 file_path: &str,
2534 source: &[u8],
2535 symbol_table: &HashMap<String, Vec<String>>,
2536 entity_map: &HashMap<String, EntityInfo>,
2537 import_table: &mut HashMap<(String, String), String>,
2538 scopes: &mut Vec<Scope>,
2539) {
2540 let text = node.utf8_text(source).unwrap_or("").trim().to_string();
2541 let text = text.strip_prefix("use ").unwrap_or(&text);
2543 let text = text.strip_prefix("pub use ").unwrap_or(text);
2544 let text = text.trim_end_matches(';').trim();
2545
2546 let text = text
2548 .strip_prefix("crate::")
2549 .or_else(|| text.strip_prefix("super::"))
2550 .or_else(|| text.strip_prefix("self::"))
2551 .unwrap_or(text);
2552
2553 if let Some(brace_pos) = text.find("::{") {
2555 let module_path = &text[..brace_pos];
2556 let source_module = module_path.rsplit("::").next().unwrap_or(module_path);
2557
2558 let names_part = &text[brace_pos + 3..];
2559 let names_part = names_part.trim_end_matches('}');
2560
2561 for name_part in names_part.split(',') {
2562 let name_part = name_part.trim();
2563 if name_part.is_empty() {
2564 continue;
2565 }
2566 let (original, local) = if let Some(pos) = name_part.find(" as ") {
2567 (name_part[..pos].trim(), name_part[pos + 4..].trim())
2568 } else {
2569 (name_part, name_part)
2570 };
2571 if !original.is_empty() {
2572 resolve_import_name(original, local, source_module, file_path, &[".rs"], symbol_table, entity_map, import_table, scopes);
2573 }
2574 }
2575 } else {
2576 let parts: Vec<&str> = text.split("::").collect();
2578 if parts.is_empty() {
2579 return;
2580 }
2581 let imported_name = parts.last().unwrap().trim();
2582 let (original, local) = if let Some(pos) = imported_name.find(" as ") {
2583 (&imported_name[..pos], imported_name[pos + 4..].trim())
2584 } else {
2585 (imported_name, imported_name)
2586 };
2587 let source_module = if parts.len() >= 2 {
2588 parts[parts.len() - 2]
2589 } else {
2590 parts[0]
2591 };
2592 if !original.is_empty() && !source_module.is_empty() {
2593 resolve_import_name(original, local, source_module, file_path, &[".rs"], symbol_table, entity_map, import_table, scopes);
2594 }
2595 }
2596}
2597
2598fn extract_go_import(
2600 node: tree_sitter::Node,
2601 file_path: &str,
2602 source: &[u8],
2603 symbol_table: &HashMap<String, Vec<String>>,
2604 entity_map: &HashMap<String, EntityInfo>,
2605 import_table: &mut HashMap<(String, String), String>,
2606 scopes: &mut Vec<Scope>,
2607 go_pkg_index: &HashMap<String, Vec<(String, String)>>,
2608) {
2609 let mut cursor = node.walk();
2610 for child in node.named_children(&mut cursor) {
2611 if child.kind() == "import_spec" || child.kind() == "import_spec_list" {
2612 extract_go_import_specs(child, file_path, source, symbol_table, entity_map, import_table, scopes, go_pkg_index);
2613 } else if child.kind() == "interpreted_string_literal" || child.kind() == "raw_string_literal" {
2614 let path = child.utf8_text(source).unwrap_or("")
2615 .trim_matches('"').trim_matches('`');
2616 let pkg_name = path.rsplit('/').next().unwrap_or(path);
2617 register_go_package_imports(pkg_name, file_path, symbol_table, entity_map, import_table, scopes, go_pkg_index);
2618 }
2619 }
2620}
2621
2622fn extract_go_import_specs(
2623 root: tree_sitter::Node,
2624 file_path: &str,
2625 source: &[u8],
2626 symbol_table: &HashMap<String, Vec<String>>,
2627 entity_map: &HashMap<String, EntityInfo>,
2628 import_table: &mut HashMap<(String, String), String>,
2629 scopes: &mut Vec<Scope>,
2630 go_pkg_index: &HashMap<String, Vec<(String, String)>>,
2631) {
2632 let mut worklist = vec![root];
2633 while let Some(node) = worklist.pop() {
2634 let mut cursor = node.walk();
2635 for child in node.named_children(&mut cursor) {
2636 if child.kind() == "import_spec" {
2637 let path_node = child.child_by_field_name("path")
2638 .or_else(|| child.named_child(0));
2639 if let Some(pn) = path_node {
2640 let path = pn.utf8_text(source).unwrap_or("")
2641 .trim_matches('"').trim_matches('`');
2642 let pkg_name = path.rsplit('/').next().unwrap_or(path);
2643 register_go_package_imports(pkg_name, file_path, symbol_table, entity_map, import_table, scopes, go_pkg_index);
2644 }
2645 } else {
2646 worklist.push(child);
2647 }
2648 }
2649 }
2650}
2651
2652fn register_go_package_imports(
2653 pkg_name: &str,
2654 file_path: &str,
2655 _symbol_table: &HashMap<String, Vec<String>>,
2656 _entity_map: &HashMap<String, EntityInfo>,
2657 import_table: &mut HashMap<(String, String), String>,
2658 scopes: &mut Vec<Scope>,
2659 go_pkg_index: &HashMap<String, Vec<(String, String)>>,
2660) {
2661 if let Some(entries) = go_pkg_index.get(pkg_name) {
2663 for (name, target_id) in entries {
2664 import_table.insert(
2665 (file_path.to_string(), name.clone()),
2666 target_id.clone(),
2667 );
2668 if !scopes.is_empty() {
2669 scopes[0].defs.insert(name.clone(), target_id.clone());
2670 }
2671 }
2672 }
2673}
2674
2675fn resolve_import_name(
2677 original_name: &str,
2678 local_name: &str,
2679 source_path: &str,
2680 file_path: &str,
2681 extensions: &[&str],
2682 symbol_table: &HashMap<String, Vec<String>>,
2683 entity_map: &HashMap<String, EntityInfo>,
2684 import_table: &mut HashMap<(String, String), String>,
2685 scopes: &mut Vec<Scope>,
2686) {
2687 if let Some(target_ids) = symbol_table.get(original_name) {
2688 let target = find_import_target(
2689 target_ids,
2690 source_path,
2691 file_path,
2692 extensions,
2693 entity_map,
2694 );
2695
2696 if let Some(target_id) = target {
2697 import_table.insert(
2698 (file_path.to_string(), local_name.to_string()),
2699 target_id.clone(),
2700 );
2701 if !scopes.is_empty() {
2702 scopes[0]
2703 .defs
2704 .insert(local_name.to_string(), target_id.clone());
2705 }
2706 }
2707 }
2708}
2709
2710fn register_namespace_import(
2714 alias: &str,
2715 source_path: &str,
2716 file_path: &str,
2717 extensions: &[&str],
2718 symbol_table: &HashMap<String, Vec<String>>,
2719 entity_map: &HashMap<String, EntityInfo>,
2720 import_table: &mut HashMap<(String, String), String>,
2721 _scopes: &mut Vec<Scope>,
2722) {
2723 for (name, target_ids) in symbol_table {
2725 for target_id in target_ids {
2726 if let Some(info) = entity_map.get(target_id) {
2727 if import_source_matches_file(file_path, source_path, extensions, &info.file_path)
2728 && info.parent_id.is_none() {
2729 let qualified_name = format!("{alias}.{name}");
2730 import_table.insert(
2731 (file_path.to_string(), qualified_name.clone()),
2732 target_id.clone(),
2733 );
2734 }
2735 }
2736 }
2737 }
2738}
2739
2740fn extract_python_import(
2741 node: tree_sitter::Node,
2742 file_path: &str,
2743 source: &[u8],
2744 symbol_table: &HashMap<String, Vec<String>>,
2745 entity_map: &HashMap<String, EntityInfo>,
2746 import_table: &mut HashMap<(String, String), String>,
2747 scopes: &mut Vec<Scope>,
2748) {
2749 let module_node = node.child_by_field_name("module_name");
2753 let module_name = module_node
2754 .and_then(|n| n.utf8_text(source).ok())
2755 .unwrap_or("");
2756
2757 let mut cursor = node.walk();
2759 for child in node.named_children(&mut cursor) {
2760 if child.kind() == "dotted_name" || child.kind() == "aliased_import" {
2761 let (original, local) = if child.kind() == "aliased_import" {
2762 let orig = child
2763 .child_by_field_name("name")
2764 .and_then(|n| n.utf8_text(source).ok())
2765 .unwrap_or("");
2766 let alias = child
2767 .child_by_field_name("alias")
2768 .and_then(|n| n.utf8_text(source).ok())
2769 .unwrap_or(orig);
2770 (orig, alias)
2771 } else {
2772 let name = child.utf8_text(source).unwrap_or("");
2773 (name, name)
2774 };
2775
2776 if original.is_empty() {
2777 continue;
2778 }
2779
2780 resolve_import_name(original, local, module_name, file_path, &[".py"], symbol_table, entity_map, import_table, scopes);
2781 }
2782 }
2783}
2784
2785fn extract_python_module_import(
2788 node: tree_sitter::Node,
2789 file_path: &str,
2790 source: &[u8],
2791 symbol_table: &HashMap<String, Vec<String>>,
2792 entity_map: &HashMap<String, EntityInfo>,
2793 import_table: &mut HashMap<(String, String), String>,
2794 scopes: &mut Vec<Scope>,
2795) {
2796 let mut cursor = node.walk();
2797 for child in node.named_children(&mut cursor) {
2798 let (module_name, _alias) = match child.kind() {
2799 "dotted_name" => {
2800 let name = child.utf8_text(source).unwrap_or("");
2801 (name, name)
2802 }
2803 "aliased_import" => {
2804 let orig = child
2805 .child_by_field_name("name")
2806 .and_then(|n| n.utf8_text(source).ok())
2807 .unwrap_or("");
2808 let alias = child
2809 .child_by_field_name("alias")
2810 .and_then(|n| n.utf8_text(source).ok())
2811 .unwrap_or(orig);
2812 (orig, alias)
2813 }
2814 _ => continue,
2815 };
2816
2817 if module_name.is_empty() {
2818 continue;
2819 }
2820
2821 register_namespace_import(
2823 _alias,
2824 module_name,
2825 file_path,
2826 &[".py"],
2827 symbol_table,
2828 entity_map,
2829 import_table,
2830 scopes,
2831 );
2832 }
2833}
2834
2835fn collect_all_file_refs(
2838 root: tree_sitter::Node,
2839 source: &[u8],
2840 config: &ScopeResolveConfig,
2841) -> Vec<AstRef> {
2842 let mut refs = Vec::new();
2843 let mut worklist = vec![root];
2844 while let Some(node) = worklist.pop() {
2845 let node_row = node.start_position().row;
2846 let kind = node.kind();
2847
2848 if config.call_nodes.contains(&kind) {
2850 match &config.call_style {
2851 CallNodeStyle::FunctionField(field) => {
2852 if let Some(func) = node.child_by_field_name(field) {
2853 extract_call_ref(func, "", "", source, &mut refs, config, node_row);
2855 }
2856 }
2857 CallNodeStyle::FirstChild => {
2858 if let Some(func) = node.named_child(0) {
2860 extract_call_ref(func, "", "", source, &mut refs, config, node_row);
2861 }
2862 }
2863 CallNodeStyle::DirectMethod { object_field, method_field } => {
2864 let method_name = node.child_by_field_name(method_field)
2865 .and_then(|n| n.utf8_text(source).ok())
2866 .unwrap_or("");
2867 if !method_name.is_empty() && !is_builtin(method_name, config) {
2868 if let Some(obj_node) = node.child_by_field_name(object_field) {
2869 let receiver = obj_node.utf8_text(source).unwrap_or("").to_string();
2870 let receiver = receiver.trim_end_matches('.').to_string();
2871 refs.push(AstRef {
2872 kind: AstRefKind::MethodCall { receiver, method: method_name.to_string() },
2873 row: node_row,
2874 });
2875 } else {
2876 refs.push(AstRef {
2877 kind: AstRefKind::Call(method_name.to_string()),
2878 row: node_row,
2879 });
2880 }
2881 }
2882 }
2883 }
2884 let mut cursor = node.walk();
2885 let children: Vec<_> = node.named_children(&mut cursor).collect();
2886 for child in children.into_iter().rev() {
2887 worklist.push(child);
2888 }
2889 continue;
2890 }
2891
2892 if kind == "macro_invocation" {
2894 if let Some(macro_node) = node.child_by_field_name("macro") {
2895 let macro_name = macro_node.utf8_text(source).unwrap_or("");
2896 if !macro_name.is_empty() && !is_builtin(macro_name, config) {
2897 refs.push(AstRef {
2898 kind: AstRefKind::Call(macro_name.to_string()),
2899 row: node_row,
2900 });
2901 }
2902 }
2903 let mut cursor = node.walk();
2904 let children: Vec<_> = node.named_children(&mut cursor).collect();
2905 for child in children.into_iter().rev() {
2906 worklist.push(child);
2907 }
2908 continue;
2909 }
2910
2911 if config.new_expr_nodes.contains(&kind) {
2913 if let Some(type_node) = node.child_by_field_name(config.new_expr_type_field) {
2914 let name = type_node.utf8_text(source).unwrap_or("");
2915 let name = name.rsplit('.').next().unwrap_or(name);
2916 if !name.is_empty() && !is_builtin(name, config) {
2917 refs.push(AstRef {
2918 kind: AstRefKind::Call(name.to_string()),
2919 row: node_row,
2920 });
2921 }
2922 }
2923 let mut cursor = node.walk();
2924 let children: Vec<_> = node.named_children(&mut cursor).collect();
2925 for child in children.into_iter().rev() {
2926 worklist.push(child);
2927 }
2928 continue;
2929 }
2930
2931 if config.composite_literal_nodes.contains(&kind) {
2933 if let Some(type_node) = node.child_by_field_name("type") {
2934 let name = type_node.utf8_text(source).unwrap_or("");
2935 if name.chars().next().map_or(false, |c| c.is_uppercase())
2936 && !is_builtin(name, config)
2937 {
2938 refs.push(AstRef {
2939 kind: AstRefKind::Call(name.to_string()),
2940 row: node_row,
2941 });
2942 }
2943 }
2944 }
2945
2946 let mut cursor = node.walk();
2948 let children: Vec<_> = node.named_children(&mut cursor).collect();
2949 for child in children.into_iter().rev() {
2950 worklist.push(child);
2951 }
2952 }
2953 refs
2954}
2955
2956fn extract_call_ref(
2958 func: tree_sitter::Node,
2959 _entity_id: &str,
2960 entity_name: &str,
2961 source: &[u8],
2962 refs: &mut Vec<AstRef>,
2963 config: &ScopeResolveConfig,
2964 row: usize,
2965) {
2966 let func_kind = func.kind();
2967
2968 if func_kind == "identifier" || func_kind == "simple_identifier" || func_kind == "type_identifier" {
2969 let name = func.utf8_text(source).unwrap_or("");
2970 if !name.is_empty() && name != entity_name && !is_builtin(name, config) {
2971 refs.push(AstRef {
2972 kind: AstRefKind::Call(name.to_string()),
2973 row,
2974 });
2975 }
2976 return;
2977 }
2978
2979 for ma in config.member_access {
2981 if func_kind == ma.node_kind {
2982 extract_member_call_ref(func, ma.object_field, ma.property_field, source, refs, row);
2983 return;
2984 }
2985 }
2986
2987 if config.scoped_call_nodes.contains(&func_kind) {
2989 let text = func.utf8_text(source).unwrap_or("");
2990 let parts: Vec<&str> = text.split("::").collect();
2991 if parts.len() >= 2 {
2992 let is_path_prefix =
2995 parts[0] == "super" || parts[0] == "self" || parts[0] == "crate";
2996 let method_name = parts[parts.len() - 1];
2997 if is_path_prefix {
2998 if !method_name.is_empty() && !is_builtin(method_name, config) {
2999 refs.push(AstRef {
3000 kind: AstRefKind::Call(method_name.to_string()),
3001 row,
3002 });
3003 }
3004 } else {
3005 let type_name = parts[parts.len() - 2];
3006 if !type_name.is_empty() && !method_name.is_empty() {
3007 refs.push(AstRef {
3008 kind: AstRefKind::Call(method_name.to_string()),
3009 row,
3010 });
3011 if type_name.chars().next().map_or(false, |c| c.is_uppercase())
3012 && !is_builtin(type_name, config)
3013 {
3014 refs.push(AstRef {
3015 kind: AstRefKind::Call(type_name.to_string()),
3016 row,
3017 });
3018 }
3019 }
3020 }
3021 }
3022 }
3023}
3024
3025fn extract_member_call_ref(
3029 node: tree_sitter::Node,
3030 object_field: &str,
3031 attr_field: &str,
3032 source: &[u8],
3033 refs: &mut Vec<AstRef>,
3034 row: usize,
3035) {
3036 let obj_text = node
3037 .child_by_field_name(object_field)
3038 .and_then(|n| n.utf8_text(source).ok())
3039 .unwrap_or("");
3040
3041 let attr_text = node
3042 .child_by_field_name(attr_field)
3043 .and_then(|n| {
3044 let text = n.utf8_text(source).ok()?;
3045 Some(text.trim_start_matches('.'))
3047 })
3048 .unwrap_or("");
3049
3050 if !obj_text.is_empty() && !attr_text.is_empty() {
3051 push_method_call_ref(obj_text, attr_text, refs, row);
3052 return;
3053 }
3054
3055 let child_count = node.named_child_count();
3057 if child_count >= 2 {
3058 let obj = node.named_child(0)
3059 .and_then(|n| n.utf8_text(source).ok())
3060 .unwrap_or("");
3061 let last_idx = (child_count - 1) as u32;
3062 let attr = node.named_child(last_idx)
3063 .and_then(|n| n.utf8_text(source).ok())
3064 .unwrap_or("");
3065 if !obj.is_empty() && !attr.is_empty() {
3066 push_method_call_ref(obj, attr, refs, row);
3067 }
3068 }
3069}
3070
3071fn push_method_call_ref(obj: &str, method: &str, refs: &mut Vec<AstRef>, row: usize) {
3072 refs.push(AstRef {
3073 kind: AstRefKind::MethodCall {
3074 receiver: obj.to_string(),
3075 method: method.to_string(),
3076 },
3077 row,
3078 });
3079}
3080
3081fn resolve_ref(
3083 ast_ref: &AstRef,
3084 scope_idx: usize,
3085 scopes: &[Scope],
3086 symbol_table: &HashMap<String, Vec<String>>,
3087 class_members: &HashMap<String, Vec<(String, String)>>,
3088 import_table: &HashMap<(String, String), String>,
3089 instance_attr_types: &HashMap<(String, String), String>,
3090 entity_map: &HashMap<String, EntityInfo>,
3091 file_path: &str,
3092 from_entity_id: &str,
3093 allow_cross_file_calls: bool,
3094) -> Option<(String, RefType, &'static str)> {
3095 match &ast_ref.kind {
3096 AstRefKind::Call(name) => {
3097 if is_local_binding_in_scopes(scope_idx, scopes, name) {
3098 return None;
3099 }
3100
3101 if let Some(eid) = lookup_scope_chain(scope_idx, scopes, name) {
3103 if eid != from_entity_id {
3104 return Some((eid, RefType::Calls, "scope_chain"));
3105 }
3106 }
3107
3108 let key = (file_path.to_string(), name.clone());
3110 if let Some(target_id) = import_table.get(&key) {
3111 return Some((target_id.clone(), RefType::Calls, "import"));
3112 }
3113
3114 if let Some(target_ids) = symbol_table.get(name.as_str()) {
3116 let is_constructor = name.chars().next().map_or(false, |c| c.is_uppercase());
3117 let ref_type = if is_constructor { RefType::TypeRef } else { RefType::Calls };
3118 let target = target_ids
3120 .iter()
3121 .find(|id| {
3122 entity_map
3123 .get(*id)
3124 .map_or(false, |e| e.file_path == file_path)
3125 })
3126 .or_else(|| {
3127 if is_constructor || allow_cross_file_calls {
3131 target_ids.first()
3132 } else {
3133 None
3134 }
3135 });
3136 if let Some(tid) = target {
3137 return Some((tid.clone(), ref_type, "scope_chain"));
3138 }
3139 }
3140
3141 None
3142 }
3143
3144 AstRefKind::MethodCall { receiver: raw_receiver, method } => {
3145 let receiver = raw_receiver.trim_start_matches('!').trim_start_matches('~');
3147 if receiver == "self" || receiver == "this" {
3148 let mut idx = scope_idx;
3150 loop {
3151 if scopes[idx].kind == "class" {
3152 if let Some(eid) = scopes[idx].defs.get(method.as_str()) {
3153 return Some((eid.clone(), RefType::Calls, "scope_chain"));
3154 }
3155 break;
3156 }
3157 match scopes[idx].parent {
3158 Some(p) => idx = p,
3159 None => break,
3160 }
3161 }
3162 return None;
3163 }
3164
3165 if receiver.starts_with("self.") || receiver.starts_with("this.") {
3168 let attr_name = &receiver[5..]; let class_name = find_enclosing_class(scope_idx, scopes, entity_map);
3171 if let Some(cn) = class_name {
3172 if let Some(attr_type) = instance_attr_types.get(&(cn, attr_name.to_string())) {
3174 if let Some(members) = class_members.get(attr_type.as_str()) {
3175 if let Some((_, mid)) = members.iter().find(|(n, _)| n == method) {
3176 return Some((mid.clone(), RefType::Calls, "type_tracking"));
3177 }
3178 }
3179 }
3180 }
3181 }
3182
3183 if receiver.contains('.') && !receiver.starts_with("self.") && !receiver.starts_with("this.") {
3185 if let Some(dot_pos) = receiver.find('.') {
3186 let var_part = &receiver[..dot_pos];
3187 let field_part = &receiver[dot_pos + 1..];
3188 if let Some(var_type) = lookup_type_in_scopes(scope_idx, scopes, var_part) {
3189 if let Some(attr_type) = instance_attr_types.get(&(var_type, field_part.to_string())) {
3190 if let Some(members) = class_members.get(attr_type.as_str()) {
3191 if let Some((_, mid)) = members.iter().find(|(n, _)| n == method) {
3192 return Some((mid.clone(), RefType::Calls, "type_tracking"));
3193 }
3194 }
3195 }
3196 }
3197 }
3198 }
3199
3200 let receiver_type = lookup_type_in_scopes(scope_idx, scopes, receiver);
3202
3203 if let Some(class_name) = receiver_type {
3204 if let Some(members) = class_members.get(class_name.as_str()) {
3205 if let Some((_, mid)) = members.iter().find(|(n, _)| n == method) {
3206 return Some((mid.clone(), RefType::Calls, "type_tracking"));
3207 }
3208 }
3209 }
3210
3211 if !is_local_binding_in_scopes(scope_idx, scopes, receiver) {
3214 if let Some(class_id) = lookup_scope_chain(scope_idx, scopes, receiver) {
3215 if let Some(info) = entity_map.get(&class_id) {
3216 if matches!(info.entity_type.as_str(), "class" | "struct" | "interface")
3217 && info.name == receiver
3218 {
3219 if let Some(members) = class_members.get(&info.name) {
3220 if let Some((_, mid)) = members.iter().find(|(n, _)| n == method) {
3221 return Some((mid.clone(), RefType::Calls, "scope_chain"));
3222 }
3223 }
3224 }
3225 }
3226 }
3227 }
3228
3229 if !is_local_binding_in_scopes(scope_idx, scopes, receiver) {
3231 let key = (file_path.to_string(), receiver.to_string());
3232 if let Some(target_id) = import_table.get(&key) {
3233 if let Some(info) = entity_map.get(target_id) {
3234 if matches!(info.entity_type.as_str(), "class" | "struct") {
3235 if let Some(members) = class_members.get(&info.name) {
3236 if let Some((_, mid)) =
3237 members.iter().find(|(n, _)| n == method)
3238 {
3239 return Some((
3240 mid.clone(),
3241 RefType::Calls,
3242 "type_tracking",
3243 ));
3244 }
3245 }
3246 }
3247 }
3248 }
3249
3250 let key = (file_path.to_string(), format!("{receiver}.{method}"));
3252 if let Some(target_id) = import_table.get(&key) {
3253 return Some((target_id.clone(), RefType::Calls, "import"));
3254 }
3255 }
3256
3257 if file_path.ends_with(".go") {
3260 let key = (file_path.to_string(), method.clone());
3261 if let Some(target_id) = import_table.get(&key) {
3262 return Some((target_id.clone(), RefType::Calls, "import"));
3263 }
3264 }
3265
3266 None
3267 }
3268 }
3269}
3270
3271fn find_enclosing_class(
3273 start_scope: usize,
3274 scopes: &[Scope],
3275 entity_map: &HashMap<String, EntityInfo>,
3276) -> Option<String> {
3277 let mut idx = start_scope;
3278 loop {
3279 if scopes[idx].kind == "class" {
3280 if let Some(ref oid) = scopes[idx].owner_id {
3281 return entity_map.get(oid).map(|e| e.name.clone());
3282 }
3283 }
3284 match scopes[idx].parent {
3285 Some(p) => idx = p,
3286 None => return None,
3287 }
3288 }
3289}
3290
3291fn lookup_scope_chain(
3293 start_scope: usize,
3294 scopes: &[Scope],
3295 name: &str,
3296) -> Option<String> {
3297 let mut idx = start_scope;
3298 loop {
3299 if let Some(eid) = scopes[idx].defs.get(name) {
3300 return Some(eid.clone());
3301 }
3302 match scopes[idx].parent {
3303 Some(p) => idx = p,
3304 None => return None,
3305 }
3306 }
3307}
3308
3309fn is_local_binding_in_scopes(
3311 start_scope: usize,
3312 scopes: &[Scope],
3313 name: &str,
3314) -> bool {
3315 let mut idx = start_scope;
3316 loop {
3317 if scopes[idx].bindings.contains(name) {
3318 return true;
3319 }
3320 match scopes[idx].parent {
3321 Some(p) => idx = p,
3322 None => return false,
3323 }
3324 }
3325}
3326
3327fn lookup_type_in_scopes(
3329 start_scope: usize,
3330 scopes: &[Scope],
3331 var_name: &str,
3332) -> Option<String> {
3333 let mut idx = start_scope;
3334 loop {
3335 if let Some(type_name) = scopes[idx].types.get(var_name) {
3336 return Some(type_name.clone());
3337 }
3338 match scopes[idx].parent {
3339 Some(p) => idx = p,
3340 None => return None,
3341 }
3342 }
3343}
3344
3345fn is_builtin(name: &str, config: &ScopeResolveConfig) -> bool {
3346 if matches!(name, "None" | "True" | "False" | "null" | "undefined" | "nil") {
3348 return true;
3349 }
3350 config.builtins.contains(&name)
3351}