about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorDylan DPC <99973273+Dylan-DPC@users.noreply.github.com>2022-04-13 17:35:32 +0200
committerGitHub <noreply@github.com>2022-04-13 17:35:32 +0200
commitdb61452b7a2ec92d90f4faebfa679c54ad3e1ab0 (patch)
tree2b1f856f0aaba53b6c53b69669f807d523c8eb66 /src
parentf38c5c8e5d76ea9a87fece143425ef7c703c706a (diff)
parent6a18b6865590704d42777fa7432436e55773d46b (diff)
downloadrust-db61452b7a2ec92d90f4faebfa679c54ad3e1ab0.tar.gz
rust-db61452b7a2ec92d90f4faebfa679c54ad3e1ab0.zip
Rollup merge of #93217 - willcrichton:example-analyzer, r=GuillaumeGomez
Improve Rustdoc UI for scraped examples with multiline arguments, fix overflow in line numbers

This PR improves a few aspects of the scrape examples feature in Rustdoc.
* Only function names and not the full call expression are highlighted.
* For call-sites with multiline arguments, the minimized code viewer will scroll to the top of the call-site rather than the middle if the argument is larger than the viewer size, ensuring that the function name is visible.
* This fixes an issue where the line numbers column had a visible x-scroll bar.

r? `@GuillaumeGomez`
Diffstat (limited to 'src')
-rw-r--r--src/doc/rustdoc/src/SUMMARY.md1
-rw-r--r--src/doc/rustdoc/src/scraped-examples.md55
-rw-r--r--src/librustdoc/html/render/context.rs19
-rw-r--r--src/librustdoc/html/render/mod.rs36
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css81
-rw-r--r--src/librustdoc/html/static/css/themes/ayu.css16
-rw-r--r--src/librustdoc/html/static/css/themes/dark.css15
-rw-r--r--src/librustdoc/html/static/css/themes/light.css30
-rw-r--r--src/librustdoc/html/static/js/scrape-examples.js32
-rw-r--r--src/librustdoc/html/static/scrape-examples-help.md34
-rw-r--r--src/librustdoc/html/static_files.rs2
-rw-r--r--src/librustdoc/scrape_examples.rs104
-rw-r--r--src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs5
13 files changed, 342 insertions, 88 deletions
diff --git a/src/doc/rustdoc/src/SUMMARY.md b/src/doc/rustdoc/src/SUMMARY.md
index d627f5b0389..747cc629ba4 100644
--- a/src/doc/rustdoc/src/SUMMARY.md
+++ b/src/doc/rustdoc/src/SUMMARY.md
@@ -9,6 +9,7 @@
     - [Linking to items by name](write-documentation/linking-to-items-by-name.md)
     - [Documentation tests](write-documentation/documentation-tests.md)
 - [Rustdoc-specific lints](lints.md)
+- [Scraped examples](scraped-examples.md)
 - [Advanced features](advanced-features.md)
 - [Unstable features](unstable-features.md)
 - [Deprecated features](deprecated-features.md)
