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

Configure Feed

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

Test search data generation

author
Gears
committer
Louis Pilfold
date (Dec 5, 2025, 10:38 AM UTC) commit 03b83ff7 parent a2b22c1b change-id orxzlzpr
+432 -81
+87 -79
compiler-core/src/docs.rs
··· 184 184 content: Content::Text(temp.render().expect("Page template rendering")), 185 185 }); 186 186 187 - search_items.push(SearchItem { 188 - type_: SearchItemType::Page, 189 - parent_title: config.name.to_string(), 190 - title: config.name.to_string(), 191 - content: escape_html_content(content), 192 - reference: page.path.to_string(), 193 - }) 187 + search_items.push(search_item_for_page(&config.name, &page.path, content)) 194 188 } 195 189 196 190 // Generate module documentation pages ··· 203 197 204 198 let documentation_content = module.ast.documentation.iter().join("\n"); 205 199 let rendered_documentation = 206 - render_markdown(&documentation_content.clone(), MarkdownSource::Comment); 200 + render_markdown(&documentation_content, MarkdownSource::Comment); 207 201 208 202 let mut printer = Printer::new( 209 203 module.ast.type_info.package.clone(), ··· 215 209 let types = printer.type_definitions(&source_links, &module.ast.definitions); 216 210 let values = printer.value_definitions(&source_links, &module.ast.definitions); 217 211 218 - types.iter().for_each(|type_| { 219 - let constructors = type_ 220 - .constructors 221 - .iter() 222 - .map(|constructor| { 223 - let arguments = constructor 224 - .arguments 225 - .iter() 226 - .map(|argument| { 227 - format!("{}\n{}", argument.name, argument.text_documentation) 228 - }) 229 - .join("\n"); 230 - 231 - format!( 232 - "{}\n{}\n{}", 233 - constructor.raw_definition, constructor.text_documentation, arguments 234 - ) 235 - }) 236 - .join("\n"); 237 - 238 - search_items.push(SearchItem { 239 - type_: SearchItemType::Type, 240 - parent_title: module.name.to_string(), 241 - title: type_.name.to_string(), 242 - content: format!( 243 - "{}\n{}\n{}\n{}", 244 - type_.raw_definition, 245 - type_.text_documentation, 246 - constructors, 247 - import_synonyms(&module.name, type_.name) 248 - ), 249 - reference: format!("{}.html#{}", module.name, type_.name), 250 - }) 251 - }); 252 - values.iter().for_each(|value| { 253 - search_items.push(SearchItem { 254 - type_: SearchItemType::Value, 255 - parent_title: module.name.to_string(), 256 - title: value.name.to_string(), 257 - content: format!( 258 - "{}\n{}\n{}", 259 - value.raw_definition, 260 - value.text_documentation, 261 - import_synonyms(&module.name, value.name) 262 - ), 263 - reference: format!("{}.html#{}", module.name, value.name), 264 - }) 265 - }); 212 + types 213 + .iter() 214 + .for_each(|type_| search_items.push(search_item_for_type(&module.name, type_))); 215 + values 216 + .iter() 217 + .for_each(|value| search_items.push(search_item_for_value(&module.name, value))); 266 218 267 - search_items.push(SearchItem { 268 - type_: SearchItemType::Module, 269 - parent_title: module.name.to_string(), 270 - title: module.name.to_string(), 271 - content: documentation_content, 272 - reference: format!("{}.html", module.name), 273 - }); 219 + search_items.push(search_item_for_module(module)); 274 220 275 221 let page_title = format!("{} · {} · v{}", name, config.name, config.version); 276 222 let page_meta_description = ""; ··· 472 418 files 473 419 } 474 420 421 + fn search_item_for_page(package: &str, path: &str, content: String) -> SearchItem { 422 + SearchItem { 423 + type_: SearchItemType::Page, 424 + parent_title: package.to_string(), 425 + title: package.to_string(), 426 + content, 427 + reference: path.to_string(), 428 + } 429 + } 430 + 431 + fn search_item_for_type(module: &str, type_: &TypeDefinition<'_>) -> SearchItem { 432 + let constructors = type_ 433 + .constructors 434 + .iter() 435 + .map(|constructor| { 436 + let arguments = constructor 437 + .arguments 438 + .iter() 439 + .map(|argument| format!("{}\n{}", argument.name, argument.text_documentation)) 440 + .join("\n"); 441 + 442 + format!( 443 + "{}\n{}\n{}", 444 + constructor.raw_definition, constructor.text_documentation, arguments 445 + ) 446 + }) 447 + .join("\n"); 448 + 449 + SearchItem { 450 + type_: SearchItemType::Type, 451 + parent_title: module.to_string(), 452 + title: type_.name.to_string(), 453 + content: format!( 454 + "{}\n{}\n{}\n{}", 455 + type_.raw_definition, 456 + type_.text_documentation, 457 + constructors, 458 + import_synonyms(module, type_.name) 459 + ), 460 + reference: format!("{}.html#{}", module, type_.name), 461 + } 462 + } 463 + 464 + fn search_item_for_value(module: &str, value: &DocsValues<'_>) -> SearchItem { 465 + SearchItem { 466 + type_: SearchItemType::Value, 467 + parent_title: module.to_string(), 468 + title: value.name.to_string(), 469 + content: format!( 470 + "{}\n{}\n{}", 471 + value.raw_definition, 472 + value.text_documentation, 473 + import_synonyms(module, value.name) 474 + ), 475 + reference: format!("{}.html#{}", module, value.name), 476 + } 477 + } 478 + 479 + fn search_item_for_module(module: &Module) -> SearchItem { 480 + SearchItem { 481 + type_: SearchItemType::Module, 482 + parent_title: module.name.to_string(), 483 + title: module.name.to_string(), 484 + content: module.ast.documentation.iter().join("\n"), 485 + reference: format!("{}.html", module.name), 486 + } 487 + } 488 + 475 489 pub fn generate_json_package_interface( 476 490 path: Utf8PathBuf, 477 491 package: &Package, ··· 527 541 assert_eq!(page_unnest("string"), "."); 528 542 assert_eq!(page_unnest("gleam/string"), ".."); 529 543 assert_eq!(page_unnest("gleam/string/inspect"), "../.."); 530 - } 531 - 532 - fn escape_html_content(it: String) -> String { 533 - it.replace('&', "&amp;") 534 - .replace('<', "&lt;") 535 - .replace('>', "&gt;") 536 - .replace('\"', "&quot;") 537 - .replace('\'', "&#39;") 538 - } 539 - 540 - #[test] 541 - fn escape_html_content_test() { 542 - assert_eq!( 543 - escape_html_content("&<>\"'".to_string()), 544 - "&amp;&lt;&gt;&quot;&#39;" 545 - ); 546 544 } 547 545 548 546 fn import_synonyms(parent: &str, child: &str) -> String { ··· 677 675 rendering_timestamp: &'a str, 678 676 } 679 677 678 + /// Search data for use by Hexdocs search, as well as the search built-in to 679 + /// generated documentation 680 680 #[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] 681 681 struct SearchData { 682 682 items: Vec<SearchItem>, ··· 684 684 programming_language: SearchProgrammingLanguage, 685 685 } 686 686 687 + /// A single item that can appear as a search result 687 688 #[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] 688 689 struct SearchItem { 690 + /// The type of item this is: Value, Type, Module, or other Page 689 691 #[serde(rename = "type")] 690 692 type_: SearchItemType, 693 + /// The title of the module or package containing this search item 691 694 #[serde(rename = "parentTitle")] 692 695 parent_title: String, 696 + /// The title of this item 693 697 title: String, 698 + /// Markdown text which describes this item, containing documentation from 699 + /// doc comments, as well as rendered definitions of types and values. 694 700 #[serde(rename = "doc")] 695 701 content: String, 702 + /// The relative URL to the documentation for this search item, for example 703 + /// `gleam/option.html#Option` 696 704 #[serde(rename = "ref")] 697 705 reference: String, 698 706 }
+45
compiler-core/src/docs/snapshots/gleam_core__docs__tests__search_item_for_constant.snap
··· 1 + --- 2 + source: compiler-core/src/docs/tests.rs 3 + expression: output 4 + --- 5 + ------ SOURCE CODE 6 + 7 + /// Reverses a `List`, and returns the list in reverse. 8 + /// ``` 9 + /// reverse([1, 2, 3]) 10 + /// // [3, 2, 1] 11 + /// ``` 12 + pub fn reverse(list: List(a), out: List(a)) -> List(a) { 13 + case list { 14 + [] -> out 15 + [first, ..rest] -> reverse(rest, [first, ..out]) 16 + } 17 + } 18 + 19 + ------------------------------------ 20 + 21 + TITLE: main 22 + PARENT TITLE: main 23 + TYPE: Module 24 + REFERENCE: main.html 25 + CONTENT: 26 + 27 + ------------------------------------ 28 + TITLE: reverse 29 + PARENT TITLE: module 30 + TYPE: Value 31 + REFERENCE: module.html#reverse 32 + CONTENT: 33 + ``` 34 + pub fn reverse(list: List(a), out: List(a)) -> List(a) 35 + ``` 36 + Reverses a `List`, and returns the list in reverse. 37 + 38 + reverse([1, 2, 3]) 39 + // [3, 2, 1] 40 + 41 + 42 + Synonyms: 43 + module.reverse 44 + module reverse 45 + ------------------------------------
+63
compiler-core/src/docs/snapshots/gleam_core__docs__tests__search_item_for_custom_type.snap
··· 1 + --- 2 + source: compiler-core/src/docs/tests.rs 3 + expression: output 4 + --- 5 + ------ SOURCE CODE 6 + 7 + /// # The `Option` type 8 + /// Represents an optional value, either `Some` or `None`. 9 + /// If it is None, the value is absent 10 + /// [Read more](https://example.com) 11 + pub type Option(inner) { 12 + /// Here is some 13 + /// documentation for the `Some` constructor 14 + Some( 15 + /// And even documentation on a **field**! 16 + value: inner 17 + ) 18 + None 19 + } 20 + 21 + ------------------------------------ 22 + 23 + TITLE: main 24 + PARENT TITLE: main 25 + TYPE: Module 26 + REFERENCE: main.html 27 + CONTENT: 28 + 29 + ------------------------------------ 30 + TITLE: Option 31 + PARENT TITLE: module 32 + TYPE: Type 33 + REFERENCE: module.html#Option 34 + CONTENT: 35 + ``` 36 + pub type Option(inner) { 37 + Some(value: inner) 38 + None 39 + } 40 + ``` 41 + # The `Option` type 42 + Represents an optional value, either `Some` or `None`. 43 + If it is None, the value is absent 44 + [Read more](https://example.com) 45 + 46 + ``` 47 + Some(value: inner) 48 + ``` 49 + Here is some 50 + documentation for the `Some` constructor 51 + 52 + value 53 + And even documentation on a **field**! 54 + 55 + ``` 56 + None 57 + ``` 58 + 59 + 60 + Synonyms: 61 + module.Option 62 + module Option 63 + ------------------------------------
+34
compiler-core/src/docs/snapshots/gleam_core__docs__tests__search_item_for_function.snap
··· 1 + --- 2 + source: compiler-core/src/docs/tests.rs 3 + expression: output 4 + --- 5 + ------ SOURCE CODE 6 + 7 + /// Pi is the ration between a circle's **radius** and its 8 + /// *circumference*. Pretty cool! 9 + pub const pi = 3.14 10 + 11 + ------------------------------------ 12 + 13 + TITLE: main 14 + PARENT TITLE: main 15 + TYPE: Module 16 + REFERENCE: main.html 17 + CONTENT: 18 + 19 + ------------------------------------ 20 + TITLE: pi 21 + PARENT TITLE: module 22 + TYPE: Value 23 + REFERENCE: module.html#pi 24 + CONTENT: 25 + ``` 26 + pub const pi: Float 27 + ``` 28 + Pi is the ration between a circle's **radius** and its 29 + *circumference*. Pretty cool! 30 + 31 + Synonyms: 32 + module.pi 33 + module pi 34 + ------------------------------------
+44
compiler-core/src/docs/snapshots/gleam_core__docs__tests__search_item_for_type_alias.snap
··· 1 + --- 2 + source: compiler-core/src/docs/tests.rs 3 + expression: output 4 + --- 5 + ------ SOURCE CODE 6 + 7 + /// This is a type alias to a list 8 + /// of integer values. 9 + /// ## Examples 10 + /// ``` 11 + /// // Examples 12 + /// ``` 13 + pub type IntList = List(Int) 14 + 15 + ------------------------------------ 16 + 17 + TITLE: main 18 + PARENT TITLE: main 19 + TYPE: Module 20 + REFERENCE: main.html 21 + CONTENT: 22 + 23 + ------------------------------------ 24 + TITLE: IntList 25 + PARENT TITLE: module 26 + TYPE: Type 27 + REFERENCE: module.html#IntList 28 + CONTENT: 29 + ``` 30 + pub type IntList = 31 + List(Int) 32 + ``` 33 + This is a type alias to a list 34 + of integer values. 35 + ## Examples 36 + 37 + // Examples 38 + 39 + 40 + 41 + Synonyms: 42 + module.IntList 43 + module IntList 44 + ------------------------------------
+159 -2
compiler-core/src/docs/tests.rs
··· 15 15 TargetCodegenConfiguration, 16 16 }, 17 17 config::{DocsPage, PackageConfig, Repository}, 18 - docs::DocContext, 18 + docs::{DocContext, search_item_for_module, search_item_for_type, search_item_for_value}, 19 19 io::{FileSystemWriter, memory::InMemoryFileSystem}, 20 20 paths::ProjectPaths, 21 21 type_, ··· 24 24 warning::WarningEmitter, 25 25 }; 26 26 use camino::Utf8PathBuf; 27 - use ecow::EcoString; 27 + use ecow::{EcoString, eco_format}; 28 28 use hexpm::version::Version; 29 29 use http::Uri; 30 30 use itertools::Itertools; ··· 1279 1279 html.contains("https://code.example.org/wibble/wobble/src/tag/v0.1.0/src/app.gleam#L1-L3") 1280 1280 ); 1281 1281 } 1282 + 1283 + fn generate_search_data(module_name: &str, module_src: &str) -> EcoString { 1284 + let module = type_::tests::compile_module(module_name, module_src, None, Vec::new()) 1285 + .expect("Module should compile successfully"); 1286 + 1287 + let mut config = PackageConfig::default(); 1288 + config.name = "thepackage".into(); 1289 + let paths = ProjectPaths::new("/".into()); 1290 + let build_module = build::Module { 1291 + name: "main".into(), 1292 + code: module_src.into(), 1293 + mtime: SystemTime::now(), 1294 + input_path: "/".into(), 1295 + origin: Origin::Src, 1296 + ast: module, 1297 + extra: Default::default(), 1298 + dependencies: Default::default(), 1299 + }; 1300 + 1301 + let source_links = SourceLinker::new(&paths, &config, &build_module); 1302 + 1303 + let module = &build_module.ast; 1304 + 1305 + dbg!(&module.documentation); 1306 + 1307 + let dependencies = HashMap::new(); 1308 + let mut printer = Printer::new( 1309 + module.type_info.package.clone(), 1310 + module.name.clone(), 1311 + &module.names, 1312 + &dependencies, 1313 + ); 1314 + 1315 + let mut search_items = Vec::new(); 1316 + 1317 + let types = printer.type_definitions(&source_links, &module.definitions); 1318 + let values = printer.value_definitions(&source_links, &module.definitions); 1319 + 1320 + search_items.push(search_item_for_module(&build_module)); 1321 + 1322 + for type_ in types { 1323 + search_items.push(search_item_for_type(module_name, &type_)); 1324 + } 1325 + for value in values { 1326 + search_items.push(search_item_for_value(module_name, &value)); 1327 + } 1328 + 1329 + let mut output = EcoString::new(); 1330 + 1331 + output.push_str("------ SOURCE CODE\n"); 1332 + output.push_str(module_src); 1333 + output.push_str("\n------------------------------------\n\n"); 1334 + 1335 + for item in search_items { 1336 + let SearchItem { 1337 + type_, 1338 + parent_title, 1339 + title, 1340 + content, 1341 + reference, 1342 + } = item; 1343 + 1344 + let type_ = match type_ { 1345 + SearchItemType::Value => "Value", 1346 + SearchItemType::Module => "Module", 1347 + SearchItemType::Page => "Page", 1348 + SearchItemType::Type => "Type", 1349 + }; 1350 + 1351 + output.push_str(&eco_format!( 1352 + "TITLE: {title} 1353 + PARENT TITLE: {parent_title} 1354 + TYPE: {type_} 1355 + REFERENCE: {reference} 1356 + CONTENT: 1357 + {content} 1358 + ------------------------------------ 1359 + " 1360 + )); 1361 + } 1362 + 1363 + output 1364 + } 1365 + 1366 + #[test] 1367 + fn search_item_for_custom_type() { 1368 + let output = generate_search_data( 1369 + "module", 1370 + " 1371 + /// # The `Option` type 1372 + /// Represents an optional value, either `Some` or `None`. 1373 + /// If it is None, the value is absent 1374 + /// [Read more](https://example.com) 1375 + pub type Option(inner) { 1376 + /// Here is some 1377 + /// documentation for the `Some` constructor 1378 + Some( 1379 + /// And even documentation on a **field**! 1380 + value: inner 1381 + ) 1382 + None 1383 + } 1384 + ", 1385 + ); 1386 + insta::assert_snapshot!(output); 1387 + } 1388 + 1389 + #[test] 1390 + fn search_item_for_type_alias() { 1391 + let output = generate_search_data( 1392 + "module", 1393 + " 1394 + /// This is a type alias to a list 1395 + /// of integer values. 1396 + /// ## Examples 1397 + /// ``` 1398 + /// // Examples 1399 + /// ``` 1400 + pub type IntList = List(Int) 1401 + ", 1402 + ); 1403 + insta::assert_snapshot!(output); 1404 + } 1405 + 1406 + #[test] 1407 + fn search_item_for_function() { 1408 + let output = generate_search_data( 1409 + "module", 1410 + " 1411 + /// Pi is the ration between a circle's **radius** and its 1412 + /// *circumference*. Pretty cool! 1413 + pub const pi = 3.14 1414 + ", 1415 + ); 1416 + insta::assert_snapshot!(output); 1417 + } 1418 + 1419 + #[test] 1420 + fn search_item_for_constant() { 1421 + let output = generate_search_data( 1422 + "module", 1423 + " 1424 + /// Reverses a `List`, and returns the list in reverse. 1425 + /// ``` 1426 + /// reverse([1, 2, 3]) 1427 + /// // [3, 2, 1] 1428 + /// ``` 1429 + pub fn reverse(list: List(a), out: List(a)) -> List(a) { 1430 + case list { 1431 + [] -> out 1432 + [first, ..rest] -> reverse(rest, [first, ..out]) 1433 + } 1434 + } 1435 + ", 1436 + ); 1437 + insta::assert_snapshot!(output); 1438 + }