diff --git a/src/Cargo.lock b/src/Cargo.lock
index 8fbf3535264fc..5b44f0ea34e04 100644
--- a/src/Cargo.lock
+++ b/src/Cargo.lock
@@ -838,7 +838,7 @@ dependencies = [
 
 [[package]]
 name = "html-diff"
-version = "0.0.5"
+version = "0.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "kuchiki 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2127,7 +2127,7 @@ version = "0.0.0"
 dependencies = [
  "build_helper 0.1.0",
  "cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "html-diff 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "html-diff 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "pulldown-cmark 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -2855,7 +2855,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum handlebars 0.29.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fb04af2006ea09d985fef82b81e0eb25337e51b691c76403332378a53d521edc"
 "checksum hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa"
 "checksum home 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f25ae61099d8f3fee8b483df0bd4ecccf4b2731897aad40d50eca1b641fe6db"
-"checksum html-diff 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9778743e3b3c3679f471f0ed1833c690f19f4a0919e33b281f12ef5f77ad64c6"
+"checksum html-diff 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ee4cfdf62a484a3ac0d9b80f562d37f99366db08a63621b917ea3056565345f7"
 "checksum html5ever 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5bfb46978eb757a603b7dfe2dafb1c62cb4dee3428d8ac1de734d83d6b022d06"
 "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d"
 "checksum if_chain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "61bb90bdd39e3af69b0172dfc6130f6cd6332bf040fbb9bdd4401d37adbd48b8"
diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs
index 8e35ecc8090c6..deade80334e96 100644
--- a/src/bootstrap/builder.rs
+++ b/src/bootstrap/builder.rs
@@ -254,7 +254,9 @@ impl<'a> Builder<'a> {
             Kind::Test => describe!(check::Tidy, check::Bootstrap, check::DefaultCompiletest,
                 check::HostCompiletest, check::Crate, check::CrateLibrustc, check::Rustdoc,
                 check::Linkcheck, check::Cargotest, check::Cargo, check::Rls, check::Docs,
-                check::ErrorIndex, check::Distcheck, check::Rustfmt, check::Miri, check::Clippy),
+                check::ErrorIndex, check::Distcheck, check::Rustfmt, check::Miri, check::Clippy,
+                check::RustdocJS),
+
             Kind::Bench => describe!(check::Crate, check::CrateLibrustc),
             Kind::Doc => describe!(doc::UnstableBook, doc::UnstableBookGen, doc::TheBook,
                 doc::Standalone, doc::Std, doc::Test, doc::Rustc, doc::ErrorIndex, doc::Nomicon,
@@ -443,7 +445,8 @@ impl<'a> Builder<'a> {
         let out_dir = self.stage_out(compiler, mode);
         cargo.env("CARGO_TARGET_DIR", out_dir)
              .arg(cmd)
-             .arg("--target").arg(target);
+             .arg("--target")
+             .arg(target);
 
         // If we were invoked from `make` then that's already got a jobserver
         // set up for us so no need to tell Cargo about jobs all over again.
diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs
index ed110762cb3c0..78ad71172a848 100644
--- a/src/bootstrap/check.rs
+++ b/src/bootstrap/check.rs
@@ -424,6 +424,43 @@ fn path_for_cargo(builder: &Builder, compiler: Compiler) -> OsString {
     env::join_paths(iter::once(path).chain(env::split_paths(&old_path))).expect("")
 }
 
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct RustdocJS {
+    pub host: Interned<String>,
+    pub target: Interned<String>,
+}
+
+impl Step for RustdocJS {
+    type Output = ();
+    const DEFAULT: bool = true;
+    const ONLY_HOSTS: bool = true;
+
+    fn should_run(run: ShouldRun) -> ShouldRun {
+        run.path("src/test/rustdoc-js")
+    }
+
+    fn make_run(run: RunConfig) {
+        run.builder.ensure(RustdocJS {
+            host: run.host,
+            target: run.target,
+        });
+    }
+
+    fn run(self, builder: &Builder) {
+        if let Some(ref nodejs) = builder.config.nodejs {
+            let mut command = Command::new(nodejs);
+            command.args(&["src/tools/rustdoc-js/tester.js", &*self.host]);
+            builder.ensure(::doc::Std {
+                target: self.target,
+                stage: builder.top_stage,
+            });
+            builder.run(&mut command);
+        } else {
+            println!("No nodejs found, skipping \"src/test/rustdoc-js\" tests");
+        }
+    }
+}
+
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct Tidy {
     host: Interned<String>,
diff --git a/src/bootstrap/doc.rs b/src/bootstrap/doc.rs
index d66c01eb4990e..2b765fa498e96 100644
--- a/src/bootstrap/doc.rs
+++ b/src/bootstrap/doc.rs
@@ -419,8 +419,8 @@ impl Step for Standalone {
 
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Std {
-    stage: u32,
-    target: Interned<String>,
+    pub stage: u32,
+    pub target: Interned<String>,
 }
 
 impl Step for Std {
diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs
index 2b0c839152ccb..ceb39aea108c8 100644
--- a/src/librustc_resolve/macros.rs
+++ b/src/librustc_resolve/macros.rs
@@ -691,8 +691,7 @@ impl<'a> Resolver<'a> {
         if let Some(suggestion) = suggestion {
             if suggestion != name {
                 if let MacroKind::Bang = kind {
-                    err.span_suggestion(span, "you could try the macro",
-                                        format!("{}!", suggestion));
+                    err.span_suggestion(span, "you could try the macro", suggestion.to_string());
                 } else {
                     err.span_suggestion(span, "try", suggestion.to_string());
                 }
diff --git a/src/librustc_trans/back/write.rs b/src/librustc_trans/back/write.rs
index 1ee04a46243a2..db0b045ef1e73 100644
--- a/src/librustc_trans/back/write.rs
+++ b/src/librustc_trans/back/write.rs
@@ -1360,15 +1360,10 @@ fn start_executing_work(tcx: TyCtxt,
     let sess = tcx.sess;
 
     // First up, convert our jobserver into a helper thread so we can use normal
-    // mpsc channels to manage our messages and such. Once we've got the helper
-    // thread then request `n-1` tokens because all of our work items are ready
-    // to go.
-    //
-    // Note that the `n-1` is here because we ourselves have a token (our
-    // process) and we'll use that token to execute at least one unit of work.
-    //
-    // After we've requested all these tokens then we'll, when we can, get
-    // tokens on `rx` above which will get managed in the main loop below.
+    // mpsc channels to manage our messages and such.
+    // After we've requested tokens then we'll, when we can,
+    // get tokens on `coordinator_receive` which will
+    // get managed in the main loop below.
     let coordinator_send2 = coordinator_send.clone();
     let helper = jobserver.into_helper_thread(move |token| {
         drop(coordinator_send2.send(Box::new(Message::Token(token))));
diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml
index 1e574964b12dc..608adcb43d6c3 100644
--- a/src/librustdoc/Cargo.toml
+++ b/src/librustdoc/Cargo.toml
@@ -12,7 +12,7 @@ doctest = false
 
 [dependencies]
 pulldown-cmark = { version = "0.1.0", default-features = false }
-html-diff = "0.0.5"
+html-diff = "0.0.6"
 tempdir = "0.3"
 
 [build-dependencies]
diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js
index b4dbd76d0b4d0..a9a5bd5de0552 100644
--- a/src/librustdoc/html/static/main.js
+++ b/src/librustdoc/html/static/main.js
@@ -353,35 +353,33 @@
      * This code is an unmodified version of the code written by Marco de Wit
      * and was found at http://stackoverflow.com/a/18514751/745719
      */
-    var levenshtein = (function() {
-        var row2 = [];
-        return function(s1, s2) {
-            if (s1 === s2) {
-                return 0;
+    var levenshtein_row2 = [];
+    function levenshtein(s1, s2) {
+        if (s1 === s2) {
+            return 0;
+        }
+        var s1_len = s1.length, s2_len = s2.length;
+        if (s1_len && s2_len) {
+            var i1 = 0, i2 = 0, a, b, c, c2, row = levenshtein_row2;
+            while (i1 < s1_len) {
+                row[i1] = ++i1;
             }
-            var s1_len = s1.length, s2_len = s2.length;
-            if (s1_len && s2_len) {
-                var i1 = 0, i2 = 0, a, b, c, c2, row = row2;
-                while (i1 < s1_len) {
-                    row[i1] = ++i1;
-                }
-                while (i2 < s2_len) {
-                    c2 = s2.charCodeAt(i2);
-                    a = i2;
-                    ++i2;
-                    b = i2;
-                    for (i1 = 0; i1 < s1_len; ++i1) {
-                        c = a + (s1.charCodeAt(i1) !== c2 ? 1 : 0);
-                        a = row[i1];
-                        b = b < a ? (b < c ? b + 1 : c) : (a < c ? a + 1 : c);
-                        row[i1] = b;
-                    }
+            while (i2 < s2_len) {
+                c2 = s2.charCodeAt(i2);
+                a = i2;
+                ++i2;
+                b = i2;
+                for (i1 = 0; i1 < s1_len; ++i1) {
+                    c = a + (s1.charCodeAt(i1) !== c2 ? 1 : 0);
+                    a = row[i1];
+                    b = b < a ? (b < c ? b + 1 : c) : (a < c ? a + 1 : c);
+                    row[i1] = b;
                 }
-                return b;
             }
-            return s1_len + s2_len;
-        };
-    })();
+            return b;
+        }
+        return s1_len + s2_len;
+    }
 
     function initSearch(rawSearchIndex) {
         var currentResults, index, searchIndex;
@@ -400,12 +398,20 @@
         /**
          * Executes the query and builds an index of results
          * @param  {[Object]} query     [The user query]
-         * @param  {[type]} max         [The maximum results returned]
          * @param  {[type]} searchWords [The list of search words to query
          *                               against]
          * @return {[type]}             [A search index of results]
          */
-        function execQuery(query, max, searchWords) {
+        function execQuery(query, searchWords) {
+            function itemTypeFromName(typename) {
+                for (var i = 0; i < itemTypes.length; ++i) {
+                    if (itemTypes[i] === typename) {
+                        return i;
+                    }
+                }
+                return -1;
+            }
+
             var valLower = query.query.toLowerCase(),
                 val = valLower,
                 typeFilter = itemTypeFromName(query.type),
@@ -1021,9 +1027,8 @@
             return true;
         }
 
-        function getQuery() {
-            var matches, type, query, raw =
-                document.getElementsByClassName('search-input')[0].value;
+        function getQuery(raw) {
+            var matches, type, query;
             query = raw;
 
             matches = query.match(/^(fn|mod|struct|enum|trait|type|const|macro)\s*:\s*/i);
@@ -1227,7 +1232,7 @@
         }
 
         function showResults(results) {
-            var output, query = getQuery();
+            var output, query = getQuery(document.getElementsByClassName('search-input')[0].value);
 
             currentResults = query.id;
             output = '<h1>Results for ' + escape(query.query) +
@@ -1271,7 +1276,7 @@
                 resultIndex;
             var params = getQueryStringParams();
 
-            query = getQuery();
+            query = getQuery(document.getElementsByClassName('search-input')[0].value);
             if (e) {
                 e.preventDefault();
             }
@@ -1293,19 +1298,10 @@
                 }
             }
 
-            results = execQuery(query, 20000, index);
+            results = execQuery(query, index);
             showResults(results);
         }
 
-        function itemTypeFromName(typename) {
-            for (var i = 0; i < itemTypes.length; ++i) {
-                if (itemTypes[i] === typename) {
-                    return i;
-                }
-            }
-            return -1;
-        }
-
         function buildIndex(rawSearchIndex) {
             searchIndex = [];
             var searchWords = [];
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 3b43eafb849bd..0b57d5d26cecf 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -242,8 +242,8 @@ pub fn opts() -> Vec<RustcOptGroup> {
                       or `#![doc(html_playground_url=...)]`",
                      "URL")
         }),
-        unstable("enable-commonmark", |o| {
-            o.optflag("", "enable-commonmark", "to enable commonmark doc rendering/testing")
+        unstable("disable-commonmark", |o| {
+            o.optflag("", "disable-commonmark", "to disable commonmark doc rendering/testing")
         }),
         unstable("display-warnings", |o| {
             o.optflag("", "display-warnings", "to print code warnings when testing doc")
@@ -347,10 +347,10 @@ pub fn main_args(args: &[String]) -> isize {
     let css_file_extension = matches.opt_str("e").map(|s| PathBuf::from(&s));
     let cfgs = matches.opt_strs("cfg");
 
-    let render_type = if matches.opt_present("enable-commonmark") {
-        RenderType::Pulldown
-    } else {
+    let render_type = if matches.opt_present("disable-commonmark") {
         RenderType::Hoedown
+    } else {
+        RenderType::Pulldown
     };
 
     if let Some(ref p) = css_file_extension {
diff --git a/src/test/rustdoc-js/basic.js b/src/test/rustdoc-js/basic.js
new file mode 100644
index 0000000000000..863437cac91d4
--- /dev/null
+++ b/src/test/rustdoc-js/basic.js
@@ -0,0 +1,25 @@
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+const QUERY = 'String';
+
+const EXPECTED = {
+    'others': [
+        { 'path': 'std::string', 'name': 'String' },
+        { 'path': 'std::ffi', 'name': 'OsString' },
+        { 'path': 'std::ffi', 'name': 'CString' },
+    ],
+    'in_args': [
+        { 'path': 'std::str', 'name': 'eq' },
+    ],
+    'returned': [
+        { 'path': 'std::string::String', 'name': 'add' },
+    ],
+};
diff --git a/src/test/rustdoc-js/enum-option.js b/src/test/rustdoc-js/enum-option.js
new file mode 100644
index 0000000000000..3dac983b11b0e
--- /dev/null
+++ b/src/test/rustdoc-js/enum-option.js
@@ -0,0 +1,17 @@
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+const QUERY = 'enum:Option';
+
+const EXPECTED = {
+    'others': [
+        { 'path': 'std::option', 'name': 'Option' },
+    ],
+};
diff --git a/src/test/rustdoc-js/fn-forget.js b/src/test/rustdoc-js/fn-forget.js
new file mode 100644
index 0000000000000..10310d5eaf7b9
--- /dev/null
+++ b/src/test/rustdoc-js/fn-forget.js
@@ -0,0 +1,18 @@
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+const QUERY = 'fn:forget';
+
+const EXPECTED = {
+    'others': [
+        { 'path': 'std::mem', 'name': 'forget' },
+        { 'path': 'std::fmt', 'name': 'format' },
+    ],
+};
diff --git a/src/test/rustdoc-js/from_u.js b/src/test/rustdoc-js/from_u.js
new file mode 100644
index 0000000000000..920620a9aeed5
--- /dev/null
+++ b/src/test/rustdoc-js/from_u.js
@@ -0,0 +1,22 @@
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+const QUERY = 'from_u';
+
+const EXPECTED = {
+    'others': [
+        { 'path': 'std::char', 'name': 'from_u32' },
+        { 'path': 'std::str', 'name': 'from_utf8' },
+        { 'path': 'std::string::String', 'name': 'from_utf8' },
+        { 'path': 'std::boxed::Box', 'name': 'from_unique' },
+        { 'path': 'std::i32', 'name': 'from_unsigned' },
+        { 'path': 'std::i128', 'name': 'from_unsigned' },
+    ],
+};
diff --git a/src/test/rustdoc-js/macro-print.js b/src/test/rustdoc-js/macro-print.js
new file mode 100644
index 0000000000000..811ba3474afa0
--- /dev/null
+++ b/src/test/rustdoc-js/macro-print.js
@@ -0,0 +1,20 @@
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+const QUERY = 'macro:print';
+
+const EXPECTED = {
+    'others': [
+        { 'path': 'std', 'name': 'print' },
+        { 'path': 'std', 'name': 'eprint' },
+        { 'path': 'std', 'name': 'println' },
+        { 'path': 'std', 'name': 'eprintln' },
+    ],
+};
diff --git a/src/test/rustdoc-js/string-from_ut.js b/src/test/rustdoc-js/string-from_ut.js
new file mode 100644
index 0000000000000..3d08ee3736612
--- /dev/null
+++ b/src/test/rustdoc-js/string-from_ut.js
@@ -0,0 +1,21 @@
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+const QUERY = 'String::from_ut';
+
+const EXPECTED = {
+    'others': [
+        { 'path': 'std::string::String', 'name': 'from_utf8' },
+        { 'path': 'std::string::String', 'name': 'from_utf8' },
+        { 'path': 'std::string::String', 'name': 'from_utf8_lossy' },
+        { 'path': 'std::string::String', 'name': 'from_utf16_lossy' },
+        { 'path': 'std::string::String', 'name': 'from_utf8_unchecked' },
+    ],
+};
diff --git a/src/test/rustdoc-js/struct-vec.js b/src/test/rustdoc-js/struct-vec.js
new file mode 100644
index 0000000000000..a91bc2d0da288
--- /dev/null
+++ b/src/test/rustdoc-js/struct-vec.js
@@ -0,0 +1,19 @@
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+const QUERY = 'struct:Vec';
+
+const EXPECTED = {
+    'others': [
+        { 'path': 'std::vec', 'name': 'Vec' },
+        { 'path': 'std::collections', 'name': 'VecDeque' },
+        { 'path': 'alloc::raw_vec', 'name': 'RawVec' },
+    ],
+};
diff --git a/src/test/ui-fulldeps/resolve-error.stderr b/src/test/ui-fulldeps/resolve-error.stderr
index be7ebae70adf5..9121ce1720c98 100644
--- a/src/test/ui-fulldeps/resolve-error.stderr
+++ b/src/test/ui-fulldeps/resolve-error.stderr
@@ -38,13 +38,13 @@ error: cannot find macro `FooWithLongNama!` in this scope
   --> $DIR/resolve-error.rs:62:5
    |
 62 |     FooWithLongNama!();
-   |     ^^^^^^^^^^^^^^^ help: you could try the macro: `FooWithLongNam!`
+   |     ^^^^^^^^^^^^^^^ help: you could try the macro: `FooWithLongNam`
 
 error: cannot find macro `attr_proc_macra!` in this scope
   --> $DIR/resolve-error.rs:65:5
    |
 65 |     attr_proc_macra!();
-   |     ^^^^^^^^^^^^^^^ help: you could try the macro: `attr_proc_mac!`
+   |     ^^^^^^^^^^^^^^^ help: you could try the macro: `attr_proc_mac`
 
 error: cannot find macro `Dlona!` in this scope
   --> $DIR/resolve-error.rs:68:5
@@ -56,7 +56,7 @@ error: cannot find macro `bang_proc_macrp!` in this scope
   --> $DIR/resolve-error.rs:71:5
    |
 71 |     bang_proc_macrp!();
-   |     ^^^^^^^^^^^^^^^ help: you could try the macro: `bang_proc_macro!`
+   |     ^^^^^^^^^^^^^^^ help: you could try the macro: `bang_proc_macro`
 
 error: aborting due to 10 previous errors
 
diff --git a/src/test/ui/macros/macro-name-typo.stderr b/src/test/ui/macros/macro-name-typo.stderr
index 84851749c7074..ebe95356c26eb 100644
--- a/src/test/ui/macros/macro-name-typo.stderr
+++ b/src/test/ui/macros/macro-name-typo.stderr
@@ -2,7 +2,7 @@ error: cannot find macro `printlx!` in this scope
   --> $DIR/macro-name-typo.rs:12:5
    |
 12 |     printlx!("oh noes!"); //~ ERROR cannot find
-   |     ^^^^^^^ help: you could try the macro: `println!`
+   |     ^^^^^^^ help: you could try the macro: `println`
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/macros/macro_undefined.stderr b/src/test/ui/macros/macro_undefined.stderr
index 6cfb05e786703..8d6da6a4732fc 100644
--- a/src/test/ui/macros/macro_undefined.stderr
+++ b/src/test/ui/macros/macro_undefined.stderr
@@ -10,7 +10,7 @@ error: cannot find macro `k!` in this scope
   --> $DIR/macro_undefined.rs:21:5
    |
 21 |     k!(); //~ ERROR cannot find
-   |     ^ help: you could try the macro: `kl!`
+   |     ^ help: you could try the macro: `kl`
 
 error: aborting due to 2 previous errors
 
diff --git a/src/tools/linkchecker/main.rs b/src/tools/linkchecker/main.rs
index 6458ec02669aa..f6eaa09f55d99 100644
--- a/src/tools/linkchecker/main.rs
+++ b/src/tools/linkchecker/main.rs
@@ -192,7 +192,17 @@ fn check(cache: &mut Cache,
             for part in Path::new(base).join(url).components() {
                 match part {
                     Component::Prefix(_) |
-                    Component::RootDir => panic!(),
+                    Component::RootDir => {
+                        // Avoid absolute paths as they make the docs not
+                        // relocatable by making assumptions on where the docs
+                        // are hosted relative to the site root.
+                        *errors = true;
+                        println!("{}:{}: absolute path - {}",
+                                 pretty_file.display(),
+                                 i + 1,
+                                 Path::new(base).join(url).display());
+                        return;
+                    }
                     Component::CurDir => {}
                     Component::ParentDir => { path.pop(); }
                     Component::Normal(s) => { path.push(s); }
diff --git a/src/tools/rustdoc-js/tester.js b/src/tools/rustdoc-js/tester.js
new file mode 100644
index 0000000000000..9b7e151b1ffec
--- /dev/null
+++ b/src/tools/rustdoc-js/tester.js
@@ -0,0 +1,210 @@
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+const fs = require('fs');
+
+const TEST_FOLDER = 'src/test/rustdoc-js/';
+
+// Stupid function extractor based on indent.
+function extractFunction(content, functionName) {
+    var x = content.split('\n');
+    var in_func = false;
+    var indent = 0;
+    var lines = [];
+
+    for (var i = 0; i < x.length; ++i) {
+        if (in_func === false) {
+            var splitter = "function " + functionName + "(";
+            if (x[i].trim().startsWith(splitter)) {
+                in_func = true;
+                indent = x[i].split(splitter)[0].length;
+                lines.push(x[i]);
+            }
+        } else {
+            lines.push(x[i]);
+            if (x[i].trim() === "}" && x[i].split("}")[0].length === indent) {
+                return lines.join("\n");
+            }
+        }
+    }
+    return null;
+}
+
+// Stupid function extractor for array.
+function extractArrayVariable(content, arrayName) {
+    var x = content.split('\n');
+    var found_var = false;
+    var lines = [];
+
+    for (var i = 0; i < x.length; ++i) {
+        if (found_var === false) {
+            var splitter = "var " + arrayName + " = [";
+            if (x[i].trim().startsWith(splitter)) {
+                found_var = true;
+                i -= 1;
+            }
+        } else {
+            lines.push(x[i]);
+            if (x[i].endsWith('];')) {
+                return lines.join("\n");
+            }
+        }
+    }
+    return null;
+}
+
+// Stupid function extractor for variable.
+function extractVariable(content, varName) {
+    var x = content.split('\n');
+    var found_var = false;
+    var lines = [];
+
+    for (var i = 0; i < x.length; ++i) {
+        if (found_var === false) {
+            var splitter = "var " + varName + " = ";
+            if (x[i].trim().startsWith(splitter)) {
+                found_var = true;
+                i -= 1;
+            }
+        } else {
+            lines.push(x[i]);
+            if (x[i].endsWith(';')) {
+                return lines.join("\n");
+            }
+        }
+    }
+    return null;
+}
+
+function loadContent(content) {
+    var Module = module.constructor;
+    var m = new Module();
+    m._compile(content, "tmp.js");
+    return m.exports;
+}
+
+function readFile(filePath) {
+    return fs.readFileSync(filePath, 'utf8');
+}
+
+function loadThings(thingsToLoad, kindOfLoad, funcToCall, fileContent) {
+    var content = '';
+    for (var i = 0; i < thingsToLoad.length; ++i) {
+        var tmp = funcToCall(fileContent, thingsToLoad[i]);
+        if (tmp === null) {
+            console.error('enable to find ' + kindOfLoad + ' "' + thingsToLoad[i] + '"');
+            process.exit(1);
+        }
+        content += tmp;
+        content += 'exports.' + thingsToLoad[i] + ' = ' + thingsToLoad[i] + ';';
+    }
+    return content;
+}
+
+function lookForEntry(entry, data) {
+    for (var i = 0; i < data.length; ++i) {
+        var allGood = true;
+        for (var key in entry) {
+            if (!entry.hasOwnProperty(key)) {
+                continue;
+            }
+            let value = data[i][key];
+            // To make our life easier, if there is a "parent" type, we add it to the path.
+            if (key === 'path' && data[i]['parent'] !== undefined) {
+                if (value.length > 0) {
+                    value += '::' + data[i]['parent']['name'];
+                } else {
+                    value = data[i]['parent']['name'];
+                }
+            }
+            if (value !== entry[key]) {
+                allGood = false;
+                break;
+            }
+        }
+        if (allGood === true) {
+            return true;
+        }
+    }
+    return false;
+}
+
+function main(argv) {
+    if (argv.length !== 3) {
+        console.error("Expected toolchain to check as argument (for example 'x86_64-apple-darwin'");
+        return 1;
+    }
+    var toolchain = argv[2];
+
+    var mainJs = readFile("build/" + toolchain + "/doc/main.js");
+    var searchIndex = readFile("build/" + toolchain + "/doc/search-index.js").split("\n");
+    if (searchIndex[searchIndex.length - 1].length === 0) {
+        searchIndex.pop();
+    }
+    searchIndex.pop();
+    searchIndex = loadContent(searchIndex.join("\n") + '\nexports.searchIndex = searchIndex;');
+    finalJS = "";
+
+    var arraysToLoad = ["itemTypes"];
+    var variablesToLoad = ["MAX_LEV_DISTANCE", "MAX_RESULTS", "TY_PRIMITIVE", "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.
+    var functionsToLoad = ["levenshtein", "validateResult", "getQuery", "buildIndex", "execQuery"];
+
+    finalJS += 'window = { "currentCrate": "std" };\n';
+    finalJS += loadThings(arraysToLoad, 'array', extractArrayVariable, mainJs);
+    finalJS += loadThings(variablesToLoad, 'variable', extractVariable, mainJs);
+    finalJS += loadThings(functionsToLoad, 'function', extractFunction, mainJs);
+
+    var loaded = loadContent(finalJS);
+    var index = loaded.buildIndex(searchIndex.searchIndex);
+
+    var errors = 0;
+
+    fs.readdirSync(TEST_FOLDER).forEach(function(file) {
+        var loadedFile = loadContent(readFile(TEST_FOLDER + file) +
+                               'exports.QUERY = QUERY;exports.EXPECTED = EXPECTED;');
+        const expected = loadedFile.EXPECTED;
+        const query = loadedFile.QUERY;
+        var results = loaded.execQuery(loaded.getQuery(query), index);
+        process.stdout.write('Checking "' + file + '" ... ');
+        var error_text = [];
+        for (var key in expected) {
+            if (!expected.hasOwnProperty(key)) {
+                continue;
+            }
+            if (!results.hasOwnProperty(key)) {
+                error_text.push('==> Unknown key "' + key + '"');
+                break;
+            }
+            var entry = expected[key];
+            var found = false;
+            for (var i = 0; i < entry.length; ++i) {
+                if (lookForEntry(entry[i], results[key]) === true) {
+                    found = true;
+                } else {
+                    error_text.push("==> Result not found in '" + key + "': '" +
+                                    JSON.stringify(entry[i]) + "'");
+                }
+            }
+        }
+        if (error_text.length !== 0) {
+            errors += 1;
+            console.error("FAILED");
+            console.error(error_text.join("\n"));
+        } else {
+            console.log("OK");
+        }
+    });
+    return errors;
+}
+
+process.exit(main(process.argv));