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