1use std::ops::Range;
5
6use objects::object::{
7 ByteSpan, ImportBinding, ImportEntry, ImportKindTag, OccurrenceEntry, OccurrenceRole,
8 ScopeEntry, ScopeKind, SymbolNamespace,
9};
10use tree_sitter::Node;
11
12use super::{
13 parser_language::Language,
14 parser_types::{FunctionDef, Import, ImportKind},
15};
16
17#[derive(Debug)]
19pub struct SyntaxIndex {
20 functions: Vec<IndexedFunction>,
21 imports: Vec<IndexedImport>,
22 semantic_scopes: Vec<ScopeEntry>,
23 semantic_imports: Vec<ImportEntry>,
24 occurrences: Vec<OccurrenceEntry>,
25 line_offsets: Vec<usize>,
26}
27
28#[derive(Clone, Copy, Debug)]
30pub struct FunctionRef<'a> {
31 inner: &'a IndexedFunction,
32 source: &'a str,
33}
34
35#[derive(Clone, Copy, Debug)]
37pub struct ImportRef<'a> {
38 inner: &'a IndexedImport,
39 source: &'a str,
40}
41
42#[derive(Debug)]
43struct IndexedFunction {
44 name: String,
45 container: String,
46 signature: String,
47 start_line: usize,
48 end_line: usize,
49 content: Range<usize>,
50}
51
52#[derive(Debug)]
53struct IndexedImport {
54 raw: Range<usize>,
55 kind: ImportKind,
56}
57
58impl SyntaxIndex {
59 pub(super) fn build(language: Language, source: &str, root: Node<'_>) -> Self {
60 let mut index = Self {
61 functions: Vec::new(),
62 imports: Vec::new(),
63 semantic_scopes: Vec::new(),
64 semantic_imports: Vec::new(),
65 occurrences: Vec::new(),
66 line_offsets: line_offsets(source),
67 };
68
69 let mut stack = vec![root];
70 while let Some(node) = stack.pop() {
71 if is_function_node(&node, language)
72 && let Some(name) = function_name(&node, source)
73 {
74 index.functions.push(IndexedFunction {
75 name: name.to_string(),
76 container: function_container(&node, source),
77 signature: function_signature(&node, source),
78 start_line: node.start_position().row,
79 end_line: node.end_position().row,
80 content: node.byte_range(),
81 });
82 }
83
84 push_children_reverse(node, &mut stack);
85 }
86
87 let mut cursor = root.walk();
88 for child in root.children(&mut cursor) {
89 match language {
90 Language::Rust => match child.kind() {
91 "use_declaration" => index.imports.push(IndexedImport {
92 raw: child.byte_range(),
93 kind: ImportKind::Use,
94 }),
95 "extern_crate_declaration" => index.imports.push(IndexedImport {
96 raw: child.byte_range(),
97 kind: ImportKind::ExternCrate,
98 }),
99 _ => {}
100 },
101 Language::Python => {
102 if matches!(child.kind(), "import_statement" | "import_from_statement") {
103 index.imports.push(IndexedImport {
104 raw: child.byte_range(),
105 kind: ImportKind::Import,
106 });
107 }
108 }
109 Language::JavaScript | Language::TypeScript => {
110 if child.kind() == "import_statement" {
111 index.imports.push(IndexedImport {
112 raw: child.byte_range(),
113 kind: ImportKind::Import,
114 });
115 }
116 }
117 Language::Go | Language::Java => {
118 if child.kind() == "import_declaration" {
119 index.imports.push(IndexedImport {
120 raw: child.byte_range(),
121 kind: ImportKind::Import,
122 });
123 }
124 }
125 Language::C | Language::Cpp | Language::Zig | Language::Unknown => {}
129 }
130 }
131
132 let (semantic_scopes, semantic_imports, occurrences) =
133 build_source_facts(language, source, root);
134 index.semantic_scopes = semantic_scopes;
135 index.semantic_imports = semantic_imports;
136 index.occurrences = occurrences;
137
138 index
139 }
140
141 pub fn functions<'a>(&'a self, source: &'a str) -> impl Iterator<Item = FunctionRef<'a>> + 'a {
142 self.functions
143 .iter()
144 .map(move |inner| FunctionRef { inner, source })
145 }
146
147 pub fn imports<'a>(&'a self, source: &'a str) -> impl Iterator<Item = ImportRef<'a>> + 'a {
148 self.imports
149 .iter()
150 .map(move |inner| ImportRef { inner, source })
151 }
152
153 pub fn line_offsets(&self) -> &[usize] {
155 &self.line_offsets
156 }
157
158 pub(crate) fn semantic_scopes(&self) -> &[ScopeEntry] {
159 &self.semantic_scopes
160 }
161
162 pub(crate) fn semantic_imports(&self) -> &[ImportEntry] {
163 &self.semantic_imports
164 }
165
166 pub(crate) fn occurrences(&self) -> &[OccurrenceEntry] {
167 &self.occurrences
168 }
169}
170
171impl FunctionRef<'_> {
172 pub fn name(&self) -> &str {
173 &self.inner.name
174 }
175
176 pub fn container(&self) -> &str {
177 &self.inner.container
178 }
179
180 pub fn signature(&self) -> &str {
181 &self.inner.signature
182 }
183
184 pub fn start_line(&self) -> usize {
185 self.inner.start_line
186 }
187
188 pub fn end_line(&self) -> usize {
189 self.inner.end_line
190 }
191
192 pub fn content(&self) -> &str {
193 &self.source[self.inner.content.clone()]
194 }
195
196 pub fn to_owned(self) -> FunctionDef {
197 FunctionDef {
198 name: self.name().to_string(),
199 container: self.container().to_string(),
200 signature: self.signature().to_string(),
201 start_line: self.start_line(),
202 end_line: self.end_line(),
203 content: self.content().to_string(),
204 }
205 }
206}
207
208impl ImportRef<'_> {
209 pub fn raw(&self) -> &str {
210 &self.source[self.inner.raw.clone()]
211 }
212
213 pub fn kind(&self) -> ImportKind {
214 self.inner.kind
215 }
216
217 pub fn to_owned(self) -> Import {
218 Import {
219 raw: self.raw().to_string(),
220 kind: self.kind(),
221 }
222 }
223}
224
225fn build_source_facts(
226 language: Language,
227 source: &str,
228 root: Node<'_>,
229) -> (Vec<ScopeEntry>, Vec<ImportEntry>, Vec<OccurrenceEntry>) {
230 let mut scopes = vec![ScopeEntry {
231 local_id: 0,
232 parent: None,
233 kind: ScopeKind::Module,
234 span: byte_span(root),
235 }];
236 let mut imports = Vec::new();
237 let mut occurrences = Vec::new();
238 let mut stack = Vec::new();
239 push_children_with_scope_reverse(root, 0, &mut stack);
240
241 while let Some((node, parent_scope)) = stack.pop() {
242 let scope = if let Some(kind) = scope_kind(node.kind()) {
243 let local_id = scopes.len() as u32;
244 scopes.push(ScopeEntry {
245 local_id,
246 parent: Some(parent_scope),
247 kind,
248 span: byte_span(node),
249 });
250 local_id
251 } else {
252 parent_scope
253 };
254
255 if is_import_node(node, language) {
256 imports.extend(extract_import_entries(node, language, source, scope));
257 continue;
258 }
259 if let Some(import) = extract_dynamic_import(node, language, source, scope) {
260 imports.push(import);
261 continue;
262 }
263
264 if is_path_node(node.kind()) {
265 if let Some(occurrence) = path_occurrence(node, source, scope) {
266 occurrences.push(occurrence);
267 }
268 continue;
269 }
270 if is_identifier_node(node.kind()) {
271 occurrences.push(identifier_occurrence(node, source, scope, &scopes));
272 continue;
273 }
274
275 push_children_with_scope_reverse(node, scope, &mut stack);
276 }
277
278 imports.sort_by_key(|import| import.span.start);
279 occurrences.sort_by_key(|occurrence| occurrence.span.start);
280 for (local_id, occurrence) in occurrences.iter_mut().enumerate() {
281 occurrence.local_id = local_id as u32;
282 }
283 (scopes, imports, occurrences)
284}
285
286fn scope_kind(kind: &str) -> Option<ScopeKind> {
287 if is_function_node_kind(kind) {
288 return Some(ScopeKind::Function);
289 }
290 if matches!(
291 kind,
292 "struct_item"
293 | "enum_item"
294 | "trait_item"
295 | "impl_item"
296 | "class_definition"
297 | "class_declaration"
298 | "interface_declaration"
299 | "type_declaration"
300 | "struct_declaration"
301 | "enum_declaration"
302 ) {
303 return Some(ScopeKind::Type);
304 }
305 if matches!(kind, "mod_item" | "namespace_definition" | "module") {
306 return Some(ScopeKind::Module);
307 }
308 if matches!(
309 kind,
310 "block" | "compound_statement" | "statement_block" | "suite"
311 ) {
312 return Some(ScopeKind::Block);
313 }
314 None
315}
316
317fn is_function_node_kind(kind: &str) -> bool {
318 matches!(
319 kind,
320 "function_item"
321 | "function_definition"
322 | "function_declaration"
323 | "method_definition"
324 | "method_declaration"
325 | "constructor_declaration"
326 | "generator_function_declaration"
327 | "closure_expression"
328 | "arrow_function"
329 | "function_expression"
330 | "generator_function"
331 )
332}
333
334fn is_import_node(node: Node<'_>, language: Language) -> bool {
335 match language {
336 Language::Rust => matches!(node.kind(), "use_declaration" | "extern_crate_declaration"),
337 Language::Python => matches!(node.kind(), "import_statement" | "import_from_statement"),
338 Language::JavaScript | Language::TypeScript => {
339 node.kind() == "import_statement"
340 || (node.kind() == "export_statement"
341 && node.child_by_field_name("source").is_some())
342 }
343 Language::Go => node.kind() == "import_spec",
344 Language::Java => node.kind() == "import_declaration",
345 Language::C | Language::Cpp => node.kind() == "preproc_include",
346 Language::Zig | Language::Unknown => false,
347 }
348}
349
350fn extract_import_entries(
351 node: Node<'_>,
352 language: Language,
353 source: &str,
354 scope: u32,
355) -> Vec<ImportEntry> {
356 match language {
357 Language::Rust => vec![rust_import(node, source, scope)],
358 Language::Python => python_imports(node, source, scope),
359 Language::JavaScript | Language::TypeScript => {
360 vec![javascript_import(node, language, source, scope)]
361 }
362 Language::Go => vec![go_import(node, source, scope)],
363 Language::Java => vec![java_import(node, source, scope)],
364 Language::C | Language::Cpp => vec![c_import(node, source, scope)],
365 Language::Zig | Language::Unknown => Vec::new(),
366 }
367}
368
369fn rust_import(node: Node<'_>, source: &str, scope: u32) -> ImportEntry {
370 if node.kind() == "extern_crate_declaration" {
371 let imported = node
372 .child_by_field_name("name")
373 .map(|name| node_text(name, source).to_string())
374 .unwrap_or_default();
375 let local = node
376 .child_by_field_name("alias")
377 .map(|alias| node_text(alias, source).to_string())
378 .unwrap_or_else(|| imported.clone());
379 return ImportEntry {
380 kind: ImportKindTag::Use,
381 module_specifier: imported.clone(),
382 bindings: vec![ImportBinding {
383 imported,
384 local,
385 namespace: SymbolNamespace::Both,
386 }],
387 scope,
388 span: byte_span(node),
389 };
390 }
391
392 let body = node
393 .child_by_field_name("argument")
394 .map(|argument| node_text(argument, source))
395 .unwrap_or_default();
396 let (module_specifier, binding_texts) = if let Some(open) = body.find('{') {
397 let prefix = body[..open].trim().trim_end_matches("::").to_string();
398 let close = body.rfind('}').unwrap_or(body.len());
399 (prefix, split_top_level(&body[open + 1..close]))
400 } else {
401 let path = body.split(" as ").next().unwrap_or(body).trim();
402 let module = path
403 .rsplit_once("::")
404 .map(|(module, _)| module)
405 .unwrap_or(path)
406 .to_string();
407 (module, vec![body])
408 };
409 let bindings = binding_texts
410 .into_iter()
411 .filter_map(|binding| rust_binding(binding.trim()))
412 .collect();
413 ImportEntry {
414 kind: if named_child_of_kind(node, "visibility_modifier").is_some() {
415 ImportKindTag::Reexport
416 } else {
417 ImportKindTag::Use
418 },
419 module_specifier,
420 bindings,
421 scope,
422 span: byte_span(node),
423 }
424}
425
426fn rust_binding(value: &str) -> Option<ImportBinding> {
427 let value = value.trim();
428 if value.is_empty() {
429 return None;
430 }
431 if value == "*" || value.ends_with("::*") {
432 return Some(ImportBinding {
433 imported: "*".to_string(),
434 local: "*".to_string(),
435 namespace: SymbolNamespace::Both,
436 });
437 }
438 let (path, alias) = value
439 .split_once(" as ")
440 .map_or((value, None), |(path, alias)| (path, Some(alias.trim())));
441 let imported = path.rsplit("::").next().unwrap_or(path).trim();
442 let local = alias.unwrap_or(imported);
443 Some(ImportBinding {
444 imported: imported.to_string(),
445 local: local.to_string(),
446 namespace: SymbolNamespace::Both,
447 })
448}
449
450fn javascript_import(node: Node<'_>, language: Language, source: &str, scope: u32) -> ImportEntry {
451 let raw = node_text(node, source);
452 let namespace = if language == Language::TypeScript
453 && (raw.trim_start().starts_with("import type")
454 || raw.trim_start().starts_with("export type"))
455 {
456 SymbolNamespace::Type
457 } else {
458 SymbolNamespace::Value
459 };
460 let module_specifier = node
461 .child_by_field_name("source")
462 .map(|source_node| unquote(node_text(source_node, source)))
463 .unwrap_or_default();
464 let mut bindings = Vec::new();
465 let mut stack = vec![node];
466 while let Some(current) = stack.pop() {
467 match current.kind() {
468 "import_clause" => {
469 let mut cursor = current.walk();
470 for child in current.named_children(&mut cursor) {
471 if child.kind() == "identifier" {
472 bindings.push(ImportBinding {
473 imported: "default".to_string(),
474 local: node_text(child, source).to_string(),
475 namespace,
476 });
477 }
478 }
479 }
480 "namespace_import" | "namespace_export" => {
481 if let Some(local) = first_identifier(current, source) {
482 bindings.push(ImportBinding {
483 imported: "*".to_string(),
484 local,
485 namespace,
486 });
487 }
488 }
489 "import_specifier" | "export_specifier" => {
490 let imported = current
491 .child_by_field_name("name")
492 .map(|name| unquote(node_text(name, source)))
493 .or_else(|| first_identifier(current, source))
494 .unwrap_or_default();
495 let local = current
496 .child_by_field_name("alias")
497 .map(|alias| unquote(node_text(alias, source)))
498 .unwrap_or_else(|| imported.clone());
499 bindings.push(ImportBinding {
500 imported,
501 local,
502 namespace,
503 });
504 }
505 _ => {}
506 }
507 push_children_reverse(current, &mut stack);
508 }
509 ImportEntry {
510 kind: if node.kind() == "export_statement" {
511 ImportKindTag::Reexport
512 } else {
513 ImportKindTag::Import
514 },
515 module_specifier,
516 bindings,
517 scope,
518 span: byte_span(node),
519 }
520}
521
522fn python_imports(node: Node<'_>, source: &str, scope: u32) -> Vec<ImportEntry> {
523 if node.kind() == "import_from_statement" {
524 let module_specifier = node
525 .child_by_field_name("module_name")
526 .map(|module| node_text(module, source).to_string())
527 .unwrap_or_default();
528 let mut bindings = Vec::new();
529 let module_id = node
530 .child_by_field_name("module_name")
531 .map(|module| module.id());
532 let mut cursor = node.walk();
533 for child in node.named_children(&mut cursor) {
534 if Some(child.id()) == module_id {
535 continue;
536 }
537 if child.kind() == "aliased_import" || child.kind() == "dotted_name" {
538 bindings.push(python_binding(child, source));
539 } else if child.kind() == "wildcard_import" {
540 bindings.push(ImportBinding {
541 imported: "*".to_string(),
542 local: "*".to_string(),
543 namespace: SymbolNamespace::Both,
544 });
545 }
546 }
547 return vec![ImportEntry {
548 kind: ImportKindTag::Import,
549 module_specifier,
550 bindings,
551 scope,
552 span: byte_span(node),
553 }];
554 }
555
556 let mut entries = Vec::new();
557 let mut cursor = node.walk();
558 for child in node.named_children(&mut cursor) {
559 if matches!(child.kind(), "aliased_import" | "dotted_name") {
560 let binding = python_binding(child, source);
561 let module_specifier = child
562 .child_by_field_name("name")
563 .map(|name| node_text(name, source).to_string())
564 .unwrap_or_else(|| {
565 node_text(child, source)
566 .split(" as ")
567 .next()
568 .unwrap()
569 .into()
570 });
571 entries.push(ImportEntry {
572 kind: ImportKindTag::Import,
573 module_specifier,
574 bindings: vec![binding],
575 scope,
576 span: byte_span(child),
577 });
578 }
579 }
580 entries
581}
582
583fn python_binding(node: Node<'_>, source: &str) -> ImportBinding {
584 let imported = node
585 .child_by_field_name("name")
586 .map(|name| node_text(name, source).to_string())
587 .unwrap_or_else(|| node_text(node, source).split(" as ").next().unwrap().into());
588 let local = node
589 .child_by_field_name("alias")
590 .map(|alias| node_text(alias, source).to_string())
591 .unwrap_or_else(|| imported.rsplit('.').next().unwrap_or(&imported).to_string());
592 ImportBinding {
593 imported,
594 local,
595 namespace: SymbolNamespace::Both,
596 }
597}
598
599fn go_import(node: Node<'_>, source: &str, scope: u32) -> ImportEntry {
600 let module_specifier = node
601 .child_by_field_name("path")
602 .map(|path| unquote(node_text(path, source)))
603 .unwrap_or_default();
604 let imported = module_specifier
605 .rsplit('/')
606 .next()
607 .unwrap_or(&module_specifier)
608 .to_string();
609 let local = node
610 .child_by_field_name("name")
611 .map(|name| node_text(name, source).to_string())
612 .unwrap_or_else(|| imported.clone());
613 ImportEntry {
614 kind: ImportKindTag::Import,
615 module_specifier,
616 bindings: vec![ImportBinding {
617 imported,
618 local,
619 namespace: SymbolNamespace::Both,
620 }],
621 scope,
622 span: byte_span(node),
623 }
624}
625
626fn java_import(node: Node<'_>, source: &str, scope: u32) -> ImportEntry {
627 let raw = node_text(node, source);
628 let module_specifier = raw
629 .trim()
630 .trim_start_matches("import ")
631 .trim_start_matches("static ")
632 .trim_end_matches(';')
633 .trim()
634 .to_string();
635 let imported = module_specifier
636 .rsplit('.')
637 .next()
638 .unwrap_or(&module_specifier)
639 .to_string();
640 ImportEntry {
641 kind: ImportKindTag::Import,
642 module_specifier,
643 bindings: vec![ImportBinding {
644 local: imported.clone(),
645 imported,
646 namespace: SymbolNamespace::Both,
647 }],
648 scope,
649 span: byte_span(node),
650 }
651}
652
653fn c_import(node: Node<'_>, source: &str, scope: u32) -> ImportEntry {
654 let raw = node_text(node, source);
655 let module_specifier = raw
656 .trim()
657 .trim_start_matches("#include")
658 .trim()
659 .trim_matches(['<', '>', '"'])
660 .to_string();
661 ImportEntry {
662 kind: ImportKindTag::Import,
663 module_specifier,
664 bindings: Vec::new(),
665 scope,
666 span: byte_span(node),
667 }
668}
669
670fn extract_dynamic_import(
671 node: Node<'_>,
672 language: Language,
673 source: &str,
674 scope: u32,
675) -> Option<ImportEntry> {
676 let is_dynamic_import = match language {
677 Language::JavaScript | Language::TypeScript if node.kind() == "call_expression" => node
678 .child_by_field_name("function")
679 .is_some_and(|function| node_text(function, source) == "import"),
680 Language::Zig if node.kind() == "builtin_function" => {
681 named_child_of_kind(node, "builtin_identifier")
682 .is_some_and(|function| node_text(function, source) == "@import")
683 }
684 _ => false,
685 };
686 if !is_dynamic_import {
687 return None;
688 }
689 let string = first_node_of_kind(node, &["string", "string_literal"])?;
690 Some(ImportEntry {
691 kind: ImportKindTag::Dynamic,
692 module_specifier: unquote(node_text(string, source)),
693 bindings: Vec::new(),
694 scope,
695 span: byte_span(node),
696 })
697}
698
699fn path_occurrence(node: Node<'_>, source: &str, scope: u32) -> Option<OccurrenceEntry> {
700 let mut segments = Vec::new();
701 let mut stack = vec![node];
702 while let Some(current) = stack.pop() {
703 if is_identifier_node(current.kind())
704 || matches!(current.kind(), "self" | "super" | "crate")
705 {
706 segments.push((current.start_byte(), node_text(current, source).to_string()));
707 continue;
708 }
709 push_children_reverse(current, &mut stack);
710 }
711 segments.sort_by_key(|(start, _)| *start);
712 segments.dedup_by_key(|(start, _)| *start);
713 let (_, name) = segments.pop()?;
714 let qualifier = segments.into_iter().map(|(_, name)| name).collect();
715 let namespace = if node.kind().contains("type") {
716 SymbolNamespace::Type
717 } else {
718 SymbolNamespace::Value
719 };
720 Some(OccurrenceEntry {
721 local_id: 0,
722 role: occurrence_role(node, namespace),
723 name,
724 qualifier,
725 namespace,
726 scope,
727 span: byte_span(node),
728 })
729}
730
731fn identifier_occurrence(
732 node: Node<'_>,
733 source: &str,
734 scope: u32,
735 scopes: &[ScopeEntry],
736) -> OccurrenceEntry {
737 let namespace = identifier_namespace(node);
738 let role = occurrence_role(node, namespace);
739 let definition_in_own_scope = role == OccurrenceRole::Definition
740 && node
741 .parent()
742 .is_some_and(|parent| scope_kind(parent.kind()).is_some());
743 OccurrenceEntry {
744 local_id: 0,
745 role,
746 name: node_text(node, source).to_string(),
747 qualifier: Vec::new(),
748 namespace,
749 scope: if definition_in_own_scope {
750 scopes[scope as usize].parent.unwrap_or(scope)
751 } else {
752 scope
753 },
754 span: byte_span(node),
755 }
756}
757
758fn occurrence_role(node: Node<'_>, namespace: SymbolNamespace) -> OccurrenceRole {
759 if is_definition_name(node) {
760 OccurrenceRole::Definition
761 } else if is_call_target(node) {
762 OccurrenceRole::Call
763 } else if namespace == SymbolNamespace::Type {
764 OccurrenceRole::TypeReference
765 } else {
766 OccurrenceRole::Reference
767 }
768}
769
770fn is_definition_name(node: Node<'_>) -> bool {
771 let Some(parent) = node.parent() else {
772 return false;
773 };
774 let is_name_field = parent
775 .child_by_field_name("name")
776 .is_some_and(|name| name.id() == node.id());
777 is_name_field
778 && matches!(
779 parent.kind(),
780 "function_item"
781 | "function_definition"
782 | "function_declaration"
783 | "method_definition"
784 | "method_declaration"
785 | "constructor_declaration"
786 | "class_definition"
787 | "class_declaration"
788 | "interface_declaration"
789 | "struct_item"
790 | "struct_declaration"
791 | "enum_item"
792 | "enum_declaration"
793 | "trait_item"
794 | "type_item"
795 | "type_alias_declaration"
796 | "mod_item"
797 | "const_item"
798 | "static_item"
799 | "variable_declarator"
800 | "parameter"
801 | "required_parameter"
802 | "optional_parameter"
803 | "formal_parameter"
804 | "field_declaration"
805 )
806}
807
808fn is_call_expression(kind: &str) -> bool {
809 matches!(kind, "call_expression" | "call")
810}
811
812fn is_call_target(mut node: Node<'_>) -> bool {
813 while let Some(parent) = node.parent() {
814 if is_path_node(parent.kind()) {
815 node = parent;
816 continue;
817 }
818 if is_call_expression(parent.kind()) {
819 return parent
820 .child_by_field_name("function")
821 .is_some_and(|function| function.id() == node.id());
822 }
823 return false;
824 }
825 false
826}
827
828fn identifier_namespace(node: Node<'_>) -> SymbolNamespace {
829 if node.kind().contains("type") {
830 return SymbolNamespace::Type;
831 }
832 let mut current = node;
833 for _ in 0..4 {
834 let Some(parent) = current.parent() else {
835 break;
836 };
837 if matches!(
838 parent.kind(),
839 "type_annotation"
840 | "generic_type"
841 | "type_arguments"
842 | "type_parameters"
843 | "return_type"
844 | "trait_bounds"
845 ) {
846 return SymbolNamespace::Type;
847 }
848 current = parent;
849 }
850 SymbolNamespace::Value
851}
852
853fn is_identifier_node(kind: &str) -> bool {
854 matches!(
855 kind,
856 "identifier"
857 | "type_identifier"
858 | "field_identifier"
859 | "property_identifier"
860 | "package_identifier"
861 | "namespace_identifier"
862 )
863}
864
865fn is_path_node(kind: &str) -> bool {
866 matches!(
867 kind,
868 "scoped_identifier"
869 | "scoped_type_identifier"
870 | "qualified_identifier"
871 | "field_expression"
872 | "member_expression"
873 | "attribute"
874 | "selector_expression"
875 | "field_access"
876 )
877}
878
879fn split_top_level(value: &str) -> Vec<&str> {
880 let mut depth = 0u32;
881 let mut start = 0usize;
882 let mut parts = Vec::new();
883 for (index, byte) in value.bytes().enumerate() {
884 match byte {
885 b'{' | b'(' | b'[' => depth += 1,
886 b'}' | b')' | b']' => depth = depth.saturating_sub(1),
887 b',' if depth == 0 => {
888 parts.push(&value[start..index]);
889 start = index + 1;
890 }
891 _ => {}
892 }
893 }
894 parts.push(&value[start..]);
895 parts
896}
897
898fn first_identifier(node: Node<'_>, source: &str) -> Option<String> {
899 let mut stack = vec![node];
900 while let Some(current) = stack.pop() {
901 if is_identifier_node(current.kind()) {
902 return Some(node_text(current, source).to_string());
903 }
904 push_children_reverse(current, &mut stack);
905 }
906 None
907}
908
909fn first_node_of_kind<'tree>(node: Node<'tree>, kinds: &[&str]) -> Option<Node<'tree>> {
910 let mut stack = vec![node];
911 while let Some(current) = stack.pop() {
912 if kinds.contains(¤t.kind()) {
913 return Some(current);
914 }
915 push_children_reverse(current, &mut stack);
916 }
917 None
918}
919
920fn named_child_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
921 let mut cursor = node.walk();
922 node.named_children(&mut cursor)
923 .find(|child| child.kind() == kind)
924}
925
926fn node_text<'a>(node: Node<'_>, source: &'a str) -> &'a str {
927 &source[node.byte_range()]
928}
929
930fn unquote(value: &str) -> String {
931 value
932 .strip_prefix(['\'', '"', '`'])
933 .and_then(|value| value.strip_suffix(['\'', '"', '`']))
934 .unwrap_or(value)
935 .to_string()
936}
937
938fn byte_span(node: Node<'_>) -> ByteSpan {
939 ByteSpan::new(node.start_byte() as u32, node.end_byte() as u32)
940}
941
942fn push_children_with_scope_reverse<'tree>(
943 node: Node<'tree>,
944 scope: u32,
945 stack: &mut Vec<(Node<'tree>, u32)>,
946) {
947 let child_count = node.child_count();
948 for index in (0..child_count).rev() {
949 if let Some(child) = node.child(index as u32) {
950 stack.push((child, scope));
951 }
952 }
953}
954
955pub(super) fn is_function_kind(kind: &str, language: Language) -> bool {
956 match language {
957 Language::Rust => {
958 kind == "function_item" || kind == "method_declaration" || kind == "closure_expression"
959 }
960 Language::Python => kind == "function_definition",
961 Language::JavaScript | Language::TypeScript => {
962 kind == "function_declaration"
963 || kind == "method_definition"
964 || kind == "generator_function_declaration"
965 || kind == "variable_declarator"
966 }
967 Language::Go => kind == "function_declaration" || kind == "method_declaration",
968 Language::C | Language::Cpp => kind == "function_definition",
969 Language::Java => kind == "method_declaration" || kind == "constructor_declaration",
970 Language::Zig => kind == "function_declaration",
971 Language::Unknown => false,
972 }
973}
974
975fn is_function_node(node: &Node<'_>, language: Language) -> bool {
976 match language {
977 Language::JavaScript | Language::TypeScript => {
978 matches!(
979 node.kind(),
980 "function_declaration" | "method_definition" | "generator_function_declaration"
981 ) || javascript_bound_function(node)
982 }
983 _ => is_function_kind(node.kind(), language),
984 }
985}
986
987fn javascript_bound_function(node: &Node<'_>) -> bool {
988 matches!(node.kind(), "variable_declarator" | "pair")
989 && node
990 .child_by_field_name("value")
991 .is_some_and(|value| is_javascript_function_value(value.kind()))
992}
993
994fn function_name<'a>(node: &Node<'_>, source: &'a str) -> Option<&'a str> {
995 if let Some(name) = node.child_by_field_name("name") {
996 return Some(&source[name.byte_range()]);
997 }
998 if let Some(key) = node.child_by_field_name("key") {
999 if matches!(
1000 key.kind(),
1001 "identifier" | "property_identifier" | "private_property_identifier"
1002 ) {
1003 return Some(&source[key.byte_range()]);
1004 }
1005 if let Some(name) = first_identifier_in_subtree(key, source) {
1006 return Some(name);
1007 }
1008 }
1009 if let Some(declarator) = node.child_by_field_name("declarator") {
1010 if let Some(name) = c_function_name(declarator, source) {
1011 return Some(name);
1012 }
1013 if let Some(name) = first_identifier_in_subtree(declarator, source) {
1014 return Some(name);
1015 }
1016 }
1017
1018 let mut cursor = node.walk();
1019 for child in node.children(&mut cursor) {
1020 if matches!(
1021 child.kind(),
1022 "identifier" | "field_identifier" | "type_identifier" | "property_identifier"
1023 ) {
1024 return Some(&source[child.byte_range()]);
1025 }
1026 }
1027 None
1028}
1029
1030fn c_function_name<'a>(function_declarator: Node<'_>, source: &'a str) -> Option<&'a str> {
1031 let mut current = function_declarator.child_by_field_name("declarator")?;
1032 for _ in 0..32 {
1033 match current.kind() {
1034 "identifier"
1035 | "field_identifier"
1036 | "type_identifier"
1037 | "property_identifier"
1038 | "operator_name"
1039 | "destructor_name" => return Some(&source[current.byte_range()]),
1040 "qualified_identifier" | "template_function" => {
1041 current = current.child_by_field_name("name")?;
1042 }
1043 "pointer_declarator"
1044 | "reference_declarator"
1045 | "function_declarator"
1046 | "parenthesized_declarator" => {
1047 current = current.child_by_field_name("declarator")?;
1048 }
1049 _ => return None,
1050 }
1051 }
1052 None
1053}
1054
1055fn first_identifier_in_subtree<'a>(node: Node<'_>, source: &'a str) -> Option<&'a str> {
1056 let mut stack = vec![node];
1057 while let Some(current) = stack.pop() {
1058 if matches!(
1059 current.kind(),
1060 "identifier" | "field_identifier" | "type_identifier" | "property_identifier"
1061 ) {
1062 return Some(&source[current.byte_range()]);
1063 }
1064 push_children_reverse(current, &mut stack);
1065 }
1066 None
1067}
1068
1069fn function_container(node: &Node<'_>, source: &str) -> String {
1070 if node.kind() == "method_declaration"
1071 && let Some(receiver) = go_receiver_type(node, source)
1072 {
1073 return receiver;
1074 }
1075 let mut parts = Vec::new();
1076 let mut current = node.parent();
1077 while let Some(parent) = current {
1078 match parent.kind() {
1079 "impl_item" => {
1080 if let Some(name) = rust_impl_type_name(&parent, source) {
1081 parts.push(name);
1082 }
1083 }
1084 "mod_item"
1085 | "trait_item"
1086 | "class_definition"
1087 | "class_declaration"
1088 | "class_specifier"
1089 | "struct_specifier"
1090 | "interface_declaration" => {
1091 if let Some(name) = parent.child_by_field_name("name") {
1092 let text = source[name.byte_range()].trim();
1093 if !text.is_empty() {
1094 parts.push(text.to_string());
1095 }
1096 }
1097 }
1098 _ => {}
1099 }
1100 current = parent.parent();
1101 }
1102 parts.reverse();
1103 parts.join("::")
1104}
1105
1106fn rust_impl_type_name(node: &Node<'_>, source: &str) -> Option<String> {
1107 if let Some(type_node) = node.child_by_field_name("type") {
1108 let name = first_type_identifier(&type_node, source);
1109 if !name.trim().is_empty() {
1110 return Some(name);
1111 }
1112 }
1113 let mut cursor = node.walk();
1114 for child in node.children(&mut cursor) {
1115 if matches!(
1116 child.kind(),
1117 "type_parameters" | "where_clause" | "declaration_list"
1118 ) {
1119 continue;
1120 }
1121 if matches!(
1122 child.kind(),
1123 "type_identifier" | "generic_type" | "scoped_type_identifier"
1124 ) {
1125 let name = first_type_identifier(&child, source);
1126 if !name.trim().is_empty() {
1127 return Some(name);
1128 }
1129 }
1130 }
1131 None
1132}
1133
1134fn go_receiver_type(node: &Node<'_>, source: &str) -> Option<String> {
1135 let params = node.child_by_field_name("receiver")?;
1136 let mut cursor = params.walk();
1137 for child in params.children(&mut cursor) {
1138 if child.kind() == "parameter_declaration"
1139 && let Some(type_node) = child.child_by_field_name("type")
1140 {
1141 let text = source[type_node.byte_range()].trim_start_matches('*');
1142 if !text.is_empty() {
1143 return Some(text.to_string());
1144 }
1145 }
1146 }
1147 None
1148}
1149
1150fn first_type_identifier(node: &Node<'_>, source: &str) -> String {
1151 match node.kind() {
1152 "type_identifier" | "identifier" => source[node.byte_range()].to_string(),
1153 "generic_type" | "scoped_type_identifier" => {
1154 let mut cursor = node.walk();
1155 let mut last = None;
1156 for child in node.children(&mut cursor) {
1157 if matches!(child.kind(), "type_identifier" | "identifier") {
1158 last = Some(source[child.byte_range()].to_string());
1159 }
1160 }
1161 match last {
1162 Some(name) => name,
1163 None => source[node.byte_range()].to_string(),
1164 }
1165 }
1166 _ => source[node.byte_range()].to_string(),
1167 }
1168}
1169
1170fn function_signature(node: &Node<'_>, source: &str) -> String {
1171 if node.kind() == "variable_declarator" {
1172 return variable_function_signature(node, source);
1173 }
1174
1175 let mut signature_parts = Vec::new();
1176 let mut cursor = node.walk();
1177 for child in node.children(&mut cursor) {
1178 let kind = child.kind();
1179 if matches!(
1180 kind,
1181 "identifier"
1182 | "field_identifier"
1183 | "type_identifier"
1184 | "property_identifier"
1185 | "parameters"
1186 | "formal_parameters"
1187 | "parameter_list"
1188 | "function_declarator"
1189 | "type_parameters"
1190 | "type_arguments"
1191 | "return_type"
1192 | "type_annotation"
1193 | "result"
1194 ) {
1195 signature_parts.push(&source[child.byte_range()]);
1196 }
1197 if matches!(
1198 kind,
1199 "block" | "compound_statement" | "statement_block" | "suite"
1200 ) {
1201 break;
1202 }
1203 }
1204
1205 signature_parts.join(" ")
1206}
1207
1208fn variable_function_signature(node: &Node<'_>, source: &str) -> String {
1209 let Some(name) = node.child_by_field_name("name") else {
1210 return String::new();
1211 };
1212 let Some(value) = node.child_by_field_name("value") else {
1213 return source[name.byte_range()].to_string();
1214 };
1215
1216 let mut signature_parts = vec![&source[name.byte_range()]];
1217 let mut cursor = value.walk();
1218 for child in value.children(&mut cursor) {
1219 if matches!(child.kind(), "formal_parameters" | "parameters") {
1220 signature_parts.push(&source[child.byte_range()]);
1221 }
1222 if matches!(child.kind(), "statement_block" | "body") {
1223 break;
1224 }
1225 }
1226 signature_parts.join(" ")
1227}
1228
1229fn line_offsets(source: &str) -> Vec<usize> {
1230 let mut offsets =
1231 Vec::with_capacity(source.as_bytes().iter().filter(|&&b| b == b'\n').count() + 1);
1232 offsets.push(0);
1233 for (index, byte) in source.bytes().enumerate() {
1234 if byte == b'\n' && index + 1 < source.len() {
1235 offsets.push(index + 1);
1236 }
1237 }
1238 offsets
1239}
1240
1241fn is_javascript_function_value(kind: &str) -> bool {
1242 matches!(
1243 kind,
1244 "arrow_function" | "function" | "function_expression" | "generator_function"
1245 )
1246}
1247
1248fn push_children_reverse<'tree>(node: Node<'tree>, stack: &mut Vec<Node<'tree>>) {
1249 let child_count = node.child_count();
1250 for index in (0..child_count).rev() {
1251 if let Some(child) = node.child(index as u32) {
1252 stack.push(child);
1253 }
1254 }
1255}