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    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                // Zig has no top-level import statement node — `@import` is a
124                // builtin call bound in a `variable_declaration`, so there is
125                // no dedicated import kind to collect here.
126                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    /// Byte offsets where each line starts. The first entry is always `0`.
152    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(&current.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}