diff options
| -rw-r--r-- | src/librustdoc/config.rs | 8 | ||||
| -rw-r--r-- | src/librustdoc/html/render/context.rs | 1 | ||||
| -rw-r--r-- | src/librustdoc/html/render/mod.rs | 91 | ||||
| -rw-r--r-- | src/librustdoc/html/sources.rs | 14 | ||||
| -rw-r--r-- | src/librustdoc/html/static/css/rustdoc.css | 2 | ||||
| -rw-r--r-- | src/librustdoc/lib.rs | 18 | ||||
| -rw-r--r-- | src/librustdoc/scrape_examples.rs | 134 |
7 files changed, 160 insertions, 108 deletions
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 65c38566a05..2f8bae5ded0 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -161,12 +161,9 @@ crate struct Options { /// Whether to skip capturing stdout and stderr of tests. crate nocapture: bool, - // Options for scraping call sites from examples/ directory /// Path to output file to write JSON of call sites. If this option is Some(..) then /// the compiler will scrape examples and not generate documentation. crate scrape_examples: Option<PathBuf>, - /// Path to the root of the workspace, used to generate workspace-relative file paths. - crate workspace_root: Option<PathBuf>, } impl fmt::Debug for Options { @@ -290,7 +287,6 @@ crate struct RenderOptions { /// If `true`, HTML source pages will generate links for items to their definition. crate generate_link_to_definition: bool, crate call_locations: Option<AllCallLocations>, - crate repository_url: Option<String>, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -682,9 +678,7 @@ impl Options { return Err(1); } - let repository_url = matches.opt_str("repository-url"); let scrape_examples = matches.opt_str("scrape-examples").map(PathBuf::from); - let workspace_root = matches.opt_str("workspace-root").map(PathBuf::from); let with_examples = matches.opt_strs("with-examples"); let each_call_locations = with_examples .into_iter() @@ -777,13 +771,11 @@ impl Options { emit, generate_link_to_definition, call_locations, - repository_url, }, crate_name, output_format, json_unused_externs, scrape_examples, - workspace_root, }) } diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 49bf760c29c..fd53a3d7bfb 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -351,6 +351,7 @@ impl<'tcx> Context<'tcx> { let hiline = span.hi(self.sess()).line; let lines = if loline == hiline { loline.to_string() } else { format!("{}-{}", loline, hiline) }; + Some(format!( "{root}src/{krate}/{path}#{lines}", root = Escape(&root), diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index aa65ca474de..693a9d7b8a3 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -70,7 +70,7 @@ use crate::html::format::{ }; use crate::html::markdown::{HeadingOffset, Markdown, MarkdownHtml, MarkdownSummaryLine}; use crate::html::sources; -use crate::scrape_examples::FnCallLocations; +use crate::scrape_examples::{CallData, FnCallLocations}; /// A pair of name and its optional document. crate type NameDoc = (String, Option<String>); @@ -2451,6 +2451,8 @@ fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec<String> { out } +const MAX_FULL_EXAMPLES: usize = 5; + fn render_call_locations( w: &mut Buffer, cx: &Context<'_>, @@ -2463,29 +2465,7 @@ fn render_call_locations( } }; - let filtered_locations: Vec<_> = call_locations - .iter() - .filter_map(|(file, locs)| { - // FIXME(wcrichto): file I/O should be cached - let mut contents = match fs::read_to_string(&file) { - Ok(contents) => contents, - Err(e) => { - eprintln!("Failed to read file {}", e); - return None; - } - }; - - // Remove the utf-8 BOM if any - if contents.starts_with('\u{feff}') { - contents.drain(..3); - } - - Some((file, contents, locs)) - }) - .collect(); - - let n_examples = filtered_locations.len(); - if n_examples == 0 { + if call_locations.len() == 0 { return; } @@ -2499,35 +2479,55 @@ fn render_call_locations( id ); - let write_example = |w: &mut Buffer, (file, contents, locs): (&String, String, _)| { - let ex_title = match cx.shared.repository_url.as_ref() { - Some(url) => format!( - r#"<a href="{url}/{file}" target="_blank">{file}</a>"#, - file = file, - url = url - ), - None => file.clone(), - }; + let example_url = |call_data: &CallData| -> String { + format!( + r#"<a href="{root}{url}" target="_blank">{name}</a>"#, + root = cx.root_path(), + url = call_data.url, + name = call_data.display_name + ) + }; + + let write_example = |w: &mut Buffer, (path, call_data): (&PathBuf, &CallData)| { + let mut contents = + fs::read_to_string(&path).expect(&format!("Failed to read file: {}", path.display())); + + let min_loc = + call_data.locations.iter().min_by_key(|loc| loc.enclosing_item_span.0).unwrap(); + let min_byte = min_loc.enclosing_item_span.0; + let min_line = min_loc.enclosing_item_lines.0; + let max_byte = + call_data.locations.iter().map(|loc| loc.enclosing_item_span.1).max().unwrap(); + contents = contents[min_byte..max_byte].to_string(); + + let locations = call_data + .locations + .iter() + .map(|loc| (loc.call_span.0 - min_byte, loc.call_span.1 - min_byte)) + .collect::<Vec<_>>(); + let edition = cx.shared.edition(); write!( w, r#"<div class="scraped-example" data-code="{code}" data-locs="{locations}"> - <strong>{title}</strong> - <div class="code-wrapper">"#, + <strong>{title}</strong> + <div class="code-wrapper">"#, code = contents.replace("\"", """), - locations = serde_json::to_string(&locs).unwrap(), - title = ex_title, + locations = serde_json::to_string(&locations).unwrap(), + title = example_url(call_data), ); write!(w, r#"<span class="prev">≺</span> <span class="next">≻</span>"#); write!(w, r#"<span class="expand">↕</span>"#); - sources::print_src(w, &contents, edition); + let file_span = rustc_span::DUMMY_SP; + let root_path = "".to_string(); + sources::print_src(w, &contents, edition, file_span, cx, &root_path, Some(min_line)); write!(w, "</div></div>"); }; - let mut it = filtered_locations.into_iter(); + let mut it = call_locations.into_iter().peekable(); write_example(w, it.next().unwrap()); - if n_examples > 1 { + if it.peek().is_some() { write!( w, r#"<details class="rustdoc-toggle more-examples-toggle"> @@ -2536,7 +2536,16 @@ fn render_call_locations( </summary> <div class="more-scraped-examples">"# ); - it.for_each(|ex| write_example(w, ex)); + (&mut it).take(MAX_FULL_EXAMPLES).for_each(|ex| write_example(w, ex)); + + if it.peek().is_some() { + write!(w, "Additional examples can be found in:<br /><ul>"); + it.for_each(|(_, call_data)| { + write!(w, "<li>{}</li>", example_url(call_data)); + }); + write!(w, "</ul>"); + } + write!(w, "</div></details>"); } diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index d6dead15205..6bd335a9b96 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -204,7 +204,15 @@ impl SourceCollector<'_, 'tcx> { &page, "", |buf: &mut _| { - print_src(buf, contents, self.cx.shared.edition(), file_span, &self.cx, &root_path) + print_src( + buf, + contents, + self.cx.shared.edition(), + file_span, + &self.cx, + &root_path, + None, + ) }, &self.cx.shared.style_files, ); @@ -250,6 +258,7 @@ crate fn print_src( file_span: rustc_span::Span, context: &Context<'_>, root_path: &str, + offset: Option<usize>, ) { let lines = s.lines().count(); let mut line_numbers = Buffer::empty_from(buf); @@ -260,8 +269,9 @@ crate fn print_src( tmp /= 10; } line_numbers.write_str("<pre class=\"line-numbers\">"); + let offset = offset.unwrap_or(0); for i in 1..=lines { - writeln!(line_numbers, "<span id=\"{0}\">{0:1$}</span>", i, cols); + writeln!(line_numbers, "<span id=\"{0}\">{0:1$}</span>", i + offset, cols); } line_numbers.write_str("</pre>"); highlight::render_with_highlighting( diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 557a1d11948..89a205be023 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -453,7 +453,7 @@ nav.sub { text-decoration: underline; } -.rustdoc:not(.source) .example-wrap > pre:not(.line-number) { +.rustdoc:not(.source) .example-wrap > pre:not(.line-numbers) { width: 100%; overflow-x: auto; } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 9bcdbc406a6..b7407ee409f 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -620,8 +620,6 @@ fn opts() -> Vec<RustcOptGroup> { ) }), unstable("scrape-examples", |o| o.optopt("", "scrape-examples", "", "")), - unstable("workspace-root", |o| o.optopt("", "workspace-root", "", "")), - unstable("repository-url", |o| o.optopt("", "repository-url", "", "")), unstable("with-examples", |o| o.optmulti("", "with-examples", "", "")), ] } @@ -705,17 +703,16 @@ fn run_renderer<'tcx, T: formats::FormatRenderer<'tcx>>( fn main_options(options: config::Options) -> MainResult { let diag = core::new_handler(options.error_format, None, &options.debugging_opts); - match (options.should_test, options.markdown_input(), options.scrape_examples.is_some()) { - (_, _, true) => return scrape_examples::run(options), - (true, true, false) => return wrap_return(&diag, markdown::test(options)), - (true, false, false) => return doctest::run(options), - (false, true, false) => { + match (options.should_test, options.markdown_input()) { + (true, true) => return wrap_return(&diag, markdown::test(options)), + (true, false) => return doctest::run(options), + (false, true) => { return wrap_return( &diag, markdown::render(&options.input, options.render_options, options.edition), ); } - (false, false, false) => {} + (false, false) => {} } // need to move these items separately because we lose them by the time the closure is called, @@ -737,6 +734,7 @@ fn main_options(options: config::Options) -> MainResult { // FIXME: fix this clone (especially render_options) let manual_passes = options.manual_passes.clone(); let render_options = options.render_options.clone(); + let scrape_examples = options.scrape_examples.clone(); let config = core::create_config(options); interface::create_compiler_and_run(config, |compiler| { @@ -773,6 +771,10 @@ fn main_options(options: config::Options) -> MainResult { }); info!("finished with rustc"); + if let Some(example_path) = scrape_examples { + return scrape_examples::run(krate, render_opts, cache, tcx, example_path); + } + cache.crate_version = crate_version; if show_coverage { diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index 67d80d01be7..950af8fbb63 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -1,8 +1,12 @@ //! This module analyzes provided crates to find examples of uses for items in the //! current crate being documented. -use crate::config::Options; -use crate::doctest::make_rustc_config; +use crate::clean; +use crate::config; +use crate::formats; +use crate::formats::renderer::FormatRenderer; +use crate::html::render::Context; + use rustc_data_structures::fx::FxHashMap; use rustc_hir::{ self as hir, @@ -11,23 +15,33 @@ use rustc_hir::{ use rustc_interface::interface; use rustc_middle::hir::map::Map; use rustc_middle::ty::{self, TyCtxt}; -use rustc_span::def_id::DefId; +use rustc_span::{def_id::DefId, FileName}; +use serde::{Deserialize, Serialize}; use std::fs; +use std::path::PathBuf; + +#[derive(Serialize, Deserialize, Debug, Clone)] +crate struct CallLocation { + crate call_span: (usize, usize), + crate enclosing_item_span: (usize, usize), + crate enclosing_item_lines: (usize, usize), +} +#[derive(Serialize, Deserialize, Debug, Clone)] +crate struct CallData { + crate locations: Vec<CallLocation>, + crate url: String, + crate display_name: String, +} crate type DefIdCallKey = String; -crate type FnCallLocations = FxHashMap<String, Vec<(usize, usize)>>; +crate type FnCallLocations = FxHashMap<PathBuf, CallData>; crate type AllCallLocations = FxHashMap<DefIdCallKey, FnCallLocations>; /// Visitor for traversing a crate and finding instances of function calls. struct FindCalls<'a, 'tcx> { tcx: TyCtxt<'tcx>, map: Map<'tcx>, - - /// Workspace-relative path to the root of the crate. Used to remember - /// which example a particular call came from. - file_path: String, - - /// Data structure to accumulate call sites across all examples. + cx: Context<'tcx>, calls: &'a mut AllCallLocations, } @@ -68,51 +82,75 @@ where } }; + if span.from_expansion() { + return; + } + // Save call site if the function resolves to a concrete definition if let ty::FnDef(def_id, _) = ty.kind() { - let key = def_id_call_key(self.tcx, *def_id); - let entries = self.calls.entry(key).or_insert_with(FxHashMap::default); - entries - .entry(self.file_path.clone()) - .or_insert_with(Vec::new) - .push((span.lo().0 as usize, span.hi().0 as usize)); + let fn_key = def_id_call_key(self.tcx, *def_id); + let entries = self.calls.entry(fn_key).or_insert_with(FxHashMap::default); + let file = self.tcx.sess.source_map().lookup_char_pos(span.lo()).file; + let file_path = match file.name.clone() { + FileName::Real(real_filename) => real_filename.into_local_path(), + _ => None, + }; + + let get_pos = + |bytepos: rustc_span::BytePos| file.original_relative_byte_pos(bytepos).0 as usize; + let get_range = |span: rustc_span::Span| (get_pos(span.lo()), get_pos(span.hi())); + let get_line = |bytepos: rustc_span::BytePos| file.lookup_line(bytepos).unwrap(); + let get_lines = |span: rustc_span::Span| (get_line(span.lo()), get_line(span.hi())); + + if let Some(file_path) = file_path { + let abs_path = fs::canonicalize(file_path.clone()).unwrap(); + let cx = &self.cx; + let enclosing_item_span = + self.tcx.hir().span_with_body(self.tcx.hir().get_parent_item(ex.hir_id)); + assert!(enclosing_item_span.contains(span)); + + let location = CallLocation { + call_span: get_range(span), + enclosing_item_span: get_range(enclosing_item_span), + enclosing_item_lines: get_lines(enclosing_item_span), + }; + + entries + .entry(abs_path) + .or_insert_with(|| { + let clean_span = crate::clean::types::Span::new(span); + let url = cx.href_from_span(clean_span).unwrap(); + let display_name = file_path.display().to_string(); + CallData { locations: Vec::new(), url, display_name } + }) + .locations + .push(location); + } } } } -crate fn run(options: Options) -> interface::Result<()> { +crate fn run( + krate: clean::Crate, + renderopts: config::RenderOptions, + cache: formats::cache::Cache, + tcx: TyCtxt<'tcx>, + example_path: PathBuf, +) -> interface::Result<()> { let inner = move || { - let config = make_rustc_config(&options); - - // Get input file path as relative to workspace root - let file_path = options - .input - .strip_prefix(options.workspace_root.as_ref().unwrap()) - .map_err(|e| format!("{}", e))?; - - interface::run_compiler(config, |compiler| { - compiler.enter(|queries| { - let mut global_ctxt = queries.global_ctxt().unwrap().take(); - global_ctxt.enter(|tcx| { - // Run call-finder on all items - let mut calls = FxHashMap::default(); - let mut finder = FindCalls { - calls: &mut calls, - tcx, - map: tcx.hir(), - file_path: file_path.display().to_string(), - }; - tcx.hir().krate().visit_all_item_likes(&mut finder.as_deep_visitor()); - - // Save output JSON to provided path - let calls_json = serde_json::to_string(&calls).map_err(|e| format!("{}", e))?; - fs::write(options.scrape_examples.as_ref().unwrap(), &calls_json) - .map_err(|e| format!("{}", e))?; - - Ok(()) - }) - }) - }) + // Generates source files for examples + let (cx, _) = Context::init(krate, renderopts, cache, tcx).map_err(|e| format!("{}", e))?; + + // Run call-finder on all items + let mut calls = FxHashMap::default(); + let mut finder = FindCalls { calls: &mut calls, tcx, map: tcx.hir(), cx }; + tcx.hir().krate().visit_all_item_likes(&mut finder.as_deep_visitor()); + + // Save output JSON to provided path + let calls_json = serde_json::to_string(&calls).map_err(|e| format!("{}", e))?; + fs::write(example_path, &calls_json).map_err(|e| format!("{}", e))?; + + Ok(()) }; inner().map_err(|e: String| { |
