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

Improve rendering of crate features via doc(cfg) #75330

Merged
merged 3 commits into from
Aug 28, 2020
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
151 changes: 128 additions & 23 deletions src/librustdoc/clean/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ impl Cfg {

/// Renders the configuration for human display, as a short HTML description.
pub(crate) fn render_short_html(&self) -> String {
let mut msg = Html(self, true).to_string();
let mut msg = Display(self, Format::ShortHtml).to_string();
if self.should_capitalize_first_letter() {
if let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric()) {
msg[i..i + 1].make_ascii_uppercase();
Expand All @@ -148,14 +148,29 @@ impl Cfg {
pub(crate) fn render_long_html(&self) -> String {
let on = if self.should_use_with_in_description() { "with" } else { "on" };

let mut msg = format!("This is supported {} <strong>{}</strong>", on, Html(self, false));
let mut msg = format!(
"This is supported {} <strong>{}</strong>",
on,
Display(self, Format::LongHtml)
);
if self.should_append_only_to_description() {
msg.push_str(" only");
}
msg.push('.');
msg
}

/// Renders the configuration for long display, as a long plain text description.
pub(crate) fn render_long_plain(&self) -> String {
let on = if self.should_use_with_in_description() { "with" } else { "on" };

let mut msg = format!("This is supported {} {}", on, Display(self, Format::LongPlain));
if self.should_append_only_to_description() {
msg.push_str(" only");
}
msg
}

fn should_capitalize_first_letter(&self) -> bool {
match *self {
Cfg::False | Cfg::True | Cfg::Not(..) => true,
Expand Down Expand Up @@ -286,9 +301,31 @@ impl ops::BitOr for Cfg {
}
}

/// Pretty-print wrapper for a `Cfg`. Also indicates whether the "short-form" rendering should be
/// used.
struct Html<'a>(&'a Cfg, bool);
#[derive(Clone, Copy)]
enum Format {
LongHtml,
LongPlain,
ShortHtml,
}

impl Format {
fn is_long(self) -> bool {
match self {
Format::LongHtml | Format::LongPlain => true,
Format::ShortHtml => false,
}
}

fn is_html(self) -> bool {
match self {
Format::LongHtml | Format::ShortHtml => true,
Format::LongPlain => false,
}
}
}

/// Pretty-print wrapper for a `Cfg`. Also indicates what form of rendering should be used.
struct Display<'a>(&'a Cfg, Format);

fn write_with_opt_paren<T: fmt::Display>(
fmt: &mut fmt::Formatter<'_>,
Expand All @@ -305,7 +342,7 @@ fn write_with_opt_paren<T: fmt::Display>(
Ok(())
}

impl<'a> fmt::Display for Html<'a> {
impl<'a> fmt::Display for Display<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self.0 {
Cfg::Not(ref child) => match **child {
Expand All @@ -314,31 +351,86 @@ impl<'a> fmt::Display for Html<'a> {
if sub_cfgs.iter().all(Cfg::is_simple) { " nor " } else { ", nor " };
for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
fmt.write_str(if i == 0 { "neither " } else { separator })?;
write_with_opt_paren(fmt, !sub_cfg.is_all(), Html(sub_cfg, self.1))?;
write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
}
Ok(())
}
ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Html(simple, self.1)),
ref c => write!(fmt, "not ({})", Html(c, self.1)),
ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Display(simple, self.1)),
ref c => write!(fmt, "not ({})", Display(c, self.1)),
},

Cfg::Any(ref sub_cfgs) => {
let separator = if sub_cfgs.iter().all(Cfg::is_simple) { " or " } else { ", or " };

let short_longhand = self.1.is_long() && {
let all_crate_features = sub_cfgs
.iter()
.all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
let all_target_features = sub_cfgs
.iter()
.all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::target_feature, Some(_))));

if all_crate_features {
fmt.write_str("crate features ")?;
true
} else if all_target_features {
fmt.write_str("target features ")?;
true
} else {
false
}
};

