Fork of daniellemaywood.uk/gleam — Wasm codegen work
22 kB
997 lines
1use lsp_types::{DefinitionParams, Location, Position, Range, TypeDefinitionParams, Uri as Url};
2
3use super::*;
4
5fn definition(tester: &TestProject<'_>, position: Position) -> Option<Location> {
6 tester.at(position, |engine, param, _| {
7 let params = DefinitionParams {
8 text_document_position_params: param,
9 work_done_progress_params: Default::default(),
10 partial_result_params: Default::default(),
11 };
12 let response = engine.goto_definition(params);
13 response.result.unwrap()
14 })
15}
16
17fn pretty_definition(project: TestProject<'_>, position_finder: PositionFinder) -> String {
18 let position = position_finder.find_position(project.src);
19 let location = definition(&project, position).expect("a location to jump to");
20 jump_locations_to_string(project, position, vec![location])
21}
22
23fn type_definition(tester: &TestProject<'_>, position: Position) -> Vec<Location> {
24 tester.at(position, |engine, param, _| {
25 let params = TypeDefinitionParams {
26 text_document_position_params: param,
27 work_done_progress_params: Default::default(),
28 partial_result_params: Default::default(),
29 };
30 let response = engine.goto_type_definition(params);
31
32 response.result.unwrap()
33 })
34}
35
36fn pretty_type_definition(project: TestProject<'_>, position_finder: PositionFinder) -> String {
37 let position = position_finder.find_position(project.src);
38 let location = type_definition(&project, position);
39 format!(
40 "Jumping to type definition\n\n{}",
41 jump_locations_to_string(project, position, location)
42 )
43}
44
45fn jump_locations_to_string(
46 project: TestProject<'_>,
47 original_position: Position,
48 locations: Vec<Location>,
49) -> String {
50 let src = hover::show_hover(
51 project.src,
52 Range {
53 start: original_position,
54 end: original_position,
55 },
56 original_position,
57 );
58
59 let destinations = locations
60 .iter()
61 .map(|location| {
62 let pretty_destination = location
63 .uri
64 .path_segments()
65 .expect("a location to jump to")
66 // To make snapshots the same both on windows and unix systems we need
67 // to discard windows' `C:` path segment at the beginning of a uri.
68 .skip_while(|segment| *segment == "C:")
69 .join("/");
70
71 let destination_code = hover::show_hover(
72 project
73 .src_from_module_url(&location.uri)
74 .expect("a module to jump to"),
75 location.range,
76 location.range.start,
77 );
78
79 format!(
80 "----- Jumped to `{pretty_destination}`
81{destination_code}"
82 )
83 })
84 .join("\n\n");
85
86 format!(
87 "----- Jumping from `src/app.gleam`
88{src}
89{destinations}",
90 )
91}
92
93#[macro_export]
94macro_rules! assert_goto {
95 ($src:literal, $position:expr) => {
96 let project = TestProject::for_source($src);
97 assert_goto!(project, $position);
98 };
99 ($project:expr, $position:expr) => {
100 let output = pretty_definition($project, $position);
101 insta::assert_snapshot!(insta::internals::AutoName, output);
102 };
103}
104
105#[macro_export]
106macro_rules! assert_goto_type {
107 ($src:literal, $position:expr) => {
108 let project = TestProject::for_source($src);
109 assert_goto_type!(project, $position);
110 };
111 ($project:expr, $position:expr) => {
112 let output = pretty_type_definition($project, $position);
113 insta::assert_snapshot!(insta::internals::AutoName, output);
114 };
115}
116
117#[test]
118fn goto_type_definition_in_same_file() {
119 assert_goto_type!(
120 "
121pub type Wibble {
122 Wibble
123}
124
125pub fn main() {
126 let x = Wibble
127 x
128}",
129 find_position_of("x").nth_occurrence(2)
130 );
131}
132
133#[test]
134fn goto_type_definition_in_different_file_of_same_project() {
135 let src = "
136import wibble.{type Wibble}
137
138pub fn main() {
139 use_wibble(todo)
140}
141
142pub fn use_wibble(wibble: Wibble) { todo }
143";
144
145 assert_goto_type!(
146 TestProject::for_source(src).add_module("wibble", "pub type Wibble"),
147 find_position_of("todo")
148 );
149}
150
151#[test]
152fn goto_type_definition_in_different_file_of_dependency() {
153 let src = "
154import wibble.{type Wibble}
155
156pub fn main() {
157 use_wibble(todo)
158}
159
160pub fn use_wibble(wibble: Wibble) { todo }
161";
162
163 assert_goto_type!(
164 TestProject::for_source(src).add_dep_module("wibble", "pub type Wibble"),
165 find_position_of("todo")
166 );
167}
168
169#[test]
170fn goto_type_definition_can_jump_to_multiple_types() {
171 let src = "
172import wibble.{type Wibble, Wibble}
173import box.{Box}
174
175pub fn main() {
176 let a = Box(Wibble)
177}
178";
179
180 assert_goto_type!(
181 TestProject::for_source(src)
182 .add_dep_module("wibble", "pub type Wibble { Wibble }")
183 .add_dep_module("box", "pub type Box(a) { Box(a) }"),
184 find_position_of("let a")
185 );
186}
187
188#[test]
189fn goto_type_definition_can_jump_to_all_types_in_a_tuple() {
190 let src = "
191import wibble.{type Wibble}
192import wobble.{type Wobble}
193import box.{type Box}
194
195pub fn main() {
196 let a: #(Box(Wibble), Wobble) = todo
197}
198";
199
200 assert_goto_type!(
201 TestProject::for_source(src)
202 .add_dep_module("wibble", "pub type Wibble { Wibble }")
203 .add_dep_module("wobble", "pub type Wobble { Wobble }")
204 .add_dep_module("box", "pub type Box(a) { Box(a) }"),
205 find_position_of("let a")
206 );
207}
208
209#[test]
210fn goto_type_definition_can_jump_to_all_types_in_a_function_type() {
211 let src = "
212import wibble.{type Wibble}
213import wobble.{type Wobble}
214import box.{type Box}
215
216pub fn main() {
217 let a = fn(wibble: Wibble) { box.Box(wobble.Wobble) }
218}
219";
220
221 assert_goto_type!(
222 TestProject::for_source(src)
223 .add_dep_module("wibble", "pub type Wibble { Wibble }")
224 .add_dep_module("wobble", "pub type Wobble { Wobble }")
225 .add_dep_module("box", "pub type Box(a) { Box(a) }"),
226 find_position_of("let a")
227 );
228}
229
230#[test]
231fn goto_definition_local_variable() {
232 assert_goto!(
233 "
234pub fn main() {
235 let x = 1
236 x
237}",
238 find_position_of("x").nth_occurrence(2)
239 );
240}
241
242#[test]
243fn goto_definition_record_update() {
244 assert_goto!(
245 "
246pub type Wibble { Wibble(one: Int, two: Int) }
247
248pub fn main() {
249 Wibble(..todo, one: 1)
250}",
251 find_position_of("Wibble").nth_occurrence(3)
252 );
253}
254
255#[test]
256fn goto_definition_same_module_constants() {
257 assert_goto!(
258 "
259const x = 1
260
261pub fn main() {
262 x
263}",
264 find_position_of("x").nth_occurrence(2)
265 );
266}
267
268#[test]
269fn goto_definition_same_module_functions() {
270 assert_goto!(
271 "
272fn add_2(x) {
273 x + 2
274}
275
276pub fn main() {
277 add_2(1)
278}",
279 find_position_of("add_2(1)")
280 );
281}
282
283#[test]
284fn goto_definition_same_module_records() {
285 assert_goto!(
286 "
287pub type Rec {
288 Var1(Int)
289 Var2(Int, Int)
290}
291
292pub fn main() {
293 let a = Var1(1)
294 let b = Var2(2, 3)
295}",
296 find_position_of("Var1(1)")
297 );
298}
299
300#[test]
301fn goto_definition_imported_module_constants() {
302 let code = "
303import example_module
304fn main() {
305 example_module.my_num
306}
307";
308
309 assert_goto!(
310 TestProject::for_source(code).add_module("example_module", "pub const my_num = 1"),
311 find_position_of("my_num")
312 );
313}
314
315#[test]
316fn goto_definition_unqualified_imported_module_constants() {
317 let code = "
318import example_module.{my_num}
319fn main() {
320 my_num
321}
322";
323
324 assert_goto!(
325 TestProject::for_source(code).add_module("example_module", "pub const my_num = 1"),
326 find_position_of("my_num").nth_occurrence(2)
327 );
328}
329
330#[test]
331fn goto_definition_module_function_calls() {
332 let code = "
333import example_module
334fn main() {
335 example_module.my_fn
336}
337";
338
339 assert_goto!(
340 TestProject::for_source(code).add_module("example_module", "pub fn my_fn() { Nil }"),
341 find_position_of("my_fn")
342 );
343}
344
345#[test]
346fn goto_definition_imported_module_records() {
347 let dep_src = "
348pub type Rec {
349 Var1(Int)
350 Var2(Int, Int)
351}";
352
353 let code = "
354import example_module
355fn main() {
356 example_module.Var1(1)
357}
358";
359
360 assert_goto!(
361 TestProject::for_source(code).add_module("example_module", dep_src),
362 find_position_of("Var1(1)")
363 );
364}
365
366#[test]
367fn goto_definition_unqualified_imported_module_records() {
368 let dep_src = "
369pub type Rec {
370 Var1(Int)
371 Var2(Int, Int)
372}";
373
374 let code = "
375import example_module.{Var1}
376fn main() {
377 Var1(1)
378}
379";
380
381 assert_goto!(
382 TestProject::for_source(code).add_module("example_module", dep_src),
383 find_position_of("Var1(1)").under_char('a')
384 );
385}
386
387#[test]
388fn goto_definition_external_module_constants() {
389 let code = "
390import example_module
391fn main() {
392 example_module.my_num
393}
394";
395
396 assert_goto!(
397 TestProject::for_source(code).add_hex_module("example_module", "pub const my_num = 1"),
398 find_position_of("my_num").under_char('u')
399 );
400}
401
402#[test]
403fn goto_definition_external_module_function_calls() {
404 let code = "
405import example_module
406fn main() {
407 example_module.my_fn
408}
409";
410
411 assert_goto!(
412 TestProject::for_source(code).add_hex_module("example_module", "pub fn my_fn() { Nil }"),
413 find_position_of("my_fn")
414 );
415}
416
417#[test]
418fn goto_definition_external_module_function_calls_with_multiple_compiles() {
419 let dep = "pub fn my_fn() { Nil }";
420 let code = "
421import example_module
422fn main() {
423 example_module.my_fn
424}
425";
426
427 let (mut engine, position_param) = TestProject::for_source(code)
428 .add_hex_module("example_module", dep)
429 .positioned_with_io(Position::new(3, 20));
430
431 let params = DefinitionParams {
432 text_document_position_params: position_param.clone(),
433 work_done_progress_params: Default::default(),
434 partial_result_params: Default::default(),
435 };
436 let response = engine.goto_definition(params.clone());
437 let response = response.result.unwrap();
438
439 assert_eq!(
440 response,
441 Some(Location {
442 uri: Url::from_file_path(Utf8PathBuf::from(if cfg!(target_family = "windows") {
443 r"\\?\C:\build\packages\hex\src\example_module.gleam"
444 } else {
445 "/build/packages/hex/src/example_module.gleam"
446 }))
447 .unwrap(),
448 range: Range {
449 start: Position {
450 line: 0,
451 character: 0
452 },
453 end: Position {
454 line: 0,
455 character: 14
456 }
457 }
458 })
459 );
460
461 engine.compiler.sources.clear();
462 let response = engine.compile_please();
463 assert!(response.result.is_ok());
464
465 let response = engine.goto_definition(params.clone());
466 let response = response.result.unwrap();
467
468 assert_eq!(
469 response,
470 Some(Location {
471 uri: Url::from_file_path(Utf8PathBuf::from(if cfg!(target_family = "windows") {
472 r"\\?\C:\build\packages\hex\src\example_module.gleam"
473 } else {
474 "/build/packages/hex/src/example_module.gleam"
475 }))
476 .unwrap(),
477 range: Range {
478 start: Position {
479 line: 0,
480 character: 0
481 },
482 end: Position {
483 line: 0,
484 character: 14
485 }
486 }
487 })
488 )
489}
490
491#[test]
492fn goto_definition_path_module_function_calls_with_multiple_compiles() {
493 let dep = "pub fn my_fn() { Nil }";
494 let code = "
495import example_module
496fn main() {
497 example_module.my_fn
498}
499";
500
501 let (mut engine, position_param) = TestProject::for_source(code)
502 .add_dep_module("example_module", dep)
503 .positioned_with_io(Position::new(3, 20));
504
505 let params = DefinitionParams {
506 text_document_position_params: position_param.clone(),
507 work_done_progress_params: Default::default(),
508 partial_result_params: Default::default(),
509 };
510
511 let response = engine.goto_definition(params.clone());
512 let response = response.result.unwrap();
513
514 assert_eq!(
515 response,
516 Some(Location {
517 uri: Url::from_file_path(Utf8PathBuf::from(if cfg!(target_family = "windows") {
518 r"\\?\C:\dep\src\example_module.gleam"
519 } else {
520 "/dep/src/example_module.gleam"
521 }))
522 .unwrap(),
523 range: Range {
524 start: Position {
525 line: 0,
526 character: 0
527 },
528 end: Position {
529 line: 0,
530 character: 14
531 }
532 }
533 })
534 );
535
536 engine.compiler.sources.clear();
537 let response = engine.compile_please();
538 assert!(response.result.is_ok());
539
540 let response = engine.goto_definition(params.clone());
541 let response = response.result.unwrap();
542
543 assert_eq!(
544 response,
545 Some(Location {
546 uri: Url::from_file_path(Utf8PathBuf::from(if cfg!(target_family = "windows") {
547 r"\\?\C:\dep\src\example_module.gleam"
548 } else {
549 "/dep/src/example_module.gleam"
550 }))
551 .unwrap(),
552 range: Range {
553 start: Position {
554 line: 0,
555 character: 0
556 },
557 end: Position {
558 line: 0,
559 character: 14
560 }
561 }
562 })
563 )
564}
565
566#[test]
567fn goto_definition_external_module_records() {
568 let hex_src = "
569pub type Rec {
570 Var1(Int)
571 Var2(Int, Int)
572}
573";
574
575 let code = "
576import example_module
577fn main() {
578 example_module.Var1(1)
579}
580";
581
582 assert_goto!(
583 TestProject::for_source(code).add_hex_module("example_module", hex_src),
584 find_position_of("Var1(1)").under_char('r')
585 );
586}
587
588#[test]
589fn goto_definition_path_module_function_calls() {
590 let code = "
591import example_module
592fn main() {
593 example_module.my_fn
594}
595";
596
597 assert_goto!(
598 TestProject::for_source(code).add_dep_module("example_module", "pub fn my_fn() { Nil }"),
599 find_position_of("my_fn").under_char('y')
600 );
601}
602
603#[test]
604fn goto_definition_type() {
605 assert_goto!(
606 "
607pub type Rec {
608 Var1(Int)
609 Var2(Int, Int)
610}
611
612pub fn make_var() -> Rec {
613 Var1(1)
614}",
615 find_position_of("Rec").nth_occurrence(2)
616 );
617}
618
619#[test]
620fn goto_definition_type_in_module() {
621 let hex_src = "
622pub type Rec {
623 Var1(Int)
624 Var2(Int, Int)
625}
626";
627
628 let code = "
629import example_module
630fn make_var() -> example_module.Rec {
631 example_module.Var1(1)
632}
633";
634
635 assert_goto!(
636 TestProject::for_source(code).add_hex_module("example_module", hex_src),
637 find_position_of("Rec")
638 );
639}
640
641#[test]
642fn goto_definition_type_in_path_dep() {
643 let dep = "
644pub type Rec {
645 Var1(Int)
646 Var2(Int, Int)
647}
648";
649
650 let code = "
651import example_module
652fn make_var() -> example_module.Rec {
653 example_module.Var1(1)
654}
655";
656
657 assert_goto!(
658 TestProject::for_source(code).add_dep_module("example_module", dep),
659 find_position_of("Rec")
660 );
661}
662
663#[test]
664fn goto_definition_deep_type_in_module() {
665 let hex_src = "
666pub type Wobble {
667 Wobble(Int)
668}
669
670pub type Wibble(a) {
671 Wibble(a)
672}
673
674pub type Wabble(a) {
675 Wabble(a)
676}
677";
678
679 let code = "
680import example_module
681fn make_var() -> example_module.Wabble(example_module.Wibble(example_module.Wobble)) {
682 example_module.Wabble(example_module.Wibble(example_module.Wobble(1)))
683}
684";
685
686 assert_goto!(
687 TestProject::for_source(code).add_hex_module("example_module", hex_src),
688 find_position_of("Wobble").under_char('o')
689 );
690}
691
692#[test]
693fn goto_definition_import() {
694 let code = "
695import example_module
696fn main() {
697 example_module.my_num
698}
699";
700
701 assert_goto!(
702 TestProject::for_source(code).add_module("example_module", "pub const my_num = 1"),
703 find_position_of("example_module").under_char('p')
704 );
705}
706
707#[test]
708fn goto_definition_import_aliased() {
709 let code = "
710import example_module as example
711fn main() {
712 example.my_num
713}
714";
715
716 assert_goto!(
717 TestProject::for_source(code).add_module("example_module", "pub const my_num = 1"),
718 find_position_of("example")
719 .nth_occurrence(2)
720 .under_char('x')
721 );
722}
723
724#[test]
725fn goto_definition_import_unqualified_value() {
726 let code = "
727import example_module.{my_num}
728fn main() {
729 my_num
730}
731";
732
733 assert_goto!(
734 TestProject::for_source(code).add_module("example_module", "pub const my_num = 1"),
735 find_position_of("my_num").under_char('_')
736 );
737}
738
739#[test]
740fn goto_definition_unqualified_function() {
741 let code = "
742import wibble.{wobble}
743fn main() {
744 wobble()
745}
746";
747
748 assert_goto!(
749 TestProject::for_source(code).add_module("wibble", "pub fn wobble() {}"),
750 find_position_of("wobble").nth_occurrence(2).under_char('o')
751 );
752}
753
754#[test]
755fn goto_definition_import_unqualified_type() {
756 let code = "
757import example_module.{type MyType}
758fn main() -> MyType {
759 0
760}
761";
762
763 assert_goto!(
764 TestProject::for_source(code).add_module("example_module", "pub type MyType = Int"),
765 find_position_of("MyType").under_char('T')
766 );
767}
768
769// https://github.com/gleam-lang/gleam/issues/3610
770#[test]
771fn goto_definition_of_external_function_in_same_module() {
772 let code = "
773@external(erlang, \"wibble\", \"wobble\")
774fn external_function() -> Nil
775
776fn main() {
777 external_function()
778}
779";
780
781 assert_goto!(
782 TestProject::for_source(code),
783 find_position_of("external_function")
784 .nth_occurrence(2)
785 .under_char('l')
786 );
787}
788
789// https://github.com/gleam-lang/gleam/issues/3758
790#[test]
791fn goto_definition_from_anonymous_function() {
792 let code = "
793pub type Wibble
794
795pub fn main() {
796 fn(w: Wibble) { todo }
797}
798";
799
800 assert_goto!(
801 TestProject::for_source(code),
802 find_position_of("w: Wibble").under_char('i')
803 );
804}
805
806#[test]
807fn goto_definition_module() {
808 let code = "
809import wibble
810
811pub fn main() {
812 wibble.wibble()
813}
814";
815
816 assert_goto!(
817 TestProject::for_source(code).add_module("wibble", "pub fn wibble() {}"),
818 find_position_of("wibble.").under_char('i')
819 );
820}
821
822#[test]
823fn goto_definition_constant() {
824 assert_goto!(
825 "
826const value = 25
827
828const my_constant = value
829",
830 find_position_of("= value").under_char('a')
831 );
832}
833
834#[test]
835fn goto_definition_constant_record() {
836 assert_goto!(
837 "
838type Wibble {
839 Wibble(Int)
840}
841
842const wibble = Wibble(10)
843",
844 find_position_of("Wibble(10)").under_char('l')
845 );
846}
847
848#[test]
849fn goto_definition_imported_constant() {
850 let src = "
851import wibble
852
853const my_constant = wibble.value
854";
855
856 assert_goto!(
857 TestProject::for_source(src).add_hex_module("wibble", "pub const value = 10"),
858 find_position_of("value").under_char('v')
859 );
860}
861
862#[test]
863fn goto_definition_constant_imported_record() {
864 let src = "
865import wibble
866
867const my_constant = wibble.Wibble(10)
868";
869
870 assert_goto!(
871 TestProject::for_source(src).add_hex_module("wibble", "pub type Wibble { Wibble(Int) }"),
872 find_position_of("Wibble(10)").under_char('W')
873 );
874}
875
876#[test]
877fn goto_definition_from_alternative_pattern() {
878 assert_goto!(
879 "
880type Wibble {
881 Wibble
882 Wobble
883}
884
885fn warble(wibble: Wibble) {
886 case wibble {
887 Wibble | Wobble -> 0
888 }
889}
890",
891 find_position_of("Wobble ->")
892 );
893}
894
895#[test]
896fn goto_definition_of_local_variable_from_guard() {
897 assert_goto!(
898 "
899pub fn main() {
900 let wibble = True
901 let wobble = False
902 case wibble {
903 True if wobble -> !wibble
904 False if !wobble -> wibble
905 _ -> wobble
906 }
907}
908",
909 find_position_of("wobble").nth_occurrence(2).under_char('o')
910 );
911}
912
913#[test]
914fn goto_definition_of_record_from_guard() {
915 assert_goto!(
916 "
917type Wibble {
918 Wibble
919 Wobble
920}
921
922pub fn main() {
923 let wibble = True
924 let wobble = Wibble
925 case wibble {
926 True if wobble == Wibble -> !wibble
927 False if wobble == Wobble -> wibble
928 _ -> Wibble
929 }
930}
931",
932 find_position_of("== Wibble").under_char('l')
933 );
934}
935
936#[test]
937fn goto_definition_of_module_select_from_guard() {
938 let src = "
939import mod
940
941pub fn main() {
942 let wibble = True
943 case wibble {
944 True if mod.wibble < 5 -> !wibble
945 False if mod.wibble != 10 -> wibble
946 _ -> mod.wibble + 1
947 }
948}
949";
950 assert_goto!(
951 TestProject::for_source(src).add_module("mod", "pub const wibble = 10"),
952 find_position_of("mod.wibble").under_char('w')
953 );
954}
955
956#[test]
957fn goto_definition_of_record_module_select_from_guard() {
958 let src = "
959import mod
960
961pub fn main() {
962 let wibble = True
963 let wobble = mod.Wibble
964 case wibble {
965 True if wobble == mod.Wobble -> !wibble
966 False if wobble == mod.Wibble -> wibble
967 _ -> mod.Wibble
968 }
969}
970";
971 assert_goto!(
972 TestProject::for_source(src).add_module("mod", "pub type Wobble { Wibble Wobble }"),
973 find_position_of("== mod.Wibble").under_char('i')
974 );
975}
976
977#[test]
978fn goto_for_invalid_constant_todo_message_still_works() {
979 assert_goto!(
980 "
981const wibble = 1
982const wobble = todo as wibble
983",
984 find_position_of("wibble").nth_occurrence(2)
985 );
986}
987
988#[test]
989fn goto_for_invalid_constant_todo_message_still_works_2() {
990 assert_goto!(
991 "
992const wibble = 1
993const wobble = todo as [wibble]
994",
995 find_position_of("wibble").nth_occurrence(2)
996 );
997}