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