for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
if i != 0 {
fmt.write_str(separator)?;
}
write_with_opt_paren(fmt, !sub_cfg.is_all(), Html(sub_cfg, self.1))?;
if let (true, Cfg::Cfg(_, Some(feat))) = (short_longhand, sub_cfg) {
if self.1.is_html() {
write!(fmt, "<code>{}</code>", feat)?;
} else {
write!(fmt, "`{}`", feat)?;
}
} else {
write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
}
}
Ok(())
}

Cfg::All(ref sub_cfgs) => {
let short_longhand = self.1.is_long() && {
let all_crate_features = sub_cfgs
.iter()
.all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
let all_target_features = sub_cfgs
.iter()
.all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::target_feature, Some(_))));

if all_crate_features {
fmt.write_str("crate features ")?;
true
} else if all_target_features {
fmt.write_str("target features ")?;
true
} else {
false
}
};

for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
if i != 0 {
fmt.write_str(" and ")?;
}
write_with_opt_paren(fmt, !sub_cfg.is_simple(), Html(sub_cfg, self.1))?;
if let (true, Cfg::Cfg(_, Some(feat))) = (short_longhand, sub_cfg) {
if self.1.is_html() {
write!(fmt, "<code>{}</code>", feat)?;
} else {
write!(fmt, "`{}`", feat)?;
}
} else {
write_with_opt_paren(fmt, !sub_cfg.is_simple(), Display(sub_cfg, self.1))?;
}
}
Ok(())
}
Expand Down Expand Up @@ -406,26 +498,39 @@ impl<'a> fmt::Display for Html<'a> {
},
(sym::target_endian, Some(endian)) => return write!(fmt, "{}-endian", endian),
(sym::target_pointer_width, Some(bits)) => return write!(fmt, "{}-bit", bits),
(sym::target_feature, Some(feat)) => {
if self.1 {
return write!(fmt, "<code>{}</code>", feat);
} else {
(sym::target_feature, Some(feat)) => match self.1 {
Format::LongHtml => {
return write!(fmt, "target feature <code>{}</code>", feat);
}
}
Format::LongPlain => return write!(fmt, "target feature `{}`", feat),
Format::ShortHtml => return write!(fmt, "<code>{}</code>", feat),
},
(sym::feature, Some(feat)) => match self.1 {
Format::LongHtml => {
return write!(fmt, "crate feature <code>{}</code>", feat);
}
Format::LongPlain => return write!(fmt, "crate feature `{}`", feat),
Format::ShortHtml => return write!(fmt, "<code>{}</code>", feat),
},
_ => "",
};
if !human_readable.is_empty() {
fmt.write_str(human_readable)
} else if let Some(v) = value {
write!(
fmt,
"<code>{}=\"{}\"</code>",
Escape(&name.as_str()),
Escape(&v.as_str())
)
} else {
if self.1.is_html() {
write!(
fmt,
r#"<code>{}="{}"</code>"#,
Escape(&name.as_str()),
Escape(&v.as_str())
)
} else {
write!(fmt, r#"`{}="{}"`"#, name, v)
}
} else if self.1.is_html() {
write!(fmt, "<code>{}</code>", Escape(&name.as_str()))
} else {
write!(fmt, "`{}`", name)
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/librustdoc/html/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2108,8 +2108,8 @@ fn item_module(w: &mut Buffer, cx: &Context, item: &clean::Item, items: &[clean:
fn stability_tags(item: &clean::Item) -> String {
let mut tags = String::new();

fn tag_html(class: &str, contents: &str) -> String {
format!(r#"<span class="stab {}">{}</span>"#, class, contents)
fn tag_html(class: &str, title: &str, contents: &str) -> String {
format!(r#"<span class="stab {}" title="{}">{}</span>"#, class, Escape(title), contents)
}

// The trailing space after each tag is to space it properly against the rest of the docs.
Expand All @@ -2118,7 +2118,7 @@ fn stability_tags(item: &clean::Item) -> String {
if !stability::deprecation_in_effect(depr.is_since_rustc_version, depr.since.as_deref()) {
message = "Deprecation planned";
}
tags += &tag_html("deprecated", message);
tags += &tag_html("deprecated", "", message);
}

// The "rustc_private" crates are permanently unstable so it makes no sense
Expand All @@ -2129,11 +2129,11 @@ fn stability_tags(item: &clean::Item) -> String {
.map(|s| s.level == stability::Unstable && s.feature.as_deref() != Some("rustc_private"))
== Some(true)
{
tags += &tag_html("unstable", "Experimental");
tags += &tag_html("unstable", "", "Experimental");
}

if let Some(ref cfg) = item.attrs.cfg {
tags += &tag_html("portability", &cfg.render_short_html());
tags += &tag_html("portability", &cfg.render_long_plain(), &cfg.render_short_html());
}

tags
Expand Down
26 changes: 21 additions & 5 deletions src/test/rustdoc/duplicate-cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,54 @@
#![crate_name = "foo"]
#![feature(doc_cfg)]

// @has 'foo/index.html'
// @matches '-' '//*[@class="module-item"]//*[@class="stab portability"]' '^sync$'
// @has '-' '//*[@class="module-item"]//*[@class="stab portability"]/@title' 'This is supported on crate feature `sync` only'

// @has 'foo/struct.Foo.html'
// @has '-' '//*[@class="stab portability"]' 'This is supported on feature="sync" only.'
// @has '-' '//*[@class="stab portability"]' 'sync'
#[doc(cfg(feature = "sync"))]
#[doc(cfg(feature = "sync"))]
pub struct Foo;

// @has 'foo/bar/index.html'
// @matches '-' '//*[@class="module-item"]//*[@class="stab portability"]' '^sync$'
// @has '-' '//*[@class="module-item"]//*[@class="stab portability"]/@title' 'This is supported on crate feature `sync` only'

// @has 'foo/bar/struct.Bar.html'
// @has '-' '//*[@class="stab portability"]' 'This is supported on feature="sync" only.'
// @has '-' '//*[@class="stab portability"]' 'This is supported on crate feature sync only.'
#[doc(cfg(feature = "sync"))]
pub mod bar {
#[doc(cfg(feature = "sync"))]
pub struct Bar;
}

// @has 'foo/baz/index.html'
// @matches '-' '//*[@class="module-item"]//*[@class="stab portability"]' '^sync and send$'
// @has '-' '//*[@class="module-item"]//*[@class="stab portability"]/@title' 'This is supported on crate features `sync` and `send` only'

// @has 'foo/baz/struct.Baz.html'
// @has '-' '//*[@class="stab portability"]' 'This is supported on feature="sync" and feature="send" only.'
// @has '-' '//*[@class="stab portability"]' 'This is supported on crate features sync and send only.'
#[doc(cfg(all(feature = "sync", feature = "send")))]
pub mod baz {
#[doc(cfg(feature = "sync"))]
pub struct Baz;
}

// @has 'foo/qux/struct.Qux.html'
// @has '-' '//*[@class="stab portability"]' 'This is supported on feature="sync" and feature="send" only.'
// @has '-' '//*[@class="stab portability"]' 'This is supported on crate features sync and send only.'
#[doc(cfg(feature = "sync"))]
pub mod qux {
#[doc(cfg(all(feature = "sync", feature = "send")))]
pub struct Qux;
}

// @has 'foo/quux/index.html'
// @matches '-' '//*[@class="module-item"]//*[@class="stab portability"]' '^sync and send and foo and bar$'
// @has '-' '//*[@class="module-item"]//*[@class="stab portability"]/@title' 'This is supported on crate feature `sync` and crate feature `send` and `foo` and `bar` only'
Comment on lines +48 to +50
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this would be nicer as sync, send, foo, and bar. But I think the existing behavior had this as well so not a blocker.


// @has 'foo/quux/struct.Quux.html'
// @has '-' '//*[@class="stab portability"]' 'This is supported on feature="sync" and feature="send" and foo and bar only.'
// @has '-' '//*[@class="stab portability"]' 'This is supported on crate feature sync and crate feature send and foo and bar only.'
#[doc(cfg(all(feature = "sync", feature = "send", foo)))]
pub mod quux {
#[doc(cfg(all(feature = "send", feature = "sync", bar)))]
Expand Down