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