Skip to content

Commit dcf9242

Browse files
authored
Rollup merge of #85833 - willcrichton:example-analyzer, r=jyn514
Scrape code examples from examples/ directory for Rustdoc Adds support for the functionality described in rust-lang/rfcs#3123 Matching changes to Cargo are here: rust-lang/cargo#9525 Live demo here: https://willcrichton.net/example-analyzer/warp/trait.Filter.html#method.and
2 parents 55ccbd0 + fd5d614 commit dcf9242

38 files changed

+1104
-30
lines changed

src/doc/rustdoc/src/unstable-features.md

+24
Original file line numberDiff line numberDiff line change
@@ -455,3 +455,27 @@ Calculating code examples follows these rules:
455455
* static
456456
* typedef
457457
2. If one of the previously listed items has a code example, then it'll be counted.
458+
459+
### `--with-examples`: include examples of uses of items as documentation
460+
461+
This option, combined with `--scrape-examples-target-crate` and
462+
`--scrape-examples-output-path`, is used to implement the functionality in [RFC
463+
#3123](https://github.com/rust-lang/rfcs/pull/3123). Uses of an item (currently
464+
functions / call-sites) are found in a crate and its reverse-dependencies, and
465+
then the uses are included as documentation for that item. This feature is
466+
intended to be used via `cargo doc --scrape-examples`, but the rustdoc-only
467+
workflow looks like:
468+
469+
```bash
470+
$ rustdoc examples/ex.rs -Z unstable-options \
471+
--extern foobar=target/deps/libfoobar.rmeta \
472+
--scrape-examples-target-crate foobar \
473+
--scrape-examples-output-path output.calls
474+
$ rustdoc src/lib.rs -Z unstable-options --with-examples output.calls
475+
```
476+
477+
First, the library must be checked to generate an `rmeta`. Then a
478+
reverse-dependency like `examples/ex.rs` is given to rustdoc with the target
479+
crate being documented (`foobar`) and a path to output the calls
480+
(`output.calls`). Then, the generated calls file can be passed via
481+
`--with-examples` to the subsequent documentation of `foobar`.

