about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustdoc/config.rs4
-rw-r--r--src/librustdoc/html/render/mod.rs53
-rw-r--r--src/librustdoc/lib.rs7
-rw-r--r--src/test/run-make-fulldeps/rustdoc-map-file/Makefile5
-rw-r--r--src/test/run-make-fulldeps/rustdoc-map-file/expected.json5
-rw-r--r--src/test/run-make-fulldeps/rustdoc-map-file/foo.rs16
-rwxr-xr-xsrc/test/run-make-fulldeps/rustdoc-map-file/validate_json.py41
-rw-r--r--src/test/rustdoc/redirect-map-empty.rs6
-rw-r--r--src/test/rustdoc/redirect-map.rs23
9 files changed, 150 insertions, 10 deletions
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index d9f5b5bfa3a..de6942968ea 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -263,6 +263,8 @@ crate struct RenderOptions {
     crate document_private: bool,
     /// Document items that have `doc(hidden)`.
     crate document_hidden: bool,
+    /// If `true`, generate a JSON file in the crate folder instead of HTML redirection files.
+    crate generate_redirect_map: bool,
     crate unstable_features: rustc_feature::UnstableFeatures,
 }
 
@@ -570,6 +572,7 @@ impl Options {
         let document_private = matches.opt_present("document-private-items");
         let document_hidden = matches.opt_present("document-hidden-items");
         let run_check = matches.opt_present("check");
+        let generate_redirect_map = matches.opt_present("generate-redirect-map");
 
         let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format);
 
@@ -627,6 +630,7 @@ impl Options {
                 generate_search_filter,
                 document_private,
                 document_hidden,
+                generate_redirect_map,
                 unstable_features: rustc_feature::UnstableFeatures::from_environment(
                     crate_name.as_deref(),
                 ),
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 4e762a40f08..394c57c7214 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -111,6 +111,10 @@ crate struct Context<'tcx> {
     /// real location of an item. This is used to allow external links to
     /// publicly reused items to redirect to the right location.
     crate render_redirect_pages: bool,
+    /// `None` by default, depends on the `generate-redirect-map` option flag. If this field is set
+    /// to `Some(...)`, it'll store redirections and then generate a JSON file at the top level of
+    /// the crate.
+    crate redirections: Option<Rc<RefCell<FxHashMap<String, String>>>>,
     /// The map used to ensure all generated 'id=' attributes are unique.
     id_map: Rc<RefCell<IdMap>>,
     /// Tracks section IDs for `Deref` targets so they match in both the main
@@ -404,6 +408,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             static_root_path,
             generate_search_filter,
             unstable_features,
+            generate_redirect_map,
             ..
         } = options;
 
@@ -509,6 +514,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             all: Rc::new(RefCell::new(AllTypes::new())),
             errors: Rc::new(receiver),
             cache: Rc::new(cache),
+            redirections: if generate_redirect_map { Some(Default::default()) } else { None },
         };
 
         CURRENT_DEPTH.with(|s| s.set(0));
@@ -587,6 +593,15 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             &style_files,
         );
         self.shared.fs.write(&settings_file, v.as_bytes())?;
+        if let Some(redirections) = self.redirections.take() {
+            if !redirections.borrow().is_empty() {
+                let redirect_map_path =
+                    self.dst.join(&*krate.name.as_str()).join("redirect-map.json");
+                let paths = serde_json::to_string(&*redirections.borrow()).unwrap();
+                self.shared.ensure_dir(&self.dst.join(&*krate.name.as_str()))?;
+                self.shared.fs.write(&redirect_map_path, paths.as_bytes())?;
+            }
+        }
 
         // Flush pending errors.
         Arc::get_mut(&mut self.shared).unwrap().fs.close();
@@ -675,9 +690,17 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             // to the new one (without).
             if item_type == ItemType::Macro {
                 let redir_name = format!("{}.{}!.html", item_type, name);
-                let redir_dst = self.dst.join(redir_name);
-                let v = layout::redirect(file_name);
-                self.shared.fs.write(&redir_dst, v.as_bytes())?;
+                if let Some(ref redirections) = self.redirections {
+                    let crate_name = &self.shared.layout.krate;
+                    redirections.borrow_mut().insert(
+                        format!("{}/{}", crate_name, redir_name),
+                        format!("{}/{}", crate_name, file_name),
+                    );
+                } else {
+                    let v = layout::redirect(file_name);
+                    let redir_dst = self.dst.join(redir_name);
+                    self.shared.fs.write(&redir_dst, v.as_bytes())?;
+                }
             }
         }
         Ok(())
@@ -1588,17 +1611,27 @@ impl Context<'_> {
                 &self.shared.style_files,
             )
         } else {
-            let mut url = self.root_path();
             if let Some(&(ref names, ty)) = self.cache.paths.get(&it.def_id) {
+                let mut path = String::new();
                 for name in &names[..names.len() - 1] {
-                    url.push_str(name);
-                    url.push('/');
+                    path.push_str(name);
+                    path.push('/');
+                }
+                path.push_str(&item_path(ty, names.last().unwrap()));
+                match self.redirections {
+                    Some(ref redirections) => {
+                        let mut current_path = String::new();
+                        for name in &self.current {
+                            current_path.push_str(name);
+                            current_path.push('/');
+                        }
+                        current_path.push_str(&item_path(ty, names.last().unwrap()));
+                        redirections.borrow_mut().insert(current_path, path);
+                    }
+                    None => return layout::redirect(&format!("{}{}", self.root_path(), path)),
                 }
-                url.push_str(&item_path(ty, names.last().unwrap()));
-                layout::redirect(&url)
-            } else {
-                String::new()
             }
+            String::new()
         }
     }
 
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 6b37643a395..c0e91a05dff 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -497,6 +497,13 @@ fn opts() -> Vec<RustcOptGroup> {
             o.optopt("", "test-builder", "The rustc-like binary to use as the test builder", "PATH")
         }),
         unstable("check", |o| o.optflag("", "check", "Run rustdoc checks")),
