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
118fn push_named_definition(
119 node: &tree_sitter::Node,
120 source: &[u8],
121 dk: DefinitionKind,
122 parent: Option<&str>,
123 out: &mut Vec<Definition>,
124) {
125 if let Some(name_node) = node.child_by_field_name("name") {
126 let name = node_text(&name_node, source).to_string();
127 if name.is_empty() {
128 return;
129 }
130 out.push(Definition {
131 name,
132 kind: dk,
133 start_line: node.start_position().row as u32 + 1,
134 end_line: node.end_position().row as u32 + 1,
135 parent_name: parent.map(String::from),
136 });
137 }
138}
139
140fn walk_definitions(
145 root: tree_sitter::Node,
146 source: &[u8],
147 out: &mut Vec<Definition>,
148) {
149 let mut stack: Vec<(tree_sitter::Node, Option<Rc<str>>)> = vec![(root, None)];
150
151 while let Some((node, parent)) = stack.pop() {
152 let current_parent = parent.as_deref();
153 let kind = node.kind();
154 let mut descended_with_new_parent = false;
155
156 match kind {
157 "function_item" => {
159 push_named_definition(&node, source, DefinitionKind::Function, current_parent, out)
160 }
161 "struct_item" => {
162 push_named_definition(&node, source, DefinitionKind::Type, current_parent, out)
163 }
164 "enum_item" => {
165 push_named_definition(&node, source, DefinitionKind::EnumDef, current_parent, out)
166 }
167 "trait_item" => {
168 push_named_definition(&node, source, DefinitionKind::Trait, current_parent, out)
169 }
170 "type_item" => {
171 push_named_definition(&node, source, DefinitionKind::TypeAlias, current_parent, out)
172 }
173 "const_item" | "static_item" => {
174 push_named_definition(&node, source, DefinitionKind::ConstDecl, current_parent, out)
175 }
176 "mod_item" => {
177 push_named_definition(&node, source, DefinitionKind::Module, current_parent, out)
178 }
179 "impl_item" => {
180 let parent_name: Option<Rc<str>> =
181 extract_rust_impl_type_name(&node, source).map(Rc::from);
182 let mut cursor = node.walk();
183 let children: Vec<_> = node.children(&mut cursor).collect();
184 for child in children.into_iter().rev() {
185 stack.push((child, parent_name.clone()));
186 }
187 descended_with_new_parent = true;
188 }
189
190 "function_definition" => {
192 push_named_definition(&node, source, DefinitionKind::Function, current_parent, out)
193 }
194 "class_definition" => {
195 let class_name: Option<Rc<str>> = node
196 .child_by_field_name("name")
197 .map(|n| Rc::from(node_text(&n, source)));
198 if let Some(ref name) = class_name
199 && !name.is_empty()
200 {
201 out.push(Definition {
202 name: name.to_string(),
203 kind: DefinitionKind::Class,
204 start_line: node.start_position().row as u32 + 1,
205 end_line: node.end_position().row as u32 + 1,
206 parent_name: current_parent.map(String::from),
207 });
208 }
209 let mut cursor = node.walk();
210 let children: Vec<_> = node.children(&mut cursor).collect();
211 for child in children.into_iter().rev() {
212 stack.push((child, class_name.clone()));
213 }
214 descended_with_new_parent = true;
215 }
216
217 "function_declaration" => {
219 push_named_definition(&node, source, DefinitionKind::Function, current_parent, out)
220 }
221 "method_declaration" => {
222 if let Some(name_node) = node.child_by_field_name("name") {
223 let name = node_text(&name_node, source).to_string();
224 if !name.is_empty() {
225 let receiver = extract_go_receiver_type(&node, source);
226 out.push(Definition {
227 name,
228 kind: DefinitionKind::Function,
229 start_line: node.start_position().row as u32 + 1,
230 end_line: node.end_position().row as u32 + 1,
231 parent_name: receiver.or_else(|| current_parent.map(String::from)),
232 });
233 }
234 }
235 }
236 "type_declaration" => {
237 let mut cursor = node.walk();
238 for child in node.children(&mut cursor) {
239 if child.kind() == "type_spec"
240 && let Some(name_node) = child.child_by_field_name("name")
241 {
242 let name = node_text(&name_node, source).to_string();
243 if name.is_empty() {
244 continue;
245 }
246 let dk = match child.child_by_field_name("type").map(|t| t.kind()) {
247 Some("interface_type") => DefinitionKind::Interface,
248 Some("struct_type") => DefinitionKind::Type,
249 _ => DefinitionKind::TypeAlias,
250 };
251 out.push(Definition {
252 name,
253 kind: dk,
254 start_line: child.start_position().row as u32 + 1,
255 end_line: child.end_position().row as u32 + 1,
256 parent_name: current_parent.map(String::from),
257 });
258 }
259 }
260 }
261
262 "method_definition" => {
264 push_named_definition(&node, source, DefinitionKind::Function, current_parent, out)
265 }
266 "class_declaration" => {
267 let class_name: Option<Rc<str>> = node
268 .child_by_field_name("name")
269 .map(|n| Rc::from(node_text(&n, source)));
270 if let Some(ref name) = class_name
271 && !name.is_empty()
272 {
273 out.push(Definition {
274 name: name.to_string(),
275 kind: DefinitionKind::Class,
276 start_line: node.start_position().row as u32 + 1,
277 end_line: node.end_position().row as u32 + 1,
278 parent_name: current_parent.map(String::from),
279 });
280 }
281 let mut cursor = node.walk();
282 let children: Vec<_> = node.children(&mut cursor).collect();
283 for child in children.into_iter().rev() {
284 stack.push((child, class_name.clone()));
285 }
286 descended_with_new_parent = true;
287 }
288 "interface_declaration" => {
289 push_named_definition(&node, source, DefinitionKind::Interface, current_parent, out)
290 }
291 "type_alias_declaration" => {
292 push_named_definition(&node, source, DefinitionKind::TypeAlias, current_parent, out)
293 }
294 "enum_declaration" => {
295 push_named_definition(&node, source, DefinitionKind::EnumDef, current_parent, out)
296 }
297 "lexical_declaration" | "variable_declaration" => {
298 let mut cursor = node.walk();
299 for child in node.children(&mut cursor) {
300 if child.kind() == "variable_declarator"
301 && let Some(name_node) = child.child_by_field_name("name")
302 {
303 let name = node_text(&name_node, source).to_string();
304 if name.is_empty() {
305 continue;
306 }
307 if let Some(value_node) = child.child_by_field_name("value") {
308 let vkind = value_node.kind();
309 let dk = if vkind == "arrow_function"
310 || vkind == "function"
311 || vkind == "function_expression"
312 {
313 DefinitionKind::Function
314 } else {
315 DefinitionKind::ConstDecl
316 };
317 out.push(Definition {
318 name,
319 kind: dk,
320 start_line: node.start_position().row as u32 + 1,
321 end_line: node.end_position().row as u32 + 1,
322 parent_name: current_parent.map(String::from),
323 });
324 }
325 }
326 }
327 }
328
329 "struct_specifier" | "class_specifier" => {
331 push_named_definition(&node, source, DefinitionKind::Class, current_parent, out)
332 }
333 "namespace_definition" => {
334 push_named_definition(&node, source, DefinitionKind::Module, current_parent, out)
335 }
336 "enum_specifier" => {
337 push_named_definition(&node, source, DefinitionKind::EnumDef, current_parent, out)
338 }
339 "constructor_declaration" => {
340 push_named_definition(&node, source, DefinitionKind::Function, current_parent, out)
341 }
342
343 _ => {}
344 }
345
346 if !descended_with_new_parent {
347 let mut cursor = node.walk();
348 let children: Vec<_> = node.children(&mut cursor).collect();
349 for child in children.into_iter().rev() {
350 stack.push((child, parent.clone()));
351 }
352 }
353 }
354}
355
356fn extract_rust_impl_type_name(node: &tree_sitter::Node, source: &[u8]) -> Option<String> {
357 let type_node = node.child_by_field_name("type")?;
358 Some(extract_type_identifier(&type_node, source))
359}
360
361fn extract_type_identifier(node: &tree_sitter::Node, source: &[u8]) -> String {
362 match node.kind() {
363 "type_identifier" | "identifier" => node_text(node, source).to_string(),
364 "generic_type" | "scoped_type_identifier" => {
365 let mut cursor = node.walk();
366 for child in node.children(&mut cursor) {
367 if child.kind() == "type_identifier" || child.kind() == "identifier" {
368 return node_text(&child, source).to_string();
369 }
370 }
371 node_text(node, source).to_string()
372 }
373 _ => node_text(node, source).to_string(),
374 }
375}
376
377fn extract_go_receiver_type(node: &tree_sitter::Node, source: &[u8]) -> Option<String> {
378 let params = node.child_by_field_name("receiver")?;
379 let mut cursor = params.walk();
380 for child in params.children(&mut cursor) {
381 if child.kind() == "parameter_declaration"
382 && let Some(type_node) = child.child_by_field_name("type")
383 {
384 let text = node_text(&type_node, source);
385 return Some(text.trim_start_matches('*').to_string());
386 }
387 }
388 None
389}
390
391pub fn resolve_symbol_lines(
399 source: &[u8],
400 path: &Path,
401 symbol: &str,
402) -> Result<(u32, u32), SymbolResolveError> {
403 let language = Language::from_path(path);
404 language.parser_handle().ok_or_else(|| {
405 SymbolResolveError::UnsupportedLanguage(
406 path.extension()
407 .map(|e| e.to_string_lossy().into_owned())
408 .unwrap_or_else(|| "<none>".to_string()),
409 )
410 })?;
411 let source_text = std::str::from_utf8(source).map_err(|_| SymbolResolveError::ParseFailed)?;
412 let parsed = ParsedFile::parse(source_text, language).ok_or(SymbolResolveError::ParseFailed)?;
413
414 let (parent_filter, target_name) = if let Some(pos) = symbol.rfind("::") {
416 (Some(&symbol[..pos]), &symbol[pos + 2..])
417 } else {
418 (None, symbol)
419 };
420
421 let definitions = find_definitions(&parsed.root_node(), source, target_name);
422
423 let matched = if let Some(parent) = parent_filter {
425 definitions
426 .iter()
427 .find(|d| {
428 d.parent_name
429 .as_deref()
430 .map(|p| p == parent)
431 .unwrap_or(false)
432 })
433 .or_else(|| definitions.first())
434 } else {
435 definitions.first()
436 };
437
438 match matched {
439 Some(sym) => Ok((sym.start_line, sym.end_line)),
440 None => Err(SymbolResolveError::SymbolNotFound(symbol.to_string())),
441 }
442}
443
444pub fn resolve_all_symbols(
449 source: &[u8],
450 path: &Path,
451 symbol: &str,
452) -> Result<Vec<ResolvedSymbol>, SymbolResolveError> {
453 let language = Language::from_path(path);
454 language.parser_handle().ok_or_else(|| {
455 SymbolResolveError::UnsupportedLanguage(
456 path.extension()
457 .map(|e| e.to_string_lossy().into_owned())
458 .unwrap_or_else(|| "<none>".to_string()),
459 )
460 })?;
461 let source_text = std::str::from_utf8(source).map_err(|_| SymbolResolveError::ParseFailed)?;
462 let parsed = ParsedFile::parse(source_text, language).ok_or(SymbolResolveError::ParseFailed)?;
463
464 let (parent_filter, target_name) = if let Some(pos) = symbol.rfind("::") {
465 (Some(&symbol[..pos]), &symbol[pos + 2..])
466 } else {
467 (None, symbol)
468 };
469
470 let definitions = find_definitions(&parsed.root_node(), source, target_name);
471
472 if let Some(parent) = parent_filter {
473 let filtered: Vec<_> = definitions
474 .into_iter()
475 .filter(|d| {
476 d.parent_name
477 .as_deref()
478 .map(|p| p == parent)
479 .unwrap_or(false)
480 })
481 .collect();
482 Ok(filtered)
483 } else {
484 Ok(definitions)
485 }
486}
487
488pub fn extract_line_range(source: &[u8], start: u32, end: u32) -> Vec<u8> {
493 let mut line: u32 = 1;
494 let mut byte_start = 0;
495
496 for (i, &b) in source.iter().enumerate() {
497 if line == start {
498 byte_start = i;
499 break;
500 }
501 if b == b'\n' {
502 line += 1;
503 }
504 }
505
506 if line < start {
507 return Vec::new();
508 }
509
510 for (i, &b) in source[byte_start..].iter().enumerate() {
511 if b == b'\n' {
512 line += 1;
513 if line > end {
514 return source[byte_start..byte_start + i + 1].to_vec();
515 }
516 }
517 }
518
519 source[byte_start..].to_vec()
520}
521
522#[cfg(test)]
523mod tests {
524 use super::*;
525
526 #[test]
527 fn resolve_rust_fn_main() {
528 let source = br#"
529fn helper() -> bool {
530 true
531}
532
533fn main() {
534 println!("hello");
535 let x = 1;
536}
537
538fn after() {}
539"#;
540 let path = Path::new("test.rs");
541 let (start, end) = resolve_symbol_lines(source, path, "main").unwrap();
542 assert_eq!(start, 6);
543 assert_eq!(end, 9);
544 }
545
546 #[test]
547 fn resolve_rust_qualified_impl_method() {
548 let source = br#"
549struct Repository {
550 path: String,
551}
552
553impl Repository {
554 pub fn open(path: &str) -> Self {
555 Repository {
556 path: path.to_string(),
557 }
558 }
559
560 pub fn close(&self) {}
561}
562
563impl Default for Repository {
564 fn default() -> Self {
565 Repository::open(".")
566 }
567}
568"#;
569 let path = Path::new("repo.rs");
570 let (start, end) = resolve_symbol_lines(source, path, "Repository::open").unwrap();
571 assert_eq!(start, 7);
572 assert_eq!(end, 11);
573 }
574
575 #[test]
576 fn resolve_rust_struct() {
577 let source = br#"
578pub struct Config {
579 pub name: String,
580 pub value: u32,
581}
582"#;
583 let path = Path::new("config.rs");
584 let (start, end) = resolve_symbol_lines(source, path, "Config").unwrap();
585 assert_eq!(start, 2);
586 assert_eq!(end, 5);
587 }
588
589 #[test]
590 fn resolve_python_function() {
591 let source = br#"
592def helper():
593 pass
594
595def process_data(items):
596 result = []
597 for item in items:
598 result.append(item * 2)
599 return result
600
601def cleanup():
602 pass
603"#;
604 let path = Path::new("main.py");
605 let (start, end) = resolve_symbol_lines(source, path, "process_data").unwrap();
606 assert_eq!(start, 5);
607 assert_eq!(end, 9);
608 }
609
610 #[test]
611 fn resolve_python_class_method() {
612 let source = br#"
613class Repository:
614 def __init__(self, path):
615 self.path = path
616
617 def open(self):
618 return True
619"#;
620 let path = Path::new("repo.py");
621 let (start, end) = resolve_symbol_lines(source, path, "Repository::open").unwrap();
622 assert_eq!(start, 6);
623 assert_eq!(end, 7);
624 }
625
626 #[test]
627 #[cfg(feature = "lang-go")]
628 fn resolve_go_function() {
629 let source = br#"package main
630
631func helper() bool {
632 return true
633}
634
635func processData(items []int) []int {
636 result := make([]int, 0)
637 for _, item := range items {
638 result = append(result, item*2)
639 }
640 return result
641}
642"#;
643 let path = Path::new("main.go");
644 let (start, end) = resolve_symbol_lines(source, path, "processData").unwrap();
645 assert_eq!(start, 7);
646 assert_eq!(end, 13);
647 }
648
649 #[test]
650 fn resolve_symbol_not_found() {
651 let source = br#"
652fn main() {}
653"#;
654 let path = Path::new("test.rs");
655 let err = resolve_symbol_lines(source, path, "nonexistent").unwrap_err();
656 assert!(matches!(err, SymbolResolveError::SymbolNotFound(_)));
657 }
658
659 #[test]
660 fn resolve_unsupported_extension() {
661 let source = b"some content";
662 let path = Path::new("test.xyz");
663 let err = resolve_symbol_lines(source, path, "main").unwrap_err();
664 assert!(matches!(err, SymbolResolveError::UnsupportedLanguage(_)));
665 }
666
667 #[test]
668 fn extract_line_range_basic() {
669 let source = b"line 1\nline 2\nline 3\nline 4\nline 5\n";
670 let result = extract_line_range(source, 2, 4);
671 assert_eq!(result, b"line 2\nline 3\nline 4\n");
672 }
673
674 #[test]
675 fn extract_line_range_single_line() {
676 let source = b"line 1\nline 2\nline 3\n";
677 let result = extract_line_range(source, 2, 2);
678 assert_eq!(result, b"line 2\n");
679 }
680
681 #[test]
682 fn resolve_js_function_declaration() {
683 let source = br#"
684function helper() {
685 return true;
686}
687
688function processData(items) {
689 return items.map(x => x * 2);
690}
691"#;
692 let path = Path::new("main.js");
693 let (start, end) = resolve_symbol_lines(source, path, "processData").unwrap();
694 assert_eq!(start, 6);
695 assert_eq!(end, 8);
696 }
697
698 #[test]
699 fn resolve_js_arrow_function_const() {
700 let source = br#"
701const helper = () => true;
702
703const processData = (items) => {
704 return items.map(x => x * 2);
705};
706"#;
707 let path = Path::new("utils.js");
708 let (start, end) = resolve_symbol_lines(source, path, "processData").unwrap();
709 assert_eq!(start, 4);
710 assert_eq!(end, 6);
711 }
712
713 #[test]
720 fn resolve_typescript_object_literal_property_arrow_function() {
721 let source = br#"
722export const db = {
723 query: async (sql: string) => {
724 return [];
725 },
726 insert: async (table: string, data: Record<string, any>) => {
727 const keys = Object.keys(data);
728 return keys;
729 },
730};
731"#;
732 let path = Path::new("db.ts");
733 let (start, end) = resolve_symbol_lines(source, path, "insert").unwrap();
734 assert!((5..=7).contains(&start), "got start={start}");
737 assert!(end > start && end <= 10, "got end={end}");
738 }
739
740 #[test]
741 fn resolve_typescript_function() {
742 let source = br#"
743function helper(): boolean {
744 return true;
745}
746
747function processData(items: number[]): number[] {
748 return items.map(x => x * 2);
749}
750"#;
751 let path = Path::new("main.ts");
752 let (start, end) = resolve_symbol_lines(source, path, "processData").unwrap();
753 assert_eq!(start, 6);
754 assert_eq!(end, 8);
755 }
756
757 #[test]
758 fn resolve_all_returns_multiple_matches() {
759 let source = br#"
760impl Foo {
761 fn do_thing(&self) {}
762}
763
764impl Bar {
765 fn do_thing(&self) {}
766}
767"#;
768 let path = Path::new("test.rs");
769 let results = resolve_all_symbols(source, path, "do_thing").unwrap();
770 assert_eq!(results.len(), 2);
771 assert_eq!(results[0].parent_name.as_deref(), Some("Foo"));
772 assert_eq!(results[1].parent_name.as_deref(), Some("Bar"));
773 }
774
775 #[test]
776 fn extract_definitions_reports_rust_taxonomy_parent_scopes_and_ranges() {
777 let source = br#"const LIMIT: usize = 10;
778pub mod outer {
779 pub struct Widget {
780 pub id: u64,
781 }
782
783 pub enum Mode {
784 Fast,
785 Slow,
786 }
787
788 pub trait Runner {
789 fn run(&self);
790 }
791
792 pub type WidgetResult<T> = Result<T, Error>;
793
794 impl Widget {
795 pub fn build(id: u64) -> Self {
796 Self { id }
797 }
798 }
799}
800"#;
801
802 let defs = extract_definitions(source, Path::new("lib.rs")).unwrap();
803
804 assert_definition(&defs, "LIMIT", DefinitionKind::ConstDecl, 1, 1, None);
805 assert_definition(&defs, "outer", DefinitionKind::Module, 2, 23, None);
806 assert_definition(&defs, "Widget", DefinitionKind::Type, 3, 5, None);
807 assert_definition(&defs, "Mode", DefinitionKind::EnumDef, 7, 10, None);
808 assert_definition(&defs, "Runner", DefinitionKind::Trait, 12, 14, None);
809 assert_definition(
810 &defs,
811 "WidgetResult",
812 DefinitionKind::TypeAlias,
813 16,
814 16,
815 None,
816 );
817 assert_definition(
818 &defs,
819 "build",
820 DefinitionKind::Function,
821 19,
822 21,
823 Some("Widget"),
824 );
825 }
826
827 #[test]
828 fn extract_definitions_reports_typescript_taxonomy_parent_scopes_and_ranges() {
829 let source = br#"interface Service {
830 run(): void;
831}
832
833type Handler = (value: string) => void;
834
835enum Status {
836 Ready,
837 Done,
838}
839
840class Controller {
841 start(): void {
842 handle("start");
843 }
844}
845
846export const handle = (value: string): void => {
847 console.log(value);
848};
849
850export const settings = { retry: 2 };
851"#;
852
853 let defs = extract_definitions(source, Path::new("controller.ts")).unwrap();
854
855 assert_definition(&defs, "Service", DefinitionKind::Interface, 1, 3, None);
856 assert_definition(&defs, "Handler", DefinitionKind::TypeAlias, 5, 5, None);
857 assert_definition(&defs, "Status", DefinitionKind::EnumDef, 7, 10, None);
858 assert_definition(&defs, "Controller", DefinitionKind::Class, 12, 16, None);
859 assert_definition(
860 &defs,
861 "start",
862 DefinitionKind::Function,
863 13,
864 15,
865 Some("Controller"),
866 );
867 assert_definition(&defs, "handle", DefinitionKind::Function, 18, 20, None);
868 assert_definition(&defs, "settings", DefinitionKind::ConstDecl, 22, 22, None);
869 }
870
871 #[test]
872 fn extract_definitions_rejects_parse_error_trees() {
873 let err =
874 extract_definitions(b"fn broken( -> usize { 1 }", Path::new("broken.rs")).unwrap_err();
875
876 assert!(matches!(err, SymbolResolveError::ParseFailed));
877 }
878
879 #[test]
883 fn walk_definitions_iterative_matches_recursive_output_on_nested_fixture() {
884 let source = br#"const LIMIT: usize = 10;
885pub mod outer {
886 pub struct Widget {
887 pub id: u64,
888 }
889
890 pub enum Mode {
891 Fast,
892 Slow,
893 }
894
895 pub trait Runner {
896 fn run(&self);
897 }
898
899 pub type WidgetResult<T> = Result<T, Error>;
900
901 impl Widget {
902 pub fn build(id: u64) -> Self {
903 Self { id }
904 }
905 }
906}
907"#;
908
909 let defs = extract_definitions(source, Path::new("lib.rs")).unwrap();
910
911 let expected: &[(&str, DefinitionKind, u32, u32, Option<&str>)] = &[
912 ("LIMIT", DefinitionKind::ConstDecl, 1, 1, None),
913 ("outer", DefinitionKind::Module, 2, 23, None),
914 ("Widget", DefinitionKind::Type, 3, 5, None),
915 ("Mode", DefinitionKind::EnumDef, 7, 10, None),
916 ("Runner", DefinitionKind::Trait, 12, 14, None),
917 ("WidgetResult", DefinitionKind::TypeAlias, 16, 16, None),
918 ("build", DefinitionKind::Function, 19, 21, Some("Widget")),
919 ];
920
921 assert_eq!(defs.len(), expected.len(), "definition count: {defs:?}");
922 for (def, (name, kind, start, end, parent)) in defs.iter().zip(expected.iter()) {
923 assert_eq!(&def.name, name);
924 assert_eq!(def.kind, *kind);
925 assert_eq!(def.start_line, *start);
926 assert_eq!(def.end_line, *end);
927 assert_eq!(def.parent_name.as_deref(), *parent);
928 }
929 }
930
931 #[cfg(feature = "lang-rust")]
934 #[test]
935 fn deeply_nested_rust_modules_walk_definitions_does_not_stack_overflow() {
936 let depth = 2000usize;
937 let mut s = String::new();
938 for i in 0..depth {
939 s.push_str(&format!("mod m{i} {{\n"));
940 }
941 s.push_str("fn target() {}\n");
942 for _ in 0..depth {
943 s.push_str("}\n");
944 }
945
946 let source = s.into_bytes();
947 let path = Path::new("nested.rs");
948
949 let handle = std::thread::Builder::new()
950 .stack_size(128 * 1024)
951 .spawn(move || extract_definitions(&source, path))
952 .expect("spawn");
953 let defs = handle
954 .join()
955 .expect("walk_definitions must not stack-overflow on deeply-nested input")
956 .expect("parse nested modules");
957 assert!(
958 defs.iter().any(|d| d.name == "target"),
959 "deep target fn must be returned, not silently dropped; got {defs:?}"
960 );
961 }
962
963 fn assert_definition(
964 defs: &[Definition],
965 name: &str,
966 kind: DefinitionKind,
967 start_line: u32,
968 end_line: u32,
969 parent_name: Option<&str>,
970 ) {
971 assert!(
972 defs.iter().any(|def| {
973 def.name == name
974 && def.kind == kind
975 && def.start_line == start_line
976 && def.end_line == end_line
977 && def.parent_name.as_deref() == parent_name
978 }),
979 "expected {name:?} {kind:?} lines {start_line}-{end_line} parent {parent_name:?}, got: {defs:?}"
980 );
981 }
982}