Skip to main content

semantic/parser/
syntax_index.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Compact syntax index derived from a tree-sitter parse tree.
3
4use 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/// Compact Heddle-owned syntax data for one parsed source file.
18#[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/// Borrowed view of a function indexed in a [`SyntaxIndex`].
29#[derive(Clone, Copy, Debug)]
30pub struct FunctionRef<'a> {
31    inner: &'a IndexedFunction,
32    source: &'a str,
33}
34
35/// Borrowed view of an import indexed in a [`SyntaxIndex`].
36#[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                // Zig has no top-level import statement node — `@import` is a
126                // builtin call bound in a `variable_declaration`, so there is
127                // no dedicated import kind to collect here.
128                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    /// Byte offsets where each line starts. The first entry is always `0`.
154    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(&current.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}