1use crate::model::{CodeGraph, RefKind, Reference, Symbol, SymbolKind};
8use serde::{Deserialize, Serialize};
9use tree_sitter::{Node, Parser};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13pub enum Language {
14 Rust,
15 Python,
16 JavaScript,
17 TypeScript,
18 Tsx,
20 Go,
21 Java,
22 CSharp,
23 C,
24 Cpp,
25 Ruby,
26 Php,
27 Bash,
28 Kotlin,
29 Swift,
30 Lua,
31 Scala,
32 Elixir,
33}
34
35impl Language {
36 pub fn from_extension(ext: &str) -> Option<Self> {
39 match ext {
40 "rs" => Some(Self::Rust),
41 "py" => Some(Self::Python),
42 "js" | "jsx" | "mjs" | "cjs" => Some(Self::JavaScript),
43 "ts" | "mts" | "cts" => Some(Self::TypeScript),
44 "tsx" => Some(Self::Tsx),
45 "go" => Some(Self::Go),
46 "java" => Some(Self::Java),
47 "cs" => Some(Self::CSharp),
48 "c" | "h" => Some(Self::C),
49 "cpp" | "cc" | "cxx" | "hpp" | "hh" => Some(Self::Cpp),
50 "rb" => Some(Self::Ruby),
51 "php" => Some(Self::Php),
52 "sh" | "bash" => Some(Self::Bash),
53 "kt" | "kts" => Some(Self::Kotlin),
54 "swift" => Some(Self::Swift),
55 "lua" => Some(Self::Lua),
56 "scala" | "sc" => Some(Self::Scala),
57 "ex" | "exs" => Some(Self::Elixir),
58 _ => None,
59 }
60 }
61
62 pub fn as_str(self) -> &'static str {
64 match self {
65 Self::Rust => "rust",
66 Self::Python => "python",
67 Self::JavaScript => "javascript",
68 Self::TypeScript => "typescript",
69 Self::Tsx => "tsx",
70 Self::Go => "go",
71 Self::Java => "java",
72 Self::CSharp => "csharp",
73 Self::C => "c",
74 Self::Cpp => "cpp",
75 Self::Ruby => "ruby",
76 Self::Php => "php",
77 Self::Bash => "bash",
78 Self::Kotlin => "kotlin",
79 Self::Swift => "swift",
80 Self::Lua => "lua",
81 Self::Scala => "scala",
82 Self::Elixir => "elixir",
83 }
84 }
85
86 fn ts_language(self) -> tree_sitter::Language {
87 match self {
88 Self::Rust => tree_sitter_rust::LANGUAGE.into(),
89 Self::Python => tree_sitter_python::LANGUAGE.into(),
90 Self::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
91 Self::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
92 Self::Tsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
93 Self::Go => tree_sitter_go::LANGUAGE.into(),
94 Self::Java => tree_sitter_java::LANGUAGE.into(),
95 Self::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
96 Self::C => tree_sitter_c::LANGUAGE.into(),
97 Self::Cpp => tree_sitter_cpp::LANGUAGE.into(),
98 Self::Ruby => tree_sitter_ruby::LANGUAGE.into(),
99 Self::Php => tree_sitter_php::LANGUAGE_PHP.into(),
100 Self::Bash => tree_sitter_bash::LANGUAGE.into(),
101 Self::Kotlin => tree_sitter_kotlin_ng::LANGUAGE.into(),
102 Self::Swift => tree_sitter_swift::LANGUAGE.into(),
103 Self::Lua => tree_sitter_lua::LANGUAGE.into(),
104 Self::Scala => tree_sitter_scala::LANGUAGE.into(),
105 Self::Elixir => tree_sitter_elixir::LANGUAGE.into(),
106 }
107 }
108
109 fn item_kind(self, node: Node<'_>, bytes: &[u8]) -> Option<SymbolKind> {
115 let node_kind = node.kind();
116 match self {
117 Self::Rust => match node_kind {
118 "function_item" => Some(SymbolKind::Function),
119 "struct_item" => Some(SymbolKind::Struct),
120 "enum_item" => Some(SymbolKind::Enum),
121 "trait_item" => Some(SymbolKind::Trait),
122 "impl_item" => Some(SymbolKind::Impl),
123 "mod_item" => Some(SymbolKind::Module),
124 "const_item" => Some(SymbolKind::Const),
125 "static_item" => Some(SymbolKind::Static),
126 "type_item" => Some(SymbolKind::TypeAlias),
127 _ => None,
128 },
129 Self::Python => match node_kind {
130 "function_definition" => Some(SymbolKind::Function),
131 "class_definition" => Some(SymbolKind::Class),
132 _ => None,
133 },
134 Self::JavaScript => js_item_kind(node),
135 Self::TypeScript | Self::Tsx => js_item_kind(node).or(match node_kind {
137 "interface_declaration" => Some(SymbolKind::Interface),
138 "type_alias_declaration" => Some(SymbolKind::TypeAlias),
139 "enum_declaration" => Some(SymbolKind::Enum),
140 _ => None,
141 }),
142 Self::Go => match node_kind {
148 "function_declaration" | "method_declaration" => Some(SymbolKind::Function),
149 "struct_type" => Some(SymbolKind::Struct),
150 "interface_type" => Some(SymbolKind::Interface),
151 "const_spec" => Some(SymbolKind::Const),
152 "var_spec" => Some(SymbolKind::Static),
153 _ => None,
154 },
155 Self::Java => match node_kind {
156 "class_declaration" => Some(SymbolKind::Class),
157 "interface_declaration" => Some(SymbolKind::Interface),
158 "enum_declaration" => Some(SymbolKind::Enum),
159 "method_declaration" | "constructor_declaration" => Some(SymbolKind::Function),
160 _ => None,
161 },
162 Self::CSharp => match node_kind {
163 "class_declaration" => Some(SymbolKind::Class),
164 "interface_declaration" => Some(SymbolKind::Interface),
165 "struct_declaration" => Some(SymbolKind::Struct),
166 "enum_declaration" => Some(SymbolKind::Enum),
167 "method_declaration" | "constructor_declaration" => Some(SymbolKind::Function),
168 _ => None,
169 },
170 Self::C => c_item_kind(node_kind),
171 Self::Cpp => c_item_kind(node_kind).or(match node_kind {
173 "class_specifier" => Some(SymbolKind::Class),
174 "namespace_definition" => Some(SymbolKind::Module),
175 _ => None,
176 }),
177 Self::Ruby => match node_kind {
178 "method" | "singleton_method" => Some(SymbolKind::Function),
179 "class" => Some(SymbolKind::Class),
180 "module" => Some(SymbolKind::Module),
181 _ => None,
182 },
183 Self::Php => match node_kind {
184 "function_definition" | "method_declaration" => Some(SymbolKind::Function),
185 "class_declaration" => Some(SymbolKind::Class),
186 "interface_declaration" => Some(SymbolKind::Interface),
187 "trait_declaration" => Some(SymbolKind::Trait),
188 "enum_declaration" => Some(SymbolKind::Enum),
189 _ => None,
190 },
191 Self::Bash => match node_kind {
193 "function_definition" => Some(SymbolKind::Function),
194 _ => None,
195 },
196 Self::Kotlin => match node_kind {
200 "function_declaration" => Some(SymbolKind::Function),
201 "class_declaration" => Some(kotlin_class_kind(node)),
202 "object_declaration" => Some(SymbolKind::Class),
203 _ => None,
204 },
205 Self::Swift => match node_kind {
209 "function_declaration" => Some(SymbolKind::Function),
210 "class_declaration" => Some(swift_type_kind(node)),
211 "protocol_declaration" => Some(SymbolKind::Interface),
212 _ => None,
213 },
214 Self::Lua => match node_kind {
217 "function_declaration" => Some(SymbolKind::Function),
218 _ => None,
219 },
220 Self::Scala => match node_kind {
222 "function_definition" | "function_declaration" => Some(SymbolKind::Function),
223 "class_definition" | "object_definition" => Some(SymbolKind::Class),
224 "trait_definition" => Some(SymbolKind::Trait),
225 "enum_definition" => Some(SymbolKind::Enum),
226 _ => None,
227 },
228 Self::Elixir => elixir_target_name(node, bytes).and_then(|t| match t.as_str() {
232 "defmodule" => Some(SymbolKind::Module),
233 "def" | "defp" | "defmacro" | "defmacrop" => Some(SymbolKind::Function),
234 _ => None,
235 }),
236 }
237 }
238
239 fn item_name(self, node: Node<'_>, kind: SymbolKind, bytes: &[u8]) -> Option<String> {
241 match (self, kind) {
242 (Self::Rust, SymbolKind::Impl) => node
244 .child_by_field_name("type")
245 .and_then(|n| node_text(n, bytes))
246 .map(str::to_string),
247 (Self::Go, SymbolKind::Struct | SymbolKind::Interface) => node
251 .parent()
252 .and_then(|p| p.child_by_field_name("name"))
253 .and_then(|n| node_text(n, bytes))
254 .map(str::to_string),
255 (Self::C | Self::Cpp, SymbolKind::Function | SymbolKind::TypeAlias) => {
258 c_declarator_name(node, bytes)
259 }
260 (Self::Elixir, _) => elixir_defined_name(node, bytes),
264 _ => name_field(node, bytes),
265 }
266 }
267
268 fn is_call(self, node_kind: &str) -> bool {
270 match self {
271 Self::Rust
272 | Self::JavaScript
273 | Self::TypeScript
274 | Self::Tsx
275 | Self::Go
276 | Self::C
277 | Self::Cpp => node_kind == "call_expression",
278 Self::Python => node_kind == "call",
279 Self::Java => node_kind == "method_invocation",
280 Self::CSharp => node_kind == "invocation_expression",
281 Self::Ruby => node_kind == "call",
282 Self::Php => matches!(
284 node_kind,
285 "function_call_expression"
286 | "member_call_expression"
287 | "nullsafe_member_call_expression"
288 | "scoped_call_expression"
289 ),
290 Self::Bash => node_kind == "command",
292 Self::Kotlin | Self::Swift | Self::Scala => node_kind == "call_expression",
293 Self::Lua => node_kind == "function_call",
294 Self::Elixir => node_kind == "call",
295 }
296 }
297
298 fn callee_kind(self, call: Node<'_>) -> RefKind {
310 let by_call_node = match self {
312 Self::Php => matches!(
313 call.kind(),
314 "member_call_expression" | "nullsafe_member_call_expression"
315 ),
316 Self::Java => call.child_by_field_name("object").is_some(),
318 Self::Ruby => call.child_by_field_name("receiver").is_some(),
320 _ => false,
321 };
322 if by_call_node {
323 return RefKind::Method;
324 }
325
326 let callee = match self {
328 Self::Kotlin | Self::Swift => call.named_child(0),
329 Self::Lua => call.child_by_field_name("name"),
330 _ => call.child_by_field_name("function"),
331 };
332 let Some(callee) = callee else {
333 return RefKind::Free;
334 };
335 let member_like = matches!(
340 (self, callee.kind()),
341 (
342 Self::Rust | Self::Scala | Self::C | Self::Cpp,
343 "field_expression"
344 ) | (Self::Python, "attribute")
345 | (
346 Self::JavaScript | Self::TypeScript | Self::Tsx,
347 "member_expression"
348 )
349 | (Self::CSharp, "member_access_expression")
350 | (Self::Kotlin | Self::Swift, "navigation_expression")
351 | (
352 Self::Lua,
353 "dot_index_expression" | "method_index_expression"
354 )
355 );
356 if member_like {
357 RefKind::Method
358 } else {
359 RefKind::Free
360 }
361 }
362
363 fn macro_arg_calls(self, node: Node<'_>, bytes: &[u8]) -> Vec<(String, usize)> {
382 if self != Self::Rust || node.kind() != "token_tree" {
383 return Vec::new();
384 }
385 let mut out = Vec::new();
386 let mut cursor = node.walk();
387 for child in node.children(&mut cursor) {
388 if child.kind() != "identifier" {
389 continue;
390 }
391 let Some(next) = child.next_sibling() else {
392 continue;
393 };
394 if next.kind() != "token_tree"
397 || !next.utf8_text(bytes).is_ok_and(|t| t.starts_with('('))
398 {
399 continue;
400 }
401 if let Some(name) = node_text(child, bytes) {
402 out.push((name.to_string(), child.start_position().row + 1));
403 }
404 }
405 out
406 }
407
408 fn call_name(self, func: Node<'_>, bytes: &[u8]) -> Option<String> {
410 match self {
411 Self::Rust => match func.kind() {
412 "identifier" => node_text(func, bytes).map(str::to_string),
413 "scoped_identifier" => func
415 .child_by_field_name("name")
416 .and_then(|n| node_text(n, bytes))
417 .map(str::to_string),
418 "field_expression" => func
420 .child_by_field_name("field")
421 .and_then(|n| node_text(n, bytes))
422 .map(str::to_string),
423 _ => node_text(func, bytes).map(str::to_string),
424 },
425 Self::Python => match func.kind() {
426 "identifier" => node_text(func, bytes).map(str::to_string),
427 "attribute" => func
429 .child_by_field_name("attribute")
430 .and_then(|n| node_text(n, bytes))
431 .map(str::to_string),
432 _ => node_text(func, bytes).map(str::to_string),
433 },
434 Self::JavaScript | Self::TypeScript | Self::Tsx => match func.kind() {
435 "identifier" => node_text(func, bytes).map(str::to_string),
436 "member_expression" => func
438 .child_by_field_name("property")
439 .and_then(|n| node_text(n, bytes))
440 .map(str::to_string),
441 _ => node_text(func, bytes).map(str::to_string),
442 },
443 Self::Go => match func.kind() {
444 "identifier" => node_text(func, bytes).map(str::to_string),
445 "selector_expression" => func
447 .child_by_field_name("field")
448 .and_then(|n| node_text(n, bytes))
449 .map(str::to_string),
450 _ => node_text(func, bytes).map(str::to_string),
451 },
452 Self::CSharp => match func.kind() {
453 "identifier" => node_text(func, bytes).map(str::to_string),
454 "member_access_expression" => func
456 .child_by_field_name("name")
457 .and_then(|n| node_text(n, bytes))
458 .map(str::to_string),
459 _ => node_text(func, bytes).map(str::to_string),
460 },
461 Self::C | Self::Cpp => match func.kind() {
462 "identifier" => node_text(func, bytes).map(str::to_string),
463 "field_expression" => func
465 .child_by_field_name("field")
466 .and_then(|n| node_text(n, bytes))
467 .map(str::to_string),
468 "qualified_identifier" => func
470 .child_by_field_name("name")
471 .and_then(|n| node_text(n, bytes))
472 .map(str::to_string),
473 _ => node_text(func, bytes).map(str::to_string),
474 },
475 Self::Scala => last_identifier(func, bytes),
479 Self::Php => node_text(func, bytes).map(str::to_string),
482 Self::Java
486 | Self::Ruby
487 | Self::Bash
488 | Self::Kotlin
489 | Self::Swift
490 | Self::Lua
491 | Self::Elixir => node_text(func, bytes).map(str::to_string),
492 }
493 }
494
495 fn callee_name(self, call: Node<'_>, bytes: &[u8]) -> Option<String> {
499 match self {
500 Self::Java | Self::Bash => call
503 .child_by_field_name("name")
504 .and_then(|n| node_text(n, bytes))
505 .map(str::to_string),
506 Self::Ruby => call
508 .child_by_field_name("method")
509 .and_then(|n| node_text(n, bytes))
510 .map(str::to_string),
511 Self::Kotlin | Self::Swift => call
516 .named_child(0)
517 .and_then(|callee| last_identifier(callee, bytes)),
518 Self::Lua => call
522 .child_by_field_name("name")
523 .and_then(|n| last_identifier(n, bytes)),
524 Self::Php => match call.kind() {
528 "function_call_expression" => call
529 .child_by_field_name("function")
530 .and_then(|func| self.call_name(func, bytes)),
531 _ => call
532 .child_by_field_name("name")
533 .and_then(|n| node_text(n, bytes))
534 .map(str::to_string),
535 },
536 Self::Elixir => match elixir_target_name(call, bytes) {
542 Some(name)
543 if !matches!(
544 name.as_str(),
545 "def" | "defp" | "defmacro" | "defmacrop" | "defmodule"
546 ) && !elixir_is_def_head(call, bytes) =>
547 {
548 Some(name)
549 }
550 _ => None,
551 },
552 _ => call
553 .child_by_field_name("function")
554 .and_then(|func| self.call_name(func, bytes)),
555 }
556 }
557}
558
559fn last_identifier(node: Node<'_>, bytes: &[u8]) -> Option<String> {
563 let mut result = if matches!(node.kind(), "identifier" | "simple_identifier") {
564 node_text(node, bytes).map(str::to_string)
565 } else {
566 None
567 };
568 let mut cursor = node.walk();
569 for child in node.children(&mut cursor) {
570 if let Some(name) = last_identifier(child, bytes) {
571 result = Some(name);
572 }
573 }
574 result
575}
576
577fn child_of_kind<'a>(node: Node<'a>, kind: &str) -> Option<Node<'a>> {
579 let mut cursor = node.walk();
580 node.children(&mut cursor).find(|c| c.kind() == kind)
581}
582
583fn elixir_target_name(node: Node<'_>, bytes: &[u8]) -> Option<String> {
587 let target = node.child_by_field_name("target")?;
588 last_identifier(target, bytes)
589}
590
591fn elixir_defined_name(node: Node<'_>, bytes: &[u8]) -> Option<String> {
595 let head = child_of_kind(node, "arguments")?.named_child(0)?;
596 match head.kind() {
597 "call" => elixir_target_name(head, bytes),
598 _ => last_identifier(head, bytes).or_else(|| node_text(head, bytes).map(str::to_string)),
599 }
600}
601
602fn elixir_is_def_head(node: Node<'_>, bytes: &[u8]) -> bool {
606 let Some(args) = node.parent().filter(|p| p.kind() == "arguments") else {
607 return false;
608 };
609 if args.named_child(0).map(|h| h.id()) != Some(node.id()) {
610 return false;
611 }
612 let Some(def_call) = args.parent().filter(|g| g.kind() == "call") else {
613 return false;
614 };
615 matches!(
616 elixir_target_name(def_call, bytes).as_deref(),
617 Some("def" | "defp" | "defmacro" | "defmacrop" | "defmodule")
618 )
619}
620
621fn js_item_kind(node: Node<'_>) -> Option<SymbolKind> {
623 match node.kind() {
624 "function_declaration" | "generator_function_declaration" | "method_definition" => {
625 Some(SymbolKind::Function)
626 }
627 "class_declaration" | "abstract_class_declaration" => Some(SymbolKind::Class),
628 "variable_declarator" | "public_field_definition" => {
632 match node.child_by_field_name("value").map(|v| v.kind()) {
633 Some("arrow_function" | "function_expression") => Some(SymbolKind::Function),
634 _ => None,
635 }
636 }
637 _ => None,
638 }
639}
640
641fn swift_type_kind(node: Node<'_>) -> SymbolKind {
644 match node
645 .child_by_field_name("declaration_kind")
646 .map(|k| k.kind())
647 {
648 Some("struct") => SymbolKind::Struct,
649 Some("enum") => SymbolKind::Enum,
650 _ => SymbolKind::Class,
651 }
652}
653
654fn kotlin_class_kind(node: Node<'_>) -> SymbolKind {
657 let mut cursor = node.walk();
658 if node.children(&mut cursor).any(|c| c.kind() == "interface") {
659 SymbolKind::Interface
660 } else {
661 SymbolKind::Class
662 }
663}
664
665fn c_item_kind(node_kind: &str) -> Option<SymbolKind> {
667 match node_kind {
668 "function_definition" => Some(SymbolKind::Function),
669 "struct_specifier" => Some(SymbolKind::Struct),
670 "enum_specifier" => Some(SymbolKind::Enum),
671 "type_definition" => Some(SymbolKind::TypeAlias),
672 _ => None,
673 }
674}
675
676fn c_declarator_name(node: Node<'_>, bytes: &[u8]) -> Option<String> {
680 let mut n = node;
681 loop {
682 if matches!(
683 n.kind(),
684 "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier"
685 ) {
686 return node_text(n, bytes).map(str::to_string);
687 }
688 n = n.child_by_field_name("declarator")?;
689 }
690}
691
692pub fn build(language: Language, src: &str) -> CodeGraph {
696 let mut parser = Parser::new();
697 if parser.set_language(&language.ts_language()).is_err() {
698 return CodeGraph::default();
699 }
700 let Some(tree) = parser.parse(src, None) else {
701 return CodeGraph::default();
702 };
703 let mut graph = CodeGraph::default();
704 walk(language, tree.root_node(), src.as_bytes(), None, &mut graph);
705 graph
706}
707
708pub fn build_rust(src: &str) -> CodeGraph {
710 build(Language::Rust, src)
711}
712
713fn node_text<'a>(node: Node<'_>, bytes: &'a [u8]) -> Option<&'a str> {
714 node.utf8_text(bytes).ok()
715}
716
717fn name_field(node: Node<'_>, bytes: &[u8]) -> Option<String> {
718 node.child_by_field_name("name")
719 .and_then(|n| node_text(n, bytes))
720 .map(str::to_string)
721}
722
723fn walk(
724 language: Language,
725 node: Node<'_>,
726 bytes: &[u8],
727 current_fn: Option<&str>,
728 graph: &mut CodeGraph,
729) {
730 let mut enclosing = current_fn.map(str::to_string);
731
732 if let Some(kind) = language.item_kind(node, bytes)
733 && let Some(name) = language.item_name(node, kind, bytes)
734 {
735 graph.symbols.push(Symbol {
736 name: name.clone(),
737 kind,
738 start_line: node.start_position().row + 1,
739 end_line: node.end_position().row + 1,
740 });
741 if kind == SymbolKind::Function {
742 enclosing = Some(name);
743 }
744 }
745
746 if language.is_call(node.kind())
747 && let Some(name) = language.callee_name(node, bytes)
748 {
749 graph.references.push(Reference {
750 name,
751 from: enclosing.clone(),
752 line: node.start_position().row + 1,
753 kind: language.callee_kind(node),
754 });
755 }
756
757 for (name, line) in language.macro_arg_calls(node, bytes) {
759 graph.references.push(Reference {
760 name,
761 from: enclosing.clone(),
762 line,
763 kind: RefKind::Free,
765 });
766 }
767
768 let mut cursor = node.walk();
769 for child in node.children(&mut cursor) {
770 walk(language, child, bytes, enclosing.as_deref(), graph);
771 }
772}
773
774#[cfg(test)]
775mod tests {
776 use super::*;
777
778 const RUST_SRC: &str = r#"
779struct Widget { n: u32 }
780
781fn helper(x: u32) -> u32 { x + 1 }
782
783fn main() {
784 let w = Widget { n: 1 };
785 let y = helper(w.n);
786 println!("{y}");
787}
788"#;
789
790 #[test]
791 fn rust_extracts_definitions() {
792 let g = build_rust(RUST_SRC);
793 let names: Vec<(&str, SymbolKind)> = g
794 .symbols
795 .iter()
796 .map(|s| (s.name.as_str(), s.kind))
797 .collect();
798 assert!(names.contains(&("Widget", SymbolKind::Struct)));
799 assert!(names.contains(&("helper", SymbolKind::Function)));
800 assert!(names.contains(&("main", SymbolKind::Function)));
801 }
802
803 #[test]
804 fn rust_records_call_with_enclosing_fn() {
805 let g = build_rust(RUST_SRC);
806 let call = g
807 .references
808 .iter()
809 .find(|r| r.name == "helper")
810 .expect("helper call recorded");
811 assert_eq!(call.from.as_deref(), Some("main"));
812 }
813
814 fn rust_ref_names(src: &str) -> Vec<String> {
818 build_rust(src)
819 .references
820 .iter()
821 .map(|r| r.name.clone())
822 .collect()
823 }
824
825 #[test]
826 fn rust_records_a_call_inside_assert_eq() {
827 let names = rust_ref_names("fn g() { assert_eq!(f(), 1); }");
830 assert!(names.contains(&"f".to_string()), "got {names:?}");
831 }
832
833 #[test]
834 fn rust_records_calls_inside_common_macros() {
835 for (src, want) in [
836 ("fn g() { println!(\"{}\", h()); }", "h"),
837 ("fn g() { let v = vec![mk()]; }", "mk"),
838 ("fn g() { panic!(\"{}\", why()); }", "why"),
839 ("fn g() { write!(w, \"{}\", val()); }", "val"),
840 ] {
841 let names = rust_ref_names(src);
842 assert!(names.contains(&want.to_string()), "{want} in {names:?}");
843 }
844 }
845
846 #[test]
847 fn rust_records_a_qualified_call_inside_a_macro_by_last_segment() {
848 let names =
851 rust_ref_names("fn g() { assert_eq!(Language::from_extension(\"rs\"), None); }");
852 assert!(names.contains(&"from_extension".to_string()), "{names:?}");
853 }
854
855 #[test]
856 fn rust_records_nested_calls_inside_a_macro() {
857 let names = rust_ref_names("fn g() { assert_eq!(outer(inner()), 1); }");
858 assert!(names.contains(&"outer".to_string()), "{names:?}");
859 assert!(names.contains(&"inner".to_string()), "{names:?}");
860 }
861
862 #[test]
863 fn rust_records_a_macro_arg_call_once() {
864 let names = rust_ref_names("fn g() { assert_eq!(f(), 1); }");
865 assert_eq!(
866 names.iter().filter(|n| *n == "f").count(),
867 1,
868 "nested token trees must not double-count: {names:?}"
869 );
870 }
871
872 #[test]
873 fn rust_macro_arg_calls_carry_the_enclosing_function() {
874 let g = build_rust("fn outer_fn() { assert_eq!(f(), 1); }");
875 let r = g.references.iter().find(|r| r.name == "f").expect("f");
876 assert_eq!(r.from.as_deref(), Some("outer_fn"));
877 }
878
879 #[test]
880 fn rust_does_not_treat_a_nested_macro_name_as_a_call() {
881 let names = rust_ref_names("fn g() { assert!(matches!(a, Some(_))); }");
884 assert!(!names.contains(&"matches".to_string()), "{names:?}");
885 }
886
887 #[test]
888 fn rust_does_not_invent_calls_from_plain_macro_arguments() {
889 let names = rust_ref_names("fn g() { println!(\"{}\", x); }");
892 assert!(!names.contains(&"x".to_string()), "{names:?}");
893 let names = rust_ref_names("fn g() { let v = vec![1, 2, 3]; }");
894 assert!(names.is_empty(), "{names:?}");
895 }
896
897 #[test]
898 fn rust_still_records_ordinary_calls_alongside_macro_ones() {
899 let names = rust_ref_names("fn g() { plain(); assert_eq!(inside(), 1); }");
900 assert!(names.contains(&"plain".to_string()), "{names:?}");
901 assert!(names.contains(&"inside".to_string()), "{names:?}");
902 }
903
904 #[test]
905 fn rust_macro_arg_call_line_is_one_based() {
906 let g = build_rust("fn g() {\n assert_eq!(f(), 1);\n}");
907 let r = g.references.iter().find(|r| r.name == "f").expect("f");
908 assert_eq!(r.line, 2);
909 }
910
911 #[test]
914 fn c_records_macro_invocations_at_the_call_site() {
915 let g = build(
918 Language::C,
919 "#define M() foo()\nvoid g(void) { M(); bar(); }",
920 );
921 let names: Vec<&str> = g.references.iter().map(|r| r.name.as_str()).collect();
922 assert!(names.contains(&"M"));
923 assert!(names.contains(&"bar"));
924 }
925
926 #[test]
927 fn c_does_not_record_calls_inside_a_define_body() {
928 let g = build(Language::C, "#define M() foo()\nvoid g(void) { M(); }");
934 let names: Vec<&str> = g.references.iter().map(|r| r.name.as_str()).collect();
935 assert!(!names.contains(&"foo"), "known gap: {names:?}");
936 }
937
938 #[test]
939 fn elixir_records_calls_inside_a_quote_block() {
940 let g = build(
942 Language::Elixir,
943 "defmodule A do\n def g do\n quote do: foo()\n end\nend",
944 );
945 let names: Vec<&str> = g.references.iter().map(|r| r.name.as_str()).collect();
946 assert!(names.contains(&"foo"), "{names:?}");
947 }
948
949 #[test]
950 fn ruby_records_calls_inside_a_block() {
951 let g = build(Language::Ruby, "def g\n define_method(:x) { foo() }\nend");
952 let names: Vec<&str> = g.references.iter().map(|r| r.name.as_str()).collect();
953 assert!(names.contains(&"foo"), "{names:?}");
954 }
955
956 #[test]
957 fn rust_symbol_lines_are_one_based() {
958 let g = build_rust(RUST_SRC);
959 let main = g.symbols.iter().find(|s| s.name == "main").unwrap();
960 assert!(main.start_line >= 1 && main.end_line >= main.start_line);
961 }
962
963 const PY_SRC: &str = r#"
964class Widget:
965 def area(self):
966 return helper(self.n)
967
968def helper(x):
969 return x + 1
970
971def main():
972 w = Widget()
973 print(w.area())
974"#;
975
976 #[test]
977 fn python_extracts_functions_and_classes() {
978 let g = build(Language::Python, PY_SRC);
979 let names: Vec<(&str, SymbolKind)> = g
980 .symbols
981 .iter()
982 .map(|s| (s.name.as_str(), s.kind))
983 .collect();
984 assert!(names.contains(&("Widget", SymbolKind::Class)));
985 assert!(names.contains(&("helper", SymbolKind::Function)));
986 assert!(names.contains(&("area", SymbolKind::Function)));
987 }
988
989 #[test]
990 fn python_records_calls_including_methods() {
991 let g = build(Language::Python, PY_SRC);
992 let helper_call = g
994 .references
995 .iter()
996 .find(|r| r.name == "helper")
997 .expect("helper call");
998 assert_eq!(helper_call.from.as_deref(), Some("area"));
999 assert!(
1001 g.references
1002 .iter()
1003 .any(|r| r.name == "area" && r.from.as_deref() == Some("main"))
1004 );
1005 }
1006
1007 #[test]
1008 fn language_from_extension() {
1009 assert_eq!(Language::from_extension("rs"), Some(Language::Rust));
1010 assert_eq!(Language::from_extension("py"), Some(Language::Python));
1011 assert_eq!(Language::from_extension("js"), Some(Language::JavaScript));
1012 assert_eq!(Language::from_extension("jsx"), Some(Language::JavaScript));
1013 assert_eq!(Language::from_extension("ts"), Some(Language::TypeScript));
1014 assert_eq!(Language::from_extension("tsx"), Some(Language::Tsx));
1015 assert_eq!(Language::from_extension("go"), Some(Language::Go));
1016 assert_eq!(Language::from_extension("java"), Some(Language::Java));
1017 assert_eq!(Language::from_extension("cs"), Some(Language::CSharp));
1018 assert_eq!(Language::from_extension("c"), Some(Language::C));
1019 assert_eq!(Language::from_extension("h"), Some(Language::C));
1020 assert_eq!(Language::from_extension("cpp"), Some(Language::Cpp));
1021 assert_eq!(Language::from_extension("cc"), Some(Language::Cpp));
1022 assert_eq!(Language::from_extension("cxx"), Some(Language::Cpp));
1023 assert_eq!(Language::from_extension("hpp"), Some(Language::Cpp));
1024 assert_eq!(Language::from_extension("hh"), Some(Language::Cpp));
1025 assert_eq!(Language::from_extension("rb"), Some(Language::Ruby));
1026 assert_eq!(Language::from_extension("php"), Some(Language::Php));
1027 assert_eq!(Language::from_extension("sh"), Some(Language::Bash));
1028 assert_eq!(Language::from_extension("bash"), Some(Language::Bash));
1029 assert_eq!(Language::from_extension("kt"), Some(Language::Kotlin));
1030 assert_eq!(Language::from_extension("kts"), Some(Language::Kotlin));
1031 assert_eq!(Language::from_extension("swift"), Some(Language::Swift));
1032 assert_eq!(Language::from_extension("lua"), Some(Language::Lua));
1033 assert_eq!(Language::from_extension("scala"), Some(Language::Scala));
1034 assert_eq!(Language::from_extension("ex"), Some(Language::Elixir));
1035 assert_eq!(Language::from_extension("exs"), Some(Language::Elixir));
1036 assert_eq!(Language::from_extension("txt"), None);
1037 }
1038
1039 const JS_SRC: &str = r#"
1040class Widget {
1041 area() {
1042 return helper(this.n);
1043 }
1044}
1045function helper(x) {
1046 return x + 1;
1047}
1048function main() {
1049 const w = new Widget();
1050 console.log(w.area());
1051}
1052"#;
1053
1054 #[test]
1055 fn javascript_extracts_symbols_and_calls() {
1056 let g = build(Language::JavaScript, JS_SRC);
1057 let names: Vec<(&str, SymbolKind)> = g
1058 .symbols
1059 .iter()
1060 .map(|s| (s.name.as_str(), s.kind))
1061 .collect();
1062 assert!(names.contains(&("Widget", SymbolKind::Class)));
1063 assert!(names.contains(&("area", SymbolKind::Function)));
1064 assert!(names.contains(&("helper", SymbolKind::Function)));
1065 assert!(names.contains(&("main", SymbolKind::Function)));
1066
1067 assert!(
1069 g.references
1070 .iter()
1071 .any(|r| r.name == "helper" && r.from.as_deref() == Some("area"))
1072 );
1073 assert!(
1075 g.references
1076 .iter()
1077 .any(|r| r.name == "area" && r.from.as_deref() == Some("main"))
1078 );
1079 }
1080
1081 const TS_SRC: &str = r#"
1082interface Shape { area(): number; }
1083type Id = string;
1084enum Color { Red, Green }
1085
1086class Circle implements Shape {
1087 area(): number { return compute(this.r); }
1088}
1089
1090function compute(r: number): number { return r * r; }
1091"#;
1092
1093 #[test]
1094 fn typescript_extracts_ts_specific_kinds() {
1095 let g = build(Language::TypeScript, TS_SRC);
1096 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1097 assert_eq!(named("Shape"), Some(SymbolKind::Interface));
1098 assert_eq!(named("Id"), Some(SymbolKind::TypeAlias));
1099 assert_eq!(named("Color"), Some(SymbolKind::Enum));
1100 assert_eq!(named("Circle"), Some(SymbolKind::Class));
1101 assert_eq!(named("compute"), Some(SymbolKind::Function));
1102
1103 assert!(
1104 g.references
1105 .iter()
1106 .any(|r| r.name == "compute" && r.from.as_deref() == Some("area"))
1107 );
1108 }
1109
1110 const GO_SRC: &str = r#"
1111package main
1112
1113type Widget struct { n int }
1114
1115type Shape interface { Area() int }
1116
1117const Limit = 10
1118
1119var counter = 0
1120
1121func helper(x int) int { return x + 1 }
1122
1123func (w Widget) Area() int { return helper(w.n) }
1124
1125func main() {
1126 w := Widget{n: 1}
1127 _ = w.Area()
1128 _ = helper(2)
1129}
1130"#;
1131
1132 #[test]
1133 fn go_extracts_definitions() {
1134 let g = build(Language::Go, GO_SRC);
1135 let names: Vec<(&str, SymbolKind)> = g
1136 .symbols
1137 .iter()
1138 .map(|s| (s.name.as_str(), s.kind))
1139 .collect();
1140 assert!(names.contains(&("Widget", SymbolKind::Struct)));
1141 assert!(names.contains(&("Shape", SymbolKind::Interface)));
1142 assert!(names.contains(&("helper", SymbolKind::Function)));
1143 assert!(names.contains(&("Area", SymbolKind::Function)));
1144 assert!(names.contains(&("main", SymbolKind::Function)));
1145 assert!(names.contains(&("Limit", SymbolKind::Const)));
1146 assert!(names.contains(&("counter", SymbolKind::Static)));
1147 }
1148
1149 #[test]
1150 fn go_records_calls_including_methods() {
1151 let g = build(Language::Go, GO_SRC);
1152 assert!(
1154 g.references
1155 .iter()
1156 .any(|r| r.name == "helper" && r.from.as_deref() == Some("Area")),
1157 "helper call from Area"
1158 );
1159 assert!(
1161 g.references
1162 .iter()
1163 .any(|r| r.name == "Area" && r.from.as_deref() == Some("main")),
1164 "w.Area() call from main"
1165 );
1166 }
1167
1168 #[test]
1169 fn tsx_parses_with_jsx() {
1170 let src = r#"
1172function App(): JSX.Element {
1173 return greet();
1174}
1175function greet() { return <div>hi</div>; }
1176"#;
1177 let g = build(Language::Tsx, src);
1178 assert!(g.symbols.iter().any(|s| s.name == "App"));
1179 assert!(
1180 g.references
1181 .iter()
1182 .any(|r| r.name == "greet" && r.from.as_deref() == Some("App"))
1183 );
1184 }
1185
1186 const JAVA_SRC: &str = r#"
1187interface Shape { int area(); }
1188
1189enum Color { RED, GREEN }
1190
1191class Widget {
1192 int n;
1193 Widget(int n) { this.n = n; }
1194 int area() { return helper(this.n); }
1195}
1196
1197class Main {
1198 static int helper(int x) { return x + 1; }
1199 static void main(String[] args) {
1200 Widget w = new Widget(1);
1201 w.area();
1202 }
1203}
1204"#;
1205
1206 #[test]
1207 fn java_extracts_definitions() {
1208 let g = build(Language::Java, JAVA_SRC);
1209 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1210 assert_eq!(named("Shape"), Some(SymbolKind::Interface));
1211 assert_eq!(named("Color"), Some(SymbolKind::Enum));
1212 assert_eq!(named("Widget"), Some(SymbolKind::Class));
1213 assert_eq!(named("area"), Some(SymbolKind::Function));
1214 assert_eq!(named("helper"), Some(SymbolKind::Function));
1215 assert!(
1217 g.symbols
1218 .iter()
1219 .any(|s| s.name == "Widget" && s.kind == SymbolKind::Function)
1220 );
1221 }
1222
1223 #[test]
1224 fn java_records_calls_with_enclosing_fn() {
1225 let g = build(Language::Java, JAVA_SRC);
1226 assert!(
1227 g.references
1228 .iter()
1229 .any(|r| r.name == "helper" && r.from.as_deref() == Some("area")),
1230 "helper() call from area"
1231 );
1232 assert!(
1233 g.references
1234 .iter()
1235 .any(|r| r.name == "area" && r.from.as_deref() == Some("main")),
1236 "w.area() call from main"
1237 );
1238 }
1239
1240 const CS_SRC: &str = r#"
1241interface IShape { int Area(); }
1242enum Color { Red, Green }
1243struct Point { public int X; }
1244
1245class Widget {
1246 int n;
1247 public Widget(int n) { this.n = n; }
1248 public int Area() { return Helper(this.n); }
1249}
1250
1251class Program {
1252 static int Helper(int x) { return x + 1; }
1253 static void Main() {
1254 var w = new Widget(1);
1255 w.Area();
1256 }
1257}
1258"#;
1259
1260 #[test]
1261 fn csharp_extracts_definitions() {
1262 let g = build(Language::CSharp, CS_SRC);
1263 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1264 assert_eq!(named("IShape"), Some(SymbolKind::Interface));
1265 assert_eq!(named("Color"), Some(SymbolKind::Enum));
1266 assert_eq!(named("Point"), Some(SymbolKind::Struct));
1267 assert_eq!(named("Widget"), Some(SymbolKind::Class));
1268 assert_eq!(named("Area"), Some(SymbolKind::Function));
1269 assert_eq!(named("Helper"), Some(SymbolKind::Function));
1270 }
1271
1272 #[test]
1273 fn csharp_records_calls_with_enclosing_fn() {
1274 let g = build(Language::CSharp, CS_SRC);
1275 assert!(
1276 g.references
1277 .iter()
1278 .any(|r| r.name == "Helper" && r.from.as_deref() == Some("Area")),
1279 "Helper() call from Area"
1280 );
1281 assert!(
1282 g.references
1283 .iter()
1284 .any(|r| r.name == "Area" && r.from.as_deref() == Some("Main")),
1285 "w.Area() call from Main"
1286 );
1287 }
1288
1289 const C_SRC: &str = r#"
1290struct Widget { int n; };
1291
1292typedef int Id;
1293
1294enum Color { RED, GREEN };
1295
1296int helper(int x) { return x + 1; }
1297
1298int main(void) {
1299 int y = helper(2);
1300 return y;
1301}
1302"#;
1303
1304 #[test]
1305 fn c_extracts_definitions() {
1306 let g = build(Language::C, C_SRC);
1307 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1308 assert_eq!(named("Widget"), Some(SymbolKind::Struct));
1309 assert_eq!(named("Id"), Some(SymbolKind::TypeAlias));
1310 assert_eq!(named("Color"), Some(SymbolKind::Enum));
1311 assert_eq!(named("helper"), Some(SymbolKind::Function));
1312 assert_eq!(named("main"), Some(SymbolKind::Function));
1313 }
1314
1315 #[test]
1316 fn c_records_call_with_enclosing_fn() {
1317 let g = build(Language::C, C_SRC);
1318 assert!(
1319 g.references
1320 .iter()
1321 .any(|r| r.name == "helper" && r.from.as_deref() == Some("main")),
1322 "helper() call from main"
1323 );
1324 }
1325
1326 const CPP_SRC: &str = r#"
1327namespace geo {
1328
1329class Widget {
1330public:
1331 int n;
1332 int area() { return helper(this->n); }
1333};
1334
1335int helper(int x) { return x + 1; }
1336
1337}
1338
1339int main() {
1340 geo::Widget w;
1341 return w.area();
1342}
1343"#;
1344
1345 #[test]
1346 fn cpp_extracts_definitions() {
1347 let g = build(Language::Cpp, CPP_SRC);
1348 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1349 assert_eq!(named("geo"), Some(SymbolKind::Module));
1350 assert_eq!(named("Widget"), Some(SymbolKind::Class));
1351 assert_eq!(named("area"), Some(SymbolKind::Function));
1352 assert_eq!(named("helper"), Some(SymbolKind::Function));
1353 assert_eq!(named("main"), Some(SymbolKind::Function));
1354 }
1355
1356 #[test]
1357 fn cpp_records_calls_including_methods() {
1358 let g = build(Language::Cpp, CPP_SRC);
1359 assert!(
1361 g.references
1362 .iter()
1363 .any(|r| r.name == "helper" && r.from.as_deref() == Some("area")),
1364 "helper() call from area"
1365 );
1366 assert!(
1368 g.references
1369 .iter()
1370 .any(|r| r.name == "area" && r.from.as_deref() == Some("main")),
1371 "w.area() call from main"
1372 );
1373 }
1374
1375 const RUBY_SRC: &str = r#"
1376class Widget
1377 def area
1378 helper(1)
1379 end
1380end
1381
1382module Util
1383end
1384
1385def helper(x)
1386 x + 1
1387end
1388
1389def main
1390 helper(2)
1391end
1392"#;
1393
1394 #[test]
1395 fn ruby_extracts_definitions() {
1396 let g = build(Language::Ruby, RUBY_SRC);
1397 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1398 assert_eq!(named("Widget"), Some(SymbolKind::Class));
1399 assert_eq!(named("Util"), Some(SymbolKind::Module));
1400 assert_eq!(named("area"), Some(SymbolKind::Function));
1401 assert_eq!(named("helper"), Some(SymbolKind::Function));
1402 assert_eq!(named("main"), Some(SymbolKind::Function));
1403 }
1404
1405 #[test]
1406 fn ruby_records_call_with_enclosing_fn() {
1407 let g = build(Language::Ruby, RUBY_SRC);
1408 assert!(
1410 g.references
1411 .iter()
1412 .any(|r| r.name == "helper" && r.from.as_deref() == Some("main")),
1413 "helper call from main"
1414 );
1415 }
1416
1417 const PHP_SRC: &str = r#"<?php
1418class Widget {
1419 function area() {
1420 return helper(1);
1421 }
1422}
1423
1424interface Shape {}
1425
1426trait Named {}
1427
1428function helper($x) {
1429 return $x + 1;
1430}
1431
1432function main() {
1433 return helper(2);
1434}
1435"#;
1436
1437 #[test]
1438 fn php_extracts_definitions() {
1439 let g = build(Language::Php, PHP_SRC);
1440 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1441 assert_eq!(named("Widget"), Some(SymbolKind::Class));
1442 assert_eq!(named("Shape"), Some(SymbolKind::Interface));
1443 assert_eq!(named("Named"), Some(SymbolKind::Trait));
1444 assert_eq!(named("area"), Some(SymbolKind::Function));
1445 assert_eq!(named("helper"), Some(SymbolKind::Function));
1446 assert_eq!(named("main"), Some(SymbolKind::Function));
1447 }
1448
1449 #[test]
1450 fn php_records_call_with_enclosing_fn() {
1451 let g = build(Language::Php, PHP_SRC);
1452 assert!(
1454 g.references
1455 .iter()
1456 .any(|r| r.name == "helper" && r.from.as_deref() == Some("main")),
1457 "helper() call from main"
1458 );
1459 }
1460
1461 const BASH_SRC: &str = r#"
1462helper() {
1463 echo "$1"
1464}
1465
1466main() {
1467 helper hello
1468}
1469"#;
1470
1471 #[test]
1472 fn bash_extracts_definitions() {
1473 let g = build(Language::Bash, BASH_SRC);
1474 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1475 assert_eq!(named("helper"), Some(SymbolKind::Function));
1476 assert_eq!(named("main"), Some(SymbolKind::Function));
1477 }
1478
1479 #[test]
1480 fn bash_records_call_with_enclosing_fn() {
1481 let g = build(Language::Bash, BASH_SRC);
1482 assert!(
1484 g.references
1485 .iter()
1486 .any(|r| r.name == "helper" && r.from.as_deref() == Some("main")),
1487 "helper command from main"
1488 );
1489 }
1490
1491 const KOTLIN_SRC: &str = r#"
1492class Widget {
1493 fun area(): Int {
1494 return helper(1)
1495 }
1496}
1497
1498object Config
1499
1500fun helper(x: Int): Int {
1501 return x + 1
1502}
1503
1504fun main() {
1505 helper(2)
1506}
1507"#;
1508
1509 #[test]
1510 fn kotlin_extracts_definitions() {
1511 let g = build(Language::Kotlin, KOTLIN_SRC);
1512 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1513 assert_eq!(named("Widget"), Some(SymbolKind::Class));
1514 assert_eq!(named("Config"), Some(SymbolKind::Class)); assert_eq!(named("area"), Some(SymbolKind::Function));
1516 assert_eq!(named("helper"), Some(SymbolKind::Function));
1517 assert_eq!(named("main"), Some(SymbolKind::Function));
1518 }
1519
1520 #[test]
1521 fn kotlin_records_call_with_enclosing_fn() {
1522 let g = build(Language::Kotlin, KOTLIN_SRC);
1523 assert!(
1526 g.references
1527 .iter()
1528 .any(|r| r.name == "helper" && r.from.as_deref() == Some("main")),
1529 "helper() call from main"
1530 );
1531 }
1532
1533 const SWIFT_SRC: &str = r#"
1534class Widget {
1535 func area() -> Int {
1536 return helper(1)
1537 }
1538}
1539
1540protocol Shape {}
1541
1542func helper(x: Int) -> Int {
1543 return x + 1
1544}
1545
1546func main() {
1547 helper(2)
1548}
1549"#;
1550
1551 #[test]
1552 fn swift_extracts_definitions() {
1553 let g = build(Language::Swift, SWIFT_SRC);
1554 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1555 assert_eq!(named("Widget"), Some(SymbolKind::Class));
1556 assert_eq!(named("Shape"), Some(SymbolKind::Interface)); assert_eq!(named("area"), Some(SymbolKind::Function));
1558 assert_eq!(named("helper"), Some(SymbolKind::Function));
1559 assert_eq!(named("main"), Some(SymbolKind::Function));
1560 }
1561
1562 #[test]
1563 fn swift_records_call_with_enclosing_fn() {
1564 let g = build(Language::Swift, SWIFT_SRC);
1565 assert!(
1568 g.references
1569 .iter()
1570 .any(|r| r.name == "helper" && r.from.as_deref() == Some("main")),
1571 "helper() call from main"
1572 );
1573 }
1574
1575 const LUA_SRC: &str = r#"
1576function helper(x)
1577 return x + 1
1578end
1579
1580function main()
1581 return helper(2)
1582end
1583"#;
1584
1585 #[test]
1586 fn lua_extracts_definitions() {
1587 let g = build(Language::Lua, LUA_SRC);
1588 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1589 assert_eq!(named("helper"), Some(SymbolKind::Function));
1590 assert_eq!(named("main"), Some(SymbolKind::Function));
1591 }
1592
1593 #[test]
1594 fn lua_records_call_with_enclosing_fn() {
1595 let g = build(Language::Lua, LUA_SRC);
1596 assert!(
1598 g.references
1599 .iter()
1600 .any(|r| r.name == "helper" && r.from.as_deref() == Some("main")),
1601 "helper() call from main"
1602 );
1603 }
1604
1605 const SCALA_SRC: &str = r#"
1606class Widget {
1607 def area(): Int = { helper(1) }
1608}
1609
1610object Config
1611
1612trait Shape
1613
1614def helper(x: Int): Int = x + 1
1615
1616def main(): Unit = { helper(2) }
1617"#;
1618
1619 #[test]
1620 fn scala_extracts_definitions() {
1621 let g = build(Language::Scala, SCALA_SRC);
1622 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1623 assert_eq!(named("Widget"), Some(SymbolKind::Class));
1624 assert_eq!(named("Config"), Some(SymbolKind::Class)); assert_eq!(named("Shape"), Some(SymbolKind::Trait));
1626 assert_eq!(named("area"), Some(SymbolKind::Function));
1627 assert_eq!(named("helper"), Some(SymbolKind::Function));
1628 assert_eq!(named("main"), Some(SymbolKind::Function));
1629 }
1630
1631 #[test]
1632 fn scala_records_call_with_enclosing_fn() {
1633 let g = build(Language::Scala, SCALA_SRC);
1634 assert!(
1636 g.references
1637 .iter()
1638 .any(|r| r.name == "helper" && r.from.as_deref() == Some("main")),
1639 "helper() call from main"
1640 );
1641 }
1642
1643 const ELIXIR_SRC: &str = r#"
1644defmodule Math do
1645 def add(a, b) do
1646 helper(a) + b
1647 end
1648
1649 defp helper(x), do: x
1650
1651 def run do
1652 Remote.compute(1)
1653 end
1654end
1655"#;
1656
1657 #[test]
1658 fn elixir_extracts_definitions() {
1659 let g = build(Language::Elixir, ELIXIR_SRC);
1660 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1661 assert_eq!(named("Math"), Some(SymbolKind::Module));
1663 assert_eq!(named("add"), Some(SymbolKind::Function));
1665 assert_eq!(named("helper"), Some(SymbolKind::Function));
1667 assert_eq!(named("run"), Some(SymbolKind::Function));
1669 }
1670
1671 #[test]
1672 fn elixir_records_calls() {
1673 let g = build(Language::Elixir, ELIXIR_SRC);
1674 assert!(
1677 g.references
1678 .iter()
1679 .any(|r| r.name == "helper" && r.from.as_deref() == Some("add")),
1680 "helper() call from add"
1681 );
1682 assert!(
1685 g.references
1686 .iter()
1687 .any(|r| r.name == "compute" && r.from.as_deref() == Some("run")),
1688 "Remote.compute() call from run"
1689 );
1690 assert!(
1692 !g.references.iter().any(|r| r.name == "add"),
1693 "def head add(a, b) not recorded as a call"
1694 );
1695 }
1696
1697 #[test]
1701 fn javascript_extracts_arrow_and_function_expression_bindings() {
1702 let src = r#"
1703const foo = () => { bar(); };
1704const baz = function () { qux(); };
1705"#;
1706 let g = build(Language::JavaScript, src);
1707 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1708 assert_eq!(named("foo"), Some(SymbolKind::Function));
1709 assert_eq!(named("baz"), Some(SymbolKind::Function));
1710 assert!(
1712 g.references
1713 .iter()
1714 .any(|r| r.name == "bar" && r.from.as_deref() == Some("foo")),
1715 "bar() call attributed to foo"
1716 );
1717 assert!(
1718 g.references
1719 .iter()
1720 .any(|r| r.name == "qux" && r.from.as_deref() == Some("baz")),
1721 "qux() call attributed to baz"
1722 );
1723 }
1724
1725 #[test]
1726 fn typescript_extracts_arrow_bindings_and_class_fields() {
1727 let src = r#"
1729const foo = (): void => { bar(); };
1730class C { handler = (): void => { onClick(); }; }
1731"#;
1732 let g = build(Language::TypeScript, src);
1733 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1734 assert_eq!(named("foo"), Some(SymbolKind::Function));
1735 assert_eq!(named("handler"), Some(SymbolKind::Function));
1736 assert!(
1737 g.references
1738 .iter()
1739 .any(|r| r.name == "bar" && r.from.as_deref() == Some("foo")),
1740 "bar() call attributed to foo"
1741 );
1742 assert!(
1743 g.references
1744 .iter()
1745 .any(|r| r.name == "onClick" && r.from.as_deref() == Some("handler")),
1746 "onClick() call attributed to handler"
1747 );
1748 }
1749
1750 #[test]
1753 fn php_records_method_and_static_calls() {
1754 let src = r#"<?php
1755class A {
1756 function run() {
1757 $this->other();
1758 self::x();
1759 B::stat();
1760 }
1761}
1762"#;
1763 let g = build(Language::Php, src);
1764 let called = |n: &str| {
1765 g.references
1766 .iter()
1767 .any(|r| r.name == n && r.from.as_deref() == Some("run"))
1768 };
1769 assert!(called("other"), "$this->other() recorded from run");
1770 assert!(called("x"), "self::x() recorded from run");
1771 assert!(called("stat"), "B::stat() recorded from run");
1772 }
1773
1774 #[test]
1777 fn swift_distinguishes_struct_enum_class() {
1778 let src = r#"
1779struct Point { var x: Int }
1780enum Color { case red }
1781class Widget {}
1782actor Worker {}
1783protocol Shape {}
1784"#;
1785 let g = build(Language::Swift, src);
1786 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1787 assert_eq!(named("Point"), Some(SymbolKind::Struct));
1788 assert_eq!(named("Color"), Some(SymbolKind::Enum));
1789 assert_eq!(named("Widget"), Some(SymbolKind::Class));
1790 assert_eq!(named("Worker"), Some(SymbolKind::Class)); assert_eq!(named("Shape"), Some(SymbolKind::Interface)); }
1793
1794 #[test]
1795 fn kotlin_distinguishes_interface_from_class() {
1796 let src = r#"
1797interface Shape { }
1798class Widget { }
1799object Config
1800"#;
1801 let g = build(Language::Kotlin, src);
1802 let named = |n: &str| g.symbols.iter().find(|s| s.name == n).map(|s| s.kind);
1803 assert_eq!(named("Shape"), Some(SymbolKind::Interface));
1804 assert_eq!(named("Widget"), Some(SymbolKind::Class));
1805 assert_eq!(named("Config"), Some(SymbolKind::Class)); }
1807}