1use std::cell::RefCell;
2use std::collections::HashMap;
3
4use tree_sitter::{Language, Parser, Tree};
5
6use crate::core::code_graph::CodeGraph;
7use crate::core::moniker::Moniker;
8
9use crate::lang::canonical_walker::CanonicalWalker;
10
11pub mod build;
12mod canonicalize;
13mod kinds;
14mod strategy;
15
16use canonicalize::compute_module_moniker;
17use strategy::{Strategy, collect_callable_table, collect_type_table};
18
19#[derive(Clone, Debug, Default)]
20pub struct Presets {}
21
22pub fn parse(source: &str) -> Tree {
23 let mut parser = Parser::new();
24 let language: Language = tree_sitter_c_sharp::LANGUAGE.into();
25 parser
26 .set_language(&language)
27 .expect("failed to load tree-sitter C# grammar");
28 parser
29 .parse(source, None)
30 .expect("tree-sitter parse returned None on a non-cancelled call")
31}
32
33pub fn extract(
34 uri: &str,
35 source: &str,
36 anchor: &Moniker,
37 deep: bool,
38 _presets: &Presets,
39) -> CodeGraph {
40 let tree = parse(source);
41 let module = compute_module_moniker(anchor, uri);
42 let (def_cap, ref_cap) = CodeGraph::capacity_for_source(source.len());
43 let mut graph = CodeGraph::with_capacity(module.clone(), kinds::MODULE, def_cap, ref_cap);
44 let mut type_table: HashMap<&[u8], Moniker> = HashMap::new();
45 collect_type_table(
46 tree.root_node(),
47 source.as_bytes(),
48 &module,
49 &mut type_table,
50 );
51 let mut callable_table: HashMap<(Moniker, Vec<u8>), Vec<u8>> = HashMap::new();
52 collect_callable_table(
53 tree.root_node(),
54 source.as_bytes(),
55 &module,
56 &mut callable_table,
57 );
58 let strat = Strategy {
59 module: module.clone(),
60 source_bytes: source.as_bytes(),
61 deep,
62 imports: RefCell::new(HashMap::new()),
63 local_scope: RefCell::new(Vec::new()),
64 type_table,
65 callable_table,
66 };
67 let walker = CanonicalWalker::new(&strat, source.as_bytes());
68 walker.walk(tree.root_node(), &module, &mut graph);
69 graph
70}
71
72pub struct Lang;
73
74impl crate::lang::LangExtractor for Lang {
75 type Presets = Presets;
76 const LANG_TAG: &'static str = "cs";
77 const ALLOWED_KINDS: &'static [&'static str] = &[
78 "class",
79 "interface",
80 "struct",
81 "record",
82 "enum",
83 "delegate",
84 "method",
85 "constructor",
86 "field",
87 "property",
88 "event",
89 ];
90 const ALLOWED_VISIBILITIES: &'static [&'static str] =
91 &["public", "protected", "package", "private"];
92
93 fn extract(
94 uri: &str,
95 source: &str,
96 anchor: &Moniker,
97 deep: bool,
98 presets: &Self::Presets,
99 ) -> CodeGraph {
100 extract(uri, source, anchor, deep, presets)
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::core::moniker::MonikerBuilder;
108 use crate::lang::assert_conformance;
109
110 fn make_anchor() -> Moniker {
111 MonikerBuilder::new().project(b"app").build()
112 }
113
114 fn extract_default(uri: &str, source: &str, anchor: &Moniker, deep: bool) -> CodeGraph {
115 let g = extract(uri, source, anchor, deep, &Presets::default());
116 assert_conformance::<super::Lang>(&g, anchor);
117 g
118 }
119
120 #[test]
121 fn parse_empty_returns_compilation_unit() {
122 let tree = parse("");
123 assert_eq!(tree.root_node().kind(), "compilation_unit");
124 }
125
126 #[test]
127 fn extract_emits_comment_def_per_comment_node() {
128 let src = "// a\n/// b\nclass Foo {}\n";
129 let g = extract_default("Foo.cs", src, &make_anchor(), false);
130 let n = g.defs().filter(|d| d.kind == b"comment").count();
131 assert_eq!(n, 2);
132 }
133
134 #[test]
135 fn extract_module_uses_path_segments() {
136 let g = extract_default(
137 "Acme/Util/Text.cs",
138 "namespace Acme.Util;\n",
139 &make_anchor(),
140 false,
141 );
142 let expected = MonikerBuilder::new()
143 .project(b"app")
144 .segment(b"lang", b"cs")
145 .segment(b"package", b"Acme")
146 .segment(b"package", b"Util")
147 .segment(b"module", b"Text")
148 .build();
149 assert_eq!(g.root(), &expected);
150 }
151
152 #[test]
153 fn extract_class_emits_class_def() {
154 let src = "namespace Foo;\npublic class Bar {}\n";
155 let g = extract_default("F.cs", src, &make_anchor(), false);
156 let bar = g.defs().find(|d| d.kind == b"class").expect("class def");
157 assert_eq!(
158 bar.moniker.as_view().segments().last().unwrap().name,
159 b"Bar"
160 );
161 assert_eq!(bar.visibility, b"public".to_vec());
162 }
163
164 #[test]
165 fn extract_struct_emits_struct_def() {
166 let src = "namespace Foo;\npublic struct Bar {}\n";
167 let g = extract_default("F.cs", src, &make_anchor(), false);
168 assert!(g.defs().any(|d| d.kind == b"struct"
169 && d.moniker.as_view().segments().last().unwrap().name == b"Bar"));
170 }
171
172 #[test]
173 fn extract_interface_emits_interface_def() {
174 let src = "namespace Foo;\npublic interface IBar {}\n";
175 let g = extract_default("F.cs", src, &make_anchor(), false);
176 let i = g
177 .defs()
178 .find(|d| d.kind == b"interface")
179 .expect("interface def");
180 assert_eq!(i.moniker.as_view().segments().last().unwrap().name, b"IBar");
181 }
182
183 #[test]
184 fn extract_enum_emits_enum_def() {
185 let src = "namespace Foo;\npublic enum Color { Red, Green }\n";
186 let g = extract_default("F.cs", src, &make_anchor(), false);
187 let e = g.defs().find(|d| d.kind == b"enum").expect("enum def");
188 assert_eq!(
189 e.moniker.as_view().segments().last().unwrap().name,
190 b"Color"
191 );
192 }
193
194 #[test]
195 fn extract_top_level_type_default_visibility_is_internal() {
196 let src = "namespace Foo;\nclass Bar {}\n";
197 let g = extract_default("F.cs", src, &make_anchor(), false);
198 let bar = g.defs().find(|d| d.kind == b"class").expect("class def");
199 assert_eq!(
200 bar.visibility,
201 b"package".to_vec(),
202 "top-level C# class without modifier defaults to internal (= VIS_PACKAGE)"
203 );
204 }
205
206 #[test]
207 fn extract_block_namespace_descends_into_body() {
208 let src = "namespace Foo {\n public class Bar {}\n}\n";
209 let g = extract_default("F.cs", src, &make_anchor(), false);
210 assert!(g.defs().any(|d| d.kind == b"class"));
211 }
212
213 #[test]
214 fn extract_method_reparented_under_class() {
215 let src = "namespace Foo;\npublic class Bar {\n public int Add(int a, int b) { return a + b; }\n}\n";
216 let g = extract_default("F.cs", src, &make_anchor(), false);
217 let m = MonikerBuilder::new()
218 .project(b"app")
219 .segment(b"lang", b"cs")
220 .segment(b"module", b"F")
221 .segment(b"class", b"Bar")
222 .segment(b"method", b"Add(a:int,b:int)")
223 .build();
224 assert!(
225 g.contains(&m),
226 "expected {m:?}, defs: {:?}",
227 g.def_monikers()
228 );
229 }
230
231 #[test]
232 fn extract_method_signature_excludes_return_type() {
233 let src = "namespace Foo;\npublic class Bar {\n public string Greet(string n) { return n; }\n}\n";
234 let g = extract_default("F.cs", src, &make_anchor(), false);
235 let m = g.defs().find(|d| d.kind == b"method").expect("method def");
236 assert_eq!(m.signature, b"n:string".to_vec());
237 }
238
239 #[test]
240 fn extract_method_default_visibility_is_private() {
241 let src = "namespace Foo;\npublic class Bar {\n int Hidden() { return 0; }\n}\n";
242 let g = extract_default("F.cs", src, &make_anchor(), false);
243 let m = g.defs().find(|d| d.kind == b"method").expect("method def");
244 assert_eq!(m.visibility, b"private".to_vec());
245 }
246
247 #[test]
248 fn extract_constructor_emits_constructor_def() {
249 let src = "namespace Foo;\npublic class Bar {\n public Bar(int x) {}\n}\n";
250 let g = extract_default("F.cs", src, &make_anchor(), false);
251 let ctor = MonikerBuilder::new()
252 .project(b"app")
253 .segment(b"lang", b"cs")
254 .segment(b"module", b"F")
255 .segment(b"class", b"Bar")
256 .segment(b"constructor", b"Bar(x:int)")
257 .build();
258 assert!(
259 g.contains(&ctor),
260 "constructor expected at {ctor:?}; defs: {:?}",
261 g.def_monikers()
262 );
263 }
264
265 #[test]
266 fn extract_method_no_params_emits_empty_parens() {
267 let src = "namespace Foo;\npublic class Bar {\n public void Boot() {}\n}\n";
268 let g = extract_default("F.cs", src, &make_anchor(), false);
269 let m = g.defs().find(|d| d.kind == b"method").expect("method def");
270 assert_eq!(
271 m.moniker.as_view().segments().last().unwrap().name,
272 b"Boot()"
273 );
274 }
275
276 #[test]
277 fn extract_method_params_modifier_emits_ellipsis() {
278 let src =
279 "namespace Foo;\npublic class Bar {\n public void Log(params object[] args) {}\n}\n";
280 let g = extract_default("F.cs", src, &make_anchor(), false);
281 let m = g.defs().find(|d| d.kind == b"method").expect("method def");
282 assert_eq!(
283 m.moniker.as_view().segments().last().unwrap().name,
284 b"Log(...)"
285 );
286 }
287
288 #[test]
289 fn extract_record_emits_record_plus_primary_constructor() {
290 let src = "namespace Foo;\npublic record Person(int Age, string Name);\n";
291 let g = extract_default("F.cs", src, &make_anchor(), false);
292 let record = MonikerBuilder::new()
293 .project(b"app")
294 .segment(b"lang", b"cs")
295 .segment(b"module", b"F")
296 .segment(b"record", b"Person")
297 .build();
298 let ctor = MonikerBuilder::new()
299 .project(b"app")
300 .segment(b"lang", b"cs")
301 .segment(b"module", b"F")
302 .segment(b"record", b"Person")
303 .segment(b"constructor", b"Person(Age:int,Name:string)")
304 .build();
305 assert!(g.contains(&record));
306 assert!(
307 g.contains(&ctor),
308 "record primary constructor expected at {ctor:?}; defs: {:?}",
309 g.def_monikers()
310 );
311 }
312
313 #[test]
314 fn extract_nested_class_attached_to_outer_class() {
315 let src = "namespace Foo;\npublic class Outer {\n public class Inner {}\n}\n";
316 let g = extract_default("F.cs", src, &make_anchor(), false);
317 let inner = MonikerBuilder::new()
318 .project(b"app")
319 .segment(b"lang", b"cs")
320 .segment(b"module", b"F")
321 .segment(b"class", b"Outer")
322 .segment(b"class", b"Inner")
323 .build();
324 assert!(g.contains(&inner));
325 }
326
327 #[test]
328 fn extract_field_emits_field_def() {
329 let src = "namespace Foo;\npublic class Bar {\n private int _count;\n public string Name = \"x\";\n}\n";
330 let g = extract_default("F.cs", src, &make_anchor(), false);
331 let names: Vec<&[u8]> = g
332 .defs()
333 .filter(|d| d.kind == b"field")
334 .map(|d| d.moniker.as_view().segments().last().unwrap().name)
335 .collect();
336 assert!(names.contains(&&b"_count"[..]));
337 assert!(names.contains(&&b"Name"[..]));
338 let count = g
339 .defs()
340 .find(|d| d.moniker.as_view().segments().last().unwrap().name == b"_count")
341 .unwrap();
342 assert_eq!(count.visibility, b"private".to_vec());
343 let name_def = g
344 .defs()
345 .find(|d| d.moniker.as_view().segments().last().unwrap().name == b"Name")
346 .unwrap();
347 assert_eq!(name_def.visibility, b"public".to_vec());
348 }
349
350 #[test]
351 fn extract_field_with_user_type_emits_uses_type() {
352 let src = "namespace Foo;\npublic class Other {}\npublic class Bar {\n private Other _ref;\n}\n";
353 let g = extract_default("F.cs", src, &make_anchor(), false);
354 let r = g
355 .refs()
356 .find(|r| {
357 r.kind == b"uses_type"
358 && r.target.as_view().segments().last().unwrap().name == b"Other"
359 })
360 .expect("uses_type Other");
361 assert!(matches!(
362 r.confidence.as_slice(),
363 b"name_match" | b"resolved"
364 ));
365 }
366
367 #[test]
368 fn extract_property_emits_property_def() {
369 let src = "namespace Foo;\npublic class Bar {\n public string Name { get; set; }\n}\n";
370 let g = extract_default("F.cs", src, &make_anchor(), false);
371 let p = g
372 .defs()
373 .find(|d| {
374 d.kind == b"property"
375 && d.moniker.as_view().segments().last().unwrap().name == b"Name"
376 })
377 .expect("property def");
378 assert_eq!(p.visibility, b"public".to_vec());
379 }
380
381 #[test]
382 fn extract_expression_bodied_property_emits_property_def() {
383 let src = "namespace Foo;\npublic class Bar {\n public int N => 42;\n}\n";
384 let g = extract_default("F.cs", src, &make_anchor(), false);
385 assert!(g.defs().any(|d| d.kind == b"property"
386 && d.moniker.as_view().segments().last().unwrap().name == b"N"));
387 }
388
389 #[test]
390 fn extract_property_with_user_type_emits_uses_type() {
391 let src = "namespace Foo;\npublic class Other {}\npublic class Bar {\n public Other Item { get; set; }\n}\n";
392 let g = extract_default("F.cs", src, &make_anchor(), false);
393 assert!(g.refs().any(|r| r.kind == b"uses_type"
394 && r.target.as_view().segments().last().unwrap().name == b"Other"));
395 }
396
397 #[test]
398 fn extract_base_list_emits_extends_per_entry() {
399 let src = "namespace Foo;\npublic class Base {}\npublic class Foo : Base, IBar {}\n";
400 let g = extract_default("F.cs", src, &make_anchor(), false);
401 let names: Vec<&[u8]> = g
402 .refs()
403 .filter(|r| r.kind == b"extends")
404 .map(|r| r.target.as_view().segments().last().unwrap().name)
405 .collect();
406 assert!(names.contains(&&b"Base"[..]));
407 assert!(names.contains(&&b"IBar"[..]));
408 }
409
410 #[test]
411 fn extract_generic_base_emits_extends_on_head_and_uses_type_on_arg() {
412 let src = "namespace Foo;\npublic class List<T> {}\npublic class Bar : List<int> {}\n";
413 let g = extract_default("F.cs", src, &make_anchor(), false);
414 assert!(g.refs().any(|r| r.kind == b"extends"
415 && r.target.as_view().segments().last().unwrap().name == b"List"));
416 }
417
418 #[test]
419 fn extract_interface_base_emits_extends_per_entry() {
420 let src = "namespace Foo;\npublic interface IFoo : IBar, IBaz {}\n";
421 let g = extract_default("F.cs", src, &make_anchor(), false);
422 let count = g.refs().filter(|r| r.kind == b"extends").count();
423 assert_eq!(count, 2);
424 }
425
426 #[test]
427 fn extract_method_param_user_type_emits_uses_type() {
428 let src = "namespace Foo;\npublic class Other {}\npublic class Bar {\n public void Take(Other o) {}\n}\n";
429 let g = extract_default("F.cs", src, &make_anchor(), false);
430 assert!(
431 g.refs().any(|r| r.kind == b"uses_type"
432 && r.target.as_view().segments().last().unwrap().name == b"Other"),
433 "refs: {:?}",
434 g.refs().map(|r| r.kind.clone()).collect::<Vec<_>>()
435 );
436 }
437
438 #[test]
439 fn extract_using_simple_emits_imports_module_external() {
440 let g = extract_default("F.cs", "using System;\n", &make_anchor(), false);
441 let r = g
442 .refs()
443 .find(|r| r.kind == b"imports_module")
444 .expect("imports_module ref");
445 assert_eq!(r.confidence, b"external".to_vec());
446 let target = MonikerBuilder::new()
447 .project(b"app")
448 .segment(b"external_pkg", b"System")
449 .build();
450 assert_eq!(r.target, target);
451 }
452
453 #[test]
454 fn extract_using_dotted_path_segments() {
455 let g = extract_default(
456 "F.cs",
457 "using System.Collections.Generic;\n",
458 &make_anchor(),
459 false,
460 );
461 let r = g
462 .refs()
463 .find(|r| r.kind == b"imports_module")
464 .expect("imports_module ref");
465 let names: Vec<&[u8]> = r.target.as_view().segments().map(|s| s.name).collect();
466 assert_eq!(
467 names,
468 vec![&b"System"[..], &b"Collections"[..], &b"Generic"[..]]
469 );
470 }
471
472 #[test]
473 fn extract_using_third_party_marks_imported() {
474 let g = extract_default("F.cs", "using Newtonsoft.Json;\n", &make_anchor(), false);
475 let r = g
476 .refs()
477 .find(|r| r.kind == b"imports_module")
478 .expect("imports_module ref");
479 assert_eq!(r.confidence, b"imported".to_vec());
480 }
481
482 #[test]
483 fn extract_using_alias_records_alias_attr() {
484 let g = extract_default("F.cs", "using IO = System.IO;\n", &make_anchor(), false);
485 let r = g
486 .refs()
487 .find(|r| r.kind == b"imports_module")
488 .expect("imports_module ref");
489 assert_eq!(r.alias, b"IO".to_vec());
490 }
491
492 #[test]
493 fn extract_global_using_emits_imports_module() {
494 let g = extract_default("F.cs", "global using System;\n", &make_anchor(), false);
495 assert!(
496 g.refs()
497 .any(|r| r.kind == b"imports_module" && r.confidence == b"external".to_vec())
498 );
499 }
500
501 #[test]
502 fn extract_using_static_emits_imports_module() {
503 let g = extract_default("F.cs", "using static System.Math;\n", &make_anchor(), false);
504 assert!(g.refs().any(|r| r.kind == b"imports_module"));
505 }
506
507 #[test]
508 fn extract_simple_invocation_to_unresolved_callee_uses_name_only() {
509 let src = "class B {\n void M() { Helper(1, 2); }\n}\n";
510 let g = extract_default("F.cs", src, &make_anchor(), false);
511 let r = g
512 .refs()
513 .find(|r| {
514 r.kind == b"calls"
515 && r.target.as_view().segments().last().unwrap().name == b"Helper"
516 })
517 .expect("calls Helper (name-only)");
518 assert_eq!(r.confidence, b"name_match".to_vec());
519 }
520
521 #[test]
522 fn extract_simple_invocation_to_same_module_resolves_slots() {
523 let src = "class B {\n void M() { Helper(1); }\n void Helper(int n) {}\n}\n";
524 let g = extract_default("F.cs", src, &make_anchor(), false);
525 let r = g
526 .refs()
527 .find(|r| {
528 r.kind == b"calls"
529 && r.target.as_view().segments().last().unwrap().name == b"Helper(n:int)"
530 })
531 .expect("calls Helper(n:int)");
532 assert_eq!(r.confidence, b"name_match".to_vec());
533 }
534
535 #[test]
536 fn extract_member_invocation_emits_method_call_with_receiver_hint() {
537 let src = "class B {\n void M() { Console.WriteLine(\"hi\"); }\n}\n";
538 let g = extract_default("F.cs", src, &make_anchor(), false);
539 let r = g
540 .refs()
541 .find(|r| r.kind == b"method_call")
542 .expect("method_call ref");
543 assert_eq!(r.receiver_hint, b"Console".to_vec());
544 assert_eq!(
545 r.target.as_view().segments().last().unwrap().name,
546 b"WriteLine"
547 );
548 }
549
550 #[test]
551 fn extract_chained_member_call_receiver_hint_is_call() {
552 let src = "class B {\n void M() { foo().bar(); }\n}\n";
553 let g = extract_default("F.cs", src, &make_anchor(), false);
554 let r = g
555 .refs()
556 .find(|r| {
557 r.kind == b"method_call"
558 && r.target.as_view().segments().last().unwrap().name == b"bar"
559 })
560 .expect("method_call bar");
561 assert_eq!(r.receiver_hint, b"call".to_vec());
562 }
563
564 #[test]
565 fn extract_object_creation_emits_instantiates() {
566 let src = "namespace Foo;\npublic class Bar {}\nclass C {\n void M() { var x = new Bar(); }\n}\n";
567 let g = extract_default("F.cs", src, &make_anchor(), false);
568 let r = g
569 .refs()
570 .find(|r| r.kind == b"instantiates")
571 .expect("instantiates ref");
572 assert_eq!(r.target.as_view().segments().last().unwrap().name, b"Bar");
573 assert_eq!(r.confidence, b"resolved".to_vec());
574 }
575
576 #[test]
577 fn extract_object_creation_unresolved_marks_name_match() {
578 let src = "class C {\n void M() { var x = new Unknown(); }\n}\n";
579 let g = extract_default("F.cs", src, &make_anchor(), false);
580 let r = g
581 .refs()
582 .find(|r| r.kind == b"instantiates")
583 .expect("instantiates ref");
584 assert_eq!(r.confidence, b"name_match".to_vec());
585 }
586
587 #[test]
588 fn extract_invocation_visits_arguments_for_nested_calls() {
589 let src = "class B {\n void M() { Outer(Inner()); }\n}\n";
590 let g = extract_default("F.cs", src, &make_anchor(), false);
591 let names: Vec<&[u8]> = g
592 .refs()
593 .filter(|r| r.kind == b"calls")
594 .map(|r| r.target.as_view().segments().last().unwrap().name)
595 .collect();
596 assert!(names.contains(&&b"Outer"[..]));
597 assert!(names.contains(&&b"Inner"[..]));
598 }
599
600 #[test]
601 fn extract_class_attribute_emits_annotates() {
602 let src = "namespace Foo;\n[Serializable]\npublic class Bar {}\n";
603 let g = extract_default("F.cs", src, &make_anchor(), false);
604 let r = g
605 .refs()
606 .find(|r| r.kind == b"annotates")
607 .expect("annotates ref");
608 assert_eq!(
609 r.target.as_view().segments().last().unwrap().name,
610 b"Serializable"
611 );
612 }
613
614 #[test]
615 fn extract_method_attribute_emits_annotates() {
616 let src = "namespace Foo;\npublic class Bar {\n [HttpGet] public void M() {}\n}\n";
617 let g = extract_default("F.cs", src, &make_anchor(), false);
618 let r = g
619 .refs()
620 .find(|r| r.kind == b"annotates")
621 .expect("annotates ref");
622 assert_eq!(
623 r.target.as_view().segments().last().unwrap().name,
624 b"HttpGet"
625 );
626 }
627
628 #[test]
629 fn extract_multiple_attribute_lists_each_emit_annotates() {
630 let src =
631 "namespace Foo;\npublic class Bar {\n [Required] [Range(1,9)] public int N;\n}\n";
632 let g = extract_default("F.cs", src, &make_anchor(), false);
633 let names: Vec<&[u8]> = g
634 .refs()
635 .filter(|r| r.kind == b"annotates")
636 .map(|r| r.target.as_view().segments().last().unwrap().name)
637 .collect();
638 assert!(names.contains(&&b"Required"[..]));
639 assert!(names.contains(&&b"Range"[..]));
640 }
641
642 #[test]
643 fn extract_qualified_attribute_resolves_leaf_name() {
644 let src = "namespace Foo;\n[System.Serializable]\npublic class Bar {}\n";
645 let g = extract_default("F.cs", src, &make_anchor(), false);
646 assert!(g.refs().any(|r| r.kind == b"annotates"
647 && r.target.as_view().segments().last().unwrap().name == b"Serializable"));
648 }
649
650 #[test]
651 fn extract_shallow_skips_param_and_local_defs() {
652 let src = "class B {\n void M(int x) { int y = 1; var z = \"\"; }\n}\n";
653 let g = extract_default("F.cs", src, &make_anchor(), false);
654 assert!(
655 g.defs().all(|d| d.kind != b"param" && d.kind != b"local"),
656 "shallow extraction must not emit param/local defs"
657 );
658 }
659
660 #[test]
661 fn extract_deep_emits_param_def() {
662 let src = "class B {\n void M(int a, string b) {}\n}\n";
663 let g = extract_default("F.cs", src, &make_anchor(), true);
664 let pa = MonikerBuilder::new()
665 .project(b"app")
666 .segment(b"lang", b"cs")
667 .segment(b"module", b"F")
668 .segment(b"class", b"B")
669 .segment(b"method", b"M(a:int,b:string)")
670 .segment(b"param", b"a")
671 .build();
672 assert!(g.contains(&pa));
673 }
674
675 #[test]
676 fn extract_deep_emits_local_def_for_typed_var() {
677 let src = "class B {\n void M() { int x = 5; }\n}\n";
678 let g = extract_default("F.cs", src, &make_anchor(), true);
679 let lx = MonikerBuilder::new()
680 .project(b"app")
681 .segment(b"lang", b"cs")
682 .segment(b"module", b"F")
683 .segment(b"class", b"B")
684 .segment(b"method", b"M()")
685 .segment(b"local", b"x")
686 .build();
687 assert!(g.contains(&lx));
688 }
689
690 #[test]
691 fn extract_deep_emits_local_def_for_implicit_var() {
692 let src = "class B {\n void M() { var s = \"hi\"; }\n}\n";
693 let g = extract_default("F.cs", src, &make_anchor(), true);
694 assert!(
695 g.defs().any(|d| d.kind == b"local"
696 && d.moniker.as_view().segments().last().unwrap().name == b"s")
697 );
698 }
699
700 #[test]
701 fn extract_deep_emits_local_def_for_foreach_iter() {
702 let src = "class B {\n void M(int[] items) { foreach (var item in items) {} }\n}\n";
703 let g = extract_default("F.cs", src, &make_anchor(), true);
704 assert!(g.defs().any(|d| d.kind == b"local"
705 && d.moniker.as_view().segments().last().unwrap().name == b"item"));
706 }
707
708 #[test]
709 fn extract_deep_skips_blank_local() {
710 let src = "class B {\n void M() { var _ = 1; var y = 2; }\n}\n";
711 let g = extract_default("F.cs", src, &make_anchor(), true);
712 let names: Vec<&[u8]> = g
713 .defs()
714 .filter(|d| d.kind == b"local")
715 .map(|d| d.moniker.as_view().segments().last().unwrap().name)
716 .collect();
717 assert_eq!(names, vec![&b"y"[..]]);
718 }
719}