+        unstable("generate-redirect-map", |o| {
+            o.optflag(
+                "",
+                "generate-redirect-map",
+                "Generate JSON file at the top level instead of generating HTML redirection files",
+            )
+        }),
     ]
 }
 
diff --git a/src/test/run-make-fulldeps/rustdoc-map-file/Makefile b/src/test/run-make-fulldeps/rustdoc-map-file/Makefile
new file mode 100644
index 00000000000..ce977fa0cea
--- /dev/null
+++ b/src/test/run-make-fulldeps/rustdoc-map-file/Makefile
@@ -0,0 +1,5 @@
+-include ../tools.mk
+
+all:
+	$(RUSTDOC) -Z unstable-options --generate-redirect-map foo.rs -o "$(TMPDIR)/out"
+	"$(PYTHON)" validate_json.py "$(TMPDIR)/out"
diff --git a/src/test/run-make-fulldeps/rustdoc-map-file/expected.json b/src/test/run-make-fulldeps/rustdoc-map-file/expected.json
new file mode 100644
index 00000000000..6b1ccbeac30
--- /dev/null
+++ b/src/test/run-make-fulldeps/rustdoc-map-file/expected.json
@@ -0,0 +1,5 @@
+{
+  "foo/macro.foo!.html": "foo/macro.foo.html",
+  "foo/private/struct.Quz.html": "foo/struct.Quz.html",
+  "foo/hidden/struct.Bar.html": "foo/struct.Bar.html"
+}
diff --git a/src/test/run-make-fulldeps/rustdoc-map-file/foo.rs b/src/test/run-make-fulldeps/rustdoc-map-file/foo.rs
new file mode 100644
index 00000000000..e12b9d2292c
--- /dev/null
+++ b/src/test/run-make-fulldeps/rustdoc-map-file/foo.rs
@@ -0,0 +1,16 @@
+pub use private::Quz;
+pub use hidden::Bar;
+
+mod private {
+    pub struct Quz;
+}
+
+#[doc(hidden)]
+pub mod hidden {
+    pub struct Bar;
+}
+
+#[macro_export]
+macro_rules! foo {
+    () => {}
+}
diff --git a/src/test/run-make-fulldeps/rustdoc-map-file/validate_json.py b/src/test/run-make-fulldeps/rustdoc-map-file/validate_json.py
new file mode 100755
index 00000000000..5c14c90b70d
--- /dev/null
+++ b/src/test/run-make-fulldeps/rustdoc-map-file/validate_json.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import json
+
+
+def find_redirect_map_file(folder, errors):
+    for root, dirs, files in os.walk(folder):
+        for name in files:
+            if not name.endswith("redirect-map.json"):
+                continue
+            with open(os.path.join(root, name)) as f:
+                data = json.load(f)
+            with open("expected.json") as f:
+                expected = json.load(f)
+            for key in expected:
+                if expected[key] != data.get(key):
+                    errors.append("Expected `{}` for key `{}`, found: `{}`".format(
+                        expected[key], key, data.get(key)))
+                else:
+                    del data[key]
+            for key in data:
+                errors.append("Extra data not expected: key: `{}`, data: `{}`".format(
+                    key, data[key]))
+            return True
+    return False
+
+
+if len(sys.argv) != 2:
+    print("Expected doc directory to check!")
+    sys.exit(1)
+
+errors = []
+if not find_redirect_map_file(sys.argv[1], errors):
+    print("Didn't find the map file in `{}`...".format(sys.argv[1]))
+    sys.exit(1)
+for err in errors:
+    print("=> {}".format(err))
+if len(errors) != 0:
+    sys.exit(1)
diff --git a/src/test/rustdoc/redirect-map-empty.rs b/src/test/rustdoc/redirect-map-empty.rs
new file mode 100644
index 00000000000..e9d021e0fa8
--- /dev/null
+++ b/src/test/rustdoc/redirect-map-empty.rs
@@ -0,0 +1,6 @@
+// compile-flags: -Z unstable-options --generate-redirect-map
+
+#![crate_name = "foo"]
+
+// @!has foo/redirect-map.json
+pub struct Foo;
diff --git a/src/test/rustdoc/redirect-map.rs b/src/test/rustdoc/redirect-map.rs
new file mode 100644
index 00000000000..b7f16b64e38
--- /dev/null
+++ b/src/test/rustdoc/redirect-map.rs
@@ -0,0 +1,23 @@
+// compile-flags: -Z unstable-options --generate-redirect-map
+
+#![crate_name = "foo"]
+
+// @!has foo/private/struct.Quz.html
+// @!has foo/hidden/struct.Bar.html
+// @has foo/redirect-map.json
+pub use private::Quz;
+pub use hidden::Bar;
+
+mod private {
+    pub struct Quz;
+}
+
+#[doc(hidden)]
+pub mod hidden {
+    pub struct Bar;
+}
+
+#[macro_export]
+macro_rules! foo {
+  () => {}
+}