Skip to content

Commit b700835

Browse files
authored
Rollup merge of #73807 - euclio:rustdoc-highlighting, r=ollie27,GuillaumeGomez
rustdoc: glue tokens before highlighting Fixes #72684. This commit also modifies the signature of `Classifier::new` to avoid copying the source being highlighted.
2 parents 6309d9f + c3ee75d commit b700835

File tree

5 files changed

+133
-22
lines changed

5 files changed

+133
-22
lines changed

src/librustdoc/html/highlight.rs

+37-14
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@ use std::io;
1212
use std::io::prelude::*;
1313

1414
use rustc_ast::token::{self, Token};
15+
use rustc_data_structures::sync::Lrc;
1516
use rustc_parse::lexer;
1617
use rustc_session::parse::ParseSess;
18+
use rustc_span::hygiene::SyntaxContext;
1719
use rustc_span::source_map::SourceMap;
1820
use rustc_span::symbol::{kw, sym};
19-
use rustc_span::{FileName, Span};
21+
use rustc_span::{BytePos, FileName, SourceFile, Span};
2022

2123
/// Highlights `src`, returning the HTML output.
2224
pub fn render_with_highlighting(
23-
src: &str,
25+
src: String,
2426
class: Option<&str>,
2527
playground_button: Option<&str>,
2628
tooltip: Option<(&str, &str)>,
@@ -38,12 +40,13 @@ pub fn render_with_highlighting(
3840
}
3941

4042
let sess = ParseSess::with_silent_emitter();
41-
let sf = sess
43+
let source_file = sess
4244
.source_map()
43-
.new_source_file(FileName::Custom(String::from("rustdoc-highlighting")), src.to_owned());
45+
.new_source_file(FileName::Custom(String::from("rustdoc-highlighting")), src);
46+
47+
let classifier_source_file = Lrc::clone(&source_file);
4448
let highlight_result = rustc_driver::catch_fatal_errors(|| {
45-
let lexer = lexer::StringReader::new(&sess, sf, None);
46-
let mut classifier = Classifier::new(lexer, sess.source_map());
49+
let mut classifier = Classifier::new(&sess, classifier_source_file);
4750

4851
let mut highlighted_source = vec![];
4952
if classifier.write_source(&mut highlighted_source).is_err() {
@@ -61,9 +64,17 @@ pub fn render_with_highlighting(
6164
write_footer(&mut out, playground_button).unwrap();
6265
}
6366
Err(()) => {
67+
// Get the source back out of the source map to avoid a copy in the happy path.
68+
let span =
69+
Span::new(BytePos(0), BytePos(source_file.byte_length()), SyntaxContext::root());
70+
let src = sess
71+
.source_map()
72+
.span_to_snippet(span)
73+
.expect("could not retrieve snippet from artificial source file");
74+
6475
// If errors are encountered while trying to highlight, just emit
6576
// the unhighlighted source.
66-
write!(out, "<pre><code>{}</code></pre>", Escape(src)).unwrap();
77+
write!(out, "<pre><code>{}</code></pre>", Escape(&src)).unwrap();
6778
}
6879
}
6980

@@ -73,10 +84,10 @@ pub fn render_with_highlighting(
7384
/// Processes a program (nested in the internal `lexer`), classifying strings of
7485
/// text by highlighting category (`Class`). Calls out to a `Writer` to write
7586
/// each span of text in sequence.
76-
struct Classifier<'a> {
77-
lexer: lexer::StringReader<'a>,
87+
struct Classifier<'sess> {
88+
lexer: lexer::StringReader<'sess>,
7889
peek_token: Option<Token>,
79-
source_map: &'a SourceMap,
90+
source_map: &'sess SourceMap,
8091

8192
// State of the classifier.
8293
in_attribute: bool,
@@ -154,6 +165,7 @@ impl<U: Write> Writer for U {
154165
}
155166
}
156167

168+
#[derive(Debug)]
157169
enum HighlightError {
158170
LexError,
159171
IoError(io::Error),
@@ -165,12 +177,14 @@ impl From<io::Error> for HighlightError {
165177
}
166178
}
167179

168-
impl<'a> Classifier<'a> {
169-
fn new(lexer: lexer::StringReader<'a>, source_map: &'a SourceMap) -> Classifier<'a> {
180+
impl<'sess> Classifier<'sess> {
181+
fn new(sess: &ParseSess, source_file: Lrc<SourceFile>) -> Classifier<'_> {
182+
let lexer = lexer::StringReader::new(sess, source_file, None);
183+
170184
Classifier {
171185
lexer,
172186
peek_token: None,
173-
source_map,
187+
source_map: sess.source_map(),
174188
in_attribute: false,
175189
in_macro: false,
176190
in_macro_nonterminal: false,
@@ -209,11 +223,17 @@ impl<'a> Classifier<'a> {
209223
/// source.
210224
fn write_source<W: Writer>(&mut self, out: &mut W) -> Result<(), HighlightError> {
211225
loop {
212-
let next = self.try_next_token()?;
226+
let mut next = self.try_next_token()?;
213227
if next == token::Eof {
214228
break;
215229
}
216230

231+
// Glue any tokens that need to be glued.
232+
if let Some(joint) = next.glue(self.peek()?) {
233+
next = joint;
234+
let _ = self.try_next_token()?;
235+
}
236+
217237
self.write_token(out, next)?;
218238
}
219239

@@ -429,3 +449,6 @@ fn write_header(class: Option<&str>, out: &mut dyn Write) -> io::Result<()> {
429449
fn write_footer(out: &mut dyn Write, playground_button: Option<&str>) -> io::Result<()> {
430450
write!(out, "</pre>{}</div>\n", if let Some(button) = playground_button { button } else { "" })
431451
}
452+
453+
#[cfg(test)]
454+
mod tests;
+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use rustc_ast::attr::with_session_globals;
2+
use rustc_session::parse::ParseSess;
3+
use rustc_span::edition::Edition;
4+
use rustc_span::FileName;
5+
6+
use super::Classifier;
7+
8+
fn highlight(src: &str) -> String {
9+
let mut out = vec![];
10+
11+
with_session_globals(Edition::Edition2018, || {
12+
let sess = ParseSess::with_silent_emitter();
13+
let source_file = sess.source_map().new_source_file(
14+
FileName::Custom(String::from("rustdoc-highlighting")),
15+
src.to_owned(),
16+
);
17+
18+
let mut classifier = Classifier::new(&sess, source_file);
19+
classifier.write_source(&mut out).unwrap();
20+
});
21+
22+
String::from_utf8(out).unwrap()
23+
}
24+
25+
#[test]
26+
fn function() {
27+
assert_eq!(
28+
highlight("fn main() {}"),
29+
r#"<span class="kw">fn</span> <span class="ident">main</span>() {}"#,
30+
);
31+
}
32+
33+
#[test]
34+
fn statement() {
35+
assert_eq!(
36+
highlight("let foo = true;"),
37+
concat!(
38+
r#"<span class="kw">let</span> <span class="ident">foo</span> "#,
39+
r#"<span class="op">=</span> <span class="bool-val">true</span>;"#,
40+
),
41+
);
42+
}
43+
44+
#[test]
45+
fn inner_attr() {
46+
assert_eq!(
47+
highlight(r##"#![crate_type = "lib"]"##),
48+
concat!(
49+
r##"<span class="attribute">#![<span class="ident">crate_type</span> "##,
50+
r##"<span class="op">=</span> <span class="string">&quot;lib&quot;</span>]</span>"##,
51+
),
52+
);
53+
}
54+
55+
#[test]
56+
fn outer_attr() {
57+
assert_eq!(
58+
highlight(r##"#[cfg(target_os = "linux")]"##),
59+
concat!(
60+
r##"<span class="attribute">#[<span class="ident">cfg</span>("##,
61+
r##"<span class="ident">target_os</span> <span class="op">=</span> "##,
62+
r##"<span class="string">&quot;linux&quot;</span>)]</span>"##,
63+
),
64+
);
65+
}
66+
67+
#[test]
68+
fn mac() {
69+
assert_eq!(
70+
highlight("mac!(foo bar)"),
71+
concat!(
72+
r#"<span class="macro">mac</span><span class="macro">!</span>("#,
73+
r#"<span class="ident">foo</span> <span class="ident">bar</span>)"#,
74+
),
75+
);
76+
}
77+
78+
// Regression test for #72684
79+
#[test]
80+
fn andand() {
81+
assert_eq!(highlight("&&"), r#"<span class="op">&amp;&amp;</span>"#);
82+
}

src/librustdoc/html/markdown.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
292292

293293
if let Some((s1, s2)) = tooltip {
294294
s.push_str(&highlight::render_with_highlighting(
295-
&text,
295+
text,
296296
Some(&format!(
297297
"rust-example-rendered{}",
298298
if ignore != Ignore::None {
@@ -313,7 +313,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
313313
Some(Event::Html(s.into()))
314314
} else {
315315
s.push_str(&highlight::render_with_highlighting(
316-
&text,
316+
text,
317317
Some(&format!(
318318
"rust-example-rendered{}",
319319
if ignore != Ignore::None {

src/librustdoc/html/render.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -4526,7 +4526,12 @@ fn sidebar_foreign_type(buf: &mut Buffer, it: &clean::Item) {
45264526

45274527
fn item_macro(w: &mut Buffer, cx: &Context, it: &clean::Item, t: &clean::Macro) {
45284528
wrap_into_docblock(w, |w| {
4529-
w.write_str(&highlight::render_with_highlighting(&t.source, Some("macro"), None, None))
4529+
w.write_str(&highlight::render_with_highlighting(
4530+
t.source.clone(),
4531+
Some("macro"),
4532+
None,
4533+
None,
4534+
))
45304535
});
45314536
document(w, cx, it)
45324537
}

src/librustdoc/html/sources.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,17 @@ impl<'a> SourceCollector<'a> {
7575
return Ok(());
7676
}
7777

78-
let contents = match fs::read_to_string(&p) {
78+
let mut contents = match fs::read_to_string(&p) {
7979
Ok(contents) => contents,
8080
Err(e) => {
8181
return Err(Error::new(e, &p));
8282
}
8383
};
8484

8585
// Remove the utf-8 BOM if any
86-
let contents =
87-
if contents.starts_with("\u{feff}") { &contents[3..] } else { &contents[..] };
86+
if contents.starts_with("\u{feff}") {
87+
contents.drain(..3);
88+
}
8889

8990
// Create the intermediate directories
9091
let mut cur = self.dst.clone();
@@ -122,7 +123,7 @@ impl<'a> SourceCollector<'a> {
122123
&self.scx.layout,
123124
&page,
124125
"",
125-
|buf: &mut _| print_src(buf, &contents),
126+
|buf: &mut _| print_src(buf, contents),
126127
&self.scx.style_files,
127128
);
128129
self.scx.fs.write(&cur, v.as_bytes())?;
@@ -160,7 +161,7 @@ where
160161

161162
/// Wrapper struct to render the source code of a file. This will do things like
162163
/// adding line numbers to the left-hand side.
163-
fn print_src(buf: &mut Buffer, s: &str) {
164+
fn print_src(buf: &mut Buffer, s: String) {
164165
let lines = s.lines().count();
165166
let mut cols = 0;
166167
let mut tmp = lines;

0 commit comments

Comments
 (0)