rust/src/librustdoc/html/markdown.rs

1368 lines
47 KiB
Rust
Raw Normal View History

2019-02-08 13:53:55 +00:00
//! Markdown formatting for rustdoc.
//!
//! This module implements markdown formatting through the pulldown-cmark library.
//!
//! ```
//! #![feature(rustc_private)]
//!
2020-01-01 18:40:49 +00:00
//! extern crate rustc_span;
2019-04-18 19:20:18 +00:00
//!
2020-01-01 18:40:49 +00:00
//! use rustc_span::edition::Edition;
//! use rustdoc::html::markdown::{IdMap, Markdown, ErrorCodes};
//!
//! let s = "My *markdown* _text_";
//! let mut id_map = IdMap::new();
//! let md = Markdown(s, &[], &mut id_map, ErrorCodes::Yes, Edition::Edition2015, &None);
//! let html = md.into_string();
//! // ... something using html
//! ```
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def_id::DefId;
use rustc_hir::HirId;
use rustc_middle::ty::TyCtxt;
use rustc_session::lint;
2020-01-01 18:40:49 +00:00
use rustc_span::edition::Edition;
use rustc_span::Span;
2019-12-22 22:42:04 +00:00
use std::borrow::Cow;
2020-12-31 09:26:24 +00:00
use std::cell::RefCell;
use std::collections::VecDeque;
use std::default::Default;
use std::fmt::Write;
use std::ops::Range;
use std::str;
use crate::clean::RenderedLink;
use crate::doctest;
2019-02-23 07:40:07 +00:00
use crate::html::highlight;
2019-12-22 22:42:04 +00:00
use crate::html::toc::TocBuilder;
use pulldown_cmark::{
html, BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag,
};
use super::format::Buffer;
#[cfg(test)]
mod tests;
/// Options for rendering Markdown in the main body of documentation.
2020-09-23 18:25:56 +00:00
pub(crate) fn opts() -> Options {
Options::ENABLE_TABLES
| Options::ENABLE_FOOTNOTES
| Options::ENABLE_STRIKETHROUGH
| Options::ENABLE_TASKLISTS
}
2017-03-11 00:43:36 +00:00
/// A subset of [`opts()`] used for rendering summaries.
pub(crate) fn summary_opts() -> Options {
Options::ENABLE_STRIKETHROUGH
}
/// When `to_string` is called, this struct will emit the HTML corresponding to
/// the rendered version of the contained markdown string.
pub struct Markdown<'a>(
pub &'a str,
2019-06-16 11:15:11 +00:00
/// A list of link replacements.
pub &'a [RenderedLink],
2019-06-16 11:15:11 +00:00
/// The current list of used header IDs.
pub &'a mut IdMap,
2019-06-16 11:15:11 +00:00
/// Whether to allow the use of explicit error codes in doctest lang strings.
pub ErrorCodes,
2019-06-16 11:15:11 +00:00
/// Default edition to use when parsing doctests (to add a `fn main`).
pub Edition,
pub &'a Option<Playground>,
2019-06-16 11:15:11 +00:00
);
/// A tuple struct like `Markdown` that renders the markdown with a table of contents.
crate struct MarkdownWithToc<'a>(
crate &'a str,
crate &'a mut IdMap,
crate ErrorCodes,
crate Edition,
crate &'a Option<Playground>,
2019-06-16 11:15:11 +00:00
);
/// A tuple struct like `Markdown` that renders the markdown escaping HTML tags.
crate struct MarkdownHtml<'a>(
crate &'a str,
crate &'a mut IdMap,
crate ErrorCodes,
crate Edition,
crate &'a Option<Playground>,
);
2019-06-16 11:15:11 +00:00
/// A tuple struct like `Markdown` that renders only the first paragraph.
crate struct MarkdownSummaryLine<'a>(pub &'a str, pub &'a [RenderedLink]);
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum ErrorCodes {
Yes,
No,
}
impl ErrorCodes {
crate fn from(b: bool) -> Self {
match b {
true => ErrorCodes::Yes,
false => ErrorCodes::No,
}
}
crate fn as_bool(self) -> bool {
match self {
ErrorCodes::Yes => true,
ErrorCodes::No => false,
}
}
}
/// Controls whether a line will be hidden or shown in HTML output.
///
/// All lines are used in documentation tests.
enum Line<'a> {
Hidden(&'a str),
2018-06-26 01:26:31 +00:00
Shown(Cow<'a, str>),
}
impl<'a> Line<'a> {
2018-06-26 01:26:31 +00:00
fn for_html(self) -> Option<Cow<'a, str>> {
match self {
Line::Shown(l) => Some(l),
Line::Hidden(_) => None,
}
}
2018-06-26 01:26:31 +00:00
fn for_code(self) -> Cow<'a, str> {
match self {
2018-06-26 01:26:31 +00:00
Line::Shown(l) => l,
Line::Hidden(l) => Cow::Borrowed(l),
}
}
}
// FIXME: There is a minor inconsistency here. For lines that start with ##, we
// have no easy way of removing a potential single space after the hashes, which
// is done in the single # case. This inconsistency seems okay, if non-ideal. In
// order to fix it we'd have to iterate to find the first non-# character, and
// then reallocate to remove it; which would make us return a String.
2019-02-23 07:40:07 +00:00
fn map_line(s: &str) -> Line<'_> {
let trimmed = s.trim();
if trimmed.starts_with("##") {
2018-06-26 01:26:31 +00:00
Line::Shown(Cow::Owned(s.replacen("##", "#", 1)))
} else if let Some(stripped) = trimmed.strip_prefix("# ") {
// # text
Line::Hidden(&stripped)
} else if trimmed == "#" {
// We cannot handle '#text' because it could be #[attr].
Line::Hidden("")
} else {
2018-06-26 01:26:31 +00:00
Line::Shown(Cow::Borrowed(s))
}
}
/// Convert chars from a title for an id.
///
/// "Hello, world!" -> "hello-world"
fn slugify(c: char) -> Option<char> {
if c.is_alphanumeric() || c == '-' || c == '_' {
2019-12-22 22:42:04 +00:00
if c.is_ascii() { Some(c.to_ascii_lowercase()) } else { Some(c) }
} else if c.is_whitespace() && c.is_ascii() {
Some('-')
} else {
None
}
}
#[derive(Clone, Debug)]
pub struct Playground {
pub crate_name: Option<String>,
pub url: String,
}
2019-02-08 13:53:55 +00:00
/// Adds syntax highlighting and playground Run buttons to Rust code blocks.
struct CodeBlocks<'p, 'a, I: Iterator<Item = Event<'a>>> {
inner: I,
check_error_codes: ErrorCodes,
edition: Edition,
// Information about the playground if a URL has been specified, containing an
// optional crate name and the URL.
playground: &'p Option<Playground>,
}
impl<'p, 'a, I: Iterator<Item = Event<'a>>> CodeBlocks<'p, 'a, I> {
fn new(
iter: I,
error_codes: ErrorCodes,
edition: Edition,
playground: &'p Option<Playground>,
) -> Self {
2019-12-22 22:42:04 +00:00
CodeBlocks { inner: iter, check_error_codes: error_codes, edition, playground }
}
}
impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
type Item = Event<'a>;
fn next(&mut self) -> Option<Self::Item> {
let event = self.inner.next();
2017-09-06 22:08:39 +00:00
let compile_fail;
let should_panic;
2017-09-06 22:08:39 +00:00
let ignore;
let edition;
2020-02-13 17:39:40 +00:00
if let Some(Event::Start(Tag::CodeBlock(kind))) = event {
let parse_result = match kind {
CodeBlockKind::Fenced(ref lang) => {
LangString::parse_without_check(&lang, self.check_error_codes, false)
2020-02-13 17:39:40 +00:00
}
CodeBlockKind::Indented => Default::default(),
2020-02-13 17:39:40 +00:00
};
2017-09-06 22:08:39 +00:00
if !parse_result.rust {
2020-02-13 17:39:40 +00:00
return Some(Event::Start(Tag::CodeBlock(kind)));
}
2017-09-06 22:08:39 +00:00
compile_fail = parse_result.compile_fail;
should_panic = parse_result.should_panic;
2017-09-06 22:08:39 +00:00
ignore = parse_result.ignore;
edition = parse_result.edition;
} else {
return event;
}
let explicit_edition = edition.is_some();
let edition = edition.unwrap_or(self.edition);
2017-03-08 00:01:23 +00:00
let mut origtext = String::new();
for event in &mut self.inner {
2017-03-11 00:43:36 +00:00
match event {
Event::End(Tag::CodeBlock(..)) => break,
2017-03-11 00:43:36 +00:00
Event::Text(ref s) => {
origtext.push_str(s);
2017-03-08 00:01:23 +00:00
}
2017-03-11 00:43:36 +00:00
_ => {}
2017-03-08 00:01:23 +00:00
}
}
let lines = origtext.lines().filter_map(|l| map_line(l).for_html());
2019-02-23 07:40:07 +00:00
let text = lines.collect::<Vec<Cow<'_, str>>>().join("\n");
let playground_button = self.playground.as_ref().and_then(|playground| {
let krate = &playground.crate_name;
let url = &playground.url;
if url.is_empty() {
return None;
}
2019-12-22 22:42:04 +00:00
let test = origtext
.lines()
.map(|l| map_line(l).for_code())
2019-12-22 22:42:04 +00:00
.collect::<Vec<Cow<'_, str>>>()
.join("\n");
let krate = krate.as_ref().map(|s| &**s);
2020-11-11 15:44:02 +00:00
let (test, _, _) =
doctest::make_test(&test, krate, false, &Default::default(), edition, None);
2019-12-22 22:42:04 +00:00
let channel = if test.contains("#![feature(") { "&amp;version=nightly" } else { "" };
let edition_string = format!("&amp;edition={}", edition);
// These characters don't need to be escaped in a URI.
// FIXME: use a library function for percent encoding.
fn dont_escape(c: u8) -> bool {
2019-12-22 22:42:04 +00:00
(b'a' <= c && c <= b'z')
|| (b'A' <= c && c <= b'Z')
|| (b'0' <= c && c <= b'9')
|| c == b'-'
|| c == b'_'
|| c == b'.'
|| c == b'~'
|| c == b'!'
|| c == b'\''
|| c == b'('
|| c == b')'
|| c == b'*'
}
let mut test_escaped = String::new();
for b in test.bytes() {
if dont_escape(b) {
test_escaped.push(char::from(b));
} else {
write!(test_escaped, "%{:02X}", b).unwrap();
}
}
Some(format!(
r#"<a class="test-arrow" target="_blank" href="{}?code={}{}{}">Run</a>"#,
url, test_escaped, channel, edition_string
))
});
2019-04-26 20:52:56 +00:00
let tooltip = if ignore != Ignore::None {
Some((None, "ignore"))
} else if compile_fail {
Some((None, "compile_fail"))
} else if should_panic {
Some((None, "should_panic"))
} else if explicit_edition {
Some((Some(edition), "edition"))
} else {
None
};
// insert newline to clearly separate it from the
// previous block so we can shorten the html output
let mut s = Buffer::new();
s.push_str("\n");
highlight::render_with_highlighting(
&text,
&mut s,
Some(&format!(
"rust-example-rendered{}",
if let Some((_, class)) = tooltip { format!(" {}", class) } else { String::new() }
)),
playground_button.as_deref(),
tooltip,
edition,
);
Some(Event::Html(s.into_inner().into()))
}
}
2019-02-08 13:53:55 +00:00
/// Make headings links with anchor IDs and build up TOC.
struct LinkReplacer<'a, I: Iterator<Item = Event<'a>>> {
2017-12-28 10:59:20 +00:00
inner: I,
links: &'a [RenderedLink],
shortcut_link: Option<&'a RenderedLink>,
2017-12-28 10:59:20 +00:00
}
impl<'a, I: Iterator<Item = Event<'a>>> LinkReplacer<'a, I> {
fn new(iter: I, links: &'a [RenderedLink]) -> Self {
LinkReplacer { inner: iter, links, shortcut_link: None }
2017-12-28 10:59:20 +00:00
}
}
impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
2017-12-28 10:59:20 +00:00
type Item = Event<'a>;
fn next(&mut self) -> Option<Self::Item> {
let mut event = self.inner.next();
// Replace intra-doc links and remove disambiguators from shortcut links (`[fn@f]`).
match &mut event {
// This is a shortcut link that was resolved by the broken_link_callback: `[fn@f]`
// Remove any disambiguator.
Some(Event::Start(Tag::Link(
// [fn@f] or [fn@f][]
LinkType::ShortcutUnknown | LinkType::CollapsedUnknown,
dest,
title,
))) => {
debug!("saw start of shortcut link to {} with title {}", dest, title);
// If this is a shortcut link, it was resolved by the broken_link_callback.
// So the URL will already be updated properly.
let link = self.links.iter().find(|&link| *link.href == **dest);
// Since this is an external iterator, we can't replace the inner text just yet.
// Store that we saw a link so we know to replace it later.
if let Some(link) = link {
trace!("it matched");
assert!(self.shortcut_link.is_none(), "shortcut links cannot be nested");
self.shortcut_link = Some(link);
}
2017-12-28 10:59:20 +00:00
}
// Now that we're done with the shortcut link, don't replace any more text.
Some(Event::End(Tag::Link(
LinkType::ShortcutUnknown | LinkType::CollapsedUnknown,
dest,
_,
))) => {
debug!("saw end of shortcut link to {}", dest);
if self.links.iter().any(|link| *link.href == **dest) {
assert!(self.shortcut_link.is_some(), "saw closing link without opening tag");
self.shortcut_link = None;
}
}
// Handle backticks in inline code blocks, but only if we're in the middle of a shortcut link.
// [`fn@f`]
Some(Event::Code(text)) => {
trace!("saw code {}", text);
if let Some(link) = self.shortcut_link {
trace!("original text was {}", link.original_text);
// NOTE: this only replaces if the code block is the *entire* text.
// If only part of the link has code highlighting, the disambiguator will not be removed.
// e.g. [fn@`f`]
// This is a limitation from `collect_intra_doc_links`: it passes a full link,
// and does not distinguish at all between code blocks.
// So we could never be sure we weren't replacing too much:
// [fn@my_`f`unc] is treated the same as [my_func()] in that pass.
//
// NOTE: &[1..len() - 1] is to strip the backticks
if **text == link.original_text[1..link.original_text.len() - 1] {
debug!("replacing {} with {}", text, link.new_text);
*text = CowStr::Borrowed(&link.new_text);
}
}
}
// Replace plain text in links, but only in the middle of a shortcut link.
// [fn@f]
Some(Event::Text(text)) => {
trace!("saw text {}", text);
if let Some(link) = self.shortcut_link {
trace!("original text was {}", link.original_text);
// NOTE: same limitations as `Event::Code`
if **text == *link.original_text {
debug!("replacing {} with {}", text, link.new_text);
*text = CowStr::Borrowed(&link.new_text);
}
}
}
// If this is a link, but not a shortcut link,
// replace the URL, since the broken_link_callback was not called.
Some(Event::Start(Tag::Link(_, dest, _))) => {
if let Some(link) = self.links.iter().find(|&link| *link.original_text == **dest) {
*dest = CowStr::Borrowed(link.href.as_ref());
}
}
// Anything else couldn't have been a valid Rust path, so no need to replace the text.
_ => {}
2017-12-28 10:59:20 +00:00
}
// Yield the modified event
event
2017-12-28 10:59:20 +00:00
}
}
type SpannedEvent<'a> = (Event<'a>, Range<usize>);
2019-02-08 13:53:55 +00:00
/// Make headings links with anchor IDs and build up TOC.
struct HeadingLinks<'a, 'b, 'ids, I> {
inner: I,
toc: Option<&'b mut TocBuilder>,
buf: VecDeque<SpannedEvent<'a>>,
id_map: &'ids mut IdMap,
}
impl<'a, 'b, 'ids, I> HeadingLinks<'a, 'b, 'ids, I> {
fn new(iter: I, toc: Option<&'b mut TocBuilder>, ids: &'ids mut IdMap) -> Self {
2019-12-22 22:42:04 +00:00
HeadingLinks { inner: iter, toc, buf: VecDeque::new(), id_map: ids }
}
}
impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator
for HeadingLinks<'a, 'b, 'ids, I>
{
type Item = SpannedEvent<'a>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(e) = self.buf.pop_front() {
return Some(e);
}
let event = self.inner.next();
if let Some((Event::Start(Tag::Heading(level)), _)) = event {
let mut id = String::new();
for event in &mut self.inner {
match &event.0 {
2020-02-13 17:39:40 +00:00
Event::End(Tag::Heading(..)) => break,
Event::Start(Tag::Link(_, _, _)) | Event::End(Tag::Link(..)) => {}
Event::Text(text) | Event::Code(text) => {
id.extend(text.chars().filter_map(slugify));
self.buf.push_back(event);
}
_ => self.buf.push_back(event),
2020-01-14 18:21:10 +00:00
}
}
let id = self.id_map.derive(id);
if let Some(ref mut builder) = self.toc {
let mut html_header = String::new();
html::push_html(&mut html_header, self.buf.iter().map(|(ev, _)| ev.clone()));
let sec = builder.push(level as u32, html_header, id.clone());
self.buf.push_front((Event::Html(format!("{} ", sec).into()), 0..0));
}
self.buf.push_back((Event::Html(format!("</a></h{}>", level).into()), 0..0));
2017-03-11 00:43:36 +00:00
2019-12-22 22:42:04 +00:00
let start_tags = format!(
"<h{level} id=\"{id}\" class=\"section-header\">\
2020-01-14 18:21:10 +00:00
<a href=\"#{id}\">",
2019-12-22 22:42:04 +00:00
id = id,
level = level
);
return Some((Event::Html(start_tags.into()), 0..0));
2017-03-11 00:43:36 +00:00
}
event
2017-03-11 00:43:36 +00:00
}
}
2017-03-11 00:43:36 +00:00
/// Extracts just the first paragraph.
struct SummaryLine<'a, I: Iterator<Item = Event<'a>>> {
inner: I,
started: bool,
depth: u32,
}
impl<'a, I: Iterator<Item = Event<'a>>> SummaryLine<'a, I> {
fn new(iter: I) -> Self {
2019-12-22 22:42:04 +00:00
SummaryLine { inner: iter, started: false, depth: 0 }
2017-03-11 00:43:36 +00:00
}
}
2017-03-11 00:43:36 +00:00
2019-02-23 07:40:07 +00:00
fn check_if_allowed_tag(t: &Tag<'_>) -> bool {
matches!(
t,
Tag::Paragraph | Tag::Item | Tag::Emphasis | Tag::Strong | Tag::Link(..) | Tag::BlockQuote
)
}
impl<'a, I: Iterator<Item = Event<'a>>> Iterator for SummaryLine<'a, I> {
type Item = Event<'a>;
2017-03-11 00:43:36 +00:00
fn next(&mut self) -> Option<Self::Item> {
if self.started && self.depth == 0 {
return None;
}
if !self.started {
self.started = true;
}
if let Some(event) = self.inner.next() {
let mut is_start = true;
let is_allowed_tag = match event {
Event::Start(Tag::CodeBlock(_)) | Event::End(Tag::CodeBlock(_)) => {
return None;
}
Event::Start(ref c) => {
self.depth += 1;
check_if_allowed_tag(c)
}
Event::End(ref c) => {
self.depth -= 1;
is_start = false;
check_if_allowed_tag(c)
}
2019-12-22 22:42:04 +00:00
_ => true,
};
return if !is_allowed_tag {
if is_start {
Some(Event::Start(Tag::Paragraph))
} else {
Some(Event::End(Tag::Paragraph))
}
} else {
Some(event)
};
}
None
2017-03-11 00:43:36 +00:00
}
}
2017-03-11 00:43:36 +00:00
/// Moves all footnote definitions to the end and add back links to the
/// references.
struct Footnotes<'a, I> {
inner: I,
footnotes: FxHashMap<String, (Vec<Event<'a>>, u16)>,
}
impl<'a, I> Footnotes<'a, I> {
fn new(iter: I) -> Self {
2019-12-22 22:42:04 +00:00
Footnotes { inner: iter, footnotes: FxHashMap::default() }
}
fn get_entry(&mut self, key: &str) -> &mut (Vec<Event<'a>>, u16) {
let new_id = self.footnotes.keys().count() + 1;
let key = key.to_owned();
self.footnotes.entry(key).or_insert((Vec::new(), new_id as u16))
}
}
impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Iterator for Footnotes<'a, I> {
type Item = SpannedEvent<'a>;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.inner.next() {
Some((Event::FootnoteReference(ref reference), range)) => {
let entry = self.get_entry(&reference);
2019-12-22 22:42:04 +00:00
let reference = format!(
2020-08-31 11:16:50 +00:00
"<sup id=\"fnref{0}\"><a href=\"#fn{0}\">{0}</a></sup>",
2019-12-22 22:42:04 +00:00
(*entry).1
);
return Some((Event::Html(reference.into()), range));
}
Some((Event::Start(Tag::FootnoteDefinition(def)), _)) => {
let mut content = Vec::new();
for (event, _) in &mut self.inner {
if let Event::End(Tag::FootnoteDefinition(..)) = event {
break;
}
content.push(event);
}
let entry = self.get_entry(&def);
(*entry).0 = content;
}
Some(e) => return Some(e),
None => {
if !self.footnotes.is_empty() {
let mut v: Vec<_> = self.footnotes.drain().map(|(_, x)| x).collect();
v.sort_by(|a, b| a.1.cmp(&b.1));
let mut ret = String::from("<div class=\"footnotes\"><hr><ol>");
for (mut content, id) in v {
write!(ret, "<li id=\"fn{}\">", id).unwrap();
let mut is_paragraph = false;
if let Some(&Event::End(Tag::Paragraph)) = content.last() {
content.pop();
is_paragraph = true;
}
html::push_html(&mut ret, content.into_iter());
2019-12-22 22:42:04 +00:00
write!(ret, "&nbsp;<a href=\"#fnref{}\" rev=\"footnote\">↩</a>", id)
.unwrap();
if is_paragraph {
ret.push_str("</p>");
}
ret.push_str("</li>");
}
ret.push_str("</ol></div>");
return Some((Event::Html(ret.into()), 0..0));
} else {
return None;
2017-03-31 17:02:46 +00:00
}
}
2017-03-08 00:01:23 +00:00
}
2017-03-08 22:56:00 +00:00
}
}
2017-03-08 00:01:23 +00:00
}
crate fn find_testable_code<T: doctest::Tester>(
2019-12-22 22:42:04 +00:00
doc: &str,
tests: &mut T,
error_codes: ErrorCodes,
enable_per_target_ignores: bool,
2021-01-01 04:25:30 +00:00
extra_info: Option<&ExtraInfo<'_>>,
2019-12-22 22:42:04 +00:00
) {
2020-02-13 17:39:40 +00:00
let mut parser = Parser::new(doc).into_offset_iter();
2017-03-08 00:01:23 +00:00
let mut prev_offset = 0;
let mut nb_lines = 0;
let mut register_header = None;
2020-02-13 17:39:40 +00:00
while let Some((event, offset)) = parser.next() {
match event {
2020-02-13 17:39:40 +00:00
Event::Start(Tag::CodeBlock(kind)) => {
let block_info = match kind {
CodeBlockKind::Fenced(ref lang) => {
if lang.is_empty() {
Default::default()
2020-02-13 17:39:40 +00:00
} else {
LangString::parse(
lang,
error_codes,
enable_per_target_ignores,
extra_info,
)
2020-02-13 17:39:40 +00:00
}
}
CodeBlockKind::Indented => Default::default(),
};
if !block_info.rust {
continue;
}
2020-02-13 17:39:40 +00:00
let mut test_s = String::new();
2020-02-13 17:39:40 +00:00
while let Some((Event::Text(s), _)) = parser.next() {
test_s.push_str(&s);
2018-02-20 19:30:29 +00:00
}
let text = test_s
.lines()
.map(|l| map_line(l).for_code())
.collect::<Vec<Cow<'_, str>>>()
.join("\n");
2020-02-13 17:39:40 +00:00
nb_lines += doc[prev_offset..offset.start].lines().count();
let line = tests.get_line() + nb_lines + 1;
tests.add_test(text, block_info, line);
2020-02-13 17:39:40 +00:00
prev_offset = offset.start;
2017-03-08 00:01:23 +00:00
}
2020-02-13 17:39:40 +00:00
Event::Start(Tag::Heading(level)) => {
register_header = Some(level as u32);
}
Event::Text(ref s) if register_header.is_some() => {
let level = register_header.unwrap();
if s.is_empty() {
tests.register_header("", level);
} else {
tests.register_header(s, level);
}
register_header = None;
}
_ => {}
2017-03-08 00:01:23 +00:00
}
}
}
2021-01-01 04:25:30 +00:00
crate struct ExtraInfo<'tcx> {
hir_id: Option<HirId>,
item_did: Option<DefId>,
sp: Span,
2021-01-01 04:25:30 +00:00
tcx: TyCtxt<'tcx>,
}
2021-01-01 04:25:30 +00:00
impl<'tcx> ExtraInfo<'tcx> {
crate fn new(tcx: TyCtxt<'tcx>, hir_id: HirId, sp: Span) -> ExtraInfo<'tcx> {
ExtraInfo { hir_id: Some(hir_id), item_did: None, sp, tcx }
}
2021-01-01 04:25:30 +00:00
crate fn new_did(tcx: TyCtxt<'tcx>, did: DefId, sp: Span) -> ExtraInfo<'tcx> {
ExtraInfo { hir_id: None, item_did: Some(did), sp, tcx }
}
fn error_invalid_codeblock_attr(&self, msg: &str, help: &str) {
let hir_id = match (self.hir_id, self.item_did) {
(Some(h), _) => h,
(None, Some(item_did)) => {
match item_did.as_local() {
Some(item_did) => self.tcx.hir().local_def_id_to_hir_id(item_did),
None => {
// If non-local, no need to check anything.
return;
}
}
}
(None, None) => return,
};
self.tcx.struct_span_lint_hir(
lint::builtin::INVALID_CODEBLOCK_ATTRIBUTES,
hir_id,
self.sp,
|lint| {
let mut diag = lint.build(msg);
diag.help(help);
diag.emit();
},
);
}
}
2015-01-28 13:34:18 +00:00
#[derive(Eq, PartialEq, Clone, Debug)]
crate struct LangString {
original: String,
crate should_panic: bool,
crate no_run: bool,
crate ignore: Ignore,
crate rust: bool,
crate test_harness: bool,
crate compile_fail: bool,
crate error_codes: Vec<String>,
crate allow_fail: bool,
crate edition: Option<Edition>,
}
2019-04-26 20:52:56 +00:00
#[derive(Eq, PartialEq, Clone, Debug)]
crate enum Ignore {
2019-04-26 20:52:56 +00:00
All,
None,
Some(Vec<String>),
}
impl Default for LangString {
fn default() -> Self {
Self {
original: String::new(),
should_panic: false,
no_run: false,
2019-04-26 20:52:56 +00:00
ignore: Ignore::None,
rust: true,
test_harness: false,
2016-01-05 22:38:11 +00:00
compile_fail: false,
2016-06-09 21:50:52 +00:00
error_codes: Vec::new(),
allow_fail: false,
edition: None,
}
}
}
impl LangString {
fn parse_without_check(
string: &str,
allow_error_code_check: ErrorCodes,
enable_per_target_ignores: bool,
) -> LangString {
Self::parse(string, allow_error_code_check, enable_per_target_ignores, None)
}
fn parse(
string: &str,
allow_error_code_check: ErrorCodes,
2019-12-22 22:42:04 +00:00
enable_per_target_ignores: bool,
2021-01-01 04:25:30 +00:00
extra: Option<&ExtraInfo<'_>>,
) -> LangString {
let allow_error_code_check = allow_error_code_check.as_bool();
let mut seen_rust_tags = false;
let mut seen_other_tags = false;
let mut data = LangString::default();
2019-04-26 20:52:56 +00:00
let mut ignores = vec![];
data.original = string.to_owned();
2019-12-22 22:42:04 +00:00
let tokens = string.split(|c: char| !(c == '_' || c == '-' || c.is_alphanumeric()));
for token in tokens {
match token.trim() {
2019-12-22 22:42:04 +00:00
"" => {}
2017-04-09 16:31:59 +00:00
"should_panic" => {
data.should_panic = true;
seen_rust_tags = !seen_other_tags;
2017-04-09 16:31:59 +00:00
}
2019-12-22 22:42:04 +00:00
"no_run" => {
data.no_run = true;
seen_rust_tags = !seen_other_tags;
}
"ignore" => {
data.ignore = Ignore::All;
seen_rust_tags = !seen_other_tags;
}
x if x.starts_with("ignore-") => {
if enable_per_target_ignores {
ignores.push(x.trim_start_matches("ignore-").to_owned());
seen_rust_tags = !seen_other_tags;
}
}
"allow_fail" => {
data.allow_fail = true;
2019-04-26 20:52:56 +00:00
seen_rust_tags = !seen_other_tags;
}
2019-12-22 22:42:04 +00:00
"rust" => {
data.rust = true;
seen_rust_tags = true;
}
2017-04-09 16:31:59 +00:00
"test_harness" => {
data.test_harness = true;
seen_rust_tags = !seen_other_tags || seen_rust_tags;
2017-04-09 16:31:59 +00:00
}
2017-08-17 20:11:41 +00:00
"compile_fail" => {
data.compile_fail = true;
seen_rust_tags = !seen_other_tags || seen_rust_tags;
data.no_run = true;
2016-06-09 21:50:52 +00:00
}
x if x.starts_with("edition") => {
data.edition = x[7..].parse::<Edition>().ok();
}
x if allow_error_code_check && x.starts_with('E') && x.len() == 5 => {
if x[1..].parse::<u32>().is_ok() {
2016-06-09 21:50:52 +00:00
data.error_codes.push(x.to_owned());
seen_rust_tags = !seen_other_tags || seen_rust_tags;
2016-06-09 21:50:52 +00:00
} else {
seen_other_tags = true;
}
}
x if extra.is_some() => {
let s = x.to_lowercase();
match if s == "compile-fail" || s == "compile_fail" || s == "compilefail" {
Some((
"compile_fail",
"the code block will either not be tested if not marked as a rust one \
or won't fail if it compiles successfully",
))
} else if s == "should-panic" || s == "should_panic" || s == "shouldpanic" {
Some((
"should_panic",
"the code block will either not be tested if not marked as a rust one \
or won't fail if it doesn't panic when running",
))
} else if s == "no-run" || s == "no_run" || s == "norun" {
Some((
"no_run",
"the code block will either not be tested if not marked as a rust one \
or will be run (which you might not want)",
))
} else if s == "allow-fail" || s == "allow_fail" || s == "allowfail" {
Some((
"allow_fail",
"the code block will either not be tested if not marked as a rust one \
or will be run (which you might not want)",
))
} else if s == "test-harness" || s == "test_harness" || s == "testharness" {
Some((
"test_harness",
"the code block will either not be tested if not marked as a rust one \
or the code will be wrapped inside a main function",
))
} else {
None
} {
Some((flag, help)) => {
if let Some(ref extra) = extra {
extra.error_invalid_codeblock_attr(
&format!("unknown attribute `{}`. Did you mean `{}`?", x, flag),
help,
);
}
}
None => {}
}
seen_other_tags = true;
}
2019-12-22 22:42:04 +00:00
_ => seen_other_tags = true,
}
}
// ignore-foo overrides ignore
if !ignores.is_empty() {
data.ignore = Ignore::Some(ignores);
2019-04-26 20:52:56 +00:00
}
data.rust &= !seen_other_tags || seen_rust_tags;
data
}
}
impl Markdown<'_> {
pub fn into_string(self) -> String {
let Markdown(md, links, mut ids, codes, edition, playground) = self;
2017-04-20 22:32:23 +00:00
// This is actually common enough to special-case
2019-12-22 22:42:04 +00:00
if md.is_empty() {
return String::new();
}
let mut replacer = |broken_link: BrokenLink<'_>| {
if let Some(link) =
links.iter().find(|link| &*link.original_text == broken_link.reference)
{
Some((link.href.as_str().into(), link.new_text.as_str().into()))
2018-02-18 22:32:34 +00:00
} else {
None
}
};
let p = Parser::new_with_broken_link_callback(md, opts(), Some(&mut replacer));
let p = p.into_offset_iter();
Remove hoedown from rustdoc Is it really time? Have our months, no, *years* of suffering come to an end? Are we finally able to cast off the pall of Hoedown? The weight which has dragged us down for so long? ----- So, timeline for those who need to catch up: * Way back in December 2016, [we decided we wanted to switch out the markdown renderer](https://github.com/rust-lang/rust/issues/38400). However, this was put on hold because the build system at the time made it difficult to pull in dependencies from crates.io. * A few months later, in March 2017, [the first PR was done, to switch out the renderers entirely](https://github.com/rust-lang/rust/pull/40338). The PR itself was fraught with CI and build system issues, but eventually landed. * However, not all was well in the Rustdoc world. During the PR and shortly after, we noticed [some differences in the way the two parsers handled some things](https://github.com/rust-lang/rust/issues/40912), and some of these differences were major enough to break the docs for some crates. * A couple weeks afterward, [Hoedown was put back in](https://github.com/rust-lang/rust/pull/41290), at this point just to catch tests that Pulldown was "spuriously" running. This would at least provide some warning about spurious tests, rather than just breaking spontaneously. * However, the problems had created enough noise by this point that just a few days after that, [Hoedown was switched back to the default](https://github.com/rust-lang/rust/pull/41431) while we came up with a solution for properly warning about the differences. * That solution came a few weeks later, [as a series of warnings when the HTML emitted by the two parsers was semantically different](https://github.com/rust-lang/rust/pull/41991). But that came at a cost, as now rustdoc needed proc-macro support (the new crate needed some custom derives farther down its dependency tree), and the build system was not equipped to handle it at the time. It was worked on for three months as the issue stumped more and more people. * In that time, [bootstrap was completely reworked](https://github.com/rust-lang/rust/pull/43059) to change how it ordered compilation, and [the method by which it built rustdoc would change](https://github.com/rust-lang/rust/pull/43482), as well. This allowed it to only be built after stage1, when proc-macros would be available, allowing the "rendering differences" PR to finally land. * The warnings were not perfect, and revealed a few [spurious](https://github.com/rust-lang/rust/pull/44368) [differences](https://github.com/rust-lang/rust/pull/45421) between how we handled the renderers. * Once these were handled, [we flipped the switch to turn on the "rendering difference" warnings all the time](https://github.com/rust-lang/rust/pull/45324), in October 2017. This began the "warning cycle" for this change, and landed in stable in 1.23, on 2018-01-04. * Once those warnings hit stable, and after a couple weeks of seeing whether we would get any more reports than what we got from sitting on nightly/beta, [we switched the renderers](https://github.com/rust-lang/rust/pull/47398), making Pulldown the default but still offering the option to use Hoedown. And that brings us to the present. We haven't received more new issues from this in the meantime, and the "switch by default" is now on beta. Our reasoning is that, at this point, anyone who would have been affected by this has run into it already.
2018-02-16 14:09:19 +00:00
let mut s = String::with_capacity(md.len() * 3 / 2);
let p = HeadingLinks::new(p, None, &mut ids);
let p = Footnotes::new(p);
let p = LinkReplacer::new(p.map(|(ev, _)| ev), links);
let p = CodeBlocks::new(p, codes, edition, playground);
2018-07-22 13:39:52 +00:00
html::push_html(&mut s, p);
s
}
}
impl MarkdownWithToc<'_> {
crate fn into_string(self) -> String {
let MarkdownWithToc(md, mut ids, codes, edition, playground) = self;
let p = Parser::new_ext(md, opts()).into_offset_iter();
Remove hoedown from rustdoc Is it really time? Have our months, no, *years* of suffering come to an end? Are we finally able to cast off the pall of Hoedown? The weight which has dragged us down for so long? ----- So, timeline for those who need to catch up: * Way back in December 2016, [we decided we wanted to switch out the markdown renderer](https://github.com/rust-lang/rust/issues/38400). However, this was put on hold because the build system at the time made it difficult to pull in dependencies from crates.io. * A few months later, in March 2017, [the first PR was done, to switch out the renderers entirely](https://github.com/rust-lang/rust/pull/40338). The PR itself was fraught with CI and build system issues, but eventually landed. * However, not all was well in the Rustdoc world. During the PR and shortly after, we noticed [some differences in the way the two parsers handled some things](https://github.com/rust-lang/rust/issues/40912), and some of these differences were major enough to break the docs for some crates. * A couple weeks afterward, [Hoedown was put back in](https://github.com/rust-lang/rust/pull/41290), at this point just to catch tests that Pulldown was "spuriously" running. This would at least provide some warning about spurious tests, rather than just breaking spontaneously. * However, the problems had created enough noise by this point that just a few days after that, [Hoedown was switched back to the default](https://github.com/rust-lang/rust/pull/41431) while we came up with a solution for properly warning about the differences. * That solution came a few weeks later, [as a series of warnings when the HTML emitted by the two parsers was semantically different](https://github.com/rust-lang/rust/pull/41991). But that came at a cost, as now rustdoc needed proc-macro support (the new crate needed some custom derives farther down its dependency tree), and the build system was not equipped to handle it at the time. It was worked on for three months as the issue stumped more and more people. * In that time, [bootstrap was completely reworked](https://github.com/rust-lang/rust/pull/43059) to change how it ordered compilation, and [the method by which it built rustdoc would change](https://github.com/rust-lang/rust/pull/43482), as well. This allowed it to only be built after stage1, when proc-macros would be available, allowing the "rendering differences" PR to finally land. * The warnings were not perfect, and revealed a few [spurious](https://github.com/rust-lang/rust/pull/44368) [differences](https://github.com/rust-lang/rust/pull/45421) between how we handled the renderers. * Once these were handled, [we flipped the switch to turn on the "rendering difference" warnings all the time](https://github.com/rust-lang/rust/pull/45324), in October 2017. This began the "warning cycle" for this change, and landed in stable in 1.23, on 2018-01-04. * Once those warnings hit stable, and after a couple weeks of seeing whether we would get any more reports than what we got from sitting on nightly/beta, [we switched the renderers](https://github.com/rust-lang/rust/pull/47398), making Pulldown the default but still offering the option to use Hoedown. And that brings us to the present. We haven't received more new issues from this in the meantime, and the "switch by default" is now on beta. Our reasoning is that, at this point, anyone who would have been affected by this has run into it already.
2018-02-16 14:09:19 +00:00
let mut s = String::with_capacity(md.len() * 3 / 2);
Remove hoedown from rustdoc Is it really time? Have our months, no, *years* of suffering come to an end? Are we finally able to cast off the pall of Hoedown? The weight which has dragged us down for so long? ----- So, timeline for those who need to catch up: * Way back in December 2016, [we decided we wanted to switch out the markdown renderer](https://github.com/rust-lang/rust/issues/38400). However, this was put on hold because the build system at the time made it difficult to pull in dependencies from crates.io. * A few months later, in March 2017, [the first PR was done, to switch out the renderers entirely](https://github.com/rust-lang/rust/pull/40338). The PR itself was fraught with CI and build system issues, but eventually landed. * However, not all was well in the Rustdoc world. During the PR and shortly after, we noticed [some differences in the way the two parsers handled some things](https://github.com/rust-lang/rust/issues/40912), and some of these differences were major enough to break the docs for some crates. * A couple weeks afterward, [Hoedown was put back in](https://github.com/rust-lang/rust/pull/41290), at this point just to catch tests that Pulldown was "spuriously" running. This would at least provide some warning about spurious tests, rather than just breaking spontaneously. * However, the problems had created enough noise by this point that just a few days after that, [Hoedown was switched back to the default](https://github.com/rust-lang/rust/pull/41431) while we came up with a solution for properly warning about the differences. * That solution came a few weeks later, [as a series of warnings when the HTML emitted by the two parsers was semantically different](https://github.com/rust-lang/rust/pull/41991). But that came at a cost, as now rustdoc needed proc-macro support (the new crate needed some custom derives farther down its dependency tree), and the build system was not equipped to handle it at the time. It was worked on for three months as the issue stumped more and more people. * In that time, [bootstrap was completely reworked](https://github.com/rust-lang/rust/pull/43059) to change how it ordered compilation, and [the method by which it built rustdoc would change](https://github.com/rust-lang/rust/pull/43482), as well. This allowed it to only be built after stage1, when proc-macros would be available, allowing the "rendering differences" PR to finally land. * The warnings were not perfect, and revealed a few [spurious](https://github.com/rust-lang/rust/pull/44368) [differences](https://github.com/rust-lang/rust/pull/45421) between how we handled the renderers. * Once these were handled, [we flipped the switch to turn on the "rendering difference" warnings all the time](https://github.com/rust-lang/rust/pull/45324), in October 2017. This began the "warning cycle" for this change, and landed in stable in 1.23, on 2018-01-04. * Once those warnings hit stable, and after a couple weeks of seeing whether we would get any more reports than what we got from sitting on nightly/beta, [we switched the renderers](https://github.com/rust-lang/rust/pull/47398), making Pulldown the default but still offering the option to use Hoedown. And that brings us to the present. We haven't received more new issues from this in the meantime, and the "switch by default" is now on beta. Our reasoning is that, at this point, anyone who would have been affected by this has run into it already.
2018-02-16 14:09:19 +00:00
let mut toc = TocBuilder::new();
{
let p = HeadingLinks::new(p, Some(&mut toc), &mut ids);
let p = Footnotes::new(p);
let p = CodeBlocks::new(p.map(|(ev, _)| ev), codes, edition, playground);
html::push_html(&mut s, p);
}
2019-09-13 12:41:27 +00:00
format!("<nav id=\"TOC\">{}</nav>{}", toc.into_toc().print(), s)
}
}
impl MarkdownHtml<'_> {
crate fn into_string(self) -> String {
let MarkdownHtml(md, mut ids, codes, edition, playground) = self;
2017-04-20 22:32:23 +00:00
// This is actually common enough to special-case
2019-12-22 22:42:04 +00:00
if md.is_empty() {
return String::new();
}
let p = Parser::new_ext(md, opts()).into_offset_iter();
Remove hoedown from rustdoc Is it really time? Have our months, no, *years* of suffering come to an end? Are we finally able to cast off the pall of Hoedown? The weight which has dragged us down for so long? ----- So, timeline for those who need to catch up: * Way back in December 2016, [we decided we wanted to switch out the markdown renderer](https://github.com/rust-lang/rust/issues/38400). However, this was put on hold because the build system at the time made it difficult to pull in dependencies from crates.io. * A few months later, in March 2017, [the first PR was done, to switch out the renderers entirely](https://github.com/rust-lang/rust/pull/40338). The PR itself was fraught with CI and build system issues, but eventually landed. * However, not all was well in the Rustdoc world. During the PR and shortly after, we noticed [some differences in the way the two parsers handled some things](https://github.com/rust-lang/rust/issues/40912), and some of these differences were major enough to break the docs for some crates. * A couple weeks afterward, [Hoedown was put back in](https://github.com/rust-lang/rust/pull/41290), at this point just to catch tests that Pulldown was "spuriously" running. This would at least provide some warning about spurious tests, rather than just breaking spontaneously. * However, the problems had created enough noise by this point that just a few days after that, [Hoedown was switched back to the default](https://github.com/rust-lang/rust/pull/41431) while we came up with a solution for properly warning about the differences. * That solution came a few weeks later, [as a series of warnings when the HTML emitted by the two parsers was semantically different](https://github.com/rust-lang/rust/pull/41991). But that came at a cost, as now rustdoc needed proc-macro support (the new crate needed some custom derives farther down its dependency tree), and the build system was not equipped to handle it at the time. It was worked on for three months as the issue stumped more and more people. * In that time, [bootstrap was completely reworked](https://github.com/rust-lang/rust/pull/43059) to change how it ordered compilation, and [the method by which it built rustdoc would change](https://github.com/rust-lang/rust/pull/43482), as well. This allowed it to only be built after stage1, when proc-macros would be available, allowing the "rendering differences" PR to finally land. * The warnings were not perfect, and revealed a few [spurious](https://github.com/rust-lang/rust/pull/44368) [differences](https://github.com/rust-lang/rust/pull/45421) between how we handled the renderers. * Once these were handled, [we flipped the switch to turn on the "rendering difference" warnings all the time](https://github.com/rust-lang/rust/pull/45324), in October 2017. This began the "warning cycle" for this change, and landed in stable in 1.23, on 2018-01-04. * Once those warnings hit stable, and after a couple weeks of seeing whether we would get any more reports than what we got from sitting on nightly/beta, [we switched the renderers](https://github.com/rust-lang/rust/pull/47398), making Pulldown the default but still offering the option to use Hoedown. And that brings us to the present. We haven't received more new issues from this in the meantime, and the "switch by default" is now on beta. Our reasoning is that, at this point, anyone who would have been affected by this has run into it already.
2018-02-16 14:09:19 +00:00
// Treat inline HTML as plain text.
let p = p.map(|event| match event.0 {
Event::Html(text) => (Event::Text(text), event.1),
2019-12-22 22:42:04 +00:00
_ => event,
Remove hoedown from rustdoc Is it really time? Have our months, no, *years* of suffering come to an end? Are we finally able to cast off the pall of Hoedown? The weight which has dragged us down for so long? ----- So, timeline for those who need to catch up: * Way back in December 2016, [we decided we wanted to switch out the markdown renderer](https://github.com/rust-lang/rust/issues/38400). However, this was put on hold because the build system at the time made it difficult to pull in dependencies from crates.io. * A few months later, in March 2017, [the first PR was done, to switch out the renderers entirely](https://github.com/rust-lang/rust/pull/40338). The PR itself was fraught with CI and build system issues, but eventually landed. * However, not all was well in the Rustdoc world. During the PR and shortly after, we noticed [some differences in the way the two parsers handled some things](https://github.com/rust-lang/rust/issues/40912), and some of these differences were major enough to break the docs for some crates. * A couple weeks afterward, [Hoedown was put back in](https://github.com/rust-lang/rust/pull/41290), at this point just to catch tests that Pulldown was "spuriously" running. This would at least provide some warning about spurious tests, rather than just breaking spontaneously. * However, the problems had created enough noise by this point that just a few days after that, [Hoedown was switched back to the default](https://github.com/rust-lang/rust/pull/41431) while we came up with a solution for properly warning about the differences. * That solution came a few weeks later, [as a series of warnings when the HTML emitted by the two parsers was semantically different](https://github.com/rust-lang/rust/pull/41991). But that came at a cost, as now rustdoc needed proc-macro support (the new crate needed some custom derives farther down its dependency tree), and the build system was not equipped to handle it at the time. It was worked on for three months as the issue stumped more and more people. * In that time, [bootstrap was completely reworked](https://github.com/rust-lang/rust/pull/43059) to change how it ordered compilation, and [the method by which it built rustdoc would change](https://github.com/rust-lang/rust/pull/43482), as well. This allowed it to only be built after stage1, when proc-macros would be available, allowing the "rendering differences" PR to finally land. * The warnings were not perfect, and revealed a few [spurious](https://github.com/rust-lang/rust/pull/44368) [differences](https://github.com/rust-lang/rust/pull/45421) between how we handled the renderers. * Once these were handled, [we flipped the switch to turn on the "rendering difference" warnings all the time](https://github.com/rust-lang/rust/pull/45324), in October 2017. This began the "warning cycle" for this change, and landed in stable in 1.23, on 2018-01-04. * Once those warnings hit stable, and after a couple weeks of seeing whether we would get any more reports than what we got from sitting on nightly/beta, [we switched the renderers](https://github.com/rust-lang/rust/pull/47398), making Pulldown the default but still offering the option to use Hoedown. And that brings us to the present. We haven't received more new issues from this in the meantime, and the "switch by default" is now on beta. Our reasoning is that, at this point, anyone who would have been affected by this has run into it already.
2018-02-16 14:09:19 +00:00
});
Remove hoedown from rustdoc Is it really time? Have our months, no, *years* of suffering come to an end? Are we finally able to cast off the pall of Hoedown? The weight which has dragged us down for so long? ----- So, timeline for those who need to catch up: * Way back in December 2016, [we decided we wanted to switch out the markdown renderer](https://github.com/rust-lang/rust/issues/38400). However, this was put on hold because the build system at the time made it difficult to pull in dependencies from crates.io. * A few months later, in March 2017, [the first PR was done, to switch out the renderers entirely](https://github.com/rust-lang/rust/pull/40338). The PR itself was fraught with CI and build system issues, but eventually landed. * However, not all was well in the Rustdoc world. During the PR and shortly after, we noticed [some differences in the way the two parsers handled some things](https://github.com/rust-lang/rust/issues/40912), and some of these differences were major enough to break the docs for some crates. * A couple weeks afterward, [Hoedown was put back in](https://github.com/rust-lang/rust/pull/41290), at this point just to catch tests that Pulldown was "spuriously" running. This would at least provide some warning about spurious tests, rather than just breaking spontaneously. * However, the problems had created enough noise by this point that just a few days after that, [Hoedown was switched back to the default](https://github.com/rust-lang/rust/pull/41431) while we came up with a solution for properly warning about the differences. * That solution came a few weeks later, [as a series of warnings when the HTML emitted by the two parsers was semantically different](https://github.com/rust-lang/rust/pull/41991). But that came at a cost, as now rustdoc needed proc-macro support (the new crate needed some custom derives farther down its dependency tree), and the build system was not equipped to handle it at the time. It was worked on for three months as the issue stumped more and more people. * In that time, [bootstrap was completely reworked](https://github.com/rust-lang/rust/pull/43059) to change how it ordered compilation, and [the method by which it built rustdoc would change](https://github.com/rust-lang/rust/pull/43482), as well. This allowed it to only be built after stage1, when proc-macros would be available, allowing the "rendering differences" PR to finally land. * The warnings were not perfect, and revealed a few [spurious](https://github.com/rust-lang/rust/pull/44368) [differences](https://github.com/rust-lang/rust/pull/45421) between how we handled the renderers. * Once these were handled, [we flipped the switch to turn on the "rendering difference" warnings all the time](https://github.com/rust-lang/rust/pull/45324), in October 2017. This began the "warning cycle" for this change, and landed in stable in 1.23, on 2018-01-04. * Once those warnings hit stable, and after a couple weeks of seeing whether we would get any more reports than what we got from sitting on nightly/beta, [we switched the renderers](https://github.com/rust-lang/rust/pull/47398), making Pulldown the default but still offering the option to use Hoedown. And that brings us to the present. We haven't received more new issues from this in the meantime, and the "switch by default" is now on beta. Our reasoning is that, at this point, anyone who would have been affected by this has run into it already.
2018-02-16 14:09:19 +00:00
let mut s = String::with_capacity(md.len() * 3 / 2);
let p = HeadingLinks::new(p, None, &mut ids);
let p = Footnotes::new(p);
let p = CodeBlocks::new(p.map(|(ev, _)| ev), codes, edition, playground);
html::push_html(&mut s, p);
s
}
}
impl MarkdownSummaryLine<'_> {
crate fn into_string(self) -> String {
let MarkdownSummaryLine(md, links) = self;
// This is actually common enough to special-case
2019-12-22 22:42:04 +00:00
if md.is_empty() {
return String::new();
}
let mut replacer = |broken_link: BrokenLink<'_>| {
if let Some(link) =
links.iter().find(|link| &*link.original_text == broken_link.reference)
{
Some((link.href.as_str().into(), link.new_text.as_str().into()))
2018-02-18 22:32:34 +00:00
} else {
None
}
};
let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer));
let mut s = String::new();
2017-12-28 10:59:20 +00:00
html::push_html(&mut s, LinkReplacer::new(SummaryLine::new(p), links));
s
}
}
/// Renders a subset of Markdown in the first paragraph of the provided Markdown.
///
/// - *Italics*, **bold**, and `inline code` styles **are** rendered.
/// - Headings and links are stripped (though the text *is* rendered).
/// - HTML, code blocks, and everything else are ignored.
///
/// Returns a tuple of the rendered HTML string and whether the output was shortened
/// due to the provided `length_limit`.
2020-11-24 03:26:15 +00:00
fn markdown_summary_with_limit(md: &str, length_limit: usize) -> (String, bool) {
if md.is_empty() {
return (String::new(), false);
}
let mut s = String::with_capacity(md.len() * 3 / 2);
let mut text_length = 0;
let mut stopped_early = false;
fn push(s: &mut String, text_length: &mut usize, text: &str) {
s.push_str(text);
*text_length += text.len();
}
'outer: for event in Parser::new_ext(md, summary_opts()) {
match &event {
Event::Text(text) => {
for word in text.split_inclusive(char::is_whitespace) {
if text_length + word.len() >= length_limit {
stopped_early = true;
break 'outer;
}
push(&mut s, &mut text_length, word);
}
}
Event::Code(code) => {
if text_length + code.len() >= length_limit {
stopped_early = true;
break;
}
s.push_str("<code>");
push(&mut s, &mut text_length, code);
s.push_str("</code>");
}
Event::Start(tag) => match tag {
Tag::Emphasis => s.push_str("<em>"),
Tag::Strong => s.push_str("<strong>"),
Tag::CodeBlock(..) => break,
_ => {}
},
Event::End(tag) => match tag {
Tag::Emphasis => s.push_str("</em>"),
Tag::Strong => s.push_str("</strong>"),
Tag::Paragraph => break,
_ => {}
},
Event::HardBreak | Event::SoftBreak => {
if text_length + 1 >= length_limit {
stopped_early = true;
break;
}
push(&mut s, &mut text_length, " ");
}
_ => {}
}
}
(s, stopped_early)
}
/// Renders a shortened first paragraph of the given Markdown as a subset of Markdown,
/// making it suitable for contexts like the search index.
///
/// Will shorten to 59 or 60 characters, including an ellipsis (…) if it was shortened.
///
/// See [`markdown_summary_with_limit`] for details about what is rendered and what is not.
crate fn short_markdown_summary(markdown: &str) -> String {
2020-11-24 03:26:15 +00:00
let (mut s, was_shortened) = markdown_summary_with_limit(markdown, 59);
if was_shortened {
s.push('…');
}
s
}
/// Renders the first paragraph of the provided markdown as plain text.
/// Useful for alt-text.
///
/// - Headings, links, and formatting are stripped.
/// - Inline code is rendered as-is, surrounded by backticks.
/// - HTML and code blocks are ignored.
crate fn plain_text_summary(md: &str) -> String {
if md.is_empty() {
return String::new();
}
let mut s = String::with_capacity(md.len() * 3 / 2);
for event in Parser::new_ext(md, summary_opts()) {
match &event {
Event::Text(text) => s.push_str(text),
Event::Code(code) => {
s.push('`');
s.push_str(code);
s.push('`');
2017-03-08 00:01:23 +00:00
}
Event::HardBreak | Event::SoftBreak => s.push(' '),
Event::Start(Tag::CodeBlock(..)) => break,
Event::End(Tag::Paragraph) => break,
Event::End(Tag::Heading(..)) => break,
_ => (),
}
}
s
}
crate struct MarkdownLink {
pub kind: LinkType,
pub link: String,
pub range: Range<usize>,
}
crate fn markdown_links(md: &str) -> Vec<MarkdownLink> {
if md.is_empty() {
return vec![];
}
2020-12-31 09:26:24 +00:00
let links = RefCell::new(vec![]);
2020-12-31 09:38:12 +00:00
// FIXME: remove this function once pulldown_cmark can provide spans for link definitions.
let locate = |s: &str, fallback: Range<usize>| unsafe {
let s_start = s.as_ptr();
let s_end = s_start.add(s.len());
let md_start = md.as_ptr();
let md_end = md_start.add(md.len());
if md_start <= s_start && s_end <= md_end {
let start = s_start.offset_from(md_start) as usize;
let end = s_end.offset_from(md_start) as usize;
start..end
} else {
fallback
}
};
2020-12-31 09:38:12 +00:00
let span_for_link = |link: &CowStr<'_>, span: Range<usize>| {
// For diagnostics, we want to underline the link's definition but `span` will point at
// where the link is used. This is a problem for reference-style links, where the definition
// is separate from the usage.
match link {
// `Borrowed` variant means the string (the link's destination) may come directly from
// the markdown text and we can locate the original link destination.
// NOTE: LinkReplacer also provides `Borrowed` but possibly from other sources,
// so `locate()` can fall back to use `span`.
CowStr::Borrowed(s) => locate(s, span),
// For anything else, we can only use the provided range.
CowStr::Boxed(_) | CowStr::Inlined(_) => span,
}
};
let mut push = |link: BrokenLink<'_>| {
2020-12-31 09:38:12 +00:00
let span = span_for_link(&CowStr::Borrowed(link.reference), link.span);
links.borrow_mut().push(MarkdownLink {
kind: LinkType::ShortcutUnknown,
link: link.reference.to_owned(),
range: span,
});
None
};
let p = Parser::new_with_broken_link_callback(md, opts(), Some(&mut push)).into_offset_iter();
// There's no need to thread an IdMap through to here because
// the IDs generated aren't going to be emitted anywhere.
let mut ids = IdMap::new();
let iter = Footnotes::new(HeadingLinks::new(p, None, &mut ids));
for ev in iter {
if let Event::Start(Tag::Link(kind, dest, _)) = ev.0 {
debug!("found link: {}", dest);
2020-12-31 09:38:12 +00:00
let span = span_for_link(&dest, ev.1);
links.borrow_mut().push(MarkdownLink { kind, link: dest.into_string(), range: span });
}
}
Remove hoedown from rustdoc Is it really time? Have our months, no, *years* of suffering come to an end? Are we finally able to cast off the pall of Hoedown? The weight which has dragged us down for so long? ----- So, timeline for those who need to catch up: * Way back in December 2016, [we decided we wanted to switch out the markdown renderer](https://github.com/rust-lang/rust/issues/38400). However, this was put on hold because the build system at the time made it difficult to pull in dependencies from crates.io. * A few months later, in March 2017, [the first PR was done, to switch out the renderers entirely](https://github.com/rust-lang/rust/pull/40338). The PR itself was fraught with CI and build system issues, but eventually landed. * However, not all was well in the Rustdoc world. During the PR and shortly after, we noticed [some differences in the way the two parsers handled some things](https://github.com/rust-lang/rust/issues/40912), and some of these differences were major enough to break the docs for some crates. * A couple weeks afterward, [Hoedown was put back in](https://github.com/rust-lang/rust/pull/41290), at this point just to catch tests that Pulldown was "spuriously" running. This would at least provide some warning about spurious tests, rather than just breaking spontaneously. * However, the problems had created enough noise by this point that just a few days after that, [Hoedown was switched back to the default](https://github.com/rust-lang/rust/pull/41431) while we came up with a solution for properly warning about the differences. * That solution came a few weeks later, [as a series of warnings when the HTML emitted by the two parsers was semantically different](https://github.com/rust-lang/rust/pull/41991). But that came at a cost, as now rustdoc needed proc-macro support (the new crate needed some custom derives farther down its dependency tree), and the build system was not equipped to handle it at the time. It was worked on for three months as the issue stumped more and more people. * In that time, [bootstrap was completely reworked](https://github.com/rust-lang/rust/pull/43059) to change how it ordered compilation, and [the method by which it built rustdoc would change](https://github.com/rust-lang/rust/pull/43482), as well. This allowed it to only be built after stage1, when proc-macros would be available, allowing the "rendering differences" PR to finally land. * The warnings were not perfect, and revealed a few [spurious](https://github.com/rust-lang/rust/pull/44368) [differences](https://github.com/rust-lang/rust/pull/45421) between how we handled the renderers. * Once these were handled, [we flipped the switch to turn on the "rendering difference" warnings all the time](https://github.com/rust-lang/rust/pull/45324), in October 2017. This began the "warning cycle" for this change, and landed in stable in 1.23, on 2018-01-04. * Once those warnings hit stable, and after a couple weeks of seeing whether we would get any more reports than what we got from sitting on nightly/beta, [we switched the renderers](https://github.com/rust-lang/rust/pull/47398), making Pulldown the default but still offering the option to use Hoedown. And that brings us to the present. We haven't received more new issues from this in the meantime, and the "switch by default" is now on beta. Our reasoning is that, at this point, anyone who would have been affected by this has run into it already.
2018-02-16 14:09:19 +00:00
2020-12-31 09:26:24 +00:00
links.into_inner()
}
#[derive(Debug)]
crate struct RustCodeBlock {
/// The range in the markdown that the code block occupies. Note that this includes the fences
/// for fenced code blocks.
crate range: Range<usize>,
/// The range in the markdown that the code within the code block occupies.
crate code: Range<usize>,
crate is_fenced: bool,
crate syntax: Option<String>,
crate is_ignore: bool,
}
/// Returns a range of bytes for each code block in the markdown that is tagged as `rust` or
/// untagged (and assumed to be rust).
2021-01-01 04:25:30 +00:00
crate fn rust_code_blocks(md: &str, extra_info: &ExtraInfo<'_>) -> Vec<RustCodeBlock> {
let mut code_blocks = vec![];
if md.is_empty() {
return code_blocks;
}
2020-02-13 17:39:40 +00:00
let mut p = Parser::new_ext(md, opts()).into_offset_iter();
2020-02-13 17:39:40 +00:00
while let Some((event, offset)) = p.next() {
if let Event::Start(Tag::CodeBlock(syntax)) = event {
let (syntax, code_start, code_end, range, is_fenced, is_ignore) = match syntax {
CodeBlockKind::Fenced(syntax) => {
let syntax = syntax.as_ref();
let lang_string = if syntax.is_empty() {
Default::default()
} else {
LangString::parse(&*syntax, ErrorCodes::Yes, false, Some(extra_info))
};
if !lang_string.rust {
continue;
}
let is_ignore = lang_string.ignore != Ignore::None;
let syntax = if syntax.is_empty() { None } else { Some(syntax.to_owned()) };
let (code_start, mut code_end) = match p.next() {
Some((Event::Text(_), offset)) => (offset.start, offset.end),
Some((_, sub_offset)) => {
let code = Range { start: sub_offset.start, end: sub_offset.start };
code_blocks.push(RustCodeBlock {
is_fenced: true,
range: offset,
code,
syntax,
is_ignore,
});
2020-02-13 17:39:40 +00:00
continue;
}
None => {
let code = Range { start: offset.end, end: offset.end };
code_blocks.push(RustCodeBlock {
is_fenced: true,
range: offset,
code,
syntax,
is_ignore,
});
continue;
}
};
while let Some((Event::Text(_), offset)) = p.next() {
code_end = offset.end;
2020-02-13 17:39:40 +00:00
}
(syntax, code_start, code_end, offset, true, is_ignore)
}
CodeBlockKind::Indented => {
// The ending of the offset goes too far sometime so we reduce it by one in
// these cases.
if offset.end > offset.start && md.get(offset.end..=offset.end) == Some(&"\n") {
(
None,
offset.start,
offset.end,
Range { start: offset.start, end: offset.end - 1 },
false,
false,
)
} else {
(None, offset.start, offset.end, offset, false, false)
2020-02-13 17:39:40 +00:00
}
}
};
code_blocks.push(RustCodeBlock {
is_fenced,
range,
code: Range { start: code_start, end: code_end },
syntax,
is_ignore,
});
}
}
code_blocks
}
#[derive(Clone, Default, Debug)]
pub struct IdMap {
map: FxHashMap<String, usize>,
}
fn init_id_map() -> FxHashMap<String, usize> {
let mut map = FxHashMap::default();
// This is the list of IDs used by rustdoc templates.
map.insert("mainThemeStyle".to_owned(), 1);
map.insert("themeStyle".to_owned(), 1);
map.insert("theme-picker".to_owned(), 1);
map.insert("theme-choices".to_owned(), 1);
map.insert("settings-menu".to_owned(), 1);
map.insert("main".to_owned(), 1);
map.insert("search".to_owned(), 1);
map.insert("crate-search".to_owned(), 1);
map.insert("render-detail".to_owned(), 1);
map.insert("toggle-all-docs".to_owned(), 1);
map.insert("all-types".to_owned(), 1);
map.insert("default-settings".to_owned(), 1);
2021-01-18 11:03:53 +00:00
map.insert("rustdoc-vars".to_owned(), 1);
map.insert("sidebar-vars".to_owned(), 1);
// This is the list of IDs used by rustdoc sections.
map.insert("fields".to_owned(), 1);
map.insert("variants".to_owned(), 1);
map.insert("implementors-list".to_owned(), 1);
map.insert("synthetic-implementors-list".to_owned(), 1);
map.insert("implementations".to_owned(), 1);
map.insert("trait-implementations".to_owned(), 1);
map.insert("synthetic-implementations".to_owned(), 1);
map.insert("blanket-implementations".to_owned(), 1);
map
}
impl IdMap {
pub fn new() -> Self {
IdMap { map: init_id_map() }
}
crate fn populate<I: IntoIterator<Item = S>, S: AsRef<str> + ToString>(&mut self, ids: I) {
for id in ids {
let _ = self.derive(id);
}
}
crate fn reset(&mut self) {
self.map = init_id_map();
}
crate fn derive<S: AsRef<str> + ToString>(&mut self, candidate: S) -> String {
let id = match self.map.get_mut(candidate.as_ref()) {
None => candidate.to_string(),
Some(a) => {
let id = format!("{}-{}", candidate.as_ref(), *a);
*a += 1;
id
}
};
self.map.insert(id.clone(), 1);
id
}
}