1
Fork 0

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.
This commit is contained in:
Manish Goregaokar 2020-07-16 11:18:31 -07:00 committed by GitHub
commit b700835118
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 133 additions and 22 deletions

View file

@ -12,15 +12,17 @@ use std::io;
use std::io::prelude::*; use std::io::prelude::*;
use rustc_ast::token::{self, Token}; use rustc_ast::token::{self, Token};
use rustc_data_structures::sync::Lrc;
use rustc_parse::lexer; use rustc_parse::lexer;
use rustc_session::parse::ParseSess; use rustc_session::parse::ParseSess;
use rustc_span::hygiene::SyntaxContext;
use rustc_span::source_map::SourceMap; use rustc_span::source_map::SourceMap;
use rustc_span::symbol::{kw, sym}; use rustc_span::symbol::{kw, sym};
use rustc_span::{FileName, Span}; use rustc_span::{BytePos, FileName, SourceFile, Span};
/// Highlights `src`, returning the HTML output. /// Highlights `src`, returning the HTML output.
pub fn render_with_highlighting( pub fn render_with_highlighting(
src: &str, src: String,
class: Option<&str>, class: Option<&str>,
playground_button: Option<&str>, playground_button: Option<&str>,
tooltip: Option<(&str, &str)>, tooltip: Option<(&str, &str)>,
@ -38,12 +40,13 @@ pub fn render_with_highlighting(
} }
let sess = ParseSess::with_silent_emitter(); let sess = ParseSess::with_silent_emitter();
let sf = sess let source_file = sess
.source_map() .source_map()
.new_source_file(FileName::Custom(String::from("rustdoc-highlighting")), src.to_owned()); .new_source_file(FileName::Custom(String::from("rustdoc-highlighting")), src);
let classifier_source_file = Lrc::clone(&source_file);
let highlight_result = rustc_driver::catch_fatal_errors(|| { let highlight_result = rustc_driver::catch_fatal_errors(|| {
let lexer = lexer::StringReader::new(&sess, sf, None); let mut classifier = Classifier::new(&sess, classifier_source_file);
let mut classifier = Classifier::new(lexer, sess.source_map());
let mut highlighted_source = vec![]; let mut highlighted_source = vec![];
if classifier.write_source(&mut highlighted_source).is_err() { if classifier.write_source(&mut highlighted_source).is_err() {
@ -61,9 +64,17 @@ pub fn render_with_highlighting(
write_footer(&mut out, playground_button).unwrap(); write_footer(&mut out, playground_button).unwrap();
} }
Err(()) => { Err(()) => {
// Get the source back out of the source map to avoid a copy in the happy path.
let span =
Span::new(BytePos(0), BytePos(source_file.byte_length()), SyntaxContext::root());
let src = sess
.source_map()
.span_to_snippet(span)
.expect("could not retrieve snippet from artificial source file");
// If errors are encountered while trying to highlight, just emit // If errors are encountered while trying to highlight, just emit
// the unhighlighted source. // the unhighlighted source.
write!(out, "<pre><code>{}</code></pre>", Escape(src)).unwrap(); write!(out, "<pre><code>{}</code></pre>", Escape(&src)).unwrap();
} }
} }
@ -73,10 +84,10 @@ pub fn render_with_highlighting(
/// Processes a program (nested in the internal `lexer`), classifying strings of /// Processes a program (nested in the internal `lexer`), classifying strings of
/// text by highlighting category (`Class`). Calls out to a `Writer` to write /// text by highlighting category (`Class`). Calls out to a `Writer` to write
/// each span of text in sequence. /// each span of text in sequence.
struct Classifier<'a> { struct Classifier<'sess> {
lexer: lexer::StringReader<'a>, lexer: lexer::StringReader<'sess>,
peek_token: Option<Token>, peek_token: Option<Token>,
source_map: &'a SourceMap, source_map: &'sess SourceMap,
// State of the classifier. // State of the classifier.
in_attribute: bool, in_attribute: bool,
@ -154,6 +165,7 @@ impl<U: Write> Writer for U {
} }
} }
#[derive(Debug)]
enum HighlightError { enum HighlightError {
LexError, LexError,
IoError(io::Error), IoError(io::Error),
@ -165,12 +177,14 @@ impl From<io::Error> for HighlightError {
} }
} }
impl<'a> Classifier<'a> { impl<'sess> Classifier<'sess> {
fn new(lexer: lexer::StringReader<'a>, source_map: &'a SourceMap) -> Classifier<'a> { fn new(sess: &ParseSess, source_file: Lrc<SourceFile>) -> Classifier<'_> {
let lexer = lexer::StringReader::new(sess, source_file, None);
Classifier { Classifier {
lexer, lexer,
peek_token: None, peek_token: None,
source_map, source_map: sess.source_map(),
in_attribute: false, in_attribute: false,
in_macro: false, in_macro: false,
in_macro_nonterminal: false, in_macro_nonterminal: false,
@ -209,11 +223,17 @@ impl<'a> Classifier<'a> {
/// source. /// source.
fn write_source<W: Writer>(&mut self, out: &mut W) -> Result<(), HighlightError> { fn write_source<W: Writer>(&mut self, out: &mut W) -> Result<(), HighlightError> {
loop { loop {
let next = self.try_next_token()?; let mut next = self.try_next_token()?;
if next == token::Eof { if next == token::Eof {
break; break;
} }
// Glue any tokens that need to be glued.
if let Some(joint) = next.glue(self.peek()?) {
next = joint;
let _ = self.try_next_token()?;
}
self.write_token(out, next)?; self.write_token(out, next)?;
} }
@ -429,3 +449,6 @@ fn write_header(class: Option<&str>, out: &mut dyn Write) -> io::Result<()> {
fn write_footer(out: &mut dyn Write, playground_button: Option<&str>) -> io::Result<()> { fn write_footer(out: &mut dyn Write, playground_button: Option<&str>) -> io::Result<()> {
write!(out, "</pre>{}</div>\n", if let Some(button) = playground_button { button } else { "" }) write!(out, "</pre>{}</div>\n", if let Some(button) = playground_button { button } else { "" })
} }
#[cfg(test)]
mod tests;

View file

@ -0,0 +1,82 @@
use rustc_ast::attr::with_session_globals;
use rustc_session::parse::ParseSess;
use rustc_span::edition::Edition;
use rustc_span::FileName;
use super::Classifier;
fn highlight(src: &str) -> String {
let mut out = vec![];
with_session_globals(Edition::Edition2018, || {
let sess = ParseSess::with_silent_emitter();
let source_file = sess.source_map().new_source_file(
FileName::Custom(String::from("rustdoc-highlighting")),
src.to_owned(),
);
let mut classifier = Classifier::new(&sess, source_file);
classifier.write_source(&mut out).unwrap();
});
String::from_utf8(out).unwrap()
}
#[test]
fn function() {
assert_eq!(
highlight("fn main() {}"),
r#"<span class="kw">fn</span> <span class="ident">main</span>() {}"#,
);
}
#[test]
fn statement() {
assert_eq!(
highlight("let foo = true;"),
concat!(
r#"<span class="kw">let</span> <span class="ident">foo</span> "#,
r#"<span class="op">=</span> <span class="bool-val">true</span>;"#,
),
);
}
#[test]
fn inner_attr() {
assert_eq!(
highlight(r##"#![crate_type = "lib"]"##),
concat!(
r##"<span class="attribute">#![<span class="ident">crate_type</span> "##,
r##"<span class="op">=</span> <span class="string">&quot;lib&quot;</span>]</span>"##,
),
);
}
#[test]
fn outer_attr() {
assert_eq!(
highlight(r##"#[cfg(target_os = "linux")]"##),
concat!(
r##"<span class="attribute">#[<span class="ident">cfg</span>("##,
r##"<span class="ident">target_os</span> <span class="op">=</span> "##,
r##"<span class="string">&quot;linux&quot;</span>)]</span>"##,
),
);
}
#[test]
fn mac() {
assert_eq!(
highlight("mac!(foo bar)"),
concat!(
r#"<span class="macro">mac</span><span class="macro">!</span>("#,
r#"<span class="ident">foo</span> <span class="ident">bar</span>)"#,
),
);
}
// Regression test for #72684
#[test]
fn andand() {
assert_eq!(highlight("&&"), r#"<span class="op">&amp;&amp;</span>"#);
}

View file

@ -292,7 +292,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
if let Some((s1, s2)) = tooltip { if let Some((s1, s2)) = tooltip {
s.push_str(&highlight::render_with_highlighting( s.push_str(&highlight::render_with_highlighting(
&text, text,
Some(&format!( Some(&format!(
"rust-example-rendered{}", "rust-example-rendered{}",
if ignore != Ignore::None { if ignore != Ignore::None {
@ -313,7 +313,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
Some(Event::Html(s.into())) Some(Event::Html(s.into()))
} else { } else {
s.push_str(&highlight::render_with_highlighting( s.push_str(&highlight::render_with_highlighting(
&text, text,
Some(&format!( Some(&format!(
"rust-example-rendered{}", "rust-example-rendered{}",
if ignore != Ignore::None { if ignore != Ignore::None {

View file

@ -4526,7 +4526,12 @@ fn sidebar_foreign_type(buf: &mut Buffer, it: &clean::Item) {
fn item_macro(w: &mut Buffer, cx: &Context, it: &clean::Item, t: &clean::Macro) { fn item_macro(w: &mut Buffer, cx: &Context, it: &clean::Item, t: &clean::Macro) {
wrap_into_docblock(w, |w| { wrap_into_docblock(w, |w| {
w.write_str(&highlight::render_with_highlighting(&t.source, Some("macro"), None, None)) w.write_str(&highlight::render_with_highlighting(
t.source.clone(),
Some("macro"),
None,
None,
))
}); });
document(w, cx, it) document(w, cx, it)
} }

View file

@ -75,7 +75,7 @@ impl<'a> SourceCollector<'a> {
return Ok(()); return Ok(());
} }
let contents = match fs::read_to_string(&p) { let mut contents = match fs::read_to_string(&p) {
Ok(contents) => contents, Ok(contents) => contents,
Err(e) => { Err(e) => {
return Err(Error::new(e, &p)); return Err(Error::new(e, &p));
@ -83,8 +83,9 @@ impl<'a> SourceCollector<'a> {
}; };
// Remove the utf-8 BOM if any // Remove the utf-8 BOM if any
let contents = if contents.starts_with("\u{feff}") {
if contents.starts_with("\u{feff}") { &contents[3..] } else { &contents[..] }; contents.drain(..3);
}
// Create the intermediate directories // Create the intermediate directories
let mut cur = self.dst.clone(); let mut cur = self.dst.clone();
@ -122,7 +123,7 @@ impl<'a> SourceCollector<'a> {
&self.scx.layout, &self.scx.layout,
&page, &page,
"", "",
|buf: &mut _| print_src(buf, &contents), |buf: &mut _| print_src(buf, contents),
&self.scx.style_files, &self.scx.style_files,
); );
self.scx.fs.write(&cur, v.as_bytes())?; self.scx.fs.write(&cur, v.as_bytes())?;
@ -160,7 +161,7 @@ where
/// Wrapper struct to render the source code of a file. This will do things like /// Wrapper struct to render the source code of a file. This will do things like
/// adding line numbers to the left-hand side. /// adding line numbers to the left-hand side.
fn print_src(buf: &mut Buffer, s: &str) { fn print_src(buf: &mut Buffer, s: String) {
let lines = s.lines().count(); let lines = s.lines().count();
let mut cols = 0; let mut cols = 0;
let mut tmp = lines; let mut tmp = lines;