1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14pub enum Language {
15 Rust,
16 Python,
17 JavaScript,
18 TypeScript,
19 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 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" | "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 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 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 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 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 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 Self::Bash => match node_kind {
201 "function_definition" => Some(SymbolKind::Function),
202 _ => None,
203 },
204 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 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 Self::Lua => match node_kind {
225 "function_declaration" => Some(SymbolKind::Function),
226 _ => None,
227 },
228 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 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 fn item_name(self, node: Node<'_>, kind: SymbolKind, bytes: &[u8]) -> Option<String> {
249 match (self, kind) {
250 (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 (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 (Self::C | Self::Cpp, SymbolKind::Function | SymbolKind::TypeAlias) => {
266 c_declarator_name(node, bytes)
267 }
268 (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 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 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 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 fn callee_kind(self, call: Node<'_>) -> RefKind {
327 let by_call_node = match self {
329 Self::Php => matches!(
330 call.kind(),
331 "member_call_expression" | "nullsafe_member_call_expression"
332 ),
333 Self::Java => call.child_by_field_name("object").is_some(),
335 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 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 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 fn callee_qualifier(self, call: Node<'_>, bytes: &[u8]) -> Option<String> {
394 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 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 Self::Python if node.kind() == "import_from_statement" => {
438 let module_name = node.child_by_field_name("module_name");
439 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 Self::Java if node.kind() == "import_declaration" => {
471 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 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 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 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 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 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 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 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 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 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 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 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 "scoped_identifier" => func
713 .child_by_field_name("name")
714 .and_then(|n| node_text(n, bytes))
715 .map(str::to_string),
716 "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 "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 "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 "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 "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 "field_expression" => func
763 .child_by_field_name("field")
764 .and_then(|n| node_text(n, bytes))
765 .map(str::to_string),
766 "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 Self::Scala => last_identifier(func, bytes),
777 Self::Php => node_text(func, bytes).map(str::to_string),
780 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 fn callee_name(self, call: Node<'_>, bytes: &[u8]) -> Option<String> {
797 match self {
798 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 Self::Ruby => call
806 .child_by_field_name("method")
807 .and_then(|n| node_text(n, bytes))
808 .map(str::to_string),
809 Self::Kotlin | Self::Swift => call
814 .named_child(0)
815 .and_then(|callee| last_identifier(callee, bytes)),
816 Self::Lua => call
820 .child_by_field_name("name")
821 .and_then(|n| last_identifier(n, bytes)),
822 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 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
857fn 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
875fn 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
885fn 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
897fn 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
913fn 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
919fn 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
927fn 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
938fn 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
957fn 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 "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
976fn 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
1003fn 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 "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
1021fn 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
1034fn 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
1045fn 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
1056fn 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
1072pub 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
1099pub 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
1114fn 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_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 "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" => {
1153 collect_js_import_names(child, bytes, path, line, out);
1154 }
1155 _ => {}
1156 }
1157 }
1158}
1159
1160fn 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 "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
1186fn 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(); 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 "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
1258fn 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
1282fn 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 "dotted_name" => out.extend(python_module_segments(child, bytes)),
1296 _ => {}
1297 }
1298 }
1299 out
1300}
1301
1302fn 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
1324fn 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 "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
1354fn 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
1370fn 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
1386fn 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" | "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
1405fn 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
1430fn 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 let name = first_type_identifier(child, bytes);
1446 if let Some(name) = name {
1447 out.insert(name);
1448 }
1449 }
1450 out
1451}
1452
1453fn 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
1463fn 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 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
1501fn 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
1539fn 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#[derive(Clone, Copy)]
1561struct Inherited<'a> {
1562 enclosing: Option<&'a str>,
1564 scope: FromScope,
1566 owner: Option<&'a str>,
1568 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 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 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 qualifier: match kind {
1626 RefKind::Value => None,
1627 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 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 for import in language.imports_at(node, bytes) {
1654 graph.imports.push(import);
1655 }
1656
1657 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 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 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 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 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 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 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 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 #[test]
1842 fn c_records_macro_invocations_at_the_call_site() {
1843 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 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 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 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 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 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 assert!(
1998 g.references
1999 .iter()
2000 .any(|r| r.name == "helper" && r.from.as_deref() == Some("area"))
2001 );
2002 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 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 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 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 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 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 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 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 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 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)); 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 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)); 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 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 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)); 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 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 assert_eq!(named("Math"), Some(SymbolKind::Module));
2592 assert_eq!(named("add"), Some(SymbolKind::Function));
2594 assert_eq!(named("helper"), Some(SymbolKind::Function));
2596 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 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 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 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 #[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 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 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 #[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 #[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)); assert_eq!(named("Shape"), Some(SymbolKind::Interface)); }
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)); }
2736
2737 #[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 #[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 #[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 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 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 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 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 let src = "const allEnv = z.object({ PORT: z.string() });";
3664
3665 let (from, scope) = attribution_of(src, "object");
3667 assert_eq!(from.as_deref(), Some("allEnv"));
3668 assert_eq!(scope, FromScope::Module);
3669
3670 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 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 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 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 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}