Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / language-server / src / tests / definition.rs
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}