rustdoc_to_markdown: Handle "stabs" in item name entries (#12494)
Marshall Bowers
created 2 years ago
This PR extends `rustdoc_to_markdown` with support for rustdoc's
"stabs".
These are used in item name lists to indicate that the construct is
behind a feature flag:
<img width="641" alt="Screenshot 2024-05-30 at 1 34 53 PM"
src="https://github.com/zed-industries/zed/assets/1486634/0216f325-dc4e-4302-b6db-149ace31deea">
We now treat these specially in the Markdown output:
<img width="813" alt="Screenshot 2024-05-30 at 1 35 27 PM"
src="https://github.com/zed-industries/zed/assets/1486634/96396305-123d-40b2-af49-7eed71b62971">
Release Notes:
- N/A
Change summary
crates/rustdoc_to_markdown/src/markdown_writer.rs | 40 +++++++++++-
crates/rustdoc_to_markdown/src/rustdoc_to_markdown.rs | 36 +++++++++++
2 files changed, 70 insertions(+), 6 deletions(-)
Detailed changes
@@ -224,10 +224,13 @@ impl MarkdownWriter {
return StartTagOutcome::Skip;
}
- if tag.attrs.borrow().iter().any(|attr| {
- attr.name.local.to_string() == "class" && attr.value.to_string() == "item-name"
- }) {
- self.push_str("`");
+ if self.is_inside_item_name() {
+ if tag.attrs.borrow().iter().any(|attr| {
+ attr.name.local.to_string() == "class"
+ && attr.value.split(' ').any(|class| class.trim() == "stab")
+ }) {
+ self.push_str(" [");
+ }
}
}
_ => {}
@@ -265,11 +268,20 @@ impl MarkdownWriter {
"table" => {
self.current_table_columns = 0;
}
- "div" => {
+ "div" | "span" => {
if tag.attrs.borrow().iter().any(|attr| {
attr.name.local.to_string() == "class" && attr.value.to_string() == "item-name"
}) {
- self.push_str("`: ");
+ self.push_str(": ");
+ }
+
+ if self.is_inside_item_name() {
+ if tag.attrs.borrow().iter().any(|attr| {
+ attr.name.local.to_string() == "class"
+ && attr.value.split(' ').any(|class| class.trim() == "stab")
+ }) {
+ self.push_str("]");
+ }
}
}
_ => {}
@@ -283,8 +295,24 @@ impl MarkdownWriter {
}
let trimmed_text = text.trim_matches(|char| char == '\n' || char == '\r' || char == '§');
+
+ if self.is_inside_item_name() && !self.is_inside("span") && !self.is_inside("code") {
+ self.push_str(&format!("`{trimmed_text}`"));
+ return Ok(());
+ }
+
self.push_str(trimmed_text);
Ok(())
}
+
+ /// Returns whether we're currently inside of an `.item-name` element, which
+ /// rustdoc uses to display Rust items in a list.
+ fn is_inside_item_name(&self) -> bool {
+ self.current_element_stack.iter().any(|element| {
+ element.attrs.borrow().iter().any(|attr| {
+ attr.name.local.to_string() == "class" && attr.value.to_string() == "item-name"
+ })
+ })
+ }
}
@@ -139,6 +139,42 @@ mod tests {
)
}
+ #[test]
+ fn test_item_table() {
+ let html = indoc! {r##"
+ <h2 id="structs" class="section-header">Structs<a href="#structs" class="anchor">§</a></h2>
+ <ul class="item-table">
+ <li><div class="item-name"><a class="struct" href="struct.Error.html" title="struct axum::Error">Error</a></div><div class="desc docblock-short">Errors that can happen when using axum.</div></li>
+ <li><div class="item-name"><a class="struct" href="struct.Extension.html" title="struct axum::Extension">Extension</a></div><div class="desc docblock-short">Extractor and response for extensions.</div></li>
+ <li><div class="item-name"><a class="struct" href="struct.Form.html" title="struct axum::Form">Form</a><span class="stab portability" title="Available on crate feature `form` only"><code>form</code></span></div><div class="desc docblock-short">URL encoded extractor and response.</div></li>
+ <li><div class="item-name"><a class="struct" href="struct.Json.html" title="struct axum::Json">Json</a><span class="stab portability" title="Available on crate feature `json` only"><code>json</code></span></div><div class="desc docblock-short">JSON Extractor / Response.</div></li>
+ <li><div class="item-name"><a class="struct" href="struct.Router.html" title="struct axum::Router">Router</a></div><div class="desc docblock-short">The router type for composing handlers and services.</div></li></ul>
+ <h2 id="functions" class="section-header">Functions<a href="#functions" class="anchor">§</a></h2>
+ <ul class="item-table">
+ <li><div class="item-name"><a class="fn" href="fn.serve.html" title="fn axum::serve">serve</a><span class="stab portability" title="Available on crate feature `tokio` and (crate features `http1` or `http2`) only"><code>tokio</code> and (<code>http1</code> or <code>http2</code>)</span></div><div class="desc docblock-short">Serve the service with the supplied listener.</div></li>
+ </ul>
+ "##};
+ let expected = indoc! {r#"
+ ## Structs
+
+ - `Error`: Errors that can happen when using axum.
+ - `Extension`: Extractor and response for extensions.
+ - `Form` [`form`]: URL encoded extractor and response.
+ - `Json` [`json`]: JSON Extractor / Response.
+ - `Router`: The router type for composing handlers and services.
+
+ ## Functions
+
+ - `serve` [`tokio` and (`http1` or `http2`)]: Serve the service with the supplied listener.
+ "#}
+ .trim();
+
+ assert_eq!(
+ convert_rustdoc_to_markdown(html.as_bytes()).unwrap(),
+ expected
+ )
+ }
+
#[test]
fn test_table() {
let html = indoc! {r##"