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