diff --git a/src/etc/htmldocck.py b/src/etc/htmldocck.py
index 5eb70ab13dba0..f762e38900552 100644
--- a/src/etc/htmldocck.py
+++ b/src/etc/htmldocck.py
@@ -94,6 +94,10 @@
in the specified file. The number of occurrences must match the given
count.
+* `@count PATH XPATH TEXT COUNT` checks for the occurrence of the given XPath
+ with the given text in the specified file. The number of occurrences must
+ match the given count.
+
* `@snapshot NAME PATH XPATH` creates a snapshot test named NAME.
A snapshot test captures a subtree of the DOM, at the location
determined by the XPath, and compares it to a pre-recorded value
@@ -382,9 +386,10 @@ def check_tree_attr(tree, path, attr, pat, regexp):
return ret
-def check_tree_text(tree, path, pat, regexp):
+# Returns the number of occurences matching the regex (`regexp`) and the text (`pat`).
+def check_tree_text(tree, path, pat, regexp, stop_at_first):
path = normalize_xpath(path)
- ret = False
+ match_count = 0
try:
for e in tree.findall(path):
try:
@@ -392,13 +397,14 @@ def check_tree_text(tree, path, pat, regexp):
except KeyError:
continue
else:
- ret = check_string(value, pat, regexp)
- if ret:
- break
+ if check_string(value, pat, regexp):
+ match_count += 1
+ if stop_at_first:
+ break
except Exception:
print('Failed to get path "{}"'.format(path))
raise
- return ret
+ return match_count
def get_tree_count(tree, path):
@@ -518,6 +524,19 @@ def print_err(lineno, context, err, message=None):
stderr("\t{}".format(context))
+def get_nb_matching_elements(cache, c, regexp, stop_at_first):
+ tree = cache.get_tree(c.args[0])
+ pat, sep, attr = c.args[1].partition('/@')
+ if sep: # attribute
+ tree = cache.get_tree(c.args[0])
+ return check_tree_attr(tree, pat, attr, c.args[2], False)
+ else: # normalized text
+ pat = c.args[1]
+ if pat.endswith('/text()'):
+ pat = pat[:-7]
+ return check_tree_text(cache.get_tree(c.args[0]), pat, c.args[2], regexp, stop_at_first)
+
+
ERR_COUNT = 0
@@ -538,16 +557,7 @@ def check_command(c, cache):
ret = check_string(cache.get_file(c.args[0]), c.args[1], regexp)
elif len(c.args) == 3: # @has/matches = XML tree test
cerr = "`XPATH PATTERN` did not match"
- tree = cache.get_tree(c.args[0])
- pat, sep, attr = c.args[1].partition('/@')
- if sep: # attribute
- tree = cache.get_tree(c.args[0])
- ret = check_tree_attr(tree, pat, attr, c.args[2], regexp)
- else: # normalized text
- pat = c.args[1]
- if pat.endswith('/text()'):
- pat = pat[:-7]
- ret = check_tree_text(cache.get_tree(c.args[0]), pat, c.args[2], regexp)
+ ret = get_nb_matching_elements(cache, c, regexp, True) != 0
else:
raise InvalidCheck('Invalid number of @{} arguments'.format(c.cmd))
@@ -557,6 +567,11 @@ def check_command(c, cache):
found = get_tree_count(cache.get_tree(c.args[0]), c.args[1])
cerr = "Expected {} occurrences but found {}".format(expected, found)
ret = expected == found
+ elif len(c.args) == 4: # @count = count test
+ expected = int(c.args[3])
+ found = get_nb_matching_elements(cache, c, False, False)
+ cerr = "Expected {} occurrences but found {}".format(expected, found)
+ ret = found == expected
else:
raise InvalidCheck('Invalid number of @{} arguments'.format(c.cmd))
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 23ce634cf286b..cb887d16906a1 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -1600,6 +1600,13 @@ fn render_impl(
}
if let Some(ref dox) = i.impl_item.collapsed_doc_value() {
+ if trait_.is_none() && i.inner_impl().items.is_empty() {
+ w.write_str(
+ "
\
+
This impl block contains no items.
+
",
+ );
+ }
write!(
w,
"
{}
",
diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css
index 7303cecc0d61a..8e0521d9ad6a1 100644
--- a/src/librustdoc/html/static/css/themes/ayu.css
+++ b/src/librustdoc/html/static/css/themes/ayu.css
@@ -281,9 +281,13 @@ details.undocumented > summary::before {
color: #000;
}
+/* Created this empty rule to satisfy the theme checks. */
+.stab.empty-impl {}
+
.stab.unstable,
.stab.deprecated,
-.stab.portability {
+.stab.portability,
+.stab.empty-impl {
color: #c5c5c5;
background: #314559 !important;
border-style: none !important;
diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css
index 34a4f446b560b..071ad006ed350 100644
--- a/src/librustdoc/html/static/css/themes/dark.css
+++ b/src/librustdoc/html/static/css/themes/dark.css
@@ -266,6 +266,7 @@ details.undocumented > summary::before {
color: #ddd;
}
+.stab.empty-impl { background: #FFF5D6; border-color: #FFC600; color: #2f2f2f; }
.stab.unstable { background: #FFF5D6; border-color: #FFC600; color: #2f2f2f; }
.stab.deprecated { background: #ffc4c4; border-color: #db7b7b; color: #2f2f2f; }
.stab.portability { background: #F3DFFF; border-color: #b07bdb; color: #2f2f2f; }
diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css
index aa6ad2f547333..5c3789bf4630a 100644
--- a/src/librustdoc/html/static/css/themes/light.css
+++ b/src/librustdoc/html/static/css/themes/light.css
@@ -255,6 +255,7 @@ details.undocumented > summary::before {
color: #000;
}
+.stab.empty-impl { background: #FFF5D6; border-color: #FFC600; }
.stab.unstable { background: #FFF5D6; border-color: #FFC600; }
.stab.deprecated { background: #ffc4c4; border-color: #db7b7b; }
.stab.portability { background: #F3DFFF; border-color: #b07bdb; }
diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs
index d5db919dc4b2a..0fd124e615415 100644
--- a/src/librustdoc/passes/stripper.rs
+++ b/src/librustdoc/passes/stripper.rs
@@ -124,8 +124,9 @@ pub(crate) struct ImplStripper<'a> {
impl<'a> DocFolder for ImplStripper<'a> {
fn fold_item(&mut self, i: Item) -> Option {
if let clean::ImplItem(ref imp) = *i.kind {
- // emptied none trait impls can be stripped
- if imp.trait_.is_none() && imp.items.is_empty() {
+ // Impl blocks can be skipped if they are: empty; not a trait impl; and have no
+ // documentation.
+ if imp.trait_.is_none() && imp.items.is_empty() && i.doc_value().is_none() {
return None;
}
if let Some(did) = imp.for_.def_id(self.cache) {
diff --git a/src/test/rustdoc/empty-impl-block.rs b/src/test/rustdoc/empty-impl-block.rs
new file mode 100644
index 0000000000000..6a2a254f63a7f
--- /dev/null
+++ b/src/test/rustdoc/empty-impl-block.rs
@@ -0,0 +1,20 @@
+#![crate_name = "foo"]
+
+// @has 'foo/struct.Foo.html'
+pub struct Foo;
+
+// @has - '//*[@class="docblock"]' 'Hello empty impl block!'
+// @has - '//*[@class="item-info"]' 'This impl block contains no items.'
+/// Hello empty impl block!
+impl Foo {}
+// We ensure that this empty impl block without doc isn't rendered.
+// @count - '//*[@class="impl has-srclink"]' 'impl Foo' 1
+impl Foo {}
+
+// Just to ensure that empty trait impl blocks are rendered.
+pub struct Another;
+pub trait Bar {}
+
+// @has 'foo/struct.Another.html'
+// @has - '//h3[@class="code-header in-band"]' 'impl Bar for Another'
+impl Bar for Another {}