From c25e866796d6183850ded6a62ce5d980d832ca79 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 9 Aug 2025 16:32:13 -0700 Subject: [PATCH 1/2] Make SectionNumber field private This removes the `pub` status of the SectionNumber field. The intent is to make this potentially extensible in the future if we decide to add more fields, or change its internal representation. With the existence of the deref impls, generally this change shouldn't be visible except for the constructor, which hopefully shouldn't be too cumbersome to use `SectionNumber::new` instead. --- crates/mdbook-core/src/book.rs | 9 ++++++- crates/mdbook-driver/src/load.rs | 4 +-- crates/mdbook-summary/src/lib.rs | 44 ++++++++++++++++---------------- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/crates/mdbook-core/src/book.rs b/crates/mdbook-core/src/book.rs index 0f8048bc..040612df 100644 --- a/crates/mdbook-core/src/book.rs +++ b/crates/mdbook-core/src/book.rs @@ -180,7 +180,14 @@ impl Display for Chapter { /// A section number like "1.2.3", basically just a newtype'd `Vec` with /// a pretty `Display` impl. #[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)] -pub struct SectionNumber(pub Vec); +pub struct SectionNumber(Vec); + +impl SectionNumber { + /// Creates a new [`SectionNumber`]. + pub fn new(numbers: impl Into>) -> SectionNumber { + SectionNumber(numbers.into()) + } +} impl Display for SectionNumber { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { diff --git a/crates/mdbook-driver/src/load.rs b/crates/mdbook-driver/src/load.rs index 2afedc74..77b1a41c 100644 --- a/crates/mdbook-driver/src/load.rs +++ b/crates/mdbook-driver/src/load.rs @@ -194,7 +194,7 @@ And here is some \ .unwrap(); let mut second = Link::new("Nested Chapter 1", &second_path); - second.number = Some(SectionNumber(vec![1, 2])); + second.number = Some(SectionNumber::new([1, 2])); root.nested_items.push(second.clone().into()); root.nested_items.push(SummaryItem::Separator); @@ -255,7 +255,7 @@ And here is some \ let nested = Chapter { name: String::from("Nested Chapter 1"), content: String::from("Hello World!"), - number: Some(SectionNumber(vec![1, 2])), + number: Some(SectionNumber::new([1, 2])), path: Some(PathBuf::from("second.md")), source_path: Some(PathBuf::from("second.md")), parent_names: vec![String::from("Chapter 1")], diff --git a/crates/mdbook-summary/src/lib.rs b/crates/mdbook-summary/src/lib.rs index f0aad211..f34488b7 100644 --- a/crates/mdbook-summary/src/lib.rs +++ b/crates/mdbook-summary/src/lib.rs @@ -536,7 +536,7 @@ impl<'a> SummaryParser<'a> { let mut link = self.parse_link(dest_url.to_string()); let mut number = parent.clone(); - number.0.push(num_existing_items as u32 + 1); + number.push(num_existing_items as u32 + 1); trace!( "Found chapter: {} {} ({})", number, @@ -602,7 +602,7 @@ fn update_section_numbers(sections: &mut [SummaryItem], level: usize, by: u32) { for section in sections { if let SummaryItem::Link(ref mut link) = *section { if let Some(ref mut number) = link.number { - number.0[level] += by; + number[level] += by; } update_section_numbers(&mut link.nested_items, level, by); @@ -779,7 +779,7 @@ mod tests { let link = Link { name: String::from("First"), location: Some(PathBuf::from("./first.md")), - number: Some(SectionNumber(vec![1])), + number: Some(SectionNumber::new([1])), ..Default::default() }; let should_be = vec![SummaryItem::Link(link)]; @@ -800,18 +800,18 @@ mod tests { SummaryItem::Link(Link { name: String::from("First"), location: Some(PathBuf::from("./first.md")), - number: Some(SectionNumber(vec![1])), + number: Some(SectionNumber::new([1])), nested_items: vec![SummaryItem::Link(Link { name: String::from("Nested"), location: Some(PathBuf::from("./nested.md")), - number: Some(SectionNumber(vec![1, 1])), + number: Some(SectionNumber::new([1, 1])), nested_items: Vec::new(), })], }), SummaryItem::Link(Link { name: String::from("Second"), location: Some(PathBuf::from("./second.md")), - number: Some(SectionNumber(vec![2])), + number: Some(SectionNumber::new([2])), nested_items: Vec::new(), }), ]; @@ -832,13 +832,13 @@ mod tests { SummaryItem::Link(Link { name: String::from("First"), location: Some(PathBuf::from("./first.md")), - number: Some(SectionNumber(vec![1])), + number: Some(SectionNumber::new([1])), nested_items: Vec::new(), }), SummaryItem::Link(Link { name: String::from("Second"), location: Some(PathBuf::from("./second.md")), - number: Some(SectionNumber(vec![2])), + number: Some(SectionNumber::new([2])), nested_items: Vec::new(), }), ]; @@ -860,24 +860,24 @@ mod tests { SummaryItem::Link(Link { name: String::from("First"), location: Some(PathBuf::from("./first.md")), - number: Some(SectionNumber(vec![1])), + number: Some(SectionNumber::new([1])), nested_items: Vec::new(), }), SummaryItem::Link(Link { name: String::from("Second"), location: Some(PathBuf::from("./second.md")), - number: Some(SectionNumber(vec![2])), + number: Some(SectionNumber::new([2])), nested_items: Vec::new(), }), SummaryItem::PartTitle(String::from("Title 2")), SummaryItem::Link(Link { name: String::from("Third"), location: Some(PathBuf::from("./third.md")), - number: Some(SectionNumber(vec![3])), + number: Some(SectionNumber::new([3])), nested_items: vec![SummaryItem::Link(Link { name: String::from("Fourth"), location: Some(PathBuf::from("./fourth.md")), - number: Some(SectionNumber(vec![3, 1])), + number: Some(SectionNumber::new([3, 1])), nested_items: Vec::new(), })], }), @@ -900,13 +900,13 @@ mod tests { SummaryItem::Link(Link { name: String::from("First"), location: Some(PathBuf::from("./first.md")), - number: Some(SectionNumber(vec![1])), + number: Some(SectionNumber::new([1])), nested_items: Vec::new(), }), SummaryItem::Link(Link { name: String::from("Second"), location: Some(PathBuf::from("./second.md")), - number: Some(SectionNumber(vec![2])), + number: Some(SectionNumber::new([2])), nested_items: Vec::new(), }), ]; @@ -928,7 +928,7 @@ mod tests { let should_be = vec![SummaryItem::Link(Link { name: String::from("Empty"), location: None, - number: Some(SectionNumber(vec![1])), + number: Some(SectionNumber::new([1])), nested_items: Vec::new(), })]; @@ -946,21 +946,21 @@ mod tests { SummaryItem::Link(Link { name: String::from("First"), location: Some(PathBuf::from("./first.md")), - number: Some(SectionNumber(vec![1])), + number: Some(SectionNumber::new([1])), nested_items: Vec::new(), }), SummaryItem::Separator, SummaryItem::Link(Link { name: String::from("Second"), location: Some(PathBuf::from("./second.md")), - number: Some(SectionNumber(vec![2])), + number: Some(SectionNumber::new([2])), nested_items: Vec::new(), }), SummaryItem::Separator, SummaryItem::Link(Link { name: String::from("Third"), location: Some(PathBuf::from("./third.md")), - number: Some(SectionNumber(vec![3])), + number: Some(SectionNumber::new([3])), nested_items: Vec::new(), }), ]; @@ -981,7 +981,7 @@ mod tests { let should_be = vec![SummaryItem::Link(Link { name: String::from("Chapter title"), location: Some(PathBuf::from("./chapter.md")), - number: Some(SectionNumber(vec![1])), + number: Some(SectionNumber::new([1])), nested_items: Vec::new(), })]; @@ -1000,13 +1000,13 @@ mod tests { SummaryItem::Link(Link { name: String::from("test1"), location: Some(PathBuf::from("./test link1.md")), - number: Some(SectionNumber(vec![1])), + number: Some(SectionNumber::new([1])), nested_items: Vec::new(), }), SummaryItem::Link(Link { name: String::from("test2"), location: Some(PathBuf::from("./test link2.md")), - number: Some(SectionNumber(vec![2])), + number: Some(SectionNumber::new([2])), nested_items: Vec::new(), }), ]; @@ -1089,7 +1089,7 @@ mod tests { SummaryItem::Link(Link { name: String::from(name), location: Some(PathBuf::from(location)), - number: Some(SectionNumber(numbers.to_vec())), + number: Some(SectionNumber::new(numbers)), nested_items, }) }; From 5956092b4be83bb186244045cf3ddc7c4d98fb92 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 9 Aug 2025 16:38:22 -0700 Subject: [PATCH 2/2] Switch all public types to non_exhaustive This switches all public types to use non_exhaustive to make it easier to make additions without a semver-breaking change. Some of the ergonomics are hampered due to the lack of exhaustiveness checking. Hopefully some day in the future, non_exhaustive_omitted_patterns_lint or something like it will get stabilized. Closes https://github.com/rust-lang/mdBook/issues/1835 --- Cargo.toml | 3 + crates/mdbook-core/src/book.rs | 9 +- crates/mdbook-core/src/config.rs | 15 +++- .../src/builtin_preprocessors/index.rs | 1 + .../src/builtin_preprocessors/links.rs | 1 + .../builtin_renderers/markdown_renderer.rs | 3 +- crates/mdbook-driver/src/load.rs | 82 +++++++------------ crates/mdbook-driver/src/mdbook.rs | 2 + crates/mdbook-driver/src/mdbook/tests.rs | 2 +- .../src/html_handlebars/hbs_renderer.rs | 60 ++++---------- .../mdbook-html/src/html_handlebars/search.rs | 4 +- crates/mdbook-html/src/theme/mod.rs | 1 + crates/mdbook-preprocessor/src/lib.rs | 4 +- crates/mdbook-renderer/src/lib.rs | 4 +- crates/mdbook-summary/src/lib.rs | 5 +- examples/nop-preprocessor.rs | 3 +- tests/testsuite/renderer.rs | 1 - tests/testsuite/search.rs | 12 +-- 18 files changed, 90 insertions(+), 122 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 69af69a8..ff73092b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,9 @@ members = [ all = { level = "allow", priority = -2 } correctness = { level = "warn", priority = -1 } complexity = { level = "warn", priority = -1 } +exhaustive_enums = "warn" +exhaustive_structs = "warn" +manual_non_exhaustive = "warn" [workspace.lints.rust] missing_docs = "warn" diff --git a/crates/mdbook-core/src/book.rs b/crates/mdbook-core/src/book.rs index 040612df..11bbb5a6 100644 --- a/crates/mdbook-core/src/book.rs +++ b/crates/mdbook-core/src/book.rs @@ -16,10 +16,10 @@ use std::path::PathBuf; /// [`iter()`]: #method.iter /// [`for_each_mut()`]: #method.for_each_mut #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] pub struct Book { /// The sections in this book. pub sections: Vec, - __non_exhaustive: (), } impl Book { @@ -30,10 +30,7 @@ impl Book { /// Creates a new book with the given items. pub fn new_with_items(items: Vec) -> Book { - Book { - sections: items, - __non_exhaustive: (), - } + Book { sections: items } } /// Get a depth-first iterator over the items in the book. @@ -81,6 +78,7 @@ where /// Enum representing any type of item which can be added to a book. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] pub enum BookItem { /// A nested chapter. Chapter(Chapter), @@ -99,6 +97,7 @@ impl From for BookItem { /// The representation of a "chapter", usually mapping to a single file on /// disk however it may contain multiple sub-chapters. #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] pub struct Chapter { /// The chapter's name. pub name: String, diff --git a/crates/mdbook-core/src/config.rs b/crates/mdbook-core/src/config.rs index 686b9465..f4533292 100644 --- a/crates/mdbook-core/src/config.rs +++ b/crates/mdbook-core/src/config.rs @@ -64,6 +64,7 @@ use toml::value::Table; /// The overall configuration object for MDBook, essentially an in-memory /// representation of `book.toml`. #[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] pub struct Config { /// Metadata about the book. pub book: BookConfig, @@ -386,6 +387,7 @@ fn is_legacy_format(table: &Value) -> bool { /// loading it from disk. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case")] +#[non_exhaustive] pub struct BookConfig { /// The book's title. pub title: Option, @@ -429,6 +431,7 @@ impl BookConfig { /// Text direction to use for HTML output #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] pub enum TextDirection { /// Left to right. #[serde(rename = "ltr")] @@ -454,6 +457,7 @@ impl TextDirection { /// Configuration for the build procedure. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case")] +#[non_exhaustive] pub struct BuildConfig { /// Where to put built artefacts relative to the book's root directory. pub build_dir: PathBuf, @@ -481,13 +485,15 @@ impl Default for BuildConfig { /// Configuration for the Rust compiler(e.g., for playground) #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case")] +#[non_exhaustive] pub struct RustConfig { /// Rust edition used in playground pub edition: Option, } -#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] /// Rust edition to use for the code. +#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] pub enum RustEdition { /// The 2024 edition of Rust #[serde(rename = "2024")] @@ -506,6 +512,7 @@ pub enum RustEdition { /// Configuration for the HTML renderer. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case")] +#[non_exhaustive] pub struct HtmlConfig { /// The theme directory, if specified. pub theme: Option, @@ -625,6 +632,7 @@ impl HtmlConfig { /// Configuration for how to render the print icon, print.html, and print.css. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case")] +#[non_exhaustive] pub struct Print { /// Whether print support is enabled. pub enable: bool, @@ -644,6 +652,7 @@ impl Default for Print { /// Configuration for how to fold chapters of sidebar. #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case")] +#[non_exhaustive] pub struct Fold { /// When off, all folds are open. Default: `false`. pub enable: bool, @@ -656,6 +665,7 @@ pub struct Fold { /// Configuration for tweaking how the HTML renderer handles the playground. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case")] +#[non_exhaustive] pub struct Playground { /// Should playground snippets be editable? Default: `false`. pub editable: bool, @@ -685,6 +695,7 @@ impl Default for Playground { /// Configuration for tweaking how the HTML renderer handles code blocks. #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case")] +#[non_exhaustive] pub struct Code { /// A prefix string to hide lines per language (one or more chars). pub hidelines: HashMap, @@ -693,6 +704,7 @@ pub struct Code { /// Configuration of the search functionality of the HTML renderer. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case")] +#[non_exhaustive] pub struct Search { /// Enable the search feature. Default: `true`. pub enable: bool, @@ -750,6 +762,7 @@ impl Default for Search { /// Search options for chapters (or paths). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] #[serde(default, rename_all = "kebab-case")] +#[non_exhaustive] pub struct SearchChapterSettings { /// Whether or not indexing is enabled, default `true`. pub enable: Option, diff --git a/crates/mdbook-driver/src/builtin_preprocessors/index.rs b/crates/mdbook-driver/src/builtin_preprocessors/index.rs index 18ac4407..046b20de 100644 --- a/crates/mdbook-driver/src/builtin_preprocessors/index.rs +++ b/crates/mdbook-driver/src/builtin_preprocessors/index.rs @@ -8,6 +8,7 @@ use std::{path::Path, sync::LazyLock}; /// A preprocessor for converting file name `README.md` to `index.md` since /// `README.md` is the de facto index file in markdown-based documentation. #[derive(Default)] +#[non_exhaustive] pub struct IndexPreprocessor; impl IndexPreprocessor { diff --git a/crates/mdbook-driver/src/builtin_preprocessors/links.rs b/crates/mdbook-driver/src/builtin_preprocessors/links.rs index 127ac3fd..e6466ee9 100644 --- a/crates/mdbook-driver/src/builtin_preprocessors/links.rs +++ b/crates/mdbook-driver/src/builtin_preprocessors/links.rs @@ -26,6 +26,7 @@ const MAX_LINK_NESTED_DEPTH: usize = 10; /// - `{{# playground}}` - Insert runnable Rust files /// - `{{# title}}` - Override \ of a webpage. #[derive(Default)] +#[non_exhaustive] pub struct LinkPreprocessor; impl LinkPreprocessor { diff --git a/crates/mdbook-driver/src/builtin_renderers/markdown_renderer.rs b/crates/mdbook-driver/src/builtin_renderers/markdown_renderer.rs index 5255bc80..d626cf17 100644 --- a/crates/mdbook-driver/src/builtin_renderers/markdown_renderer.rs +++ b/crates/mdbook-driver/src/builtin_renderers/markdown_renderer.rs @@ -5,9 +5,10 @@ use mdbook_core::utils; use mdbook_renderer::{RenderContext, Renderer}; use std::fs; -#[derive(Default)] /// A renderer to output the Markdown after the preprocessors have run. Mostly useful /// when debugging preprocessors. +#[derive(Default)] +#[non_exhaustive] pub struct MarkdownRenderer; impl MarkdownRenderer { diff --git a/crates/mdbook-driver/src/load.rs b/crates/mdbook-driver/src/load.rs index 77b1a41c..2c970696 100644 --- a/crates/mdbook-driver/src/load.rs +++ b/crates/mdbook-driver/src/load.rs @@ -95,6 +95,7 @@ fn load_summary_item + Clone>( SummaryItem::Separator => Ok(BookItem::Separator), SummaryItem::Link(link) => load_chapter(link, src_dir, parent_names).map(BookItem::Chapter), SummaryItem::PartTitle(title) => Ok(BookItem::PartTitle(title.clone())), + _ => panic!("SummaryItem {item:?} not covered"), } } @@ -252,28 +253,21 @@ And here is some \ fn load_recursive_link_with_separators() { let (root, temp) = nested_links(); - let nested = Chapter { - name: String::from("Nested Chapter 1"), - content: String::from("Hello World!"), - number: Some(SectionNumber::new([1, 2])), - path: Some(PathBuf::from("second.md")), - source_path: Some(PathBuf::from("second.md")), - parent_names: vec![String::from("Chapter 1")], - sub_items: Vec::new(), - }; - let should_be = BookItem::Chapter(Chapter { - name: String::from("Chapter 1"), - content: String::from(DUMMY_SRC), - number: None, - path: Some(PathBuf::from("chapter_1.md")), - source_path: Some(PathBuf::from("chapter_1.md")), - parent_names: Vec::new(), - sub_items: vec![ - BookItem::Chapter(nested.clone()), - BookItem::Separator, - BookItem::Chapter(nested), - ], - }); + let mut nested = Chapter::new( + "Nested Chapter 1", + String::from("Hello World!"), + "second.md", + vec![String::from("Chapter 1")], + ); + nested.number = Some(SectionNumber::new([1, 2])); + let mut chapter = + Chapter::new("Chapter 1", String::from(DUMMY_SRC), "chapter_1.md", vec![]); + chapter.sub_items = vec![ + BookItem::Chapter(nested.clone()), + BookItem::Separator, + BookItem::Chapter(nested), + ]; + let should_be = BookItem::Chapter(chapter); let got = load_summary_item(&SummaryItem::Link(root), temp.path(), Vec::new()).unwrap(); assert_eq!(got, should_be); @@ -282,17 +276,15 @@ And here is some \ #[test] fn load_a_book_with_a_single_chapter() { let (link, temp) = dummy_link(); - let summary = Summary { - numbered_chapters: vec![SummaryItem::Link(link)], - ..Default::default() - }; - let sections = vec![BookItem::Chapter(Chapter { - name: String::from("Chapter 1"), - content: String::from(DUMMY_SRC), - path: Some(PathBuf::from("chapter_1.md")), - source_path: Some(PathBuf::from("chapter_1.md")), - ..Default::default() - })]; + let mut summary = Summary::default(); + summary.numbered_chapters = vec![SummaryItem::Link(link)]; + let chapter = Chapter::new( + "Chapter 1", + String::from(DUMMY_SRC), + PathBuf::from("chapter_1.md"), + vec![], + ); + let sections = vec![BookItem::Chapter(chapter)]; let should_be = Book::new_with_items(sections); let got = load_book_from_disk(&summary, temp.path()).unwrap(); @@ -303,16 +295,9 @@ And here is some \ #[test] fn cant_load_chapters_with_an_empty_path() { let (_, temp) = dummy_link(); - let summary = Summary { - numbered_chapters: vec![SummaryItem::Link(Link { - name: String::from("Empty"), - location: Some(PathBuf::from("")), - ..Default::default() - })], - - ..Default::default() - }; - + let mut summary = Summary::default(); + let link = Link::new("Empty", ""); + summary.numbered_chapters = vec![SummaryItem::Link(link)]; let got = load_book_from_disk(&summary, temp.path()); assert!(got.is_err()); } @@ -323,14 +308,9 @@ And here is some \ let dir = temp.path().join("nested"); fs::create_dir(&dir).unwrap(); - let summary = Summary { - numbered_chapters: vec![SummaryItem::Link(Link { - name: String::from("nested"), - location: Some(dir), - ..Default::default() - })], - ..Default::default() - }; + let mut summary = Summary::default(); + let link = Link::new("nested", dir); + summary.numbered_chapters = vec![SummaryItem::Link(link)]; let got = load_book_from_disk(&summary, temp.path()); assert!(got.is_err()); diff --git a/crates/mdbook-driver/src/mdbook.rs b/crates/mdbook-driver/src/mdbook.rs index 0655a6f3..38c2bd8d 100644 --- a/crates/mdbook-driver/src/mdbook.rs +++ b/crates/mdbook-driver/src/mdbook.rs @@ -136,6 +136,7 @@ impl MDBook { /// BookItem::Chapter(ref chapter) => {}, /// BookItem::Separator => {}, /// BookItem::PartTitle(ref title) => {} + /// _ => {} /// } /// } /// @@ -329,6 +330,7 @@ impl MDBook { RustEdition::E2024 => { cmd.args(["--edition", "2024"]); } + _ => panic!("RustEdition {edition:?} not covered"), } } diff --git a/crates/mdbook-driver/src/mdbook/tests.rs b/crates/mdbook-driver/src/mdbook/tests.rs index c5caf36f..cdcd4e77 100644 --- a/crates/mdbook-driver/src/mdbook/tests.rs +++ b/crates/mdbook-driver/src/mdbook/tests.rs @@ -230,7 +230,7 @@ fn config_respects_preprocessor_selection() { let cfg = Config::from_str(cfg_str).unwrap(); - let html_renderer = HtmlHandlebars; + let html_renderer = HtmlHandlebars::default(); let pre = LinkPreprocessor::new(); let should_run = preprocessor_should_run(&pre, &html_renderer, &cfg).unwrap(); diff --git a/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs b/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs index 3b47235a..b04aca7b 100644 --- a/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs +++ b/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs @@ -21,6 +21,7 @@ use std::sync::LazyLock; /// The HTML renderer for mdBook. #[derive(Default)] +#[non_exhaustive] pub struct HtmlHandlebars; impl HtmlHandlebars { @@ -656,6 +657,7 @@ fn make_data( BookItem::Separator => { chapter.insert("spacer".to_owned(), json!("_spacer_")); } + _ => panic!("BookItem {item:?} not covered"), } chapters.push(chapter); @@ -778,6 +780,7 @@ fn add_playground_pre( Some(RustEdition::E2018) => " edition2018", Some(RustEdition::E2021) => " edition2021", Some(RustEdition::E2024) => " edition2024", + Some(_) => panic!("edition {edition:?} not covered"), None => "", } }; @@ -1085,14 +1088,9 @@ mod tests { ), ]; for (src, should_be) in &inputs { - let got = add_playground_pre( - src, - &Playground { - editable: true, - ..Playground::default() - }, - None, - ); + let mut p = Playground::default(); + p.editable = true; + let got = add_playground_pre(src, &p, None); assert_eq!(&*got, *should_be); } } @@ -1117,14 +1115,9 @@ mod tests { ), ]; for (src, should_be) in &inputs { - let got = add_playground_pre( - src, - &Playground { - editable: true, - ..Playground::default() - }, - Some(RustEdition::E2015), - ); + let mut p = Playground::default(); + p.editable = true; + let got = add_playground_pre(src, &p, Some(RustEdition::E2015)); assert_eq!(&*got, *should_be); } } @@ -1149,14 +1142,9 @@ mod tests { ), ]; for (src, should_be) in &inputs { - let got = add_playground_pre( - src, - &Playground { - editable: true, - ..Playground::default() - }, - Some(RustEdition::E2018), - ); + let mut p = Playground::default(); + p.editable = true; + let got = add_playground_pre(src, &p, Some(RustEdition::E2018)); assert_eq!(&*got, *should_be); } } @@ -1181,14 +1169,9 @@ mod tests { ), ]; for (src, should_be) in &inputs { - let got = add_playground_pre( - src, - &Playground { - editable: true, - ..Playground::default() - }, - Some(RustEdition::E2021), - ); + let mut p = Playground::default(); + p.editable = true; + let got = add_playground_pre(src, &p, Some(RustEdition::E2021)); assert_eq!(&*got, *should_be); } } @@ -1248,17 +1231,10 @@ mod tests { "hidden()\nnothidden():\n hidden()\n hidden()\n nothidden()\n", ), ]; + let mut code = Code::default(); + code.hidelines.insert("python".to_string(), "~".to_string()); for (src, should_be) in &inputs { - let got = hide_lines( - src, - &Code { - hidelines: { - let mut map = HashMap::new(); - map.insert("python".to_string(), "~".to_string()); - map - }, - }, - ); + let got = hide_lines(src, &code); assert_eq!(&*got, *should_be); } } diff --git a/crates/mdbook-html/src/html_handlebars/search.rs b/crates/mdbook-html/src/html_handlebars/search.rs index b14c9c67..25f19d26 100644 --- a/crates/mdbook-html/src/html_handlebars/search.rs +++ b/crates/mdbook-html/src/html_handlebars/search.rs @@ -409,9 +409,11 @@ fn chapter_settings_priority() { ("cli/inner/index.md", Some(true)), ("cli/inner/foo.md", Some(false)), ] { + let mut settings = SearchChapterSettings::default(); + settings.enable = enable; assert_eq!( get_chapter_settings(&chapter_configs, Path::new(path)), - SearchChapterSettings { enable } + settings ); } } diff --git a/crates/mdbook-html/src/theme/mod.rs b/crates/mdbook-html/src/theme/mod.rs index 2547d3f9..373ef67e 100644 --- a/crates/mdbook-html/src/theme/mod.rs +++ b/crates/mdbook-html/src/theme/mod.rs @@ -49,6 +49,7 @@ pub static FONT_AWESOME_OTF: &[u8] = include_bytes!("../../front-end/fonts/FontA /// You should only ever use the static variables directly if you want to /// override the user's theme with the defaults. #[derive(Debug, PartialEq)] +#[non_exhaustive] pub struct Theme { pub index: Vec, pub head: Vec, diff --git a/crates/mdbook-preprocessor/src/lib.rs b/crates/mdbook-preprocessor/src/lib.rs index 898d19a3..c1aa35ea 100644 --- a/crates/mdbook-preprocessor/src/lib.rs +++ b/crates/mdbook-preprocessor/src/lib.rs @@ -47,6 +47,7 @@ pub trait Preprocessor { /// Extra information for a `Preprocessor` to give them more context when /// processing a book. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] pub struct PreprocessorContext { /// The location of the book directory on disk. pub root: PathBuf, @@ -62,8 +63,6 @@ pub struct PreprocessorContext { /// This should not be used outside of mdbook's internals. #[serde(skip)] pub chapter_titles: RefCell>, - #[serde(skip)] - __non_exhaustive: (), } impl PreprocessorContext { @@ -75,7 +74,6 @@ impl PreprocessorContext { renderer, mdbook_version: crate::MDBOOK_VERSION.to_string(), chapter_titles: RefCell::new(HashMap::new()), - __non_exhaustive: (), } } } diff --git a/crates/mdbook-renderer/src/lib.rs b/crates/mdbook-renderer/src/lib.rs index 12b0aab4..ba2fe046 100644 --- a/crates/mdbook-renderer/src/lib.rs +++ b/crates/mdbook-renderer/src/lib.rs @@ -36,6 +36,7 @@ pub trait Renderer { /// The context provided to all renderers. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] pub struct RenderContext { /// Which version of `mdbook` did this come from (as written in `mdbook`'s /// `Cargo.toml`). Useful if you know the renderer is only compatible with @@ -57,8 +58,6 @@ pub struct RenderContext { /// This should not be used outside of mdbook's internals. #[serde(skip)] pub chapter_titles: HashMap, - #[serde(skip)] - __non_exhaustive: (), } impl RenderContext { @@ -75,7 +74,6 @@ impl RenderContext { root: root.into(), destination: destination.into(), chapter_titles: HashMap::new(), - __non_exhaustive: (), } } diff --git a/crates/mdbook-summary/src/lib.rs b/crates/mdbook-summary/src/lib.rs index f34488b7..24a4bc61 100644 --- a/crates/mdbook-summary/src/lib.rs +++ b/crates/mdbook-summary/src/lib.rs @@ -63,6 +63,7 @@ pub fn parse_summary(summary: &str) -> Result { /// The parsed `SUMMARY.md`, specifying how the book should be laid out. #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] pub struct Summary { /// An optional title for the `SUMMARY.md`, currently just ignored. pub title: Option, @@ -79,6 +80,7 @@ pub struct Summary { /// /// This is roughly the equivalent of `[Some section](./path/to/file.md)`. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] pub struct Link { /// The name of the chapter. pub name: String, @@ -114,8 +116,9 @@ impl Default for Link { } } -/// An item in `SUMMARY.md` which could be either a separator or a `Link`. +/// An item in `SUMMARY.md`. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] pub enum SummaryItem { /// A link to a chapter. Link(Link), diff --git a/examples/nop-preprocessor.rs b/examples/nop-preprocessor.rs index 91e1f102..3423b481 100644 --- a/examples/nop-preprocessor.rs +++ b/examples/nop-preprocessor.rs @@ -146,8 +146,7 @@ mod nop_lib { "parent_names": [] } } - ], - "__non_exhaustive": null + ] } ]"##; let input_json = input_json.as_bytes(); diff --git a/tests/testsuite/renderer.rs b/tests/testsuite/renderer.rs index 9d74f9ba..43a022d3 100644 --- a/tests/testsuite/renderer.rs +++ b/tests/testsuite/renderer.rs @@ -167,7 +167,6 @@ fn backends_receive_render_context_via_stdin() { str![[r##" { "book": { - "__non_exhaustive": null, "sections": [ { "Chapter": { diff --git a/tests/testsuite/search.rs b/tests/testsuite/search.rs index c3fa0085..0a177276 100644 --- a/tests/testsuite/search.rs +++ b/tests/testsuite/search.rs @@ -3,7 +3,7 @@ use crate::prelude::*; use mdbook_core::book::{BookItem, Chapter}; use snapbox::file; -use std::path::{Path, PathBuf}; +use std::path::Path; fn read_book_index(root: &Path) -> serde_json::Value { let index = root.join("book/searchindex.js"); @@ -116,15 +116,7 @@ fn can_disable_individual_chapters() { fn with_no_source_path() { let test = BookTest::from_dir("search/reasonable_search_index"); let mut book = test.load_book(); - let chapter = Chapter { - name: "Sample chapter".to_string(), - content: "".to_string(), - number: None, - sub_items: Vec::new(), - path: Some(PathBuf::from("sample.html")), - source_path: None, - parent_names: Vec::new(), - }; + let chapter = Chapter::new("Sample chapter", String::new(), "sample.html", vec![]); book.book.sections.push(BookItem::Chapter(chapter)); book.build().unwrap(); }