1use std::cmp::Ordering;
15use std::collections::{HashMap, HashSet};
16use std::path::Path;
17use std::sync::{Arc, Mutex, OnceLock};
18
19#[cfg(feature = "parallel")]
20use rayon::prelude::*;
21
22use crate::model::entity::SemanticEntity;
23
24macro_rules! maybe_par_iter {
25 ($slice:expr) => {{
26 #[cfg(feature = "parallel")]
27 {
28 $slice.par_iter()
29 }
30 #[cfg(not(feature = "parallel"))]
31 {
32 $slice.iter()
33 }
34 }};
35}
36use crate::parser::graph::{EntityInfo, RefType};
37use crate::parser::import_resolution::{
38 find_import_file, find_import_target, import_source_matches_file, is_js_ts_file,
39 js_ts_named_exports_from_content, sort_import_candidate_files, JS_TS_EXTENSIONS,
40};
41use crate::parser::plugins::code::languages::{
42 get_language_config, AssignmentStrategy, CallNodeStyle, ClassNameField, InitStrategy,
43 ParamNameField, ScopeResolveConfig,
44};
45
46type AttrToParamIndex<'a> = HashMap<(&'a str, &'a str), Vec<(&'a str, &'a str)>>;
47
48pub struct Scope {
50 parent: Option<usize>,
51 defs: HashMap<String, String>,
53 bindings: HashSet<String>,
55 binding_rows: HashMap<String, Vec<usize>>,
57 types: HashMap<String, String>,
59 pending_call_types: HashMap<String, String>,
62 owner_id: Option<String>,
64 kind: &'static str,
66}
67
68struct AstRef {
70 kind: AstRefKind,
72 row: usize,
74 start_byte: usize,
76 end_byte: usize,
77}
78
79enum AstRefKind {
80 Call {
82 name: String,
83 argument_labels: Option<Vec<Option<String>>>,
84 },
85 ScopedCall { path: String, name: String },
87 MethodCall {
89 receiver: String,
90 method: String,
91 argument_labels: Option<Vec<Option<String>>>,
92 },
93}
94
95struct SwiftCallSignature {
96 argument_labels: Vec<Option<String>>,
97}
98
99enum SwiftOverloadSelection {
100 Matched(String),
101 NoMatch,
102 NotApplicable,
103}
104
105#[derive(Clone, Copy)]
106struct SourceSpan {
107 start_byte: usize,
108 end_byte: usize,
109}
110
111fn entity_creates_reference_scope(entity_type: &str) -> bool {
112 matches!(
113 entity_type,
114 "function"
115 | "method"
116 | "constructor"
117 | "init"
118 | "init_declaration"
119 | "class"
120 | "struct"
121 | "interface"
122 | "impl"
123 | "enum"
124 | "protocol"
125 | "protocol_declaration"
126 | "object_declaration"
127 | "companion_object"
128 | "extension"
129 | "module"
130 | "namespace"
131 )
132}
133
134type ChildRefCheck = (usize, usize, Option<(usize, usize)>);
138
139fn ref_owned_by_entity(
144 ast_ref: &AstRef,
145 entity_span: Option<SourceSpan>,
146 child_ref_checks: &[ChildRefCheck],
147) -> bool {
148 if let Some(entity_span) = entity_span {
149 if ast_ref.end_byte <= entity_span.start_byte || ast_ref.start_byte >= entity_span.end_byte
150 {
151 return false;
152 }
153 }
154
155 let source_line = ast_ref.row + 1;
156 child_ref_checks
157 .iter()
158 .all(|(child_start_line, child_end_line, child_span)| {
159 if source_line < *child_start_line || source_line > *child_end_line {
160 return true;
161 }
162 match child_span {
163 Some((start_byte, end_byte)) => {
164 ast_ref.end_byte <= *start_byte || ast_ref.start_byte >= *end_byte
165 }
166 None => false,
167 }
168 })
169}
170
171fn find_entity_source_spans<'a>(
172 entities: &[&'a SemanticEntity],
173 source: &str,
174) -> HashMap<&'a str, SourceSpan> {
175 let mut spans = HashMap::new();
176 let line_starts = source_line_starts(source);
177 for entity in entities {
178 if entity.content.is_empty() {
179 continue;
180 }
181
182 if let Some(span) = find_entity_source_span(entity, source, &line_starts) {
183 spans.insert(entity.id.as_str(), span);
184 }
185 }
186 spans
187}
188
189fn source_line_starts(source: &str) -> Vec<usize> {
190 let mut starts = vec![0];
191 for (idx, byte) in source.bytes().enumerate() {
192 if byte == b'\n' && idx + 1 < source.len() {
193 starts.push(idx + 1);
194 }
195 }
196 starts
197}
198
199fn find_entity_source_span(
200 entity: &SemanticEntity,
201 source: &str,
202 line_starts: &[usize],
203) -> Option<SourceSpan> {
204 if entity.file_path.ends_with(".swift") && entity.entity_type == "property" {
205 if let Some(span) = swift_property_binding_span(entity, source.as_bytes(), line_starts) {
206 return Some(span);
207 }
208 }
209
210 let line_index = entity.start_line.checked_sub(1)?;
211 let line_start = *line_starts.get(line_index)?;
212
213 if let Some(span) = source_span_at(source, &entity.content, line_start) {
214 return Some(span);
215 }
216
217 let line_end = line_starts
218 .get(line_index + 1)
219 .copied()
220 .unwrap_or(source.len());
221 let line = source.get(line_start..line_end)?;
222 let trimmed_line_start = line_start + line.len().saturating_sub(line.trim_start().len());
223 if trimmed_line_start != line_start {
224 if let Some(span) = source_span_at(source, &entity.content, trimmed_line_start) {
225 return Some(span);
226 }
227 }
228
229 let first_content_line = entity.content.lines().next().unwrap_or("").trim_start();
230 if first_content_line.is_empty() {
231 return None;
232 }
233
234 for (candidate_offset, _) in line.match_indices(first_content_line) {
235 if let Some(span) = source_span_at(source, &entity.content, line_start + candidate_offset) {
236 return Some(span);
237 }
238 }
239
240 None
241}
242
243fn source_span_at(source: &str, content: &str, start_byte: usize) -> Option<SourceSpan> {
244 if source.get(start_byte..)?.starts_with(content) {
245 Some(SourceSpan {
246 start_byte,
247 end_byte: start_byte + content.len(),
248 })
249 } else {
250 None
251 }
252}
253
254fn line_start(line_starts: &[usize], line: usize) -> usize {
255 line_starts
256 .get(line.saturating_sub(1))
257 .copied()
258 .unwrap_or(0)
259}
260
261fn line_end(line_starts: &[usize], source_len: usize, line: usize) -> usize {
262 line_starts
263 .get(line)
264 .copied()
265 .map(|offset| offset.saturating_sub(1))
266 .unwrap_or(source_len)
267}
268
269fn swift_property_binding_span(
270 entity: &SemanticEntity,
271 source: &[u8],
272 line_starts: &[usize],
273) -> Option<SourceSpan> {
274 let search_start = line_start(line_starts, entity.start_line);
275 let search_end = line_end(line_starts, source.len(), entity.end_line).min(source.len());
276 let haystack = source.get(search_start..search_end)?;
277 let content = entity.content.trim();
278 if !content.is_empty() {
279 if let Some(local_start) = find_subslice(haystack, content.as_bytes()) {
280 let start = search_start + local_start;
281 return Some(SourceSpan {
282 start_byte: start,
283 end_byte: start + content.len(),
284 });
285 }
286 }
287
288 let name = entity.name.as_bytes();
289 if name.is_empty() {
290 return None;
291 }
292 let mut local_search_start = 0;
293 while let Some(local_start) = find_subslice(&haystack[local_search_start..], name) {
294 let local_start = local_search_start + local_start;
295 let start = search_start + local_start;
296 let end = start + entity.name.len();
297 if !identifier_boundary(source, start, end) {
298 local_search_start = local_start + name.len();
299 continue;
300 }
301 let segment_end = swift_binding_segment_end(source, end, search_end);
302 return Some(SourceSpan {
303 start_byte: start,
304 end_byte: segment_end,
305 });
306 }
307 None
308}
309
310fn find_subslice(haystack: &[u8], needle: &[u8]) -> Option<usize> {
311 if needle.is_empty() {
312 return Some(0);
313 }
314 haystack
315 .windows(needle.len())
316 .position(|window| window == needle)
317}
318
319fn swift_binding_segment_end(source: &[u8], start: usize, search_end: usize) -> usize {
320 let mut depth = 0usize;
321 let mut idx = start;
322 let mut string_delimiter: Option<u8> = None;
323 while idx < search_end {
324 let byte = source[idx];
325 if let Some(delimiter) = string_delimiter {
326 if byte == b'\\' {
327 idx = (idx + 2).min(search_end);
328 continue;
329 }
330 if byte == delimiter {
331 string_delimiter = None;
332 }
333 idx += 1;
334 continue;
335 }
336
337 match byte {
338 b'"' | b'\'' => string_delimiter = Some(byte),
339 b'(' | b'[' | b'{' => depth += 1,
340 b')' | b']' | b'}' => depth = depth.saturating_sub(1),
341 b',' if depth == 0 => return idx,
342 _ => {}
343 }
344 idx += 1;
345 }
346 search_end
347}
348
349fn identifier_boundary(source: &[u8], start: usize, end: usize) -> bool {
350 let before = start
351 .checked_sub(1)
352 .and_then(|idx| source.get(idx))
353 .copied();
354 let after = source.get(end).copied();
355 !before.map_or(false, is_identifier_byte) && !after.map_or(false, is_identifier_byte)
356}
357
358fn is_identifier_byte(byte: u8) -> bool {
359 byte.is_ascii_alphanumeric() || byte == b'_'
360}
361
362pub struct ScopeResult {
364 pub edges: Vec<(String, String, RefType)>,
365 pub resolution_log: Vec<ResolutionEntry>,
367}
368
369pub(crate) struct ScopeResultFull {
370 pub(crate) edges: Vec<(String, String, RefType)>,
371 pub(crate) resolution_log: Vec<ResolutionEntry>,
372 pub(crate) consumed_words: HashMap<String, HashSet<String>>,
373}
374
375#[derive(Clone)]
376pub struct ResolutionEntry {
377 pub from_entity: String,
378 pub reference: String,
379 pub resolved_to: Option<String>,
380 pub method: &'static str, }
382
383pub(crate) struct PreBuiltLookups {
393 pub(crate) symbol_table: Arc<HashMap<String, Vec<String>>>,
394 pub(crate) class_members: HashMap<String, Vec<(String, String)>>,
395 pub(crate) owner_members: HashMap<String, Vec<(String, String)>>,
396 pub(crate) entity_ranges: HashMap<String, Vec<(usize, usize, String)>>,
397 pub(crate) go_pkg_index: HashMap<String, Vec<(String, String)>>,
400}
401
402struct TsDefaultExportTable {
403 exports_by_file: HashMap<String, String>,
404 sorted_files: Vec<String>,
405}
406
407struct TsDefaultReExport {
408 file_path: String,
409 original_name: String,
410 module_path: String,
411}
412
413struct TopLevelEntityIndex {
414 entities_by_file: HashMap<String, Vec<(String, String)>>,
415 sorted_files: Vec<String>,
416}
417
418struct FileEntityLookup<'a> {
419 by_name: HashMap<&'a str, Vec<&'a SemanticEntity>>,
420}
421
422impl<'a> FileEntityLookup<'a> {
423 fn new(file_entities: &[&'a SemanticEntity]) -> Self {
424 let mut by_name: HashMap<&'a str, Vec<&'a SemanticEntity>> = HashMap::new();
425 for entity in file_entities {
426 by_name
427 .entry(entity.name.as_str())
428 .or_default()
429 .push(*entity);
430 }
431 Self { by_name }
432 }
433
434 fn first_id_by_name(&self, name: &str) -> Option<&'a str> {
438 self.by_name
439 .get(name)
440 .and_then(|entities| entities.first())
441 .map(|entity| entity.id.as_str())
442 }
443
444 fn find_at_line<F>(
445 &self,
446 name: &str,
447 line: usize,
448 type_matches: F,
449 ) -> Option<&'a SemanticEntity>
450 where
451 F: Fn(&SemanticEntity) -> bool,
452 {
453 if name.is_empty() {
454 return None;
455 }
456 self.by_name.get(name)?.iter().find_map(|entity| {
457 if entity.start_line <= line && line <= entity.end_line && type_matches(entity) {
458 Some(*entity)
459 } else {
460 None
461 }
462 })
463 }
464}
465
466#[derive(Default)]
467struct ScopeLookupCache {
468 defs: HashMap<usize, HashMap<String, Option<String>>>,
469 local_bindings: HashMap<usize, HashMap<String, bool>>,
470 types: HashMap<usize, HashMap<String, Option<String>>>,
471 enclosing_classes: HashMap<usize, Option<String>>,
472}
473
474pub(crate) fn class_member_owner_name(parent: &EntityInfo) -> Option<&str> {
475 matches!(
476 parent.entity_type.as_str(),
477 "class"
478 | "struct"
479 | "interface"
480 | "impl"
481 | "enum"
482 | "protocol"
483 | "protocol_declaration"
484 | "object_declaration"
485 | "companion_object"
486 | "extension"
487 )
488 .then_some(parent.name.as_str())
489}
490
491fn sort_symbol_table_targets_by_source(
492 symbol_table: &mut HashMap<String, Vec<String>>,
493 entity_map: &HashMap<String, EntityInfo>,
494) {
495 for target_ids in symbol_table.values_mut() {
496 if target_ids.len() > 1 {
497 target_ids.sort_unstable_by(|left, right| {
498 compare_entity_ids_by_source(left, right, entity_map)
499 });
500 }
501 }
502}
503
504fn compare_entity_ids_by_source(
505 left: &str,
506 right: &str,
507 entity_map: &HashMap<String, EntityInfo>,
508) -> Ordering {
509 match (entity_map.get(left), entity_map.get(right)) {
510 (Some(left), Some(right)) => (
511 left.file_path.as_str(),
512 left.start_line,
513 left.end_line,
514 left.id.as_str(),
515 )
516 .cmp(&(
517 right.file_path.as_str(),
518 right.start_line,
519 right.end_line,
520 right.id.as_str(),
521 )),
522 (Some(_), None) => Ordering::Less,
523 (None, Some(_)) => Ordering::Greater,
524 (None, None) => left.cmp(right),
525 }
526}
527
528pub fn resolve_with_scopes(
530 root: &Path,
531 file_paths: &[String],
532 all_entities: &[SemanticEntity],
533 entity_map: &HashMap<String, EntityInfo>,
534 pre_parsed: Option<Vec<(String, String, tree_sitter::Tree)>>,
535) -> ScopeResult {
536 let result = resolve_with_scopes_full(
537 root,
538 file_paths,
539 all_entities,
540 entity_map,
541 pre_parsed,
542 None,
543 None,
544 true,
545 );
546 ScopeResult {
547 edges: result.edges,
548 resolution_log: result.resolution_log,
549 }
550}
551
552pub(crate) fn resolve_with_scopes_full(
554 root: &Path,
555 file_paths: &[String],
556 all_entities: &[SemanticEntity],
557 entity_map: &HashMap<String, EntityInfo>,
558 pre_parsed: Option<Vec<(String, String, tree_sitter::Tree)>>,
559 pre_built: Option<&PreBuiltLookups>,
560 pre_built_import_table: Option<&HashMap<(String, String), String>>,
561 emit_local_binding_log: bool,
562) -> ScopeResultFull {
563 resolve_with_scopes_full_inner(
564 root,
565 file_paths,
566 all_entities,
567 entity_map,
568 pre_parsed,
569 pre_built,
570 pre_built_import_table,
571 emit_local_binding_log,
572 None,
573 )
574}
575
576pub(crate) fn resolve_with_scopes_full_for_entities(
577 root: &Path,
578 file_paths: &[String],
579 all_entities: &[SemanticEntity],
580 entity_map: &HashMap<String, EntityInfo>,
581 pre_parsed: Option<Vec<(String, String, tree_sitter::Tree)>>,
582 pre_built: Option<&PreBuiltLookups>,
583 pre_built_import_table: Option<&HashMap<(String, String), String>>,
584 emit_entity_ids: &HashSet<&str>,
585) -> ScopeResultFull {
586 resolve_with_scopes_full_inner(
587 root,
588 file_paths,
589 all_entities,
590 entity_map,
591 pre_parsed,
592 pre_built,
593 pre_built_import_table,
594 false,
595 Some(emit_entity_ids),
596 )
597}
598
599fn resolve_with_scopes_full_inner(
600 root: &Path,
601 file_paths: &[String],
602 all_entities: &[SemanticEntity],
603 entity_map: &HashMap<String, EntityInfo>,
604 pre_parsed: Option<Vec<(String, String, tree_sitter::Tree)>>,
605 pre_built: Option<&PreBuiltLookups>,
606 pre_built_import_table: Option<&HashMap<(String, String), String>>,
607 emit_local_binding_log: bool,
608 emit_entity_ids: Option<&HashSet<&str>>,
609) -> ScopeResultFull {
610 let mut all_edges: Vec<(String, String, RefType)> = Vec::new();
611 let mut log: Vec<ResolutionEntry> = Vec::new();
612 let mut consumed_words: HashMap<String, HashSet<String>> = HashMap::new();
613
614 let owned_lookups;
616 let lookups = if let Some(pb) = pre_built {
617 pb
618 } else {
619 let mut symbol_table: HashMap<String, Vec<String>> = HashMap::new();
620 let mut class_members: HashMap<String, Vec<(String, String)>> = HashMap::new();
621 let mut owner_members: HashMap<String, Vec<(String, String)>> = HashMap::new();
622 let mut entity_ranges: HashMap<String, Vec<(usize, usize, String)>> = HashMap::new();
623
624 for entity in all_entities {
625 symbol_table
626 .entry(entity.name.clone())
627 .or_default()
628 .push(entity.id.clone());
629
630 if let Some(ref pid) = entity.parent_id {
631 owner_members
632 .entry(pid.clone())
633 .or_default()
634 .push((entity.name.clone(), entity.id.clone()));
635 if let Some(parent) = entity_map.get(pid) {
636 if let Some(owner_name) = class_member_owner_name(parent) {
637 class_members
638 .entry(owner_name.to_string())
639 .or_default()
640 .push((entity.name.clone(), entity.id.clone()));
641 }
642 }
643 }
644
645 if entity.entity_type == "method" && entity.file_path.ends_with(".go") {
646 if let Some(struct_name) = extract_go_receiver_type(&entity.content) {
647 class_members
648 .entry(struct_name)
649 .or_default()
650 .push((entity.name.clone(), entity.id.clone()));
651 }
652 }
653
654 entity_ranges
655 .entry(entity.file_path.clone())
656 .or_default()
657 .push((entity.start_line, entity.end_line, entity.id.clone()));
658 }
659 sort_symbol_table_targets_by_source(&mut symbol_table, entity_map);
660 for members in class_members.values_mut() {
661 members.sort_unstable();
662 }
663 for members in owner_members.values_mut() {
664 members.sort_unstable();
665 }
666 for ranges in entity_ranges.values_mut() {
667 ranges.sort_unstable();
668 }
669
670 let go_pkg_index = build_go_pkg_index(&symbol_table, entity_map);
672
673 owned_lookups = PreBuiltLookups {
674 symbol_table: Arc::new(symbol_table),
675 class_members,
676 owner_members,
677 entity_ranges,
678 go_pkg_index,
679 };
680 &owned_lookups
681 };
682 let symbol_table = lookups.symbol_table.as_ref();
683 let class_members = &lookups.class_members;
684 let owner_members = &lookups.owner_members;
685 let entity_ranges = &lookups.entity_ranges;
686 let go_pkg_index = &lookups.go_pkg_index;
687
688 let mut entities_by_file: HashMap<&str, Vec<&SemanticEntity>> = HashMap::new();
690 for entity in all_entities {
691 entities_by_file
692 .entry(entity.file_path.as_str())
693 .or_default()
694 .push(entity);
695 }
696
697 let mut children_by_parent: HashMap<&str, Vec<&SemanticEntity>> = HashMap::new();
699 for entity in all_entities {
700 if let Some(ref pid) = entity.parent_id {
701 children_by_parent
702 .entry(pid.as_str())
703 .or_default()
704 .push(entity);
705 }
706 }
707
708 let mut return_type_map: HashMap<String, String> = HashMap::new();
710
711 let mut instance_attr_types: HashMap<(String, String), String> = HashMap::new();
713
714 let mut init_params: HashMap<String, Vec<String>> = HashMap::new();
717 let mut attr_to_param: HashMap<(String, String), String> = HashMap::new();
718
719 let mut owned_parsed_files: Vec<(String, String, tree_sitter::Tree)> = Vec::new();
721 let pre_set: std::collections::HashSet<String> = if let Some(pp) = pre_parsed {
722 let set = pp.iter().map(|(fp, _, _)| fp.clone()).collect();
723 owned_parsed_files = pp;
724 set
725 } else {
726 std::collections::HashSet::new()
727 };
728 for file_path in file_paths {
730 if pre_set.contains(file_path) {
731 continue;
732 }
733 let full_path = root.join(file_path);
734 let content = match std::fs::read_to_string(&full_path) {
735 Ok(c) => c,
736 Err(_) => continue,
737 };
738 let ext = file_path.rfind('.').map(|i| &file_path[i..]).unwrap_or("");
739 let config = match get_language_config(ext) {
740 Some(c) => c,
741 None => continue,
742 };
743 let language = match (config.get_language)() {
744 Some(l) => l,
745 None => continue,
746 };
747 let mut parser = tree_sitter::Parser::new();
748 let _ = parser.set_language(&language);
749 if let Some(tree) = parser.parse(content.as_bytes(), None) {
750 owned_parsed_files.push((file_path.clone(), content, tree));
751 }
752 }
753 let parsed_files: &[(String, String, tree_sitter::Tree)] = &owned_parsed_files;
754 let content_by_file = OnceLock::new();
755 let exported_names_by_file: Mutex<HashMap<String, Arc<HashSet<String>>>> =
756 Mutex::new(HashMap::new());
757 let ts_default_exports = if pre_built_import_table.is_some() {
762 TsDefaultExportTable {
763 exports_by_file: HashMap::new(),
764 sorted_files: Vec::new(),
765 }
766 } else {
767 build_ts_default_export_table(parsed_files, &symbol_table, entity_map)
768 };
769 let top_level_entities = OnceLock::new();
770
771 let pass1_results: Vec<(
775 HashMap<String, String>,
776 HashMap<(String, String), String>,
777 HashMap<String, Vec<String>>,
778 HashMap<(String, String), String>,
779 )> = maybe_par_iter!(parsed_files)
780 .filter_map(|(file_path, content, tree)| {
781 let source = content.as_bytes();
782 let ext = file_path.rfind('.').map(|i| &file_path[i..]).unwrap_or("");
783 let config = get_language_config(ext).and_then(|c| c.scope_resolve)?;
784
785 let file_entities = entities_by_file
786 .get(file_path.as_str())
787 .map(|v| v.as_slice())
788 .unwrap_or(&[]);
789 let file_lookup = FileEntityLookup::new(file_entities);
790
791 let mut local_return_type_map: HashMap<String, String> = HashMap::new();
792 scan_return_types(
793 tree.root_node(),
794 file_path,
795 &file_lookup,
796 source,
797 &mut local_return_type_map,
798 config,
799 );
800
801 let mut local_instance_attr_types: HashMap<(String, String), String> = HashMap::new();
802 let mut local_init_params: HashMap<String, Vec<String>> = HashMap::new();
803 let mut local_attr_to_param: HashMap<(String, String), String> = HashMap::new();
804 scan_init_self_attrs(
805 tree.root_node(),
806 file_path,
807 file_entities,
808 entity_map,
809 source,
810 &mut local_instance_attr_types,
811 &mut local_init_params,
812 &mut local_attr_to_param,
813 config,
814 );
815
816 Some((
817 local_return_type_map,
818 local_instance_attr_types,
819 local_init_params,
820 local_attr_to_param,
821 ))
822 })
823 .collect();
824
825 for (local_rtm, local_iat, local_ip, local_atp) in pass1_results {
826 return_type_map.extend(local_rtm);
827 instance_attr_types.extend(local_iat);
828 init_params.extend(local_ip);
829 attr_to_param.extend(local_atp);
830 }
831
832 infer_constructor_param_types(
836 parsed_files,
837 &return_type_map,
838 &init_params,
839 &attr_to_param,
840 &symbol_table,
841 &mut instance_attr_types,
842 );
843 let func_name_return_types = deterministic_return_types_by_name(&return_type_map, symbol_table);
844
845 let swift_call_signatures = if parsed_files
846 .iter()
847 .any(|(file_path, _, _)| file_path.ends_with(".swift"))
848 {
849 build_swift_call_signatures(parsed_files, all_entities, &entity_ranges, entity_map)
850 } else {
851 HashMap::new()
852 };
853
854 let import_table_by_file: HashMap<&str, Vec<(&str, &str)>> =
859 if let Some(import_table) = pre_built_import_table {
860 let mut grouped: HashMap<&str, Vec<(&str, &str)>> = HashMap::new();
861 for ((import_file_path, local_name), target_id) in import_table {
862 grouped
863 .entry(import_file_path.as_str())
864 .or_default()
865 .push((local_name.as_str(), target_id.as_str()));
866 }
867 grouped
868 } else {
869 HashMap::new()
870 };
871
872 let per_file_results: Vec<(
874 Vec<(String, String, RefType)>,
875 Vec<ResolutionEntry>,
876 HashMap<String, HashSet<String>>,
877 )> = maybe_par_iter!(parsed_files)
878 .filter_map(|(file_path, content, tree)| {
879 let source = content.as_bytes();
880 let ext = file_path.rfind('.').map(|i| &file_path[i..]).unwrap_or("");
881 let config = get_language_config(ext).and_then(|c| c.scope_resolve)?;
882
883 let mut scopes: Vec<Scope> = vec![Scope {
884 parent: None,
885 defs: HashMap::new(),
886 bindings: HashSet::new(),
887 binding_rows: HashMap::new(),
888 types: HashMap::new(),
889 pending_call_types: HashMap::new(),
890 owner_id: None,
891 kind: "module",
892 }];
893
894 let mut entity_scope_map: HashMap<String, usize> = HashMap::new();
895 let mut entity_inner_scope: HashMap<String, usize> = HashMap::new();
896
897 if let Some(ranges) = entity_ranges.get(file_path.as_str()) {
898 for (_start, _end, eid) in ranges {
899 if let Some(info) = entity_map.get(eid) {
900 if info.parent_id.is_none() {
901 scopes[0].defs.insert(info.name.clone(), eid.clone());
902 entity_scope_map.insert(eid.clone(), 0);
903 }
904 }
905 }
906 }
907
908 let file_entities: Vec<&SemanticEntity> = entities_by_file
909 .get(file_path.as_str())
910 .map(|v| v.as_slice())
911 .unwrap_or(&[])
912 .to_vec();
913 let file_lookup = FileEntityLookup::new(&file_entities);
914 let entity_spans = find_entity_source_spans(&file_entities, content);
915
916 build_scopes_from_ast(
917 tree.root_node(),
918 0,
919 &mut scopes,
920 &mut entity_scope_map,
921 &mut entity_inner_scope,
922 &file_lookup,
923 &children_by_parent,
924 entity_map,
925 file_path,
926 source,
927 config,
928 );
929
930 let mut local_import_table: HashMap<(String, String), String> = HashMap::new();
931 if pre_built_import_table.is_some() {
932 if let Some(entries) = import_table_by_file.get(file_path.as_str()) {
933 for (local_name, target_id) in entries {
934 local_import_table.insert(
935 (file_path.clone(), (*local_name).to_string()),
936 (*target_id).to_string(),
937 );
938 scopes[0]
939 .defs
940 .insert((*local_name).to_string(), (*target_id).to_string());
941 }
942 }
943 }
944 extract_imports_from_ast(
945 tree.root_node(),
946 file_path,
947 source,
948 &symbol_table,
949 entity_map,
950 &mut local_import_table,
951 &mut scopes,
952 config,
953 &go_pkg_index,
954 &ts_default_exports,
955 &top_level_entities,
956 parsed_files,
957 &content_by_file,
958 &exported_names_by_file,
959 pre_built_import_table.is_some(),
960 );
961
962 let local_import_by_name: HashMap<&str, &str> = local_import_table
966 .iter()
967 .map(|((_, name), target_id)| (name.as_str(), target_id.as_str()))
968 .collect();
969
970 inject_return_type_bindings(
972 &mut scopes,
973 &func_name_return_types,
974 &return_type_map,
975 &local_import_by_name,
976 );
977
978 let mut file_edges: Vec<(String, String, RefType)> = Vec::new();
979 let mut file_log: Vec<ResolutionEntry> = Vec::new();
980 let mut file_consumed_words: HashMap<String, HashSet<String>> = HashMap::new();
981
982 let all_file_refs = collect_all_file_refs(tree.root_node(), source, config);
984 let refs_by_row = build_refs_by_row(&all_file_refs);
985 let descendant_ranges_by_entity =
986 build_descendant_ranges_by_entity(&file_entities, entity_map);
987 let mut lookup_cache = ScopeLookupCache::default();
988
989 for entity in &file_entities {
990 if emit_entity_ids
991 .as_ref()
992 .is_some_and(|ids| !ids.contains(entity.id.as_str()))
993 {
994 continue;
995 }
996
997 let scope_idx = entity_inner_scope
998 .get(&entity.id)
999 .or_else(|| entity_scope_map.get(&entity.id))
1000 .copied()
1001 .unwrap_or(0);
1002
1003 let start_row = entity.start_line.saturating_sub(1).min(refs_by_row.len());
1004 let end_row = entity.end_line.min(refs_by_row.len()).max(start_row);
1005 if emit_local_binding_log {
1006 log_scope_bindings(
1007 &mut file_log,
1008 &entity.id,
1009 &scopes[scope_idx],
1010 start_row,
1011 end_row,
1012 &descendant_ranges_by_entity,
1013 );
1014 }
1015 let entity_consumed = file_consumed_words.entry(entity.id.clone()).or_default();
1020 add_local_bindings_to_consumed_words(entity_consumed, scope_idx, &scopes);
1021
1022 let entity_span = entity_spans.get(entity.id.as_str()).copied();
1023 let child_ref_checks: Vec<ChildRefCheck> = children_by_parent
1024 .get(entity.id.as_str())
1025 .map(|children| {
1026 children
1027 .iter()
1028 .filter(|child| {
1029 entity_creates_reference_scope(&child.entity_type)
1030 && child.file_path == entity.file_path
1031 })
1032 .map(|child| {
1033 let span = entity_spans
1034 .get(child.id.as_str())
1035 .map(|span| (span.start_byte, span.end_byte));
1036 (child.start_line, child.end_line, span)
1037 })
1038 .collect()
1039 })
1040 .unwrap_or_default();
1041 let entity_descendant_ranges = descendant_ranges_by_entity.get(&entity.id);
1042
1043 let allow_implicit_instance_member_receiver =
1044 allows_implicit_instance_member_receiver(
1045 file_path,
1046 &entity.entity_type,
1047 &entity.content,
1048 );
1049
1050 for row_refs in &refs_by_row[start_row..end_row] {
1052 for &ref_idx in row_refs {
1053 let ast_ref = &all_file_refs[ref_idx];
1054 if !ref_owned_by_entity(ast_ref, entity_span, &child_ref_checks) {
1055 continue;
1056 }
1057 if row_in_descendant_ranges(entity_descendant_ranges, ast_ref.row) {
1058 continue;
1059 }
1060 let is_self_ref = match &ast_ref.kind {
1062 AstRefKind::Call { name, .. } => name == &entity.name,
1063 AstRefKind::ScopedCall { .. } => false,
1064 AstRefKind::MethodCall { .. } => false,
1065 };
1066 if is_self_ref {
1067 continue;
1068 }
1069
1070 let allow_cross_file = config.import_extractor.is_none();
1073 let resolution = resolve_ref(
1074 ast_ref,
1075 scope_idx,
1076 &scopes,
1077 &symbol_table,
1078 &class_members,
1079 &owner_members,
1080 &local_import_by_name,
1081 &instance_attr_types,
1082 entity_map,
1083 &swift_call_signatures,
1084 file_path,
1085 &entity.id,
1086 allow_cross_file,
1087 allow_implicit_instance_member_receiver,
1088 &file_lookup,
1089 &mut lookup_cache,
1090 );
1091
1092 if let Some((target_id, ref_type, method)) = resolution {
1093 if target_id != entity.id {
1094 let is_parent_child =
1095 entity.parent_id.as_ref().map_or(false, |pid| {
1096 pid == &target_id
1097 || entity_map.get(&target_id).map_or(false, |t| {
1098 t.parent_id.as_ref() == Some(&entity.id)
1099 })
1100 });
1101
1102 if !is_parent_child {
1103 let reference = ref_description(ast_ref);
1104 file_edges.push((
1105 entity.id.clone(),
1106 target_id.clone(),
1107 ref_type,
1108 ));
1109 add_scope_reference_words(entity_consumed, &reference);
1110 file_log.push(ResolutionEntry {
1111 from_entity: entity.id.clone(),
1112 reference,
1113 resolved_to: Some(target_id),
1114 method,
1115 });
1116 }
1117 }
1118 } else {
1119 let reference = ref_description(ast_ref);
1120 add_scope_reference_words(entity_consumed, &reference);
1121 file_log.push(ResolutionEntry {
1122 from_entity: entity.id.clone(),
1123 reference,
1124 resolved_to: None,
1125 method: "unresolved",
1126 });
1127 }
1128 }
1129 }
1130 }
1131
1132 Some((file_edges, file_log, file_consumed_words))
1133 })
1134 .collect();
1135
1136 for (file_edges, file_log, file_consumed_words) in per_file_results {
1137 all_edges.extend(file_edges);
1138 log.extend(file_log);
1139 for (entity_id, words) in file_consumed_words {
1140 consumed_words.entry(entity_id).or_default().extend(words);
1141 }
1142 }
1143
1144 let mut seen: std::collections::HashSet<(String, String)> =
1146 std::collections::HashSet::with_capacity(all_edges.len());
1147 let deduped_edges: Vec<(String, String, RefType)> = {
1148 let mut result = Vec::with_capacity(all_edges.len());
1149 for edge in all_edges {
1150 if seen.insert((edge.0.clone(), edge.1.clone())) {
1151 result.push(edge);
1152 }
1153 }
1154 result
1155 };
1156 let all_edges = deduped_edges;
1157
1158 ScopeResultFull {
1159 edges: all_edges,
1160 resolution_log: log,
1161 consumed_words,
1162 }
1163}
1164
1165fn ref_description(ast_ref: &AstRef) -> String {
1166 match &ast_ref.kind {
1167 AstRefKind::Call {
1168 name,
1169 argument_labels,
1170 } => format!(
1171 "{}({})",
1172 name,
1173 format_argument_labels(argument_labels.as_deref())
1174 ),
1175 AstRefKind::ScopedCall { path, name } => format!("{}::{}()", path, name),
1176 AstRefKind::MethodCall {
1177 receiver,
1178 method,
1179 argument_labels,
1180 } => format!(
1181 "{}.{}({})",
1182 receiver,
1183 method,
1184 format_argument_labels(argument_labels.as_deref())
1185 ),
1186 }
1187}
1188
1189fn format_argument_labels(argument_labels: Option<&[Option<String>]>) -> String {
1190 argument_labels
1191 .map(|labels| {
1192 labels
1193 .iter()
1194 .map(|label| {
1195 label
1196 .as_deref()
1197 .map_or("_:".to_string(), |label| format!("{label}:"))
1198 })
1199 .collect::<Vec<_>>()
1200 .join(", ")
1201 })
1202 .unwrap_or_default()
1203}
1204
1205fn add_scope_reference_words(words: &mut HashSet<String>, reference: &str) {
1206 let reference = reference.strip_suffix("()").unwrap_or(reference);
1207 let reference = reference
1208 .split_once('(')
1209 .map_or(reference, |(name, _)| name);
1210 if let Some((receiver, member)) = reference.rsplit_once('.') {
1211 if !receiver.is_empty() {
1212 words.insert(receiver.to_string());
1213 }
1214 if !member.is_empty() {
1215 words.insert(member.to_string());
1216 }
1217 } else if reference.contains("::") {
1218 for part in reference.split("::").filter(|part| !part.is_empty()) {
1219 words.insert(part.to_string());
1220 }
1221 } else if !reference.is_empty() {
1222 words.insert(reference.to_string());
1223 }
1224}
1225
1226fn add_local_bindings_to_consumed_words(
1227 words: &mut HashSet<String>,
1228 start_scope: usize,
1229 scopes: &[Scope],
1230) {
1231 let mut idx = Some(start_scope);
1232 while let Some(scope_idx) = idx {
1233 words.extend(scopes[scope_idx].bindings.iter().cloned());
1234 idx = scopes[scope_idx].parent;
1235 }
1236}
1237
1238fn log_scope_bindings(
1239 file_log: &mut Vec<ResolutionEntry>,
1240 from_entity: &str,
1241 scope: &Scope,
1242 start_row: usize,
1243 end_row: usize,
1244 descendant_ranges_by_entity: &HashMap<String, Vec<(usize, usize)>>,
1245) {
1246 let mut bindings: Vec<&String> = scope.bindings.iter().collect();
1247 bindings.sort();
1248 for binding in bindings {
1249 let belongs_to_entity = scope.binding_rows.get(binding).map_or(false, |rows| {
1250 rows.iter().any(|row| {
1251 *row >= start_row
1252 && *row < end_row
1253 && !row_belongs_to_descendant(descendant_ranges_by_entity, from_entity, *row)
1254 })
1255 });
1256 if !belongs_to_entity {
1257 continue;
1258 }
1259 file_log.push(ResolutionEntry {
1260 from_entity: from_entity.to_string(),
1261 reference: binding.clone(),
1262 resolved_to: None,
1263 method: "local_binding",
1264 });
1265 }
1266}
1267
1268fn build_descendant_ranges_by_entity(
1269 file_entities: &[&SemanticEntity],
1270 entity_map: &HashMap<String, EntityInfo>,
1271) -> HashMap<String, Vec<(usize, usize)>> {
1272 let mut ranges_by_entity: HashMap<String, Vec<(usize, usize)>> = HashMap::new();
1273 let mut sorted_entities = file_entities.to_vec();
1274 sorted_entities.sort_by(|left, right| {
1275 left.start_line
1276 .cmp(&right.start_line)
1277 .then_with(|| right.end_line.cmp(&left.end_line))
1278 .then_with(|| left.id.cmp(&right.id))
1279 });
1280
1281 let mut ancestor_stack: Vec<&SemanticEntity> = Vec::new();
1282 for entity in sorted_entities {
1283 while ancestor_stack.last().map_or(false, |candidate| {
1284 !is_strict_enclosing_range(candidate, entity)
1285 }) {
1286 ancestor_stack.pop();
1287 }
1288
1289 if !entity_creates_reference_scope(&entity.entity_type) {
1290 ancestor_stack.push(entity);
1291 continue;
1292 }
1293
1294 let child_range = (entity.start_line.saturating_sub(1), entity.end_line);
1295 let mut current = entity.parent_id.as_deref();
1296 let mut visited = HashSet::new();
1297 while let Some(parent_id) = current {
1298 if !visited.insert(parent_id.to_string()) {
1299 break;
1300 }
1301 ranges_by_entity
1302 .entry(parent_id.to_string())
1303 .or_default()
1304 .push(child_range);
1305 current = entity_map
1306 .get(parent_id)
1307 .and_then(|parent| parent.parent_id.as_deref());
1308 }
1309
1310 for ancestor in &ancestor_stack {
1311 ranges_by_entity
1312 .entry(ancestor.id.clone())
1313 .or_default()
1314 .push(child_range);
1315 }
1316
1317 ancestor_stack.push(entity);
1318 }
1319 for ranges in ranges_by_entity.values_mut() {
1320 ranges.sort_unstable();
1321 ranges.dedup();
1322 }
1323 ranges_by_entity
1324}
1325
1326fn is_strict_enclosing_range(candidate: &SemanticEntity, child: &SemanticEntity) -> bool {
1327 candidate.file_path == child.file_path
1328 && candidate.start_line <= child.start_line
1329 && child.end_line <= candidate.end_line
1330 && (candidate.start_line < child.start_line || child.end_line < candidate.end_line)
1331}
1332
1333fn row_belongs_to_descendant(
1334 descendant_ranges_by_entity: &HashMap<String, Vec<(usize, usize)>>,
1335 entity_id: &str,
1336 row: usize,
1337) -> bool {
1338 row_in_descendant_ranges(descendant_ranges_by_entity.get(entity_id), row)
1339}
1340
1341fn row_in_descendant_ranges(ranges: Option<&Vec<(usize, usize)>>, row: usize) -> bool {
1344 ranges.map_or(false, |ranges| {
1345 let eligible = ranges.partition_point(|(start, _)| *start <= row);
1346 ranges[..eligible]
1347 .iter()
1348 .rev()
1349 .any(|(start, end)| row >= *start && row < *end)
1350 })
1351}
1352
1353fn push_named_children_rev<'a>(
1358 worklist: &mut Vec<tree_sitter::Node<'a>>,
1359 node: tree_sitter::Node<'a>,
1360) {
1361 for idx in (0..node.named_child_count()).rev() {
1362 if let Some(child) = node.named_child(idx as u32) {
1363 worklist.push(child);
1364 }
1365 }
1366}
1367
1368fn push_scoped_named_children_rev<'a>(
1369 worklist: &mut Vec<(tree_sitter::Node<'a>, usize)>,
1370 node: tree_sitter::Node<'a>,
1371 scope: usize,
1372) {
1373 for idx in (0..node.named_child_count()).rev() {
1374 if let Some(child) = node.named_child(idx as u32) {
1375 worklist.push((child, scope));
1376 }
1377 }
1378}
1379
1380fn build_scopes_from_ast(
1381 root: tree_sitter::Node,
1382 root_scope: usize,
1383 scopes: &mut Vec<Scope>,
1384 entity_scope_map: &mut HashMap<String, usize>,
1385 entity_inner_scope: &mut HashMap<String, usize>,
1386 file_lookup: &FileEntityLookup<'_>,
1387 children_by_parent: &HashMap<&str, Vec<&SemanticEntity>>,
1388 entity_map: &HashMap<String, EntityInfo>,
1389 _file_path: &str,
1390 source: &[u8],
1391 config: &ScopeResolveConfig,
1392) {
1393 let mut worklist: Vec<(tree_sitter::Node, usize)> = vec![(root, root_scope)];
1395
1396 while let Some((node, current_scope)) = worklist.pop() {
1397 let kind = node.kind();
1398
1399 let is_class_like = config.class_scope_nodes.contains(&kind);
1401
1402 let is_impl = config.impl_scope_nodes.contains(&kind);
1404
1405 if is_class_like || is_impl {
1406 let class_name = if is_impl {
1407 node.child_by_field_name("type")
1408 .and_then(|n| n.utf8_text(source).ok())
1409 .unwrap_or("")
1410 } else {
1411 match &config.class_name_field {
1412 ClassNameField::Simple(field) => node
1413 .child_by_field_name(field)
1414 .and_then(|n| n.utf8_text(source).ok())
1415 .unwrap_or(""),
1416 ClassNameField::TypeSpec { spec_kind, field } => {
1417 let mut name = "";
1418 let mut cursor = node.walk();
1419 for child in node.named_children(&mut cursor) {
1420 if child.kind() == *spec_kind {
1421 name = child
1422 .child_by_field_name(field)
1423 .and_then(|n| n.utf8_text(source).ok())
1424 .unwrap_or("");
1425 break;
1426 }
1427 }
1428 name
1429 }
1430 ClassNameField::ImplType(field) => node
1431 .child_by_field_name(field)
1432 .and_then(|n| n.utf8_text(source).ok())
1433 .unwrap_or(""),
1434 }
1435 };
1436
1437 let line = node.start_position().row + 1;
1438 let class_entity = file_lookup.find_at_line(class_name, line, |entity| {
1439 matches!(
1440 entity.entity_type.as_str(),
1441 "class"
1442 | "struct"
1443 | "interface"
1444 | "enum"
1445 | "protocol"
1446 | "protocol_declaration"
1447 | "object_declaration"
1448 | "companion_object"
1449 )
1450 });
1451
1452 if let Some(ce) = class_entity {
1453 let existing_scope = entity_inner_scope.get(&ce.id).copied();
1454
1455 let class_scope_idx = if let Some(idx) = existing_scope {
1456 idx
1457 } else {
1458 let idx = scopes.len();
1459 scopes.push(Scope {
1460 parent: Some(current_scope),
1461 defs: HashMap::new(),
1462 bindings: HashSet::new(),
1463 binding_rows: HashMap::new(),
1464 types: HashMap::new(),
1465 pending_call_types: HashMap::new(),
1466 owner_id: Some(ce.id.clone()),
1467 kind: "class",
1468 });
1469 entity_scope_map.insert(ce.id.clone(), current_scope);
1470 entity_inner_scope.insert(ce.id.clone(), idx);
1471 idx
1472 };
1473
1474 if let Some(children) = children_by_parent.get(ce.id.as_str()) {
1475 for entity in children {
1476 scopes[class_scope_idx]
1477 .defs
1478 .insert(entity.name.clone(), entity.id.clone());
1479 entity_scope_map.insert(entity.id.clone(), class_scope_idx);
1480 }
1481 }
1482
1483 push_scoped_named_children_rev(&mut worklist, node, class_scope_idx);
1484 continue;
1485 } else if !is_impl {
1486 let class_scope_idx = scopes.len();
1487 scopes.push(Scope {
1488 parent: Some(current_scope),
1489 defs: HashMap::new(),
1490 bindings: HashSet::new(),
1491 binding_rows: HashMap::new(),
1492 types: HashMap::new(),
1493 pending_call_types: HashMap::new(),
1494 owner_id: None,
1495 kind: "class",
1496 });
1497 push_scoped_named_children_rev(&mut worklist, node, class_scope_idx);
1498 continue;
1499 }
1500 }
1501
1502 if kind == "mod_item" {
1505 let mod_name = node
1506 .child_by_field_name("name")
1507 .and_then(|n| n.utf8_text(source).ok())
1508 .unwrap_or("");
1509 let mod_scope_idx = scopes.len();
1510 scopes.push(Scope {
1511 parent: Some(current_scope),
1512 defs: HashMap::new(),
1513 bindings: HashSet::new(),
1514 binding_rows: HashMap::new(),
1515 types: HashMap::new(),
1516 pending_call_types: HashMap::new(),
1517 owner_id: None,
1518 kind: "module",
1519 });
1520
1521 let line = node.start_position().row + 1;
1523 let mod_entity =
1524 file_lookup.find_at_line(mod_name, line, |entity| entity.entity_type == "module");
1525
1526 if let Some(me) = mod_entity {
1527 scopes[mod_scope_idx].owner_id = Some(me.id.clone());
1528 entity_scope_map
1529 .entry(me.id.clone())
1530 .or_insert(current_scope);
1531 entity_inner_scope.insert(me.id.clone(), mod_scope_idx);
1532
1533 if let Some(children) = children_by_parent.get(me.id.as_str()) {
1535 for child_entity in children {
1536 scopes[mod_scope_idx]
1537 .defs
1538 .insert(child_entity.name.clone(), child_entity.id.clone());
1539 entity_scope_map.insert(child_entity.id.clone(), mod_scope_idx);
1540 }
1541 }
1542 }
1543
1544 push_scoped_named_children_rev(&mut worklist, node, mod_scope_idx);
1545 continue;
1546 }
1547
1548 let is_function_like = config.function_scope_nodes.contains(&kind);
1550
1551 if is_function_like {
1552 let func_name = node
1553 .child_by_field_name("name")
1554 .and_then(|n| n.utf8_text(source).ok())
1555 .unwrap_or("");
1556
1557 let parent_scope = if config.external_method && kind == "method_declaration" {
1558 let receiver_type = node
1559 .utf8_text(source)
1560 .ok()
1561 .and_then(|t| extract_go_receiver_type(t));
1562 if let Some(ref struct_name) = receiver_type {
1563 let found = scopes.iter().enumerate().find(|(_, s)| {
1564 s.kind == "class"
1565 && s.owner_id.as_ref().map_or(false, |oid| {
1566 entity_map
1567 .get(oid)
1568 .map_or(false, |e| e.name == *struct_name)
1569 })
1570 });
1571 found.map(|(idx, _)| idx).unwrap_or(current_scope)
1572 } else {
1573 current_scope
1574 }
1575 } else {
1576 current_scope
1577 };
1578
1579 let func_scope_idx = scopes.len();
1580 scopes.push(Scope {
1581 parent: Some(parent_scope),
1582 defs: HashMap::new(),
1583 bindings: HashSet::new(),
1584 binding_rows: HashMap::new(),
1585 types: HashMap::new(),
1586 pending_call_types: HashMap::new(),
1587 owner_id: None,
1588 kind: "function",
1589 });
1590
1591 let line = node.start_position().row + 1;
1592 let func_entity = file_lookup.find_at_line(func_name, line, |_| true);
1593
1594 if let Some(fe) = func_entity {
1595 scopes[func_scope_idx].owner_id = Some(fe.id.clone());
1596 entity_scope_map
1597 .entry(fe.id.clone())
1598 .or_insert(parent_scope);
1599 entity_inner_scope.insert(fe.id.clone(), func_scope_idx);
1600 if config.external_method
1601 && kind == "method_declaration"
1602 && parent_scope != current_scope
1603 {
1604 scopes[parent_scope]
1605 .defs
1606 .insert(fe.name.clone(), fe.id.clone());
1607 }
1608 }
1609
1610 scan_assignments(node, func_scope_idx, scopes, source, config);
1611 scan_function_params(node, func_scope_idx, scopes, source, config);
1612
1613 if config.external_method && kind == "method_declaration" {
1614 if let Some(receiver) = node.child_by_field_name("receiver") {
1615 let mut rcursor = receiver.walk();
1616 for param in receiver.named_children(&mut rcursor) {
1617 if param.kind() == "parameter_declaration" {
1618 let param_name = param
1619 .child_by_field_name("name")
1620 .and_then(|n| n.utf8_text(source).ok())
1621 .unwrap_or("");
1622 let param_type = param
1623 .child_by_field_name("type")
1624 .map(|n| extract_base_type(n, source))
1625 .unwrap_or_default();
1626 if !param_name.is_empty() && !param_type.is_empty() {
1627 scopes[func_scope_idx]
1628 .types
1629 .insert(param_name.to_string(), param_type);
1630 }
1631 }
1632 }
1633 }
1634 }
1635
1636 push_scoped_named_children_rev(&mut worklist, node, func_scope_idx);
1637 continue;
1638 }
1639
1640 push_scoped_named_children_rev(&mut worklist, node, current_scope);
1641 }
1642}
1643
1644fn scan_assignments(
1646 root: tree_sitter::Node,
1647 scope_idx: usize,
1648 scopes: &mut Vec<Scope>,
1649 source: &[u8],
1650 config: &ScopeResolveConfig,
1651) {
1652 let mut worklist = vec![root];
1653 while let Some(node) = worklist.pop() {
1654 let mut cursor = node.walk();
1655 for child in node.named_children(&mut cursor) {
1656 let ck = child.kind();
1657
1658 for rule in config.assignment_rules {
1660 if ck == rule.node_kind {
1661 match rule.strategy {
1662 AssignmentStrategy::LeftRight => {
1663 scan_single_assignment(child, scope_idx, scopes, source);
1664 }
1665 AssignmentStrategy::Declarators => {
1666 scan_ts_var_declaration(child, scope_idx, scopes, source);
1667 }
1668 AssignmentStrategy::PatternBased => {
1669 scan_rust_let_declaration(child, scope_idx, scopes, source);
1670 }
1671 AssignmentStrategy::ShortVar => {
1672 scan_go_short_var(child, scope_idx, scopes, source);
1673 }
1674 AssignmentStrategy::VarSpec => {
1675 scan_go_var_declaration(child, scope_idx, scopes, source);
1676 }
1677 }
1678 }
1679 }
1680
1681 if config.assignment_recurse_into.contains(&ck) {
1683 worklist.push(child);
1684 }
1685 }
1686 }
1687}
1688
1689fn record_binding(scopes: &mut [Scope], scope_idx: usize, name: &str, row: usize) {
1690 scopes[scope_idx].bindings.insert(name.to_string());
1691 scopes[scope_idx]
1692 .binding_rows
1693 .entry(name.to_string())
1694 .or_default()
1695 .push(row);
1696}
1697
1698fn scan_function_params(
1701 node: tree_sitter::Node,
1702 scope_idx: usize,
1703 scopes: &mut Vec<Scope>,
1704 source: &[u8],
1705 config: &ScopeResolveConfig,
1706) {
1707 let mut params_node = node.child_by_field_name("parameters");
1711 if params_node.is_none() {
1712 let mut c = node.walk();
1714 for ch in node.named_children(&mut c) {
1715 if ch.kind() == "function_value_parameters" {
1716 params_node = Some(ch);
1717 break;
1718 }
1719 }
1720 }
1721
1722 let (iter_node, use_direct) = match params_node {
1725 Some(p) => (p, false),
1726 None => (node, true),
1727 };
1728
1729 let mut cursor = iter_node.walk();
1730 for child in iter_node.named_children(&mut cursor) {
1731 if use_direct {
1733 let is_param = config
1734 .param_rules
1735 .iter()
1736 .any(|r| child.kind() == r.node_kind);
1737 if !is_param {
1738 continue;
1739 }
1740 }
1741 for rule in config.param_rules {
1742 if child.kind() != rule.node_kind {
1743 continue;
1744 }
1745
1746 let param_name = match &rule.name_field {
1747 ParamNameField::Simple(field) => child
1748 .child_by_field_name(field)
1749 .and_then(|n| n.utf8_text(source).ok())
1750 .unwrap_or(""),
1751 ParamNameField::WithFallback(field) => child
1752 .child_by_field_name(field)
1753 .or_else(|| child.named_child(0).filter(|n| n.kind() == "identifier"))
1754 .and_then(|n| n.utf8_text(source).ok())
1755 .unwrap_or(""),
1756 ParamNameField::RustPattern => child
1757 .child_by_field_name("pattern")
1758 .and_then(|n| {
1759 if n.kind() == "identifier" {
1760 n.utf8_text(source).ok()
1761 } else if n.kind() == "mut_pattern" {
1762 n.named_child(0).and_then(|c| c.utf8_text(source).ok())
1763 } else if n.kind() == "reference_pattern" {
1764 n.named_child(0).and_then(|c| {
1765 if c.kind() == "identifier" {
1766 c.utf8_text(source).ok()
1767 } else if c.kind() == "mut_pattern" {
1768 c.named_child(0).and_then(|cc| cc.utf8_text(source).ok())
1769 } else {
1770 None
1771 }
1772 })
1773 } else {
1774 None
1775 }
1776 })
1777 .unwrap_or(""),
1778 };
1779
1780 if param_name.is_empty() || rule.skip_names.contains(¶m_name) {
1781 continue;
1782 }
1783 record_binding(scopes, scope_idx, param_name, child.start_position().row);
1784
1785 let mut type_node = child.child_by_field_name(rule.type_field);
1788 if type_node.is_none() {
1789 let mut tc = child.walk();
1790 for ch in child.named_children(&mut tc) {
1791 if matches!(
1792 ch.kind(),
1793 "user_type" | "type_annotation" | "type_identifier"
1794 ) {
1795 type_node = Some(ch);
1796 break;
1797 }
1798 }
1799 }
1800 if let Some(tn) = type_node {
1801 let type_text = extract_base_type(tn, source);
1802 if !type_text.is_empty()
1803 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1804 {
1805 scopes[scope_idx]
1806 .types
1807 .insert(param_name.to_string(), type_text);
1808 }
1809 }
1810 }
1811 }
1812}
1813
1814fn scan_single_assignment(
1816 node: tree_sitter::Node,
1817 scope_idx: usize,
1818 scopes: &mut Vec<Scope>,
1819 source: &[u8],
1820) {
1821 let assign = if node.kind() == "assignment" {
1822 node
1823 } else {
1824 let mut cursor = node.walk();
1825 let children: Vec<_> = node.named_children(&mut cursor).collect();
1826 match children
1827 .into_iter()
1828 .find(|c| c.kind() == "assignment" || c.kind() == "assignment_expression")
1829 {
1830 Some(a) => a,
1831 None => return,
1832 }
1833 };
1834
1835 let left = match assign.child_by_field_name("left") {
1836 Some(l) => l,
1837 None => return,
1838 };
1839 let right = match assign.child_by_field_name("right") {
1840 Some(r) => r,
1841 None => return,
1842 };
1843
1844 if left.kind() != "identifier" {
1845 return;
1846 }
1847 let var_name = match left.utf8_text(source) {
1848 Ok(n) => n.to_string(),
1849 Err(_) => return,
1850 };
1851 record_binding(scopes, scope_idx, &var_name, left.start_position().row);
1852
1853 record_type_from_rhs(right, &var_name, scope_idx, scopes, source);
1854}
1855
1856fn scan_ts_var_declaration(
1859 node: tree_sitter::Node,
1860 scope_idx: usize,
1861 scopes: &mut Vec<Scope>,
1862 source: &[u8],
1863) {
1864 let mut cursor = node.walk();
1865 for child in node.named_children(&mut cursor) {
1866 if child.kind() == "variable_declarator" {
1867 let var_name = child
1868 .child_by_field_name("name")
1869 .and_then(|n| n.utf8_text(source).ok())
1870 .unwrap_or("")
1871 .to_string();
1872 if var_name.is_empty() {
1873 continue;
1874 }
1875 let binding_row = child
1876 .child_by_field_name("name")
1877 .map(|n| n.start_position().row)
1878 .unwrap_or_else(|| child.start_position().row);
1879 record_binding(scopes, scope_idx, &var_name, binding_row);
1880
1881 if let Some(type_ann) = child.child_by_field_name("type") {
1883 let type_text = extract_base_type(type_ann, source);
1884 if !type_text.is_empty()
1885 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1886 {
1887 scopes[scope_idx].types.insert(var_name.clone(), type_text);
1888 continue;
1889 }
1890 }
1891
1892 if let Some(value) = child.child_by_field_name("value") {
1894 record_type_from_rhs(value, &var_name, scope_idx, scopes, source);
1895 }
1896 }
1897 }
1898
1899 if node.kind() == "property_declaration" {
1900 let var_names = swift_property_declaration_names(node, source);
1901
1902 if !var_names.is_empty() {
1903 if let Some(name_nodes) = swift_property_declaration_name_nodes(node) {
1904 for (idx, var_name) in var_names.iter().enumerate() {
1905 let binding_row = name_nodes
1906 .get(idx)
1907 .map(|name_node| name_node.start_position().row)
1908 .unwrap_or_else(|| node.start_position().row);
1909 record_binding(scopes, scope_idx, var_name, binding_row);
1910 }
1911
1912 let type_names: Vec<Option<String>> = name_nodes
1913 .iter()
1914 .enumerate()
1915 .map(|(idx, name_node)| {
1916 swift_property_type_for_name(node, *name_node, idx, source)
1917 })
1918 .collect();
1919 for (idx, name_node) in name_nodes.iter().enumerate() {
1920 let Some(var_name) = var_names.get(idx) else {
1921 continue;
1922 };
1923 let type_name =
1924 type_names
1925 .get(idx)
1926 .and_then(|name| name.clone())
1927 .or_else(|| {
1928 if type_names[..idx].iter().any(Option::is_some) {
1929 None
1930 } else {
1931 type_names
1932 .iter()
1933 .skip(idx + 1)
1934 .find_map(|name| name.clone())
1935 }
1936 });
1937 if let Some(type_name) = type_name {
1938 if !type_name.is_empty()
1939 && type_name.chars().next().map_or(false, |c| c.is_uppercase())
1940 {
1941 scopes[scope_idx].types.insert(var_name.clone(), type_name);
1942 continue;
1943 }
1944 }
1945 if let Some(value) =
1946 swift_property_value_for_name(node, *name_node, idx, source)
1947 {
1948 record_type_from_rhs(value, var_name, scope_idx, scopes, source);
1949 }
1950 }
1951 } else if let Some(var_name) = var_names.first() {
1952 record_binding(scopes, scope_idx, var_name, node.start_position().row);
1953
1954 if let Some(type_ann) = node.child_by_field_name("type") {
1955 let type_text = extract_base_type(type_ann, source);
1956 if !type_text.is_empty()
1957 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1958 {
1959 scopes[scope_idx].types.insert(var_name.clone(), type_text);
1960 return;
1961 }
1962 }
1963 if let Some(value) = node.child_by_field_name("value") {
1964 record_type_from_rhs(value, var_name, scope_idx, scopes, source);
1965 } else {
1966 let mut c = node.walk();
1967 for ch in node.named_children(&mut c) {
1968 if ch.kind() == "call_expression" || ch.kind() == "new_expression" {
1969 record_type_from_rhs(ch, var_name, scope_idx, scopes, source);
1970 break;
1971 }
1972 }
1973 }
1974 }
1975 return;
1976 }
1977
1978 let mut c = node.walk();
1980 for child in node.named_children(&mut c) {
1981 if child.kind() == "variable_declaration" {
1982 let var_name_kt = child
1983 .child_by_field_name("name")
1984 .or_else(|| child.named_child(0).filter(|n| n.kind() == "identifier"))
1985 .and_then(|n| n.utf8_text(source).ok())
1986 .unwrap_or("")
1987 .to_string();
1988
1989 if !var_name_kt.is_empty() {
1990 if let Some(type_ann) = node.child_by_field_name("type") {
1992 let type_text = extract_base_type(type_ann, source);
1993 if !type_text.is_empty()
1994 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
1995 {
1996 scopes[scope_idx]
1997 .types
1998 .insert(var_name_kt.clone(), type_text);
1999 return;
2000 }
2001 }
2002 let mut c2 = node.walk();
2004 for sibling in node.named_children(&mut c2) {
2005 if sibling.kind() == "call_expression" || sibling.kind() == "new_expression"
2006 {
2007 record_type_from_rhs(sibling, &var_name_kt, scope_idx, scopes, source);
2008 break;
2009 }
2010 }
2011 }
2012 break;
2013 }
2014 }
2015 }
2016}
2017
2018fn swift_property_declaration_names(node: tree_sitter::Node, source: &[u8]) -> Vec<String> {
2019 let mut names = Vec::new();
2020 for index in 0..node.child_count() {
2021 if node.field_name_for_child(index as u32) == Some("name") {
2022 if let Some(child) = node.child(index as u32) {
2023 if let Ok(name) = child.utf8_text(source) {
2024 if !name.is_empty() {
2025 names.push(name.to_string());
2026 }
2027 }
2028 }
2029 }
2030 }
2031
2032 if !names.is_empty() {
2033 return names;
2034 }
2035
2036 let mut cursor = node.walk();
2037 for child in node.named_children(&mut cursor) {
2038 if child.kind() != "pattern" {
2039 continue;
2040 }
2041 if let Some(id) = child.named_child(0) {
2042 if id.kind() == "simple_identifier" || id.kind() == "identifier" {
2043 if let Ok(name) = id.utf8_text(source) {
2044 if !name.is_empty() {
2045 names.push(name.to_string());
2046 }
2047 }
2048 }
2049 }
2050 }
2051
2052 names
2053}
2054
2055fn swift_property_declaration_name_nodes<'a>(
2056 node: tree_sitter::Node<'a>,
2057) -> Option<Vec<tree_sitter::Node<'a>>> {
2058 let mut nodes = Vec::new();
2059 for index in 0..node.child_count() {
2060 if node.field_name_for_child(index as u32) == Some("name") {
2061 if let Some(child) = node.child(index as u32) {
2062 nodes.push(child);
2063 }
2064 }
2065 }
2066 if nodes.is_empty() {
2067 None
2068 } else {
2069 Some(nodes)
2070 }
2071}
2072
2073fn swift_property_value_for_name<'a>(
2074 node: tree_sitter::Node<'a>,
2075 name_node: tree_sitter::Node<'a>,
2076 name_index: usize,
2077 source: &[u8],
2078) -> Option<tree_sitter::Node<'a>> {
2079 let segment_end = swift_property_segment_end_for_name(node, name_node, name_index);
2080
2081 for child_index in 0..node.child_count() {
2082 let Some(child) = node.child(child_index as u32) else {
2083 continue;
2084 };
2085 if child.start_byte() < name_node.end_byte() || child.start_byte() >= segment_end {
2086 continue;
2087 }
2088 let field_name = node.field_name_for_child(child_index as u32);
2089 if matches!(field_name, Some("value") | Some("computed_value"))
2090 || child.kind() == "call_expression"
2091 || child.kind() == "new_expression"
2092 {
2093 return Some(child);
2094 }
2095 }
2096
2097 let segment = source
2098 .get(name_node.end_byte()..segment_end)
2099 .and_then(|bytes| std::str::from_utf8(bytes).ok())
2100 .unwrap_or("");
2101 if segment.contains('=') {
2102 let mut cursor = node.walk();
2103 for child in node.named_children(&mut cursor) {
2104 if child.start_byte() >= name_node.end_byte()
2105 && child.start_byte() < segment_end
2106 && (child.kind() == "call_expression" || child.kind() == "new_expression")
2107 {
2108 return Some(child);
2109 }
2110 }
2111 }
2112
2113 None
2114}
2115
2116fn swift_property_type_for_name(
2117 node: tree_sitter::Node,
2118 name_node: tree_sitter::Node,
2119 name_index: usize,
2120 source: &[u8],
2121) -> Option<String> {
2122 let segment_end = swift_property_segment_end_for_name(node, name_node, name_index);
2123 for child_index in 0..node.child_count() {
2124 let Some(child) = node.child(child_index as u32) else {
2125 continue;
2126 };
2127 if child.start_byte() < name_node.end_byte() || child.start_byte() >= segment_end {
2128 continue;
2129 }
2130 let field_name = node.field_name_for_child(child_index as u32);
2131 if field_name == Some("type") || child.kind() == "type_annotation" {
2132 let type_text = extract_base_type(child, source);
2133 if !type_text.is_empty() {
2134 return Some(type_text);
2135 }
2136 }
2137 }
2138 None
2139}
2140
2141fn swift_property_segment_end_for_name(
2142 node: tree_sitter::Node,
2143 name_node: tree_sitter::Node,
2144 name_index: usize,
2145) -> usize {
2146 let name_nodes = swift_property_declaration_name_nodes(node).unwrap_or_default();
2147 let next_name_start = name_nodes.get(name_index + 1).map(|next| next.start_byte());
2148 let mut segment_end = next_name_start.unwrap_or_else(|| node.end_byte());
2149
2150 let mut cursor = node.walk();
2151 for child in node.children(&mut cursor) {
2152 if child.kind() == ","
2153 && child.start_byte() >= name_node.end_byte()
2154 && next_name_start.map_or(true, |next| child.start_byte() < next)
2155 {
2156 segment_end = child.start_byte();
2157 break;
2158 }
2159 }
2160
2161 segment_end
2162}
2163
2164fn scan_rust_let_declaration(
2166 node: tree_sitter::Node,
2167 scope_idx: usize,
2168 scopes: &mut Vec<Scope>,
2169 source: &[u8],
2170) {
2171 let var_name = node
2172 .child_by_field_name("pattern")
2173 .and_then(|n| {
2174 if n.kind() == "identifier" {
2176 n.utf8_text(source).ok()
2177 } else if n.kind() == "mut_pattern" {
2178 n.named_child(0).and_then(|c| c.utf8_text(source).ok())
2179 } else {
2180 None
2181 }
2182 })
2183 .unwrap_or("")
2184 .to_string();
2185
2186 if var_name.is_empty() {
2187 return;
2188 }
2189 record_binding(scopes, scope_idx, &var_name, node.start_position().row);
2190
2191 if let Some(type_node) = node.child_by_field_name("type") {
2193 let type_text = extract_base_type(type_node, source);
2194 if !type_text.is_empty() && type_text.chars().next().map_or(false, |c| c.is_uppercase()) {
2195 scopes[scope_idx].types.insert(var_name, type_text);
2196 return;
2197 }
2198 }
2199
2200 if let Some(value) = node.child_by_field_name("value") {
2202 record_type_from_rhs(value, &var_name, scope_idx, scopes, source);
2203 }
2204}
2205
2206fn scan_go_short_var(
2208 node: tree_sitter::Node,
2209 scope_idx: usize,
2210 scopes: &mut Vec<Scope>,
2211 source: &[u8],
2212) {
2213 let left = match node.child_by_field_name("left") {
2214 Some(l) => l,
2215 None => return,
2216 };
2217 let right = match node.child_by_field_name("right") {
2218 Some(r) => r,
2219 None => return,
2220 };
2221
2222 let var_name = if left.kind() == "expression_list" {
2224 left.named_child(0)
2225 .and_then(|n| n.utf8_text(source).ok())
2226 .unwrap_or("")
2227 .to_string()
2228 } else {
2229 left.utf8_text(source).unwrap_or("").to_string()
2230 };
2231
2232 if var_name.is_empty() {
2233 return;
2234 }
2235 record_binding(scopes, scope_idx, &var_name, left.start_position().row);
2236
2237 let rhs = if right.kind() == "expression_list" {
2238 match right.named_child(0) {
2239 Some(n) => n,
2240 None => return,
2241 }
2242 } else {
2243 right
2244 };
2245
2246 record_type_from_rhs(rhs, &var_name, scope_idx, scopes, source);
2247}
2248
2249fn scan_go_var_declaration(
2251 node: tree_sitter::Node,
2252 scope_idx: usize,
2253 scopes: &mut Vec<Scope>,
2254 source: &[u8],
2255) {
2256 let mut cursor = node.walk();
2257 for child in node.named_children(&mut cursor) {
2258 if child.kind() == "var_spec" {
2259 let var_name = child
2260 .child_by_field_name("name")
2261 .and_then(|n| n.utf8_text(source).ok())
2262 .unwrap_or("")
2263 .to_string();
2264 if var_name.is_empty() {
2265 if let Some(first) = child.named_child(0) {
2267 if first.kind() == "identifier" {
2268 let name = first.utf8_text(source).unwrap_or("").to_string();
2269 if !name.is_empty() {
2270 record_binding(scopes, scope_idx, &name, first.start_position().row);
2271 if let Some(type_node) = child.child_by_field_name("type") {
2273 let type_text = extract_base_type(type_node, source);
2274 if !type_text.is_empty()
2275 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
2276 {
2277 scopes[scope_idx].types.insert(name, type_text);
2278 }
2279 }
2280 }
2281 }
2282 }
2283 continue;
2284 }
2285 let binding_row = child
2286 .child_by_field_name("name")
2287 .map(|n| n.start_position().row)
2288 .unwrap_or_else(|| child.start_position().row);
2289 record_binding(scopes, scope_idx, &var_name, binding_row);
2290
2291 if let Some(type_node) = child.child_by_field_name("type") {
2293 let type_text = extract_base_type(type_node, source);
2294 if !type_text.is_empty()
2295 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
2296 {
2297 scopes[scope_idx].types.insert(var_name, type_text);
2298 continue;
2299 }
2300 }
2301
2302 if let Some(value) = child.child_by_field_name("value") {
2304 let rhs = if value.kind() == "expression_list" {
2305 value.named_child(0).unwrap_or(value)
2306 } else {
2307 value
2308 };
2309 record_type_from_rhs(rhs, &var_name, scope_idx, scopes, source);
2310 }
2311 }
2312 }
2313}
2314
2315fn record_type_from_rhs(
2318 rhs: tree_sitter::Node,
2319 var_name: &str,
2320 scope_idx: usize,
2321 scopes: &mut Vec<Scope>,
2322 source: &[u8],
2323) {
2324 match rhs.kind() {
2325 "call" | "call_expression" => {
2327 let func_node = rhs
2328 .child_by_field_name("function")
2329 .or_else(|| rhs.named_child(0));
2330 if let Some(func) = func_node {
2331 if func.kind() == "identifier"
2332 || func.kind() == "simple_identifier"
2333 || func.kind() == "type_identifier"
2334 {
2335 let name = func.utf8_text(source).unwrap_or("");
2336 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
2337 scopes[scope_idx]
2338 .types
2339 .insert(var_name.to_string(), name.to_string());
2340 } else {
2341 scopes[scope_idx]
2342 .pending_call_types
2343 .insert(var_name.to_string(), name.to_string());
2344 }
2345 }
2346 if func.kind() == "scoped_identifier" {
2348 let text = func.utf8_text(source).unwrap_or("");
2349 let parts: Vec<&str> = text.split("::").collect();
2350 if parts.len() >= 2 {
2351 let type_name = parts[0];
2352 let method_name = parts[parts.len() - 1];
2353 if type_name.chars().next().map_or(false, |c| c.is_uppercase()) {
2354 scopes[scope_idx]
2355 .types
2356 .insert(var_name.to_string(), type_name.to_string());
2357 } else {
2358 scopes[scope_idx]
2359 .pending_call_types
2360 .insert(var_name.to_string(), method_name.to_string());
2361 }
2362 }
2363 }
2364 if func.kind() == "selector_expression" {
2366 let field = func
2367 .child_by_field_name("field")
2368 .and_then(|n| n.utf8_text(source).ok())
2369 .unwrap_or("");
2370 if let Some(type_name) = field.strip_prefix("New") {
2372 if !type_name.is_empty()
2373 && type_name.chars().next().map_or(false, |c| c.is_uppercase())
2374 {
2375 scopes[scope_idx]
2376 .types
2377 .insert(var_name.to_string(), type_name.to_string());
2378 }
2379 } else if field.starts_with("Get")
2380 || field.chars().next().map_or(false, |c| c.is_uppercase())
2381 {
2382 scopes[scope_idx]
2384 .pending_call_types
2385 .insert(var_name.to_string(), field.to_string());
2386 }
2387 }
2388 }
2389 }
2390 "new_expression" => {
2392 if let Some(constructor) = rhs.child_by_field_name("constructor") {
2393 let name = constructor.utf8_text(source).unwrap_or("");
2394 if !name.is_empty() {
2395 scopes[scope_idx]
2396 .types
2397 .insert(var_name.to_string(), name.to_string());
2398 }
2399 }
2400 }
2401 "composite_literal" => {
2403 if let Some(type_node) = rhs.child_by_field_name("type") {
2404 let name = type_node.utf8_text(source).unwrap_or("");
2405 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
2406 scopes[scope_idx]
2407 .types
2408 .insert(var_name.to_string(), name.to_string());
2409 }
2410 }
2411 }
2412 _ => {}
2413 }
2414}
2415
2416fn extract_base_type(type_node: tree_sitter::Node, source: &[u8]) -> String {
2419 let text = type_node.utf8_text(source).unwrap_or("").trim().to_string();
2420 let text = text.trim_start_matches('&').trim_start_matches('*');
2422 let text = text.strip_prefix("mut ").unwrap_or(text).trim_start();
2423 let text = if let Some(i) = text.find('<') {
2425 &text[..i]
2426 } else if let Some(i) = text.find('[') {
2427 &text[..i]
2428 } else {
2429 text
2430 };
2431 let text = text.trim();
2433 let text = text.trim_start_matches(':').trim();
2435 text.to_string()
2436}
2437
2438pub fn extract_go_receiver_type(content: &str) -> Option<String> {
2440 let after_func = content.strip_prefix("func")?.trim_start();
2441 let paren_start = after_func.find('(')?;
2442 let paren_end = after_func.find(')')?;
2443 let receiver_block = &after_func[paren_start + 1..paren_end];
2444 let parts: Vec<&str> = receiver_block.split_whitespace().collect();
2446 let type_str = parts.last()?;
2447 let name = type_str.trim_start_matches('*');
2448 if name.is_empty() {
2449 None
2450 } else {
2451 Some(name.to_string())
2452 }
2453}
2454
2455pub(crate) fn build_go_pkg_index(
2458 symbol_table: &HashMap<String, Vec<String>>,
2459 entity_map: &HashMap<String, EntityInfo>,
2460) -> HashMap<String, Vec<(String, String)>> {
2461 let mut idx: HashMap<String, Vec<(String, String)>> = HashMap::new();
2462 for (name, target_ids) in symbol_table.iter() {
2463 for target_id in target_ids {
2464 if let Some(entity) = entity_map.get(target_id) {
2465 if !entity.file_path.ends_with(".go") {
2466 continue;
2467 }
2468 let file_stem = entity
2469 .file_path
2470 .rsplit('/')
2471 .next()
2472 .unwrap_or(&entity.file_path);
2473 let file_stem = file_stem.strip_suffix(".go").unwrap_or(file_stem);
2474 idx.entry(file_stem.to_string())
2475 .or_default()
2476 .push((name.clone(), target_id.clone()));
2477 if let Some(parent_start) = entity.file_path.rfind('/') {
2478 let parent_path = &entity.file_path[..parent_start];
2479 if let Some(dir_name_start) = parent_path.rfind('/') {
2480 let dir_name = &parent_path[dir_name_start + 1..];
2481 if dir_name != file_stem {
2482 idx.entry(dir_name.to_string())
2483 .or_default()
2484 .push((name.clone(), target_id.clone()));
2485 }
2486 } else if !parent_path.is_empty() && parent_path != file_stem {
2487 idx.entry(parent_path.to_string())
2488 .or_default()
2489 .push((name.clone(), target_id.clone()));
2490 }
2491 }
2492 }
2493 }
2494 }
2495 for entries in idx.values_mut() {
2496 entries.sort_unstable();
2497 }
2498 idx
2499}
2500
2501fn scan_return_types(
2503 root: tree_sitter::Node,
2504 _file_path: &str,
2505 file_lookup: &FileEntityLookup<'_>,
2506 source: &[u8],
2507 return_type_map: &mut HashMap<String, String>,
2508 config: &ScopeResolveConfig,
2509) {
2510 let mut worklist = vec![root];
2511 while let Some(node) = worklist.pop() {
2512 let kind = node.kind();
2513
2514 let is_func = config.function_scope_nodes.contains(&kind);
2515
2516 if is_func {
2517 let func_name = node
2518 .child_by_field_name("name")
2519 .and_then(|n| n.utf8_text(source).ok())
2520 .unwrap_or("");
2521
2522 let line = node.start_position().row + 1;
2523 let func_entity = file_lookup.find_at_line(func_name, line, |_| true);
2524
2525 if let Some(fe) = func_entity {
2526 let ret_type = config.return_type_field.and_then(|field| {
2528 node.child_by_field_name(field)
2529 .map(|n| extract_base_type(n, source))
2530 .filter(|t| {
2531 !t.is_empty() && t.chars().next().map_or(false, |c| c.is_uppercase())
2532 })
2533 });
2534
2535 if let Some(rt) = ret_type {
2536 return_type_map.insert(fe.id.clone(), rt);
2537 } else {
2538 if let Some(ret_type) = find_return_constructor(node, source) {
2540 return_type_map.insert(fe.id.clone(), ret_type);
2541 }
2542 }
2543 }
2544 }
2545
2546 push_named_children_rev(&mut worklist, node);
2547 }
2548}
2549
2550fn find_return_constructor(root: tree_sitter::Node, source: &[u8]) -> Option<String> {
2552 let mut worklist = vec![root];
2553 while let Some(node) = worklist.pop() {
2554 let mut cursor = node.walk();
2555 for child in node.named_children(&mut cursor) {
2556 if child.kind() == "return_statement" {
2557 let mut inner_cursor = child.walk();
2558 for ret_child in child.named_children(&mut inner_cursor) {
2559 if ret_child.kind() == "call" || ret_child.kind() == "call_expression" {
2561 if let Some(func) = ret_child.child_by_field_name("function") {
2562 if func.kind() == "identifier" {
2563 let name = func.utf8_text(source).unwrap_or("");
2564 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
2565 return Some(name.to_string());
2566 }
2567 }
2568 }
2569 }
2570 if ret_child.kind() == "new_expression" {
2572 if let Some(constructor) = ret_child.child_by_field_name("constructor") {
2573 let name = constructor.utf8_text(source).unwrap_or("");
2574 if !name.is_empty() {
2575 return Some(name.to_string());
2576 }
2577 }
2578 }
2579 if ret_child.kind() == "composite_literal" {
2581 if let Some(type_node) = ret_child.child_by_field_name("type") {
2582 let name = type_node.utf8_text(source).unwrap_or("");
2583 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
2584 return Some(name.to_string());
2585 }
2586 }
2587 }
2588 }
2589 }
2590 let ck = child.kind();
2592 if ck == "block" || ck == "statement_block" {
2593 worklist.push(child);
2594 }
2595 }
2596 }
2597 None
2598}
2599
2600fn scan_init_self_attrs(
2603 root: tree_sitter::Node,
2604 _file_path: &str,
2605 _file_entities: &[&SemanticEntity],
2606 _entity_map: &HashMap<String, EntityInfo>,
2607 source: &[u8],
2608 instance_attr_types: &mut HashMap<(String, String), String>,
2609 init_params_map: &mut HashMap<String, Vec<String>>,
2610 attr_to_param_map: &mut HashMap<(String, String), String>,
2611 config: &ScopeResolveConfig,
2612) {
2613 let mut worklist = vec![root];
2614 while let Some(node) = worklist.pop() {
2615 let kind = node.kind();
2616
2617 match &config.init_strategy {
2618 InitStrategy::ConstructorBody {
2619 class_nodes,
2620 init_node_kind,
2621 self_keyword: _,
2622 ..
2623 } => {
2624 if class_nodes.contains(&kind) {
2625 let class_name = node
2626 .child_by_field_name("name")
2627 .and_then(|n| n.utf8_text(source).ok())
2628 .unwrap_or("")
2629 .to_string();
2630
2631 if !class_name.is_empty() {
2632 let lang = match *init_node_kind {
2634 "function_definition" => "python",
2635 "method_definition" => "typescript",
2636 "init_declaration" => "swift",
2637 "anonymous_initializer" => "kotlin",
2638 _ => "typescript",
2639 };
2640 scan_class_for_init(
2641 node,
2642 &class_name,
2643 source,
2644 instance_attr_types,
2645 init_params_map,
2646 attr_to_param_map,
2647 lang,
2648 );
2649 }
2650 }
2651 }
2652 InitStrategy::StructFields { struct_nodes } => {
2653 if struct_nodes.contains(&kind) {
2654 if kind == "struct_item" {
2656 let struct_name = node
2657 .child_by_field_name("name")
2658 .and_then(|n| n.utf8_text(source).ok())
2659 .unwrap_or("")
2660 .to_string();
2661
2662 if !struct_name.is_empty() {
2663 scan_rust_struct_fields(
2664 node,
2665 &struct_name,
2666 source,
2667 instance_attr_types,
2668 );
2669 }
2670 }
2671 if kind == "type_declaration" {
2673 scan_go_struct_fields(node, source, instance_attr_types);
2674 }
2675 }
2676 }
2677 InitStrategy::None => {}
2678 }
2679
2680 push_named_children_rev(&mut worklist, node);
2681 }
2682}
2683
2684fn scan_rust_struct_fields(
2686 node: tree_sitter::Node,
2687 struct_name: &str,
2688 source: &[u8],
2689 instance_attr_types: &mut HashMap<(String, String), String>,
2690) {
2691 let mut cursor = node.walk();
2692 for child in node.named_children(&mut cursor) {
2693 if child.kind() == "field_declaration_list" {
2694 let mut inner_cursor = child.walk();
2695 for field in child.named_children(&mut inner_cursor) {
2696 if field.kind() == "field_declaration" {
2697 let field_name = field
2698 .child_by_field_name("name")
2699 .and_then(|n| n.utf8_text(source).ok())
2700 .unwrap_or("");
2701 let field_type = field
2702 .child_by_field_name("type")
2703 .map(|n| extract_base_type(n, source))
2704 .unwrap_or_default();
2705
2706 if !field_name.is_empty()
2707 && !field_type.is_empty()
2708 && field_type
2709 .chars()
2710 .next()
2711 .map_or(false, |c| c.is_uppercase())
2712 {
2713 instance_attr_types.insert(
2714 (struct_name.to_string(), field_name.to_string()),
2715 field_type,
2716 );
2717 }
2718 }
2719 }
2720 }
2721 }
2722}
2723
2724fn scan_go_struct_fields(
2726 node: tree_sitter::Node,
2727 source: &[u8],
2728 instance_attr_types: &mut HashMap<(String, String), String>,
2729) {
2730 let mut cursor = node.walk();
2731 for child in node.named_children(&mut cursor) {
2732 if child.kind() == "type_spec" {
2733 let struct_name = child
2734 .child_by_field_name("name")
2735 .and_then(|n| n.utf8_text(source).ok())
2736 .unwrap_or("")
2737 .to_string();
2738
2739 if struct_name.is_empty() {
2740 continue;
2741 }
2742
2743 if let Some(type_node) = child.child_by_field_name("type") {
2745 if type_node.kind() == "struct_type" {
2746 let mut fields_cursor = type_node.walk();
2747 for field_list in type_node.named_children(&mut fields_cursor) {
2748 if field_list.kind() == "field_declaration_list" {
2749 let mut inner = field_list.walk();
2750 for field in field_list.named_children(&mut inner) {
2751 if field.kind() == "field_declaration" {
2752 let field_name = field
2754 .child_by_field_name("name")
2755 .and_then(|n| n.utf8_text(source).ok())
2756 .unwrap_or("");
2757 let field_type = field
2758 .child_by_field_name("type")
2759 .map(|n| extract_base_type(n, source))
2760 .unwrap_or_default();
2761
2762 if !field_name.is_empty()
2763 && !field_type.is_empty()
2764 && field_type
2765 .chars()
2766 .next()
2767 .map_or(false, |c| c.is_uppercase())
2768 {
2769 instance_attr_types.insert(
2770 (struct_name.clone(), field_name.to_string()),
2771 field_type,
2772 );
2773 }
2774 }
2775 }
2776 }
2777 }
2778 }
2779 }
2780 }
2781 }
2782}
2783
2784fn scan_class_for_init(
2785 root: tree_sitter::Node,
2786 class_name: &str,
2787 source: &[u8],
2788 instance_attr_types: &mut HashMap<(String, String), String>,
2789 init_params_map: &mut HashMap<String, Vec<String>>,
2790 attr_to_param_map: &mut HashMap<(String, String), String>,
2791 lang: &str,
2792) {
2793 if lang == "kotlin" {
2795 scan_kotlin_primary_constructor(root, class_name, source, instance_attr_types);
2796 }
2797
2798 let mut worklist = vec![root];
2799 while let Some(node) = worklist.pop() {
2800 let mut cursor = node.walk();
2801 for child in node.named_children(&mut cursor) {
2802 let ck = child.kind();
2803
2804 if ck == "function_definition" && lang == "python" {
2806 let name = child
2807 .child_by_field_name("name")
2808 .and_then(|n| n.utf8_text(source).ok())
2809 .unwrap_or("");
2810 if name == "__init__" {
2811 let params = extract_init_params(child, source);
2812 let ordered_params = extract_init_param_names_ordered(child, source);
2813 init_params_map.insert(class_name.to_string(), ordered_params);
2814 scan_init_body(
2815 child,
2816 class_name,
2817 ¶ms,
2818 source,
2819 instance_attr_types,
2820 attr_to_param_map,
2821 );
2822 }
2823 }
2824
2825 if ck == "method_definition" && lang == "typescript" {
2827 let name = child
2828 .child_by_field_name("name")
2829 .and_then(|n| n.utf8_text(source).ok())
2830 .unwrap_or("");
2831 if name == "constructor" {
2832 scan_ts_constructor_body(
2834 child,
2835 class_name,
2836 source,
2837 instance_attr_types,
2838 init_params_map,
2839 attr_to_param_map,
2840 );
2841 }
2842 }
2843
2844 if ck == "init_declaration" && lang == "swift" {
2846 scan_swift_init_body(
2847 child,
2848 class_name,
2849 source,
2850 instance_attr_types,
2851 init_params_map,
2852 attr_to_param_map,
2853 );
2854 }
2855
2856 if ck == "anonymous_initializer" && lang == "kotlin" {
2858 scan_kotlin_init_body(
2859 child,
2860 class_name,
2861 source,
2862 instance_attr_types,
2863 attr_to_param_map,
2864 );
2865 }
2866
2867 if (ck == "public_field_definition"
2869 || ck == "property_declaration"
2870 || ck == "field_definition")
2871 && lang == "typescript"
2872 {
2873 let field_name = child
2874 .child_by_field_name("name")
2875 .and_then(|n| n.utf8_text(source).ok())
2876 .unwrap_or("");
2877 if let Some(type_ann) = child.child_by_field_name("type") {
2878 let type_text = extract_base_type(type_ann, source);
2879 if !field_name.is_empty()
2880 && !type_text.is_empty()
2881 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
2882 {
2883 instance_attr_types
2884 .insert((class_name.to_string(), field_name.to_string()), type_text);
2885 }
2886 }
2887 }
2888
2889 if ck == "property_declaration" && lang == "swift" {
2891 scan_swift_property_declaration(child, class_name, source, instance_attr_types);
2892 }
2893
2894 if ck == "property_declaration" && lang == "kotlin" {
2896 scan_kotlin_property_declaration(child, class_name, source, instance_attr_types);
2897 }
2898
2899 if ck == "block"
2900 || ck == "class_body"
2901 || ck == "statement_block"
2902 || ck == "struct_body"
2903 || ck == "function_body"
2904 || ck == "code_block"
2905 || ck == "statements"
2906 || ck == "enum_class_body"
2907 {
2908 worklist.push(child);
2909 }
2910 }
2911 }
2912}
2913
2914fn scan_swift_init_body(
2916 node: tree_sitter::Node,
2917 class_name: &str,
2918 source: &[u8],
2919 instance_attr_types: &mut HashMap<(String, String), String>,
2920 init_params_map: &mut HashMap<String, Vec<String>>,
2921 attr_to_param_map: &mut HashMap<(String, String), String>,
2922) {
2923 let params = extract_init_params(node, source);
2924 let ordered_params = extract_init_param_names_ordered(node, source);
2925 init_params_map.insert(class_name.to_string(), ordered_params);
2926
2927 let mut worklist = vec![node];
2929 while let Some(wnode) = worklist.pop() {
2930 let mut cursor = wnode.walk();
2931 for child in wnode.named_children(&mut cursor) {
2932 let ck = child.kind();
2933 if ck == "directly_assigned_expression" || ck == "assignment" {
2935 if let Some(left) = child
2936 .child_by_field_name("left")
2937 .or_else(|| child.named_child(0))
2938 {
2939 if left.kind() == "navigation_expression" {
2940 let obj = left
2941 .child_by_field_name("target")
2942 .and_then(|n| n.utf8_text(source).ok())
2943 .unwrap_or("");
2944 let prop = left
2945 .child_by_field_name("suffix")
2946 .and_then(|n| n.utf8_text(source).ok())
2947 .map(|text| text.strip_prefix('.').unwrap_or(text))
2948 .unwrap_or("");
2949 if obj == "self" && !prop.is_empty() {
2950 if let Some(right) = child
2951 .child_by_field_name("right")
2952 .or_else(|| child.named_child(1))
2953 {
2954 if right.kind() == "simple_identifier"
2955 || right.kind() == "identifier"
2956 {
2957 let rhs_name = right.utf8_text(source).unwrap_or("");
2958 if params.contains_key(rhs_name) {
2959 attr_to_param_map.insert(
2960 (class_name.to_string(), prop.to_string()),
2961 rhs_name.to_string(),
2962 );
2963 if let Some(Some(type_hint)) = params.get(rhs_name) {
2964 instance_attr_types.insert(
2965 (class_name.to_string(), prop.to_string()),
2966 type_hint.clone(),
2967 );
2968 }
2969 }
2970 }
2971 }
2972 }
2973 }
2974 }
2975 }
2976 if ck == "function_body"
2977 || ck == "code_block"
2978 || ck == "statements"
2979 || ck == "expression_statement"
2980 || ck == "block"
2981 {
2982 worklist.push(child);
2983 }
2984 }
2985 }
2986}
2987
2988fn scan_swift_property_declaration(
2990 node: tree_sitter::Node,
2991 class_name: &str,
2992 source: &[u8],
2993 instance_attr_types: &mut HashMap<(String, String), String>,
2994) {
2995 let mut processed_pattern_binding = false;
2996
2997 let mut cursor = node.walk();
2998
2999 for child in node.named_children(&mut cursor) {
3000 if child.kind() == "pattern_binding" {
3001 processed_pattern_binding = true;
3002 scan_swift_property_binding(child, class_name, source, instance_attr_types);
3003 }
3004 }
3005 if processed_pattern_binding {
3006 return;
3007 }
3008
3009 let mut pending_names = Vec::new();
3012 let mut cursor = node.walk();
3013 for child in node.named_children(&mut cursor) {
3014 match child.kind() {
3015 "pattern" | "simple_identifier" | "identifier" => {
3016 if let Some(name) = extract_swift_property_pattern_name(child, source) {
3017 pending_names.push(name);
3018 }
3019 }
3020 "type_annotation" | "user_type" | "type_identifier" => {
3021 let type_text = extract_base_type(child, source);
3022 if !type_text.is_empty()
3023 && type_text.chars().next().map_or(false, |c| c.is_uppercase())
3024 {
3025 for name in pending_names.drain(..) {
3026 instance_attr_types
3027 .insert((class_name.to_string(), name), type_text.clone());
3028 }
3029 }
3030 }
3031 "call_expression" | "new_expression" | "value_argument" => pending_names.clear(),
3032 _ => {}
3033 }
3034 }
3035}
3036
3037fn scan_swift_property_binding(
3038 node: tree_sitter::Node,
3039 class_name: &str,
3040 source: &[u8],
3041 instance_attr_types: &mut HashMap<(String, String), String>,
3042) {
3043 let mut field_names = Vec::new();
3044 let mut field_type = node.child_by_field_name("type").and_then(|type_node| {
3045 let type_text = extract_base_type(type_node, source);
3046 if type_text.is_empty() {
3047 None
3048 } else {
3049 Some(type_text)
3050 }
3051 });
3052
3053 let mut cursor = node.walk();
3054 for child in node.named_children(&mut cursor) {
3055 match child.kind() {
3056 "pattern" | "simple_identifier" | "identifier" => {
3057 if let Some(name) = extract_swift_property_pattern_name(child, source) {
3058 field_names.push(name);
3059 }
3060 }
3061 "type_annotation" => {
3062 if field_type.is_none() {
3063 let type_text = extract_base_type(child, source);
3064 if !type_text.is_empty() {
3065 field_type = Some(type_text);
3066 }
3067 }
3068 }
3069 _ => {}
3070 }
3071 }
3072
3073 let Some(type_text) = field_type else {
3074 return;
3075 };
3076 if !type_text.chars().next().map_or(false, |c| c.is_uppercase()) {
3077 return;
3078 }
3079
3080 for field_name in field_names {
3081 instance_attr_types.insert((class_name.to_string(), field_name), type_text.clone());
3082 }
3083}
3084
3085fn extract_swift_property_pattern_name(node: tree_sitter::Node, source: &[u8]) -> Option<String> {
3086 if matches!(node.kind(), "simple_identifier" | "identifier") {
3087 let name = node.utf8_text(source).ok()?.trim();
3088 return (!name.is_empty()).then(|| name.to_string());
3089 }
3090
3091 if let Some(name_node) = node.child_by_field_name("name") {
3092 return extract_swift_property_pattern_name(name_node, source);
3093 }
3094
3095 let mut cursor = node.walk();
3096 for child in node.named_children(&mut cursor) {
3097 if matches!(child.kind(), "simple_identifier" | "identifier") {
3098 return extract_swift_property_pattern_name(child, source);
3099 }
3100 }
3101
3102 None
3103}
3104
3105fn scan_kotlin_property_declaration(
3107 node: tree_sitter::Node,
3108 class_name: &str,
3109 source: &[u8],
3110 instance_attr_types: &mut HashMap<(String, String), String>,
3111) {
3112 let field_name = node
3113 .child_by_field_name("name")
3114 .and_then(|n| n.utf8_text(source).ok())
3115 .unwrap_or("");
3116 let field_type = node
3117 .child_by_field_name("type")
3118 .map(|n| extract_base_type(n, source))
3119 .unwrap_or_default();
3120
3121 if !field_name.is_empty()
3122 && !field_type.is_empty()
3123 && field_type
3124 .chars()
3125 .next()
3126 .map_or(false, |c| c.is_uppercase())
3127 {
3128 instance_attr_types.insert((class_name.to_string(), field_name.to_string()), field_type);
3129 }
3130}
3131
3132fn scan_kotlin_primary_constructor(
3134 class_node: tree_sitter::Node,
3135 class_name: &str,
3136 source: &[u8],
3137 instance_attr_types: &mut HashMap<(String, String), String>,
3138) {
3139 let mut cursor = class_node.walk();
3141 for child in class_node.named_children(&mut cursor) {
3142 if child.kind() == "primary_constructor" {
3143 let mut pc_cursor = child.walk();
3144 for param in child.named_children(&mut pc_cursor) {
3145 if param.kind() == "class_parameter" {
3146 let text = param.utf8_text(source).unwrap_or("");
3148 let has_val_var = text.starts_with("val ")
3149 || text.starts_with("var ")
3150 || text.contains("val ")
3151 || text.contains("var ");
3152 if has_val_var {
3153 let param_name = param
3154 .child_by_field_name("name")
3155 .and_then(|n| n.utf8_text(source).ok())
3156 .unwrap_or("");
3157 let param_type = param
3158 .child_by_field_name("type")
3159 .map(|n| extract_base_type(n, source))
3160 .unwrap_or_default();
3161 if !param_name.is_empty()
3162 && !param_type.is_empty()
3163 && param_type
3164 .chars()
3165 .next()
3166 .map_or(false, |c| c.is_uppercase())
3167 {
3168 instance_attr_types.insert(
3169 (class_name.to_string(), param_name.to_string()),
3170 param_type,
3171 );
3172 }
3173 }
3174 }
3175 }
3176 }
3177 }
3178}
3179
3180fn scan_kotlin_init_body(
3182 node: tree_sitter::Node,
3183 class_name: &str,
3184 source: &[u8],
3185 instance_attr_types: &mut HashMap<(String, String), String>,
3186 attr_to_param_map: &mut HashMap<(String, String), String>,
3187) {
3188 let mut worklist = vec![node];
3189 while let Some(wnode) = worklist.pop() {
3190 let mut cursor = wnode.walk();
3191 for child in wnode.named_children(&mut cursor) {
3192 let ck = child.kind();
3193 if ck == "assignment" || ck == "directly_assigned_expression" {
3194 if let Some(left) = child
3195 .child_by_field_name("left")
3196 .or_else(|| child.named_child(0))
3197 {
3198 if left.kind() == "navigation_expression" {
3199 let obj = left
3200 .child_by_field_name("expression")
3201 .and_then(|n| n.utf8_text(source).ok())
3202 .unwrap_or("");
3203 let prop = left
3204 .child_by_field_name("navigation_suffix")
3205 .and_then(|n| n.utf8_text(source).ok())
3206 .unwrap_or("");
3207 if obj == "this" && !prop.is_empty() {
3208 if let Some(right) = child
3209 .child_by_field_name("right")
3210 .or_else(|| child.named_child(1))
3211 {
3212 if right.kind() == "simple_identifier"
3213 || right.kind() == "identifier"
3214 {
3215 let rhs_name = right.utf8_text(source).unwrap_or("");
3216 attr_to_param_map.insert(
3217 (class_name.to_string(), prop.to_string()),
3218 rhs_name.to_string(),
3219 );
3220 }
3221 if right.kind() == "call_expression" {
3223 let callee = right
3224 .child_by_field_name("function")
3225 .and_then(|n| n.utf8_text(source).ok())
3226 .unwrap_or("");
3227 if !callee.is_empty()
3228 && callee.chars().next().map_or(false, |c| c.is_uppercase())
3229 {
3230 instance_attr_types.insert(
3231 (class_name.to_string(), prop.to_string()),
3232 callee.to_string(),
3233 );
3234 }
3235 }
3236 }
3237 }
3238 }
3239 }
3240 }
3241 if ck == "statements" || ck == "block" || ck == "expression_statement" {
3242 worklist.push(child);
3243 }
3244 }
3245 }
3246}
3247
3248fn scan_ts_constructor_body(
3250 node: tree_sitter::Node,
3251 class_name: &str,
3252 source: &[u8],
3253 instance_attr_types: &mut HashMap<(String, String), String>,
3254 init_params_map: &mut HashMap<String, Vec<String>>,
3255 attr_to_param_map: &mut HashMap<(String, String), String>,
3256) {
3257 let params = extract_init_params(node, source);
3259 let ordered_params = extract_init_param_names_ordered(node, source);
3260 init_params_map.insert(class_name.to_string(), ordered_params);
3261
3262 scan_init_body_this(
3264 node,
3265 class_name,
3266 ¶ms,
3267 source,
3268 instance_attr_types,
3269 attr_to_param_map,
3270 );
3271}
3272
3273fn scan_init_body_this(
3275 root: tree_sitter::Node,
3276 class_name: &str,
3277 params: &HashMap<String, Option<String>>,
3278 source: &[u8],
3279 instance_attr_types: &mut HashMap<(String, String), String>,
3280 attr_to_param_map: &mut HashMap<(String, String), String>,
3281) {
3282 let mut worklist = vec![root];
3283 while let Some(node) = worklist.pop() {
3284 let mut cursor = node.walk();
3285 for child in node.named_children(&mut cursor) {
3286 let ck = child.kind();
3287 if ck == "expression_statement" {
3288 let mut inner_cursor = child.walk();
3290 for inner in child.named_children(&mut inner_cursor) {
3291 if inner.kind() == "assignment_expression" {
3292 if let Some(left) = inner.child_by_field_name("left") {
3293 if left.kind() == "member_expression" {
3294 let obj = left
3295 .child_by_field_name("object")
3296 .and_then(|n| n.utf8_text(source).ok())
3297 .unwrap_or("");
3298 let prop = left
3299 .child_by_field_name("property")
3300 .and_then(|n| n.utf8_text(source).ok())
3301 .unwrap_or("");
3302 if obj == "this" && !prop.is_empty() {
3303 if let Some(right) = inner.child_by_field_name("right") {
3304 if right.kind() == "identifier" {
3305 let rhs_name = right.utf8_text(source).unwrap_or("");
3306 if params.contains_key(rhs_name) {
3307 attr_to_param_map.insert(
3308 (class_name.to_string(), prop.to_string()),
3309 rhs_name.to_string(),
3310 );
3311 if let Some(Some(type_hint)) = params.get(rhs_name)
3312 {
3313 instance_attr_types.insert(
3314 (class_name.to_string(), prop.to_string()),
3315 type_hint.clone(),
3316 );
3317 }
3318 }
3319 }
3320 if right.kind() == "new_expression" {
3321 if let Some(ctor) =
3322 right.child_by_field_name("constructor")
3323 {
3324 let name = ctor.utf8_text(source).unwrap_or("");
3325 if !name.is_empty() {
3326 instance_attr_types.insert(
3327 (class_name.to_string(), prop.to_string()),
3328 name.to_string(),
3329 );
3330 }
3331 }
3332 }
3333 }
3334 }
3335 }
3336 }
3337 }
3338 }
3339 }
3340 if ck == "statement_block" || ck == "block" {
3341 worklist.push(child);
3342 }
3343 }
3344 }
3345}
3346
3347fn extract_init_param_names_ordered(func_node: tree_sitter::Node, source: &[u8]) -> Vec<String> {
3349 let mut names = Vec::new();
3350 if let Some(params_node) = func_node.child_by_field_name("parameters") {
3351 let mut cursor = params_node.walk();
3352 for child in params_node.named_children(&mut cursor) {
3353 let param_name = if child.kind() == "identifier" {
3354 child.utf8_text(source).unwrap_or("").to_string()
3355 } else if child.kind() == "typed_parameter" || child.kind() == "typed_default_parameter"
3356 {
3357 child
3358 .child_by_field_name("name")
3359 .or_else(|| child.named_child(0))
3360 .and_then(|n| n.utf8_text(source).ok())
3361 .unwrap_or("")
3362 .to_string()
3363 } else {
3364 continue;
3365 };
3366 if param_name != "self" && param_name != "cls" && !param_name.is_empty() {
3367 names.push(param_name);
3368 }
3369 }
3370 }
3371 names
3372}
3373
3374fn extract_init_params(
3375 func_node: tree_sitter::Node,
3376 source: &[u8],
3377) -> HashMap<String, Option<String>> {
3378 let mut params = HashMap::new();
3379 if let Some(params_node) = func_node.child_by_field_name("parameters") {
3380 let mut cursor = params_node.walk();
3381 for child in params_node.named_children(&mut cursor) {
3382 let param_name = if child.kind() == "identifier" {
3383 child.utf8_text(source).unwrap_or("").to_string()
3384 } else if child.kind() == "typed_parameter" || child.kind() == "typed_default_parameter"
3385 {
3386 child
3387 .child_by_field_name("name")
3388 .or_else(|| child.named_child(0))
3389 .and_then(|n| n.utf8_text(source).ok())
3390 .unwrap_or("")
3391 .to_string()
3392 } else {
3393 continue;
3394 };
3395 if param_name != "self" && param_name != "cls" {
3396 let type_hint = child
3398 .child_by_field_name("type")
3399 .and_then(|n| n.utf8_text(source).ok())
3400 .map(|s| s.to_string());
3401 params.insert(param_name, type_hint);
3402 }
3403 }
3404 }
3405 params
3406}
3407
3408fn scan_init_body(
3409 root: tree_sitter::Node,
3410 class_name: &str,
3411 params: &HashMap<String, Option<String>>,
3412 source: &[u8],
3413 instance_attr_types: &mut HashMap<(String, String), String>,
3414 attr_to_param_map: &mut HashMap<(String, String), String>,
3415) {
3416 let mut worklist = vec![root];
3417 while let Some(node) = worklist.pop() {
3418 let mut cursor = node.walk();
3419 for child in node.named_children(&mut cursor) {
3420 if child.kind() == "expression_statement" || child.kind() == "assignment" {
3421 let assign = if child.kind() == "assignment" {
3422 child
3423 } else {
3424 let mut inner_cursor = child.walk();
3425 let children: Vec<_> = child.named_children(&mut inner_cursor).collect();
3426 match children.into_iter().find(|c| c.kind() == "assignment") {
3427 Some(a) => a,
3428 None => continue,
3429 }
3430 };
3431
3432 if let Some(left) = assign.child_by_field_name("left") {
3433 if left.kind() == "attribute" {
3434 let obj = left
3435 .child_by_field_name("object")
3436 .and_then(|n| n.utf8_text(source).ok())
3437 .unwrap_or("");
3438 let attr = left
3439 .child_by_field_name("attribute")
3440 .and_then(|n| n.utf8_text(source).ok())
3441 .unwrap_or("");
3442
3443 if obj == "self" && !attr.is_empty() {
3444 if let Some(right) = assign.child_by_field_name("right") {
3445 if right.kind() == "identifier" {
3446 let rhs_name = right.utf8_text(source).unwrap_or("");
3447 if params.contains_key(rhs_name) {
3449 attr_to_param_map.insert(
3450 (class_name.to_string(), attr.to_string()),
3451 rhs_name.to_string(),
3452 );
3453 }
3454 if let Some(Some(type_hint)) = params.get(rhs_name) {
3456 instance_attr_types.insert(
3457 (class_name.to_string(), attr.to_string()),
3458 type_hint.clone(),
3459 );
3460 }
3461 }
3462 if right.kind() == "call" {
3463 if let Some(func) = right.child_by_field_name("function") {
3464 if func.kind() == "identifier" {
3465 let fname = func.utf8_text(source).unwrap_or("");
3466 if fname
3467 .chars()
3468 .next()
3469 .map_or(false, |c| c.is_uppercase())
3470 {
3471 instance_attr_types.insert(
3472 (class_name.to_string(), attr.to_string()),
3473 fname.to_string(),
3474 );
3475 }
3476 }
3477 }
3478 }
3479 }
3480 }
3481 }
3482 }
3483 }
3484 if child.kind() == "block" {
3485 worklist.push(child);
3486 }
3487 }
3488 }
3489}
3490
3491fn infer_constructor_param_types(
3496 parsed_files: &[(String, String, tree_sitter::Tree)],
3497 return_type_map: &HashMap<String, String>,
3498 init_params: &HashMap<String, Vec<String>>,
3499 attr_to_param: &HashMap<(String, String), String>,
3500 symbol_table: &HashMap<String, Vec<String>>,
3501 instance_attr_types: &mut HashMap<(String, String), String>,
3502) {
3503 let func_name_returns = deterministic_return_types_by_name(return_type_map, symbol_table);
3504 let attr_to_param_index = build_attr_to_param_index(attr_to_param);
3505
3506 let local_results: Vec<HashMap<(String, String), String>> = maybe_par_iter!(parsed_files)
3509 .map(|(_file_path, content, tree)| {
3510 let source = content.as_bytes();
3511 let mut local_attr_types: HashMap<(String, String), String> = HashMap::new();
3512 scan_constructor_calls(
3513 tree.root_node(),
3514 source,
3515 &func_name_returns,
3516 init_params,
3517 &attr_to_param_index,
3518 &mut local_attr_types,
3519 );
3520 local_attr_types
3521 })
3522 .collect();
3523
3524 for local in local_results {
3525 let mut local_entries: Vec<((String, String), String)> = local.into_iter().collect();
3526 local_entries.sort_unstable();
3527 for (key, val) in local_entries {
3528 instance_attr_types.entry(key).or_insert(val);
3529 }
3530 }
3531}
3532
3533fn deterministic_return_types_by_name(
3534 return_type_map: &HashMap<String, String>,
3535 symbol_table: &HashMap<String, Vec<String>>,
3536) -> HashMap<String, String> {
3537 let mut by_name = HashMap::with_capacity(return_type_map.len());
3538 for (name, target_ids) in symbol_table {
3539 if let Some(return_type) = target_ids
3540 .iter()
3541 .find_map(|target_id| return_type_map.get(target_id))
3542 {
3543 by_name.insert(name.clone(), return_type.clone());
3544 }
3545 }
3546 by_name
3547}
3548
3549fn build_attr_to_param_index(
3550 attr_to_param: &HashMap<(String, String), String>,
3551) -> AttrToParamIndex<'_> {
3552 let mut index: AttrToParamIndex<'_> = HashMap::with_capacity(attr_to_param.len());
3553 for ((class_name, attr_name), param_name) in attr_to_param {
3554 index
3555 .entry((class_name.as_str(), param_name.as_str()))
3556 .or_default()
3557 .push((class_name.as_str(), attr_name.as_str()));
3558 }
3559 for attrs in index.values_mut() {
3560 attrs.sort_unstable();
3561 }
3562 index
3563}
3564
3565fn scan_constructor_calls(
3566 root: tree_sitter::Node,
3567 source: &[u8],
3568 func_name_returns: &HashMap<String, String>,
3569 init_params: &HashMap<String, Vec<String>>,
3570 attr_to_param_index: &AttrToParamIndex<'_>,
3571 instance_attr_types: &mut HashMap<(String, String), String>,
3572) {
3573 let mut worklist = vec![root];
3574 while let Some(node) = worklist.pop() {
3575 let kind = node.kind();
3576
3577 if kind == "call" {
3578 if let Some(func) = node.child_by_field_name("function") {
3579 if func.kind() == "identifier" {
3580 let class_name = func.utf8_text(source).unwrap_or("");
3581 if class_name
3583 .chars()
3584 .next()
3585 .map_or(false, |c| c.is_uppercase())
3586 {
3587 if let Some(param_names) = init_params.get(class_name) {
3588 if let Some(args_node) = node.child_by_field_name("arguments") {
3590 let mut arg_idx = 0;
3591 let mut args_cursor = args_node.walk();
3592 for arg in args_node.named_children(&mut args_cursor) {
3593 if arg_idx >= param_names.len() {
3594 break;
3595 }
3596 let param_name = ¶m_names[arg_idx];
3597
3598 let arg_type = infer_expr_type(arg, source, func_name_returns);
3600
3601 if let Some(at) = arg_type {
3602 if let Some(attrs) = attr_to_param_index
3603 .get(&(class_name, param_name.as_str()))
3604 {
3605 for (cn, attr) in attrs {
3606 instance_attr_types
3607 .entry(((*cn).to_string(), (*attr).to_string()))
3608 .or_insert_with(|| at.clone());
3609 }
3610 }
3611 }
3612
3613 arg_idx += 1;
3614 }
3615 }
3616 }
3617 }
3618 }
3619 }
3620 }
3621
3622 push_named_children_rev(&mut worklist, node);
3623 }
3624}
3625
3626fn infer_expr_type(
3628 node: tree_sitter::Node,
3629 source: &[u8],
3630 func_name_returns: &HashMap<String, String>,
3631) -> Option<String> {
3632 match node.kind() {
3633 "call" => {
3634 if let Some(func) = node.child_by_field_name("function") {
3635 if func.kind() == "identifier" {
3636 let name = func.utf8_text(source).unwrap_or("");
3637 if name.chars().next().map_or(false, |c| c.is_uppercase()) {
3639 return Some(name.to_string());
3640 }
3641 if let Some(ret) = func_name_returns.get(name) {
3643 return Some(ret.clone());
3644 }
3645 }
3646 }
3647 None
3648 }
3649 "identifier" => {
3650 None
3652 }
3653 _ => None,
3654 }
3655}
3656
3657fn inject_return_type_bindings(
3660 scopes: &mut Vec<Scope>,
3661 func_name_return_types: &HashMap<String, String>,
3662 return_type_map: &HashMap<String, String>,
3663 import_table_by_name: &HashMap<&str, &str>,
3664) {
3665 for scope in scopes.iter_mut() {
3667 let resolved: Vec<(String, String)> = scope
3668 .pending_call_types
3669 .iter()
3670 .filter_map(|(var_name, func_name)| {
3671 import_table_by_name
3672 .get(func_name.as_str())
3673 .and_then(|target_id| return_type_map.get(*target_id))
3674 .or_else(|| func_name_return_types.get(func_name))
3675 .map(|ret_type| (var_name.clone(), ret_type.clone()))
3676 })
3677 .collect();
3678
3679 for (var_name, ret_type) in resolved {
3680 scope.types.insert(var_name, ret_type);
3681 }
3682 }
3683}
3684
3685fn build_ts_default_export_table(
3686 parsed_files: &[(String, String, tree_sitter::Tree)],
3687 symbol_table: &HashMap<String, Vec<String>>,
3688 entity_map: &HashMap<String, EntityInfo>,
3689) -> TsDefaultExportTable {
3690 let per_file: Vec<(Option<(String, String)>, Vec<TsDefaultReExport>)> =
3693 maybe_par_iter!(parsed_files)
3694 .filter_map(|(file_path, content, tree)| {
3695 if !is_js_ts_file(file_path) {
3696 return None;
3697 }
3698
3699 let extracted = extract_ts_default_exports(tree.root_node(), content.as_bytes());
3700 let mut default_export: Option<(String, String)> = None;
3701 for name in extracted.names {
3702 let Some(target_ids) = symbol_table.get(&name) else {
3703 continue;
3704 };
3705 let target = target_ids.iter().find(|id| {
3706 entity_map.get(*id).map_or(false, |entity| {
3707 entity.file_path == *file_path && entity.parent_id.is_none()
3708 })
3709 });
3710 if let Some(target_id) = target {
3711 default_export = Some((file_path.clone(), target_id.clone()));
3712 }
3713 }
3714
3715 let re_exports: Vec<TsDefaultReExport> = extracted
3716 .re_exports
3717 .into_iter()
3718 .map(|(original_name, module_path)| TsDefaultReExport {
3719 file_path: file_path.clone(),
3720 original_name,
3721 module_path,
3722 })
3723 .collect();
3724
3725 Some((default_export, re_exports))
3726 })
3727 .collect();
3728
3729 let mut default_exports = HashMap::new();
3730 let mut re_exports = Vec::new();
3731 for (default_export, file_re_exports) in per_file {
3732 if let Some((file_path, target_id)) = default_export {
3733 default_exports.insert(file_path, target_id);
3734 }
3735 re_exports.extend(file_re_exports);
3736 }
3737
3738 resolve_ts_default_re_exports(&mut default_exports, re_exports, symbol_table, entity_map);
3739 let sorted_files = sorted_default_export_files(&default_exports);
3740
3741 TsDefaultExportTable {
3742 exports_by_file: default_exports,
3743 sorted_files,
3744 }
3745}
3746
3747fn sorted_default_export_files(default_exports: &HashMap<String, String>) -> Vec<String> {
3748 let mut sorted_files: Vec<String> = default_exports.keys().cloned().collect();
3749 sort_import_candidate_files(&mut sorted_files, JS_TS_EXTENSIONS);
3750 sorted_files
3751}
3752
3753fn resolve_ts_default_re_exports(
3754 default_exports: &mut HashMap<String, String>,
3755 pending: Vec<TsDefaultReExport>,
3756 symbol_table: &HashMap<String, Vec<String>>,
3757 entity_map: &HashMap<String, EntityInfo>,
3758) {
3759 let mut pending = pending;
3760 while !pending.is_empty() {
3761 let sorted_files = sorted_default_export_files(default_exports);
3762 let mut unresolved = Vec::new();
3763 let mut progressed = false;
3764
3765 for re_export in pending {
3766 let target_id = if re_export.original_name == "default" {
3767 find_import_file(
3768 &sorted_files,
3769 &re_export.module_path,
3770 &re_export.file_path,
3771 JS_TS_EXTENSIONS,
3772 )
3773 .and_then(|target_file| default_exports.get(target_file))
3774 .cloned()
3775 } else {
3776 symbol_table
3777 .get(&re_export.original_name)
3778 .and_then(|target_ids| {
3779 find_import_target(
3780 target_ids,
3781 &re_export.module_path,
3782 &re_export.file_path,
3783 JS_TS_EXTENSIONS,
3784 entity_map,
3785 )
3786 .cloned()
3787 })
3788 };
3789
3790 if let Some(target_id) = target_id {
3791 default_exports.insert(re_export.file_path, target_id);
3792 progressed = true;
3793 } else {
3794 unresolved.push(re_export);
3795 }
3796 }
3797
3798 if !progressed {
3799 break;
3800 }
3801 pending = unresolved;
3802 }
3803}
3804
3805fn build_top_level_entity_index(
3806 symbol_table: &HashMap<String, Vec<String>>,
3807 entity_map: &HashMap<String, EntityInfo>,
3808) -> TopLevelEntityIndex {
3809 let mut entities_by_file: HashMap<String, Vec<(String, String)>> = HashMap::new();
3810
3811 for (name, target_ids) in symbol_table {
3812 for target_id in target_ids {
3813 let Some(info) = entity_map.get(target_id) else {
3814 continue;
3815 };
3816 if !is_js_ts_file(&info.file_path) || info.parent_id.is_some() {
3817 continue;
3818 }
3819 entities_by_file
3820 .entry(info.file_path.clone())
3821 .or_default()
3822 .push((name.clone(), target_id.clone()));
3823 }
3824 }
3825
3826 let mut sorted_files: Vec<String> = entities_by_file.keys().cloned().collect();
3827 sort_import_candidate_files(&mut sorted_files, JS_TS_EXTENSIONS);
3828
3829 TopLevelEntityIndex {
3830 entities_by_file,
3831 sorted_files,
3832 }
3833}
3834
3835struct TsDefaultExports {
3836 names: Vec<String>,
3837 re_exports: Vec<(String, String)>,
3838}
3839
3840fn extract_ts_default_exports(root: tree_sitter::Node, source: &[u8]) -> TsDefaultExports {
3841 let mut names = Vec::new();
3842 let mut re_exports = Vec::new();
3843 let mut worklist = vec![root];
3844
3845 while let Some(node) = worklist.pop() {
3846 if node.kind() == "export_statement" {
3847 let has_source = node.child_by_field_name("source").is_some();
3848 let source_path = node
3849 .child_by_field_name("source")
3850 .and_then(|n| n.utf8_text(source).ok())
3851 .map(|text| {
3852 text.trim_matches(|c: char| c == '\'' || c == '"')
3853 .to_string()
3854 });
3855 let text = node.utf8_text(source).unwrap_or("");
3856 if !has_source {
3857 if let Some(declaration) = node.child_by_field_name("declaration") {
3858 if text.contains("default") {
3859 if let Some(name) = ts_default_declaration_name(declaration, source) {
3860 names.push(name);
3861 }
3862 }
3863 } else if text.contains("default") && !has_ts_export_specifier(node) {
3864 if let Some(name) = ts_bare_default_export_identifier(node, source) {
3865 names.push(name);
3866 }
3867 }
3868 }
3869 collect_ts_default_export_specifiers(
3870 node,
3871 source,
3872 source_path.as_deref(),
3873 &mut names,
3874 &mut re_exports,
3875 );
3876 }
3877
3878 let mut cursor = node.walk();
3879 for child in node.named_children(&mut cursor) {
3880 worklist.push(child);
3881 }
3882 }
3883
3884 TsDefaultExports { names, re_exports }
3885}
3886
3887fn ts_default_declaration_name(node: tree_sitter::Node, source: &[u8]) -> Option<String> {
3888 match node.kind() {
3889 "function_declaration"
3890 | "generator_function_declaration"
3891 | "class_declaration"
3892 | "abstract_class_declaration"
3893 | "lexical_declaration"
3894 | "variable_declaration" => ts_declaration_name(node, source),
3895 "identifier" => node.utf8_text(source).ok().map(str::to_string),
3896 _ => None,
3897 }
3898}
3899
3900fn has_ts_export_specifier(node: tree_sitter::Node) -> bool {
3901 let mut worklist = vec![node];
3902 while let Some(current) = worklist.pop() {
3903 let mut cursor = current.walk();
3904 for child in current.named_children(&mut cursor) {
3905 if child.kind() == "export_specifier" {
3906 return true;
3907 }
3908 worklist.push(child);
3909 }
3910 }
3911 false
3912}
3913
3914fn collect_ts_default_export_specifiers(
3915 node: tree_sitter::Node,
3916 source: &[u8],
3917 source_path: Option<&str>,
3918 names: &mut Vec<String>,
3919 re_exports: &mut Vec<(String, String)>,
3920) {
3921 let mut worklist = vec![node];
3922 while let Some(current) = worklist.pop() {
3923 let mut cursor = current.walk();
3924 for child in current.named_children(&mut cursor) {
3925 if child.kind() == "export_specifier" {
3926 let original = child
3927 .child_by_field_name("name")
3928 .and_then(|n| n.utf8_text(source).ok())
3929 .unwrap_or("");
3930 let local = child
3931 .child_by_field_name("alias")
3932 .and_then(|n| n.utf8_text(source).ok())
3933 .unwrap_or(original);
3934 if local == "default" && !original.is_empty() {
3935 if let Some(source_path) = source_path {
3936 re_exports.push((original.to_string(), source_path.to_string()));
3937 } else {
3938 names.push(original.to_string());
3939 }
3940 }
3941 } else {
3942 worklist.push(child);
3943 }
3944 }
3945 }
3946}
3947
3948fn ts_declaration_name(node: tree_sitter::Node, source: &[u8]) -> Option<String> {
3949 if let Some(name) = node.child_by_field_name("name") {
3950 return Some(name.utf8_text(source).ok()?.to_string());
3951 }
3952
3953 if node.kind() == "lexical_declaration" || node.kind() == "variable_declaration" {
3954 let mut cursor = node.walk();
3955 for child in node.named_children(&mut cursor) {
3956 if child.kind() == "variable_declarator" {
3957 if let Some(name) = child.child_by_field_name("name") {
3958 return Some(name.utf8_text(source).ok()?.to_string());
3959 }
3960 }
3961 }
3962 }
3963
3964 let mut cursor = node.walk();
3965 let name = node
3966 .named_children(&mut cursor)
3967 .find(|child| matches!(child.kind(), "identifier" | "type_identifier"))
3968 .and_then(|child| child.utf8_text(source).ok())
3969 .map(str::to_string);
3970 name
3971}
3972
3973fn ts_bare_default_export_identifier(node: tree_sitter::Node, source: &[u8]) -> Option<String> {
3974 let text = node.utf8_text(source).ok()?.trim();
3975 let rest = text.strip_prefix("export")?.trim_start();
3976 let rest = rest.strip_prefix("default")?.trim_start();
3977 let name_end = js_ts_identifier_end(rest)?;
3978 let name = &rest[..name_end];
3979 let trailing = rest[name_end..].trim_start();
3980 only_js_ts_statement_trivia(trailing).then(|| name.to_string())
3981}
3982
3983fn js_ts_identifier_end(text: &str) -> Option<usize> {
3984 let mut chars = text.char_indices();
3985 let (_, first) = chars.next()?;
3986 if !(first == '_' || first == '$' || first.is_ascii_alphabetic()) {
3987 return None;
3988 }
3989
3990 let mut end = first.len_utf8();
3991 for (idx, ch) in chars {
3992 if ch == '_' || ch == '$' || ch.is_ascii_alphanumeric() {
3993 end = idx + ch.len_utf8();
3994 } else {
3995 break;
3996 }
3997 }
3998 Some(end)
3999}
4000
4001fn only_js_ts_statement_trivia(mut text: &str) -> bool {
4002 loop {
4003 text = text.trim_start();
4004 if let Some(rest) = text.strip_prefix(';') {
4005 text = rest;
4006 continue;
4007 }
4008 if text.is_empty() {
4009 return true;
4010 }
4011 if text.starts_with("//") {
4012 return true;
4013 }
4014 if let Some(rest) = text.strip_prefix("/*") {
4015 let Some(end) = rest.find("*/") else {
4016 return false;
4017 };
4018 text = &rest[end + 2..];
4019 continue;
4020 }
4021 return false;
4022 }
4023}
4024
4025fn extract_imports_from_ast<'a>(
4027 root: tree_sitter::Node,
4028 file_path: &str,
4029 source: &[u8],
4030 symbol_table: &HashMap<String, Vec<String>>,
4031 entity_map: &HashMap<String, EntityInfo>,
4032 import_table: &mut HashMap<(String, String), String>,
4033 scopes: &mut Vec<Scope>,
4034 config: &ScopeResolveConfig,
4035 go_pkg_index: &HashMap<String, Vec<(String, String)>>,
4036 ts_default_exports: &TsDefaultExportTable,
4037 top_level_entities: &OnceLock<TopLevelEntityIndex>,
4038 parsed_files: &'a [(String, String, tree_sitter::Tree)],
4039 content_by_file: &OnceLock<HashMap<&'a str, &'a str>>,
4040 exported_names_by_file: &Mutex<HashMap<String, Arc<HashSet<String>>>>,
4041 skip_js_ts_imports: bool,
4042) {
4043 let mut worklist = vec![root];
4044 while let Some(node) = worklist.pop() {
4045 let mut cursor = node.walk();
4046 for child in node.named_children(&mut cursor) {
4047 let ck = child.kind();
4048 let handled = match ck {
4049 "import_from_statement" => {
4050 extract_python_import(
4051 child,
4052 file_path,
4053 source,
4054 symbol_table,
4055 entity_map,
4056 import_table,
4057 scopes,
4058 );
4059 true
4060 }
4061 "import_statement"
4062 if config.self_keywords.contains(&"self")
4063 && config.self_keywords.contains(&"cls") =>
4064 {
4065 extract_python_module_import(
4067 child,
4068 file_path,
4069 source,
4070 symbol_table,
4071 entity_map,
4072 import_table,
4073 scopes,
4074 );
4075 true
4076 }
4077 "import_statement" if !config.self_keywords.contains(&"cls") => {
4078 if !skip_js_ts_imports {
4079 extract_ts_import(
4080 child,
4081 file_path,
4082 source,
4083 symbol_table,
4084 entity_map,
4085 import_table,
4086 scopes,
4087 ts_default_exports,
4088 top_level_entities,
4089 parsed_files,
4090 content_by_file,
4091 exported_names_by_file,
4092 );
4093 }
4094 true
4095 }
4096 "export_statement" if !config.self_keywords.contains(&"cls") => {
4097 if !skip_js_ts_imports {
4098 extract_ts_re_export(
4099 child,
4100 file_path,
4101 source,
4102 symbol_table,
4103 entity_map,
4104 import_table,
4105 scopes,
4106 ts_default_exports,
4107 );
4108 }
4109 true
4110 }
4111 "use_declaration" => {
4112 extract_rust_use(
4113 child,
4114 file_path,
4115 source,
4116 symbol_table,
4117 entity_map,
4118 import_table,
4119 scopes,
4120 );
4121 true
4122 }
4123 "import_declaration" => {
4124 extract_go_import(
4125 child,
4126 file_path,
4127 source,
4128 symbol_table,
4129 entity_map,
4130 import_table,
4131 scopes,
4132 go_pkg_index,
4133 );
4134 true
4135 }
4136 _ => false,
4137 };
4138 if !handled {
4139 worklist.push(child);
4140 }
4141 }
4142 }
4143}
4144
4145fn extract_ts_import<'a>(
4147 node: tree_sitter::Node,
4148 file_path: &str,
4149 source: &[u8],
4150 symbol_table: &HashMap<String, Vec<String>>,
4151 entity_map: &HashMap<String, EntityInfo>,
4152 import_table: &mut HashMap<(String, String), String>,
4153 scopes: &mut Vec<Scope>,
4154 ts_default_exports: &TsDefaultExportTable,
4155 top_level_entities: &OnceLock<TopLevelEntityIndex>,
4156 parsed_files: &'a [(String, String, tree_sitter::Tree)],
4157 content_by_file: &OnceLock<HashMap<&'a str, &'a str>>,
4158 exported_names_by_file: &Mutex<HashMap<String, Arc<HashSet<String>>>>,
4159) {
4160 let source_path = node
4162 .child_by_field_name("source")
4163 .and_then(|n| n.utf8_text(source).ok())
4164 .unwrap_or("")
4165 .trim_matches(|c: char| c == '\'' || c == '"');
4166
4167 if source_path.is_empty() {
4168 return;
4169 }
4170
4171 let mut cursor = node.walk();
4173 for child in node.named_children(&mut cursor) {
4174 if child.kind() == "import_clause" {
4175 let mut clause_cursor = child.walk();
4176 for clause_child in child.named_children(&mut clause_cursor) {
4177 if clause_child.kind() == "named_imports" {
4178 let mut imports_cursor = clause_child.walk();
4180 for spec in clause_child.named_children(&mut imports_cursor) {
4181 if spec.kind() == "import_specifier" {
4182 let original = spec
4183 .child_by_field_name("name")
4184 .and_then(|n| n.utf8_text(source).ok())
4185 .unwrap_or("");
4186 let local = spec
4187 .child_by_field_name("alias")
4188 .and_then(|n| n.utf8_text(source).ok())
4189 .unwrap_or(original);
4190
4191 if !original.is_empty() {
4192 resolve_import_name(
4193 original,
4194 local,
4195 source_path,
4196 file_path,
4197 JS_TS_EXTENSIONS,
4198 symbol_table,
4199 entity_map,
4200 import_table,
4201 scopes,
4202 );
4203 }
4204 }
4205 }
4206 } else if clause_child.kind() == "namespace_import" {
4207 let mut ns_cursor = clause_child.walk();
4210 let alias = clause_child
4211 .child_by_field_name("alias")
4212 .or_else(|| {
4213 clause_child
4214 .named_children(&mut ns_cursor)
4215 .find(|c| c.kind() == "identifier")
4216 })
4217 .and_then(|n| n.utf8_text(source).ok())
4218 .unwrap_or("");
4219 if !alias.is_empty() {
4220 register_ts_namespace_import(
4221 alias,
4222 source_path,
4223 file_path,
4224 JS_TS_EXTENSIONS,
4225 top_level_entities,
4226 symbol_table,
4227 entity_map,
4228 parsed_files,
4229 content_by_file,
4230 exported_names_by_file,
4231 import_table,
4232 scopes,
4233 );
4234 }
4235 } else if clause_child.kind() == "identifier" {
4236 let name = clause_child.utf8_text(source).unwrap_or("");
4238 if !name.is_empty() {
4239 resolve_default_import(
4240 name,
4241 source_path,
4242 file_path,
4243 JS_TS_EXTENSIONS,
4244 ts_default_exports,
4245 import_table,
4246 scopes,
4247 );
4248 }
4249 }
4250 }
4251 }
4252 }
4253}
4254
4255fn extract_ts_re_export(
4256 node: tree_sitter::Node,
4257 file_path: &str,
4258 source: &[u8],
4259 symbol_table: &HashMap<String, Vec<String>>,
4260 entity_map: &HashMap<String, EntityInfo>,
4261 import_table: &mut HashMap<(String, String), String>,
4262 scopes: &mut Vec<Scope>,
4263 ts_default_exports: &TsDefaultExportTable,
4264) {
4265 let source_path = node
4266 .child_by_field_name("source")
4267 .and_then(|n| n.utf8_text(source).ok())
4268 .unwrap_or("")
4269 .trim_matches(|c: char| c == '\'' || c == '"');
4270
4271 if source_path.is_empty() {
4272 return;
4273 }
4274
4275 let mut worklist = vec![node];
4276 while let Some(current) = worklist.pop() {
4277 let mut cursor = current.walk();
4278 for child in current.named_children(&mut cursor) {
4279 match child.kind() {
4280 "export_specifier" => {
4281 let original = child
4282 .child_by_field_name("name")
4283 .and_then(|n| n.utf8_text(source).ok())
4284 .unwrap_or("");
4285 let local = child
4286 .child_by_field_name("alias")
4287 .and_then(|n| n.utf8_text(source).ok())
4288 .unwrap_or(original);
4289
4290 if original.is_empty() || local.is_empty() {
4291 continue;
4292 }
4293
4294 if original == "default" {
4295 resolve_default_import(
4296 local,
4297 source_path,
4298 file_path,
4299 JS_TS_EXTENSIONS,
4300 ts_default_exports,
4301 import_table,
4302 scopes,
4303 );
4304 } else {
4305 resolve_import_name(
4306 original,
4307 local,
4308 source_path,
4309 file_path,
4310 JS_TS_EXTENSIONS,
4311 symbol_table,
4312 entity_map,
4313 import_table,
4314 scopes,
4315 );
4316 }
4317 }
4318 "export_clause" | "namespace_export" => {
4319 worklist.push(child);
4320 }
4321 _ => {}
4322 }
4323 }
4324 }
4325}
4326
4327fn extract_rust_use(
4330 node: tree_sitter::Node,
4331 file_path: &str,
4332 source: &[u8],
4333 symbol_table: &HashMap<String, Vec<String>>,
4334 entity_map: &HashMap<String, EntityInfo>,
4335 import_table: &mut HashMap<(String, String), String>,
4336 scopes: &mut Vec<Scope>,
4337) {
4338 let text = node.utf8_text(source).unwrap_or("").trim().to_string();
4339 let text = text.strip_prefix("use ").unwrap_or(&text);
4341 let text = text.strip_prefix("pub use ").unwrap_or(text);
4342 let text = text.trim_end_matches(';').trim();
4343
4344 let text = text
4346 .strip_prefix("crate::")
4347 .or_else(|| text.strip_prefix("super::"))
4348 .or_else(|| text.strip_prefix("self::"))
4349 .unwrap_or(text);
4350
4351 if let Some(brace_pos) = text.find("::{") {
4353 let module_path = &text[..brace_pos];
4354 let source_module = module_path.rsplit("::").next().unwrap_or(module_path);
4355
4356 let names_part = &text[brace_pos + 3..];
4357 let names_part = names_part.trim_end_matches('}');
4358
4359 for name_part in names_part.split(',') {
4360 let name_part = name_part.trim();
4361 if name_part.is_empty() {
4362 continue;
4363 }
4364 let (original, local) = if let Some(pos) = name_part.find(" as ") {
4365 (name_part[..pos].trim(), name_part[pos + 4..].trim())
4366 } else {
4367 (name_part, name_part)
4368 };
4369 if !original.is_empty() {
4370 resolve_import_name(
4371 original,
4372 local,
4373 source_module,
4374 file_path,
4375 &[".rs"],
4376 symbol_table,
4377 entity_map,
4378 import_table,
4379 scopes,
4380 );
4381 }
4382 }
4383 } else {
4384 let parts: Vec<&str> = text.split("::").collect();
4386 if parts.is_empty() {
4387 return;
4388 }
4389 let imported_name = parts.last().unwrap().trim();
4390 let (original, local) = if let Some(pos) = imported_name.find(" as ") {
4391 (&imported_name[..pos], imported_name[pos + 4..].trim())
4392 } else {
4393 (imported_name, imported_name)
4394 };
4395 let source_module = if parts.len() >= 2 {
4396 parts[parts.len() - 2]
4397 } else {
4398 parts[0]
4399 };
4400 if !original.is_empty() && !source_module.is_empty() {
4401 resolve_import_name(
4402 original,
4403 local,
4404 source_module,
4405 file_path,
4406 &[".rs"],
4407 symbol_table,
4408 entity_map,
4409 import_table,
4410 scopes,
4411 );
4412 }
4413 }
4414}
4415
4416fn extract_go_import(
4418 node: tree_sitter::Node,
4419 file_path: &str,
4420 source: &[u8],
4421 symbol_table: &HashMap<String, Vec<String>>,
4422 entity_map: &HashMap<String, EntityInfo>,
4423 import_table: &mut HashMap<(String, String), String>,
4424 scopes: &mut Vec<Scope>,
4425 go_pkg_index: &HashMap<String, Vec<(String, String)>>,
4426) {
4427 let mut cursor = node.walk();
4428 for child in node.named_children(&mut cursor) {
4429 if child.kind() == "import_spec" || child.kind() == "import_spec_list" {
4430 extract_go_import_specs(
4431 child,
4432 file_path,
4433 source,
4434 symbol_table,
4435 entity_map,
4436 import_table,
4437 scopes,
4438 go_pkg_index,
4439 );
4440 } else if child.kind() == "interpreted_string_literal"
4441 || child.kind() == "raw_string_literal"
4442 {
4443 let path = child
4444 .utf8_text(source)
4445 .unwrap_or("")
4446 .trim_matches('"')
4447 .trim_matches('`');
4448 let pkg_name = path.rsplit('/').next().unwrap_or(path);
4449 register_go_package_imports(
4450 pkg_name,
4451 file_path,
4452 symbol_table,
4453 entity_map,
4454 import_table,
4455 scopes,
4456 go_pkg_index,
4457 );
4458 }
4459 }
4460}
4461
4462fn extract_go_import_specs(
4463 root: tree_sitter::Node,
4464 file_path: &str,
4465 source: &[u8],
4466 symbol_table: &HashMap<String, Vec<String>>,
4467 entity_map: &HashMap<String, EntityInfo>,
4468 import_table: &mut HashMap<(String, String), String>,
4469 scopes: &mut Vec<Scope>,
4470 go_pkg_index: &HashMap<String, Vec<(String, String)>>,
4471) {
4472 let mut worklist = vec![root];
4473 while let Some(node) = worklist.pop() {
4474 let mut cursor = node.walk();
4475 for child in node.named_children(&mut cursor) {
4476 if child.kind() == "import_spec" {
4477 let path_node = child
4478 .child_by_field_name("path")
4479 .or_else(|| child.named_child(0));
4480 if let Some(pn) = path_node {
4481 let path = pn
4482 .utf8_text(source)
4483 .unwrap_or("")
4484 .trim_matches('"')
4485 .trim_matches('`');
4486 let pkg_name = path.rsplit('/').next().unwrap_or(path);
4487 register_go_package_imports(
4488 pkg_name,
4489 file_path,
4490 symbol_table,
4491 entity_map,
4492 import_table,
4493 scopes,
4494 go_pkg_index,
4495 );
4496 }
4497 } else {
4498 worklist.push(child);
4499 }
4500 }
4501 }
4502}
4503
4504fn register_go_package_imports(
4505 pkg_name: &str,
4506 file_path: &str,
4507 _symbol_table: &HashMap<String, Vec<String>>,
4508 _entity_map: &HashMap<String, EntityInfo>,
4509 import_table: &mut HashMap<(String, String), String>,
4510 scopes: &mut Vec<Scope>,
4511 go_pkg_index: &HashMap<String, Vec<(String, String)>>,
4512) {
4513 if let Some(entries) = go_pkg_index.get(pkg_name) {
4515 for (name, target_id) in entries {
4516 import_table.insert((file_path.to_string(), name.clone()), target_id.clone());
4517 if !scopes.is_empty() {
4518 scopes[0].defs.insert(name.clone(), target_id.clone());
4519 }
4520 }
4521 }
4522}
4523
4524fn resolve_import_name(
4526 original_name: &str,
4527 local_name: &str,
4528 source_path: &str,
4529 file_path: &str,
4530 extensions: &[&str],
4531 symbol_table: &HashMap<String, Vec<String>>,
4532 entity_map: &HashMap<String, EntityInfo>,
4533 import_table: &mut HashMap<(String, String), String>,
4534 scopes: &mut Vec<Scope>,
4535) {
4536 if let Some(target_ids) = symbol_table.get(original_name) {
4537 let target = find_import_target(target_ids, source_path, file_path, extensions, entity_map);
4538
4539 if let Some(target_id) = target {
4540 import_table.insert(
4541 (file_path.to_string(), local_name.to_string()),
4542 target_id.clone(),
4543 );
4544 if !scopes.is_empty() {
4545 scopes[0]
4546 .defs
4547 .insert(local_name.to_string(), target_id.clone());
4548 }
4549 }
4550 }
4551}
4552
4553fn resolve_default_import(
4554 local_name: &str,
4555 source_path: &str,
4556 file_path: &str,
4557 extensions: &[&str],
4558 default_exports: &TsDefaultExportTable,
4559 import_table: &mut HashMap<(String, String), String>,
4560 scopes: &mut Vec<Scope>,
4561) {
4562 let target = find_import_file(
4563 &default_exports.sorted_files,
4564 source_path,
4565 file_path,
4566 extensions,
4567 )
4568 .and_then(|target_file| default_exports.exports_by_file.get(target_file))
4569 .cloned();
4570
4571 if let Some(target_id) = target {
4572 import_table.insert(
4573 (file_path.to_string(), local_name.to_string()),
4574 target_id.clone(),
4575 );
4576 if !scopes.is_empty() {
4577 scopes[0].defs.insert(local_name.to_string(), target_id);
4578 }
4579 }
4580}
4581
4582fn register_ts_namespace_import<'a>(
4586 alias: &str,
4587 source_path: &str,
4588 file_path: &str,
4589 extensions: &[&str],
4590 top_level_entities: &OnceLock<TopLevelEntityIndex>,
4591 symbol_table: &HashMap<String, Vec<String>>,
4592 entity_map: &HashMap<String, EntityInfo>,
4593 parsed_files: &'a [(String, String, tree_sitter::Tree)],
4594 content_by_file: &OnceLock<HashMap<&'a str, &'a str>>,
4595 exported_names_by_file: &Mutex<HashMap<String, Arc<HashSet<String>>>>,
4596 import_table: &mut HashMap<(String, String), String>,
4597 _scopes: &mut Vec<Scope>,
4598) {
4599 let top_level_entities =
4600 top_level_entities.get_or_init(|| build_top_level_entity_index(symbol_table, entity_map));
4601 let Some(candidate_file) = find_import_file(
4602 &top_level_entities.sorted_files,
4603 source_path,
4604 file_path,
4605 extensions,
4606 ) else {
4607 return;
4608 };
4609 let Some(entries) = top_level_entities.entities_by_file.get(candidate_file) else {
4610 return;
4611 };
4612 let exported_names = {
4613 let mut cache = exported_names_by_file.lock().unwrap();
4614 cache
4615 .entry(candidate_file.to_string())
4616 .or_insert_with(|| {
4617 let content_by_file = content_by_file.get_or_init(|| {
4618 parsed_files
4619 .iter()
4620 .map(|(file_path, content, _)| (file_path.as_str(), content.as_str()))
4621 .collect()
4622 });
4623 Arc::new(
4624 content_by_file
4625 .get(candidate_file)
4626 .map(|content| js_ts_named_exports_from_content(content))
4627 .unwrap_or_default(),
4628 )
4629 })
4630 .clone()
4631 };
4632 for (name, target_id) in entries {
4633 if !exported_names.contains(name) {
4634 continue;
4635 }
4636 let qualified_name = format!("{alias}.{name}");
4637 import_table
4638 .entry((file_path.to_string(), qualified_name))
4639 .or_insert_with(|| target_id.clone());
4640 }
4641}
4642
4643fn register_namespace_import(
4644 alias: &str,
4645 source_path: &str,
4646 file_path: &str,
4647 extensions: &[&str],
4648 symbol_table: &HashMap<String, Vec<String>>,
4649 entity_map: &HashMap<String, EntityInfo>,
4650 import_table: &mut HashMap<(String, String), String>,
4651 _scopes: &mut Vec<Scope>,
4652) {
4653 for (name, target_ids) in symbol_table {
4655 for target_id in target_ids {
4656 if let Some(info) = entity_map.get(target_id) {
4657 if import_source_matches_file(file_path, source_path, extensions, &info.file_path)
4658 && info.parent_id.is_none()
4659 {
4660 let qualified_name = format!("{alias}.{name}");
4661 import_table.insert(
4662 (file_path.to_string(), qualified_name.clone()),
4663 target_id.clone(),
4664 );
4665 }
4666 }
4667 }
4668 }
4669}
4670
4671fn extract_python_import(
4672 node: tree_sitter::Node,
4673 file_path: &str,
4674 source: &[u8],
4675 symbol_table: &HashMap<String, Vec<String>>,
4676 entity_map: &HashMap<String, EntityInfo>,
4677 import_table: &mut HashMap<(String, String), String>,
4678 scopes: &mut Vec<Scope>,
4679) {
4680 let module_node = node.child_by_field_name("module_name");
4684 let module_name = module_node
4685 .and_then(|n| n.utf8_text(source).ok())
4686 .unwrap_or("");
4687
4688 let mut cursor = node.walk();
4690 for child in node.named_children(&mut cursor) {
4691 if child.kind() == "dotted_name" || child.kind() == "aliased_import" {
4692 let (original, local) = if child.kind() == "aliased_import" {
4693 let orig = child
4694 .child_by_field_name("name")
4695 .and_then(|n| n.utf8_text(source).ok())
4696 .unwrap_or("");
4697 let alias = child
4698 .child_by_field_name("alias")
4699 .and_then(|n| n.utf8_text(source).ok())
4700 .unwrap_or(orig);
4701 (orig, alias)
4702 } else {
4703 let name = child.utf8_text(source).unwrap_or("");
4704 (name, name)
4705 };
4706
4707 if original.is_empty() {
4708 continue;
4709 }
4710
4711 resolve_import_name(
4712 original,
4713 local,
4714 module_name,
4715 file_path,
4716 &[".py"],
4717 symbol_table,
4718 entity_map,
4719 import_table,
4720 scopes,
4721 );
4722 }
4723 }
4724}
4725
4726fn extract_python_module_import(
4729 node: tree_sitter::Node,
4730 file_path: &str,
4731 source: &[u8],
4732 symbol_table: &HashMap<String, Vec<String>>,
4733 entity_map: &HashMap<String, EntityInfo>,
4734 import_table: &mut HashMap<(String, String), String>,
4735 scopes: &mut Vec<Scope>,
4736) {
4737 let mut cursor = node.walk();
4738 for child in node.named_children(&mut cursor) {
4739 let (module_name, _alias) = match child.kind() {
4740 "dotted_name" => {
4741 let name = child.utf8_text(source).unwrap_or("");
4742 (name, name)
4743 }
4744 "aliased_import" => {
4745 let orig = child
4746 .child_by_field_name("name")
4747 .and_then(|n| n.utf8_text(source).ok())
4748 .unwrap_or("");
4749 let alias = child
4750 .child_by_field_name("alias")
4751 .and_then(|n| n.utf8_text(source).ok())
4752 .unwrap_or(orig);
4753 (orig, alias)
4754 }
4755 _ => continue,
4756 };
4757
4758 if module_name.is_empty() {
4759 continue;
4760 }
4761
4762 register_namespace_import(
4764 _alias,
4765 module_name,
4766 file_path,
4767 &[".py"],
4768 symbol_table,
4769 entity_map,
4770 import_table,
4771 scopes,
4772 );
4773 }
4774}
4775
4776fn build_swift_call_signatures(
4777 parsed_files: &[(String, String, tree_sitter::Tree)],
4778 all_entities: &[SemanticEntity],
4779 entity_ranges: &HashMap<String, Vec<(usize, usize, String)>>,
4780 entity_map: &HashMap<String, EntityInfo>,
4781) -> HashMap<String, SwiftCallSignature> {
4782 let mut signatures = HashMap::new();
4783
4784 for (file_path, content, tree) in parsed_files {
4785 if !file_path.ends_with(".swift") {
4786 continue;
4787 }
4788
4789 let Some(ranges) = entity_ranges.get(file_path.as_str()) else {
4790 continue;
4791 };
4792
4793 let source = content.as_bytes();
4794 let mut worklist = vec![tree.root_node()];
4795 while let Some(node) = worklist.pop() {
4796 if matches!(node.kind(), "function_declaration" | "init_declaration") {
4797 if let Some(entity_id) =
4798 find_entity_id_for_swift_declaration(node, ranges, entity_map)
4799 {
4800 let argument_labels = extract_swift_declaration_argument_labels(node, source);
4801 signatures.insert(entity_id, SwiftCallSignature { argument_labels });
4802 }
4803 }
4804
4805 push_named_children_rev(&mut worklist, node);
4806 }
4807 }
4808
4809 let mut content_parser: Option<tree_sitter::Parser> = None;
4810 for entity in all_entities {
4811 if signatures.contains_key(&entity.id) || !is_swift_callable_entity_info(entity) {
4812 continue;
4813 }
4814
4815 if content_parser.is_none() {
4816 content_parser = swift_signature_parser();
4817 }
4818 let Some(parser) = content_parser.as_mut() else {
4819 break;
4820 };
4821
4822 if let Some(argument_labels) = extract_swift_signature_from_entity_content(entity, parser) {
4823 signatures.insert(entity.id.clone(), SwiftCallSignature { argument_labels });
4824 }
4825 }
4826
4827 signatures
4828}
4829
4830fn find_entity_id_for_swift_declaration(
4831 node: tree_sitter::Node,
4832 ranges: &[(usize, usize, String)],
4833 entity_map: &HashMap<String, EntityInfo>,
4834) -> Option<String> {
4835 let start_line = node.start_position().row + 1;
4836 let end_line = node.end_position().row + 1;
4837
4838 ranges
4839 .iter()
4840 .filter(|(start, end, id)| {
4841 *start <= start_line
4842 && *end >= end_line
4843 && entity_map.get(id).map_or(false, is_swift_callable_entity)
4844 })
4845 .min_by_key(|(start, end, _)| end.saturating_sub(*start))
4846 .map(|(_, _, id)| id.clone())
4847}
4848
4849fn is_swift_callable_entity(info: &EntityInfo) -> bool {
4850 info.file_path.ends_with(".swift")
4851 && matches!(
4852 info.entity_type.as_str(),
4853 "function" | "method" | "init" | "init_declaration"
4854 )
4855}
4856
4857fn is_swift_callable_entity_info(entity: &SemanticEntity) -> bool {
4858 entity.file_path.ends_with(".swift")
4859 && matches!(
4860 entity.entity_type.as_str(),
4861 "function" | "method" | "init" | "init_declaration"
4862 )
4863}
4864
4865fn swift_signature_parser() -> Option<tree_sitter::Parser> {
4866 let language = get_language_config(".swift").and_then(|config| (config.get_language)())?;
4867 let mut parser = tree_sitter::Parser::new();
4868 parser.set_language(&language).ok()?;
4869 Some(parser)
4870}
4871
4872fn extract_swift_signature_from_entity_content(
4873 entity: &SemanticEntity,
4874 parser: &mut tree_sitter::Parser,
4875) -> Option<Vec<Option<String>>> {
4876 if let Some(argument_labels) = parse_swift_signature_source(parser, &entity.content) {
4877 return Some(argument_labels);
4878 }
4879
4880 if matches!(entity.entity_type.as_str(), "init" | "init_declaration") {
4881 let wrapped = format!("struct __SemSignature {{\n{}\n}}\n", entity.content);
4882 parse_swift_signature_source(parser, &wrapped)
4883 } else {
4884 None
4885 }
4886}
4887
4888fn parse_swift_signature_source(
4889 parser: &mut tree_sitter::Parser,
4890 source_text: &str,
4891) -> Option<Vec<Option<String>>> {
4892 let tree = parser.parse(source_text.as_bytes(), None)?;
4893 let source = source_text.as_bytes();
4894 find_first_swift_callable_declaration(tree.root_node())
4895 .map(|node| extract_swift_declaration_argument_labels(node, source))
4896}
4897
4898fn find_first_swift_callable_declaration<'a>(
4899 root: tree_sitter::Node<'a>,
4900) -> Option<tree_sitter::Node<'a>> {
4901 let mut worklist = vec![root];
4902 while let Some(node) = worklist.pop() {
4903 if matches!(node.kind(), "function_declaration" | "init_declaration") {
4904 return Some(node);
4905 }
4906
4907 push_named_children_rev(&mut worklist, node);
4908 }
4909
4910 None
4911}
4912
4913fn extract_swift_declaration_argument_labels(
4914 node: tree_sitter::Node,
4915 source: &[u8],
4916) -> Vec<Option<String>> {
4917 let mut labels = Vec::new();
4918 let mut worklist = vec![node];
4919
4920 while let Some(current) = worklist.pop() {
4921 if current.kind() == "function_body" {
4922 continue;
4923 }
4924
4925 if current.kind() == "parameter" {
4926 labels.push(swift_parameter_argument_label(current, source));
4927 continue;
4928 }
4929
4930 push_named_children_rev(&mut worklist, current);
4931 }
4932
4933 labels
4934}
4935
4936fn swift_parameter_argument_label(parameter: tree_sitter::Node, source: &[u8]) -> Option<String> {
4937 parameter
4938 .child_by_field_name("external_name")
4939 .or_else(|| parameter.child_by_field_name("name"))
4940 .and_then(|label| normalize_swift_label(label.utf8_text(source).ok()?))
4941}
4942
4943fn extract_swift_call_argument_labels(
4944 call: tree_sitter::Node,
4945 source: &[u8],
4946) -> Option<Vec<Option<String>>> {
4947 let mut cursor = call.walk();
4948 let call_suffix = call
4949 .named_children(&mut cursor)
4950 .find(|child| child.kind() == "call_suffix")?;
4951
4952 let mut suffix_cursor = call_suffix.walk();
4953 let value_arguments = call_suffix
4954 .named_children(&mut suffix_cursor)
4955 .find(|child| child.kind() == "value_arguments")?;
4956
4957 let mut labels = Vec::new();
4958 let mut arg_cursor = value_arguments.walk();
4959 for argument in value_arguments
4960 .named_children(&mut arg_cursor)
4961 .filter(|child| child.kind() == "value_argument")
4962 {
4963 let label = argument
4964 .child_by_field_name("name")
4965 .and_then(|label| normalize_swift_label(label.utf8_text(source).ok()?));
4966 labels.push(label);
4967 }
4968
4969 Some(labels)
4970}
4971
4972fn normalize_swift_label(label: &str) -> Option<String> {
4973 let label = label.trim().trim_end_matches(':').trim();
4974 if label.is_empty() || label == "_" {
4975 None
4976 } else {
4977 Some(label.to_string())
4978 }
4979}
4980
4981fn select_member_candidate(
4982 members: &[(String, String)],
4983 method: &str,
4984 argument_labels: Option<&[Option<String>]>,
4985 swift_call_signatures: &HashMap<String, SwiftCallSignature>,
4986) -> SwiftOverloadSelection {
4987 let candidates: Vec<&String> = members
4988 .iter()
4989 .filter_map(|(name, id)| (name == method).then_some(id))
4990 .collect();
4991
4992 if argument_labels.is_none()
4993 && has_ambiguous_swift_signature_candidates(&candidates, swift_call_signatures)
4994 {
4995 return SwiftOverloadSelection::NoMatch;
4996 }
4997
4998 match select_swift_overload_candidate(&candidates, argument_labels, swift_call_signatures) {
4999 SwiftOverloadSelection::NotApplicable => candidates
5000 .first()
5001 .map(|id| SwiftOverloadSelection::Matched((*id).clone()))
5002 .unwrap_or(SwiftOverloadSelection::NotApplicable),
5003 selection => selection,
5004 }
5005}
5006
5007fn has_ambiguous_swift_signature_candidates(
5008 candidates: &[&String],
5009 swift_call_signatures: &HashMap<String, SwiftCallSignature>,
5010) -> bool {
5011 candidates
5012 .iter()
5013 .filter(|candidate| swift_call_signatures.contains_key(candidate.as_str()))
5014 .take(2)
5015 .count()
5016 > 1
5017}
5018
5019fn select_swift_overload_candidate(
5020 candidates: &[&String],
5021 argument_labels: Option<&[Option<String>]>,
5022 swift_call_signatures: &HashMap<String, SwiftCallSignature>,
5023) -> SwiftOverloadSelection {
5024 let Some(argument_labels) = argument_labels else {
5025 return SwiftOverloadSelection::NotApplicable;
5026 };
5027
5028 let signature_candidates: Vec<(&String, &SwiftCallSignature)> = candidates
5029 .iter()
5030 .copied()
5031 .filter_map(|candidate| {
5032 swift_call_signatures
5033 .get(candidate.as_str())
5034 .map(|signature| (candidate, signature))
5035 })
5036 .collect();
5037 if signature_candidates.is_empty() {
5038 return SwiftOverloadSelection::NotApplicable;
5039 }
5040
5041 let exact_matches: Vec<&String> = signature_candidates
5042 .iter()
5043 .filter_map(|(candidate, signature)| {
5044 (signature.argument_labels.as_slice() == argument_labels).then_some(*candidate)
5045 })
5046 .collect();
5047 if exact_matches.len() == 1 {
5048 return SwiftOverloadSelection::Matched(exact_matches[0].clone());
5049 }
5050 if exact_matches.len() > 1 {
5051 return SwiftOverloadSelection::NoMatch;
5052 }
5053
5054 if argument_labels.iter().all(Option::is_none) {
5055 let same_arity_matches: Vec<&String> = signature_candidates
5056 .iter()
5057 .filter_map(|(candidate, signature)| {
5058 (signature.argument_labels.len() == argument_labels.len()).then_some(*candidate)
5059 })
5060 .collect();
5061 if same_arity_matches.len() == 1 {
5062 return SwiftOverloadSelection::Matched(same_arity_matches[0].clone());
5063 }
5064 if same_arity_matches.len() > 1 {
5065 return SwiftOverloadSelection::NoMatch;
5066 }
5067 }
5068
5069 SwiftOverloadSelection::NoMatch
5070}
5071
5072fn collect_all_file_refs(
5075 root: tree_sitter::Node,
5076 source: &[u8],
5077 config: &ScopeResolveConfig,
5078) -> Vec<AstRef> {
5079 let mut refs = Vec::new();
5080 let mut worklist = vec![root];
5081 while let Some(node) = worklist.pop() {
5082 let node_row = node.start_position().row;
5083 let kind = node.kind();
5084
5085 if config.call_nodes.contains(&kind) {
5087 match &config.call_style {
5088 CallNodeStyle::FunctionField(field) => {
5089 if let Some(func) = node.child_by_field_name(field) {
5090 extract_call_ref(
5092 func, node, "", "", source, &mut refs, config, node_row, None,
5093 );
5094 }
5095 }
5096 CallNodeStyle::FirstChild => {
5097 if let Some(func) = node.named_child(0) {
5099 let argument_labels = extract_swift_call_argument_labels(node, source);
5100 extract_call_ref(
5101 func,
5102 node,
5103 "",
5104 "",
5105 source,
5106 &mut refs,
5107 config,
5108 node_row,
5109 argument_labels,
5110 );
5111 }
5112 }
5113 CallNodeStyle::DirectMethod {
5114 object_field,
5115 method_field,
5116 } => {
5117 let method_name = node
5118 .child_by_field_name(method_field)
5119 .and_then(|n| n.utf8_text(source).ok())
5120 .unwrap_or("");
5121 if !method_name.is_empty() && !is_builtin(method_name, config) {
5122 if let Some(obj_node) = node.child_by_field_name(object_field) {
5123 let receiver = obj_node.utf8_text(source).unwrap_or("").to_string();
5124 let receiver = receiver.trim_end_matches('.').to_string();
5125 refs.push(AstRef {
5126 kind: AstRefKind::MethodCall {
5127 receiver,
5128 method: method_name.to_string(),
5129 argument_labels: None,
5130 },
5131 row: node_row,
5132 start_byte: node.start_byte(),
5133 end_byte: node.end_byte(),
5134 });
5135 } else {
5136 refs.push(AstRef {
5137 kind: AstRefKind::Call {
5138 name: method_name.to_string(),
5139 argument_labels: None,
5140 },
5141 row: node_row,
5142 start_byte: node.start_byte(),
5143 end_byte: node.end_byte(),
5144 });
5145 }
5146 }
5147 }
5148 }
5149 push_named_children_rev(&mut worklist, node);
5150 continue;
5151 }
5152
5153 if kind == "macro_invocation" {
5155 if let Some(macro_node) = node.child_by_field_name("macro") {
5156 let macro_name = macro_node.utf8_text(source).unwrap_or("");
5157 if !macro_name.is_empty() && !is_builtin(macro_name, config) {
5158 refs.push(AstRef {
5159 kind: AstRefKind::Call {
5160 name: macro_name.to_string(),
5161 argument_labels: None,
5162 },
5163 row: macro_node.start_position().row,
5164 start_byte: macro_node.start_byte(),
5165 end_byte: macro_node.end_byte(),
5166 });
5167 }
5168 }
5169 push_named_children_rev(&mut worklist, node);
5170 continue;
5171 }
5172
5173 if config.new_expr_nodes.contains(&kind) {
5175 if let Some(type_node) = node.child_by_field_name(config.new_expr_type_field) {
5176 let name = type_node.utf8_text(source).unwrap_or("");
5177 let name = name.rsplit('.').next().unwrap_or(name);
5178 if !name.is_empty() && !is_builtin(name, config) {
5179 refs.push(AstRef {
5180 kind: AstRefKind::Call {
5181 name: name.to_string(),
5182 argument_labels: None,
5183 },
5184 row: type_node.start_position().row,
5185 start_byte: type_node.start_byte(),
5186 end_byte: type_node.end_byte(),
5187 });
5188 }
5189 }
5190 push_named_children_rev(&mut worklist, node);
5191 continue;
5192 }
5193
5194 if config.composite_literal_nodes.contains(&kind) {
5196 if let Some(type_node) = node.child_by_field_name("type") {
5197 let name = type_node.utf8_text(source).unwrap_or("");
5198 if name.chars().next().map_or(false, |c| c.is_uppercase())
5199 && !is_builtin(name, config)
5200 {
5201 refs.push(AstRef {
5202 kind: AstRefKind::Call {
5203 name: name.to_string(),
5204 argument_labels: None,
5205 },
5206 row: type_node.start_position().row,
5207 start_byte: type_node.start_byte(),
5208 end_byte: type_node.end_byte(),
5209 });
5210 }
5211 }
5212 }
5213
5214 push_named_children_rev(&mut worklist, node);
5216 }
5217 refs
5218}
5219
5220fn build_refs_by_row(refs: &[AstRef]) -> Vec<Vec<usize>> {
5221 let max_row = refs.iter().map(|r| r.row).max().unwrap_or(0);
5222 let mut refs_by_row = vec![Vec::new(); max_row + 1];
5223 for (idx, ast_ref) in refs.iter().enumerate() {
5224 refs_by_row[ast_ref.row].push(idx);
5225 }
5226 refs_by_row
5227}
5228
5229fn extract_call_ref(
5231 func: tree_sitter::Node,
5232 ref_node: tree_sitter::Node,
5233 _entity_id: &str,
5234 entity_name: &str,
5235 source: &[u8],
5236 refs: &mut Vec<AstRef>,
5237 config: &ScopeResolveConfig,
5238 row: usize,
5239 argument_labels: Option<Vec<Option<String>>>,
5240) {
5241 let func_kind = func.kind();
5242
5243 if func_kind == "identifier"
5244 || func_kind == "simple_identifier"
5245 || func_kind == "type_identifier"
5246 {
5247 let name = func.utf8_text(source).unwrap_or("");
5248 if !name.is_empty() && name != entity_name && !is_builtin(name, config) {
5249 refs.push(AstRef {
5250 kind: AstRefKind::Call {
5251 name: name.to_string(),
5252 argument_labels,
5253 },
5254 row,
5255 start_byte: ref_node.start_byte(),
5256 end_byte: ref_node.end_byte(),
5257 });
5258 }
5259 return;
5260 }
5261
5262 for ma in config.member_access {
5264 if func_kind == ma.node_kind {
5265 extract_member_call_ref(
5266 func,
5267 ref_node,
5268 ma.object_field,
5269 ma.property_field,
5270 source,
5271 refs,
5272 row,
5273 argument_labels,
5274 );
5275 return;
5276 }
5277 }
5278
5279 if config.scoped_call_nodes.contains(&func_kind) {
5281 let text = func.utf8_text(source).unwrap_or("");
5282 let parts: Vec<&str> = text.split("::").collect();
5283 if parts.len() >= 2 {
5284 let is_path_prefix = parts[0] == "super" || parts[0] == "self" || parts[0] == "crate";
5287 let method_name = parts[parts.len() - 1];
5288 if is_path_prefix && parts.len() == 2 {
5289 if !method_name.is_empty() && !is_builtin(method_name, config) {
5290 refs.push(AstRef {
5291 kind: AstRefKind::Call {
5292 name: method_name.to_string(),
5293 argument_labels: None,
5294 },
5295 row,
5296 start_byte: ref_node.start_byte(),
5297 end_byte: ref_node.end_byte(),
5298 });
5299 }
5300 } else {
5301 let receiver = parts[..parts.len() - 1].join("::");
5302 let receiver_base = parts[parts.len() - 2];
5303 if !receiver.is_empty() && !method_name.is_empty() {
5304 if parts.len() == 2
5305 && receiver_base
5306 .chars()
5307 .next()
5308 .map_or(false, |c| c.is_uppercase())
5309 && !is_builtin(receiver_base, config)
5310 {
5311 refs.push(AstRef {
5312 kind: AstRefKind::MethodCall {
5313 receiver: receiver_base.to_string(),
5314 method: method_name.to_string(),
5315 argument_labels: None,
5316 },
5317 row,
5318 start_byte: ref_node.start_byte(),
5319 end_byte: ref_node.end_byte(),
5320 });
5321 } else if !is_builtin(method_name, config) {
5322 refs.push(AstRef {
5323 kind: AstRefKind::ScopedCall {
5324 path: receiver,
5325 name: method_name.to_string(),
5326 },
5327 row,
5328 start_byte: ref_node.start_byte(),
5329 end_byte: ref_node.end_byte(),
5330 });
5331 }
5332 }
5333 }
5334 }
5335 }
5336}
5337
5338fn extract_member_call_ref(
5342 node: tree_sitter::Node,
5343 ref_node: tree_sitter::Node,
5344 object_field: &str,
5345 attr_field: &str,
5346 source: &[u8],
5347 refs: &mut Vec<AstRef>,
5348 row: usize,
5349 argument_labels: Option<Vec<Option<String>>>,
5350) {
5351 let obj_text = node
5352 .child_by_field_name(object_field)
5353 .and_then(|n| n.utf8_text(source).ok())
5354 .unwrap_or("");
5355
5356 let attr_text = node
5357 .child_by_field_name(attr_field)
5358 .and_then(|n| {
5359 let text = n.utf8_text(source).ok()?;
5360 Some(text.trim_start_matches('.'))
5362 })
5363 .unwrap_or("");
5364
5365 if !obj_text.is_empty() && !attr_text.is_empty() {
5366 push_method_call_ref(obj_text, attr_text, refs, ref_node, row, argument_labels);
5367 return;
5368 }
5369
5370 let child_count = node.named_child_count();
5372 if child_count >= 2 {
5373 let obj = node
5374 .named_child(0)
5375 .and_then(|n| n.utf8_text(source).ok())
5376 .unwrap_or("");
5377 let last_idx = (child_count - 1) as u32;
5378 let attr = node
5379 .named_child(last_idx)
5380 .and_then(|n| n.utf8_text(source).ok())
5381 .unwrap_or("");
5382 if !obj.is_empty() && !attr.is_empty() {
5383 push_method_call_ref(obj, attr, refs, ref_node, row, argument_labels);
5384 }
5385 }
5386}
5387
5388fn push_method_call_ref(
5389 obj: &str,
5390 method: &str,
5391 refs: &mut Vec<AstRef>,
5392 node: tree_sitter::Node,
5393 row: usize,
5394 argument_labels: Option<Vec<Option<String>>>,
5395) {
5396 refs.push(AstRef {
5397 kind: AstRefKind::MethodCall {
5398 receiver: obj.to_string(),
5399 method: method.to_string(),
5400 argument_labels,
5401 },
5402 row,
5403 start_byte: node.start_byte(),
5404 end_byte: node.end_byte(),
5405 });
5406}
5407
5408fn resolve_ref(
5410 ast_ref: &AstRef,
5411 scope_idx: usize,
5412 scopes: &[Scope],
5413 symbol_table: &HashMap<String, Vec<String>>,
5414 class_members: &HashMap<String, Vec<(String, String)>>,
5415 owner_members: &HashMap<String, Vec<(String, String)>>,
5416 import_table_by_name: &HashMap<&str, &str>,
5417 instance_attr_types: &HashMap<(String, String), String>,
5418 entity_map: &HashMap<String, EntityInfo>,
5419 swift_call_signatures: &HashMap<String, SwiftCallSignature>,
5420 file_path: &str,
5421 from_entity_id: &str,
5422 allow_cross_file_calls: bool,
5423 allow_implicit_instance_member_receiver: bool,
5424 file_lookup: &FileEntityLookup<'_>,
5425 lookup_cache: &mut ScopeLookupCache,
5426) -> Option<(String, RefType, &'static str)> {
5427 match &ast_ref.kind {
5428 AstRefKind::Call {
5429 name,
5430 argument_labels,
5431 } => {
5432 if is_local_binding_in_scopes_cached(scope_idx, scopes, name, lookup_cache) {
5433 return None;
5434 }
5435
5436 if !swift_call_signatures.is_empty() {
5443 if argument_labels.is_some() {
5444 if let Some(target_ids) = symbol_table.get(name.as_str()) {
5445 let same_file_targets: Vec<&String> = target_ids
5446 .iter()
5447 .filter(|id| {
5448 entity_map
5449 .get(*id)
5450 .map_or(false, |e| e.file_path == file_path)
5451 })
5452 .collect();
5453 let visible_targets: Vec<&String> = if !same_file_targets.is_empty() {
5454 same_file_targets
5455 } else if allow_cross_file_calls {
5456 target_ids.iter().collect()
5457 } else {
5458 Vec::new()
5459 };
5460 match select_swift_overload_candidate(
5461 &visible_targets,
5462 argument_labels.as_deref(),
5463 swift_call_signatures,
5464 ) {
5465 SwiftOverloadSelection::Matched(target_id) => {
5466 let is_constructor =
5467 name.chars().next().map_or(false, |c| c.is_uppercase());
5468 let ref_type = if is_constructor {
5469 RefType::TypeRef
5470 } else {
5471 RefType::Calls
5472 };
5473 return Some((target_id, ref_type, "scope_chain"));
5474 }
5475 SwiftOverloadSelection::NoMatch => return None,
5476 SwiftOverloadSelection::NotApplicable => {}
5477 }
5478 }
5479 } else if let Some(target_ids) = symbol_table.get(name.as_str()) {
5480 let same_file_targets: Vec<&String> = target_ids
5481 .iter()
5482 .filter(|id| {
5483 entity_map
5484 .get(*id)
5485 .map_or(false, |e| e.file_path == file_path)
5486 })
5487 .collect();
5488 let visible_targets: Vec<&String> = if !same_file_targets.is_empty() {
5489 same_file_targets
5490 } else if allow_cross_file_calls {
5491 target_ids.iter().collect()
5492 } else {
5493 Vec::new()
5494 };
5495 if has_ambiguous_swift_signature_candidates(
5496 &visible_targets,
5497 swift_call_signatures,
5498 ) {
5499 return None;
5500 }
5501 }
5502 }
5503
5504 if let Some(eid) = lookup_scope_chain_cached(scope_idx, scopes, name, lookup_cache) {
5506 if eid != from_entity_id {
5507 return Some((eid, RefType::Calls, "scope_chain"));
5508 }
5509 }
5510
5511 if let Some(target_id) = import_table_by_name.get(name.as_str()) {
5515 return Some(((*target_id).to_string(), RefType::Calls, "import"));
5516 }
5517
5518 if let Some(target_ids) = symbol_table.get(name.as_str()) {
5520 let is_constructor = name.chars().next().map_or(false, |c| c.is_uppercase());
5521 let ref_type = if is_constructor {
5522 RefType::TypeRef
5523 } else {
5524 RefType::Calls
5525 };
5526
5527 if swift_call_signatures.is_empty() {
5528 let target = file_lookup
5534 .first_id_by_name(name)
5535 .map(str::to_string)
5536 .or_else(|| {
5537 if is_constructor || allow_cross_file_calls {
5538 target_ids.first().cloned()
5539 } else {
5540 None
5541 }
5542 });
5543 if let Some(tid) = target {
5544 return Some((tid, ref_type, "scope_chain"));
5545 }
5546 return None;
5547 }
5548
5549 let same_file_targets: Vec<&String> = target_ids
5550 .iter()
5551 .filter(|id| {
5552 entity_map
5553 .get(*id)
5554 .map_or(false, |e| e.file_path == file_path)
5555 })
5556 .collect();
5557 let visible_targets: Vec<&String> = if !same_file_targets.is_empty() {
5558 same_file_targets
5559 } else if is_constructor || allow_cross_file_calls {
5560 target_ids.iter().collect()
5561 } else {
5562 Vec::new()
5563 };
5564 let target = match select_swift_overload_candidate(
5565 &visible_targets,
5566 argument_labels.as_deref(),
5567 swift_call_signatures,
5568 ) {
5569 SwiftOverloadSelection::Matched(target_id) => Some(target_id),
5570 SwiftOverloadSelection::NoMatch => return None,
5571 SwiftOverloadSelection::NotApplicable => {
5572 visible_targets.first().map(|id| (*id).clone())
5573 }
5574 };
5575 if let Some(tid) = target {
5576 return Some((tid, ref_type, "scope_chain"));
5577 }
5578 }
5579
5580 None
5581 }
5582
5583 AstRefKind::ScopedCall { .. } => None,
5584
5585 AstRefKind::MethodCall {
5586 receiver: raw_receiver,
5587 method,
5588 argument_labels,
5589 } => {
5590 let receiver = raw_receiver.trim_start_matches('!').trim_start_matches('~');
5592 if receiver == "self" || receiver == "this" {
5593 let mut idx = scope_idx;
5595 loop {
5596 if scopes[idx].kind == "class" {
5597 if let Some(class_name) = scopes[idx]
5598 .owner_id
5599 .as_ref()
5600 .and_then(|owner_id| entity_map.get(owner_id))
5601 .map(|owner| owner.name.as_str())
5602 {
5603 if let Some(members) = class_members.get(class_name) {
5604 match select_member_candidate(
5605 members,
5606 method,
5607 argument_labels.as_deref(),
5608 swift_call_signatures,
5609 ) {
5610 SwiftOverloadSelection::Matched(eid) => {
5611 return Some((eid, RefType::Calls, "scope_chain"));
5612 }
5613 SwiftOverloadSelection::NoMatch => return None,
5614 SwiftOverloadSelection::NotApplicable => {
5615 if argument_labels.is_some() {
5616 return None;
5617 }
5618 }
5619 }
5620 }
5621 }
5622 if let Some(eid) = scopes[idx].defs.get(method.as_str()) {
5623 return Some((eid.clone(), RefType::Calls, "scope_chain"));
5624 }
5625 break;
5626 }
5627 match scopes[idx].parent {
5628 Some(p) => idx = p,
5629 None => break,
5630 }
5631 }
5632 return None;
5633 }
5634
5635 if receiver.starts_with("self.") || receiver.starts_with("this.") {
5638 let attr_name = &receiver[5..]; let class_name =
5641 find_enclosing_class_cached(scope_idx, scopes, entity_map, lookup_cache);
5642 if let Some(cn) = class_name {
5643 if let Some(attr_type) = instance_attr_types.get(&(cn, attr_name.to_string())) {
5645 if let Some(members) = class_members.get(attr_type.as_str()) {
5646 match select_member_candidate(
5647 members,
5648 method,
5649 argument_labels.as_deref(),
5650 swift_call_signatures,
5651 ) {
5652 SwiftOverloadSelection::Matched(mid) => {
5653 return Some((mid, RefType::Calls, "type_tracking"));
5654 }
5655 SwiftOverloadSelection::NoMatch => return None,
5656 SwiftOverloadSelection::NotApplicable => {}
5657 }
5658 }
5659 }
5660 }
5661 }
5662
5663 if receiver.contains('.')
5665 && !receiver.starts_with("self.")
5666 && !receiver.starts_with("this.")
5667 {
5668 if let Some(dot_pos) = receiver.find('.') {
5669 let var_part = &receiver[..dot_pos];
5670 let field_part = &receiver[dot_pos + 1..];
5671 if let Some(var_type) =
5672 lookup_type_in_scopes_cached(scope_idx, scopes, var_part, lookup_cache)
5673 {
5674 if let Some(attr_type) =
5675 instance_attr_types.get(&(var_type, field_part.to_string()))
5676 {
5677 if let Some(members) = class_members.get(attr_type.as_str()) {
5678 match select_member_candidate(
5679 members,
5680 method,
5681 argument_labels.as_deref(),
5682 swift_call_signatures,
5683 ) {
5684 SwiftOverloadSelection::Matched(mid) => {
5685 return Some((mid, RefType::Calls, "type_tracking"));
5686 }
5687 SwiftOverloadSelection::NoMatch => return None,
5688 SwiftOverloadSelection::NotApplicable => {}
5689 }
5690 }
5691 }
5692 }
5693 }
5694 }
5695
5696 let receiver_type = if let Some(receiver_type) =
5698 lookup_type_before_class_scope(scope_idx, scopes, receiver)
5699 {
5700 Some(receiver_type)
5701 } else if allow_implicit_instance_member_receiver
5702 && is_simple_identifier_name(receiver)
5703 && !is_local_binding_in_scopes_cached(scope_idx, scopes, receiver, lookup_cache)
5704 {
5705 match find_enclosing_class_cached(scope_idx, scopes, entity_map, lookup_cache) {
5706 Some(class_name) => instance_attr_types
5707 .get(&(class_name, receiver.to_string()))
5708 .cloned(),
5709 None => None,
5710 }
5711 } else {
5712 None
5713 };
5714
5715 if let Some(class_name) = receiver_type {
5716 if let Some(members) = class_members.get(class_name.as_str()) {
5717 match select_member_candidate(
5718 members,
5719 method,
5720 argument_labels.as_deref(),
5721 swift_call_signatures,
5722 ) {
5723 SwiftOverloadSelection::Matched(mid) => {
5724 return Some((mid, RefType::Calls, "type_tracking"));
5725 }
5726 SwiftOverloadSelection::NoMatch => return None,
5727 SwiftOverloadSelection::NotApplicable => {}
5728 }
5729 }
5730 }
5731
5732 let from_entity_is_container_type =
5735 entity_map.get(from_entity_id).map_or(false, |entity| {
5736 matches!(
5737 entity.entity_type.as_str(),
5738 "class"
5739 | "struct"
5740 | "interface"
5741 | "enum"
5742 | "protocol_declaration"
5743 | "object_declaration"
5744 | "companion_object"
5745 )
5746 });
5747
5748 if allow_implicit_instance_member_receiver
5749 && !from_entity_is_container_type
5750 && !is_local_binding_in_scopes_cached(scope_idx, scopes, receiver, lookup_cache)
5751 {
5752 if let Some(class_name) =
5753 find_enclosing_class_cached(scope_idx, scopes, entity_map, lookup_cache)
5754 {
5755 if let Some(attr_type) =
5756 instance_attr_types.get(&(class_name, receiver.to_string()))
5757 {
5758 if let Some(members) = class_members.get(attr_type.as_str()) {
5759 match select_member_candidate(
5760 members,
5761 method,
5762 argument_labels.as_deref(),
5763 swift_call_signatures,
5764 ) {
5765 SwiftOverloadSelection::Matched(mid) => {
5766 return Some((mid, RefType::Calls, "type_tracking"));
5767 }
5768 SwiftOverloadSelection::NoMatch => return None,
5769 SwiftOverloadSelection::NotApplicable => {}
5770 }
5771 }
5772 }
5773 }
5774 }
5775
5776 if !is_local_binding_in_scopes_cached(scope_idx, scopes, receiver, lookup_cache) {
5779 if let Some(class_id) =
5780 lookup_scope_chain_cached(scope_idx, scopes, receiver, lookup_cache)
5781 {
5782 if let Some(info) = entity_map.get(&class_id) {
5783 if matches!(info.entity_type.as_str(), "module" | "variable" | "object")
5784 && info.name == receiver
5785 {
5786 if let Some(mid) =
5787 lookup_entity_member(owner_members, &class_id, method).or_else(
5788 || lookup_owned_scope_member(scopes, &class_id, method),
5789 )
5790 {
5791 return Some((mid, RefType::Calls, "scope_chain"));
5792 }
5793 } else if matches!(
5794 info.entity_type.as_str(),
5795 "class" | "struct" | "interface"
5796 ) && info.name == receiver
5797 {
5798 if let Some(members) = class_members.get(&info.name) {
5799 match select_member_candidate(
5800 members,
5801 method,
5802 argument_labels.as_deref(),
5803 swift_call_signatures,
5804 ) {
5805 SwiftOverloadSelection::Matched(mid) => {
5806 return Some((mid, RefType::Calls, "scope_chain"));
5807 }
5808 SwiftOverloadSelection::NoMatch => return None,
5809 SwiftOverloadSelection::NotApplicable => {}
5810 }
5811 }
5812 }
5813 }
5814 }
5815 }
5816
5817 if !is_local_binding_in_scopes_cached(scope_idx, scopes, receiver, lookup_cache) {
5819 if let Some(target_id) = import_table_by_name.get(receiver) {
5820 if let Some(info) = entity_map.get(*target_id) {
5821 if matches!(info.entity_type.as_str(), "class" | "struct") {
5822 if let Some(members) = class_members.get(&info.name) {
5823 match select_member_candidate(
5824 members,
5825 method,
5826 argument_labels.as_deref(),
5827 swift_call_signatures,
5828 ) {
5829 SwiftOverloadSelection::Matched(mid) => {
5830 return Some((mid, RefType::Calls, "type_tracking"));
5831 }
5832 SwiftOverloadSelection::NoMatch => return None,
5833 SwiftOverloadSelection::NotApplicable => {}
5834 }
5835 }
5836 }
5837 }
5838 }
5839
5840 let namespaced = format!("{receiver}.{method}");
5842 if let Some(target_id) = import_table_by_name.get(namespaced.as_str()) {
5843 return Some(((*target_id).to_string(), RefType::Calls, "import"));
5844 }
5845 }
5846
5847 if file_path.ends_with(".go") {
5850 if let Some(target_id) = import_table_by_name.get(method.as_str()) {
5851 return Some(((*target_id).to_string(), RefType::Calls, "import"));
5852 }
5853 }
5854
5855 None
5856 }
5857 }
5858}
5859
5860fn allows_implicit_instance_member_receiver(
5861 file_path: &str,
5862 entity_type: &str,
5863 entity_content: &str,
5864) -> bool {
5865 let ext = file_path.rsplit('.').next().unwrap_or("");
5866 let supports_implicit_receiver = matches!(
5867 ext,
5868 "swift"
5869 | "kt"
5870 | "kts"
5871 | "java"
5872 | "cs"
5873 | "cpp"
5874 | "cc"
5875 | "cxx"
5876 | "hpp"
5877 | "hh"
5878 | "hxx"
5879 | "h"
5880 | "scala"
5881 | "dart"
5882 );
5883
5884 supports_implicit_receiver
5885 && matches!(
5886 entity_type,
5887 "function" | "method" | "init" | "init_declaration" | "constructor_declaration"
5888 )
5889 && !has_static_member_modifier(ext, entity_content)
5890}
5891
5892fn has_static_member_modifier(ext: &str, entity_content: &str) -> bool {
5893 let header = entity_content
5894 .split(|ch| ch == '{' || ch == '=')
5895 .next()
5896 .unwrap_or(entity_content);
5897 let header_without_comments = strip_member_header_comments(header);
5898 let tokens = header_without_comments
5899 .split(|ch: char| !ch.is_alphanumeric() && ch != '_')
5900 .filter(|token| !token.is_empty())
5901 .collect::<Vec<_>>();
5902 let declaration_start = tokens
5903 .iter()
5904 .position(|token| {
5905 matches!(
5906 *token,
5907 "func" | "function" | "fn" | "constructor" | "init" | "var" | "let" | "subscript"
5908 )
5909 })
5910 .unwrap_or(tokens.len());
5911
5912 tokens[..declaration_start]
5913 .iter()
5914 .any(|token| *token == "static" || (ext == "swift" && *token == "class"))
5915}
5916
5917fn strip_member_header_comments(header: &str) -> String {
5918 let mut output = String::with_capacity(header.len());
5919 let mut chars = header.chars().peekable();
5920
5921 while let Some(ch) = chars.next() {
5922 if ch != '/' {
5923 output.push(ch);
5924 continue;
5925 }
5926
5927 match chars.peek().copied() {
5928 Some('/') => {
5929 chars.next();
5930 for next in chars.by_ref() {
5931 if next == '\n' {
5932 output.push(' ');
5933 break;
5934 }
5935 }
5936 }
5937 Some('*') => {
5938 chars.next();
5939 let mut previous = '\0';
5940 for next in chars.by_ref() {
5941 if previous == '*' && next == '/' {
5942 break;
5943 }
5944 previous = next;
5945 }
5946 output.push(' ');
5947 }
5948 _ => output.push(ch),
5949 }
5950 }
5951
5952 output
5953}
5954
5955fn is_simple_identifier_name(name: &str) -> bool {
5956 let mut chars = name.chars();
5957 let Some(first) = chars.next() else {
5958 return false;
5959 };
5960
5961 (first == '_' || first.is_alphabetic()) && chars.all(|ch| ch == '_' || ch.is_alphanumeric())
5962}
5963
5964fn lookup_owned_scope_member(scopes: &[Scope], owner_id: &str, member: &str) -> Option<String> {
5965 scopes
5966 .iter()
5967 .find(|scope| scope.owner_id.as_deref() == Some(owner_id))
5968 .and_then(|scope| scope.defs.get(member).cloned())
5969}
5970
5971fn lookup_entity_member(
5972 owner_members: &HashMap<String, Vec<(String, String)>>,
5973 owner_id: &str,
5974 member: &str,
5975) -> Option<String> {
5976 owner_members
5977 .get(owner_id)
5978 .and_then(|members| members.iter().find(|(name, _)| name == member))
5979 .map(|(_, id)| id.clone())
5980}
5981
5982fn find_enclosing_class(
5984 start_scope: usize,
5985 scopes: &[Scope],
5986 entity_map: &HashMap<String, EntityInfo>,
5987) -> Option<String> {
5988 let mut idx = start_scope;
5989 loop {
5990 if scopes[idx].kind == "class" {
5991 if let Some(ref oid) = scopes[idx].owner_id {
5992 return entity_map.get(oid).map(|e| e.name.clone());
5993 }
5994 }
5995 match scopes[idx].parent {
5996 Some(p) => idx = p,
5997 None => return None,
5998 }
5999 }
6000}
6001
6002fn find_enclosing_class_cached(
6003 start_scope: usize,
6004 scopes: &[Scope],
6005 entity_map: &HashMap<String, EntityInfo>,
6006 cache: &mut ScopeLookupCache,
6007) -> Option<String> {
6008 if let Some(cached) = cache.enclosing_classes.get(&start_scope) {
6009 return cached.clone();
6010 }
6011 let value = find_enclosing_class(start_scope, scopes, entity_map);
6012 cache.enclosing_classes.insert(start_scope, value.clone());
6013 value
6014}
6015
6016fn lookup_scope_chain(start_scope: usize, scopes: &[Scope], name: &str) -> Option<String> {
6018 let mut idx = start_scope;
6019 loop {
6020 if let Some(eid) = scopes[idx].defs.get(name) {
6021 return Some(eid.clone());
6022 }
6023 match scopes[idx].parent {
6024 Some(p) => idx = p,
6025 None => return None,
6026 }
6027 }
6028}
6029
6030fn lookup_scope_chain_cached(
6031 start_scope: usize,
6032 scopes: &[Scope],
6033 name: &str,
6034 cache: &mut ScopeLookupCache,
6035) -> Option<String> {
6036 if let Some(cached) = cache
6037 .defs
6038 .get(&start_scope)
6039 .and_then(|scope_cache| scope_cache.get(name))
6040 {
6041 return cached.clone();
6042 }
6043 let value = lookup_scope_chain(start_scope, scopes, name);
6044 cache
6045 .defs
6046 .entry(start_scope)
6047 .or_default()
6048 .insert(name.to_string(), value.clone());
6049 value
6050}
6051
6052fn is_local_binding_in_scopes(start_scope: usize, scopes: &[Scope], name: &str) -> bool {
6054 let mut idx = start_scope;
6055 loop {
6056 if scopes[idx].bindings.contains(name) {
6057 return true;
6058 }
6059 match scopes[idx].parent {
6060 Some(p) => idx = p,
6061 None => return false,
6062 }
6063 }
6064}
6065
6066fn is_local_binding_in_scopes_cached(
6067 start_scope: usize,
6068 scopes: &[Scope],
6069 name: &str,
6070 cache: &mut ScopeLookupCache,
6071) -> bool {
6072 if let Some(cached) = cache
6073 .local_bindings
6074 .get(&start_scope)
6075 .and_then(|scope_cache| scope_cache.get(name))
6076 {
6077 return *cached;
6078 }
6079 let value = is_local_binding_in_scopes(start_scope, scopes, name);
6080 cache
6081 .local_bindings
6082 .entry(start_scope)
6083 .or_default()
6084 .insert(name.to_string(), value);
6085 value
6086}
6087
6088fn lookup_type_in_scopes(start_scope: usize, scopes: &[Scope], var_name: &str) -> Option<String> {
6090 let mut idx = start_scope;
6091 loop {
6092 if let Some(type_name) = scopes[idx].types.get(var_name) {
6093 return Some(type_name.clone());
6094 }
6095 match scopes[idx].parent {
6096 Some(p) => idx = p,
6097 None => return None,
6098 }
6099 }
6100}
6101
6102fn lookup_type_before_class_scope(
6103 start_scope: usize,
6104 scopes: &[Scope],
6105 var_name: &str,
6106) -> Option<String> {
6107 let mut idx = start_scope;
6108 loop {
6109 if scopes[idx].kind == "class" {
6110 return None;
6111 }
6112 if let Some(type_name) = scopes[idx].types.get(var_name) {
6113 return Some(type_name.clone());
6114 }
6115 match scopes[idx].parent {
6116 Some(p) => idx = p,
6117 None => return None,
6118 }
6119 }
6120}
6121
6122fn lookup_type_in_scopes_cached(
6123 start_scope: usize,
6124 scopes: &[Scope],
6125 var_name: &str,
6126 cache: &mut ScopeLookupCache,
6127) -> Option<String> {
6128 if let Some(cached) = cache
6129 .types
6130 .get(&start_scope)
6131 .and_then(|scope_cache| scope_cache.get(var_name))
6132 {
6133 return cached.clone();
6134 }
6135 let value = lookup_type_in_scopes(start_scope, scopes, var_name);
6136 cache
6137 .types
6138 .entry(start_scope)
6139 .or_default()
6140 .insert(var_name.to_string(), value.clone());
6141 value
6142}
6143
6144fn is_builtin(name: &str, config: &ScopeResolveConfig) -> bool {
6145 if matches!(
6147 name,
6148 "None" | "True" | "False" | "null" | "undefined" | "nil"
6149 ) {
6150 return true;
6151 }
6152 config.builtins.contains(&name)
6153}
6154
6155#[cfg(test)]
6156mod tests {
6157 use super::*;
6158
6159 #[test]
6160 fn return_type_name_lookup_uses_symbol_table_order() {
6161 let mut return_type_map = HashMap::new();
6162 return_type_map.insert(
6163 "z_backup.py::function::make_conn".to_string(),
6164 "Backup".to_string(),
6165 );
6166 return_type_map.insert(
6167 "a_primary.py::function::make_conn".to_string(),
6168 "Primary".to_string(),
6169 );
6170
6171 let mut symbol_table = HashMap::new();
6172 symbol_table.insert(
6173 "make_conn".to_string(),
6174 vec![
6175 "a_primary.py::function::make_conn".to_string(),
6176 "z_backup.py::function::make_conn".to_string(),
6177 ],
6178 );
6179
6180 let by_name = deterministic_return_types_by_name(&return_type_map, &symbol_table);
6181
6182 assert_eq!(
6183 by_name.get("make_conn").map(String::as_str),
6184 Some("Primary")
6185 );
6186 }
6187
6188 #[test]
6189 fn go_package_index_entries_are_sorted() {
6190 let first_id = "pkg/foo/a.go::function::zeta".to_string();
6191 let second_id = "pkg/foo/b.go::function::alpha".to_string();
6192
6193 let mut symbol_table = HashMap::new();
6194 symbol_table.insert("zeta".to_string(), vec![first_id.clone()]);
6195 symbol_table.insert("alpha".to_string(), vec![second_id.clone()]);
6196
6197 let mut entity_map = HashMap::new();
6198 entity_map.insert(
6199 first_id.clone(),
6200 EntityInfo {
6201 id: first_id.clone(),
6202 name: "zeta".to_string(),
6203 entity_type: "function".to_string(),
6204 file_path: "pkg/foo/a.go".to_string(),
6205 parent_id: None,
6206 start_line: 1,
6207 end_line: 3,
6208 },
6209 );
6210 entity_map.insert(
6211 second_id.clone(),
6212 EntityInfo {
6213 id: second_id.clone(),
6214 name: "alpha".to_string(),
6215 entity_type: "function".to_string(),
6216 file_path: "pkg/foo/b.go".to_string(),
6217 parent_id: None,
6218 start_line: 1,
6219 end_line: 3,
6220 },
6221 );
6222
6223 let index = build_go_pkg_index(&symbol_table, &entity_map);
6224
6225 assert_eq!(
6226 index.get("foo"),
6227 Some(&vec![
6228 ("alpha".to_string(), second_id),
6229 ("zeta".to_string(), first_id),
6230 ])
6231 );
6232 }
6233}