Skip to main content

gonzalo_graph/
builder.rs

1//! Build a [`CodeGraph`] from source using tree-sitter. Parsing is
2//! language-parameterized ([`Language`]); Rust, Python, JavaScript,
3//! TypeScript/TSX, Go, Java, C#, C, C++, Ruby, PHP, Bash, Kotlin, Swift, Lua,
4//! Scala, and Elixir are supported, and a new grammar is a matter of adding its
5//! node-kind mappings.
6
7use crate::model::{CodeGraph, FromScope, Import, RefKind, Reference, Symbol, SymbolKind};
8use serde::{Deserialize, Serialize};
9use std::collections::{BTreeMap, BTreeSet};
10use tree_sitter::{Node, Parser};
11
12/// A source language the graph builder understands.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14pub enum Language {
15    Rust,
16    Python,
17    JavaScript,
18    TypeScript,
19    /// TypeScript with JSX (`.tsx`).
20    Tsx,
21    Go,
22    Java,
23    CSharp,
24    C,
25    Cpp,
26    Ruby,
27    Php,
28    Bash,
29    Kotlin,
30    Swift,
31    Lua,
32    Scala,
33    Elixir,
34}
35
36impl Language {
37    /// The [`Language`] for a file extension (without the dot), or `None` if
38    /// unsupported.
39    pub fn from_extension(ext: &str) -> Option<Self> {
40        match ext {
41            "rs" => Some(Self::Rust),
42            "py" => Some(Self::Python),
43            "js" | "jsx" | "mjs" | "cjs" => Some(Self::JavaScript),
44            "ts" | "mts" | "cts" => Some(Self::TypeScript),
45            "tsx" => Some(Self::Tsx),
46            "go" => Some(Self::Go),
47            "java" => Some(Self::Java),
48            "cs" => Some(Self::CSharp),
49            "c" => Some(Self::C),
50            // `.h` is the conventional header extension for C++ as much as for
51            // C, and the C grammar mis-parses C++ rather than failing: `enum
52            // class Color` recorded `Color` as a function and invented a symbol
53            // named `class`. C++ is a near superset and the two agree on plain
54            // C — pinned by
55            // [`a_pure_c_header_extracts_the_same_symbols_under_either_grammar`]
56            // — so a header goes through the wider grammar (#266).
57            "h" | "cpp" | "cc" | "cxx" | "hpp" | "hh" => Some(Self::Cpp),
58            "rb" => Some(Self::Ruby),
59            "php" => Some(Self::Php),
60            "sh" | "bash" => Some(Self::Bash),
61            "kt" | "kts" => Some(Self::Kotlin),
62            "swift" => Some(Self::Swift),
63            "lua" => Some(Self::Lua),
64            "scala" | "sc" => Some(Self::Scala),
65            "ex" | "exs" => Some(Self::Elixir),
66            _ => None,
67        }
68    }
69
70    /// Lowercase name, used as a stable key when bucketing symbols by language.
71    pub fn as_str(self) -> &'static str {
72        match self {
73            Self::Rust => "rust",
74            Self::Python => "python",
75            Self::JavaScript => "javascript",
76            Self::TypeScript => "typescript",
77            Self::Tsx => "tsx",
78            Self::Go => "go",
79            Self::Java => "java",
80            Self::CSharp => "csharp",
81            Self::C => "c",
82            Self::Cpp => "cpp",
83            Self::Ruby => "ruby",
84            Self::Php => "php",
85            Self::Bash => "bash",
86            Self::Kotlin => "kotlin",
87            Self::Swift => "swift",
88            Self::Lua => "lua",
89            Self::Scala => "scala",
90            Self::Elixir => "elixir",
91        }
92    }
93
94    fn ts_language(self) -> tree_sitter::Language {
95        match self {
96            Self::Rust => tree_sitter_rust::LANGUAGE.into(),
97            Self::Python => tree_sitter_python::LANGUAGE.into(),
98            Self::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
99            Self::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
100            Self::Tsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
101            Self::Go => tree_sitter_go::LANGUAGE.into(),
102            Self::Java => tree_sitter_java::LANGUAGE.into(),
103            Self::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
104            Self::C => tree_sitter_c::LANGUAGE.into(),
105            Self::Cpp => tree_sitter_cpp::LANGUAGE.into(),
106            Self::Ruby => tree_sitter_ruby::LANGUAGE.into(),
107            Self::Php => tree_sitter_php::LANGUAGE_PHP.into(),
108            Self::Bash => tree_sitter_bash::LANGUAGE.into(),
109            Self::Kotlin => tree_sitter_kotlin_ng::LANGUAGE.into(),
110            Self::Swift => tree_sitter_swift::LANGUAGE.into(),
111            Self::Lua => tree_sitter_lua::LANGUAGE.into(),
112            Self::Scala => tree_sitter_scala::LANGUAGE.into(),
113            Self::Elixir => tree_sitter_elixir::LANGUAGE.into(),
114        }
115    }
116
117    /// Map a node to the symbol it defines, if any. Takes the whole node (not
118    /// just its kind) because some languages need to inspect children — e.g. a
119    /// JS `variable_declarator` is only a function when its value is an
120    /// arrow/function expression, and Swift/Kotlin distinguish struct/enum/
121    /// interface by a keyword child.
122    fn item_kind(self, node: Node<'_>, bytes: &[u8]) -> Option<SymbolKind> {
123        let node_kind = node.kind();
124        match self {
125            Self::Rust => match node_kind {
126                "function_item" => Some(SymbolKind::Function),
127                "struct_item" => Some(SymbolKind::Struct),
128                "enum_item" => Some(SymbolKind::Enum),
129                "trait_item" => Some(SymbolKind::Trait),
130                "impl_item" => Some(SymbolKind::Impl),
131                "mod_item" => Some(SymbolKind::Module),
132                "const_item" => Some(SymbolKind::Const),
133                "static_item" => Some(SymbolKind::Static),
134                "type_item" => Some(SymbolKind::TypeAlias),
135                _ => None,
136            },
137            Self::Python => match node_kind {
138                "function_definition" => Some(SymbolKind::Function),
139                "class_definition" => Some(SymbolKind::Class),
140                _ => None,
141            },
142            Self::JavaScript => js_item_kind(node, bytes),
143            // TypeScript/TSX are a superset of JavaScript's declarations.
144            Self::TypeScript | Self::Tsx => js_item_kind(node, bytes).or(match node_kind {
145                "interface_declaration" => Some(SymbolKind::Interface),
146                "type_alias_declaration" => Some(SymbolKind::TypeAlias),
147                "enum_declaration" => Some(SymbolKind::Enum),
148                _ => None,
149            }),
150            // Go names a type on the `type_spec`, but struct vs interface is
151            // determined by its inner `type` node — so the symbol is defined at
152            // the `struct_type`/`interface_type` node, and `item_name` reaches
153            // back to the enclosing `type_spec` for the name. `const`/`var` specs
154            // are best-effort (first name of a possibly multi-name spec).
155            Self::Go => match node_kind {
156                "function_declaration" | "method_declaration" => Some(SymbolKind::Function),
157                "struct_type" => Some(SymbolKind::Struct),
158                "interface_type" => Some(SymbolKind::Interface),
159                "const_spec" => Some(SymbolKind::Const),
160                "var_spec" => Some(SymbolKind::Static),
161                _ => None,
162            },
163            Self::Java => match node_kind {
164                "class_declaration" => Some(SymbolKind::Class),
165                "interface_declaration" => Some(SymbolKind::Interface),
166                "enum_declaration" => Some(SymbolKind::Enum),
167                "method_declaration" | "constructor_declaration" => Some(SymbolKind::Function),
168                _ => None,
169            },
170            Self::CSharp => match node_kind {
171                "class_declaration" => Some(SymbolKind::Class),
172                "interface_declaration" => Some(SymbolKind::Interface),
173                "struct_declaration" => Some(SymbolKind::Struct),
174                "enum_declaration" => Some(SymbolKind::Enum),
175                "method_declaration" | "constructor_declaration" => Some(SymbolKind::Function),
176                _ => None,
177            },
178            Self::C => c_item_kind(node_kind),
179            // C++ is a superset of C's declarations.
180            Self::Cpp => c_item_kind(node_kind).or(match node_kind {
181                "class_specifier" => Some(SymbolKind::Class),
182                "namespace_definition" => Some(SymbolKind::Module),
183                _ => None,
184            }),
185            Self::Ruby => match node_kind {
186                "method" | "singleton_method" => Some(SymbolKind::Function),
187                "class" => Some(SymbolKind::Class),
188                "module" => Some(SymbolKind::Module),
189                _ => None,
190            },
191            Self::Php => match node_kind {
192                "function_definition" | "method_declaration" => Some(SymbolKind::Function),
193                "class_declaration" => Some(SymbolKind::Class),
194                "interface_declaration" => Some(SymbolKind::Interface),
195                "trait_declaration" => Some(SymbolKind::Trait),
196                "enum_declaration" => Some(SymbolKind::Enum),
197                _ => None,
198            },
199            // Bash has only functions.
200            Self::Bash => match node_kind {
201                "function_definition" => Some(SymbolKind::Function),
202                _ => None,
203            },
204            // Kotlin `class_declaration` covers both `class` and `interface`
205            // (distinguished by a leading keyword child); `object` (a named
206            // singleton) is its own `object_declaration` node and reads as Class.
207            Self::Kotlin => match node_kind {
208                "function_declaration" => Some(SymbolKind::Function),
209                "class_declaration" => Some(kotlin_class_kind(node)),
210                "object_declaration" => Some(SymbolKind::Class),
211                _ => None,
212            },
213            // Swift `class_declaration` covers class/struct/enum/actor,
214            // distinguished by a `declaration_kind` keyword child; `protocol`
215            // maps to Interface.
216            Self::Swift => match node_kind {
217                "function_declaration" => Some(SymbolKind::Function),
218                "class_declaration" => Some(swift_type_kind(node)),
219                "protocol_declaration" => Some(SymbolKind::Interface),
220                _ => None,
221            },
222            // Lua has only functions (named `function_declaration`; anonymous
223            // `function_definition` carries no name and is skipped).
224            Self::Lua => match node_kind {
225                "function_declaration" => Some(SymbolKind::Function),
226                _ => None,
227            },
228            // Scala `object` (a named singleton) surfaces as a class-like type.
229            Self::Scala => match node_kind {
230                "function_definition" | "function_declaration" => Some(SymbolKind::Function),
231                "class_definition" | "object_definition" => Some(SymbolKind::Class),
232                "trait_definition" => Some(SymbolKind::Trait),
233                "enum_definition" => Some(SymbolKind::Enum),
234                _ => None,
235            },
236            // Elixir is homoiconic: `def`/`defp`/`defmacro`/`defmacrop` and
237            // `defmodule` all parse as ordinary `call` nodes distinguished by
238            // their target identifier's *text*, not by node kind.
239            Self::Elixir => elixir_target_name(node, bytes).and_then(|t| match t.as_str() {
240                "defmodule" => Some(SymbolKind::Module),
241                "def" | "defp" | "defmacro" | "defmacrop" => Some(SymbolKind::Function),
242                _ => None,
243            }),
244        }
245    }
246
247    /// The display name of an item node.
248    fn item_name(self, node: Node<'_>, kind: SymbolKind, bytes: &[u8]) -> Option<String> {
249        match (self, kind) {
250            // Rust `impl` has no `name` field; use its `type` (e.g. `Foo` in `impl Foo`).
251            (Self::Rust, SymbolKind::Impl) => node
252                .child_by_field_name("type")
253                .and_then(|n| node_text(n, bytes))
254                .map(str::to_string),
255            // Go `struct_type`/`interface_type` carry no name; the name lives on
256            // the enclosing `type_spec`. Anonymous types (no `type_spec` parent
257            // with a name) yield `None` and are skipped.
258            (Self::Go, SymbolKind::Struct | SymbolKind::Interface) => node
259                .parent()
260                .and_then(|p| p.child_by_field_name("name"))
261                .and_then(|n| node_text(n, bytes))
262                .map(str::to_string),
263            // C/C++ name a function or typedef through nested `declarator` nodes,
264            // not a flat `name` field. Struct/enum/class/namespace do use `name`.
265            (Self::C | Self::Cpp, SymbolKind::Function | SymbolKind::TypeAlias) => {
266                c_declarator_name(node, bytes)
267            }
268            // Elixir defs carry no `name` field; the defined name is the head of
269            // the first argument — a nested `call` (`def add(a, b)`), a bare
270            // `identifier` (`def run`), or an `alias` (`defmodule Math`).
271            // An object property bound to a function carries its name in `key`;
272            // every other JS/TS function uses `name` (#257).
273            (Self::JavaScript | Self::TypeScript | Self::Tsx, SymbolKind::Function) => {
274                name_field(node, bytes).or_else(|| {
275                    node.child_by_field_name("key")
276                        .and_then(|n| node_text(n, bytes))
277                        .map(str::to_string)
278                })
279            }
280            (Self::Elixir, _) => elixir_defined_name(node, bytes),
281            _ => name_field(node, bytes),
282        }
283    }
284
285    /// Whether `node_kind` is a call expression for this language.
286    fn is_call(self, node_kind: &str) -> bool {
287        match self {
288            Self::Rust
289            | Self::JavaScript
290            | Self::TypeScript
291            | Self::Tsx
292            | Self::Go
293            | Self::C
294            | Self::Cpp => node_kind == "call_expression",
295            Self::Python => node_kind == "call",
296            Self::Java => node_kind == "method_invocation",
297            Self::CSharp => node_kind == "invocation_expression",
298            Self::Ruby => node_kind == "call",
299            // PHP: plain `f()`, method `$x->m()` / `$x?->m()`, and static `A::b()`.
300            Self::Php => matches!(
301                node_kind,
302                "function_call_expression"
303                    | "member_call_expression"
304                    | "nullsafe_member_call_expression"
305                    | "scoped_call_expression"
306            ),
307            // Bash "calls" are commands (`helper arg`).
308            Self::Bash => node_kind == "command",
309            Self::Kotlin | Self::Swift | Self::Scala => node_kind == "call_expression",
310            Self::Lua => node_kind == "function_call",
311            Self::Elixir => node_kind == "call",
312        }
313    }
314
315    /// Whether a call reaches its callee through a receiver expression, so the
316    /// callee belongs to a value whose type the graph does not know.
317    ///
318    /// `x.foo()` is [`RefKind::Method`]; `foo()` and path calls like
319    /// `a::b::foo()` are [`RefKind::Free`]. The distinction is what stops the
320    /// resolver attributing a std or dependency method to a same-named free
321    /// function that happens to be the view's only definition (#223).
322    ///
323    /// Languages whose grammar does not surface a receiver here fall through to
324    /// `Free`, which is the behaviour they had before this existed — no worse,
325    /// just not yet improved.
326    fn callee_kind(self, call: Node<'_>) -> RefKind {
327        // A few grammars mark a method call on the call node itself.
328        let by_call_node = match self {
329            Self::Php => matches!(
330                call.kind(),
331                "member_call_expression" | "nullsafe_member_call_expression"
332            ),
333            // `obj.m()` carries an `object`; a bare `m()` does not.
334            Self::Java => call.child_by_field_name("object").is_some(),
335            // Ruby's `call` names its receiver explicitly.
336            Self::Ruby => call.child_by_field_name("receiver").is_some(),
337            _ => false,
338        };
339        if by_call_node {
340            return RefKind::Method;
341        }
342
343        // Otherwise the shape is visible on the callee expression.
344        let callee = match self {
345            Self::Kotlin | Self::Swift => call.named_child(0),
346            Self::Lua => call.child_by_field_name("name"),
347            _ => call.child_by_field_name("function"),
348        };
349        let Some(callee) = callee else {
350            return RefKind::Free;
351        };
352        // Note what is deliberately absent: Rust `scoped_identifier`, C++
353        // `qualified_identifier` and Go's package-qualified `selector_expression`
354        // are paths, not receivers. Go cannot tell `pkg.Func()` from `x.Method()`
355        // at this level, so it stays Free rather than guessing.
356        let member_like = matches!(
357            (self, callee.kind()),
358            (
359                Self::Rust | Self::Scala | Self::C | Self::Cpp,
360                "field_expression"
361            ) | (Self::Python, "attribute")
362                | (
363                    Self::JavaScript | Self::TypeScript | Self::Tsx,
364                    "member_expression"
365                )
366                | (Self::CSharp, "member_access_expression")
367                | (Self::Kotlin | Self::Swift, "navigation_expression")
368                | (
369                    Self::Lua,
370                    "dot_index_expression" | "method_index_expression"
371                )
372        );
373        if member_like {
374            RefKind::Method
375        } else {
376            RefKind::Free
377        }
378    }
379
380    /// The path segment immediately before the callee name, for a path-shaped
381    /// call — `Language` in `Language::from_extension()`, `b` in `a::b::c()`.
382    ///
383    /// Only grammars that make a path *unambiguously* a path are handled. Go's
384    /// `selector_expression` covers both `pkg.Func()` and `x.Method()` with no
385    /// way to tell them apart here, and Java's `object` field likewise covers a
386    /// static call and a receiver call — recording `pkg` or `x` as a qualifier
387    /// would name a value, not a type. Every other language falls through to
388    /// `None`, which is the behaviour it had before this existed (#248).
389    ///
390    /// A [`RefKind::Method`] call never has one: its receiver is a value whose
391    /// type the graph does not know. #251 fills that in where the type is
392    /// syntactically visible.
393    fn callee_qualifier(self, call: Node<'_>, bytes: &[u8]) -> Option<String> {
394        // PHP marks a static call with its own node kind, and puts the scope on
395        // the call node rather than on a nested callee.
396        if self == Self::Php {
397            return (call.kind() == "scoped_call_expression")
398                .then(|| call.child_by_field_name("scope"))
399                .flatten()
400                .and_then(|n| trailing_path_segment(n, bytes));
401        }
402        let (path_kind, scope_field) = match self {
403            Self::Rust => ("scoped_identifier", "path"),
404            Self::Cpp => ("qualified_identifier", "scope"),
405            _ => return None,
406        };
407        let callee = call.child_by_field_name("function")?;
408        if callee.kind() != path_kind {
409            return None;
410        }
411        callee
412            .child_by_field_name(scope_field)
413            .and_then(|n| trailing_path_segment(n, bytes))
414    }
415
416    /// Names `node` brings into this file's scope, if it is an import node.
417    ///
418    /// Recorded as a lexical signal rather than a resolved path: an import says
419    /// which *module* a name came from, which is enough to prefer one candidate
420    /// definition over another without knowing where a crate root is — and so
421    /// without extraction reading any file but this one (ADR 0012, #252).
422    ///
423    /// Rust, Python, Java, Kotlin, JavaScript and TypeScript. Every other
424    /// language records nothing and resolves exactly as it did.
425    fn imports_at(self, node: Node<'_>, bytes: &[u8]) -> Vec<Import> {
426        let line = node.start_position().row + 1;
427        let mut out = Vec::new();
428        match self {
429            Self::Rust if node.kind() == "use_declaration" => {
430                if let Some(argument) = node.child_by_field_name("argument") {
431                    collect_rust_uses(argument, bytes, &[], &mut out);
432                }
433            }
434            // `from pkg.model import Symbol [as Other]`. A plain
435            // `import pkg.model` binds `pkg`, which names no symbol, so it is
436            // left alone.
437            Self::Python if node.kind() == "import_from_statement" => {
438                let module_name = node.child_by_field_name("module_name");
439                // `from ..pkg import X` — the dots say how far up from this
440                // file's own package to anchor, and are the whole reason a
441                // relative import needs no project root (#261).
442                let depth = module_name
443                    .filter(|m| m.kind() == "relative_import")
444                    .and_then(|m| child_of_kind(m, "import_prefix"))
445                    .and_then(|prefix| node_text(prefix, bytes))
446                    .map_or(0, |dots| dots.chars().filter(|c| *c == '.').count());
447                let path = module_name
448                    .map(|m| python_module_segments(m, bytes))
449                    .unwrap_or_default();
450                let mut cursor = node.walk();
451                for child in node.children_by_field_name("name", &mut cursor) {
452                    let name = match child.kind() {
453                        "aliased_import" => child.child_by_field_name("alias"),
454                        _ => Some(child),
455                    }
456                    .and_then(|n| node_text(n, bytes));
457                    if let Some(name) = name {
458                        out.push(Import {
459                            depth,
460                            name: name.to_string(),
461                            path: path.clone(),
462                            line,
463                        });
464                    }
465                }
466            }
467            // `import a.b.C;` and `import static a.b.C.d;` share one shape: the
468            // trailing segment is the name brought into scope — the class in the
469            // first case, the member in the second — and the rest is its path.
470            Self::Java if node.kind() == "import_declaration" => {
471                // `import a.b.*;` introduces names the file never spells out.
472                if child_of_kind(node, "asterisk").is_some() {
473                    return out;
474                }
475                let Some(path_node) = child_of_kind(node, "scoped_identifier")
476                    .or_else(|| child_of_kind(node, "identifier"))
477                else {
478                    return out;
479                };
480                let mut segments = java_path_segments(path_node, bytes);
481                if let Some(name) = segments.pop() {
482                    out.push(Import {
483                        depth: 0,
484                        name,
485                        path: segments,
486                        line,
487                    });
488                }
489            }
490            Self::Kotlin if node.kind() == "import" => {
491                // The grammar drops the `*`, so `import a.b.*` and `import a.b`
492                // parse identically — the source text is the only way to tell
493                // them apart.
494                if node_text(node, bytes).is_some_and(|t| t.trim_end().ends_with(".*")) {
495                    return out;
496                }
497                let Some(qualified) = child_of_kind(node, "qualified_identifier") else {
498                    return out;
499                };
500                let mut segments: Vec<String> = Vec::new();
501                let mut cursor = qualified.walk();
502                for child in qualified.named_children(&mut cursor) {
503                    if let Some(text) = node_text(child, bytes) {
504                        segments.push(text.to_string());
505                    }
506                }
507                // `import a.b.Thing as Other` puts the alias in a bare
508                // identifier beside the qualified name; it is the name this
509                // file will actually write.
510                let mut outer = node.walk();
511                let alias = node
512                    .named_children(&mut outer)
513                    .find(|c| c.kind() == "identifier")
514                    .and_then(|n| node_text(n, bytes))
515                    .map(str::to_string);
516                let name = match alias {
517                    Some(alias) => {
518                        segments.pop();
519                        Some(alias)
520                    }
521                    None => segments.pop(),
522                };
523                if let Some(name) = name {
524                    out.push(Import {
525                        depth: 0,
526                        name,
527                        path: segments,
528                        line,
529                    });
530                }
531            }
532            // `#include "a/b.h"` names a *file*, and every declaration in it
533            // becomes visible — a stronger signal than any package path,
534            // because the file is the thing rather than a convention a path is
535            // assumed to mirror. Recorded with no name; resolution matches it
536            // by path (#267).
537            Self::C | Self::Cpp if node.kind() == "preproc_include" => {
538                let path = node
539                    .child_by_field_name("path")
540                    .and_then(|p| node_text(p, bytes))
541                    .map(include_segments)
542                    .unwrap_or_default();
543                if !path.is_empty() {
544                    out.push(Import {
545                        depth: 0,
546                        name: String::new(),
547                        path,
548                        line,
549                    });
550                }
551            }
552            // CommonJS: `const fs = require('fs')` and its destructuring
553            // forms. The module system for a large amount of Node code, and
554            // invisible until now (#269).
555            Self::JavaScript | Self::TypeScript | Self::Tsx
556                if node.kind() == "variable_declarator" =>
557            {
558                let Some(path) = require_source(node, bytes) else {
559                    return out;
560                };
561                for name in require_bound_names(node, bytes) {
562                    out.push(Import {
563                        depth: 0,
564                        name,
565                        path: path.clone(),
566                        line,
567                    });
568                }
569            }
570            Self::JavaScript | Self::TypeScript | Self::Tsx
571                if node.kind() == "import_statement" =>
572            {
573                let path = node
574                    .child_by_field_name("source")
575                    .and_then(|s| node_text(s, bytes))
576                    .map(js_module_segments)
577                    .unwrap_or_default();
578                collect_js_import_names(node, bytes, &path, line, &mut out);
579            }
580            _ => {}
581        }
582        out
583    }
584
585    /// The name a module-level binding gives to the calls in its initializer,
586    /// when that binding is not itself a function.
587    ///
588    /// `const schema = z.object({..})` and a handler keyed in a router object
589    /// both name their contents; #257 correctly refuses to call them functions,
590    /// which left every call inside them with no caller at all (#268).
591    ///
592    /// JS/TS only. Every other language attributes exactly as it did — Python's
593    /// module-level tables and Rust's `static` initializers are the same shape
594    /// and worth revisiting, but each needs its own evidence.
595    fn module_binding_name(self, node: Node<'_>, bytes: &[u8]) -> Option<String> {
596        if !matches!(self, Self::JavaScript | Self::TypeScript | Self::Tsx) {
597            return None;
598        }
599        if !matches!(
600            node.kind(),
601            "variable_declarator" | "pair" | "public_field_definition"
602        ) {
603            return None;
604        }
605        // A binding that *is* a function already became a symbol and set the
606        // enclosing name itself; this is only for the ones that did not.
607        let value = node.child_by_field_name("value")?;
608        if js_binds_a_function(value, bytes, 3) {
609            return None;
610        }
611        name_field(node, bytes).or_else(|| {
612            node.child_by_field_name("key")
613                .and_then(|n| node_text(n, bytes))
614                .map(str::to_string)
615        })
616    }
617
618    /// Bare names appearing in a call's argument list, as `(name, line)` —
619    /// `helper` in `and_then(helper)`.
620    ///
621    /// A function used as a value is a path expression, not a call expression,
622    /// so it records no edge at all. A function only ever passed as a callback
623    /// therefore looked unused, and `unreferenced` reported it as deletable
624    /// (#250).
625    ///
626    /// Only the argument list's own children are inspected, one wrapper deep. A
627    /// nested call is its own node and [`walk`] reaches it separately, so
628    /// nothing is counted twice, and the callee itself lives in a different
629    /// field so it is never picked up here.
630    ///
631    /// Deliberately over-inclusive: extraction is per-file, so it cannot tell an
632    /// identifier naming a function from one naming a local, and records both.
633    /// See [`RefKind::Value`] for why that is the safe direction.
634    fn value_args(self, call: Node<'_>, bytes: &[u8]) -> Vec<(String, usize)> {
635        let Some(args) = call.child_by_field_name("arguments") else {
636            return Vec::new();
637        };
638        let is_ident = |node: &Node<'_>| matches!(node.kind(), "identifier" | "simple_identifier");
639        let mut out = Vec::new();
640        let mut cursor = args.walk();
641        for child in args.named_children(&mut cursor) {
642            // Some grammars wrap each argument (C#'s and PHP's `argument`), and
643            // `&helper` wraps the name it borrows. Look one level in, never
644            // further: a deeper expression is not a bare name.
645            let leaf = if is_ident(&child) {
646                Some(child)
647            } else if child.named_child_count() == 1 {
648                child.named_child(0).filter(is_ident)
649            } else {
650                None
651            };
652            if let Some(leaf) = leaf
653                && let Some(name) = node_text(leaf, bytes)
654            {
655                out.push((name.to_string(), leaf.start_position().row + 1));
656            }
657        }
658        out
659    }
660
661    /// Calls hidden inside an opaque macro-argument node, as `(name, line)`.
662    ///
663    /// Rust macro arguments parse as a `token_tree` of raw tokens rather than
664    /// expressions, so `assert_eq!(f(), 1)` contains no `call_expression` and
665    /// the call to `f` is invisible to [`is_call`](Self::is_call). Since
666    /// assertions are where much of a codebase is exercised, that silently
667    /// removed a large share of the call graph (#216).
668    ///
669    /// Inside a token tree a call is an `identifier` whose immediate next
670    /// sibling is another `token_tree` — `f` followed by `()`. A nested macro
671    /// (`matches!(..)`) has a `!` between the two, so it is naturally excluded.
672    /// Each token tree inspects only its own direct children, and [`walk`]
673    /// recurses into nested trees, so nothing is counted twice.
674    ///
675    /// This is a token-level heuristic, not type resolution: a token tree is not
676    /// type-checked, so a tuple-struct pattern like `Some(_)` reads as a call.
677    /// That matches the base graph, which already records constructors and enum
678    /// variants as calls.
679    fn macro_arg_calls(self, node: Node<'_>, bytes: &[u8]) -> Vec<(String, usize)> {
680        if self != Self::Rust || node.kind() != "token_tree" {
681            return Vec::new();
682        }
683        let mut out = Vec::new();
684        let mut cursor = node.walk();
685        for child in node.children(&mut cursor) {
686            if child.kind() != "identifier" {
687                continue;
688            }
689            let Some(next) = child.next_sibling() else {
690                continue;
691            };
692            // Only a parenthesised tree is an argument list; `vec![..]`'s own
693            // brackets belong to the macro, not to a call.
694            if next.kind() != "token_tree"
695                || !next.utf8_text(bytes).is_ok_and(|t| t.starts_with('('))
696            {
697                continue;
698            }
699            if let Some(name) = node_text(child, bytes) {
700                out.push((name.to_string(), child.start_position().row + 1));
701            }
702        }
703        out
704    }
705
706    /// The called name from a call node's `function` field.
707    fn call_name(self, func: Node<'_>, bytes: &[u8]) -> Option<String> {
708        match self {
709            Self::Rust => match func.kind() {
710                "identifier" => node_text(func, bytes).map(str::to_string),
711                // a::b::c -> the `name` field (last segment)
712                "scoped_identifier" => func
713                    .child_by_field_name("name")
714                    .and_then(|n| node_text(n, bytes))
715                    .map(str::to_string),
716                // x.method(...) -> the `field` field
717                "field_expression" => func
718                    .child_by_field_name("field")
719                    .and_then(|n| node_text(n, bytes))
720                    .map(str::to_string),
721                _ => node_text(func, bytes).map(str::to_string),
722            },
723            Self::Python => match func.kind() {
724                "identifier" => node_text(func, bytes).map(str::to_string),
725                // obj.method(...) -> the `attribute` field (method name)
726                "attribute" => func
727                    .child_by_field_name("attribute")
728                    .and_then(|n| node_text(n, bytes))
729                    .map(str::to_string),
730                _ => node_text(func, bytes).map(str::to_string),
731            },
732            Self::JavaScript | Self::TypeScript | Self::Tsx => match func.kind() {
733                "identifier" => node_text(func, bytes).map(str::to_string),
734                // obj.method(...) -> the member expression's `property` field
735                "member_expression" => func
736                    .child_by_field_name("property")
737                    .and_then(|n| node_text(n, bytes))
738                    .map(str::to_string),
739                _ => node_text(func, bytes).map(str::to_string),
740            },
741            Self::Go => match func.kind() {
742                "identifier" => node_text(func, bytes).map(str::to_string),
743                // pkg.Func(...) / x.Method(...) -> the selector's `field`.
744                "selector_expression" => func
745                    .child_by_field_name("field")
746                    .and_then(|n| node_text(n, bytes))
747                    .map(str::to_string),
748                _ => node_text(func, bytes).map(str::to_string),
749            },
750            Self::CSharp => match func.kind() {
751                "identifier" => node_text(func, bytes).map(str::to_string),
752                // obj.Method(...) -> the member access's `name` field.
753                "member_access_expression" => func
754                    .child_by_field_name("name")
755                    .and_then(|n| node_text(n, bytes))
756                    .map(str::to_string),
757                _ => node_text(func, bytes).map(str::to_string),
758            },
759            Self::C | Self::Cpp => match func.kind() {
760                "identifier" => node_text(func, bytes).map(str::to_string),
761                // x.m(...) / x->m(...) -> the field expression's `field`.
762                "field_expression" => func
763                    .child_by_field_name("field")
764                    .and_then(|n| node_text(n, bytes))
765                    .map(str::to_string),
766                // C++ `Ns::func(...)` -> the qualified id's `name` (last segment).
767                "qualified_identifier" => func
768                    .child_by_field_name("name")
769                    .and_then(|n| node_text(n, bytes))
770                    .map(str::to_string),
771                _ => node_text(func, bytes).map(str::to_string),
772            },
773            // Scala `call_expression` holds the callee in a `function` field — a
774            // bare `identifier` (`helper(..)`) or a `field_expression`
775            // (`obj.method(..)`); take the trailing identifier.
776            Self::Scala => last_identifier(func, bytes),
777            // PHP `function_call_expression` holds the callee in a `function`
778            // field — a `name` (or `qualified_name`) node; take its text.
779            Self::Php => node_text(func, bytes).map(str::to_string),
780            // Java, Ruby, Bash, Kotlin, Swift, and Lua route through `callee_name`
781            // (their callee is a dedicated field/child on the call node, not a
782            // nested `function` node); these arms only keep the match exhaustive.
783            Self::Java
784            | Self::Ruby
785            | Self::Bash
786            | Self::Kotlin
787            | Self::Swift
788            | Self::Lua
789            | Self::Elixir => node_text(func, bytes).map(str::to_string),
790        }
791    }
792
793    /// The called name from a call node. Most languages hold the callee in a
794    /// `function` field (dispatched by [`call_name`]); Java's `method_invocation`
795    /// instead carries the method name directly in its `name` field.
796    fn callee_name(self, call: Node<'_>, bytes: &[u8]) -> Option<String> {
797        match self {
798            // Java's `method_invocation` and Bash's `command` carry the callee in
799            // a `name` field (an identifier / a `command_name` node).
800            Self::Java | Self::Bash => call
801                .child_by_field_name("name")
802                .and_then(|n| node_text(n, bytes))
803                .map(str::to_string),
804            // Ruby's `call` names the callee in a `method` field.
805            Self::Ruby => call
806                .child_by_field_name("method")
807                .and_then(|n| node_text(n, bytes))
808                .map(str::to_string),
809            // Kotlin and Swift `call_expression` have no field; the callee is the
810            // first named child — an `identifier`/`simple_identifier`
811            // (`helper(..)`) or a navigation/member expression (`a.b.method(..)`),
812            // whose trailing identifier is the invoked member.
813            Self::Kotlin | Self::Swift => call
814                .named_child(0)
815                .and_then(|callee| last_identifier(callee, bytes)),
816            // Lua's `function_call` names the callee in a `name` field — an
817            // `identifier` (`helper(..)`) or a dotted/method index (`m.f`/`o:m`),
818            // whose trailing identifier is the invoked function.
819            Self::Lua => call
820                .child_by_field_name("name")
821                .and_then(|n| last_identifier(n, bytes)),
822            // PHP: plain calls carry the callee in a `function` field (a
823            // `name`/`qualified_name`); method (`$x->m()`) and static (`A::b()`)
824            // calls carry the invoked member in a `name` field.
825            Self::Php => match call.kind() {
826                "function_call_expression" => call
827                    .child_by_field_name("function")
828                    .and_then(|func| self.call_name(func, bytes)),
829                _ => call
830                    .child_by_field_name("name")
831                    .and_then(|n| node_text(n, bytes))
832                    .map(str::to_string),
833            },
834            // Elixir: every `call` carries its callee in a `target` field. A
835            // definition call (`def`/`defp`/`defmacro`/`defmacrop`/`defmodule`)
836            // and a definition *head* (`add(a, b)` in `def add(a, b)`) are not
837            // references; every other call is, keyed by the target's trailing
838            // identifier (`helper` for `helper(..)`, `add` for `Mod.add(..)`).
839            Self::Elixir => match elixir_target_name(call, bytes) {
840                Some(name)
841                    if !matches!(
842                        name.as_str(),
843                        "def" | "defp" | "defmacro" | "defmacrop" | "defmodule"
844                    ) && !elixir_is_def_head(call, bytes) =>
845                {
846                    Some(name)
847                }
848                _ => None,
849            },
850            _ => call
851                .child_by_field_name("function")
852                .and_then(|func| self.call_name(func, bytes)),
853        }
854    }
855}
856
857/// The last `identifier`/`simple_identifier` in `node`'s subtree (depth-first).
858/// For a Kotlin/Swift callee that is a bare identifier this is the node itself;
859/// for a navigation/member expression (`a.b.method`) it is the trailing member.
860fn last_identifier(node: Node<'_>, bytes: &[u8]) -> Option<String> {
861    let mut result = if matches!(node.kind(), "identifier" | "simple_identifier") {
862        node_text(node, bytes).map(str::to_string)
863    } else {
864        None
865    };
866    let mut cursor = node.walk();
867    for child in node.children(&mut cursor) {
868        if let Some(name) = last_identifier(child, bytes) {
869            result = Some(name);
870        }
871    }
872    result
873}
874
875/// The trailing segment of a path node: `b` for `a::b`, `Language` for a bare
876/// `Language`. A nested path names its own last segment in a `name` field; a
877/// leaf is its own text (#248).
878fn trailing_path_segment(node: Node<'_>, bytes: &[u8]) -> Option<String> {
879    node.child_by_field_name("name")
880        .and_then(|n| node_text(n, bytes))
881        .or_else(|| node_text(node, bytes))
882        .map(str::to_string)
883}
884
885/// The name a call site would use to qualify a member of this item: the base
886/// type, without generic arguments and without a leading path. `impl<T>
887/// Holder<T>` owns `Holder`, and `impl crate::model::Widget` owns `Widget`.
888///
889/// A call is written `Holder::get()`, never `Holder<T>::get()`, and its
890/// qualifier is a single segment, so the stored owner has to be reduced to the
891/// same shape or it could never match (#248).
892fn owner_key(name: &str) -> String {
893    let base = name.split('<').next().unwrap_or(name).trim();
894    base.rsplit("::").next().unwrap_or(base).trim().to_string()
895}
896
897/// Whether a symbol of this kind owns the symbols defined inside it, so a
898/// method records the type it hangs off and a function in an inline module
899/// records the module (#248).
900fn establishes_ownership(kind: SymbolKind) -> bool {
901    matches!(
902        kind,
903        SymbolKind::Impl
904            | SymbolKind::Class
905            | SymbolKind::Interface
906            | SymbolKind::Trait
907            | SymbolKind::Struct
908            | SymbolKind::Enum
909            | SymbolKind::Module
910    )
911}
912
913/// The first named child of `node` whose kind is `kind`, if any.
914fn child_of_kind<'a>(node: Node<'a>, kind: &str) -> Option<Node<'a>> {
915    let mut cursor = node.walk();
916    node.children(&mut cursor).find(|c| c.kind() == kind)
917}
918
919/// The trailing identifier text of an Elixir `call` node's `target` — a bare
920/// `identifier` (`helper(..)`) or the `right` member of a `dot` (`Mod.fun(..)`).
921/// `None` when `node` is not a call (no `target` field).
922fn elixir_target_name(node: Node<'_>, bytes: &[u8]) -> Option<String> {
923    let target = node.child_by_field_name("target")?;
924    last_identifier(target, bytes)
925}
926
927/// The name defined by an Elixir definition call. The signature is the first
928/// argument: a nested `call` (`def add(a, b)` → `add`), a bare `identifier`
929/// (`def run` → `run`), or an `alias` (`defmodule Math` → `Math`).
930fn elixir_defined_name(node: Node<'_>, bytes: &[u8]) -> Option<String> {
931    let head = child_of_kind(node, "arguments")?.named_child(0)?;
932    match head.kind() {
933        "call" => elixir_target_name(head, bytes),
934        _ => last_identifier(head, bytes).or_else(|| node_text(head, bytes).map(str::to_string)),
935    }
936}
937
938/// Whether an Elixir `call` is the *head* of a definition — the first argument
939/// of a `def`/`defp`/`defmacro`/`defmacrop`/`defmodule` call (e.g. `add(a, b)`
940/// in `def add(a, b)`). Such a head names the defined symbol, not a call.
941fn elixir_is_def_head(node: Node<'_>, bytes: &[u8]) -> bool {
942    let Some(args) = node.parent().filter(|p| p.kind() == "arguments") else {
943        return false;
944    };
945    if args.named_child(0).map(|h| h.id()) != Some(node.id()) {
946        return false;
947    }
948    let Some(def_call) = args.parent().filter(|g| g.kind() == "call") else {
949        return false;
950    };
951    matches!(
952        elixir_target_name(def_call, bytes).as_deref(),
953        Some("def" | "defp" | "defmacro" | "defmacrop" | "defmodule")
954    )
955}
956
957/// JavaScript declaration node kinds shared by JS and TS/TSX.
958fn js_item_kind(node: Node<'_>, bytes: &[u8]) -> Option<SymbolKind> {
959    match node.kind() {
960        "function_declaration" | "generator_function_declaration" | "method_definition" => {
961            Some(SymbolKind::Function)
962        }
963        "class_declaration" | "abstract_class_declaration" => Some(SymbolKind::Class),
964        // `const foo = () => {}`, class-field `foo = () => {}`, object property
965        // `{ foo: () => {} }`, and a function wrapped in a higher-order call —
966        // `const C = React.memo(() => {})`. A binding whose value is, or wraps,
967        // a function literal is a named function (#257).
968        "variable_declarator" | "public_field_definition" | "pair" => node
969            .child_by_field_name("value")
970            .filter(|value| js_binds_a_function(*value, bytes, 3))
971            .map(|_| SymbolKind::Function),
972        _ => None,
973    }
974}
975
976/// Whether a JS/TS initializer is, or wraps, a function literal.
977///
978/// Direct for `() => {}` and `function () {}`. Through a higher-order call for
979/// `memo(() => {})` and `memo(forwardRef(() => {}))`, the idiom that dominates
980/// React code and that used to leave whole component files with no symbols at
981/// all (#257).
982///
983/// The wrapper must be called on a **bare identifier** (`memo`, `forwardRef`,
984/// `styled`) or on a **capitalized namespace** (`React.memo`). That is what
985/// keeps `items.map(x => f(x))` out: an iterator method returns an array, not a
986/// function, and treating `const total = items.map(..)` as a function would
987/// attribute everything inside the lambda to `total` instead of to the function
988/// actually containing it — trading a missing answer for a wrong one.
989fn js_binds_a_function(node: Node<'_>, bytes: &[u8], depth: usize) -> bool {
990    match node.kind() {
991        "arrow_function" | "function_expression" | "generator_function" => true,
992        "call_expression" if depth > 0 && js_is_wrapper_call(node, bytes) => {
993            node.child_by_field_name("arguments").is_some_and(|args| {
994                let mut cursor = args.walk();
995                args.named_children(&mut cursor)
996                    .any(|arg| js_binds_a_function(arg, bytes, depth - 1))
997            })
998        }
999        _ => false,
1000    }
1001}
1002
1003/// Whether a JS/TS call looks like a wrapper rather than a method on a value.
1004fn js_is_wrapper_call(call: Node<'_>, bytes: &[u8]) -> bool {
1005    let Some(callee) = call.child_by_field_name("function") else {
1006        return false;
1007    };
1008    match callee.kind() {
1009        "identifier" => true,
1010        // `React.memo` — a capitalized object is a namespace by convention,
1011        // where `items.map` is a value.
1012        "member_expression" => callee
1013            .child_by_field_name("object")
1014            .filter(|object| object.kind() == "identifier")
1015            .and_then(|object| node_text(object, bytes))
1016            .is_some_and(|name| name.starts_with(char::is_uppercase)),
1017        _ => false,
1018    }
1019}
1020
1021/// Swift `class_declaration` keyword (`struct`/`enum`/`actor`/`class`) → kind.
1022/// `actor` (a reference type) and `class` both read as Class.
1023fn swift_type_kind(node: Node<'_>) -> SymbolKind {
1024    match node
1025        .child_by_field_name("declaration_kind")
1026        .map(|k| k.kind())
1027    {
1028        Some("struct") => SymbolKind::Struct,
1029        Some("enum") => SymbolKind::Enum,
1030        _ => SymbolKind::Class,
1031    }
1032}
1033
1034/// Kotlin `class_declaration` is an `interface` when it has a leading
1035/// `interface` keyword child; otherwise a `class`.
1036fn kotlin_class_kind(node: Node<'_>) -> SymbolKind {
1037    let mut cursor = node.walk();
1038    if node.children(&mut cursor).any(|c| c.kind() == "interface") {
1039        SymbolKind::Interface
1040    } else {
1041        SymbolKind::Class
1042    }
1043}
1044
1045/// C declaration node kinds shared by C and C++ (C++ adds classes/namespaces).
1046fn c_item_kind(node_kind: &str) -> Option<SymbolKind> {
1047    match node_kind {
1048        "function_definition" => Some(SymbolKind::Function),
1049        "struct_specifier" => Some(SymbolKind::Struct),
1050        "enum_specifier" => Some(SymbolKind::Enum),
1051        "type_definition" => Some(SymbolKind::TypeAlias),
1052        _ => None,
1053    }
1054}
1055
1056/// Extract the identifier from a C/C++ `declarator` chain: descend the nested
1057/// `declarator` field (through pointer/function/parenthesized declarators) until
1058/// an identifier-like leaf is reached. Anonymous declarators yield `None`.
1059fn c_declarator_name(node: Node<'_>, bytes: &[u8]) -> Option<String> {
1060    let mut n = node;
1061    loop {
1062        if matches!(
1063            n.kind(),
1064            "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier"
1065        ) {
1066            return node_text(n, bytes).map(str::to_string);
1067        }
1068        n = n.child_by_field_name("declarator")?;
1069    }
1070}
1071
1072/// Parse `src` as `language` into a **path-agnostic** slice: its symbols and
1073/// name-based references, with no file path (ADR 0012). The path is supplied
1074/// later at assembly from the manifest.
1075pub fn build(language: Language, src: &str) -> CodeGraph {
1076    let mut parser = Parser::new();
1077    if parser.set_language(&language.ts_language()).is_err() {
1078        return CodeGraph::default();
1079    }
1080    let Some(tree) = parser.parse(src, None) else {
1081        return CodeGraph::default();
1082    };
1083    let mut graph = CodeGraph::default();
1084    walk(
1085        language,
1086        tree.root_node(),
1087        src.as_bytes(),
1088        Inherited {
1089            enclosing: None,
1090            scope: FromScope::Function,
1091            owner: None,
1092            receivers: &BTreeMap::new(),
1093        },
1094        &mut graph,
1095    );
1096    graph
1097}
1098
1099/// Parse Rust source. Back-compatible shorthand for `build(Language::Rust, src)`.
1100pub fn build_rust(src: &str) -> CodeGraph {
1101    build(Language::Rust, src)
1102}
1103
1104fn node_text<'a>(node: Node<'_>, bytes: &'a [u8]) -> Option<&'a str> {
1105    node.utf8_text(bytes).ok()
1106}
1107
1108fn name_field(node: Node<'_>, bytes: &[u8]) -> Option<String> {
1109    node.child_by_field_name("name")
1110        .and_then(|n| node_text(n, bytes))
1111        .map(str::to_string)
1112}
1113
1114/// Local names a JS/TS import clause binds, under module path `path`.
1115fn collect_js_import_names(
1116    node: Node<'_>,
1117    bytes: &[u8],
1118    path: &[String],
1119    line: usize,
1120    out: &mut Vec<Import>,
1121) {
1122    let mut cursor = node.walk();
1123    for child in node.named_children(&mut cursor) {
1124        match child.kind() {
1125            // `import { A, B as C } from '...'` — the specifier's alias wins.
1126            "import_specifier" => {
1127                let name = child
1128                    .child_by_field_name("alias")
1129                    .or_else(|| child.child_by_field_name("name"))
1130                    .and_then(|n| node_text(n, bytes));
1131                if let Some(name) = name {
1132                    out.push(Import {
1133                        depth: 0,
1134                        name: name.to_string(),
1135                        path: path.to_vec(),
1136                        line,
1137                    });
1138                }
1139            }
1140            // `import Default from '...'`.
1141            "identifier" => {
1142                if let Some(name) = node_text(child, bytes) {
1143                    out.push(Import {
1144                        depth: 0,
1145                        name: name.to_string(),
1146                        path: path.to_vec(),
1147                        line,
1148                    });
1149                }
1150            }
1151            // `import_clause`, `named_imports` — containers; look inside.
1152            "import_clause" | "named_imports" => {
1153                collect_js_import_names(child, bytes, path, line, out);
1154            }
1155            _ => {}
1156        }
1157    }
1158}
1159
1160/// The module segments a Rust path node names, with relative markers dropped:
1161/// `crate::model` is `["model"]`, `a::b` is `["a", "b"]`.
1162fn rust_path_segments(node: Node<'_>, bytes: &[u8]) -> Vec<String> {
1163    match node.kind() {
1164        "scoped_identifier" => {
1165            let mut segments = node
1166                .child_by_field_name("path")
1167                .map(|p| rust_path_segments(p, bytes))
1168                .unwrap_or_default();
1169            if let Some(name) = node
1170                .child_by_field_name("name")
1171                .and_then(|n| node_text(n, bytes))
1172            {
1173                segments.push(name.to_string());
1174            }
1175            segments
1176        }
1177        // Relative markers position the path; they name no module (#252).
1178        "crate" | "self" | "super" => Vec::new(),
1179        _ => node_text(node, bytes)
1180            .filter(|t| !t.contains(':'))
1181            .map(|t| vec![t.to_string()])
1182            .unwrap_or_default(),
1183    }
1184}
1185
1186/// Names a Rust `use` tree brings into scope, under `prefix`.
1187///
1188/// A glob (`use a::*`) contributes nothing: it introduces names this file never
1189/// spells out, so there is no name to key on.
1190fn collect_rust_uses(node: Node<'_>, bytes: &[u8], prefix: &[String], out: &mut Vec<Import>) {
1191    let line = node.start_position().row + 1;
1192    match node.kind() {
1193        "scoped_identifier" => {
1194            let mut segments = rust_path_segments(node, bytes);
1195            if let Some(name) = segments.pop() {
1196                let mut path = prefix.to_vec();
1197                path.append(&mut segments);
1198                out.push(Import {
1199                    depth: 0,
1200                    name,
1201                    path,
1202                    line,
1203                });
1204            }
1205        }
1206        "use_as_clause" => {
1207            let Some(alias) = node
1208                .child_by_field_name("alias")
1209                .and_then(|n| node_text(n, bytes))
1210            else {
1211                return;
1212            };
1213            let mut segments = node
1214                .child_by_field_name("path")
1215                .map(|p| rust_path_segments(p, bytes))
1216                .unwrap_or_default();
1217            segments.pop(); // the original name; the alias replaces it
1218            let mut path = prefix.to_vec();
1219            path.append(&mut segments);
1220            out.push(Import {
1221                depth: 0,
1222                name: alias.to_string(),
1223                path,
1224                line,
1225            });
1226        }
1227        "scoped_use_list" => {
1228            let mut path = prefix.to_vec();
1229            if let Some(inner) = node.child_by_field_name("path") {
1230                path.extend(rust_path_segments(inner, bytes));
1231            }
1232            if let Some(list) = node.child_by_field_name("list") {
1233                collect_rust_uses(list, bytes, &path, out);
1234            }
1235        }
1236        "use_list" => {
1237            let mut cursor = node.walk();
1238            for child in node.named_children(&mut cursor) {
1239                collect_rust_uses(child, bytes, prefix, out);
1240            }
1241        }
1242        "use_wildcard" => {}
1243        // A bare `use foo;`.
1244        "identifier" => {
1245            if let Some(name) = node_text(node, bytes) {
1246                out.push(Import {
1247                    depth: 0,
1248                    name: name.to_string(),
1249                    path: prefix.to_vec(),
1250                    line,
1251                });
1252            }
1253        }
1254        _ => {}
1255    }
1256}
1257
1258/// The dotted segments a Java `scoped_identifier` names, outermost first.
1259/// Same shape as Rust's, under different field names (`scope`/`name`).
1260fn java_path_segments(node: Node<'_>, bytes: &[u8]) -> Vec<String> {
1261    match node.kind() {
1262        "scoped_identifier" => {
1263            let mut segments = node
1264                .child_by_field_name("scope")
1265                .map(|s| java_path_segments(s, bytes))
1266                .unwrap_or_default();
1267            if let Some(name) = node
1268                .child_by_field_name("name")
1269                .and_then(|n| node_text(n, bytes))
1270            {
1271                segments.push(name.to_string());
1272            }
1273            segments
1274        }
1275        _ => node_text(node, bytes)
1276            .filter(|t| !t.contains('.'))
1277            .map(|t| vec![t.to_string()])
1278            .unwrap_or_default(),
1279    }
1280}
1281
1282/// The module segments a Python dotted name or relative import names.
1283fn python_module_segments(node: Node<'_>, bytes: &[u8]) -> Vec<String> {
1284    let mut out = Vec::new();
1285    let mut cursor = node.walk();
1286    for child in node.named_children(&mut cursor) {
1287        match child.kind() {
1288            "identifier" => {
1289                if let Some(text) = node_text(child, bytes) {
1290                    out.push(text.to_string());
1291                }
1292            }
1293            // `from .model import X` nests the dotted name under the relative
1294            // import; the leading dots are a marker, not a module.
1295            "dotted_name" => out.extend(python_module_segments(child, bytes)),
1296            _ => {}
1297        }
1298    }
1299    out
1300}
1301
1302/// The module segments a `require(...)` initializer names, or `None` when the
1303/// declarator is not a require or its argument is not a literal path.
1304///
1305/// A computed `require(name)` names no module, so it records nothing — the same
1306/// reasoning as a glob import, which introduces names the file never spells out.
1307fn require_source(declarator: Node<'_>, bytes: &[u8]) -> Option<Vec<String>> {
1308    let value = declarator.child_by_field_name("value")?;
1309    if value.kind() != "call_expression" {
1310        return None;
1311    }
1312    let callee = value.child_by_field_name("function")?;
1313    if callee.kind() != "identifier" || node_text(callee, bytes) != Some("require") {
1314        return None;
1315    }
1316    let argument = value.child_by_field_name("arguments")?.named_child(0)?;
1317    if argument.kind() != "string" {
1318        return None;
1319    }
1320    let segments = js_module_segments(node_text(argument, bytes)?);
1321    (!segments.is_empty()).then_some(segments)
1322}
1323
1324/// The local names a `require` declarator binds: one for `const fs = ...`, one
1325/// per property for `const { a, b } = ...`, and the *local* half of a rename.
1326fn require_bound_names(declarator: Node<'_>, bytes: &[u8]) -> Vec<String> {
1327    let Some(pattern) = declarator.child_by_field_name("name") else {
1328        return Vec::new();
1329    };
1330    match pattern.kind() {
1331        "identifier" => node_text(pattern, bytes)
1332            .map(|name| vec![name.to_string()])
1333            .unwrap_or_default(),
1334        "object_pattern" => {
1335            let mut out = Vec::new();
1336            let mut cursor = pattern.walk();
1337            for child in pattern.named_children(&mut cursor) {
1338                let named = match child.kind() {
1339                    "shorthand_property_identifier_pattern" => Some(child),
1340                    // `{ a: c }` — `c` is what this file writes.
1341                    "pair_pattern" => child.child_by_field_name("value"),
1342                    _ => None,
1343                };
1344                if let Some(name) = named.and_then(|n| node_text(n, bytes)) {
1345                    out.push(name.to_string());
1346                }
1347            }
1348            out
1349        }
1350        _ => Vec::new(),
1351    }
1352}
1353
1354/// The path segments a C/C++ include names: `"engine/render/pipeline.h"` is
1355/// `["engine", "render", "pipeline"]`, `<vector>` is `["vector"]`.
1356///
1357/// The extension is dropped so an include of a header matches the translation
1358/// unit that defines what it declares — a prototype in a `.h` is not a symbol,
1359/// the definition in the `.c` is, and both live at the same path stem (#267).
1360fn include_segments(raw: &str) -> Vec<String> {
1361    raw.trim_matches(|c| c == '"' || c == '<' || c == '>')
1362        .split('/')
1363        .filter(|part| !matches!(*part, "" | "." | ".."))
1364        .map(|part| part.rsplit_once('.').map_or(part, |(stem, _)| stem))
1365        .filter(|part| !part.is_empty())
1366        .map(str::to_string)
1367        .collect()
1368}
1369
1370/// The module segments a JS/TS import source string names: `'./model'` is
1371/// `["model"]`, `'pkg/sub/model.js'` is `["pkg", "sub", "model"]`.
1372///
1373/// A bare `@` is a tsconfig path alias rooted at the project (`@/lib/utils`),
1374/// so like `.` and `..` it positions the path rather than naming a directory.
1375/// A *scoped package* leads with `@scope`, which is a real name and survives.
1376fn js_module_segments(raw: &str) -> Vec<String> {
1377    raw.trim_matches(|c| c == '\'' || c == '"' || c == '`')
1378        .split('/')
1379        .filter(|part| !matches!(*part, "" | "." | ".." | "@" | "~"))
1380        .map(|part| part.rsplit_once('.').map_or(part, |(stem, _)| stem))
1381        .filter(|part| !part.is_empty())
1382        .map(str::to_string)
1383        .collect()
1384}
1385
1386/// The base type a Rust type node names, or `None` when it needs inference:
1387/// `Widget` for `Widget`, `&Widget`, `&mut Widget` and `a::b::Widget`, `Vec` for
1388/// `Vec<T>`. `impl Trait`, `dyn Trait` and tuples yield nothing (#251).
1389fn rust_type_name(node: Node<'_>, bytes: &[u8]) -> Option<String> {
1390    match node.kind() {
1391        "type_identifier" => node_text(node, bytes).map(str::to_string),
1392        "reference_type" => node
1393            .child_by_field_name("type")
1394            .and_then(|t| rust_type_name(t, bytes)),
1395        // `generic_type` names its base in `type`; `scoped_type_identifier`
1396        // names its last segment in `name`.
1397        "generic_type" | "scoped_type_identifier" => node
1398            .child_by_field_name("type")
1399            .or_else(|| node.child_by_field_name("name"))
1400            .and_then(|t| rust_type_name(t, bytes)),
1401        _ => None,
1402    }
1403}
1404
1405/// The type a Rust initializer expression obviously produces, syntactically:
1406/// `Widget::new(..)` and `Widget { .. }` both name `Widget`.
1407///
1408/// A plain call (`make()`) names nothing without a return type, and a chain
1409/// (`Widget::new().wrap()`) is deliberately not followed — the last call decides
1410/// the type and the graph cannot know it (#251).
1411fn rust_value_type(node: Node<'_>, bytes: &[u8]) -> Option<String> {
1412    match node.kind() {
1413        "call_expression" => {
1414            let func = node.child_by_field_name("function")?;
1415            (func.kind() == "scoped_identifier")
1416                .then(|| func.child_by_field_name("path"))
1417                .flatten()
1418                .and_then(|path| trailing_path_segment(path, bytes))
1419        }
1420        "struct_expression" => node
1421            .child_by_field_name("name")
1422            .and_then(|n| rust_type_name(n, bytes)),
1423        "reference_expression" => node
1424            .child_by_field_name("value")
1425            .and_then(|v| rust_value_type(v, bytes)),
1426        _ => None,
1427    }
1428}
1429
1430/// Type parameters declared on a Rust function (`fn g<T: Tr>`).
1431///
1432/// A receiver typed `T` names no type in the view, and recording it would
1433/// invite the resolver to pick an impl — the guess #223 removed. Excluded so
1434/// those receivers keep declining.
1435fn rust_type_parameters(func: Node<'_>, bytes: &[u8]) -> BTreeSet<String> {
1436    let mut out = BTreeSet::new();
1437    let Some(params) = func.child_by_field_name("type_parameters") else {
1438        return out;
1439    };
1440    let mut cursor = params.walk();
1441    for child in params.named_children(&mut cursor) {
1442        // `T`, `T: Bound`, `T = Default`, `T: A + B` all lead with the
1443        // parameter's own name, so the first `type_identifier` in the subtree is
1444        // it. Lifetimes and const parameters have none and drop out.
1445        let name = first_type_identifier(child, bytes);
1446        if let Some(name) = name {
1447            out.insert(name);
1448        }
1449    }
1450    out
1451}
1452
1453/// The first `type_identifier` in `node`'s subtree, depth-first.
1454fn first_type_identifier(node: Node<'_>, bytes: &[u8]) -> Option<String> {
1455    if node.kind() == "type_identifier" {
1456        return node_text(node, bytes).map(str::to_string);
1457    }
1458    let mut cursor = node.walk();
1459    node.children(&mut cursor)
1460        .find_map(|child| first_type_identifier(child, bytes))
1461}
1462
1463/// `let` bindings in a Rust body whose type is written or obvious, added to
1464/// `out`. Does not descend into a nested `function_item`: that body is its own
1465/// scope and gets its own table.
1466fn collect_rust_lets(
1467    node: Node<'_>,
1468    bytes: &[u8],
1469    generics: &BTreeSet<String>,
1470    out: &mut BTreeMap<String, String>,
1471) {
1472    let mut cursor = node.walk();
1473    for child in node.children(&mut cursor) {
1474        if child.kind() == "function_item" {
1475            continue;
1476        }
1477        if child.kind() == "let_declaration"
1478            && let Some(pattern) = child.child_by_field_name("pattern")
1479            && pattern.kind() == "identifier"
1480            && let Some(name) = node_text(pattern, bytes)
1481        {
1482            // An explicit annotation beats the initializer.
1483            let ty = child
1484                .child_by_field_name("type")
1485                .and_then(|t| rust_type_name(t, bytes))
1486                .or_else(|| {
1487                    child
1488                        .child_by_field_name("value")
1489                        .and_then(|v| rust_value_type(v, bytes))
1490                });
1491            if let Some(ty) = ty
1492                && !generics.contains(&ty)
1493            {
1494                out.insert(name.to_string(), ty);
1495            }
1496        }
1497        collect_rust_lets(child, bytes, generics, out);
1498    }
1499}
1500
1501/// Receiver types visible inside one Rust function body, by binding name.
1502///
1503/// Three shapes carry most of the volume and are all purely syntactic: a
1504/// parameter with a written type, a binding initialized from a constructor-style
1505/// call, and a binding initialized from a struct literal. Anything needing
1506/// inference — a return type, a generic, a trait object — is left out, so those
1507/// receivers keep declining and keep being counted (#251).
1508///
1509/// Rust only. Every other language records no receiver type and behaves exactly
1510/// as it did.
1511fn rust_receiver_types(func: Node<'_>, bytes: &[u8]) -> BTreeMap<String, String> {
1512    let mut out = BTreeMap::new();
1513    let generics = rust_type_parameters(func, bytes);
1514
1515    if let Some(params) = func.child_by_field_name("parameters") {
1516        let mut cursor = params.walk();
1517        for param in params.named_children(&mut cursor) {
1518            let (Some(pattern), Some(ty)) = (
1519                param.child_by_field_name("pattern"),
1520                param.child_by_field_name("type"),
1521            ) else {
1522                continue;
1523            };
1524            if let Some(name) = node_text(pattern, bytes)
1525                && let Some(ty) = rust_type_name(ty, bytes)
1526                && !generics.contains(&ty)
1527            {
1528                out.insert(name.to_string(), ty);
1529            }
1530        }
1531    }
1532
1533    if let Some(body) = func.child_by_field_name("body") {
1534        collect_rust_lets(body, bytes, &generics, &mut out);
1535    }
1536    out
1537}
1538
1539/// The bare receiver of a Rust method call — `w` in `w.get()` — or `None` when
1540/// the receiver is an expression rather than a name.
1541fn rust_receiver_name(call: Node<'_>, bytes: &[u8]) -> Option<String> {
1542    let func = call.child_by_field_name("function")?;
1543    if func.kind() != "field_expression" {
1544        return None;
1545    }
1546    let value = func.child_by_field_name("value")?;
1547    matches!(value.kind(), "identifier" | "self")
1548        .then(|| node_text(value, bytes))
1549        .flatten()
1550        .map(str::to_string)
1551}
1552
1553/// What a node inherits from the subtree above it: the name a call in it is
1554/// attributed to, what that name is, the type owning it, and the receiver types
1555/// its body declared.
1556///
1557/// A struct rather than four more parameters — [`walk`] threads all of it
1558/// unchanged through most nodes, and the set has grown once per ticket
1559/// (#248, #251, #268).
1560#[derive(Clone, Copy)]
1561struct Inherited<'a> {
1562    /// The name a call here is attributed to, if any.
1563    enclosing: Option<&'a str>,
1564    /// What that name is — a function, or a module-level binding.
1565    scope: FromScope,
1566    /// The type or module enclosing definitions here.
1567    owner: Option<&'a str>,
1568    /// Receiver types visible in the enclosing function body.
1569    receivers: &'a BTreeMap<String, String>,
1570}
1571
1572fn walk(
1573    language: Language,
1574    node: Node<'_>,
1575    bytes: &[u8],
1576    inherited: Inherited<'_>,
1577    graph: &mut CodeGraph,
1578) {
1579    let mut enclosing = inherited.enclosing.map(str::to_string);
1580    let mut scope = inherited.scope;
1581    let mut owner = inherited.owner.map(str::to_string);
1582    // Assigned only when this node opens a function body, and only then does
1583    // `receivers` point at it — so the table is scoped to the body it came from
1584    // and cannot leak into a sibling function (#251).
1585    let scoped_receivers;
1586    let mut receivers = inherited.receivers;
1587
1588    if let Some(kind) = language.item_kind(node, bytes)
1589        && let Some(name) = language.item_name(node, kind, bytes)
1590    {
1591        graph.symbols.push(Symbol {
1592            name: name.clone(),
1593            kind,
1594            start_line: node.start_position().row + 1,
1595            end_line: node.end_position().row + 1,
1596            // The owner in scope *around* this item — an item is not its own
1597            // owner, exactly as a function is not its own enclosing function.
1598            owner: owner.clone(),
1599        });
1600        if kind == SymbolKind::Function {
1601            enclosing = Some(name.clone());
1602            scope = FromScope::Function;
1603            if language == Language::Rust {
1604                scoped_receivers = rust_receiver_types(node, bytes);
1605                receivers = &scoped_receivers;
1606            }
1607        }
1608        if establishes_ownership(kind) {
1609            owner = Some(owner_key(&name));
1610        }
1611    }
1612
1613    if language.is_call(node.kind())
1614        && let Some(name) = language.callee_name(node, bytes)
1615    {
1616        let kind = language.callee_kind(node);
1617        graph.references.push(Reference {
1618            from_scope: scope,
1619            name,
1620            from: enclosing.clone(),
1621            line: node.start_position().row + 1,
1622            kind,
1623            // A receiver is a value, not a type (#248); a value reference is
1624            // not a call site at all (#250).
1625            qualifier: match kind {
1626                RefKind::Value => None,
1627                // `x.foo()` belongs to whatever `x` is. Where the body says so
1628                // outright, say so; otherwise keep declining (#251).
1629                RefKind::Method if language == Language::Rust => rust_receiver_name(node, bytes)
1630                    .and_then(|recv| match recv.as_str() {
1631                        "self" => owner.clone(),
1632                        other => receivers.get(other).cloned(),
1633                    }),
1634                RefKind::Method => None,
1635                RefKind::Free => language.callee_qualifier(node, bytes),
1636            },
1637        });
1638    }
1639
1640    // A module-level binding that is not a function still names the calls in
1641    // its initializer — a Zod schema, a tRPC handler (#268). Fires only where
1642    // there is no enclosing *function*, so every existing attribution is
1643    // untouched; a nested binding takes over from an outer one, since the
1644    // nearest name is the useful one.
1645    if (enclosing.is_none() || scope == FromScope::Module)
1646        && let Some(name) = language.module_binding_name(node, bytes)
1647    {
1648        enclosing = Some(name);
1649        scope = FromScope::Module;
1650    }
1651
1652    // Names this file brings into scope (#252).
1653    for import in language.imports_at(node, bytes) {
1654        graph.imports.push(import);
1655    }
1656
1657    // Names handed to a call as values rather than called — `and_then(helper)`
1658    // records no call edge, so without this a live callback looks dead (#250).
1659    if language.is_call(node.kind()) {
1660        for (name, line) in language.value_args(node, bytes) {
1661            graph.references.push(Reference {
1662                from_scope: scope,
1663                name,
1664                from: enclosing.clone(),
1665                line,
1666                kind: RefKind::Value,
1667                qualifier: None,
1668            });
1669        }
1670    }
1671
1672    // Calls the grammar hides inside an opaque macro-argument node (#216).
1673    for (name, line) in language.macro_arg_calls(node, bytes) {
1674        graph.references.push(Reference {
1675            from_scope: scope,
1676            name,
1677            from: enclosing.clone(),
1678            line,
1679            // A token-tree call is a bare `ident(` by construction (#216).
1680            kind: RefKind::Free,
1681            qualifier: None,
1682        });
1683    }
1684
1685    let mut cursor = node.walk();
1686    for child in node.children(&mut cursor) {
1687        walk(
1688            language,
1689            child,
1690            bytes,
1691            Inherited {
1692                enclosing: enclosing.as_deref(),
1693                scope,
1694                owner: owner.as_deref(),
1695                receivers,
1696            },
1697            graph,
1698        );
1699    }
1700}
1701
1702#[cfg(test)]
1703mod tests {
1704    use super::*;
1705
1706    const RUST_SRC: &str = r#"
1707struct Widget { n: u32 }
1708
1709fn helper(x: u32) -> u32 { x + 1 }
1710
1711fn main() {
1712    let w = Widget { n: 1 };
1713    let y = helper(w.n);
1714    println!("{y}");
1715}
1716"#;
1717
1718    #[test]
1719    fn rust_extracts_definitions() {
1720        let g = build_rust(RUST_SRC);
1721        let names: Vec<(&str, SymbolKind)> = g
1722            .symbols
1723            .iter()
1724            .map(|s| (s.name.as_str(), s.kind))
1725            .collect();
1726        assert!(names.contains(&("Widget", SymbolKind::Struct)));
1727        assert!(names.contains(&("helper", SymbolKind::Function)));
1728        assert!(names.contains(&("main", SymbolKind::Function)));
1729    }
1730
1731    #[test]
1732    fn rust_records_call_with_enclosing_fn() {
1733        let g = build_rust(RUST_SRC);
1734        let call = g
1735            .references
1736            .iter()
1737            .find(|r| r.name == "helper")
1738            .expect("helper call recorded");
1739        assert_eq!(call.from.as_deref(), Some("main"));
1740    }
1741
1742    // ---- calls inside macro arguments (#216) ------------------------------
1743
1744    /// Names referenced in `src`, for the macro-argument cases below.
1745    fn rust_ref_names(src: &str) -> Vec<String> {
1746        build_rust(src)
1747            .references
1748            .iter()
1749            .map(|r| r.name.clone())
1750            .collect()
1751    }
1752
1753    #[test]
1754    fn rust_records_a_call_inside_assert_eq() {
1755        // Rust macro bodies parse as token trees, so this call used to vanish —
1756        // the dominant false positive behind `unreferenced` (#216).
1757        let names = rust_ref_names("fn g() { assert_eq!(f(), 1); }");
1758        assert!(names.contains(&"f".to_string()), "got {names:?}");
1759    }
1760
1761    #[test]
1762    fn rust_records_calls_inside_common_macros() {
1763        for (src, want) in [
1764            ("fn g() { println!(\"{}\", h()); }", "h"),
1765            ("fn g() { let v = vec![mk()]; }", "mk"),
1766            ("fn g() { panic!(\"{}\", why()); }", "why"),
1767            ("fn g() { write!(w, \"{}\", val()); }", "val"),
1768        ] {
1769            let names = rust_ref_names(src);
1770            assert!(names.contains(&want.to_string()), "{want} in {names:?}");
1771        }
1772    }
1773
1774    #[test]
1775    fn rust_records_a_qualified_call_inside_a_macro_by_last_segment() {
1776        // The real #216 repro: `Language::from_extension` is called only from
1777        // assertions, so it looked uncalled.
1778        let names =
1779            rust_ref_names("fn g() { assert_eq!(Language::from_extension(\"rs\"), None); }");
1780        assert!(names.contains(&"from_extension".to_string()), "{names:?}");
1781    }
1782
1783    #[test]
1784    fn rust_records_nested_calls_inside_a_macro() {
1785        let names = rust_ref_names("fn g() { assert_eq!(outer(inner()), 1); }");
1786        assert!(names.contains(&"outer".to_string()), "{names:?}");
1787        assert!(names.contains(&"inner".to_string()), "{names:?}");
1788    }
1789
1790    #[test]
1791    fn rust_records_a_macro_arg_call_once() {
1792        let names = rust_ref_names("fn g() { assert_eq!(f(), 1); }");
1793        assert_eq!(
1794            names.iter().filter(|n| *n == "f").count(),
1795            1,
1796            "nested token trees must not double-count: {names:?}"
1797        );
1798    }
1799
1800    #[test]
1801    fn rust_macro_arg_calls_carry_the_enclosing_function() {
1802        let g = build_rust("fn outer_fn() { assert_eq!(f(), 1); }");
1803        let r = g.references.iter().find(|r| r.name == "f").expect("f");
1804        assert_eq!(r.from.as_deref(), Some("outer_fn"));
1805    }
1806
1807    #[test]
1808    fn rust_does_not_treat_a_nested_macro_name_as_a_call() {
1809        // `matches!` is a macro, not a function: the `!` between the identifier
1810        // and the token tree is what distinguishes them.
1811        let names = rust_ref_names("fn g() { assert!(matches!(a, Some(_))); }");
1812        assert!(!names.contains(&"matches".to_string()), "{names:?}");
1813    }
1814
1815    #[test]
1816    fn rust_does_not_invent_calls_from_plain_macro_arguments() {
1817        // Bare identifiers and literals are not calls — only an identifier
1818        // immediately followed by a parenthesised token tree is.
1819        let names = rust_ref_names("fn g() { println!(\"{}\", x); }");
1820        assert!(!names.contains(&"x".to_string()), "{names:?}");
1821        let names = rust_ref_names("fn g() { let v = vec![1, 2, 3]; }");
1822        assert!(names.is_empty(), "{names:?}");
1823    }
1824
1825    #[test]
1826    fn rust_still_records_ordinary_calls_alongside_macro_ones() {
1827        let names = rust_ref_names("fn g() { plain(); assert_eq!(inside(), 1); }");
1828        assert!(names.contains(&"plain".to_string()), "{names:?}");
1829        assert!(names.contains(&"inside".to_string()), "{names:?}");
1830    }
1831
1832    #[test]
1833    fn rust_macro_arg_call_line_is_one_based() {
1834        let g = build_rust("fn g() {\n    assert_eq!(f(), 1);\n}");
1835        let r = g.references.iter().find(|r| r.name == "f").expect("f");
1836        assert_eq!(r.line, 2);
1837    }
1838
1839    // ---- grammar audit for the same opaque-node hole (#216) ----------------
1840
1841    #[test]
1842    fn c_records_macro_invocations_at_the_call_site() {
1843        // A C macro *use* is indistinguishable from a call to the grammar, so
1844        // it and its neighbours are recorded — no Rust-style hole here.
1845        let g = build(
1846            Language::C,
1847            "#define M() foo()\nvoid g(void) { M(); bar(); }",
1848        );
1849        let names: Vec<&str> = g.references.iter().map(|r| r.name.as_str()).collect();
1850        assert!(names.contains(&"M"));
1851        assert!(names.contains(&"bar"));
1852    }
1853
1854    #[test]
1855    fn c_does_not_record_calls_inside_a_define_body() {
1856        // The one analogous hole the audit found: a `#define` body is a single
1857        // opaque `preproc_arg` token, not an expression tree, so `foo()` here is
1858        // invisible. Unlike Rust's token tree there are no child nodes to read,
1859        // so recovering it means lexing macro text — deliberately out of scope.
1860        // Pinned so the gap is discoverable rather than silent.
1861        let g = build(Language::C, "#define M() foo()\nvoid g(void) { M(); }");
1862        let names: Vec<&str> = g.references.iter().map(|r| r.name.as_str()).collect();
1863        assert!(!names.contains(&"foo"), "known gap: {names:?}");
1864    }
1865
1866    #[test]
1867    fn elixir_records_calls_inside_a_quote_block() {
1868        // `quote do: foo()` parses as real expressions — no hole.
1869        let g = build(
1870            Language::Elixir,
1871            "defmodule A do\n  def g do\n    quote do: foo()\n  end\nend",
1872        );
1873        let names: Vec<&str> = g.references.iter().map(|r| r.name.as_str()).collect();
1874        assert!(names.contains(&"foo"), "{names:?}");
1875    }
1876
1877    #[test]
1878    fn ruby_records_calls_inside_a_block() {
1879        let g = build(Language::Ruby, "def g\n  define_method(:x) { foo() }\nend");
1880        let names: Vec<&str> = g.references.iter().map(|r| r.name.as_str()).collect();
1881        assert!(names.contains(&"foo"), "{names:?}");
1882    }
1883
1884    #[test]
1885    fn rust_symbol_lines_are_one_based() {
1886        let g = build_rust(RUST_SRC);
1887        let main = g.symbols.iter().find(|s| s.name == "main").unwrap();
1888        assert!(main.start_line >= 1 && main.end_line >= main.start_line);
1889    }
1890
1891    const PY_SRC: &str = r#"
1892class Widget:
1893    def area(self):
1894        return helper(self.n)
1895
1896def helper(x):
1897    return x + 1
1898
1899def main():
1900    w = Widget()
1901    print(w.area())
1902"#;
1903
1904    #[test]
1905    fn python_extracts_functions_and_classes() {
1906        let g = build(Language::Python, PY_SRC);
1907        let names: Vec<(&str, SymbolKind)> = g
1908            .symbols
1909            .iter()
1910            .map(|s| (s.name.as_str(), s.kind))
1911            .collect();
1912        assert!(names.contains(&("Widget", SymbolKind::Class)));
1913        assert!(names.contains(&("helper", SymbolKind::Function)));
1914        assert!(names.contains(&("area", SymbolKind::Function)));
1915    }
1916
1917    #[test]
1918    fn python_records_calls_including_methods() {
1919        let g = build(Language::Python, PY_SRC);
1920        // Plain call `helper(...)` from inside `area`.
1921        let helper_call = g
1922            .references
1923            .iter()
1924            .find(|r| r.name == "helper")
1925            .expect("helper call");
1926        assert_eq!(helper_call.from.as_deref(), Some("area"));
1927        // Method call `w.area()` from inside `main` -> the attribute name `area`.
1928        assert!(
1929            g.references
1930                .iter()
1931                .any(|r| r.name == "area" && r.from.as_deref() == Some("main"))
1932        );
1933    }
1934
1935    #[test]
1936    fn language_from_extension() {
1937        assert_eq!(Language::from_extension("rs"), Some(Language::Rust));
1938        assert_eq!(Language::from_extension("py"), Some(Language::Python));
1939        assert_eq!(Language::from_extension("js"), Some(Language::JavaScript));
1940        assert_eq!(Language::from_extension("jsx"), Some(Language::JavaScript));
1941        assert_eq!(Language::from_extension("ts"), Some(Language::TypeScript));
1942        assert_eq!(Language::from_extension("tsx"), Some(Language::Tsx));
1943        assert_eq!(Language::from_extension("go"), Some(Language::Go));
1944        assert_eq!(Language::from_extension("java"), Some(Language::Java));
1945        assert_eq!(Language::from_extension("cs"), Some(Language::CSharp));
1946        assert_eq!(Language::from_extension("c"), Some(Language::C));
1947        // A header goes through the wider grammar; see #266.
1948        assert_eq!(Language::from_extension("h"), Some(Language::Cpp));
1949        assert_eq!(Language::from_extension("cpp"), Some(Language::Cpp));
1950        assert_eq!(Language::from_extension("cc"), Some(Language::Cpp));
1951        assert_eq!(Language::from_extension("cxx"), Some(Language::Cpp));
1952        assert_eq!(Language::from_extension("hpp"), Some(Language::Cpp));
1953        assert_eq!(Language::from_extension("hh"), Some(Language::Cpp));
1954        assert_eq!(Language::from_extension("rb"), Some(Language::Ruby));
1955        assert_eq!(Language::from_extension("php"), Some(Language::Php));
1956        assert_eq!(Language::from_extension("sh"), Some(Language::Bash));
1957        assert_eq!(Language::from_extension("bash"), Some(Language::Bash));
1958        assert_eq!(Language::from_extension("kt"), Some(Language::Kotlin));
1959        assert_eq!(Language::from_extension("kts"), Some(Language::Kotlin));
1960        assert_eq!(Language::from_extension("swift"), Some(Language::Swift));
1961        assert_eq!(Language::from_extension("lua"), Some(Language::Lua));
1962        assert_eq!(Language::from_extension("scala"), Some(Language::Scala));
1963        assert_eq!(Language::from_extension("ex"), Some(Language::Elixir));
1964        assert_eq!(Language::from_extension("exs"), Some(Language::Elixir));
1965        assert_eq!(Language::from_extension("txt"), None);
1966    }
1967
1968    const JS_SRC: &str = r#"
1969class Widget {
1970  area() {
1971    return helper(this.n);
1972  }
1973}
1974function helper(x) {
1975  return x + 1;
1976}
1977function main() {
1978  const w = new Widget();
1979  console.log(w.area());
1980}
1981"#;
1982
1983    #[test]
1984    fn javascript_extracts_symbols_and_calls() {
1985        let g = build(Language::JavaScript, JS_SRC);
1986        let names: Vec<(&str, SymbolKind)> = g
1987            .symbols
1988            .iter()
1989            .map(|s| (s.name.as_str(), s.kind))
1990            .collect();
1991        assert!(names.contains(&("Widget", SymbolKind::Class)));
1992        assert!(names.contains(&("area", SymbolKind::Function)));
1993        assert!(names.contains(&("helper", SymbolKind::Function)));
1994        assert!(names.contains(&("main", SymbolKind::Function)));
1995
1996        // `helper(...)` called from inside `area`.
1997        assert!(
1998            g.references
1999                .iter()
2000                .any(|r| r.name == "helper" && r.from.as_deref() == Some("area"))
2001        );
2002        // Method call `w.area()` -> member-expression property `area`, from `main`.
2003        assert!(
2004            g.references
2005                .iter()
2006                .any(|r| r.name == "area" && r.from.as_deref() == Some("main"))
2007        );
2008    }
2009
2010    const TS_SRC: &str = r#"
2011interface Shape { area(): number; }
2012type Id = string;
2013enum Color { Red, Green }
2014
2015class Circle implements Shape {
2016  area(): number { return compute(this.r); }
2017}
2018
2019function compute(r: number): number { return r * r; }
2020"#;
2021
2022    #[test]
2023    fn typescript_extracts_ts_specific_kinds() {
2024        let g = build(Language::TypeScript, TS_SRC);
2025        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2026        assert_eq!(named("Shape"), Some(SymbolKind::Interface));
2027        assert_eq!(named("Id"), Some(SymbolKind::TypeAlias));
2028        assert_eq!(named("Color"), Some(SymbolKind::Enum));
2029        assert_eq!(named("Circle"), Some(SymbolKind::Class));
2030        assert_eq!(named("compute"), Some(SymbolKind::Function));
2031
2032        assert!(
2033            g.references
2034                .iter()
2035                .any(|r| r.name == "compute" && r.from.as_deref() == Some("area"))
2036        );
2037    }
2038
2039    const GO_SRC: &str = r#"
2040package main
2041
2042type Widget struct { n int }
2043
2044type Shape interface { Area() int }
2045
2046const Limit = 10
2047
2048var counter = 0
2049
2050func helper(x int) int { return x + 1 }
2051
2052func (w Widget) Area() int { return helper(w.n) }
2053
2054func main() {
2055	w := Widget{n: 1}
2056	_ = w.Area()
2057	_ = helper(2)
2058}
2059"#;
2060
2061    #[test]
2062    fn go_extracts_definitions() {
2063        let g = build(Language::Go, GO_SRC);
2064        let names: Vec<(&str, SymbolKind)> = g
2065            .symbols
2066            .iter()
2067            .map(|s| (s.name.as_str(), s.kind))
2068            .collect();
2069        assert!(names.contains(&("Widget", SymbolKind::Struct)));
2070        assert!(names.contains(&("Shape", SymbolKind::Interface)));
2071        assert!(names.contains(&("helper", SymbolKind::Function)));
2072        assert!(names.contains(&("Area", SymbolKind::Function)));
2073        assert!(names.contains(&("main", SymbolKind::Function)));
2074        assert!(names.contains(&("Limit", SymbolKind::Const)));
2075        assert!(names.contains(&("counter", SymbolKind::Static)));
2076    }
2077
2078    #[test]
2079    fn go_records_calls_including_methods() {
2080        let g = build(Language::Go, GO_SRC);
2081        // Plain call `helper(...)` from inside the `Area` method.
2082        assert!(
2083            g.references
2084                .iter()
2085                .any(|r| r.name == "helper" && r.from.as_deref() == Some("Area")),
2086            "helper call from Area"
2087        );
2088        // Method call `w.Area()` -> selector-expression field `Area`, from `main`.
2089        assert!(
2090            g.references
2091                .iter()
2092                .any(|r| r.name == "Area" && r.from.as_deref() == Some("main")),
2093            "w.Area() call from main"
2094        );
2095    }
2096
2097    #[test]
2098    fn tsx_parses_with_jsx() {
2099        // The TSX grammar must accept JSX syntax that plain TS would reject.
2100        let src = r#"
2101function App(): JSX.Element {
2102  return greet();
2103}
2104function greet() { return <div>hi</div>; }
2105"#;
2106        let g = build(Language::Tsx, src);
2107        assert!(g.symbols.iter().any(|s| s.name == "App"));
2108        assert!(
2109            g.references
2110                .iter()
2111                .any(|r| r.name == "greet" && r.from.as_deref() == Some("App"))
2112        );
2113    }
2114
2115    const JAVA_SRC: &str = r#"
2116interface Shape { int area(); }
2117
2118enum Color { RED, GREEN }
2119
2120class Widget {
2121    int n;
2122    Widget(int n) { this.n = n; }
2123    int area() { return helper(this.n); }
2124}
2125
2126class Main {
2127    static int helper(int x) { return x + 1; }
2128    static void main(String[] args) {
2129        Widget w = new Widget(1);
2130        w.area();
2131    }
2132}
2133"#;
2134
2135    #[test]
2136    fn java_extracts_definitions() {
2137        let g = build(Language::Java, JAVA_SRC);
2138        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2139        assert_eq!(named("Shape"), Some(SymbolKind::Interface));
2140        assert_eq!(named("Color"), Some(SymbolKind::Enum));
2141        assert_eq!(named("Widget"), Some(SymbolKind::Class));
2142        assert_eq!(named("area"), Some(SymbolKind::Function));
2143        assert_eq!(named("helper"), Some(SymbolKind::Function));
2144        // Constructor is recorded as a Function named for its class.
2145        assert!(
2146            g.symbols
2147                .iter()
2148                .any(|s| s.name == "Widget" && s.kind == SymbolKind::Function)
2149        );
2150    }
2151
2152    #[test]
2153    fn java_records_calls_with_enclosing_fn() {
2154        let g = build(Language::Java, JAVA_SRC);
2155        assert!(
2156            g.references
2157                .iter()
2158                .any(|r| r.name == "helper" && r.from.as_deref() == Some("area")),
2159            "helper() call from area"
2160        );
2161        assert!(
2162            g.references
2163                .iter()
2164                .any(|r| r.name == "area" && r.from.as_deref() == Some("main")),
2165            "w.area() call from main"
2166        );
2167    }
2168
2169    const CS_SRC: &str = r#"
2170interface IShape { int Area(); }
2171enum Color { Red, Green }
2172struct Point { public int X; }
2173
2174class Widget {
2175    int n;
2176    public Widget(int n) { this.n = n; }
2177    public int Area() { return Helper(this.n); }
2178}
2179
2180class Program {
2181    static int Helper(int x) { return x + 1; }
2182    static void Main() {
2183        var w = new Widget(1);
2184        w.Area();
2185    }
2186}
2187"#;
2188
2189    #[test]
2190    fn csharp_extracts_definitions() {
2191        let g = build(Language::CSharp, CS_SRC);
2192        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2193        assert_eq!(named("IShape"), Some(SymbolKind::Interface));
2194        assert_eq!(named("Color"), Some(SymbolKind::Enum));
2195        assert_eq!(named("Point"), Some(SymbolKind::Struct));
2196        assert_eq!(named("Widget"), Some(SymbolKind::Class));
2197        assert_eq!(named("Area"), Some(SymbolKind::Function));
2198        assert_eq!(named("Helper"), Some(SymbolKind::Function));
2199    }
2200
2201    #[test]
2202    fn csharp_records_calls_with_enclosing_fn() {
2203        let g = build(Language::CSharp, CS_SRC);
2204        assert!(
2205            g.references
2206                .iter()
2207                .any(|r| r.name == "Helper" && r.from.as_deref() == Some("Area")),
2208            "Helper() call from Area"
2209        );
2210        assert!(
2211            g.references
2212                .iter()
2213                .any(|r| r.name == "Area" && r.from.as_deref() == Some("Main")),
2214            "w.Area() call from Main"
2215        );
2216    }
2217
2218    const C_SRC: &str = r#"
2219struct Widget { int n; };
2220
2221typedef int Id;
2222
2223enum Color { RED, GREEN };
2224
2225int helper(int x) { return x + 1; }
2226
2227int main(void) {
2228    int y = helper(2);
2229    return y;
2230}
2231"#;
2232
2233    #[test]
2234    fn c_extracts_definitions() {
2235        let g = build(Language::C, C_SRC);
2236        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2237        assert_eq!(named("Widget"), Some(SymbolKind::Struct));
2238        assert_eq!(named("Id"), Some(SymbolKind::TypeAlias));
2239        assert_eq!(named("Color"), Some(SymbolKind::Enum));
2240        assert_eq!(named("helper"), Some(SymbolKind::Function));
2241        assert_eq!(named("main"), Some(SymbolKind::Function));
2242    }
2243
2244    #[test]
2245    fn c_records_call_with_enclosing_fn() {
2246        let g = build(Language::C, C_SRC);
2247        assert!(
2248            g.references
2249                .iter()
2250                .any(|r| r.name == "helper" && r.from.as_deref() == Some("main")),
2251            "helper() call from main"
2252        );
2253    }
2254
2255    const CPP_SRC: &str = r#"
2256namespace geo {
2257
2258class Widget {
2259public:
2260    int n;
2261    int area() { return helper(this->n); }
2262};
2263
2264int helper(int x) { return x + 1; }
2265
2266}
2267
2268int main() {
2269    geo::Widget w;
2270    return w.area();
2271}
2272"#;
2273
2274    #[test]
2275    fn cpp_extracts_definitions() {
2276        let g = build(Language::Cpp, CPP_SRC);
2277        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2278        assert_eq!(named("geo"), Some(SymbolKind::Module));
2279        assert_eq!(named("Widget"), Some(SymbolKind::Class));
2280        assert_eq!(named("area"), Some(SymbolKind::Function));
2281        assert_eq!(named("helper"), Some(SymbolKind::Function));
2282        assert_eq!(named("main"), Some(SymbolKind::Function));
2283    }
2284
2285    #[test]
2286    fn cpp_records_calls_including_methods() {
2287        let g = build(Language::Cpp, CPP_SRC);
2288        // this->helper(...) -> field_expression `field`, from `area`.
2289        assert!(
2290            g.references
2291                .iter()
2292                .any(|r| r.name == "helper" && r.from.as_deref() == Some("area")),
2293            "helper() call from area"
2294        );
2295        // w.area() -> field_expression `field`, from `main`.
2296        assert!(
2297            g.references
2298                .iter()
2299                .any(|r| r.name == "area" && r.from.as_deref() == Some("main")),
2300            "w.area() call from main"
2301        );
2302    }
2303
2304    const RUBY_SRC: &str = r#"
2305class Widget
2306  def area
2307    helper(1)
2308  end
2309end
2310
2311module Util
2312end
2313
2314def helper(x)
2315  x + 1
2316end
2317
2318def main
2319  helper(2)
2320end
2321"#;
2322
2323    #[test]
2324    fn ruby_extracts_definitions() {
2325        let g = build(Language::Ruby, RUBY_SRC);
2326        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2327        assert_eq!(named("Widget"), Some(SymbolKind::Class));
2328        assert_eq!(named("Util"), Some(SymbolKind::Module));
2329        assert_eq!(named("area"), Some(SymbolKind::Function));
2330        assert_eq!(named("helper"), Some(SymbolKind::Function));
2331        assert_eq!(named("main"), Some(SymbolKind::Function));
2332    }
2333
2334    #[test]
2335    fn ruby_records_call_with_enclosing_fn() {
2336        let g = build(Language::Ruby, RUBY_SRC);
2337        // `helper(2)` is called from the `main` method (callee is the `method` field).
2338        assert!(
2339            g.references
2340                .iter()
2341                .any(|r| r.name == "helper" && r.from.as_deref() == Some("main")),
2342            "helper call from main"
2343        );
2344    }
2345
2346    const PHP_SRC: &str = r#"<?php
2347class Widget {
2348    function area() {
2349        return helper(1);
2350    }
2351}
2352
2353interface Shape {}
2354
2355trait Named {}
2356
2357function helper($x) {
2358    return $x + 1;
2359}
2360
2361function main() {
2362    return helper(2);
2363}
2364"#;
2365
2366    #[test]
2367    fn php_extracts_definitions() {
2368        let g = build(Language::Php, PHP_SRC);
2369        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2370        assert_eq!(named("Widget"), Some(SymbolKind::Class));
2371        assert_eq!(named("Shape"), Some(SymbolKind::Interface));
2372        assert_eq!(named("Named"), Some(SymbolKind::Trait));
2373        assert_eq!(named("area"), Some(SymbolKind::Function));
2374        assert_eq!(named("helper"), Some(SymbolKind::Function));
2375        assert_eq!(named("main"), Some(SymbolKind::Function));
2376    }
2377
2378    #[test]
2379    fn php_records_call_with_enclosing_fn() {
2380        let g = build(Language::Php, PHP_SRC);
2381        // `helper(2)` -> function_call_expression `function` field, from `main`.
2382        assert!(
2383            g.references
2384                .iter()
2385                .any(|r| r.name == "helper" && r.from.as_deref() == Some("main")),
2386            "helper() call from main"
2387        );
2388    }
2389
2390    const BASH_SRC: &str = r#"
2391helper() {
2392  echo "$1"
2393}
2394
2395main() {
2396  helper hello
2397}
2398"#;
2399
2400    #[test]
2401    fn bash_extracts_definitions() {
2402        let g = build(Language::Bash, BASH_SRC);
2403        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2404        assert_eq!(named("helper"), Some(SymbolKind::Function));
2405        assert_eq!(named("main"), Some(SymbolKind::Function));
2406    }
2407
2408    #[test]
2409    fn bash_records_call_with_enclosing_fn() {
2410        let g = build(Language::Bash, BASH_SRC);
2411        // `helper hello` is a command whose `name` field is the callee, from `main`.
2412        assert!(
2413            g.references
2414                .iter()
2415                .any(|r| r.name == "helper" && r.from.as_deref() == Some("main")),
2416            "helper command from main"
2417        );
2418    }
2419
2420    const KOTLIN_SRC: &str = r#"
2421class Widget {
2422    fun area(): Int {
2423        return helper(1)
2424    }
2425}
2426
2427object Config
2428
2429fun helper(x: Int): Int {
2430    return x + 1
2431}
2432
2433fun main() {
2434    helper(2)
2435}
2436"#;
2437
2438    #[test]
2439    fn kotlin_extracts_definitions() {
2440        let g = build(Language::Kotlin, KOTLIN_SRC);
2441        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2442        assert_eq!(named("Widget"), Some(SymbolKind::Class));
2443        assert_eq!(named("Config"), Some(SymbolKind::Class)); // `object` singleton
2444        assert_eq!(named("area"), Some(SymbolKind::Function));
2445        assert_eq!(named("helper"), Some(SymbolKind::Function));
2446        assert_eq!(named("main"), Some(SymbolKind::Function));
2447    }
2448
2449    #[test]
2450    fn kotlin_records_call_with_enclosing_fn() {
2451        let g = build(Language::Kotlin, KOTLIN_SRC);
2452        // `helper(2)` -> call_expression whose first child is an `identifier`,
2453        // from `main`.
2454        assert!(
2455            g.references
2456                .iter()
2457                .any(|r| r.name == "helper" && r.from.as_deref() == Some("main")),
2458            "helper() call from main"
2459        );
2460    }
2461
2462    const SWIFT_SRC: &str = r#"
2463class Widget {
2464    func area() -> Int {
2465        return helper(1)
2466    }
2467}
2468
2469protocol Shape {}
2470
2471func helper(x: Int) -> Int {
2472    return x + 1
2473}
2474
2475func main() {
2476    helper(2)
2477}
2478"#;
2479
2480    #[test]
2481    fn swift_extracts_definitions() {
2482        let g = build(Language::Swift, SWIFT_SRC);
2483        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2484        assert_eq!(named("Widget"), Some(SymbolKind::Class));
2485        assert_eq!(named("Shape"), Some(SymbolKind::Interface)); // `protocol`
2486        assert_eq!(named("area"), Some(SymbolKind::Function));
2487        assert_eq!(named("helper"), Some(SymbolKind::Function));
2488        assert_eq!(named("main"), Some(SymbolKind::Function));
2489    }
2490
2491    #[test]
2492    fn swift_records_call_with_enclosing_fn() {
2493        let g = build(Language::Swift, SWIFT_SRC);
2494        // `helper(2)` -> call_expression whose first child is a `simple_identifier`,
2495        // from `main`.
2496        assert!(
2497            g.references
2498                .iter()
2499                .any(|r| r.name == "helper" && r.from.as_deref() == Some("main")),
2500            "helper() call from main"
2501        );
2502    }
2503
2504    const LUA_SRC: &str = r#"
2505function helper(x)
2506  return x + 1
2507end
2508
2509function main()
2510  return helper(2)
2511end
2512"#;
2513
2514    #[test]
2515    fn lua_extracts_definitions() {
2516        let g = build(Language::Lua, LUA_SRC);
2517        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2518        assert_eq!(named("helper"), Some(SymbolKind::Function));
2519        assert_eq!(named("main"), Some(SymbolKind::Function));
2520    }
2521
2522    #[test]
2523    fn lua_records_call_with_enclosing_fn() {
2524        let g = build(Language::Lua, LUA_SRC);
2525        // `helper(2)` -> function_call whose `name` field is the callee, from `main`.
2526        assert!(
2527            g.references
2528                .iter()
2529                .any(|r| r.name == "helper" && r.from.as_deref() == Some("main")),
2530            "helper() call from main"
2531        );
2532    }
2533
2534    const SCALA_SRC: &str = r#"
2535class Widget {
2536  def area(): Int = { helper(1) }
2537}
2538
2539object Config
2540
2541trait Shape
2542
2543def helper(x: Int): Int = x + 1
2544
2545def main(): Unit = { helper(2) }
2546"#;
2547
2548    #[test]
2549    fn scala_extracts_definitions() {
2550        let g = build(Language::Scala, SCALA_SRC);
2551        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2552        assert_eq!(named("Widget"), Some(SymbolKind::Class));
2553        assert_eq!(named("Config"), Some(SymbolKind::Class)); // `object` singleton
2554        assert_eq!(named("Shape"), Some(SymbolKind::Trait));
2555        assert_eq!(named("area"), Some(SymbolKind::Function));
2556        assert_eq!(named("helper"), Some(SymbolKind::Function));
2557        assert_eq!(named("main"), Some(SymbolKind::Function));
2558    }
2559
2560    #[test]
2561    fn scala_records_call_with_enclosing_fn() {
2562        let g = build(Language::Scala, SCALA_SRC);
2563        // `helper(2)` -> call_expression `function` field, from `main`.
2564        assert!(
2565            g.references
2566                .iter()
2567                .any(|r| r.name == "helper" && r.from.as_deref() == Some("main")),
2568            "helper() call from main"
2569        );
2570    }
2571
2572    const ELIXIR_SRC: &str = r#"
2573defmodule Math do
2574  def add(a, b) do
2575    helper(a) + b
2576  end
2577
2578  defp helper(x), do: x
2579
2580  def run do
2581    Remote.compute(1)
2582  end
2583end
2584"#;
2585
2586    #[test]
2587    fn elixir_extracts_definitions() {
2588        let g = build(Language::Elixir, ELIXIR_SRC);
2589        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2590        // `defmodule Math` -> Module (name is the `alias`).
2591        assert_eq!(named("Math"), Some(SymbolKind::Module));
2592        // `def add(a, b)` -> Function (name is the nested-call head).
2593        assert_eq!(named("add"), Some(SymbolKind::Function));
2594        // `defp helper(x)` -> Function.
2595        assert_eq!(named("helper"), Some(SymbolKind::Function));
2596        // `def run` (no parens) -> Function (name is a bare identifier head).
2597        assert_eq!(named("run"), Some(SymbolKind::Function));
2598    }
2599
2600    #[test]
2601    fn elixir_records_calls() {
2602        let g = build(Language::Elixir, ELIXIR_SRC);
2603        // `helper(a)` -> reference to `helper` from inside `add`; the def head
2604        // `add(a, b)` is not itself recorded as a call.
2605        assert!(
2606            g.references
2607                .iter()
2608                .any(|r| r.name == "helper" && r.from.as_deref() == Some("add")),
2609            "helper() call from add"
2610        );
2611        // Remote call `Remote.compute(1)` -> reference to `compute` from `run`
2612        // (the target's trailing identifier).
2613        assert!(
2614            g.references
2615                .iter()
2616                .any(|r| r.name == "compute" && r.from.as_deref() == Some("run")),
2617            "Remote.compute() call from run"
2618        );
2619        // The definition heads must not leak in as self-calls.
2620        assert!(
2621            !g.references.iter().any(|r| r.name == "add"),
2622            "def head add(a, b) not recorded as a call"
2623        );
2624    }
2625
2626    // #136: JS/TS arrow-function and function-expression bindings are named
2627    // functions; a `variable_declarator`/`public_field_definition` whose value
2628    // is an `arrow_function`/`function_expression`.
2629    #[test]
2630    fn javascript_extracts_arrow_and_function_expression_bindings() {
2631        let src = r#"
2632const foo = () => { bar(); };
2633const baz = function () { qux(); };
2634"#;
2635        let g = build(Language::JavaScript, src);
2636        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2637        assert_eq!(named("foo"), Some(SymbolKind::Function));
2638        assert_eq!(named("baz"), Some(SymbolKind::Function));
2639        // Calls inside are attributed to the binding name (`walk` sets enclosing).
2640        assert!(
2641            g.references
2642                .iter()
2643                .any(|r| r.name == "bar" && r.from.as_deref() == Some("foo")),
2644            "bar() call attributed to foo"
2645        );
2646        assert!(
2647            g.references
2648                .iter()
2649                .any(|r| r.name == "qux" && r.from.as_deref() == Some("baz")),
2650            "qux() call attributed to baz"
2651        );
2652    }
2653
2654    #[test]
2655    fn typescript_extracts_arrow_bindings_and_class_fields() {
2656        // A `const` arrow binding and a class-field arrow (`public_field_definition`).
2657        let src = r#"
2658const foo = (): void => { bar(); };
2659class C { handler = (): void => { onClick(); }; }
2660"#;
2661        let g = build(Language::TypeScript, src);
2662        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2663        assert_eq!(named("foo"), Some(SymbolKind::Function));
2664        assert_eq!(named("handler"), Some(SymbolKind::Function));
2665        assert!(
2666            g.references
2667                .iter()
2668                .any(|r| r.name == "bar" && r.from.as_deref() == Some("foo")),
2669            "bar() call attributed to foo"
2670        );
2671        assert!(
2672            g.references
2673                .iter()
2674                .any(|r| r.name == "onClick" && r.from.as_deref() == Some("handler")),
2675            "onClick() call attributed to handler"
2676        );
2677    }
2678
2679    // #137: PHP method (`$this->m()`, `member_call_expression`) and static
2680    // (`A::b()`, `scoped_call_expression`) calls are recorded.
2681    #[test]
2682    fn php_records_method_and_static_calls() {
2683        let src = r#"<?php
2684class A {
2685    function run() {
2686        $this->other();
2687        self::x();
2688        B::stat();
2689    }
2690}
2691"#;
2692        let g = build(Language::Php, src);
2693        let called = |n: &str| {
2694            g.references
2695                .iter()
2696                .any(|r| r.name == n && r.from.as_deref() == Some("run"))
2697        };
2698        assert!(called("other"), "$this->other() recorded from run");
2699        assert!(called("x"), "self::x() recorded from run");
2700        assert!(called("stat"), "B::stat() recorded from run");
2701    }
2702
2703    // #151: Swift struct/enum/actor and Kotlin interface/object are no longer all
2704    // mislabeled Class.
2705    #[test]
2706    fn swift_distinguishes_struct_enum_class() {
2707        let src = r#"
2708struct Point { var x: Int }
2709enum Color { case red }
2710class Widget {}
2711actor Worker {}
2712protocol Shape {}
2713"#;
2714        let g = build(Language::Swift, src);
2715        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2716        assert_eq!(named("Point"), Some(SymbolKind::Struct));
2717        assert_eq!(named("Color"), Some(SymbolKind::Enum));
2718        assert_eq!(named("Widget"), Some(SymbolKind::Class));
2719        assert_eq!(named("Worker"), Some(SymbolKind::Class)); // `actor` -> Class
2720        assert_eq!(named("Shape"), Some(SymbolKind::Interface)); // `protocol`
2721    }
2722
2723    #[test]
2724    fn kotlin_distinguishes_interface_from_class() {
2725        let src = r#"
2726interface Shape { }
2727class Widget { }
2728object Config
2729"#;
2730        let g = build(Language::Kotlin, src);
2731        let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
2732        assert_eq!(named("Shape"), Some(SymbolKind::Interface));
2733        assert_eq!(named("Widget"), Some(SymbolKind::Class));
2734        assert_eq!(named("Config"), Some(SymbolKind::Class)); // `object` singleton
2735    }
2736
2737    // ---- qualified symbol identity (#248) ---------------------------------
2738
2739    #[test]
2740    fn rust_records_the_qualifier_of_a_path_call() {
2741        let g = build_rust(r#"fn g() { Language::from_extension("rs"); }"#);
2742        let r = g
2743            .references
2744            .iter()
2745            .find(|r| r.name == "from_extension")
2746            .expect("call recorded");
2747        assert_eq!(r.qualifier.as_deref(), Some("Language"));
2748    }
2749
2750    #[test]
2751    fn rust_records_the_last_path_segment_as_the_qualifier() {
2752        let g = build_rust("fn g() { a::b::c(); }");
2753        let r = g
2754            .references
2755            .iter()
2756            .find(|r| r.name == "c")
2757            .expect("call recorded");
2758        assert_eq!(r.qualifier.as_deref(), Some("b"));
2759    }
2760
2761    #[test]
2762    fn rust_records_no_qualifier_for_a_bare_call() {
2763        let g = build_rust("fn g() { helper(); }");
2764        let r = g
2765            .references
2766            .iter()
2767            .find(|r| r.name == "helper")
2768            .expect("call recorded");
2769        assert_eq!(r.qualifier, None);
2770    }
2771
2772    #[test]
2773    fn rust_never_records_a_receiver_name_as_a_qualifier() {
2774        // The receiver is a value, so `x` itself must never be the qualifier.
2775        // #251 types the receiver where the body says what it is, and the
2776        // qualifier is then the *type*; where it does not, nothing is recorded.
2777        let typed = build_rust("fn g(x: Thing) { x.helper(); }");
2778        let r = typed
2779            .references
2780            .iter()
2781            .find(|r| r.name == "helper")
2782            .expect("call recorded");
2783        assert_eq!(r.qualifier.as_deref(), Some("Thing"), "the type, not `x`");
2784        assert_eq!(r.kind, RefKind::Method);
2785
2786        let untyped = build_rust("fn g() { let x = make(); x.helper(); }");
2787        let r = untyped
2788            .references
2789            .iter()
2790            .find(|r| r.name == "helper")
2791            .expect("call recorded");
2792        assert_eq!(r.qualifier, None);
2793        assert_eq!(r.kind, RefKind::Method);
2794    }
2795
2796    #[test]
2797    fn rust_records_the_owning_type_of_a_method() {
2798        let g = build_rust("struct Widget; impl Widget { fn get(&self) {} }");
2799        let s = g
2800            .symbols
2801            .iter()
2802            .find(|s| s.name == "get")
2803            .expect("method recorded");
2804        assert_eq!(s.owner.as_deref(), Some("Widget"));
2805    }
2806
2807    #[test]
2808    fn rust_records_no_owner_for_a_free_function() {
2809        let g = build_rust("fn helper() {}");
2810        let s = g
2811            .symbols
2812            .iter()
2813            .find(|s| s.name == "helper")
2814            .expect("fn recorded");
2815        assert_eq!(s.owner, None);
2816    }
2817
2818    #[test]
2819    fn rust_records_an_inline_module_as_the_owner() {
2820        // `util::helper()` names the module, so the module has to be an owner
2821        // for the qualifier to match anything.
2822        let g = build_rust("mod util { pub fn helper() {} }");
2823        let s = g
2824            .symbols
2825            .iter()
2826            .find(|s| s.name == "helper")
2827            .expect("fn recorded");
2828        assert_eq!(s.owner.as_deref(), Some("util"));
2829    }
2830
2831    #[test]
2832    fn python_records_the_owning_class_of_a_method() {
2833        let g = build(
2834            Language::Python,
2835            "class Widget:\n    def get(self):\n        pass\n",
2836        );
2837        let s = g
2838            .symbols
2839            .iter()
2840            .find(|s| s.name == "get")
2841            .expect("method recorded");
2842        assert_eq!(s.owner.as_deref(), Some("Widget"));
2843    }
2844
2845    #[test]
2846    fn a_slice_with_no_qualifier_or_owner_serializes_unchanged() {
2847        // Both new fields are skipped when absent, so a file of plain free
2848        // functions keeps the byte-identical slice — and therefore the content
2849        // hash — it had before #248, exactly as `RefKind` did in #223.
2850        let g = build_rust("fn helper() -> u32 { 1 } fn main() { helper(); }");
2851        let json = serde_json::to_string(&g).expect("serialize");
2852        assert!(!json.contains("qualifier"), "{json}");
2853        assert!(!json.contains("owner"), "{json}");
2854    }
2855
2856    #[test]
2857    fn rust_records_a_generic_impl_by_its_base_type() {
2858        // A call site writes `Holder::get()`, never `Holder<T>::get()`, so the
2859        // owner has to be the bare type name or the qualifier never matches.
2860        let g = build_rust("struct Holder<T>(T); impl<T> Holder<T> { fn get(&self) {} }");
2861        let s = g
2862            .symbols
2863            .iter()
2864            .find(|s| s.name == "get")
2865            .expect("method recorded");
2866        assert_eq!(s.owner.as_deref(), Some("Holder"));
2867    }
2868
2869    #[test]
2870    fn rust_records_a_path_qualified_impl_by_its_last_segment() {
2871        let g = build_rust("impl crate::model::Widget { fn get(&self) {} }");
2872        let s = g
2873            .symbols
2874            .iter()
2875            .find(|s| s.name == "get")
2876            .expect("method recorded");
2877        assert_eq!(s.owner.as_deref(), Some("Widget"));
2878    }
2879
2880    // ---- functions passed as values (#250) --------------------------------
2881
2882    /// `(name, kind)` for every reference in `src`.
2883    fn rust_refs(src: &str) -> Vec<(String, RefKind)> {
2884        build_rust(src)
2885            .references
2886            .iter()
2887            .map(|r| (r.name.clone(), r.kind))
2888            .collect()
2889    }
2890
2891    #[test]
2892    fn rust_records_a_function_passed_as_a_call_argument() {
2893        let refs = rust_refs("fn g(o: Opt) { o.and_then(helper); }");
2894        assert!(
2895            refs.contains(&("helper".to_string(), RefKind::Value)),
2896            "{refs:?}"
2897        );
2898    }
2899
2900    #[test]
2901    fn rust_records_a_function_passed_to_a_free_call() {
2902        let refs = rust_refs("fn g() { register(handler); }");
2903        assert!(
2904            refs.contains(&("handler".to_string(), RefKind::Value)),
2905            "{refs:?}"
2906        );
2907    }
2908
2909    #[test]
2910    fn a_called_function_is_not_also_recorded_as_a_value() {
2911        let refs = rust_refs("fn g() { helper(); }");
2912        assert_eq!(
2913            refs.iter().filter(|(n, _)| n == "helper").count(),
2914            1,
2915            "{refs:?}"
2916        );
2917        assert!(
2918            refs.contains(&("helper".to_string(), RefKind::Free)),
2919            "{refs:?}"
2920        );
2921    }
2922
2923    #[test]
2924    fn a_literal_argument_records_nothing() {
2925        let refs = rust_refs("fn g() { take(1); }");
2926        assert_eq!(
2927            refs.iter().filter(|(n, _)| n != "take").count(),
2928            0,
2929            "{refs:?}"
2930        );
2931    }
2932
2933    #[test]
2934    fn a_local_variable_passed_as_an_argument_is_recorded_too() {
2935        // Deliberate over-inclusion, pinned so it is a decision rather than a
2936        // surprise: extraction is per-file and cannot know whether `x` names a
2937        // local or a function. It costs a row and, for `unreferenced`, errs
2938        // towards *not* calling something dead — the safe direction for a tool
2939        // whose suggested action is deletion.
2940        let refs = rust_refs("fn g(x: u32) { take(x); }");
2941        assert!(
2942            refs.contains(&("x".to_string(), RefKind::Value)),
2943            "{refs:?}"
2944        );
2945    }
2946
2947    // ---- syntactic receiver typing (#251) ---------------------------------
2948
2949    /// The qualifier recorded on the reference named `name`.
2950    fn rust_qualifier_of(src: &str, name: &str) -> Option<String> {
2951        build_rust(src)
2952            .references
2953            .iter()
2954            .find(|r| r.name == name)
2955            .expect("reference recorded")
2956            .qualifier
2957            .clone()
2958    }
2959
2960    #[test]
2961    fn a_receiver_bound_from_a_constructor_is_typed() {
2962        let q = rust_qualifier_of("fn g() { let w = Widget::new(); w.get(); }", "get");
2963        assert_eq!(q.as_deref(), Some("Widget"));
2964    }
2965
2966    #[test]
2967    fn a_receiver_bound_from_a_struct_literal_is_typed() {
2968        let q = rust_qualifier_of("fn g() { let w = Widget { n: 1 }; w.get(); }", "get");
2969        assert_eq!(q.as_deref(), Some("Widget"));
2970    }
2971
2972    #[test]
2973    fn a_receiver_from_a_typed_parameter_is_typed() {
2974        let q = rust_qualifier_of("fn g(w: Widget) { w.get(); }", "get");
2975        assert_eq!(q.as_deref(), Some("Widget"));
2976    }
2977
2978    #[test]
2979    fn a_receiver_from_a_reference_parameter_is_typed() {
2980        // `&Widget` and `&mut Widget` are the common shapes; the type is what
2981        // matters, not how it is borrowed.
2982        assert_eq!(
2983            rust_qualifier_of("fn g(w: &Widget) { w.get(); }", "get").as_deref(),
2984            Some("Widget")
2985        );
2986        assert_eq!(
2987            rust_qualifier_of("fn g(w: &mut Widget) { w.get(); }", "get").as_deref(),
2988            Some("Widget")
2989        );
2990    }
2991
2992    #[test]
2993    fn a_receiver_of_unknown_origin_stays_unqualified() {
2994        // `make()` says nothing about what it returns without a type system.
2995        let q = rust_qualifier_of("fn g() { let w = make(); w.get(); }", "get");
2996        assert_eq!(q, None);
2997    }
2998
2999    #[test]
3000    fn a_generic_receiver_stays_unqualified() {
3001        // Explicitly out of scope: a type parameter names no type in the view,
3002        // and guessing an impl is the thing #223 stopped doing.
3003        let q = rust_qualifier_of("fn g<T: Tr>(w: T) { w.get(); }", "get");
3004        assert_eq!(q, None);
3005    }
3006
3007    #[test]
3008    fn receiver_types_do_not_leak_between_functions() {
3009        // `w` means something different in each body; a flat table would carry
3010        // the first one into the second.
3011        let src = "fn a() { let w = Widget::new(); }\nfn b(w: Other) { w.get(); }";
3012        assert_eq!(rust_qualifier_of(src, "get").as_deref(), Some("Other"));
3013    }
3014
3015    #[test]
3016    fn a_plain_method_call_is_still_a_method_reference() {
3017        // Typing the receiver must not turn it into a free call: the shape is
3018        // what stops a std method being credited to a project function (#223).
3019        let g = build_rust("fn g(w: Widget) { w.get(); }");
3020        let r = g.references.iter().find(|r| r.name == "get").unwrap();
3021        assert_eq!(r.kind, RefKind::Method);
3022    }
3023
3024    #[test]
3025    fn a_self_receiver_takes_the_enclosing_type() {
3026        // `self` is the one receiver whose type is always written down: it is
3027        // the impl block the call sits in.
3028        let g = build_rust("impl Widget { fn get(&self) { self.helper(); } }");
3029        let r = g
3030            .references
3031            .iter()
3032            .find(|r| r.name == "helper")
3033            .expect("call recorded");
3034        assert_eq!(r.qualifier.as_deref(), Some("Widget"));
3035    }
3036
3037    #[test]
3038    fn a_non_rust_method_call_records_no_receiver_type() {
3039        // Receiver typing is Rust-only for now; every other language behaves
3040        // exactly as it did rather than guessing (#251).
3041        let g = build(Language::Python, "def g(w):\n    w.get()\n");
3042        let r = g
3043            .references
3044            .iter()
3045            .find(|r| r.name == "get")
3046            .expect("call recorded");
3047        assert_eq!(r.qualifier, None);
3048    }
3049
3050    // ---- imports (#252) ---------------------------------------------------
3051
3052    /// `(name, path)` for every import in `src`.
3053    fn imports_of(language: Language, src: &str) -> Vec<(String, Vec<String>)> {
3054        build(language, src)
3055            .imports
3056            .iter()
3057            .map(|i| (i.name.clone(), i.path.clone()))
3058            .collect()
3059    }
3060
3061    #[test]
3062    fn rust_records_a_simple_use_declaration() {
3063        let imports = imports_of(Language::Rust, "use crate::model::Symbol;");
3064        assert_eq!(
3065            imports,
3066            vec![("Symbol".to_string(), vec!["model".to_string()])],
3067            "`crate` is a relative marker, not a path component"
3068        );
3069    }
3070
3071    #[test]
3072    fn rust_records_each_name_in_a_use_list() {
3073        let imports = imports_of(Language::Rust, "use a::b::{C, D};");
3074        assert_eq!(
3075            imports,
3076            vec![
3077                ("C".to_string(), vec!["a".to_string(), "b".to_string()]),
3078                ("D".to_string(), vec!["a".to_string(), "b".to_string()]),
3079            ]
3080        );
3081    }
3082
3083    #[test]
3084    fn rust_records_an_aliased_import_under_its_local_name() {
3085        // `as` renames it for this file, and the local name is what a reference
3086        // in this file will be written as.
3087        let imports = imports_of(Language::Rust, "use a::Thing as Other;");
3088        assert_eq!(imports, vec![("Other".to_string(), vec!["a".to_string()])]);
3089    }
3090
3091    #[test]
3092    fn rust_records_nothing_for_a_glob_import() {
3093        // `use a::*` brings in names the file never spells out, so there is no
3094        // name to key on.
3095        assert!(imports_of(Language::Rust, "use a::b::*;").is_empty());
3096    }
3097
3098    #[test]
3099    fn python_records_a_from_import() {
3100        let imports = imports_of(Language::Python, "from pkg.model import Symbol\n");
3101        assert_eq!(
3102            imports,
3103            vec![(
3104                "Symbol".to_string(),
3105                vec!["pkg".to_string(), "model".to_string()]
3106            )]
3107        );
3108    }
3109
3110    #[test]
3111    fn typescript_records_a_named_import() {
3112        let imports = imports_of(Language::TypeScript, "import { Symbol } from './model';\n");
3113        assert_eq!(
3114            imports,
3115            vec![("Symbol".to_string(), vec!["model".to_string()])],
3116            "a leading `.` is a relative marker, not a path component"
3117        );
3118    }
3119
3120    #[test]
3121    fn a_slice_with_no_imports_serializes_unchanged() {
3122        let g = build_rust("fn helper() {}");
3123        let json = serde_json::to_string(&g).expect("serialize");
3124        assert!(!json.contains("imports"), "{json}");
3125    }
3126
3127    #[test]
3128    fn typescript_drops_a_bare_path_alias_segment() {
3129        // Found by scanning a real Next.js app: `@/` is a tsconfig path alias
3130        // rooted at the project, so it names no directory. Left in, it added a
3131        // segment that could never match and the import matched nothing at all.
3132        let imports = imports_of(
3133            Language::TypeScript,
3134            "import { resetDefaults } from '@/lib/redux/slices/colors';\n",
3135        );
3136        assert_eq!(
3137            imports,
3138            vec![(
3139                "resetDefaults".to_string(),
3140                vec![
3141                    "lib".to_string(),
3142                    "redux".to_string(),
3143                    "slices".to_string(),
3144                    "colors".to_string()
3145                ]
3146            )]
3147        );
3148    }
3149
3150    #[test]
3151    fn typescript_keeps_a_scoped_package_name() {
3152        // `@scope/pkg` is a package name, not an alias — only a *bare* `@` is
3153        // the alias marker, so the scope survives.
3154        let imports = imports_of(
3155            Language::TypeScript,
3156            "import { render } from '@testing-library/react';\n",
3157        );
3158        assert_eq!(
3159            imports,
3160            vec![(
3161                "render".to_string(),
3162                vec!["@testing-library".to_string(), "react".to_string()]
3163            )]
3164        );
3165    }
3166
3167    // ---- Java / Kotlin imports (#260) --------------------------------------
3168
3169    #[test]
3170    fn java_records_an_import_declaration() {
3171        let imports = imports_of(Language::Java, "import java.util.List;\n");
3172        assert_eq!(
3173            imports,
3174            vec![(
3175                "List".to_string(),
3176                vec!["java".to_string(), "util".to_string()]
3177            )]
3178        );
3179    }
3180
3181    #[test]
3182    fn java_records_a_static_import_under_the_member_name() {
3183        // `import static a.b.C.d;` brings `d` into scope, not `C`.
3184        let imports = imports_of(
3185            Language::Java,
3186            "import static org.junit.Assert.assertEquals;\n",
3187        );
3188        assert_eq!(
3189            imports,
3190            vec![(
3191                "assertEquals".to_string(),
3192                vec!["org".to_string(), "junit".to_string(), "Assert".to_string()]
3193            )]
3194        );
3195    }
3196
3197    #[test]
3198    fn java_records_nothing_for_a_wildcard_import() {
3199        assert!(imports_of(Language::Java, "import java.util.*;\n").is_empty());
3200        assert!(imports_of(Language::Java, "import static java.util.Map.*;\n").is_empty());
3201    }
3202
3203    #[test]
3204    fn kotlin_records_an_import_header() {
3205        let imports = imports_of(Language::Kotlin, "import kotlin.collections.List\n");
3206        assert_eq!(
3207            imports,
3208            vec![(
3209                "List".to_string(),
3210                vec!["kotlin".to_string(), "collections".to_string()]
3211            )]
3212        );
3213    }
3214
3215    #[test]
3216    fn kotlin_records_an_aliased_import_under_its_local_name() {
3217        let imports = imports_of(Language::Kotlin, "import a.b.Thing as Other\n");
3218        assert_eq!(
3219            imports,
3220            vec![("Other".to_string(), vec!["a".to_string(), "b".to_string()])]
3221        );
3222    }
3223
3224    #[test]
3225    fn kotlin_records_nothing_for_a_wildcard_import() {
3226        assert!(imports_of(Language::Kotlin, "import a.b.*\n").is_empty());
3227    }
3228
3229    // ---- functions bound to a name in JS/TS (#257) -------------------------
3230
3231    /// Symbols of kind `Function` in `src`, with their owner.
3232    fn ts_functions(src: &str) -> Vec<(String, Option<String>)> {
3233        build(Language::TypeScript, src)
3234            .symbols
3235            .iter()
3236            .filter(|s| s.kind == SymbolKind::Function)
3237            .map(|s| (s.name.clone(), s.owner.clone()))
3238            .collect()
3239    }
3240
3241    #[test]
3242    fn ts_still_records_a_plainly_bound_function() {
3243        // Regression guard: these already worked before #257 and must keep
3244        // working.
3245        assert_eq!(
3246            ts_functions("const handler = () => {};"),
3247            vec![("handler".to_string(), None)]
3248        );
3249        assert_eq!(
3250            ts_functions("const handler = function () {};"),
3251            vec![("handler".to_string(), None)]
3252        );
3253        assert_eq!(
3254            ts_functions("export const Component = async () => {};"),
3255            vec![("Component".to_string(), None)]
3256        );
3257        assert_eq!(
3258            ts_functions("class Widget { render = () => {}; }"),
3259            vec![("render".to_string(), Some("Widget".to_string()))]
3260        );
3261    }
3262
3263    #[test]
3264    fn ts_records_a_function_wrapped_in_a_higher_order_call() {
3265        // The real defect: `export const C = React.memo(() => {})` is the
3266        // dominant React idiom, and gonzalo extracted *zero* symbols from files
3267        // written that way (#257).
3268        let fns = ts_functions("export const Controls = React.memo(() => {});");
3269        assert_eq!(fns, vec![("Controls".to_string(), None)]);
3270    }
3271
3272    #[test]
3273    fn ts_records_a_function_through_two_wrappers() {
3274        let fns = ts_functions("const Input = memo(forwardRef(() => {}));");
3275        assert_eq!(fns, vec![("Input".to_string(), None)]);
3276    }
3277
3278    #[test]
3279    fn a_binding_to_a_call_with_no_function_in_it_records_nothing() {
3280        assert!(ts_functions("const total = compute(1, 2);").is_empty());
3281        assert!(ts_functions("const client = createClient({ url });").is_empty());
3282    }
3283
3284    #[test]
3285    fn a_call_inside_a_wrapped_component_records_it_as_the_caller() {
3286        let g = build(
3287            Language::TypeScript,
3288            "export const Controls = React.memo(() => { useAppDispatch(); });",
3289        );
3290        let call = g
3291            .references
3292            .iter()
3293            .find(|r| r.name == "useAppDispatch")
3294            .expect("call recorded");
3295        assert_eq!(call.from.as_deref(), Some("Controls"));
3296    }
3297
3298    #[test]
3299    fn ts_records_an_object_property_arrow_function() {
3300        // The name lives in `key` here, not `name`.
3301        let fns = ts_functions("const api = { fetchAll: () => {} };");
3302        assert_eq!(fns, vec![("fetchAll".to_string(), None)]);
3303    }
3304
3305    #[test]
3306    fn a_binding_that_is_not_a_function_is_not_recorded_as_one() {
3307        assert!(ts_functions("const count = 1;").is_empty());
3308        assert!(ts_functions("const items = [1, 2];").is_empty());
3309        assert!(ts_functions("const other = helper;").is_empty());
3310    }
3311
3312    #[test]
3313    fn a_call_inside_a_bound_function_records_it_as_the_caller() {
3314        // The whole point: 72% of references in a real Next.js app had no
3315        // enclosing function, so they reached neither `callers` nor `impact`.
3316        let g = build(
3317            Language::TypeScript,
3318            "const boolToString = (b: boolean) => format(b);",
3319        );
3320        let call = g
3321            .references
3322            .iter()
3323            .find(|r| r.name == "format")
3324            .expect("call recorded");
3325        assert_eq!(call.from.as_deref(), Some("boolToString"));
3326    }
3327
3328    #[test]
3329    fn a_nested_bound_function_takes_over_as_the_caller() {
3330        let g = build(
3331            Language::TypeScript,
3332            "const outer = () => { const inner = () => { deep(); }; shallow(); };",
3333        );
3334        let from = |name: &str| {
3335            g.references
3336                .iter()
3337                .find(|r| r.name == name)
3338                .and_then(|r| r.from.clone())
3339        };
3340        assert_eq!(from("deep").as_deref(), Some("inner"));
3341        assert_eq!(from("shallow").as_deref(), Some("outer"));
3342    }
3343
3344    #[test]
3345    fn an_iterator_method_does_not_make_its_binding_a_function() {
3346        // The guard that keeps this from trading a missing answer for a wrong
3347        // one: `items.map(..)` returns an array. Treating `total` as a function
3348        // would attribute everything inside the lambda to `total` rather than
3349        // to the function actually containing it.
3350        assert!(ts_functions("const total = items.map((x) => f(x));").is_empty());
3351        assert!(ts_functions("const kept = list.filter((x) => keep(x));").is_empty());
3352    }
3353
3354    #[test]
3355    fn a_lambda_in_an_iterator_call_keeps_the_real_enclosing_function() {
3356        let g = build(
3357            Language::TypeScript,
3358            "const render = () => { const rows = items.map((x) => cell(x)); };",
3359        );
3360        let call = g
3361            .references
3362            .iter()
3363            .find(|r| r.name == "cell")
3364            .expect("call recorded");
3365        assert_eq!(call.from.as_deref(), Some("render"));
3366    }
3367
3368    // ---- relative import depth (#261) --------------------------------------
3369
3370    /// `(name, path, depth)` for every import in `src`.
3371    fn imports_with_depth(language: Language, src: &str) -> Vec<(String, Vec<String>, usize)> {
3372        build(language, src)
3373            .imports
3374            .iter()
3375            .map(|i| (i.name.clone(), i.path.clone(), i.depth))
3376            .collect()
3377    }
3378
3379    #[test]
3380    fn python_records_the_depth_of_a_relative_import() {
3381        // `..pkg.sub` is two dots deep: the referencing file's parent package,
3382        // then `pkg/sub` under it.
3383        let imports = imports_with_depth(Language::Python, "from ..pkg.sub import C\n");
3384        assert_eq!(
3385            imports,
3386            vec![(
3387                "C".to_string(),
3388                vec!["pkg".to_string(), "sub".to_string()],
3389                2
3390            )]
3391        );
3392    }
3393
3394    #[test]
3395    fn python_records_a_bare_single_dot_import() {
3396        let imports = imports_with_depth(Language::Python, "from . import A\n");
3397        assert_eq!(imports, vec![("A".to_string(), vec![], 1)]);
3398    }
3399
3400    #[test]
3401    fn an_absolute_python_import_records_no_depth() {
3402        let imports = imports_with_depth(Language::Python, "from pkg import D\n");
3403        assert_eq!(imports, vec![("D".to_string(), vec!["pkg".to_string()], 0)]);
3404    }
3405
3406    #[test]
3407    fn a_slice_with_only_absolute_imports_serializes_without_depth() {
3408        let g = build(Language::Python, "from pkg import D\n");
3409        let json = serde_json::to_string(&g).expect("serialize");
3410        assert!(!json.contains("depth"), "{json}");
3411    }
3412
3413    // ---- .h headers are C++ too (#266) -------------------------------------
3414
3415    /// `(name, kind)` for every symbol in `src` under `language`.
3416    fn symbols_of(language: Language, src: &str) -> Vec<(String, SymbolKind)> {
3417        let mut out: Vec<(String, SymbolKind)> = build(language, src)
3418            .symbols
3419            .iter()
3420            .map(|s| (s.name.clone(), s.kind))
3421            .collect();
3422        out.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| a.1.as_str().cmp(b.1.as_str())));
3423        out
3424    }
3425
3426    #[test]
3427    fn a_h_file_is_parsed_as_cpp() {
3428        assert_eq!(Language::from_extension("h"), Some(Language::Cpp));
3429    }
3430
3431    #[test]
3432    fn a_scoped_enum_in_a_header_records_its_own_name() {
3433        // The bug, pinned: under the C grammar `enum class Color` recorded
3434        // `Color` as a *function* and invented a symbol named `class`. On a
3435        // real C++ project that produced 372 symbols called `class` across 133
3436        // files (#266).
3437        let header = Language::from_extension("h").expect("h is a source extension");
3438        let symbols = symbols_of(header, "enum class Color { Red, Green };");
3439        assert_eq!(symbols, vec![("Color".to_string(), SymbolKind::Enum)]);
3440    }
3441
3442    #[test]
3443    fn a_header_records_no_keyword_shaped_symbols() {
3444        let header = Language::from_extension("h").expect("h is a source extension");
3445        let symbols = symbols_of(
3446            header,
3447            "enum class A {};\nenum struct B { x };\nenum class C : unsigned char { y };",
3448        );
3449        let names: Vec<&str> = symbols.iter().map(|(n, _)| n.as_str()).collect();
3450        assert!(
3451            !names
3452                .iter()
3453                .any(|n| matches!(*n, "class" | "struct" | "namespace" | "template")),
3454            "{names:?}"
3455        );
3456        assert_eq!(names, vec!["A", "B", "C"]);
3457    }
3458
3459    #[test]
3460    fn cpp_declarations_in_a_header_record_their_real_names() {
3461        let header = Language::from_extension("h").expect("h is a source extension");
3462        let symbols = symbols_of(
3463            header,
3464            "namespace engine { class Widget { public: void go(); }; }",
3465        );
3466        let names: Vec<&str> = symbols.iter().map(|(n, _)| n.as_str()).collect();
3467        assert!(names.contains(&"engine"), "{names:?}");
3468        assert!(names.contains(&"Widget"), "{names:?}");
3469    }
3470
3471    #[test]
3472    fn a_pure_c_header_extracts_the_same_symbols_under_either_grammar() {
3473        // The criterion that decides whether this fix is safe: C++ is a near
3474        // superset, but "near" has to be evidence rather than assertion. If
3475        // these ever diverge, the fix is to try C++ first and re-parse as C on
3476        // a tree with errors.
3477        let c_source = "\
3478#include <stdio.h>
3479typedef struct Point { int x; int y; } Point;
3480enum Color { RED, GREEN };
3481struct Opaque;
3482static int helper(int n) { return n + 1; }
3483int main(void) { return helper(1); }
3484typedef int (*Callback)(void *ctx);
3485";
3486        assert_eq!(
3487            symbols_of(Language::C, c_source),
3488            symbols_of(Language::Cpp, c_source),
3489            "the two grammars must agree on plain C"
3490        );
3491    }
3492
3493    #[test]
3494    fn a_pure_c_header_records_the_same_references_under_either_grammar() {
3495        let c_source = "\
3496static int helper(int n) { return n + 1; }
3497int main(void) { return helper(1) + abs(-2); }
3498";
3499        let names = |language| {
3500            let mut out: Vec<String> = build(language, c_source)
3501                .references
3502                .iter()
3503                .map(|r| format!("{}:{:?}", r.name, r.kind))
3504                .collect();
3505            out.sort();
3506            out
3507        };
3508        assert_eq!(names(Language::C), names(Language::Cpp));
3509    }
3510
3511    // ---- C/C++ includes (#267) ---------------------------------------------
3512
3513    /// `(name, path)` for every import in `src` under `language`.
3514    fn includes_of(language: Language, src: &str) -> Vec<(String, Vec<String>)> {
3515        build(language, src)
3516            .imports
3517            .iter()
3518            .map(|i| (i.name.clone(), i.path.clone()))
3519            .collect()
3520    }
3521
3522    #[test]
3523    fn c_records_a_quoted_include_as_a_path() {
3524        // An include names a *file*, not a name, so it carries no name — a
3525        // stronger signal than any package path, because the file is the thing
3526        // rather than a convention a path is assumed to mirror (#267).
3527        let imports = includes_of(Language::C, "#include \"engine/render/pipeline.h\"\n");
3528        assert_eq!(
3529            imports,
3530            vec![(
3531                String::new(),
3532                vec![
3533                    "engine".to_string(),
3534                    "render".to_string(),
3535                    "pipeline".to_string()
3536                ]
3537            )]
3538        );
3539    }
3540
3541    #[test]
3542    fn an_include_is_marked_as_bringing_a_whole_file() {
3543        let g = build(Language::C, "#include \"a/b.h\"\n");
3544        assert!(g.imports[0].brings_whole_file());
3545        let py = build(Language::Python, "from pkg import thing\n");
3546        assert!(!py.imports[0].brings_whole_file());
3547    }
3548
3549    #[test]
3550    fn c_records_an_angled_include() {
3551        // A system header matches nothing in the view, exactly as
3552        // `import java.io.File` does after #260 — recorded, and correctly inert.
3553        let imports = includes_of(Language::C, "#include <vector>\n");
3554        assert_eq!(imports, vec![(String::new(), vec!["vector".to_string()])]);
3555    }
3556
3557    #[test]
3558    fn cpp_records_includes_the_same_way_as_c() {
3559        let src = "#include \"a/b.hpp\"\n#include <memory>\n";
3560        assert_eq!(
3561            includes_of(Language::C, src),
3562            includes_of(Language::Cpp, src)
3563        );
3564    }
3565
3566    #[test]
3567    fn an_include_with_no_directory_records_its_stem() {
3568        let imports = includes_of(Language::C, "#include \"config.h\"\n");
3569        assert_eq!(imports, vec![(String::new(), vec!["config".to_string()])]);
3570    }
3571
3572    // ---- CommonJS require (#269) -------------------------------------------
3573
3574    #[test]
3575    fn js_records_a_plain_require() {
3576        let imports = imports_of(Language::JavaScript, "const fs = require('fs');\n");
3577        assert_eq!(imports, vec![("fs".to_string(), vec!["fs".to_string()])]);
3578    }
3579
3580    #[test]
3581    fn js_records_each_destructured_require_name() {
3582        let imports = imports_of(
3583            Language::JavaScript,
3584            "const { getEnv, createLogger } = require('../util/env');\n",
3585        );
3586        assert_eq!(
3587            imports,
3588            vec![
3589                (
3590                    "getEnv".to_string(),
3591                    vec!["util".to_string(), "env".to_string()]
3592                ),
3593                (
3594                    "createLogger".to_string(),
3595                    vec!["util".to_string(), "env".to_string()]
3596                ),
3597            ]
3598        );
3599    }
3600
3601    #[test]
3602    fn js_records_a_renamed_destructured_require_under_its_local_name() {
3603        // `{ a: c }` means this file writes `c`, the same rule the ES `as`
3604        // alias already follows.
3605        let imports = imports_of(
3606            Language::JavaScript,
3607            "const { helper: localHelper } = require('./mod');\n",
3608        );
3609        assert_eq!(
3610            imports,
3611            vec![("localHelper".to_string(), vec!["mod".to_string()])]
3612        );
3613    }
3614
3615    #[test]
3616    fn a_computed_require_records_nothing() {
3617        // No literal path means no module to key on, the same way a glob import
3618        // introduces no name.
3619        assert!(imports_of(Language::JavaScript, "const m = require(name);\n").is_empty());
3620        assert!(imports_of(Language::JavaScript, "const m = require(`./${x}`);\n").is_empty());
3621    }
3622
3623    #[test]
3624    fn a_binding_that_is_not_a_require_records_nothing() {
3625        assert!(imports_of(Language::JavaScript, "const total = compute(1);\n").is_empty());
3626        assert!(imports_of(Language::JavaScript, "const n = 1;\n").is_empty());
3627    }
3628
3629    #[test]
3630    fn typescript_records_a_require_too() {
3631        let imports = imports_of(Language::TypeScript, "const fs = require('node:fs');\n");
3632        assert_eq!(
3633            imports,
3634            vec![("fs".to_string(), vec!["node:fs".to_string()])]
3635        );
3636    }
3637
3638    // ---- module-level attribution (#268) -----------------------------------
3639
3640    /// `(name, from, scope)` for every reference in `src`.
3641    fn attributed(language: Language, src: &str) -> Vec<(String, Option<String>, FromScope)> {
3642        build(language, src)
3643            .references
3644            .iter()
3645            .map(|r| (r.name.clone(), r.from.clone(), r.from_scope))
3646            .collect()
3647    }
3648
3649    /// The `from` and scope recorded for the reference named `name`.
3650    fn attribution_of(src: &str, name: &str) -> (Option<String>, FromScope) {
3651        attributed(Language::TypeScript, src)
3652            .into_iter()
3653            .find(|(n, _, _)| n == name)
3654            .map(|(_, from, scope)| (from, scope))
3655            .expect("reference recorded")
3656    }
3657
3658    #[test]
3659    fn a_module_level_binding_names_the_calls_in_its_initializer() {
3660        // A Zod schema: `z.object(..)` is a call on a lowercase receiver, so
3661        // #257's wrapper rule correctly refuses to call `allEnv` a function —
3662        // and every call inside it used to be orphaned (#268).
3663        let src = "const allEnv = z.object({ PORT: z.string() });";
3664
3665        // The call in the binding's own initializer takes the binding.
3666        let (from, scope) = attribution_of(src, "object");
3667        assert_eq!(from.as_deref(), Some("allEnv"));
3668        assert_eq!(scope, FromScope::Module);
3669
3670        // A call nested under a keyed property takes that key, because it is
3671        // nearer and says more: `PORT` names which setting this call builds.
3672        let (from, scope) = attribution_of(src, "string");
3673        assert_eq!(from.as_deref(), Some("PORT"));
3674        assert_eq!(scope, FromScope::Module);
3675    }
3676
3677    #[test]
3678    fn a_call_inside_a_real_function_is_unchanged() {
3679        // The guarantee that makes this strictly additive: an enclosing
3680        // function still wins, and still reports itself as a function.
3681        let (from, scope) = attribution_of("const render = () => { cell(); };", "cell");
3682        assert_eq!(from.as_deref(), Some("render"));
3683        assert_eq!(scope, FromScope::Function);
3684    }
3685
3686    #[test]
3687    fn an_iterator_inside_a_function_keeps_the_function() {
3688        // #257's guard still holds where it matters: inside a function the
3689        // alternative was a correct answer, not nothing.
3690        let (from, scope) = attribution_of(
3691            "const render = () => { const rows = items.map((x) => cell(x)); };",
3692            "cell",
3693        );
3694        assert_eq!(from.as_deref(), Some("render"));
3695        assert_eq!(scope, FromScope::Function);
3696    }
3697
3698    #[test]
3699    fn the_nearest_module_binding_wins() {
3700        // A tRPC router: the handler's own key names it, not the whole router.
3701        let (from, scope) = attribution_of(
3702            "export const router = make({ createBookmark: proc.input(z.string()) });",
3703            "string",
3704        );
3705        assert_eq!(from.as_deref(), Some("createBookmark"));
3706        assert_eq!(scope, FromScope::Module);
3707    }
3708
3709    #[test]
3710    fn a_top_level_call_in_no_binding_stays_unattributed() {
3711        // `export default memo(X)` has no name to give it, and inventing one
3712        // would be worse than reporting none.
3713        let (from, scope) = attribution_of("export default memo(Widget);", "memo");
3714        assert_eq!(from, None);
3715        assert_eq!(scope, FromScope::Function, "the default, meaning nothing");
3716    }
3717
3718    #[test]
3719    fn a_module_scope_slice_still_serializes_without_the_field_when_absent() {
3720        let g = build(Language::TypeScript, "const render = () => { cell(); };");
3721        let json = serde_json::to_string(&g).expect("serialize");
3722        assert!(!json.contains("from_scope"), "{json}");
3723    }
3724}