diff --git a/src/doc/rustdoc/src/scraped-examples.md b/src/doc/rustdoc/src/scraped-examples.md
new file mode 100644
index 00000000000..d75f6d522e8
--- /dev/null
+++ b/src/doc/rustdoc/src/scraped-examples.md
@@ -0,0 +1,55 @@
+# Scraped examples
+
+Rustdoc has an unstable feature where it can automatically scrape examples of items being documented from the `examples/` directory of a Cargo workspace. These examples will be included within the generated documentation for that item. For example, if your library contains a public function:
+
+```rust,ignore (needs-other-file)
+// a_crate/src/lib.rs
+pub fn a_func() {}
+```
+
+And you have an example calling this function:
+
+```rust,ignore (needs-other-file)
+// a_crate/examples/ex.rs
+fn main() {
+  a_crate::a_func();
+}
+```
+
+Then this code snippet will be included in the documentation for `a_func`. This documentation is inserted by Rustdoc and cannot be manually edited by the crate author.
+
+
+## How to use this feature
+
+This feature is unstable, so you can enable it by calling Rustdoc with the unstable `rustdoc-scrape-examples` flag:
+
+```bash
+cargo doc -Zunstable-options -Zrustdoc-scrape-examples=examples
+```
+
+To enable this feature on [docs.rs](https://docs.rs), add this to your Cargo.toml:
+
+```toml
+[package.metadata.docs.rs]
+cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples=examples"]
+```
+
+
+## How it works
+
+When you run `cargo doc`, Rustdoc will analyze all the crates that match Cargo's `--examples` filter for instances of items being documented. Then Rustdoc will include the source code of these instances in the generated documentation.
+
+Rustdoc has a few techniques to ensure these examples don't overwhelm documentation readers, and that it doesn't blow up the page size:
+
+1. For a given item, a maximum of 5 examples are included in the page. The remaining examples are just links to source code.
+2. Only one example is shown by default, and the remaining examples are hidden behind a toggle.
+3. For a given file that contains examples, only the item containing the examples will be included in the generated documentation.
+
+For a given item, Rustdoc sorts its examples based on the size of the example &mdash; smaller ones are shown first.
+
+
+## FAQ
+
+### My example is not showing up in the documentation
+
+This feature uses Cargo's convention for finding examples. You should ensure that `cargo check --examples` includes your example file.
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 34784bbed0c..90123655758 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -17,8 +17,8 @@ use super::print_item::{full_path, item_path, print_item};
 use super::search_index::build_index;
 use super::write_shared::write_shared;
 use super::{
-    collect_spans_and_sources, print_sidebar, settings, AllTypes, LinkFromSrc, NameDoc, StylePath,
-    BASIC_KEYWORDS,
+    collect_spans_and_sources, print_sidebar, scrape_examples_help, settings, AllTypes,
+    LinkFromSrc, NameDoc, StylePath, BASIC_KEYWORDS,
 };
 
 use crate::clean::{self, types::ExternalLocation, ExternalCrate};
@@ -551,6 +551,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
         let crate_name = self.tcx().crate_name(LOCAL_CRATE);
         let final_file = self.dst.join(crate_name.as_str()).join("all.html");
         let settings_file = self.dst.join("settings.html");
+        let scrape_examples_help_file = self.dst.join("scrape-examples-help.html");
 
         let mut root_path = self.dst.to_str().expect("invalid path").to_owned();
         if !root_path.ends_with('/') {
@@ -606,6 +607,20 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             &self.shared.style_files,
         );
         self.shared.fs.write(settings_file, v)?;
+
+        if self.shared.layout.scrape_examples_extension {
+            page.title = "About scraped examples";
+            page.description = "How the scraped examples feature works in Rustdoc";
+            let v = layout::render(
+                &self.shared.layout,
+                &page,
+                "",
+                scrape_examples_help(&*self.shared),
+                &self.shared.style_files,
+            );
+            self.shared.fs.write(scrape_examples_help_file, v)?;
+        }
+
         if let Some(ref redirections) = self.shared.redirections {
             if !redirections.borrow().is_empty() {
                 let redirect_map_path =
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 9891c4b676f..a4cc42e2a01 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -75,8 +75,10 @@ use crate::html::format::{
 use crate::html::highlight;
 use crate::html::markdown::{HeadingOffset, IdMap, Markdown, MarkdownHtml, MarkdownSummaryLine};
 use crate::html::sources;
+use crate::html::static_files::SCRAPE_EXAMPLES_HELP_MD;
 use crate::scrape_examples::{CallData, CallLocation};
 use crate::try_none;
+use crate::DOC_RUST_LANG_ORG_CHANNEL;
 
 /// A pair of name and its optional document.
 crate type NameDoc = (String, Option<String>);
@@ -460,6 +462,34 @@ fn settings(root_path: &str, suffix: &str, theme_names: Vec<String>) -> Result<S
     ))
 }
 
+fn scrape_examples_help(shared: &SharedContext<'_>) -> String {
+    let mut content = SCRAPE_EXAMPLES_HELP_MD.to_owned();
+    content.push_str(&format!(
+      "## More information\n\n\
+      If you want more information about this feature, please read the [corresponding chapter in the Rustdoc book]({}/rustdoc/scraped-examples.html).",
+      DOC_RUST_LANG_ORG_CHANNEL));
+
+    let mut ids = IdMap::default();
+    format!(
+        "<div class=\"main-heading\">\
+            <h1 class=\"fqn\">\
+                <span class=\"in-band\">About scraped examples</span>\
+            </h1>\
+        </div>\
+        <div>{}</div>",
+        Markdown {
+            content: &content,
+            links: &[],
+            ids: &mut ids,
+            error_codes: shared.codes,
+            edition: shared.edition(),
+            playground: &shared.playground,
+            heading_offset: HeadingOffset::H1
+        }
+        .into_string()
+    )
+}
+
 fn document(
     w: &mut Buffer,
     cx: &Context<'_>,
@@ -2743,7 +2773,9 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) {
           <span></span>\
           <h5 id=\"{id}\">\
              <a href=\"#{id}\">Examples found in repository</a>\
+             <a class=\"scrape-help\" href=\"{root_path}scrape-examples-help.html\">?</a>\
           </h5>",
+        root_path = cx.root_path(),
         id = id
     );
 
@@ -2795,9 +2827,10 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) {
             .locations
             .iter()
             .map(|loc| {
-                let (byte_lo, byte_hi) = loc.call_expr.byte_span;
+                let (byte_lo, byte_hi) = loc.call_ident.byte_span;
                 let (line_lo, line_hi) = loc.call_expr.line_span;
                 let byte_range = (byte_lo - byte_min, byte_hi - byte_min);
+
                 let line_range = (line_lo - line_min, line_hi - line_min);
                 let (line_url, line_title) = link_to_loc(call_data, loc);
 
@@ -2913,6 +2946,7 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) {
                   <summary class=\"hideme\">\
                      <span>More examples</span>\
                   </summary>\
+                  <div class=\"hide-more\">Hide additional examples</div>\
                   <div class=\"more-scraped-examples\">\
                     <div class=\"toggle-line\"><div class=\"toggle-line-inner\"></div></div>\
                     <div class=\"more-scraped-examples-inner\">"
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 68c88b551ca..48cb0a46ad6 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -618,7 +618,7 @@ h2.location a {
 	position: relative;
 }
 
-.docblock > :not(.information) {
+.docblock > :not(.information):not(.more-examples-toggle) {
 	max-width: 100%;
 	overflow-x: auto;
 }
@@ -840,8 +840,8 @@ h2.small-section-header > .anchor {
 	content: 'ยง';
 }
 
-.docblock a:not(.srclink):not(.test-arrow):hover,
-.docblock-short a:not(.srclink):not(.test-arrow):hover, .item-info a {
+.docblock a:not(.srclink):not(.test-arrow):not(.scrape-help):hover,
+.docblock-short a:not(.srclink):not(.test-arrow):not(.scrape-help):hover, .item-info a {
 	text-decoration: underline;
 }
 
@@ -2038,21 +2038,45 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 
 /* Begin: styles for --scrape-examples feature */
 
+.scraped-example-list .scrape-help {
+	margin-left: 10px;
+	padding: 0 4px;
+	font-weight: normal;
+	font-size: 12px;
+	position: relative;
+	bottom: 1px;
+	background: transparent;
+	border-width: 1px;
+	border-style: solid;
+	border-radius: 50px;
+}
+
 .scraped-example-title {
 	font-family: 'Fira Sans';
 }
 
-.scraped-example:not(.expanded) .code-wrapper pre.line-numbers {
-	overflow: hidden;
+.scraped-example .code-wrapper {
+	position: relative;
+	display: flex;
+	flex-direction: row;
+	flex-wrap: wrap;
+	width: 100%;
+}
+
+.scraped-example:not(.expanded) .code-wrapper {
 	max-height: 240px;
 }
 
-.scraped-example:not(.expanded) .code-wrapper .example-wrap pre.rust {
+.scraped-example:not(.expanded) .code-wrapper pre {
 	overflow-y: hidden;
 	max-height: 240px;
 	padding-bottom: 0;
 }
 
+.scraped-example:not(.expanded) .code-wrapper pre.line-numbers {
+	overflow-x: hidden;
+}
+
 .scraped-example .code-wrapper .prev {
 	position: absolute;
 	top: 0.25em;
@@ -2077,14 +2101,6 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 	cursor: pointer;
 }
 
-.scraped-example .code-wrapper {
-	position: relative;
-	display: flex;
-	flex-direction: row;
-	flex-wrap: wrap;
-	width: 100%;
-}
-
 .scraped-example:not(.expanded) .code-wrapper:before {
 	content: " ";
 	width: 100%;
@@ -2092,7 +2108,6 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 	position: absolute;
 	z-index: 100;
 	top: 0;
-	background: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
 }
 
 .scraped-example:not(.expanded) .code-wrapper:after {
@@ -2102,12 +2117,6 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 	position: absolute;
 	z-index: 100;
 	bottom: 0;
-	background: linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
-}
-
-.scraped-example:not(.expanded) .code-wrapper {
-	overflow: hidden;
-	max-height: 240px;
 }
 
 .scraped-example .code-wrapper .line-numbers {
@@ -2126,34 +2135,37 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 	margin-bottom: 0;
 }
 
+.scraped-example:not(.expanded) .code-wrapper .example-wrap {
+	overflow-x: hidden;
+}
+
 .scraped-example .code-wrapper .example-wrap pre.rust {
 	overflow-x: inherit;
 	width: inherit;
 	overflow-y: hidden;
 }
 
-.scraped-example .example-wrap .rust span.highlight {
-	background: #fcffd6;
-}
-
-.scraped-example .example-wrap .rust span.highlight.focus {
-	background: #f6fdb0;
-}
 
 .more-examples-toggle {
+	max-width: calc(100% + 25px);
 	margin-top: 10px;
+	margin-left: -25px;
+}
+
+.more-examples-toggle .hide-more {
+	margin-left: 25px;
+	margin-bottom: 5px;
+	cursor: pointer;
 }
 
-.more-examples-toggle summary {
-	color: #999;
+.more-examples-toggle summary, .more-examples-toggle .hide-more {
 	font-family: 'Fira Sans';
 }
 
 .more-scraped-examples {
-	margin-left: 25px;
+	margin-left: 5px;
 	display: flex;
 	flex-direction: row;
-	width: calc(100% - 25px);
 }
 
 .more-scraped-examples-inner {
@@ -2169,13 +2181,8 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 	cursor: pointer;
 }
 
-.toggle-line:hover .toggle-line-inner {
-	background: #aaa;
-}
-
 .toggle-line-inner {
 	min-width: 2px;
-	background: #ddd;
 	height: 100%;
 }
 
diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css
index e402b3583f3..b1bf06c1865 100644
--- a/src/librustdoc/html/static/css/themes/ayu.css
+++ b/src/librustdoc/html/static/css/themes/ayu.css
@@ -611,6 +611,18 @@ input:checked + .slider {
 	background-color: #ffb454 !important;
 }
 
+
+.scraped-example-list .scrape-help {
+	border-color: #aaa;
+	color: #eee;
+}
+.scraped-example-list .scrape-help:hover {
+	border-color: white;
+	color: white;
+}
+.more-examples-toggle summary, .more-examples-toggle .hide-more {
+	color: #999;
+}
 .scraped-example .example-wrap .rust span.highlight {
 	background: rgb(91, 59, 1);
 }
@@ -624,8 +636,8 @@ input:checked + .slider {
 	background: linear-gradient(to top, rgba(15, 20, 25, 1), rgba(15, 20, 25, 0));
 }
 .toggle-line-inner {
-	background: #616161;
+	background: #999;
 }
 .toggle-line:hover .toggle-line-inner {
-	background: #898989;
+	background: #c5c5c5;
 }
diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css
index 0a56055b8cb..236304ccc9f 100644
--- a/src/librustdoc/html/static/css/themes/dark.css
+++ b/src/librustdoc/html/static/css/themes/dark.css
@@ -478,6 +478,17 @@ div.files > .selected {
 	border-bottom-color: #ddd;
 }
 
+.scraped-example-list .scrape-help {
+	border-color: #aaa;
+	color: #eee;
+}
+.scraped-example-list .scrape-help:hover {
+	border-color: white;
+	color: white;
+}
+.more-examples-toggle summary, .more-examples-toggle .hide-more {
+	color: #999;
+}
 .scraped-example .example-wrap .rust span.highlight {
 	background: rgb(91, 59, 1);
 }
@@ -491,8 +502,8 @@ div.files > .selected {
 	background: linear-gradient(to top, rgba(53, 53, 53, 1), rgba(53, 53, 53, 0));
 }
 .toggle-line-inner {
-	background: #616161;
+	background: #999;
 }
 .toggle-line:hover .toggle-line-inner {
-	background: #898989;
+	background: #c5c5c5;
 }
diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css
index dc1715b2a78..c923902aba2 100644
--- a/src/librustdoc/html/static/css/themes/light.css
+++ b/src/librustdoc/html/static/css/themes/light.css
@@ -462,3 +462,33 @@ div.files > .selected {
 .setting-line > .title {
 	border-bottom-color: #D5D5D5;
 }
+
+.scraped-example-list .scrape-help {
+	border-color: #555;
+	color: #333;
+}
+.scraped-example-list .scrape-help:hover {
+	border-color: black;
+	color: black;
+}
+.more-examples-toggle summary, .more-examples-toggle .hide-more {
+	color: #999;
+}
+.scraped-example .example-wrap .rust span.highlight {
+	background: #fcffd6;
+}
+.scraped-example .example-wrap .rust span.highlight.focus {
+	background: #f6fdb0;
+}
+.scraped-example:not(.expanded) .code-wrapper:before {
+	background: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
+}
+.scraped-example:not(.expanded) .code-wrapper:after {
+	background: linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
+}
+.toggle-line-inner {
+	background: #ccc;
+}
+.toggle-line:hover .toggle-line-inner {
+	background: #999;
+}
diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js
index 664b187e33e..a28fb461729 100644
--- a/src/librustdoc/html/static/js/scrape-examples.js
+++ b/src/librustdoc/html/static/js/scrape-examples.js
@@ -1,14 +1,28 @@
 /* global addClass, hasClass, removeClass, onEach */
 
 (function () {
-    // Scroll code block to put the given code location in the middle of the viewer
+    // Number of lines shown when code viewer is not expanded
+    const MAX_LINES = 10;
+
+    // Scroll code block to the given code location
     function scrollToLoc(elt, loc) {
-        var wrapper = elt.querySelector(".code-wrapper");
-        var halfHeight = wrapper.offsetHeight / 2;
         var lines = elt.querySelector('.line-numbers');
-        var offsetMid = (lines.children[loc[0]].offsetTop
-                         + lines.children[loc[1]].offsetTop) / 2;
-        var scrollOffset = offsetMid - halfHeight;
+        var scrollOffset;
+
+        // If the block is greater than the size of the viewer,
+        // then scroll to the top of the block. Otherwise scroll
+        // to the middle of the block.
+        if (loc[1] - loc[0] > MAX_LINES) {
+            var line = Math.max(0, loc[0] - 1);
+            scrollOffset = lines.children[line].offsetTop;
+        } else {
+            var wrapper = elt.querySelector(".code-wrapper");
+            var halfHeight = wrapper.offsetHeight / 2;
+            var offsetMid = (lines.children[loc[0]].offsetTop
+                             + lines.children[loc[1]].offsetTop) / 2;
+            scrollOffset = offsetMid - halfHeight;
+        }
+
         lines.scrollTo(0, scrollOffset);
         elt.querySelector(".rust").scrollTo(0, scrollOffset);
     }
@@ -70,8 +84,10 @@
     onEach(document.querySelectorAll('.more-examples-toggle'), function(toggle) {
         // Allow users to click the left border of the <details> section to close it,
         // since the section can be large and finding the [+] button is annoying.
-        toggle.querySelector('.toggle-line').addEventListener('click', function() {
-            toggle.open = false;
+        toggle.querySelectorAll('.toggle-line, .hide-more').forEach(button => {
+            button.addEventListener('click', function() {
+                toggle.open = false;
+            });
         });
 
         var moreExamples = toggle.querySelectorAll('.scraped-example');
diff --git a/src/librustdoc/html/static/scrape-examples-help.md b/src/librustdoc/html/static/scrape-examples-help.md
new file mode 100644
index 00000000000..035b2e18b00
--- /dev/null
+++ b/src/librustdoc/html/static/scrape-examples-help.md
@@ -0,0 +1,34 @@
+Rustdoc will automatically scrape examples of documented items from the `examples/` directory of a project. These examples will be included within the generated documentation for that item. For example, if your library contains a public function:
+
+```rust
+// src/lib.rs
+pub fn a_func() {}
+```
+
+And you have an example calling this function:
+
+```rust
+// examples/ex.rs
+fn main() {
+  a_crate::a_func();
+}
+```
+
+Then this code snippet will be included in the documentation for `a_func`.
+
+## How to read scraped examples
+
+Scraped examples are shown as blocks of code from a given file. The relevant item will be highlighted. If the file is larger than a couple lines, only a small window will be shown which you can expand by clicking &varr; in the top-right. If a file contains multiple instances of an item, you can use the &pr; and &sc; buttons to toggle through each instance.
+
+If there is more than one file that contains examples, then you should click "More examples" to see these examples.
+
+
+## How Rustdoc scrapes examples
+
+When you run `cargo doc`, Rustdoc will analyze all the crates that match Cargo's `--examples` filter for instances of items that occur in the crates being documented. Then Rustdoc will include the source code of these instances in the generated documentation.
+
+Rustdoc has a few techniques to ensure this doesn't overwhelm documentation readers, and that it doesn't blow up the page size:
+
+1. For a given item, a maximum of 5 examples are included in the page. The remaining examples are just links to source code.
+2. Only one example is shown by default, and the remaining examples are hidden behind a toggle.
+3. For a given file that contains examples, only the item containing the examples will be included in the generated documentation.
diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs
index cd369a93d82..1837e4a3b65 100644
--- a/src/librustdoc/html/static_files.rs
+++ b/src/librustdoc/html/static_files.rs
@@ -39,6 +39,8 @@ crate static STORAGE_JS: &str = include_str!("static/js/storage.js");
 /// --scrape-examples flag that inserts automatically-found examples of usages of items.
 crate static SCRAPE_EXAMPLES_JS: &str = include_str!("static/js/scrape-examples.js");
 
+crate static SCRAPE_EXAMPLES_HELP_MD: &str = include_str!("static/scrape-examples-help.md");
+
 /// The file contents of `brush.svg`, the icon used for the theme-switch button.
 crate static BRUSH_SVG: &[u8] = include_bytes!("static/images/brush.svg");
 
diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs
index 7cf0ea9e84e..0da490f3cd6 100644
--- a/src/librustdoc/scrape_examples.rs
+++ b/src/librustdoc/scrape_examples.rs
@@ -71,33 +71,36 @@ crate struct SyntaxRange {
 }
 
 impl SyntaxRange {
-    fn new(span: rustc_span::Span, file: &SourceFile) -> Self {
+    fn new(span: rustc_span::Span, file: &SourceFile) -> Option<Self> {
         let get_pos = |bytepos: BytePos| file.original_relative_byte_pos(bytepos).0;
-        let get_line = |bytepos: BytePos| file.lookup_line(bytepos).unwrap();
+        let get_line = |bytepos: BytePos| file.lookup_line(bytepos);
 
-        SyntaxRange {
+        Some(SyntaxRange {
             byte_span: (get_pos(span.lo()), get_pos(span.hi())),
-            line_span: (get_line(span.lo()), get_line(span.hi())),
-        }
+            line_span: (get_line(span.lo())?, get_line(span.hi())?),
+        })
     }
 }
 
 #[derive(Encodable, Decodable, Debug, Clone)]
 crate struct CallLocation {
     crate call_expr: SyntaxRange,
+    crate call_ident: SyntaxRange,
     crate enclosing_item: SyntaxRange,
 }
 
 impl CallLocation {
     fn new(
         expr_span: rustc_span::Span,
+        ident_span: rustc_span::Span,
         enclosing_item_span: rustc_span::Span,
         source_file: &SourceFile,
-    ) -> Self {
-        CallLocation {
-            call_expr: SyntaxRange::new(expr_span, source_file),
-            enclosing_item: SyntaxRange::new(enclosing_item_span, source_file),
-        }
+    ) -> Option<Self> {
+        Some(CallLocation {
+            call_expr: SyntaxRange::new(expr_span, source_file)?,
+            call_ident: SyntaxRange::new(ident_span, source_file)?,
+            enclosing_item: SyntaxRange::new(enclosing_item_span, source_file)?,
+        })
     }
 }
 
@@ -146,24 +149,26 @@ where
         }
 
         // Get type of function if expression is a function call
-        let (ty, span) = match ex.kind {
+        let (ty, call_span, ident_span) = match ex.kind {
             hir::ExprKind::Call(f, _) => {
                 let types = tcx.typeck(ex.hir_id.owner);
 
                 if let Some(ty) = types.node_type_opt(f.hir_id) {
-                    (ty, ex.span)
+                    (ty, ex.span, f.span)
                 } else {
                     trace!("node_type_opt({}) = None", f.hir_id);
                     return;
                 }
             }
-            hir::ExprKind::MethodCall(_, _, span) => {
+            hir::ExprKind::MethodCall(path, _, call_span) => {
                 let types = tcx.typeck(ex.hir_id.owner);
                 let Some(def_id) = types.type_dependent_def_id(ex.hir_id) else {
                     trace!("type_dependent_def_id({}) = None", ex.hir_id);
                     return;
                 };
-                (tcx.type_of(def_id), span)
+
+                let ident_span = path.ident.span;
+                (tcx.type_of(def_id), call_span, ident_span)
             }
             _ => {
                 return;
@@ -172,8 +177,8 @@ where
 
         // If this span comes from a macro expansion, then the source code may not actually show
         // a use of the given item, so it would be a poor example. Hence, we skip all uses in macros.
-        if span.from_expansion() {
-            trace!("Rejecting expr from macro: {:?}", span);
+        if call_span.from_expansion() {
+            trace!("Rejecting expr from macro: {call_span:?}");
             return;
         }
 
@@ -183,49 +188,82 @@ where
             .hir()
             .span_with_body(tcx.hir().local_def_id_to_hir_id(tcx.hir().get_parent_item(ex.hir_id)));
         if enclosing_item_span.from_expansion() {
-            trace!("Rejecting expr ({:?}) from macro item: {:?}", span, enclosing_item_span);
+            trace!("Rejecting expr ({call_span:?}) from macro item: {enclosing_item_span:?}");
+            return;
+        }
+
+        // If the enclosing item doesn't actually enclose the call, this means we probably have a weird
+        // macro issue even though the spans aren't tagged as being from an expansion.
+        if !enclosing_item_span.contains(call_span) {
+            warn!(
+                "Attempted to scrape call at [{call_span:?}] whose enclosing item [{enclosing_item_span:?}] doesn't contain the span of the call."
+            );
             return;
         }
 
-        assert!(
-            enclosing_item_span.contains(span),
-            "Attempted to scrape call at [{:?}] whose enclosing item [{:?}] doesn't contain the span of the call.",
-            span,
-            enclosing_item_span
-        );
+        // Similarly for the call w/ the function ident.
+        if !call_span.contains(ident_span) {
+            warn!(
+                "Attempted to scrape call at [{call_span:?}] whose identifier [{ident_span:?}] was not contained in the span of the call."
+            );
+            return;
+        }
 
         // Save call site if the function resolves to a concrete definition
         if let ty::FnDef(def_id, _) = ty.kind() {
             if self.target_crates.iter().all(|krate| *krate != def_id.krate) {
-                trace!("Rejecting expr from crate not being documented: {:?}", span);
+                trace!("Rejecting expr from crate not being documented: {call_span:?}");
                 return;
             }
 
             let source_map = tcx.sess.source_map();
-            let file = source_map.lookup_char_pos(span.lo()).file;
+            let file = source_map.lookup_char_pos(call_span.lo()).file;
             let file_path = match file.name.clone() {
                 FileName::Real(real_filename) => real_filename.into_local_path(),
                 _ => None,
             };
 
             if let Some(file_path) = file_path {
-                let abs_path = fs::canonicalize(file_path.clone()).unwrap();
+                let abs_path = match fs::canonicalize(file_path.clone()) {
+                    Ok(abs_path) => abs_path,
+                    Err(_) => {
+                        trace!("Could not canonicalize file path: {}", file_path.display());
+                        return;
+                    }
+                };
+
                 let cx = &self.cx;
+                let clean_span = crate::clean::types::Span::new(call_span);
+                let url = match cx.href_from_span(clean_span, false) {
+                    Some(url) => url,
+                    None => {
+                        trace!(
+                            "Rejecting expr ({call_span:?}) whose clean span ({clean_span:?}) cannot be turned into a link"
+                        );
+                        return;
+                    }
+                };
+
                 let mk_call_data = || {
-                    let clean_span = crate::clean::types::Span::new(span);
-                    let url = cx.href_from_span(clean_span, false).unwrap();
                     let display_name = file_path.display().to_string();
-                    let edition = span.edition();
+                    let edition = call_span.edition();
                     CallData { locations: Vec::new(), url, display_name, edition }
                 };
 
                 let fn_key = tcx.def_path_hash(*def_id);
                 let fn_entries = self.calls.entry(fn_key).or_default();
 
-                trace!("Including expr: {:?}", span);
+                trace!("Including expr: {:?}", call_span);
                 let enclosing_item_span =
                     source_map.span_extend_to_prev_char(enclosing_item_span, '\n', false);
-                let location = CallLocation::new(span, enclosing_item_span, &file);
+                let location =
+                    match CallLocation::new(call_span, ident_span, enclosing_item_span, &file) {
+                        Some(location) => location,
+                        None => {
+                            trace!("Could not get serializable call location for {call_span:?}");
+                            return;
+                        }
+                    };
                 fn_entries.entry(abs_path).or_insert_with(mk_call_data).locations.push(location);
             }
         }
@@ -259,8 +297,8 @@ crate fn run(
             .map(|(crate_num, _)| **crate_num)
             .collect::<Vec<_>>();
 
-        debug!("All crates in TyCtxt: {:?}", all_crates);
-        debug!("Scrape examples target_crates: {:?}", target_crates);
+        debug!("All crates in TyCtxt: {all_crates:?}");
+        debug!("Scrape examples target_crates: {target_crates:?}");
 
         // Run call-finder on all items
         let mut calls = FxHashMap::default();
diff --git a/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs b/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs
index 5afffffdf99..c53c987a7cb 100644
--- a/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs
+++ b/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs
@@ -1,7 +1,6 @@
 // @has foobar/fn.ok.html '//*[@class="docblock scraped-example-list"]' 'ex2'
 // @has foobar/fn.ok.html '//*[@class="more-scraped-examples"]' 'ex1'
-// @has foobar/fn.ok.html '//*[@class="highlight focus"]' '1'
-// @has foobar/fn.ok.html '//*[@class="highlight"]' '2'
-// @has foobar/fn.ok.html '//*[@class="highlight focus"]' '0'
+// @has foobar/fn.ok.html '//*[@class="highlight focus"]' 'ok'
+// @has foobar/fn.ok.html '//*[@class="highlight"]' 'ok'
 
 pub fn ok(_x: i32) {}