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

Configure Feed

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

gleam / compiler-core / src / docs / tests.rs
31 kB 1299 lines
1use std::{ 2 collections::{HashMap, HashSet}, 3 time::SystemTime, 4}; 5 6use super::{ 7 Dependency, DependencyKind, DocumentationConfig, SearchData, SearchItem, SearchItemType, 8 SearchProgrammingLanguage, 9 printer::{PrintOptions, Printer}, 10 source_links::SourceLinker, 11}; 12use crate::{ 13 build::{ 14 self, Mode, NullTelemetry, Origin, PackageCompiler, StaleTracker, 15 TargetCodegenConfiguration, 16 }, 17 config::{DocsPage, PackageConfig, Repository}, 18 docs::DocContext, 19 io::{FileSystemWriter, memory::InMemoryFileSystem}, 20 paths::ProjectPaths, 21 type_, 22 uid::UniqueIdGenerator, 23 version::COMPILER_VERSION, 24 warning::WarningEmitter, 25}; 26use camino::Utf8PathBuf; 27use ecow::EcoString; 28use hexpm::version::Version; 29use http::Uri; 30use itertools::Itertools; 31use serde_json::to_string as serde_to_string; 32 33#[derive(Default)] 34struct CompileWithMarkdownPagesOpts { 35 hex_publish: Option<DocContext>, 36} 37 38fn compile_with_markdown_pages( 39 config: PackageConfig, 40 modules: Vec<(&str, &str)>, 41 markdown_pages: Vec<(&str, &str)>, 42 opts: CompileWithMarkdownPagesOpts, 43) -> EcoString { 44 let fs = InMemoryFileSystem::new(); 45 for (name, src) in modules { 46 fs.write(&Utf8PathBuf::from(format!("/src/{name}")), src) 47 .unwrap(); 48 } 49 50 // We're saving the pages under a different `InMemoryFileSystem` for these 51 // tests so we don't have to juggle with borrows and lifetimes. 52 // The package compiler is going to take ownership of `fs` but later 53 // `generate_html` also needs a `FileSystemReader` to go and read the 54 // markdown pages' content. 55 let pages_fs = InMemoryFileSystem::new(); 56 for (title, src) in markdown_pages.iter() { 57 pages_fs 58 .write(&Utf8PathBuf::from(format!("{title}.md")), src) 59 .unwrap(); 60 } 61 62 let ids = UniqueIdGenerator::new(); 63 let mut type_manifests = im::HashMap::new(); 64 let mut defined_modules = im::HashMap::new(); 65 let warnings = WarningEmitter::null(); 66 let target = TargetCodegenConfiguration::Erlang { app_file: None }; 67 68 let root = Utf8PathBuf::from("/"); 69 let build = root.join("build"); 70 let lib = root.join("lib"); 71 let paths = ProjectPaths::new(root.clone()); 72 let mut compiler = 73 PackageCompiler::new(&config, Mode::Dev, &root, &build, &lib, &target, ids, fs); 74 compiler.write_entrypoint = false; 75 compiler.write_metadata = false; 76 compiler.compile_beam_bytecode = true; 77 let mut modules = compiler 78 .compile( 79 &warnings, 80 &mut type_manifests, 81 &mut defined_modules, 82 &mut StaleTracker::default(), 83 &mut HashSet::new(), 84 &NullTelemetry, 85 ) 86 .unwrap() 87 .modules; 88 89 for module in &mut modules { 90 module.attach_doc_and_module_comments(); 91 } 92 93 let docs_pages = markdown_pages 94 .into_iter() 95 .map(|(title, _)| DocsPage { 96 title: (*title).into(), 97 path: format!("{title}.html"), 98 source: format!("{title}.md").into(), 99 }) 100 .collect_vec(); 101 102 super::generate_html( 103 &paths, 104 DocumentationConfig { 105 package_config: &config, 106 dependencies: HashMap::new(), 107 analysed: &modules, 108 docs_pages: &docs_pages, 109 rendering_timestamp: SystemTime::UNIX_EPOCH, 110 context: if let Some(doc_context) = opts.hex_publish { 111 doc_context 112 } else { 113 DocContext::HexPublish 114 }, 115 }, 116 pages_fs, 117 ) 118 .into_iter() 119 .filter(|file| file.path.extension() == Some("html")) 120 .sorted_by(|a, b| a.path.cmp(&b.path)) 121 .flat_map(|file| { 122 Some(format!( 123 "//// {}\n\n{}\n\n", 124 file.path.as_str(), 125 file.content 126 .text()? 127 .replace(COMPILER_VERSION, "GLEAM_VERSION_HERE") 128 )) 129 }) 130 .collect::<String>() 131 .chars() 132 .collect() 133} 134 135pub fn compile(config: PackageConfig, modules: Vec<(&str, &str)>) -> EcoString { 136 compile_with_markdown_pages( 137 config, 138 modules, 139 vec![], 140 CompileWithMarkdownPagesOpts::default(), 141 ) 142} 143 144fn compile_documentation( 145 module_name: &str, 146 module_src: &str, 147 modules: Vec<(&str, &str, &str)>, 148 dependency_kind: DependencyKind, 149 options: PrintOptions, 150) -> EcoString { 151 let module = type_::tests::compile_module(module_name, module_src, None, modules.clone()) 152 .expect("Module should compile successfully"); 153 154 let mut config = PackageConfig::default(); 155 config.name = "thepackage".into(); 156 let paths = ProjectPaths::new("/".into()); 157 let build_module = build::Module { 158 name: "main".into(), 159 code: module_src.into(), 160 mtime: SystemTime::now(), 161 input_path: "/".into(), 162 origin: Origin::Src, 163 ast: module, 164 extra: Default::default(), 165 dependencies: Default::default(), 166 }; 167 168 let source_links = SourceLinker::new(&paths, &config, &build_module); 169 170 let module = build_module.ast; 171 let dependencies = modules 172 .iter() 173 .map(|(package, _, _)| { 174 ( 175 EcoString::from(*package), 176 Dependency { 177 version: Version::new(1, 0, 0), 178 kind: dependency_kind, 179 }, 180 ) 181 }) 182 .collect(); 183 184 let mut printer = Printer::new( 185 module.type_info.package.clone(), 186 module.name.clone(), 187 &module.names, 188 &dependencies, 189 ); 190 printer.set_options(options); 191 192 let types = module 193 .definitions 194 .iter() 195 .filter_map( 196 |definition: &crate::ast::Definition< 197 std::sync::Arc<type_::Type>, 198 crate::ast::TypedExpr, 199 EcoString, 200 EcoString, 201 >| printer.type_definition(&source_links, definition), 202 ) 203 .sorted() 204 .collect_vec(); 205 206 let values = module 207 .definitions 208 .iter() 209 .filter_map(|definition| printer.value(&source_links, definition)) 210 .sorted() 211 .collect_vec(); 212 213 let mut output = EcoString::new(); 214 215 output.push_str("---- SOURCE CODE\n"); 216 for (_package, name, src) in modules { 217 output.push_str(&format!("-- {name}.gleam\n{src}\n\n")); 218 } 219 output.push_str("-- "); 220 output.push_str(module_name); 221 output.push_str(".gleam\n"); 222 output.push_str(module_src); 223 224 if !types.is_empty() { 225 output.push_str("\n\n---- TYPES"); 226 } 227 for type_ in types { 228 output.push_str("\n\n--- "); 229 output.push_str(type_.name); 230 if !type_.documentation.is_empty() { 231 output.push('\n'); 232 output.push_str(&type_.documentation); 233 } 234 output.push_str("\n<pre><code>"); 235 output.push_str(&type_.definition); 236 output.push_str("</code></pre>"); 237 238 if !type_.constructors.is_empty() { 239 output.push_str("\n\n-- CONSTRUCTORS"); 240 } 241 for constructor in type_.constructors { 242 output.push_str("\n\n"); 243 if !constructor.documentation.is_empty() { 244 output.push_str(&constructor.documentation); 245 output.push('\n'); 246 } 247 output.push_str("<pre><code>"); 248 output.push_str(&constructor.definition); 249 output.push_str("</code></pre>"); 250 } 251 } 252 253 if !values.is_empty() { 254 output.push_str("\n\n---- VALUES"); 255 } 256 for value in values { 257 output.push_str("\n\n--- "); 258 output.push_str(value.name); 259 if !value.documentation.is_empty() { 260 output.push('\n'); 261 output.push_str(&value.documentation); 262 } 263 output.push_str("\n<pre><code>"); 264 output.push_str(&value.definition); 265 output.push_str("</code></pre>"); 266 } 267 268 output 269} 270 271macro_rules! assert_documentation { 272 ($src:literal $(,)?) => { 273 assert_documentation!($src, PrintOptions::all()); 274 }; 275 276 ($src:literal, $options:expr $(,)?) => { 277 let output = compile_documentation("main", $src, Vec::new(), DependencyKind::Hex, $options); 278 insta::assert_snapshot!(output); 279 }; 280 281 ($(($name:expr, $module_src:literal)),+, $src:literal $(,)?) => { 282 let output = compile_documentation( 283 "main", 284 $src, 285 vec![$(("thepackage", $name, $module_src)),*], 286 DependencyKind::Hex, 287 PrintOptions::all(), 288 ); 289 insta::assert_snapshot!(output); 290 }; 291 292 ($(($name:expr, $module_src:literal)),+, $src:literal, $options:expr $(,)?) => { 293 let output = compile_documentation( 294 "main", 295 $src, 296 vec![$(("thepackage", $name, $module_src)),*], 297 DependencyKind::Hex, 298 $options, 299 ); 300 insta::assert_snapshot!(output); 301 }; 302 303 ($(($name:expr, $module_src:literal)),+, $main_module:literal, $src:literal, $options:expr $(,)?) => { 304 let output = compile_documentation( 305 $main_module, 306 $src, 307 vec![$(("thepackage", $name, $module_src)),*], 308 DependencyKind::Hex, 309 $options, 310 ); 311 insta::assert_snapshot!(output); 312 }; 313 314 ($(($package:expr, $name:expr, $module_src:literal)),+, $src:literal $(,)?) => { 315 let output = compile_documentation( 316 "main", 317 $src, 318 vec![$(($package, $name, $module_src)),*], 319 DependencyKind::Hex, 320 PrintOptions::all(), 321 ); 322 insta::assert_snapshot!(output); 323 }; 324 325 ($(($package:expr, $name:expr, $module_src:literal)),+, $src:literal, $options:expr $(,)?) => { 326 let output = compile_documentation( 327 "main", 328 $src, 329 vec![$(($package, $name, $module_src)),*], 330 DependencyKind::Hex, 331 $options, 332 ); 333 insta::assert_snapshot!(output); 334 }; 335 336 (git: $(($package:expr, $name:expr, $module_src:literal)),+, $src:literal, $options:expr $(,)?) => { 337 let output = compile_documentation( 338 "main", 339 $src, 340 vec![$(($package, $name, $module_src)),*], 341 DependencyKind::Git, 342 $options, 343 ); 344 insta::assert_snapshot!(output); 345 }; 346 347 (path: $(($package:expr, $name:expr, $module_src:literal)),+, $src:literal, $options:expr $(,)?) => { 348 let output = compile_documentation( 349 "main", 350 $src, 351 vec![$(($package, $name, $module_src)),*], 352 DependencyKind::Path, 353 $options, 354 ); 355 insta::assert_snapshot!(output); 356 }; 357} 358 359#[test] 360fn hello_docs() { 361 let mut config = PackageConfig::default(); 362 config.name = EcoString::from("test_project_name"); 363 let modules = vec![( 364 "app.gleam", 365 r#" 366/// Here is some documentation 367pub fn one() { 368 1 369} 370"#, 371 )]; 372 insta::assert_snapshot!(compile(config, modules)); 373} 374 375#[test] 376fn ignored_argument_is_called_arg() { 377 let mut config = PackageConfig::default(); 378 config.name = EcoString::from("test_project_name"); 379 let modules = vec![("app.gleam", "pub fn one(_) { 1 }")]; 380 insta::assert_snapshot!(compile(config, modules)); 381} 382 383// https://github.com/gleam-lang/gleam/issues/2347 384#[test] 385fn tables() { 386 let mut config = PackageConfig::default(); 387 config.name = EcoString::from("test_project_name"); 388 let modules = vec![( 389 "app.gleam", 390 r#" 391/// | heading 1 | heading 2 | 392/// |--------------|--------------| 393/// | row 1 cell 1 | row 1 cell 2 | 394/// | row 2 cell 1 | row 2 cell 2 | 395/// 396pub fn one() { 397 1 398} 399"#, 400 )]; 401 insta::assert_snapshot!(compile(config, modules)); 402} 403 404// https://github.com/gleam-lang/gleam/issues/2202 405#[test] 406fn long_function_wrapping() { 407 let mut config = PackageConfig::default(); 408 config.name = EcoString::from("test_project_name"); 409 let modules = vec![( 410 "app.gleam", 411 r#" 412pub type Option(t) { 413 Some(t) 414 None 415} 416 417/// Returns the first value if it is `Some`, otherwise evaluates the given 418/// function for a fallback value. 419/// 420pub fn lazy_or(first: Option(a), second: fn() -> Option(a)) -> Option(a) { 421 case first { 422 Some(_) -> first 423 None -> second() 424 } 425} 426"#, 427 )]; 428 429 insta::assert_snapshot!(compile(config, modules)); 430} 431 432#[test] 433fn internal_definitions_are_not_included() { 434 let mut config = PackageConfig::default(); 435 config.name = EcoString::from("test_project_name"); 436 let modules = vec![( 437 "app.gleam", 438 r#" 439@internal 440pub const wibble = 1 441 442@internal 443pub type Wibble = Int 444 445@internal 446pub type Wobble { Wobble } 447 448@internal 449pub fn one() { 1 } 450"#, 451 )]; 452 insta::assert_snapshot!(compile(config, modules)); 453} 454 455// https://github.com/gleam-lang/gleam/issues/2561 456#[test] 457fn discarded_arguments_are_not_shown() { 458 let mut config = PackageConfig::default(); 459 config.name = EcoString::from("test_project_name"); 460 let modules = vec![("app.gleam", "pub fn discard(_discarded: a) -> Int { 1 }")]; 461 insta::assert_snapshot!(compile(config, modules)); 462} 463 464// https://github.com/gleam-lang/gleam/issues/2631 465#[test] 466fn docs_of_a_type_constructor_are_not_used_by_the_following_function() { 467 let mut config = PackageConfig::default(); 468 config.name = EcoString::from("test_project_name"); 469 let modules = vec![( 470 "app.gleam", 471 r#" 472pub type Wibble { 473 Wobble( 474 /// Documentation!! 475 wabble: Int, 476 ) 477} 478 479pub fn main() { todo } 480"#, 481 )]; 482 insta::assert_snapshot!(compile(config, modules)); 483} 484 485#[test] 486fn markdown_code_from_standalone_pages_is_not_trimmed() { 487 let mut config = PackageConfig::default(); 488 config.name = EcoString::from("test_project_name"); 489 let pages = vec![( 490 "one", 491 " 492This is an example code snippet that should be indented 493```gleam 494pub fn indentation_test() { 495 todo as \"This line should be indented by two spaces\" 496} 497```", 498 )]; 499 insta::assert_snapshot!(compile_with_markdown_pages( 500 config, 501 vec![], 502 pages, 503 CompileWithMarkdownPagesOpts::default() 504 )); 505} 506 507#[test] 508fn markdown_code_from_function_comment_is_trimmed() { 509 let mut config = PackageConfig::default(); 510 config.name = EcoString::from("test_project_name"); 511 let modules = vec![( 512 "app.gleam", 513 " 514/// Here's an example code snippet: 515/// ``` 516/// wibble 517/// |> wobble 518/// ``` 519/// 520pub fn indentation_test() { 521 todo 522} 523", 524 )]; 525 insta::assert_snapshot!(compile(config, modules)); 526} 527 528#[test] 529fn markdown_code_from_module_comment_is_trimmed() { 530 let mut config = PackageConfig::default(); 531 config.name = EcoString::from("test_project_name"); 532 let modules = vec![( 533 "app.gleam", 534 " 535//// Here's an example code snippet: 536//// ``` 537//// wibble 538//// |> wobble 539//// ``` 540//// 541", 542 )]; 543 insta::assert_snapshot!(compile(config, modules)); 544} 545 546#[test] 547fn doc_for_commented_definitions_is_not_included_in_next_constant() { 548 let mut config = PackageConfig::default(); 549 config.name = EcoString::from("test_project_name"); 550 let modules = vec![( 551 "app.gleam", 552 " 553/// Not included! 554// pub fn wibble() {} 555 556/// Included! 557pub const wobble = 1 558", 559 )]; 560 assert!(!compile(config, modules).contains("Not included!")); 561} 562 563#[test] 564fn doc_for_commented_definitions_is_not_included_in_next_type() { 565 let mut config = PackageConfig::default(); 566 config.name = EcoString::from("test_project_name"); 567 let modules = vec![( 568 "app.gleam", 569 " 570/// Not included! 571// pub fn wibble() {} 572 573/// Included! 574pub type Wibble { 575 /// Wobble! 576 Wobble 577} 578", 579 )]; 580 assert!(!compile(config, modules).contains("Not included!")); 581} 582 583#[test] 584fn doc_for_commented_definitions_is_not_included_in_next_function() { 585 let mut config = PackageConfig::default(); 586 config.name = EcoString::from("test_project_name"); 587 let modules = vec![( 588 "app.gleam", 589 " 590/// Not included! 591// pub fn wibble() {} 592 593/// Included! 594pub fn wobble(arg) {} 595", 596 )]; 597 assert!(!compile(config, modules).contains("Not included!")); 598} 599 600#[test] 601fn doc_for_commented_definitions_is_not_included_in_next_type_alias() { 602 let mut config = PackageConfig::default(); 603 config.name = EcoString::from("test_project_name"); 604 let modules = vec![( 605 "app.gleam", 606 " 607/// Not included! 608// pub fn wibble() {} 609 610/// Included! 611pub type Wibble = Int 612", 613 )]; 614 assert!(!compile(config, modules).contains("Not included!")); 615} 616 617#[test] 618fn source_link_for_github_repository() { 619 let mut config = PackageConfig::default(); 620 config.name = EcoString::from("test_project_name"); 621 config.repository = Some(Repository::GitHub { 622 user: "wibble".to_string(), 623 repo: "wobble".to_string(), 624 path: None, 625 tag_prefix: None, 626 }); 627 628 let modules = vec![("app.gleam", "pub type Wibble = Int")]; 629 assert!( 630 compile(config, modules) 631 .contains("https://github.com/wibble/wobble/blob/v0.1.0/src/app.gleam#L1") 632 ); 633} 634 635#[test] 636fn source_link_for_github_repository_with_path_and_tag_prefix() { 637 let mut config = PackageConfig::default(); 638 config.name = EcoString::from("test_project_name"); 639 config.repository = Some(Repository::GitHub { 640 user: "wibble".to_string(), 641 repo: "wobble".to_string(), 642 path: Some("path/to/package".to_string()), 643 tag_prefix: Some("subdir-".into()), 644 }); 645 646 let modules = vec![("app.gleam", "pub type Wibble = Int")]; 647 assert!(compile(config, modules).contains( 648 "https://github.com/wibble/wobble/blob/subdir-v0.1.0/path/to/package/src/app.gleam#L1" 649 )); 650} 651 652#[test] 653fn canonical_link() { 654 let mut config = PackageConfig::default(); 655 config.name = EcoString::from("test_project_name"); 656 let modules = vec![ 657 ( 658 "app.gleam", 659 r#" 660/// Here is some documentation 661pub fn one() { 662 1 663} 664"#, 665 ), 666 ( 667 "gleam/otp/actor.gleam", 668 r#" 669/// Here is some documentation 670pub fn one() { 671 1 672} 673"#, 674 ), 675 ]; 676 677 let pages = vec![( 678 "LICENSE", 679 r#" 680# LICENSE 681 "#, 682 )]; 683 insta::assert_snapshot!(compile_with_markdown_pages( 684 config, 685 modules, 686 pages, 687 CompileWithMarkdownPagesOpts::default() 688 )); 689} 690 691#[test] 692fn no_hex_publish() { 693 let mut config = PackageConfig::default(); 694 config.name = EcoString::from("test_project_name"); 695 let modules = vec![ 696 ( 697 "app.gleam", 698 r#" 699/// Here is some documentation 700pub fn one() { 701 1 702} 703"#, 704 ), 705 ( 706 "gleam/otp/actor.gleam", 707 r#" 708/// Here is some documentation 709pub fn one() { 710 1 711} 712"#, 713 ), 714 ]; 715 716 let pages = vec![( 717 "LICENSE", 718 r#" 719# LICENSE 720 "#, 721 )]; 722 insta::assert_snapshot!(compile_with_markdown_pages( 723 config, 724 modules, 725 pages, 726 CompileWithMarkdownPagesOpts { 727 hex_publish: Some(DocContext::Build) 728 } 729 )); 730} 731 732fn create_sample_search_data() -> SearchData { 733 SearchData { 734 items: vec![ 735 SearchItem { 736 type_: SearchItemType::Module, 737 parent_title: "gleam/option".to_string(), 738 title: "gleam/option".to_string(), 739 content: "".to_string(), 740 reference: "gleam/option.html".to_string(), 741 }, 742 SearchItem { 743 type_: SearchItemType::Type, 744 parent_title: "gleam/option".to_string(), 745 title: "Option".to_string(), 746 content: "`Option` represents a value that may be present or not. `Some` means the value is present, `None` means the value is not.".to_string(), 747 reference: "gleam/option.html#Option".to_string(), 748 }, 749 SearchItem { 750 type_: SearchItemType::Value, 751 parent_title: "gleam/option".to_string(), 752 title: "unwrap".to_string(), 753 content: "Extracts the value from an `Option`, returning a default value if there is none.".to_string(), 754 reference: "gleam/option.html#unwrap".to_string(), 755 }, 756 SearchItem { 757 type_: SearchItemType::Value, 758 parent_title: "gleam/dynamic/decode".to_string(), 759 title: "bool".to_string(), 760 content: "A decoder that decodes `Bool` values.\n\n # Examples\n\n \n let result = decode.run(dynamic.from(True), decode.bool)\n assert result == Ok(True)\n \n".to_string(), 761 reference: "gleam/dynamic/decode.html#bool".to_string(), 762 }, 763 ], 764 programming_language: SearchProgrammingLanguage::Gleam, 765 } 766} 767 768#[test] 769fn ensure_search_data_matches_exdocs_search_data_model_specification() { 770 let data = create_sample_search_data(); 771 let json = serde_to_string(&data).unwrap(); 772 773 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap(); 774 775 // Ensure output of SearchData matches specification 776 assert!(parsed.is_object()); 777 let obj = parsed.as_object().unwrap(); 778 assert!(obj.contains_key("items")); 779 assert!(obj.contains_key("proglang")); 780 781 // Ensure output of SearchItem matches specification 782 let items = obj.get("items").unwrap().as_array().unwrap(); 783 for item in items { 784 let item = item.as_object().unwrap(); 785 assert!(item.contains_key("type")); 786 assert!(item.contains_key("parentTitle")); 787 assert!(item.contains_key("title")); 788 assert!(item.contains_key("doc")); 789 assert!(item.contains_key("ref")); 790 } 791} 792 793#[test] 794fn output_of_search_data_json() { 795 let data = create_sample_search_data(); 796 let json = serde_to_string(&data).unwrap(); 797 insta::assert_snapshot!(json); 798} 799 800const ONLY_LINKS: PrintOptions = PrintOptions { 801 print_highlighting: false, 802 print_html: true, 803}; 804const NONE: PrintOptions = PrintOptions { 805 print_highlighting: false, 806 print_html: false, 807}; 808 809#[test] 810fn highlight_function_definition() { 811 assert_documentation!( 812 " 813pub fn wibble(list: List(Int), generic: a, function: fn(a) -> b) -> #(a, b) { todo } 814" 815 ); 816} 817 818#[test] 819fn highlight_constant_definition() { 820 assert_documentation!( 821 " 822pub const x = 22 823" 824 ); 825} 826 827#[test] 828fn highlight_type_alias() { 829 assert_documentation!( 830 " 831pub type Option(a) = Result(a, Nil) 832" 833 ); 834} 835 836#[test] 837fn highlight_custom_type() { 838 assert_documentation!( 839 " 840pub type Wibble(a, b) { 841 Wibble(a, i: Int) 842 Wobble(b: b, c: String) 843} 844" 845 ); 846} 847 848#[test] 849fn highlight_opaque_custom_type() { 850 assert_documentation!( 851 " 852pub opaque type Wibble(a, b) { 853 Wibble(a, i: Int) 854 Wobble(b: b, c: String) 855} 856" 857 ); 858} 859 860// https://github.com/gleam-lang/gleam/issues/2629 861#[test] 862fn print_type_variables_in_function_signatures() { 863 assert_documentation!( 864 " 865pub type Dict(key, value) 866 867pub fn insert(dict: Dict(key, value), key: key, value: value) -> Dict(key, value) { 868 dict 869} 870", 871 NONE 872 ); 873} 874 875// https://github.com/gleam-lang/gleam/issues/828 876#[test] 877fn print_qualified_names_from_other_modules() { 878 assert_documentation!( 879 ( 880 "gleam/option", 881 " 882pub type Option(t) { 883 Some(t) 884 None 885} 886" 887 ), 888 " 889import gleam/option.{type Option, Some, None} 890 891pub fn from_option(o: Option(t), e: e) -> Result(t, e) { 892 case o { 893 Some(t) -> Ok(t) 894 None -> Error(e) 895 } 896} 897", 898 NONE 899 ); 900} 901 902// https://github.com/gleam-lang/gleam/issues/3461 903#[test] 904fn link_to_type_in_same_module() { 905 assert_documentation!( 906 " 907pub type Dict(a, b) 908 909pub fn new() -> Dict(a, b) { todo } 910", 911 ONLY_LINKS 912 ); 913} 914 915// https://github.com/gleam-lang/gleam/issues/3461 916#[test] 917fn link_to_type_in_different_module() { 918 assert_documentation!( 919 ("gleam/dict", "pub type Dict(a, b)"), 920 " 921import gleam/dict 922 923pub fn make_dict() -> dict.Dict(a, b) { todo } 924", 925 ONLY_LINKS 926 ); 927} 928 929#[test] 930fn link_to_type_in_different_module_from_nested_module() { 931 assert_documentation!( 932 ("gleam/dict", "pub type Dict(a, b)"), 933 "gleam/dynamic/decode", 934 " 935import gleam/dict 936 937pub fn decode_dict() -> dict.Dict(a, b) { todo } 938", 939 ONLY_LINKS 940 ); 941} 942 943#[test] 944fn link_to_type_in_different_module_from_nested_module_with_shared_path() { 945 assert_documentation!( 946 ("gleam/dynamic", "pub type Dynamic"), 947 "gleam/dynamic/decode", 948 " 949import gleam/dynamic 950 951pub type Dynamic = dynamic.Dynamic 952", 953 ONLY_LINKS 954 ); 955} 956 957// https://github.com/gleam-lang/gleam/issues/3461 958#[test] 959fn link_to_type_in_different_package() { 960 assert_documentation!( 961 ("gleam_stdlib", "gleam/dict", "pub type Dict(a, b)"), 962 " 963import gleam/dict 964 965pub fn make_dict() -> dict.Dict(a, b) { todo } 966", 967 ONLY_LINKS 968 ); 969} 970 971#[test] 972fn no_link_to_type_in_git_dependency() { 973 assert_documentation!( 974 git: ("gleam_stdlib", "gleam/dict", "pub type Dict(a, b)"), 975 " 976import gleam/dict 977 978pub fn make_dict() -> dict.Dict(a, b) { todo } 979", 980 ONLY_LINKS 981 ); 982} 983 984#[test] 985fn no_link_to_type_in_path_dependency() { 986 assert_documentation!( 987 path: ("gleam_stdlib", "gleam/dict", "pub type Dict(a, b)"), 988 " 989import gleam/dict 990 991pub fn make_dict() -> dict.Dict(a, b) { todo } 992", 993 ONLY_LINKS 994 ); 995} 996 997#[test] 998fn no_links_to_prelude_types() { 999 assert_documentation!( 1000 " 1001pub fn int_to_string(i: Int) -> String { todo } 1002", 1003 ONLY_LINKS 1004 ); 1005} 1006 1007#[test] 1008fn generated_type_variables() { 1009 assert_documentation!( 1010 " 1011pub fn wibble(_a, _b, _c, _d) { 1012 todo 1013} 1014", 1015 NONE 1016 ); 1017} 1018 1019#[test] 1020fn generated_type_variables_mixed_with_existing_variables() { 1021 assert_documentation!( 1022 " 1023pub fn wibble(_a: b, _b: a, _c, _d) { 1024 todo 1025} 1026", 1027 NONE 1028 ); 1029} 1030 1031#[test] 1032fn generated_type_variables_with_existing_variables_coming_afterwards() { 1033 assert_documentation!( 1034 " 1035pub fn wibble(_a, _b, _c: b, _d: a) { 1036 todo 1037} 1038", 1039 NONE 1040 ); 1041} 1042 1043#[test] 1044fn generated_type_variables_do_not_take_into_account_other_definitions() { 1045 assert_documentation!( 1046 " 1047pub fn wibble(_a: a, _b: b, _c: c) -> d { 1048 todo 1049} 1050 1051pub fn identity(x) { x } 1052", 1053 NONE 1054 ); 1055} 1056 1057#[test] 1058fn internal_type_reexport_in_same_module_as_parameter() { 1059 assert_documentation!( 1060 " 1061@internal 1062pub type Internal 1063 1064pub type External = 1065 List(Internal) 1066", 1067 ONLY_LINKS 1068 ); 1069} 1070 1071#[test] 1072fn internal_type_reexport_in_same_module_as_parameter_colours() { 1073 assert_documentation!( 1074 " 1075@internal 1076pub type Internal 1077 1078pub type External = 1079 List(Internal) 1080", 1081 ); 1082} 1083 1084#[test] 1085fn internal_type_reexport_in_same_module() { 1086 assert_documentation!( 1087 " 1088@internal 1089pub type Internal 1090 1091pub type External = 1092 Internal 1093", 1094 ONLY_LINKS 1095 ); 1096} 1097 1098#[test] 1099fn internal_type_reexport_in_different_module() { 1100 assert_documentation!( 1101 ("other", "@internal pub type Internal"), 1102 " 1103import other 1104 1105pub type External = 1106 other.Internal 1107", 1108 ONLY_LINKS 1109 ); 1110} 1111 1112#[test] 1113fn public_type_reexport_in_different_internal_module() { 1114 assert_documentation!( 1115 ("thepackage/internal/other", "pub type Internal"), 1116 " 1117import thepackage/internal/other 1118 1119pub type External = 1120 other.Internal 1121", 1122 ONLY_LINKS 1123 ); 1124} 1125 1126#[test] 1127fn use_reexport_from_other_package() { 1128 assert_documentation!( 1129 ("some_package", "some_package/internal", "pub type Internal"), 1130 ( 1131 "some_package", 1132 "some_package/api", 1133 " 1134import some_package/internal 1135pub type External = internal.Internal 1136" 1137 ), 1138 " 1139import some_package/api 1140 1141pub fn do_thing(value: api.External) { 1142 value 1143} 1144", 1145 ONLY_LINKS 1146 ); 1147} 1148 1149#[test] 1150fn function_uses_reexport_of_internal_type() { 1151 assert_documentation!( 1152 ("thepackage/internal", "pub type Internal"), 1153 " 1154import thepackage/internal 1155 1156pub type External = internal.Internal 1157 1158pub fn do_thing(value: internal.Internal) -> External { 1159 value 1160} 1161", 1162 ONLY_LINKS 1163 ); 1164} 1165 1166#[test] 1167fn function_uses_reexport_of_internal_type_in_other_module() { 1168 assert_documentation!( 1169 ("thepackage/internal", "pub type Internal"), 1170 ( 1171 "thepackage/something", 1172 " 1173import thepackage/internal 1174 1175pub type External = internal.Internal 1176" 1177 ), 1178 " 1179import thepackage/something 1180 1181pub fn do_thing(value: something.External) { 1182 value 1183} 1184", 1185 ONLY_LINKS 1186 ); 1187} 1188 1189#[test] 1190fn constructor_with_long_types_and_many_fields() { 1191 assert_documentation!( 1192 ("option", "pub type Option(a)"), 1193 " 1194import option 1195 1196pub type Uri { 1197 Uri( 1198 scheme: option.Option(String), 1199 userinfo: option.Option(String), 1200 host: option.Option(String), 1201 port: option.Option(Int), 1202 path: String, 1203 query: option.Option(String), 1204 fragment: option.Option(String) 1205 ) 1206} 1207", 1208 NONE 1209 ); 1210} 1211 1212#[test] 1213fn constructor_with_long_types_and_many_fields_that_need_splitting() { 1214 assert_documentation!( 1215 ("option", "pub type Option(a)"), 1216 " 1217import option 1218 1219pub type TypeWithAVeryLoooooooooooooooooooongName 1220 1221pub type Wibble { 1222 Wibble( 1223 wibble: #(TypeWithAVeryLoooooooooooooooooooongName, TypeWithAVeryLoooooooooooooooooooongName), 1224 wobble: option.Option(String), 1225 ) 1226} 1227", 1228 NONE 1229 ); 1230} 1231 1232#[test] 1233fn gitea_repository_url_has_no_double_slash() { 1234 let repo = Repository::Forgejo { 1235 host: "https://code.example.org/".parse::<Uri>().unwrap(), 1236 user: "person".into(), 1237 repo: "forgejo_bug".into(), 1238 path: None, 1239 tag_prefix: None, 1240 }; 1241 1242 assert_eq!(repo.url(), "https://code.example.org/person/forgejo_bug"); 1243} 1244 1245#[test] 1246fn long_function_with_no_arguments_parentheses_are_not_split() { 1247 assert_documentation!( 1248 " 1249pub fn aaaaaaaaaaaaaaaaaaaaaaaaaaaa() -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { 1250 todo 1251} 1252", 1253 NONE 1254 ); 1255} 1256 1257#[test] 1258fn forgejo_single_line_definition() { 1259 let mut config = PackageConfig::default(); 1260 let repo = Repository::Forgejo { 1261 host: "https://code.example.org/".parse::<Uri>().unwrap(), 1262 user: "wibble".into(), 1263 repo: "wobble".into(), 1264 path: None, 1265 tag_prefix: None, 1266 }; 1267 1268 config.name = EcoString::from("test_project_name"); 1269 config.repository = Some(repo); 1270 1271 let modules = vec![("app.gleam", "pub type Wibble = Int")]; 1272 let html = compile(config, modules); 1273 1274 assert!( 1275 html.contains("https://code.example.org/wibble/wobble/src/tag/v0.1.0/src/app.gleam#L1") 1276 ); 1277} 1278 1279#[test] 1280fn forgejo_multiple_line_definition() { 1281 let mut config = PackageConfig::default(); 1282 let repo = Repository::Forgejo { 1283 host: "https://code.example.org/".parse::<Uri>().unwrap(), 1284 user: "wibble".into(), 1285 repo: "wobble".into(), 1286 path: None, 1287 tag_prefix: None, 1288 }; 1289 1290 config.name = EcoString::from("test_project_name"); 1291 config.repository = Some(repo); 1292 1293 let modules = vec![("app.gleam", "pub type Wibble \n\n= Int")]; 1294 let html = compile(config, modules); 1295 1296 assert!( 1297 html.contains("https://code.example.org/wibble/wobble/src/tag/v0.1.0/src/app.gleam#L1-L3") 1298 ); 1299}