rustdoc_to_markdown: Handle "stabs" in item name entries (#12494)

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
This commit is contained in:
Marshall Bowers 2024-05-30 13:46:14 -04:00 committed by GitHub
parent 39a2cdb13f
commit c83d1c23d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 70 additions and 6 deletions

View File

@ -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"
})
})
}
}

View File

@ -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##"