1use std::collections::HashMap;
2
3use lsp_types::{
4 PartialResultParams, Position, Range, ReferenceContext, ReferenceParams,
5 TextDocumentPositionParams, WorkDoneProgressParams,
6};
7
8use crate::language_server::tests::{TestProject, find_position_of};
9
10fn find_references(
11 tester: &TestProject<'_>,
12 position: Position,
13) -> Option<HashMap<String, Vec<Range>>> {
14 let locations = tester.at(position, |engine, params, _| {
15 let params = ReferenceParams {
16 text_document_position: TextDocumentPositionParams {
17 text_document: params.text_document,
18 position,
19 },
20 work_done_progress_params: WorkDoneProgressParams::default(),
21 partial_result_params: PartialResultParams::default(),
22 context: ReferenceContext {
23 include_declaration: true,
24 },
25 };
26 engine.find_references(params).result.unwrap()
27 })?;
28 let mut references: HashMap<String, Vec<Range>> = HashMap::new();
29
30 for location in locations {
31 let module_name = tester
32 .module_name_from_url(&location.uri)
33 .expect("Valid uri");
34 _ = references
35 .entry(module_name)
36 .or_default()
37 .push(location.range);
38 }
39
40 Some(references)
41}
42
43fn show_references(code: &str, position: Option<Position>, ranges: &[Range]) -> String {
44 let mut buffer = String::new();
45
46 for (line_number, line) in code.lines().enumerate() {
47 let mut underline = String::new();
48 let mut underline_empty = true;
49 let line_number = line_number as u32;
50
51 if let Some(Range { start, end }) = ranges
52 .iter()
53 .find(|range| range.start.line == line_number && range.end.line == line_number)
54 {
55 for (column_number, _) in line.chars().enumerate() {
56 let current_position = Position::new(line_number, column_number as u32);
57 if Some(current_position) == position {
58 underline_empty = false;
59 underline.push('↑');
60 } else if start.le(¤t_position) && current_position.lt(&end) {
61 underline_empty = false;
62 underline.push('▔');
63 } else {
64 underline.push(' ');
65 }
66 }
67 }
68
69 buffer.push_str(line);
70 if !underline_empty {
71 buffer.push('\n');
72 buffer.push_str(&underline);
73 }
74 buffer.push('\n');
75 }
76
77 buffer
78}
79
80macro_rules! assert_references {
81 ($code:literal, $position:expr $(,)?) => {
82 assert_references!(TestProject::for_source($code), $position);
83 };
84
85 (($module_name:literal, $module_src:literal), $code:literal, $position:expr $(,)?) => {
86 assert_references!(
87 TestProject::for_source($code).add_module($module_name, $module_src),
88 $position
89 );
90 };
91
92 ($project:expr, $position:expr $(,)?) => {
93 let project = $project;
94 let src = project.src;
95 let position = $position.find_position(src);
96 let result = find_references(&project, position).expect("References not found");
97
98 let mut output = String::new();
99 for (name, src) in project.root_package_modules.iter() {
100 output.push_str(&format!(
101 "-- {name}.gleam\n{}\n\n",
102 show_references(src, None, result.get(*name).unwrap_or(&Vec::new()))
103 ));
104 }
105 output.push_str(&format!(
106 "-- app.gleam\n{}",
107 show_references(
108 src,
109 Some(position),
110 result.get("app").unwrap_or(&Vec::new())
111 )
112 ));
113
114 insta::assert_snapshot!(insta::internals::AutoName, output, src);
115 };
116}
117
118macro_rules! assert_no_references {
119 ($code:literal, $position:expr $(,)?) => {
120 let project = TestProject::for_source($code);
121 assert_no_references!(&project, $position);
122 };
123
124 ($project:expr, $position:expr $(,)?) => {
125 let src = $project.src;
126 let position = $position.find_position(src);
127 let result = find_references($project, position);
128 assert_eq!(result, None);
129 };
130}
131
132#[test]
133fn references_for_local_variable() {
134 assert_references!(
135 "
136pub fn main() {
137 let wibble = 10
138 let wobble = wibble + 1
139 wibble + wobble
140}
141",
142 find_position_of("wibble").nth_occurrence(2),
143 );
144}
145
146#[test]
147fn references_for_local_variable_from_definition() {
148 assert_references!(
149 "
150pub fn main() {
151 let wibble = 10
152 let wobble = wibble + 1
153 wibble + wobble
154}
155",
156 find_position_of("wibble"),
157 );
158}
159
160#[test]
161fn references_for_private_function() {
162 assert_references!(
163 "
164fn wibble() {
165 wibble()
166}
167
168pub fn main() {
169 let _ = wibble()
170 wibble() + 4
171}
172
173fn wobble() {
174 wibble() || wobble()
175}
176",
177 find_position_of("wibble"),
178 );
179}
180
181#[test]
182fn references_for_private_function_from_reference() {
183 assert_references!(
184 "
185fn wibble() {
186 wibble()
187}
188
189pub fn main() {
190 let _ = wibble()
191 wibble() + 4
192}
193
194fn wobble() {
195 wibble() || wobble()
196}
197",
198 find_position_of("wibble").nth_occurrence(2),
199 );
200}
201
202#[test]
203fn references_for_public_function() {
204 assert_references!(
205 (
206 "mod",
207 "
208import app.{wibble}
209
210fn wobble() {
211 app.wibble()
212}
213
214fn other() {
215 wibble()
216}
217"
218 ),
219 "
220pub fn wibble() {
221 wibble()
222}
223",
224 find_position_of("wibble").nth_occurrence(2),
225 );
226}
227
228#[test]
229fn references_for_function_from_qualified_reference() {
230 assert_references!(
231 (
232 "mod",
233 "
234pub fn wibble() {
235 wibble()
236}
237"
238 ),
239 "
240import mod
241
242pub fn main() {
243 let value = mod.wibble()
244 mod.wibble()
245 value
246}
247",
248 find_position_of("wibble"),
249 );
250}
251
252#[test]
253fn references_for_function_from_unqualified_reference() {
254 assert_references!(
255 (
256 "mod",
257 "
258pub fn wibble() {
259 wibble()
260}
261"
262 ),
263 "
264import mod.{wibble}
265
266pub fn main() {
267 let value = wibble()
268 mod.wibble()
269 value
270}
271",
272 find_position_of("wibble()"),
273 );
274}
275
276#[test]
277fn references_for_private_constant() {
278 assert_references!(
279 "
280const wibble = 10
281
282pub fn main() {
283 let _ = wibble
284 wibble + 4
285}
286
287fn wobble() {
288 wibble + wobble()
289}
290",
291 find_position_of("wibble"),
292 );
293}
294
295#[test]
296fn references_for_private_constant_from_reference() {
297 assert_references!(
298 "
299const wibble = 10
300
301pub fn main() {
302 let _ = wibble
303 wibble + 4
304}
305
306fn wobble() {
307 wibble + wobble()
308}
309",
310 find_position_of("wibble").nth_occurrence(2),
311 );
312}
313
314#[test]
315fn references_for_public_constant() {
316 assert_references!(
317 (
318 "mod",
319 "
320import app.{wibble}
321
322fn wobble() {
323 app.wibble
324}
325
326fn other() {
327 wibble
328}
329"
330 ),
331 "
332pub const wibble = 10
333
334pub fn main() {
335 wibble
336}
337",
338 find_position_of("wibble").nth_occurrence(2),
339 );
340}
341
342#[test]
343fn references_for_constant_from_qualified_reference() {
344 assert_references!(
345 (
346 "mod",
347 "
348pub const wibble = 10
349
350fn wobble() {
351 wibble
352}
353"
354 ),
355 "
356import mod
357
358pub fn main() {
359 let value = mod.wibble
360 mod.wibble + value
361}
362",
363 find_position_of("wibble"),
364 );
365}
366
367#[test]
368fn references_for_constant_from_unqualified_reference() {
369 assert_references!(
370 (
371 "mod",
372 "
373pub const wibble = 10
374
375fn wobble() {
376 wibble
377}
378"
379 ),
380 "
381import mod.{wibble}
382
383pub fn main() {
384 let value = mod.wibble
385 wibble + value
386}
387",
388 find_position_of("wibble +"),
389 );
390}
391
392#[test]
393fn references_for_private_type_variant() {
394 assert_references!(
395 "
396type Wibble { Wibble }
397
398fn main() {
399 let _ = Wibble
400 Wibble
401}
402
403fn wobble() {
404 Wibble
405 wobble()
406}
407",
408 find_position_of("Wibble }"),
409 );
410}
411
412#[test]
413fn references_for_private_type_variant_from_reference() {
414 assert_references!(
415 "
416type Wibble { Wibble }
417
418fn main() {
419 let _ = Wibble
420 Wibble
421}
422
423fn wobble() {
424 Wibble
425 wobble()
426}
427",
428 find_position_of(" = Wibble").under_char('W'),
429 );
430}
431
432#[test]
433fn references_for_public_type_variant() {
434 assert_references!(
435 (
436 "mod",
437 "
438import app.{Wibble}
439
440fn wobble() {
441 app.Wibble
442}
443
444fn other() {
445 Wibble
446}
447"
448 ),
449 "
450pub type Wibble { Wibble }
451
452pub fn main() {
453 Wibble
454}
455",
456 find_position_of("Wibble }"),
457 );
458}
459
460#[test]
461fn references_for_type_variant_from_qualified_reference() {
462 assert_references!(
463 (
464 "mod",
465 "
466pub type Wibble { Wibble }
467
468fn wobble() {
469 Wibble
470}
471"
472 ),
473 "
474import mod
475
476pub fn main() {
477 let value = mod.Wibble
478 mod.Wibble
479 value
480}
481",
482 find_position_of("Wibble"),
483 );
484}
485
486#[test]
487fn references_for_type_variant_from_unqualified_reference() {
488 assert_references!(
489 (
490 "mod",
491 "
492pub type Wibble { Wibble }
493
494fn wobble() {
495 Wibble
496}
497"
498 ),
499 "
500import mod.{Wibble}
501
502pub fn main() {
503 let value = mod.Wibble
504 Wibble
505}
506",
507 find_position_of("Wibble").nth_occurrence(3),
508 );
509}
510
511#[test]
512fn no_references_for_keyword() {
513 assert_no_references!(
514 "
515pub fn wibble() {
516 todo
517}
518",
519 find_position_of("fn")
520 );
521}
522
523#[test]
524fn references_for_aliased_value() {
525 assert_references!(
526 (
527 "mod",
528 "
529import app.{Wibble as Wobble}
530
531fn wobble() {
532 Wobble
533}
534"
535 ),
536 "
537pub type Wibble { Wibble }
538
539pub fn main() {
540 Wibble
541}
542",
543 find_position_of("Wibble").nth_occurrence(2),
544 );
545}
546
547#[test]
548fn references_for_aliased_const() {
549 assert_references!(
550 (
551 "mod",
552 "
553import app.{wibble as other}
554
555fn wobble() {
556 other
557}
558"
559 ),
560 "
561pub const wibble = 123
562
563pub fn main() {
564 wibble
565}
566",
567 find_position_of("wibble").nth_occurrence(2),
568 );
569}
570
571#[test]
572fn references_for_aliased_function() {
573 assert_references!(
574 (
575 "mod",
576 "
577import app.{wibble as other}
578
579fn wobble() {
580 other()
581}
582"
583 ),
584 "
585pub fn wibble() {
586 123
587}
588
589pub fn main() {
590 wibble()
591}
592",
593 find_position_of("wibble").nth_occurrence(2),
594 );
595}
596
597#[test]
598fn references_for_private_type() {
599 assert_references!(
600 "
601type Wibble { Wibble }
602
603fn main() -> Wibble {
604 todo
605}
606
607fn wobble(w: Wibble) {
608 todo
609}
610",
611 find_position_of("Wibble"),
612 );
613}
614
615#[test]
616fn references_for_private_type_from_reference() {
617 assert_references!(
618 "
619type Wibble { Wibble }
620
621fn main() -> Wibble {
622 todo
623}
624
625fn wobble(w: Wibble) {
626 todo
627}
628",
629 find_position_of("-> Wibble").under_char('W'),
630 );
631}
632
633#[test]
634fn references_for_public_type() {
635 assert_references!(
636 (
637 "mod",
638 "
639import app.{type Wibble}
640
641fn wobble() -> Wibble {
642 todo
643}
644
645fn other(w: app.Wibble) {
646 todo
647}
648"
649 ),
650 "
651pub type Wibble { Wibble }
652
653pub fn main() -> Wibble {
654 todo
655}
656",
657 find_position_of("Wibble"),
658 );
659}
660
661#[test]
662fn references_for_type_from_qualified_reference() {
663 assert_references!(
664 (
665 "mod",
666 "
667pub type Wibble { Wibble }
668
669fn wobble() -> Wibble {
670 todo
671}
672"
673 ),
674 "
675import mod
676
677pub fn main() -> mod.Wibble {
678 let _: mod.Wibble = todo
679}
680",
681 find_position_of("Wibble"),
682 );
683}
684
685#[test]
686fn references_for_type_from_unqualified_reference() {
687 assert_references!(
688 (
689 "mod",
690 "
691pub type Wibble { Wibble }
692
693fn wobble() -> Wibble {
694 todo
695}
696"
697 ),
698 "
699import mod.{type Wibble}
700
701pub fn main() -> Wibble {
702 let _: mod.Wibble = todo
703}
704",
705 find_position_of("Wibble").nth_occurrence(2),
706 );
707}
708
709#[test]
710fn references_for_aliased_type() {
711 assert_references!(
712 (
713 "mod",
714 "
715import app.{type Wibble as Wobble}
716
717fn wobble() -> Wobble {
718 todo
719}
720
721fn other(w: app.Wibble) {
722 todo
723}
724"
725 ),
726 "
727pub type Wibble { Wibble }
728
729pub fn main() -> Wibble {
730 todo
731}
732",
733 find_position_of("-> Wibble").under_char('W'),
734 );
735}
736
737#[test]
738fn references_for_type_from_let_annotation() {
739 assert_references!(
740 (
741 "mod",
742 "
743pub type Wibble { Wibble }
744
745fn wobble() -> Wibble {
746 todo
747}
748"
749 ),
750 "
751import mod.{type Wibble}
752
753pub fn main() -> Wibble {
754 let _: mod.Wibble = todo
755}
756",
757 find_position_of("mod.Wibble").under_char('W'),
758 );
759}