1use tree_sitter::{Language, Parser, Tree};
2
3use crate::core::code_graph::CodeGraph;
4use crate::core::moniker::Moniker;
5
6use crate::lang::canonical_walker::CanonicalWalker;
7
8pub mod build;
9mod canonicalize;
10mod kinds;
11mod strategy;
12
13use std::collections::HashMap;
14
15use canonicalize::compute_module_moniker;
16use strategy::{Strategy, collect_callable_table, collect_local_mods};
17
18pub fn parse(source: &str) -> Tree {
19 let mut parser = Parser::new();
20 let language: Language = tree_sitter_rust::LANGUAGE.into();
21 parser
22 .set_language(&language)
23 .expect("failed to load tree-sitter Rust grammar");
24 parser
25 .parse(source, None)
26 .expect("tree-sitter parse returned None on a non-cancelled call")
27}
28
29#[derive(Clone, Debug, Default)]
30pub struct Presets {}
31
32pub fn extract(
33 uri: &str,
34 source: &str,
35 anchor: &Moniker,
36 deep: bool,
37 _presets: &Presets,
38) -> CodeGraph {
39 let module = compute_module_moniker(anchor, uri);
40 let (def_cap, ref_cap) = CodeGraph::capacity_for_source(source.len());
41 let mut graph = CodeGraph::with_capacity(module.clone(), kinds::MODULE, def_cap, ref_cap);
42 let tree = parse(source);
43 let local_mods = collect_local_mods(tree.root_node(), source.as_bytes());
44 let mut callable_table: HashMap<(Moniker, Vec<u8>), Vec<u8>> = HashMap::new();
45 collect_callable_table(
46 tree.root_node(),
47 source.as_bytes(),
48 &module,
49 &mut callable_table,
50 );
51 let strat = Strategy {
52 module: module.clone(),
53 source_bytes: source.as_bytes(),
54 deep,
55 local_mods,
56 local_scope: std::cell::RefCell::new(Vec::new()),
57 type_params: std::cell::RefCell::new(Vec::new()),
58 callable_table,
59 };
60 let walker = CanonicalWalker::new(&strat, source.as_bytes());
61 walker.walk(tree.root_node(), &module, &mut graph);
62 graph
63}
64
65pub struct Lang;
66
67impl crate::lang::LangExtractor for Lang {
68 type Presets = Presets;
69 const LANG_TAG: &'static str = "rs";
70 const ALLOWED_KINDS: &'static [&'static str] = &[
71 "struct", "enum", "trait", "impl", "fn", "method", "const", "static", "type",
72 ];
73 const ALLOWED_VISIBILITIES: &'static [&'static str] = &["public", "private", "module"];
74
75 fn extract(
76 uri: &str,
77 source: &str,
78 anchor: &Moniker,
79 deep: bool,
80 presets: &Self::Presets,
81 ) -> CodeGraph {
82 extract(uri, source, anchor, deep, presets)
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use crate::core::moniker::{Moniker, MonikerBuilder};
90 use crate::lang::assert_conformance;
91
92 fn extract(uri: &str, source: &str, anchor: &Moniker, deep: bool) -> CodeGraph {
93 let g = super::extract(uri, source, anchor, deep, &Presets::default());
94 assert_conformance::<super::Lang>(&g, anchor);
95 g
96 }
97
98 fn make_anchor() -> Moniker {
99 MonikerBuilder::new().project(b"code-moniker").build()
100 }
101
102 fn has_parent_segment(m: &Moniker, kind: &[u8], name: &[u8]) -> bool {
103 let segments: Vec<_> = m.as_view().segments().collect();
104 segments
105 .get(segments.len().saturating_sub(2))
106 .is_some_and(|seg| seg.kind == kind && seg.name == name)
107 }
108
109 #[test]
110 fn parse_empty_returns_source_file() {
111 let tree = parse("");
112 assert_eq!(tree.root_node().kind(), "source_file");
113 }
114
115 #[test]
116 fn extract_emits_comment_def_per_comment_node() {
117 let src = "// a\n/// b\nstruct Foo;\n";
118 let g = extract("src/lib.rs", src, &make_anchor(), false);
119 let n = g.defs().filter(|d| d.kind == b"comment").count();
120 assert_eq!(n, 2);
121 }
122
123 #[test]
124 fn extract_emits_comments_inside_type_bodies() {
125 let src = r#"
126struct Foo {
127 // field comment
128 value: i32,
129}
130
131trait Bar {
132 // trait comment
133 fn bar(&self);
134}
135
136enum Baz {
137 // enum comment
138 A,
139}
140"#;
141 let g = extract("src/lib.rs", src, &make_anchor(), true);
142 let comments: Vec<_> = g.defs().filter(|d| d.kind == b"comment").collect();
143 assert_eq!(comments.len(), 3);
144 assert!(
145 comments
146 .iter()
147 .any(|d| has_parent_segment(&d.moniker, b"struct", b"Foo"))
148 );
149 assert!(
150 comments
151 .iter()
152 .any(|d| has_parent_segment(&d.moniker, b"trait", b"Bar"))
153 );
154 assert!(
155 comments
156 .iter()
157 .any(|d| has_parent_segment(&d.moniker, b"enum", b"Baz"))
158 );
159 }
160
161 #[test]
162 fn extract_empty_yields_module_only_graph() {
163 let anchor = make_anchor();
164 let g = extract("src/lib.rs", "", &anchor, false);
165 assert_eq!(g.def_count(), 1);
166 assert_eq!(g.ref_count(), 0);
167
168 let expected = MonikerBuilder::new()
169 .project(b"code-moniker")
170 .segment(b"lang", b"rs")
171 .segment(b"dir", b"src")
172 .segment(b"module", b"lib")
173 .build();
174 assert_eq!(g.root(), &expected);
175 }
176
177 #[test]
178 fn extract_struct_emits_class_def() {
179 let g = extract(
180 "util.rs",
181 "pub struct Foo { x: i32 }",
182 &make_anchor(),
183 false,
184 );
185 let foo = MonikerBuilder::new()
186 .project(b"code-moniker")
187 .segment(b"lang", b"rs")
188 .segment(b"module", b"util")
189 .segment(b"struct", b"Foo")
190 .build();
191 assert!(g.contains(&foo));
192 }
193
194 #[test]
195 fn extract_enum_emits_enum_def() {
196 let g = extract(
197 "util.rs",
198 "pub enum Color { Red, Green }",
199 &make_anchor(),
200 false,
201 );
202 let color = MonikerBuilder::new()
203 .project(b"code-moniker")
204 .segment(b"lang", b"rs")
205 .segment(b"module", b"util")
206 .segment(b"enum", b"Color")
207 .build();
208 assert!(g.contains(&color));
209 }
210
211 #[test]
212 fn extract_trait_emits_interface_def() {
213 let g = extract(
214 "util.rs",
215 "pub trait Greet { fn hi(&self); }",
216 &make_anchor(),
217 false,
218 );
219 let greet = MonikerBuilder::new()
220 .project(b"code-moniker")
221 .segment(b"lang", b"rs")
222 .segment(b"module", b"util")
223 .segment(b"trait", b"Greet")
224 .build();
225 assert!(g.contains(&greet));
226 }
227
228 #[test]
229 fn extract_type_alias_emits_type_alias_def() {
230 let g = extract("util.rs", "pub type Id = u64;", &make_anchor(), false);
231 let id = MonikerBuilder::new()
232 .project(b"code-moniker")
233 .segment(b"lang", b"rs")
234 .segment(b"module", b"util")
235 .segment(b"type", b"Id")
236 .build();
237 assert!(g.contains(&id));
238 }
239
240 #[test]
241 fn extract_top_level_fn_emits_function_def() {
242 let g = extract(
243 "util.rs",
244 "pub fn add(a: i32, b: i32) -> i32 { a + b }",
245 &make_anchor(),
246 false,
247 );
248 let add = MonikerBuilder::new()
249 .project(b"code-moniker")
250 .segment(b"lang", b"rs")
251 .segment(b"module", b"util")
252 .segment(b"fn", b"add(a:i32,b:i32)")
253 .build();
254 assert!(
255 g.contains(&add),
256 "expected {add:?}, defs: {:?}",
257 g.def_monikers()
258 );
259 }
260
261 #[test]
262 fn extract_fn_no_args_uses_arity_zero_form() {
263 let g = extract("util.rs", "pub fn boot() {}", &make_anchor(), false);
264 let boot = MonikerBuilder::new()
265 .project(b"code-moniker")
266 .segment(b"lang", b"rs")
267 .segment(b"module", b"util")
268 .segment(b"fn", b"boot()")
269 .build();
270 assert!(g.contains(&boot));
271 }
272
273 #[test]
274 fn extract_impl_block_reparents_methods_to_type() {
275 let src = r#"
276 pub struct Foo;
277 impl Foo {
278 pub fn bar(&self) -> i32 { 0 }
279 }
280 "#;
281 let g = extract("util.rs", src, &make_anchor(), false);
282 let foo = MonikerBuilder::new()
283 .project(b"code-moniker")
284 .segment(b"lang", b"rs")
285 .segment(b"module", b"util")
286 .segment(b"struct", b"Foo")
287 .build();
288 let bar = MonikerBuilder::new()
289 .project(b"code-moniker")
290 .segment(b"lang", b"rs")
291 .segment(b"module", b"util")
292 .segment(b"struct", b"Foo")
293 .segment(b"method", b"bar()")
294 .build();
295 assert!(g.contains(&foo));
296 assert!(
297 g.contains(&bar),
298 "expected {bar:?}, defs: {:?}",
299 g.def_monikers()
300 );
301 }
302
303 #[test]
304 fn extract_impl_trait_for_emits_implements_ref() {
305 let src = r#"
306 pub trait Greet { fn hi(&self); }
307 pub struct Foo;
308 impl Greet for Foo {
309 fn hi(&self) {}
310 }
311 "#;
312 let g = extract("util.rs", src, &make_anchor(), false);
313 let foo = MonikerBuilder::new()
314 .project(b"code-moniker")
315 .segment(b"lang", b"rs")
316 .segment(b"module", b"util")
317 .segment(b"struct", b"Foo")
318 .build();
319 let greet = MonikerBuilder::new()
320 .project(b"code-moniker")
321 .segment(b"lang", b"rs")
322 .segment(b"module", b"util")
323 .segment(b"trait", b"Greet")
324 .build();
325 let r = g
326 .refs()
327 .find(|r| r.kind == b"implements".to_vec())
328 .expect("implements ref");
329 assert_eq!(g.defs().nth(r.source).unwrap().moniker, foo);
330 assert_eq!(r.target, greet);
331 }
332
333 #[test]
334 fn extract_impl_trait_for_external_type_keeps_methods_and_ref() {
335 let src = r#"
336 use alloc::collections::VecDeque;
337 pub trait Buf { fn remaining(&self) -> usize; }
338 impl Buf for VecDeque<u8> {
339 fn remaining(&self) -> usize { 0 }
340 fn chunk(&self) -> &[u8] { &[] }
341 }
342 "#;
343 let g = extract("util.rs", src, &make_anchor(), false);
344 let vec_deque = MonikerBuilder::new()
345 .project(b"code-moniker")
346 .segment(b"lang", b"rs")
347 .segment(b"module", b"util")
348 .segment(b"struct", b"VecDeque")
349 .build();
350 let remaining = MonikerBuilder::new()
351 .project(b"code-moniker")
352 .segment(b"lang", b"rs")
353 .segment(b"module", b"util")
354 .segment(b"struct", b"VecDeque")
355 .segment(b"method", b"remaining()")
356 .build();
357 assert!(
358 g.contains(&vec_deque),
359 "VecDeque must be synthesized as a placeholder struct so its methods can land. defs: {:?}",
360 g.def_monikers()
361 );
362 assert!(
363 g.contains(&remaining),
364 "method on impl-for-external-type must be captured. defs: {:?}",
365 g.def_monikers()
366 );
367 assert!(
368 g.refs().any(|r| r.kind == b"implements".to_vec()
369 && r.target.as_view().segments().last().unwrap().name == b"Buf"),
370 "impl-for-external must still emit the implements ref"
371 );
372 }
373
374 #[test]
375 fn extract_use_bare_ident_is_external() {
376 let g = extract("util.rs", "use foo;", &make_anchor(), false);
377 assert_eq!(g.ref_count(), 1);
378 let r = g.refs().next().unwrap();
379 assert_eq!(r.kind, b"imports_symbol".to_vec());
380 let target = MonikerBuilder::new()
381 .project(b"code-moniker")
382 .segment(b"external_pkg", b"foo")
383 .build();
384 assert_eq!(r.target, target);
385 }
386
387 #[test]
388 fn extract_use_external_crate_marks_external_pkg() {
389 let g = extract(
390 "util.rs",
391 "use std::collections::HashMap;",
392 &make_anchor(),
393 false,
394 );
395 let r = g.refs().next().unwrap();
396 let target = MonikerBuilder::new()
397 .project(b"code-moniker")
398 .segment(b"external_pkg", b"std")
399 .segment(b"path", b"collections")
400 .segment(b"path", b"HashMap")
401 .build();
402 assert_eq!(r.target, target);
403 }
404
405 #[test]
406 fn extract_use_crate_prefix_resolves_project_local() {
407 let g = extract(
408 "util.rs",
409 "use crate::core::moniker::Moniker;",
410 &make_anchor(),
411 false,
412 );
413 let r = g.refs().next().unwrap();
414 let target = MonikerBuilder::new()
415 .project(b"code-moniker")
416 .segment(b"lang", b"rs")
417 .segment(b"dir", b"core")
418 .segment(b"module", b"moniker")
419 .segment(b"path", b"Moniker")
420 .build();
421 assert_eq!(r.target, target);
422 }
423
424 #[test]
425 fn extract_use_super_walks_up_one_segment() {
426 let anchor = MonikerBuilder::new()
427 .project(b"code-moniker")
428 .segment(b"path", b"src")
429 .segment(b"path", b"lang")
430 .build();
431 let g = extract("rs/walker.rs", "use super::kinds;", &anchor, false);
432 let r = g.refs().next().unwrap();
433 let target = MonikerBuilder::new()
434 .project(b"code-moniker")
435 .segment(b"path", b"src")
436 .segment(b"path", b"lang")
437 .segment(b"lang", b"rs")
438 .segment(b"dir", b"rs")
439 .segment(b"path", b"kinds")
440 .build();
441 assert_eq!(r.target, target);
442 }
443
444 #[test]
445 fn extract_use_local_mod_resolves_as_self() {
446 let src = r#"
447 mod canonicalize;
448 use canonicalize::compute_module_moniker;
449 "#;
450 let g = extract("util.rs", src, &make_anchor(), false);
451 let r = g.refs().next().unwrap();
452 let target = MonikerBuilder::new()
453 .project(b"code-moniker")
454 .segment(b"lang", b"rs")
455 .segment(b"module", b"util")
456 .segment(b"module", b"canonicalize")
457 .segment(b"path", b"compute_module_moniker")
458 .build();
459 assert_eq!(
460 r.target, target,
461 "bare path matching a local mod must resolve project-local"
462 );
463 }
464
465 #[test]
466 fn extract_use_unknown_first_segment_stays_external() {
467 let g = extract("util.rs", "use foo::bar;", &make_anchor(), false);
468 let target = MonikerBuilder::new()
469 .project(b"code-moniker")
470 .segment(b"external_pkg", b"foo")
471 .segment(b"path", b"bar")
472 .build();
473 assert_eq!(g.refs().next().unwrap().target, target);
474 }
475
476 #[test]
477 fn extract_use_self_keeps_module_prefix() {
478 let g = extract("util.rs", "use self::kinds::PATH;", &make_anchor(), false);
479 let r = g.refs().next().unwrap();
480 let target = MonikerBuilder::new()
481 .project(b"code-moniker")
482 .segment(b"lang", b"rs")
483 .segment(b"module", b"util")
484 .segment(b"module", b"kinds")
485 .segment(b"path", b"PATH")
486 .build();
487 assert_eq!(r.target, target);
488 }
489
490 #[test]
491 fn extract_use_list_emits_one_ref_per_leaf() {
492 let g = extract(
493 "util.rs",
494 "use std::collections::{HashMap, HashSet};",
495 &make_anchor(),
496 false,
497 );
498 let imports_symbol: Vec<_> = g.refs().filter(|r| r.kind == b"imports_symbol").collect();
499 assert_eq!(imports_symbol.len(), 2);
500 let targets: Vec<_> = imports_symbol.iter().map(|r| r.target.clone()).collect();
501 let hashmap = MonikerBuilder::new()
502 .project(b"code-moniker")
503 .segment(b"external_pkg", b"std")
504 .segment(b"path", b"collections")
505 .segment(b"path", b"HashMap")
506 .build();
507 let hashset = MonikerBuilder::new()
508 .project(b"code-moniker")
509 .segment(b"external_pkg", b"std")
510 .segment(b"path", b"collections")
511 .segment(b"path", b"HashSet")
512 .build();
513 assert!(targets.contains(&hashmap));
514 assert!(targets.contains(&hashset));
515 }
516
517 #[test]
518 fn extract_use_wildcard_splits_scoped_path() {
519 let g = extract("util.rs", "use pgrx::prelude::*;", &make_anchor(), false);
520 let imports_symbol: Vec<_> = g.refs().filter(|r| r.kind == b"imports_symbol").collect();
521 assert_eq!(imports_symbol.len(), 1);
522 let target = MonikerBuilder::new()
523 .project(b"code-moniker")
524 .segment(b"external_pkg", b"pgrx")
525 .segment(b"path", b"prelude")
526 .build();
527 assert_eq!(
528 imports_symbol[0].target, target,
529 "wildcard parent path must split on :: AND mark crate root as external"
530 );
531 }
532
533 #[test]
534 fn extract_use_alias_drops_alias_keeps_path() {
535 let g = extract(
536 "util.rs",
537 "use std::io::Result as IoResult;",
538 &make_anchor(),
539 false,
540 );
541 let imports_symbol: Vec<_> = g.refs().filter(|r| r.kind == b"imports_symbol").collect();
542 assert_eq!(imports_symbol.len(), 1);
543 let r = &imports_symbol[0];
544 let target = MonikerBuilder::new()
545 .project(b"code-moniker")
546 .segment(b"external_pkg", b"std")
547 .segment(b"path", b"io")
548 .segment(b"path", b"Result")
549 .build();
550 assert_eq!(r.target, target);
551 }
552
553 #[test]
554 fn extract_shallow_skips_param_and_local() {
555 let src = "pub fn add(a: i32, b: i32) -> i32 { let sum = a + b; sum }";
556 let g = extract("util.rs", src, &make_anchor(), false);
557 assert!(
558 g.defs().all(|d| d.kind != b"param" && d.kind != b"local"),
559 "shallow extraction must not produce param/local defs"
560 );
561 }
562
563 #[test]
564 fn extract_deep_emits_params_under_function() {
565 let src = "pub fn add(a: i32, b: i32) -> i32 { a + b }";
566 let g = extract("util.rs", src, &make_anchor(), true);
567 let add = MonikerBuilder::new()
568 .project(b"code-moniker")
569 .segment(b"lang", b"rs")
570 .segment(b"module", b"util")
571 .segment(b"fn", b"add(a:i32,b:i32)")
572 .build();
573 let pa = MonikerBuilder::new()
574 .project(b"code-moniker")
575 .segment(b"lang", b"rs")
576 .segment(b"module", b"util")
577 .segment(b"fn", b"add(a:i32,b:i32)")
578 .segment(b"param", b"a")
579 .build();
580 let pb = MonikerBuilder::new()
581 .project(b"code-moniker")
582 .segment(b"lang", b"rs")
583 .segment(b"module", b"util")
584 .segment(b"fn", b"add(a:i32,b:i32)")
585 .segment(b"param", b"b")
586 .build();
587 assert!(g.contains(&add));
588 assert!(
589 g.contains(&pa),
590 "missing param:a, defs: {:?}",
591 g.def_monikers()
592 );
593 assert!(g.contains(&pb));
594 }
595
596 #[test]
597 fn extract_deep_self_parameter_named_self() {
598 let src = "pub struct Foo; impl Foo { fn bar(&self, x: i32) {} }";
599 let g = extract("util.rs", src, &make_anchor(), true);
600 let bar_self = MonikerBuilder::new()
601 .project(b"code-moniker")
602 .segment(b"lang", b"rs")
603 .segment(b"module", b"util")
604 .segment(b"struct", b"Foo")
605 .segment(b"method", b"bar(x:i32)")
606 .segment(b"param", b"self")
607 .build();
608 let bar_x = MonikerBuilder::new()
609 .project(b"code-moniker")
610 .segment(b"lang", b"rs")
611 .segment(b"module", b"util")
612 .segment(b"struct", b"Foo")
613 .segment(b"method", b"bar(x:i32)")
614 .segment(b"param", b"x")
615 .build();
616 assert!(g.contains(&bar_self));
617 assert!(g.contains(&bar_x));
618 }
619
620 #[test]
621 fn extract_deep_emits_locals_under_function() {
622 let src = r#"pub fn run() {
623 let x = 1;
624 let y = 2;
625 }"#;
626 let g = extract("util.rs", src, &make_anchor(), true);
627 let lx = MonikerBuilder::new()
628 .project(b"code-moniker")
629 .segment(b"lang", b"rs")
630 .segment(b"module", b"util")
631 .segment(b"fn", b"run()")
632 .segment(b"local", b"x")
633 .build();
634 let ly = MonikerBuilder::new()
635 .project(b"code-moniker")
636 .segment(b"lang", b"rs")
637 .segment(b"module", b"util")
638 .segment(b"fn", b"run()")
639 .segment(b"local", b"y")
640 .build();
641 assert!(g.contains(&lx));
642 assert!(g.contains(&ly));
643 }
644
645 #[test]
646 fn extract_deep_locals_in_nested_block_attach_to_function() {
647 let src = r#"pub fn run(flag: bool) {
648 if flag { let inner = 1; }
649 }"#;
650 let g = extract("util.rs", src, &make_anchor(), true);
651 let inner = MonikerBuilder::new()
652 .project(b"code-moniker")
653 .segment(b"lang", b"rs")
654 .segment(b"module", b"util")
655 .segment(b"fn", b"run(flag:bool)")
656 .segment(b"local", b"inner")
657 .build();
658 assert!(
659 g.contains(&inner),
660 "local inside `if` block should attach to the function, not the block; defs: {:?}",
661 g.def_monikers()
662 );
663 }
664
665 #[test]
666 fn extract_deep_named_closure_emits_function_def() {
667 let src = "pub fn run() { let f = |x| x + 1; }";
668 let g = extract("util.rs", src, &make_anchor(), true);
669 let f = MonikerBuilder::new()
670 .project(b"code-moniker")
671 .segment(b"lang", b"rs")
672 .segment(b"module", b"util")
673 .segment(b"fn", b"run()")
674 .segment(b"fn", b"f(x)")
675 .build();
676 assert!(
677 g.contains(&f),
678 "expected {f:?}, defs: {:?}",
679 g.def_monikers()
680 );
681 }
682
683 #[test]
684 fn extract_deep_skips_underscore_pattern() {
685 let src = "pub fn run(_: i32) { let _ = 1; }";
686 let g = extract("util.rs", src, &make_anchor(), true);
687 assert!(
688 g.defs().all(|d| d.kind != b"param" && d.kind != b"local"),
689 "`_` patterns must not produce defs; got: {:?}",
690 g.def_monikers()
691 );
692 }
693
694 #[test]
695 fn extract_deep_comment_inside_match_arm_emits_def() {
696 let src = r#"pub fn run() {
697 match Some(1) {
698 Some(_) => {}
699 // inside-arm
700 None => {}
701 }
702 }"#;
703 let g = extract("util.rs", src, &make_anchor(), true);
704 assert!(
705 g.defs().any(|d| d.kind == b"comment"),
706 "comment between match arms must emit a comment def; defs: {:?}",
707 g.def_monikers()
708 );
709 }
710
711 #[test]
712 fn extract_deep_comment_inside_let_value_expression_emits_def() {
713 let src = r#"pub fn run() {
714 let _value = if true {
715 1
716 } else {
717 match Some(2) {
718 Some(_) => 3,
719 // hidden-in-let-value
720 None => 4,
721 }
722 };
723 }"#;
724 let g = extract("util.rs", src, &make_anchor(), true);
725 assert!(
726 g.defs().any(|d| d.kind == b"comment"),
727 "comment nested in the value of a let must emit a comment def; defs: {:?}",
728 g.def_monikers()
729 );
730 }
731
732 #[test]
733 fn extract_deep_comment_inside_call_closure_emits_def() {
734 let src = r#"pub fn run() {
735 let _ = (0..1).map(|x| {
736 // hidden-in-closure-arg
737 x + 1
738 });
739 }"#;
740 let g = extract("util.rs", src, &make_anchor(), true);
741 assert!(
742 g.defs().any(|d| d.kind == b"comment"),
743 "comment inside a closure passed as a call argument must emit a comment def; defs: {:?}",
744 g.def_monikers()
745 );
746 }
747
748 #[test]
749 fn extract_deep_local_inside_let_value_emits_def() {
750 let src = r#"pub fn run() {
751 let _v = if true {
752 let inner = 7;
753 inner
754 } else {
755 0
756 };
757 }"#;
758 let g = extract("util.rs", src, &make_anchor(), true);
759 let inner = MonikerBuilder::new()
760 .project(b"code-moniker")
761 .segment(b"lang", b"rs")
762 .segment(b"module", b"util")
763 .segment(b"fn", b"run()")
764 .segment(b"local", b"inner")
765 .build();
766 assert!(
767 g.contains(&inner),
768 "local inside a let-value expression must attach to the function; defs: {:?}",
769 g.def_monikers()
770 );
771 }
772
773 #[test]
774 fn extract_self_dot_method_emits_method_call_ref() {
775 let src = r#"
776pub struct W;
777impl W {
778 fn dispatch(&self) { self.walk(); }
779 fn walk(&self) {}
780}
781"#;
782 let g = extract("util.rs", src, &make_anchor(), true);
783 let refs: Vec<_> = g.refs().filter(|r| r.kind == b"method_call").collect();
784 assert_eq!(
785 refs.len(),
786 1,
787 "expected one method_call ref; refs: {:?}",
788 refs
789 );
790 let target = &refs[0].target;
791 let last = target.as_view().segments().last().unwrap();
792 assert_eq!(last.kind, b"method");
793 let bare = crate::core::moniker::query::bare_callable_name(last.name);
794 assert_eq!(
795 bare,
796 b"walk",
797 "method_call target must point at `walk`; got name={:?}",
798 std::str::from_utf8(last.name)
799 );
800 let source_def = g.def_at(refs[0].source);
801 let source_last = source_def.moniker.as_view().segments().last().unwrap();
802 let source_bare = crate::core::moniker::query::bare_callable_name(source_last.name);
803 assert_eq!(
804 source_bare,
805 b"dispatch",
806 "method_call source must be `dispatch`; got name={:?}",
807 std::str::from_utf8(source_last.name)
808 );
809 }
810
811 #[test]
812 fn extract_non_self_method_call_emits_method_call_ref() {
813 let src = r#"
814pub struct W;
815impl W {
816 fn run(&self, other: W) { other.walk(); }
817 fn walk(&self) {}
818}
819"#;
820 let g = extract("util.rs", src, &make_anchor(), true);
821 let n = g.refs().filter(|r| r.kind == b"method_call").count();
822 assert!(
823 n >= 1,
824 "non-self receiver must emit method_call with arity-only target; refs: {:?}",
825 g.refs().collect::<Vec<_>>()
826 );
827 }
828
829 #[test]
830 fn extract_nested_self_call_emits_two_method_call_refs() {
831 let src = r#"
832pub struct W;
833impl W {
834 fn outer(&self) { self.foo(self.bar()); }
835 fn foo(&self, _: u8) {}
836 fn bar(&self) -> u8 { 0 }
837}
838"#;
839 let g = extract("util.rs", src, &make_anchor(), true);
840 let n = g.refs().filter(|r| r.kind == b"method_call").count();
841 assert_eq!(
842 n,
843 2,
844 "nested self.foo(self.bar()) must emit two method_call refs; refs: {:?}",
845 g.refs().collect::<Vec<_>>()
846 );
847 }
848
849 #[test]
850 fn extract_use_emits_imports_module_to_parent() {
851 let src = "use crate::foo::bar::Baz;";
852 let g = extract("lib.rs", src, &make_anchor(), false);
853 let ims: Vec<_> = g.refs().filter(|r| r.kind == b"imports_module").collect();
854 assert!(
855 !ims.is_empty(),
856 "use must emit imports_module; refs: {:?}",
857 g.refs().collect::<Vec<_>>()
858 );
859 let last = ims[0].target.as_view().segments().last().unwrap();
860 assert_ne!(
861 last.kind,
862 b"path",
863 "imports_module target must point at a module, not at the leaf path:Baz; last={:?}",
864 std::str::from_utf8(last.kind)
865 );
866 }
867
868 #[test]
869 fn extract_use_external_emits_imports_module() {
870 let src = "use std::collections::HashMap;";
871 let g = extract("lib.rs", src, &make_anchor(), false);
872 let n = g.refs().filter(|r| r.kind == b"imports_module").count();
873 assert!(
874 n >= 1,
875 "extern use must emit imports_module; refs: {:?}",
876 g.refs().collect::<Vec<_>>()
877 );
878 }
879
880 #[test]
881 fn extract_use_single_segment_skips_imports_module() {
882 let src = "use foo;";
883 let g = extract("lib.rs", src, &make_anchor(), false);
884 let n = g.refs().filter(|r| r.kind == b"imports_module").count();
885 assert_eq!(n, 0, "single-segment use has no parent module to point at");
886 }
887
888 #[test]
889 fn extract_free_function_call_emits_calls_ref() {
890 let src = "pub fn run() { foo(); }";
891 let g = extract("util.rs", src, &make_anchor(), true);
892 let n = g.refs().filter(|r| r.kind == b"calls").count();
893 assert!(
894 n >= 1,
895 "free fn call must emit calls ref; refs: {:?}",
896 g.refs().collect::<Vec<_>>()
897 );
898 }
899
900 #[test]
901 fn extract_path_qualified_call_emits_calls_ref() {
902 let src = "pub fn run() { ::foo::bar::baz(); }";
903 let g = extract("util.rs", src, &make_anchor(), true);
904 let n = g.refs().filter(|r| r.kind == b"calls").count();
905 assert!(
906 n >= 1,
907 "path-qualified call must emit calls ref; refs: {:?}",
908 g.refs().collect::<Vec<_>>()
909 );
910 }
911
912 #[test]
913 fn extract_param_type_emits_uses_type_ref() {
914 let src = "pub fn run(x: SomeType) {}";
915 let g = extract("util.rs", src, &make_anchor(), false);
916 let n = g.refs().filter(|r| r.kind == b"uses_type").count();
917 assert!(
918 n >= 1,
919 "param type annotation must emit uses_type; refs: {:?}",
920 g.refs().collect::<Vec<_>>()
921 );
922 }
923
924 #[test]
925 fn extract_return_type_emits_uses_type_ref() {
926 let src = "pub fn run() -> SomeType { todo!() }";
927 let g = extract("util.rs", src, &make_anchor(), false);
928 let n = g.refs().filter(|r| r.kind == b"uses_type").count();
929 assert!(
930 n >= 1,
931 "return type must emit uses_type; refs: {:?}",
932 g.refs().collect::<Vec<_>>()
933 );
934 }
935
936 #[test]
937 fn extract_let_type_emits_uses_type_ref() {
938 let src = "pub fn run() { let x: SomeType = todo!(); }";
939 let g = extract("util.rs", src, &make_anchor(), true);
940 let n = g.refs().filter(|r| r.kind == b"uses_type").count();
941 assert!(
942 n >= 1,
943 "typed let binding must emit uses_type; refs: {:?}",
944 g.refs().collect::<Vec<_>>()
945 );
946 }
947
948 #[test]
949 fn extract_struct_field_type_emits_uses_type_ref() {
950 let src = "pub struct Foo { pub value: SomeType }";
951 let g = extract("util.rs", src, &make_anchor(), false);
952 let n = g.refs().filter(|r| r.kind == b"uses_type").count();
953 assert!(
954 n >= 1,
955 "struct field type must emit uses_type; refs: {:?}",
956 g.refs().collect::<Vec<_>>()
957 );
958 }
959
960 #[test]
961 fn extract_struct_literal_emits_instantiates_ref() {
962 let src = "pub fn run() { let _ = Foo { x: 1 }; }";
963 let g = extract("util.rs", src, &make_anchor(), true);
964 let n = g.refs().filter(|r| r.kind == b"instantiates").count();
965 assert!(
966 n >= 1,
967 "struct literal must emit instantiates; refs: {:?}",
968 g.refs().collect::<Vec<_>>()
969 );
970 }
971
972 #[test]
973 fn extract_path_constructor_emits_instantiates_ref() {
974 let src = "pub fn run() { let _ = Foo::new(); }";
975 let g = extract("util.rs", src, &make_anchor(), true);
976 let n = g.refs().filter(|r| r.kind == b"instantiates").count();
977 assert!(
978 n >= 1,
979 "Foo::new() must emit instantiates; refs: {:?}",
980 g.refs().collect::<Vec<_>>()
981 );
982 }
983
984 #[test]
985 fn extract_comprehensive_fixture_covers_all_expected_ref_kinds() {
986 let src = r#"
987use std::collections::HashMap;
988use crate::foo::Bar;
989
990pub trait Greet { fn hi(&self); }
991
992pub struct Service { backing: HashMap<String, Bar> }
993
994impl Greet for Service {
995 fn hi(&self) {
996 let _ = Service { backing: HashMap::new() };
997 let other: Service = Service::new();
998 other.hi();
999 self.hi();
1000 helper();
1001 }
1002}
1003
1004pub fn helper() {}
1005"#;
1006 let g = extract("util.rs", src, &make_anchor(), true);
1007 let kinds: std::collections::HashSet<Vec<u8>> = g.refs().map(|r| r.kind.clone()).collect();
1008 let expected: &[&[u8]] = &[
1009 b"imports_module",
1010 b"imports_symbol",
1011 b"calls",
1012 b"method_call",
1013 b"uses_type",
1014 b"instantiates",
1015 b"implements",
1016 ];
1017 let missing: Vec<&str> = expected
1018 .iter()
1019 .filter(|k| !kinds.contains(*k as &[u8]))
1020 .map(|k| std::str::from_utf8(k).unwrap())
1021 .collect();
1022 assert!(
1023 missing.is_empty(),
1024 "missing ref kinds in comprehensive fixture: {:?}; got: {:?}",
1025 missing,
1026 kinds
1027 .iter()
1028 .map(|k| std::str::from_utf8(k).unwrap_or("?"))
1029 .collect::<Vec<_>>()
1030 );
1031 }
1032
1033 #[test]
1034 fn extract_method_chain_emits_method_call_per_link() {
1035 let src = r#"
1036pub struct W;
1037impl W {
1038 fn outer(&self) { self.foo().bar(); }
1039 fn foo(&self) -> Self { W }
1040 fn bar(&self) {}
1041}
1042"#;
1043 let g = extract("util.rs", src, &make_anchor(), true);
1044 let n = g.refs().filter(|r| r.kind == b"method_call").count();
1045 assert_eq!(
1046 n,
1047 2,
1048 "method chain self.foo().bar() must emit one method_call per link; refs: {:?}",
1049 g.refs().collect::<Vec<_>>()
1050 );
1051 }
1052
1053 #[test]
1054 fn extract_enum_variant_construction_emits_instantiates() {
1055 let src = r#"
1056pub fn run() { let _ = Color::Red(1); }
1057"#;
1058 let g = extract("util.rs", src, &make_anchor(), true);
1059 let n = g
1060 .refs()
1061 .filter(|r| {
1062 r.kind == b"instantiates"
1063 && r.target
1064 .as_view()
1065 .segments()
1066 .last()
1067 .is_some_and(|s| s.kind == b"enum" && s.name == b"Color")
1068 })
1069 .count();
1070 assert_eq!(
1071 n,
1072 1,
1073 "Type::Variant(args) must emit instantiates → enum:Type; refs: {:?}",
1074 g.refs().collect::<Vec<_>>()
1075 );
1076 }
1077
1078 #[test]
1079 fn extract_tuple_struct_construction_emits_instantiates() {
1080 let src = "pub fn run() { let _ = Foo(1, 2); }";
1081 let g = extract("util.rs", src, &make_anchor(), true);
1082 let n = g
1083 .refs()
1084 .filter(|r| {
1085 r.kind == b"instantiates"
1086 && r.target
1087 .as_view()
1088 .segments()
1089 .last()
1090 .is_some_and(|s| s.kind == b"struct" && s.name == b"Foo")
1091 })
1092 .count();
1093 assert_eq!(
1094 n,
1095 1,
1096 "CamelCase identifier call Foo(...) must emit instantiates → struct:Foo; refs: {:?}",
1097 g.refs().collect::<Vec<_>>()
1098 );
1099 let mistaken = g
1100 .refs()
1101 .filter(|r| r.kind == b"calls")
1102 .any(|r| r.target.as_view().segments().last().unwrap().name == b"Foo(2)");
1103 assert!(
1104 !mistaken,
1105 "Foo(...) must NOT emit calls → fn:Foo; refs: {:?}",
1106 g.refs().collect::<Vec<_>>()
1107 );
1108 }
1109
1110 #[test]
1111 fn extract_macro_invocation_emits_calls_ref() {
1112 let src = "pub fn run() { vec![1, 2]; format!(\"{}\", 1); }";
1113 let g = extract("util.rs", src, &make_anchor(), true);
1114 let names: Vec<_> = g
1115 .refs()
1116 .filter(|r| r.kind == b"calls")
1117 .map(|r| {
1118 r.target
1119 .as_view()
1120 .segments()
1121 .last()
1122 .map(|s| s.name.to_vec())
1123 .unwrap_or_default()
1124 })
1125 .collect();
1126 assert!(
1127 names.iter().any(|n| n.starts_with(b"vec")),
1128 "vec! must emit calls; refs: {:?}",
1129 g.refs().collect::<Vec<_>>()
1130 );
1131 assert!(
1132 names.iter().any(|n| n.starts_with(b"format")),
1133 "format! must emit calls; refs: {:?}",
1134 g.refs().collect::<Vec<_>>()
1135 );
1136 }
1137
1138 #[test]
1139 fn extract_primitive_types_emit_no_uses_type_ref() {
1140 let src = "pub fn run(x: i32, y: bool, z: String) -> u8 { let _: f64 = 0.0; 0 }";
1141 let g = extract("util.rs", src, &make_anchor(), true);
1142 let primitives: &[&[u8]] = &[b"i32", b"u8", b"bool", b"f64", b"String", b"str"];
1143 let leaked: Vec<_> = g
1144 .refs()
1145 .filter(|r| r.kind == b"uses_type")
1146 .filter(|r| {
1147 let name = r.target.as_view().segments().last().unwrap().name;
1148 primitives.contains(&name)
1149 })
1150 .collect();
1151 assert!(
1152 leaked.is_empty(),
1153 "primitive types must NOT emit uses_type; leaked: {:?}",
1154 leaked
1155 );
1156 }
1157
1158 #[test]
1159 fn extract_generic_type_param_emits_no_uses_type_ref() {
1160 let src = "pub fn run<T>(x: T) -> T { x }";
1161 let g = extract("util.rs", src, &make_anchor(), true);
1162 let leaked = g
1163 .refs()
1164 .filter(|r| r.kind == b"uses_type")
1165 .any(|r| r.target.as_view().segments().last().unwrap().name == b"T");
1166 assert!(
1167 !leaked,
1168 "generic type param T must NOT emit uses_type; refs: {:?}",
1169 g.refs().collect::<Vec<_>>()
1170 );
1171 }
1172
1173 #[test]
1174 fn extract_closure_bound_call_targets_local_def() {
1175 let src = "pub fn run() { let f = |x| x + 1; f(2); }";
1176 let g = extract("util.rs", src, &make_anchor(), true);
1177 let local_call = g
1178 .refs()
1179 .filter(|r| r.kind == b"calls")
1180 .find(|r| r.target.as_view().segments().last().unwrap().name == b"f");
1181 assert!(
1182 local_call.is_some(),
1183 "call to closure-bound name `f` must target the local closure def; refs: {:?}",
1184 g.refs().collect::<Vec<_>>()
1185 );
1186 let r = local_call.unwrap();
1187 assert_eq!(
1188 r.confidence,
1189 b"local",
1190 "closure-bound call confidence must be `local`, got {:?}",
1191 std::str::from_utf8(&r.confidence)
1192 );
1193 }
1194
1195 #[test]
1196 fn extract_scoped_variant_in_value_position_emits_reads() {
1197 let src = "pub fn run() { let _ = Color::Red; }";
1198 let g = extract("util.rs", src, &make_anchor(), true);
1199 let n = g
1200 .refs()
1201 .filter(|r| r.kind == b"reads")
1202 .any(|r| r.target.as_view().segments().last().unwrap().name == b"Red");
1203 assert!(
1204 n,
1205 "Color::Red in value position must emit reads → variant; refs: {:?}",
1206 g.refs().collect::<Vec<_>>()
1207 );
1208 }
1209
1210 #[test]
1211 fn extract_trait_supertype_emits_extends_ref() {
1212 let src = "pub trait Foo: Bar + Baz {}";
1213 let g = extract("util.rs", src, &make_anchor(), false);
1214 let extends: Vec<_> = g.refs().filter(|r| r.kind == b"extends").collect();
1215 assert_eq!(
1216 extends.len(),
1217 2,
1218 "trait Foo: Bar + Baz must emit two extends refs; refs: {:?}",
1219 g.refs().collect::<Vec<_>>()
1220 );
1221 }
1222
1223 #[test]
1224 fn extract_derive_attribute_emits_annotates_ref() {
1225 let src = "#[derive(Clone, Debug)] pub struct Foo;";
1226 let g = extract("util.rs", src, &make_anchor(), false);
1227 let n = g.refs().filter(|r| r.kind == b"annotates").count();
1228 assert!(
1229 n >= 2,
1230 "#[derive(Clone, Debug)] must emit at least 2 annotates refs (one per trait); refs: {:?}",
1231 g.refs().collect::<Vec<_>>()
1232 );
1233 }
1234
1235 #[test]
1236 fn extract_local_var_reference_emits_reads_ref() {
1237 let src = "pub fn run() { let x = 1; foo(x); }";
1238 let g = extract("util.rs", src, &make_anchor(), true);
1239 let reads_x = g
1240 .refs()
1241 .filter(|r| r.kind == b"reads")
1242 .any(|r| r.target.as_view().segments().last().unwrap().name == b"x");
1243 assert!(
1244 reads_x,
1245 "local variable read `x` must emit reads → local:x; refs: {:?}",
1246 g.refs().collect::<Vec<_>>()
1247 );
1248 }
1249}