1use std::{path::Path, rc::Rc};
12
13use crate::parser::{Language, ParsedFile};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct ResolvedSymbol {
18 pub name: String,
20 pub start_line: u32,
22 pub end_line: u32,
24 pub parent_name: Option<String>,
26}
27
28#[derive(Debug, thiserror::Error)]
30pub enum SymbolResolveError {
31 #[error("unsupported file extension: {0}")]
32 UnsupportedLanguage(String),
33
34 #[error("failed to parse source file")]
35 ParseFailed,
36
37 #[error("symbol not found: {0}")]
38 SymbolNotFound(String),
39}
40
41pub use objects::object::SymbolKindTag as DefinitionKind;
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct Definition {
47 pub name: String,
51 pub kind: DefinitionKind,
52 pub start_line: u32,
54 pub end_line: u32,
56 pub parent_name: Option<String>,
58}
59
60pub fn extract_definitions(
67 source: &[u8],
68 path: &Path,
69) -> Result<Vec<Definition>, SymbolResolveError> {
70 let language = Language::from_path(path);
71 language.parser_handle().ok_or_else(|| {
72 SymbolResolveError::UnsupportedLanguage(
73 path.extension()
74 .map(|e| e.to_string_lossy().into_owned())
75 .unwrap_or_else(|| "<none>".to_string()),
76 )
77 })?;
78 let source_text = std::str::from_utf8(source).map_err(|_| SymbolResolveError::ParseFailed)?;
79 let parsed = ParsedFile::parse(source_text, language).ok_or(SymbolResolveError::ParseFailed)?;
80
81 let mut out = Vec::new();
82 walk_definitions(parsed.root_node(), source, &mut out);
83 Ok(out)
84}
85
86fn node_text<'a>(node: &tree_sitter::Node, source: &'a [u8]) -> &'a str {
87 std::str::from_utf8(&source[node.byte_range()]).unwrap_or("")
88}
89
90pub(crate) struct DefinitionSite<'tree> {
94 pub node: tree_sitter::Node<'tree>,
95 pub name: String,
96 pub kind: DefinitionKind,
97 pub parent_name: Option<String>,
98 pub start_line: u32,
99 pub end_line: u32,
100}
101
102fn emit_named_definition<'tree>(
103 node: tree_sitter::Node<'tree>,
104 source: &[u8],
105 dk: DefinitionKind,
106 parent: Option<&str>,
107 emit: &mut impl FnMut(DefinitionSite<'tree>),
108) {
109 if let Some(name_node) = node.child_by_field_name("name") {
110 let name = node_text(&name_node, source).to_string();
111 if name.is_empty() {
112 return;
113 }
114 emit(DefinitionSite {
115 node,
116 name,
117 kind: dk,
118 parent_name: parent.map(String::from),
119 start_line: node.start_position().row as u32 + 1,
120 end_line: node.end_position().row as u32 + 1,
121 });
122 }
123}
124
125pub(crate) fn visit_definitions<'tree>(
133 root: tree_sitter::Node<'tree>,
134 source: &[u8],
135 emit: &mut impl FnMut(DefinitionSite<'tree>),
136) {
137 let mut stack: Vec<(tree_sitter::Node<'tree>, Option<Rc<str>>)> = vec![(root, None)];
138
139 while let Some((node, parent)) = stack.pop() {
140 let current_parent = parent.as_deref();
141 let kind = node.kind();
142 let mut descended_with_new_parent = false;
143
144 match kind {
145 "function_item" => {
147 emit_named_definition(node, source, DefinitionKind::Function, current_parent, emit)
148 }
149 "struct_item" => {
150 emit_named_definition(node, source, DefinitionKind::Type, current_parent, emit)
151 }
152 "enum_item" => {
153 emit_named_definition(node, source, DefinitionKind::Enum, current_parent, emit)
154 }
155 "trait_item" => {
156 emit_named_definition(node, source, DefinitionKind::Trait, current_parent, emit)
157 }
158 "type_item" => emit_named_definition(
159 node,
160 source,
161 DefinitionKind::TypeAlias,
162 current_parent,
163 emit,
164 ),
165 "const_item" | "static_item" => {
166 emit_named_definition(node, source, DefinitionKind::Const, current_parent, emit)
167 }
168 "mod_item" => {
169 let mod_name: Option<Rc<str>> = node
174 .child_by_field_name("name")
175 .map(|n| Rc::from(node_text(&n, source)))
176 .filter(|name: &Rc<str>| !name.is_empty());
177 emit_named_definition(node, source, DefinitionKind::Module, current_parent, emit);
178 if let Some(name) = mod_name {
179 let mut cursor = node.walk();
180 let children: Vec<_> = node.children(&mut cursor).collect();
181 for child in children.into_iter().rev() {
182 stack.push((child, Some(name.clone())));
183 }
184 descended_with_new_parent = true;
185 }
186 }
187 "impl_item" => {
188 let parent_name: Option<Rc<str>> =
189 extract_rust_impl_type_name(&node, source).map(Rc::from);
190 let mut cursor = node.walk();
191 let children: Vec<_> = node.children(&mut cursor).collect();
192 for child in children.into_iter().rev() {
193 stack.push((child, parent_name.clone()));
194 }
195 descended_with_new_parent = true;
196 }
197
198 "function_definition" => {
200 emit_named_definition(node, source, DefinitionKind::Function, current_parent, emit)
201 }
202 "class_definition" => {
203 let class_name: Option<Rc<str>> = node
204 .child_by_field_name("name")
205 .map(|n| Rc::from(node_text(&n, source)));
206 if let Some(ref name) = class_name
207 && !name.is_empty()
208 {
209 emit(DefinitionSite {
210 node,
211 name: name.to_string(),
212 kind: DefinitionKind::Class,
213 parent_name: current_parent.map(String::from),
214 start_line: node.start_position().row as u32 + 1,
215 end_line: node.end_position().row as u32 + 1,
216 });
217 }
218 let mut cursor = node.walk();
219 let children: Vec<_> = node.children(&mut cursor).collect();
220 for child in children.into_iter().rev() {
221 stack.push((child, class_name.clone()));
222 }
223 descended_with_new_parent = true;
224 }
225
226 "function_declaration" => {
228 emit_named_definition(node, source, DefinitionKind::Function, current_parent, emit)
229 }
230 "method_declaration" => {
231 if let Some(name_node) = node.child_by_field_name("name") {
232 let name = node_text(&name_node, source).to_string();
233 if !name.is_empty() {
234 let receiver = extract_go_receiver_type(&node, source);
235 emit(DefinitionSite {
236 node,
237 name,
238 kind: DefinitionKind::Function,
239 parent_name: receiver.or_else(|| current_parent.map(String::from)),
240 start_line: node.start_position().row as u32 + 1,
241 end_line: node.end_position().row as u32 + 1,
242 });
243 }
244 }
245 }
246 "type_declaration" => {
247 let mut cursor = node.walk();
248 for child in node.children(&mut cursor) {
249 if child.kind() == "type_spec"
250 && let Some(name_node) = child.child_by_field_name("name")
251 {
252 let name = node_text(&name_node, source).to_string();
253 if name.is_empty() {
254 continue;
255 }
256 let dk = match child.child_by_field_name("type").map(|t| t.kind()) {
257 Some("interface_type") => DefinitionKind::Interface,
258 Some("struct_type") => DefinitionKind::Type,
259 _ => DefinitionKind::TypeAlias,
260 };
261 emit(DefinitionSite {
262 node: child,
263 name,
264 kind: dk,
265 parent_name: current_parent.map(String::from),
266 start_line: child.start_position().row as u32 + 1,
267 end_line: child.end_position().row as u32 + 1,
268 });
269 }
270 }
271 }
272
273 "method_definition" => {
275 emit_named_definition(node, source, DefinitionKind::Function, current_parent, emit)
276 }
277 "class_declaration" => {
278 let class_name: Option<Rc<str>> = node
279 .child_by_field_name("name")
280 .map(|n| Rc::from(node_text(&n, source)));
281 if let Some(ref name) = class_name
282 && !name.is_empty()
283 {
284 emit(DefinitionSite {
285 node,
286 name: name.to_string(),
287 kind: DefinitionKind::Class,
288 parent_name: current_parent.map(String::from),
289 start_line: node.start_position().row as u32 + 1,
290 end_line: node.end_position().row as u32 + 1,
291 });
292 }
293 let mut cursor = node.walk();
294 let children: Vec<_> = node.children(&mut cursor).collect();
295 for child in children.into_iter().rev() {
296 stack.push((child, class_name.clone()));
297 }
298 descended_with_new_parent = true;
299 }
300 "interface_declaration" => emit_named_definition(
301 node,
302 source,
303 DefinitionKind::Interface,
304 current_parent,
305 emit,
306 ),
307 "type_alias_declaration" => emit_named_definition(
308 node,
309 source,
310 DefinitionKind::TypeAlias,
311 current_parent,
312 emit,
313 ),
314 "enum_declaration" => {
315 emit_named_definition(node, source, DefinitionKind::Enum, current_parent, emit)
316 }
317 "lexical_declaration" | "variable_declaration" => {
318 let mut cursor = node.walk();
319 let mut saw_declarator = false;
320 for child in node.children(&mut cursor) {
321 if child.kind() == "variable_declarator"
322 && let Some(name_node) = child.child_by_field_name("name")
323 {
324 saw_declarator = true;
325 let name = node_text(&name_node, source).to_string();
326 if name.is_empty() {
327 continue;
328 }
329 if let Some(value_node) = child.child_by_field_name("value") {
330 let vkind = value_node.kind();
331 let dk = if vkind == "arrow_function"
332 || vkind == "function"
333 || vkind == "function_expression"
334 {
335 DefinitionKind::Function
336 } else {
337 DefinitionKind::Const
338 };
339 emit(DefinitionSite {
340 node,
341 name,
342 kind: dk,
343 parent_name: current_parent.map(String::from),
344 start_line: node.start_position().row as u32 + 1,
345 end_line: node.end_position().row as u32 + 1,
346 });
347 }
348 }
349 }
350 if kind == "variable_declaration" && !saw_declarator {
358 descended_with_new_parent = zig_visit_variable_declaration(
359 node,
360 source,
361 current_parent,
362 emit,
363 &mut stack,
364 );
365 }
366 }
367 "pair" => {
368 let Some(name_node) = node.child_by_field_name("key") else {
369 continue;
370 };
371 let Some(value_node) = node.child_by_field_name("value") else {
372 continue;
373 };
374 if matches!(
375 value_node.kind(),
376 "arrow_function" | "function" | "function_expression"
377 ) {
378 let name = node_text(&name_node, source).to_string();
379 if !name.is_empty() {
380 emit(DefinitionSite {
381 node,
382 name,
383 kind: DefinitionKind::Function,
384 parent_name: current_parent.map(String::from),
385 start_line: node.start_position().row as u32 + 1,
386 end_line: node.end_position().row as u32 + 1,
387 });
388 }
389 }
390 }
391 "test_declaration" => {
392 let mut cursor = node.walk();
396 let test_name = node.children(&mut cursor).find_map(|c| match c.kind() {
397 "string" | "identifier" => Some(format!("test:{}", node_text(&c, source))),
398 _ => None,
399 });
400 if let Some(name) = test_name {
401 emit(DefinitionSite {
402 node,
403 name,
404 kind: DefinitionKind::Function,
405 parent_name: current_parent.map(String::from),
406 start_line: node.start_position().row as u32 + 1,
407 end_line: node.end_position().row as u32 + 1,
408 });
409 }
410 }
411
412 "struct_specifier" | "class_specifier" => {
414 emit_named_definition(node, source, DefinitionKind::Class, current_parent, emit)
415 }
416 "namespace_definition" => {
417 emit_named_definition(node, source, DefinitionKind::Module, current_parent, emit)
418 }
419 "enum_specifier" => {
420 emit_named_definition(node, source, DefinitionKind::Enum, current_parent, emit)
421 }
422 "constructor_declaration" => {
423 emit_named_definition(node, source, DefinitionKind::Function, current_parent, emit)
424 }
425
426 _ => {}
427 }
428
429 if !descended_with_new_parent {
430 let mut cursor = node.walk();
431 let children: Vec<_> = node.children(&mut cursor).collect();
432 for child in children.into_iter().rev() {
433 stack.push((child, parent.clone()));
434 }
435 }
436 }
437}
438
439fn walk_definitions(root: tree_sitter::Node, source: &[u8], out: &mut Vec<Definition>) {
441 visit_definitions(root, source, &mut |site| {
442 out.push(Definition {
443 name: site.name,
444 kind: site.kind,
445 start_line: site.start_line,
446 end_line: site.end_line,
447 parent_name: site.parent_name,
448 });
449 });
450}
451
452fn find_definitions(
453 root: tree_sitter::Node<'_>,
454 source: &[u8],
455 target_name: &str,
456) -> Vec<ResolvedSymbol> {
457 let mut matches = Vec::new();
458 visit_definitions(root, source, &mut |site| {
459 if site.name == target_name {
460 matches.push(ResolvedSymbol {
461 name: site.name,
462 start_line: site.start_line,
463 end_line: site.end_line,
464 parent_name: site.parent_name,
465 });
466 }
467 });
468 matches
469}
470
471fn extract_rust_impl_type_name(node: &tree_sitter::Node, source: &[u8]) -> Option<String> {
472 let type_node = node.child_by_field_name("type")?;
473 Some(extract_type_identifier(&type_node, source))
474}
475
476fn extract_type_identifier(node: &tree_sitter::Node, source: &[u8]) -> String {
477 match node.kind() {
478 "type_identifier" | "identifier" => node_text(node, source).to_string(),
479 "generic_type" | "scoped_type_identifier" => {
480 let mut cursor = node.walk();
481 for child in node.children(&mut cursor) {
482 if child.kind() == "type_identifier" || child.kind() == "identifier" {
483 return node_text(&child, source).to_string();
484 }
485 }
486 node_text(node, source).to_string()
487 }
488 _ => node_text(node, source).to_string(),
489 }
490}
491
492fn extract_go_receiver_type(node: &tree_sitter::Node, source: &[u8]) -> Option<String> {
493 let params = node.child_by_field_name("receiver")?;
494 let mut cursor = params.walk();
495 for child in params.children(&mut cursor) {
496 if child.kind() == "parameter_declaration"
497 && let Some(type_node) = child.child_by_field_name("type")
498 {
499 let text = node_text(&type_node, source);
500 return Some(text.trim_start_matches('*').to_string());
501 }
502 }
503 None
504}
505
506fn is_zig_container_scope(kind: &str) -> bool {
511 matches!(
512 kind,
513 "source_file"
514 | "struct_declaration"
515 | "union_declaration"
516 | "enum_declaration"
517 | "opaque_declaration"
518 )
519}
520
521fn zig_container_kind(kind: &str) -> Option<DefinitionKind> {
524 match kind {
525 "struct_declaration" | "union_declaration" | "opaque_declaration" => {
526 Some(DefinitionKind::Type)
527 }
528 "enum_declaration" => Some(DefinitionKind::Enum),
529 _ => None,
530 }
531}
532
533fn zig_visit_variable_declaration<'tree>(
542 node: tree_sitter::Node<'tree>,
543 source: &[u8],
544 current_parent: Option<&str>,
545 emit: &mut impl FnMut(DefinitionSite<'tree>),
546 stack: &mut Vec<(tree_sitter::Node<'tree>, Option<Rc<str>>)>,
547) -> bool {
548 let mut cursor = node.walk();
549 let children: Vec<tree_sitter::Node<'tree>> = node.children(&mut cursor).collect();
550
551 let Some(name) = children
554 .iter()
555 .find(|c| c.kind() == "identifier")
556 .map(|c| node_text(c, source).to_string())
557 .filter(|s| !s.is_empty())
558 else {
559 return false;
560 };
561
562 if let Some(container) = children
563 .iter()
564 .find(|c| zig_container_kind(c.kind()).is_some())
565 {
566 let dk = zig_container_kind(container.kind()).expect("checked by find");
567 emit(DefinitionSite {
568 node,
569 name: name.clone(),
570 kind: dk,
571 parent_name: current_parent.map(String::from),
572 start_line: node.start_position().row as u32 + 1,
573 end_line: node.end_position().row as u32 + 1,
574 });
575 let child_parent: Rc<str> = Rc::from(name.as_str());
576 let mut member_cursor = container.walk();
577 let members: Vec<_> = container.children(&mut member_cursor).collect();
578 for member in members.into_iter().rev() {
579 stack.push((member, Some(child_parent.clone())));
580 }
581 return true;
582 }
583
584 let at_container_scope = node
586 .parent()
587 .map(|p| is_zig_container_scope(p.kind()))
588 .unwrap_or(false);
589 if at_container_scope {
590 emit(DefinitionSite {
591 node,
592 name,
593 kind: DefinitionKind::Const,
594 parent_name: current_parent.map(String::from),
595 start_line: node.start_position().row as u32 + 1,
596 end_line: node.end_position().row as u32 + 1,
597 });
598 }
599 false
600}
601
602pub fn resolve_symbol_lines(
610 source: &[u8],
611 path: &Path,
612 symbol: &str,
613) -> Result<(u32, u32), SymbolResolveError> {
614 let language = Language::from_path(path);
615 language.parser_handle().ok_or_else(|| {
616 SymbolResolveError::UnsupportedLanguage(
617 path.extension()
618 .map(|e| e.to_string_lossy().into_owned())
619 .unwrap_or_else(|| "<none>".to_string()),
620 )
621 })?;
622 let source_text = std::str::from_utf8(source).map_err(|_| SymbolResolveError::ParseFailed)?;
623 let parsed = ParsedFile::parse(source_text, language).ok_or(SymbolResolveError::ParseFailed)?;
624
625 let (parent_filter, target_name) = if let Some(pos) = symbol.rfind("::") {
627 (Some(&symbol[..pos]), &symbol[pos + 2..])
628 } else {
629 (None, symbol)
630 };
631
632 let definitions = find_definitions(parsed.root_node(), source, target_name);
633
634 let matched = if let Some(parent) = parent_filter {
636 definitions
637 .iter()
638 .find(|d| {
639 d.parent_name
640 .as_deref()
641 .map(|p| p == parent)
642 .unwrap_or(false)
643 })
644 .or_else(|| definitions.first())
645 } else {
646 definitions.first()
647 };
648
649 match matched {
650 Some(sym) => Ok((sym.start_line, sym.end_line)),
651 None => Err(SymbolResolveError::SymbolNotFound(symbol.to_string())),
652 }
653}
654
655pub fn resolve_all_symbols(
660 source: &[u8],
661 path: &Path,
662 symbol: &str,
663) -> Result<Vec<ResolvedSymbol>, SymbolResolveError> {
664 let language = Language::from_path(path);
665 language.parser_handle().ok_or_else(|| {
666 SymbolResolveError::UnsupportedLanguage(
667 path.extension()
668 .map(|e| e.to_string_lossy().into_owned())
669 .unwrap_or_else(|| "<none>".to_string()),
670 )
671 })?;
672 let source_text = std::str::from_utf8(source).map_err(|_| SymbolResolveError::ParseFailed)?;
673 let parsed = ParsedFile::parse(source_text, language).ok_or(SymbolResolveError::ParseFailed)?;
674
675 let (parent_filter, target_name) = if let Some(pos) = symbol.rfind("::") {
676 (Some(&symbol[..pos]), &symbol[pos + 2..])
677 } else {
678 (None, symbol)
679 };
680
681 let definitions = find_definitions(parsed.root_node(), source, target_name);
682
683 if let Some(parent) = parent_filter {
684 let filtered: Vec<_> = definitions
685 .into_iter()
686 .filter(|d| {
687 d.parent_name
688 .as_deref()
689 .map(|p| p == parent)
690 .unwrap_or(false)
691 })
692 .collect();
693 Ok(filtered)
694 } else {
695 Ok(definitions)
696 }
697}
698
699pub fn extract_line_range(source: &[u8], start: u32, end: u32) -> Vec<u8> {
704 let mut line: u32 = 1;
705 let mut byte_start = 0;
706
707 for (i, &b) in source.iter().enumerate() {
708 if line == start {
709 byte_start = i;
710 break;
711 }
712 if b == b'\n' {
713 line += 1;
714 }
715 }
716
717 if line < start {
718 return Vec::new();
719 }
720
721 for (i, &b) in source[byte_start..].iter().enumerate() {
722 if b == b'\n' {
723 line += 1;
724 if line > end {
725 return source[byte_start..byte_start + i + 1].to_vec();
726 }
727 }
728 }
729
730 source[byte_start..].to_vec()
731}
732
733#[cfg(test)]
734mod tests {
735 use super::*;
736
737 #[test]
738 fn resolve_rust_fn_main() {
739 let source = br#"
740fn helper() -> bool {
741 true
742}
743
744fn main() {
745 println!("hello");
746 let x = 1;
747}
748
749fn after() {}
750"#;
751 let path = Path::new("test.rs");
752 let (start, end) = resolve_symbol_lines(source, path, "main").unwrap();
753 assert_eq!(start, 6);
754 assert_eq!(end, 9);
755 }
756
757 #[test]
758 fn resolve_rust_qualified_impl_method() {
759 let source = br#"
760struct Repository {
761 path: String,
762}
763
764impl Repository {
765 pub fn open(path: &str) -> Self {
766 Repository {
767 path: path.to_string(),
768 }
769 }
770
771 pub fn close(&self) {}
772}
773
774impl Default for Repository {
775 fn default() -> Self {
776 Repository::open(".")
777 }
778}
779"#;
780 let path = Path::new("repo.rs");
781 let (start, end) = resolve_symbol_lines(source, path, "Repository::open").unwrap();
782 assert_eq!(start, 7);
783 assert_eq!(end, 11);
784 }
785
786 #[test]
787 fn resolve_rust_struct() {
788 let source = br#"
789pub struct Config {
790 pub name: String,
791 pub value: u32,
792}
793"#;
794 let path = Path::new("config.rs");
795 let (start, end) = resolve_symbol_lines(source, path, "Config").unwrap();
796 assert_eq!(start, 2);
797 assert_eq!(end, 5);
798 }
799
800 #[test]
801 fn resolve_python_function() {
802 let source = br#"
803def helper():
804 pass
805
806def process_data(items):
807 result = []
808 for item in items:
809 result.append(item * 2)
810 return result
811
812def cleanup():
813 pass
814"#;
815 let path = Path::new("main.py");
816 let (start, end) = resolve_symbol_lines(source, path, "process_data").unwrap();
817 assert_eq!(start, 5);
818 assert_eq!(end, 9);
819 }
820
821 #[test]
822 fn resolve_python_class_method() {
823 let source = br#"
824class Repository:
825 def __init__(self, path):
826 self.path = path
827
828 def open(self):
829 return True
830"#;
831 let path = Path::new("repo.py");
832 let (start, end) = resolve_symbol_lines(source, path, "Repository::open").unwrap();
833 assert_eq!(start, 6);
834 assert_eq!(end, 7);
835 }
836
837 #[test]
838 #[cfg(feature = "lang-go")]
839 fn resolve_go_function() {
840 let source = br#"package main
841
842func helper() bool {
843 return true
844}
845
846func processData(items []int) []int {
847 result := make([]int, 0)
848 for _, item := range items {
849 result = append(result, item*2)
850 }
851 return result
852}
853"#;
854 let path = Path::new("main.go");
855 let (start, end) = resolve_symbol_lines(source, path, "processData").unwrap();
856 assert_eq!(start, 7);
857 assert_eq!(end, 13);
858 }
859
860 #[test]
861 fn resolve_symbol_not_found() {
862 let source = br#"
863fn main() {}
864"#;
865 let path = Path::new("test.rs");
866 let err = resolve_symbol_lines(source, path, "nonexistent").unwrap_err();
867 assert!(matches!(err, SymbolResolveError::SymbolNotFound(_)));
868 }
869
870 #[test]
871 fn resolve_unsupported_extension() {
872 let source = b"some content";
873 let path = Path::new("test.xyz");
874 let err = resolve_symbol_lines(source, path, "main").unwrap_err();
875 assert!(matches!(err, SymbolResolveError::UnsupportedLanguage(_)));
876 }
877
878 #[test]
879 fn extract_line_range_basic() {
880 let source = b"line 1\nline 2\nline 3\nline 4\nline 5\n";
881 let result = extract_line_range(source, 2, 4);
882 assert_eq!(result, b"line 2\nline 3\nline 4\n");
883 }
884
885 #[test]
886 fn extract_line_range_single_line() {
887 let source = b"line 1\nline 2\nline 3\n";
888 let result = extract_line_range(source, 2, 2);
889 assert_eq!(result, b"line 2\n");
890 }
891
892 #[test]
893 fn resolve_js_function_declaration() {
894 let source = br#"
895function helper() {
896 return true;
897}
898
899function processData(items) {
900 return items.map(x => x * 2);
901}
902"#;
903 let path = Path::new("main.js");
904 let (start, end) = resolve_symbol_lines(source, path, "processData").unwrap();
905 assert_eq!(start, 6);
906 assert_eq!(end, 8);
907 }
908
909 #[test]
910 fn resolve_js_arrow_function_const() {
911 let source = br#"
912const helper = () => true;
913
914const processData = (items) => {
915 return items.map(x => x * 2);
916};
917"#;
918 let path = Path::new("utils.js");
919 let (start, end) = resolve_symbol_lines(source, path, "processData").unwrap();
920 assert_eq!(start, 4);
921 assert_eq!(end, 6);
922 }
923
924 #[test]
931 fn resolve_typescript_object_literal_property_arrow_function() {
932 let source = br#"
933export const db = {
934 query: async (sql: string) => {
935 return [];
936 },
937 insert: async (table: string, data: Record<string, any>) => {
938 const keys = Object.keys(data);
939 return keys;
940 },
941};
942"#;
943 let path = Path::new("db.ts");
944 let (start, end) = resolve_symbol_lines(source, path, "insert").unwrap();
945 assert!((5..=7).contains(&start), "got start={start}");
948 assert!(end > start && end <= 10, "got end={end}");
949 }
950
951 #[test]
952 fn resolve_typescript_function() {
953 let source = br#"
954function helper(): boolean {
955 return true;
956}
957
958function processData(items: number[]): number[] {
959 return items.map(x => x * 2);
960}
961"#;
962 let path = Path::new("main.ts");
963 let (start, end) = resolve_symbol_lines(source, path, "processData").unwrap();
964 assert_eq!(start, 6);
965 assert_eq!(end, 8);
966 }
967
968 #[test]
969 fn resolve_all_returns_multiple_matches() {
970 let source = br#"
971impl Foo {
972 fn do_thing(&self) {}
973}
974
975impl Bar {
976 fn do_thing(&self) {}
977}
978"#;
979 let path = Path::new("test.rs");
980 let results = resolve_all_symbols(source, path, "do_thing").unwrap();
981 assert_eq!(results.len(), 2);
982 assert_eq!(results[0].parent_name.as_deref(), Some("Foo"));
983 assert_eq!(results[1].parent_name.as_deref(), Some("Bar"));
984 }
985
986 #[test]
987 fn extract_definitions_reports_rust_taxonomy_parent_scopes_and_ranges() {
988 let source = br#"const LIMIT: usize = 10;
989pub mod outer {
990 pub struct Widget {
991 pub id: u64,
992 }
993
994 pub enum Mode {
995 Fast,
996 Slow,
997 }
998
999 pub trait Runner {
1000 fn run(&self);
1001 }
1002
1003 pub type WidgetResult<T> = Result<T, Error>;
1004
1005 impl Widget {
1006 pub fn build(id: u64) -> Self {
1007 Self { id }
1008 }
1009 }
1010}
1011"#;
1012
1013 let defs = extract_definitions(source, Path::new("lib.rs")).unwrap();
1014
1015 assert_definition(&defs, "LIMIT", DefinitionKind::Const, 1, 1, None);
1016 assert_definition(&defs, "outer", DefinitionKind::Module, 2, 23, None);
1017 assert_definition(&defs, "Widget", DefinitionKind::Type, 3, 5, Some("outer"));
1019 assert_definition(&defs, "Mode", DefinitionKind::Enum, 7, 10, Some("outer"));
1020 assert_definition(
1021 &defs,
1022 "Runner",
1023 DefinitionKind::Trait,
1024 12,
1025 14,
1026 Some("outer"),
1027 );
1028 assert_definition(
1029 &defs,
1030 "WidgetResult",
1031 DefinitionKind::TypeAlias,
1032 16,
1033 16,
1034 Some("outer"),
1035 );
1036 assert_definition(
1037 &defs,
1038 "build",
1039 DefinitionKind::Function,
1040 19,
1041 21,
1042 Some("Widget"),
1043 );
1044 }
1045
1046 #[test]
1047 fn extract_definitions_reports_typescript_taxonomy_parent_scopes_and_ranges() {
1048 let source = br#"interface Service {
1049 run(): void;
1050}
1051
1052type Handler = (value: string) => void;
1053
1054enum Status {
1055 Ready,
1056 Done,
1057}
1058
1059class Controller {
1060 start(): void {
1061 handle("start");
1062 }
1063}
1064
1065export const handle = (value: string): void => {
1066 console.log(value);
1067};
1068
1069export const settings = { retry: 2 };
1070"#;
1071
1072 let defs = extract_definitions(source, Path::new("controller.ts")).unwrap();
1073
1074 assert_definition(&defs, "Service", DefinitionKind::Interface, 1, 3, None);
1075 assert_definition(&defs, "Handler", DefinitionKind::TypeAlias, 5, 5, None);
1076 assert_definition(&defs, "Status", DefinitionKind::Enum, 7, 10, None);
1077 assert_definition(&defs, "Controller", DefinitionKind::Class, 12, 16, None);
1078 assert_definition(
1079 &defs,
1080 "start",
1081 DefinitionKind::Function,
1082 13,
1083 15,
1084 Some("Controller"),
1085 );
1086 assert_definition(&defs, "handle", DefinitionKind::Function, 18, 20, None);
1087 assert_definition(&defs, "settings", DefinitionKind::Const, 22, 22, None);
1088 }
1089
1090 #[test]
1091 fn extract_definitions_rejects_parse_error_trees() {
1092 let err =
1093 extract_definitions(b"fn broken( -> usize { 1 }", Path::new("broken.rs")).unwrap_err();
1094
1095 assert!(matches!(err, SymbolResolveError::ParseFailed));
1096 }
1097
1098 #[test]
1102 fn walk_definitions_iterative_matches_recursive_output_on_nested_fixture() {
1103 let source = br#"const LIMIT: usize = 10;
1104pub mod outer {
1105 pub struct Widget {
1106 pub id: u64,
1107 }
1108
1109 pub enum Mode {
1110 Fast,
1111 Slow,
1112 }
1113
1114 pub trait Runner {
1115 fn run(&self);
1116 }
1117
1118 pub type WidgetResult<T> = Result<T, Error>;
1119
1120 impl Widget {
1121 pub fn build(id: u64) -> Self {
1122 Self { id }
1123 }
1124 }
1125}
1126"#;
1127
1128 let defs = extract_definitions(source, Path::new("lib.rs")).unwrap();
1129
1130 let expected: &[(&str, DefinitionKind, u32, u32, Option<&str>)] = &[
1131 ("LIMIT", DefinitionKind::Const, 1, 1, None),
1132 ("outer", DefinitionKind::Module, 2, 23, None),
1133 ("Widget", DefinitionKind::Type, 3, 5, Some("outer")),
1134 ("Mode", DefinitionKind::Enum, 7, 10, Some("outer")),
1135 ("Runner", DefinitionKind::Trait, 12, 14, Some("outer")),
1136 (
1137 "WidgetResult",
1138 DefinitionKind::TypeAlias,
1139 16,
1140 16,
1141 Some("outer"),
1142 ),
1143 ("build", DefinitionKind::Function, 19, 21, Some("Widget")),
1144 ];
1145
1146 assert_eq!(defs.len(), expected.len(), "definition count: {defs:?}");
1147 for (def, (name, kind, start, end, parent)) in defs.iter().zip(expected.iter()) {
1148 assert_eq!(&def.name, name);
1149 assert_eq!(def.kind, *kind);
1150 assert_eq!(def.start_line, *start);
1151 assert_eq!(def.end_line, *end);
1152 assert_eq!(def.parent_name.as_deref(), *parent);
1153 }
1154 }
1155
1156 #[cfg(feature = "lang-rust")]
1159 #[test]
1160 fn deeply_nested_rust_modules_walk_definitions_does_not_stack_overflow() {
1161 let depth = 2000usize;
1162 let mut s = String::new();
1163 for i in 0..depth {
1164 s.push_str(&format!("mod m{i} {{\n"));
1165 }
1166 s.push_str("fn target() {}\n");
1167 for _ in 0..depth {
1168 s.push_str("}\n");
1169 }
1170
1171 let source = s.into_bytes();
1172 let path = Path::new("nested.rs");
1173
1174 let handle = std::thread::Builder::new()
1175 .stack_size(128 * 1024)
1176 .spawn(move || extract_definitions(&source, path))
1177 .expect("spawn");
1178 let defs = handle
1179 .join()
1180 .expect("walk_definitions must not stack-overflow on deeply-nested input")
1181 .expect("parse nested modules");
1182 assert!(
1183 defs.iter().any(|d| d.name == "target"),
1184 "deep target fn must be returned, not silently dropped; got {defs:?}"
1185 );
1186 }
1187
1188 #[cfg(feature = "lang-zig")]
1192 #[test]
1193 fn extract_definitions_reports_zig_taxonomy_parents_and_ranges() {
1194 let source = br#"const std = @import("std");
1195
1196pub const MAX: usize = 100;
1197var counter: u32 = 0;
1198
1199pub fn add(a: i32, b: i32) i32 {
1200 const local = 1;
1201 return a + b + local;
1202}
1203
1204pub const Point = struct {
1205 x: f64,
1206 pub fn dist(self: Point) f64 {
1207 const scale = 2.0;
1208 return self.x * scale;
1209 }
1210};
1211
1212const Color = enum { red, green };
1213
1214const Shape = union(enum) { circle: f64 };
1215
1216const Handle = opaque {
1217 pub fn get() void {}
1218};
1219
1220test "addition works" {
1221 const r = add(1, 2);
1222 _ = r;
1223}
1224"#;
1225
1226 let defs = extract_definitions(source, Path::new("sample.zig")).unwrap();
1227
1228 assert_definition(&defs, "std", DefinitionKind::Const, 1, 1, None);
1229 assert_definition(&defs, "MAX", DefinitionKind::Const, 3, 3, None);
1230 assert_definition(&defs, "counter", DefinitionKind::Const, 4, 4, None);
1231 assert_definition(&defs, "add", DefinitionKind::Function, 6, 9, None);
1232 assert_definition(&defs, "Point", DefinitionKind::Type, 11, 17, None);
1233 assert_definition(
1234 &defs,
1235 "dist",
1236 DefinitionKind::Function,
1237 13,
1238 16,
1239 Some("Point"),
1240 );
1241 assert_definition(&defs, "Color", DefinitionKind::Enum, 19, 19, None);
1242 assert_definition(&defs, "Shape", DefinitionKind::Type, 21, 21, None);
1243 assert_definition(&defs, "Handle", DefinitionKind::Type, 23, 25, None);
1244 assert_definition(
1245 &defs,
1246 "get",
1247 DefinitionKind::Function,
1248 24,
1249 24,
1250 Some("Handle"),
1251 );
1252 assert_definition(
1253 &defs,
1254 "test:\"addition works\"",
1255 DefinitionKind::Function,
1256 27,
1257 30,
1258 None,
1259 );
1260
1261 for leaked in ["local", "scale", "r"] {
1264 assert!(
1265 !defs.iter().any(|d| d.name == leaked),
1266 "local {leaked:?} leaked as a symbol: {defs:?}"
1267 );
1268 }
1269 }
1270
1271 fn assert_definition(
1272 defs: &[Definition],
1273 name: &str,
1274 kind: DefinitionKind,
1275 start_line: u32,
1276 end_line: u32,
1277 parent_name: Option<&str>,
1278 ) {
1279 assert!(
1280 defs.iter().any(|def| {
1281 def.name == name
1282 && def.kind == kind
1283 && def.start_line == start_line
1284 && def.end_line == end_line
1285 && def.parent_name.as_deref() == parent_name
1286 }),
1287 "expected {name:?} {kind:?} lines {start_line}-{end_line} parent {parent_name:?}, got: {defs:?}"
1288 );
1289 }
1290}