Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rustdoc DocFragment rework #80261

Merged
merged 6 commits into from
Jan 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 109 additions & 22 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ impl Item {

/// Finds the `doc` attribute as a NameValue and returns the corresponding
/// value found.
crate fn doc_value(&self) -> Option<&str> {
crate fn doc_value(&self) -> Option<String> {
self.attrs.doc_value()
}

Expand Down Expand Up @@ -469,31 +469,66 @@ crate struct DocFragment {
/// This allows distinguishing between the original documentation and a pub re-export.
/// If it is `None`, the item was not re-exported.
crate parent_module: Option<DefId>,
crate doc: String,
crate doc: Symbol,
crate kind: DocFragmentKind,
crate need_backline: bool,
crate indent: usize,
}

#[derive(Clone, PartialEq, Eq, Debug, Hash)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
crate enum DocFragmentKind {
/// A doc fragment created from a `///` or `//!` doc comment.
SugaredDoc,
/// A doc fragment created from a "raw" `#[doc=""]` attribute.
RawDoc,
/// A doc fragment created from a `#[doc(include="filename")]` attribute. Contains both the
/// given filename and the file contents.
Include { filename: String },
Include { filename: Symbol },
}

// The goal of this function is to apply the `DocFragment` transformations that are required when
// transforming into the final markdown. So the transformations in here are:
//
// * Applying the computed indent to each lines in each doc fragment (a `DocFragment` can contain
// multiple lines in case of `#[doc = ""]`).
// * Adding backlines between `DocFragment`s and adding an extra one if required (stored in the
// `need_backline` field).
fn add_doc_fragment(out: &mut String, frag: &DocFragment) {
let s = frag.doc.as_str();
let mut iter = s.lines().peekable();
while let Some(line) = iter.next() {
if line.chars().any(|c| !c.is_whitespace()) {
assert!(line.len() >= frag.indent);
out.push_str(&line[frag.indent..]);
} else {
out.push_str(line);
}
if iter.peek().is_some() {
out.push('\n');
}
}
if frag.need_backline {
out.push('\n');
}
}

impl<'a> FromIterator<&'a DocFragment> for String {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = &'a DocFragment>,
{
let mut prev_kind: Option<DocFragmentKind> = None;
iter.into_iter().fold(String::new(), |mut acc, frag| {
if !acc.is_empty() {
if !acc.is_empty()
&& prev_kind
.take()
.map(|p| matches!(p, DocFragmentKind::Include { .. }) && p != frag.kind)
.unwrap_or(false)
{
acc.push('\n');
}
acc.push_str(&frag.doc);
add_doc_fragment(&mut acc, &frag);
prev_kind = Some(frag.kind);
acc
})
}
Expand Down Expand Up @@ -565,25 +600,25 @@ impl Attributes {
/// Reads a `MetaItem` from within an attribute, looks for whether it is a
/// `#[doc(include="file")]`, and returns the filename and contents of the file as loaded from
/// its expansion.
crate fn extract_include(mi: &ast::MetaItem) -> Option<(String, String)> {
crate fn extract_include(mi: &ast::MetaItem) -> Option<(Symbol, Symbol)> {
mi.meta_item_list().and_then(|list| {
for meta in list {
if meta.has_name(sym::include) {
// the actual compiled `#[doc(include="filename")]` gets expanded to
// `#[doc(include(file="filename", contents="file contents")]` so we need to
// look for that instead
return meta.meta_item_list().and_then(|list| {
let mut filename: Option<String> = None;
let mut contents: Option<String> = None;
let mut filename: Option<Symbol> = None;
let mut contents: Option<Symbol> = None;
GuillaumeGomez marked this conversation as resolved.
Show resolved Hide resolved

for it in list {
if it.has_name(sym::file) {
if let Some(name) = it.value_str() {
filename = Some(name.to_string());
filename = Some(name);
}
} else if it.has_name(sym::contents) {
if let Some(docs) = it.value_str() {
contents = Some(docs.to_string());
contents = Some(docs);
}
}
}
Expand Down Expand Up @@ -622,30 +657,51 @@ impl Attributes {
attrs: &[ast::Attribute],
additional_attrs: Option<(&[ast::Attribute], DefId)>,
) -> Attributes {
let mut doc_strings = vec![];
let mut doc_strings: Vec<DocFragment> = vec![];
GuillaumeGomez marked this conversation as resolved.
Show resolved Hide resolved
let mut sp = None;
let mut cfg = Cfg::True;
let mut doc_line = 0;

fn update_need_backline(doc_strings: &mut Vec<DocFragment>, frag: &DocFragment) {
if let Some(prev) = doc_strings.last_mut() {
if matches!(prev.kind, DocFragmentKind::Include { .. })
|| prev.kind != frag.kind
|| prev.parent_module != frag.parent_module
{
// add a newline for extra padding between segments
prev.need_backline = prev.kind == DocFragmentKind::SugaredDoc
|| prev.kind == DocFragmentKind::RawDoc
} else {
prev.need_backline = true;
}
}
}

let clean_attr = |(attr, parent_module): (&ast::Attribute, _)| {
if let Some(value) = attr.doc_str() {
trace!("got doc_str={:?}", value);
let value = beautify_doc_string(value).to_string();
let value = beautify_doc_string(value);
let kind = if attr.is_doc_comment() {
DocFragmentKind::SugaredDoc
} else {
DocFragmentKind::RawDoc
};

let line = doc_line;
doc_line += value.lines().count();
doc_strings.push(DocFragment {
doc_line += value.as_str().lines().count();
let frag = DocFragment {
line,
span: attr.span,
doc: value,
kind,
parent_module,
});
need_backline: false,
indent: 0,
};

update_need_backline(&mut doc_strings, &frag);

doc_strings.push(frag);

if sp.is_none() {
sp = Some(attr.span);
Expand All @@ -663,14 +719,18 @@ impl Attributes {
} else if let Some((filename, contents)) = Attributes::extract_include(&mi)
{
let line = doc_line;
doc_line += contents.lines().count();
doc_strings.push(DocFragment {
doc_line += contents.as_str().lines().count();
let frag = DocFragment {
line,
span: attr.span,
doc: contents,
kind: DocFragmentKind::Include { filename },
parent_module,
});
need_backline: false,
indent: 0,
};
update_need_backline(&mut doc_strings, &frag);
doc_strings.push(frag);
}
}
}
Expand Down Expand Up @@ -721,14 +781,41 @@ impl Attributes {

/// Finds the `doc` attribute as a NameValue and returns the corresponding
/// value found.
crate fn doc_value(&self) -> Option<&str> {
self.doc_strings.first().map(|s| s.doc.as_str())
crate fn doc_value(&self) -> Option<String> {
let mut iter = self.doc_strings.iter();

let ori = iter.next()?;
let mut out = String::new();
add_doc_fragment(&mut out, &ori);
while let Some(new_frag) = iter.next() {
if matches!(ori.kind, DocFragmentKind::Include { .. })
|| new_frag.kind != ori.kind
|| new_frag.parent_module != ori.parent_module
{
break;
}
add_doc_fragment(&mut out, &new_frag);
}
if out.is_empty() { None } else { Some(out) }
}

/// Return the doc-comments on this item, grouped by the module they came from.
///
/// The module can be different if this is a re-export with added documentation.
crate fn collapsed_doc_value_by_module_level(&self) -> FxHashMap<Option<DefId>, String> {
GuillaumeGomez marked this conversation as resolved.
Show resolved Hide resolved
let mut ret = FxHashMap::default();

for new_frag in self.doc_strings.iter() {
let out = ret.entry(new_frag.parent_module).or_default();
add_doc_fragment(out, &new_frag);
}
ret
}

/// Finds all `doc` attributes as NameValues and returns their corresponding values, joined
/// with newlines.
crate fn collapsed_doc_value(&self) -> Option<String> {
if !self.doc_strings.is_empty() { Some(self.doc_strings.iter().collect()) } else { None }
if self.doc_strings.is_empty() { None } else { Some(self.doc_strings.iter().collect()) }
}

/// Gets links as a vector
Expand Down
5 changes: 4 additions & 1 deletion src/librustdoc/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ crate fn run_global_ctxt(
let mut krate = tcx.sess.time("clean_crate", || clean::krate(&mut ctxt));

if let Some(ref m) = krate.module {
if let None | Some("") = m.doc_value() {
if m.doc_value().map(|d| d.is_empty()).unwrap_or(true) {
jyn514 marked this conversation as resolved.
Show resolved Hide resolved
let help = "The following guide may be of use:\n\
https://doc.rust-lang.org/nightly/rustdoc/how-to-write-documentation.html";
tcx.struct_lint_node(
Expand Down Expand Up @@ -623,6 +623,9 @@ crate fn run_global_ctxt(

ctxt.sess().abort_if_errors();

// The main crate doc comments are always collapsed.
krate.collapsed = true;

(krate, ctxt.renderinfo.into_inner(), ctxt.render_options)
}

Expand Down
1 change: 0 additions & 1 deletion src/librustdoc/doctest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -987,7 +987,6 @@ impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> {
self.collector.names.push(name);
}

attrs.collapse_doc_comments();
attrs.unindent_doc_comments();
// The collapse-docs pass won't combine sugared/raw doc attributes, or included files with
// anything else, this will combine them for us.
Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/formats/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ impl DocFolder for Cache {
path: path.join("::"),
desc: item
.doc_value()
.map_or_else(|| String::new(), short_markdown_summary),
.map_or_else(String::new, |x| short_markdown_summary(&x.as_str())),
parent,
parent_idx: None,
search_type: get_index_search_type(&item),
Expand Down
4 changes: 2 additions & 2 deletions src/librustdoc/html/render/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ crate fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String {
ty: item.type_(),
name: item.name.unwrap().to_string(),
path: fqp[..fqp.len() - 1].join("::"),
desc: item.doc_value().map_or_else(|| String::new(), short_markdown_summary),
desc: item.doc_value().map_or_else(String::new, |s| short_markdown_summary(&s)),
parent: Some(did),
parent_idx: None,
search_type: get_index_search_type(&item),
Expand Down Expand Up @@ -127,7 +127,7 @@ crate fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String {
let crate_doc = krate
.module
.as_ref()
.map(|module| module.doc_value().map_or_else(|| String::new(), short_markdown_summary))
.map(|module| module.doc_value().map_or_else(String::new, |s| short_markdown_summary(&s)))
.unwrap_or_default();

#[derive(Serialize)]
Expand Down
17 changes: 6 additions & 11 deletions src/librustdoc/html/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ crate mod cache;
#[cfg(test)]
mod tests;

use std::borrow::Cow;
use std::cell::{Cell, RefCell};
use std::cmp::Ordering;
use std::collections::{BTreeMap, VecDeque};
Expand Down Expand Up @@ -198,12 +197,8 @@ impl SharedContext<'_> {

/// Based on whether the `collapse-docs` pass was run, return either the `doc_value` or the
/// `collapsed_doc_value` of the given item.
crate fn maybe_collapsed_doc_value<'a>(&self, item: &'a clean::Item) -> Option<Cow<'a, str>> {
if self.collapsed {
item.collapsed_doc_value().map(|s| s.into())
} else {
item.doc_value().map(|s| s.into())
}
crate fn maybe_collapsed_doc_value<'a>(&self, item: &'a clean::Item) -> Option<String> {
if self.collapsed { item.collapsed_doc_value() } else { item.doc_value() }
}
}

Expand Down Expand Up @@ -1622,7 +1617,7 @@ impl Context<'_> {
let short = short.to_string();
map.entry(short).or_default().push((
myname,
Some(item.doc_value().map_or_else(|| String::new(), plain_text_summary)),
Some(item.doc_value().map_or_else(String::new, |s| plain_text_summary(&s))),
));
}

Expand Down Expand Up @@ -1880,7 +1875,7 @@ fn document_short(
return;
}
if let Some(s) = item.doc_value() {
let mut summary_html = MarkdownSummaryLine(s, &item.links()).into_string();
let mut summary_html = MarkdownSummaryLine(&s, &item.links()).into_string();

if s.contains('\n') {
let link = format!(r#" <a href="{}">Read more</a>"#, naive_assoc_href(item, link));
Expand Down Expand Up @@ -2197,7 +2192,7 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl
let stab = myitem.stability_class(cx.tcx());
let add = if stab.is_some() { " " } else { "" };

let doc_value = myitem.doc_value().unwrap_or("");
let doc_value = myitem.doc_value().unwrap_or_default();
write!(
w,
"<tr class=\"{stab}{add}module-item\">\
Expand All @@ -2207,7 +2202,7 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl
</tr>",
name = *myitem.name.as_ref().unwrap(),
stab_tags = extra_info_tags(myitem, item, cx.tcx()),
docs = MarkdownSummaryLine(doc_value, &myitem.links()).into_string(),
docs = MarkdownSummaryLine(&doc_value, &myitem.links()).into_string(),
class = myitem.type_(),
add = add,
stab = stab.unwrap_or_else(String::new),
Expand Down
7 changes: 1 addition & 6 deletions src/librustdoc/passes/calculate_doc_coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,7 @@ impl<'a, 'b> fold::DocFolder for CoverageCalculator<'a, 'b> {
let mut tests = Tests { found_tests: 0 };

find_testable_code(
&i.attrs
.doc_strings
.iter()
.map(|d| d.doc.as_str())
.collect::<Vec<_>>()
.join("\n"),
&i.attrs.collapsed_doc_value().unwrap_or_default(),
&mut tests,
ErrorCodes::No,
false,
Expand Down
Loading