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