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