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 {}