1use normalize_languages::GrammarLoader;
56use streaming_iterator::StreamingIterator;
57
58#[derive(Debug, Clone, serde::Serialize, schemars::JsonSchema)]
60pub struct Location {
61 pub line: usize,
63 pub column: usize,
65 pub start_byte: usize,
66 pub end_byte: usize,
67}
68
69#[derive(Debug, Clone, serde::Serialize, schemars::JsonSchema)]
71pub struct Reference {
72 pub name: String,
73 pub location: Location,
74 pub definition: Option<Location>,
76}
77
78#[derive(Debug, Clone, serde::Serialize, schemars::JsonSchema)]
80pub struct Definition {
81 pub name: String,
82 pub location: Location,
83 #[serde(skip_serializing_if = "Option::is_none")]
89 pub kind: Option<String>,
90}
91
92pub struct ScopeEngine<'a> {
97 loader: &'a GrammarLoader,
98}
99
100impl<'a> ScopeEngine<'a> {
101 pub fn new(loader: &'a GrammarLoader) -> Self {
102 Self { loader }
103 }
104
105 pub fn has_locals(&self, lang: &str) -> bool {
107 self.loader.get_locals(lang).is_some()
108 }
109
110 pub fn find_definitions(&self, lang: &str, source: &str, name: &str) -> Vec<Definition> {
112 let Some(analysis) = self.analyze(lang, source) else {
113 return Vec::new();
114 };
115 analysis
116 .definitions
117 .into_iter()
118 .filter(|d| d.name == name)
119 .map(|d| Definition {
120 name: d.name,
121 location: d.location,
122 kind: d.kind,
123 })
124 .collect()
125 }
126
127 pub fn find_references(&self, lang: &str, source: &str, name: &str) -> Vec<Reference> {
132 let Some(analysis) = self.analyze(lang, source) else {
133 return Vec::new();
134 };
135 analysis
136 .references
137 .into_iter()
138 .filter(|r| r.name == name)
139 .collect()
140 }
141
142 pub fn all_definitions(&self, lang: &str, source: &str) -> Vec<Definition> {
144 let Some(analysis) = self.analyze(lang, source) else {
145 return Vec::new();
146 };
147 analysis
148 .definitions
149 .into_iter()
150 .map(|d| Definition {
151 name: d.name,
152 location: d.location,
153 kind: d.kind,
154 })
155 .collect()
156 }
157
158 pub fn find_unused_parameters(&self, lang: &str, source: &str) -> Vec<Definition> {
168 let Some(analysis) = self.analyze(lang, source) else {
169 return Vec::new();
170 };
171
172 let referenced_names: std::collections::HashSet<&str> = analysis
174 .references
175 .iter()
176 .filter(|r| r.definition.is_some())
177 .map(|r| r.name.as_str())
178 .collect();
179
180 analysis
181 .definitions
182 .into_iter()
183 .filter(|d| d.kind.as_deref() == Some("parameter"))
184 .filter(|d| !d.name.starts_with('_'))
186 .filter(|d| !referenced_names.contains(d.name.as_str()))
187 .map(|d| Definition {
188 name: d.name,
189 location: d.location,
190 kind: d.kind,
191 })
192 .collect()
193 }
194
195 fn analyze(&self, lang: &str, source: &str) -> Option<FileAnalysis> {
198 let grammar = self.loader.get(lang).ok()?;
199 let locals_src = self.loader.get_locals(lang)?;
200
201 let query = tree_sitter::Query::new(&grammar, &locals_src).ok()?;
202
203 let mut parser = tree_sitter::Parser::new();
204 parser.set_language(&grammar).ok()?;
205 let tree = parser.parse(source, None)?;
206
207 let capture_names: Vec<String> = query
208 .capture_names()
209 .iter()
210 .map(|s| s.to_string())
211 .collect();
212
213 let mut binding_leaf_kinds: std::collections::HashSet<String> =
216 std::collections::HashSet::new();
217 let mut deferred_each: Vec<tree_sitter::Node> = Vec::new();
218 let mut scopes: Vec<ScopeRange> = Vec::new();
219 let mut raw_defs: Vec<RawCapture> = Vec::new();
220 let mut raw_refs: Vec<RawCapture> = Vec::new();
221
222 let mut cursor = tree_sitter::QueryCursor::new();
223 let mut matches = cursor.matches(&query, tree.root_node(), source.as_bytes());
224
225 while let Some(m) = matches.next() {
226 if !check_general_predicates(m, &query, source.as_bytes()) {
230 continue;
231 }
232
233 for cap in m.captures {
234 let name = &capture_names[cap.index as usize];
235 let node = cap.node;
236 let Ok(text) = node.utf8_text(source.as_bytes()) else {
237 continue;
238 };
239
240 let loc = Location {
241 line: node.start_position().row + 1,
242 column: node.start_position().column + 1,
243 start_byte: node.start_byte(),
244 end_byte: node.end_byte(),
245 };
246
247 if name == "local.binding-leaf" {
248 binding_leaf_kinds.insert(node.kind().to_string());
252 } else if name == "local.definition.each" {
253 deferred_each.push(node);
255 } else if name == "local.scope" {
256 scopes.push(ScopeRange {
257 start_byte: node.start_byte(),
258 end_byte: node.end_byte(),
259 });
260 } else if name.starts_with("local.definition") {
261 let kind = name
263 .strip_prefix("local.definition.")
264 .filter(|s| !s.is_empty())
265 .map(|s| s.to_string());
266 raw_defs.push(RawCapture {
267 name: text.to_string(),
268 location: loc,
269 kind,
270 });
271 } else if name == "local.reference" {
272 raw_refs.push(RawCapture {
273 name: text.to_string(),
274 location: loc,
275 kind: None,
276 });
277 }
278 }
279 }
280
281 for node in deferred_each {
283 collect_binding_identifiers(
284 node,
285 source.as_bytes(),
286 &binding_leaf_kinds,
287 &mut raw_defs,
288 );
289 }
290
291 let references: Vec<Reference> = raw_refs
293 .into_iter()
294 .map(|r| {
295 let definition = resolve_reference(&r, &scopes, &raw_defs);
296 Reference {
297 name: r.name,
298 location: r.location,
299 definition,
300 }
301 })
302 .collect();
303
304 Some(FileAnalysis {
305 definitions: raw_defs,
306 references,
307 })
308 }
309}
310
311fn check_general_predicates(
327 m: &tree_sitter::QueryMatch<'_, '_>,
328 query: &tree_sitter::Query,
329 source: &[u8],
330) -> bool {
331 use tree_sitter::QueryPredicateArg;
332 query
333 .general_predicates(m.pattern_index)
334 .iter()
335 .all(|pred| match pred.operator.as_ref() {
336 "is-match-op!" => {
337 if let Some(QueryPredicateArg::Capture(cap_idx)) = pred.args.first() {
339 m.captures
340 .iter()
341 .filter(|c| c.index == *cap_idx)
342 .any(|c| node_has_unnamed_child(c.node, "=", source))
343 } else {
344 true
345 }
346 }
347 _ => true,
348 })
349}
350
351fn node_has_unnamed_child(node: tree_sitter::Node<'_>, text: &str, source: &[u8]) -> bool {
353 (0..node.child_count()).any(|i| {
354 node.child(i as u32)
355 .is_some_and(|child| !child.is_named() && child.utf8_text(source) == Ok(text))
356 })
357}
358
359struct FileAnalysis {
362 definitions: Vec<RawCapture>,
363 references: Vec<Reference>,
364}
365
366struct ScopeRange {
367 start_byte: usize,
368 end_byte: usize,
369}
370
371struct RawCapture {
372 name: String,
373 location: Location,
374 kind: Option<String>,
376}
377
378fn resolve_reference(
388 r: &RawCapture,
389 scopes: &[ScopeRange],
390 definitions: &[RawCapture],
391) -> Option<Location> {
392 let ref_start = r.location.start_byte;
393
394 let mut containing: Vec<&ScopeRange> = scopes
396 .iter()
397 .filter(|s| s.start_byte <= ref_start && ref_start < s.end_byte)
398 .collect();
399 containing.sort_by_key(|s| s.end_byte - s.start_byte);
401
402 for scope in &containing {
403 let def = definitions.iter().find(|d| {
404 d.name == r.name
405 && d.location.start_byte >= scope.start_byte
406 && d.location.start_byte < scope.end_byte
407 && d.location.start_byte < ref_start
408 });
409 if let Some(d) = def {
410 return Some(d.location.clone());
411 }
412 }
413
414 None
415}
416
417fn collect_binding_identifiers(
424 node: tree_sitter::Node,
425 source: &[u8],
426 binding_leaf_kinds: &std::collections::HashSet<String>,
427 out: &mut Vec<RawCapture>,
428) {
429 if !node.is_named() {
430 return;
431 }
432 if node.child_count() == 0 {
433 if binding_leaf_kinds.contains(node.kind())
434 && let Ok(text) = node.utf8_text(source)
435 {
436 out.push(RawCapture {
437 name: text.to_string(),
438 location: Location {
439 line: node.start_position().row + 1,
440 column: node.start_position().column + 1,
441 start_byte: node.start_byte(),
442 end_byte: node.end_byte(),
443 },
444 kind: None,
445 });
446 }
447 } else {
448 let mut cursor = node.walk();
449 for child in node.children(&mut cursor) {
450 collect_binding_identifiers(child, source, binding_leaf_kinds, out);
451 }
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458
459 fn print_tree(lang: &str, src: &str, loader: &GrammarLoader) {
460 let Some(grammar) = loader.get(lang).ok() else {
461 eprintln!("no grammar for {lang}");
462 return;
463 };
464 let mut parser = tree_sitter::Parser::new();
465 parser.set_language(&grammar).unwrap();
467 let tree = parser.parse(src, None).unwrap();
469 fn walk(node: tree_sitter::Node, src: &[u8], indent: usize) {
470 let text = node.utf8_text(src).unwrap_or("?");
471 let display = if text.len() > 40 { &text[..40] } else { text };
472 eprintln!(
473 "{}{} [{}..{}] {:?}",
474 " ".repeat(indent),
475 node.kind(),
476 node.start_byte(),
477 node.end_byte(),
478 display
479 );
480 for child in node.children(&mut node.walk()) {
481 walk(child, src, indent + 1);
482 }
483 }
484 walk(tree.root_node(), src.as_bytes(), 0);
485 }
486
487 fn grammar_dir() -> Option<std::path::PathBuf> {
488 let p = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
489 .parent()
490 .and_then(|p| p.parent())
491 .map(|p| p.join("target/grammars"));
492 p.filter(|p| p.exists())
493 }
494
495 fn loader() -> GrammarLoader {
496 let mut l = GrammarLoader::new();
497 if let Some(dir) = grammar_dir() {
498 l.add_path(dir);
499 }
500 l
501 }
502
503 #[test]
504 fn test_has_locals_javascript() {
505 let l = loader();
506 let engine = ScopeEngine::new(&l);
507 if l.get("javascript").is_err() {
509 return;
510 }
511 let _ = engine.has_locals("javascript");
513 }
514
515 #[test]
516 fn test_no_locals_graceful() {
517 let l = GrammarLoader::with_paths(vec![]);
518 let engine = ScopeEngine::new(&l);
519 let refs = engine.find_references("rust", "fn main() {}", "main");
520 assert!(
521 refs.is_empty(),
522 "should return empty when no grammar/locals"
523 );
524 }
525
526 fn skip_if_no(l: &GrammarLoader, lang: &str) -> bool {
527 l.get(lang).is_err() || l.get_locals(lang).is_none()
528 }
529
530 fn skip_if_no_rust(l: &GrammarLoader) -> bool {
531 skip_if_no(l, "rust")
532 }
533
534 #[test]
535 fn test_rust_has_locals() {
536 let l = loader();
537 if l.get("rust").is_err() {
538 return;
539 }
540 let engine = ScopeEngine::new(&l);
541 assert!(
542 engine.has_locals("rust"),
543 "rust.locals.scm should be present after xtask build-grammars"
544 );
545 }
546
547 #[test]
548 fn test_rust_function_parameter() {
549 let l = loader();
550 if skip_if_no_rust(&l) {
551 return;
552 }
553 let engine = ScopeEngine::new(&l);
554 let src = "fn add(x: i32, y: i32) -> i32 { x + y }";
555 let refs = engine.find_references("rust", src, "x");
557 assert!(!refs.is_empty(), "x should appear as reference");
558 let has_def = refs.iter().any(|r| r.definition.is_some());
559 assert!(
560 has_def,
561 "x reference should resolve to its parameter definition"
562 );
563 let defs = engine.find_definitions("rust", src, "x");
564 assert_eq!(defs.len(), 1, "x should have exactly one definition");
565 }
566
567 #[test]
568 fn test_rust_let_binding() {
569 let l = loader();
570 if skip_if_no_rust(&l) {
571 return;
572 }
573 let engine = ScopeEngine::new(&l);
574 let src = "fn f() { let v = 1; let w = v + 1; w }";
575 let defs = engine.find_definitions("rust", src, "v");
576 assert_eq!(defs.len(), 1, "v should have one definition");
577 let refs = engine.find_references("rust", src, "v");
578 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
580 assert!(
581 resolved >= 1,
582 "v reference in body should resolve to let binding"
583 );
584 }
585
586 #[test]
587 fn test_rust_for_loop_variable() {
588 let l = loader();
589 if skip_if_no_rust(&l) {
590 return;
591 }
592 let engine = ScopeEngine::new(&l);
593 let src = "fn f() { for i in 0..10 { let _ = i; } }";
594 let defs = engine.find_definitions("rust", src, "i");
595 assert_eq!(
596 defs.len(),
597 1,
598 "for loop variable i should have one definition"
599 );
600 let refs = engine.find_references("rust", src, "i");
601 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
602 assert!(resolved >= 1, "i inside loop should resolve to for pattern");
603 }
604
605 #[test]
606 fn test_rust_closure_parameter() {
607 let l = loader();
608 if skip_if_no_rust(&l) {
609 return;
610 }
611 let engine = ScopeEngine::new(&l);
612 let src = "fn f() { let g = |a: i32| a * 2; }";
613 let defs = engine.find_definitions("rust", src, "a");
614 assert_eq!(defs.len(), 1, "closure param a should have one definition");
615 let refs = engine.find_references("rust", src, "a");
616 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
617 assert!(
618 resolved >= 1,
619 "a in closure body should resolve to closure param"
620 );
621 }
622
623 #[test]
624 fn test_rust_no_cross_scope_leakage() {
625 let l = loader();
626 if skip_if_no_rust(&l) {
627 return;
628 }
629 let engine = ScopeEngine::new(&l);
630 let src = "fn f(x: i32) -> i32 { x } fn g(x: i32) -> i32 { x }";
632 let defs = engine.find_definitions("rust", src, "x");
633 assert_eq!(defs.len(), 2, "two separate x parameter definitions");
634 }
635
636 #[test]
639 fn test_python_function_parameter() {
640 let l = loader();
641 if skip_if_no(&l, "python") {
642 return;
643 }
644 let engine = ScopeEngine::new(&l);
645 let src = "def add(x, y):\n return x + y\n";
646 let defs = engine.find_definitions("python", src, "x");
647 assert_eq!(defs.len(), 1, "python: x should have one definition");
648 let refs = engine.find_references("python", src, "x");
649 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
650 assert!(resolved >= 1, "python: x reference should resolve to param");
651 }
652
653 #[test]
654 fn test_python_assignment() {
655 let l = loader();
656 if skip_if_no(&l, "python") {
657 return;
658 }
659 let engine = ScopeEngine::new(&l);
660 let src = "def f():\n v = 1\n return v\n";
661 let defs = engine.find_definitions("python", src, "v");
662 assert_eq!(defs.len(), 1, "python: v should have one definition");
663 }
664
665 #[test]
666 fn test_python_for_variable() {
667 let l = loader();
668 if skip_if_no(&l, "python") {
669 return;
670 }
671 let engine = ScopeEngine::new(&l);
672 let src = "def f():\n for i in range(10):\n print(i)\n";
673 let defs = engine.find_definitions("python", src, "i");
674 assert_eq!(defs.len(), 1, "python: for loop variable i");
675 }
676
677 #[test]
680 fn test_go_function_parameter() {
681 let l = loader();
682 if skip_if_no(&l, "go") {
683 return;
684 }
685 let engine = ScopeEngine::new(&l);
686 let src = "package p\nfunc add(x int, y int) int { return x + y }\n";
687 let defs = engine.find_definitions("go", src, "x");
688 assert_eq!(defs.len(), 1, "go: x should have one definition");
689 let refs = engine.find_references("go", src, "x");
690 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
691 assert!(resolved >= 1, "go: x reference should resolve to param");
692 }
693
694 #[test]
695 fn test_go_short_var_decl() {
696 let l = loader();
697 if skip_if_no(&l, "go") {
698 return;
699 }
700 let engine = ScopeEngine::new(&l);
701 let src = "package p\nfunc f() {\n v := 1\n _ = v\n}\n";
702 let defs = engine.find_definitions("go", src, "v");
703 assert_eq!(defs.len(), 1, "go: short var decl v");
704 }
705
706 #[test]
709 fn test_java_method_parameter() {
710 let l = loader();
711 if skip_if_no(&l, "java") {
712 return;
713 }
714 let engine = ScopeEngine::new(&l);
715 let src = "class A { int add(int x, int y) { return x + y; } }\n";
716 let defs = engine.find_definitions("java", src, "x");
717 assert_eq!(defs.len(), 1, "java: x should have one definition");
718 let refs = engine.find_references("java", src, "x");
719 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
720 assert!(resolved >= 1, "java: x should resolve to param def");
721 }
722
723 #[test]
724 fn test_java_local_variable() {
725 let l = loader();
726 if skip_if_no(&l, "java") {
727 return;
728 }
729 let engine = ScopeEngine::new(&l);
730 let src = "class A { void f() { int v = 1; int w = v + 1; } }\n";
731 let defs = engine.find_definitions("java", src, "v");
732 assert_eq!(defs.len(), 1, "java: local variable v");
733 }
734
735 #[test]
738 fn test_c_function_parameter() {
739 let l = loader();
740 if skip_if_no(&l, "c") {
741 return;
742 }
743 let engine = ScopeEngine::new(&l);
744 let src = "int add(int x, int y) { return x + y; }\n";
745 let defs = engine.find_definitions("c", src, "x");
746 assert_eq!(defs.len(), 1, "c: x should have one definition");
747 let refs = engine.find_references("c", src, "x");
748 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
749 assert!(resolved >= 1, "c: x reference should resolve to param");
750 }
751
752 #[test]
753 fn test_c_local_variable() {
754 let l = loader();
755 if skip_if_no(&l, "c") {
756 return;
757 }
758 let engine = ScopeEngine::new(&l);
759 let src = "void f() { int v = 1; int w = v + 1; }\n";
760 let defs = engine.find_definitions("c", src, "v");
761 assert_eq!(defs.len(), 1, "c: local variable v");
762 }
763
764 #[test]
767 fn test_cpp_function_parameter() {
768 let l = loader();
769 if skip_if_no(&l, "cpp") {
770 return;
771 }
772 let engine = ScopeEngine::new(&l);
773 let src = "int add(int x, int y) { return x + y; }\n";
774 let defs = engine.find_definitions("cpp", src, "x");
775 assert_eq!(defs.len(), 1, "cpp: x should have one definition");
776 let refs = engine.find_references("cpp", src, "x");
777 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
778 assert!(resolved >= 1, "cpp: x reference should resolve to param");
779 }
780
781 #[test]
782 fn test_cpp_local_variable() {
783 let l = loader();
784 if skip_if_no(&l, "cpp") {
785 return;
786 }
787 let engine = ScopeEngine::new(&l);
788 let src = "void f() { int v = 1; int w = v + 1; }\n";
789 let defs = engine.find_definitions("cpp", src, "v");
790 assert_eq!(defs.len(), 1, "cpp: local variable v");
791 }
792
793 #[test]
796 fn test_csharp_method_parameter() {
797 let l = loader();
798 if skip_if_no(&l, "c-sharp") {
799 return;
800 }
801 let engine = ScopeEngine::new(&l);
802 let src = "class A { int Add(int x, int y) { return x + y; } }\n";
803 let defs = engine.find_definitions("c-sharp", src, "x");
804 assert_eq!(defs.len(), 1, "c#: x should have one definition");
805 let refs = engine.find_references("c-sharp", src, "x");
806 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
807 assert!(resolved >= 1, "c#: x reference should resolve to param");
808 }
809
810 #[test]
811 fn test_csharp_local_variable() {
812 let l = loader();
813 if skip_if_no(&l, "c-sharp") {
814 return;
815 }
816 let engine = ScopeEngine::new(&l);
817 let src = "class A { void F() { int v = 1; int w = v + 1; } }\n";
818 let defs = engine.find_definitions("c-sharp", src, "v");
819 assert_eq!(defs.len(), 1, "c#: local variable v");
820 }
821
822 #[test]
825 fn test_ruby_method_parameter() {
826 let l = loader();
827 if skip_if_no(&l, "ruby") {
828 return;
829 }
830 let engine = ScopeEngine::new(&l);
831 let src = "def add(x, y)\n x + y\nend\n";
832 let defs = engine.find_definitions("ruby", src, "x");
833 assert_eq!(defs.len(), 1, "ruby: x should have one definition");
834 let refs = engine.find_references("ruby", src, "x");
835 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
836 assert!(resolved >= 1, "ruby: x reference should resolve to param");
837 }
838
839 #[test]
840 fn test_ruby_assignment() {
841 let l = loader();
842 if skip_if_no(&l, "ruby") {
843 return;
844 }
845 let engine = ScopeEngine::new(&l);
846 let src = "def f\n v = 1\n v + 2\nend\n";
847 let defs = engine.find_definitions("ruby", src, "v");
848 assert_eq!(defs.len(), 1, "ruby: assignment v");
849 }
850
851 #[test]
854 fn test_bash_function_and_variable() {
855 let l = loader();
856 if skip_if_no(&l, "bash") {
857 return;
858 }
859 let engine = ScopeEngine::new(&l);
860 let src = "greet() {\n name=\"world\"\n echo \"$name\"\n}\n";
861 let defs = engine.find_definitions("bash", src, "name");
862 assert_eq!(defs.len(), 1, "bash: variable assignment name");
863 }
864
865 #[test]
866 fn test_bash_for_variable() {
867 let l = loader();
868 if skip_if_no(&l, "bash") {
869 return;
870 }
871 let engine = ScopeEngine::new(&l);
872 let src = "for item in a b c; do\n echo \"$item\"\ndone\n";
873 let defs = engine.find_definitions("bash", src, "item");
874 assert_eq!(defs.len(), 1, "bash: for loop variable item");
875 }
876
877 #[test]
880 fn test_kotlin_function_parameter() {
881 let l = loader();
882 if skip_if_no(&l, "kotlin") {
883 return;
884 }
885 let engine = ScopeEngine::new(&l);
886 let src = "fun add(x: Int, y: Int): Int = x + y\n";
887 let defs = engine.find_definitions("kotlin", src, "x");
888 assert_eq!(defs.len(), 1, "kotlin: x should have one definition");
889 let resolved = engine.find_references("kotlin", src, "x");
890 assert!(!resolved.is_empty(), "kotlin: x reference should exist");
891 }
892
893 #[test]
894 fn test_kotlin_variable_declaration() {
895 let l = loader();
896 if skip_if_no(&l, "kotlin") {
897 return;
898 }
899 let engine = ScopeEngine::new(&l);
900 let src = "fun f() {\n val v = 1\n val w = v + 1\n}\n";
901 let defs = engine.find_definitions("kotlin", src, "v");
902 assert_eq!(defs.len(), 1, "kotlin: val declaration v");
903 }
904
905 #[test]
908 fn test_php_function_parameter() {
909 let l = loader();
910 if skip_if_no(&l, "php") {
911 return;
912 }
913 let engine = ScopeEngine::new(&l);
914 let src = "<?php\nfunction add($x, $y) {\n return $x + $y;\n}\n";
915 let defs = engine.find_definitions("php", src, "$x");
917 assert_eq!(defs.len(), 1, "php: $x should have one definition");
918 let refs = engine.find_references("php", src, "$x");
919 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
920 assert!(resolved >= 1, "php: $x reference should resolve to param");
921 }
922
923 #[test]
926 fn test_zig_function_parameter() {
927 let l = loader();
928 if skip_if_no(&l, "zig") {
929 return;
930 }
931 let engine = ScopeEngine::new(&l);
932 let src = "fn add(x: i32, y: i32) i32 { return x + y; }\n";
933 let defs = engine.find_definitions("zig", src, "x");
935 assert_eq!(defs.len(), 1, "zig: x should have one definition");
936 let refs = engine.find_references("zig", src, "x");
938 assert!(!refs.is_empty(), "zig: x should appear as a reference");
939 }
940
941 #[test]
942 fn test_zig_var_decl() {
943 let l = loader();
944 if skip_if_no(&l, "zig") {
945 return;
946 }
947 let engine = ScopeEngine::new(&l);
948 let src = "fn f() void {\n const v = 1;\n const w = v + 1;\n}\n";
949 let defs = engine.find_definitions("zig", src, "v");
950 assert_eq!(defs.len(), 1, "zig: const declaration v");
951 }
952
953 #[test]
956 fn test_dart_function_parameter() {
957 let l = loader();
958 if skip_if_no(&l, "dart") {
959 return;
960 }
961 let engine = ScopeEngine::new(&l);
962 let src = "int add(int x, int y) { return x + y; }\n";
963 let defs = engine.find_definitions("dart", src, "x");
967 assert_eq!(defs.len(), 1, "dart: x should have one definition");
968 }
969
970 #[test]
971 fn test_dart_local_variable() {
972 let l = loader();
973 if skip_if_no(&l, "dart") {
974 return;
975 }
976 let engine = ScopeEngine::new(&l);
977 let src = "void f() {\n var result = 42;\n print(result);\n}\n";
978 let defs = engine.find_definitions("dart", src, "result");
979 assert_eq!(defs.len(), 1, "dart: local variable result");
980 let refs = engine.find_references("dart", src, "result");
981 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
982 assert!(resolved >= 1, "dart: result reference should resolve");
983 }
984
985 #[test]
988 fn test_elixir_anon_function_parameter() {
989 let l = loader();
990 if skip_if_no(&l, "elixir") {
991 return;
992 }
993 let engine = ScopeEngine::new(&l);
994 let src = "f = fn x, y -> x + y end\n";
995 let defs = engine.find_definitions("elixir", src, "x");
996 assert_eq!(defs.len(), 1, "elixir: anonymous function parameter x");
997 let refs = engine.find_references("elixir", src, "x");
998 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
999 assert!(resolved >= 1, "elixir: x reference should resolve to param");
1000 }
1001
1002 #[test]
1003 fn test_elixir_pattern_match() {
1004 let l = loader();
1005 if skip_if_no(&l, "elixir") {
1006 return;
1007 }
1008 let engine = ScopeEngine::new(&l);
1009 let src = "x = 42\n";
1011 let defs = engine.find_definitions("elixir", src, "x");
1012 assert_eq!(defs.len(), 1, "elixir: x = 42 should define x");
1013 }
1014
1015 #[test]
1016 fn test_elixir_no_false_definitions() {
1017 let l = loader();
1018 if skip_if_no(&l, "elixir") {
1019 return;
1020 }
1021 let engine = ScopeEngine::new(&l);
1022 let src = "x + y\n";
1024 let defs = engine.find_definitions("elixir", src, "x");
1025 assert_eq!(
1026 defs.len(),
1027 0,
1028 "elixir: x in x + y should not be a definition"
1029 );
1030 }
1031
1032 #[test]
1035 fn test_erlang_function_parameter() {
1036 let l = loader();
1037 if skip_if_no(&l, "erlang") {
1038 return;
1039 }
1040 let engine = ScopeEngine::new(&l);
1041 let src = "add(X, Y) -> X + Y.\n";
1042 let defs = engine.find_definitions("erlang", src, "X");
1043 assert_eq!(defs.len(), 1, "erlang: function parameter X");
1044 let refs = engine.find_references("erlang", src, "X");
1045 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1046 assert!(resolved >= 1, "erlang: X reference should resolve to param");
1047 }
1048
1049 #[test]
1050 fn test_erlang_anon_function() {
1051 let l = loader();
1052 if skip_if_no(&l, "erlang") {
1053 return;
1054 }
1055 let engine = ScopeEngine::new(&l);
1056 let src = "F = fun(X) -> X * 2 end.\n";
1057 let defs = engine.find_definitions("erlang", src, "X");
1058 assert_eq!(defs.len(), 1, "erlang: anonymous function parameter X");
1059 }
1060
1061 #[test]
1064 fn test_clojure_defn_name() {
1065 let l = loader();
1066 if skip_if_no(&l, "clojure") {
1067 return;
1068 }
1069 let engine = ScopeEngine::new(&l);
1070 let src = "(defn add [x y] (+ x y))\n";
1071 let defs = engine.find_definitions("clojure", src, "add");
1072 assert_eq!(defs.len(), 1, "clojure: defn should define function name");
1073 }
1074
1075 #[test]
1076 fn test_clojure_fn_parameter() {
1077 let l = loader();
1078 if skip_if_no(&l, "clojure") {
1079 return;
1080 }
1081 let engine = ScopeEngine::new(&l);
1082 let src = "(fn [x y] (+ x y))\n";
1083 let defs = engine.find_definitions("clojure", src, "x");
1084 assert_eq!(defs.len(), 1, "clojure: fn parameter x");
1085 let refs = engine.find_references("clojure", src, "x");
1086 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1087 assert!(
1088 resolved >= 1,
1089 "clojure: x reference should resolve to param"
1090 );
1091 }
1092
1093 #[test]
1094 fn test_clojure_let_binding() {
1095 let l = loader();
1096 if skip_if_no(&l, "clojure") {
1097 return;
1098 }
1099 let engine = ScopeEngine::new(&l);
1100 let src = "(let [x 1] (inc x))\n";
1101 let defs = engine.find_definitions("clojure", src, "x");
1102 assert_eq!(defs.len(), 1, "clojure: let binding x");
1103 let refs = engine.find_references("clojure", src, "x");
1104 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1105 assert!(resolved >= 1, "clojure: x in (inc x) should resolve");
1106 }
1107
1108 #[test]
1109 fn test_clojure_no_false_scope() {
1110 let l = loader();
1111 if skip_if_no(&l, "clojure") {
1112 return;
1113 }
1114 let engine = ScopeEngine::new(&l);
1115 let src = "(foo x y)\n";
1117 let defs = engine.find_definitions("clojure", src, "foo");
1118 assert_eq!(defs.len(), 0, "clojure: (foo x y) should not define foo");
1119 }
1120
1121 #[test]
1124 fn test_julia_function_parameter() {
1125 let l = loader();
1126 if skip_if_no(&l, "julia") {
1127 return;
1128 }
1129 let engine = ScopeEngine::new(&l);
1130 let src = "function add(x, y)\n x + y\nend\n";
1131 let defs = engine.find_definitions("julia", src, "x");
1132 assert_eq!(
1133 defs.len(),
1134 1,
1135 "julia: function param x should have one definition"
1136 );
1137 let refs = engine.find_references("julia", src, "x");
1138 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1139 assert!(resolved >= 1, "julia: x reference should resolve to param");
1140 }
1141
1142 #[test]
1143 fn test_julia_for_variable() {
1144 let l = loader();
1145 if skip_if_no(&l, "julia") {
1146 return;
1147 }
1148 let engine = ScopeEngine::new(&l);
1149 let src = "for i in 1:10\n println(i)\nend\n";
1150 let defs = engine.find_definitions("julia", src, "i");
1151 assert_eq!(defs.len(), 1, "julia: for loop variable i");
1152 let refs = engine.find_references("julia", src, "i");
1153 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1154 assert!(
1155 resolved >= 1,
1156 "julia: i reference should resolve to for binding"
1157 );
1158 }
1159
1160 #[test]
1161 fn test_julia_let_binding() {
1162 let l = loader();
1163 if skip_if_no(&l, "julia") {
1164 return;
1165 }
1166 let engine = ScopeEngine::new(&l);
1167 let src = "let a = 1\n a + 1\nend\n";
1168 let defs = engine.find_definitions("julia", src, "a");
1169 assert_eq!(defs.len(), 1, "julia: let binding a");
1170 }
1171
1172 #[test]
1175 fn test_perl_my_scalar() {
1176 let l = loader();
1177 if skip_if_no(&l, "perl") {
1178 return;
1179 }
1180 let engine = ScopeEngine::new(&l);
1181 let src = "sub greet {\n my $name = \"world\";\n print $name;\n}\n";
1183 let defs = engine.find_definitions("perl", src, "name");
1184 assert_eq!(defs.len(), 1, "perl: my $name should define name");
1185 let refs = engine.find_references("perl", src, "name");
1186 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1187 assert!(resolved >= 1, "perl: $name reference should resolve");
1188 }
1189
1190 #[test]
1191 fn test_perl_my_list() {
1192 let l = loader();
1193 if skip_if_no(&l, "perl") {
1194 return;
1195 }
1196 let engine = ScopeEngine::new(&l);
1197 let src = "my ($a, $b) = (1, 2);\n";
1198 let defs_a = engine.find_definitions("perl", src, "a");
1199 assert_eq!(defs_a.len(), 1, "perl: my ($a, $b) should define a");
1200 let defs_b = engine.find_definitions("perl", src, "b");
1201 assert_eq!(defs_b.len(), 1, "perl: my ($a, $b) should define b");
1202 }
1203
1204 #[test]
1207 fn test_groovy_function_parameter() {
1208 let l = loader();
1209 if skip_if_no(&l, "groovy") {
1210 return;
1211 }
1212 let engine = ScopeEngine::new(&l);
1213 let src = "def add(x, y) {\n return x + y\n}\n";
1214 let defs = engine.find_definitions("groovy", src, "x");
1215 assert_eq!(
1216 defs.len(),
1217 1,
1218 "groovy: function param x should have one definition"
1219 );
1220 let refs = engine.find_references("groovy", src, "x");
1221 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1222 assert!(resolved >= 1, "groovy: x reference should resolve to param");
1223 }
1224
1225 #[test]
1226 fn test_groovy_closure_parameter() {
1227 let l = loader();
1228 if skip_if_no(&l, "groovy") {
1229 return;
1230 }
1231 let engine = ScopeEngine::new(&l);
1232 let src = "def f = { a, b -> a + b }\n";
1233 let defs = engine.find_definitions("groovy", src, "a");
1234 assert_eq!(
1235 defs.len(),
1236 1,
1237 "groovy: closure param a should have one definition"
1238 );
1239 let refs = engine.find_references("groovy", src, "a");
1240 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1241 assert!(
1242 resolved >= 1,
1243 "groovy: a reference should resolve to closure param"
1244 );
1245 }
1246
1247 #[test]
1248 fn test_groovy_variable_declaration() {
1249 let l = loader();
1250 if skip_if_no(&l, "groovy") {
1251 return;
1252 }
1253 let engine = ScopeEngine::new(&l);
1254 let src = "def f() {\n def v = 1\n return v\n}\n";
1255 let defs = engine.find_definitions("groovy", src, "v");
1256 assert_eq!(defs.len(), 1, "groovy: def v should define v");
1257 }
1258
1259 #[test]
1262 fn test_d_function_parameter() {
1263 let l = loader();
1264 if skip_if_no(&l, "d") {
1265 return;
1266 }
1267 let engine = ScopeEngine::new(&l);
1268 let src = "int add(int x, int y) {\n return x + y;\n}\n";
1269 let defs = engine.find_definitions("d", src, "x");
1270 assert_eq!(
1271 defs.len(),
1272 1,
1273 "d: function param x should have one definition"
1274 );
1275 let refs = engine.find_references("d", src, "x");
1276 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1277 assert!(resolved >= 1, "d: x reference should resolve to param");
1278 }
1279
1280 #[test]
1281 fn test_d_auto_declaration() {
1282 let l = loader();
1283 if skip_if_no(&l, "d") {
1284 return;
1285 }
1286 let engine = ScopeEngine::new(&l);
1287 let src = "void f() {\n auto v = 42;\n writeln(v);\n}\n";
1288 let defs = engine.find_definitions("d", src, "v");
1289 assert_eq!(defs.len(), 1, "d: auto v should define v");
1290 let refs = engine.find_references("d", src, "v");
1291 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1292 assert!(
1293 resolved >= 1,
1294 "d: v reference should resolve to auto binding"
1295 );
1296 }
1297
1298 #[test]
1301 fn test_typescript_function_parameter() {
1302 let l = loader();
1303 if skip_if_no(&l, "typescript") {
1304 return;
1305 }
1306 let engine = ScopeEngine::new(&l);
1307 let src = "function add(x: number, y: number): number { return x + y; }";
1308 let defs = engine.find_definitions("typescript", src, "x");
1309 assert_eq!(
1310 defs.len(),
1311 1,
1312 "typescript: required parameter x should have one definition"
1313 );
1314 let refs = engine.find_references("typescript", src, "x");
1315 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1316 assert!(
1317 resolved >= 1,
1318 "typescript: x reference should resolve to param"
1319 );
1320 }
1321
1322 #[test]
1323 fn test_typescript_variable_declarator() {
1324 let l = loader();
1325 if skip_if_no(&l, "typescript") {
1326 return;
1327 }
1328 let engine = ScopeEngine::new(&l);
1329 let src = "function f() { const x = 1; return x; }";
1331 let defs = engine.find_definitions("typescript", src, "x");
1332 assert_eq!(defs.len(), 1, "typescript: const x should define x");
1333 let refs = engine.find_references("typescript", src, "x");
1334 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1335 assert!(
1336 resolved >= 1,
1337 "typescript: x reference should resolve to const"
1338 );
1339 }
1340
1341 #[test]
1342 fn test_typescript_function_declaration() {
1343 let l = loader();
1344 if skip_if_no(&l, "typescript") {
1345 return;
1346 }
1347 let engine = ScopeEngine::new(&l);
1348 let src = "function greet(name: string): void { console.log(name); }";
1349 let defs = engine.find_definitions("typescript", src, "greet");
1350 assert_eq!(
1351 defs.len(),
1352 1,
1353 "typescript: function declaration greet should be defined"
1354 );
1355 }
1356
1357 #[test]
1358 fn test_typescript_arrow_function_single_param() {
1359 let l = loader();
1360 if skip_if_no(&l, "typescript") {
1361 return;
1362 }
1363 let engine = ScopeEngine::new(&l);
1364 let src = "const double = x => x * 2;";
1365 let defs = engine.find_definitions("typescript", src, "x");
1366 assert_eq!(
1367 defs.len(),
1368 1,
1369 "typescript: arrow function single param x should be defined"
1370 );
1371 let refs = engine.find_references("typescript", src, "x");
1372 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1373 assert!(resolved >= 1, "typescript: x in arrow body should resolve");
1374 }
1375
1376 #[test]
1377 fn test_typescript_object_destructuring_param() {
1378 let l = loader();
1379 if skip_if_no(&l, "typescript") {
1380 return;
1381 }
1382 let engine = ScopeEngine::new(&l);
1383 let src = "function f({ a, b }: T) { return a + b; }";
1384 let defs_a = engine.find_definitions("typescript", src, "a");
1385 assert_eq!(
1386 defs_a.len(),
1387 1,
1388 "typescript: destructured param a should be defined"
1389 );
1390 let refs_a = engine.find_references("typescript", src, "a");
1391 let resolved = refs_a.iter().filter(|r| r.definition.is_some()).count();
1392 assert!(
1393 resolved >= 1,
1394 "typescript: a in body should resolve to destructured param"
1395 );
1396 }
1397
1398 #[test]
1399 fn test_typescript_array_destructuring_param() {
1400 let l = loader();
1401 if skip_if_no(&l, "typescript") {
1402 return;
1403 }
1404 let engine = ScopeEngine::new(&l);
1405 let src = "function f([x, y]: U) { return x + y; }";
1406 let defs = engine.find_definitions("typescript", src, "x");
1407 assert_eq!(
1408 defs.len(),
1409 1,
1410 "typescript: destructured array param x should be defined"
1411 );
1412 let refs = engine.find_references("typescript", src, "x");
1413 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1414 assert!(
1415 resolved >= 1,
1416 "typescript: x in body should resolve to destructured param"
1417 );
1418 }
1419
1420 #[test]
1423 fn test_javascript_function_parameter() {
1424 let l = loader();
1425 if skip_if_no(&l, "javascript") {
1426 return;
1427 }
1428 let engine = ScopeEngine::new(&l);
1429 let src = "function add(x, y) { return x + y; }";
1430 let defs = engine.find_definitions("javascript", src, "x");
1431 assert_eq!(
1432 defs.len(),
1433 1,
1434 "javascript: function param x should have one definition"
1435 );
1436 let refs = engine.find_references("javascript", src, "x");
1437 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1438 assert!(
1439 resolved >= 1,
1440 "javascript: x reference should resolve to param"
1441 );
1442 }
1443
1444 #[test]
1445 fn test_javascript_variable_declarator() {
1446 let l = loader();
1447 if skip_if_no(&l, "javascript") {
1448 return;
1449 }
1450 let engine = ScopeEngine::new(&l);
1451 let src = "function f() { const x = 1; return x; }";
1453 let defs = engine.find_definitions("javascript", src, "x");
1454 assert_eq!(defs.len(), 1, "javascript: const x should define x");
1455 let refs = engine.find_references("javascript", src, "x");
1456 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1457 assert!(resolved >= 1, "javascript: x reference should resolve");
1458 }
1459
1460 #[test]
1461 fn test_javascript_function_name() {
1462 let l = loader();
1463 if skip_if_no(&l, "javascript") {
1464 return;
1465 }
1466 let engine = ScopeEngine::new(&l);
1467 let src = "function greet() { return 'hello'; }";
1468 let defs = engine.find_definitions("javascript", src, "greet");
1469 assert_eq!(
1470 defs.len(),
1471 1,
1472 "javascript: function declaration greet should be defined"
1473 );
1474 }
1475
1476 #[test]
1477 fn test_javascript_arrow_function_single_param() {
1478 let l = loader();
1479 if skip_if_no(&l, "javascript") {
1480 return;
1481 }
1482 let engine = ScopeEngine::new(&l);
1483 let src = "const double = x => x * 2;";
1484 let defs = engine.find_definitions("javascript", src, "x");
1485 assert_eq!(
1486 defs.len(),
1487 1,
1488 "javascript: arrow single param x should be defined"
1489 );
1490 let refs = engine.find_references("javascript", src, "x");
1491 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1492 assert!(resolved >= 1, "javascript: x in arrow body should resolve");
1493 }
1494
1495 #[test]
1496 fn test_javascript_object_destructuring_param() {
1497 let l = loader();
1498 if skip_if_no(&l, "javascript") {
1499 return;
1500 }
1501 let engine = ScopeEngine::new(&l);
1502 let src = "function f({ a, b }) { return a + b; }";
1503 let defs_a = engine.find_definitions("javascript", src, "a");
1504 assert_eq!(
1505 defs_a.len(),
1506 1,
1507 "javascript: destructured param a should be defined"
1508 );
1509 let refs_a = engine.find_references("javascript", src, "a");
1510 let resolved = refs_a.iter().filter(|r| r.definition.is_some()).count();
1511 assert!(
1512 resolved >= 1,
1513 "javascript: a in body should resolve to destructured param"
1514 );
1515 }
1516
1517 #[test]
1518 fn test_javascript_array_destructuring_param() {
1519 let l = loader();
1520 if skip_if_no(&l, "javascript") {
1521 return;
1522 }
1523 let engine = ScopeEngine::new(&l);
1524 let src = "function f([x, y]) { return x + y; }";
1525 let defs = engine.find_definitions("javascript", src, "x");
1526 assert_eq!(
1527 defs.len(),
1528 1,
1529 "javascript: destructured array param x should be defined"
1530 );
1531 let refs = engine.find_references("javascript", src, "x");
1532 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1533 assert!(
1534 resolved >= 1,
1535 "javascript: x in body should resolve to destructured param"
1536 );
1537 }
1538
1539 #[test]
1540 fn test_javascript_default_param() {
1541 let l = loader();
1542 if skip_if_no(&l, "javascript") {
1543 return;
1544 }
1545 let engine = ScopeEngine::new(&l);
1546 let src = "function f(c = 1) { return c; }";
1547 let defs = engine.find_definitions("javascript", src, "c");
1548 assert_eq!(
1549 defs.len(),
1550 1,
1551 "javascript: default param c should be defined"
1552 );
1553 let refs = engine.find_references("javascript", src, "c");
1554 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1555 assert!(
1556 resolved >= 1,
1557 "javascript: c in body should resolve to default param"
1558 );
1559 }
1560
1561 #[test]
1562 fn test_javascript_nested_destructuring_param() {
1563 let l = loader();
1564 if skip_if_no(&l, "javascript") {
1565 return;
1566 }
1567 let engine = ScopeEngine::new(&l);
1568 let src = "function f({ a: { b } }) { return b; }";
1570 let defs = engine.find_definitions("javascript", src, "b");
1571 assert_eq!(
1572 defs.len(),
1573 1,
1574 "javascript: nested destructured param b should be defined"
1575 );
1576 let refs = engine.find_references("javascript", src, "b");
1577 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1578 assert!(
1579 resolved >= 1,
1580 "javascript: b should resolve from nested destructuring"
1581 );
1582 }
1583
1584 #[test]
1587 fn test_lua_function_parameter() {
1588 let l = loader();
1589 if skip_if_no(&l, "lua") {
1590 return;
1591 }
1592 let engine = ScopeEngine::new(&l);
1593 let src = "function add(x, y) return x + y end";
1594 let defs = engine.find_definitions("lua", src, "x");
1595 assert_eq!(
1596 defs.len(),
1597 1,
1598 "lua: function param x should have one definition"
1599 );
1600 let refs = engine.find_references("lua", src, "x");
1601 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1602 assert!(resolved >= 1, "lua: x reference should resolve to param");
1603 }
1604
1605 #[test]
1606 fn test_lua_function_name() {
1607 let l = loader();
1608 if skip_if_no(&l, "lua") {
1609 return;
1610 }
1611 let engine = ScopeEngine::new(&l);
1612 let src = "function greet() return 'hello' end";
1613 let defs = engine.find_definitions("lua", src, "greet");
1614 assert_eq!(
1615 defs.len(),
1616 1,
1617 "lua: function declaration greet should be defined"
1618 );
1619 }
1620
1621 #[test]
1622 fn test_lua_for_numeric() {
1623 let l = loader();
1624 if skip_if_no(&l, "lua") {
1625 return;
1626 }
1627 let engine = ScopeEngine::new(&l);
1628 let src = "for i = 1, 10 do print(i) end";
1629 let defs = engine.find_definitions("lua", src, "i");
1630 assert_eq!(
1631 defs.len(),
1632 1,
1633 "lua: for numeric variable i should be defined"
1634 );
1635 let refs = engine.find_references("lua", src, "i");
1636 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1637 assert!(resolved >= 1, "lua: i in for body should resolve");
1638 }
1639
1640 #[test]
1643 fn test_scala_function_parameter() {
1644 let l = loader();
1645 if skip_if_no(&l, "scala") {
1646 return;
1647 }
1648 let engine = ScopeEngine::new(&l);
1649 let src = "def add(x: Int, y: Int): Int = x + y";
1650 let defs = engine.find_definitions("scala", src, "x");
1651 assert_eq!(
1652 defs.len(),
1653 1,
1654 "scala: function param x should have one definition"
1655 );
1656 let refs = engine.find_references("scala", src, "x");
1657 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1658 assert!(resolved >= 1, "scala: x reference should resolve to param");
1659 }
1660
1661 #[test]
1662 fn test_scala_val_definition() {
1663 let l = loader();
1664 if skip_if_no(&l, "scala") {
1665 return;
1666 }
1667 let engine = ScopeEngine::new(&l);
1668 let src = "val x = 42";
1669 let defs = engine.find_definitions("scala", src, "x");
1670 assert_eq!(defs.len(), 1, "scala: val x should be defined");
1671 }
1672
1673 #[test]
1674 fn test_scala_function_name() {
1675 let l = loader();
1676 if skip_if_no(&l, "scala") {
1677 return;
1678 }
1679 let engine = ScopeEngine::new(&l);
1680 let src = "def greet(name: String): String = \"hello \" + name";
1681 let defs = engine.find_definitions("scala", src, "greet");
1682 assert_eq!(
1683 defs.len(),
1684 1,
1685 "scala: def greet should define function name"
1686 );
1687 }
1688
1689 #[test]
1692 fn test_r_arrow_assignment() {
1693 let l = loader();
1694 if skip_if_no(&l, "r") {
1695 return;
1696 }
1697 let engine = ScopeEngine::new(&l);
1698 let src = "f <- function(a) { x <- a * 2; x }\n";
1700 let defs = engine.find_definitions("r", src, "x");
1701 assert_eq!(defs.len(), 1, "r: x <- ... should define x");
1702 let refs = engine.find_references("r", src, "x");
1703 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1704 assert!(resolved >= 1, "r: x reference in body should resolve");
1705 }
1706
1707 #[test]
1708 fn test_r_function_parameter() {
1709 let l = loader();
1710 if skip_if_no(&l, "r") {
1711 return;
1712 }
1713 let engine = ScopeEngine::new(&l);
1714 let src = "f <- function(a, b) { a + b }\n";
1715 let defs = engine.find_definitions("r", src, "a");
1716 assert_eq!(defs.len(), 1, "r: function param a should be defined");
1717 let refs = engine.find_references("r", src, "a");
1718 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1719 assert!(resolved >= 1, "r: a reference in body should resolve");
1720 }
1721
1722 #[test]
1725 fn test_ocaml_let_binding() {
1726 let l = loader();
1727 if skip_if_no(&l, "ocaml") {
1728 return;
1729 }
1730 let engine = ScopeEngine::new(&l);
1731 let src = "let x = 42 in x + 1";
1733 let defs = engine.find_definitions("ocaml", src, "x");
1734 assert_eq!(defs.len(), 1, "ocaml: let x = 42 should define x");
1735 }
1736
1737 #[test]
1738 fn test_ocaml_function_params() {
1739 let l = loader();
1740 if skip_if_no(&l, "ocaml") {
1741 return;
1742 }
1743 let engine = ScopeEngine::new(&l);
1744 let src = "let add x y = x + y";
1746 let defs = engine.find_definitions("ocaml", src, "x");
1747 assert_eq!(
1748 defs.len(),
1749 1,
1750 "ocaml: curried function param x should be defined"
1751 );
1752 }
1753
1754 #[test]
1757 fn test_tsx_function_parameter() {
1758 let l = loader();
1759 if skip_if_no(&l, "tsx") {
1760 return;
1761 }
1762 let engine = ScopeEngine::new(&l);
1763 let src = "function add(x: number, y: number): number { return x + y; }";
1764 let defs = engine.find_definitions("tsx", src, "x");
1765 assert_eq!(defs.len(), 1, "tsx: required parameter x should be defined");
1766 let refs = engine.find_references("tsx", src, "x");
1767 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1768 assert!(resolved >= 1, "tsx: x reference should resolve to param");
1769 }
1770
1771 #[test]
1772 fn test_tsx_variable_declarator() {
1773 let l = loader();
1774 if skip_if_no(&l, "tsx") {
1775 return;
1776 }
1777 let engine = ScopeEngine::new(&l);
1778 let src = "function f() { const x = 1; return x; }";
1779 let defs = engine.find_definitions("tsx", src, "x");
1780 assert_eq!(defs.len(), 1, "tsx: const x should define x");
1781 }
1782
1783 #[test]
1786 fn test_gleam_function_parameter() {
1787 let l = loader();
1788 if skip_if_no(&l, "gleam") {
1789 return;
1790 }
1791 let engine = ScopeEngine::new(&l);
1792 let src = "fn add(x, y) { x + y }";
1793 let defs = engine.find_definitions("gleam", src, "x");
1794 assert_eq!(
1795 defs.len(),
1796 1,
1797 "gleam: function_parameter x should be defined"
1798 );
1799 let refs = engine.find_references("gleam", src, "x");
1800 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1801 assert!(resolved >= 1, "gleam: x reference should resolve to param");
1802 }
1803
1804 #[test]
1805 fn test_gleam_let_binding() {
1806 let l = loader();
1807 if skip_if_no(&l, "gleam") {
1808 return;
1809 }
1810 let engine = ScopeEngine::new(&l);
1811 let src = "fn f() { let x = 1 x }";
1812 let defs = engine.find_definitions("gleam", src, "x");
1813 assert_eq!(defs.len(), 1, "gleam: let x should define x");
1814 }
1815
1816 #[test]
1819 fn test_tlaplus_operator_definition() {
1820 let l = loader();
1821 if skip_if_no(&l, "tlaplus") {
1822 return;
1823 }
1824 let engine = ScopeEngine::new(&l);
1825 let src = "---- MODULE Test ----\nOp(x, y) == x + y\n====";
1827 let defs = engine.find_definitions("tlaplus", src, "x");
1828 assert_eq!(
1829 defs.len(),
1830 1,
1831 "tlaplus: operator parameter x should be defined"
1832 );
1833 let refs = engine.find_references("tlaplus", src, "x");
1834 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1835 assert!(resolved >= 1, "tlaplus: x reference should resolve");
1836 }
1837
1838 #[test]
1839 fn test_tlaplus_let_in() {
1840 let l = loader();
1841 if skip_if_no(&l, "tlaplus") {
1842 return;
1843 }
1844 let engine = ScopeEngine::new(&l);
1845 let src = "---- MODULE Test ----\nExpr == LET x == 1 IN x + 1\n====";
1846 let defs = engine.find_definitions("tlaplus", src, "x");
1847 assert_eq!(defs.len(), 1, "tlaplus: LET x should define x");
1848 }
1849
1850 #[test]
1853 fn test_swift_function_parameter() {
1854 let l = loader();
1855 if skip_if_no(&l, "swift") {
1856 return;
1857 }
1858 let engine = ScopeEngine::new(&l);
1859 let src = "func add(_ x: Int, _ y: Int) -> Int { return x + y }";
1861 let defs = engine.find_definitions("swift", src, "x");
1862 assert_eq!(
1863 defs.len(),
1864 1,
1865 "swift: function parameter x should be defined"
1866 );
1867 let refs = engine.find_references("swift", src, "x");
1868 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1869 assert!(resolved >= 1, "swift: x reference should resolve to param");
1870 }
1871
1872 #[test]
1873 fn test_swift_function_name() {
1874 let l = loader();
1875 if skip_if_no(&l, "swift") {
1876 return;
1877 }
1878 let engine = ScopeEngine::new(&l);
1879 let src = "func greet() -> String { return \"hello\" }";
1880 let defs = engine.find_definitions("swift", src, "greet");
1881 assert_eq!(
1882 defs.len(),
1883 1,
1884 "swift: function name greet should be defined"
1885 );
1886 }
1887
1888 #[test]
1891 fn test_elm_function_definition() {
1892 let l = loader();
1893 if skip_if_no(&l, "elm") {
1894 return;
1895 }
1896 let engine = ScopeEngine::new(&l);
1897 let src = "add x y = x + y";
1899 let defs = engine.find_definitions("elm", src, "x");
1900 assert_eq!(defs.len(), 1, "elm: function parameter x should be defined");
1901 }
1902
1903 #[test]
1906 fn test_fsharp_function_parameter() {
1907 let l = loader();
1908 if skip_if_no(&l, "fsharp") {
1909 return;
1910 }
1911 let engine = ScopeEngine::new(&l);
1912 let src = "let add x y = x + y";
1913 let defs = engine.find_definitions("fsharp", src, "x");
1914 assert_eq!(
1915 defs.len(),
1916 1,
1917 "fsharp: function parameter x should be defined"
1918 );
1919 }
1920
1921 #[test]
1922 fn test_fsharp_value_binding() {
1923 let l = loader();
1924 if skip_if_no(&l, "fsharp") {
1925 return;
1926 }
1927 let engine = ScopeEngine::new(&l);
1928 let src = "let x = 42";
1929 let defs = engine.find_definitions("fsharp", src, "x");
1930 assert_eq!(defs.len(), 1, "fsharp: let x should define x");
1931 }
1932
1933 #[test]
1936 fn test_ada_parameter() {
1937 let l = loader();
1938 if skip_if_no(&l, "ada") {
1939 return;
1940 }
1941 let engine = ScopeEngine::new(&l);
1942 let src = "procedure Add(X : Integer; Y : Integer) is begin null; end Add;";
1943 let defs = engine.find_definitions("ada", src, "X");
1944 assert_eq!(defs.len(), 1, "ada: parameter X should be defined");
1945 }
1946
1947 #[test]
1950 fn test_starlark_function_parameter() {
1951 let l = loader();
1952 if skip_if_no(&l, "starlark") {
1953 return;
1954 }
1955 let engine = ScopeEngine::new(&l);
1956 let src = "def add(x, y):\n return x + y\n";
1957 let defs = engine.find_definitions("starlark", src, "x");
1958 assert_eq!(
1959 defs.len(),
1960 1,
1961 "starlark: function param x should be defined"
1962 );
1963 let refs = engine.find_references("starlark", src, "x");
1964 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
1965 assert!(resolved >= 1, "starlark: x in body should resolve");
1966 }
1967
1968 #[test]
1969 fn test_starlark_assignment() {
1970 let l = loader();
1971 if skip_if_no(&l, "starlark") {
1972 return;
1973 }
1974 let engine = ScopeEngine::new(&l);
1975 let src = "def f():\n x = 1\n return x\n";
1976 let defs = engine.find_definitions("starlark", src, "x");
1977 assert_eq!(defs.len(), 1, "starlark: assignment x should be defined");
1978 }
1979
1980 #[test]
1983 fn test_thrift_function_parameter() {
1984 let l = loader();
1985 if skip_if_no(&l, "thrift") {
1986 return;
1987 }
1988 let engine = ScopeEngine::new(&l);
1989 let src = "service Calc { i32 add(1: i32 x, 2: i32 y) }";
1990 let defs = engine.find_definitions("thrift", src, "x");
1991 assert_eq!(defs.len(), 1, "thrift: parameter x should be defined");
1992 }
1993
1994 #[test]
1995 fn test_thrift_service_name() {
1996 let l = loader();
1997 if skip_if_no(&l, "thrift") {
1998 return;
1999 }
2000 let engine = ScopeEngine::new(&l);
2001 let src = "service Calc { i32 add(1: i32 x) }";
2002 let defs = engine.find_definitions("thrift", src, "Calc");
2003 assert_eq!(defs.len(), 1, "thrift: service name Calc should be defined");
2004 }
2005
2006 #[test]
2009 fn test_objc_method_parameter() {
2010 let l = loader();
2011 if skip_if_no(&l, "objc") {
2012 return;
2013 }
2014 let engine = ScopeEngine::new(&l);
2015 let src = "@implementation Foo\n- (int)add:(int)x {\n return x + 1;\n}\n@end";
2017 let defs = engine.find_definitions("objc", src, "x");
2018 assert_eq!(defs.len(), 1, "objc: method parameter x should be defined");
2019 let refs = engine.find_references("objc", src, "x");
2020 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
2021 assert!(resolved >= 1, "objc: x reference should resolve");
2022 }
2023
2024 #[test]
2027 fn test_nix_function_formal() {
2028 let l = loader();
2029 if skip_if_no(&l, "nix") {
2030 return;
2031 }
2032 let engine = ScopeEngine::new(&l);
2033 let src = "{ x, y }: x + y";
2035 let defs = engine.find_definitions("nix", src, "x");
2036 assert_eq!(defs.len(), 1, "nix: formal parameter x should be defined");
2037 }
2038
2039 #[test]
2040 fn test_nix_let_binding() {
2041 let l = loader();
2042 if skip_if_no(&l, "nix") {
2043 return;
2044 }
2045 let engine = ScopeEngine::new(&l);
2046 let src = "let x = 1; in x + 1";
2047 let defs = engine.find_definitions("nix", src, "x");
2048 assert_eq!(defs.len(), 1, "nix: let binding x should be defined");
2049 }
2050
2051 #[test]
2054 fn test_rescript_function_parameter() {
2055 let l = loader();
2056 if skip_if_no(&l, "rescript") {
2057 return;
2058 }
2059 let engine = ScopeEngine::new(&l);
2060 let src = "let add = (x, y) => x + y";
2061 let defs = engine.find_definitions("rescript", src, "x");
2062 assert_eq!(
2063 defs.len(),
2064 1,
2065 "rescript: function parameter x should be defined"
2066 );
2067 }
2068
2069 #[test]
2070 fn test_rescript_let_binding() {
2071 let l = loader();
2072 if skip_if_no(&l, "rescript") {
2073 return;
2074 }
2075 let engine = ScopeEngine::new(&l);
2076 let src = "let x = 1";
2077 let defs = engine.find_definitions("rescript", src, "x");
2078 assert_eq!(defs.len(), 1, "rescript: let x should be defined");
2079 }
2080
2081 #[test]
2084 fn test_haskell_function_parameter() {
2085 let l = loader();
2086 if skip_if_no(&l, "haskell") {
2087 return;
2088 }
2089 let engine = ScopeEngine::new(&l);
2090 let src = "add x y = x + y";
2091 let defs = engine.find_definitions("haskell", src, "x");
2092 assert_eq!(
2093 defs.len(),
2094 1,
2095 "haskell: function parameter x should be defined"
2096 );
2097 let refs = engine.find_references("haskell", src, "x");
2098 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
2099 assert!(resolved >= 1, "haskell: x reference should resolve");
2100 }
2101
2102 #[test]
2103 fn test_haskell_let_binding() {
2104 let l = loader();
2105 if skip_if_no(&l, "haskell") {
2106 return;
2107 }
2108 let engine = ScopeEngine::new(&l);
2109 let src = "f = let x = 1 in x + 1";
2110 let defs = engine.find_definitions("haskell", src, "x");
2111 assert_eq!(defs.len(), 1, "haskell: let x should be defined");
2112 }
2113
2114 #[test]
2117 fn test_capnp_field_definition() {
2118 let l = loader();
2119 if skip_if_no(&l, "capnp") {
2120 return;
2121 }
2122 let engine = ScopeEngine::new(&l);
2123 let src = "@0xdbb9ad1f14bf0b36;\nstruct Point { x @0 :Float64; y @1 :Float64; }";
2124 let defs = engine.find_definitions("capnp", src, "x");
2125 assert_eq!(defs.len(), 1, "capnp: field x should be defined");
2126 }
2127
2128 #[test]
2131 fn test_scheme_function_define() {
2132 let l = loader();
2133 if skip_if_no(&l, "scheme") {
2134 return;
2135 }
2136 let engine = ScopeEngine::new(&l);
2137 let src = "(define (f x y) (+ x y))";
2138 let defs = engine.find_definitions("scheme", src, "x");
2139 assert_eq!(
2140 defs.len(),
2141 1,
2142 "scheme: parameter x in (define (f x y) ...) should be defined"
2143 );
2144 let refs = engine.find_references("scheme", src, "x");
2145 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
2146 assert!(resolved >= 1, "scheme: x reference should resolve");
2147 }
2148
2149 #[test]
2150 fn test_scheme_variable_define() {
2151 let l = loader();
2152 if skip_if_no(&l, "scheme") {
2153 return;
2154 }
2155 let engine = ScopeEngine::new(&l);
2156 let src = "(define x 42)";
2157 let defs = engine.find_definitions("scheme", src, "x");
2158 assert_eq!(defs.len(), 1, "scheme: (define x val) should define x");
2159 }
2160
2161 #[test]
2162 fn test_scheme_let_binding() {
2163 let l = loader();
2164 if skip_if_no(&l, "scheme") {
2165 return;
2166 }
2167 let engine = ScopeEngine::new(&l);
2168 let src = "(lambda (unused) (let ((z 1)) z))";
2169 let defs = engine.find_definitions("scheme", src, "z");
2170 assert_eq!(defs.len(), 1, "scheme: let binding z should be defined");
2171 let refs = engine.find_references("scheme", src, "z");
2172 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
2173 assert!(
2174 resolved >= 1,
2175 "scheme: z reference should resolve to let binding"
2176 );
2177 }
2178
2179 #[test]
2180 fn test_scheme_lambda_param() {
2181 let l = loader();
2182 if skip_if_no(&l, "scheme") {
2183 return;
2184 }
2185 let engine = ScopeEngine::new(&l);
2186 let src = "(lambda (x y) (+ x y))";
2187 let defs = engine.find_definitions("scheme", src, "x");
2188 assert_eq!(
2189 defs.len(),
2190 1,
2191 "scheme: lambda parameter x should be defined"
2192 );
2193 }
2194
2195 #[test]
2198 fn test_commonlisp_defun_parameter() {
2199 let l = loader();
2200 if skip_if_no(&l, "commonlisp") {
2201 return;
2202 }
2203 let engine = ScopeEngine::new(&l);
2204 let src = "(defun add (x y) (+ x y))";
2205 let defs = engine.find_definitions("commonlisp", src, "x");
2206 assert_eq!(
2207 defs.len(),
2208 1,
2209 "commonlisp: defun parameter x should be defined"
2210 );
2211 let refs = engine.find_references("commonlisp", src, "x");
2212 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
2213 assert!(resolved >= 1, "commonlisp: x reference should resolve");
2214 }
2215
2216 #[test]
2217 fn test_commonlisp_defun_name() {
2218 let l = loader();
2219 if skip_if_no(&l, "commonlisp") {
2220 return;
2221 }
2222 let engine = ScopeEngine::new(&l);
2223 let src = "(defun add (x y) (+ x y))";
2224 let defs = engine.find_definitions("commonlisp", src, "add");
2225 assert_eq!(
2226 defs.len(),
2227 1,
2228 "commonlisp: defun name add should be defined"
2229 );
2230 }
2231
2232 #[test]
2233 fn test_commonlisp_let_binding() {
2234 let l = loader();
2235 if skip_if_no(&l, "commonlisp") {
2236 return;
2237 }
2238 let engine = ScopeEngine::new(&l);
2239 let src = "(defun f () (let ((z 1)) z))";
2240 let defs = engine.find_definitions("commonlisp", src, "z");
2241 assert_eq!(defs.len(), 1, "commonlisp: let binding z should be defined");
2242 let refs = engine.find_references("commonlisp", src, "z");
2243 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
2244 assert!(
2245 resolved >= 1,
2246 "commonlisp: z reference should resolve to let binding"
2247 );
2248 }
2249
2250 #[test]
2253 fn test_elisp_defun_parameter() {
2254 let l = loader();
2255 if skip_if_no(&l, "elisp") {
2256 return;
2257 }
2258 let engine = ScopeEngine::new(&l);
2259 let src = "(defun add (x y) (+ x y))";
2260 let defs = engine.find_definitions("elisp", src, "x");
2261 assert_eq!(defs.len(), 1, "elisp: defun parameter x should be defined");
2262 let refs = engine.find_references("elisp", src, "x");
2263 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
2264 assert!(resolved >= 1, "elisp: x reference should resolve");
2265 }
2266
2267 #[test]
2268 fn test_elisp_defun_name() {
2269 let l = loader();
2270 if skip_if_no(&l, "elisp") {
2271 return;
2272 }
2273 let engine = ScopeEngine::new(&l);
2274 let src = "(defun add (x y) (+ x y))";
2275 let defs = engine.find_definitions("elisp", src, "add");
2276 assert_eq!(defs.len(), 1, "elisp: defun name add should be defined");
2277 }
2278
2279 #[test]
2280 fn test_elisp_let_binding() {
2281 let l = loader();
2282 if skip_if_no(&l, "elisp") {
2283 return;
2284 }
2285 let engine = ScopeEngine::new(&l);
2286 let src = "(defun f () (let ((z 1)) z))";
2287 let defs = engine.find_definitions("elisp", src, "z");
2288 assert_eq!(defs.len(), 1, "elisp: let binding z should be defined");
2289 let refs = engine.find_references("elisp", src, "z");
2290 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
2291 assert!(
2292 resolved >= 1,
2293 "elisp: z reference should resolve to let binding"
2294 );
2295 }
2296
2297 #[test]
2300 fn test_prolog_clause_variable() {
2301 let l = loader();
2302 if skip_if_no(&l, "prolog") {
2303 return;
2304 }
2305 let engine = ScopeEngine::new(&l);
2306 let src = "foo(X, Y) :- bar(X), baz(Y).";
2307 let defs = engine.find_definitions("prolog", src, "X");
2308 assert!(!defs.is_empty(), "prolog: variable X should be captured");
2309 let refs = engine.find_references("prolog", src, "X");
2310 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
2311 assert!(
2312 resolved >= 1,
2313 "prolog: X reference should resolve within clause"
2314 );
2315 }
2316
2317 #[test]
2318 fn test_prolog_anon_variable_excluded() {
2319 let l = loader();
2320 if skip_if_no(&l, "prolog") {
2321 return;
2322 }
2323 let engine = ScopeEngine::new(&l);
2324 let src = "foo(_, X) :- bar(X).";
2325 let defs = engine.find_definitions("prolog", src, "_");
2326 assert_eq!(
2327 defs.len(),
2328 0,
2329 "prolog: anonymous variable _ should not be defined"
2330 );
2331 }
2332
2333 #[test]
2336 fn test_fish_function_name() {
2337 let l = loader();
2338 if skip_if_no(&l, "fish") {
2339 return;
2340 }
2341 let engine = ScopeEngine::new(&l);
2342 let src = "function myfunc\n echo hello\nend";
2343 let defs = engine.find_definitions("fish", src, "myfunc");
2344 assert_eq!(
2345 defs.len(),
2346 1,
2347 "fish: function name myfunc should be defined"
2348 );
2349 }
2350
2351 #[test]
2352 fn test_fish_for_variable() {
2353 let l = loader();
2354 if skip_if_no(&l, "fish") {
2355 return;
2356 }
2357 let engine = ScopeEngine::new(&l);
2358 let src = "function outer\n for i in a b c\n echo $i\n end\nend";
2359 let defs = engine.find_definitions("fish", src, "i");
2360 assert_eq!(defs.len(), 1, "fish: for loop variable i should be defined");
2361 let refs = engine.find_references("fish", src, "i");
2362 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
2363 assert!(
2364 resolved >= 1,
2365 "fish: $i reference should resolve to for variable"
2366 );
2367 }
2368
2369 #[test]
2372 fn test_zsh_variable_assignment() {
2373 let l = loader();
2374 if skip_if_no(&l, "zsh") {
2375 return;
2376 }
2377 let engine = ScopeEngine::new(&l);
2378 let src = "x=1";
2379 let defs = engine.find_definitions("zsh", src, "x");
2380 assert_eq!(
2381 defs.len(),
2382 1,
2383 "zsh: bare variable assignment x should be defined"
2384 );
2385 }
2386
2387 #[test]
2390 fn test_powershell_function_param() {
2391 let l = loader();
2392 if skip_if_no(&l, "powershell") {
2393 return;
2394 }
2395 let engine = ScopeEngine::new(&l);
2396 let src = "function foo {\n param($x)\n $x + 1\n}";
2397 let defs = engine.find_definitions("powershell", src, "$x");
2398 assert_eq!(defs.len(), 1, "powershell: param $x should be defined");
2399 let refs = engine.find_references("powershell", src, "$x");
2400 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
2401 assert!(resolved >= 1, "powershell: $x reference should resolve");
2402 }
2403
2404 #[test]
2405 fn test_powershell_function_name() {
2406 let l = loader();
2407 if skip_if_no(&l, "powershell") {
2408 return;
2409 }
2410 let engine = ScopeEngine::new(&l);
2411 let src = "function foo {\n param($x)\n $x + 1\n}";
2412 let defs = engine.find_definitions("powershell", src, "foo");
2413 assert_eq!(
2414 defs.len(),
2415 1,
2416 "powershell: function name foo should be defined"
2417 );
2418 }
2419
2420 #[test]
2421 fn test_powershell_foreach_variable() {
2422 let l = loader();
2423 if skip_if_no(&l, "powershell") {
2424 return;
2425 }
2426 let engine = ScopeEngine::new(&l);
2427 let src = "foreach ($item in @(1,2,3)) { $item }";
2428 let defs = engine.find_definitions("powershell", src, "$item");
2429 assert_eq!(
2430 defs.len(),
2431 1,
2432 "powershell: foreach variable $item should be defined"
2433 );
2434 }
2435
2436 #[test]
2439 fn test_vim_let_binding() {
2440 let l = loader();
2441 if skip_if_no(&l, "vim") {
2442 return;
2443 }
2444 let engine = ScopeEngine::new(&l);
2445 let src = "function! Foo()\n let x = 1\n let y = x + 1\nendfunction";
2446 let defs = engine.find_definitions("vim", src, "x");
2447 assert_eq!(defs.len(), 1, "vim: let x should be defined");
2448 let refs = engine.find_references("vim", src, "x");
2449 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
2450 assert!(resolved >= 1, "vim: x reference should resolve");
2451 }
2452
2453 #[test]
2454 fn test_vim_function_param() {
2455 let l = loader();
2456 if skip_if_no(&l, "vim") {
2457 return;
2458 }
2459 let engine = ScopeEngine::new(&l);
2460 let src = "function! Foo(bar)\n let l:x = bar\nendfunction";
2461 let defs = engine.find_definitions("vim", src, "bar");
2462 assert_eq!(
2463 defs.len(),
2464 1,
2465 "vim: function parameter bar should be defined"
2466 );
2467 }
2468
2469 #[test]
2470 fn test_vim_scoped_let() {
2471 let l = loader();
2472 if skip_if_no(&l, "vim") {
2473 return;
2474 }
2475 let engine = ScopeEngine::new(&l);
2476 let src = "function! Foo()\n let l:y = 1\nendfunction";
2477 let defs = engine.find_definitions("vim", src, "y");
2478 assert_eq!(defs.len(), 1, "vim: let l:y should define y");
2479 }
2480
2481 #[test]
2484 fn test_sql_cte_name() {
2485 let l = loader();
2486 if skip_if_no(&l, "sql") {
2487 return;
2488 }
2489 let engine = ScopeEngine::new(&l);
2490 let src = "WITH x AS (SELECT 1) SELECT * FROM x";
2491 let defs = engine.find_definitions("sql", src, "x");
2492 assert_eq!(defs.len(), 1, "sql: CTE name x should be defined");
2493 }
2494
2495 #[test]
2496 fn test_sql_table_alias() {
2497 let l = loader();
2498 if skip_if_no(&l, "sql") {
2499 return;
2500 }
2501 let engine = ScopeEngine::new(&l);
2502 let src = "SELECT t.col FROM tbl AS t";
2503 let defs = engine.find_definitions("sql", src, "t");
2504 assert_eq!(defs.len(), 1, "sql: table alias t should be defined");
2505 }
2506
2507 #[test]
2512 fn test_matlab_function_parameter() {
2513 let l = loader();
2514 if skip_if_no(&l, "matlab") {
2515 return;
2516 }
2517 let engine = ScopeEngine::new(&l);
2518 let src = "function y = foo(x)\n y = x + 1;\nend";
2519 let defs = engine.find_definitions("matlab", src, "x");
2520 assert_eq!(
2521 defs.len(),
2522 1,
2523 "matlab: function parameter x should be defined"
2524 );
2525 let refs = engine.find_references("matlab", src, "x");
2526 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
2527 assert!(resolved >= 1, "matlab: x reference should resolve");
2528 }
2529
2530 #[test]
2531 fn test_matlab_return_variable() {
2532 let l = loader();
2533 if skip_if_no(&l, "matlab") {
2534 return;
2535 }
2536 let engine = ScopeEngine::new(&l);
2537 let src = "function y = foo(x)\n y = x + 1;\nend";
2538 let defs = engine.find_definitions("matlab", src, "y");
2539 assert!(
2540 defs.len() >= 1,
2541 "matlab: return variable y should be defined"
2542 );
2543 }
2544
2545 #[test]
2546 fn test_matlab_function_name() {
2547 let l = loader();
2548 if skip_if_no(&l, "matlab") {
2549 return;
2550 }
2551 let engine = ScopeEngine::new(&l);
2552 let src = "function y = foo(x)\n y = x + 1;\nend";
2553 let defs = engine.find_definitions("matlab", src, "foo");
2554 assert_eq!(defs.len(), 1, "matlab: function name foo should be defined");
2555 }
2556
2557 #[test]
2560 fn test_awk_function_parameter() {
2561 let l = loader();
2562 if skip_if_no(&l, "awk") {
2563 return;
2564 }
2565 let engine = ScopeEngine::new(&l);
2566 let src = "function foo(x, y) { return x + y }";
2567 let defs = engine.find_definitions("awk", src, "x");
2568 assert_eq!(defs.len(), 1, "awk: function parameter x should be defined");
2569 let refs = engine.find_references("awk", src, "x");
2570 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
2571 assert!(resolved >= 1, "awk: x reference should resolve");
2572 }
2573
2574 #[test]
2575 fn test_awk_function_name() {
2576 let l = loader();
2577 if skip_if_no(&l, "awk") {
2578 return;
2579 }
2580 let engine = ScopeEngine::new(&l);
2581 let src = "function foo(x) { return x }";
2582 let defs = engine.find_definitions("awk", src, "foo");
2583 assert_eq!(defs.len(), 1, "awk: function name foo should be defined");
2584 }
2585
2586 #[test]
2589 fn test_cmake_function_name() {
2590 let l = loader();
2591 if skip_if_no(&l, "cmake") {
2592 return;
2593 }
2594 let engine = ScopeEngine::new(&l);
2595 let src = "function(foo x y)\n set(z 1)\nendfunction()";
2596 let defs = engine.find_definitions("cmake", src, "foo");
2597 assert_eq!(defs.len(), 1, "cmake: function name foo should be defined");
2598 }
2599
2600 #[test]
2601 fn test_cmake_set_variable() {
2602 let l = loader();
2603 if skip_if_no(&l, "cmake") {
2604 return;
2605 }
2606 let engine = ScopeEngine::new(&l);
2607 let src = "set(MY_VAR hello)";
2608 let defs = engine.find_definitions("cmake", src, "MY_VAR");
2609 assert_eq!(defs.len(), 1, "cmake: set(MY_VAR ...) should define MY_VAR");
2610 }
2611
2612 #[test]
2613 fn test_cmake_foreach_variable() {
2614 let l = loader();
2615 if skip_if_no(&l, "cmake") {
2616 return;
2617 }
2618 let engine = ScopeEngine::new(&l);
2619 let src = "foreach(item IN LISTS mylist)\n message(${item})\nendforeach()";
2620 let defs = engine.find_definitions("cmake", src, "item");
2621 assert_eq!(
2622 defs.len(),
2623 1,
2624 "cmake: foreach loop variable item should be defined"
2625 );
2626 }
2627
2628 #[test]
2631 fn test_typst_let_binding() {
2632 let l = loader();
2633 if skip_if_no(&l, "typst") {
2634 return;
2635 }
2636 let engine = ScopeEngine::new(&l);
2637 let src = "#let x = 1\n#let y = x + 1";
2638 let defs = engine.find_definitions("typst", src, "x");
2639 assert_eq!(defs.len(), 1, "typst: #let x = 1 should define x");
2640 }
2641
2642 #[test]
2643 fn test_typst_function_parameter() {
2644 let l = loader();
2645 if skip_if_no(&l, "typst") {
2646 return;
2647 }
2648 let engine = ScopeEngine::new(&l);
2649 let src = "#let foo(a, b) = a + b";
2650 let defs = engine.find_definitions("typst", src, "a");
2651 assert_eq!(
2652 defs.len(),
2653 1,
2654 "typst: function parameter a should be defined"
2655 );
2656 }
2657
2658 #[test]
2659 fn test_typst_function_name() {
2660 let l = loader();
2661 if skip_if_no(&l, "typst") {
2662 return;
2663 }
2664 let engine = ScopeEngine::new(&l);
2665 let src = "#let foo(a) = a";
2666 let defs = engine.find_definitions("typst", src, "foo");
2667 assert_eq!(defs.len(), 1, "typst: function name foo should be defined");
2668 }
2669
2670 #[test]
2673 fn test_hcl_locals_attribute() {
2674 let l = loader();
2675 if skip_if_no(&l, "hcl") {
2676 return;
2677 }
2678 let engine = ScopeEngine::new(&l);
2679 let src = "locals {\n x = 1\n y = local.x + 1\n}";
2680 let defs = engine.find_definitions("hcl", src, "x");
2681 assert_eq!(defs.len(), 1, "hcl: locals block should define x");
2682 }
2683
2684 #[test]
2687 fn test_verilog_module_port() {
2688 let l = loader();
2689 if skip_if_no(&l, "verilog") {
2690 return;
2691 }
2692 let engine = ScopeEngine::new(&l);
2693 let src = "module foo (input x, output y); endmodule";
2694 let defs = engine.find_definitions("verilog", src, "x");
2695 assert_eq!(defs.len(), 1, "verilog: input port x should be defined");
2696 }
2697
2698 #[test]
2699 fn test_verilog_wire_declaration() {
2700 let l = loader();
2701 if skip_if_no(&l, "verilog") {
2702 return;
2703 }
2704 let engine = ScopeEngine::new(&l);
2705 let src = "module foo (); wire z; endmodule";
2706 let defs = engine.find_definitions("verilog", src, "z");
2707 assert_eq!(defs.len(), 1, "verilog: wire z should be defined");
2708 }
2709
2710 #[test]
2713 fn test_vhdl_signal_declaration() {
2714 let l = loader();
2715 if skip_if_no(&l, "vhdl") {
2716 return;
2717 }
2718 let engine = ScopeEngine::new(&l);
2719 let src = "architecture Foo of Bar is\n signal x : std_logic;\nbegin\nend Foo;";
2720 let defs = engine.find_definitions("vhdl", src, "x");
2721 assert_eq!(defs.len(), 1, "vhdl: signal x should be defined");
2722 }
2723
2724 #[test]
2725 fn test_vhdl_process_variable() {
2726 let l = loader();
2727 if skip_if_no(&l, "vhdl") {
2728 return;
2729 }
2730 let engine = ScopeEngine::new(&l);
2731 let src = "architecture Foo of Bar is\nbegin\nprocess\n variable y : integer;\nbegin\nend process;\nend Foo;";
2732 let defs = engine.find_definitions("vhdl", src, "y");
2733 assert_eq!(defs.len(), 1, "vhdl: process variable y should be defined");
2734 }
2735
2736 #[test]
2739 fn test_jq_function_parameter() {
2740 let l = loader();
2741 if skip_if_no(&l, "jq") {
2742 return;
2743 }
2744 let engine = ScopeEngine::new(&l);
2745 let src = "def foo(x): x + 1;";
2746 let defs = engine.find_definitions("jq", src, "x");
2747 assert_eq!(defs.len(), 1, "jq: function parameter x should be defined");
2748 }
2749
2750 #[test]
2751 fn test_jq_function_name() {
2752 let l = loader();
2753 if skip_if_no(&l, "jq") {
2754 return;
2755 }
2756 let engine = ScopeEngine::new(&l);
2757 let src = "def foo(x): x;";
2758 let defs = engine.find_definitions("jq", src, "foo");
2759 assert_eq!(defs.len(), 1, "jq: function name foo should be defined");
2760 }
2761
2762 #[test]
2763 fn test_jq_as_binding() {
2764 let l = loader();
2765 if skip_if_no(&l, "jq") {
2766 return;
2767 }
2768 let engine = ScopeEngine::new(&l);
2769 let src = "def f: . as $item | $item; f";
2770 let defs = engine.find_definitions("jq", src, "$item");
2771 assert_eq!(defs.len(), 1, "jq: as binding $item should be defined");
2772 let refs = engine.find_references("jq", src, "$item");
2773 let resolved = refs.iter().filter(|r| r.definition.is_some()).count();
2774 assert!(resolved >= 1, "jq: $item reference should resolve");
2775 }
2776
2777 #[test]
2780 fn test_meson_variable_assignment() {
2781 let l = loader();
2782 if skip_if_no(&l, "meson") {
2783 return;
2784 }
2785 let engine = ScopeEngine::new(&l);
2786 let src = "x = 1\ny = x";
2787 let defs = engine.find_definitions("meson", src, "x");
2788 assert_eq!(
2789 defs.len(),
2790 1,
2791 "meson: variable assignment x should be defined"
2792 );
2793 }
2794
2795 #[test]
2796 fn test_meson_foreach_variable() {
2797 let l = loader();
2798 if skip_if_no(&l, "meson") {
2799 return;
2800 }
2801 let engine = ScopeEngine::new(&l);
2802 let src = "foreach item : ['a', 'b']\n message(item)\nendforeach";
2803 let defs = engine.find_definitions("meson", src, "item");
2804 assert_eq!(
2805 defs.len(),
2806 1,
2807 "meson: foreach variable item should be defined"
2808 );
2809 }
2810
2811 #[test]
2818 fn scope_vb_method_param() {
2819 let l = loader();
2820 if skip_if_no(&l, "vb") {
2821 return;
2822 }
2823 let engine = ScopeEngine::new(&l);
2824 let src = "Module M\r\n Function Add(ByVal x As Integer, ByVal y As Integer) As Integer\r\n Return x + y\r\n End Function\r\nEnd Module\r\n";
2825 let defs = engine.find_definitions("vb", src, "x");
2826 assert_eq!(defs.len(), 1, "vb: x should be defined as parameter");
2827 let refs = engine.find_references("vb", src, "x");
2828 assert!(
2829 refs.iter().any(|r| r.definition.is_some()),
2830 "vb: x reference should resolve to param"
2831 );
2832 }
2833
2834 #[test]
2835 fn scope_vb_dim_statement() {
2836 let l = loader();
2837 if skip_if_no(&l, "vb") {
2838 return;
2839 }
2840 let engine = ScopeEngine::new(&l);
2841 let src = "Module M\r\n Sub Run()\r\n Dim result As Integer = 42\r\n Console.WriteLine(result)\r\n End Sub\r\nEnd Module\r\n";
2842 let defs = engine.find_definitions("vb", src, "result");
2843 assert_eq!(defs.len(), 1, "vb: result should be defined via Dim");
2844 let refs = engine.find_references("vb", src, "result");
2845 assert!(
2846 refs.iter().any(|r| r.definition.is_some()),
2847 "vb: result should resolve to Dim"
2848 );
2849 }
2850
2851 #[test]
2852 fn scope_vb_for_each() {
2853 let l = loader();
2854 if skip_if_no(&l, "vb") {
2855 return;
2856 }
2857 let engine = ScopeEngine::new(&l);
2858 let src = "Module M\r\n Sub Run(items As Object)\r\n For Each item In items\r\n Console.WriteLine(item)\r\n Next\r\n End Sub\r\nEnd Module\r\n";
2859 let defs = engine.find_definitions("vb", src, "item");
2860 assert_eq!(defs.len(), 1, "vb: item should be defined by For Each");
2861 }
2862
2863 #[test]
2866 fn scope_idris_function_param() {
2867 let l = loader();
2868 if skip_if_no(&l, "idris") {
2869 return;
2870 }
2871 let engine = ScopeEngine::new(&l);
2872 let src = "add : Int -> Int -> Int\nadd x y = x + y\n";
2873 let defs = engine.find_definitions("idris", src, "x");
2874 assert_eq!(defs.len(), 1, "idris: x should be defined as pattern param");
2875 let refs = engine.find_references("idris", src, "x");
2876 assert!(
2877 refs.iter().any(|r| r.definition.is_some()),
2878 "idris: x should resolve to param definition"
2879 );
2880 }
2881
2882 #[test]
2883 fn scope_idris_function_name() {
2884 let l = loader();
2885 if skip_if_no(&l, "idris") {
2886 return;
2887 }
2888 let engine = ScopeEngine::new(&l);
2889 let src = "double : Int -> Int\ndouble n = n + n\n";
2890 let defs = engine.find_definitions("idris", src, "n");
2891 assert_eq!(defs.len(), 1, "idris: n should be defined as pattern param");
2892 }
2893
2894 #[test]
2897 fn scope_lean_def_param() {
2898 let l = loader();
2899 if skip_if_no(&l, "lean") {
2900 return;
2901 }
2902 let engine = ScopeEngine::new(&l);
2903 let src = "def double (n : Nat) : Nat := n + n\n";
2904 let defs = engine.find_definitions("lean", src, "n");
2905 assert_eq!(
2906 defs.len(),
2907 1,
2908 "lean: n should be defined as explicit binder"
2909 );
2910 let refs = engine.find_references("lean", src, "n");
2911 assert!(
2912 refs.iter().any(|r| r.definition.is_some()),
2913 "lean: n should resolve to param"
2914 );
2915 }
2916
2917 #[test]
2918 fn scope_lean_let_binding() {
2919 let l = loader();
2920 if skip_if_no(&l, "lean") {
2921 return;
2922 }
2923 let engine = ScopeEngine::new(&l);
2924 let src = "def example : Nat :=\n let x := 5\n x + x\n";
2925 let defs = engine.find_definitions("lean", src, "x");
2926 assert_eq!(defs.len(), 1, "lean: x should be defined by let binding");
2927 let refs = engine.find_references("lean", src, "x");
2928 assert!(
2929 refs.iter().any(|r| r.definition.is_some()),
2930 "lean: x should resolve to let binding"
2931 );
2932 }
2933
2934 #[test]
2935 fn scope_lean_fun_param() {
2936 let l = loader();
2937 if skip_if_no(&l, "lean") {
2938 return;
2939 }
2940 let engine = ScopeEngine::new(&l);
2941 let src = "def apply := fun x => x + 1\n";
2942 let defs = engine.find_definitions("lean", src, "x");
2943 assert_eq!(defs.len(), 1, "lean: x should be defined as fun parameter");
2944 }
2945
2946 #[test]
2949 fn scope_agda_type_sig_def() {
2950 let l = loader();
2951 if skip_if_no(&l, "agda") {
2952 return;
2953 }
2954 let engine = ScopeEngine::new(&l);
2955 let src = "module Main where\n\ndouble : Nat -> Nat\ndouble n = n + n\n";
2956 let defs = engine.find_definitions("agda", src, "double");
2957 assert_eq!(
2958 defs.len(),
2959 1,
2960 "agda: double type sig should be captured as definition"
2961 );
2962 }
2963
2964 #[test]
2965 fn scope_agda_reference_resolves() {
2966 let l = loader();
2967 if skip_if_no(&l, "agda") {
2968 return;
2969 }
2970 let engine = ScopeEngine::new(&l);
2971 let src = "module Main where\n\ndouble : Nat -> Nat\ndouble n = n + n\n\nmain : IO ()\nmain = double 5\n";
2973 let defs = engine.find_definitions("agda", src, "double");
2974 assert_eq!(
2975 defs.len(),
2976 1,
2977 "agda: double type sig should be a definition"
2978 );
2979 let refs = engine.find_references("agda", src, "double");
2980 assert!(
2981 refs.iter().any(|r| r.definition.is_some()),
2982 "agda: double call should resolve to type sig definition"
2983 );
2984 }
2985
2986 #[test]
2989 fn scope_glsl_function_param() {
2990 let l = loader();
2991 if skip_if_no(&l, "glsl") {
2992 return;
2993 }
2994 let engine = ScopeEngine::new(&l);
2995 let src = "float computeLight(vec3 pos, float intensity) {\n float result = pos.x * intensity;\n return result;\n}\n";
2996 let defs = engine.find_definitions("glsl", src, "intensity");
2997 assert_eq!(
2998 defs.len(),
2999 1,
3000 "glsl: intensity should be defined as parameter"
3001 );
3002 let refs = engine.find_references("glsl", src, "intensity");
3003 assert!(
3004 refs.iter().any(|r| r.definition.is_some()),
3005 "glsl: intensity should resolve to param"
3006 );
3007 }
3008
3009 #[test]
3010 fn scope_glsl_local_var() {
3011 let l = loader();
3012 if skip_if_no(&l, "glsl") {
3013 return;
3014 }
3015 let engine = ScopeEngine::new(&l);
3016 let src = "void main() {\n vec3 color = vec3(1.0, 0.0, 0.0);\n gl_FragColor = vec4(color, 1.0);\n}\n";
3017 let defs = engine.find_definitions("glsl", src, "color");
3018 assert_eq!(defs.len(), 1, "glsl: color should be defined as local var");
3019 let refs = engine.find_references("glsl", src, "color");
3020 assert!(
3021 refs.iter().any(|r| r.definition.is_some()),
3022 "glsl: color should resolve to local var"
3023 );
3024 }
3025
3026 #[test]
3029 fn scope_hlsl_function_param() {
3030 let l = loader();
3031 if skip_if_no(&l, "hlsl") {
3032 return;
3033 }
3034 let engine = ScopeEngine::new(&l);
3035 let src = "float computeValue(float x, float y) {\n float result = x + y;\n return result;\n}\n";
3036 let defs = engine.find_definitions("hlsl", src, "x");
3037 assert_eq!(defs.len(), 1, "hlsl: x should be defined as parameter");
3038 let refs = engine.find_references("hlsl", src, "x");
3039 assert!(
3040 refs.iter().any(|r| r.definition.is_some()),
3041 "hlsl: x should resolve to param"
3042 );
3043 }
3044
3045 #[test]
3046 fn scope_hlsl_local_var() {
3047 let l = loader();
3048 if skip_if_no(&l, "hlsl") {
3049 return;
3050 }
3051 let engine = ScopeEngine::new(&l);
3052 let src = "float4 PSMain() : SV_Target {\n float brightness = 1.0;\n return float4(brightness, 0.0, 0.0, 1.0);\n}\n";
3053 let defs = engine.find_definitions("hlsl", src, "brightness");
3054 assert_eq!(
3055 defs.len(),
3056 1,
3057 "hlsl: brightness should be defined as local var"
3058 );
3059 let refs = engine.find_references("hlsl", src, "brightness");
3060 assert!(
3061 refs.iter().any(|r| r.definition.is_some()),
3062 "hlsl: brightness should resolve to local var"
3063 );
3064 }
3065
3066 #[test]
3069 fn scope_yuri_function_param() {
3070 let l = loader();
3071 if skip_if_no(&l, "yuri") {
3072 return;
3073 }
3074 let engine = ScopeEngine::new(&l);
3075 let src = "fn add(a: f32, b: f32) : f32 {\n let result = a\n result\n}\n";
3078 let defs = engine.find_definitions("yuri", src, "a");
3079 assert_eq!(defs.len(), 1, "yuri: a should be defined as parameter");
3080 let refs = engine.find_references("yuri", src, "a");
3081 assert!(
3082 refs.iter().any(|r| r.definition.is_some()),
3083 "yuri: a should resolve to param definition"
3084 );
3085 }
3086
3087 #[test]
3088 fn scope_yuri_let_binding() {
3089 let l = loader();
3090 if skip_if_no(&l, "yuri") {
3091 return;
3092 }
3093 let engine = ScopeEngine::new(&l);
3094 let src = "fn add(a: f32, b: f32) : f32 {\n let result = a\n result\n}\n";
3095 let defs = engine.find_definitions("yuri", src, "result");
3096 assert_eq!(
3097 defs.len(),
3098 1,
3099 "yuri: result should be defined by let binding"
3100 );
3101 let refs = engine.find_references("yuri", src, "result");
3102 assert!(
3103 refs.iter().any(|r| r.definition.is_some()),
3104 "yuri: result should resolve to let binding"
3105 );
3106 }
3107}