diff --git a/src/etc/htmldocck.py b/src/etc/htmldocck.py
index 8a11c6f7cfc4c..569788fe9c08a 100644
--- a/src/etc/htmldocck.py
+++ b/src/etc/htmldocck.py
@@ -346,15 +346,19 @@ def check_tree_attr(tree, path, attr, pat, regexp):
def check_tree_text(tree, path, pat, regexp):
path = normalize_xpath(path)
ret = False
- for e in tree.findall(path):
- try:
- value = flatten(e)
- except KeyError:
- continue
- else:
- ret = check_string(value, pat, regexp)
- if ret:
- break
+ try:
+ for e in tree.findall(path):
+ try:
+ value = flatten(e)
+ except KeyError:
+ continue
+ else:
+ ret = check_string(value, pat, regexp)
+ if ret:
+ break
+ except Exception as e:
+ print('Failed to get path "{}"'.format(path))
+ raise e
return ret
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 1c1ba208678ed..178a85f9364de 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -26,7 +26,7 @@ use syntax::attr;
use syntax::codemap::{dummy_spanned, Spanned};
use syntax::feature_gate::UnstableFeatures;
use syntax::ptr::P;
-use syntax::symbol::keywords;
+use syntax::symbol::keywords::{self, Keyword};
use syntax::symbol::{Symbol, InternedString};
use syntax_pos::{self, DUMMY_SP, Pos, FileName};
@@ -54,6 +54,7 @@ use std::{mem, slice, vec};
use std::iter::{FromIterator, once};
use rustc_data_structures::sync::Lrc;
use std::rc::Rc;
+use std::str::FromStr;
use std::cell::RefCell;
use std::sync::Arc;
use std::u32;
@@ -177,7 +178,7 @@ impl<'a, 'tcx, 'rcx> Clean for visit_ast::RustdocVisitor<'a, 'tcx, 'rcx>
_ => unreachable!(),
}
- let ExternalCrate { name, src, primitives, .. } = LOCAL_CRATE.clean(cx);
+ let ExternalCrate { name, src, primitives, keywords, .. } = LOCAL_CRATE.clean(cx);
{
let m = match module.inner {
ModuleItem(ref mut m) => m,
@@ -195,6 +196,18 @@ impl<'a, 'tcx, 'rcx> Clean for visit_ast::RustdocVisitor<'a, 'tcx, 'rcx>
inner: PrimitiveItem(prim),
}
}));
+ m.items.extend(keywords.into_iter().map(|(def_id, kw, attrs)| {
+ Item {
+ source: Span::empty(),
+ name: Some(kw.clone()),
+ attrs: attrs,
+ visibility: Some(Public),
+ stability: get_stability(cx, def_id),
+ deprecation: get_deprecation(cx, def_id),
+ def_id,
+ inner: KeywordItem(kw),
+ }
+ }));
}
let mut access_levels = cx.access_levels.borrow_mut();
@@ -220,6 +233,7 @@ pub struct ExternalCrate {
pub src: FileName,
pub attrs: Attributes,
pub primitives: Vec<(DefId, PrimitiveType, Attributes)>,
+ pub keywords: Vec<(DefId, String, Attributes)>,
}
impl Clean for CrateNum {
@@ -286,11 +300,53 @@ impl Clean for CrateNum {
.filter_map(as_primitive).collect()
};
+ let as_keyword = |def: Def| {
+ if let Def::Mod(def_id) = def {
+ let attrs = cx.tcx.get_attrs(def_id).clean(cx);
+ let mut keyword = None;
+ for attr in attrs.lists("doc") {
+ if let Some(v) = attr.value_str() {
+ if attr.check_name("keyword") {
+ keyword = Keyword::from_str(&v.as_str()).ok()
+ .map(|x| x.name().to_string());
+ if keyword.is_some() {
+ break
+ }
+ // FIXME: should warn on unknown keywords?
+ }
+ }
+ }
+ return keyword.map(|p| (def_id, p, attrs));
+ }
+ None
+ };
+ let keywords = if root.is_local() {
+ cx.tcx.hir.krate().module.item_ids.iter().filter_map(|&id| {
+ let item = cx.tcx.hir.expect_item(id.id);
+ match item.node {
+ hir::ItemMod(_) => {
+ as_keyword(Def::Mod(cx.tcx.hir.local_def_id(id.id)))
+ }
+ hir::ItemUse(ref path, hir::UseKind::Single)
+ if item.vis == hir::Visibility::Public => {
+ as_keyword(path.def).map(|(_, prim, attrs)| {
+ (cx.tcx.hir.local_def_id(id.id), prim, attrs)
+ })
+ }
+ _ => None
+ }
+ }).collect()
+ } else {
+ cx.tcx.item_children(root).iter().map(|item| item.def)
+ .filter_map(as_keyword).collect()
+ };
+
ExternalCrate {
name: cx.tcx.crate_name(*self).to_string(),
src: krate_src,
attrs: cx.tcx.get_attrs(root).clean(cx),
primitives,
+ keywords,
}
}
}
@@ -397,6 +453,9 @@ impl Item {
pub fn is_extern_crate(&self) -> bool {
self.type_() == ItemType::ExternCrate
}
+ pub fn is_keyword(&self) -> bool {
+ self.type_() == ItemType::Keyword
+ }
pub fn is_stripped(&self) -> bool {
match self.inner { StrippedItem(..) => true, _ => false }
@@ -475,6 +534,7 @@ pub enum ItemEnum {
AssociatedTypeItem(Vec, Option),
/// An item that has been stripped by a rustdoc pass
StrippedItem(Box),
+ KeywordItem(String),
}
impl ItemEnum {
diff --git a/src/librustdoc/html/item_type.rs b/src/librustdoc/html/item_type.rs
index 537828de2c7f3..9b8ada1f6e6b2 100644
--- a/src/librustdoc/html/item_type.rs
+++ b/src/librustdoc/html/item_type.rs
@@ -42,6 +42,7 @@ pub enum ItemType {
AssociatedConst = 18,
Union = 19,
ForeignType = 20,
+ Keyword = 21,
}
@@ -50,6 +51,7 @@ pub enum NameSpace {
Type,
Value,
Macro,
+ Keyword,
}
impl<'a> From<&'a clean::Item> for ItemType {
@@ -83,6 +85,7 @@ impl<'a> From<&'a clean::Item> for ItemType {
clean::AssociatedConstItem(..) => ItemType::AssociatedConst,
clean::AssociatedTypeItem(..) => ItemType::AssociatedType,
clean::ForeignTypeItem => ItemType::ForeignType,
+ clean::KeywordItem(..) => ItemType::Keyword,
clean::StrippedItem(..) => unreachable!(),
}
}
@@ -131,6 +134,7 @@ impl ItemType {
ItemType::Constant => "constant",
ItemType::AssociatedConst => "associatedconstant",
ItemType::ForeignType => "foreigntype",
+ ItemType::Keyword => "keyword",
}
}
@@ -159,6 +163,8 @@ impl ItemType {
ItemType::AssociatedConst => NameSpace::Value,
ItemType::Macro => NameSpace::Macro,
+
+ ItemType::Keyword => NameSpace::Keyword,
}
}
}
@@ -172,6 +178,7 @@ impl fmt::Display for ItemType {
pub const NAMESPACE_TYPE: &'static str = "t";
pub const NAMESPACE_VALUE: &'static str = "v";
pub const NAMESPACE_MACRO: &'static str = "m";
+pub const NAMESPACE_KEYWORD: &'static str = "k";
impl NameSpace {
pub fn to_static_str(&self) -> &'static str {
@@ -179,6 +186,7 @@ impl NameSpace {
NameSpace::Type => NAMESPACE_TYPE,
NameSpace::Value => NAMESPACE_VALUE,
NameSpace::Macro => NAMESPACE_MACRO,
+ NameSpace::Keyword => NAMESPACE_KEYWORD,
}
}
}
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs
index 5377cd9a39143..5c2ec2058ee9d 100644
--- a/src/librustdoc/html/render.rs
+++ b/src/librustdoc/html/render.rs
@@ -1541,6 +1541,7 @@ struct AllTypes {
typedefs: HashSet,
statics: HashSet,
constants: HashSet,
+ keywords: HashSet,
}
impl AllTypes {
@@ -1556,6 +1557,7 @@ impl AllTypes {
typedefs: HashSet::with_capacity(100),
statics: HashSet::with_capacity(100),
constants: HashSet::with_capacity(100),
+ keywords: HashSet::with_capacity(100),
}
}
@@ -2063,12 +2065,13 @@ impl<'a> fmt::Display for Item<'a> {
clean::StaticItem(..) | clean::ForeignStaticItem(..) => write!(fmt, "Static ")?,
clean::ConstantItem(..) => write!(fmt, "Constant ")?,
clean::ForeignTypeItem => write!(fmt, "Foreign Type ")?,
+ clean::KeywordItem(..) => write!(fmt, "Keyword ")?,
_ => {
// We don't generate pages for any other type.
unreachable!();
}
}
- if !self.item.is_primitive() {
+ if !self.item.is_primitive() && !self.item.is_keyword() {
let cur = &self.cx.current;
let amt = if self.item.is_mod() { cur.len() - 1 } else { cur.len() };
for (i, component) in cur.iter().enumerate().take(amt) {
@@ -2126,6 +2129,7 @@ impl<'a> fmt::Display for Item<'a> {
item_static(fmt, self.cx, self.item, i),
clean::ConstantItem(ref c) => item_constant(fmt, self.cx, self.item, c),
clean::ForeignTypeItem => item_foreign_type(fmt, self.cx, self.item),
+ clean::KeywordItem(ref k) => item_keyword(fmt, self.cx, self.item, k),
_ => {
// We don't generate pages for any other type.
unreachable!();
@@ -2353,29 +2357,7 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context,
write!(w, "")?;
}
curty = myty;
- let (short, name) = match myty.unwrap() {
- ItemType::ExternCrate |
- ItemType::Import => ("reexports", "Re-exports"),
- ItemType::Module => ("modules", "Modules"),
- ItemType::Struct => ("structs", "Structs"),
- ItemType::Union => ("unions", "Unions"),
- ItemType::Enum => ("enums", "Enums"),
- ItemType::Function => ("functions", "Functions"),
- ItemType::Typedef => ("types", "Type Definitions"),
- ItemType::Static => ("statics", "Statics"),
- ItemType::Constant => ("constants", "Constants"),
- ItemType::Trait => ("traits", "Traits"),
- ItemType::Impl => ("impls", "Implementations"),
- ItemType::TyMethod => ("tymethods", "Type Methods"),
- ItemType::Method => ("methods", "Methods"),
- ItemType::StructField => ("fields", "Struct Fields"),
- ItemType::Variant => ("variants", "Variants"),
- ItemType::Macro => ("macros", "Macros"),
- ItemType::Primitive => ("primitives", "Primitive Types"),
- ItemType::AssociatedType => ("associated-types", "Associated Types"),
- ItemType::AssociatedConst => ("associated-consts", "Associated Constants"),
- ItemType::ForeignType => ("foreign-types", "Foreign Types"),
- };
+ let (short, name) = item_ty_to_strs(&myty.unwrap());
write!(w, "\n",
id = derive_id(short.to_owned()), name = name)?;
@@ -4360,6 +4342,33 @@ fn sidebar_enum(fmt: &mut fmt::Formatter, it: &clean::Item,
Ok(())
}
+fn item_ty_to_strs(ty: &ItemType) -> (&'static str, &'static str) {
+ match *ty {
+ ItemType::ExternCrate |
+ ItemType::Import => ("reexports", "Re-exports"),
+ ItemType::Module => ("modules", "Modules"),
+ ItemType::Struct => ("structs", "Structs"),
+ ItemType::Union => ("unions", "Unions"),
+ ItemType::Enum => ("enums", "Enums"),
+ ItemType::Function => ("functions", "Functions"),
+ ItemType::Typedef => ("types", "Type Definitions"),
+ ItemType::Static => ("statics", "Statics"),
+ ItemType::Constant => ("constants", "Constants"),
+ ItemType::Trait => ("traits", "Traits"),
+ ItemType::Impl => ("impls", "Implementations"),
+ ItemType::TyMethod => ("tymethods", "Type Methods"),
+ ItemType::Method => ("methods", "Methods"),
+ ItemType::StructField => ("fields", "Struct Fields"),
+ ItemType::Variant => ("variants", "Variants"),
+ ItemType::Macro => ("macros", "Macros"),
+ ItemType::Primitive => ("primitives", "Primitive Types"),
+ ItemType::AssociatedType => ("associated-types", "Associated Types"),
+ ItemType::AssociatedConst => ("associated-consts", "Associated Constants"),
+ ItemType::ForeignType => ("foreign-types", "Foreign Types"),
+ ItemType::Keyword => ("keywords", "Keywords"),
+ }
+}
+
fn sidebar_module(fmt: &mut fmt::Formatter, _it: &clean::Item,
items: &[clean::Item]) -> fmt::Result {
let mut sidebar = String::new();
@@ -4379,29 +4388,7 @@ fn sidebar_module(fmt: &mut fmt::Formatter, _it: &clean::Item,
ItemType::TyMethod, ItemType::Method, ItemType::StructField, ItemType::Variant,
ItemType::AssociatedType, ItemType::AssociatedConst, ItemType::ForeignType] {
if items.iter().any(|it| !it.is_stripped() && it.type_() == myty) {
- let (short, name) = match myty {
- ItemType::ExternCrate |
- ItemType::Import => ("reexports", "Re-exports"),
- ItemType::Module => ("modules", "Modules"),
- ItemType::Struct => ("structs", "Structs"),
- ItemType::Union => ("unions", "Unions"),
- ItemType::Enum => ("enums", "Enums"),
- ItemType::Function => ("functions", "Functions"),
- ItemType::Typedef => ("types", "Type Definitions"),
- ItemType::Static => ("statics", "Statics"),
- ItemType::Constant => ("constants", "Constants"),
- ItemType::Trait => ("traits", "Traits"),
- ItemType::Impl => ("impls", "Implementations"),
- ItemType::TyMethod => ("tymethods", "Type Methods"),
- ItemType::Method => ("methods", "Methods"),
- ItemType::StructField => ("fields", "Struct Fields"),
- ItemType::Variant => ("variants", "Variants"),
- ItemType::Macro => ("macros", "Macros"),
- ItemType::Primitive => ("primitives", "Primitive Types"),
- ItemType::AssociatedType => ("associated-types", "Associated Types"),
- ItemType::AssociatedConst => ("associated-consts", "Associated Constants"),
- ItemType::ForeignType => ("foreign-types", "Foreign Types"),
- };
+ let (short, name) = item_ty_to_strs(&myty);
sidebar.push_str(&format!("{name}",
id = short,
name = name));
@@ -4462,6 +4449,12 @@ fn item_primitive(w: &mut fmt::Formatter, cx: &Context,
render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
}
+fn item_keyword(w: &mut fmt::Formatter, cx: &Context,
+ it: &clean::Item,
+ _p: &str) -> fmt::Result {
+ document(w, cx, it)
+}
+
const BASIC_KEYWORDS: &'static str = "rust, rustlang, rust-lang";
fn make_item_keywords(it: &clean::Item) -> String {
diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js
index 0c937759120ef..983da51779ca2 100644
--- a/src/librustdoc/html/static/main.js
+++ b/src/librustdoc/html/static/main.js
@@ -38,7 +38,8 @@
"constant",
"associatedconstant",
"union",
- "foreigntype"];
+ "foreigntype",
+ "keyword"];
var search_input = document.getElementsByClassName('search-input')[0];
@@ -158,6 +159,7 @@
// used for special search precedence
var TY_PRIMITIVE = itemTypes.indexOf("primitive");
+ var TY_KEYWORD = itemTypes.indexOf("keyword");
onEach(document.getElementsByClassName('js-only'), function(e) {
removeClass(e, 'js-only');
@@ -530,11 +532,13 @@
b = bbb.index;
if (a !== b) { return a - b; }
- // special precedence for primitive pages
- if ((aaa.item.ty === TY_PRIMITIVE) && (bbb.item.ty !== TY_PRIMITIVE)) {
+ // special precedence for primitive and keyword pages
+ if ((aaa.item.ty === TY_PRIMITIVE && bbb.item.ty !== TY_KEYWORD) ||
+ (aaa.item.ty === TY_KEYWORD && bbb.item.ty !== TY_PRIMITIVE)) {
return -1;
}
- if ((bbb.item.ty === TY_PRIMITIVE) && (aaa.item.ty !== TY_PRIMITIVE)) {
+ if ((bbb.item.ty === TY_PRIMITIVE && aaa.item.ty !== TY_PRIMITIVE) ||
+ (bbb.item.ty === TY_KEYWORD && aaa.item.ty !== TY_KEYWORD)) {
return 1;
}
@@ -1206,7 +1210,7 @@
displayPath = item.path + '::';
href = rootPath + item.path.replace(/::/g, '/') + '/' +
name + '/index.html';
- } else if (type === "primitive") {
+ } else if (type === "primitive" || type === "keyword") {
displayPath = "";
href = rootPath + item.path.replace(/::/g, '/') +
'/' + type + '.' + name + '.html';
@@ -1700,6 +1704,7 @@
block("fn", "Functions");
block("type", "Type Definitions");
block("foreigntype", "Foreign Types");
+ block("keyword", "Keywords");
}
window.initSidebarItems = initSidebarItems;
diff --git a/src/librustdoc/html/static/rustdoc.css b/src/librustdoc/html/static/rustdoc.css
index 773b8174e56bc..d684db152df01 100644
--- a/src/librustdoc/html/static/rustdoc.css
+++ b/src/librustdoc/html/static/rustdoc.css
@@ -628,6 +628,11 @@ tr.result span.primitive::after {
font-style: italic;
}
+tr.result span.keyword::after {
+ content: ' (keyword)';
+ font-style: italic;
+}
+
body.blur > :not(#help) {
filter: blur(8px);
-webkit-filter: blur(8px);
diff --git a/src/librustdoc/html/static/themes/dark.css b/src/librustdoc/html/static/themes/dark.css
index f96dcd9ec1c88..7add0e21f548c 100644
--- a/src/librustdoc/html/static/themes/dark.css
+++ b/src/librustdoc/html/static/themes/dark.css
@@ -128,6 +128,7 @@ pre {
.content .highlighted.constant,
.content .highlighted.static { background-color: #0063cc; }
.content .highlighted.primitive { background-color: #00708a; }
+.content .highlighted.keyword { background-color: #884719; }
.content span.enum, .content a.enum, .block a.current.enum { color: #82b089; }
.content span.struct, .content a.struct, .block a.current.struct { color: #2dbfb8; }
@@ -145,6 +146,7 @@ pre {
.content span.method, .content a.method, .block a.current.method,
.content span.tymethod, .content a.tymethod, .block a.current.tymethod,
.content .fnname{ color: #2BAB63; }
+.content span.keyword, .content a.keyword, .block a.current.keyword { color: #de5249; }
pre.rust .comment { color: #8d8d8b; }
pre.rust .doccomment { color: #8ca375; }
@@ -209,7 +211,7 @@ a.test-arrow {
color: grey;
}
-tr.result span.primitive::after {
+tr.result span.primitive::after, tr.result span.keyword::after {
color: #ddd;
}
diff --git a/src/librustdoc/html/static/themes/light.css b/src/librustdoc/html/static/themes/light.css
index 54cf50cfffd1e..7d9980363de9c 100644
--- a/src/librustdoc/html/static/themes/light.css
+++ b/src/librustdoc/html/static/themes/light.css
@@ -128,6 +128,7 @@ pre {
.content .highlighted.constant,
.content .highlighted.static { background-color: #c3e0ff; }
.content .highlighted.primitive { background-color: #9aecff; }
+.content .highlighted.keyword { background-color: #f99650; }
.content span.enum, .content a.enum, .block a.current.enum { color: #508157; }
.content span.struct, .content a.struct, .block a.current.struct { color: #ad448e; }
@@ -145,6 +146,7 @@ pre {
.content span.method, .content a.method, .block a.current.method,
.content span.tymethod, .content a.tymethod, .block a.current.tymethod,
.content .fnname { color: #9a6e31; }
+.content span.keyword, .content a.keyword, .block a.current.keyword { color: #de5249; }
pre.rust .comment { color: #8E908C; }
pre.rust .doccomment { color: #4D4D4C; }
@@ -203,7 +205,7 @@ a.test-arrow {
color: grey;
}
-tr.result span.primitive::after {
+tr.result span.primitive::after, tr.result span.keyword::after {
color: black;
}
diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs
index 32f0bcada1d20..fe116a22eccbf 100644
--- a/src/librustdoc/passes/mod.rs
+++ b/src/librustdoc/passes/mod.rs
@@ -126,6 +126,9 @@ impl<'a> fold::DocFolder for Stripper<'a> {
// Associated types are never stripped
clean::AssociatedTypeItem(..) => {}
+
+ // Keywords are never stripped
+ clean::KeywordItem(..) => {}
}
let fastreturn = match i.inner {
diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs
index c576245edb755..8266cec5139f9 100644
--- a/src/libstd/lib.rs
+++ b/src/libstd/lib.rs
@@ -316,6 +316,7 @@
#![cfg_attr(test, feature(update_panic_count))]
#![cfg_attr(windows, feature(used))]
#![feature(doc_alias)]
+#![feature(doc_keyword)]
#![feature(float_internals)]
#![feature(panic_info_message)]
#![cfg_attr(not(stage0), feature(panic_implementation))]
diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs
index 7349745fefea1..51788b6063a33 100644
--- a/src/libsyntax/feature_gate.rs
+++ b/src/libsyntax/feature_gate.rs
@@ -476,8 +476,12 @@ declare_features! (
// 'a: { break 'a; }
(active, label_break_value, "1.28.0", Some(48594), None),
+
// #[panic_implementation]
(active, panic_implementation, "1.28.0", Some(44489), None),
+
+ // #[doc(keyword = "...")]
+ (active, doc_keyword, "1.28.0", Some(51315), None),
);
declare_features! (
@@ -1506,6 +1510,10 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
gate_feature_post!(&self, doc_alias, attr.span,
"#[doc(alias = \"...\")] is experimental"
);
+ } else if content.iter().any(|c| c.check_name("keyword")) {
+ gate_feature_post!(&self, doc_keyword, attr.span,
+ "#[doc(keyword = \"...\")] is experimental"
+ );
}
}
}
diff --git a/src/libsyntax_pos/symbol.rs b/src/libsyntax_pos/symbol.rs
index a4b2d9de932a7..283b41e5725a9 100644
--- a/src/libsyntax_pos/symbol.rs
+++ b/src/libsyntax_pos/symbol.rs
@@ -311,6 +311,17 @@ macro_rules! declare_keywords {(
ident: Ident::with_empty_ctxt(super::Symbol($index))
};
)*
+
+ impl ::std::str::FromStr for Keyword {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result {
+ match s {
+ $($string => Ok($konst),)*
+ _ => Err(()),
+ }
+ }
+ }
}
impl Interner {
diff --git a/src/test/rustdoc/keyword.rs b/src/test/rustdoc/keyword.rs
new file mode 100644
index 0000000000000..b255ffddafac8
--- /dev/null
+++ b/src/test/rustdoc/keyword.rs
@@ -0,0 +1,24 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 or the MIT license
+// , at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![crate_name = "foo"]
+
+#![feature(doc_keyword)]
+
+// @has foo/index.html '//h2[@id="keywords"]' 'Keywords'
+// @has foo/index.html '//a[@href="keyword.match.html"]' 'match'
+// @has foo/keyword.match.html '//a[@class="keyword"]' 'match'
+// @has foo/keyword.match.html '//section[@id="main"]//div[@class="docblock"]//p' 'this is a test!'
+// @!has foo/index.html '//a/@href' 'foo/index.html'
+// @!has foo/foo/index.html
+// @!has-dir foo/foo
+#[doc(keyword = "match")]
+/// this is a test!
+mod foo{}
diff --git a/src/test/ui/feature-gate-doc_keyword.rs b/src/test/ui/feature-gate-doc_keyword.rs
new file mode 100644
index 0000000000000..2ff44626ccd06
--- /dev/null
+++ b/src/test/ui/feature-gate-doc_keyword.rs
@@ -0,0 +1,13 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 or the MIT license
+// , at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#[doc(keyword = "match")] //~ ERROR: #[doc(keyword = "...")] is experimental
+/// wonderful
+mod foo{}
diff --git a/src/test/ui/feature-gate-doc_keyword.stderr b/src/test/ui/feature-gate-doc_keyword.stderr
new file mode 100644
index 0000000000000..e4f5109afc3eb
--- /dev/null
+++ b/src/test/ui/feature-gate-doc_keyword.stderr
@@ -0,0 +1,11 @@
+error[E0658]: #[doc(keyword = "...")] is experimental (see issue #51315)
+ --> $DIR/feature-gate-doc_keyword.rs:11:1
+ |
+LL | #[doc(keyword = "match")] //~ ERROR: #[doc(keyword = "...")] is experimental
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: add #![feature(doc_keyword)] to the crate attributes to enable
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/tools/rustdoc-js/tester.js b/src/tools/rustdoc-js/tester.js
index bc4503903d665..a05c8a8ac9133 100644
--- a/src/tools/rustdoc-js/tester.js
+++ b/src/tools/rustdoc-js/tester.js
@@ -231,7 +231,9 @@ function main(argv) {
finalJS = "";
var arraysToLoad = ["itemTypes"];
- var variablesToLoad = ["MAX_LEV_DISTANCE", "MAX_RESULTS", "TY_PRIMITIVE", "levenshtein_row2"];
+ var variablesToLoad = ["MAX_LEV_DISTANCE", "MAX_RESULTS",
+ "TY_PRIMITIVE", "TY_KEYWORD",
+ "levenshtein_row2"];
// execQuery first parameter is built in getQuery (which takes in the search input).
// execQuery last parameter is built in buildIndex.
// buildIndex requires the hashmap from search-index.