src/librustdoc/clean/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -2062,7 +2062,8 @@ fn clean_use_statement(
20622062
impl Clean<Item> for (&hir::ForeignItem<'_>, Option<Symbol>) {
20632063
fn clean(&self, cx: &mut DocContext<'_>) -> Item {
20642064
let (item, renamed) = self;
2065-
cx.with_param_env(item.def_id.to_def_id(), |cx| {
2065+
let def_id = item.def_id.to_def_id();
2066+
cx.with_param_env(def_id, |cx| {
20662067
let kind = match item.kind {
20672068
hir::ForeignItemKind::Fn(ref decl, ref names, ref generics) => {
20682069
let abi = cx.tcx.hir().get_foreign_abi(item.hir_id());

src/librustdoc/config.rs

+13
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use crate::html::render::StylePath;
2525
use crate::html::static_files;
2626
use crate::opts;
2727
use crate::passes::{self, Condition, DefaultPassOption};
28+
use crate::scrape_examples::{AllCallLocations, ScrapeExamplesOptions};
2829
use crate::theme;
2930

3031
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
@@ -158,6 +159,10 @@ crate struct Options {
158159
crate json_unused_externs: bool,
159160
/// Whether to skip capturing stdout and stderr of tests.
160161
crate nocapture: bool,
162+
163+
/// Configuration for scraping examples from the current crate. If this option is Some(..) then
164+
/// the compiler will scrape examples and not generate documentation.
165+
crate scrape_examples_options: Option<ScrapeExamplesOptions>,
161166
}
162167

163168
impl fmt::Debug for Options {
@@ -202,6 +207,7 @@ impl fmt::Debug for Options {
202207
.field("run_check", &self.run_check)
203208
.field("no_run", &self.no_run)
204209
.field("nocapture", &self.nocapture)
210+
.field("scrape_examples_options", &self.scrape_examples_options)
205211
.finish()
206212
}
207213
}
@@ -280,6 +286,7 @@ crate struct RenderOptions {
280286
crate emit: Vec<EmitType>,
281287
/// If `true`, HTML source pages will generate links for items to their definition.
282288
crate generate_link_to_definition: bool,
289+
crate call_locations: AllCallLocations,
283290
}
284291

285292
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -671,6 +678,10 @@ impl Options {
671678
return Err(1);
672679
}
673680

681+
let scrape_examples_options = ScrapeExamplesOptions::new(&matches, &diag)?;
682+
let with_examples = matches.opt_strs("with-examples");
683+
let call_locations = crate::scrape_examples::load_call_locations(with_examples, &diag)?;
684+
674685
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format);
675686

676687
Ok(Options {
@@ -737,10 +748,12 @@ impl Options {
737748
),
738749
emit,
739750
generate_link_to_definition,
751+
call_locations,
740752
},
741753
crate_name,
742754
output_format,
743755
json_unused_externs,
756+
scrape_examples_options,
744757
})
745758
}
746759

src/librustdoc/html/highlight.rs

+64-11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::html::render::Context;
1212
use std::collections::VecDeque;
1313
use std::fmt::{Display, Write};
1414

15+
use rustc_data_structures::fx::FxHashMap;
1516
use rustc_lexer::{LiteralKind, TokenKind};
1617
use rustc_span::edition::Edition;
1718
use rustc_span::symbol::Symbol;
@@ -30,6 +31,10 @@ crate struct ContextInfo<'a, 'b, 'c> {
3031
crate root_path: &'c str,
3132
}
3233

34+
/// Decorations are represented as a map from CSS class to vector of character ranges.
35+
/// Each range will be wrapped in a span with that class.
36+
crate struct DecorationInfo(crate FxHashMap<&'static str, Vec<(u32, u32)>>);
37+
3338
/// Highlights `src`, returning the HTML output.
3439
crate fn render_with_highlighting(
3540
src: &str,
@@ -40,6 +45,7 @@ crate fn render_with_highlighting(
4045
edition: Edition,
4146
extra_content: Option<Buffer>,
4247
context_info: Option<ContextInfo<'_, '_, '_>>,
48+
decoration_info: Option<DecorationInfo>,
4349
) {
4450
debug!("highlighting: ================\n{}\n==============", src);
4551
if let Some((edition_info, class)) = tooltip {
@@ -56,7 +62,7 @@ crate fn render_with_highlighting(
5662
}
5763

5864
write_header(out, class, extra_content);
59-
write_code(out, &src, edition, context_info);
65+
write_code(out, &src, edition, context_info, decoration_info);
6066
write_footer(out, playground_button);
6167
}
6268

@@ -89,17 +95,23 @@ fn write_code(
8995
src: &str,
9096
edition: Edition,
9197
context_info: Option<ContextInfo<'_, '_, '_>>,
98+
decoration_info: Option<DecorationInfo>,
9299
) {
93100
// This replace allows to fix how the code source with DOS backline characters is displayed.
94101
let src = src.replace("\r\n", "\n");
95-
Classifier::new(&src, edition, context_info.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP))
96-
.highlight(&mut |highlight| {
97-
match highlight {
98-
Highlight::Token { text, class } => string(out, Escape(text), class, &context_info),
99-
Highlight::EnterSpan { class } => enter_span(out, class),
100-
Highlight::ExitSpan => exit_span(out),
101-
};
102-
});
102+
Classifier::new(
103+
&src,
104+
edition,
105+
context_info.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
106+
decoration_info,
107+
)
108+
.highlight(&mut |highlight| {
109+
match highlight {
110+
Highlight::Token { text, class } => string(out, Escape(text), class, &context_info),
111+
Highlight::EnterSpan { class } => enter_span(out, class),
112+
Highlight::ExitSpan => exit_span(out),
113+
};
114+
});
103115
}
104116

105117
fn write_footer(out: &mut Buffer, playground_button: Option<&str>) {
@@ -127,6 +139,7 @@ enum Class {
127139
PreludeTy,
128140
PreludeVal,
129141
QuestionMark,
142+
Decoration(&'static str),
130143
}
131144

132145
impl Class {
@@ -150,6 +163,7 @@ impl Class {
150163
Class::PreludeTy => "prelude-ty",
151164
Class::PreludeVal => "prelude-val",
152165
Class::QuestionMark => "question-mark",
166+
Class::Decoration(kind) => kind,
153167
}
154168
}
155169

@@ -248,6 +262,24 @@ impl Iterator for PeekIter<'a> {
248262
}
249263
}
250264

265+
/// Custom spans inserted into the source. Eg --scrape-examples uses this to highlight function calls
266+
struct Decorations {
267+
starts: Vec<(u32, &'static str)>,
268+
ends: Vec<u32>,
269+
}
270+
271+
impl Decorations {
272+
fn new(info: DecorationInfo) -> Self {
273+
let (starts, ends) = info
274+
.0
275+
.into_iter()
276+
.map(|(kind, ranges)| ranges.into_iter().map(move |(lo, hi)| ((lo, kind), hi)))
277+
.flatten()
278+
.unzip();
279+
Decorations { starts, ends }
280+
}
281+
}
282+
251283
/// Processes program tokens, classifying strings of text by highlighting
252284
/// category (`Class`).
253285
struct Classifier<'a> {
@@ -259,13 +291,20 @@ struct Classifier<'a> {
259291
byte_pos: u32,
260292
file_span: Span,
261293
src: &'a str,
294+
decorations: Option<Decorations>,
262295
}
263296

264297
impl<'a> Classifier<'a> {
265298
/// Takes as argument the source code to HTML-ify, the rust edition to use and the source code
266299
/// file span which will be used later on by the `span_correspondance_map`.
267-
fn new(src: &str, edition: Edition, file_span: Span) -> Classifier<'_> {
300+
fn new(
301+
src: &str,
302+
edition: Edition,
303+
file_span: Span,
304+
decoration_info: Option<DecorationInfo>,
305+
) -> Classifier<'_> {
268306
let tokens = PeekIter::new(TokenIter { src });
307+
let decorations = decoration_info.map(Decorations::new);
269308
Classifier {
270309
tokens,
271310
in_attribute: false,
@@ -275,6 +314,7 @@ impl<'a> Classifier<'a> {
275314
byte_pos: 0,
276315
file_span,
277316
src,
317+
decorations,
278318
}
279319
}
280320

@@ -356,6 +396,19 @@ impl<'a> Classifier<'a> {
356396
/// token is used.
357397
fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'a>)) {
358398
loop {
399+
if let Some(decs) = self.decorations.as_mut() {
400+
let byte_pos = self.byte_pos;
401+
let n_starts = decs.starts.iter().filter(|(i, _)| byte_pos >= *i).count();
402+
for (_, kind) in decs.starts.drain(0..n_starts) {
403+
sink(Highlight::EnterSpan { class: Class::Decoration(kind) });
404+
}
405+
406+
let n_ends = decs.ends.iter().filter(|i| byte_pos >= **i).count();
407+
for _ in decs.ends.drain(0..n_ends) {
408+
sink(Highlight::ExitSpan);
409+
}
410+
}
411+
359412
if self
360413
.tokens
361414
.peek()
@@ -657,7 +710,7 @@ fn string<T: Display>(
657710
// https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
658711
match href {
659712
LinkFromSrc::Local(span) => context
660-
.href_from_span(*span)
713+
.href_from_span(*span, true)
661714
.map(|s| format!("{}{}", context_info.root_path, s)),
662715
LinkFromSrc::External(def_id) => {
663716
format::href_with_root_path(*def_id, context, Some(context_info.root_path))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<span class="example"><span class="kw">let</span> <span class="ident">x</span> <span class="op">=</span> <span class="number">1</span>;</span>
2+
<span class="kw">let</span> <span class="ident">y</span> <span class="op">=</span> <span class="number">2</span>;

src/librustdoc/html/highlight/tests.rs

+20-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
use super::write_code;
1+
use super::{write_code, DecorationInfo};
22
use crate::html::format::Buffer;
33
use expect_test::expect_file;
4+
use rustc_data_structures::fx::FxHashMap;
45
use rustc_span::create_default_session_globals_then;
56
use rustc_span::edition::Edition;
67

@@ -22,7 +23,7 @@ fn test_html_highlighting() {
2223
let src = include_str!("fixtures/sample.rs");
2324
let html = {
2425
let mut out = Buffer::new();
25-
write_code(&mut out, src, Edition::Edition2018, None);
26+
write_code(&mut out, src, Edition::Edition2018, None, None);
2627
format!("{}<pre><code>{}</code></pre>\n", STYLE, out.into_inner())
2728
};
2829
expect_file!["fixtures/sample.html"].assert_eq(&html);
@@ -36,7 +37,7 @@ fn test_dos_backline() {
3637
println!(\"foo\");\r\n\
3738
}\r\n";
3839
let mut html = Buffer::new();
39-
write_code(&mut html, src, Edition::Edition2018, None);
40+
write_code(&mut html, src, Edition::Edition2018, None, None);
4041
expect_file!["fixtures/dos_line.html"].assert_eq(&html.into_inner());
4142
});
4243
}
@@ -50,7 +51,7 @@ let x = super::b::foo;
5051
let y = Self::whatever;";
5152

5253
let mut html = Buffer::new();
53-
write_code(&mut html, src, Edition::Edition2018, None);
54+
write_code(&mut html, src, Edition::Edition2018, None, None);
5455
expect_file!["fixtures/highlight.html"].assert_eq(&html.into_inner());
5556
});
5657
}
@@ -60,7 +61,21 @@ fn test_union_highlighting() {
6061
create_default_session_globals_then(|| {
6162
let src = include_str!("fixtures/union.rs");
6263
let mut html = Buffer::new();
63-
write_code(&mut html, src, Edition::Edition2018, None);
64+
write_code(&mut html, src, Edition::Edition2018, None, None);
6465
expect_file!["fixtures/union.html"].assert_eq(&html.into_inner());
6566
});
6667
}
68+
69+
#[test]
70+
fn test_decorations() {
71+
create_default_session_globals_then(|| {
72+
let src = "let x = 1;
73+
let y = 2;";
74+
let mut decorations = FxHashMap::default();
75+
decorations.insert("example", vec![(0, 10)]);
76+
77+
let mut html = Buffer::new();
78+
write_code(&mut html, src, Edition::Edition2018, None, Some(DecorationInfo(decorations)));
79+
expect_file!["fixtures/decorations.html"].assert_eq(&html.into_inner());
80+
});
81+
}

src/librustdoc/html/layout.rs

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ crate struct Layout {
2222
/// If false, the `select` element to have search filtering by crates on rendered docs
2323
/// won't be generated.
2424
crate generate_search_filter: bool,
25+
/// If true, then scrape-examples.js will be included in the output HTML file
26+
crate scrape_examples_extension: bool,
2527
}
2628

2729
#[derive(Serialize)]

src/librustdoc/html/markdown.rs

+1
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
360360
edition,
361361
None,
362362
None,
363+
None,
363364
);
364365
Some(Event::Html(s.into_inner().into()))
365366
}

0 commit comments

Comments
 (0)